1 //-------------------------------------------------------------
2 // <copyright company=
\92Microsoft Corporation
\92>
3 // Copyright © Microsoft Corporation. All Rights Reserved.
5 //-------------------------------------------------------------
6 // @owner=alexgor, deliant
7 //=================================================================
10 // Namespace: DataVisualization.Charting.ChartTypes
12 // Classes: Provides 2D and 3D drawing and hit testing of the
17 // Kagi Chart Overview:
18 // --------------------
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
28 // When working with this type of chart, the following should be
29 // taken into account:
31 // - The X values of data points are automatically indexed.
33 // - There is a formula applied to the original data before plotting,
34 // which changes the number of points and their X/Y values.
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.
42 // - Data point anchoring, used for annotations, is not supported
43 // with this type of chart.
45 // Reviewed: AG - Microsoft 6, 2007
47 //===================================================================
49 #region Used namespaces
52 using System.Resources;
53 using System.Reflection;
54 using System.Collections;
56 using System.Drawing.Drawing2D;
57 using System.ComponentModel.Design;
58 using System.Globalization;
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;
68 using System.Web.UI.DataVisualization.Charting;
70 using System.Web.UI.DataVisualization.Charting.ChartTypes;
71 using System.Web.UI.DataVisualization.Charting.Data;
72 using System.Web.UI.DataVisualization.Charting.Utilities;
78 namespace System.Windows.Forms.DataVisualization.Charting.ChartTypes
80 namespace System.Web.UI.DataVisualization.Charting.ChartTypes
84 /// KagiChart class provides 2D and 3D drawing and hit testing of
87 internal class KagiChart : StepLineChart
91 // Color used to draw up direction lines
92 internal Color kagiUpColor = Color.Empty;
94 // Current properties used for kagi line (1 up; -1 down; 0 none)
95 internal int currentKagiDirection = 0;
102 /// Prepares Kagi chart type for rendering.
104 /// <param name="series">Series to be prepared.</param>
105 internal static void PrepareData(Series series)
107 // Check series chart type
108 if (String.Compare(series.ChartTypeName, ChartTypeNames.Kagi, StringComparison.OrdinalIgnoreCase) != 0 || !series.IsVisible())
113 // Get reference to the chart control
114 Chart chart = series.Chart;
117 throw(new InvalidOperationException(SR.ExceptionKagiNullReference));
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)
124 if (currentSeries.IsVisible() && currentSeries != series && area == chart.ChartAreas[currentSeries.ChartArea])
126 throw (new InvalidOperationException(SR.ExceptionKagiCanNotCombine));
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)
134 return; // the temp series has already been added
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)
142 seriesOriginalData.Points.Add(dp);
144 series.Points.Clear();
145 if(series.IsCustomPropertySet("TempDesignData"))
147 seriesOriginalData["TempDesignData"] = "true";
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;
156 // Calculate date-time interval for indexed series
157 if(series.ChartArea.Length > 0 &&
158 series.IsXValueDateTime())
160 // Get X axis connected to the series
161 Axis xAxis = area.GetAxis(AxisName.X, series.XAxisType, series.XSubAxisName);
163 // Change interval for auto-calculated interval only
164 if(xAxis.Interval == 0 && xAxis.IntervalType == DateTimeIntervalType.Auto)
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)
177 nonZeroXValues = true;
192 // Save flag that axis interval is automatic
193 series["OldAutomaticXAxisInterval"] = "true";
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;
203 // Calculate Kagi bricks data points values
204 FillKagiData(series, seriesOriginalData);
208 /// Remove any changes done while preparing Kagi chart type for rendering.
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)
214 if(series.Name.StartsWith("KAGI_ORIGINAL_DATA_", StringComparison.Ordinal))
216 // Get reference to the chart control
217 Chart chart = series.Chart;
220 throw (new InvalidOperationException(SR.ExceptionKagiNullReference));
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"))
230 foreach(DataPoint dp in series.Points)
232 kagiSeries.Points.Add(dp);
236 // Restore series properties
238 bool parseSucceed = bool.TryParse(kagiSeries["OldXValueIndexed"], out xValIndexed);
239 kagiSeries.IsXValueIndexed = parseSucceed && xValIndexed;
242 parseSucceed = int.TryParse(kagiSeries["OldYValuesPerPoint"], NumberStyles.Any, CultureInfo.InvariantCulture, out yValPerPoint);
245 kagiSeries.YValuesPerPoint = yValPerPoint;
248 kagiSeries.DeleteCustomProperty("OldXValueIndexed");
249 kagiSeries.DeleteCustomProperty("OldYValuesPerPoint");
251 series["OldAutomaticXAxisInterval"] = "true";
252 if(kagiSeries.IsCustomPropertySet("OldAutomaticXAxisInterval"))
254 kagiSeries.DeleteCustomProperty("OldAutomaticXAxisInterval");
256 // Reset automatic interval for X axis
257 if(kagiSeries.ChartArea.Length > 0)
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);
263 xAxis.interval = 0.0;
264 xAxis.intervalType = DateTimeIntervalType.Auto;
268 // Remove series from the collection
269 chart.Series.Remove(series);
277 /// Gets reversal amount of the kagi chart.
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)
283 // Check "ReversalAmount" custom attribute
284 double reversalAmount = 1.0;
285 percentOfPrice = 3.0;
286 if (series.IsCustomPropertySet(CustomPropertyName.ReversalAmount))
288 string attrValue = series[CustomPropertyName.ReversalAmount].Trim();
289 bool usePercentage = attrValue.EndsWith("%", StringComparison.Ordinal);
292 attrValue = attrValue.Substring(0, attrValue.Length - 1);
298 bool parseSucceed = double.TryParse(attrValue, NumberStyles.Any, CultureInfo.InvariantCulture, out percent);
301 percentOfPrice = percent;
305 throw (new InvalidOperationException(SR.ExceptionKagiAttributeFormatInvalid("ReversalAmount")));
311 bool parseSucceed = double.TryParse(attrValue, NumberStyles.Any, CultureInfo.InvariantCulture, out amount);
314 reversalAmount = amount;
315 percentOfPrice = 0.0;
319 throw (new InvalidOperationException(SR.ExceptionKagiAttributeFormatInvalid("ReversalAmount")));
325 return reversalAmount;
330 /// Fills step line series with data to draw the Kagi chart.
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)
336 // Get index of the Y values used
338 if (series.IsCustomPropertySet(CustomPropertyName.UsedYValue))
341 bool parseSucceed = int.TryParse(series[CustomPropertyName.UsedYValue], NumberStyles.Any, CultureInfo.InvariantCulture, out yi);
349 throw (new InvalidOperationException(SR.ExceptionKagiAttributeFormatInvalid("UsedYValue")));
352 if (yValueIndex >= series.YValuesPerPoint)
354 throw (new InvalidOperationException(SR.ExceptionKagiAttributeOutOfRange("UsedYValue")));
358 // Calculate reversal amount
359 double reversalAmountPercentage = 0.0;
360 double reversalAmount = GetReversalAmount(series, out reversalAmountPercentage);
363 double prevClose = double.NaN;
364 int prevDirection = 0; // 1 up; -1 down; 0 none
366 foreach(DataPoint dataPoint in originalData.Points)
368 // Check if previus values exists
369 if(double.IsNaN(prevClose))
371 prevClose = dataPoint.YValues[yValueIndex];
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);
385 // Calculate reversal amount as percentage of previous price
386 if(reversalAmountPercentage != 0.0)
388 reversalAmount = (prevClose / 100.0) * reversalAmountPercentage;
391 // Check direction of the price change
393 if(dataPoint.YValues[yValueIndex] > prevClose)
397 else if(dataPoint.YValues[yValueIndex] < prevClose)
406 // Check if value was changed - otherwise do nothing
409 // Extend line in same direction
410 if(direction == prevDirection)
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;
417 else if( Math.Abs(dataPoint.YValues[yValueIndex] - prevClose) < reversalAmount)
419 // Ignore opposite direction change if value is less than reversal amount
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);
436 // Save previous close value and direction
437 prevClose = dataPoint.YValues[yValueIndex];
438 prevDirection = direction;
444 #endregion // Methods
446 #region Line drawing and selecting methods
449 /// Draw chart line using horisontal and vertical lines.
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(
460 CommonElements common,
467 // Start drawing from the second point
473 if(currentKagiDirection == 0)
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)
483 this.kagiUpColor = (Color)colorConverter.ConvertFromString(null, CultureInfo.InvariantCulture, priceUpColorString);
487 throw(new InvalidOperationException(SR.ExceptionKagiAttributeFormatInvalid("Up Brick color")));
491 // Check direction of first line (up or down)
492 currentKagiDirection = (points[pointIndex - 1].Y > points[pointIndex].Y) ?
496 // Set up movement colors and width
497 Color lineColor = (currentKagiDirection == 1) ? this.kagiUpColor : point.Color;
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;
505 // Check if vertical line should be draw as to segments of different color
508 // Check current direction
509 int direction = (points[pointIndex - 1].Y > points[pointIndex].Y) ?
512 // Proceed only when direction is changed
513 if(direction != currentKagiDirection)
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)
522 twoVertSegments = true;
524 else if(point1.Y < prevPoint.Y &&
525 point1.Y < point3.Y &&
526 prevPoint.Y < point3.Y)
528 twoVertSegments = true;
533 // Calculate point where vertical line is split
534 point4.Y = prevPoint.Y;
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);
549 point4.X = (float)Math.Round(point4.X);
550 point4.Y = (float)Math.Round(point4.Y);
554 // Draw horizontal segment
555 graph.DrawLineRel( lineColor, point.BorderWidth, point.BorderDashStyle,
556 graph.GetRelativePoint(point1), graph.GetRelativePoint(point2),
557 series.ShadowColor, series.ShadowOffset );
559 // Check if vertical segment should be drawn as one ore two segments
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 );
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 );
575 currentKagiDirection = (currentKagiDirection == 1) ? -1 : 1;
578 lineColor = (currentKagiDirection == 1) ? this.kagiUpColor : point.Color;
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 );
586 if( common.ProcessModeRegions )
588 // Create grapics path object dor the curve
589 using (GraphicsPath path = new GraphicsPath())
593 path.AddLine(point1, point2);
594 path.AddLine(point2, point3);
595 path.Widen(new Pen(point.Color, point.BorderWidth + 2));
597 catch (OutOfMemoryException)
599 // GraphicsPath.Widen incorrectly throws OutOfMemoryException
600 // catching here and reacting by not widening
602 catch (ArgumentException)
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++)
612 pointNew = graph.GetRelativePoint(pathPoints[i]);
613 coord[2 * i] = pointNew.X;
614 coord[2 * i + 1] = pointNew.Y;
617 common.HotRegionsList.AddHotRegion(
630 /// Fills a PointF array of data points absolute pixel positions.
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)
638 PointF[] pointPos = new PointF[series.Points.Count];
640 foreach( DataPoint point in series.Points )
642 // Change Y value if line is out of plot area
643 double yValue = GetYValue(Common, Area, series, point, index, this.YValueIndex);
645 // Recalculates y position
646 double yPosition = VAxis.GetPosition( yValue );
648 // Recalculates x position
649 double xPosition = HAxis.GetPosition( point.XValue );
652 xPosition = HAxis.GetPosition( index + 1 );
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));
668 #region 3D Line drawing and selection
671 /// Draws a 3D surface connecting the two specified points in 2D space.
672 /// Used to draw Line based charts.
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(
696 LightStyle lightStyle,
697 DataPoint3D prevDataPointEx,
704 DrawingOperationTypes operationType,
706 float bottomDarkening,
707 PointF thirdPointPosition,
708 PointF fourthPointPosition,
711 // Create graphics path for selection
712 GraphicsPath resultPath = ((operationType & DrawingOperationTypes.CalcElementPath) == DrawingOperationTypes.CalcElementPath)
713 ? new GraphicsPath() : null;
715 // Check if points are drawn from sides to center (do only once)
716 if(centerPointIndex == int.MaxValue)
718 centerPointIndex = GetCenterPointIndex(points);
721 //************************************************************
722 //** Find line first & second points
723 //************************************************************
724 DataPoint3D secondPoint = (DataPoint3D)points[pointIndex];
725 int pointArrayIndex = pointIndex;
726 DataPoint3D firstPoint = ChartGraphics.FindPointByIndex(
728 secondPoint.index - 1,
729 (this.multiSeries) ? secondPoint : null,
730 ref pointArrayIndex);
732 // Fint point with line properties
733 DataPoint3D pointAttr = secondPoint;
734 if(prevDataPointEx.dataPoint.IsEmpty)
736 pointAttr = prevDataPointEx;
738 else if(firstPoint.index > secondPoint.index)
740 pointAttr = firstPoint;
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)
750 if( pointAttr.dataPoint.IsEmpty && pointAttr.dataPoint.BorderDashStyle == ChartDashStyle.NotSet )
752 dashStyle = ChartDashStyle.Solid;
756 if(currentKagiDirection == 0)
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)
766 this.kagiUpColor = (Color)colorConverter.ConvertFromString(null, CultureInfo.InvariantCulture, priceUpColorString);
770 throw(new InvalidOperationException(SR.ExceptionKagiAttributeFormatInvalid("Up Brick color")));
774 // Check direction of first line (up or down)
775 currentKagiDirection = (firstPoint.yPosition > secondPoint.yPosition) ?
779 // Set up movement colors and width
780 Color lineColor = (currentKagiDirection == 1) ? this.kagiUpColor : color;
782 //************************************************************
783 //** Create "middle" point
784 //************************************************************
785 DataPoint3D middlePoint = new DataPoint3D();
786 middlePoint.xPosition = secondPoint.xPosition;
787 middlePoint.yPosition = firstPoint.yPosition;
789 // Check if reversed drawing order required
790 bool originalDrawOrder = true;
791 if((pointIndex + 1) < points.Count)
793 DataPoint3D p = (DataPoint3D)points[pointIndex + 1];
794 if(p.index == firstPoint.index)
796 originalDrawOrder = false;
800 // Check in which order vertical & horizontal lines segments should be drawn
801 if(centerPointIndex != int.MaxValue)
803 if(pointIndex >= centerPointIndex)
805 originalDrawOrder = false;
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
814 // Check current direction
815 int direction = (firstPoint.yPosition > secondPoint.yPosition) ?
818 // Proceed only when direction is changed
819 if(direction != currentKagiDirection)
821 // Find prev line low & high
822 DataPoint3D prevPoint = ChartGraphics.FindPointByIndex(
824 secondPoint.index - 2,
825 (this.multiSeries) ? secondPoint : null,
826 ref pointArrayIndex);
828 bool twoVertSegments = false;
829 if(firstPoint.yPosition > prevPoint.yPosition &&
830 firstPoint.yPosition > secondPoint.yPosition &&
831 prevPoint.yPosition > secondPoint.yPosition)
833 twoVertSegments = true;
835 else if(firstPoint.yPosition < prevPoint.yPosition &&
836 firstPoint.yPosition < secondPoint.yPosition &&
837 prevPoint.yPosition < secondPoint.yPosition)
839 twoVertSegments = true;
844 vertSplitPoint = new DataPoint3D();
845 vertSplitPoint.xPosition = secondPoint.xPosition;
846 vertSplitPoint.yPosition = prevPoint.yPosition;
847 vertSplitPoint.dataPoint = secondPoint.dataPoint;
852 // Draw two or three segments of the step line
853 GraphicsPath[] resultPathLine = new GraphicsPath[3];
854 for(int segmentIndex = 0; segmentIndex < 2; segmentIndex++)
856 DataPoint3D point1 = firstPoint, point2 = secondPoint;
857 LineSegmentType lineSegmentType = LineSegmentType.First;
859 if(segmentIndex == 0)
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;
866 else if(segmentIndex == 1)
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;
874 // Draw horizontal surface
875 if(lineSegmentType == LineSegmentType.First ||
876 vertSplitPoint == null)
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,
883 points, pointIndex, 0f, operationType, lineSegmentType,
884 (this.showPointLines) ? true : false, false,
885 area.ReverseSeriesOrder,
886 this.multiSeries, 0, true);
890 if(!originalDrawOrder)
892 lineColor = (currentKagiDirection == -1) ? this.kagiUpColor : color;
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);
906 // No second draw of the prev. front line required
907 graph.frontLinePen = null;
910 currentKagiDirection = (currentKagiDirection == 1) ? -1 : 1;
913 if(originalDrawOrder)
915 lineColor = (currentKagiDirection == 1) ? this.kagiUpColor : color;
919 lineColor = (currentKagiDirection == -1) ? this.kagiUpColor : color;
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);
932 if(!originalDrawOrder)
934 lineColor = (currentKagiDirection == 1) ? this.kagiUpColor : color;
939 // No second draw of the prev. front line required
940 graph.frontLinePen = null;
943 if(resultPath != null)
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);
957 #region IChartType interface implementation
962 override public string Name { get{ return ChartTypeNames.Kagi;}}
965 /// Gets chart type image.
967 /// <param name="registry">Chart types registry object.</param>
968 /// <returns>Chart type image.</returns>
969 override public System.Drawing.Image GetImage(ChartTypeRegistry registry)
971 return (System.Drawing.Image)registry.ResourceManager.GetObject(this.Name + "ChartType");
975 #region Painting and selection methods
978 /// Paint Line Chart.
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 )
986 // Reset current direction
987 this.currentKagiDirection = 0;
989 // Call base class methods
990 base.Paint(graph, common, area, seriesToDraw);
993 #endregion // Painting and selection methods