Updates referencesource to .NET 4.7
[mono.git] / mcs / class / referencesource / System.Web.DataVisualization / Common / ChartTypes / KagiChart.cs
1 //-------------------------------------------------------------
2 // <copyright company=\92Microsoft Corporation\92>
3 //   Copyright © Microsoft Corporation. All Rights Reserved.
4 // </copyright>
5 //-------------------------------------------------------------
6 // @owner=alexgor, deliant
7 //=================================================================
8 //  File:               KagiChart.cs
9 //
10 //  Namespace:  DataVisualization.Charting.ChartTypes
11 //
12 //      Classes:        Provides 2D and 3D drawing and hit testing of the 
13 //              Kagi chart.
14 //
15 //  Purpose:    
16 //      
17 //      Kagi Chart Overview:
18 //      --------------------
19 //
20 //      Kagi charts are believed to have been created around the time 
21 //      that the Japanese stock market began trading in the 1870's. Kagi 
22 //      charts display a series of connecting vertical lines where the 
23 //      thickness and direction of the lines are dependent on the action 
24 //      of the price value. These charts ignore the passage of time, but 
25 //      can be used to illustrate the forces of supply and demand on a 
26 //      security.
27 //      
28 //      When working with this type of chart, the following should be 
29 //      taken into account:
30 //      
31 //      - The X values of data points are automatically indexed.  
32 //      
33 //      - There is a formula applied to the original data before plotting, 
34 //      which changes the number of points and their X/Y values. 
35 //      
36 //      - Due to the data being recalculated we do not recommend setting 
37 //      the minimum and/or maximum values for the X axis, since it cannot 
38 //      be determined how many data points will actually get plotted. 
39 //      However, if the axis' maximum or minimum is set then the Maximum 
40 //      or Minimum properties should use data point index values. 
41 //      
42 //      - Data point anchoring, used for annotations, is not supported 
43 //      with this type of chart.
44 //      
45 //      Reviewed:       AG - Microsoft 6, 2007
46 //
47 //===================================================================
48
49 #region Used namespaces
50
51 using System;
52 using System.Resources;
53 using System.Reflection;
54 using System.Collections;
55 using System.Drawing;
56 using System.Drawing.Drawing2D;
57 using System.ComponentModel.Design;
58 using System.Globalization;
59
60 #if Microsoft_CONTROL
61         using System.Windows.Forms.DataVisualization.Charting;
62         using System.Windows.Forms.DataVisualization.Charting.Data;
63         using System.Windows.Forms.DataVisualization.Charting.ChartTypes;
64         using System.Windows.Forms.DataVisualization.Charting.Utilities;
65         using System.Windows.Forms.DataVisualization.Charting.Borders3D;
66
67 #else
68 using System.Web.UI.DataVisualization.Charting;
69
70 using System.Web.UI.DataVisualization.Charting.ChartTypes;
71 using System.Web.UI.DataVisualization.Charting.Data;
72 using System.Web.UI.DataVisualization.Charting.Utilities;
73 #endif
74
75 #endregion
76
77 #if Microsoft_CONTROL
78         namespace System.Windows.Forms.DataVisualization.Charting.ChartTypes
79 #else
80 namespace System.Web.UI.DataVisualization.Charting.ChartTypes
81 #endif
82 {
83         /// <summary>
84     /// KagiChart class provides 2D and 3D drawing and hit testing of
85     /// the Kagi chart.
86     /// </summary>
87         internal class KagiChart : StepLineChart
88         {
89                 #region Fields
90
91                 // Color used to draw up direction lines
92                 internal        Color   kagiUpColor = Color.Empty;
93
94                 // Current properties used for kagi line (1 up; -1 down; 0 none)
95                 internal        int             currentKagiDirection = 0;
96
97                 #endregion // Fields
98
99                 #region Methods
100
101                 /// <summary>
102                 /// Prepares Kagi chart type for rendering.
103                 /// </summary>
104                 /// <param name="series">Series to be prepared.</param>
105                 internal static void PrepareData(Series series)
106                 {
107                         // Check series chart type
108             if (String.Compare(series.ChartTypeName, ChartTypeNames.Kagi, StringComparison.OrdinalIgnoreCase) != 0 || !series.IsVisible())
109                         {
110                                 return;
111                         }
112
113                         // Get reference to the chart control
114                         Chart   chart = series.Chart;
115                         if(chart == null)
116                         {
117                                 throw(new InvalidOperationException(SR.ExceptionKagiNullReference));
118                         }
119
120             // Kagi chart may not be combined with any other chart types
121             ChartArea   area = chart.ChartAreas[series.ChartArea];
122             foreach (Series currentSeries in chart.Series)
123             {
124                 if (currentSeries.IsVisible() && currentSeries != series && area == chart.ChartAreas[currentSeries.ChartArea])
125                 {
126                     throw (new InvalidOperationException(SR.ExceptionKagiCanNotCombine));
127                 }
128             }
129
130                         // Create a temp series which will hold original series data points
131                         string tempSeriesName = "KAGI_ORIGINAL_DATA_" + series.Name;
132                         if (chart.Series.IndexOf(tempSeriesName) != -1)
133                         {
134                                 return; // the temp series has already been added
135                         }
136                         Series seriesOriginalData = new Series(tempSeriesName, series.YValuesPerPoint);
137                         seriesOriginalData.Enabled = false;
138                         seriesOriginalData.IsVisibleInLegend = false;
139                         chart.Series.Add(seriesOriginalData);
140                         foreach(DataPoint dp in series.Points)
141                         {
142                                 seriesOriginalData.Points.Add(dp);
143                         }
144                         series.Points.Clear();
145                         if(series.IsCustomPropertySet("TempDesignData"))
146                         {
147                                 seriesOriginalData["TempDesignData"] = "true";
148                         }
149
150
151                         // Remember prev. series parameters
152             series["OldXValueIndexed"] = series.IsXValueIndexed.ToString(CultureInfo.InvariantCulture);
153             series["OldYValuesPerPoint"] = series.YValuesPerPoint.ToString(CultureInfo.InvariantCulture);
154                         series.IsXValueIndexed = true;
155
156                         // Calculate date-time interval for indexed series
157                         if(series.ChartArea.Length > 0 &&
158                                 series.IsXValueDateTime())
159                         {
160                                 // Get X axis connected to the series
161                                 Axis            xAxis = area.GetAxis(AxisName.X, series.XAxisType, series.XSubAxisName);
162
163                                 // Change interval for auto-calculated interval only
164                                 if(xAxis.Interval == 0 && xAxis.IntervalType == DateTimeIntervalType.Auto)
165                                 {
166                                         // Check if original data has X values set to date-time values and
167                                         // calculate min/max X values.
168                                         bool    nonZeroXValues = false;
169                                         double  minX = double.MaxValue;
170                                         double  maxX = double.MinValue;
171                                         foreach(DataPoint dp in seriesOriginalData.Points)
172                                         {
173                                                 if(!dp.IsEmpty)
174                                                 {
175                                                         if(dp.XValue != 0.0)
176                                                         {
177                                                                 nonZeroXValues = true;
178                                                         }
179                                                         if(dp.XValue > maxX)
180                                                         {
181                                                                 maxX = dp.XValue;
182                                                         }
183                                                         if(dp.XValue < minX)
184                                                         {
185                                                                 minX = dp.XValue;
186                                                         }
187                                                 }
188                                         }
189
190                                         if(nonZeroXValues)
191                                         {
192                                                 // Save flag that axis interval is automatic
193                                                 series["OldAutomaticXAxisInterval"] = "true";
194
195                                                 // Calculate and set axis date-time interval
196                                                 DateTimeIntervalType    intervalType = DateTimeIntervalType.Auto;
197                                                 xAxis.interval = xAxis.CalcInterval(minX, maxX, true, out intervalType, series.XValueType);
198                                                 xAxis.intervalType = intervalType;
199                                         }
200                                 }
201                         }
202
203                         // Calculate Kagi bricks data points values
204                         FillKagiData(series, seriesOriginalData);
205                 }
206
207                 /// <summary>
208                 /// Remove any changes done while preparing Kagi chart type for rendering.
209                 /// </summary>
210                 /// <param name="series">Series to be un-prepared.</param>
211                 /// <returns>True if series was removed from collection.</returns>
212                 internal static bool UnPrepareData(Series series)
213                 {
214                         if(series.Name.StartsWith("KAGI_ORIGINAL_DATA_", StringComparison.Ordinal))
215                         {
216                                 // Get reference to the chart control
217                                 Chart   chart = series.Chart;
218                                 if(chart == null)
219                                 {
220                     throw (new InvalidOperationException(SR.ExceptionKagiNullReference));
221                                 }
222
223                                 // Get original Kagi series
224                                 Series  kagiSeries = chart.Series[series.Name.Substring(19)];
225                 Series.MovePositionMarkers(kagiSeries, series);
226                 // Copy data back to original Kagi series
227                                 kagiSeries.Points.Clear();
228                                 if(!series.IsCustomPropertySet("TempDesignData"))
229                                 {
230                                         foreach(DataPoint dp in series.Points)
231                                         {
232                                                 kagiSeries.Points.Add(dp);
233                                         }
234                                 }
235
236                                 // Restore series properties
237                 bool xValIndexed;
238                 bool parseSucceed = bool.TryParse(kagiSeries["OldXValueIndexed"], out xValIndexed);
239                 kagiSeries.IsXValueIndexed = parseSucceed && xValIndexed;
240
241                 int yValPerPoint;
242                 parseSucceed = int.TryParse(kagiSeries["OldYValuesPerPoint"], NumberStyles.Any, CultureInfo.InvariantCulture, out yValPerPoint);
243                 if (parseSucceed)
244                 {
245                     kagiSeries.YValuesPerPoint = yValPerPoint;
246                 }
247
248                                 kagiSeries.DeleteCustomProperty("OldXValueIndexed");
249                                 kagiSeries.DeleteCustomProperty("OldYValuesPerPoint");
250
251                                 series["OldAutomaticXAxisInterval"] = "true";
252                                 if(kagiSeries.IsCustomPropertySet("OldAutomaticXAxisInterval"))
253                                 {
254                                         kagiSeries.DeleteCustomProperty("OldAutomaticXAxisInterval");
255
256                                         // Reset automatic interval for X axis
257                                         if(kagiSeries.ChartArea.Length > 0)
258                                         {
259                                                 // Get X axis connected to the series
260                                                 ChartArea       area = chart.ChartAreas[kagiSeries.ChartArea];
261                                                 Axis            xAxis = area.GetAxis(AxisName.X, kagiSeries.XAxisType, kagiSeries.XSubAxisName);
262
263                                                 xAxis.interval = 0.0;
264                                                 xAxis.intervalType = DateTimeIntervalType.Auto;
265                                         }
266                                 }
267
268                                 // Remove series from the collection
269                                 chart.Series.Remove(series);
270                                 return true;
271                         }
272
273                         return false;
274                 }
275
276                 /// <summary>
277                 /// Gets reversal amount of the kagi chart.
278                 /// </summary>
279                 /// <param name="series">Step line chart series used to dispaly the kagi chart.</param>
280                 /// <param name="percentOfPrice">Returns reversal amount in percentage.</param>
281                 private static double GetReversalAmount(Series series, out double percentOfPrice)
282                 {
283                         // Check "ReversalAmount" custom attribute
284                         double  reversalAmount = 1.0;
285                         percentOfPrice = 3.0;
286             if (series.IsCustomPropertySet(CustomPropertyName.ReversalAmount))
287             {
288                 string attrValue = series[CustomPropertyName.ReversalAmount].Trim();
289                 bool usePercentage = attrValue.EndsWith("%", StringComparison.Ordinal);
290                 if (usePercentage)
291                 {
292                     attrValue = attrValue.Substring(0, attrValue.Length - 1);
293                 }
294
295                 if (usePercentage)
296                 {
297                     double percent;
298                     bool parseSucceed = double.TryParse(attrValue, NumberStyles.Any, CultureInfo.InvariantCulture, out percent);
299                     if (parseSucceed)
300                     {
301                         percentOfPrice = percent;
302                     }
303                     else
304                     {
305                         throw (new InvalidOperationException(SR.ExceptionKagiAttributeFormatInvalid("ReversalAmount")));
306                     }
307                 }
308                 else
309                 {
310                     double amount;
311                     bool parseSucceed = double.TryParse(attrValue, NumberStyles.Any, CultureInfo.InvariantCulture, out amount);
312                     if (parseSucceed)
313                     {
314                         reversalAmount = amount;
315                         percentOfPrice = 0.0;
316                     }
317                     else
318                     {
319                         throw (new InvalidOperationException(SR.ExceptionKagiAttributeFormatInvalid("ReversalAmount")));
320                     }
321                 }
322
323             }
324
325                         return reversalAmount;
326                 }
327
328
329                 /// <summary>
330                 /// Fills step line series with data to draw the Kagi chart.
331                 /// </summary>
332                 /// <param name="series">Step line chart series used to dispaly the Kagi chart.</param>
333                 /// <param name="originalData">Series with original data.</param>
334                 private static void FillKagiData(Series series, Series originalData)
335                 {
336                         // Get index of the Y values used
337                         int     yValueIndex = 0;
338             if (series.IsCustomPropertySet(CustomPropertyName.UsedYValue))
339             {
340                 int yi;
341                 bool parseSucceed = int.TryParse(series[CustomPropertyName.UsedYValue], NumberStyles.Any, CultureInfo.InvariantCulture, out yi);
342
343                 if (parseSucceed)
344                 {
345                     yValueIndex = yi;
346                 }
347                 else
348                 {
349                     throw (new InvalidOperationException(SR.ExceptionKagiAttributeFormatInvalid("UsedYValue")));
350                 }
351
352                 if (yValueIndex >= series.YValuesPerPoint)
353                 {
354                     throw (new InvalidOperationException(SR.ExceptionKagiAttributeOutOfRange("UsedYValue")));
355                 }
356             }
357
358                         // Calculate reversal amount
359                         double  reversalAmountPercentage = 0.0;
360                         double  reversalAmount = GetReversalAmount(series, out reversalAmountPercentage);
361
362                         // Fill points
363                         double  prevClose = double.NaN;
364                         int             prevDirection = 0;      // 1 up; -1 down; 0 none
365                         int             pointIndex = 0;
366                         foreach(DataPoint dataPoint in originalData.Points)
367                         {
368                                 // Check if previus values exists
369                                 if(double.IsNaN(prevClose))
370                                 {
371                                         prevClose = dataPoint.YValues[yValueIndex];
372
373                                         // Add first point
374                                         DataPoint newDataPoint = (DataPoint)dataPoint.Clone();
375                     newDataPoint["OriginalPointIndex"] = pointIndex.ToString(CultureInfo.InvariantCulture);
376                                         newDataPoint.series = series;
377                                         newDataPoint.XValue = dataPoint.XValue;
378                                         newDataPoint.YValues[0] = dataPoint.YValues[yValueIndex];
379                     newDataPoint.Tag = dataPoint;
380                                         series.Points.Add(newDataPoint);
381                                         ++pointIndex;
382                                         continue;
383                                 }
384
385                                 // Calculate reversal amount as percentage of previous price
386                                 if(reversalAmountPercentage != 0.0)
387                                 {
388                                         reversalAmount = (prevClose / 100.0) * reversalAmountPercentage;
389                                 }
390
391                                 // Check direction of the price change
392                                 int direction = 0;
393                                 if(dataPoint.YValues[yValueIndex] > prevClose)
394                                 {
395                                         direction = 1;
396                                 }
397                                 else if(dataPoint.YValues[yValueIndex] < prevClose)
398                                 {
399                                         direction = -1;
400                                 }
401                                 else
402                                 {
403                                         direction = 0;
404                                 }
405
406                                 // Check if value was changed - otherwise do nothing
407                                 if(direction != 0)
408                                 {
409                                         // Extend line in same direction
410                                         if(direction == prevDirection)
411                                         {
412                                                 series.Points[series.Points.Count - 1].YValues[0] = 
413                                                         dataPoint.YValues[yValueIndex];
414                                                 series.Points[series.Points.Count - 1]["OriginalPointIndex"] = pointIndex.ToString(CultureInfo.InvariantCulture);
415                         series.Points[series.Points.Count - 1].Tag = dataPoint;
416                                         }
417                                         else if( Math.Abs(dataPoint.YValues[yValueIndex] - prevClose) < reversalAmount)
418                                         {
419                                                 // Ignore opposite direction change if value is less than reversal amount
420                                                 ++pointIndex;
421                                                 continue;
422                                         }
423                                         else
424                                         {
425                                                 // Opposite direction by more than reversal amount
426                                                 DataPoint newDataPoint = (DataPoint)dataPoint.Clone();
427                         newDataPoint["OriginalPointIndex"] = pointIndex.ToString(CultureInfo.InvariantCulture);
428                                                 newDataPoint.series = series;
429                                                 newDataPoint.XValue = dataPoint.XValue;
430                                                 newDataPoint.YValues[0] = dataPoint.YValues[yValueIndex];
431                         newDataPoint.Tag = dataPoint;
432                         // Add Kagi to the range step line series
433                                                 series.Points.Add(newDataPoint);
434                                         }
435
436                                         // Save previous close value and direction
437                                         prevClose = dataPoint.YValues[yValueIndex];
438                                         prevDirection = direction;
439                                 }
440                                 ++pointIndex;
441                         }
442                 }
443
444                 #endregion // Methods
445                 
446                 #region Line drawing and selecting methods
447
448                 /// <summary>
449                 /// Draw chart line using horisontal and vertical lines.
450                 /// </summary>
451                 /// <param name="graph">Graphics object.</param>
452                 /// <param name="common">The Common elements object</param>
453                 /// <param name="point">Point to draw the line for.</param>
454                 /// <param name="series">Point series.</param>
455                 /// <param name="points">Array of points coordinates.</param>
456                 /// <param name="pointIndex">Index of point to draw.</param>
457                 /// <param name="tension">Line tension</param>
458                 override protected void DrawLine(
459                         ChartGraphics graph,  
460                         CommonElements common, 
461                         DataPoint point, 
462                         Series series, 
463                         PointF[] points, 
464                         int pointIndex, 
465                         float tension)
466                 {
467                         // Start drawing from the second point
468                         if(pointIndex <= 0)
469                         {
470                                 return;
471                         }
472                         
473                         if(currentKagiDirection == 0)
474                         {
475                                 // Get up price color
476                                 this.kagiUpColor = ChartGraphics.GetGradientColor(series.Color, Color.Black, 0.5);
477                                 string  priceUpColorString = series[CustomPropertyName.PriceUpColor];
478                                 ColorConverter colorConverter = new ColorConverter();
479                                 if(priceUpColorString != null)
480                                 {
481                                         try
482                                         {
483                                                 this.kagiUpColor = (Color)colorConverter.ConvertFromString(null, CultureInfo.InvariantCulture, priceUpColorString);
484                                         }
485                                         catch
486                                         {
487                                                 throw(new InvalidOperationException(SR.ExceptionKagiAttributeFormatInvalid("Up Brick color")));
488                                         }
489                                 }
490
491                                 // Check direction of first line (up or down)
492                                 currentKagiDirection = (points[pointIndex - 1].Y > points[pointIndex].Y) ? 
493                                         1 : -1;
494                         }
495
496                         // Set up movement colors and width
497                         Color   lineColor = (currentKagiDirection == 1) ? this.kagiUpColor : point.Color;
498
499                         // Prepare coordinate to draw 2 or 3 segments of the step line
500                         PointF  point1 = points[pointIndex - 1];
501                         PointF  point2 = new PointF(points[pointIndex].X, points[pointIndex - 1].Y);
502                         PointF  point3 = points[pointIndex];
503                         PointF  point4 = PointF.Empty;
504                         
505                         // Check if vertical line should be draw as to segments of different color
506                         if(pointIndex >= 2)
507                         {
508                                 // Check current direction
509                                 int direction = (points[pointIndex - 1].Y > points[pointIndex].Y) ? 
510                                         1 : -1;
511
512                                 // Proceed only when direction is changed
513                                 if(direction != currentKagiDirection)
514                                 {
515                                         // Find prev line low & high
516                                         PointF  prevPoint = points[pointIndex - 2];
517                                         bool    twoVertSegments = false;
518                                         if(point1.Y > prevPoint.Y &&
519                                                 point1.Y > point3.Y &&
520                                                 prevPoint.Y > point3.Y)
521                                         {
522                                                 twoVertSegments = true;
523                                         }
524                                         else if(point1.Y < prevPoint.Y &&
525                                                 point1.Y < point3.Y &&
526                                                 prevPoint.Y < point3.Y)
527                                         {
528                                                 twoVertSegments = true;
529                                         }
530
531                                         if(twoVertSegments)
532                                         {
533                                                 // Calculate point where vertical line is split
534                                                 point4.Y = prevPoint.Y;
535                                                 point4.X = point2.X;
536                                         }
537                                 }
538                         }
539
540                         // Round line point values
541                         point1.X = (float)Math.Round(point1.X);
542                         point1.Y = (float)Math.Round(point1.Y);
543                         point2.X = (float)Math.Round(point2.X);
544                         point2.Y = (float)Math.Round(point2.Y);
545                         point3.X = (float)Math.Round(point3.X);
546                         point3.Y = (float)Math.Round(point3.Y);
547                         if(!point4.IsEmpty)
548                         {
549                                 point4.X = (float)Math.Round(point4.X);
550                                 point4.Y = (float)Math.Round(point4.Y);
551                         }
552
553
554                         // Draw horizontal segment
555                         graph.DrawLineRel( lineColor, point.BorderWidth, point.BorderDashStyle, 
556                                 graph.GetRelativePoint(point1), graph.GetRelativePoint(point2), 
557                                 series.ShadowColor, series.ShadowOffset );
558                         
559                         // Check if vertical segment should be drawn as one ore two segments
560                         if(point4.IsEmpty)
561                         {
562                                 // Draw 1 vertical segment
563                                 graph.DrawLineRel( lineColor, point.BorderWidth, point.BorderDashStyle, 
564                                         graph.GetRelativePoint(point2), graph.GetRelativePoint(point3), 
565                                         series.ShadowColor, series.ShadowOffset );
566                         }
567                         else
568                         {
569                                 // Draw firts part of vertical segment
570                                 graph.DrawLineRel( lineColor, point.BorderWidth, point.BorderDashStyle, 
571                                         graph.GetRelativePoint(point2), graph.GetRelativePoint(point4), 
572                                         series.ShadowColor, series.ShadowOffset );
573
574                                 // Change direction 
575                                 currentKagiDirection = (currentKagiDirection == 1) ? -1 : 1;
576
577                                 // Set color
578                                 lineColor = (currentKagiDirection == 1) ? this.kagiUpColor : point.Color;
579
580                                 // Draw second part of vertical segment
581                                 graph.DrawLineRel( lineColor, point.BorderWidth, point.BorderDashStyle, 
582                                         graph.GetRelativePoint(point4), graph.GetRelativePoint(point3), 
583                                         series.ShadowColor, series.ShadowOffset );
584                         }
585
586                         if( common.ProcessModeRegions )
587                         {
588                                 // Create grapics path object dor the curve
589                 using (GraphicsPath path = new GraphicsPath())
590                 {
591                     try
592                     {
593                         path.AddLine(point1, point2);
594                         path.AddLine(point2, point3);
595                         path.Widen(new Pen(point.Color, point.BorderWidth + 2));
596                     }
597                     catch (OutOfMemoryException)
598                     {
599                         // GraphicsPath.Widen incorrectly throws OutOfMemoryException
600                         // catching here and reacting by not widening
601                     }
602                     catch (ArgumentException)
603                     {
604                     }
605
606                     // Allocate array of floats
607                     PointF pointNew = PointF.Empty;
608                     float[] coord = new float[path.PointCount * 2];
609                     PointF[] pathPoints = path.PathPoints;
610                     for (int i = 0; i < path.PointCount; i++)
611                     {
612                         pointNew = graph.GetRelativePoint(pathPoints[i]);
613                         coord[2 * i] = pointNew.X;
614                         coord[2 * i + 1] = pointNew.Y;
615                     }
616
617                     common.HotRegionsList.AddHotRegion(
618                         path,
619                         false,
620                         coord,
621                         point,
622                         series.Name,
623                         pointIndex);
624                 }
625                         }
626
627                 }
628                 
629                 /// <summary>
630                 /// Fills a PointF array of data points absolute pixel positions.
631                 /// </summary>
632                 /// <param name="graph">Graphics object.</param>
633                 /// <param name="series">Point series.</param>
634                 /// <param name="indexedSeries">Indicate that point index should be used as X value.</param>
635                 /// <returns>Array of data points position.</returns>
636                 override protected PointF[] GetPointsPosition(ChartGraphics graph, Series series, bool indexedSeries)
637                 {
638                         PointF[]        pointPos = new PointF[series.Points.Count];
639                         int index = 0;
640                         foreach( DataPoint point in series.Points )
641                         {
642                                 // Change Y value if line is out of plot area
643                                 double yValue = GetYValue(Common, Area, series, point, index, this.YValueIndex);
644
645                                 // Recalculates y position
646                                 double yPosition = VAxis.GetPosition( yValue );
647
648                                 // Recalculates x position
649                                 double xPosition = HAxis.GetPosition( point.XValue );
650                                 if( indexedSeries )
651                                 {
652                                         xPosition = HAxis.GetPosition( index + 1 );
653                                 }
654                                                                 
655                                 // Add point position into array
656                                 pointPos[index] = new PointF(
657                     (float)(xPosition * (graph.Common.ChartPicture.Width - 1) / 100F),
658                     (float)(yPosition * (graph.Common.ChartPicture.Height - 1) / 100F)); 
659
660                                 index++;
661                         }
662
663                         return pointPos;
664                 }
665
666                 #endregion
667
668                 #region 3D Line drawing and selection
669
670                 /// <summary>
671                 /// Draws a 3D surface connecting the two specified points in 2D space.
672                 /// Used to draw Line based charts.
673                 /// </summary>
674                 /// <param name="area">Chart area reference.</param>
675                 /// <param name="graph">Chart graphics.</param>
676                 /// <param name="matrix">Coordinates transformation matrix.</param>
677                 /// <param name="lightStyle">LightStyle style (None, Simplistic, Realistic).</param>
678                 /// <param name="prevDataPointEx">Previous data point object.</param>
679                 /// <param name="positionZ">Z position of the back side of the 3D surface.</param>
680                 /// <param name="depth">Depth of the 3D surface.</param>
681                 /// <param name="points">Array of points.</param>
682                 /// <param name="pointIndex">Index of point to draw.</param>
683                 /// <param name="pointLoopIndex">Index of points loop.</param>
684                 /// <param name="tension">Line tension.</param>
685                 /// <param name="operationType">AxisName of operation Drawing, Calculating Path or Both</param>
686                 /// <param name="topDarkening">Darkenning scale for top surface. 0 - None.</param>
687                 /// <param name="bottomDarkening">Darkenning scale for bottom surface. 0 - None.</param>
688                 /// <param name="thirdPointPosition">Position where the third point is actually located or float.NaN if same as in "firstPoint".</param>
689                 /// <param name="fourthPointPosition">Position where the fourth point is actually located or float.NaN if same as in "secondPoint".</param>
690                 /// <param name="clippedSegment">Indicates that drawn segment is 3D clipped. Only top/bottom should be drawn.</param>
691                 /// <returns>Returns elemnt shape path if operationType parameter is set to CalcElementPath, otherwise Null.</returns>
692                 protected override GraphicsPath Draw3DSurface( 
693                         ChartArea area,
694                         ChartGraphics graph, 
695                         Matrix3D matrix,
696                         LightStyle lightStyle,
697                         DataPoint3D prevDataPointEx,
698                         float positionZ, 
699                         float depth, 
700                         ArrayList points,
701                         int pointIndex, 
702                         int pointLoopIndex,
703                         float tension,
704                         DrawingOperationTypes operationType,
705                         float topDarkening,
706                         float bottomDarkening,
707                         PointF thirdPointPosition,
708                         PointF fourthPointPosition,
709                         bool clippedSegment)
710                 {
711                         // Create graphics path for selection
712                         GraphicsPath    resultPath = ((operationType & DrawingOperationTypes.CalcElementPath) == DrawingOperationTypes.CalcElementPath)
713                                 ? new GraphicsPath() : null;
714
715                         // Check if points are drawn from sides to center (do only once)
716                         if(centerPointIndex == int.MaxValue)
717                         {
718                                 centerPointIndex = GetCenterPointIndex(points);
719                         }
720
721                         //************************************************************
722                         //** Find line first & second points
723                         //************************************************************
724                         DataPoint3D     secondPoint = (DataPoint3D)points[pointIndex];
725                         int pointArrayIndex = pointIndex;
726                         DataPoint3D firstPoint = ChartGraphics.FindPointByIndex(
727                                 points, 
728                                 secondPoint.index - 1, 
729                                 (this.multiSeries) ? secondPoint : null, 
730                                 ref pointArrayIndex);
731
732                         // Fint point with line properties
733                         DataPoint3D             pointAttr = secondPoint;
734                         if(prevDataPointEx.dataPoint.IsEmpty)
735                         {
736                                 pointAttr = prevDataPointEx;
737                         }
738                         else if(firstPoint.index > secondPoint.index)
739                         {
740                                 pointAttr = firstPoint;
741                         }
742
743                         // Adjust point visual properties 
744                         Color                   color = (useBorderColor) ? pointAttr.dataPoint.BorderColor : pointAttr.dataPoint.Color;
745                         ChartDashStyle  dashStyle = pointAttr.dataPoint.BorderDashStyle;
746                         if( pointAttr.dataPoint.IsEmpty && pointAttr.dataPoint.Color == Color.Empty)
747                         {
748                                 color = Color.Gray;
749                         }
750                         if( pointAttr.dataPoint.IsEmpty && pointAttr.dataPoint.BorderDashStyle == ChartDashStyle.NotSet )
751                         {
752                                 dashStyle = ChartDashStyle.Solid;
753                         }
754
755                         // Set up kagi chart
756                         if(currentKagiDirection == 0)
757                         {
758                                 // Get up price color
759                                 this.kagiUpColor = secondPoint.dataPoint.series.Color;
760                                 string  priceUpColorString = secondPoint.dataPoint.series[CustomPropertyName.PriceUpColor];
761                                 ColorConverter colorConverter = new ColorConverter();
762                                 if(priceUpColorString != null)
763                                 {
764                                         try
765                                         {
766                                                 this.kagiUpColor = (Color)colorConverter.ConvertFromString(null, CultureInfo.InvariantCulture, priceUpColorString);
767                                         }
768                                         catch
769                                         {
770                                                 throw(new InvalidOperationException(SR.ExceptionKagiAttributeFormatInvalid("Up Brick color")));
771                                         }
772                                 }
773
774                                 // Check direction of first line (up or down)
775                                 currentKagiDirection = (firstPoint.yPosition > secondPoint.yPosition) ? 
776                                         1 : -1;
777                         }
778
779                         // Set up movement colors and width
780                         Color   lineColor = (currentKagiDirection == 1) ? this.kagiUpColor : color;
781
782                         //************************************************************
783                         //** Create "middle" point
784                         //************************************************************
785                         DataPoint3D     middlePoint = new DataPoint3D();
786                         middlePoint.xPosition = secondPoint.xPosition;
787                         middlePoint.yPosition = firstPoint.yPosition;
788
789                         // Check if reversed drawing order required
790                         bool originalDrawOrder = true;
791                         if((pointIndex + 1) < points.Count)
792                         {
793                                 DataPoint3D p = (DataPoint3D)points[pointIndex + 1];
794                                 if(p.index == firstPoint.index)
795                                 {
796                                         originalDrawOrder = false;
797                                 }
798                         }
799
800                         // Check in which order vertical & horizontal lines segments should be drawn
801                         if(centerPointIndex != int.MaxValue)
802                         {
803                                 if(pointIndex >= centerPointIndex)
804                                 {
805                                         originalDrawOrder = false;
806                                 }
807                         }
808
809         
810                         // Check if vertical line should be draw as to segments of different color
811                         DataPoint3D     vertSplitPoint = null;
812                         if(secondPoint.index >= 3) //Point3D.index is 1 based
813                         {
814                                 // Check current direction
815                                 int direction = (firstPoint.yPosition > secondPoint.yPosition) ? 
816                                         1 : -1;
817
818                                 // Proceed only when direction is changed
819                                 if(direction != currentKagiDirection)
820                                 {
821                                         // Find prev line low & high
822                                         DataPoint3D prevPoint = ChartGraphics.FindPointByIndex(
823                                                 points, 
824                                                 secondPoint.index - 2, 
825                                                 (this.multiSeries) ? secondPoint : null, 
826                                                 ref pointArrayIndex);
827
828                                         bool    twoVertSegments = false;
829                                         if(firstPoint.yPosition > prevPoint.yPosition &&
830                                                 firstPoint.yPosition > secondPoint.yPosition &&
831                                                 prevPoint.yPosition > secondPoint.yPosition)
832                                         {
833                                                 twoVertSegments = true;
834                                         }
835                                         else if(firstPoint.yPosition < prevPoint.yPosition &&
836                                                 firstPoint.yPosition < secondPoint.yPosition &&
837                                                 prevPoint.yPosition < secondPoint.yPosition)
838                                         {
839                                                 twoVertSegments = true;
840                                         }
841
842                                         if(twoVertSegments)
843                                         {
844                                                 vertSplitPoint = new DataPoint3D();
845                                                 vertSplitPoint.xPosition = secondPoint.xPosition;
846                                                 vertSplitPoint.yPosition = prevPoint.yPosition;
847                                                 vertSplitPoint.dataPoint = secondPoint.dataPoint;
848                                         }
849                                 }
850                         }
851
852                         // Draw two or three segments of the step line
853                         GraphicsPath[] resultPathLine = new GraphicsPath[3];
854                         for(int segmentIndex = 0; segmentIndex < 2; segmentIndex++)
855                         {
856                                 DataPoint3D     point1 = firstPoint, point2 = secondPoint;
857                                 LineSegmentType lineSegmentType = LineSegmentType.First;
858
859                                 if(segmentIndex == 0)
860                                 {
861                                         lineSegmentType = (originalDrawOrder) ? LineSegmentType.First : LineSegmentType.Last;
862                                         middlePoint.dataPoint = (originalDrawOrder) ? secondPoint.dataPoint : firstPoint.dataPoint;
863                                         point1 = (originalDrawOrder) ? firstPoint : middlePoint;
864                                         point2 = (originalDrawOrder) ? middlePoint : secondPoint;
865                                 }
866                                 else if(segmentIndex == 1)
867                                 {
868                                         lineSegmentType = (!originalDrawOrder) ? LineSegmentType.First : LineSegmentType.Last;
869                                         middlePoint.dataPoint = (!originalDrawOrder) ? secondPoint.dataPoint : secondPoint.dataPoint;
870                                         point1 = (!originalDrawOrder) ? firstPoint : middlePoint;
871                                         point2 = (!originalDrawOrder) ? middlePoint : secondPoint;
872                                 }
873
874                                 // Draw horizontal surface
875                                 if(lineSegmentType == LineSegmentType.First ||
876                                         vertSplitPoint == null)
877                                 {
878                                         resultPathLine[segmentIndex] = new GraphicsPath();
879                                         resultPathLine[segmentIndex] = graph.Draw3DSurface( 
880                                                 area, matrix, lightStyle, SurfaceNames.Top, positionZ, depth, lineColor, 
881                                                 pointAttr.dataPoint.BorderColor, pointAttr.dataPoint.BorderWidth, dashStyle, 
882                                                 point1, point2, 
883                                                 points, pointIndex, 0f, operationType, lineSegmentType, 
884                                                 (this.showPointLines) ? true : false, false,
885                         area.ReverseSeriesOrder,
886                                                 this.multiSeries, 0, true);
887                                 }
888                                 else
889                                 {
890                                         if(!originalDrawOrder)
891                                         {
892                                                 lineColor = (currentKagiDirection == -1) ? this.kagiUpColor : color;
893                                         }
894
895                                         // Draw verticla line as two segments
896                                         resultPathLine[segmentIndex] = new GraphicsPath();
897                                         resultPathLine[segmentIndex] = graph.Draw3DSurface( 
898                                                 area, matrix, lightStyle, SurfaceNames.Top, positionZ, depth, lineColor, 
899                                                 pointAttr.dataPoint.BorderColor, pointAttr.dataPoint.BorderWidth, dashStyle, 
900                                                 point1, vertSplitPoint, 
901                                                 points, pointIndex, 0f, operationType, LineSegmentType.Middle, 
902                                                 (this.showPointLines) ? true : false, false,
903                         area.ReverseSeriesOrder,
904                                                 this.multiSeries, 0, true);
905
906                                         // No second draw of the prev. front line required
907                                         graph.frontLinePen = null;
908
909                                         // Change direction 
910                                         currentKagiDirection = (currentKagiDirection == 1) ? -1 : 1;
911
912                                         // Set color
913                                         if(originalDrawOrder)
914                                         {
915                                                 lineColor = (currentKagiDirection == 1) ? this.kagiUpColor : color;
916                                         }
917                                         else
918                                         {
919                                                 lineColor = (currentKagiDirection == -1) ? this.kagiUpColor : color;
920                                         }
921
922                                         resultPathLine[2] = new GraphicsPath();
923                                         resultPathLine[2] = graph.Draw3DSurface( 
924                                                 area, matrix, lightStyle, SurfaceNames.Top, positionZ, depth, lineColor, 
925                                                 pointAttr.dataPoint.BorderColor, pointAttr.dataPoint.BorderWidth, dashStyle, 
926                                                 vertSplitPoint, point2, 
927                                                 points, pointIndex, 0f, operationType, lineSegmentType, 
928                                                 (this.showPointLines) ? true : false, false,
929                         area.ReverseSeriesOrder,
930                                                 this.multiSeries, 0, true);
931
932                                         if(!originalDrawOrder)
933                                         {
934                                                 lineColor = (currentKagiDirection == 1) ? this.kagiUpColor : color;
935                                         }
936
937                                 }
938
939                                 // No second draw of the prev. front line required
940                                 graph.frontLinePen = null;
941                         }
942
943                         if(resultPath != null)
944                         {
945                                 if(resultPathLine[0] != null)
946                                         resultPath.AddPath(resultPathLine[0], true);
947                                 if(resultPathLine[1] != null)
948                                         resultPath.AddPath(resultPathLine[1], true);
949                                 if(resultPathLine[2] != null)
950                                         resultPath.AddPath(resultPathLine[2], true);
951                         }
952                         return resultPath;
953                 }
954
955                 #endregion
956
957                 #region IChartType interface implementation
958
959                 /// <summary>
960                 /// Chart type name
961                 /// </summary>
962                 override public string Name                     { get{ return ChartTypeNames.Kagi;}}
963
964                 /// <summary>
965                 /// Gets chart type image.
966                 /// </summary>
967                 /// <param name="registry">Chart types registry object.</param>
968                 /// <returns>Chart type image.</returns>
969                 override public System.Drawing.Image GetImage(ChartTypeRegistry registry)
970                 {
971                         return (System.Drawing.Image)registry.ResourceManager.GetObject(this.Name + "ChartType");
972                 }
973                 #endregion
974
975                 #region Painting and selection methods
976
977                 /// <summary>
978                 /// Paint Line Chart.
979                 /// </summary>
980                 /// <param name="graph">The Chart Graphics object.</param>
981                 /// <param name="common">The Common elements object.</param>
982                 /// <param name="area">Chart area for this char.t</param>
983                 /// <param name="seriesToDraw">Chart series to draw.</param>
984                 public override void Paint( ChartGraphics graph, CommonElements common, ChartArea area, Series seriesToDraw )
985                 {       
986                         // Reset current direction
987                         this.currentKagiDirection = 0;
988
989                         // Call base class methods
990                         base.Paint(graph, common, area, seriesToDraw);
991                 }
992
993                 #endregion      // Painting and selection methods
994         }
995 }
996