/// <summary> /// Check if any series is indexed. IsXValueIndexed flag is set or all X values are zeros. /// </summary> /// <param name="common">Reference to common chart classes.</param> /// <param name="series">Data series names.</param> /// <returns>True if any series is indexed.</returns> static internal bool IndexedSeries(CommonElements common, params string[] series) { // Data series loop bool zeroXValues = true; foreach (string ser in series) { Series localSeries = common.DataManager.Series[ser]; // Check series indexed flag if (localSeries.IsXValueIndexed) { // If flag set in at least one series - all series are indexed return(true); } // Check if series has all X values set to zero if (zeroXValues && !IndexedSeries(localSeries)) { zeroXValues = false; } } return(zeroXValues); }
/// <summary> /// Returns marker size. /// </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="point">Data point.</param> /// <param name="markerSize">Marker size.</param> /// <param name="markerImage">Marker image.</param> /// <returns>Marker width and height.</returns> override protected SKSize GetMarkerSize( ChartGraphics graph, CommonElements common, ChartArea area, DataPoint point, int markerSize, string markerImage) { // Check required Y values number if (point.YValues.Length < YValuesPerPoint) { throw(new InvalidOperationException(SR.ExceptionChartTypeRequiresYValues(Name, YValuesPerPoint.ToString(CultureInfo.InvariantCulture)))); } // Marker size SKSize size = new(markerSize, markerSize); if (graph != null && graph.Graphics != null) { // Marker size is in pixels and we do the mapping for higher DPIs size.Width = markerSize; size.Height = markerSize; } // Check number of Y values for non empty points if (point.series.YValuesPerPoint > 1 && !point.IsEmpty) { // Scale Y values size.Width = ScaleBubbleSize(graph, common, area, point.YValues[1]); size.Height = ScaleBubbleSize(graph, common, area, point.YValues[1]); } return(size); }
/// <summary> /// Paint event arguments constructor. /// </summary> /// <param name="chartElement">Chart element.</param> /// <param name="chartGraph">Chart graphics.</param> /// <param name="common">Common elements.</param> /// <param name="position">Position.</param> internal ChartPaintEventArgs(object chartElement, ChartGraphics chartGraph, CommonElements common, ElementPosition position) { _chartElement = chartElement; _chartGraph = chartGraph; _common = common; _position = position; }
/// <summary> /// This method recalculates size of the bars. This method is used /// from Paint or Select method. /// </summary> /// <param name="selection">If True selection mode is active, otherwise paint mode is active</param> /// <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> override protected void ProcessChartType( bool selection, ChartGraphics graph, CommonElements common, ChartArea area, Series seriesToDraw) { _scaleDetected = false; base.ProcessChartType(selection, graph, common, area, seriesToDraw); }
/// <summary> /// Helper function, which returns the Y value of the point. /// </summary> /// <param name="common">Chart common elements.</param> /// <param name="area">Chart area the series belongs to.</param> /// <param name="series">Sereis of the point.</param> /// <param name="point">Point object.</param> /// <param name="pointIndex">Index of the point.</param> /// <param name="yValueIndex">Index of the Y value to get.</param> /// <returns>Y value of the point.</returns> virtual public double GetYValue( CommonElements common, ChartArea area, Series series, DataPoint point, int pointIndex, int yValueIndex) { return(point.YValues[yValueIndex]); }
/// <summary> /// Check if all data points in many series have X value set to 0. /// </summary> /// <param name="common">Reference to common chart classes.</param> /// <param name="series">Data series.</param> /// <returns>True if all data points have value 0.</returns> static internal bool SeriesXValuesZeros(CommonElements common, params string[] series) { // Data series loop foreach (string ser in series) { // Check one series X values if (!SeriesXValuesZeros(common.DataManager.Series[ser])) { return(false); } } return(true); }
/// <summary> /// Get value from custom attribute BubbleMaxSize /// </summary> /// <param name="area">Chart Area</param> /// <returns>Bubble Max size</returns> static internal double GetBubbleMaxSize(ChartArea area) { double maxPossibleBubbleSize = 15; // Try to find bubble size scale in the custom series properties foreach (Series ser in area.Common.DataManager.Series) { if (string.Compare(ser.ChartTypeName, ChartTypeNames.Bubble, StringComparison.OrdinalIgnoreCase) == 0 && ser.ChartArea == area.Name && ser.IsVisible() && ser.IsCustomPropertySet(CustomPropertyName.BubbleMaxSize)) { maxPossibleBubbleSize = CommonElements.ParseDouble(ser[CustomPropertyName.BubbleMaxSize]); if (maxPossibleBubbleSize < 0 || maxPossibleBubbleSize > 100) { throw (new ArgumentException(SR.ExceptionCustomAttributeIsNotInRange0to100("BubbleMaxSize"))); } } } return(maxPossibleBubbleSize / 100); }
/// <summary> /// Data manager public constructor /// </summary> /// <param name="container">Service container object.</param> public DataManager(CommonElements common) { Common = common; _series = new SeriesCollection(this); }
/// <summary> /// Scales the value used to determine the size of the Bubble. /// </summary> /// <param name="common">The Common elements object</param> /// <param name="area">Chart area for this chart</param> /// <param name="value">Value to scale.</param> /// <param name="yValue">True if Y value is calculated, false if X.</param> /// <returns>Scaled values.</returns> static internal double AxisScaleBubbleSize(CommonElements common, ChartArea area, double value, bool yValue) { // Try to find bubble size scale in the custom series properties double minAll = double.MaxValue; double maxAll = double.MinValue; double maxPossibleBubbleSize = 15F; double minPossibleBubbleSize = 3F; float maxBubleSize; float minBubleSize; double valueScale; double valueDiff; foreach (Series ser in common.DataManager.Series) { if (String.Compare(ser.ChartTypeName, ChartTypeNames.Bubble, StringComparison.OrdinalIgnoreCase) == 0 && ser.ChartArea == area.Name && ser.IsVisible()) { // Check if custom properties are set to specify scale if (ser.IsCustomPropertySet(CustomPropertyName.BubbleScaleMin)) { minAll = Math.Min(minAll, CommonElements.ParseDouble(ser[CustomPropertyName.BubbleScaleMin])); } if (ser.IsCustomPropertySet(CustomPropertyName.BubbleScaleMax)) { maxAll = Math.Max(maxAll, CommonElements.ParseDouble(ser[CustomPropertyName.BubbleScaleMax])); } // Check if attribute for max. size is set if (ser.IsCustomPropertySet(CustomPropertyName.BubbleMaxSize)) { maxPossibleBubbleSize = CommonElements.ParseDouble(ser[CustomPropertyName.BubbleMaxSize]); if (maxPossibleBubbleSize < 0 || maxPossibleBubbleSize > 100) { throw(new ArgumentException(SR.ExceptionCustomAttributeIsNotInRange0to100("BubbleMaxSize"))); } } // Check if custom properties set to use second Y value (bubble size) as label text if (ser.IsCustomPropertySet(CustomPropertyName.BubbleUseSKSizeorLabel) && String.Compare(ser[CustomPropertyName.BubbleUseSKSizeorLabel], "true", StringComparison.OrdinalIgnoreCase) == 0) { break; } } } // Scale values are not specified - auto detect double minimum = double.MaxValue; double maximum = double.MinValue; double minSer = double.MaxValue; double maxSer = double.MinValue; foreach (Series ser in common.DataManager.Series) { if (String.Compare(ser.ChartTypeName, ChartTypeNames.Bubble, StringComparison.OrdinalIgnoreCase) == 0 && ser.ChartArea == area.Name && ser.IsVisible()) { foreach (DataPoint point in ser.Points) { if (!point.IsEmpty) { minSer = Math.Min(minSer, point.YValues[1]); maxSer = Math.Max(maxSer, point.YValues[1]); if (yValue) { minimum = Math.Min(minimum, point.YValues[0]); maximum = Math.Max(maximum, point.YValues[0]); } else { minimum = Math.Min(minimum, point.XValue); maximum = Math.Max(maximum, point.XValue); } } } } } if (minAll == double.MaxValue) { minAll = minSer; } if (maxAll == double.MinValue) { maxAll = maxSer; } // Calculate maximum bubble size maxBubleSize = (float)((maximum - minimum) / (100.0 / maxPossibleBubbleSize)); minBubleSize = (float)((maximum - minimum) / (100.0 / minPossibleBubbleSize)); // Calculate scaling variables depending on the Min/Max values if (maxAll == minAll) { valueScale = 1; valueDiff = minAll - (maxBubleSize - minBubleSize) / 2f; } else { valueScale = (maxBubleSize - minBubleSize) / (maxAll - minAll); valueDiff = minAll; } // Check if value do not exceed Min&Max if (value > maxAll) { return(0F); } if (value < minAll) { return(0F); } // Return scaled value return((float)((value - valueDiff) * valueScale) + minBubleSize); }
/// <summary> /// Scales the value used to determine the size of the Bubble. /// </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="value">Value to scale.</param> /// <returns>Scaled values.</returns> private float ScaleBubbleSize(ChartGraphics graph, CommonElements common, ChartArea area, double value) { // Check if scaling numbers are detected if (!_scaleDetected) { // Try to find bubble size scale in the custom series properties _minAll = double.MaxValue; _maxAll = double.MinValue; foreach (Series ser in common.DataManager.Series) { if (String.Compare(ser.ChartTypeName, Name, true, System.Globalization.CultureInfo.CurrentCulture) == 0 && ser.ChartArea == area.Name && ser.IsVisible()) { // Check if custom properties are set to specify scale if (ser.IsCustomPropertySet(CustomPropertyName.BubbleScaleMin)) { _minAll = Math.Min(_minAll, CommonElements.ParseDouble(ser[CustomPropertyName.BubbleScaleMin])); } if (ser.IsCustomPropertySet(CustomPropertyName.BubbleScaleMax)) { _maxAll = Math.Max(_maxAll, CommonElements.ParseDouble(ser[CustomPropertyName.BubbleScaleMax])); } // Check if attribute for max. size is set if (ser.IsCustomPropertySet(CustomPropertyName.BubbleMaxSize)) { _maxPossibleBubbleSize = CommonElements.ParseDouble(ser[CustomPropertyName.BubbleMaxSize]); if (_maxPossibleBubbleSize < 0 || _maxPossibleBubbleSize > 100) { throw(new ArgumentException(SR.ExceptionCustomAttributeIsNotInRange0to100("BubbleMaxSize"))); } } // Check if attribute for min. size is set if (ser.IsCustomPropertySet(CustomPropertyName.BubbleMinSize)) { _minPossibleBubbleSize = CommonElements.ParseDouble(ser[CustomPropertyName.BubbleMinSize]); if (_minPossibleBubbleSize < 0 || _minPossibleBubbleSize > 100) { throw(new ArgumentException(SR.ExceptionCustomAttributeIsNotInRange0to100("BubbleMinSize"))); } } // Check if custom properties set to use second Y value (bubble size) as label text labelYValueIndex = 0; if (ser.IsCustomPropertySet(CustomPropertyName.BubbleUseSKSizeorLabel) && String.Compare(ser[CustomPropertyName.BubbleUseSKSizeorLabel], "true", StringComparison.OrdinalIgnoreCase) == 0) { labelYValueIndex = 1; break; } } } // Scale values are not specified - auto detect if (_minAll == double.MaxValue || _maxAll == double.MinValue) { double minSer = double.MaxValue; double maxSer = double.MinValue; foreach (Series ser in common.DataManager.Series) { if (ser.ChartTypeName == Name && ser.ChartArea == area.Name && ser.IsVisible()) { foreach (DataPoint point in ser.Points) { if (!point.IsEmpty) { // Check required Y values number if (point.YValues.Length < YValuesPerPoint) { throw (new InvalidOperationException(SR.ExceptionChartTypeRequiresYValues(Name, YValuesPerPoint.ToString(CultureInfo.InvariantCulture)))); } minSer = Math.Min(minSer, point.YValues[1]); maxSer = Math.Max(maxSer, point.YValues[1]); } } } } if (_minAll == double.MaxValue) { _minAll = minSer; } if (_maxAll == double.MinValue) { _maxAll = maxSer; } } // Calculate maximum bubble size SKSize areaSize = graph.GetAbsoluteSize(area.PlotAreaPosition.Size); _maxBubleSize = (float)(Math.Min(areaSize.Width, areaSize.Height) / (100.0 / _maxPossibleBubbleSize)); _minBubleSize = (float)(Math.Min(areaSize.Width, areaSize.Height) / (100.0 / _minPossibleBubbleSize)); // Calculate scaling variables depending on the Min/Max values if (_maxAll == _minAll) { _valueScale = 1; _valueDiff = _minAll - (_maxBubleSize - _minBubleSize) / 2f; } else { _valueScale = (_maxBubleSize - _minBubleSize) / (_maxAll - _minAll); _valueDiff = _minAll; } _scaleDetected = true; } // Check if value do not exceed Min&Max if (value > _maxAll) { return(0F); } if (value < _minAll) { return(0F); } // Return scaled value return((float)((value - _valueDiff) * _valueScale) + _minBubleSize); }
/// <summary> /// Adds markers position to the list. Used to check SmartLabelStyle overlapping. /// </summary> /// <param name="common">Common chart elements.</param> /// <param name="area">Chart area.</param> /// <param name="series">Series values to be used.</param> /// <param name="list">List to add to.</param> public void AddSmartLabelMarkerPositions(CommonElements common, ChartArea area, Series series, ArrayList list) { // Fast Line chart type do not support labels }
/// <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> /// 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> /// Draw chart line using horisontal and vertical lines. /// </summary> /// <param name="graph">Graphics object.</param> /// <param name="common">The Common elements object</param> /// <param name="point">Point to draw the line for.</param> /// <param name="series">Point series.</param> /// <param name="points">Array of points coordinates.</param> /// <param name="pointIndex">Index of point to draw.</param> /// <param name="tension">Line tension</param> override protected void DrawLine( ChartGraphics graph, CommonElements common, DataPoint point, Series series, SKPoint[] points, int pointIndex, float tension) { // Start drawing from the second point if (pointIndex <= 0) { return; } // Darw two lines SKPoint point1 = points[pointIndex - 1]; SKPoint point2 = new(points[pointIndex].X, points[pointIndex - 1].Y); SKPoint point3 = points[pointIndex]; graph.DrawLineRel(point.Color, point.BorderWidth, point.BorderDashStyle, graph.GetRelativePoint(point1), graph.GetRelativePoint(point2), series.ShadowColor, series.ShadowOffset); graph.DrawLineRel(point.Color, point.BorderWidth, point.BorderDashStyle, graph.GetRelativePoint(point2), graph.GetRelativePoint(point3), series.ShadowColor, series.ShadowOffset); if (common.ProcessModeRegions) { // Create grapics path object for the line // Split line into 2 segments. SKPath path = new(); try { path.AddLine(point2, point3); if (!point2.Equals(point3)) { // path.Widen(new Pen(point.Color, point.BorderWidth + 2)); } } catch (OutOfMemoryException) { // SKPath.Widen incorrectly throws OutOfMemoryException // catching here and reacting by not widening } catch (ArgumentException) { // Ignore } float[] coord = new float[path.PointCount * 2]; SKPoint[] pathPoints = path.Points; // Allocate array of floats SKPoint pointNew; for (int i = 0; i < path.PointCount; i++) { pointNew = graph.GetRelativePoint(pathPoints[i]); coord[2 * i] = pointNew.X; coord[2 * i + 1] = pointNew.Y; } common.HotRegionsList.AddHotRegion( path, false, coord, point, series.Name, pointIndex); path.Dispose(); // Create grapics path object for the line path = new SKPath(); try { path.AddLine(point1, point2); //path.Widen(new Pen(point.Color, point.BorderWidth + 2)); } catch (OutOfMemoryException) { // SKPath.Widen incorrectly throws OutOfMemoryException // catching here and reacting by not widening } catch (ArgumentException) { // Ignore } // Allocate array of floats coord = new float[path.PointCount * 2]; pathPoints = path.Points; for (int i = 0; i < path.PointCount; i++) { pointNew = graph.GetRelativePoint(pathPoints[i]); coord[2 * i] = pointNew.X; coord[2 * i + 1] = pointNew.Y; } common.HotRegionsList.AddHotRegion( path, false, coord, series.Points[pointIndex - 1], series.Name, pointIndex - 1); path.Dispose(); } }
public ImageLoader(CommonElements common) { _common = common; }