File: Common\ChartTypes\FastPointChart.cs
Project: ndp\fx\src\DataVisualization\System.Web.DataVisualization.csproj (System.Web.DataVisualization)
//-------------------------------------------------------------
// <copyright company=’Microsoft Corporation’>
//   Copyright © Microsoft Corporation. All Rights Reserved.
// </copyright>
//-------------------------------------------------------------
// @owner=alexgor, deliant
//=================================================================
//  File:		FastPointChart.cs
//
//  Namespace:	DataVisualization.Charting.ChartTypes
//
//	Classes:	FastPointChart
//
//  Purpose:	When performance is critical, the FastPoint chart 
//              type is a good alternative to the Point chart. FastPoint 
//              charts significantly reduce the drawing time of a 
//              series that contains a very large number of data points.
//              
//              To make the FastPoint chart a high performance chart, 
//              some charting features have been omitted. The features 
//              omitted include the ability to control Point level 
//              visual properties the use of data point labels, shadows, 
//              and the use of chart animation.
//
//              FastPoint 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. FastPoint 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>
    /// FastPointChart class implements a simplified point chart drawing 
    /// algorithm which is optimized for the performance.
    /// </summary>
    internal class FastPointChart : 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 FastPointChart()
		{
		}
 
		#endregion
 
		#region IChartType interface implementation
 
		/// <summary>
		/// Chart type name
		/// </summary>
		virtual public string Name			{ get{ return ChartTypeNames.FastPoint;}}
 
		/// <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.Marker;
		}
 
		/// <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 FastPoint 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;
			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 FastPoint 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 this.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.
				// By default use 1/3 of the marker size.
				float	permittedPixelError = series.MarkerSize / 3f;
                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));
				double axesValuesPixelSizeY = Math.Abs(vAxis.PositionToValue(axesMin.Height + pixelSize.Height, false) - vAxis.PositionToValue(axesMin.Height, false));
 
				// Create point marker brush
				SolidBrush	markerBrush = new SolidBrush( ((series.MarkerColor.IsEmpty) ? series.Color : series.MarkerColor) );
				SolidBrush	emptyMarkerBrush = new SolidBrush( ((series.EmptyPointStyle.MarkerColor.IsEmpty) ? series.EmptyPointStyle.Color : series.EmptyPointStyle.MarkerColor) );
 
				// Create point marker border pen
				Pen	borderPen = null;
				Pen	emptyBorderPen = null;
				if(!series.MarkerBorderColor.IsEmpty && series.MarkerBorderWidth > 0)
				{
					borderPen = new Pen(series.MarkerBorderColor, series.MarkerBorderWidth);
				}
				if(!series.EmptyPointStyle.MarkerBorderColor.IsEmpty && series.EmptyPointStyle.MarkerBorderWidth > 0)
				{
					emptyBorderPen = new Pen(series.EmptyPointStyle.MarkerBorderColor, series.EmptyPointStyle.MarkerBorderWidth);
				}
 
				// Check if series is indexed
				bool indexedSeries = ChartHelper.IndexedSeries(this.Common, series.Name );
 
                // Get marker size taking in consideration current DPIs
                int markerSize = series.MarkerSize;
                if (graph != null && graph.Graphics != null)
                {
                    // Marker size is in pixels and we do the mapping for higher DPIs
                    markerSize = (int)Math.Max(markerSize * graph.Graphics.DpiX / 96, markerSize * graph.Graphics.DpiY / 96);
                }
 
				// Loop through all ponts in the series
				int		index = 0;
				double	xValue = 0.0;
				double	yValue = 0.0;
				double	xValuePrev = 0.0;
				double	yValuePrev = 0.0;
				PointF	currentPoint = PointF.Empty;
				bool	currentPointIsEmpty = false;
				double	xPixelConverter = (graph.Common.ChartPicture.Width - 1.0) / 100.0;
				double	yPixelConverter = (graph.Common.ChartPicture.Height - 1.0) / 100.0;
				MarkerStyle	markerStyle = series.MarkerStyle;
				MarkerStyle	emptyMarkerStyle = series.EmptyPointStyle.MarkerStyle;
				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;
 
					// Check if point is completly out of the data scaleView
					if( xValue < hAxisMin ||
						xValue > hAxisMax ||
						yValue < vAxisMin ||
						yValue > vAxisMax )
					{
						xValuePrev = xValue;
						yValuePrev = yValue;
						++index;
						continue;
					}
 
					// Check if point may be skipped
					if(index > 0)
					{
						// Check if current point location is in the specified distance from the 
						// preious data location.
						if(Math.Abs(xValue - xValuePrev) < axesValuesPixelSizeX &&
							Math.Abs(yValue - yValuePrev) < axesValuesPixelSizeY)
						{
							// Increase counter and proceed to the next data point
							++index;
							continue;
						}
					}
 
					// Get point pixel position
					currentPoint.X = (float)
						(hAxis.GetLinearPosition( xValue ) * xPixelConverter);
					currentPoint.Y = (float)
						(vAxis.GetLinearPosition( yValue ) * yPixelConverter);
 
					// Draw point marker
                    MarkerStyle	currentMarkerStyle = (currentPointIsEmpty) ? emptyMarkerStyle : markerStyle;
                    if(currentMarkerStyle != MarkerStyle.None)
                    {
					    this.DrawMarker(
						    graph,
						    point,
						    index,
						    currentPoint,
                            currentMarkerStyle,
						    markerSize,
						    (currentPointIsEmpty) ? emptyMarkerBrush : markerBrush,
						    (currentPointIsEmpty) ? emptyBorderPen : borderPen);
                    }
 
					// Remember last point coordinates
					xValuePrev = xValue;
					yValuePrev = yValue;
					++index;
				}
 
				// Dispose used brushes and pens
				markerBrush.Dispose();
				emptyMarkerBrush.Dispose();
				if(borderPen != null)
				{
					borderPen.Dispose();
				}
				if(emptyBorderPen != null)
				{
					emptyBorderPen.Dispose();
				}
			}
		}
 
		/// <summary>
		/// Draws a marker that represents a data point in FastPoint series.
		/// </summary>
		/// <param name="graph">Chart graphics used to draw the marker.</param>
		/// <param name="point">Series data point drawn.</param>
		/// <param name="pointIndex">Data point index in the series.</param>
		/// <param name="location">Marker location in pixels.</param>
		/// <param name="markerStyle">Marker style.</param>
		/// <param name="markerSize">Marker size in pixels.</param>
		/// <param name="brush">Brush used to fill marker shape.</param>
		/// <param name="borderPen">Marker border pen.</param>
		virtual protected void DrawMarker(
			ChartGraphics graph, 
			DataPoint point, 
			int pointIndex, 
			PointF location, 
			MarkerStyle markerStyle, 
			int markerSize, 
			Brush brush,
			Pen borderPen)
		{
			// Transform 3D coordinates
			if(chartArea3DEnabled)
			{
				Point3D [] points = new Point3D[1];
				location = graph.GetRelativePoint(location);
				points[0] = new Point3D(location.X, location.Y, this.seriesZCoordinate);
				matrix3D.TransformPoints( points );
				location.X = points[0].X;
				location.Y = points[0].Y;
				location = graph.GetAbsolutePoint(location);
			}
 
			// Create marker bounding rectangle in pixels
			RectangleF markerBounds = new RectangleF(
				location.X - markerSize / 2f, location.Y - markerSize / 2f, markerSize, markerSize);
 
			// Draw Marker
			switch(markerStyle)
			{
				case(MarkerStyle.Star4):
				case(MarkerStyle.Star5):
				case(MarkerStyle.Star6):
				case(MarkerStyle.Star10):
				{
					// Set number of corners
					int cornerNumber = 4;
					if(markerStyle == MarkerStyle.Star5)
					{
						cornerNumber = 5;
					}
					else if(markerStyle == MarkerStyle.Star6)
					{
						cornerNumber = 6;
					}
					else if(markerStyle == MarkerStyle.Star10)
					{
						cornerNumber = 10;
					}
 
					// Get star polygon
					PointF[]	points = graph.CreateStarPolygon(markerBounds, cornerNumber);
 
					// Fill shape
					graph.FillPolygon(brush, points);
 
					// Draw border
					if(borderPen != null)
					{
						graph.DrawPolygon(borderPen, points);
					}
					break;
				}
				case(MarkerStyle.Circle):
				{
					graph.FillEllipse(brush, markerBounds);
 
					// Draw border
					if(borderPen != null)
					{
						graph.DrawEllipse(borderPen, markerBounds);
					}
 
					break;
				}
				case(MarkerStyle.Square):
				{
					graph.FillRectangle(brush, markerBounds);
 
					// Draw border
					if(borderPen != null)
					{
						graph.DrawRectangle(
							borderPen, 
							(int)Math.Round(markerBounds.X, 0), 
							(int)Math.Round(markerBounds.Y, 0), 
							(int)Math.Round(markerBounds.Width, 0), 
							(int)Math.Round(markerBounds.Height, 0) );
					}
 
					break;
				}
				case(MarkerStyle.Cross):
				{
					// Calculate cross line width and size
					float crossLineWidth = (float)Math.Ceiling(markerSize/4F);
					float crossSize = markerSize;	// * (float)Math.Sin(45f/180f*Math.PI);
 
					// Calculate cross coordinates
					PointF[] points = new PointF[12];
					points[0].X = location.X - crossSize/2F;
					points[0].Y = location.Y + crossLineWidth/2F;
					points[1].X = location.X - crossSize/2F;
					points[1].Y = location.Y - crossLineWidth/2F;
 
					points[2].X = location.X - crossLineWidth/2F;
					points[2].Y = location.Y - crossLineWidth/2F;
					points[3].X = location.X - crossLineWidth/2F;
					points[3].Y = location.Y - crossSize/2F;
					points[4].X = location.X + crossLineWidth/2F;
					points[4].Y = location.Y - crossSize/2F;
 
					points[5].X = location.X + crossLineWidth/2F;
					points[5].Y = location.Y - crossLineWidth/2F;
					points[6].X = location.X + crossSize/2F;
					points[6].Y = location.Y - crossLineWidth/2F;
					points[7].X = location.X + crossSize/2F;
					points[7].Y = location.Y + crossLineWidth/2F;
 
					points[8].X = location.X + crossLineWidth/2F;
					points[8].Y = location.Y + crossLineWidth/2F;
					points[9].X = location.X + crossLineWidth/2F;
					points[9].Y = location.Y + crossSize/2F;
					points[10].X = location.X - crossLineWidth/2F;
					points[10].Y = location.Y + crossSize/2F;
					points[11].X = location.X - crossLineWidth/2F;
					points[11].Y = location.Y + crossLineWidth/2F;
 
					// Rotate cross coordinates 45 degrees
					Matrix rotationMatrix = new Matrix();
					rotationMatrix.RotateAt(45, location);
					rotationMatrix.TransformPoints(points);
					rotationMatrix.Dispose();
 
					// Fill shape
					graph.FillPolygon(brush, points);
 
					// Draw border
					if(borderPen != null)
					{
						graph.DrawPolygon(borderPen, points);
					}
					break;
				}
				case(MarkerStyle.Diamond):
				{
					PointF[] points = new PointF[4];
					points[0].X = markerBounds.X;
					points[0].Y = markerBounds.Y + markerBounds.Height/2F;
					points[1].X = markerBounds.X + markerBounds.Width/2F;
					points[1].Y = markerBounds.Top;
					points[2].X = markerBounds.Right;
					points[2].Y = markerBounds.Y + markerBounds.Height/2F;
					points[3].X = markerBounds.X + markerBounds.Width/2F;
					points[3].Y = markerBounds.Bottom;
 
					graph.FillPolygon(brush, points);
 
					// Draw border
					if(borderPen != null)
					{
						graph.DrawPolygon(borderPen, points);
					}
					break;
				}
				case(MarkerStyle.Triangle):
				{
					PointF[] points = new PointF[3];
					points[0].X = markerBounds.X;
					points[0].Y = markerBounds.Bottom;
					points[1].X = markerBounds.X + markerBounds.Width/2F;
					points[1].Y = markerBounds.Top;
					points[2].X = markerBounds.Right;
					points[2].Y = markerBounds.Bottom;
 
					graph.FillPolygon(brush, points);
 
					// Draw border
					if(borderPen != null)
					{
						graph.DrawPolygon(borderPen, points);
					}
					break;
				}
				default:
				{
                    throw (new InvalidOperationException(SR.ExceptionFastPointMarkerStyleUnknown));
				}
			}
			
			// Process selection regions
			if( this.Common.ProcessModeRegions )
			{
				this.Common.HotRegionsList.AddHotRegion(
					graph.GetRelativeRectangle(markerBounds),
					point, 
					point.series.Name, 
					pointIndex );
			}
		}
 
		#endregion
 
		#region Y values related methods
 
		/// <summary>
		/// Helper function, which returns the Y value of the location.
		/// </summary>
		/// <param name="common">Chart common elements.</param>
		/// <param name="area">Chart area the series belongs to.</param>
		/// <param name="series">Sereis of the location.</param>
		/// <param name="point">Point object.</param>
		/// <param name="pointIndex">Index of the location.</param>
		/// <param name="yValueIndex">Index of the Y value to get.</param>
		/// <returns>Y value of the location.</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 Point 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
	}
}