/// <summary> /// Paint FastPoint Chart. /// </summary> /// <param name="graph">The Chart Graphics object.</param> /// <param name="common">The Common elements object.</param> /// <param name="area">Chart area for this chart.</param> /// <param name="seriesToDraw">Chart series to draw.</param> virtual public void Paint( ChartGraphics graph, CommonElements common, ChartArea area, Series seriesToDraw) { Common = common; Graph = graph; if (area.Area3DStyle.Enable3D) { // Initialize variables chartArea3DEnabled = true; matrix3D = area.matrix3D; } else { chartArea3DEnabled = false; } //************************************************************ //** Loop through all series //************************************************************ foreach (Series series in common.DataManager.Series) { // Process non empty series of the area with FastPoint chart type if (String.Compare(series.ChartTypeName, Name, true, System.Globalization.CultureInfo.CurrentCulture) != 0 || series.ChartArea != area.Name || !series.IsVisible()) { continue; } // Get 3D series depth and Z position if (chartArea3DEnabled) { area.GetSeriesZPositionAndDepth(series, out float seriesDepth, out seriesZCoordinate); seriesZCoordinate += seriesDepth / 2.0f; } // Set active horizontal/vertical axis Axis hAxis = area.GetAxis(AxisName.X, series.XAxisType, (area.Area3DStyle.Enable3D) ? string.Empty : Series.XSubAxisName); Axis vAxis = area.GetAxis(AxisName.Y, series.YAxisType, (area.Area3DStyle.Enable3D) ? string.Empty : Series.YSubAxisName); double hAxisMin = hAxis.ViewMinimum; double hAxisMax = hAxis.ViewMaximum; double vAxisMin = vAxis.ViewMinimum; double vAxisMax = vAxis.ViewMaximum; // Get "PermittedPixelError" attribute. // By default use 1/3 of the marker size. float permittedPixelError = series.MarkerSize / 3f; if (series.IsCustomPropertySet(CustomPropertyName.PermittedPixelError)) { string attrValue = series[CustomPropertyName.PermittedPixelError]; bool parseSucceed = float.TryParse(attrValue, NumberStyles.Any, CultureInfo.CurrentCulture, out float pixelError); if (parseSucceed) { permittedPixelError = pixelError; } else { throw (new InvalidOperationException(SR.ExceptionCustomAttributeValueInvalid2("PermittedPixelError"))); } // "PermittedPixelError" attribute value should be in range from zero to 1 if (permittedPixelError < 0f || permittedPixelError > 1f) { throw (new InvalidOperationException(SR.ExceptionCustomAttributeIsNotInRange0to1("PermittedPixelError"))); } } // Get pixel size in axes coordinates SKSize pixelSize = graph.GetRelativeSize(new SKSize(permittedPixelError, permittedPixelError)); SKSize axesMin = graph.GetRelativeSize(new SKSize((float)hAxisMin, (float)vAxisMin)); double axesValuesPixelSizeX = Math.Abs(hAxis.PositionToValue(axesMin.Width + pixelSize.Width, false) - hAxis.PositionToValue(axesMin.Width, false)); double axesValuesPixelSizeY = Math.Abs(vAxis.PositionToValue(axesMin.Height + pixelSize.Height, false) - vAxis.PositionToValue(axesMin.Height, false)); // Create point marker brush SKPaint markerBrush = new() { Color = (series.MarkerColor == SKColor.Empty) ? series.Color : series.MarkerColor, Style = SKPaintStyle.Fill }; SKPaint emptyMarkerBrush = new() { Color = (series.EmptyPointStyle.MarkerColor == SKColor.Empty) ? series.EmptyPointStyle.Color : series.EmptyPointStyle.MarkerColor, Style = SKPaintStyle.Fill }; // Create point marker border pen SKPaint borderPen = null; SKPaint emptyBorderPen = null; if (series.MarkerBorderColor != SKColor.Empty && series.MarkerBorderWidth > 0) { borderPen = new SKPaint() { Style = SKPaintStyle.Stroke, Color = series.MarkerBorderColor, StrokeWidth = series.MarkerBorderWidth }; } if (series.EmptyPointStyle.MarkerBorderColor != SKColor.Empty && series.EmptyPointStyle.MarkerBorderWidth > 0) { emptyBorderPen = new SKPaint() { Style = SKPaintStyle.Stroke, Color = series.EmptyPointStyle.MarkerBorderColor, StrokeWidth = series.EmptyPointStyle.MarkerBorderWidth }; } // Check if series is indexed bool indexedSeries = ChartHelper.IndexedSeries(Common, series.Name); // Get marker size taking in consideration current DPIs int markerSize = series.MarkerSize; if (graph != null && graph.Graphics != null) { // Marker size is in pixels and we do the mapping for higher DPIs markerSize = Math.Max(markerSize, markerSize); } // Loop through all ponts in the series int index = 0; double xValuePrev = 0.0; double yValuePrev = 0.0; SKPoint currentPoint = SKPoint.Empty; double xPixelConverter = (graph.Common.ChartPicture.Width - 1.0) / 100.0; double yPixelConverter = (graph.Common.ChartPicture.Height - 1.0) / 100.0; MarkerStyle markerStyle = series.MarkerStyle; MarkerStyle emptyMarkerStyle = series.EmptyPointStyle.MarkerStyle; foreach (DataPoint point in series.Points) { // Get point X and Y values double xValue = (indexedSeries) ? index + 1 : point.XValue; xValue = hAxis.GetLogValue(xValue); double yValue = vAxis.GetLogValue(point.YValues[0]); bool currentPointIsEmpty = point.IsEmpty; // Check if point is completly out of the data scaleView if (xValue < hAxisMin || xValue > hAxisMax || yValue < vAxisMin || yValue > vAxisMax) { xValuePrev = xValue; yValuePrev = yValue; ++index; continue; } // Check if point may be skipped if (index > 0 && Math.Abs(xValue - xValuePrev) < axesValuesPixelSizeX && Math.Abs(yValue - yValuePrev) < axesValuesPixelSizeY) { // Increase counter and proceed to the next data point ++index; continue; } // Get point pixel position currentPoint.X = (float) (hAxis.GetLinearPosition(xValue) * xPixelConverter); currentPoint.Y = (float) (vAxis.GetLinearPosition(yValue) * yPixelConverter); // Draw point marker MarkerStyle currentMarkerStyle = (currentPointIsEmpty) ? emptyMarkerStyle : markerStyle; if (currentMarkerStyle != MarkerStyle.None) { this.DrawMarker( graph, point, index, currentPoint, currentMarkerStyle, markerSize, (currentPointIsEmpty) ? emptyMarkerBrush : markerBrush, (currentPointIsEmpty) ? emptyBorderPen : borderPen); } // Remember last point coordinates xValuePrev = xValue; yValuePrev = yValue; ++index; } // Dispose used brushes and pens markerBrush.Dispose(); emptyMarkerBrush.Dispose(); if (borderPen != null) { borderPen.Dispose(); } if (emptyBorderPen != null) { emptyBorderPen.Dispose(); } } }
/// <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); }