1 //-------------------------------------------------------------
2 // <copyright company=
\92Microsoft Corporation
\92>
3 // Copyright © Microsoft Corporation. All Rights Reserved.
5 //-------------------------------------------------------------
6 // @owner=alexgor, deliant
7 //=================================================================
8 // File: ThreeLineBreakChart.cs
10 // Namespace: DataVisualization.Charting.ChartTypes
12 // Purpose: ThreeLineBreak chart type provides methods for
13 // calculations and depends on the Range Column chart
14 // type to do all the drawing. PrepareData method is
15 // used to create temporary RangeColumn series and fill
16 // it with data. Changes are then reversed in the
17 // UnPrepareData method.
19 // ThreeLineBreak Chart Overview:
20 // ------------------------------
22 // The Three Line Break chart is popular in Japan for financial
23 // charting. These charts display a series of vertical boxes ("lines")
24 // that reflect changes in price values. Similar to Kagi, Renko, and
25 // Point & Figure charts, the Three Line Break chart ignores the
28 // The Three Line Break charting method is so-named because of the
29 // number of lines typically used. Each line may indicate "Buy",
30 // "Sell", and "trend less" markets. An advantage of Three Line Break
31 // charts is that there is no arbitrary fixed reversal amount. It is
32 // the price action which gives the indication of a reversal. The
33 // disadvantage of Three Line Break charts is that the signals are
34 // generated after the new trend is well under way. However, many
35 // traders are willing to accept the late signals in exchange for
36 // calling major trends.
38 // The sensitivity of the reversal criteria can be set by changing
39 // the number of lines in the break. For example, short-term traders
40 // might use two-line breaks to get more reversals, while a
41 // longer-term investor might use four-line, or even 10-line breaks
42 // to reduce the number of reversals. This is done using the
43 // NumberOfLinesInBreak custom attribute.
45 // The following should be taken into account when working with
46 // Three Line Break charts:
48 // - The X values of data points are automatically indexed.
50 // - There is a formula applied to the original data before that data
51 // gets plotted. This formula changes the number of points in the data,
52 // and also changes the data points' X/Y values.
54 // - Due to data being recalculated, we do not recommend setting the
55 // minimum and/or maximum values for the X axis. This is because it
56 // cannot be determined how many data points will actually be plotted.
57 // However, if the axis' Maximum, or Minimum is set, then the Maximum,
58 // or Minimum properties should use data point index values.
60 // - Data point anchoring, used for annotations, is not supported in
61 // this type of chart.
63 // Reviewed: AG - Microsoft 7, 2007
65 //===================================================================
67 #region Used namespaces
70 using System.Resources;
71 using System.Reflection;
72 using System.Collections;
74 using System.Drawing.Drawing2D;
75 using System.ComponentModel.Design;
76 using System.Globalization;
79 using System.Windows.Forms.DataVisualization.Charting;
80 using System.Windows.Forms.DataVisualization.Charting.Data;
81 using System.Windows.Forms.DataVisualization.Charting.ChartTypes;
82 using System.Windows.Forms.DataVisualization.Charting.Utilities;
83 using System.Windows.Forms.DataVisualization.Charting.Borders3D;
85 using System.Web.UI.DataVisualization.Charting;
87 using System.Web.UI.DataVisualization.Charting.ChartTypes;
88 using System.Web.UI.DataVisualization.Charting.Data;
89 using System.Web.UI.DataVisualization.Charting.Utilities;
95 namespace System.Windows.Forms.DataVisualization.Charting.ChartTypes
97 namespace System.Web.UI.DataVisualization.Charting.ChartTypes
101 /// ThreeLineBreakChart class provides methods to perform all nessesary
102 /// calculations to display ThreeLineBreak chart with the help of the
103 /// temporary RangeColumn series. This series is created in the
104 /// PrepareData method and then removed in the UnPrepareData method.
106 internal class ThreeLineBreakChart : IChartType
111 /// Prepares ThreeLineBreak chart type for rendering.
113 /// <param name="series">Series to be prepared.</param>
114 internal static void PrepareData(Series series)
116 // Check series chart type
117 if(String.Compare(series.ChartTypeName, ChartTypeNames.ThreeLineBreak, StringComparison.OrdinalIgnoreCase ) != 0 || !series.IsVisible())
122 // Get reference to the chart control
123 Chart chart = series.Chart;
126 throw (new InvalidOperationException(SR.ExceptionThreeLineBreakNullReference));
129 // ThreeLineBreak chart may not be combined with any other chart types
130 ChartArea area = chart.ChartAreas[series.ChartArea];
131 foreach (Series currentSeries in chart.Series)
133 if (currentSeries.IsVisible() && currentSeries != series && area == chart.ChartAreas[currentSeries.ChartArea])
135 throw (new InvalidOperationException(SR.ExceptionThreeLineBreakCanNotCobine));
139 // Create a temp series which will hold original series data points
140 Series seriesOriginalData = new Series("THREELINEBREAK_ORIGINAL_DATA_" + series.Name, series.YValuesPerPoint);
141 seriesOriginalData.Enabled = false;
142 seriesOriginalData.IsVisibleInLegend = false;
143 chart.Series.Add(seriesOriginalData);
144 foreach(DataPoint dp in series.Points)
146 seriesOriginalData.Points.Add(dp);
148 series.Points.Clear();
149 if(series.IsCustomPropertySet("TempDesignData"))
151 seriesOriginalData["TempDesignData"] = "true";
155 // Change ThreeLineBreak series type to range column
156 series["OldXValueIndexed"] = series.IsXValueIndexed.ToString(CultureInfo.InvariantCulture);
157 series["OldYValuesPerPoint"] = series.YValuesPerPoint.ToString(CultureInfo.InvariantCulture);
158 series.ChartType = SeriesChartType.RangeColumn;
159 series.IsXValueIndexed = true;
160 series.YValuesPerPoint = 2;
162 // Calculate date-time interval for indexed series
163 if(series.ChartArea.Length > 0 &&
164 series.IsXValueDateTime())
166 // Get X axis connected to the series
167 Axis xAxis = area.GetAxis(AxisName.X, series.XAxisType, series.XSubAxisName);
169 // Change interval for auto-calculated interval only
170 if(xAxis.Interval == 0 && xAxis.IntervalType == DateTimeIntervalType.Auto)
172 // Check if original data has X values set to date-time values and
173 // calculate min/max X values.
174 bool nonZeroXValues = false;
175 double minX = double.MaxValue;
176 double maxX = double.MinValue;
177 foreach(DataPoint dp in seriesOriginalData.Points)
183 nonZeroXValues = true;
198 // Save flag that axis interval is automatic
199 series["OldAutomaticXAxisInterval"] = "true";
201 // Calculate and set axis date-time interval
202 DateTimeIntervalType intervalType = DateTimeIntervalType.Auto;
203 xAxis.interval = xAxis.CalcInterval(minX, maxX, true, out intervalType, series.XValueType);
204 xAxis.intervalType = intervalType;
209 // Calculate ThreeLineBreak bricks data points values
210 FillThreeLineBreakData(series, seriesOriginalData);
214 /// Remove any changes done while preparing ThreeLineBreak chart type for rendering.
216 /// <param name="series">Series to be un-prepared.</param>
217 /// <returns>True if series was removed from collection.</returns>
218 internal static bool UnPrepareData(Series series)
220 if (series.Name.StartsWith("THREELINEBREAK_ORIGINAL_DATA_", StringComparison.Ordinal))
222 // Get reference to the chart control
223 Chart chart = series.Chart;
226 throw (new InvalidOperationException(SR.ExceptionThreeLineBreakNullReference));
229 // Get original ThreeLineBreak series
230 Series threeLineBreakSeries = chart.Series[series.Name.Substring(29)];
231 Series.MovePositionMarkers(threeLineBreakSeries, series);
232 // Copy data back to original ThreeLineBreak series
233 threeLineBreakSeries.Points.Clear();
234 if (!series.IsCustomPropertySet("TempDesignData"))
236 foreach (DataPoint dp in series.Points)
238 threeLineBreakSeries.Points.Add(dp);
242 // Restore ThreeLineBreak series properties
243 threeLineBreakSeries.ChartType = SeriesChartType.ThreeLineBreak;
246 bool parseSucceed = bool.TryParse(threeLineBreakSeries["OldXValueIndexed"], out xValIndexed);
247 threeLineBreakSeries.IsXValueIndexed = parseSucceed && xValIndexed;
250 parseSucceed = int.TryParse(threeLineBreakSeries["OldYValuesPerPoint"], NumberStyles.Any, CultureInfo.InvariantCulture, out yValsPerPoint);
254 threeLineBreakSeries.YValuesPerPoint = yValsPerPoint;
258 threeLineBreakSeries.DeleteCustomProperty("OldXValueIndexed");
259 threeLineBreakSeries.DeleteCustomProperty("OldYValuesPerPoint");
261 series["OldAutomaticXAxisInterval"] = "true";
262 if (threeLineBreakSeries.IsCustomPropertySet("OldAutomaticXAxisInterval"))
264 threeLineBreakSeries.DeleteCustomProperty("OldAutomaticXAxisInterval");
266 // Reset automatic interval for X axis
267 if (threeLineBreakSeries.ChartArea.Length > 0)
269 // Get X axis connected to the series
270 ChartArea area = chart.ChartAreas[threeLineBreakSeries.ChartArea];
271 Axis xAxis = area.GetAxis(AxisName.X, threeLineBreakSeries.XAxisType, threeLineBreakSeries.XSubAxisName);
273 xAxis.interval = 0.0;
274 xAxis.intervalType = DateTimeIntervalType.Auto;
278 // Remove series from the collection
279 chart.Series.Remove(series);
287 /// Fills range column series with data to draw the ThreeLineBreak chart.
289 /// <param name="series">Range column chart series used to dispaly the ThreeLineBreak chart.</param>
290 /// <param name="originalData">Series with original data.</param>
291 private static void FillThreeLineBreakData(Series series, Series originalData)
293 // Get index of the Y values used
295 if(series.IsCustomPropertySet(CustomPropertyName.UsedYValue))
299 yValueIndex = int.Parse(series[CustomPropertyName.UsedYValue], CultureInfo.InvariantCulture);
303 throw (new InvalidOperationException(SR.ExceptionThreeLineBreakUsedYValueInvalid));
306 if(yValueIndex >= series.YValuesPerPoint)
308 throw (new InvalidOperationException(SR.ExceptionThreeLineBreakUsedYValueOutOfRange));
312 // Get number of lines in the break
313 int linesInBreak = 3;
314 if(series.IsCustomPropertySet(CustomPropertyName.NumberOfLinesInBreak))
318 linesInBreak = int.Parse(series[CustomPropertyName.NumberOfLinesInBreak], CultureInfo.InvariantCulture);
322 throw (new InvalidOperationException(SR.ExceptionThreeLineBreakNumberOfLinesInBreakFormatInvalid));
325 if(linesInBreak <= 0)
327 throw (new InvalidOperationException(SR.ExceptionThreeLineBreakNumberOfLinesInBreakValueInvalid));
331 // Create an array to store the history of high/low values of drawn lines
332 ArrayList highLowHistory = new ArrayList();
335 double prevLow = double.NaN;
336 double prevHigh = double.NaN;
337 int sameDirectionLines = 0;
338 int prevDirection = 0;
340 foreach(DataPoint dataPoint in originalData.Points)
342 int direction = 0; // 1 up; -1 down
345 if(dataPoint.IsEmpty)
351 // Check if previus values exists
352 if(double.IsNaN(prevLow) || double.IsNaN(prevHigh))
354 prevHigh = dataPoint.YValues[yValueIndex];
355 prevLow = dataPoint.YValues[yValueIndex];
360 // Get up price color
361 Color priceUpColor = Color.Transparent;
362 string priceUpColorString = dataPoint[CustomPropertyName.PriceUpColor];
363 if(priceUpColorString == null)
365 priceUpColorString = series[CustomPropertyName.PriceUpColor];
367 if(priceUpColorString != null)
371 ColorConverter colorConverter = new ColorConverter();
372 priceUpColor = (Color)colorConverter.ConvertFromString(null, CultureInfo.InvariantCulture, priceUpColorString);
376 throw (new InvalidOperationException(SR.ExceptionThreeLineBreakUpBrickColorInvalid));
380 // Check if close value exceeds last brick position by box size
381 if(dataPoint.YValues[yValueIndex] > prevHigh)
385 else if(dataPoint.YValues[yValueIndex] < prevLow)
394 // Process up/down direction
397 // Check if direction is same as previous
398 if(prevDirection == direction)
400 ++sameDirectionLines;
404 // If number of lines darwn in same direction is more or equal
405 // to number of lines in the break, the price must extend the
406 // high or low price of the lines in the whole break.
407 if(sameDirectionLines >= linesInBreak)
411 // Calculate high value for the last N lines
412 double lineBreakHigh = double.MinValue;
413 for(int index = 0; index < highLowHistory.Count; index += 2)
415 if(((double)highLowHistory[index]) > lineBreakHigh)
417 lineBreakHigh = ((double)highLowHistory[index]);
421 // If point value is less - ignore it
422 if(dataPoint.YValues[yValueIndex] <= lineBreakHigh)
427 else if(direction == -1)
429 // Calculate low value for the last N lines
430 double lineBreakLow = double.MaxValue;
431 for(int index = 1; index < highLowHistory.Count; index += 2)
433 if(((double)highLowHistory[index]) < lineBreakLow)
435 lineBreakLow = ((double)highLowHistory[index]);
439 // If point value is more - ignore it
440 if(dataPoint.YValues[yValueIndex] >= lineBreakLow)
449 sameDirectionLines = 1;
456 DataPoint newDataPoint = (DataPoint)dataPoint.Clone();
457 newDataPoint["OriginalPointIndex"] = pointIndex.ToString(CultureInfo.InvariantCulture);
458 newDataPoint.series = series;
459 newDataPoint.YValues = new double[2];
460 newDataPoint.XValue = dataPoint.XValue;
461 newDataPoint.Tag = dataPoint;
464 newDataPoint.YValues[1] = prevHigh;
465 newDataPoint.YValues[0] = dataPoint.YValues[yValueIndex];
467 prevHigh = dataPoint.YValues[yValueIndex];
469 // Set ThreeLineBreak up brick appearance
470 newDataPoint.Color = priceUpColor;
471 if(newDataPoint.BorderWidth < 1)
473 newDataPoint.BorderWidth = 1;
475 if(newDataPoint.BorderDashStyle == ChartDashStyle.NotSet)
477 newDataPoint.BorderDashStyle = ChartDashStyle.Solid;
479 if( (newDataPoint.BorderColor == Color.Empty || newDataPoint.BorderColor == Color.Transparent) &&
480 (newDataPoint.Color == Color.Empty || newDataPoint.Color == Color.Transparent) )
482 newDataPoint.BorderColor = series.Color;
487 newDataPoint.YValues[1] = prevLow;
488 newDataPoint.YValues[0] = dataPoint.YValues[yValueIndex];
490 prevLow = dataPoint.YValues[yValueIndex];
493 // Add ThreeLineBreak brick to the range column series
494 series.Points.Add(newDataPoint);
496 // Remember high/low values of drawn line
497 highLowHistory.Add(prevHigh);
498 highLowHistory.Add(prevLow);
500 // Do not store all values in array only number of break lines
501 if(highLowHistory.Count > linesInBreak * 2)
503 // Remove two items at a time (high & low)
504 highLowHistory.RemoveAt(0);
505 highLowHistory.RemoveAt(0);
510 // Remember last direction
513 prevDirection = direction;
520 #endregion // Methods
522 #region Painting and Selection methods
527 /// <param name="graph">The Chart Graphics object.</param>
528 /// <param name="common">The Common elements object.</param>
529 /// <param name="area">Chart area for this chart.</param>
530 /// <param name="seriesToDraw">Chart series to draw.</param>
531 virtual public void Paint( ChartGraphics graph, CommonElements common, ChartArea area, Series seriesToDraw )
533 // Three Line Break series is never drawn directly. It is replaced with the range column chart.
534 // See PrepareData method.
538 #region IChartType interface implementation
543 virtual public string Name { get{ return ChartTypeNames.ThreeLineBreak;}}
546 /// True if chart type is stacked
548 virtual public bool Stacked { get{ return false;}}
552 /// True if stacked chart type supports groups
554 virtual public bool SupportStackedGroups { get { return false; } }
558 /// True if stacked chart type should draw separately positive and
559 /// negative data points ( Bar and column Stacked types ).
561 public bool StackSign { get{ return false;}}
564 /// True if chart type supports axeses
566 virtual public bool RequireAxes { get{ return true;} }
569 /// Chart type with two y values used for scale ( bubble chart type )
571 public bool SecondYScale{ get{ return false;} }
574 /// True if chart type requires circular chart area.
576 public bool CircularChartArea { get{ return false;} }
579 /// True if chart type supports logarithmic axes
581 virtual public bool SupportLogarithmicAxes { get{ return true;} }
584 /// True if chart type requires to switch the value (Y) axes position
586 virtual public bool SwitchValueAxes { get{ return false;} }
589 /// True if chart series can be placed side-by-side.
591 public bool SideBySideSeries { get{ return false;} }
594 /// True if each data point of a chart must be represented in the legend
596 virtual public bool DataPointsInLegend { get{ return false;} }
599 /// If the crossing value is auto Crossing value should be
600 /// automatically set to zero for some chart
601 /// types (Bar, column, area etc.)
603 virtual public bool ZeroCrossing { get{ return false;} }
606 /// True if palette colors should be applied for each data paoint.
607 /// Otherwise the color is applied to the series.
609 virtual public bool ApplyPaletteColorsToPoints { get { return false; } }
612 /// Indicates that extra Y values are connected to the scale of the Y axis
614 virtual public bool ExtraYValuesConnectedToYAxis{ get { return true; } }
617 /// Indicates that it's a hundredred percent chart.
618 /// Axis scale from 0 to 100 percent should be used.
620 virtual public bool HundredPercent{ get{return false;} }
623 /// Indicates that it's a hundredred percent chart.
624 /// Axis scale from 0 to 100 percent should be used.
626 virtual public bool HundredPercentSupportNegative{ get{return false;} }
629 /// How to draw series/points in legend:
630 /// Filled rectangle, Line or Marker
632 /// <param name="series">Legend item series.</param>
633 /// <returns>Legend item style.</returns>
634 virtual public LegendImageStyle GetLegendImageStyle(Series series)
636 return LegendImageStyle.Rectangle;
640 /// Number of supported Y value(s) per point
642 virtual public int YValuesPerPoint { get { return 1; } }
645 /// Gets chart type image.
647 /// <param name="registry">Chart types registry object.</param>
648 /// <returns>Chart type image.</returns>
649 virtual public System.Drawing.Image GetImage(ChartTypeRegistry registry)
651 return (System.Drawing.Image)registry.ResourceManager.GetObject(this.Name + "ChartType");
655 #region Y values related methods
658 /// Helper function, which returns the Y value of the point.
660 /// <param name="common">Chart common elements.</param>
661 /// <param name="area">Chart area the series belongs to.</param>
662 /// <param name="series">Sereis of the point.</param>
663 /// <param name="point">Point object.</param>
664 /// <param name="pointIndex">Index of the point.</param>
665 /// <param name="yValueIndex">Index of the Y value to get.</param>
666 /// <returns>Y value of the point.</returns>
667 virtual public double GetYValue(
668 CommonElements common,
675 return point.YValues[yValueIndex];
680 #region SmartLabelStyle methods
683 /// Adds markers position to the list. Used to check SmartLabelStyle overlapping.
685 /// <param name="common">Common chart elements.</param>
686 /// <param name="area">Chart area.</param>
687 /// <param name="series">Series values to be used.</param>
688 /// <param name="list">List to add to.</param>
689 public void AddSmartLabelMarkerPositions(CommonElements common, ChartArea area, Series series, ArrayList list)
695 #region IDisposable interface implementation
697 /// Releases unmanaged and - optionally - managed resources
699 /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
700 protected virtual void Dispose(bool disposing)
702 //Nothing to dispose at the base class.
706 /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
708 public void Dispose()
711 GC.SuppressFinalize(this);