|
//-------------------------------------------------------------
// <copyright company=’Microsoft Corporation’>
// Copyright © Microsoft Corporation. All Rights Reserved.
// </copyright>
//-------------------------------------------------------------
// @owner=alexgor, deliant
//=================================================================
// File: FastLineChart.cs
//
// Namespace: DataVisualization.Charting.ChartTypes
//
// Classes: FastLineChart
//
// Purpose: When performance is critical, the FastLine chart
// type is a good alternative to the Line chart. FastLine
// charts significantly reduce the drawing time of a
// series that contains a very large number of data points.
//
// To make the FastLine chart a high performance chart,
// some charting features have been omitted. The features
// omitted include the ability to control Point level
// visual properties, the ability to draw markers, the
// use of data point labels, shadows, and the use of
// chart animation.
//
// FastLine chart performance was improved by limiting
// visual appearance features and by introducing data
// point compacting algorithm. When chart contains
// thousands of data points, it is common to have tens
// or hundreds points displayed in the area comparable
// to a single pixel. FastLine algorithm accumulates
// point information and only draw points if they extend
// outside currently filled pixels.
//
// Reviewed: AG - Microsoft 6, 2007
//
//===================================================================
#region Used namespaces
using System;
using System.Collections;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Globalization;
#if Microsoft_CONTROL
using System.Windows.Forms.DataVisualization.Charting.Utilities;
#else
using System.Web.UI.DataVisualization.Charting;
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>
/// FastLineChart class implements a simplified line chart drawing
/// algorithm which is optimized for the performance.
/// </summary>
internal class FastLineChart : IChartType
{
#region Fields and Constructor
/// <summary>
/// Indicates that chart is drawn in 3D area
/// </summary>
internal bool chartArea3DEnabled = false;
/// <summary>
/// Current chart graphics
/// </summary>
internal ChartGraphics Graph { get; set; }
/// <summary>
/// Z coordinate of the 3D series
/// </summary>
internal float seriesZCoordinate = 0f;
/// <summary>
/// 3D transformation matrix
/// </summary>
internal Matrix3D matrix3D = null;
/// <summary>
/// Reference to common chart elements
/// </summary>
internal CommonElements Common { get; set; }
/// <summary>
/// Default constructor
/// </summary>
public FastLineChart()
{
}
#endregion
#region IChartType interface implementation
/// <summary>
/// Chart type name
/// </summary>
virtual public string Name { get{ return ChartTypeNames.FastLine;}}
/// <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 true;} }
/// <summary>
/// Chart type with two y values used for scale ( bubble chart type )
/// </summary>
virtual 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 true;} }
/// <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>
/// True if each data point of a chart must be represented in the legend
/// </summary>
virtual public bool DataPointsInLegend { 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 palette colors should be applied for each data paoint.
/// Otherwise the color is applied to the series.
/// </summary>
virtual public bool ApplyPaletteColorsToPoints { get { return false; } }
/// <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>
/// 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.Line;
}
/// <summary>
/// Number of supported Y value(s) per point
/// </summary>
virtual public int YValuesPerPoint { get { return 1; } }
/// <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");
}
#endregion
#region Painting
/// <summary>
/// Paint FastLine 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>
virtual public void Paint(
ChartGraphics graph,
CommonElements common,
ChartArea area,
Series seriesToDraw )
{
this.Common = common;
this.Graph = graph;
bool clipRegionSet = false;
if(area.Area3DStyle.Enable3D)
{
// Initialize variables
this.chartArea3DEnabled = true;
matrix3D = area.matrix3D;
}
else
{
this.chartArea3DEnabled = false;
}
//************************************************************
//** Loop through all series
//************************************************************
foreach( Series series in common.DataManager.Series )
{
// Process non empty series of the area with FastLine chart type
if( String.Compare( series.ChartTypeName, this.Name, true, System.Globalization.CultureInfo.CurrentCulture ) != 0
|| series.ChartArea != area.Name ||
!series.IsVisible())
{
continue;
}
// Get 3D series depth and Z position
if(this.chartArea3DEnabled)
{
float seriesDepth;
area.GetSeriesZPositionAndDepth(series, out seriesDepth, out seriesZCoordinate);
this.seriesZCoordinate += seriesDepth/2.0f;
}
// Set active horizontal/vertical axis
Axis hAxis = area.GetAxis(AxisName.X, series.XAxisType, (area.Area3DStyle.Enable3D) ? string.Empty : series.XSubAxisName);
Axis vAxis = area.GetAxis(AxisName.Y, series.YAxisType, (area.Area3DStyle.Enable3D) ? string.Empty : series.YSubAxisName);
double hAxisMin = hAxis.ViewMinimum;
double hAxisMax = hAxis.ViewMaximum;
double vAxisMin = vAxis.ViewMinimum;
double vAxisMax = vAxis.ViewMaximum;
// Get "PermittedPixelError" attribute
float permittedPixelError = 1.0f;
if (series.IsCustomPropertySet(CustomPropertyName.PermittedPixelError))
{
string attrValue = series[CustomPropertyName.PermittedPixelError];
float pixelError;
bool parseSucceed = float.TryParse(attrValue, NumberStyles.Any, CultureInfo.CurrentCulture, out pixelError);
if (parseSucceed)
{
permittedPixelError = pixelError;
}
else
{
throw (new InvalidOperationException(SR.ExceptionCustomAttributeValueInvalid2("PermittedPixelError")));
}
// "PermittedPixelError" attribute value should be in range from zero to 1
if (permittedPixelError < 0f || permittedPixelError > 1f)
{
throw (new InvalidOperationException(SR.ExceptionCustomAttributeIsNotInRange0to1("PermittedPixelError")));
}
}
// Get pixel size in axes coordinates
SizeF pixelSize = graph.GetRelativeSize(new SizeF(permittedPixelError, permittedPixelError));
SizeF axesMin = graph.GetRelativeSize(new SizeF((float)hAxisMin, (float)vAxisMin));
double axesValuesPixelSizeX = Math.Abs(hAxis.PositionToValue(axesMin.Width + pixelSize.Width, false) - hAxis.PositionToValue(axesMin.Width, false));
// Create line pen
Pen linePen = new Pen(series.Color, series.BorderWidth);
linePen.DashStyle = graph.GetPenStyle( series.BorderDashStyle );
linePen.StartCap = LineCap.Round;
linePen.EndCap = LineCap.Round;
// Create empty line pen
Pen emptyLinePen = new Pen(series.EmptyPointStyle.Color, series.EmptyPointStyle.BorderWidth);
emptyLinePen.DashStyle = graph.GetPenStyle( series.EmptyPointStyle.BorderDashStyle );
emptyLinePen.StartCap = LineCap.Round;
emptyLinePen.EndCap = LineCap.Round;
// Check if series is indexed
bool indexedSeries = ChartHelper.IndexedSeries(this.Common, series.Name );
// Loop through all ponts in the series
int index = 0;
double yValueRangeMin = double.NaN;
double yValueRangeMax = double.NaN;
DataPoint pointRangeMin = null;
DataPoint pointRangeMax = null;
double xValue = 0;
double yValue = 0;
double xValuePrev = 0;
double yValuePrev = 0;
DataPoint prevDataPoint = null;
PointF lastVerticalSegmentPoint = PointF.Empty;
PointF prevPoint = PointF.Empty;
PointF currentPoint = PointF.Empty;
bool prevPointInAxesCoordinates = false;
bool verticalLineDetected = false;
bool prevPointIsEmpty = false;
bool currentPointIsEmpty = false;
bool firstNonEmptyPoint = false;
double xPixelConverter = (graph.Common.ChartPicture.Width - 1.0) / 100.0;
double yPixelConverter = (graph.Common.ChartPicture.Height - 1.0) / 100.0;
foreach( DataPoint point in series.Points )
{
// Get point X and Y values
xValue = (indexedSeries) ? index + 1 : point.XValue;
xValue = hAxis.GetLogValue(xValue);
yValue = vAxis.GetLogValue(point.YValues[0]);
currentPointIsEmpty = point.IsEmpty;
// NOTE: Fixes issue #7094
// If current point is non-empty but the previous one was,
// use empty point style properties to draw it.
if (prevPointIsEmpty && !currentPointIsEmpty && !firstNonEmptyPoint)
{
firstNonEmptyPoint = true;
currentPointIsEmpty = true;
}
else
{
firstNonEmptyPoint = false;
}
// Check if line is completly out of the data scaleView
if( !verticalLineDetected &&
((xValue < hAxisMin && xValuePrev < hAxisMin) ||
(xValue > hAxisMax && xValuePrev > hAxisMax) ||
(yValue < vAxisMin && yValuePrev < vAxisMin) ||
(yValue > vAxisMax && yValuePrev > vAxisMax) ))
{
xValuePrev = xValue;
yValuePrev = yValue;
prevPointInAxesCoordinates = true;
++index;
continue;
}
else if(!clipRegionSet)
{
// Check if line is partialy in the data scaleView
if(xValuePrev < hAxisMin || xValuePrev > hAxisMax ||
xValue > hAxisMax || xValue < hAxisMin ||
yValuePrev < vAxisMin || yValuePrev > vAxisMax ||
yValue < vAxisMin || yValue > vAxisMax )
{
// Set clipping region for line drawing
graph.SetClip( area.PlotAreaPosition.ToRectangleF() );
clipRegionSet = true;
}
}
// Check if point may be skipped
if(index > 0 &&
currentPointIsEmpty == prevPointIsEmpty)
{
// Check if points X value in acceptable error boundary
if( Math.Abs(xValue - xValuePrev) < axesValuesPixelSizeX)
{
if(!verticalLineDetected)
{
verticalLineDetected = true;
if(yValue > yValuePrev)
{
yValueRangeMax = yValue;
yValueRangeMin = yValuePrev;
pointRangeMax = point;
pointRangeMin = prevDataPoint;
}
else
{
yValueRangeMax = yValuePrev;
yValueRangeMin = yValue;
pointRangeMax = prevDataPoint;
pointRangeMin = point;
}
// NOTE: Prev. version code - A.G.
// yValueRangeMin = Math.Min(yValue, yValuePrev);
// yValueRangeMax = Math.Max(yValue, yValuePrev);
}
else
{
if(yValue > yValueRangeMax)
{
yValueRangeMax = yValue;
pointRangeMax = point;
}
else if(yValue < yValueRangeMin)
{
yValueRangeMin = yValue;
pointRangeMin = point;
}
// NOTE: Prev. version code - A.G.
// yValueRangeMin = Math.Min(yValue, yValueRangeMin);
// yValueRangeMax = Math.Max(yValue, yValueRangeMax);
}
// Remember last point
prevDataPoint = point;
// Remember last vertical range point
// Note! Point is in axes coordinate.
lastVerticalSegmentPoint.Y = (float)yValue;
// Increase counter and proceed to next data point
++index;
continue;
}
}
// Get point pixel position
currentPoint.X = (float)
(hAxis.GetLinearPosition( xValue ) * xPixelConverter);
currentPoint.Y = (float)
(vAxis.GetLinearPosition( yValue ) * yPixelConverter);
// Check if previous point must be converted from axes values to pixels
if(prevPointInAxesCoordinates)
{
prevPoint.X = (float)
(hAxis.GetLinearPosition( xValuePrev ) * xPixelConverter);
prevPoint.Y = (float)
(vAxis.GetLinearPosition( yValuePrev ) * yPixelConverter);
}
// Draw accumulated vertical line (with minimal X values differences)
if(verticalLineDetected)
{
// Convert Y coordinates to pixels
yValueRangeMin = (vAxis.GetLinearPosition( yValueRangeMin ) * yPixelConverter);
yValueRangeMax = (vAxis.GetLinearPosition( yValueRangeMax ) * yPixelConverter);
// Draw accumulated vertical line
DrawLine(
series,
prevDataPoint,
pointRangeMin,
pointRangeMax,
index,
(prevPointIsEmpty) ? emptyLinePen : linePen,
prevPoint.X,
(float)yValueRangeMin,
prevPoint.X,
(float)yValueRangeMax);
// Reset vertical line detected flag
verticalLineDetected = false;
// Convert last point of the vertical line segment to pixel coordinates
prevPoint.Y = (float)
(vAxis.GetLinearPosition( lastVerticalSegmentPoint.Y ) * yPixelConverter);
}
// Draw line from previous to current point
if(index > 0)
{
DrawLine(
series,
point,
pointRangeMin,
pointRangeMax,
index,
(currentPointIsEmpty) ? emptyLinePen : linePen,
prevPoint.X,
prevPoint.Y,
currentPoint.X,
currentPoint.Y);
}
// Remember last point coordinates
xValuePrev = xValue;
yValuePrev = yValue;
prevDataPoint = point;
prevPoint = currentPoint;
prevPointInAxesCoordinates = false;
prevPointIsEmpty = currentPointIsEmpty;
++index;
}
// Draw last accumulated line segment
if(verticalLineDetected)
{
// Check if previous point must be converted from axes values to pixels
if(prevPointInAxesCoordinates)
{
prevPoint.X = (float)
(hAxis.GetLinearPosition( xValuePrev ) * xPixelConverter);
prevPoint.Y = (float)
(vAxis.GetLinearPosition( yValuePrev ) * yPixelConverter);
}
// Convert Y coordinates to pixels
yValueRangeMin = (vAxis.GetLinearPosition( yValueRangeMin ) * yPixelConverter);
yValueRangeMax = (vAxis.GetLinearPosition( yValueRangeMax ) * yPixelConverter);
// Draw accumulated vertical line
DrawLine(
series,
prevDataPoint,
pointRangeMin,
pointRangeMax,
index - 1,
(prevPointIsEmpty) ? emptyLinePen : linePen,
prevPoint.X,
(float)yValueRangeMin,
prevPoint.X,
(float)yValueRangeMax);
verticalLineDetected = false;
yValueRangeMin = double.NaN;
yValueRangeMax = double.NaN;
pointRangeMin = null;
pointRangeMax = null;
}
}
// Reset Clip Region
if(clipRegionSet)
{
graph.ResetClip();
}
}
/// <summary>
/// Draws a line connecting two PointF structures.
/// </summary>
/// <param name="series">Chart series.</param>
/// <param name="point">Series last data point in the group.</param>
/// <param name="pointMin">Series minimum Y value data point in the group.</param>
/// <param name="pointMax">Series maximum Y value data point in the group.</param>
/// <param name="pointIndex">Point index.</param>
/// <param name="pen">Pen object that determines the color, width, and style of the line.</param>
/// <param name="firstPointX">First point X coordinate.</param>
/// <param name="firstPointY">First point Y coordinate</param>
/// <param name="secondPointX">Second point X coordinate.</param>
/// <param name="secondPointY">Second point Y coordinate</param>
public virtual void DrawLine(
Series series,
DataPoint point,
DataPoint pointMin,
DataPoint pointMax,
int pointIndex,
Pen pen,
float firstPointX,
float firstPointY,
float secondPointX,
float secondPointY
)
{
// Transform 3D coordinates
if(chartArea3DEnabled)
{
Point3D [] points = new Point3D[2];
// All coordinates has to be transformed in relative coordinate system
// NOTE: Fixes issue #5496
PointF firstPoint = Graph.GetRelativePoint(new PointF(firstPointX, firstPointY));
PointF secondPoint = Graph.GetRelativePoint(new PointF(secondPointX, secondPointY));
points[0] = new Point3D(firstPoint.X, firstPoint.Y, seriesZCoordinate);
points[1] = new Point3D(secondPoint.X, secondPoint.Y, seriesZCoordinate);
matrix3D.TransformPoints( points );
// All coordinates has to be transformed back to pixels
// NOTE: Fixes issue #5496
points[0].PointF = Graph.GetAbsolutePoint(points[0].PointF);
points[1].PointF = Graph.GetAbsolutePoint(points[1].PointF);
firstPointX = points[0].X;
firstPointY = points[0].Y;
secondPointX = points[1].X;
secondPointY = points[1].Y;
}
// Draw line
Graph.DrawLine(pen, firstPointX, firstPointY, secondPointX,secondPointY);
// Process selection regions
if( this.Common.ProcessModeRegions )
{
// Create grapics path object for the line
using (GraphicsPath path = new GraphicsPath())
{
float width = pen.Width + 2;
if (Math.Abs(firstPointX - secondPointX) > Math.Abs(firstPointY - secondPointY))
{
path.AddLine(firstPointX, firstPointY - width, secondPointX, secondPointY - width);
path.AddLine(secondPointX, secondPointY + width, firstPointX, firstPointY + width);
path.CloseAllFigures();
}
else
{
path.AddLine(firstPointX - width, firstPointY, secondPointX - width, secondPointY);
path.AddLine(secondPointX + width, secondPointY, firstPointX + width, firstPointY);
path.CloseAllFigures();
}
// Calculate bounding rectangle
RectangleF pathBounds = path.GetBounds();
// If one side of the bounding rectangle is less than 2 pixels
// use rectangle region shape to optimize used coordinates space
if (pathBounds.Width <= 2.0 || pathBounds.Height <= 2.0)
{
// Add hot region path as rectangle
pathBounds.Inflate(pen.Width, pen.Width);
this.Common.HotRegionsList.AddHotRegion(
Graph.GetRelativeRectangle(pathBounds),
point,
point.series.Name,
pointIndex);
}
else
{
// Add hot region path as polygon
this.Common.HotRegionsList.AddHotRegion(
path,
false,
Graph,
point,
point.series.Name,
pointIndex);
}
}
}
}
#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 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)
{
// Fast Line chart type do not support labels
}
#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
}
}
|