/// <summary> /// Draws 3D border. /// </summary> /// <param name="graph">Graphics to draw the border on.</param> /// <param name="borderSkin">Border skin object.</param> /// <param name="rect">Rectangle of the border.</param> /// <param name="backColor">Color of rectangle</param> /// <param name="backHatchStyle">Hatch style</param> /// <param name="backImage">Back Image</param> /// <param name="backImageWrapMode">Image mode</param> /// <param name="backImageTransparentColor">Image transparent color.</param> /// <param name="backImageAlign">Image alignment</param> /// <param name="backGradientStyle">Gradient type</param> /// <param name="backSecondaryColor">Gradient End Color</param> /// <param name="borderColor">Border Color</param> /// <param name="borderWidth">Border Width</param> /// <param name="borderDashStyle">Border Style</param> public virtual void DrawBorder( ChartGraphics graph, BorderSkin borderSkin, SKRect rect, SKColor backColor, ChartHatchStyle backHatchStyle, string backImage, ChartImageWrapMode backImageWrapMode, SKColor backImageTransparentColor, ChartImageAlignmentStyle backImageAlign, GradientStyle backGradientStyle, SKColor backSecondaryColor, SKColor borderColor, int borderWidth, ChartDashStyle borderDashStyle) { SKRect absolute = ChartGraphics.Round(rect); // Calculate shadow colors (0.2 - 0.6) float colorDarkeningIndex = 0.2f + (0.4f * (borderSkin.PageColor.Red + borderSkin.PageColor.Green + borderSkin.PageColor.Blue) / 765f); SKColor shadowColor = new( (byte)(borderSkin.PageColor.Red * colorDarkeningIndex), (byte)(borderSkin.PageColor.Green * colorDarkeningIndex), (byte)(borderSkin.PageColor.Blue * colorDarkeningIndex)); if (borderSkin.PageColor == SKColors.Transparent) { shadowColor = new(0, 0, 0, 60); } colorDarkeningIndex += 0.2f; SKColor shadowLightColor = new( (byte)(borderSkin.PageColor.Red * colorDarkeningIndex), (byte)(borderSkin.PageColor.Green * colorDarkeningIndex), (byte)(borderSkin.PageColor.Blue * colorDarkeningIndex)); // Calculate rounded rect radius float radius = defaultRadiusSize; radius = Math.Max(radius, 2f * resolution / 96.0f); radius = Math.Min(radius, rect.Width / 2f); radius = Math.Min(radius, rect.Height / 2f); radius = (float)Math.Ceiling(radius); // Fill page background color using (SKPaint brush = new() { Style = SKPaintStyle.Fill, Color = borderSkin.PageColor }) { graph.FillRectangle(brush, rect); } // Top/Left shadow SKRect shadowRect = absolute; shadowRect.Right -= radius * .3f; shadowRect.Bottom -= radius * .3f; graph.DrawRoundedRectShadowAbs(shadowRect, cornerRadius, radius + 1 * resolution / 96.0f, shadowLightColor, borderSkin.PageColor, 1.4f); // Bottom/Right shadow shadowRect = absolute; shadowRect.Left = absolute.Left + radius / 3f; shadowRect.Top = absolute.Top + radius / 3f; shadowRect.Right -= radius / 3.5f; shadowRect.Bottom -= radius / 3.5f; graph.DrawRoundedRectShadowAbs(shadowRect, cornerRadius, radius, shadowColor, borderSkin.PageColor, 1.3f); // Draw Background shadowRect = absolute; shadowRect.Left = absolute.Left + 3f * resolution / 96.0f; shadowRect.Top = absolute.Top + 3f * resolution / 96.0f; shadowRect.Right -= radius * .75f; shadowRect.Bottom -= radius * .75f; SKPath path = ChartGraphics.CreateRoundedRectPath(shadowRect, cornerRadius); graph.DrawPathAbs( path, backColor, backHatchStyle, backImage, backImageWrapMode, backImageTransparentColor, backImageAlign, backGradientStyle, backSecondaryColor, borderColor, borderWidth, borderDashStyle, PenAlignment.Inset); // Dispose Graphic path if (path != null) { path.Dispose(); } // Bottom/Right inner shadow SKRegion innerShadowRegion = new( ChartGraphics.CreateRoundedRectPath( new SKRect( shadowRect.Left - radius, shadowRect.Top - radius, shadowRect.Width + radius - radius * 0.25f, shadowRect.Height + radius - radius * 0.25f), cornerRadius)); innerShadowRegion.Op(ChartGraphics.CreateRoundedRectPath(shadowRect, cornerRadius), SKRegionOperation.Difference); graph.Clip = innerShadowRegion; graph.DrawRoundedRectShadowAbs( shadowRect, cornerRadius, radius, SKColors.Transparent, new(SKColors.Gray.Red, SKColors.Gray.Green, SKColors.Gray.Blue, 128), .5f); graph.Clip = new(); }
/// <summary> /// Paints annotation object on specified graphics. /// </summary> /// <param name="graphics"> /// A <see cref="ChartGraphics"/> used to paint annotation object. /// </param> /// <param name="chart"> /// Reference to the <see cref="ChartService"/> control. /// </param> override internal void Paint(ChartService chart, ChartGraphics graphics) { // Get annotation position in relative coordinates GetRelativePosition(out SKPoint firstPoint, out SKSize size, out _); SKPoint secondPoint = new(firstPoint.X + size.Width, firstPoint.Y + size.Height); // Create selection rectangle SKRect selectionRect = new(firstPoint.X, firstPoint.Y, secondPoint.X, secondPoint.Y); // Check if text position is valid if (float.IsNaN(firstPoint.X) || float.IsNaN(firstPoint.Y) || float.IsNaN(secondPoint.X) || float.IsNaN(secondPoint.Y)) { return; } // Get arrow shape path using SKPath arrowPathAbs = GetArrowPath(graphics, selectionRect); // Draw arrow shape if (Common.ProcessModePaint) { graphics.DrawPathAbs( arrowPathAbs, (BackColor == SKColor.Empty) ? SKColors.White : BackColor, BackHatchStyle, String.Empty, ChartImageWrapMode.Scaled, SKColor.Empty, ChartImageAlignmentStyle.Center, BackGradientStyle, BackSecondaryColor, LineColor, LineWidth, LineDashStyle, PenAlignment.Center, ShadowOffset, ShadowColor); } // Process hot region if (Common.ProcessModeRegions) { // Use callout defined hot region Common.HotRegionsList.AddHotRegion( graphics, arrowPathAbs, false, ReplaceKeywords(ToolTip), String.Empty, String.Empty, String.Empty, this, ChartElementType.Annotation); } // Paint selection handles PaintSelectionHandles(graphics, selectionRect, null); }
/// <summary> /// Get arrow path for the specified annotation position /// </summary> /// <param name="graphics"></param> /// <param name="position"></param> /// <returns></returns> private SKPath GetArrowPath( ChartGraphics graphics, SKRect position) { // Get absolute position SKRect positionAbs = graphics.GetAbsoluteRectangle(position); SKPoint firstPoint = positionAbs.Location; SKPoint secondPoint = new(positionAbs.Right, positionAbs.Bottom); // Calculate arrow length float deltaX = secondPoint.X - firstPoint.X; float deltaY = secondPoint.Y - firstPoint.Y; float arrowLength = (float)Math.Sqrt(deltaX * deltaX + deltaY * deltaY); // Create unrotated graphics path for the arrow started at the annotation location // and going to the right for the length of the rotated arrow. SKPath path = new(); float pointerRatio = 2.1f; SKPoint[] points; if (ArrowStyle == ArrowStyle.Simple) { points = new SKPoint[] { firstPoint, new SKPoint(firstPoint.X + ArrowSize * pointerRatio, firstPoint.Y - ArrowSize * pointerRatio), new SKPoint(firstPoint.X + ArrowSize * pointerRatio, firstPoint.Y - ArrowSize), new SKPoint(firstPoint.X + arrowLength, firstPoint.Y - ArrowSize), new SKPoint(firstPoint.X + arrowLength, firstPoint.Y + ArrowSize), new SKPoint(firstPoint.X + ArrowSize * pointerRatio, firstPoint.Y + ArrowSize), new SKPoint(firstPoint.X + ArrowSize * pointerRatio, firstPoint.Y + ArrowSize * pointerRatio) }; } else if (ArrowStyle == ArrowStyle.DoubleArrow) { points = new SKPoint[] { firstPoint, new SKPoint(firstPoint.X + ArrowSize * pointerRatio, firstPoint.Y - ArrowSize * pointerRatio), new SKPoint(firstPoint.X + ArrowSize * pointerRatio, firstPoint.Y - ArrowSize), new SKPoint(firstPoint.X + arrowLength - ArrowSize * pointerRatio, firstPoint.Y - ArrowSize), new SKPoint(firstPoint.X + arrowLength - ArrowSize * pointerRatio, firstPoint.Y - ArrowSize * pointerRatio), new SKPoint(firstPoint.X + arrowLength, firstPoint.Y), new SKPoint(firstPoint.X + arrowLength - ArrowSize * pointerRatio, firstPoint.Y + ArrowSize * pointerRatio), new SKPoint(firstPoint.X + arrowLength - ArrowSize * pointerRatio, firstPoint.Y + ArrowSize), new SKPoint(firstPoint.X + ArrowSize * pointerRatio, firstPoint.Y + ArrowSize), new SKPoint(firstPoint.X + ArrowSize * pointerRatio, firstPoint.Y + ArrowSize * pointerRatio) }; } else if (ArrowStyle == ArrowStyle.Tailed) { float tailRatio = 2.1f; points = new SKPoint[] { firstPoint, new SKPoint(firstPoint.X + ArrowSize * pointerRatio, firstPoint.Y - ArrowSize * pointerRatio), new SKPoint(firstPoint.X + ArrowSize * pointerRatio, firstPoint.Y - ArrowSize), new SKPoint(firstPoint.X + arrowLength, firstPoint.Y - ArrowSize * tailRatio), new SKPoint(firstPoint.X + arrowLength - ArrowSize * tailRatio, firstPoint.Y), new SKPoint(firstPoint.X + arrowLength, firstPoint.Y + ArrowSize * tailRatio), new SKPoint(firstPoint.X + ArrowSize * pointerRatio, firstPoint.Y + ArrowSize), new SKPoint(firstPoint.X + ArrowSize * pointerRatio, firstPoint.Y + ArrowSize * pointerRatio) }; } else { throw (new InvalidOperationException(SR.ExceptionAnnotationArrowStyleUnknown)); } path.AddLines(points); path.Close(); // Calculate arrow angle float angle = (float)(Math.Atan(deltaY / deltaX) * 180f / Math.PI); if (deltaX < 0) { angle += 180f; } // Rotate arrow path around the first point SKMatrix matrix = SKMatrix.CreateRotationDegrees(angle, firstPoint.X, firstPoint.Y); path.Transform(matrix); return(path); }
/// <summary> /// Return intensity of lightStyle for Polygons. There are tree types of lights: None, /// Simplistic and Realistic. None Style have same lightStyle intensity on /// all polygons. Normal vector doesn’t have influence on this type /// of lighting. Simplistic style have lightStyle source, which is /// rotated together with scene. Realistic lighting have fixed lightStyle /// source and intensity of lightStyle is change when scene is rotated. /// </summary> /// <param name="points">Points of the polygon</param> /// <param name="surfaceColor">Color used for polygons without lighting</param> /// <param name="visiblePolygon">This flag gets information if polygon is visible or not.</param> /// <param name="rotation">Y angle ( from -90 to 90 ) Should be used width switchSeriesOrder to get from -180 to 180</param> /// <param name="surfaceName">Used for lighting of front - back and left - right sides</param> /// <param name="switchSeriesOrder">Used to calculate real y angle</param> /// <returns>Color corrected with intensity of lightStyle</returns> internal SKColor GetPolygonLight(Point3D[] points, SKColor surfaceColor, bool visiblePolygon, float rotation, SurfaceNames surfaceName, bool switchSeriesOrder) { // Corrected color SKColor color = surfaceColor; // Direction of lightStyle source Point3D lightSource; lightSource = new Point3D(0F, 0F, 1F); // There are tree different lightStyle styles: None, Simplistic and realistic. switch (_lightStyle) { // LightStyle style is None case LightStyle.None: { // Use same color break; } // LightStyle style is Simplistic case LightStyle.Simplistic: { // Find two vectors of polygon Point3D firstVector = new(); firstVector.X = points[0].X - points[1].X; firstVector.Y = points[0].Y - points[1].Y; firstVector.Z = points[0].Z - points[1].Z; Point3D secondVector = new(); secondVector.X = points[2].X - points[1].X; secondVector.Y = points[2].Y - points[1].Y; secondVector.Z = points[2].Z - points[1].Z; // Find Normal vector for Polygon Point3D normalVector = new(); normalVector.X = firstVector.Y * secondVector.Z - firstVector.Z * secondVector.Y; normalVector.Y = firstVector.Z * secondVector.X - firstVector.X * secondVector.Z; normalVector.Z = firstVector.X * secondVector.Y - firstVector.Y * secondVector.X; // Polygon is left side ( like side of area chart ) if (surfaceName == SurfaceNames.Left) { color = ChartGraphics.GetGradientColor(surfaceColor, SKColors.Black, 0.15); } // Polygon is right side ( like side of area chart ) else if (surfaceName == SurfaceNames.Right) { color = ChartGraphics.GetGradientColor(surfaceColor, SKColors.Black, 0.15); } // Polygon is front side ( like side of area chart ) else if (surfaceName == SurfaceNames.Front) { color = surfaceColor; } // Polygon is back side ( like side of area chart ) else if (surfaceName == SurfaceNames.Back) { color = surfaceColor; } // Polygon has angle with bottom side ( Line chart or top of area chart ) else { float angleLeft; float angleRight; // Find angles between lightStyle and polygon for different y-axis angles. if (switchSeriesOrder) { if (rotation > 0 && rotation <= 90) { angleLeft = GetAngle(normalVector, _lightVectors[3]); angleRight = GetAngle(normalVector, _lightVectors[4]); } else { angleLeft = GetAngle(normalVector, _lightVectors[4]); angleRight = GetAngle(normalVector, _lightVectors[3]); } } else { if (rotation > 0 && rotation <= 90) { angleLeft = GetAngle(normalVector, _lightVectors[4]); angleRight = GetAngle(normalVector, _lightVectors[3]); } else { angleLeft = GetAngle(normalVector, _lightVectors[3]); angleRight = GetAngle(normalVector, _lightVectors[4]); } } if (Math.Abs(angleLeft - angleRight) < 0.01) { color = ChartGraphics.GetGradientColor(surfaceColor, SKColors.Black, 0.25); } else if (angleLeft < angleRight) { color = ChartGraphics.GetGradientColor(surfaceColor, SKColors.Black, 0.25); } else { color = ChartGraphics.GetGradientColor(surfaceColor, SKColors.Black, 0.15); } } break; } // LightStyle style is Realistic default: { // Find two vectors of polygon Point3D firstVector = new(); firstVector.X = points[0].X - points[1].X; firstVector.Y = points[0].Y - points[1].Y; firstVector.Z = points[0].Z - points[1].Z; Point3D secondVector = new(); secondVector.X = points[2].X - points[1].X; secondVector.Y = points[2].Y - points[1].Y; secondVector.Z = points[2].Z - points[1].Z; // Find Normal vector for Polygon Point3D normalVector = new(); normalVector.X = firstVector.Y * secondVector.Z - firstVector.Z * secondVector.Y; normalVector.Y = firstVector.Z * secondVector.X - firstVector.X * secondVector.Z; normalVector.Z = firstVector.X * secondVector.Y - firstVector.Y * secondVector.X; // ****************************************************************** // Color correction. Angle between Normal vector of polygon and // vector of lightStyle source is used. // ****************************************************************** if (surfaceName == SurfaceNames.Front) { lightSource.Z *= -1; color = GetBrightGradientColor(surfaceColor, GetAngle(lightSource, _lightVectors[2]) / Math.PI); } else if (surfaceName == SurfaceNames.Back) { lightSource.Z *= -1; color = GetBrightGradientColor(surfaceColor, GetAngle(lightSource, _lightVectors[1]) / Math.PI); } else { if (visiblePolygon) { lightSource.Z *= -1; } color = GetBrightGradientColor(surfaceColor, GetAngle(lightSource, normalVector) / Math.PI); } break; } } return(color); }
/// <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> /// 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> /// Return intensity of lightStyle for 3D Cube. There are tree types of lights: None, /// Simplistic and Realistic. None Style have same lightStyle intensity on /// all polygons. Normal vector doesn’t have influence on this type /// of lighting. Simplistic style have lightStyle source, which is /// rotated together with scene. Realistic lighting have fixed lightStyle /// source and intensity of lightStyle is change when scene is rotated. /// </summary> /// <param name="surfaceColor">Color used for polygons without lighting</param> /// <param name="front">Color corrected with intensity of lightStyle for Front side of the 3D Rectangle</param> /// <param name="back">Color corrected with intensity of lightStyle for Back side of the 3D Rectangle</param> /// <param name="left">Color corrected with intensity of lightStyle for Left side of the 3D Rectangle</param> /// <param name="right">Color corrected with intensity of lightStyle for Right side of the 3D Rectangle</param> /// <param name="top">Color corrected with intensity of lightStyle for Top side of the 3D Rectangle</param> /// <param name="bottom">Color corrected with intensity of lightStyle for Bottom side of the 3D Rectangle</param> internal void GetLight(SKColor surfaceColor, out SKColor front, out SKColor back, out SKColor left, out SKColor right, out SKColor top, out SKColor bottom) { switch (_lightStyle) { // LightStyle style is None case LightStyle.None: { front = surfaceColor; left = surfaceColor; top = surfaceColor; back = surfaceColor; right = surfaceColor; bottom = surfaceColor; break; } // LightStyle style is Simplistic case LightStyle.Simplistic: { front = surfaceColor; left = ChartGraphics.GetGradientColor(surfaceColor, SKColors.Black, 0.25); top = ChartGraphics.GetGradientColor(surfaceColor, SKColors.Black, 0.15); back = surfaceColor; right = ChartGraphics.GetGradientColor(surfaceColor, SKColors.Black, 0.25); bottom = ChartGraphics.GetGradientColor(surfaceColor, SKColors.Black, 0.15); break; } // LightStyle style is Realistic default: { // For Right Axis angle Realistic lightStyle should be different if (_rightAngleAxis) { // LightStyle source Vector Point3D lightSource = new(0F, 0F, -1F); Point3D[] rightPRpoints = new Point3D[1]; rightPRpoints[0] = lightSource; RightAngleProjection(rightPRpoints); // ****************************************************************** // Color correction. Angle between Normal vector of polygon and // vector of lightStyle source is used. // ****************************************************************** if (_angleY >= 45 || _angleY <= -45) { front = ChartGraphics.GetGradientColor(surfaceColor, SKColors.Black, GetAngle(lightSource, _lightVectors[1]) / Math.PI); back = ChartGraphics.GetGradientColor(surfaceColor, SKColors.Black, GetAngle(lightSource, _lightVectors[2]) / Math.PI); left = ChartGraphics.GetGradientColor(surfaceColor, SKColors.Black, 0); right = ChartGraphics.GetGradientColor(surfaceColor, SKColors.Black, 0); } else { front = ChartGraphics.GetGradientColor(surfaceColor, SKColors.Black, 0); back = ChartGraphics.GetGradientColor(surfaceColor, SKColors.Black, 1); left = ChartGraphics.GetGradientColor(surfaceColor, SKColors.Black, GetAngle(lightSource, _lightVectors[3]) / Math.PI); right = ChartGraphics.GetGradientColor(surfaceColor, SKColors.Black, GetAngle(lightSource, _lightVectors[4]) / Math.PI); } top = ChartGraphics.GetGradientColor(surfaceColor, SKColors.Black, GetAngle(lightSource, _lightVectors[5]) / Math.PI); bottom = ChartGraphics.GetGradientColor(surfaceColor, SKColors.Black, GetAngle(lightSource, _lightVectors[6]) / Math.PI); } else { // LightStyle source Vector Point3D lightSource = new(0F, 0F, 1F); // ****************************************************************** // Color correction. Angle between Normal vector of polygon and // vector of lightStyle source is used. // ****************************************************************** front = GetBrightGradientColor(surfaceColor, GetAngle(lightSource, _lightVectors[1]) / Math.PI); back = GetBrightGradientColor(surfaceColor, GetAngle(lightSource, _lightVectors[2]) / Math.PI); left = GetBrightGradientColor(surfaceColor, GetAngle(lightSource, _lightVectors[3]) / Math.PI); right = GetBrightGradientColor(surfaceColor, GetAngle(lightSource, _lightVectors[4]) / Math.PI); top = GetBrightGradientColor(surfaceColor, GetAngle(lightSource, _lightVectors[5]) / Math.PI); bottom = GetBrightGradientColor(surfaceColor, GetAngle(lightSource, _lightVectors[6]) / Math.PI); } break; } } }
/// <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 marker that represents a data point in FastPoint series. /// </summary> /// <param name="graph">Chart graphics used to draw the marker.</param> /// <param name="point">Series data point drawn.</param> /// <param name="pointIndex">Data point index in the series.</param> /// <param name="location">Marker location in pixels.</param> /// <param name="markerStyle">Marker style.</param> /// <param name="markerSize">Marker size in pixels.</param> /// <param name="brush">Brush used to fill marker shape.</param> /// <param name="borderPen">Marker border pen.</param> virtual protected void DrawMarker( ChartGraphics graph, DataPoint point, int pointIndex, SKPoint location, MarkerStyle markerStyle, int markerSize, SKPaint brush, SKPaint borderPen) { // Transform 3D coordinates if (chartArea3DEnabled) { Point3D[] points = new Point3D[1]; location = graph.GetRelativePoint(location); points[0] = new Point3D(location.X, location.Y, seriesZCoordinate); matrix3D.TransformPoints(points); location.X = points[0].X; location.Y = points[0].Y; location = graph.GetAbsolutePoint(location); } // Create marker bounding rectangle in pixels SKRect markerBounds = new( location.X - markerSize / 2f, location.Y - markerSize / 2f, markerSize, markerSize); // Draw Marker switch (markerStyle) { case (MarkerStyle.Star4): case (MarkerStyle.Star5): case (MarkerStyle.Star6): case (MarkerStyle.Star10): { // Set number of corners int cornerNumber = 4; if (markerStyle == MarkerStyle.Star5) { cornerNumber = 5; } else if (markerStyle == MarkerStyle.Star6) { cornerNumber = 6; } else if (markerStyle == MarkerStyle.Star10) { cornerNumber = 10; } // Get star polygon SKPoint[] points = ChartGraphics.CreateStarPolygon(markerBounds, cornerNumber); // Fill shape graph.FillPolygon(brush, points); // Draw border if (borderPen != null) { graph.DrawPolygon(borderPen, points); } break; } case (MarkerStyle.Circle): { graph.FillEllipse(brush, markerBounds); // Draw border if (borderPen != null) { graph.DrawEllipse(borderPen, markerBounds); } break; } case (MarkerStyle.Square): { graph.FillRectangle(brush, markerBounds); // Draw border if (borderPen != null) { graph.DrawRectangle( borderPen, (int)Math.Round(markerBounds.Left, 0), (int)Math.Round(markerBounds.Top, 0), (int)Math.Round(markerBounds.Width, 0), (int)Math.Round(markerBounds.Height, 0)); } break; } case (MarkerStyle.Cross): { // Calculate cross line width and size float crossLineWidth = (float)Math.Ceiling(markerSize / 4F); float crossSize = markerSize; // * (float)Math.Sin(45f/180f*Math.PI) // Calculate cross coordinates SKPoint[] points = new SKPoint[12]; points[0].X = location.X - crossSize / 2F; points[0].Y = location.Y + crossLineWidth / 2F; points[1].X = location.X - crossSize / 2F; points[1].Y = location.Y - crossLineWidth / 2F; points[2].X = location.X - crossLineWidth / 2F; points[2].Y = location.Y - crossLineWidth / 2F; points[3].X = location.X - crossLineWidth / 2F; points[3].Y = location.Y - crossSize / 2F; points[4].X = location.X + crossLineWidth / 2F; points[4].Y = location.Y - crossSize / 2F; points[5].X = location.X + crossLineWidth / 2F; points[5].Y = location.Y - crossLineWidth / 2F; points[6].X = location.X + crossSize / 2F; points[6].Y = location.Y - crossLineWidth / 2F; points[7].X = location.X + crossSize / 2F; points[7].Y = location.Y + crossLineWidth / 2F; points[8].X = location.X + crossLineWidth / 2F; points[8].Y = location.Y + crossLineWidth / 2F; points[9].X = location.X + crossLineWidth / 2F; points[9].Y = location.Y + crossSize / 2F; points[10].X = location.X - crossLineWidth / 2F; points[10].Y = location.Y + crossSize / 2F; points[11].X = location.X - crossLineWidth / 2F; points[11].Y = location.Y + crossLineWidth / 2F; // Rotate cross coordinates 45 degrees SKMatrix rotationMatrix = SKMatrix.CreateRotationDegrees(45, location.X, location.Y); rotationMatrix.TransformPoints(points); // Fill shape graph.FillPolygon(brush, points); // Draw border if (borderPen != null) { graph.DrawPolygon(borderPen, points); } break; } case (MarkerStyle.Diamond): { SKPoint[] points = new SKPoint[4]; points[0].X = markerBounds.Left; points[0].Y = markerBounds.Top + markerBounds.Height / 2F; points[1].X = markerBounds.Left + markerBounds.Width / 2F; points[1].Y = markerBounds.Top; points[2].X = markerBounds.Right; points[2].Y = markerBounds.Top + markerBounds.Height / 2F; points[3].X = markerBounds.Left + markerBounds.Width / 2F; points[3].Y = markerBounds.Bottom; graph.FillPolygon(brush, points); // Draw border if (borderPen != null) { graph.DrawPolygon(borderPen, points); } break; } case (MarkerStyle.Triangle): { SKPoint[] points = new SKPoint[3]; points[0].X = markerBounds.Left; points[0].Y = markerBounds.Bottom; points[1].X = markerBounds.Top + markerBounds.Width / 2F; points[1].Y = markerBounds.Top; points[2].X = markerBounds.Right; points[2].Y = markerBounds.Bottom; graph.FillPolygon(brush, points); // Draw border if (borderPen != null) { graph.DrawPolygon(borderPen, points); } break; } default: { throw (new InvalidOperationException(SR.ExceptionFastPointMarkerStyleUnknown)); } } // Process selection regions if (Common.ProcessModeRegions) { Common.HotRegionsList.AddHotRegion( graph.GetRelativeRectangle(markerBounds), point, point.series.Name, pointIndex); } }
/// <summary> /// Draws chart area cursor and selection. /// </summary> /// <param name="graph">Reference to the ChartGraphics object.</param> internal void Paint(ChartGraphics graph) { //*************************************************** //** Prepare for drawing //*************************************************** // Do not proceed with painting if cursor is not attached to the axis if (GetAxis() == null || _chartArea == null || _chartArea.Common == null || _chartArea.Common.ChartPicture == null || _chartArea.Common.ChartPicture.isPrinting) { return; } // Get plot area position SKRect plotAreaPosition = _chartArea.PlotAreaPosition.ToSKRect(); // Detect if cursor is horizontal or vertical bool horizontal = true; if (GetAxis().AxisPosition == AxisPosition.Bottom || GetAxis().AxisPosition == AxisPosition.Top) { horizontal = false; } //*************************************************** //** Draw selection //*************************************************** // Check if selection need to be drawn if (_drawSelection && !double.IsNaN(SelectionStart) && !double.IsNaN(SelectionEnd) && SelectionColor != SKColor.Empty) { // Calculate selection rectangle SKRect rectSelection = GetSelectionRect(plotAreaPosition); rectSelection.Intersect(plotAreaPosition); // Get opposite axis selection rectangle SKRect rectOppositeSelection = GetOppositeSelectionRect(plotAreaPosition); // Draw selection if rectangle is not empty if (!rectSelection.IsEmpty && rectSelection.Width > 0 && rectSelection.Height > 0) { // Limit selection rectangle to the area of the opposite selection if (!rectOppositeSelection.IsEmpty && rectOppositeSelection.Width > 0 && rectOppositeSelection.Height > 0) { rectSelection.Intersect(rectOppositeSelection); // We do not need to draw selection in the opposite axis Cursor oppositeCursor = (_attachedToXAxis == AxisName.X || _attachedToXAxis == AxisName.X2) ? _chartArea.CursorY : _chartArea.CursorX; oppositeCursor._drawSelection = false; } // Make sure selection is inside plotting area rectSelection.Intersect(plotAreaPosition); // If selection rectangle is not empty if (rectSelection.Width > 0 && rectSelection.Height > 0) { // Add transparency to solid colors var rangeSelectionColor = SelectionColor; if (rangeSelectionColor.Alpha == 255) { rangeSelectionColor = new(rangeSelectionColor.Red, rangeSelectionColor.Green, rangeSelectionColor.Blue, 120); } // Draw selection graph.FillRectangleRel(rectSelection, rangeSelectionColor, ChartHatchStyle.None, "", ChartImageWrapMode.Tile, SKColor.Empty, ChartImageAlignmentStyle.Center, GradientStyle.None, SKColor.Empty, SKColor.Empty, 0, ChartDashStyle.NotSet, SKColor.Empty, 0, PenAlignment.Inset); } } } //*************************************************** //** Draw cursor //*************************************************** // Check if cursor need to be drawn if (!double.IsNaN(Position) && LineColor != SKColor.Empty && LineWidth > 0 && LineDashStyle != ChartDashStyle.NotSet) { // Calculate line position bool insideArea = false; SKPoint point1 = SKPoint.Empty; SKPoint point2 = SKPoint.Empty; if (horizontal) { // Set cursor coordinates point1.X = plotAreaPosition.Left; point1.Y = (float)GetAxis().GetLinearPosition(Position); point2.X = plotAreaPosition.Right; point2.Y = point1.Y; // Check if cursor is inside plotting rect if (point1.Y >= plotAreaPosition.Top && point1.Y <= plotAreaPosition.Bottom) { insideArea = true; } } else { // Set cursor coordinates point1.X = (float)GetAxis().GetLinearPosition(Position); point1.Y = plotAreaPosition.Top; point2.X = point1.X; point2.Y = plotAreaPosition.Bottom; // Check if cursor is inside plotting rect if (point1.X >= plotAreaPosition.Left && point1.X <= plotAreaPosition.Right) { insideArea = true; } } // Draw cursor if it's inside the chart area plotting rectangle if (insideArea) { graph.DrawLineRel(LineColor, LineWidth, LineDashStyle, point1, point2); } } // Reset draw selection flag _drawSelection = true; }
/// <summary> /// Draws 3D button in the scroll bar /// </summary> /// <param name="graph">Chart graphics.</param> /// <param name="buttonRect">Button position.</param> /// <param name="pressedState">Indicates that button is pressed.</param> /// <param name="buttonType">Button type to draw.</param> internal void PaintScrollBar3DButton( ChartGraphics graph, SKRect buttonRect, bool pressedState, ScrollBarButtonType buttonType) { // Page Up/Down buttons do not require drawing if (buttonType == ScrollBarButtonType.LargeIncrement || buttonType == ScrollBarButtonType.LargeDecrement) { return; } // Get 3 levels of colors for button drawing var darkerColor = ChartGraphics.GetGradientColor(_buttonCurrentColor, SKColors.Black, 0.5); var darkestColor = ChartGraphics.GetGradientColor(_buttonCurrentColor, SKColors.Black, 0.8); var lighterColor = ChartGraphics.GetGradientColor(_buttonCurrentColor, SKColors.White, 0.5); // Fill button rectangle background graph.FillRectangleRel( buttonRect, _buttonCurrentColor, ChartHatchStyle.None, "", ChartImageWrapMode.Tile, SKColor.Empty, ChartImageAlignmentStyle.Center, GradientStyle.None, SKColor.Empty, darkerColor, (pressedState) ? 1 : 0, ChartDashStyle.Solid, SKColor.Empty, 0, PenAlignment.Outset); // Check if 2 or 1 pixel border will be drawn (if size too small) bool singlePixelBorder = Size <= 12; // Draw 3D effect around the button when not pressed if (!pressedState) { // Get relative size of 1 pixel SKSize pixelRelativeSize = new(1, 1); pixelRelativeSize = graph.GetRelativeSize(pixelRelativeSize); // Draw top/left border with button color graph.DrawLineRel( (singlePixelBorder) ? lighterColor : _buttonCurrentColor, 1, ChartDashStyle.Solid, new SKPoint(buttonRect.Left, buttonRect.Bottom), new SKPoint(buttonRect.Left, buttonRect.Top)); graph.DrawLineRel( (singlePixelBorder) ? lighterColor : _buttonCurrentColor, 1, ChartDashStyle.Solid, new SKPoint(buttonRect.Left, buttonRect.Top), new SKPoint(buttonRect.Right, buttonRect.Top)); // Draw right/bottom border with the darkest color graph.DrawLineRel( (singlePixelBorder) ? darkerColor : darkestColor, 1, ChartDashStyle.Solid, new SKPoint(buttonRect.Right, buttonRect.Bottom), new SKPoint(buttonRect.Right, buttonRect.Top)); graph.DrawLineRel( (singlePixelBorder) ? darkerColor : darkestColor, 1, ChartDashStyle.Solid, new SKPoint(buttonRect.Left, buttonRect.Bottom), new SKPoint(buttonRect.Right, buttonRect.Bottom)); if (!singlePixelBorder) { // Draw right/bottom border (offset 1) with the dark color graph.DrawLineRel( darkerColor, 1, ChartDashStyle.Solid, new SKPoint(buttonRect.Right - pixelRelativeSize.Width, buttonRect.Bottom - pixelRelativeSize.Height), new SKPoint(buttonRect.Right - pixelRelativeSize.Width, buttonRect.Top + pixelRelativeSize.Height)); graph.DrawLineRel( darkerColor, 1, ChartDashStyle.Solid, new SKPoint(buttonRect.Left + pixelRelativeSize.Width, buttonRect.Bottom - pixelRelativeSize.Height), new SKPoint(buttonRect.Right - pixelRelativeSize.Width, buttonRect.Bottom - pixelRelativeSize.Height)); // Draw top/left border (offset 1) with lighter color graph.DrawLineRel( lighterColor, 1, ChartDashStyle.Solid, new SKPoint(buttonRect.Left + pixelRelativeSize.Width, buttonRect.Bottom - pixelRelativeSize.Height), new SKPoint(buttonRect.Left + pixelRelativeSize.Width, buttonRect.Top + pixelRelativeSize.Height)); graph.DrawLineRel( lighterColor, 1, ChartDashStyle.Solid, new SKPoint(buttonRect.Left + pixelRelativeSize.Width, buttonRect.Left + pixelRelativeSize.Height), new SKPoint(buttonRect.Right - pixelRelativeSize.Width, buttonRect.Left + pixelRelativeSize.Height)); } } // Check axis orientation bool verticalAxis = (axis.AxisPosition == AxisPosition.Left || axis.AxisPosition == AxisPosition.Right); // Set graphics transformation for button pressed mode float pressedShifting = (singlePixelBorder) ? 0.5f : 1f; if (pressedState) { graph.TranslateTransform(pressedShifting, pressedShifting); } // Draw button image SKRect buttonAbsRect = graph.GetAbsoluteRectangle(buttonRect); float imageOffset = (singlePixelBorder) ? 2 : 3; switch (buttonType) { case (ScrollBarButtonType.SmallDecrement): { // Calculate triangal points position SKPoint[] points = new SKPoint[3]; if (verticalAxis) { points[0].X = buttonAbsRect.Left + imageOffset; points[0].Y = buttonAbsRect.Top + (imageOffset + 1f); points[1].X = buttonAbsRect.Left + buttonAbsRect.Width / 2f; points[1].Y = buttonAbsRect.Bottom - imageOffset; points[2].X = buttonAbsRect.Right - imageOffset; points[2].Y = buttonAbsRect.Top + (imageOffset + 1f); } else { points[0].X = buttonAbsRect.Left + imageOffset; points[0].Y = buttonAbsRect.Top + buttonAbsRect.Height / 2f; points[1].X = buttonAbsRect.Right - (imageOffset + 1f); points[1].Y = buttonAbsRect.Top + imageOffset; points[2].X = buttonAbsRect.Right - (imageOffset + 1f); points[2].Y = buttonAbsRect.Bottom - imageOffset; } using var brush = new SKPaint { Style = SKPaintStyle.Fill, Color = _lineCurrentColor }; graph.FillPolygon(brush, points); break; } case (ScrollBarButtonType.SmallIncrement): { // Calculate triangal points position SKPoint[] points = new SKPoint[3]; if (verticalAxis) { points[0].X = buttonAbsRect.Left + imageOffset; points[0].Y = buttonAbsRect.Bottom - (imageOffset + 1f); points[1].X = buttonAbsRect.Left + buttonAbsRect.Width / 2f; points[1].Y = buttonAbsRect.Top + imageOffset; points[2].X = buttonAbsRect.Right - imageOffset; points[2].Y = buttonAbsRect.Bottom - (imageOffset + 1f); } else { points[0].X = buttonAbsRect.Right - imageOffset; points[0].Y = buttonAbsRect.Top + buttonAbsRect.Height / 2f; points[1].X = buttonAbsRect.Left + (imageOffset + 1f); points[1].Y = buttonAbsRect.Top + imageOffset; points[2].X = buttonAbsRect.Left + (imageOffset + 1f); points[2].Y = buttonAbsRect.Bottom - imageOffset; } using var brush = new SKPaint { Style = SKPaintStyle.Fill, Color = _lineCurrentColor }; graph.FillPolygon(brush, points); break; } case (ScrollBarButtonType.ZoomReset): { // Draw circule with a minus sign using var pen = new SKPaint { Style = SKPaintStyle.Fill, Color = _lineCurrentColor }; graph.DrawEllipse(pen, buttonAbsRect.Left + imageOffset - 0.5f, buttonAbsRect.Top + imageOffset - 0.5f, buttonAbsRect.Width - 2f * imageOffset, buttonAbsRect.Height - 2f * imageOffset); graph.DrawLine(pen, buttonAbsRect.Left + imageOffset + 1.5f, buttonAbsRect.Top + buttonAbsRect.Height / 2f - 0.5f, buttonAbsRect.Right - imageOffset - 2.5f, buttonAbsRect.Top + buttonAbsRect.Height / 2f - 0.5f); break; } } // Reset graphics transformation for button pressed mode if (pressedState) { graph.TranslateTransform(-pressedShifting, -pressedShifting); } }
/// <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(); } }
/// <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); }
/// <summary> /// Paints an annotation object on the specified graphics. /// </summary> /// <param name="graphics"> /// A <see cref="ChartGraphics"/> object, used to paint an annotation object. /// </param> /// <param name="chart"> /// Reference to the <see cref="ChartService"/> owner control. /// </param> override internal void Paint(ChartService chart, ChartGraphics graphics) { // Get annotation position in relative coordinates GetRelativePosition(out SKPoint firstPoint, out SKSize size, out SKPoint anchorPoint); SKPoint secondPoint = new(firstPoint.X + size.Width, firstPoint.Y + size.Height); // Create selection rectangle SKRect selectionRect = new(firstPoint.X, firstPoint.Y, secondPoint.X, secondPoint.Y); // Adjust coordinates AdjustLineCoordinates(ref firstPoint, ref secondPoint, ref selectionRect); // Check if text position is valid if (float.IsNaN(firstPoint.X) || float.IsNaN(firstPoint.Y) || float.IsNaN(secondPoint.X) || float.IsNaN(secondPoint.Y)) { return; } // TODO: this #if false // Set line caps bool capChanged = false; SKStrokeCap oldCap = SKStrokeCap.Butt; if (_startCap != LineAnchorCapStyle.None || _endCap != LineAnchorCapStyle.None) { capChanged = true; oldCap = graphics.Pen.StrokeCap; // Apply anchor cap settings if (_startCap == LineAnchorCapStyle.Arrow) { // Adjust arrow size for small line width if (LineWidth < 4) { int adjustment = 3 - LineWidth; graphics.Pen.StrokeCap = LineCap.Custom; graphics.Pen.CustomStartCap = new AdjustableArrowCap( LineWidth + adjustment, LineWidth + adjustment, true); } else { graphics.Pen.StartCap = LineCap.ArrowAnchor; } } else if (_startCap == LineAnchorCapStyle.Diamond) { graphics.Pen.StartCap = LineCap.DiamondAnchor; } else if (_startCap == LineAnchorCapStyle.Round) { graphics.Pen.StartCap = LineCap.RoundAnchor; } else if (_startCap == LineAnchorCapStyle.Square) { graphics.Pen.StartCap = LineCap.SquareAnchor; } if (_endCap == LineAnchorCapStyle.Arrow) { // Adjust arrow size for small line width if (LineWidth < 4) { int adjustment = 3 - LineWidth; graphics.Pen.StrokeCap = SKStrokeCap.Round; // graphics.Pen.EndCap = LineCap.Custom; // graphics.Pen.CustomEndCap = new AdjustableArrowCap( // this.LineWidth + adjustment, // this.LineWidth + adjustment, // true); } else { graphics.Pen.StrokeCap = SKStrokeCap.Round;//LineCap.ArrowAnchor; } } else if (_endCap == LineAnchorCapStyle.Diamond) { graphics.Pen.EndCap = LineCap.DiamondAnchor; } else if (_endCap == LineAnchorCapStyle.Round) { graphics.Pen.EndCap = LineCap.RoundAnchor; } else if (_endCap == LineAnchorCapStyle.Square) { graphics.Pen.EndCap = LineCap.SquareAnchor; } } if (Common.ProcessModePaint) { // Draw line graphics.DrawLineRel( LineColor, LineWidth, LineDashStyle, firstPoint, secondPoint, ShadowColor, ShadowOffset); } // Restore line caps if (capChanged) { graphics.Pen.StartCap = oldStartCap; graphics.Pen.EndCap = oldEndCap; } #endif // Paint selection handles PaintSelectionHandles(graphics, selectionRect, null); }
/// <summary> /// Draws 3D border /// </summary> /// <param name="graph">Graphics to draw the border on.</param> /// <param name="borderSkin">Border skin object.</param> /// <param name="rect">Rectangle of the border.</param> /// <param name="backColor">Color of rectangle</param> /// <param name="backHatchStyle">Hatch style</param> /// <param name="backImage">Back Image</param> /// <param name="backImageWrapMode">Image mode</param> /// <param name="backImageTransparentColor">Image transparent color.</param> /// <param name="backImageAlign">Image alignment</param> /// <param name="backGradientStyle">Gradient type</param> /// <param name="backSecondaryColor">Gradient End Color</param> /// <param name="borderColor">Border Color</param> /// <param name="borderWidth">Border Width</param> /// <param name="borderDashStyle">Border Style</param> public virtual void DrawBorder( ChartGraphics graph, BorderSkin borderSkin, SKRect rect, SKColor backColor, ChartHatchStyle backHatchStyle, string backImage, ChartImageWrapMode backImageWrapMode, SKColor backImageTransparentColor, ChartImageAlignmentStyle backImageAlign, GradientStyle backGradientStyle, SKColor backSecondaryColor, SKColor borderColor, int borderWidth, ChartDashStyle borderDashStyle) { SKRect absolute = ChartGraphics.Round(rect); // Calculate shadow colors (0.2 - 0.6) float colorDarkeningIndex = 0.3f + (0.4f * (borderSkin.PageColor.Red + borderSkin.PageColor.Green + borderSkin.PageColor.Blue) / 765f); SKColor shadowColor = new( (byte)(backColor.Red * colorDarkeningIndex), (byte)(backColor.Green * colorDarkeningIndex), (byte)(backColor.Blue * colorDarkeningIndex)); colorDarkeningIndex += 0.2f; SKColor shadowLightColor = new( (byte)(borderSkin.PageColor.Red * colorDarkeningIndex), (byte)(borderSkin.PageColor.Green * colorDarkeningIndex), (byte)(borderSkin.PageColor.Blue * colorDarkeningIndex)); if (borderSkin.PageColor == SKColors.Transparent) { shadowLightColor = new SKColor(0, 0, 0, 60); } // Calculate rounded rect radius float radius = defaultRadiusSize; radius = Math.Max(radius, 2f * resolution / 96.0f); radius = Math.Min(radius, rect.Width / 2f); radius = Math.Min(radius, rect.Height / 2f); radius = (float)Math.Ceiling(radius); // Fill page background color using (SKPaint brush = new() { Color = borderSkin.PageColor, Style = SKPaintStyle.Fill }) { graph.FillRectangle(brush, rect); } SKRect shadowRect; if (drawOutsideTopLeftShadow) { // Top/Left outside shadow shadowRect = absolute; shadowRect.Left -= radius * 0.3f; shadowRect.Top -= radius * 0.3f; shadowRect.Right -= radius * .3f; shadowRect.Bottom -= radius * .3f; graph.DrawRoundedRectShadowAbs(shadowRect, cornerRadius, radius, Color.FromArgb(128, SKColors.Black), borderSkin.PageColor, outsideShadowRate); } // Bottom/Right outside shadow shadowRect = absolute; shadowRect.Left += radius * 0.3f; shadowRect.Top += radius * 0.3f; shadowRect.Right -= radius * .3f; shadowRect.Bottom -= radius * .3f; graph.DrawRoundedRectShadowAbs(shadowRect, cornerRadius, radius, shadowLightColor, borderSkin.PageColor, outsideShadowRate); // Background shadowRect = absolute; shadowRect.Right -= radius * .3f; shadowRect.Bottom -= radius * .3f; SKPath path = ChartGraphics.CreateRoundedRectPath(shadowRect, cornerRadius); graph.DrawPathAbs( path, backColor, backHatchStyle, backImage, backImageWrapMode, backImageTransparentColor, backImageAlign, backGradientStyle, backSecondaryColor, borderColor, borderWidth, borderDashStyle, PenAlignment.Inset); // Dispose Graphic path if (path != null) { path.Dispose(); } // Draw screws imitation in the corners of the farame if (drawScrews) { // Left/Top screw SKRect screwRect = SKRect.Empty; float offset = radius * 0.4f; screwRect.Left = shadowRect.Left + offset; screwRect.Top = shadowRect.Top + offset; screwRect.Size = new(radius * 0.55f, screwRect.Width); DrawScrew(graph, screwRect); // Right/Top screw screwRect.Left = shadowRect.Right - offset - screwRect.Width; DrawScrew(graph, screwRect); // Right/Bottom screw screwRect.Left = shadowRect.Right - offset - screwRect.Width; screwRect.Top = shadowRect.Bottom - offset - screwRect.Height; DrawScrew(graph, screwRect); // Left/Bottom screw screwRect.Left = shadowRect.Left + offset; screwRect.Top = shadowRect.Bottom - offset - screwRect.Height; DrawScrew(graph, screwRect); } // Bottom/Right inner shadow SKRegion innerShadowRegion; if (drawBottomShadow) { shadowRect = absolute; shadowRect.Right -= radius * .3f; shadowRect.Bottom -= radius * .3f; innerShadowRegion = new SKRegion( ChartGraphics.CreateRoundedRectPath( new SKRect( shadowRect.Left - radius, shadowRect.Top - radius, shadowRect.Width + 0.5f * radius, shadowRect.Height + 0.5f * radius), cornerRadius)); // TODO: innerShadowRegion.Complement(graph.CreateRoundedRectPath(shadowRect, cornerRadius)); graph.Clip = innerShadowRegion; shadowRect.Left -= 0.5f * radius; shadowRect.Top -= 0.5f * radius; shadowRect.Right += 0.5f * radius; shadowRect.Bottom += 0.5f * radius; graph.DrawRoundedRectShadowAbs( shadowRect, cornerRadius, radius, SKColors.Transparent, Color.FromArgb(175, (sunken) ? SKColors.White : shadowColor), 1.0f); graph.Clip = new SKRegion(); } // Top/Left inner shadow shadowRect = absolute; shadowRect.Right -= radius * .3f; shadowRect.Bottom -= radius * .3f; innerShadowRegion = new( ChartGraphics.CreateRoundedRectPath( new SKRect( shadowRect.Left + radius * .5f, shadowRect.Top + radius * .5f, shadowRect.Width - .2f * radius, shadowRect.Height - .2f * radius), cornerRadius)); SKRect shadowWithOffset = shadowRect; shadowWithOffset.Right += radius; shadowWithOffset.Bottom += radius; // TODO: innerShadowRegion.Complement(graph.CreateRoundedRectPath(shadowWithOffset, cornerRadius)); innerShadowRegion.SetPath(ChartGraphics.CreateRoundedRectPath(shadowRect, cornerRadius), innerShadowRegion); graph.Clip = innerShadowRegion; graph.DrawRoundedRectShadowAbs( shadowWithOffset, cornerRadius, radius, SKColors.Transparent, Color.FromArgb(175, (sunken) ? shadowColor : SKColors.White), 1.0f); graph.Clip = new(); }
/// <summary> /// Draws 3D border. /// </summary> /// <param name="graph">Graphics to draw the border on.</param> /// <param name="borderSkin">Border skin object.</param> /// <param name="rect">Rectangle of the border.</param> /// <param name="backColor">Color of rectangle</param> /// <param name="backHatchStyle">Hatch style</param> /// <param name="backImage">Back Image</param> /// <param name="backImageWrapMode">Image mode</param> /// <param name="backImageTransparentColor">Image transparent color.</param> /// <param name="backImageAlign">Image alignment</param> /// <param name="backGradientStyle">Gradient type</param> /// <param name="backSecondaryColor">Gradient End Color</param> /// <param name="borderColor">Border Color</param> /// <param name="borderWidth">Border Width</param> /// <param name="borderDashStyle">Border Style</param> public override void DrawBorder( ChartGraphics graph, BorderSkin borderSkin, SKRect rect, SKColor backColor, ChartHatchStyle backHatchStyle, string backImage, ChartImageWrapMode backImageWrapMode, SKColor backImageTransparentColor, ChartImageAlignmentStyle backImageAlign, GradientStyle backGradientStyle, SKColor backSecondaryColor, SKColor borderColor, int borderWidth, ChartDashStyle borderDashStyle) { drawBottomShadow = true; sunken = false; outsideShadowRate = .9f; drawOutsideTopLeftShadow = false; bool oldScrewsFlag = drawScrews; drawScrews = false; base.DrawBorder( graph, borderSkin, rect, borderSkin.BackColor, borderSkin.BackHatchStyle, borderSkin.BackImage, borderSkin.BackImageWrapMode, borderSkin.BackImageTransparentColor, borderSkin.BackImageAlignment, borderSkin.BackGradientStyle, borderSkin.BackSecondaryColor, borderSkin.BorderColor, borderSkin.BorderWidth, borderSkin.BorderDashStyle); drawScrews = oldScrewsFlag; rect.Left += sizeLeftTop.Width; rect.Top += sizeLeftTop.Height; rect.Right -= sizeRightBottom.Width + sizeLeftTop.Width; rect.Bottom -= sizeRightBottom.Height + sizeLeftTop.Height; if (rect.Width > 0 && rect.Height > 0) { float[] oldCorners = (float[])cornerRadius.Clone(); cornerRadius = innerCorners; drawBottomShadow = false; sunken = true; drawOutsideTopLeftShadow = true; outsideShadowRate = 1.4f; SKColor oldPageColor = borderSkin.PageColor; borderSkin.PageColor = SKColors.Transparent; base.DrawBorder( graph, borderSkin, rect, backColor, backHatchStyle, backImage, backImageWrapMode, backImageTransparentColor, backImageAlign, backGradientStyle, backSecondaryColor, borderColor, borderWidth, borderDashStyle); borderSkin.PageColor = oldPageColor; cornerRadius = oldCorners; } }