1 //-------------------------------------------------------------
2 // <copyright company=
\92Microsoft Corporation
\92>
3 // Copyright © Microsoft Corporation. All Rights Reserved.
5 //-------------------------------------------------------------
6 // @owner=alexgor, deliant
7 //=================================================================
8 // File: FastLineChart.cs
10 // Namespace: DataVisualization.Charting.ChartTypes
12 // Classes: FastLineChart
14 // Purpose: When performance is critical, the FastLine chart
15 // type is a good alternative to the Line chart. FastLine
16 // charts significantly reduce the drawing time of a
17 // series that contains a very large number of data points.
19 // To make the FastLine chart a high performance chart,
20 // some charting features have been omitted. The features
21 // omitted include the ability to control Point level
22 // visual properties, the ability to draw markers, the
23 // use of data point labels, shadows, and the use of
26 // FastLine chart performance was improved by limiting
27 // visual appearance features and by introducing data
28 // point compacting algorithm. When chart contains
29 // thousands of data points, it is common to have tens
30 // or hundreds points displayed in the area comparable
31 // to a single pixel. FastLine algorithm accumulates
32 // point information and only draw points if they extend
33 // outside currently filled pixels.
35 // Reviewed: AG - Microsoft 6, 2007
37 //===================================================================
39 #region Used namespaces
42 using System.Collections;
44 using System.Drawing.Drawing2D;
45 using System.Globalization;
48 using System.Windows.Forms.DataVisualization.Charting.Utilities;
50 using System.Web.UI.DataVisualization.Charting;
52 using System.Web.UI.DataVisualization.Charting.Utilities;
57 namespace System.Windows.Forms.DataVisualization.Charting.ChartTypes
59 namespace System.Web.UI.DataVisualization.Charting.ChartTypes
63 /// FastLineChart class implements a simplified line chart drawing
64 /// algorithm which is optimized for the performance.
66 internal class FastLineChart : IChartType
68 #region Fields and Constructor
71 /// Indicates that chart is drawn in 3D area
73 internal bool chartArea3DEnabled = false;
76 /// Current chart graphics
78 internal ChartGraphics Graph { get; set; }
81 /// Z coordinate of the 3D series
83 internal float seriesZCoordinate = 0f;
86 /// 3D transformation matrix
88 internal Matrix3D matrix3D = null;
91 /// Reference to common chart elements
93 internal CommonElements Common { get; set; }
96 /// Default constructor
98 public FastLineChart()
104 #region IChartType interface implementation
109 virtual public string Name { get{ return ChartTypeNames.FastLine;}}
112 /// True if chart type is stacked
114 virtual public bool Stacked { get{ return false;}}
118 /// True if stacked chart type supports groups
120 virtual public bool SupportStackedGroups { get { return false; } }
124 /// True if stacked chart type should draw separately positive and
125 /// negative data points ( Bar and column Stacked types ).
127 public bool StackSign { get{ return false;}}
130 /// True if chart type supports axeses
132 virtual public bool RequireAxes { get{ return true;} }
135 /// Chart type with two y values used for scale ( bubble chart type )
137 virtual public bool SecondYScale{ get{ return false;} }
140 /// True if chart type requires circular chart area.
142 public bool CircularChartArea { get{ return false;} }
145 /// True if chart type supports logarithmic axes
147 virtual public bool SupportLogarithmicAxes { get{ return true;} }
150 /// True if chart type requires to switch the value (Y) axes position
152 virtual public bool SwitchValueAxes { get{ return false;} }
155 /// True if chart series can be placed side-by-side.
157 virtual public bool SideBySideSeries { get{ return false;} }
160 /// True if each data point of a chart must be represented in the legend
162 virtual public bool DataPointsInLegend { get{ return false;} }
165 /// If the crossing value is auto Crossing value should be
166 /// automatically set to zero for some chart
167 /// types (Bar, column, area etc.)
169 virtual public bool ZeroCrossing { get{ return false;} }
172 /// True if palette colors should be applied for each data paoint.
173 /// Otherwise the color is applied to the series.
175 virtual public bool ApplyPaletteColorsToPoints { get { return false; } }
178 /// Indicates that extra Y values are connected to the scale of the Y axis
180 virtual public bool ExtraYValuesConnectedToYAxis{ get { return false; } }
183 /// Indicates that it's a hundredred percent chart.
184 /// Axis scale from 0 to 100 percent should be used.
186 virtual public bool HundredPercent{ get{return false;} }
189 /// Indicates that it's a hundredred percent chart.
190 /// Axis scale from 0 to 100 percent should be used.
192 virtual public bool HundredPercentSupportNegative{ get{return false;} }
195 /// How to draw series/points in legend:
196 /// Filled rectangle, Line or Marker
198 /// <param name="series">Legend item series.</param>
199 /// <returns>Legend item style.</returns>
200 virtual public LegendImageStyle GetLegendImageStyle(Series series)
202 return LegendImageStyle.Line;
206 /// Number of supported Y value(s) per point
208 virtual public int YValuesPerPoint { get { return 1; } }
211 /// Gets chart type image.
213 /// <param name="registry">Chart types registry object.</param>
214 /// <returns>Chart type image.</returns>
215 virtual public System.Drawing.Image GetImage(ChartTypeRegistry registry)
217 return (System.Drawing.Image)registry.ResourceManager.GetObject(this.Name + "ChartType");
225 /// Paint FastLine Chart.
227 /// <param name="graph">The Chart Graphics object.</param>
228 /// <param name="common">The Common elements object.</param>
229 /// <param name="area">Chart area for this chart.</param>
230 /// <param name="seriesToDraw">Chart series to draw.</param>
231 virtual public void Paint(
233 CommonElements common,
235 Series seriesToDraw )
237 this.Common = common;
239 bool clipRegionSet = false;
240 if(area.Area3DStyle.Enable3D)
242 // Initialize variables
243 this.chartArea3DEnabled = true;
244 matrix3D = area.matrix3D;
248 this.chartArea3DEnabled = false;
251 //************************************************************
252 //** Loop through all series
253 //************************************************************
254 foreach( Series series in common.DataManager.Series )
256 // Process non empty series of the area with FastLine chart type
257 if( String.Compare( series.ChartTypeName, this.Name, true, System.Globalization.CultureInfo.CurrentCulture ) != 0
258 || series.ChartArea != area.Name ||
264 // Get 3D series depth and Z position
265 if(this.chartArea3DEnabled)
268 area.GetSeriesZPositionAndDepth(series, out seriesDepth, out seriesZCoordinate);
269 this.seriesZCoordinate += seriesDepth/2.0f;
272 // Set active horizontal/vertical axis
273 Axis hAxis = area.GetAxis(AxisName.X, series.XAxisType, (area.Area3DStyle.Enable3D) ? string.Empty : series.XSubAxisName);
274 Axis vAxis = area.GetAxis(AxisName.Y, series.YAxisType, (area.Area3DStyle.Enable3D) ? string.Empty : series.YSubAxisName);
275 double hAxisMin = hAxis.ViewMinimum;
276 double hAxisMax = hAxis.ViewMaximum;
277 double vAxisMin = vAxis.ViewMinimum;
278 double vAxisMax = vAxis.ViewMaximum;
280 // Get "PermittedPixelError" attribute
281 float permittedPixelError = 1.0f;
282 if (series.IsCustomPropertySet(CustomPropertyName.PermittedPixelError))
284 string attrValue = series[CustomPropertyName.PermittedPixelError];
287 bool parseSucceed = float.TryParse(attrValue, NumberStyles.Any, CultureInfo.CurrentCulture, out pixelError);
291 permittedPixelError = pixelError;
295 throw (new InvalidOperationException(SR.ExceptionCustomAttributeValueInvalid2("PermittedPixelError")));
298 // "PermittedPixelError" attribute value should be in range from zero to 1
299 if (permittedPixelError < 0f || permittedPixelError > 1f)
301 throw (new InvalidOperationException(SR.ExceptionCustomAttributeIsNotInRange0to1("PermittedPixelError")));
305 // Get pixel size in axes coordinates
306 SizeF pixelSize = graph.GetRelativeSize(new SizeF(permittedPixelError, permittedPixelError));
307 SizeF axesMin = graph.GetRelativeSize(new SizeF((float)hAxisMin, (float)vAxisMin));
308 double axesValuesPixelSizeX = Math.Abs(hAxis.PositionToValue(axesMin.Width + pixelSize.Width, false) - hAxis.PositionToValue(axesMin.Width, false));
311 Pen linePen = new Pen(series.Color, series.BorderWidth);
312 linePen.DashStyle = graph.GetPenStyle( series.BorderDashStyle );
313 linePen.StartCap = LineCap.Round;
314 linePen.EndCap = LineCap.Round;
316 // Create empty line pen
317 Pen emptyLinePen = new Pen(series.EmptyPointStyle.Color, series.EmptyPointStyle.BorderWidth);
318 emptyLinePen.DashStyle = graph.GetPenStyle( series.EmptyPointStyle.BorderDashStyle );
319 emptyLinePen.StartCap = LineCap.Round;
320 emptyLinePen.EndCap = LineCap.Round;
322 // Check if series is indexed
323 bool indexedSeries = ChartHelper.IndexedSeries(this.Common, series.Name );
325 // Loop through all ponts in the series
327 double yValueRangeMin = double.NaN;
328 double yValueRangeMax = double.NaN;
329 DataPoint pointRangeMin = null;
330 DataPoint pointRangeMax = null;
333 double xValuePrev = 0;
334 double yValuePrev = 0;
335 DataPoint prevDataPoint = null;
336 PointF lastVerticalSegmentPoint = PointF.Empty;
337 PointF prevPoint = PointF.Empty;
338 PointF currentPoint = PointF.Empty;
339 bool prevPointInAxesCoordinates = false;
340 bool verticalLineDetected = false;
341 bool prevPointIsEmpty = false;
342 bool currentPointIsEmpty = false;
343 bool firstNonEmptyPoint = false;
344 double xPixelConverter = (graph.Common.ChartPicture.Width - 1.0) / 100.0;
345 double yPixelConverter = (graph.Common.ChartPicture.Height - 1.0) / 100.0;
346 foreach( DataPoint point in series.Points )
348 // Get point X and Y values
349 xValue = (indexedSeries) ? index + 1 : point.XValue;
350 xValue = hAxis.GetLogValue(xValue);
351 yValue = vAxis.GetLogValue(point.YValues[0]);
352 currentPointIsEmpty = point.IsEmpty;
354 // NOTE: Fixes issue #7094
355 // If current point is non-empty but the previous one was,
356 // use empty point style properties to draw it.
357 if (prevPointIsEmpty && !currentPointIsEmpty && !firstNonEmptyPoint)
359 firstNonEmptyPoint = true;
360 currentPointIsEmpty = true;
364 firstNonEmptyPoint = false;
367 // Check if line is completly out of the data scaleView
368 if( !verticalLineDetected &&
369 ((xValue < hAxisMin && xValuePrev < hAxisMin) ||
370 (xValue > hAxisMax && xValuePrev > hAxisMax) ||
371 (yValue < vAxisMin && yValuePrev < vAxisMin) ||
372 (yValue > vAxisMax && yValuePrev > vAxisMax) ))
376 prevPointInAxesCoordinates = true;
380 else if(!clipRegionSet)
382 // Check if line is partialy in the data scaleView
383 if(xValuePrev < hAxisMin || xValuePrev > hAxisMax ||
384 xValue > hAxisMax || xValue < hAxisMin ||
385 yValuePrev < vAxisMin || yValuePrev > vAxisMax ||
386 yValue < vAxisMin || yValue > vAxisMax )
388 // Set clipping region for line drawing
389 graph.SetClip( area.PlotAreaPosition.ToRectangleF() );
390 clipRegionSet = true;
394 // Check if point may be skipped
396 currentPointIsEmpty == prevPointIsEmpty)
398 // Check if points X value in acceptable error boundary
399 if( Math.Abs(xValue - xValuePrev) < axesValuesPixelSizeX)
401 if(!verticalLineDetected)
403 verticalLineDetected = true;
404 if(yValue > yValuePrev)
406 yValueRangeMax = yValue;
407 yValueRangeMin = yValuePrev;
408 pointRangeMax = point;
409 pointRangeMin = prevDataPoint;
413 yValueRangeMax = yValuePrev;
414 yValueRangeMin = yValue;
415 pointRangeMax = prevDataPoint;
416 pointRangeMin = point;
419 // NOTE: Prev. version code - A.G.
420 // yValueRangeMin = Math.Min(yValue, yValuePrev);
421 // yValueRangeMax = Math.Max(yValue, yValuePrev);
425 if(yValue > yValueRangeMax)
427 yValueRangeMax = yValue;
428 pointRangeMax = point;
431 else if(yValue < yValueRangeMin)
433 yValueRangeMin = yValue;
434 pointRangeMin = point;
437 // NOTE: Prev. version code - A.G.
438 // yValueRangeMin = Math.Min(yValue, yValueRangeMin);
439 // yValueRangeMax = Math.Max(yValue, yValueRangeMax);
442 // Remember last point
443 prevDataPoint = point;
445 // Remember last vertical range point
446 // Note! Point is in axes coordinate.
447 lastVerticalSegmentPoint.Y = (float)yValue;
449 // Increase counter and proceed to next data point
455 // Get point pixel position
456 currentPoint.X = (float)
457 (hAxis.GetLinearPosition( xValue ) * xPixelConverter);
458 currentPoint.Y = (float)
459 (vAxis.GetLinearPosition( yValue ) * yPixelConverter);
461 // Check if previous point must be converted from axes values to pixels
462 if(prevPointInAxesCoordinates)
464 prevPoint.X = (float)
465 (hAxis.GetLinearPosition( xValuePrev ) * xPixelConverter);
466 prevPoint.Y = (float)
467 (vAxis.GetLinearPosition( yValuePrev ) * yPixelConverter);
470 // Draw accumulated vertical line (with minimal X values differences)
471 if(verticalLineDetected)
473 // Convert Y coordinates to pixels
474 yValueRangeMin = (vAxis.GetLinearPosition( yValueRangeMin ) * yPixelConverter);
475 yValueRangeMax = (vAxis.GetLinearPosition( yValueRangeMax ) * yPixelConverter);
477 // Draw accumulated vertical line
484 (prevPointIsEmpty) ? emptyLinePen : linePen,
486 (float)yValueRangeMin,
488 (float)yValueRangeMax);
490 // Reset vertical line detected flag
491 verticalLineDetected = false;
493 // Convert last point of the vertical line segment to pixel coordinates
494 prevPoint.Y = (float)
495 (vAxis.GetLinearPosition( lastVerticalSegmentPoint.Y ) * yPixelConverter);
498 // Draw line from previous to current point
507 (currentPointIsEmpty) ? emptyLinePen : linePen,
514 // Remember last point coordinates
517 prevDataPoint = point;
518 prevPoint = currentPoint;
519 prevPointInAxesCoordinates = false;
520 prevPointIsEmpty = currentPointIsEmpty;
524 // Draw last accumulated line segment
525 if(verticalLineDetected)
527 // Check if previous point must be converted from axes values to pixels
528 if(prevPointInAxesCoordinates)
530 prevPoint.X = (float)
531 (hAxis.GetLinearPosition( xValuePrev ) * xPixelConverter);
532 prevPoint.Y = (float)
533 (vAxis.GetLinearPosition( yValuePrev ) * yPixelConverter);
536 // Convert Y coordinates to pixels
537 yValueRangeMin = (vAxis.GetLinearPosition( yValueRangeMin ) * yPixelConverter);
538 yValueRangeMax = (vAxis.GetLinearPosition( yValueRangeMax ) * yPixelConverter);
540 // Draw accumulated vertical line
547 (prevPointIsEmpty) ? emptyLinePen : linePen,
549 (float)yValueRangeMin,
551 (float)yValueRangeMax);
553 verticalLineDetected = false;
554 yValueRangeMin = double.NaN;
555 yValueRangeMax = double.NaN;
556 pointRangeMin = null;
557 pointRangeMax = null;
571 /// Draws a line connecting two PointF structures.
573 /// <param name="series">Chart series.</param>
574 /// <param name="point">Series last data point in the group.</param>
575 /// <param name="pointMin">Series minimum Y value data point in the group.</param>
576 /// <param name="pointMax">Series maximum Y value data point in the group.</param>
577 /// <param name="pointIndex">Point index.</param>
578 /// <param name="pen">Pen object that determines the color, width, and style of the line.</param>
579 /// <param name="firstPointX">First point X coordinate.</param>
580 /// <param name="firstPointY">First point Y coordinate</param>
581 /// <param name="secondPointX">Second point X coordinate.</param>
582 /// <param name="secondPointY">Second point Y coordinate</param>
583 public virtual void DrawLine(
596 // Transform 3D coordinates
597 if(chartArea3DEnabled)
599 Point3D [] points = new Point3D[2];
601 // All coordinates has to be transformed in relative coordinate system
602 // NOTE: Fixes issue #5496
603 PointF firstPoint = Graph.GetRelativePoint(new PointF(firstPointX, firstPointY));
604 PointF secondPoint = Graph.GetRelativePoint(new PointF(secondPointX, secondPointY));
606 points[0] = new Point3D(firstPoint.X, firstPoint.Y, seriesZCoordinate);
607 points[1] = new Point3D(secondPoint.X, secondPoint.Y, seriesZCoordinate);
608 matrix3D.TransformPoints( points );
610 // All coordinates has to be transformed back to pixels
611 // NOTE: Fixes issue #5496
612 points[0].PointF = Graph.GetAbsolutePoint(points[0].PointF);
613 points[1].PointF = Graph.GetAbsolutePoint(points[1].PointF);
615 firstPointX = points[0].X;
616 firstPointY = points[0].Y;
617 secondPointX = points[1].X;
618 secondPointY = points[1].Y;
622 Graph.DrawLine(pen, firstPointX, firstPointY, secondPointX,secondPointY);
624 // Process selection regions
625 if( this.Common.ProcessModeRegions )
627 // Create grapics path object for the line
628 using (GraphicsPath path = new GraphicsPath())
630 float width = pen.Width + 2;
632 if (Math.Abs(firstPointX - secondPointX) > Math.Abs(firstPointY - secondPointY))
634 path.AddLine(firstPointX, firstPointY - width, secondPointX, secondPointY - width);
635 path.AddLine(secondPointX, secondPointY + width, firstPointX, firstPointY + width);
636 path.CloseAllFigures();
640 path.AddLine(firstPointX - width, firstPointY, secondPointX - width, secondPointY);
641 path.AddLine(secondPointX + width, secondPointY, firstPointX + width, firstPointY);
642 path.CloseAllFigures();
645 // Calculate bounding rectangle
646 RectangleF pathBounds = path.GetBounds();
648 // If one side of the bounding rectangle is less than 2 pixels
649 // use rectangle region shape to optimize used coordinates space
650 if (pathBounds.Width <= 2.0 || pathBounds.Height <= 2.0)
652 // Add hot region path as rectangle
653 pathBounds.Inflate(pen.Width, pen.Width);
654 this.Common.HotRegionsList.AddHotRegion(
655 Graph.GetRelativeRectangle(pathBounds),
662 // Add hot region path as polygon
663 this.Common.HotRegionsList.AddHotRegion(
677 #region Y values related methods
680 /// Helper function, which returns the Y value of the point.
682 /// <param name="common">Chart common elements.</param>
683 /// <param name="area">Chart area the series belongs to.</param>
684 /// <param name="series">Sereis of the point.</param>
685 /// <param name="point">Point object.</param>
686 /// <param name="pointIndex">Index of the point.</param>
687 /// <param name="yValueIndex">Index of the Y value to get.</param>
688 /// <returns>Y value of the point.</returns>
689 virtual public double GetYValue(
690 CommonElements common,
697 return point.YValues[yValueIndex];
702 #region SmartLabelStyle methods
705 /// Adds markers position to the list. Used to check SmartLabelStyle overlapping.
707 /// <param name="common">Common chart elements.</param>
708 /// <param name="area">Chart area.</param>
709 /// <param name="series">Series values to be used.</param>
710 /// <param name="list">List to add to.</param>
711 public void AddSmartLabelMarkerPositions(CommonElements common, ChartArea area, Series series, ArrayList list)
713 // Fast Line chart type do not support labels
718 #region IDisposable interface implementation
720 /// Releases unmanaged and - optionally - managed resources
722 /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
723 protected virtual void Dispose(bool disposing)
725 //Nothing to dispose at the base class.
729 /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
731 public void Dispose()
734 GC.SuppressFinalize(this);