|
//-------------------------------------------------------------
// <copyright company=’Microsoft Corporation’>
// Copyright © Microsoft Corporation. All Rights Reserved.
// </copyright>
//-------------------------------------------------------------
// @owner=alexgor, deliant
//=================================================================
// File: PieChart.cs
//
// Namespace: DataVisualization.Charting.ChartTypes
//
// Classes: PieChart
//
// Purpose: Provides 2D/3D drawing and hit testing functionality
// for the Pie chart. A pie chart shows how proportions
// of data, shown as pie-shaped pieces, contribute to
// the data as a whole.
//
// PieChart class is used as a base class for the
// DoughnutChart class.
//
// Reviewed: GS - Aug 8, 2002
// AG - Aug 8, 2002
// AG - Microsoft 6, 2007
//
//===================================================================
#region Used namespaces
using System;
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.ComponentModel.Design;
using System.Globalization;
#if Microsoft_CONTROL
using System.Windows.Forms.DataVisualization.Charting.Utilities;
#else
using System.Web.UI.DataVisualization.Charting.Utilities;
#endif
#endregion
#if Microsoft_CONTROL
namespace System.Windows.Forms.DataVisualization.Charting.ChartTypes
#else
namespace System.Web.UI.DataVisualization.Charting.ChartTypes
#endif
{
#region Enumerations
/// <summary>
/// Pie Labels style
/// </summary>
internal enum PieLabelStyle
{
/// <summary>
/// Labels are inside pie slice
/// </summary>
Inside,
/// <summary>
/// Labels are outside pie slice
/// </summary>
Outside,
/// <summary>
/// Labels are disabled
/// </summary>
Disabled,
};
#endregion
/// <summary>
/// PieChart class provides 2D/3D drawing and hit testing functionality
/// for the Pie chart.
/// </summary>
internal class PieChart : IChartType
{
#region Enumerations
/// <summary>
/// Labels Mode for preparing data
/// </summary>
enum LabelsMode
{
/// <summary>
/// There are no labels
/// </summary>
Off,
/// <summary>
/// Drawing labels mode
/// </summary>
Draw,
/// <summary>
/// Labels Estimation mode
/// </summary>
EstimateSize,
/// <summary>
/// Labels Overlap Mode
/// </summary>
LabelsOverlap
};
#endregion
#region Fields
// True if labels fit inside plot area.
private bool _labelsFit = true;
// Field that is used to resize pie
// because of labels.
private float _sizeCorrection = 0.95F;
// True if any pie slice is exploded
private bool _sliceExploded = false;
// True if labels overlap for 2D Pie and outside labels
private bool _labelsOverlap = false;
// Left Lable column used for 3D chart and outside labels
internal LabelColumn labelColumnLeft;
// Right Lable column used for 3D chart and outside labels
internal LabelColumn labelColumnRight;
// Array of label rectangles used to prevent labels overlapping
// for 2D pie chart outside labels.
private ArrayList _labelsRectangles = new ArrayList();
#endregion
#region IChartType interface implementation
/// <summary>
/// Chart type name
/// </summary>
virtual public string Name { get{ return ChartTypeNames.Pie;}}
/// <summary>
/// Gets chart type image.
/// </summary>
/// <param name="registry">Chart types registry object.</param>
/// <returns>Chart type image.</returns>
virtual public System.Drawing.Image GetImage(ChartTypeRegistry registry)
{
return (System.Drawing.Image)registry.ResourceManager.GetObject(this.Name + "ChartType");
}
/// <summary>
/// True if chart type is stacked
/// </summary>
virtual public bool Stacked { get{ return false;}}
/// <summary>
/// True if stacked chart type supports groups
/// </summary>
virtual public bool SupportStackedGroups { get { return false; } }
/// <summary>
/// True if stacked chart type should draw separately positive and
/// negative data points ( Bar and column Stacked types ).
/// </summary>
public bool StackSign { get{ return false;}}
/// <summary>
/// True if chart type supports axeses
/// </summary>
virtual public bool RequireAxes { get{ return false;} }
/// <summary>
/// Chart type with two y values used for scale ( bubble chart type )
/// </summary>
public bool SecondYScale{ get{ return false;} }
/// <summary>
/// True if chart type requires circular chart area.
/// </summary>
public bool CircularChartArea { get{ return false;} }
/// <summary>
/// True if chart type supports logarithmic axes
/// </summary>
virtual public bool SupportLogarithmicAxes { get{ return false;} }
/// <summary>
/// True if chart type requires to switch the value (Y) axes position
/// </summary>
virtual public bool SwitchValueAxes { get{ return false;} }
/// <summary>
/// True if chart series can be placed side-by-side.
/// </summary>
virtual public bool SideBySideSeries { get{ return false;} }
/// <summary>
/// If the crossing value is auto Crossing value should be
/// automatically set to zero for some chart
/// types (Bar, column, area etc.)
/// </summary>
virtual public bool ZeroCrossing { get{ return false;} }
/// <summary>
/// True if each data point of a chart must be represented in the legend
/// </summary>
virtual public bool DataPointsInLegend { get{ return true;} }
/// <summary>
/// Indicates that extra Y values are connected to the scale of the Y axis
/// </summary>
virtual public bool ExtraYValuesConnectedToYAxis{ get { return false; } }
/// <summary>
/// Indicates that it's a hundredred percent chart.
/// Axis scale from 0 to 100 percent should be used.
/// </summary>
virtual public bool HundredPercent{ get{return false;} }
/// <summary>
/// Indicates that it's a hundredred percent chart.
/// Axis scale from 0 to 100 percent should be used.
/// </summary>
virtual public bool HundredPercentSupportNegative{ get{return false;} }
/// <summary>
/// True if palette colors should be applied for each data paoint.
/// Otherwise the color is applied to the series.
/// </summary>
virtual public bool ApplyPaletteColorsToPoints { get { return true; } }
/// <summary>
/// How to draw series/points in legend:
/// Filled rectangle, Line or Marker
/// </summary>
/// <param name="series">Legend item series.</param>
/// <returns>Legend item style.</returns>
virtual public LegendImageStyle GetLegendImageStyle(Series series)
{
return LegendImageStyle.Rectangle;
}
/// <summary>
/// Number of supported Y value(s) per point
/// </summary>
virtual public int YValuesPerPoint{ get { return 1; } }
/// <summary>
/// Chart is Doughnut or Pie type
/// </summary>
virtual public bool Doughnut{ get { return false; } }
#endregion
#region Methods
/// <summary>
/// Default constructor
/// </summary>
public PieChart()
{
}
/// <summary>
/// Calculates Collected pie slice if required.
/// </summary>
/// <param name="series">Series to be prepared.</param>
internal static void PrepareData(Series series)
{
// Check series chart type
if( String.Compare(series.ChartTypeName, ChartTypeNames.Pie, StringComparison.OrdinalIgnoreCase ) != 0 &&
String.Compare(series.ChartTypeName, ChartTypeNames.Doughnut, StringComparison.OrdinalIgnoreCase ) != 0
)
{
return;
}
// Check if collected threshold value is set
double threshold = 0.0;
if (series.IsCustomPropertySet(CustomPropertyName.CollectedThreshold))
{
double t;
bool parseSucceed = double.TryParse(series[CustomPropertyName.CollectedThreshold], NumberStyles.Any, CultureInfo.InvariantCulture, out t);
if (parseSucceed)
{
threshold = t;
}
else
{
throw (new InvalidOperationException(SR.ExceptionDoughnutCollectedThresholdInvalidFormat));
}
if (threshold < 0.0)
{
throw (new InvalidOperationException(SR.ExceptionDoughnutThresholdInvalid));
}
}
// Check if threshold is set
if(threshold > 0.0)
{
// Get reference to the chart control
Chart chart = series.Chart;
if(chart == null)
{
throw (new InvalidOperationException(SR.ExceptionDoughnutNullReference));
}
// Create a temp series which will hold original series data points
Series seriesOriginalData = new Series("PIE_ORIGINAL_DATA_" + series.Name, series.YValuesPerPoint);
seriesOriginalData.Enabled = false;
seriesOriginalData.IsVisibleInLegend = false;
chart.Series.Add(seriesOriginalData);
foreach(DataPoint dp in series.Points)
{
seriesOriginalData.Points.Add(dp.Clone());
}
// Copy temporary design data attribute
if(series.IsCustomPropertySet("TempDesignData"))
{
seriesOriginalData["TempDesignData"] = "true";
}
// Calculate total value of all data points. IsEmpty points are
// ignored. Absolute value is used.
double total = 0.0;
foreach(DataPoint dp in series.Points)
{
if(!dp.IsEmpty)
{
total += Math.Abs(dp.YValues[0]);
}
}
// Check if threshold value is set in percents
bool percent = true;
if(series.IsCustomPropertySet(CustomPropertyName.CollectedThresholdUsePercent))
{
if(string.Compare(series[CustomPropertyName.CollectedThresholdUsePercent], "True", StringComparison.OrdinalIgnoreCase) == 0)
{
percent = true;
}
else if (string.Compare(series[CustomPropertyName.CollectedThresholdUsePercent], "False", StringComparison.OrdinalIgnoreCase) == 0)
{
percent = false;
}
else
{
throw (new InvalidOperationException(SR.ExceptionDoughnutCollectedThresholdUsePercentInvalid));
}
}
// Convert from percent valur to data point value
if(percent)
{
if(threshold > 100.0)
{
throw (new InvalidOperationException(SR.ExceptionDoughnutCollectedThresholdInvalidRange));
}
threshold = total * threshold / 100.0;
}
// Count how many points will be collected and remove collected points
DataPoint collectedPoint = null;
double collectedTotal = 0.0;
int collectedCount = 0;
int firstCollectedPointIndex = 0;
int originalDataPointIndex = 0;
for(int dataPointIndex = 0; dataPointIndex < series.Points.Count; dataPointIndex++)
{
DataPoint dataPoint = series.Points[dataPointIndex];
if(!dataPoint.IsEmpty && Math.Abs(dataPoint.YValues[0]) <= threshold)
{
// Keep statistics
++collectedCount;
collectedTotal += Math.Abs(dataPoint.YValues[0]);
// Make a template for the collected point using the first removed point
if(collectedPoint == null)
{
firstCollectedPointIndex = dataPointIndex;
collectedPoint = dataPoint.Clone();
}
// Remove first collected point only when second collected point found
if(collectedCount == 2)
{
series.Points.RemoveAt(firstCollectedPointIndex);
--dataPointIndex;
}
// Remove collected point
if(collectedCount > 1)
{
series.Points.RemoveAt(dataPointIndex);
--dataPointIndex;
}
}
// Set point index that will be used for tooltips
dataPoint["OriginalPointIndex"] = originalDataPointIndex.ToString(CultureInfo.InvariantCulture);
++originalDataPointIndex;
}
// Add collected data point into the series
if(collectedCount > 1 && collectedPoint != null)
{
collectedPoint["_COLLECTED_DATA_POINT"] = "TRUE";
collectedPoint.YValues[0] = collectedTotal;
series.Points.Add(collectedPoint);
// Set collected point color
if(series.IsCustomPropertySet(CustomPropertyName.CollectedColor))
{
ColorConverter colorConverter = new ColorConverter();
try
{
collectedPoint.Color = (Color)colorConverter.ConvertFromString(null, CultureInfo.InvariantCulture, series[CustomPropertyName.CollectedColor]);
}
catch
{
throw (new InvalidOperationException(SR.ExceptionDoughnutCollectedColorInvalidFormat));
}
}
// Set collected point exploded attribute
if(series.IsCustomPropertySet(CustomPropertyName.CollectedSliceExploded))
{
collectedPoint[CustomPropertyName.Exploded] = series[CustomPropertyName.CollectedSliceExploded];
}
// Set collected point tooltip
if(series.IsCustomPropertySet(CustomPropertyName.CollectedToolTip))
{
collectedPoint.ToolTip = series[CustomPropertyName.CollectedToolTip];
}
// Set collected point legend text
if(series.IsCustomPropertySet(CustomPropertyName.CollectedLegendText))
{
collectedPoint.LegendText = series[CustomPropertyName.CollectedLegendText];
}
else
{
collectedPoint.LegendText = SR.DescriptionCustomAttributeCollectedLegendDefaultText;
}
// Set collected point label
if(series.IsCustomPropertySet(CustomPropertyName.CollectedLabel))
{
collectedPoint.Label = series[CustomPropertyName.CollectedLabel];
}
}
}
}
/// <summary>
/// Remove any changes done while preparing Pie/Doughnut charts
/// to draw the collected slice.
/// </summary>
/// <param name="series">Series to be un-prepared.</param>
/// <returns>True if series was removed from collection.</returns>
internal static bool UnPrepareData(Series series)
{
if(series.Name.StartsWith("PIE_ORIGINAL_DATA_", StringComparison.Ordinal))
{
// Get reference to the chart control
Chart chart = series.Chart;
if(chart == null)
{
throw (new InvalidOperationException(SR.ExceptionDoughnutNullReference));
}
// Get original Renko series
Series pieSeries = chart.Series[series.Name.Substring(18)];
// Copy data back to original Pie series
pieSeries.Points.Clear();
if(!series.IsCustomPropertySet("TempDesignData"))
{
foreach(DataPoint dp in series.Points)
{
pieSeries.Points.Add(dp);
}
}
// Remove series from the collection
chart.Series.Remove(series);
return true;
}
return false;
}
/// <summary>
/// Paint Pie Chart
/// </summary>
/// <param name="graph">The Chart Graphics object</param>
/// <param name="common">The Common elements object</param>
/// <param name="area">Chart area for this chart</param>
/// <param name="seriesToDraw">Chart series to draw.</param>
public void Paint( ChartGraphics graph, CommonElements common, ChartArea area, Series seriesToDraw )
{
// Pie chart cannot be combined with other chart types
foreach( Series series in common.DataManager.Series )
{
// Check if series is visible and belong to the current chart area
if( series.IsVisible() &&
series.ChartArea == area.Name )
{
// Check if series chart type matches
if( String.Compare( series.ChartTypeName, this.Name, true, System.Globalization.CultureInfo.CurrentCulture ) != 0 )
{
if(!common.ChartPicture.SuppressExceptions)
{
// Pie/Doughnut chart can not be combined with other chart type
throw (new InvalidOperationException(SR.ExceptionChartCanNotCombine( this.Name )));
}
}
}
}
// 3D Pie Chart
if( area.Area3DStyle.Enable3D )
{
float pieWidth = 10 * area.Area3DStyle.PointDepth / 100;
// Set Clip Region
graph.SetClip(area.Position.ToRectangleF());
// Make reversed X angle because of default angle.
area.Area3DStyle.Inclination *= -1;
int oldYAngle = area.Area3DStyle.Rotation;
area.Area3DStyle.Rotation = area.GetRealYAngle( );
// Draw Pie
ProcessChartType3D( false, graph, common, area, pieWidth );
// Make reversed X angle because of default angle.
area.Area3DStyle.Inclination *= -1;
area.Area3DStyle.Rotation = oldYAngle;
// Reset Clip Region
graph.ResetClip();
}
else
{
// Reset overlapped labels flag
this._labelsOverlap = false;
//Set Clip Region
((ChartGraphics)graph).SetClip( area.Position.ToRectangleF() );
// Resize pie because of labels
SizeCorrection( graph, common, area );
// Draw Pie labels
ProcessChartType( false, graph, common, area, false, LabelsMode.LabelsOverlap );
// If overlapping labels are detected they will be drawn in "columns" on each
// side of the pie. Adjust plotting area to fit the labels
if(this._labelsOverlap)
{
// Resize pie because of labels
SizeCorrection( graph, common, area );
// Reset overlapped labels flag
this._labelsOverlap = false;
// Draw Pie labels
ProcessChartType( false, graph, common, area, false, LabelsMode.LabelsOverlap );
}
// Draw Shadow
ProcessChartType( false, graph, common, area, true, LabelsMode.Off );
// Draw Pie
ProcessChartType( false, graph, common, area, false, LabelsMode.Off );
// Draw Pie labels
ProcessChartType( false, graph, common, area, false, LabelsMode.Draw );
//Reset Clip Region
((ChartGraphics)graph).ResetClip();
}
}
/// <summary>
/// Take Relative Minimum Pie Size attribute
/// </summary>
/// <param name="area">Chart Area</param>
/// <returns>Custom attribute value.</returns>
private double MinimumRelativePieSize( ChartArea area )
{
// Default value
double minimumSize = 0.3;
// All data series from chart area which have Pie chart type
List<string> typeSeries = area.GetSeriesFromChartType(Name);
// Data series collection
SeriesCollection dataSeries = area.Common.DataManager.Series;
// Take Relative Minimum Pie Size attribute
if(dataSeries[typeSeries[0]].IsCustomPropertySet(CustomPropertyName.MinimumRelativePieSize))
{
minimumSize = CommonElements.ParseFloat(dataSeries[typeSeries[0]][CustomPropertyName.MinimumRelativePieSize]) / 100.0;
// Validation
if( minimumSize < 0.1 || minimumSize > 0.7 )
throw (new ArgumentException(SR.ExceptionPieMinimumRelativePieSizeInvalid));
}
return minimumSize;
}
/// <summary>
/// Method that is used to resize pie
/// because of labels.
/// </summary>
private void SizeCorrection( ChartGraphics graph, CommonElements common, ChartArea area )
{
float correction = (this._labelsOverlap) ? this._sizeCorrection : 0.95F;
_sliceExploded = false;
// Estimate Labels
if( area.InnerPlotPosition.Auto )
{
for( ; correction >= (float)MinimumRelativePieSize( area ); correction -= 0.05F )
{
// Decrease Pie size
this._sizeCorrection = correction;
// Check if labels fit.
ProcessChartType( false, graph, common, area, false, LabelsMode.EstimateSize );
if( _labelsFit )
{
break;
}
}
// Size correction for exploded pie can not be larger then 0.8
if( _sliceExploded && _sizeCorrection > 0.8F )
{
_sizeCorrection = 0.8F;
}
}
else
{
_sizeCorrection = 0.95F;
}
}
/// <summary>
/// This method recalculates position of pie slices
/// or checks if pie slice is selected.
/// </summary>
/// <param name="selection">If True selection mode is active, otherwise paint mode is active</param>
/// <param name="graph">The Chart Graphics object</param>
/// <param name="common">The Common elements object</param>
/// <param name="area">Chart area for this chart</param>
/// <param name="shadow">Draw pie shadow</param>
/// <param name="labels">Pie labels</param>
private void ProcessChartType( bool selection, ChartGraphics graph, CommonElements common, ChartArea area, bool shadow, LabelsMode labels )
{
float startAngle = 0; // Angle in degrees measured clockwise from the x-axis to the first side of the pie section.
string explodedAttrib = ""; // Exploded attribute
bool exploded; // Exploded pie slice
float midAngle; // Angle between Start Angle and End Angle
// Data series collection
SeriesCollection dataSeries = common.DataManager.Series;
// Clear Labels overlap collection
if( labels == LabelsMode.LabelsOverlap )
{
_labelsRectangles.Clear();
}
// All data series from chart area which have Pie chart type
List<string> typeSeries = area.GetSeriesFromChartType(Name);
if(typeSeries.Count == 0)
{
return;
}
// Get first pie starting angle
if(typeSeries.Count > 0)
{
if (dataSeries[typeSeries[0]].IsCustomPropertySet(CustomPropertyName.PieStartAngle))
{
float angle;
bool parseSucceed = float.TryParse(dataSeries[typeSeries[0]][CustomPropertyName.PieStartAngle], NumberStyles.Any, CultureInfo.InvariantCulture, out angle);
if (parseSucceed)
{
startAngle = angle;
}
if (!parseSucceed || startAngle > 360f || startAngle < 0f)
{
throw (new InvalidOperationException(SR.ExceptionCustomAttributeAngleOutOfRange("PieStartAngle")));
}
}
}
// Call Back Paint event
if( !selection )
{
common.Chart.CallOnPrePaint(new ChartPaintEventArgs(dataSeries[typeSeries[0]], graph, common, area.PlotAreaPosition));
}
// The data points loop. Find Sum of data points.
double sum = 0.0;
foreach( DataPoint point in dataSeries[typeSeries[0]].Points )
{
if( !point.IsEmpty )
{
sum += Math.Abs( point.YValues[0] );
}
}
// No points or all points have zero values
if(sum == 0.0)
{
return;
}
// Take radius attribute
float doughnutRadius = 60f;
if(dataSeries[typeSeries[0]].IsCustomPropertySet(CustomPropertyName.DoughnutRadius))
{
doughnutRadius = CommonElements.ParseFloat(dataSeries[typeSeries[0]][CustomPropertyName.DoughnutRadius]);
// Validation
if( doughnutRadius < 0f || doughnutRadius > 99f )
throw (new ArgumentException(SR.ExceptionPieRadiusInvalid));
}
// This method is introduced to check colors of palette. For
// pie chart the first pie slice and the second pie slice can
// not have same color because they are connected.
CheckPaleteColors( dataSeries[typeSeries[0]].Points );
//************************************************************
//** Data point loop
//************************************************************
int pointIndx = 0;
int nonEmptyPointIndex = 0;
foreach( DataPoint point in dataSeries[typeSeries[0]].Points )
{
// Do not process empty points
if( point.IsEmpty )
{
pointIndx++;
continue;
}
// Rectangle size
RectangleF rectangle;
if( area.InnerPlotPosition.Auto )
{
rectangle = new RectangleF( area.Position.ToRectangleF().X, area.Position.ToRectangleF().Y, area.Position.ToRectangleF().Width, area.Position.ToRectangleF().Height );
}
else
{
rectangle = new RectangleF( area.PlotAreaPosition.ToRectangleF().X, area.PlotAreaPosition.ToRectangleF().Y, area.PlotAreaPosition.ToRectangleF().Width, area.PlotAreaPosition.ToRectangleF().Height );
}
if(rectangle.Width < 0f || rectangle.Height < 0f)
{
return;
}
// Find smallest edge
SizeF absoluteSize = graph.GetAbsoluteSize( new SizeF( rectangle.Width, rectangle.Height ) );
float absRadius = ( absoluteSize.Width < absoluteSize.Height ) ? absoluteSize.Width : absoluteSize.Height;
// Size of the square, which will be used for drawing pie.
SizeF relativeSize = graph.GetRelativeSize( new SizeF( absRadius, absRadius ) );
// Center of the pie
PointF middlePoint = new PointF( rectangle.X + rectangle.Width / 2, rectangle.Y + rectangle.Height / 2 );
// Rectangle which will always create circle, never ellipse.
rectangle = new RectangleF( middlePoint.X - relativeSize.Width / 2, middlePoint.Y - relativeSize.Height / 2, relativeSize.Width, relativeSize.Height );
// Size correction because of exploded or labels
if( _sizeCorrection != 1 )
{
rectangle.X += rectangle.Width * ( 1 - _sizeCorrection ) / 2;
rectangle.Y += rectangle.Height * ( 1 - _sizeCorrection ) / 2;
rectangle.Width = rectangle.Width * _sizeCorrection;
rectangle.Height = rectangle.Height * _sizeCorrection;
// Adjust inner plot position
if(area.InnerPlotPosition.Auto)
{
RectangleF rect = rectangle;
rect.X = (rect.X - area.Position.X) / area.Position.Width * 100f;
rect.Y = (rect.Y - area.Position.Y) / area.Position.Height * 100f;
rect.Width = rect.Width / area.Position.Width * 100f;
rect.Height = rect.Height / area.Position.Height * 100f;
area.InnerPlotPosition.SetPositionNoAuto(rect.X, rect.Y, rect.Width, rect.Height);
}
}
float sweepAngle = (float)( Math.Abs(point.YValues[0]) / sum * 360);
// Check Exploded attribute for data point
exploded = false;
if(point.IsCustomPropertySet(CustomPropertyName.Exploded))
{
explodedAttrib = point[CustomPropertyName.Exploded];
if( String.Compare(explodedAttrib,"true", StringComparison.OrdinalIgnoreCase) == 0 )
exploded = true;
else
exploded = false;
}
Color pieLineColor = Color.Empty;
ColorConverter colorConverter = new ColorConverter();
// Check if special color properties are set
if(point.IsCustomPropertySet(CustomPropertyName.PieLineColor) || dataSeries[typeSeries[0]].IsCustomPropertySet(CustomPropertyName.PieLineColor))
{
bool failed = false;
try
{
pieLineColor = (Color)colorConverter.ConvertFromString(
(point.IsCustomPropertySet(CustomPropertyName.PieLineColor)) ? point[CustomPropertyName.PieLineColor] : dataSeries[typeSeries[0]][CustomPropertyName.PieLineColor]);
failed = false;
}
catch (ArgumentException)
{
failed = true;
}
catch (NotSupportedException)
{
failed = true;
}
if (failed)
{
pieLineColor = (Color)colorConverter.ConvertFromInvariantString(
(point.IsCustomPropertySet(CustomPropertyName.PieLineColor)) ? point[CustomPropertyName.PieLineColor] : dataSeries[typeSeries[0]][CustomPropertyName.PieLineColor]);
}
}
// Find Direction to move exploded pie slice
if( exploded )
{
_sliceExploded = true;
midAngle = ( 2 * startAngle + sweepAngle ) / 2;
double xComponent = Math.Cos( midAngle * Math.PI / 180 ) * rectangle.Width / 10;
double yComponent = Math.Sin( midAngle * Math.PI / 180 ) * rectangle.Height / 10;
rectangle.Offset( (float)xComponent, (float)yComponent );
}
// Hot regions of the data points. Labels hot regions are processed aftre drawing.
if( common.ProcessModeRegions && labels == LabelsMode.Draw )
{
Map( common, point, startAngle, sweepAngle, rectangle, Doughnut, doughnutRadius, graph, pointIndx );
}
// Painting mode
if( common.ProcessModePaint )
{
// Draw Shadow
if( shadow )
{
double offset = graph.GetRelativeSize( new SizeF( point.series.ShadowOffset, point.series.ShadowOffset ) ).Width;
// Offset is zero. Do not draw shadow pie slice.
if( offset == 0.0 )
{
break;
}
// Shadow Rectangle
RectangleF shadowRect = new RectangleF( rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height );
shadowRect.Offset( (float)offset, (float)offset );
// Change shadow color
Color shcolor = new Color();
Color shGradientColor = new Color();
Color shBorderColor = new Color();
// Solid color
if( point.Color.A != 255 )
shcolor = Color.FromArgb( point.Color.A/2, point.series.ShadowColor );
else
shcolor = point.series.ShadowColor;
// Gradient Color
if( !point.BackSecondaryColor.IsEmpty )
{
if( point.BackSecondaryColor.A != 255 )
shGradientColor = Color.FromArgb( point.BackSecondaryColor.A/2, point.series.ShadowColor );
else
shGradientColor = point.series.ShadowColor;
}
else
shGradientColor = Color.Empty;
// Border color
if( !point.BorderColor.IsEmpty )
{
if( point.BorderColor.A != 255 )
shBorderColor = Color.FromArgb( point.BorderColor.A/2, point.series.ShadowColor );
else
shBorderColor = point.series.ShadowColor;
}
else
shBorderColor = Color.Empty;
// Draw shadow of pie slice
graph.DrawPieRel(
shadowRect,
startAngle,
sweepAngle,
shcolor,
ChartHatchStyle.None,
"",
point.BackImageWrapMode,
point.BackImageTransparentColor,
point.BackGradientStyle,
shGradientColor,
shBorderColor,
point.BorderWidth,
point.BorderDashStyle,
true,
Doughnut,
doughnutRadius,
PieDrawingStyle.Default);
}
else
{
if( labels == LabelsMode.Off )
{
// Start Svg Selection mode
graph.StartHotRegion( point );
// Draw pie slice
graph.DrawPieRel(
rectangle,
startAngle,
sweepAngle,
point.Color,
point.BackHatchStyle,
point.BackImage,
point.BackImageWrapMode,
point.BackImageTransparentColor,
point.BackGradientStyle,
point.BackSecondaryColor,
point.BorderColor,
point.BorderWidth,
point.BorderDashStyle,
false,
Doughnut,
doughnutRadius,
ChartGraphics.GetPieDrawingStyle(point) );
// End Svg Selection mode
graph.EndHotRegion( );
}
}
// Estimate labels
if( labels == LabelsMode.EstimateSize )
{
EstimateLabels( graph, middlePoint, rectangle.Size, startAngle, sweepAngle, point, exploded, area );
if( _labelsFit == false )
{
return;
}
}
// Labels overlap test
if( labels == LabelsMode.LabelsOverlap )
{
DrawLabels( graph, middlePoint, rectangle.Size, startAngle, sweepAngle, point, doughnutRadius, exploded, area, true, nonEmptyPointIndex, pieLineColor );
}
// Draw labels and markers
if( labels == LabelsMode.Draw )
{
DrawLabels( graph, middlePoint, rectangle.Size, startAngle, sweepAngle, point, doughnutRadius, exploded, area, false, nonEmptyPointIndex, pieLineColor );
}
}
if( common.ProcessModeRegions && labels == LabelsMode.Draw )
{
// Add labels hot regions if it was not done during the painting
if( !common.ProcessModePaint )
{
DrawLabels( graph, middlePoint, rectangle.Size, startAngle, sweepAngle, point, doughnutRadius, exploded, area, false, nonEmptyPointIndex, pieLineColor );
}
}
//**************************************************
//** Remember point relative position
//**************************************************
point.positionRel = new PointF(float.NaN, float.NaN);
// If exploded the shift is bigger
float expShift = 1;
if( exploded )
expShift = 1.2F;
midAngle = startAngle + sweepAngle / 2;
// Find first line position
point.positionRel.X = (float)Math.Cos( (midAngle) * Math.PI / 180 ) * rectangle.Width * expShift / 2 + middlePoint.X;
point.positionRel.Y = (float)Math.Sin( (midAngle) * Math.PI / 180 ) * rectangle.Height * expShift / 2 + middlePoint.Y;
// Increase point index and sweep angle
pointIndx++;
nonEmptyPointIndex++;
startAngle += sweepAngle;
if(startAngle >= 360)
{
startAngle -= 360;
}
}
if( labels == LabelsMode.LabelsOverlap && this._labelsOverlap )
{
this._labelsOverlap = PrepareLabels( area.Position.ToRectangleF() );
}
// Call Paint event
if( !selection )
{
common.Chart.CallOnPostPaint(new ChartPaintEventArgs(dataSeries[typeSeries[0]], graph, common, area.PlotAreaPosition));
}
}
/// <summary>
/// Draw Pie labels or test for overlaping.
/// </summary>
/// <param name="graph">Chart Graphics object</param>
/// <param name="middlePoint">Center of the pie chart</param>
/// <param name="relativeSize">Size of the square, which will be used for drawing pie.</param>
/// <param name="startAngle">Starting angle of a pie slice</param>
/// <param name="sweepAngle">Sweep angle of a pie slice</param>
/// <param name="point">Data point</param>
/// <param name="doughnutRadius">Radius for Doughnut Chart in %</param>
/// <param name="exploded">The pie slice is exploded</param>
/// <param name="area">Chart area</param>
/// <param name="overlapTest">True if test mode is on</param>
/// <param name="pointIndex">Data Point Index</param>
/// <param name="pieLineColor">Color of line labels</param>
public void DrawLabels( ChartGraphics graph, PointF middlePoint, SizeF relativeSize, float startAngle, float sweepAngle, DataPoint point, float doughnutRadius, bool exploded, ChartArea area, bool overlapTest, int pointIndex, Color pieLineColor )
{
bool added = false; // Indicates that label position was added
float x; // Label Position
float y; // Label Position
Series series; // Data Series
float labelsHorizontalLineSize = 1; // Horizontal line size for outside labels
float labelsRadialLineSize = 1; // Radial line size for outside labels
string text;
// Disable the clip region
Region oldClipRegion = graph.Clip;
graph.Clip = new Region();
// Get label text
text = this.GetLabelText( point );
if(text.Length == 0)
{
return;
}
float shift;
series = point.series;
PieLabelStyle style = PieLabelStyle.Inside;
// Get label style attribute from series
if(series.IsCustomPropertySet(CustomPropertyName.LabelStyle))
{
string labelStyleAttrib = series[CustomPropertyName.LabelStyle];
// Labels Disabled
if( String.Compare(labelStyleAttrib,"disabled",StringComparison.OrdinalIgnoreCase) == 0 )
style = PieLabelStyle.Disabled;
else if (String.Compare(labelStyleAttrib, "outside", StringComparison.OrdinalIgnoreCase) == 0)
style = PieLabelStyle.Outside;
else
style = PieLabelStyle.Inside;
}
else if(series.IsCustomPropertySet(CustomPropertyName.PieLabelStyle))
{
string labelStyleAttrib = series[CustomPropertyName.PieLabelStyle];
// Labels Disabled
if (String.Compare(labelStyleAttrib, "disabled", StringComparison.OrdinalIgnoreCase) == 0)
style = PieLabelStyle.Disabled;
else if (String.Compare(labelStyleAttrib, "outside", StringComparison.OrdinalIgnoreCase) == 0)
style = PieLabelStyle.Outside;
else
style = PieLabelStyle.Inside;
}
// Get label style attribute from point
if(point.IsCustomPropertySet(CustomPropertyName.LabelStyle))
{
string labelStyleAttrib = point[CustomPropertyName.LabelStyle];
// Labels Disabled
if (String.Compare(labelStyleAttrib, "disabled", StringComparison.OrdinalIgnoreCase) == 0)
style = PieLabelStyle.Disabled;
else if (String.Compare(labelStyleAttrib, "outside", StringComparison.OrdinalIgnoreCase) == 0)
style = PieLabelStyle.Outside;
else
style = PieLabelStyle.Inside;
}
// Get label style attribute from point
else if(point.IsCustomPropertySet(CustomPropertyName.PieLabelStyle))
{
string labelStyleAttrib = point[CustomPropertyName.PieLabelStyle];
// Labels Disabled
if (String.Compare(labelStyleAttrib, "disabled", StringComparison.OrdinalIgnoreCase) == 0)
style = PieLabelStyle.Disabled;
else if (String.Compare(labelStyleAttrib, "outside", StringComparison.OrdinalIgnoreCase) == 0)
style = PieLabelStyle.Outside;
else
style = PieLabelStyle.Inside;
}
// Take labels radial line size attribute from series
if(series.IsCustomPropertySet(CustomPropertyName.LabelsRadialLineSize))
{
string labelsRadialLineSizeAttrib = series[CustomPropertyName.LabelsRadialLineSize];
labelsRadialLineSize = CommonElements.ParseFloat( labelsRadialLineSizeAttrib);
// Validation
if( labelsRadialLineSize < 0 || labelsRadialLineSize > 100 )
throw new InvalidOperationException(SR.ExceptionPieRadialLineSizeInvalid);
}
// Take labels radial line size attribute from point
if(point.IsCustomPropertySet(CustomPropertyName.LabelsRadialLineSize))
{
string labelsRadialLineSizeAttrib = point[CustomPropertyName.LabelsRadialLineSize];
labelsRadialLineSize = CommonElements.ParseFloat( labelsRadialLineSizeAttrib);
// Validation
if( labelsRadialLineSize < 0 || labelsRadialLineSize > 100 )
throw new InvalidOperationException(SR.ExceptionPieRadialLineSizeInvalid);
}
// Take labels horizontal line size attribute from series
if(series.IsCustomPropertySet(CustomPropertyName.LabelsHorizontalLineSize))
{
string labelsHorizontalLineSizeAttrib = series[CustomPropertyName.LabelsHorizontalLineSize];
labelsHorizontalLineSize = CommonElements.ParseFloat( labelsHorizontalLineSizeAttrib);
// Validation
if( labelsHorizontalLineSize < 0 || labelsHorizontalLineSize > 100 )
throw new InvalidOperationException(SR.ExceptionPieHorizontalLineSizeInvalid);
}
// Take labels horizontal line size attribute from point
if(point.IsCustomPropertySet(CustomPropertyName.LabelsHorizontalLineSize))
{
string labelsHorizontalLineSizeAttrib = point[CustomPropertyName.LabelsHorizontalLineSize];
labelsHorizontalLineSize = CommonElements.ParseFloat( labelsHorizontalLineSizeAttrib);
// Validation
if( labelsHorizontalLineSize < 0 || labelsHorizontalLineSize > 100 )
throw new InvalidOperationException(SR.ExceptionPieHorizontalLineSizeInvalid);
}
float expShift = 1;
// ********************************************
// Labels are set inside pie
// ********************************************
if( style == PieLabelStyle.Inside && !overlapTest )
{
float width;
float height;
// If exploded the shift is bigger
if( exploded )
{
expShift = 1.4F;
}
// Get offset of the inside labels position
// NOTE: This custom attribute is NOT released!
float positionRatio = 4.0f;
if(point.IsCustomPropertySet("InsideLabelOffset"))
{
bool parseSucceed = float.TryParse(point["InsideLabelOffset"], NumberStyles.Any, CultureInfo.InvariantCulture, out positionRatio);
if(!parseSucceed || positionRatio < 0f || positionRatio > 100f)
{
throw(new InvalidOperationException(SR.ExceptionCustomAttributeIsNotInRange0to100("InsideLabelOffset")));
}
positionRatio = 4f / (1f + positionRatio / 100f);
}
// Shift the string for Doughnut type
if( Doughnut )
{
width = relativeSize.Width * expShift / positionRatio * ( 1 + ( 100 - doughnutRadius ) / 100F );
height = relativeSize.Height * expShift / positionRatio * ( 1 + ( 100 - doughnutRadius ) / 100F );
}
else
{
width = relativeSize.Width * expShift / positionRatio;
height = relativeSize.Height * expShift / positionRatio;
}
// Find string position
x = (float)Math.Cos( (startAngle + sweepAngle / 2) * Math.PI / 180 ) * width + middlePoint.X;
y = (float)Math.Sin( (startAngle + sweepAngle / 2) * Math.PI / 180 ) * height + middlePoint.Y;
// Center the string horizontally and vertically.
using (StringFormat format = new StringFormat())
{
format.Alignment = StringAlignment.Center;
format.LineAlignment = StringAlignment.Center;
SizeF sizeFont = graph.GetRelativeSize(
graph.MeasureString(
text.Replace("\\n", "\n"),
point.Font,
new SizeF(1000f, 1000f),
StringFormat.GenericTypographic));
// Get label background position
RectangleF labelBackPosition = RectangleF.Empty;
SizeF sizeLabel = new SizeF(sizeFont.Width, sizeFont.Height);
sizeLabel.Height += sizeLabel.Height / 8;
sizeLabel.Width += sizeLabel.Width / text.Length;
labelBackPosition = PointChart.GetLabelPosition(
graph,
new PointF(x, y),
sizeLabel,
format,
true);
// Draw the label inside the pie
using (Brush brush = new SolidBrush(point.LabelForeColor))
{
graph.DrawPointLabelStringRel(
area.Common,
text,
point.Font,
brush,
new PointF(x, y),
format,
point.LabelAngle,
labelBackPosition,
point.LabelBackColor,
point.LabelBorderColor,
point.LabelBorderWidth,
point.LabelBorderDashStyle,
series,
point,
pointIndex);
}
}
}
// ********************************************
// Labels are set outside pie
// ********************************************
else if( style == PieLabelStyle.Outside )
{
// Coefficient which represent shift from pie border
shift = 0.5F + labelsRadialLineSize * 0.1F;
// If exploded the shift is bigger
if( exploded )
expShift = 1.2F;
float midAngle = startAngle + sweepAngle / 2;
// Find first line position
float x1 = (float)Math.Cos( (midAngle) * Math.PI / 180 ) * relativeSize.Width * expShift / 2 + middlePoint.X;
float y1 = (float)Math.Sin( (midAngle) * Math.PI / 180 ) * relativeSize.Height * expShift / 2 + middlePoint.Y;
float x2 = (float)Math.Cos( (midAngle) * Math.PI / 180 ) * relativeSize.Width * shift * expShift + middlePoint.X;
float y2 = (float)Math.Sin( (midAngle) * Math.PI / 180 ) * relativeSize.Height * shift * expShift + middlePoint.Y;
if( pieLineColor == Color.Empty )
{
pieLineColor = point.BorderColor;
}
// Draw first line
if( !overlapTest )
{
graph.DrawLineRel( pieLineColor, point.BorderWidth, ChartDashStyle.Solid, new PointF( x1, y1 ), new PointF( x2, y2 ) );
}
// Set string alingment
using (StringFormat format = new StringFormat())
{
format.Alignment = StringAlignment.Center;
format.LineAlignment = StringAlignment.Center;
// Find second line position
float y3 = (float)Math.Sin((midAngle) * Math.PI / 180) * relativeSize.Height * shift * expShift + middlePoint.Y;
float x3;
float x3Overlap;
RectangleF labelRect = RectangleF.Empty;
RectangleF labelRectOver = RectangleF.Empty;
if (midAngle > 90 && midAngle < 270)
{
format.Alignment = StringAlignment.Far;
x3Overlap = -relativeSize.Width * shift * expShift + middlePoint.X - relativeSize.Width / 10 * labelsHorizontalLineSize;
x3 = (float)Math.Cos((midAngle) * Math.PI / 180) * relativeSize.Width * shift * expShift + middlePoint.X - relativeSize.Width / 10 * labelsHorizontalLineSize;
if (overlapTest)
{
x3Overlap = x3;
}
// This method returns calculated rectangle from point position
// for outside label. Rectangle mustn’t be out of chart area.
labelRect = GetLabelRect(new PointF(x3, y3), area, text, format, graph, point, true);
labelRectOver = GetLabelRect(new PointF(x3Overlap, y3), area, text, format, graph, point, true);
}
else
{
format.Alignment = StringAlignment.Near;
x3Overlap = relativeSize.Width * shift * expShift + middlePoint.X + relativeSize.Width / 10 * labelsHorizontalLineSize;
x3 = (float)Math.Cos((midAngle) * Math.PI / 180) * relativeSize.Width * shift * expShift + middlePoint.X + relativeSize.Width / 10 * labelsHorizontalLineSize;
if (overlapTest)
{
x3Overlap = x3;
}
// This method returns calculated rectangle from point position
// for outside label. Rectangle mustn’t be out of chart area.
labelRect = GetLabelRect(new PointF(x3, y3), area, text, format, graph, point, false);
labelRectOver = GetLabelRect(new PointF(x3Overlap, y3), area, text, format, graph, point, false);
}
// Draw second line
if (!overlapTest)
{
if (this._labelsOverlap)
{
float calculatedY3 = (((RectangleF)this._labelsRectangles[pointIndex]).Top + ((RectangleF)this._labelsRectangles[pointIndex]).Bottom) / 2f;
graph.DrawLineRel(pieLineColor, point.BorderWidth, ChartDashStyle.Solid, new PointF(x2, y2), new PointF(x3Overlap, calculatedY3));
}
else
{
graph.DrawLineRel(pieLineColor, point.BorderWidth, ChartDashStyle.Solid, new PointF(x2, y2), new PointF(x3, y3));
}
}
// Draw the string
if (!overlapTest)
{
RectangleF rect = new RectangleF(labelRect.Location, labelRect.Size);
if (this._labelsOverlap)
{
// Draw label from collection if original labels overlap.
rect = (RectangleF)this._labelsRectangles[pointIndex];
rect.X = labelRectOver.X;
rect.Width = labelRectOver.Width;
}
// Get label background position
SizeF valueTextSize = graph.MeasureStringRel(text.Replace("\\n", "\n"), point.Font);
valueTextSize.Height += valueTextSize.Height / 8;
float spacing = valueTextSize.Width / text.Length / 2;
valueTextSize.Width += spacing;
RectangleF labelBackPosition = new RectangleF(
rect.X,
rect.Y + rect.Height / 2f - valueTextSize.Height / 2f,
valueTextSize.Width,
valueTextSize.Height);
// Adjust position based on alignment
if (format.Alignment == StringAlignment.Near)
{
labelBackPosition.X -= spacing / 2f;
}
else if (format.Alignment == StringAlignment.Center)
{
labelBackPosition.X = rect.X + (rect.Width - valueTextSize.Width) / 2f;
}
else if (format.Alignment == StringAlignment.Far)
{
labelBackPosition.X = rect.Right - valueTextSize.Width - spacing / 2f;
}
// Draw label text outside
using (Brush brush = new SolidBrush(point.LabelForeColor))
{
graph.DrawPointLabelStringRel(
area.Common,
text,
point.Font,
brush,
rect,
format,
point.LabelAngle,
labelBackPosition,
point.LabelBackColor,
point.LabelBorderColor,
point.LabelBorderWidth,
point.LabelBorderDashStyle,
series,
point,
pointIndex);
}
}
else
{
// Insert labels in label collection. This
// code is executed only if labels overlap.
this.InsertOverlapLabel(labelRectOver);
added = true;
}
}
}
// Restore old clip region
graph.Clip = oldClipRegion;
// Add empty overlap empty position
if(!added)
{
InsertOverlapLabel( RectangleF.Empty );
}
return;
}
/// <summary>
/// This method returns calculated rectangle from point position
/// for outside label. Rectangle mustn’t be out of chart area.
/// </summary>
/// <param name="labelPosition">The first position for label</param>
/// <param name="area">Chart area used for chart area position</param>
/// <param name="text">Label text</param>
/// <param name="format">Text format</param>
/// <param name="graph">Chart Graphics object</param>
/// <param name="point">Data point</param>
/// <param name="leftOrientation">Orientation for label. It could be left or right.</param>
/// <returns>Calculated rectangle for label</returns>
private RectangleF GetLabelRect( PointF labelPosition, ChartArea area, string text, StringFormat format, ChartGraphics graph, DataPoint point, bool leftOrientation )
{
RectangleF labelRect = RectangleF.Empty;
if( leftOrientation )
{
labelRect.X = area.Position.X;
labelRect.Y = area.Position.Y;
labelRect.Width = labelPosition.X - area.Position.X;
labelRect.Height = area.Position.Height;
}
else
{
labelRect.X = labelPosition.X;
labelRect.Y = area.Position.Y;
labelRect.Width = area.Position.Right - labelPosition.X;
labelRect.Height = area.Position.Height;
}
// Find bounding rectangle of the text
SizeF size = graph.MeasureStringRel( text.Replace("\\n", "\n"), point.Font, labelRect.Size, format );
labelRect.Y = labelPosition.Y - size.Height / 2 * 1.8f;
labelRect.Height = size.Height * 1.8f;
return labelRect;
}
/// <summary>
/// This method returns Pie Label Style enumeration
/// from Data Point Custom attribute.
/// </summary>
/// <param name="point">Data Point</param>
/// <returns>Pie label style enumeration</returns>
private PieLabelStyle GetLabelStyle( DataPoint point )
{
Series series = point.series;
PieLabelStyle style = PieLabelStyle.Inside;
// Get label style attribute from series
if(series.IsCustomPropertySet(CustomPropertyName.LabelStyle))
{
string labelStyleAttrib = series[CustomPropertyName.LabelStyle];
// Labels Disabled
if (String.Compare(labelStyleAttrib, "disabled", StringComparison.OrdinalIgnoreCase) == 0)
style = PieLabelStyle.Disabled;
else if (String.Compare(labelStyleAttrib, "outside", StringComparison.OrdinalIgnoreCase) == 0)
style = PieLabelStyle.Outside;
else
style = PieLabelStyle.Inside;
}
else if(series.IsCustomPropertySet(CustomPropertyName.PieLabelStyle))
{
string labelStyleAttrib = series[CustomPropertyName.PieLabelStyle];
// Labels Disabled
if (String.Compare(labelStyleAttrib, "disabled", StringComparison.OrdinalIgnoreCase) == 0)
style = PieLabelStyle.Disabled;
else if (String.Compare(labelStyleAttrib, "outside", StringComparison.OrdinalIgnoreCase) == 0)
style = PieLabelStyle.Outside;
else
style = PieLabelStyle.Inside;
}
// Get label style attribute from point
if(point.IsCustomPropertySet(CustomPropertyName.LabelStyle))
{
string labelStyleAttrib = point[CustomPropertyName.LabelStyle];
// Labels Disabled
if (String.Compare(labelStyleAttrib, "disabled", StringComparison.OrdinalIgnoreCase) == 0)
style = PieLabelStyle.Disabled;
else if (String.Compare(labelStyleAttrib, "outside", StringComparison.OrdinalIgnoreCase) == 0)
style = PieLabelStyle.Outside;
else
style = PieLabelStyle.Inside;
}
else if(point.IsCustomPropertySet(CustomPropertyName.PieLabelStyle))
{
string labelStyleAttrib = point[CustomPropertyName.PieLabelStyle];
// Labels Disabled
if (String.Compare(labelStyleAttrib, "disabled", StringComparison.OrdinalIgnoreCase) == 0)
style = PieLabelStyle.Disabled;
else if (String.Compare(labelStyleAttrib, "outside", StringComparison.OrdinalIgnoreCase) == 0)
style = PieLabelStyle.Outside;
else
style = PieLabelStyle.Inside;
}
return style;
}
/// <summary>
/// Estimate Labels.
/// </summary>
/// <param name="graph">Chart Graphics object</param>
/// <param name="middlePoint">Center of the pie chart</param>
/// <param name="relativeSize">Size of the square, which will be used for drawing pie.</param>
/// <param name="startAngle">Starting angle of a pie slice</param>
/// <param name="sweepAngle">Sweep angle of a pie slice</param>
/// <param name="point">Data point</param>
/// <param name="exploded">The pie slice is exploded</param>
/// <param name="area">Chart area</param>
public bool EstimateLabels( ChartGraphics graph, PointF middlePoint, SizeF relativeSize, float startAngle, float sweepAngle, DataPoint point, bool exploded, ChartArea area )
{
float labelsHorizontalLineSize = 1; // Horizontal line size for outside labels
float labelsRadialLineSize = 1; // Radial line size for outside labels
float shift;
string pointLabel = this.GetPointLabel(point);
Series series = point.series;
PieLabelStyle style = PieLabelStyle.Inside;
// Get label style attribute from series
if(series.IsCustomPropertySet(CustomPropertyName.LabelStyle))
{
string labelStyleAttrib = series[CustomPropertyName.LabelStyle];
// Labels Disabled
if( String.Compare(labelStyleAttrib,"disabled", StringComparison.OrdinalIgnoreCase) == 0 )
style = PieLabelStyle.Disabled;
else if (String.Compare(labelStyleAttrib, "outside", StringComparison.OrdinalIgnoreCase) == 0)
style = PieLabelStyle.Outside;
else
style = PieLabelStyle.Inside;
}
else if(series.IsCustomPropertySet(CustomPropertyName.PieLabelStyle))
{
string labelStyleAttrib = series[CustomPropertyName.PieLabelStyle];
// Labels Disabled
if (String.Compare(labelStyleAttrib, "disabled", StringComparison.OrdinalIgnoreCase) == 0)
style = PieLabelStyle.Disabled;
else if (String.Compare(labelStyleAttrib, "outside", StringComparison.OrdinalIgnoreCase) == 0)
style = PieLabelStyle.Outside;
else
style = PieLabelStyle.Inside;
}
// Get label style attribute from point
if(point.IsCustomPropertySet(CustomPropertyName.LabelStyle))
{
string labelStyleAttrib = point[CustomPropertyName.LabelStyle];
// Labels Disabled
if (String.Compare(labelStyleAttrib, "disabled", StringComparison.OrdinalIgnoreCase) == 0)
style = PieLabelStyle.Disabled;
else if (String.Compare(labelStyleAttrib, "outside", StringComparison.OrdinalIgnoreCase) == 0)
style = PieLabelStyle.Outside;
else
style = PieLabelStyle.Inside;
}
else if(point.IsCustomPropertySet(CustomPropertyName.PieLabelStyle))
{
string labelStyleAttrib = point[CustomPropertyName.PieLabelStyle];
// Labels Disabled
if (String.Compare(labelStyleAttrib, "disabled", StringComparison.OrdinalIgnoreCase) == 0)
style = PieLabelStyle.Disabled;
else if (String.Compare(labelStyleAttrib, "outside", StringComparison.OrdinalIgnoreCase) == 0)
style = PieLabelStyle.Outside;
else
style = PieLabelStyle.Inside;
}
// Take labels radial line size attribute from series
if(series.IsCustomPropertySet(CustomPropertyName.LabelsRadialLineSize))
{
string labelsRadialLineSizeAttrib = series[CustomPropertyName.LabelsRadialLineSize];
labelsRadialLineSize = CommonElements.ParseFloat( labelsRadialLineSizeAttrib );
// Validation
if( labelsRadialLineSize < 0 || labelsRadialLineSize > 100 )
throw new InvalidOperationException(SR.ExceptionPieRadialLineSizeInvalid);
}
// Take labels radial line size attribute from point
if(point.IsCustomPropertySet(CustomPropertyName.LabelsRadialLineSize))
{
string labelsRadialLineSizeAttrib = point[CustomPropertyName.LabelsRadialLineSize];
labelsRadialLineSize = CommonElements.ParseFloat( labelsRadialLineSizeAttrib );
// Validation
if( labelsRadialLineSize < 0 || labelsRadialLineSize > 100 )
throw new InvalidOperationException(SR.ExceptionPieRadialLineSizeInvalid);
}
// Take labels horizontal line size attribute from series
if(series.IsCustomPropertySet(CustomPropertyName.LabelsHorizontalLineSize))
{
string labelsHorizontalLineSizeAttrib = series[CustomPropertyName.LabelsHorizontalLineSize];
labelsHorizontalLineSize = CommonElements.ParseFloat( labelsHorizontalLineSizeAttrib );
// Validation
if( labelsHorizontalLineSize < 0 || labelsHorizontalLineSize > 100 )
throw new InvalidOperationException(SR.ExceptionPieHorizontalLineSizeInvalid);
}
// Take labels horizontal line size attribute from point
if(point.IsCustomPropertySet(CustomPropertyName.LabelsHorizontalLineSize))
{
string labelsHorizontalLineSizeAttrib = point[CustomPropertyName.LabelsHorizontalLineSize];
labelsHorizontalLineSize = CommonElements.ParseFloat( labelsHorizontalLineSizeAttrib );
// Validation
if( labelsHorizontalLineSize < 0 || labelsHorizontalLineSize > 100 )
throw new InvalidOperationException(SR.ExceptionPieHorizontalLineSizeInvalid);
}
float expShift = 1;
// ********************************************
// Labels are set outside pie
// ********************************************
if( style == PieLabelStyle.Outside )
{
// Coefficient which represent shift from pie border
shift = 0.5F + labelsRadialLineSize * 0.1F;
// If exploded the shift is bigger
if( exploded )
expShift = 1.2F;
float midAngle = startAngle + sweepAngle / 2;
// Find second line position
float y3 = (float)Math.Sin( (midAngle) * Math.PI / 180 ) * relativeSize.Height * shift * expShift + middlePoint.Y;
float x3;
if( midAngle > 90 && midAngle < 270 )
{
x3 = (float)Math.Cos( (midAngle) * Math.PI / 180 ) * relativeSize.Width * shift * expShift + middlePoint.X - relativeSize.Width / 10 * labelsHorizontalLineSize;
}
else
{
x3 = (float)Math.Cos( (midAngle) * Math.PI / 180 ) * relativeSize.Width * shift * expShift + middlePoint.X + relativeSize.Width / 10 * labelsHorizontalLineSize;
}
// Get label text
string text;
if( pointLabel.Length == 0 && point.IsValueShownAsLabel )
{
text = ValueConverter.FormatValue(
series.Chart,
point,
point.Tag,
point.YValues[0],
point.LabelFormat,
point.series.YValueType,
ChartElementType.DataPoint);
}
else
{
text = pointLabel;
}
SizeF size = graph.MeasureStringRel( text.Replace("\\n", "\n"), point.Font);
_labelsFit = true;
if(this._labelsOverlap)
{
if( midAngle > 90 && midAngle < 270 )
{
float xOverlap = -relativeSize.Width * shift * expShift + middlePoint.X - relativeSize.Width / 10 * labelsHorizontalLineSize;
if( (xOverlap - size.Width) < area.Position.X )
{
_labelsFit = false;
}
}
else
{
float xOverlap = relativeSize.Width * shift * expShift + middlePoint.X + relativeSize.Width / 10 * labelsHorizontalLineSize;
if( (xOverlap + size.Width) > area.Position.Right )
{
_labelsFit = false;
}
}
}
else
{
if( midAngle > 90 && midAngle < 270 )
{
if( x3 - size.Width < area.PlotAreaPosition.ToRectangleF().Left )
_labelsFit = false;
}
else
{
if( x3 + size.Width > area.PlotAreaPosition.ToRectangleF().Right )
_labelsFit = false;
}
if( midAngle > 180 && midAngle < 360 )
{
if( y3 - size.Height/2 < area.PlotAreaPosition.ToRectangleF().Top )
_labelsFit = false;
}
else
{
if( y3 + size.Height/2 > area.PlotAreaPosition.ToRectangleF().Bottom )
_labelsFit = false;
}
}
}
return true;
}
/// <summary>
/// This method adds map area information.
/// </summary>
/// <param name="common">The Common elements object</param>
/// <param name="point">Data Point</param>
/// <param name="startAngle">Start Angle</param>
/// <param name="sweepAngle">Sweep Angle</param>
/// <param name="rectangle">Rectangle of the pie</param>
/// <param name="doughnut">True if doughnut</param>
/// <param name="doughnutRadius">Doughnut radius in %</param>
/// <param name="graph">Chart graphics object</param>
/// <param name="pointIndex">Data point index</param>
private void Map( CommonElements common, DataPoint point, float startAngle, float sweepAngle, RectangleF rectangle, bool doughnut, float doughnutRadius, ChartGraphics graph, int pointIndex )
{
// Create a graphics path
using (GraphicsPath path = new GraphicsPath())
{
// Create the interior doughnut rectangle
RectangleF doughnutRect = RectangleF.Empty;
doughnutRect.X = rectangle.X + rectangle.Width * (1 - (100 - doughnutRadius) / 100) / 2;
doughnutRect.Y = rectangle.Y + rectangle.Height * (1 - (100 - doughnutRadius) / 100) / 2;
doughnutRect.Width = rectangle.Width * (100 - doughnutRadius) / 100;
doughnutRect.Height = rectangle.Height * (100 - doughnutRadius) / 100;
// Get absolute coordinates of the pie rectangle
rectangle = graph.GetAbsoluteRectangle(rectangle);
// Add the pie to the graphics path
path.AddPie(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height, startAngle, sweepAngle);
// VSTS #250394 (Dev10:591140) Fix - Control should not return “useless” map areas
if (sweepAngle <= 0)
{
return;
}
// If the chart type is doughnut
if (doughnut)
{
// Get absolute coordinates of the interior doughnut rectangle
doughnutRect = graph.GetAbsoluteRectangle(doughnutRect);
// Add the interior doughnut region to the graphics path
path.AddPie(doughnutRect.X, doughnutRect.Y, doughnutRect.Width, doughnutRect.Width, startAngle, sweepAngle);
}
// Make a polygon from curves
path.Flatten(new Matrix(), 1f);
// Create an area of points and convert them to
// relative coordinates.
PointF[] pointNew = new PointF[path.PointCount];
for (int i = 0; i < path.PointCount; i++)
{
pointNew[i] = graph.GetRelativePoint(path.PathPoints[i]);
}
// Allocate array of floats
float[] coord = new float[path.PointCount * 2];
// Transfer path points
for (int index = 0; index < path.PointCount; index++)
{
coord[2 * index] = pointNew[index].X;
coord[2 * index + 1] = pointNew[index].Y;
}
// Check if processing collected data point
if (point.IsCustomPropertySet("_COLLECTED_DATA_POINT"))
{
// Add point to the map area
common.HotRegionsList.AddHotRegion(
graph,
path,
false,
point.ReplaceKeywords(point.ToolTip),
#if Microsoft_CONTROL
string.Empty,
string.Empty,
string.Empty,
#else // Microsoft_CONTROL
point.ReplaceKeywords(point.Url),
point.ReplaceKeywords(point.MapAreaAttributes),
point.ReplaceKeywords(point.PostBackValue),
#endif // Microsoft_CONTROL
point,
ChartElementType.DataPoint);
return;
}
// Add points to the map area
common.HotRegionsList.AddHotRegion(
path,
false,
coord,
point,
point.series.Name,
pointIndex
);
}
}
/// <summary>
/// This method is introduced to check colors of palette. For
/// pie chart the first pie slice and the second pie slice can
/// not have same color because they are connected.
/// </summary>
/// <param name="points">Data points used for pie chart</param>
private void CheckPaleteColors( DataPointCollection points )
{
DataPoint firstPoint, lastPoint;
firstPoint = points[0];
lastPoint = points[ points.Count - 1 ];
// Change color for last point if same as the first and if it is from pallete.
if( firstPoint.tempColorIsSet && lastPoint.tempColorIsSet && firstPoint.Color == lastPoint.Color )
{
lastPoint.Color = points[ points.Count / 2 ].Color;
lastPoint.tempColorIsSet = true;
}
}
#endregion
#region 2DLabels
/// <summary>
/// This method finds vertical position for left and
/// right labels on that way that labels do not
/// overlap each other.
/// </summary>
/// <param name="area">Chart area position</param>
/// <returns>True if it is possible to find position that labels do not overlap each other.</returns>
private bool PrepareLabels( RectangleF area )
{
// Initialization of local variables
float splitPoint = area.X + area.Width / 2f;
int numberOfLeft = 0;
int numberOfRight = 0;
// Find the number of left and right labels.
foreach( RectangleF rect in this._labelsRectangles )
{
if( rect.X < splitPoint )
{
numberOfLeft++;
}
else
{
numberOfRight++;
}
}
// **********************************************
// Find the best position for LEFT labels
// **********************************************
bool leftResult = true;
if(numberOfLeft > 0)
{
double [] startPoints = new double[numberOfLeft];
double [] endPoints = new double[numberOfLeft];
int [] positionIndex = new Int32[numberOfLeft];
// Fill double arrays with Top and Bottom coordinates
// from the label rectangle.
int splitIndex = 0;
for( int index = 0; index < _labelsRectangles.Count; index++ )
{
RectangleF rect = (RectangleF)_labelsRectangles[index];
if( rect.X < splitPoint )
{
startPoints[ splitIndex ] = rect.Top;
endPoints[ splitIndex ] = rect.Bottom;
positionIndex[ splitIndex ] = index;
splitIndex++;
}
}
// Sort label positions
this.SortIntervals( startPoints, endPoints, positionIndex );
// Find no overlapping positions if possible.
if( this.ArrangeOverlappingIntervals( startPoints, endPoints, area.Top, area.Bottom ) )
{
// Fill label rectangle top and bottom coordinates
// from double arrays.
splitIndex = 0;
for( int index = 0; index < _labelsRectangles.Count; index++ )
{
RectangleF rect = (RectangleF)_labelsRectangles[index];
if( rect.X < splitPoint )
{
rect.Y = (float)startPoints[ splitIndex ];
rect.Height = (float)(endPoints[ splitIndex ] - rect.Top);
_labelsRectangles[positionIndex[ splitIndex ]] = rect;
splitIndex++;
}
}
}
else
{
leftResult = false;
}
}
// **********************************************
// Find the best position for Right labels
// **********************************************
bool rigthResult = true;
if(numberOfRight > 0)
{
double [] startPoints = new double[numberOfRight];
double [] endPoints = new double[numberOfRight];
int [] positionIndex = new Int32[numberOfRight];
// Fill double arrays with Top and Bottom coordinates
// from the label rectangle.
int splitIndex = 0;
for( int index = 0; index < _labelsRectangles.Count; index++ )
{
RectangleF rect = (RectangleF)_labelsRectangles[index];
if( rect.X >= splitPoint )
{
startPoints[ splitIndex ] = rect.Top;
endPoints[ splitIndex ] = rect.Bottom;
positionIndex[ splitIndex ] = index;
splitIndex++;
}
}
// Sort label positions
this.SortIntervals( startPoints, endPoints, positionIndex );
// Find no overlapping positions if possible.
if( this.ArrangeOverlappingIntervals( startPoints, endPoints, area.Top, area.Bottom ) )
{
// Fill label rectangle top and bottom coordinates
// from double arrays.
splitIndex = 0;
for( int index = 0; index < _labelsRectangles.Count; index++ )
{
RectangleF rect = (RectangleF)_labelsRectangles[index];
if( rect.X >= splitPoint )
{
rect.Y = (float)startPoints[ splitIndex ];
rect.Height = (float)(endPoints[ splitIndex ] - rect.Top);
_labelsRectangles[positionIndex[ splitIndex ]] = rect;
splitIndex++;
}
}
}
else
{
rigthResult = false;
}
}
return ( (!leftResult || !rigthResult) ? true : false );
}
/// <summary>
/// This algorithm sorts labels vertical intervals.
/// </summary>
/// <param name="startOfIntervals">Double array of label interval start points</param>
/// <param name="endOfIntervals">Double array of label interval end points</param>
/// <param name="positinIndex">Integer array of label interval indexes</param>
private void SortIntervals( double [] startOfIntervals, double [] endOfIntervals, int [] positinIndex )
{
double firstCenter;
double secondCenter;
double midDouble;
int midInt;
// Sorting loops
for( int firstIndex = 0; firstIndex < startOfIntervals.Length; firstIndex++ )
{
for( int secondIndex = firstIndex; secondIndex < startOfIntervals.Length; secondIndex++ )
{
firstCenter = ( startOfIntervals[ firstIndex ] + endOfIntervals[ firstIndex ] ) / 2.0;
secondCenter = ( startOfIntervals[ secondIndex ] + endOfIntervals[ secondIndex ] ) / 2.0;
if( firstCenter > secondCenter )
{
// Sort start points
midDouble = startOfIntervals[ firstIndex ];
startOfIntervals[ firstIndex ] = startOfIntervals[ secondIndex ];
startOfIntervals[ secondIndex ] = midDouble;
// Sort end points
midDouble = endOfIntervals[ firstIndex ];
endOfIntervals[ firstIndex ] = endOfIntervals[ secondIndex ];
endOfIntervals[ secondIndex ] = midDouble;
// Sort indexes
midInt = positinIndex[ firstIndex ];
positinIndex[ firstIndex ] = positinIndex[ secondIndex ];
positinIndex[ secondIndex ] = midInt;
}
}
}
}
/// <summary>
/// This method inserts label rectangles
/// into the collection.
/// </summary>
/// <param name="labelRect">Label Rectangle</param>
private void InsertOverlapLabel( RectangleF labelRect )
{
// Check if any pair of labels overlap
if(!labelRect.IsEmpty)
{
foreach( RectangleF rect in _labelsRectangles )
{
if( labelRect.IntersectsWith( rect ) )
{
this._labelsOverlap = true;
}
}
}
// Add rectangle to the collection
_labelsRectangles.Add( labelRect );
}
/// <summary>
/// This method will find the best position for labels.
/// It is based on finding non overlap intervals for
/// left or right side of the pie. This is
/// recursive algorithm.
/// </summary>
/// <param name="startOfIntervals">The start positions of intervals.</param>
/// <param name="endOfIntervals">The end positions of intervals.</param>
/// <param name="startArea">Start position of chart area vertical range.</param>
/// <param name="endArea">End position of chart area vertical range.</param>
/// <returns>False if non overlapping positions for intervals can not be found.</returns>
private bool ArrangeOverlappingIntervals( double [] startOfIntervals, double [] endOfIntervals, double startArea, double endArea )
{
// Invalidation
if( startOfIntervals.Length != endOfIntervals.Length )
{
throw new InvalidOperationException(SR.ExceptionPieIntervalsInvalid);
}
ShiftOverlappingIntervals( startOfIntervals, endOfIntervals );
// Find amount of empty space between intervals.
double emptySpace = 0;
for( int intervalIndex = 0; intervalIndex < startOfIntervals.Length - 1; intervalIndex++ )
{
// Check overlapping
if( startOfIntervals[ intervalIndex + 1 ] < endOfIntervals[ intervalIndex ] )
{
//throw new InvalidOperationException( SR.ExceptionPieIntervalsOverlapping );
}
emptySpace += startOfIntervals[ intervalIndex + 1 ] - endOfIntervals[ intervalIndex ];
}
//Find how much intervals are out of area. Out of area could be positive value only.
double outOfArea = ( endOfIntervals[ endOfIntervals.Length - 1 ] - endArea ) + ( startArea - startOfIntervals[ 0 ] );
if( outOfArea <= 0 )
{
// This algorithm shifts all intervals for the same
// amount. It is trying to put all intervals inside
// chart area range.
ShiftIntervals( startOfIntervals, endOfIntervals, startArea, endArea );
return true;
}
// There is no enough space for all intervals.
if( outOfArea > emptySpace )
{
return false;
}
// This method reduces empty space between intervals.
ReduceEmptySpace( startOfIntervals, endOfIntervals, ( emptySpace - outOfArea ) / emptySpace );
// This algorithm shifts all intervals for the same
// amount. It is trying to put all intervals inside
// chart area range.
ShiftIntervals( startOfIntervals, endOfIntervals, startArea, endArea );
return true;
}
/// <summary>
/// This method reduces empty space between intervals.
/// </summary>
/// <param name="startOfIntervals">The start positions of intervals.</param>
/// <param name="endOfIntervals">The end positions of intervals.</param>
/// <param name="reduction">Relative value which presents size reduction.</param>
private void ReduceEmptySpace( double [] startOfIntervals, double [] endOfIntervals, double reduction )
{
for( int intervalIndex = 0; intervalIndex < startOfIntervals.Length - 1; intervalIndex++ )
{
// Check overlapping
if( startOfIntervals[ intervalIndex + 1 ] < endOfIntervals[ intervalIndex ] )
{
//throw new InvalidOperationException( SR.ExceptionPieIntervalsOverlapping );
}
// Reduce space
double shift = ( startOfIntervals[ intervalIndex + 1 ] - endOfIntervals[ intervalIndex ] ) - ( startOfIntervals[ intervalIndex + 1 ] - endOfIntervals[ intervalIndex ] ) * reduction;
for( int reductionIndex = intervalIndex + 1; reductionIndex < startOfIntervals.Length; reductionIndex++ )
{
startOfIntervals[ reductionIndex ] -= shift;
endOfIntervals[ reductionIndex ] -= shift;
}
}
}
/// <summary>
/// This algorithm shifts all intervals for the same
/// amount. It is trying to put all intervals inside
/// chart area range.
/// </summary>
/// <param name="startOfIntervals">The start positions of intervals.</param>
/// <param name="endOfIntervals">The end positions of intervals.</param>
/// <param name="startArea">Start position of chart area vertical range.</param>
/// <param name="endArea">End position of chart area vertical range.</param>
private void ShiftIntervals( double [] startOfIntervals, double [] endOfIntervals, double startArea, double endArea )
{
double shift = 0;
if( startOfIntervals[ 0 ] < startArea )
{
shift = startArea - startOfIntervals[ 0 ];
}
else if( endOfIntervals[ endOfIntervals.Length - 1 ] > endArea )
{
shift = endArea - endOfIntervals[ endOfIntervals.Length - 1 ];
}
for( int index = 0; index < startOfIntervals.Length; index++ )
{
startOfIntervals[ index ] += shift;
endOfIntervals[ index ] += shift;
}
}
/// <summary>
/// This is used to find non overlapping position for intervals.
/// </summary>
/// <param name="startOfIntervals">The start positions of intervals.</param>
/// <param name="endOfIntervals">The end positions of intervals.</param>
/// <returns>Returns true if any label overlaps before method is used.</returns>
private void ShiftOverlappingIntervals( double [] startOfIntervals, double [] endOfIntervals )
{
// Invalidation
if( startOfIntervals.Length != endOfIntervals.Length )
{
throw new InvalidOperationException(SR.ExceptionPieIntervalsInvalid);
}
// Find first overlaping intervals
for( int index = 0; index < startOfIntervals.Length - 1; index++ )
{
// Intervals overlap
if( endOfIntervals[ index ] > startOfIntervals[ index + 1 ] )
{
double overlapRange = endOfIntervals[ index ] - startOfIntervals[ index + 1 ];
SpreadInterval( startOfIntervals, endOfIntervals, index, Math.Floor( overlapRange / 2.0 ) );
}
}
}
/// <summary>
/// This method spread all intervals down or up from
/// splitIndex. Intervals are spread only if there is no
/// empty space which will compensate shifting of intervals.
/// </summary>
/// <param name="startOfIntervals">The start positions of intervals.</param>
/// <param name="endOfIntervals">The end positions of intervals.</param>
/// <param name="splitIndex">Position of the interval which ovelap.</param>
/// <param name="overlapShift">The half of the overlapping range.</param>
private void SpreadInterval( double [] startOfIntervals, double [] endOfIntervals, int splitIndex, double overlapShift )
{
// Move first overlapping intervals.
endOfIntervals[ splitIndex ] -= overlapShift;
startOfIntervals[ splitIndex ] -= overlapShift;
endOfIntervals[ splitIndex + 1 ] += overlapShift;
startOfIntervals[ splitIndex + 1 ] += overlapShift;
// Move up other intervals if there is no enough empty space
// to compensate overlapping intervals.
if( splitIndex > 0 )
{
for( int index = splitIndex - 1; index >= 0; index-- )
{
if( endOfIntervals[ index ] > startOfIntervals[ index + 1 ] - overlapShift )
{
endOfIntervals[ index ] -= overlapShift;
startOfIntervals[ index ] -= overlapShift;
}
else
{
break;
}
}
}
// Move down other intervals if there is no enough empty space
// to compensate overlapping intervals.
if( splitIndex + 2 < startOfIntervals.Length - 1 )
{
for( int index = splitIndex + 2; index < startOfIntervals.Length; index++ )
{
if( startOfIntervals[ index ] > endOfIntervals[ index - 1 ] + overlapShift )
{
endOfIntervals[ index ] += overlapShift;
startOfIntervals[ index ] += overlapShift;
}
else
{
break;
}
}
}
}
#endregion
#region Y values related methods
/// <summary>
/// Helper function, which returns the Y value of the point.
/// </summary>
/// <param name="common">Chart common elements.</param>
/// <param name="area">Chart area the series belongs to.</param>
/// <param name="series">Sereis of the point.</param>
/// <param name="point">Point object.</param>
/// <param name="pointIndex">Index of the point.</param>
/// <param name="yValueIndex">Index of the Y value to get.</param>
/// <returns>Y value of the point.</returns>
virtual public double GetYValue(
CommonElements common,
ChartArea area,
Series series,
DataPoint point,
int pointIndex,
int yValueIndex)
{
return point.YValues[yValueIndex];
}
#endregion
#region 3D painting and selection methods
/// <summary>
/// This method recalculates position of pie slices
/// or checks if pie slice is selected.
/// </summary>
/// <param name="selection">If True selection mode is active, otherwise paint mode is active</param>
/// <param name="graph">The Chart Graphics object</param>
/// <param name="common">The Common elements object</param>
/// <param name="area">Chart area for this chart</param>
/// <param name="pieWidth">Pie width.</param>
private void ProcessChartType3D(
bool selection,
ChartGraphics graph,
CommonElements common,
ChartArea area,
float pieWidth )
{
string explodedAttrib = ""; // Exploded attribute
bool exploded; // Exploded pie slice
float midAngle; // Angle between Start Angle and End Angle
// Data series collection
SeriesCollection dataSeries = common.DataManager.Series;
// All data series from chart area which have Pie chart type
List<string> typeSeries = area.GetSeriesFromChartType(Name);
if( typeSeries.Count == 0 )
{
return;
}
// Get first pie starting angle
if (dataSeries[typeSeries[0]].IsCustomPropertySet(CustomPropertyName.PieStartAngle))
{
int angle;
bool parseSucceed = int.TryParse(dataSeries[typeSeries[0]][CustomPropertyName.PieStartAngle], NumberStyles.Any, CultureInfo.InvariantCulture, out angle);
if (parseSucceed)
{
if (angle > 180 && angle <= 360)
{
angle = -(360 - angle);
}
area.Area3DStyle.Rotation = angle;
}
if (!parseSucceed || area.Area3DStyle.Rotation > 180 || area.Area3DStyle.Rotation < -180)
{
throw (new InvalidOperationException(SR.ExceptionCustomAttributeAngleOutOfRange("PieStartAngle")));
}
}
// Call Back Paint event
if( !selection )
{
common.Chart.CallOnPrePaint(new ChartPaintEventArgs(dataSeries[typeSeries[0]], graph, common, area.PlotAreaPosition));
}
// The data points loop. Find Sum of data points.
double sum = 0;
foreach( DataPoint point in dataSeries[typeSeries[0]].Points )
{
if( !point.IsEmpty )
{
sum += Math.Abs(point.YValues[0]);
}
}
// Is exploded if only one is exploded
bool isExploded = false;
foreach( DataPoint point in dataSeries[typeSeries[0]].Points )
{
if(point.IsCustomPropertySet(CustomPropertyName.Exploded))
{
explodedAttrib = point[CustomPropertyName.Exploded];
if( String.Compare(explodedAttrib,"true",StringComparison.OrdinalIgnoreCase) == 0 )
{
isExploded = true;
}
}
}
// Take radius attribute
float doughnutRadius = 60f;
if(dataSeries[typeSeries[0]].IsCustomPropertySet(CustomPropertyName.DoughnutRadius))
{
doughnutRadius = CommonElements.ParseFloat(dataSeries[typeSeries[0]][CustomPropertyName.DoughnutRadius] );
// Validation
if( doughnutRadius < 0f || doughnutRadius > 99f )
throw (new ArgumentException(SR.ExceptionPieRadiusInvalid));
}
// Take 3D Label Line Size attribute
float labelLineSize = 100f;
if(dataSeries[typeSeries[0]].IsCustomPropertySet(CustomPropertyName._3DLabelLineSize))
{
labelLineSize = CommonElements.ParseFloat(dataSeries[typeSeries[0]][CustomPropertyName._3DLabelLineSize] );
// Validation
if( labelLineSize < 30f || labelLineSize > 200f )
throw (new ArgumentException(SR.ExceptionPie3DLabelLineSizeInvalid));
}
labelLineSize = labelLineSize * 0.1F / 100F;
//************************************************************
//** Data point loop
//************************************************************
float [] startAngleList;
float [] sweepAngleList;
int [] pointIndexList;
// This method is introduced to check colors of palette. For
// pie chart the first pie slice and the second pie slice can
// not have same color because they are connected.
CheckPaleteColors( dataSeries[typeSeries[0]].Points );
bool sameBackFront;
DataPoint [] points = PointOrder( dataSeries[typeSeries[0]], area, out startAngleList, out sweepAngleList, out pointIndexList, out sameBackFront );
// There are no points or all points are empty.
if( points == null )
{
return;
}
RectangleF plotingRectangle = new RectangleF( area.Position.ToRectangleF().X + 1, area.Position.ToRectangleF().Y + 1, area.Position.ToRectangleF().Width-2, area.Position.ToRectangleF().Height-2 );
// Check if any data point has outside label
bool outside = false;
foreach( DataPoint point in points )
{
if( GetLabelStyle( point ) == PieLabelStyle.Outside )
{
outside = true;
}
}
// If outside labels resize Pie size
if( outside )
{
InitPieSize( graph, area, ref plotingRectangle, ref pieWidth, points, startAngleList, sweepAngleList, dataSeries[typeSeries[0]], labelLineSize );
}
// Initialize Matrix 3D
area.matrix3D.Initialize(
plotingRectangle,
pieWidth,
area.Area3DStyle.Inclination,
0F,
0,
false);
//***********************************************************
//** Initialize Lighting
//***********************************************************
area.matrix3D.InitLight(
area.Area3DStyle.LightStyle
);
// Turns are introduce because of special case – Big pie slice, which
// is bigger, then 180 degree and it is back and
// front point in same time. If special case exists drawing has to be split
// into 4 parts: 1. Drawing back pie slices, 2. Drawing the first part of
// big slice and other points, 3. Drawing second part of big slice and
// 4. Drawing top of the pie slices.
for( int turn = 0; turn < 5; turn++ )
{
int pointIndx = 0;
foreach( DataPoint point in points )
{
// Reset point anchor location
point.positionRel = PointF.Empty;
// Do not process empty points
if( point.IsEmpty )
{
pointIndx++;
continue;
}
float sweepAngle = sweepAngleList[pointIndx];
float startAngle = startAngleList[pointIndx];
// Rectangle size
RectangleF rectangle;
if( area.InnerPlotPosition.Auto )
rectangle = new RectangleF( plotingRectangle.X, plotingRectangle.Y, plotingRectangle.Width, plotingRectangle.Height );
else
rectangle = new RectangleF( area.PlotAreaPosition.ToRectangleF().X, area.PlotAreaPosition.ToRectangleF().Y, area.PlotAreaPosition.ToRectangleF().Width, area.PlotAreaPosition.ToRectangleF().Height );
// Find smallest edge
SizeF absoluteSize = graph.GetAbsoluteSize( new SizeF( rectangle.Width, rectangle.Height ) );
float absRadius = ( absoluteSize.Width < absoluteSize.Height ) ? absoluteSize.Width : absoluteSize.Height;
// Size of the square, which will be used for drawing pie.
SizeF relativeSize = graph.GetRelativeSize( new SizeF( absRadius, absRadius ) );
// Center of the pie
PointF middlePoint = new PointF( rectangle.X + rectangle.Width / 2, rectangle.Y + rectangle.Height / 2 );
// Rectangle which will always create circle, never ellipse.
rectangle = new RectangleF( middlePoint.X - relativeSize.Width / 2, middlePoint.Y - relativeSize.Height / 2, relativeSize.Width, relativeSize.Height );
// Check Exploded attribute for data point
exploded = false;
if(point.IsCustomPropertySet(CustomPropertyName.Exploded))
{
explodedAttrib = point[CustomPropertyName.Exploded];
if( String.Compare(explodedAttrib,"true",StringComparison.OrdinalIgnoreCase) == 0 )
exploded = true;
else
exploded = false;
}
// Size correction because of exploded or labels
float sizeCorrection = 1.0F;
if( isExploded )
{
sizeCorrection = 0.82F;
rectangle.X += rectangle.Width * ( 1 - sizeCorrection ) / 2;
rectangle.Y += rectangle.Height * ( 1 - sizeCorrection ) / 2;
rectangle.Width = rectangle.Width * sizeCorrection;
rectangle.Height = rectangle.Height * sizeCorrection;
}
// Find Direction to move exploded pie slice
if( exploded )
{
_sliceExploded = true;
midAngle = ( 2 * startAngle + sweepAngle ) / 2;
double xComponent = Math.Cos( midAngle * Math.PI / 180 ) * rectangle.Width / 10;
double yComponent = Math.Sin( midAngle * Math.PI / 180 ) * rectangle.Height / 10;
rectangle.Offset( (float)xComponent, (float)yComponent );
}
// Adjust inner plot position
if(area.InnerPlotPosition.Auto)
{
RectangleF rect = rectangle;
rect.X = (rect.X - area.Position.X) / area.Position.Width * 100f;
rect.Y = (rect.Y - area.Position.Y) / area.Position.Height * 100f;
rect.Width = rect.Width / area.Position.Width * 100f;
rect.Height = rect.Height / area.Position.Height * 100f;
area.InnerPlotPosition.SetPositionNoAuto(rect.X, rect.Y, rect.Width, rect.Height);
}
// Start Svg Selection mode
graph.StartHotRegion( point );
// Drawing or selection of pie clice
Draw3DPie( turn, graph, point, area, rectangle, startAngle, sweepAngle, doughnutRadius, pieWidth, sameBackFront, exploded, pointIndexList[pointIndx] );
// End Svg Selection mode
graph.EndHotRegion( );
if( turn == 1 )
{
// Outside labels
if( GetLabelStyle( point ) == PieLabelStyle.Outside )
{
FillPieLabelOutside( graph, area, rectangle, pieWidth, point, startAngle, sweepAngle, pointIndx, doughnutRadius, exploded );
}
}
if( turn == 2 )
{
// Outside labels
if( GetLabelStyle( point ) == PieLabelStyle.Outside && pointIndx == 0 )
{
labelColumnLeft.Sort();
labelColumnLeft.AdjustPositions();
labelColumnRight.Sort();
labelColumnRight.AdjustPositions();
}
}
// Increae point index
pointIndx++;
}
}
// Call Paint event
if( !selection )
{
common.Chart.CallOnPostPaint(new ChartPaintEventArgs(dataSeries[typeSeries[0]], graph, common, area.PlotAreaPosition));
}
}
/// <summary>
/// This method draws a part of a pie slice. Which part is drown
/// depend on turn. There is special case if there is a big pie
/// slice (>180) when one pie slice has to be split on parts
/// and between that other small pie slices has to be drawn.
/// </summary>
/// <param name="turn">Turn for drawing.</param>
/// <param name="graph">Chart Graphics</param>
/// <param name="point">Data Point to draw</param>
/// <param name="area">Chart area</param>
/// <param name="rectangle">Rectangle used for drawing pie clice.</param>
/// <param name="startAngle">Start angle for pie slice</param>
/// <param name="sweepAngle">End angle for pie slice</param>
/// <param name="doughnutRadius">Inner Radius if chart is doughnut</param>
/// <param name="pieWidth">Width of the pie</param>
/// <param name="sameBackFront">Pie slice is >180 and same pie slice is back and front slice</param>
/// <param name="exploded">Pie slice is exploded</param>
/// <param name="pointIndex">Point Index</param>
private void Draw3DPie(
int turn,
ChartGraphics graph,
DataPoint point,
ChartArea area,
RectangleF rectangle,
float startAngle,
float sweepAngle,
float doughnutRadius,
float pieWidth,
bool sameBackFront,
bool exploded,
int pointIndex
)
{
SolidBrush brush = new SolidBrush(point.Color);
// For lightStyle style Non, Border color always exist.
Color penColor = Color.Empty;
Color penCurveColor = Color.Empty;
if( point.BorderColor == Color.Empty && area.Area3DStyle.LightStyle == LightStyle.None )
{
penColor = ChartGraphics.GetGradientColor( point.Color, Color.Black, 0.5 );
}
else if( point.BorderColor == Color.Empty )
{
penColor = point.Color;
}
else
{
penColor = point.BorderColor;
}
if( point.BorderColor != Color.Empty || area.Area3DStyle.LightStyle == LightStyle.None )
{
penCurveColor = penColor;
}
Pen pen = new Pen(penColor, point.BorderWidth);
pen.DashStyle = graph.GetPenStyle( point.BorderDashStyle );
// Pen for back side slice.
Pen backSlicePen;
if( point.BorderColor == Color.Empty )
{
backSlicePen = new Pen(point.Color);
}
else
{
backSlicePen = pen;
}
Pen penCurve = new Pen(penCurveColor, point.BorderWidth);
penCurve.DashStyle = graph.GetPenStyle( point.BorderDashStyle );
// Set Border Width;
PointF [] points = GetPiePoints( graph, area, pieWidth, rectangle, startAngle, sweepAngle, true, doughnutRadius, exploded );
if( points == null )
return;
// Remember data point anchor location
point.positionRel.X = points[(int)PiePoints.TopLabelLine].X;
point.positionRel.Y = points[(int)PiePoints.TopLabelLine].Y;
point.positionRel = graph.GetRelativePoint(point.positionRel);
float midAngle = startAngle + sweepAngle / 2F;
float endAngle = startAngle + sweepAngle;
if( turn == 0 )
{
// Draw back pie slice (do not fill).
// Used for transparency.
if( !this.Doughnut )
{
graph.FillPieSlice(
area,
point,
brush,
backSlicePen,
points[(int)PiePoints.BottomRectTopLeftPoint],
points[(int)PiePoints.BottomStart],
points[(int)PiePoints.BottomRectBottomRightPoint],
points[(int)PiePoints.BottomEnd],
points[(int)PiePoints.BottomCenter],
startAngle,
sweepAngle,
false,
pointIndex
);
}
else
{
graph.FillDoughnutSlice(
area,
point,
brush,
backSlicePen,
points[(int)PiePoints.BottomRectTopLeftPoint],
points[(int)PiePoints.BottomStart],
points[(int)PiePoints.BottomRectBottomRightPoint],
points[(int)PiePoints.BottomEnd],
points[(int)PiePoints.DoughnutBottomEnd],
points[(int)PiePoints.DoughnutBottomStart],
startAngle,
sweepAngle,
false,
doughnutRadius,
pointIndex
);
}
}
else if( turn == 1 )
{
// Case when there is big pie slice ( > 180 ) and big slice is
// back and front point in same time.
if( sameBackFront )
{
// Draw the first part of the curve of the big slice and
// all curves from other slices. Big pie slice could be on the
// right or the left side.
if( midAngle > -90 && midAngle < 90 || midAngle > 270 && midAngle < 450 )
{
// Draw Inner Arc for Doughnut
if( Doughnut )
{
DrawDoughnutCurves( graph, area, point, startAngle, sweepAngle, points, brush, penCurve, false, true, pointIndex );
}
DrawPieCurves( graph, area, point, startAngle, sweepAngle, points, brush, penCurve, true, true, pointIndex );
}
else
{
// Draw Inner Arc for Doughnut
if( Doughnut )
{
DrawDoughnutCurves( graph, area, point, startAngle, sweepAngle, points, brush, penCurve, true, true, pointIndex );
}
DrawPieCurves( graph, area, point, startAngle, sweepAngle, points, brush, penCurve, false, true, pointIndex );
}
// Draw sides of pie slices
graph.FillPieSides( area, area.Area3DStyle.Inclination, startAngle, sweepAngle, points, brush, pen, Doughnut );
}
else
{
// Draw Inner Arc for Doughnut
if( Doughnut )
{
DrawDoughnutCurves( graph, area, point, startAngle, sweepAngle, points, brush, penCurve, false, false, pointIndex );
}
// This is regular case. There is no big pie slice
// which is back nad front point in same time.
graph.FillPieSides( area, area.Area3DStyle.Inclination, startAngle, sweepAngle, points, brush, pen, Doughnut );
DrawPieCurves( graph, area, point, startAngle, sweepAngle, points, brush, penCurve, false, false, pointIndex );
}
}
else if( turn == 2 )
{
// This second turned is used only for big pie slice (>180). If big pie
// slice exist it has to be split if it is necessary. If the big pie slice
// cover other pie slice from both sides, the big pie slice have to curves.
// The first curve from big pie slice is drawn first, after that all other
// pie slices and at the end second curve from big pie slice.
if( sameBackFront && sweepAngle > 180 )
{
// Condition when two draw Doughnut arcs after sides for big pie slice ( > 180 ).
bool BackFrontDoughnut = ( startAngle > -180 && startAngle < 0 || startAngle > 180 && startAngle < 360 ) && ( endAngle > -180 && endAngle < 0 || endAngle > 180 && endAngle < 360 );
if( area.Area3DStyle.Inclination > 0 )
BackFrontDoughnut = !BackFrontDoughnut;
if( midAngle > -90 && midAngle < 90 || midAngle > 270 && midAngle < 450 )
{
// Draw Inner Arc for Doughnut
if( Doughnut )
{
// Draw second part of doughnut curve only for very big slices > 300 ( Visibility issue for Big point depth ).
if( BackFrontDoughnut && sweepAngle > 300 )
{
DrawDoughnutCurves( graph, area, point, startAngle, sweepAngle, points, brush, penCurve, true, true, pointIndex );
}
}
DrawPieCurves( graph, area, point, startAngle, sweepAngle, points, brush, penCurve, false, true, pointIndex );
}
else
{
// Draw Inner Arc for Doughnut
if( Doughnut )
{
// Draw second part of doughnut curve only for very big slices > 300( Visibility issue for Big point depth ).
if( BackFrontDoughnut && sweepAngle > 300 )
{
DrawDoughnutCurves( graph, area, point, startAngle, sweepAngle, points, brush, penCurve, false, true, pointIndex );
}
}
DrawPieCurves( graph, area, point, startAngle, sweepAngle, points, brush, penCurve, true, true, pointIndex );
}
}
}
else if( turn == 3 )
{
if( !this.Doughnut )
{
// Fill pie slice
graph.FillPieSlice(
area,
point,
brush,
pen,
points[(int)PiePoints.TopRectTopLeftPoint],
points[(int)PiePoints.TopStart],
points[(int)PiePoints.TopRectBottomRightPoint],
points[(int)PiePoints.TopEnd],
points[(int)PiePoints.TopCenter],
startAngle,
sweepAngle,
true,
pointIndex
);
// Draw Border
graph.FillPieSlice(
area,
point,
brush,
pen,
points[(int)PiePoints.TopRectTopLeftPoint],
points[(int)PiePoints.TopStart],
points[(int)PiePoints.TopRectBottomRightPoint],
points[(int)PiePoints.TopEnd],
points[(int)PiePoints.TopCenter],
startAngle,
sweepAngle,
false,
pointIndex
);
}
else
{
// Fill
graph.FillDoughnutSlice(
area,
point,
brush,
pen,
points[(int)PiePoints.TopRectTopLeftPoint],
points[(int)PiePoints.TopStart],
points[(int)PiePoints.TopRectBottomRightPoint],
points[(int)PiePoints.TopEnd],
points[(int)PiePoints.DoughnutTopEnd],
points[(int)PiePoints.DoughnutTopStart],
startAngle,
sweepAngle,
true,
doughnutRadius,
pointIndex
);
// Draw Border
graph.FillDoughnutSlice(
area,
point,
brush,
pen,
points[(int)PiePoints.TopRectTopLeftPoint],
points[(int)PiePoints.TopStart],
points[(int)PiePoints.TopRectBottomRightPoint],
points[(int)PiePoints.TopEnd],
points[(int)PiePoints.DoughnutTopEnd],
points[(int)PiePoints.DoughnutTopStart],
startAngle,
sweepAngle,
false,
doughnutRadius,
pointIndex
);
}
// Draw 3D Outside labels
if( GetLabelStyle( point ) == PieLabelStyle.Outside )
{
// Check if special color properties are set
Color pieLineColor = pen.Color;
if(point.IsCustomPropertySet(CustomPropertyName.PieLineColor) || (point.series != null && point.series.IsCustomPropertySet(CustomPropertyName.PieLineColor)) )
{
ColorConverter colorConverter = new ColorConverter();
bool failed = false;
try
{
if(point.IsCustomPropertySet(CustomPropertyName.PieLineColor))
{
pieLineColor = (Color)colorConverter.ConvertFromString(point[CustomPropertyName.PieLineColor]);
}
else if(point.series != null && point.series.IsCustomPropertySet(CustomPropertyName.PieLineColor))
{
pieLineColor = (Color)colorConverter.ConvertFromString(point.series[CustomPropertyName.PieLineColor]);
}
}
catch (ArgumentException)
{
failed = true;
}
catch (NotSupportedException)
{
failed = true;
}
if(failed)
{
if(point.IsCustomPropertySet(CustomPropertyName.PieLineColor))
{
pieLineColor = (Color)colorConverter.ConvertFromInvariantString(point[CustomPropertyName.PieLineColor]);
}
else if(point.series != null && point.series.IsCustomPropertySet(CustomPropertyName.PieLineColor))
{
pieLineColor = (Color)colorConverter.ConvertFromInvariantString(point.series[CustomPropertyName.PieLineColor]);
}
}
}
// Draw labels
using (Pen labelPen = new Pen(pieLineColor, pen.Width))
{
Draw3DOutsideLabels(graph, area, labelPen, points, point, midAngle, pointIndex);
}
}
}
else
{
// Draw 3D Inside labels
if( GetLabelStyle( point ) == PieLabelStyle.Inside )
{
Draw3DInsideLabels( graph, points, point, pointIndex );
}
}
//Clean up resources
if (brush!=null)
brush.Dispose();
if (pen != null)
pen.Dispose();
if (penCurve != null)
penCurve.Dispose();
}
/// <summary>
/// This method transforms in 3D space important points for
/// doughnut or pie slice.
/// </summary>
/// <param name="graph">Chart Graphics</param>
/// <param name="area">Chart Area</param>
/// <param name="pieWidth">The width of a pie.</param>
/// <param name="rectangle">Rectangle used for drawing pie clice.</param>
/// <param name="startAngle">Start angle for pie slice.</param>
/// <param name="sweepAngle">End angle for pie slice.</param>
/// <param name="relativeCoordinates">true if relative coordinates has to be returned.</param>
/// <param name="doughnutRadius">Doughnut Radius</param>
/// <param name="exploded">Exploded pie slice</param>
/// <returns>Returns 3D Transformed pie or doughnut points.</returns>
private PointF [] GetPiePoints(
ChartGraphics graph,
ChartArea area,
float pieWidth,
RectangleF rectangle,
float startAngle,
float sweepAngle,
bool relativeCoordinates,
float doughnutRadius,
bool exploded
)
{
doughnutRadius = 1 - doughnutRadius / 100F;
Point3D [] points;
PointF [] result;
// Doughnut chart has 12 more points
if( Doughnut )
{
points = new Point3D[29];
result = new PointF[29];
}
else
{
points = new Point3D[17];
result = new PointF[17];
}
// Angle 180 Top point on the arc
points[(int)PiePoints.Top180] = new Point3D(
rectangle.X + (float)Math.Cos( 180 * Math.PI / 180 ) * rectangle.Width / 2F + rectangle.Width / 2F,
rectangle.Y + (float)Math.Sin( 180 * Math.PI / 180 ) * rectangle.Height / 2F + rectangle.Height / 2F,
pieWidth );
// Angle 180 Bottom point on the arc
points[(int)PiePoints.Bottom180] = new Point3D(
rectangle.X + (float)Math.Cos( 180 * Math.PI / 180 ) * rectangle.Width / 2F + rectangle.Width / 2F,
rectangle.Y + (float)Math.Sin( 180 * Math.PI / 180 ) * rectangle.Height / 2F + rectangle.Height / 2F,
0 );
// Angle 0 Top point on the arc
points[(int)PiePoints.Top0] = new Point3D(
rectangle.X + (float)Math.Cos( 0 * Math.PI / 180 ) * rectangle.Width / 2F + rectangle.Width / 2F,
rectangle.Y + (float)Math.Sin( 0 * Math.PI / 180 ) * rectangle.Height / 2F + rectangle.Height / 2F,
pieWidth );
// Angle 0 Bottom point on the arc
points[(int)PiePoints.Bottom0] = new Point3D(
rectangle.X + (float)Math.Cos( 0 * Math.PI / 180 ) * rectangle.Width / 2F + rectangle.Width / 2F,
rectangle.Y + (float)Math.Sin( 0 * Math.PI / 180 ) * rectangle.Height / 2F + rectangle.Height / 2F,
0 );
// Top Start Angle point on the arc
points[(int)PiePoints.TopStart] = new Point3D(
rectangle.X + (float)Math.Cos( startAngle * Math.PI / 180 ) * rectangle.Width / 2F + rectangle.Width / 2F,
rectangle.Y + (float)Math.Sin( startAngle * Math.PI / 180 ) * rectangle.Height / 2F + rectangle.Height / 2F,
pieWidth );
// Top End Angle point on the arc
points[(int)PiePoints.TopEnd] = new Point3D(
rectangle.X + (float)Math.Cos( ( startAngle + sweepAngle ) * Math.PI / 180 ) * rectangle.Width / 2F + rectangle.Width / 2F,
rectangle.Y + (float)Math.Sin( ( startAngle + sweepAngle ) * Math.PI / 180 ) * rectangle.Height / 2F + rectangle.Height / 2F,
pieWidth );
// Bottom Start Angle point on the arc
points[(int)PiePoints.BottomStart] = new Point3D(
rectangle.X + (float)Math.Cos( startAngle * Math.PI / 180 ) * rectangle.Width / 2F + rectangle.Width / 2F,
rectangle.Y + (float)Math.Sin( startAngle * Math.PI / 180 ) * rectangle.Height / 2F + rectangle.Height / 2F,
0 );
// Bottom End Angle point on the arc
points[(int)PiePoints.BottomEnd] = new Point3D(
rectangle.X + (float)Math.Cos( ( startAngle + sweepAngle ) * Math.PI / 180 ) * rectangle.Width / 2F + rectangle.Width / 2F,
rectangle.Y + (float)Math.Sin( ( startAngle + sweepAngle ) * Math.PI / 180 ) * rectangle.Height / 2F + rectangle.Height / 2F,
0 );
// Center Top
points[(int)PiePoints.TopCenter] = new Point3D(
rectangle.X + rectangle.Width / 2F,
rectangle.Y + rectangle.Height / 2F,
pieWidth );
// Center Bottom
points[(int)PiePoints.BottomCenter] = new Point3D(
rectangle.X + rectangle.Width / 2F,
rectangle.Y + rectangle.Height / 2F,
0 );
// Top Label Line
points[(int)PiePoints.TopLabelLine] = new Point3D(
rectangle.X + (float)Math.Cos( ( startAngle + sweepAngle / 2 ) * Math.PI / 180 ) * rectangle.Width / 2F + rectangle.Width / 2F,
rectangle.Y + (float)Math.Sin( ( startAngle + sweepAngle / 2 ) * Math.PI / 180 ) * rectangle.Height / 2F + rectangle.Height / 2F,
pieWidth );
// If Pie slice is exploded Label line out size is changed
float sizeOut;
if( exploded )
{
sizeOut = 1.1F;
}
else
{
sizeOut = 1.3F;
}
// Top Label Line Out
points[(int)PiePoints.TopLabelLineout] = new Point3D(
rectangle.X + (float)Math.Cos( ( startAngle + sweepAngle / 2 ) * Math.PI / 180 ) * rectangle.Width * sizeOut / 2F + rectangle.Width / 2F,
rectangle.Y + (float)Math.Sin( ( startAngle + sweepAngle / 2 ) * Math.PI / 180 ) * rectangle.Height * sizeOut / 2F + rectangle.Height / 2F,
pieWidth );
// Top Label Center
if( this.Doughnut )
{
points[(int)PiePoints.TopLabelCenter] = new Point3D(
rectangle.X + (float)Math.Cos( ( startAngle + sweepAngle / 2 ) * Math.PI / 180 ) * rectangle.Width * ( 1 + doughnutRadius ) / 4F + rectangle.Width / 2F,
rectangle.Y + (float)Math.Sin( ( startAngle + sweepAngle / 2 ) * Math.PI / 180 ) * rectangle.Height * ( 1 + doughnutRadius ) / 4F + rectangle.Height / 2F,
pieWidth );
}
else
{
points[(int)PiePoints.TopLabelCenter] = new Point3D(
rectangle.X + (float)Math.Cos( ( startAngle + sweepAngle / 2 ) * Math.PI / 180 ) * rectangle.Width * 0.5F / 2F + rectangle.Width / 2F,
rectangle.Y + (float)Math.Sin( ( startAngle + sweepAngle / 2 ) * Math.PI / 180 ) * rectangle.Height * 0.5F / 2F + rectangle.Height / 2F,
pieWidth );
}
// Top Rectangle Top Left Point
points[(int)PiePoints.TopRectTopLeftPoint] = new Point3D(rectangle.X,rectangle.Y,pieWidth);
// Top Rectangle Right Bottom Point
points[(int)PiePoints.TopRectBottomRightPoint] = new Point3D(rectangle.Right,rectangle.Bottom,pieWidth);
// Bottom Rectangle Top Left Point
points[(int)PiePoints.BottomRectTopLeftPoint] = new Point3D(rectangle.X,rectangle.Y,0);
// Bottom Rectangle Right Bottom Point
points[(int)PiePoints.BottomRectBottomRightPoint] = new Point3D(rectangle.Right,rectangle.Bottom,0);
if( Doughnut )
{
// Angle 180 Top point on the Doughnut arc
points[(int)PiePoints.DoughnutTop180] = new Point3D(
rectangle.X + (float)Math.Cos( 180 * Math.PI / 180 ) * rectangle.Width * doughnutRadius / 2F + rectangle.Width / 2F,
rectangle.Y + (float)Math.Sin( 180 * Math.PI / 180 ) * rectangle.Height * doughnutRadius / 2F + rectangle.Height / 2F,
pieWidth );
// Angle 180 Bottom point on the Doughnut arc
points[(int)PiePoints.DoughnutBottom180] = new Point3D(
rectangle.X + (float)Math.Cos( 180 * Math.PI / 180 ) * rectangle.Width * doughnutRadius / 2F + rectangle.Width / 2F,
rectangle.Y + (float)Math.Sin( 180 * Math.PI / 180 ) * rectangle.Height * doughnutRadius / 2F + rectangle.Height / 2F,
0 );
// Angle 0 Top point on the Doughnut arc
points[(int)PiePoints.DoughnutTop0] = new Point3D(
rectangle.X + (float)Math.Cos( 0 * Math.PI / 180 ) * rectangle.Width * doughnutRadius / 2F + rectangle.Width / 2F,
rectangle.Y + (float)Math.Sin( 0 * Math.PI / 180 ) * rectangle.Height * doughnutRadius / 2F + rectangle.Height / 2F,
pieWidth );
// Angle 0 Bottom point on the Doughnut arc
points[(int)PiePoints.DoughnutBottom0] = new Point3D(
rectangle.X + (float)Math.Cos( 0 * Math.PI / 180 ) * rectangle.Width * doughnutRadius / 2F + rectangle.Width / 2F,
rectangle.Y + (float)Math.Sin( 0 * Math.PI / 180 ) * rectangle.Height * doughnutRadius / 2F + rectangle.Height / 2F,
0 );
// Top Start Angle point on the Doughnut arc
points[(int)PiePoints.DoughnutTopStart] = new Point3D(
rectangle.X + (float)Math.Cos( startAngle * Math.PI / 180 ) * rectangle.Width * doughnutRadius / 2F + rectangle.Width / 2F,
rectangle.Y + (float)Math.Sin( startAngle * Math.PI / 180 ) * rectangle.Height * doughnutRadius / 2F + rectangle.Height / 2F,
pieWidth );
// Top End Angle point on the Doughnut arc
points[(int)PiePoints.DoughnutTopEnd] = new Point3D(
rectangle.X + (float)Math.Cos( ( startAngle + sweepAngle ) * Math.PI / 180 ) * rectangle.Width * doughnutRadius / 2F + rectangle.Width / 2F,
rectangle.Y + (float)Math.Sin( ( startAngle + sweepAngle ) * Math.PI / 180 ) * rectangle.Height * doughnutRadius / 2F + rectangle.Height / 2F,
pieWidth );
// Bottom Start Angle point on the Doughnut arc
points[(int)PiePoints.DoughnutBottomStart] = new Point3D(
rectangle.X + (float)Math.Cos( startAngle * Math.PI / 180 ) * rectangle.Width * doughnutRadius / 2F + rectangle.Width / 2F,
rectangle.Y + (float)Math.Sin( startAngle * Math.PI / 180 ) * rectangle.Height * doughnutRadius / 2F + rectangle.Height / 2F,
0 );
// Bottom End Angle point on the Doughnut arc
points[(int)PiePoints.DoughnutBottomEnd] = new Point3D(
rectangle.X + (float)Math.Cos( ( startAngle + sweepAngle ) * Math.PI / 180 ) * rectangle.Width * doughnutRadius / 2F + rectangle.Width / 2F,
rectangle.Y + (float)Math.Sin( ( startAngle + sweepAngle ) * Math.PI / 180 ) * rectangle.Height * doughnutRadius / 2F + rectangle.Height / 2F,
0 );
rectangle.Inflate( -rectangle.Width * (1 - doughnutRadius) / 2F, -rectangle.Height * (1 - doughnutRadius) / 2F);
// Doughnut Top Rectangle Top Left Point
points[(int)PiePoints.DoughnutTopRectTopLeftPoint] = new Point3D(rectangle.X,rectangle.Y,pieWidth);
// Doughnut Top Rectangle Right Bottom Point
points[(int)PiePoints.DoughnutTopRectBottomRightPoint] = new Point3D(rectangle.Right,rectangle.Bottom,pieWidth);
// Doughnut Bottom Rectangle Top Left Point
points[(int)PiePoints.DoughnutBottomRectTopLeftPoint] = new Point3D(rectangle.X,rectangle.Y,0);
// Doughnut Bottom Rectangle Right Bottom Point
points[(int)PiePoints.DoughnutBottomRectBottomRightPoint] = new Point3D(rectangle.Right,rectangle.Bottom,0);
}
// Make 3D transformations
area.matrix3D.TransformPoints(points);
int pointIndx = 0;
foreach( Point3D point in points )
{
result[pointIndx] = point.PointF;
// Convert Relative coordinates to absolute.
if( relativeCoordinates )
{
result[pointIndx] = graph.GetAbsolutePoint(result[pointIndx]);
}
pointIndx++;
}
return result;
}
#endregion
#region 3D Drawing surfaces
/// <summary>
/// This method is used for drawing curve around pie slices. This is
/// the most complex part of 3D Pie slice. There is special case if
/// pie slice is bigger then 180 degree.
/// </summary>
/// <param name="graph">Chart Grahics.</param>
/// <param name="area">Chart Area.</param>
/// <param name="dataPoint">Data Point used for pie slice.</param>
/// <param name="startAngle">Start angle of a pie slice.</param>
/// <param name="sweepAngle">Sweep angle of a pie slice.</param>
/// <param name="points">Important 3d points of a pie slice.</param>
/// <param name="brushWithoutLight">Brush without lithing efects.</param>
/// <param name="pen">Pen used for border.</param>
/// <param name="rightPosition">Position of the curve of big pie slice. Big pie slice coud have to visible curves - left and right</param>
/// <param name="sameBackFront">This is big pie slice which is in same time back and front slice.</param>
/// <param name="pointIndex">Data Point Index</param>
private void DrawPieCurves(
ChartGraphics graph,
ChartArea area,
DataPoint dataPoint,
float startAngle,
float sweepAngle,
PointF [] points,
SolidBrush brushWithoutLight,
Pen pen,
bool rightPosition,
bool sameBackFront,
int pointIndex
)
{
// Create a graphics path
using (GraphicsPath path = new GraphicsPath())
{
Brush brush;
if (area.Area3DStyle.LightStyle == LightStyle.None)
{
brush = brushWithoutLight;
}
else
{
brush = graph.GetGradientBrush(graph.GetAbsoluteRectangle(area.Position.ToRectangleF()), Color.FromArgb(brushWithoutLight.Color.A, 0, 0, 0), brushWithoutLight.Color, GradientStyle.VerticalCenter);
}
float endAngle = startAngle + sweepAngle;
// Very big pie slice ( > 180 degree )
if (sweepAngle > 180)
{
if (DrawPieCurvesBigSlice(graph, area, dataPoint, startAngle, sweepAngle, points, brush, pen, rightPosition, sameBackFront, pointIndex))
return;
}
// Pie slice pass throw 180 degree. Curve has to be spited.
if (startAngle < 180 && endAngle > 180)
{
if (area.Area3DStyle.Inclination < 0)
{
graph.FillPieCurve(
area,
dataPoint,
brush,
pen,
points[(int)PiePoints.TopRectTopLeftPoint],
points[(int)PiePoints.TopRectBottomRightPoint],
points[(int)PiePoints.BottomRectTopLeftPoint],
points[(int)PiePoints.BottomRectBottomRightPoint],
points[(int)PiePoints.TopStart],
points[(int)PiePoints.Top180],
points[(int)PiePoints.BottomStart],
points[(int)PiePoints.Bottom180],
startAngle,
180 - startAngle,
pointIndex
);
}
else
{
graph.FillPieCurve(
area,
dataPoint,
brush,
pen,
points[(int)PiePoints.TopRectTopLeftPoint],
points[(int)PiePoints.TopRectBottomRightPoint],
points[(int)PiePoints.BottomRectTopLeftPoint],
points[(int)PiePoints.BottomRectBottomRightPoint],
points[(int)PiePoints.Top180],
points[(int)PiePoints.TopEnd],
points[(int)PiePoints.Bottom180],
points[(int)PiePoints.BottomEnd],
180,
startAngle + sweepAngle - 180,
pointIndex
);
}
}
// Pie slice pass throw 0 degree. Curve has to be spited.
else if (startAngle < 0 && endAngle > 0)
{
if (area.Area3DStyle.Inclination > 0)
{
graph.FillPieCurve(
area,
dataPoint,
brush,
pen,
points[(int)PiePoints.TopRectTopLeftPoint],
points[(int)PiePoints.TopRectBottomRightPoint],
points[(int)PiePoints.BottomRectTopLeftPoint],
points[(int)PiePoints.BottomRectBottomRightPoint],
points[(int)PiePoints.TopStart],
points[(int)PiePoints.Top0],
points[(int)PiePoints.BottomStart],
points[(int)PiePoints.Bottom0],
startAngle,
-startAngle,
pointIndex
);
}
else
{
graph.FillPieCurve(
area,
dataPoint,
brush,
pen,
points[(int)PiePoints.TopRectTopLeftPoint],
points[(int)PiePoints.TopRectBottomRightPoint],
points[(int)PiePoints.BottomRectTopLeftPoint],
points[(int)PiePoints.BottomRectBottomRightPoint],
points[(int)PiePoints.Top0],
points[(int)PiePoints.TopEnd],
points[(int)PiePoints.Bottom0],
points[(int)PiePoints.BottomEnd],
0,
sweepAngle + startAngle,
pointIndex
);
}
}
// Pie slice pass throw 360 degree. Curve has to be spited.
else if (startAngle < 360 && endAngle > 360)
{
if (area.Area3DStyle.Inclination > 0)
{
graph.FillPieCurve(
area,
dataPoint,
brush,
pen,
points[(int)PiePoints.TopRectTopLeftPoint],
points[(int)PiePoints.TopRectBottomRightPoint],
points[(int)PiePoints.BottomRectTopLeftPoint],
points[(int)PiePoints.BottomRectBottomRightPoint],
points[(int)PiePoints.TopStart],
points[(int)PiePoints.Top0],
points[(int)PiePoints.BottomStart],
points[(int)PiePoints.Bottom0],
startAngle,
360 - startAngle,
pointIndex
);
}
else
{
graph.FillPieCurve(
area,
dataPoint,
brush,
pen,
points[(int)PiePoints.TopRectTopLeftPoint],
points[(int)PiePoints.TopRectBottomRightPoint],
points[(int)PiePoints.BottomRectTopLeftPoint],
points[(int)PiePoints.BottomRectBottomRightPoint],
points[(int)PiePoints.Top0],
points[(int)PiePoints.TopEnd],
points[(int)PiePoints.Bottom0],
points[(int)PiePoints.BottomEnd],
0,
endAngle - 360,
pointIndex
);
}
}
else
{
// ***************************************************
// REGULAR CASE: The curve is not split.
// ***************************************************
if (startAngle < 180 && startAngle >= 0 && area.Area3DStyle.Inclination < 0
|| startAngle < 540 && startAngle >= 360 && area.Area3DStyle.Inclination < 0
|| startAngle >= 180 && startAngle < 360 && area.Area3DStyle.Inclination > 0
|| startAngle >= -180 && startAngle < 0 && area.Area3DStyle.Inclination > 0
)
{
graph.FillPieCurve(
area,
dataPoint,
brush,
pen,
points[(int)PiePoints.TopRectTopLeftPoint],
points[(int)PiePoints.TopRectBottomRightPoint],
points[(int)PiePoints.BottomRectTopLeftPoint],
points[(int)PiePoints.BottomRectBottomRightPoint],
points[(int)PiePoints.TopStart],
points[(int)PiePoints.TopEnd],
points[(int)PiePoints.BottomStart],
points[(int)PiePoints.BottomEnd],
startAngle,
sweepAngle,
pointIndex
);
}
}
}
}
/// <summary>
/// This method is used for special case when big pie slice has to be drawn.
/// </summary>
/// <param name="graph">Chart Grahics.</param>
/// <param name="area">Chart Area.</param>
/// <param name="dataPoint">Data Point used for pie slice.</param>
/// <param name="startAngle">Start angle of a pie slice.</param>
/// <param name="sweepAngle">Sweep angle of a pie slice.</param>
/// <param name="points">Important 3d points of a pie slice.</param>
/// <param name="brush">Brush without lithing efects.</param>
/// <param name="pen">Pen used for border.</param>
/// <param name="rightPosition">Position of the curve of big pie slice. Big pie slice coud have to visible curves - left and right</param>
/// <param name="sameBackFront">This is big pie slice which is in same time back and front slice.</param>
/// <param name="pointIndex">Data Point Index</param>
/// <returns>True if slice is special case and it is drawn as a special case.</returns>
private bool DrawPieCurvesBigSlice
(
ChartGraphics graph,
ChartArea area,
DataPoint dataPoint,
float startAngle,
float sweepAngle,
PointF [] points,
Brush brush,
Pen pen,
bool rightPosition,
bool sameBackFront,
int pointIndex
)
{
float endAngle = startAngle + sweepAngle;
// Two different cases connected with X angle.
// *****************************************************
// X angle is positive
// *****************************************************
if( area.Area3DStyle.Inclination > 0 )
{
// Show curve from 0 to 180.
if( startAngle < 180 && endAngle > 360 )
{
graph.FillPieCurve(
area,
dataPoint,
brush,
pen,
points[(int)PiePoints.TopRectTopLeftPoint],
points[(int)PiePoints.TopRectBottomRightPoint],
points[(int)PiePoints.BottomRectTopLeftPoint],
points[(int)PiePoints.BottomRectBottomRightPoint],
points[(int)PiePoints.Top0],
points[(int)PiePoints.Top180],
points[(int)PiePoints.Bottom0],
points[(int)PiePoints.Bottom180],
0,
-180,
pointIndex
);
}
else if( startAngle < 0 && endAngle > 180 )
{
// There is big data point which is back and
// front point in same time.
if( sameBackFront )
{
// The big pie slice has to be split. This part makes
// decision which part of this big slice will be
// drawn first.
if( rightPosition )
{
graph.FillPieCurve(
area,
dataPoint,
brush,
pen,
points[(int)PiePoints.TopRectTopLeftPoint],
points[(int)PiePoints.TopRectBottomRightPoint],
points[(int)PiePoints.BottomRectTopLeftPoint],
points[(int)PiePoints.BottomRectBottomRightPoint],
points[(int)PiePoints.Top180],
points[(int)PiePoints.TopEnd],
points[(int)PiePoints.Bottom180],
points[(int)PiePoints.BottomEnd],
180,
endAngle - 180,
pointIndex
);
}
else
{
graph.FillPieCurve(
area,
dataPoint,
brush,
pen,
points[(int)PiePoints.TopRectTopLeftPoint],
points[(int)PiePoints.TopRectBottomRightPoint],
points[(int)PiePoints.BottomRectTopLeftPoint],
points[(int)PiePoints.BottomRectBottomRightPoint],
points[(int)PiePoints.TopStart],
points[(int)PiePoints.Top0],
points[(int)PiePoints.BottomStart],
points[(int)PiePoints.Bottom0],
startAngle,
-startAngle,
pointIndex
);
}
}
else
{
// There is big pie slice (>180), but that pie slice
// is not back and front point in same time.
graph.FillPieCurve(
area,
dataPoint,
brush,
pen,
points[(int)PiePoints.TopRectTopLeftPoint],
points[(int)PiePoints.TopRectBottomRightPoint],
points[(int)PiePoints.BottomRectTopLeftPoint],
points[(int)PiePoints.BottomRectBottomRightPoint],
points[(int)PiePoints.TopStart],
points[(int)PiePoints.Top0],
points[(int)PiePoints.BottomStart],
points[(int)PiePoints.Bottom0],
startAngle,
-startAngle,
pointIndex
);
graph.FillPieCurve(
area,
dataPoint,
brush,
pen,
points[(int)PiePoints.TopRectTopLeftPoint],
points[(int)PiePoints.TopRectBottomRightPoint],
points[(int)PiePoints.BottomRectTopLeftPoint],
points[(int)PiePoints.BottomRectBottomRightPoint],
points[(int)PiePoints.Top180],
points[(int)PiePoints.TopEnd],
points[(int)PiePoints.Bottom180],
points[(int)PiePoints.BottomEnd],
180,
endAngle - 180,
pointIndex
);
}
}
else
{
// Big pie slice behaves as normal pie slice. Continue
// Non special case alghoritham
return false;
}
}
// *********************************************
// X angle negative
// *********************************************
else
{
// Show curve from 0 to 180.
if( startAngle < 0 && endAngle > 180 )
{
graph.FillPieCurve(
area,
dataPoint,
brush,
pen,
points[(int)PiePoints.TopRectTopLeftPoint],
points[(int)PiePoints.TopRectBottomRightPoint],
points[(int)PiePoints.BottomRectTopLeftPoint],
points[(int)PiePoints.BottomRectBottomRightPoint],
points[(int)PiePoints.Top0],
points[(int)PiePoints.Top180],
points[(int)PiePoints.Bottom0],
points[(int)PiePoints.Bottom180],
0,
180,
pointIndex
);
}
else if( startAngle < 180 && endAngle > 360 )
{
// There is big data point which is back and
// front point in same time.
if( sameBackFront )
{
// The big pie slice has to be split. This part makes
// decision which part of this big slice will be
// drawn first.
if( rightPosition )
{
graph.FillPieCurve(
area,
dataPoint,
brush,
pen,
points[(int)PiePoints.TopRectTopLeftPoint],
points[(int)PiePoints.TopRectBottomRightPoint],
points[(int)PiePoints.BottomRectTopLeftPoint],
points[(int)PiePoints.BottomRectBottomRightPoint],
points[(int)PiePoints.TopStart],
points[(int)PiePoints.Top180],
points[(int)PiePoints.BottomStart],
points[(int)PiePoints.Bottom180],
startAngle,
180 - startAngle,
pointIndex
);
}
else
{
graph.FillPieCurve(
area,
dataPoint,
brush,
pen,
points[(int)PiePoints.TopRectTopLeftPoint],
points[(int)PiePoints.TopRectBottomRightPoint],
points[(int)PiePoints.BottomRectTopLeftPoint],
points[(int)PiePoints.BottomRectBottomRightPoint],
points[(int)PiePoints.Top0],
points[(int)PiePoints.TopEnd],
points[(int)PiePoints.Bottom0],
points[(int)PiePoints.BottomEnd],
0,
endAngle - 360,
pointIndex
);
}
}
else
{
// There is big pie slice (>180), but that pie slice
// is not back and front point in same time.
graph.FillPieCurve(
area,
dataPoint,
brush,
pen,
points[(int)PiePoints.TopRectTopLeftPoint],
points[(int)PiePoints.TopRectBottomRightPoint],
points[(int)PiePoints.BottomRectTopLeftPoint],
points[(int)PiePoints.BottomRectBottomRightPoint],
points[(int)PiePoints.Top0],
points[(int)PiePoints.TopEnd],
points[(int)PiePoints.Bottom0],
points[(int)PiePoints.BottomEnd],
0,
endAngle - 360,
pointIndex
);
graph.FillPieCurve(
area,
dataPoint,
brush,
pen,
points[(int)PiePoints.TopRectTopLeftPoint],
points[(int)PiePoints.TopRectBottomRightPoint],
points[(int)PiePoints.BottomRectTopLeftPoint],
points[(int)PiePoints.BottomRectBottomRightPoint],
points[(int)PiePoints.TopStart],
points[(int)PiePoints.Top180],
points[(int)PiePoints.BottomStart],
points[(int)PiePoints.Bottom180],
startAngle,
180 - startAngle,
pointIndex
);
}
}
else
{
// Big pie slice behaves as normal pie slice. Continue
// Non special case alghoritham
return false;
}
}
return true;
}
/// <summary>
/// This method is used for drawing curve around doughnut slices - inner curve.
/// This is the most complex part of 3D Doughnut slice. There is special case if
/// pie slice is bigger then 180 degree.
/// </summary>
/// <param name="graph">Chart Grahics.</param>
/// <param name="area">Chart Area.</param>
/// <param name="dataPoint">Data Point used for pie slice.</param>
/// <param name="startAngle">Start angle of a pie slice.</param>
/// <param name="sweepAngle">Sweep angle of a pie slice.</param>
/// <param name="points">Important 3d points of a pie slice.</param>
/// <param name="brushWithoutLight">Brush without lithing efects.</param>
/// <param name="pen">Pen used for border.</param>
/// <param name="rightPosition">Position of the curve of big pie slice. Big pie slice coud have to visible curves - left and right</param>
/// <param name="sameBackFront">This is big pie slice which is in same time back and front slice.</param>
/// <param name="pointIndex">Data Point Index</param>
private void DrawDoughnutCurves(
ChartGraphics graph,
ChartArea area,
DataPoint dataPoint,
float startAngle,
float sweepAngle,
PointF [] points,
SolidBrush brushWithoutLight,
Pen pen,
bool rightPosition,
bool sameBackFront,
int pointIndex
)
{
// Create a graphics path
using (GraphicsPath path = new GraphicsPath())
{
Brush brush;
if (area.Area3DStyle.LightStyle == LightStyle.None)
{
brush = brushWithoutLight;
}
else
{
brush = graph.GetGradientBrush(graph.GetAbsoluteRectangle(area.Position.ToRectangleF()), Color.FromArgb(brushWithoutLight.Color.A, 0, 0, 0), brushWithoutLight.Color, GradientStyle.VerticalCenter);
}
float endAngle = startAngle + sweepAngle;
// Very big pie slice ( > 180 degree )
if (sweepAngle > 180)
{
if (DrawDoughnutCurvesBigSlice(graph, area, dataPoint, startAngle, sweepAngle, points, brush, pen, rightPosition, sameBackFront, pointIndex))
return;
}
// Pie slice pass throw 180 degree. Curve has to be spited.
if (startAngle < 180 && endAngle > 180)
{
if (area.Area3DStyle.Inclination > 0)
{
graph.FillPieCurve(
area,
dataPoint,
brush,
pen,
points[(int)PiePoints.DoughnutTopRectTopLeftPoint],
points[(int)PiePoints.DoughnutTopRectBottomRightPoint],
points[(int)PiePoints.DoughnutBottomRectTopLeftPoint],
points[(int)PiePoints.DoughnutBottomRectBottomRightPoint],
points[(int)PiePoints.DoughnutTopStart],
points[(int)PiePoints.DoughnutTop180],
points[(int)PiePoints.DoughnutBottomStart],
points[(int)PiePoints.DoughnutBottom180],
startAngle,
180 - startAngle,
pointIndex
);
}
else
{
graph.FillPieCurve(
area,
dataPoint,
brush,
pen,
points[(int)PiePoints.DoughnutTopRectTopLeftPoint],
points[(int)PiePoints.DoughnutTopRectBottomRightPoint],
points[(int)PiePoints.DoughnutBottomRectTopLeftPoint],
points[(int)PiePoints.DoughnutBottomRectBottomRightPoint],
points[(int)PiePoints.DoughnutTop180],
points[(int)PiePoints.DoughnutTopEnd],
points[(int)PiePoints.DoughnutBottom180],
points[(int)PiePoints.DoughnutBottomEnd],
180,
startAngle + sweepAngle - 180,
pointIndex
);
}
}
// Pie slice pass throw 0 degree. Curve has to be spited.
else if (startAngle < 0 && endAngle > 0)
{
if (area.Area3DStyle.Inclination < 0)
{
graph.FillPieCurve(
area,
dataPoint,
brush,
pen,
points[(int)PiePoints.DoughnutTopRectTopLeftPoint],
points[(int)PiePoints.DoughnutTopRectBottomRightPoint],
points[(int)PiePoints.DoughnutBottomRectTopLeftPoint],
points[(int)PiePoints.DoughnutBottomRectBottomRightPoint],
points[(int)PiePoints.DoughnutTopStart],
points[(int)PiePoints.DoughnutTop0],
points[(int)PiePoints.DoughnutBottomStart],
points[(int)PiePoints.DoughnutBottom0],
startAngle,
-startAngle,
pointIndex
);
}
else
{
graph.FillPieCurve(
area,
dataPoint,
brush,
pen,
points[(int)PiePoints.DoughnutTopRectTopLeftPoint],
points[(int)PiePoints.DoughnutTopRectBottomRightPoint],
points[(int)PiePoints.DoughnutBottomRectTopLeftPoint],
points[(int)PiePoints.DoughnutBottomRectBottomRightPoint],
points[(int)PiePoints.DoughnutTop0],
points[(int)PiePoints.DoughnutTopEnd],
points[(int)PiePoints.DoughnutBottom0],
points[(int)PiePoints.DoughnutBottomEnd],
0,
sweepAngle + startAngle,
pointIndex
);
}
}
// Pie slice pass throw 360 degree. Curve has to be spited.
else if (startAngle < 360 && endAngle > 360)
{
if (area.Area3DStyle.Inclination < 0)
{
graph.FillPieCurve(
area,
dataPoint,
brush,
pen,
points[(int)PiePoints.DoughnutTopRectTopLeftPoint],
points[(int)PiePoints.DoughnutTopRectBottomRightPoint],
points[(int)PiePoints.DoughnutBottomRectTopLeftPoint],
points[(int)PiePoints.DoughnutBottomRectBottomRightPoint],
points[(int)PiePoints.DoughnutTopStart],
points[(int)PiePoints.DoughnutTop0],
points[(int)PiePoints.DoughnutBottomStart],
points[(int)PiePoints.DoughnutBottom0],
startAngle,
360 - startAngle,
pointIndex
);
}
else
{
graph.FillPieCurve(
area,
dataPoint,
brush,
pen,
points[(int)PiePoints.DoughnutTopRectTopLeftPoint],
points[(int)PiePoints.DoughnutTopRectBottomRightPoint],
points[(int)PiePoints.DoughnutBottomRectTopLeftPoint],
points[(int)PiePoints.DoughnutBottomRectBottomRightPoint],
points[(int)PiePoints.DoughnutTop0],
points[(int)PiePoints.DoughnutTopEnd],
points[(int)PiePoints.DoughnutBottom0],
points[(int)PiePoints.DoughnutBottomEnd],
0,
endAngle - 360,
pointIndex
);
}
}
else
{
// ***************************************************
// REGULAR CASE: The curve is not split.
// ***************************************************
if (startAngle < 180 && startAngle >= 0 && area.Area3DStyle.Inclination > 0
|| startAngle < 540 && startAngle >= 360 && area.Area3DStyle.Inclination > 0
|| startAngle >= 180 && startAngle < 360 && area.Area3DStyle.Inclination < 0
|| startAngle >= -180 && startAngle < 0 && area.Area3DStyle.Inclination < 0
)
{
graph.FillPieCurve(
area,
dataPoint,
brush,
pen,
points[(int)PiePoints.DoughnutTopRectTopLeftPoint],
points[(int)PiePoints.DoughnutTopRectBottomRightPoint],
points[(int)PiePoints.DoughnutBottomRectTopLeftPoint],
points[(int)PiePoints.DoughnutBottomRectBottomRightPoint],
points[(int)PiePoints.DoughnutTopStart],
points[(int)PiePoints.DoughnutTopEnd],
points[(int)PiePoints.DoughnutBottomStart],
points[(int)PiePoints.DoughnutBottomEnd],
startAngle,
sweepAngle,
pointIndex
);
}
}
}
}
/// <summary>
/// This method is used for special case when big doughnut slice has to be drawn.
/// </summary>
/// <param name="graph">Chart Grahics.</param>
/// <param name="area">Chart Area.</param>
/// <param name="dataPoint">Data Point used for pie slice.</param>
/// <param name="startAngle">Start angle of a pie slice.</param>
/// <param name="sweepAngle">Sweep angle of a pie slice.</param>
/// <param name="points">Important 3d points of a pie slice.</param>
/// <param name="brush">Brush without lithing efects.</param>
/// <param name="pen">Pen used for border.</param>
/// <param name="rightPosition">Position of the curve of big pie slice. Big pie slice coud have to visible curves - left and right</param>
/// <param name="sameBackFront">This is big pie slice which is in same time back and front slice.</param>
/// <param name="pointIndex">Data Point Index</param>
/// <returns>True if slice is special case and it is drawn as a special case.</returns>
private bool DrawDoughnutCurvesBigSlice
(
ChartGraphics graph,
ChartArea area,
DataPoint dataPoint,
float startAngle,
float sweepAngle,
PointF [] points,
Brush brush,
Pen pen,
bool rightPosition,
bool sameBackFront,
int pointIndex
)
{
float endAngle = startAngle + sweepAngle;
// Two different cases connected with X angle.
// *****************************************************
// X angle is positive
// *****************************************************
if( area.Area3DStyle.Inclination < 0 )
{
// Show curve from 0 to 180.
if( startAngle < 180 && endAngle > 360 )
{
graph.FillPieCurve(
area,
dataPoint,
brush,
pen,
points[(int)PiePoints.DoughnutTopRectTopLeftPoint],
points[(int)PiePoints.DoughnutTopRectBottomRightPoint],
points[(int)PiePoints.DoughnutBottomRectTopLeftPoint],
points[(int)PiePoints.DoughnutBottomRectBottomRightPoint],
points[(int)PiePoints.DoughnutTop0],
points[(int)PiePoints.DoughnutTop180],
points[(int)PiePoints.DoughnutBottom0],
points[(int)PiePoints.DoughnutBottom180],
0,
-180,
pointIndex
);
}
else if( startAngle < 0 && endAngle > 180 )
{
// There is big data point which is back and
// front point in same time.
if( sameBackFront )
{
// The big pie slice has to be split. This part makes
// decision which part of this big slice will be
// drawn first.
if( rightPosition )
{
graph.FillPieCurve(
area,
dataPoint,
brush,
pen,
points[(int)PiePoints.DoughnutTopRectTopLeftPoint],
points[(int)PiePoints.DoughnutTopRectBottomRightPoint],
points[(int)PiePoints.DoughnutBottomRectTopLeftPoint],
points[(int)PiePoints.DoughnutBottomRectBottomRightPoint],
points[(int)PiePoints.DoughnutTop180],
points[(int)PiePoints.DoughnutTopEnd],
points[(int)PiePoints.DoughnutBottom180],
points[(int)PiePoints.DoughnutBottomEnd],
180,
endAngle - 180,
pointIndex
);
}
else
{
graph.FillPieCurve(
area,
dataPoint,
brush,
pen,
points[(int)PiePoints.DoughnutTopRectTopLeftPoint],
points[(int)PiePoints.DoughnutTopRectBottomRightPoint],
points[(int)PiePoints.DoughnutBottomRectTopLeftPoint],
points[(int)PiePoints.DoughnutBottomRectBottomRightPoint],
points[(int)PiePoints.DoughnutTopStart],
points[(int)PiePoints.DoughnutTop0],
points[(int)PiePoints.DoughnutBottomStart],
points[(int)PiePoints.DoughnutBottom0],
startAngle,
-startAngle,
pointIndex
);
}
}
else
{
// There is big pie slice (>180), but that pie slice
// is not back and front point in same time.
graph.FillPieCurve(
area,
dataPoint,
brush,
pen,
points[(int)PiePoints.DoughnutTopRectTopLeftPoint],
points[(int)PiePoints.DoughnutTopRectBottomRightPoint],
points[(int)PiePoints.DoughnutBottomRectTopLeftPoint],
points[(int)PiePoints.DoughnutBottomRectBottomRightPoint],
points[(int)PiePoints.DoughnutTopStart],
points[(int)PiePoints.DoughnutTop0],
points[(int)PiePoints.DoughnutBottomStart],
points[(int)PiePoints.DoughnutBottom0],
startAngle,
-startAngle,
pointIndex
);
graph.FillPieCurve(
area,
dataPoint,
brush,
pen,
points[(int)PiePoints.DoughnutTopRectTopLeftPoint],
points[(int)PiePoints.DoughnutTopRectBottomRightPoint],
points[(int)PiePoints.DoughnutBottomRectTopLeftPoint],
points[(int)PiePoints.DoughnutBottomRectBottomRightPoint],
points[(int)PiePoints.DoughnutTop180],
points[(int)PiePoints.DoughnutTopEnd],
points[(int)PiePoints.DoughnutBottom180],
points[(int)PiePoints.DoughnutBottomEnd],
180,
endAngle - 180,
pointIndex
);
}
}
else
{
// Big pie slice behaves as normal pie slice. Continue
// Non special case alghoritham
return false;
}
}
// *********************************************
// X angle negative
// *********************************************
else
{
// Show curve from 0 to 180.
if( startAngle < 0 && endAngle > 180 )
{
graph.FillPieCurve(
area,
dataPoint,
brush,
pen,
points[(int)PiePoints.DoughnutTopRectTopLeftPoint],
points[(int)PiePoints.DoughnutTopRectBottomRightPoint],
points[(int)PiePoints.DoughnutBottomRectTopLeftPoint],
points[(int)PiePoints.DoughnutBottomRectBottomRightPoint],
points[(int)PiePoints.DoughnutTop0],
points[(int)PiePoints.DoughnutTop180],
points[(int)PiePoints.DoughnutBottom0],
points[(int)PiePoints.DoughnutBottom180],
0,
180,
pointIndex
);
}
else if( startAngle < 180 && endAngle > 360 )
{
// There is big data point which is back and
// front point in same time.
if( sameBackFront )
{
// The big pie slice has to be split. This part makes
// decision which part of this big slice will be
// drawn first.
if( rightPosition )
{
graph.FillPieCurve(
area,
dataPoint,
brush,
pen,
points[(int)PiePoints.DoughnutTopRectTopLeftPoint],
points[(int)PiePoints.DoughnutTopRectBottomRightPoint],
points[(int)PiePoints.DoughnutBottomRectTopLeftPoint],
points[(int)PiePoints.DoughnutBottomRectBottomRightPoint],
points[(int)PiePoints.DoughnutTopStart],
points[(int)PiePoints.DoughnutTop180],
points[(int)PiePoints.DoughnutBottomStart],
points[(int)PiePoints.DoughnutBottom180],
startAngle,
180 - startAngle,
pointIndex
);
}
else
{
graph.FillPieCurve(
area,
dataPoint,
brush,
pen,
points[(int)PiePoints.DoughnutTopRectTopLeftPoint],
points[(int)PiePoints.DoughnutTopRectBottomRightPoint],
points[(int)PiePoints.DoughnutBottomRectTopLeftPoint],
points[(int)PiePoints.DoughnutBottomRectBottomRightPoint],
points[(int)PiePoints.DoughnutTop0],
points[(int)PiePoints.DoughnutTopEnd],
points[(int)PiePoints.DoughnutBottom0],
points[(int)PiePoints.DoughnutBottomEnd],
0,
endAngle - 360,
pointIndex
);
}
}
else
{
// There is big pie slice (>180), but that pie slice
// is not back and front point in same time.
graph.FillPieCurve(
area,
dataPoint,
brush,
pen,
points[(int)PiePoints.DoughnutTopRectTopLeftPoint],
points[(int)PiePoints.DoughnutTopRectBottomRightPoint],
points[(int)PiePoints.DoughnutBottomRectTopLeftPoint],
points[(int)PiePoints.DoughnutBottomRectBottomRightPoint],
points[(int)PiePoints.DoughnutTop0],
points[(int)PiePoints.DoughnutTopEnd],
points[(int)PiePoints.DoughnutBottom0],
points[(int)PiePoints.DoughnutBottomEnd],
0,
endAngle - 360,
pointIndex
);
graph.FillPieCurve(
area,
dataPoint,
brush,
pen,
points[(int)PiePoints.DoughnutTopRectTopLeftPoint],
points[(int)PiePoints.DoughnutTopRectBottomRightPoint],
points[(int)PiePoints.DoughnutBottomRectTopLeftPoint],
points[(int)PiePoints.DoughnutBottomRectBottomRightPoint],
points[(int)PiePoints.DoughnutTopStart],
points[(int)PiePoints.DoughnutTop180],
points[(int)PiePoints.DoughnutBottomStart],
points[(int)PiePoints.DoughnutBottom180],
startAngle,
180 - startAngle,
pointIndex
);
}
}
else
{
// Big pie slice behaves as normal pie slice. Continue
// Non special case alghoritham
return false;
}
}
return true;
}
#endregion
#region 3D Order of points Methods
/// <summary>
/// This method sort data points on specific way. Because
/// of order of drawing in 3D space, the back data point
/// (point which pass throw 270 degree has to be drawn first.
/// After that side data points have to be drawn. At the end
/// front data point (data point which pass throw 0 degree)
/// has to be drawn. There is special case if there is big
/// data point, which is back and front point in same time.
/// </summary>
/// <param name="series">Data series</param>
/// <param name="area">Chart area</param>
/// <param name="newStartAngleList">Unsorted List of Start angles.</param>
/// <param name="newSweepAngleList">Unsorted List of Sweep angles.</param>
/// <param name="newPointIndexList">Data Point index list</param>
/// <param name="sameBackFrontPoint">Beck and Fron Points are same - There is a big pie slice.</param>
/// <returns>Sorted data point list.</returns>
private DataPoint [] PointOrder( Series series, ChartArea area, out float [] newStartAngleList, out float [] newSweepAngleList, out int [] newPointIndexList, out bool sameBackFrontPoint )
{
double startAngle;
double sweepAngle;
double endAngle;
int backPoint = -1;
int frontPoint = -1;
sameBackFrontPoint = false;
// The data points loop. Find Sum of data points.
double sum = 0;
int numOfEmpty = 0;
foreach( DataPoint point in series.Points )
{
if( point.IsEmpty )
numOfEmpty++;
if( !point.IsEmpty )
{
sum += Math.Abs(point.YValues[0]);
}
}
// Find number of data points
int numOfPoints = series.Points.Count - numOfEmpty;
DataPoint [] points = new DataPoint[ numOfPoints ];
float [] startAngleList = new float[ numOfPoints ];
float [] sweepAngleList = new float[ numOfPoints ];
int [] pointIndexList = new int[ numOfPoints ];
newStartAngleList = new float[ numOfPoints ];
newSweepAngleList = new float[ numOfPoints ];
newPointIndexList = new int[ numOfPoints ];
// If sum is less then 0 do not draw pie chart
if( sum <= 0 )
{
return null;
}
// *****************************************************
// Find Back and Front Points. Back point is a point
// which pass throw 270 degree. Front point pass
// throw 90 degree.
// There are two points in the data point list which will be
// placed at the end and at the beginning on the sorted list: Back
// point (beginning) and Front point (the end). Back point could
// be only after Front point at the unsorted list.
// *****************************************************
int pointIndx = 0;
startAngle = area.Area3DStyle.Rotation;
foreach( DataPoint point in series.Points )
{
// Do not process empty points
if( point.IsEmpty )
{
continue;
}
// Find angles
sweepAngle = (float)( Math.Abs(point.YValues[0]) * 360 / sum );
endAngle = startAngle + sweepAngle;
startAngleList[ pointIndx ] = (float)startAngle;
sweepAngleList[ pointIndx ] = (float)sweepAngle;
pointIndexList[ pointIndx ] = pointIndx;
// ***************************************************************
// Find Back point.
// Because angle could be between -180 and 540 ( Y axis
// rotation from -180 to 180 ), Back point could be at -90 and 270
// ***************************************************************
if( startAngle <= -90 && endAngle > -90 || startAngle <= 270 && endAngle > 270 && points[0] == null )
{
/*if( points[0] != null )
throw new InvalidOperationException(SR.ExceptionPiePointOrderInvalid);
*/
backPoint = pointIndx;
points[0] = point;
newStartAngleList[0] = startAngleList[pointIndx];
newSweepAngleList[0] = sweepAngleList[pointIndx];
newPointIndexList[0] = pointIndexList[pointIndx];
}
// ***************************************************************
// Find Front point.
// Because angle could be between -180 and 540 ( Y axis
// rotation from -180 to 180 ), Front point could be at 90 and 450
// Case frontPoint == -1 is set because of rounding error.
// ***************************************************************
if( startAngle <= 90 && endAngle > 90 || startAngle <= 450 && endAngle > 450 && frontPoint == -1 && ( points[points.Length-1] == null || points.Length == 1 ) )
{
/*
if( points[points.Length-1] != null && points.Length != 1)
throw new InvalidOperationException(SR.ExceptionPiePointOrderInvalid);
*/
frontPoint = pointIndx;
points[points.Length-1] = point;
newStartAngleList[points.Length-1] = startAngleList[pointIndx];
newSweepAngleList[points.Length-1] = sweepAngleList[pointIndx];
newPointIndexList[points.Length-1] = pointIndexList[pointIndx];
}
pointIndx++;
startAngle += sweepAngle;
}
if( frontPoint == -1 || backPoint == -1 )
{
throw new InvalidOperationException(SR.ExceptionPieUnassignedFrontBackPoints);
}
// If front point and back point are same do not
// put same point in two fields.
if( frontPoint == backPoint && points.Length != 1 )
{
points[points.Length-1] = null;
newStartAngleList[points.Length-1] = 0;
newSweepAngleList[points.Length-1] = 0;
newPointIndexList[points.Length-1] = 0;
sameBackFrontPoint = true;
}
// ********************************************
// Special case. Front Point and Back points
// are same.
// ********************************************
if( frontPoint == backPoint )
{
// Find middle angle of a data point
float midAngle = startAngleList[backPoint] + sweepAngleList[backPoint] / 2F;
int listIndx;
bool rightSidePoints = false;
// If big pie slice is on the right and all other
// pie slices are on the left.
if( midAngle > -90 && midAngle < 90 || midAngle > 270 && midAngle < 450 )
{
rightSidePoints = true;
}
listIndx = numOfPoints - frontPoint;
pointIndx = 0;
foreach( DataPoint point in series.Points )
{
// Do not process empty points
if( point.IsEmpty )
{
continue;
}
// If Front and back points continue with loop
if( pointIndx == frontPoint )
{
pointIndx++;
continue;
}
if( pointIndx < frontPoint )
{
if( points[listIndx] != null )
throw new InvalidOperationException(SR.ExceptionPiePointOrderInvalid);
points[listIndx] = point;
newStartAngleList[listIndx] = startAngleList[pointIndx];
newSweepAngleList[listIndx] = sweepAngleList[pointIndx];
newPointIndexList[listIndx] = pointIndexList[pointIndx];
listIndx++;
}
pointIndx++;
}
pointIndx = 0;
listIndx = 1;
foreach( DataPoint point in series.Points )
{
// Do not process empty points
if( point.IsEmpty )
{
continue;
}
// If Front and back points continue with loop
if( pointIndx == frontPoint )
{
pointIndx++;
continue;
}
if( pointIndx > frontPoint )
{
if( points[listIndx] != null )
throw new InvalidOperationException(SR.ExceptionPiePointOrderInvalid);
points[listIndx] = point;
newStartAngleList[listIndx] = startAngleList[pointIndx];
newSweepAngleList[listIndx] = sweepAngleList[pointIndx];
newPointIndexList[listIndx] = pointIndexList[pointIndx];
listIndx++;
}
pointIndx++;
}
if( rightSidePoints )
{
SwitchPoints( numOfPoints, ref points, ref newStartAngleList, ref newSweepAngleList, ref newPointIndexList, backPoint == frontPoint );
}
}
else if( frontPoint < backPoint )
{
// ************************************************
// Fill From Back Point to the end of unsorted list
// ************************************************
pointIndx = 0;
int listIndx = 1;
foreach( DataPoint point in series.Points )
{
// Do not process empty points
if( point.IsEmpty )
{
continue;
}
// If Front and back points continue with loop
if( pointIndx == frontPoint || pointIndx == backPoint )
{
pointIndx++;
continue;
}
// If curent point is after front point.
else if( pointIndx > backPoint )
{
if( points[listIndx] != null )
throw new InvalidOperationException(SR.ExceptionPiePointOrderInvalid);
points[listIndx] = point;
newStartAngleList[listIndx] = startAngleList[pointIndx];
newSweepAngleList[listIndx] = sweepAngleList[pointIndx];
newPointIndexList[listIndx] = pointIndexList[pointIndx];
listIndx++;
}
pointIndx++;
}
// ******************************************************
// Fill from the begining of unsorted list to Front Point
// ******************************************************
pointIndx = 0;
foreach( DataPoint point in series.Points )
{
// Do not process empty points
if( point.IsEmpty )
{
continue;
}
// If Front and back points continue with loop
if( pointIndx == frontPoint || pointIndx == backPoint )
{
pointIndx++;
continue;
}
// If curent point is before front point.
else if( pointIndx < frontPoint )
{
if( points[listIndx] != null )
throw new InvalidOperationException(SR.ExceptionPiePointOrderInvalid);
points[listIndx] = point;
newStartAngleList[listIndx] = startAngleList[pointIndx];
newSweepAngleList[listIndx] = sweepAngleList[pointIndx];
newPointIndexList[listIndx] = pointIndexList[pointIndx];
listIndx++;
}
pointIndx++;
}
// *********************************************************
// This code run only if special case is not active.
// Special case: FrontPoint and back point are same. This is
// happening because pie slice is bigger then 180 degree.
// *********************************************************
// **********************************
// Fill from Front Point to Back Point
// **********************************
listIndx = points.Length - 2;
pointIndx = 0;
foreach( DataPoint point in series.Points )
{
// Do not process empty points
if( point.IsEmpty )
{
continue;
}
// If Front and back points continue with loop
if( pointIndx == frontPoint || pointIndx == backPoint )
{
pointIndx++;
continue;
}
// If curent point is between front point and back point.
else if( pointIndx > frontPoint && pointIndx < backPoint )
{
if (points[listIndx] != null) throw new InvalidOperationException(SR.ExceptionPiePointOrderInvalid);
points[listIndx] = point;
newStartAngleList[listIndx] = startAngleList[pointIndx];
newSweepAngleList[listIndx] = sweepAngleList[pointIndx];
newPointIndexList[listIndx] = pointIndexList[pointIndx];
listIndx--;
}
pointIndx++;
}
}
else
{
// **********************************
// Fill from Back Point to Front Point
// **********************************
int listIndx = 1;
pointIndx = 0;
foreach( DataPoint point in series.Points )
{
// Do not process empty points
if( point.IsEmpty )
{
continue;
}
// If Front and back points continue with loop
if( pointIndx == frontPoint || pointIndx == backPoint )
{
pointIndx++;
continue;
}
// If curent point is between front back and front points.
else if( pointIndx > backPoint && pointIndx < frontPoint )
{
if (points[listIndx] != null) throw new InvalidOperationException(SR.ExceptionPiePointOrderInvalid);
points[listIndx] = point;
newStartAngleList[listIndx] = startAngleList[pointIndx];
newSweepAngleList[listIndx] = sweepAngleList[pointIndx];
newPointIndexList[listIndx] = pointIndexList[pointIndx];
listIndx++;
}
pointIndx++;
}
// ************************************************
// Fill From Front Point to the end of unsorted list
// ************************************************
listIndx = points.Length - 2;
pointIndx = 0;
foreach( DataPoint point in series.Points )
{
// Do not process empty points
if( point.IsEmpty )
{
continue;
}
// If Front and back points continue with loop
if( pointIndx == frontPoint || pointIndx == backPoint )
{
pointIndx++;
continue;
}
// If curent point is after front point.
else if( pointIndx > frontPoint )
{
if( points[listIndx] != null )
throw new InvalidOperationException(SR.ExceptionPiePointOrderInvalid);
points[listIndx] = point;
newStartAngleList[listIndx] = startAngleList[pointIndx];
newSweepAngleList[listIndx] = sweepAngleList[pointIndx];
newPointIndexList[listIndx] = pointIndexList[pointIndx];
listIndx--;
}
pointIndx++;
}
// ******************************************************
// Fill from the begining of unsorted list to Back Point
// ******************************************************
pointIndx = 0;
foreach( DataPoint point in series.Points )
{
// Do not process empty points
if( point.IsEmpty )
{
continue;
}
// If Front and back points continue with loop
if( pointIndx == frontPoint || pointIndx == backPoint )
{
pointIndx++;
continue;
}
// If curent point is before front point.
else if( pointIndx < backPoint )
{
if( points[listIndx] != null )
throw new InvalidOperationException(SR.ExceptionPiePointOrderInvalid);
points[listIndx] = point;
newStartAngleList[listIndx] = startAngleList[pointIndx];
newSweepAngleList[listIndx] = sweepAngleList[pointIndx];
newPointIndexList[listIndx] = pointIndexList[pointIndx];
listIndx--;
}
pointIndx++;
}
// *********************************************************
// This code run only if special case is not active.
// Special case: FrontPoint and back point are same. This is
// happening because pie slice is bigger then 180 degree.
// *********************************************************
}
// *******************************************************
// If X angle is positive direction of drawing data points
// should be opposite. This part of code switch order of
// data points.
// *******************************************************
if( area.Area3DStyle.Inclination > 0 )
{
SwitchPoints( numOfPoints, ref points, ref newStartAngleList, ref newSweepAngleList, ref newPointIndexList, backPoint == frontPoint );
}
return points;
}
/// <summary>
/// This method switches order of data points in the array of points.
/// </summary>
/// <param name="numOfPoints">Number of data points</param>
/// <param name="points">Array of Data points</param>
/// <param name="newStartAngleList">List of start angles which has to be switched together with data points</param>
/// <param name="newSweepAngleList">List of sweep angles which has to be switched together with data points</param>
/// <param name="newPointIndexList">Indexes (position) of data points in the series</param>
/// <param name="sameBackFront">There is big pie slice which has same back and front pie slice</param>
private void SwitchPoints( int numOfPoints, ref DataPoint [] points, ref float [] newStartAngleList, ref float [] newSweepAngleList, ref int [] newPointIndexList, bool sameBackFront )
{
float [] tempStartAngles = new float[ numOfPoints ];
float [] tempSweepAngles = new float[ numOfPoints ];
int [] tempPointIndexList = new int[ numOfPoints ];
DataPoint [] tempPoints = new DataPoint[ numOfPoints ];
int start = 0;;
// The big pie slice (special case) is always on the beginning.
if( sameBackFront )
{
start = 1;
// Switch order.
tempPoints[0] = points[0];
tempStartAngles[0] = newStartAngleList[0];
tempSweepAngles[0] = newSweepAngleList[0];
tempPointIndexList[0] = newPointIndexList[0];
}
for( int index = start; index < numOfPoints; index++ )
{
if( points[ index ] == null )
{
throw new InvalidOperationException(SR.ExceptionPieOrderOperationInvalid);
}
// Switch order.
tempPoints[ numOfPoints - index - 1 + start] = points[ index ];
tempStartAngles[ numOfPoints - index - 1 + start] = newStartAngleList[ index ];
tempSweepAngles[ numOfPoints - index - 1 + start] = newSweepAngleList[ index ];
tempPointIndexList[ numOfPoints - index - 1 + start] = newPointIndexList[ index ];
}
points = tempPoints;
newStartAngleList = tempStartAngles;
newSweepAngleList = tempSweepAngles;
newPointIndexList = tempPointIndexList;
}
#endregion
#region 3D Label column class
/// <summary>
/// LabelColumn class is used for labels manipulation - outside label style
/// </summary>
internal class LabelColumn
{
// Fields of Label Column class
private RectangleF _chartAreaPosition;
private RectangleF _innerPlotPosition;
internal float columnHeight;
internal int numOfItems = 0;
private int _numOfInsertedLabels = 0;
private DataPoint [] _points;
private float [] _yPositions;
private bool _rightPosition = true;
private float _labelLineSize;
/// <summary>
/// Constructor
/// </summary>
/// <param name="position">Chart Area position.</param>
public LabelColumn( RectangleF position )
{
_chartAreaPosition = position;
}
/// <summary>
/// Return index of label position in the column.
/// </summary>
/// <param name="y">y coordinate</param>
/// <returns>Index of column</returns>
internal int GetLabelIndex( float y )
{
// y coordinate is out of chart area.
if( y < _chartAreaPosition.Y )
{
y = _chartAreaPosition.Y;
}
else if( y > _chartAreaPosition.Bottom )
{
y = _chartAreaPosition.Bottom - columnHeight;
}
return (int) (( y - _chartAreaPosition.Y ) / columnHeight ) ;
}
/// <summary>
/// This method sorts labels by y Position
/// </summary>
internal void Sort()
{
for( int indexA = 0; indexA < _points.Length; indexA++ )
{
for( int indexB = 0; indexB < indexA; indexB++ )
{
if( _yPositions[indexA] < _yPositions[indexB] && _points[indexA] != null && _points[indexB] != null )
{
float tempYPos;
DataPoint tempPoint;
tempYPos = _yPositions[indexA];
tempPoint = _points[indexA];
_yPositions[indexA] = _yPositions[indexB];
_points[indexA] = _points[indexB];
_yPositions[indexB] = tempYPos;
_points[indexB] = tempPoint;
}
}
}
}
/// <summary>
/// Returns label position y coordinate from index position
/// </summary>
/// <param name="index">Index position of the row</param>
/// <returns>Y coordinate row position</returns>
internal float GetLabelPosition( int index )
{
if( index < 0 || index > numOfItems - 1 )
throw new InvalidOperationException(SR.Exception3DPieLabelsIndexInvalid);
return (float) _chartAreaPosition.Y + columnHeight * index + columnHeight / 2;
}
/// <summary>
/// This method finds X and Y position for outside
/// labels. There is discrete number of cells and
/// Y position depends on cell position. X position
/// is connected with angle between invisible
/// line (which connects center of a pie and label)
/// and any horizontal line.
/// </summary>
/// <param name="dataPoint">Data Point</param>
/// <returns>Position of a label</returns>
internal PointF GetLabelPosition( DataPoint dataPoint )
{
PointF position = PointF.Empty;
int pointIndex = 0;
// Find Y position of Data Point
// Loop is necessary to find index of data point in the array list.
foreach( DataPoint point in _points )
{
if( point == dataPoint )
{
position.Y = GetLabelPosition( pointIndex );
break;
}
pointIndex++;
}
// Find initial X position for labels ( All labels are aligne ).
if( _rightPosition )
{
position.X = _innerPlotPosition.Right + _chartAreaPosition.Width * this._labelLineSize;
}
else
{
position.X = _innerPlotPosition.Left - _chartAreaPosition.Width * this._labelLineSize;
}
// Find angle between invisible line (which connects center of a pie and label)
// and any horizontal line.
float angle;
angle = (float)Math.Atan( ( position.Y - _innerPlotPosition.Top - _innerPlotPosition.Height / 2) / ( position.X - _innerPlotPosition.Left - _innerPlotPosition.Width / 2 ));
// Make Angle correction for X Position
float correct;
if( Math.Cos( angle ) == 0 )
{
correct = 0;
}
else
{
correct = (float)(_innerPlotPosition.Width * 0.4 - _innerPlotPosition.Width * 0.4 / Math.Cos( angle ));
}
// Set Corrected X Position
if( _rightPosition )
{
position.X += correct;
}
else
{
position.X -= correct;
}
return position;
}
/// <summary>
/// This method inserts outside labels in Column label list. Column label
/// list has defined number of cells. This method has to put labels on
/// the best position in the list. If two labels according to their
/// positions belong to same cell of the list, this method should
/// assign to them different positions.
/// </summary>
/// <param name="point">Data Point which label has to be inserted</param>
/// <param name="yCoordinate">Y coordinate which is the best position for this label</param>
/// <param name="pointIndx">Point index of this data point in the series</param>
internal void InsertLabel( DataPoint point, float yCoordinate, int pointIndx )
{
// Find index of label list by Y value
int indexYValue = GetLabelIndex( yCoordinate );
// This position is already used.
if( _points[indexYValue] != null )
{
// All even elements go up and other
// Down (If there are many labels which use this position).
if( pointIndx % 2 == 0 )
{
// Check if there is space Down
if( CheckFreeSpace( indexYValue, false ) )
{
// Move labels Down
MoveLabels( indexYValue, false );
}
else
{
// Move labels Up
MoveLabels( indexYValue, true );
}
}
else
{
// Check if there is space Up
if( CheckFreeSpace( indexYValue, true ) )
{
// Move labels Up
MoveLabels( indexYValue, true );
}
else
{
// Move labels Down
MoveLabels( indexYValue, false );
}
}
}
// Set label position
_points[indexYValue] = point;
_yPositions[indexYValue] = yCoordinate;
_numOfInsertedLabels++;
}
/// <summary>
/// This method is used for inserting labels. When label is inserted
/// and that position was previously used, labels have to be
/// moved on proper way.
/// </summary>
/// <param name="position">Position which has to be free</param>
/// <param name="upDirection">Direction for moving labels</param>
private void MoveLabels( int position, bool upDirection )
{
if( upDirection )
{
DataPoint point = _points[position];
float yValue = _yPositions[position];
_points[position] = null;
_yPositions[position] = 0;
for( int index = position; index > 0; index-- )
{
// IsEmpty position found. Stop moving cells UP
if( _points[index-1] == null )
{
_points[index-1] = point;
_yPositions[index-1] = yValue;
break;
}
else
{
DataPoint tempPoint;
float tempYValue;
tempPoint = _points[index-1];
tempYValue = _yPositions[index-1];
_points[index-1] = point;
_yPositions[index-1] = yValue;
point = tempPoint;
yValue = tempYValue;
}
}
}
else
{
DataPoint point = _points[position];
float yValue = _yPositions[position];
_points[position] = null;
_yPositions[position] = 0;
for( int index = position; index < numOfItems-1; index++ )
{
// IsEmpty position found. Stop moving cells UP
if( _points[index+1] == null )
{
_points[index+1] = point;
_yPositions[index+1] = yValue;
break;
}
else
{
DataPoint tempPoint;
float tempYValue;
tempPoint = _points[index+1];
tempYValue = _yPositions[index+1];
_points[index+1] = point;
_yPositions[index+1] = yValue;
point = tempPoint;
yValue = tempYValue;
}
}
}
}
/// <summary>
/// This method is used to center labels in
/// the middle of chart area (vertically).
/// </summary>
internal void AdjustPositions()
{
int numEmptyUp = 0;
int numEmptyDown = 0;
// Adjust position only if there are many labels
if( _numOfInsertedLabels < _points.Length / 2 )
return;
// Find the number of empty label positions on the top.
for( int point = 0; point < _points.Length && _points[point] == null; point++ )
{
numEmptyUp++;
}
// Find the number of empty label positions on the bottom.
for( int point = _points.Length - 1; point >= 0 && _points[point] == null; point-- )
{
numEmptyDown++;
}
// Find where are more empty spaces – on the top or on the bottom.
bool moreEmptyUp = numEmptyUp > numEmptyDown ? true : false;
// Find average number of empty spaces for top and bottom.
int numMove = ( numEmptyUp + numEmptyDown ) / 2;
// If difference between empty spaces on the top and
// the bottom is not bigger then 2 do not adjust labels.
if( Math.Abs( numEmptyUp - numEmptyDown ) < 2 )
return;
if( moreEmptyUp )
{
// Move labels UP
int indexPoint = 0;
for( int point = numMove; point < _points.Length; point++ )
{
if(numEmptyUp+indexPoint > _points.Length - 1)
break;
_points[point] = _points[numEmptyUp+indexPoint];
_points[numEmptyUp+indexPoint] = null;
indexPoint++;
}
}
else
{
// Move labels DOWN
int indexPoint = _points.Length - 1;
for( int point = _points.Length - 1 - numMove; point >= 0; point-- )
{
if(indexPoint - numEmptyDown < 0)
break;
_points[point] = _points[indexPoint - numEmptyDown];
_points[indexPoint - numEmptyDown] = null;
indexPoint--;
}
}
}
/// <summary>
/// Check if there is empty cell Labels column in
/// specified direction from specified position
/// </summary>
/// <param name="position">Start Position for testing</param>
/// <param name="upDirection">True if direction is upward, false if downward</param>
/// <returns>True if there is empty cell</returns>
private bool CheckFreeSpace( int position, bool upDirection )
{
if( upDirection )
{
// Position is on the beginning. There is no empty space.
if( position == 0 )
{
return false;
}
for( int index = position - 1; index >= 0; index-- )
{
// There is empty space
if( _points[index] == null )
{
return true;
}
}
}
else
{
// Position is on the end. There is no empty space.
if( position == numOfItems - 1 )
{
return false;
}
for( int index = position + 1; index < numOfItems; index++ )
{
// There is empty space
if( _points[index] == null )
{
return true;
}
}
}
// There is no empty space
return false;
}
/// <summary>
/// This method initialize label column.
/// </summary>
/// <param name="rectangle">Rectangle used for labels</param>
/// <param name="rightPosition">True if labels are on the right side of chart area.</param>
/// <param name="maxNumOfRows">Maximum nuber of rows.</param>
/// <param name="labelLineSize">Value for label line size from custom attribute.</param>
internal void Initialize( RectangleF rectangle, bool rightPosition, int maxNumOfRows, float labelLineSize )
{
// Minimum number of rows.
numOfItems = Math.Max( numOfItems, maxNumOfRows );
// Find height of rows
columnHeight = _chartAreaPosition.Height / numOfItems;
// Set inner plot position
_innerPlotPosition = rectangle;
// Init data column
_points = new DataPoint[numOfItems];
// Init y position column
_yPositions = new float[numOfItems];
// Label column position
this._rightPosition = rightPosition;
// 3D Label line size
this._labelLineSize = labelLineSize;
}
}
#endregion // 3D Label column class
#region 3D Labels
/// <summary>
/// This method calculates initial pie size if outside 3D labels is active.
/// </summary>
/// <param name="graph">Chart Graphics object.</param>
/// <param name="area">Chart Area.</param>
/// <param name="pieRectangle">Rectangle which is used for drawing pie.</param>
/// <param name="pieWidth">Width of pie slice.</param>
/// <param name="dataPoints">List of data points.</param>
/// <param name="startAngleList">List of start angles.</param>
/// <param name="sweepAngleList">List of sweep angles.</param>
/// <param name="series">Data series used for drawing pie chart.</param>
/// <param name="labelLineSize">Custom Attribute for label line size.</param>
private void InitPieSize(
ChartGraphics graph,
ChartArea area,
ref RectangleF pieRectangle,
ref float pieWidth,
DataPoint [] dataPoints,
float [] startAngleList,
float [] sweepAngleList,
Series series,
float labelLineSize
)
{
labelColumnLeft = new LabelColumn(area.Position.ToRectangleF());
labelColumnRight = new LabelColumn(area.Position.ToRectangleF());
float maxSize = float.MinValue;
float maxSizeVertical = float.MinValue;
int pointIndx = 0;
// Loop which finds max label size and number of label rows.
foreach( DataPoint point in dataPoints )
{
// Do not process empty points
if( point.IsEmpty )
{
continue;
}
float midAngle = startAngleList[pointIndx] + sweepAngleList[pointIndx] / 2F;
if( midAngle >= -90 && midAngle < 90 || midAngle >= 270 && midAngle < 450 )
{
labelColumnRight.numOfItems++;
}
else
{
labelColumnLeft.numOfItems++;
}
// Find size of the maximum label string.
SizeF size = graph.MeasureStringRel( GetLabelText( point ).Replace("\\n", "\n"), point.Font );
maxSize = Math.Max( size.Width, maxSize );
maxSizeVertical = Math.Max( size.Height, maxSizeVertical );
pointIndx++;
}
float oldWidth = pieRectangle.Width;
float oldHeight = pieRectangle.Height;
// Find size of inner plot are
pieRectangle.Width = pieRectangle.Width - 2F * maxSize - 2 * pieRectangle.Width * labelLineSize;
pieRectangle.Height = pieRectangle.Height - pieRectangle.Height * 0.3F;
// Size of pie chart can not be less then MinimumRelativePieSize of chart area.
if( pieRectangle.Width < oldWidth * (float)this.MinimumRelativePieSize( area ) )
{
pieRectangle.Width = oldWidth * (float)this.MinimumRelativePieSize( area );
}
// Size of pie chart can not be less then MinimumRelativePieSize of chart area.
if( pieRectangle.Height < oldHeight * (float)this.MinimumRelativePieSize( area ) )
{
pieRectangle.Height = oldHeight * (float)this.MinimumRelativePieSize( area );
}
// Size has to be reduce always because of label lines.
if( oldWidth * 0.8F < pieRectangle.Width )
{
pieRectangle.Width *= 0.8F;
}
pieRectangle.X = pieRectangle.X + ( oldWidth - pieRectangle.Width ) / 2F;
pieWidth = pieRectangle.Width / oldWidth * pieWidth;
pieRectangle.Y = pieRectangle.Y + ( oldHeight - pieRectangle.Height ) / 2F;
// Find maximum number of rows. Number of rows will be changed
// but this is only recommendation, which depends on font size
// and Height of chart area.
SizeF fontSize = new SizeF(1.4F * series.Font.Size,1.4F * series.Font.Size);
fontSize = graph.GetRelativeSize( fontSize );
int maxNumOfRows = (int)( pieRectangle.Height / maxSizeVertical/*fontSize.Height*/ );
// Initialize label column
labelColumnRight.Initialize( pieRectangle, true, maxNumOfRows, labelLineSize );
labelColumnLeft.Initialize( pieRectangle, false, maxNumOfRows, labelLineSize );
}
/// <summary>
/// This method inserts outside 3D labels into array of Label column class.
/// </summary>
/// <param name="graph">Chart Graphics object.</param>
/// <param name="area">Chart Area.</param>
/// <param name="pieRectangle">Rectangle used for drawing pie slices.</param>
/// <param name="pieWidth">Width of a pie slice.</param>
/// <param name="point">Data Point.</param>
/// <param name="startAngle">Start angle of a pie slice.</param>
/// <param name="sweepAngle">Sweep angle of a pie slice.</param>
/// <param name="pointIndx">Data point index.</param>
/// <param name="doughnutRadius">Inner Radius of the doughnut.</param>
/// <param name="exploded">true if pie slice is exploded.</param>
private void FillPieLabelOutside(
ChartGraphics graph,
ChartArea area,
RectangleF pieRectangle,
float pieWidth,
DataPoint point,
float startAngle,
float sweepAngle,
int pointIndx,
float doughnutRadius,
bool exploded
)
{
float midAngle = startAngle + sweepAngle / 2F;
PointF [] piePoints = GetPiePoints( graph, area, pieWidth, pieRectangle, startAngle, sweepAngle, false, doughnutRadius, exploded );
float y = piePoints[(int)PiePoints.TopLabelLineout].Y;
if( midAngle >= -90 && midAngle < 90 || midAngle >= 270 && midAngle < 450 )
{
labelColumnRight.InsertLabel( point, y, pointIndx );
}
else
{
labelColumnLeft.InsertLabel( point, y, pointIndx );
}
}
/// <summary>
/// This method draws outside labels with lines, which
/// connect labels with pie slices.
/// </summary>
/// <param name="graph">Chart Graphics object</param>
/// <param name="area">Chart Area</param>
/// <param name="pen">Pen object</param>
/// <param name="points">Important pie points</param>
/// <param name="point">Data point</param>
/// <param name="midAngle">Middle Angle for pie slice</param>
/// <param name="pointIndex">Point Index.</param>
private void Draw3DOutsideLabels(
ChartGraphics graph,
ChartArea area,
Pen pen,
PointF [] points,
DataPoint point,
float midAngle,
int pointIndex)
{
// Take label text
string text = GetLabelText( point );
if(text.Length == 0)
{
return;
}
graph.DrawLine( pen, points[(int)PiePoints.TopLabelLine], points[(int)PiePoints.TopLabelLineout] );
LabelColumn columnLabel;
using (StringFormat format = new StringFormat())
{
format.LineAlignment = StringAlignment.Center;
RectangleF chartAreaPosition = graph.GetAbsoluteRectangle(area.Position.ToRectangleF());
RectangleF labelPosition = RectangleF.Empty;
PointF labelPoint;
if (midAngle >= -90 && midAngle < 90 || midAngle >= 270 && midAngle < 450)
{
columnLabel = labelColumnRight;
format.Alignment = StringAlignment.Near;
float labelVertSize = graph.GetAbsoluteSize(new SizeF(0f, this.labelColumnRight.columnHeight)).Height;
labelPoint = graph.GetAbsolutePoint(columnLabel.GetLabelPosition(point));
// Label has to be right from TopLabelLineOut
if (points[(int)PiePoints.TopLabelLineout].X > labelPoint.X)
{
labelPoint.X = points[(int)PiePoints.TopLabelLineout].X + 10;
}
labelPosition.X = labelPoint.X;
labelPosition.Width = chartAreaPosition.Right - labelPosition.X;
labelPosition.Y = labelPoint.Y - labelVertSize / 2;
labelPosition.Height = labelVertSize;
}
else
{
columnLabel = labelColumnLeft;
format.Alignment = StringAlignment.Far;
float labelVertSize = graph.GetAbsoluteSize(new SizeF(0f, this.labelColumnLeft.columnHeight)).Height;
labelPoint = graph.GetAbsolutePoint(columnLabel.GetLabelPosition(point));
// Label has to be left from TopLabelLineOut
if (points[(int)PiePoints.TopLabelLineout].X < labelPoint.X)
{
labelPoint.X = points[(int)PiePoints.TopLabelLineout].X - 10;
}
labelPosition.X = chartAreaPosition.X;
labelPosition.Width = labelPoint.X - labelPosition.X;
labelPosition.Y = labelPoint.Y - labelVertSize / 2;
labelPosition.Height = labelVertSize;
}
format.FormatFlags = StringFormatFlags.NoWrap | StringFormatFlags.LineLimit;
format.Trimming = StringTrimming.EllipsisWord;
graph.DrawLine(pen, points[(int)PiePoints.TopLabelLineout], labelPoint);
// Get label relative position
labelPosition = graph.GetRelativeRectangle(labelPosition);
// Get label background position
SizeF valueTextSize = graph.MeasureStringRel(text.Replace("\\n", "\n"), point.Font);
valueTextSize.Height += valueTextSize.Height / 8;
float spacing = valueTextSize.Width / text.Length / 2;
valueTextSize.Width += spacing;
RectangleF labelBackPosition = new RectangleF(
labelPosition.X,
labelPosition.Y + labelPosition.Height / 2f - valueTextSize.Height / 2f,
valueTextSize.Width,
valueTextSize.Height);
// Adjust position based on alignment
if (format.Alignment == StringAlignment.Near)
{
labelBackPosition.X -= spacing / 2f;
}
else if (format.Alignment == StringAlignment.Center)
{
labelBackPosition.X = labelPosition.X + (labelPosition.Width - valueTextSize.Width) / 2f;
}
else if (format.Alignment == StringAlignment.Far)
{
labelBackPosition.X = labelPosition.Right - valueTextSize.Width - spacing / 2f;
}
// Draw label text
using (Brush brush = new SolidBrush(point.LabelForeColor))
{
graph.DrawPointLabelStringRel(
graph.Common,
text,
point.Font,
brush,
labelPosition,
format,
0,
labelBackPosition,
point.LabelBackColor,
point.LabelBorderColor,
point.LabelBorderWidth,
point.LabelBorderDashStyle,
point.series,
point,
pointIndex);
}
}
}
/// <summary>
/// This method draws inside labels.
/// </summary>
/// <param name="graph">Chart Graphics object</param>
/// <param name="points">Important pie points</param>
/// <param name="point">Data point</param>
/// <param name="pointIndex">Data point index</param>
private void Draw3DInsideLabels( ChartGraphics graph, PointF [] points, DataPoint point, int pointIndex )
{
// Set String Alignment
StringFormat format = new StringFormat();
format.LineAlignment = StringAlignment.Center;
format.Alignment = StringAlignment.Center;
// Take label text
string text = GetLabelText( point );
// Get label relative position
PointF labelPosition = graph.GetRelativePoint(points[(int)PiePoints.TopLabelCenter]);
// Measure string
SizeF sizeFont = graph.GetRelativeSize(
graph.MeasureString(
text.Replace("\\n", "\n"),
point.Font,
new SizeF(1000f, 1000f),
new StringFormat(StringFormat.GenericTypographic)));
// Get label background position
RectangleF labelBackPosition = RectangleF.Empty;
SizeF sizeLabel = new SizeF(sizeFont.Width, sizeFont.Height);
sizeLabel.Height += sizeFont.Height / 8;
sizeLabel.Width += sizeLabel.Width / text.Length;
labelBackPosition = new RectangleF(
labelPosition.X - sizeLabel.Width/2,
labelPosition.Y - sizeLabel.Height/2 - sizeFont.Height / 10,
sizeLabel.Width,
sizeLabel.Height);
// Draw label text
using (Brush brush = new SolidBrush(point.LabelForeColor))
{
graph.DrawPointLabelStringRel(
graph.Common,
text,
point.Font,
brush,
labelPosition,
format,
0,
labelBackPosition,
point.LabelBackColor,
point.LabelBorderColor,
point.LabelBorderWidth,
point.LabelBorderDashStyle,
point.series,
point,
pointIndex);
}
}
/// <summary>
/// Gets the point label.
/// </summary>
/// <param name="point">The point.</param>
/// <returns></returns>
private String GetPointLabel(DataPoint point)
{
String pointLabel = String.Empty;
// If There is no Label take axis Label
if( point.Label.Length == 0 )
{
pointLabel = point.AxisLabel;
// remove axis label if is set the CustomPropertyName.PieAutoAxisLabels and is set to false
if (point.series != null &&
point.series.IsCustomPropertySet(CustomPropertyName.PieAutoAxisLabels) &&
String.Equals(point.series.GetCustomProperty(CustomPropertyName.PieAutoAxisLabels), "false", StringComparison.OrdinalIgnoreCase))
{
pointLabel = String.Empty;
}
}
else
pointLabel = point.Label;
return point.ReplaceKeywords(pointLabel);
}
/// <summary>
/// Take formated text from label or axis label
/// </summary>
/// <param name="point">Data point which is used.</param>
/// <returns>Formated text</returns>
private string GetLabelText( DataPoint point )
{
string pointLabel = this.GetPointLabel(point);
// Get label text
string text;
if( point.Label.Length == 0 && point.IsValueShownAsLabel )
{
text = ValueConverter.FormatValue(
point.series.Chart,
point,
point.Tag,
point.YValues[0],
point.LabelFormat,
point.series.YValueType,
ChartElementType.DataPoint);
}
else
{
text = pointLabel;
}
// Retuen formated label or axis label text
return text;
}
#endregion
#region SmartLabelStyle methods
/// <summary>
/// Adds markers position to the list. Used to check SmartLabelStyle overlapping.
/// </summary>
/// <param name="common">Common chart elements.</param>
/// <param name="area">Chart area.</param>
/// <param name="series">Series values to be used.</param>
/// <param name="list">List to add to.</param>
public void AddSmartLabelMarkerPositions(CommonElements common, ChartArea area, Series series, ArrayList list)
{
}
#endregion
#region IDisposable interface implementation
/// <summary>
/// Releases unmanaged and - optionally - managed resources
/// </summary>
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
protected virtual void Dispose(bool disposing)
{
//Nothing to dispose at the base class.
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
}
}
|