File: Common\ChartTypes\ThreeLineBreakChart.cs
Project: ndp\fx\src\DataVisualization\System.Windows.Forms.DataVisualization.csproj (System.Windows.Forms.DataVisualization)
//-------------------------------------------------------------
// <copyright company=’Microsoft Corporation’>
//   Copyright © Microsoft Corporation. All Rights Reserved.
// </copyright>
//-------------------------------------------------------------
// @owner=alexgor, deliant
//=================================================================
//  File:		ThreeLineBreakChart.cs
//
//  Namespace:	DataVisualization.Charting.ChartTypes
//
//  Purpose:	ThreeLineBreak chart type provides methods for 
//              calculations and depends on the Range Column chart 
//              type to do all the drawing. PrepareData method is 
//              used to create temporary RangeColumn series and fill 
//              it with data. Changes are then reversed in the 
//              UnPrepareData method.
//
//	ThreeLineBreak Chart Overview:
//	------------------------------
//  
//  The Three Line Break chart is popular in Japan for financial 
//  charting. These charts display a series of vertical boxes ("lines") 
//  that reflect changes in price values. Similar to Kagi, Renko, and 
//  Point & Figure charts, the Three Line Break chart ignores the 
//  passage of time.
//  
//  The Three Line Break charting method is so-named because of the 
//  number of lines typically used. Each line may indicate "Buy", 
//  "Sell", and "trend less" markets. An advantage of Three Line Break 
//  charts is that there is no arbitrary fixed reversal amount. It is 
//  the price action which gives the indication of a reversal. The 
//  disadvantage of Three Line Break charts is that the signals are 
//  generated after the new trend is well under way. However, many 
//  traders are willing to accept the late signals in exchange for 
//  calling major trends.
//  
//  The sensitivity of the reversal criteria can be set by changing 
//  the number of lines in the break. For example, short-term traders 
//  might use two-line breaks to get more reversals, while a 
//  longer-term investor might use four-line, or even 10-line breaks 
//  to reduce the number of reversals. This is done using the 
//  NumberOfLinesInBreak custom attribute.
//  
//  The following should be taken into account when working with 
//  Three Line Break charts:
//  
//  - The X values of data points are automatically indexed. 
//  
//  - There is a formula applied to the original data before that data 
//  gets plotted. This formula changes the number of points in the data, 
//  and also changes the data points' X/Y values. 
//  
//  - Due to data being recalculated, we do not recommend setting the 
//  minimum and/or maximum values for the X axis. This is because it 
//  cannot be determined how many data points will actually be plotted. 
//  However, if the axis' Maximum, or Minimum is set, then the Maximum, 
//  or Minimum properties should use data point index values. 
//  
//  - Data point anchoring, used for annotations, is not supported in 
//  this type of chart. 
//  
//	Reviewed:	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;
using System.ComponentModel.Design;
using System.Globalization;
 
#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>
    /// ThreeLineBreakChart class provides methods to perform all nessesary 
    /// calculations to display ThreeLineBreak chart with the help of the 
    /// temporary RangeColumn series. This series is created in the 
    /// PrepareData method and then removed in the UnPrepareData method.
	/// </summary>
	internal class ThreeLineBreakChart : IChartType
	{
		#region Methods
 
		/// <summary>
		/// Prepares ThreeLineBreak chart type for rendering.
		/// </summary>
		/// <param name="series">Series to be prepared.</param>
		internal static void PrepareData(Series series)
		{
			// Check series chart type
			if(String.Compare(series.ChartTypeName, ChartTypeNames.ThreeLineBreak, StringComparison.OrdinalIgnoreCase ) != 0 || !series.IsVisible())
			{
				return;
			}
 
			// Get reference to the chart control
			Chart	chart = series.Chart;
			if(chart == null)
			{
                throw (new InvalidOperationException(SR.ExceptionThreeLineBreakNullReference));
			}
 
            // ThreeLineBreak chart may not be combined with any other chart types
            ChartArea area = chart.ChartAreas[series.ChartArea];
            foreach (Series currentSeries in chart.Series)
            {
                if (currentSeries.IsVisible() && currentSeries != series && area == chart.ChartAreas[currentSeries.ChartArea])
                {
                    throw (new InvalidOperationException(SR.ExceptionThreeLineBreakCanNotCobine));
                }
            }
 
			// Create a temp series which will hold original series data points
			Series seriesOriginalData = new Series("THREELINEBREAK_ORIGINAL_DATA_" + series.Name, series.YValuesPerPoint);
			seriesOriginalData.Enabled = false;
			seriesOriginalData.IsVisibleInLegend = false;
			chart.Series.Add(seriesOriginalData);
			foreach(DataPoint dp in series.Points)
			{
				seriesOriginalData.Points.Add(dp);
			}
			series.Points.Clear();
			if(series.IsCustomPropertySet("TempDesignData"))
			{
				seriesOriginalData["TempDesignData"] = "true";
			}
 
 
			// Change ThreeLineBreak series type to range column
			series["OldXValueIndexed"] = series.IsXValueIndexed.ToString(CultureInfo.InvariantCulture);
			series["OldYValuesPerPoint"] = series.YValuesPerPoint.ToString(CultureInfo.InvariantCulture);
			series.ChartType = SeriesChartType.RangeColumn;
			series.IsXValueIndexed = true;
			series.YValuesPerPoint = 2;
 
			// Calculate date-time interval for indexed series
			if(series.ChartArea.Length > 0 &&
				series.IsXValueDateTime())
			{
				// Get X axis connected to the series
				Axis		xAxis = area.GetAxis(AxisName.X, series.XAxisType, series.XSubAxisName);
 
				// Change interval for auto-calculated interval only
				if(xAxis.Interval == 0 && xAxis.IntervalType == DateTimeIntervalType.Auto)
				{
					// Check if original data has X values set to date-time values and
					// calculate min/max X values.
					bool	nonZeroXValues = false;
					double	minX = double.MaxValue;
					double	maxX = double.MinValue;
					foreach(DataPoint dp in seriesOriginalData.Points)
					{
						if(!dp.IsEmpty)
						{
							if(dp.XValue != 0.0)
							{
								nonZeroXValues = true;
							}
							if(dp.XValue > maxX)
							{
								maxX = dp.XValue;
							}
							if(dp.XValue < minX)
							{
								minX = dp.XValue;
							}
						}
					}
 
					if(nonZeroXValues)
					{
						// Save flag that axis interval is automatic
						series["OldAutomaticXAxisInterval"] = "true";
 
						// Calculate and set axis date-time interval
						DateTimeIntervalType	intervalType = DateTimeIntervalType.Auto;
						xAxis.interval = xAxis.CalcInterval(minX, maxX, true, out intervalType, series.XValueType);
						xAxis.intervalType = intervalType;
					}
				}
			}
 
			// Calculate ThreeLineBreak bricks data points values
			FillThreeLineBreakData(series, seriesOriginalData);
		}
 
		/// <summary>
		/// Remove any changes done while preparing ThreeLineBreak chart type for rendering.
		/// </summary>
		/// <param name="series">Series to be un-prepared.</param>
		/// <returns>True if series was removed from collection.</returns>
		internal static bool UnPrepareData(Series series)
		{
            if (series.Name.StartsWith("THREELINEBREAK_ORIGINAL_DATA_", StringComparison.Ordinal))
            {
                // Get reference to the chart control
                Chart chart = series.Chart;
                if (chart == null)
                {
                    throw (new InvalidOperationException(SR.ExceptionThreeLineBreakNullReference));
                }
 
                // Get original ThreeLineBreak series
                Series threeLineBreakSeries = chart.Series[series.Name.Substring(29)];
                Series.MovePositionMarkers(threeLineBreakSeries, series);
                // Copy data back to original ThreeLineBreak series
                threeLineBreakSeries.Points.Clear();
                if (!series.IsCustomPropertySet("TempDesignData"))
                {
                    foreach (DataPoint dp in series.Points)
                    {
                        threeLineBreakSeries.Points.Add(dp);
                    }
                }
 
                // Restore ThreeLineBreak series properties
                threeLineBreakSeries.ChartType = SeriesChartType.ThreeLineBreak;
 
                bool xValIndexed;
                bool parseSucceed = bool.TryParse(threeLineBreakSeries["OldXValueIndexed"], out xValIndexed);
                threeLineBreakSeries.IsXValueIndexed = parseSucceed && xValIndexed;
 
                int yValsPerPoint;
                parseSucceed = int.TryParse(threeLineBreakSeries["OldYValuesPerPoint"], NumberStyles.Any, CultureInfo.InvariantCulture, out yValsPerPoint);
 
                if (parseSucceed)
                {
                    threeLineBreakSeries.YValuesPerPoint = yValsPerPoint;
                }
 
 
                threeLineBreakSeries.DeleteCustomProperty("OldXValueIndexed");
                threeLineBreakSeries.DeleteCustomProperty("OldYValuesPerPoint");
 
                series["OldAutomaticXAxisInterval"] = "true";
                if (threeLineBreakSeries.IsCustomPropertySet("OldAutomaticXAxisInterval"))
                {
                    threeLineBreakSeries.DeleteCustomProperty("OldAutomaticXAxisInterval");
 
                    // Reset automatic interval for X axis
                    if (threeLineBreakSeries.ChartArea.Length > 0)
                    {
                        // Get X axis connected to the series
                        ChartArea area = chart.ChartAreas[threeLineBreakSeries.ChartArea];
                        Axis xAxis = area.GetAxis(AxisName.X, threeLineBreakSeries.XAxisType, threeLineBreakSeries.XSubAxisName);
 
                        xAxis.interval = 0.0;
                        xAxis.intervalType = DateTimeIntervalType.Auto;
                    }
                }
 
                // Remove series from the collection
                chart.Series.Remove(series);
                return true;
            }
 
			return false;
		}
 
		/// <summary>
		/// Fills range column series with data to draw the ThreeLineBreak chart.
		/// </summary>
		/// <param name="series">Range column chart series used to dispaly the ThreeLineBreak chart.</param>
		/// <param name="originalData">Series with original data.</param>
		private static void FillThreeLineBreakData(Series series, Series originalData)
		{
			// Get index of the Y values used
			int	yValueIndex = 0;
			if(series.IsCustomPropertySet(CustomPropertyName.UsedYValue))
			{
				try
				{
					yValueIndex = int.Parse(series[CustomPropertyName.UsedYValue], CultureInfo.InvariantCulture);
				}
				catch
				{
                    throw (new InvalidOperationException(SR.ExceptionThreeLineBreakUsedYValueInvalid));
				}
 
				if(yValueIndex >= series.YValuesPerPoint)
				{
                    throw (new InvalidOperationException(SR.ExceptionThreeLineBreakUsedYValueOutOfRange));
				}
			}
 
			// Get number of lines in the break
			int	linesInBreak = 3;
			if(series.IsCustomPropertySet(CustomPropertyName.NumberOfLinesInBreak))
			{
				try
				{
					linesInBreak = int.Parse(series[CustomPropertyName.NumberOfLinesInBreak], CultureInfo.InvariantCulture);
				}
				catch
				{
                    throw (new InvalidOperationException(SR.ExceptionThreeLineBreakNumberOfLinesInBreakFormatInvalid));
				}
 
				if(linesInBreak <= 0)
				{
                    throw (new InvalidOperationException(SR.ExceptionThreeLineBreakNumberOfLinesInBreakValueInvalid));
				}
			}
 
			// Create an array to store the history of high/low values of drawn lines
			ArrayList	highLowHistory = new ArrayList();
 
			// Fill points
			double	prevLow = double.NaN;
			double	prevHigh = double.NaN;
			int		sameDirectionLines = 0;
			int		prevDirection = 0;
			int		pointIndex = 0;
			foreach(DataPoint dataPoint in originalData.Points)
			{
				int	direction = 0;	// 1 up; -1 down
 
				// Skip empty points
				if(dataPoint.IsEmpty)
				{
					++pointIndex;
					continue;
				}
 
				// Check if previus values exists
				if(double.IsNaN(prevLow) || double.IsNaN(prevHigh))
				{
					prevHigh = dataPoint.YValues[yValueIndex];
					prevLow = dataPoint.YValues[yValueIndex];
					++pointIndex;
					continue;
				}
 
				// Get up price color
				Color	priceUpColor = Color.Transparent;
				string	priceUpColorString = dataPoint[CustomPropertyName.PriceUpColor];
				if(priceUpColorString == null)
				{
					priceUpColorString = series[CustomPropertyName.PriceUpColor];
				}
				if(priceUpColorString != null)
				{
					try
					{
						ColorConverter colorConverter = new ColorConverter();
						priceUpColor = (Color)colorConverter.ConvertFromString(null, CultureInfo.InvariantCulture, priceUpColorString);
					}
					catch
					{
                        throw (new InvalidOperationException(SR.ExceptionThreeLineBreakUpBrickColorInvalid));
					}
				}
 
				// Check if close value exceeds last brick position by box size
				if(dataPoint.YValues[yValueIndex] > prevHigh)
				{
					direction = 1;
				}
				else if(dataPoint.YValues[yValueIndex] < prevLow)
				{
					direction = -1;
				}
				else
				{
					direction = 0;
				}
 
				// Process up/down direction
				if(direction != 0)
				{
					// Check if direction is same as previous
					if(prevDirection == direction)
					{
						++sameDirectionLines;
					}
					else
					{
						// If number of lines darwn in same direction is more or equal
						// to number of lines in the break, the price must extend the 
						// high or low price of the lines in the whole break.
						if(sameDirectionLines >= linesInBreak)
						{
							if(direction == 1)
							{
								// Calculate high value for the last N lines
								double lineBreakHigh = double.MinValue;
								for(int index = 0; index < highLowHistory.Count; index += 2)
								{
									if(((double)highLowHistory[index]) > lineBreakHigh)
									{
										lineBreakHigh = ((double)highLowHistory[index]);
									}
								}
 
								// If point value is less - ignore it
								if(dataPoint.YValues[yValueIndex] <= lineBreakHigh)
								{
									direction = 0;
								}
							}
							else if(direction == -1)
							{
								// Calculate low value for the last N lines
								double lineBreakLow = double.MaxValue;
								for(int index = 1; index < highLowHistory.Count; index += 2)
								{
									if(((double)highLowHistory[index]) < lineBreakLow)
									{
										lineBreakLow = ((double)highLowHistory[index]);
									}
								}
 
								// If point value is more - ignore it
								if(dataPoint.YValues[yValueIndex] >= lineBreakLow)
								{
									direction = 0;
								}
							}
						}
 
						if(direction != 0)
						{
							sameDirectionLines = 1;
						}
					}
 
					if(direction != 0)
					{
						// Add point
						DataPoint newDataPoint = (DataPoint)dataPoint.Clone();
						newDataPoint["OriginalPointIndex"] = pointIndex.ToString(CultureInfo.InvariantCulture);
						newDataPoint.series = series;
						newDataPoint.YValues = new double[2];
						newDataPoint.XValue = dataPoint.XValue;
                        newDataPoint.Tag = dataPoint;
						if(direction == 1)
						{
							newDataPoint.YValues[1] = prevHigh;
							newDataPoint.YValues[0] = dataPoint.YValues[yValueIndex];
							prevLow = prevHigh;
							prevHigh = dataPoint.YValues[yValueIndex];
 
							// Set ThreeLineBreak up brick appearance
							newDataPoint.Color = priceUpColor;
							if(newDataPoint.BorderWidth < 1)
							{
								newDataPoint.BorderWidth = 1;
							}
							if(newDataPoint.BorderDashStyle == ChartDashStyle.NotSet)
							{
								newDataPoint.BorderDashStyle = ChartDashStyle.Solid;
							}
							if( (newDataPoint.BorderColor == Color.Empty || newDataPoint.BorderColor == Color.Transparent) &&
								(newDataPoint.Color == Color.Empty || newDataPoint.Color == Color.Transparent) )
							{
								newDataPoint.BorderColor = series.Color;
							}
						}
						else
						{
							newDataPoint.YValues[1] = prevLow;
							newDataPoint.YValues[0] = dataPoint.YValues[yValueIndex];
							prevHigh = prevLow;
							prevLow = dataPoint.YValues[yValueIndex];
						}
 
						// Add ThreeLineBreak brick to the range column series
						series.Points.Add(newDataPoint);
 
						// Remember high/low values of drawn line
						highLowHistory.Add(prevHigh);
						highLowHistory.Add(prevLow);
 
						// Do not store all values in array only number of break lines
						if(highLowHistory.Count > linesInBreak * 2)
						{
							// Remove two items at a time (high & low)
							highLowHistory.RemoveAt(0);
							highLowHistory.RemoveAt(0);
						}
					}
				}
 
				// Remember last direction
				if(direction != 0)
				{
					prevDirection = direction;
				}
 
				++pointIndex;
			}
		}
 
		#endregion // Methods
 
		#region Painting and Selection methods
 
		/// <summary>
		/// Paint 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 )
		{
            // Three Line Break series is never drawn directly. It is replaced with the range column chart. 
            // See PrepareData method.
		}
		#endregion
 
		#region IChartType interface implementation
 
		/// <summary>
		/// Chart type name
		/// </summary>
		virtual public string Name			{ get{ return ChartTypeNames.ThreeLineBreak;}}
 
		/// <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>
		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>
		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 true; } }
		
		/// <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.Rectangle;
		}
	
		/// <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 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)
		{
		}
 
		#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
	}
}