/// <summary> /// Paint FastLine 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; bool clipRegionSet = false; 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 FastLine 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 float permittedPixelError = 1.0f; 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)); // Create line pen SKPaint linePen = new() { Color = series.Color, StrokeWidth = series.BorderWidth }; linePen.PathEffect = ChartGraphics.GetPenStyle(series.BorderDashStyle, series.BorderWidth); linePen.StrokeCap = SKStrokeCap.Round; // Create empty line pen SKPaint emptyLinePen = new() { Style = SKPaintStyle.Stroke, Color = series.EmptyPointStyle.Color, StrokeWidth = series.EmptyPointStyle.BorderWidth }; emptyLinePen.PathEffect = ChartGraphics.GetPenStyle(series.EmptyPointStyle.BorderDashStyle, series.EmptyPointStyle.BorderWidth); emptyLinePen.StrokeCap = SKStrokeCap.Round; // Check if series is indexed bool indexedSeries = ChartHelper.IndexedSeries(Common, series.Name); // Loop through all ponts in the series int index = 0; double yValueRangeMin = double.NaN; double yValueRangeMax = double.NaN; DataPoint pointRangeMin = null; DataPoint pointRangeMax = null; double xValue = 0; double yValue = 0; double xValuePrev = 0; double yValuePrev = 0; DataPoint prevDataPoint = null; SKPoint lastVerticalSegmentPoint = SKPoint.Empty; SKPoint prevPoint = SKPoint.Empty; SKPoint currentPoint = SKPoint.Empty; bool prevPointInAxesCoordinates = false; bool verticalLineDetected = false; bool prevPointIsEmpty = false; bool currentPointIsEmpty = false; bool firstNonEmptyPoint = false; double xPixelConverter = (graph.Common.ChartPicture.Width - 1.0) / 100.0; double yPixelConverter = (graph.Common.ChartPicture.Height - 1.0) / 100.0; foreach (DataPoint point in series.Points) { // Get point X and Y values xValue = (indexedSeries) ? index + 1 : point.XValue; xValue = hAxis.GetLogValue(xValue); yValue = vAxis.GetLogValue(point.YValues[0]); currentPointIsEmpty = point.IsEmpty; // NOTE: Fixes issue #7094 // If current point is non-empty but the previous one was, // use empty point style properties to draw it. if (prevPointIsEmpty && !currentPointIsEmpty && !firstNonEmptyPoint) { firstNonEmptyPoint = true; currentPointIsEmpty = true; } else { firstNonEmptyPoint = false; } // Check if line is completly out of the data scaleView if (!verticalLineDetected && ((xValue < hAxisMin && xValuePrev < hAxisMin) || (xValue > hAxisMax && xValuePrev > hAxisMax) || (yValue < vAxisMin && yValuePrev < vAxisMin) || (yValue > vAxisMax && yValuePrev > vAxisMax))) { xValuePrev = xValue; yValuePrev = yValue; prevPointInAxesCoordinates = true; ++index; continue; } else if (!clipRegionSet && (xValuePrev < hAxisMin || xValuePrev > hAxisMax || xValue > hAxisMax || xValue < hAxisMin || yValuePrev < vAxisMin || yValuePrev > vAxisMax || yValue < vAxisMin || yValue > vAxisMax)) { // Set clipping region for line drawing graph.SetClip(area.PlotAreaPosition.ToSKRect()); clipRegionSet = true; } // Check if point may be skipped // Check if points X value in acceptable error boundary if (index > 0 && currentPointIsEmpty == prevPointIsEmpty && Math.Abs(xValue - xValuePrev) < axesValuesPixelSizeX) { if (!verticalLineDetected) { verticalLineDetected = true; if (yValue > yValuePrev) { yValueRangeMax = yValue; yValueRangeMin = yValuePrev; pointRangeMax = point; pointRangeMin = prevDataPoint; } else { yValueRangeMax = yValuePrev; yValueRangeMin = yValue; pointRangeMax = prevDataPoint; pointRangeMin = point; } } else { if (yValue > yValueRangeMax) { yValueRangeMax = yValue; pointRangeMax = point; } else if (yValue < yValueRangeMin) { yValueRangeMin = yValue; pointRangeMin = point; } } // Remember last point prevDataPoint = point; // Remember last vertical range point // Note! Point is in axes coordinate. lastVerticalSegmentPoint.Y = (float)yValue; // Increase counter and proceed to next data point ++index; continue; } // Get point pixel position currentPoint.X = (float) (hAxis.GetLinearPosition(xValue) * xPixelConverter); currentPoint.Y = (float) (vAxis.GetLinearPosition(yValue) * yPixelConverter); // Check if previous point must be converted from axes values to pixels if (prevPointInAxesCoordinates) { prevPoint.X = (float) (hAxis.GetLinearPosition(xValuePrev) * xPixelConverter); prevPoint.Y = (float) (vAxis.GetLinearPosition(yValuePrev) * yPixelConverter); } // Draw accumulated vertical line (with minimal X values differences) if (verticalLineDetected) { // Convert Y coordinates to pixels yValueRangeMin = (vAxis.GetLinearPosition(yValueRangeMin) * yPixelConverter); yValueRangeMax = (vAxis.GetLinearPosition(yValueRangeMax) * yPixelConverter); // Draw accumulated vertical line DrawLine( series, prevDataPoint, pointRangeMin, pointRangeMax, index, (prevPointIsEmpty) ? emptyLinePen : linePen, prevPoint.X, (float)yValueRangeMin, prevPoint.X, (float)yValueRangeMax); // Reset vertical line detected flag verticalLineDetected = false; // Convert last point of the vertical line segment to pixel coordinates prevPoint.Y = (float) (vAxis.GetLinearPosition(lastVerticalSegmentPoint.Y) * yPixelConverter); } // Draw line from previous to current point if (index > 0) { DrawLine( series, point, pointRangeMin, pointRangeMax, index, (currentPointIsEmpty) ? emptyLinePen : linePen, prevPoint.X, prevPoint.Y, currentPoint.X, currentPoint.Y); } // Remember last point coordinates xValuePrev = xValue; yValuePrev = yValue; prevDataPoint = point; prevPoint = currentPoint; prevPointInAxesCoordinates = false; prevPointIsEmpty = currentPointIsEmpty; ++index; } // Draw last accumulated line segment if (verticalLineDetected) { // Check if previous point must be converted from axes values to pixels if (prevPointInAxesCoordinates) { prevPoint.X = (float) (hAxis.GetLinearPosition(xValuePrev) * xPixelConverter); prevPoint.Y = (float) (vAxis.GetLinearPosition(yValuePrev) * yPixelConverter); } // Convert Y coordinates to pixels yValueRangeMin = (vAxis.GetLinearPosition(yValueRangeMin) * yPixelConverter); yValueRangeMax = (vAxis.GetLinearPosition(yValueRangeMax) * yPixelConverter); // Draw accumulated vertical line DrawLine( series, prevDataPoint, pointRangeMin, pointRangeMax, index - 1, prevPointIsEmpty ? emptyLinePen : linePen, prevPoint.X, (float)yValueRangeMin, prevPoint.X, (float)yValueRangeMax); } } // Reset Clip Region if (clipRegionSet) { graph.ResetClip(); } }
/// <summary> /// Fill labels from data from data manager or /// from axis scale. /// </summary> /// <param name="removeFirstRow">True if first row of auto generated labels must be removed.</param> internal void FillLabels(bool removeFirstRow) { #if SUBAXES // Process all sub-axis foreach (SubAxis subAxis in ((Axis)this).SubAxes) { subAxis.FillLabels(true); } #endif // SUBAXES // Labels are disabled for this axis if (!LabelStyle.Enabled || !enabled) { return; } // For circular chart area fill only Y axis labels if (ChartArea != null && ChartArea.chartAreaIsCurcular && axisType != AxisName.Y) { ICircularChartType type = ChartArea.GetCircularChartType(); if (type == null || !type.XAxisLabelsSupported()) { return; } } // Check if the custom labels exist bool customLabelsFlag = false; foreach (CustomLabel lab in CustomLabels) { if (lab.customLabel && (lab.RowIndex == 0 || ChartArea.chartAreaIsCurcular)) { customLabelsFlag = true; } } // Remove the first row of labels if custom labels not exist if (removeFirstRow) { if (!customLabelsFlag) { for (int index = 0; index < CustomLabels.Count; index++) { if (CustomLabels[index].RowIndex == 0) { CustomLabels.RemoveAt(index); index = -1; } } } else { return; } } // Get data series for this axis. List <string> dataSeries = null; switch (axisType) { case AxisName.X: dataSeries = ChartArea.GetXAxesSeries(AxisType.Primary, SubAxisName); break; case AxisName.Y: dataSeries = ChartArea.GetYAxesSeries(AxisType.Primary, SubAxisName); break; case AxisName.X2: dataSeries = ChartArea.GetXAxesSeries(AxisType.Secondary, SubAxisName); break; case AxisName.Y2: dataSeries = ChartArea.GetYAxesSeries(AxisType.Secondary, SubAxisName); break; } // There aren't data series connected with this axis. if (dataSeries == null || dataSeries.Count == 0) { return; } //Let's convert the ArrayList of the series names into to string[] string[] dataSeriesNames = new string[dataSeries.Count]; for (int i = 0; i < dataSeries.Count; i++) { dataSeriesNames[i] = dataSeries[i]; } // Check if series X values all set to zeros bool seriesXValuesZeros = ChartHelper.SeriesXValuesZeros(Common, dataSeriesNames); // Check if series is indexed (All X values zeros or IsXValueIndexed flag set) bool indexedSeries = true; if (!seriesXValuesZeros) { indexedSeries = ChartHelper.IndexedSeries(Common, dataSeriesNames); } // Show End Labels int endLabels = 0; if (labelStyle.IsEndLabelVisible) { endLabels = 1; } // Get chart type of the first series IChartType chartType = Common.ChartTypeRegistry.GetChartType(ChartArea.GetFirstSeries().ChartTypeName); bool fromSeries = false; if (!chartType.RequireAxes) { return; } else if (axisType == AxisName.Y || axisType == AxisName.Y2) { fromSeries = false; } else { fromSeries = true; } // X values from data points are not 0. if (fromSeries && !ChartHelper.SeriesXValuesZeros(Common, dataSeries.ToArray())) { fromSeries = false; } // X values from data points are not 0. if (fromSeries && (labelStyle.GetIntervalOffset() != 0 || labelStyle.GetInterval() != 0)) { fromSeries = false; } // Get value type ChartValueType valueType; if (axisType == AxisName.X || axisType == AxisName.X2) { // If X value is indexed the type is always String. So we use indexed type instead valueType = Common.DataManager.Series[dataSeries[0]].indexedXValueType; } else { valueType = Common.DataManager.Series[dataSeries[0]].YValueType; } if (labelStyle.GetIntervalType() != DateTimeIntervalType.Auto && labelStyle.GetIntervalType() != DateTimeIntervalType.Number && valueType != ChartValueType.Time && valueType != ChartValueType.Date && valueType != ChartValueType.DateTimeOffset) { valueType = ChartValueType.DateTime; } // *********************************** // Pre calculate some values // *********************************** double viewMaximum = ViewMaximum; double viewMinimum = ViewMinimum; // *********************************** // Labels are filled from data series. // *********************************** if (fromSeries) { int numOfPoints; numOfPoints = Common.DataManager.GetNumberOfPoints(dataSeries.ToArray()); // Show end labels if (endLabels == 1) { // min position CustomLabels.Add(-0.5, 0.5, ValueConverter.FormatValue( Common.Chart, this, null, 0.0, LabelStyle.Format, valueType, ChartElementType.AxisLabels), false); } // Labels from point position for (int point = 0; point < numOfPoints; point++) { CustomLabels.Add(point + 0.5, point + 1.5, ValueConverter.FormatValue( Common.Chart, this, null, point + 1, LabelStyle.Format, valueType, ChartElementType.AxisLabels), false); } // Show end labels if (endLabels == 1) { // max position CustomLabels.Add(numOfPoints + 0.5, numOfPoints + 1.5, ValueConverter.FormatValue( Common.Chart, this, null, numOfPoints + 1, LabelStyle.Format, valueType, ChartElementType.AxisLabels), false); } int pointIndx; foreach (string seriesIndx in dataSeries) { // End labels enabled if (endLabels == 1) { pointIndx = 1; } else { pointIndx = 0; } // Set labels from data points labels foreach (DataPoint dataPoint in Common.DataManager.Series[seriesIndx].Points) { // Find first row of labels while (CustomLabels[pointIndx].RowIndex > 0) { pointIndx++; } // Add X labels if ((axisType == AxisName.X || axisType == AxisName.X2) && dataPoint.AxisLabel.Length > 0) { CustomLabels[pointIndx].Text = dataPoint.AxisLabel; } pointIndx++; } } } // *********************************** // Labels are filled from axis scale. // *********************************** else { if (viewMinimum == viewMaximum) { return; } double labValue; // Value, which will be converted to text and used for, labels. double beginPosition; // Begin position for a label double endPosition; // End position for a label double start; // Start position for all labels // Get first series attached to this axis Series axisSeries = null; if (axisType == AxisName.X || axisType == AxisName.X2) { List <string> seriesArray = ChartArea.GetXAxesSeries((axisType == AxisName.X) ? AxisType.Primary : AxisType.Secondary, SubAxisName); if (seriesArray.Count > 0) { axisSeries = Common.DataManager.Series[seriesArray[0]]; if (axisSeries != null && !axisSeries.IsXValueIndexed) { axisSeries = null; } } } // *********************************** // Check if the AJAX zooming and scrolling mode is enabled. // Labels are filled slightly different in this case. // *********************************** DateTimeIntervalType offsetType = (labelStyle.GetIntervalOffsetType() == DateTimeIntervalType.Auto) ? labelStyle.GetIntervalType() : labelStyle.GetIntervalOffsetType(); // By default start is equal to minimum start = viewMinimum; // Adjust start position depending on the interval type if (!ChartArea.chartAreaIsCurcular || axisType == AxisName.Y || axisType == AxisName.Y2) { start = ChartHelper.AlignIntervalStart(start, labelStyle.GetInterval(), labelStyle.GetIntervalType(), axisSeries); } // Move start if there is start position if (labelStyle.GetIntervalOffset() != 0 && axisSeries == null) { start += ChartHelper.GetIntervalSize(start, labelStyle.GetIntervalOffset(), offsetType, axisSeries, 0, DateTimeIntervalType.Number, true, false); } // *************************************** // Date type // *************************************** if (valueType == ChartValueType.DateTime || valueType == ChartValueType.Date || valueType == ChartValueType.Time || valueType == ChartValueType.DateTimeOffset || axisSeries != null) { double position = start; double dateInterval; // Too many labels if ((viewMaximum - start) / ChartHelper.GetIntervalSize(start, labelStyle.GetInterval(), labelStyle.GetIntervalType(), axisSeries, 0, DateTimeIntervalType.Number, true) > ChartHelper.MaxNumOfGridlines) { return; } int counter = 0; double endLabelMaxPosition = viewMaximum - ChartHelper.GetIntervalSize(viewMaximum, labelStyle.GetInterval(), labelStyle.GetIntervalType(), axisSeries, labelStyle.GetIntervalOffset(), offsetType, true) / 2f; double endLabelMinPosition = viewMinimum + ChartHelper.GetIntervalSize(viewMinimum, labelStyle.GetInterval(), labelStyle.GetIntervalType(), axisSeries, labelStyle.GetIntervalOffset(), offsetType, true) / 2f; while ((decimal)position <= (decimal)viewMaximum) { dateInterval = ChartHelper.GetIntervalSize(position, labelStyle.GetInterval(), labelStyle.GetIntervalType(), axisSeries, labelStyle.GetIntervalOffset(), offsetType, true); labValue = position; // For IsLogarithmic axes if (IsLogarithmic) { labValue = Math.Pow(logarithmBase, labValue); } // Check if we do not exceed max number of elements if (counter++ > ChartHelper.MaxNumOfGridlines) { break; } if (endLabels == 0 && position >= endLabelMaxPosition) { break; } beginPosition = position - dateInterval * 0.5; endPosition = position + dateInterval * 0.5; if (endLabels == 0 && position <= endLabelMinPosition) { position += dateInterval; continue; } if ((decimal)beginPosition > (decimal)viewMaximum) { position += dateInterval; continue; } string pointLabel = GetPointLabel(dataSeries, labValue, !seriesXValuesZeros, indexedSeries); if (pointLabel.Length == 0) { // Do not draw last label for indexed series if (position <= maximum && (position != maximum || !Common.DataManager.Series[dataSeries[0]].IsXValueIndexed)) { CustomLabels.Add(beginPosition, endPosition, ValueConverter.FormatValue( Common.Chart, this, null, labValue, LabelStyle.Format, valueType, ChartElementType.AxisLabels), false); } } else { // Add a label to the collection CustomLabels.Add(beginPosition, endPosition, pointLabel, false); } position += dateInterval; } } else { // *************************************** // Scale value type // *************************************** // Show First label if Start Label position is used if (start != viewMinimum) { endLabels = 1; } // Set labels int labelCounter = 0; for (double position = start - endLabels * labelStyle.GetInterval(); position < viewMaximum - 1.5 * labelStyle.GetInterval() * (1 - endLabels); position = (double)((decimal)position + (decimal)labelStyle.GetInterval())) { // Prevent endless loop that may be caused by very small interval // and double/decimal rounding errors ++labelCounter; if (labelCounter > ChartHelper.MaxNumOfGridlines) { break; } labValue = (double)((decimal)position + (decimal)labelStyle.GetInterval()); // This line is introduce because sometimes 0 value will appear as // very small value close to zero. double inter = Math.Log(labelStyle.GetInterval()); double valu = Math.Log(Math.Abs(labValue)); int digits = (int)Math.Abs(inter) + 5; if (digits > 15) { digits = 15; } if (Math.Abs(inter) < Math.Abs(valu) - 5) { labValue = Math.Round(labValue, digits); } // Too many labels if ((viewMaximum - start) / labelStyle.GetInterval() > ChartHelper.MaxNumOfGridlines) { return; } // For IsLogarithmic axes if (IsLogarithmic) { labValue = Math.Pow(logarithmBase, labValue); } beginPosition = (double)((decimal)position + (decimal)labelStyle.GetInterval() * 0.5m); endPosition = (double)((decimal)position + (decimal)labelStyle.GetInterval() * 1.5m); if ((decimal)beginPosition > (decimal)viewMaximum) { continue; } // Show End label if Start Label position is used // Use decimal type to solve rounding issues if ((decimal)((beginPosition + endPosition) / 2.0) > (decimal)viewMaximum) { continue; } string pointLabel = GetPointLabel(dataSeries, labValue, !seriesXValuesZeros, indexedSeries); if (pointLabel.Length > 15 && labValue < 0.000001) { labValue = 0.0; } if (pointLabel.Length == 0) { // Do not draw last label for indexed series if (!(Common.DataManager.Series[dataSeries[0]].IsXValueIndexed && position > maximum)) { // Add a label to the collection CustomLabels.Add(beginPosition, endPosition, ValueConverter.FormatValue( Common.Chart, this, null, labValue, LabelStyle.Format, valueType, ChartElementType.AxisLabels), false); } } else { // Add a label to the collection CustomLabels.Add(beginPosition, endPosition, pointLabel, false); } } } } }
/// <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(); } } }