/// <summary> /// Test wether the mouse hits a plot item. /// </summary> /// <param name="layer">The layer in which this plot item is drawn into.</param> /// <param name="hitpoint">The point where the mouse is pressed.</param> /// <returns>Null if no hit, or a <see cref="IHitTestObject" /> if there was a hit.</returns> public override IHitTestObject HitTest(IPlotArea layer, PointD2D hitpoint) { Processed2DPlotData pdata = _cachedPlotDataUsedForPainting; if (null == pdata) { return(null); } PlotRangeList rangeList = pdata.RangeList; PointF[] ptArray = pdata.PlotPointsInAbsoluteLayerCoordinates; if (ptArray.Length < 2) { return(null); } if (ptArray.Length < 2048) { if (GdiExtensionMethods.IsPointIntoDistance((PointF)hitpoint, 2.5, ptArray)) { var gp = new GraphicsPath(); gp.AddLines(ptArray); gp.Widen(new Pen(Color.Black, 5)); return(new HitTestObject(gp, this)); } } else // we have too much points for the graphics path, so make a hit test first { int hitindex = -1; for (int i = 1; i < ptArray.Length; i++) { if (Math2D.IsPointIntoDistance((PointF)hitpoint, 5, ptArray[i - 1], ptArray[i])) { hitindex = i; break; } } if (hitindex < 0) { return(null); } var gp = new GraphicsPath(); int start = Math.Max(0, hitindex - 2); gp.AddLine(ptArray[start], ptArray[start + 1]); gp.AddLine(ptArray[start + 1], ptArray[start + 2]); gp.Widen(new Pen(Color.Black, 5)); return(new HitTestObject(gp, this)); } return(null); }
/// <inheritdoc/> public override void FillOneRange( GraphicsPath gp, Processed2DPlotData pdata, IPlotRange range, IPlotArea layer, CSPlaneID fillDirection, bool ignoreMissingDataPoints, bool connectCircular, PointF[] allLinePoints, double logicalShiftX, double logicalShiftY ) { if (range.Length < 4) { return; } if (connectCircular) { var circularLinePointsLengthM1 = 2 + TrimToValidBezierLength(range.Length); var circularLinePoints = new PointF[circularLinePointsLengthM1 + 1]; Array.Copy(allLinePoints, range.LowerBound, circularLinePoints, 0, range.Length); // Extract circularLinePoints[circularLinePointsLengthM1] = circularLinePoints[0]; // amend missing control points if (circularLinePointsLengthM1 - range.Length >= 1) { circularLinePoints[circularLinePointsLengthM1 - 1] = GdiExtensionMethods.Interpolate(circularLinePoints[circularLinePointsLengthM1 - 3], circularLinePoints[circularLinePointsLengthM1], 0.5); // Last Control point should be halfway between } if (circularLinePointsLengthM1 - range.Length >= 2) { circularLinePoints[circularLinePointsLengthM1 - 2] = GdiExtensionMethods.Interpolate(circularLinePoints[circularLinePointsLengthM1 - 3], circularLinePoints[circularLinePointsLengthM1], 0.5); // Middle Control point should be halfway between previous fixed point and last(=first) fixed point } FillOneRange_PreprocessedPoints(gp, pdata, range, layer, fillDirection, circularLinePoints, connectCircular, logicalShiftX, logicalShiftY); } else { var trimmedLinePointsLength = TrimToValidBezierLength(range.Length); var trimmedLinePoints = new PointF[trimmedLinePointsLength]; Array.Copy(allLinePoints, range.LowerBound, trimmedLinePoints, 0, trimmedLinePointsLength); // Extract FillOneRange_PreprocessedPoints(gp, pdata, range, layer, fillDirection, trimmedLinePoints, connectCircular, logicalShiftX, logicalShiftY); } }
/// <summary> /// Template to make a line draw. /// </summary> /// <param name="g">Graphics context.</param> /// <param name="allLinePoints">The plot data. Don't use the Range property of the pdata, since it is overriden by the next argument.</param> /// <param name="range">The plot range to use.</param> /// <param name="layer">Graphics layer.</param> /// <param name="linePen">The pen to draw the line.</param> /// <param name="symbolGap">The size of the symbol gap. Argument is the original index of the data. The return value is the absolute symbol gap at this index. /// This function is null if no symbol gap is required.</param> /// <param name="skipFrequency">Skip frequency. Normally 1, thus all gaps are taken into account. If 2, only every 2nd gap is taken into account, and so on.</param> /// <param name="connectCircular">If true, there is a line connecting the start and the end of the range.</param> /// <param name="linePlotStyle">The line plot style.</param> public override void PaintOneRange( Graphics g, PointF[] allLinePoints, IPlotRange range, IPlotArea layer, PenX linePen, Func <int, double> symbolGap, int skipFrequency, bool connectCircular, LinePlotStyle linePlotStyle) { PointF[] subLinePoints = Segment2Connection_GetSubPoints(allLinePoints, range, layer, connectCircular, out var lastIdx); var gp = new GraphicsPath(); int i; // special efforts are necessary to realize a line/symbol gap // I decided to use a path for this // and hope that not so many segments are added to the path due // to the exclusion criteria that a line only appears between two symbols (rel<0.5) // if the symbols do not overlap. So for a big array of points it is very likely // that the symbols overlap and no line between the symbols needs to be plotted if (null != symbolGap) { float startx, starty, stopx, stopy; for (i = 0; i < lastIdx; i += 2) { var diff = GdiExtensionMethods.Subtract(subLinePoints[i + 1], subLinePoints[i]); var diffLength = GdiExtensionMethods.VectorLength(diff); int originalIndex = range.GetOriginalRowIndexFromPlotPointIndex(range.LowerBound + i); double gapAtStart = 0 == i % skipFrequency ? symbolGap(originalIndex) : 0; double gapAtEnd; if ((0 == (i + 1) % skipFrequency) || ((i + 1) == range.Length)) { gapAtEnd = ((i + 1) != range.Length) ? symbolGap(originalIndex + 1) : symbolGap(range.GetOriginalRowIndexFromPlotPointIndex(range.LowerBound)); } else { gapAtEnd = 0; } var relAtStart = (float)(0.5 * gapAtStart / diffLength); // 0.5 because symbolGap is the full gap between two lines, thus between the symbol center and the beginning of the line it is only 1/2 var relAtEnd = (float)(0.5 * gapAtEnd / diffLength); // 0.5 because symbolGap is the full gap between two lines, thus between the symbol center and the beginning of the line it is only 1/2 if ((relAtStart + relAtEnd) < 1) // a line only appears if sum of the gaps is smaller than 1 { startx = subLinePoints[i].X + relAtStart * diff.X; starty = subLinePoints[i].Y + relAtStart * diff.Y; stopx = subLinePoints[i + 1].X - relAtEnd * diff.X; stopy = subLinePoints[i + 1].Y - relAtEnd * diff.Y; gp.AddLine(startx, starty, stopx, stopy); gp.StartFigure(); } } // end for g.DrawPath(linePen, gp); gp.Reset(); } else // no line symbol gap required, so we can use DrawLines to draw the lines { for (i = 0; i < lastIdx; i += 2) { gp.AddLine(subLinePoints[i].X, subLinePoints[i].Y, subLinePoints[i + 1].X, subLinePoints[i + 1].Y); gp.StartFigure(); } // end for g.DrawPath(linePen, gp); gp.Reset(); } }
/// <summary> /// Template to make a line draw. /// </summary> /// <param name="g">Graphics context.</param> /// <param name="allLinePoints">The plot data. Don't use the Range property of the pdata, since it is overriden by the next argument.</param> /// <param name="range">The plot range to use.</param> /// <param name="layer">Graphics layer.</param> /// <param name="linePen">The pen to draw the line.</param> /// <param name="symbolGap">The size of the symbol gap. Argument is the original index of the data. The return value is the absolute symbol gap at this index. /// This function is null if no symbol gap is required.</param> /// <param name="skipFrequency">Skip frequency. Normally 1, thus all gaps are taken into account. If 2, only every 2nd gap is taken into account, and so on.</param> /// <param name="connectCircular">If true, there is a line connecting the start and the end of the range.</param> /// <param name="linePlotStyle">The line plot style.</param> public override void PaintOneRange( Graphics g, PointF[] allLinePoints, IPlotRange range, IPlotArea layer, PenX linePen, Func <int, double> symbolGap, int skipFrequency, bool connectCircular, LinePlotStyle linePlotStyle) { // Bezier is only supported with point numbers n=4+3*k // so trim the range appropriately if (range.Length < 4) { return; // then too less points are in this range } PointF[] circularLinePoints; if (connectCircular) { var circularLinePointsLengthM1 = 2 + TrimToValidBezierLength(range.Length); circularLinePoints = new PointF[circularLinePointsLengthM1 + 1]; Array.Copy(allLinePoints, range.LowerBound, circularLinePoints, 0, range.Length); // Extract circularLinePoints[circularLinePointsLengthM1] = circularLinePoints[0]; // amend missing control points if (circularLinePointsLengthM1 - range.Length >= 1) { circularLinePoints[circularLinePointsLengthM1 - 1] = GdiExtensionMethods.Interpolate(circularLinePoints[circularLinePointsLengthM1 - 3], circularLinePoints[circularLinePointsLengthM1], 0.5); // Last Control point should be halfway between } if (circularLinePointsLengthM1 - range.Length >= 2) { circularLinePoints[circularLinePointsLengthM1 - 2] = GdiExtensionMethods.Interpolate(circularLinePoints[circularLinePointsLengthM1 - 3], circularLinePoints[circularLinePointsLengthM1], 0.5); // Middle Control point should be halfway between previous fixed point and last(=first) fixed point } range = range.WithUpperBoundExtendedBy(circularLinePointsLengthM1 - range.Length); } else // not circular { var trimmedLength = TrimToValidBezierLength(range.Length); if (range.Length != trimmedLength) { range = range.WithUpperBoundShortenedBy(range.Length - trimmedLength); } if (range.LowerBound == 0 && trimmedLength == allLinePoints.Length) { circularLinePoints = allLinePoints; } else { circularLinePoints = new PointF[trimmedLength]; Array.Copy(allLinePoints, range.LowerBound, circularLinePoints, 0, trimmedLength); // Extract } } if (null != symbolGap) // circular with symbol gap { var realSkipFrequency = skipFrequency % 3 == 0 ? skipFrequency : skipFrequency * 3; // least common multiple of skipFrequency and 3 var skipLinePoints = new PointF[0]; foreach (var segmentRange in GetSegmentRanges(range, symbolGap, realSkipFrequency, connectCircular)) { if (segmentRange.IsFullRangeClosedCurve) // test if this is a closed polygon without any gaps -> draw a closed polygon and return { // use the whole circular arry to draw a closed polygon without any gaps g.DrawBeziers(linePen, circularLinePoints); } else { var skipLinePointsLength = 1 + segmentRange.Length; if (skipLinePoints.Length != skipLinePointsLength) { skipLinePoints = new PointF[skipLinePointsLength]; } Array.Copy(circularLinePoints, segmentRange.IndexAtSubRangeStart, skipLinePoints, 0, skipLinePointsLength); PointF[] shortenedLinePoints; if (segmentRange.GapAtSubRangeStart != 0 || segmentRange.GapAtSubRangeEnd != 0) { shortenedLinePoints = GdiExtensionMethods.ShortenBezierCurve(skipLinePoints, segmentRange.GapAtSubRangeStart / 2, segmentRange.GapAtSubRangeEnd / 2); } else { shortenedLinePoints = skipLinePoints; } if (null != shortenedLinePoints) { g.DrawBeziers(linePen, shortenedLinePoints); } } } } else // no symbol gap { g.DrawBeziers(linePen, circularLinePoints); } }
private void PaintOneRange(Graphics g, IPlotArea layer, IPlotRange range, Processed2DPlotData pdata) { // adjust the skip frequency if it was not set appropriate if (_skipFrequency <= 0) { _skipFrequency = 1; } var dropTargets = new List <CSPlaneID>(_dropTargets.Select(id => layer.UpdateCSPlaneID(id))); if (_additionalDropTargetIsEnabled) { CSPlaneID userPlane; if (_additionalDropTargetUsePhysicalBaseValue) { userPlane = new CSPlaneID(_additionalDropTargetPerpendicularAxis, layer.Scales[_additionalDropTargetPerpendicularAxis].PhysicalVariantToNormal(_additionalDropTargetBaseValue)); } else { userPlane = new CSPlaneID(_additionalDropTargetPerpendicularAxis, _additionalDropTargetBaseValue); } dropTargets.Add(userPlane); } // paint the scatter style PointD3D pos = PointD3D.Empty; var gpath = new GraphicsPath(); if (null == _cachedSymbolSizeForIndexFunction && null == _cachedColorForIndexFunction) // using a constant symbol size and constant color { // update pen widths var pen = _pen.Clone(); double w1 = _lineWidth1Offset + _lineWidth1Factor * _cachedSymbolSize; pen.Width = w1; var gapStart = 0.5 * (_gapAtStartOffset + _gapAtStartFactor * _cachedSymbolSize); var gapEnd = 0.5 * (_gapAtEndOffset + _gapAtEndFactor * _cachedSymbolSize); int lower = range.LowerBound; int upper = range.UpperBound; for (int j = lower; j < upper; j += _skipFrequency) { var originalRowIndex = range.GetOriginalRowIndexFromPlotPointIndex(j); Logical3D r3d = layer.GetLogical3D(pdata, originalRowIndex); r3d.RX += _cachedLogicalShiftX; r3d.RY += _cachedLogicalShiftY; foreach (CSPlaneID id in dropTargets) { gpath.Reset(); layer.CoordinateSystem.GetIsolineFromPointToPlane(gpath, r3d, id); PointF[] shortenedPathPoints = null; if (gapStart != 0 || gapEnd != 0) { gpath.Flatten(); var pathPoints = gpath.PathPoints; shortenedPathPoints = GdiExtensionMethods.ShortenedBy(pathPoints, RADouble.NewAbs(gapStart), RADouble.NewAbs(gapEnd)); if (null != shortenedPathPoints) { g.DrawLines(pen, shortenedPathPoints); } } else { g.DrawPath(pen, gpath); } } } } else // using a variable symbol size or variable symbol color { int lower = range.LowerBound; int upper = range.UpperBound; for (int j = lower; j < upper; j += _skipFrequency) { var originalRowIndex = range.GetOriginalRowIndexFromPlotPointIndex(j); var pen = _pen.Clone(); if (null == _cachedColorForIndexFunction) { _cachedSymbolSize = _cachedSymbolSizeForIndexFunction(originalRowIndex); double w1 = _lineWidth1Offset + _lineWidth1Factor * _cachedSymbolSize; pen.Width = w1; } else { _cachedSymbolSize = null == _cachedSymbolSizeForIndexFunction ? _cachedSymbolSize : _cachedSymbolSizeForIndexFunction(originalRowIndex); double w1 = _lineWidth1Offset + _lineWidth1Factor * _cachedSymbolSize; var customSymbolColor = _cachedColorForIndexFunction(originalRowIndex); pen.Width = w1; pen.Color = NamedColor.FromArgb(customSymbolColor.A, customSymbolColor.R, customSymbolColor.G, customSymbolColor.B); } var gapStart = 0.5 * (_gapAtStartOffset + _gapAtStartFactor * _cachedSymbolSize); var gapEnd = 0.5 * (_gapAtEndOffset + _gapAtEndFactor * _cachedSymbolSize); Logical3D r3d = layer.GetLogical3D(pdata, originalRowIndex); r3d.RX += _cachedLogicalShiftX; r3d.RY += _cachedLogicalShiftY; foreach (CSPlaneID id in _dropTargets) { gpath.Reset(); layer.CoordinateSystem.GetIsolineFromPointToPlane(gpath, r3d, id); PointF[] shortenedPathPoints = null; if (gapStart != 0 || gapEnd != 0) { gpath.Flatten(); var pathPoints = gpath.PathPoints; shortenedPathPoints = GdiExtensionMethods.ShortenedBy(pathPoints, RADouble.NewAbs(gapStart), RADouble.NewAbs(gapEnd)); if (null != shortenedPathPoints) { g.DrawLines(pen, shortenedPathPoints); } } else { g.DrawPath(pen, gpath); } } } } }
/// <summary> /// Template to make a line draw. /// </summary> /// <param name="g">Graphics context.</param> /// <param name="allLinePoints">The plot data. Don't use the Range property of the pdata, since it is overriden by the next argument.</param> /// <param name="range">The plot range to use.</param> /// <param name="layer">Graphics layer.</param> /// <param name="linePen">The pen to draw the line.</param> /// <param name="symbolGap">The size of the symbol gap. Argument is the original index of the data. The return value is the absolute symbol gap at this index. /// This function is null if no symbol gap is required.</param> /// <param name="skipFrequency">Skip frequency. Normally 1, thus all gaps are taken into account. If 2, only every 2nd gap is taken into account, and so on.</param> /// <param name="connectCircular">If true, there is a line connecting the start and the end of the range.</param> /// <param name="linePlotStyle">The line plot style.</param> public override void PaintOneRange( Graphics g, PointF[] allLinePoints, IPlotRange range, IPlotArea layer, PenX linePen, Func <int, double> symbolGap, int skipFrequency, bool connectCircular, LinePlotStyle linePlotStyle) { PointF[] subLinePoints; if (range.LowerBound == 0 && range.UpperBound == allLinePoints.Length) { // under optimal conditions we can use allLinePoints directly subLinePoints = allLinePoints; } else { // otherwise, make a new array subLinePoints = new PointF[range.Length]; Array.Copy(allLinePoints, range.LowerBound, subLinePoints, 0, range.Length); // Extract } int lastIdx = range.Length - 1; var layerSize = layer.Size; if (connectCircular) { if (null != symbolGap) { // convert points to bezier segments var bezierSegments = GdiExtensionMethods.ClosedCardinalSplineToBezierSegments(subLinePoints, subLinePoints.Length); var subBezierSegments = new PointF[0]; foreach (var segmentRange in GetSegmentRanges(range, symbolGap, skipFrequency, connectCircular)) { if (segmentRange.IsFullRangeClosedCurve) // test if this is a closed polygon without any gaps -> draw a closed polygon and return { // use the whole circular arry to draw a closed polygon without any gaps g.DrawClosedCurve(linePen, subLinePoints); } else { var subBezierLength = 3 * segmentRange.Length + 1; if (subBezierSegments.Length != subBezierLength) { subBezierSegments = new PointF[subBezierLength]; } Array.Copy(bezierSegments, segmentRange.IndexAtSubRangeStart * 3, subBezierSegments, 0, subBezierLength); var shortenedBezierSegments = GdiExtensionMethods.ShortenBezierCurve(subBezierSegments, segmentRange.GapAtSubRangeStart / 2, segmentRange.GapAtSubRangeEnd / 2); if (null != shortenedBezierSegments) { g.DrawBeziers(linePen, shortenedBezierSegments); } } } } else { g.DrawClosedCurve(linePen, subLinePoints); } } else // not circular { if (symbolGap != null) { // convert points to bezier segments var bezierSegments = GdiExtensionMethods.OpenCardinalSplineToBezierSegments(subLinePoints, subLinePoints.Length); var subBezierSegments = new PointF[0]; foreach (var segmentRange in GetSegmentRanges(range, symbolGap, skipFrequency, connectCircular)) { if (segmentRange.IsFullRangeClosedCurve) // test if this is a closed polygon without any gaps -> draw a closed polygon and return { // use the whole circular arry to draw a closed polygon without any gaps g.DrawCurve(linePen, subLinePoints); } else { var subBezierLength = 3 * segmentRange.Length + 1; if (subBezierSegments.Length != subBezierLength) { subBezierSegments = new PointF[subBezierLength]; } Array.Copy(bezierSegments, segmentRange.IndexAtSubRangeStart * 3, subBezierSegments, 0, subBezierLength); var shortenedBezierSegments = GdiExtensionMethods.ShortenBezierCurve(subBezierSegments, segmentRange.GapAtSubRangeStart / 2, segmentRange.GapAtSubRangeEnd / 2); if (null != shortenedBezierSegments) { g.DrawBeziers(linePen, shortenedBezierSegments); } } } } else { g.DrawCurve(linePen, subLinePoints); } } }