/// <summary> /// Draws a 3D surface connecting the two specified points in 2D space. /// Used to draw Line based charts. /// </summary> /// <param name="area">Chart area reference.</param> /// <param name="graph">Chart graphics.</param> /// <param name="matrix">Coordinates transformation matrix.</param> /// <param name="lightStyle">LightStyle style (None, Simplistic, Realistic).</param> /// <param name="prevDataPointEx">Previous data point object.</param> /// <param name="positionZ">Z position of the back side of the 3D surface.</param> /// <param name="depth">Depth of the 3D surface.</param> /// <param name="points">Array of points.</param> /// <param name="pointIndex">Index of point to draw.</param> /// <param name="pointLoopIndex">Index of points loop.</param> /// <param name="tension">Line tension.</param> /// <param name="operationType">AxisName of operation Drawing, Calculating Path or Both</param> /// <param name="topDarkening">Darkenning scale for top surface. 0 - None.</param> /// <param name="bottomDarkening">Darkenning scale for bottom surface. 0 - None.</param> /// <param name="thirdPointPosition">Position where the third point is actually located or float.NaN if same as in "firstPoint".</param> /// <param name="fourthPointPosition">Position where the fourth point is actually located or float.NaN if same as in "secondPoint".</param> /// <param name="clippedSegment">Indicates that drawn segment is 3D clipped. Only top/bottom should be drawn.</param> /// <returns>Returns elemnt shape path if operationType parameter is set to CalcElementPath, otherwise Null.</returns> protected override SKPath Draw3DSurface( ChartArea area, ChartGraphics graph, Matrix3D matrix, LightStyle lightStyle, DataPoint3D prevDataPointEx, float positionZ, float depth, ArrayList points, int pointIndex, int pointLoopIndex, float tension, DrawingOperationTypes operationType, float topDarkening, float bottomDarkening, SKPoint thirdPointPosition, SKPoint fourthPointPosition, bool clippedSegment) { // Create graphics path for selection SKPath resultPath = ((operationType & DrawingOperationTypes.CalcElementPath) == DrawingOperationTypes.CalcElementPath) ? new SKPath() : null; // Check if points are drawn from sides to center (do only once) if (centerPointIndex == int.MaxValue) { centerPointIndex = GetCenterPointIndex(points); } //************************************************************ //** Find line first & second points //************************************************************ DataPoint3D secondPoint = (DataPoint3D)points[pointIndex]; int pointArrayIndex = pointIndex; DataPoint3D firstPoint = ChartGraphics.FindPointByIndex( points, secondPoint.index - 1, (multiSeries) ? secondPoint : null, ref pointArrayIndex); // Fint point with line properties DataPoint3D pointAttr = secondPoint; if (prevDataPointEx.dataPoint.IsEmpty) { pointAttr = prevDataPointEx; } else if (firstPoint.index > secondPoint.index) { pointAttr = firstPoint; } // Adjust point visual properties SKColor color = (useBorderColor) ? pointAttr.dataPoint.BorderColor : pointAttr.dataPoint.Color; ChartDashStyle dashStyle = pointAttr.dataPoint.BorderDashStyle; if (pointAttr.dataPoint.IsEmpty && pointAttr.dataPoint.Color == SKColor.Empty) { color = SKColors.Gray; } if (pointAttr.dataPoint.IsEmpty && pointAttr.dataPoint.BorderDashStyle == ChartDashStyle.NotSet) { dashStyle = ChartDashStyle.Solid; } //************************************************************ //** Create "middle" point //************************************************************ DataPoint3D middlePoint = new(); middlePoint.xPosition = secondPoint.xPosition; middlePoint.yPosition = firstPoint.yPosition; // Check if reversed drawing order required bool originalDrawOrder = true; if ((pointIndex + 1) < points.Count) { DataPoint3D p = (DataPoint3D)points[pointIndex + 1]; if (p.index == firstPoint.index) { originalDrawOrder = false; } } // Check in which order vertical & horizontal lines segments should be drawn if (centerPointIndex != int.MaxValue && pointIndex >= centerPointIndex) { originalDrawOrder = false; } // Draw two segments of the step line SKPath resultPathLine1, resultPathLine2; if (originalDrawOrder) { // Draw first line middlePoint.dataPoint = secondPoint.dataPoint; resultPathLine1 = graph.Draw3DSurface( area, matrix, lightStyle, SurfaceNames.Top, positionZ, depth, color, pointAttr.dataPoint.BorderColor, pointAttr.dataPoint.BorderWidth, dashStyle, firstPoint, middlePoint, points, pointIndex, 0f, operationType, LineSegmentType.First, showPointLines, false, area.ReverseSeriesOrder, multiSeries, 0, true); // No second draw of the prev. front line required graph.frontLinePen = null; // Draw second line middlePoint.dataPoint = firstPoint.dataPoint; resultPathLine2 = graph.Draw3DSurface( area, matrix, lightStyle, SurfaceNames.Top, positionZ, depth, color, pointAttr.dataPoint.BorderColor, pointAttr.dataPoint.BorderWidth, dashStyle, middlePoint, secondPoint, points, pointIndex, 0f, operationType, LineSegmentType.Last, showPointLines, false, area.ReverseSeriesOrder, multiSeries, 0, true); // No second draw of the prev. front line required graph.frontLinePen = null; } else { // Draw second line middlePoint.dataPoint = firstPoint.dataPoint; resultPathLine2 = graph.Draw3DSurface( area, matrix, lightStyle, SurfaceNames.Top, positionZ, depth, color, pointAttr.dataPoint.BorderColor, pointAttr.dataPoint.BorderWidth, dashStyle, middlePoint, secondPoint, points, pointIndex, 0f, operationType, LineSegmentType.Last, showPointLines, false, area.ReverseSeriesOrder, multiSeries, 0, true); // No second draw of the prev. front line required graph.frontLinePen = null; // Draw first line middlePoint.dataPoint = secondPoint.dataPoint; resultPathLine1 = graph.Draw3DSurface( area, matrix, lightStyle, SurfaceNames.Top, positionZ, depth, color, pointAttr.dataPoint.BorderColor, pointAttr.dataPoint.BorderWidth, dashStyle, firstPoint, middlePoint, points, pointIndex, 0f, operationType, LineSegmentType.First, showPointLines, false, area.ReverseSeriesOrder, multiSeries, 0, true); // No second draw of the prev. front line required graph.frontLinePen = null; } if (resultPath != null) { if (area.Common.ProcessModeRegions && resultPathLine1 != null && resultPathLine1.PointCount > 0) { area.Common.HotRegionsList.AddHotRegion( resultPathLine1, false, prevDataPointEx.dataPoint, prevDataPointEx.dataPoint.series.Name, prevDataPointEx.index - 1); } if (resultPathLine2 != null && resultPathLine2.PointCount > 0) { resultPath.AddPath(resultPathLine2); } } return(resultPath); }