|
//-------------------------------------------------------------
// <copyright company=’Microsoft Corporation’>
// Copyright © Microsoft Corporation. All Rights Reserved.
// </copyright>
//-------------------------------------------------------------
// @owner=alexgor, deliant
//=================================================================
// File: StackedAreaChart.cs
//
// Namespace: DataVisualization.Charting.ChartTypes
//
// Classes: StackedAreaChart, HundredPercentStackedAreaChart
//
// Purpose: Stacked area and hundred percent stacked area charts.
//
// Reviewed: AG - Aug 6, 2002
// AG - Microsoft 7, 2007
//
//===================================================================
#region Used namespaces
using System;
using System.Resources;
using System.Reflection;
using System.Collections;
using System.Drawing;
using System.Drawing.Drawing2D;
#if Microsoft_CONTROL
using System.Windows.Forms.DataVisualization.Charting;
using System.Windows.Forms.DataVisualization.Charting.Data;
using System.Windows.Forms.DataVisualization.Charting.ChartTypes;
using System.Windows.Forms.DataVisualization.Charting.Utilities;
using System.Windows.Forms.DataVisualization.Charting.Borders3D;
#else
using System.Web.UI.DataVisualization.Charting;
using System.Web.UI.DataVisualization.Charting.ChartTypes;
using System.Web.UI.DataVisualization.Charting.Data;
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
{
/// <summary>
/// HundredPercentStackedAreaChart class extends StackedAreaChart class
/// by providing its own algorithm for calculating series data point
/// Y values. It makes sure that total Y value of all data points in a
/// single cluster from all series adds up to 100%.
/// </summary>
internal class HundredPercentStackedAreaChart : StackedAreaChart
{
#region Constructor
/// <summary>
/// Default constructor.
/// </summary>
public HundredPercentStackedAreaChart()
{
hundredPercentStacked = true;
}
#endregion
#region Fields
// Array of total points values
double[] _totalPerPoint = null;
int _seriesCount = -1;
#endregion
#region IChartType interface implementation
/// <summary>
/// Chart type name
/// </summary>
override public string Name { get{ return ChartTypeNames.OneHundredPercentStackedArea;}}
/// <summary>
/// Indicates that it's a hundredred percent chart.
/// Axis scale from 0 to 100 percent should be used.
/// </summary>
override public bool HundredPercent{ get{return true;} }
#endregion
#region Painting and Selection methods
/// <summary>
/// Paint HundredPercentStackedAreaChart 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>
override public void Paint( ChartGraphics graph, CommonElements common, ChartArea area, Series seriesToDraw )
{
this.Common = common;
// Reset total per point value
_totalPerPoint = null;
_seriesCount = -1;
// Call base class implementation
base.Paint( graph, common, area, seriesToDraw );
}
#endregion
#region Y values related methods
/// <summary>
/// Returns series count of same type for given chart area.
/// </summary>
/// <param name="common">The common elements</param>
/// <param name="area">The chart area to inspect</param>
/// <returns>Series count of same type</returns>
private int GetSeriesCount(CommonElements common, ChartArea area)
{
if (_seriesCount == -1)
{
// Get number of series
int seriesCount = 0;
foreach (Series ser in common.DataManager.Series)
{
// Use series of the same type which belong to this area
if (String.Compare(ser.ChartTypeName, Name, true, System.Globalization.CultureInfo.CurrentCulture) == 0
&& ser.ChartArea == area.Name && ser.IsVisible())
{
++seriesCount;
}
}
_seriesCount = seriesCount;
}
return _seriesCount;
}
/// <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>
override public double GetYValue(CommonElements common, ChartArea area, Series series, DataPoint point, int pointIndex, int yValueIndex)
{
// Calculate the totals of all Y values for all series
if(_totalPerPoint == null)
{
// Get number of series
int seriesCount = GetSeriesCount(common, area);
// Fill array of series with this type, which are drawn on this area
Series[] seriesArray = new Series[seriesCount];
int seriesIndex = 0;
foreach( Series ser in common.DataManager.Series )
{
// Use series of the same type which belong to this area
if( String.Compare( ser.ChartTypeName, Name, true, System.Globalization.CultureInfo.CurrentCulture ) == 0
&& ser.ChartArea == area.Name && ser.IsVisible())
{
seriesArray[seriesIndex++] = ser;
}
}
// Check if series are aligned
common.DataManipulator.CheckXValuesAlignment(seriesArray);
// Allocate memory for the array
_totalPerPoint = new double[series.Points.Count];
// Calculate the total of Y value per point
for(int index = 0; index < series.Points.Count; index++)
{
_totalPerPoint[index] = 0;
foreach( Series ser in seriesArray )
{
_totalPerPoint[index] += Math.Abs(ser.Points[index].YValues[0]);
}
}
}
// NOTE: In stacked area chart we need to do processing even if Y value is not set
// if(point.YValues[0] == 0 || point.IsEmpty)
// {
// return 0;
// }
// Calculate stacked area Y value for 2D chart
if(area.Area3DStyle.Enable3D == false)
{
if (_totalPerPoint[pointIndex] == 0)
{
// Get number of series
int seriesCount = GetSeriesCount(common, area);
return 100.0 / seriesCount;
}
return (point.YValues[0] / _totalPerPoint[pointIndex]) * 100.0;
}
// Get point Height if pointIndex == -1
double yValue = double.NaN;
if(yValueIndex == -1)
{
Axis vAxis = area.GetAxis(AxisName.Y, series.YAxisType, series.YSubAxisName);
double areaZeroValue = vAxis.Crossing;
yValue = GetYValue(common, area, series, point, pointIndex, 0);
if(area.Area3DStyle.Enable3D && yValue < 0.0)
{
// No negative values support in 3D stacked area chart
yValue = -yValue;
}
if( yValue >= 0 )
{
if(!double.IsNaN(prevPosY))
{
areaZeroValue = prevPosY;
}
}
else
{
if(!double.IsNaN(prevNegY))
{
areaZeroValue = prevNegY;
}
}
return yValue - areaZeroValue;
}
// Loop through all series
prevPosY = double.NaN;
prevNegY = double.NaN;
prevPositionX = double.NaN;
foreach(Series ser in common.DataManager.Series)
{
// Check series of the current chart type & area
if(String.Compare(series.ChartArea, ser.ChartArea, true, System.Globalization.CultureInfo.CurrentCulture) == 0 &&
String.Compare(series.ChartTypeName, ser.ChartTypeName, true, System.Globalization.CultureInfo.CurrentCulture) == 0 &&
series.IsVisible())
{
yValue = (ser.Points[pointIndex].YValues[0] / _totalPerPoint[pointIndex]) * 100.0;
// Fix of bug #677411 - Dev10 3D stacked area throws an exception when casting NaN to decimal
if (double.IsNaN(yValue) && _totalPerPoint[pointIndex] == 0)
{
yValue = 100.0 / GetSeriesCount(common, area);
}
if(!double.IsNaN(yValue))
if(area.Area3DStyle.Enable3D && yValue < 0.0)
{
// No negative values support in 3D stacked area chart
yValue = -yValue;
}
{
if(yValue >= 0.0 && !double.IsNaN(prevPosY))
{
yValue += prevPosY;
}
if(yValue < 0.0 && !double.IsNaN(prevNegY))
{
yValue += prevNegY;
}
}
// Exit loop when current series was found
if (String.Compare(series.Name, ser.Name, StringComparison.Ordinal) == 0)
{
break;
}
// Remenber privious position
if(yValue >= 0.0)
{
prevPosY = yValue;
}
else
{
prevNegY = yValue;
}
prevPositionX = ser.Points[pointIndex].XValue;
if(prevPositionX == 0.0 && ChartHelper.IndexedSeries(series))
{
prevPositionX = pointIndex + 1;
}
}
}
// Y value can't be more than a 100%
if(yValue > 100.0)
{
return 100.0;
}
return yValue;
}
#endregion
}
/// <summary>
/// StackedAreaChart class extends AreaChart so that chart series are
/// positioned on top of each other.
/// </summary>
internal class StackedAreaChart : AreaChart
{
#region Fields
/// <summary>
/// Shape of the previous series
/// </summary>
protected GraphicsPath areaBottomPath = new GraphicsPath();
/// <summary>
/// Previous stacked positive Y values.
/// </summary>
protected double prevPosY = double.NaN;
/// <summary>
/// Previous stacked negative Y values.
/// </summary>
protected double prevNegY = double.NaN;
/// <summary>
/// Previous X value.
/// </summary>
protected double prevPositionX = double.NaN;
/// <summary>
/// Indicates if chart is 100% stacked
/// </summary>
protected bool hundredPercentStacked = false;
#endregion
#region Constructor
/// <summary>
/// Public constructor.
/// </summary>
public StackedAreaChart()
{
multiSeries = true;
COPCoordinatesToCheck = COPCoordinates.X | COPCoordinates.Y;
}
#endregion
#region Default tension method
/// <summary>
/// Gets default line tension.
/// </summary>
/// <returns>Line tension.</returns>
override protected float GetDefaultTension()
{
return 0f;
}
#endregion
#region IChartType interface implementation
/// <summary>
/// Chart type name
/// </summary>
public override string Name { get{ return ChartTypeNames.StackedArea;}}
/// <summary>
/// True if chart type is stacked
/// </summary>
public override bool Stacked { get{ return true;}}
/// <summary>
/// Gets chart type image.
/// </summary>
/// <param name="registry">Chart types registry object.</param>
/// <returns>Chart type image.</returns>
override public System.Drawing.Image GetImage(ChartTypeRegistry registry)
{
return (System.Drawing.Image)registry.ResourceManager.GetObject(this.Name + "ChartType");
}
#endregion
#region Painting and Selection methods
/// <summary>
/// Paint Stacked Area 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 override void Paint( ChartGraphics graph, CommonElements common, ChartArea area, Series seriesToDraw )
{
this.Common = common;
// Set Clip Region
graph.SetClip( area.PlotAreaPosition.ToRectangleF() );
// Draw chart
ProcessChartType( false, graph, common, area, seriesToDraw );
// Reset Clip Region
((ChartGraphics)graph).ResetClip();
}
/// <summary>
/// This method calculates position of the area and either draws it or checks selection.
/// </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="seriesToDraw">Chart series to draw.</param>
override protected void ProcessChartType(
bool selection,
ChartGraphics graph,
CommonElements common,
ChartArea area,
Series seriesToDraw)
{
this.Common = common;
ArrayList prevPointsArray = null;
ArrayList curentPointsArray = null;
// Prosess 3D chart type
if(area.Area3DStyle.Enable3D)
{
base.ProcessChartType(
selection,
graph,
common,
area,
seriesToDraw);
return;
}
// Zero X values mode.
bool indexedSeries = ChartHelper.IndexedSeries(this.Common, area.GetSeriesFromChartType(this.Name).ToArray() );
// Indicates that the second point loop for drawing lines or labels is required
bool requiresSecondPointLoop = false;
bool requiresThirdPointLoop = false;
//************************************************************
//** Loop through all series
//************************************************************
int seriesPointsNumber = -1;
foreach( Series ser in common.DataManager.Series )
{
// Process non empty series of the area with area chart type
if( String.Compare( ser.ChartTypeName, this.Name, StringComparison.OrdinalIgnoreCase ) != 0
|| ser.ChartArea != area.Name || !ser.IsVisible())
{
continue;
}
// Reset area shape paths
if(areaPath != null)
{
areaPath.Dispose();
areaPath = null;
}
areaBottomPath.Reset();
// Check that all seres has the same number of points
if(seriesPointsNumber == -1)
{
seriesPointsNumber = ser.Points.Count;
}
else if(seriesPointsNumber != ser.Points.Count)
{
throw (new ArgumentException(SR.ExceptionStackedAreaChartSeriesDataPointsNumberMismatch));
}
// Set active horizontal/vertical axis
HAxis = area.GetAxis(AxisName.X, ser.XAxisType, ser.XSubAxisName);
VAxis = area.GetAxis(AxisName.Y, ser.YAxisType, ser.YSubAxisName);
hAxisMin = HAxis.ViewMinimum;
hAxisMax = HAxis.ViewMaximum;
vAxisMin = VAxis.ViewMinimum;
vAxisMax = VAxis.ViewMaximum;
// Get axis position
axisPos.X = (float)VAxis.GetPosition(this.VAxis.Crossing);
axisPos.Y = (float)VAxis.GetPosition(this.VAxis.Crossing);
axisPos = graph.GetAbsolutePoint(axisPos);
// Fill previous series values array
if(curentPointsArray == null)
{
curentPointsArray = new ArrayList(ser.Points.Count);
}
else
{
prevPointsArray = curentPointsArray;
curentPointsArray = new ArrayList(ser.Points.Count);
}
// Call Back Paint event
if( !selection )
{
common.Chart.CallOnPrePaint(new ChartPaintEventArgs(ser, graph, common, area.PlotAreaPosition));
}
// The data points loop
int index = 0;
float prevYValue1 = axisPos.Y;
float prevYValue2 = axisPos.Y;
PointF firstPoint = PointF.Empty;
PointF secondPoint = PointF.Empty;
foreach( DataPoint point in ser.Points )
{
// Reset pre-calculated point position
point.positionRel = new PointF(float.NaN, float.NaN);
// Get point value
double yValue = (point.IsEmpty) ? 0.0 : GetYValue(common, area, ser, point, index, 0);
double xValue = (indexedSeries) ? (index + 1.0) : point.XValue;
// Adjust point position with previous value
if(prevPointsArray != null && index < prevPointsArray.Count)
{
yValue += (double)prevPointsArray[index];
}
curentPointsArray.Insert(index, yValue);
// Get point position
float yPosition = (float)VAxis.GetPosition(yValue);
float xPosition = (float)HAxis.GetPosition(xValue);
// Remeber pre-calculated point position
point.positionRel = new PointF(xPosition, yPosition);
yValue = VAxis.GetLogValue(yValue);
xValue = HAxis.GetLogValue(xValue);
// Calculate 2 points to draw area and line
if(firstPoint == PointF.Empty)
{
firstPoint.X = xPosition;
firstPoint.Y = yPosition;
if(prevPointsArray != null && index < prevPointsArray.Count)
{
prevYValue1 = (float)VAxis.GetPosition((double)prevPointsArray[index]);
prevYValue1 = graph.GetAbsolutePoint(new PointF(prevYValue1, prevYValue1)).Y;
}
firstPoint = graph.GetAbsolutePoint(firstPoint);
++index;
continue;
}
else
{
secondPoint.X = xPosition;
secondPoint.Y = yPosition;
if(prevPointsArray != null && index < prevPointsArray.Count)
{
prevYValue2 = (float)VAxis.GetPosition((double)prevPointsArray[index]);
prevYValue2 = graph.GetAbsolutePoint(new PointF(prevYValue2, prevYValue2)).Y;
}
secondPoint = graph.GetAbsolutePoint(secondPoint);
}
// Round X coordinates
firstPoint.X = (float)Math.Round(firstPoint.X);
secondPoint.X = (float)Math.Round(secondPoint.X);
// Calculate data point area segment path
using (GraphicsPath path = new GraphicsPath())
{
path.AddLine(firstPoint.X, firstPoint.Y, secondPoint.X, secondPoint.Y);
path.AddLine(secondPoint.X, secondPoint.Y, secondPoint.X, prevYValue2);
path.AddLine(secondPoint.X, prevYValue2, firstPoint.X, prevYValue1);
path.AddLine(firstPoint.X, prevYValue1, firstPoint.X, firstPoint.Y);
// Painting mode
if (common.ProcessModePaint)
{
// Get previous point value
double xPrevValue = (indexedSeries) ? (index) : ser.Points[index - 1].XValue;
// Check if line is completly out of the data scaleView
if ((xValue <= hAxisMin && xPrevValue <= hAxisMin) ||
(xValue >= hAxisMax && xPrevValue >= hAxisMax))
{
// Save previous point
firstPoint = secondPoint;
prevYValue1 = prevYValue2;
// Increase data point index
++index;
continue;
}
// Create area brush
Brush areaBrush = null;
if (point.BackHatchStyle != ChartHatchStyle.None)
{
areaBrush = graph.GetHatchBrush(point.BackHatchStyle, point.Color, point.BackSecondaryColor);
}
else if (point.BackGradientStyle != GradientStyle.None)
{
this.gradientFill = true;
this.Series = point.series;
}
else if (point.BackImage.Length > 0 && point.BackImageWrapMode != ChartImageWrapMode.Unscaled && point.BackImageWrapMode != ChartImageWrapMode.Scaled)
{
areaBrush = graph.GetTextureBrush(point.BackImage, point.BackImageTransparentColor, point.BackImageWrapMode, point.Color);
}
else if (point.IsEmpty && point.Color == Color.Empty)
{
// Stacked area chart empty points should always use
// series color, otherwise chart will have empty 'holes'.
areaBrush = new SolidBrush(ser.Color);
}
else
{
areaBrush = new SolidBrush(point.Color);
}
// Check if we need second loop to draw area border
if ((point.BorderColor != Color.Empty && point.BorderWidth > 0))
{
requiresSecondPointLoop = true;
}
// Check if we need third loop to draw labels
if (point.Label.Length > 0 || point.IsValueShownAsLabel)
{
requiresThirdPointLoop = true;
}
// Draw area
if (!this.gradientFill)
{
// Start Svg Selection mode
graph.StartHotRegion(point);
// Turn off anti aliasing and fill area
SmoothingMode oldMode = graph.SmoothingMode;
graph.SmoothingMode = SmoothingMode.None;
graph.FillPath(areaBrush, path);
graph.SmoothingMode = oldMode;
// Draw top and bottom lines with antialiasing turned On.
// Process only if line is drawn by an angle
Pen areaLinePen = new Pen(areaBrush, 1);
if (!(firstPoint.X == secondPoint.X || firstPoint.Y == secondPoint.Y))
{
graph.DrawLine(areaLinePen, firstPoint.X, firstPoint.Y, secondPoint.X, secondPoint.Y);
}
if (!(firstPoint.X == secondPoint.X || prevYValue2 == prevYValue1))
{
graph.DrawLine(areaLinePen, secondPoint.X, prevYValue2, firstPoint.X, prevYValue1);
}
// End Svg Selection mode
graph.EndHotRegion();
}
if (areaPath == null)
{
areaPath = new GraphicsPath();
}
areaPath.AddLine(firstPoint.X, firstPoint.Y, secondPoint.X, secondPoint.Y);
areaBottomPath.AddLine(firstPoint.X, prevYValue1, secondPoint.X, prevYValue2);
//Clean up
if (areaBrush != null)
areaBrush.Dispose();
}
if (common.ProcessModeRegions)
{
//**************************************************************
//** Add area for the inside of the area
//**************************************************************
// Allocate array of floats
PointF pointNew = PointF.Empty;
float[] coord = new float[path.PointCount * 2];
PointF[] pathPoints = path.PathPoints;
for (int i = 0; i < path.PointCount; i++)
{
pointNew = graph.GetRelativePoint(pathPoints[i]);
coord[2 * i] = pointNew.X;
coord[2 * i + 1] = pointNew.Y;
}
common.HotRegionsList.AddHotRegion(
path,
false,
coord,
point,
ser.Name,
index);
//**************************************************************
//** Add area for the top line (with thickness)
//**************************************************************
if (point.BorderWidth > 1 && point.BorderDashStyle != ChartDashStyle.NotSet && point.BorderColor != Color.Empty)
{
// Create grapics path object dor the curve
using (GraphicsPath linePath = new GraphicsPath())
{
try
{
linePath.AddLine(firstPoint.X, firstPoint.Y, secondPoint.X, secondPoint.Y);
// Widen the lines to the size of pen plus 2
linePath.Widen(new Pen(point.Color, point.BorderWidth + 2));
}
catch (OutOfMemoryException)
{
// GraphicsPath.Widen incorrectly throws OutOfMemoryException
// catching here and reacting by not widening
}
catch (ArgumentException)
{
}
// Allocate array of floats
pointNew = PointF.Empty;
coord = new float[linePath.PointCount * 2];
for (int i = 0; i < linePath.PointCount; i++)
{
pointNew = graph.GetRelativePoint(linePath.PathPoints[i]);
coord[2 * i] = pointNew.X;
coord[2 * i + 1] = pointNew.Y;
}
common.HotRegionsList.AddHotRegion(
linePath,
false,
coord,
point,
ser.Name,
index);
}
}
}
}
// Save previous point
firstPoint = secondPoint;
prevYValue1 = prevYValue2;
// Increase data point index
++index;
}
// Fill whole series area with gradient
if(gradientFill && areaPath != null)
{
// Create gradient path
using (GraphicsPath gradientPath = new GraphicsPath())
{
gradientPath.AddPath(areaPath, true);
areaBottomPath.Reverse();
gradientPath.AddPath(areaBottomPath, true);
// Create brush
using (Brush areaBrush = graph.GetGradientBrush(gradientPath.GetBounds(), this.Series.Color, this.Series.BackSecondaryColor, this.Series.BackGradientStyle))
{
// Fill area with gradient
graph.FillPath(areaBrush, gradientPath);
}
}
areaPath.Dispose();
areaPath = null;
gradientFill = false;
}
areaBottomPath.Reset();
// Call Paint event
if( !selection )
{
common.Chart.CallOnPostPaint(new ChartPaintEventArgs(ser, graph, common, area.PlotAreaPosition));
}
}
//************************************************************
//** Loop through all series/points for the second time
//** Draw border lines.
//************************************************************
if(requiresSecondPointLoop)
{
prevPointsArray = null;
curentPointsArray = null;
foreach( Series ser in common.DataManager.Series )
{
if( String.Compare( ser.ChartTypeName, this.Name, StringComparison.OrdinalIgnoreCase ) != 0
|| ser.ChartArea != area.Name || !ser.IsVisible())
{
continue;
}
// Set active horizontal/vertical axis
HAxis = area.GetAxis(AxisName.X, ser.XAxisType, ser.XSubAxisName);
VAxis = area.GetAxis(AxisName.Y, ser.YAxisType, ser.YSubAxisName);
// Get axis position
axisPos.X = (float)VAxis.GetPosition(this.VAxis.Crossing);
axisPos.Y = (float)VAxis.GetPosition(this.VAxis.Crossing);
axisPos = graph.GetAbsolutePoint(axisPos);
// Fill previous series values array
if(curentPointsArray == null)
{
curentPointsArray = new ArrayList(ser.Points.Count);
}
else
{
prevPointsArray = curentPointsArray;
curentPointsArray = new ArrayList(ser.Points.Count);
}
// The data points loop
int index = 0;
float prevYValue1 = axisPos.Y;
float prevYValue2 = axisPos.Y;
PointF firstPoint = PointF.Empty;
PointF secondPoint = PointF.Empty;
foreach( DataPoint point in ser.Points )
{
// Get point value
double yValue = (point.IsEmpty) ? 0.0 : GetYValue(common, area, ser, point, index, 0);
double xValue = (indexedSeries) ? (index + 1.0) : point.XValue;
// Adjust point position with previous value
if(prevPointsArray != null && index < prevPointsArray.Count)
{
yValue += (double)prevPointsArray[index];
}
curentPointsArray.Insert(index, yValue);
// Get point position
float yPosition = (float)VAxis.GetPosition(yValue);
float xPosition = (float)HAxis.GetPosition(xValue);
// Calculate 2 points to draw area and line
if(firstPoint == PointF.Empty)
{
firstPoint.X = xPosition;
firstPoint.Y = yPosition;
if(prevPointsArray != null && index < prevPointsArray.Count)
{
prevYValue1 = (float)VAxis.GetPosition((double)prevPointsArray[index]);
prevYValue1 = graph.GetAbsolutePoint(new PointF(prevYValue1, prevYValue1)).Y;
}
firstPoint = graph.GetAbsolutePoint(firstPoint);
secondPoint = firstPoint;
prevYValue2 = prevYValue1;
}
else
{
secondPoint.X = xPosition;
secondPoint.Y = yPosition;
if(prevPointsArray != null && index < prevPointsArray.Count)
{
prevYValue2 = (float)VAxis.GetPosition((double)prevPointsArray[index]);
prevYValue2 = graph.GetAbsolutePoint(new PointF(prevYValue2, prevYValue2)).Y;
}
secondPoint = graph.GetAbsolutePoint(secondPoint);
}
if(index != 0)
{
// Round X coordinates
firstPoint.X = (float)Math.Round(firstPoint.X);
secondPoint.X = (float)Math.Round(secondPoint.X);
// Draw border
graph.DrawLineRel(point.BorderColor, point.BorderWidth, point.BorderDashStyle, graph.GetRelativePoint(firstPoint), graph.GetRelativePoint(secondPoint), point.series.ShadowColor, point.series.ShadowOffset );
}
// Save previous point
firstPoint = secondPoint;
prevYValue1 = prevYValue2;
// Increase data point index
++index;
}
}
}
//************************************************************
//** Loop through all series/points for the second time
//** Draw labels.
//************************************************************
if(requiresThirdPointLoop)
{
prevPointsArray = null;
curentPointsArray = null;
foreach( Series ser in common.DataManager.Series )
{
if( String.Compare( ser.ChartTypeName, this.Name, StringComparison.OrdinalIgnoreCase ) != 0
|| ser.ChartArea != area.Name || !ser.IsVisible())
{
continue;
}
// Set active horizontal/vertical axis
HAxis = area.GetAxis(AxisName.X, ser.XAxisType, ser.XSubAxisName);
VAxis = area.GetAxis(AxisName.Y, ser.YAxisType, ser.YSubAxisName);
// Get axis position
axisPos.X = (float)VAxis.GetPosition(this.VAxis.Crossing);
axisPos.Y = (float)VAxis.GetPosition(this.VAxis.Crossing);
axisPos = graph.GetAbsolutePoint(axisPos);
// Fill previous series values array
if(curentPointsArray == null)
{
curentPointsArray = new ArrayList(ser.Points.Count);
}
else
{
prevPointsArray = curentPointsArray;
curentPointsArray = new ArrayList(ser.Points.Count);
}
// The data points loop
int index = 0;
float prevYValue1 = axisPos.Y;
float prevYValue2 = axisPos.Y;
PointF firstPoint = PointF.Empty;
PointF secondPoint = PointF.Empty;
foreach( DataPoint point in ser.Points )
{
// Get point value
double yValue = (point.IsEmpty) ? 0.0 : GetYValue(common, area, ser, point, index, 0);
double xValue = (indexedSeries) ? (index + 1.0) : point.XValue;
// Adjust point position with previous value
if(prevPointsArray != null && index < prevPointsArray.Count)
{
yValue += (double)prevPointsArray[index];
}
curentPointsArray.Insert(index, yValue);
// Get point position
float yPosition = (float)VAxis.GetPosition(yValue);
float xPosition = (float)HAxis.GetPosition(xValue);
// Calculate 2 points to draw area and line
if(firstPoint == PointF.Empty)
{
firstPoint.X = xPosition;
firstPoint.Y = yPosition;
if(prevPointsArray != null && index < prevPointsArray.Count)
{
prevYValue1 = (float)VAxis.GetPosition((double)prevPointsArray[index]);
prevYValue1 = graph.GetAbsolutePoint(new PointF(prevYValue1, prevYValue1)).Y;
}
firstPoint = graph.GetAbsolutePoint(firstPoint);
secondPoint = firstPoint;
prevYValue2 = prevYValue1;
}
else
{
secondPoint.X = xPosition;
secondPoint.Y = yPosition;
if(prevPointsArray != null && index < prevPointsArray.Count)
{
prevYValue2 = (float)VAxis.GetPosition((double)prevPointsArray[index]);
prevYValue2 = graph.GetAbsolutePoint(new PointF(prevYValue2, prevYValue2)).Y;
}
secondPoint = graph.GetAbsolutePoint(secondPoint);
}
if(!point.IsEmpty && (ser.IsValueShownAsLabel || point.IsValueShownAsLabel || point.Label.Length > 0))
{
// Label text format
using (StringFormat format = new StringFormat())
{
format.Alignment = StringAlignment.Center;
format.LineAlignment = StringAlignment.Center;
// Get label text
string text;
if (point.Label.Length == 0)
{
double pointLabelValue = GetYValue(common, area, ser, point, index, 0);
// Round Y values for 100% stacked area
if (this.hundredPercentStacked && point.LabelFormat.Length == 0)
{
pointLabelValue = Math.Round(pointLabelValue, 2);
}
text = ValueConverter.FormatValue(
ser.Chart,
point,
point.Tag,
pointLabelValue,
point.LabelFormat,
ser.YValueType,
ChartElementType.DataPoint);
}
else
{
text = point.ReplaceKeywords(point.Label);
}
// Disable the clip region
Region oldClipRegion = graph.Clip;
graph.Clip = new Region();
// Draw label
PointF labelPosition = PointF.Empty;
labelPosition.X = secondPoint.X;
labelPosition.Y = secondPoint.Y - (secondPoint.Y - prevYValue2) / 2f;
labelPosition = graph.GetRelativePoint(labelPosition);
// Measure string
SizeF sizeFont = graph.GetRelativeSize(
graph.MeasureString(
text,
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 += 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(
common,
text,
point.Font,
brush,
labelPosition,
format,
point.LabelAngle,
labelBackPosition,
point.LabelBackColor,
point.LabelBorderColor,
point.LabelBorderWidth,
point.LabelBorderDashStyle,
ser,
point,
index);
}
// Restore old clip region
graph.Clip = oldClipRegion;
}
}
// Save previous point
firstPoint = secondPoint;
prevYValue1 = prevYValue2;
// Increase data point index
++index;
}
}
}
}
#endregion
#region 3D Drawing and selection methods
/// <summary>
/// Draws a 3D surface connecting the two specified points in 2D space.
/// Used to draw Line based charts.
/// </summary>
/// <param name="area">Chart area reference.</param>
/// <param name="graph">Chart graphics.</param>
/// <param name="matrix">Coordinates transformation matrix.</param>
/// <param name="lightStyle">LightStyle style (None, Simplistic, Realistic).</param>
/// <param name="prevDataPointEx">Previous data point object.</param>
/// <param name="positionZ">Z position of the back side of the 3D surface.</param>
/// <param name="depth">Depth of the 3D surface.</param>
/// <param name="points">Array of points.</param>
/// <param name="pointIndex">Index of point to draw.</param>
/// <param name="pointLoopIndex">Index of points loop.</param>
/// <param name="tension">Line tension.</param>
/// <param name="operationType">AxisName of operation Drawing, Calculating Path or Both</param>
/// <param name="topDarkening">Darkenning scale for top surface. 0 - None.</param>
/// <param name="bottomDarkening">Darkenning scale for bottom surface. 0 - None.</param>
/// <param name="thirdPointPosition">Position where the third point is actually located or float.NaN if same as in "firstPoint".</param>
/// <param name="fourthPointPosition">Position where the fourth point is actually located or float.NaN if same as in "secondPoint".</param>
/// <param name="clippedSegment">Indicates that drawn segment is 3D clipped. Only top/bottom should be drawn.</param>
/// <returns>Returns elemnt shape path if operationType parameter is set to CalcElementPath, otherwise Null.</returns>
protected override GraphicsPath Draw3DSurface(
ChartArea area,
ChartGraphics graph,
Matrix3D matrix,
LightStyle lightStyle,
DataPoint3D prevDataPointEx,
float positionZ,
float depth,
ArrayList points,
int pointIndex,
int pointLoopIndex,
float tension,
DrawingOperationTypes operationType,
float topDarkening,
float bottomDarkening,
PointF thirdPointPosition,
PointF fourthPointPosition,
bool clippedSegment)
{
// Call base method
if(pointLoopIndex != 2)
{
return base.Draw3DSurface(
area,
graph,
matrix,
lightStyle,
prevDataPointEx,
positionZ,
depth,
points,
pointIndex,
pointLoopIndex,
tension,
operationType,
topDarkening,
bottomDarkening,
thirdPointPosition,
fourthPointPosition,
clippedSegment);
}
// Draw labels in the third loop
else
{
DataPoint3D pointEx = ((DataPoint3D)points[pointIndex]);
// Draw label for the first point
if(pointEx.index == 2)
{
// Get point with prev index
int neighborPointIndex = 0;
DataPoint3D pointPrevEx = ChartGraphics.FindPointByIndex(points, pointEx.index - 1, pointEx, ref neighborPointIndex);
// Draw labels in the third loop
DrawLabels3D(
area,
graph,
area.Common,
pointPrevEx,
positionZ,
depth);
}
// Draw labels in the third loop
DrawLabels3D(
area,
graph,
area.Common,
pointEx,
positionZ,
depth);
}
return new GraphicsPath();
}
/// <summary>
/// Gets visibility of the top surface.
/// </summary>
/// <param name="area">Chart area object.</param>
/// <param name="firstPoint">First data point of the line.</param>
/// <param name="secondPoint">Second data point of the line.</param>
/// <param name="upSideDown">Indicates that Y values of the data points are below axis line.</param>
/// <param name="positionZ">Z coordinate of the back side of the cube.</param>
/// <param name="depth">Cube depth.</param>
/// <param name="matrix">Coordinate transformation matrix.</param>
/// <param name="visibleSurfaces">Surface visibility reference. Initialized with bounary cube visibility.</param>
protected override void GetTopSurfaceVisibility(
ChartArea area,
DataPoint3D firstPoint,
DataPoint3D secondPoint,
bool upSideDown,
float positionZ,
float depth,
Matrix3D matrix,
ref SurfaceNames visibleSurfaces)
{
// Call base class method first
base.GetTopSurfaceVisibility(area, firstPoint, secondPoint, upSideDown,
positionZ, depth, matrix, ref visibleSurfaces);
// Check if the Top surface is overlapped with data point from other series
if( (visibleSurfaces & SurfaceNames.Top) == SurfaceNames.Top )
{
// Try to find data point with same index from the series above
bool seriesFound = false;
foreach(Series ser in area.Common.DataManager.Series)
{
if(String.Compare(ser.ChartTypeName, secondPoint.dataPoint.series.ChartTypeName, true, System.Globalization.CultureInfo.CurrentCulture) == 0)
{
// If series on top of current was found - check point transparency
if(seriesFound)
{
DataPointCustomProperties pointProperties = ser.Points[secondPoint.index - 1];
if(ser.Points[secondPoint.index - 1].IsEmpty)
{
pointProperties = ser.EmptyPointStyle;
}
if (pointProperties.Color.A == 255)
{
visibleSurfaces ^= SurfaceNames.Top;
}
break;
}
// Check series name
if(String.Compare(ser.Name, secondPoint.dataPoint.series.Name, StringComparison.Ordinal) == 0)
{
seriesFound = true;
}
}
}
}
// Check if the Bottom surface is on top of the transparent data point from other series
if( (visibleSurfaces & SurfaceNames.Bottom) != SurfaceNames.Bottom )
{
// Try to find data point with same index from the series above
DataPointCustomProperties pointProperties = null;
foreach(Series ser in area.Common.DataManager.Series)
{
if(String.Compare(ser.ChartTypeName, secondPoint.dataPoint.series.ChartTypeName, StringComparison.OrdinalIgnoreCase) == 0)
{
// Check series name
if (pointProperties != null && String.Compare(ser.Name, secondPoint.dataPoint.series.Name, StringComparison.Ordinal) == 0)
{
if (pointProperties.Color.A != 255)
{
visibleSurfaces |= SurfaceNames.Bottom;
}
break;
}
// Get properties
pointProperties = ser.Points[secondPoint.index - 1];
if(ser.Points[secondPoint.index - 1].IsEmpty)
{
pointProperties = ser.EmptyPointStyle;
}
}
}
}
}
/// <summary>
/// Gets position ob the bottom points in area chart.
/// </summary>
/// <param name="common">Chart common elements.</param>
/// <param name="area">Chart area the series belongs to.</param>
/// <param name="axisPosition">Axis position.</param>
/// <param name="firstPoint">First top point coordinates.</param>
/// <param name="secondPoint">Second top point coordinates.</param>
/// <param name="thirdPointPosition">Position where the third point is actually located or float.NaN if same as in "firstPoint".</param>
/// <param name="fourthPointPosition">Position where the fourth point is actually located or float.NaN if same as in "secondPoint".</param>
/// <param name="thirdPoint">Returns third bottom point coordinates.</param>
/// <param name="fourthPoint">Returns fourth bottom point coordinates.</param>
protected override void GetBottomPointsPosition(
CommonElements common,
ChartArea area,
float axisPosition,
ref DataPoint3D firstPoint,
ref DataPoint3D secondPoint,
PointF thirdPointPosition,
PointF fourthPointPosition,
out PointF thirdPoint,
out PointF fourthPoint)
{
// Set active vertical/horizontal axis
Axis vAxis = area.GetAxis(AxisName.Y, firstPoint.dataPoint.series.YAxisType, firstPoint.dataPoint.series.YSubAxisName);
Axis hAxis = area.GetAxis(AxisName.X, firstPoint.dataPoint.series.XAxisType, firstPoint.dataPoint.series.XSubAxisName);
// Find bottom points position
double yValue = GetYValue(area.Common, area, firstPoint.dataPoint.series, firstPoint.dataPoint, firstPoint.index - 1, 0);
double xValue = (float)firstPoint.xPosition;
if(yValue >= 0.0)
{
if(double.IsNaN(this.prevPosY))
{
yValue = axisPosition;
}
else
{
yValue = vAxis.GetPosition(this.prevPosY);
xValue = hAxis.GetPosition(this.prevPositionX);
}
}
else
{
if(double.IsNaN(this.prevNegY))
{
yValue = axisPosition;
}
else
{
yValue = vAxis.GetPosition(this.prevNegY);
xValue = hAxis.GetPosition(this.prevPositionX);
}
}
thirdPoint = new PointF((float)xValue, (float)yValue);
yValue = GetYValue(area.Common, area, secondPoint.dataPoint.series, secondPoint.dataPoint, secondPoint.index - 1, 0);
xValue = (float)secondPoint.xPosition;
if(yValue >= 0.0)
{
if(double.IsNaN(this.prevPosY))
{
yValue = axisPosition;
}
else
{
yValue = vAxis.GetPosition(this.prevPosY);
xValue = hAxis.GetPosition(this.prevPositionX);
}
}
else
{
if(double.IsNaN(this.prevNegY))
{
yValue = axisPosition;
}
else
{
yValue = vAxis.GetPosition(this.prevNegY);
xValue = hAxis.GetPosition(this.prevPositionX);
}
}
fourthPoint = new PointF((float)xValue, (float)yValue);
// Check if position of the third and/or fourth point(s) should be adjusted
if(!float.IsNaN(thirdPointPosition.X))
{
thirdPoint.X = (float)((firstPoint.xCenterVal == 0.0) ? firstPoint.xPosition : firstPoint.xCenterVal);
// Calculate new Y value as an intersection point of two lines:
// line between current 3d & 4th points and vertical line with X value = thirdPointPositionX.
thirdPoint.Y = (thirdPointPosition.X - fourthPoint.X) /
(thirdPoint.X - fourthPoint.X) *
(thirdPoint.Y - fourthPoint.Y) +
fourthPoint.Y;
// Set new X value
thirdPoint.X = thirdPointPosition.X;
}
if(!float.IsNaN(thirdPointPosition.Y))
{
thirdPoint.Y = thirdPointPosition.Y;
}
if(!float.IsNaN(fourthPointPosition.X))
{
fourthPoint.X = (float)((secondPoint.xCenterVal == 0.0) ? secondPoint.xPosition : secondPoint.xCenterVal);
// Calculate new Y value as an intersection point of two lines:
// line between current 3d & 4th points and vertical line with X value = thirdPointPositionX.
fourthPoint.Y = (fourthPointPosition.X - fourthPoint.X) /
(thirdPoint.X - fourthPoint.X) *
(thirdPoint.Y - fourthPoint.Y) +
fourthPoint.Y;
// Set new X value
fourthPoint.X = fourthPointPosition.X;
}
if(!float.IsNaN(fourthPointPosition.Y))
{
fourthPoint.Y = fourthPointPosition.Y;
}
}
/// <summary>
/// Returns how many loops through all data points is required (1 or 2)
/// </summary>
/// <param name="selection">Selection indicator.</param>
/// <param name="pointsArray">Points array list.</param>
/// <returns>Number of loops (1 or 2).</returns>
override protected int GetPointLoopNumber(bool selection, ArrayList pointsArray)
{
// Always one loop for selection
if(selection)
{
return 1;
}
// Second loop will be required for semi-transparent colors
int loopNumber = 1;
foreach(object obj in pointsArray)
{
// Get point & series
DataPoint3D pointEx = (DataPoint3D) obj;
// Check properties
if(pointEx.dataPoint.Color.A != 255)
{
loopNumber = 2;
}
// Check title
// VSTS fix #529011: 3-d stacked area and 100% stacked area charts do not show data labels.
if( pointEx.dataPoint.Label.Length > 0 ||
pointEx.dataPoint.IsValueShownAsLabel ||
pointEx.dataPoint.series.IsValueShownAsLabel)
{
// S loops through all data points required
loopNumber = 3;
break;
}
}
return loopNumber;
}
/// <summary>
/// This method draws labels in point 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="pointEx">Data point 3D.</param>
/// <param name="positionZ">Z position of the back side of the 3D surface.</param>
/// <param name="depth">Depth of the 3D surface.</param>
private void DrawLabels3D(
ChartArea area,
ChartGraphics graph,
CommonElements common,
DataPoint3D pointEx,
float positionZ,
float depth)
{
// Get some properties for performance
string pointLabel = pointEx.dataPoint.Label;
bool pointShowLabelAsValue = pointEx.dataPoint.IsValueShownAsLabel;
// ****************************
// Draw data point value label
// ****************************
if((!pointEx.dataPoint.IsEmpty && (pointEx.dataPoint.series.IsValueShownAsLabel || pointShowLabelAsValue || pointLabel.Length > 0)) ||
(pointShowLabelAsValue || pointLabel.Length > 0))
{
// Label text format
using (StringFormat format = new StringFormat())
{
format.Alignment = StringAlignment.Center;
format.LineAlignment = StringAlignment.Center;
// Get label text
string text;
if (pointLabel.Length == 0)
{
// Round Y values for 100% stacked area
double pointLabelValue = pointEx.dataPoint.YValues[(labelYValueIndex == -1) ? YValueIndex : labelYValueIndex];
if (this.hundredPercentStacked && pointEx.dataPoint.LabelFormat.Length == 0)
{
pointLabelValue = Math.Round(pointLabelValue, 2);
}
text = ValueConverter.FormatValue(
pointEx.dataPoint.series.Chart,
pointEx.dataPoint,
pointEx.dataPoint.Tag,
pointLabelValue,
pointEx.dataPoint.LabelFormat,
pointEx.dataPoint.series.YValueType,
ChartElementType.DataPoint);
}
else
{
text = pointEx.dataPoint.ReplaceKeywords(pointLabel);
}
// Get label position
Point3D[] points = new Point3D[1];
points[0] = new Point3D((float)pointEx.xPosition, (float)(pointEx.yPosition + pointEx.height) / 2f, positionZ + depth);
area.matrix3D.TransformPoints(points);
// Measure string
SizeF sizeFont = graph.GetRelativeSize(
graph.MeasureString(
text,
pointEx.dataPoint.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 += sizeFont.Height / 8;
sizeLabel.Width += sizeLabel.Width / text.Length;
labelBackPosition = new RectangleF(
points[0].PointF.X - sizeLabel.Width / 2,
points[0].PointF.Y - sizeLabel.Height / 2 - sizeFont.Height / 10,
sizeLabel.Width,
sizeLabel.Height);
// Draw label text
using (Brush brush = new SolidBrush(pointEx.dataPoint.LabelForeColor))
{
graph.DrawPointLabelStringRel(
common,
text,
pointEx.dataPoint.Font,
brush,
points[0].PointF,
format,
pointEx.dataPoint.LabelAngle,
labelBackPosition,
pointEx.dataPoint.LabelBackColor,
pointEx.dataPoint.LabelBorderColor,
pointEx.dataPoint.LabelBorderWidth,
pointEx.dataPoint.LabelBorderDashStyle,
pointEx.dataPoint.series,
pointEx.dataPoint,
pointEx.index - 1);
}
}
}
}
#endregion
#region Y values 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. Set to -1 to get the height.</param>
/// <returns>Y value of the point.</returns>
override public double GetYValue(
CommonElements common,
ChartArea area,
Series series,
DataPoint point,
int pointIndex,
int yValueIndex)
{
double yValue = double.NaN;
// Calculate stacked column Y value for 2D chart
if(area.Area3DStyle.Enable3D == false)
{
return point.YValues[0];
}
// Get point Height if pointIndex == -1
if(yValueIndex == -1)
{
Axis vAxis = area.GetAxis(AxisName.Y, series.YAxisType, series.YSubAxisName);
double areaZeroValue = vAxis.Crossing;
yValue = GetYValue(common, area, series, point, pointIndex, 0);
if(area.Area3DStyle.Enable3D && yValue < 0.0)
{
// No negative values support in 3D stacked area chart
yValue = -yValue;
}
if( yValue >= 0 )
{
if(!double.IsNaN(prevPosY))
{
areaZeroValue = prevPosY;
}
}
else
{
if(!double.IsNaN(prevNegY))
{
areaZeroValue = prevNegY;
}
}
return yValue - areaZeroValue;
}
// Loop through all series
prevPosY = double.NaN;
prevNegY = double.NaN;
prevPositionX = double.NaN;
foreach(Series ser in common.DataManager.Series)
{
// Check series of the current chart type & area
if(String.Compare(series.ChartArea, ser.ChartArea, StringComparison.Ordinal) == 0 &&
String.Compare(series.ChartTypeName, ser.ChartTypeName, StringComparison.OrdinalIgnoreCase) == 0 &&
ser.IsVisible())
{
yValue = ser.Points[pointIndex].YValues[0];
if(area.Area3DStyle.Enable3D && yValue < 0.0)
{
// No negative values support in 3D stacked area chart
yValue = -yValue;
}
if(!double.IsNaN(yValue))
{
if(yValue >= 0.0 && !double.IsNaN(prevPosY))
{
yValue += prevPosY;
}
if(yValue < 0.0 && !double.IsNaN(prevNegY))
{
yValue += prevNegY;
}
}
// Exit loop when current series was found
if (String.Compare(series.Name, ser.Name, StringComparison.Ordinal) == 0)
{
break;
}
// Remember privious position
if(yValue >= 0.0)
{
prevPosY = yValue;
}
if(yValue < 0.0)
{
prevNegY = yValue;
}
prevPositionX = ser.Points[pointIndex].XValue;
if(prevPositionX == 0.0 && ChartHelper.IndexedSeries(series))
{
prevPositionX = pointIndex + 1;
}
}
}
return yValue;
}
#endregion
#region IDisposable overrides
/// <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 override void Dispose(bool disposing)
{
if (disposing)
{
// Dispose managed resources
if (this.areaBottomPath != null)
{
this.areaBottomPath.Dispose();
this.areaBottomPath = null;
}
}
base.Dispose(disposing);
}
#endregion
}
}
|