/// <inheritdoc/> public override void FillOneRange( GraphicsPath gp, Processed2DPlotData pdata, IPlotRange range, IPlotArea layer, CSPlaneID fillDirection, bool ignoreMissingDataPoints, bool connectCircular, PointF[] allLinePointsShiftedAlready, double logicalShiftX, double logicalShiftY ) { if (range.Length < 2) { return; } PointF[] linePoints = Segment2Connection_GetSubPoints(allLinePointsShiftedAlready, range, layer, connectCircular, out var lastIdx); if (connectCircular) { gp.AddLines(linePoints); gp.CloseFigure(); } else { int offs = range.LowerBound; for (int i = 0; i < linePoints.Length - 1; i += 2) { Logical3D r0 = layer.GetLogical3D(pdata, range.GetOriginalRowIndexFromPlotPointIndex(range.LowerBound + i)); r0.RX += logicalShiftX; r0.RY += logicalShiftY; layer.CoordinateSystem.GetIsolineFromPlaneToPoint(gp, fillDirection, r0); gp.AddLine(linePoints[i].X, linePoints[i].Y, linePoints[i + 1].X, linePoints[i + 1].Y); Logical3D r1 = layer.GetLogical3D(pdata, range.GetOriginalRowIndexFromPlotPointIndex(range.LowerBound + i + 1)); r1.RX += logicalShiftX; r1.RY += logicalShiftY; layer.CoordinateSystem.GetIsolineFromPointToPlane(gp, r1, fillDirection); layer.CoordinateSystem.GetIsolineOnPlane(gp, fillDirection, r1, r0); gp.StartFigure(); } gp.CloseFigure(); } }
/// <summary> /// Template to get a fill path. /// </summary> /// <param name="gp">Graphics path to fill with data.</param> /// <param name="pdata">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="fillDirection">Designates a bound to fill to.</param> /// <param name="linePoints">The points that mark the line.</param> /// <param name="connectCircular">If true, a circular connection is drawn.</param> private void FillOneRange_PreprocessedPoints( GraphicsPath gp, Processed2DPlotData pdata, IPlotRange range, IPlotArea layer, CSPlaneID fillDirection, PointF[] linePoints, bool connectCircular, double logicalShiftX, double logicalShiftY ) { if (connectCircular) { gp.AddBeziers(linePoints); gp.CloseFigure(); } else { Logical3D r0 = layer.GetLogical3D(pdata, range.OriginalFirstPoint); r0.RX += logicalShiftX; r0.RY += logicalShiftY; layer.CoordinateSystem.GetIsolineFromPlaneToPoint(gp, fillDirection, r0); gp.AddBeziers(linePoints); Logical3D r1 = layer.GetLogical3D(pdata, range.GetOriginalRowIndexFromPlotPointIndex(range.LowerBound + linePoints.Length - 1)); r1.RX += logicalShiftX; r1.RY += logicalShiftY; layer.CoordinateSystem.GetIsolineFromPointToPlane(gp, r1, fillDirection); layer.CoordinateSystem.GetIsolineOnPlane(gp, fillDirection, r1, r0); gp.CloseFigure(); } }
/// <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) { int lastIdx; PointF[] subLinePoints = Segment2Connection_GetSubPoints(allLinePoints, range, layer, connectCircular, out lastIdx); GraphicsPath 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(); } }
/// <inheritdoc/> public override void FillOneRange( GraphicsPath gp, Processed2DPlotData pdata, IPlotRange range, IPlotArea layer, CSPlaneID fillDirection, bool ignoreMissingDataPoints, bool connectCircular, PointF[] allLinePointsShiftedAlready, double logicalShiftX, double logicalShiftY ) { if (range.Length < 2) return; int lastIdx; PointF[] linePoints = Segment2Connection_GetSubPoints(allLinePointsShiftedAlready, range, layer, connectCircular, out lastIdx); if (connectCircular) { gp.AddLines(linePoints); gp.CloseFigure(); } else { int offs = range.LowerBound; for (int i = 0; i < linePoints.Length - 1; i += 2) { Logical3D r0 = layer.GetLogical3D(pdata, range.GetOriginalRowIndexFromPlotPointIndex(range.LowerBound + i)); r0.RX += logicalShiftX; r0.RY += logicalShiftY; layer.CoordinateSystem.GetIsolineFromPlaneToPoint(gp, fillDirection, r0); gp.AddLine(linePoints[i].X, linePoints[i].Y, linePoints[i + 1].X, linePoints[i + 1].Y); Logical3D r1 = layer.GetLogical3D(pdata, range.GetOriginalRowIndexFromPlotPointIndex(range.LowerBound + i + 1)); r1.RX += logicalShiftX; r1.RY += logicalShiftY; layer.CoordinateSystem.GetIsolineFromPointToPlane(gp, r1, fillDirection); layer.CoordinateSystem.GetIsolineOnPlane(gp, fillDirection, r1, r0); gp.StartFigure(); } gp.CloseFigure(); } }
public void PaintOneRange(Graphics g, IPlotArea layer, IPlotRange range, Processed2DPlotData pdata) { if (this._labelColumnProxy.Document == null) return; _cachedStringFormat.Alignment = GdiExtensionMethods.ToGdi(_alignmentX); _cachedStringFormat.LineAlignment = GdiExtensionMethods.ToGdi(_alignmentY); if (null != _attachedPlane) _attachedPlane = layer.UpdateCSPlaneID(_attachedPlane); var ptArray = pdata.PlotPointsInAbsoluteLayerCoordinates; Altaxo.Data.IReadableColumn labelColumn = this._labelColumnProxy.Document; bool isUsingVariableColorForLabelText = null != _cachedColorForIndexFunction && IsColorReceiver; bool isUsingVariableColorForLabelBackground = null != _cachedColorForIndexFunction && (null != _backgroundStyle && _backgroundStyle.SupportsBrush && (_backgroundColorLinkage == ColorLinkage.Dependent || _backgroundColorLinkage == ColorLinkage.PreserveAlpha)); bool isUsingVariableColor = isUsingVariableColorForLabelText || isUsingVariableColorForLabelBackground; BrushX clonedTextBrush = null; BrushX clonedBackBrush = null; if (isUsingVariableColorForLabelText) clonedTextBrush = _brush.Clone(); if (isUsingVariableColorForLabelBackground) clonedBackBrush = _backgroundStyle.Brush.Clone(); // save the graphics stat since we have to translate the origin var gs = g.Save(); double xpos = 0, ypos = 0; double xpre, ypre; double xdiff, ydiff; bool isFormatStringContainingBraces = _labelFormatString?.IndexOf('{') >= 0; var culture = System.Threading.Thread.CurrentThread.CurrentCulture; bool mustUseLogicalCoordinates = null != this._attachedPlane || 0 != _cachedLogicalShiftX || 0 != _cachedLogicalShiftY; int lower = range.LowerBound; int upper = range.UpperBound; for (int j = lower; j < upper; j+=_skipFrequency) { int originalRowIndex = range.GetOriginalRowIndexFromPlotPointIndex(j); string label; if (string.IsNullOrEmpty(_labelFormatString)) { label = labelColumn[originalRowIndex].ToString(); } else if (!isFormatStringContainingBraces) { label = labelColumn[originalRowIndex].ToString(_labelFormatString, culture); } else { // the label format string can contain {0} for the label column item, {1} for the row index, {2} .. {4} for the x, y and z component of the data point label = string.Format(_labelFormatString, labelColumn[originalRowIndex], originalRowIndex, pdata.GetPhysical(0, originalRowIndex), pdata.GetPhysical(1, originalRowIndex), pdata.GetPhysical(2, originalRowIndex)); } if (string.IsNullOrEmpty(label)) continue; double localSymbolSize = _symbolSize; if (null != _cachedSymbolSizeForIndexFunction) { localSymbolSize = _cachedSymbolSizeForIndexFunction(originalRowIndex); } double localFontSize = _fontSizeOffset + _fontSizeFactor * localSymbolSize; if (!(localFontSize > 0)) continue; _font = _font.WithSize(localFontSize); // Start of preparation of brushes, if a variable color is used if (isUsingVariableColor) { Color c = _cachedColorForIndexFunction(originalRowIndex); if (isUsingVariableColorForLabelText) { clonedTextBrush.Color = new NamedColor(AxoColor.FromArgb(c.A, c.R, c.G, c.B), "e"); } if (isUsingVariableColorForLabelBackground) { if (_backgroundColorLinkage == ColorLinkage.PreserveAlpha) clonedBackBrush.Color = new NamedColor(AxoColor.FromArgb(clonedBackBrush.Color.Color.A, c.R, c.G, c.B), "e"); else clonedBackBrush.Color = new NamedColor(AxoColor.FromArgb(c.A, c.R, c.G, c.B), "e"); } } // end of preparation of brushes for variable colors if (mustUseLogicalCoordinates) // we must use logical coordinates because either there is a shift of logical coordinates, or an attached plane { Logical3D r3d = layer.GetLogical3D(pdata, originalRowIndex); r3d.RX += _cachedLogicalShiftX; r3d.RY += _cachedLogicalShiftY; if (null != this._attachedPlane) { var pp = layer.CoordinateSystem.GetPointOnPlane(this._attachedPlane, r3d); xpre = pp.X; ypre = pp.Y; } else { PointD3D pt; layer.CoordinateSystem.LogicalToLayerCoordinates(r3d, out xpre, out ypre); } } else // no shifting, thus we can use layer coordinates { xpre = ptArray[j].X; ypre = ptArray[j].Y; } xdiff = xpre - xpos; ydiff = ypre - ypos; xpos = xpre; ypos = ypre; g.TranslateTransform((float)xdiff, (float)ydiff); if (this._rotation != 0) g.RotateTransform((float)-this._rotation); this.PaintOneItem(g, label, localSymbolSize, clonedTextBrush, clonedBackBrush); if (this._rotation != 0) g.RotateTransform((float)this._rotation); } g.Restore(gs); // Restore the graphics state }
protected static IEnumerable <SegmentRange> GetSegmentRanges( IPlotRange range, Func <int, double> symbolGap, int skipFrequency, bool connectCircular) { int lengthNonCircular = range.Length; int lengthCircularMinus1 = range.Length - 1 + (connectCircular ? 1 : 0); // SubRange is a number of consecutive line segments, with joints where the symbol gap is zero double gapAtSubRangeStart = symbolGap(range.GetOriginalRowIndexFromPlotPointIndex(range.LowerBound)); // start index of the subrange double gapAtSubRangeEnd = 0; int indexAtSubRangeEnd; for (int indexAtSubRangeStart = 0; indexAtSubRangeStart < lengthCircularMinus1; indexAtSubRangeStart = indexAtSubRangeEnd, gapAtSubRangeStart = gapAtSubRangeEnd) { // search for the last index of this subrange, i.e. for a point where the gap is non-zero for (indexAtSubRangeEnd = indexAtSubRangeStart + 1, gapAtSubRangeEnd = 0; indexAtSubRangeEnd < lengthNonCircular; ++indexAtSubRangeEnd) { if (0 == indexAtSubRangeEnd % skipFrequency) // calculate gap only if in skip period { gapAtSubRangeEnd = symbolGap(range.GetOriginalRowIndexFromPlotPointIndex(range.LowerBound + indexAtSubRangeEnd)); } if (0 != gapAtSubRangeEnd) { break; } } // if this is circular connected and is the last point, then use the gap of the very first point if (connectCircular && indexAtSubRangeEnd == lengthNonCircular) { gapAtSubRangeEnd = symbolGap(range.GetOriginalRowIndexFromPlotPointIndex(range.LowerBound)); } // test if this is a closed polygon without any gaps -> draw a closed polygon and return if (connectCircular && 0 == gapAtSubRangeStart && indexAtSubRangeEnd == (lengthCircularMinus1 + 1)) { // use the whole circular array to draw a closed polygon without any gaps yield return(new SegmentRange() { IsFullRangeClosedCurve = true, IndexAtSubRangeStart = indexAtSubRangeStart, IndexAtSubRangeEnd = indexAtSubRangeStart, GapAtSubRangeStart = 0, GapAtSubRangeEnd = 0 }); break; } // adjust the end index to be valid if (!(indexAtSubRangeEnd <= lengthCircularMinus1)) { indexAtSubRangeEnd = lengthCircularMinus1; } yield return(new SegmentRange() { IsFullRangeClosedCurve = false, IndexAtSubRangeStart = indexAtSubRangeStart, IndexAtSubRangeEnd = indexAtSubRangeEnd, GapAtSubRangeStart = gapAtSubRangeStart, GapAtSubRangeEnd = gapAtSubRangeEnd }); } }
/// <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) { if (range.Length < 2) return; int lastIdx; int numberOfPointsPerOriginalPoint; PointF[] stepPolylinePoints = GetStepPolylinePoints(allLinePoints, range, layer, connectCircular, out numberOfPointsPerOriginalPoint, out lastIdx); GraphicsPath gp = new GraphicsPath(); if (null != symbolGap) { int end = range.UpperBound - 1; var subPointsLength = skipFrequency * numberOfPointsPerOriginalPoint + 1; for (int i = 0; i < range.Length; i += skipFrequency) { int partialPolylineLength = Math.Min(subPointsLength, stepPolylinePoints.Length - numberOfPointsPerOriginalPoint * i); if (partialPolylineLength < 2) continue; // happens probably at the end of the range if there are not enough points to draw double gapAtStart = symbolGap(range.GetOriginalRowIndexFromPlotPointIndex(range.LowerBound + i)); double gapAtEnd; if (connectCircular && skipFrequency >= (range.Length - i)) gapAtEnd = symbolGap(range.OriginalFirstPoint); else if (skipFrequency <= (range.Length - 1 - i)) gapAtEnd = symbolGap(range.GetOriginalRowIndexFromPlotPointIndex(range.LowerBound + i + skipFrequency)); else gapAtEnd = 0; int startOfPartialPolyline = numberOfPointsPerOriginalPoint * i; var shortenedPolyline = stepPolylinePoints.ShortenPartialPolylineByDistanceFromStartAndEnd(startOfPartialPolyline, startOfPartialPolyline + partialPolylineLength - 1, gapAtStart / 2, gapAtEnd / 2); if (null != shortenedPolyline) g.DrawLines(linePen, shortenedPolyline); } } else { if (connectCircular) g.DrawPolygon(linePen, stepPolylinePoints); else g.DrawLines(linePen, stepPolylinePoints); } }
/// <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 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 if (null != symbolGap) // circular with symbol gap { var realSkipFrequency = skipFrequency % 3 == 0 ? skipFrequency : skipFrequency * 3; // least common multiple of skipFrequency and 3 for (int i = 0; i < range.Length; i += realSkipFrequency) { var skipLinePointsLength = Math.Min(realSkipFrequency + 1, TrimToValidBezierLength(circularLinePoints.Length - i)); if (skipLinePointsLength >= 4) { var skipLinePoints = new PointF[skipLinePointsLength]; Array.Copy(circularLinePoints, i, skipLinePoints, 0, skipLinePointsLength); // Extract var gapAtStart = symbolGap(range.GetOriginalRowIndexFromPlotPointIndex(range.LowerBound + i)); double gapAtEnd; if (connectCircular && realSkipFrequency >= (range.Length - 1 - i)) gapAtEnd = symbolGap(range.OriginalFirstPoint); else if (realSkipFrequency <= (range.Length - 1 - i)) gapAtEnd = symbolGap(range.GetOriginalRowIndexFromPlotPointIndex(range.LowerBound + i + realSkipFrequency)); else gapAtEnd = 0; if (gapAtStart != 0 || gapAtEnd != 0) { skipLinePoints = GdiExtensionMethods.ShortenBezierCurve(skipLinePoints, gapAtStart / 2, gapAtEnd / 2); } if (null != skipLinePoints) { g.DrawBeziers(linePen, skipLinePoints); } } } } else // circular without symbol gap { g.DrawBeziers(linePen, circularLinePoints); } } else // not circular { if (null != symbolGap) // not circular with symbol gap { var realSkipFrequency = skipFrequency % 3 == 0 ? skipFrequency : skipFrequency * 3; // least common multiple of skipFrequency and 3 for (int i = 0; i < range.Length; i += realSkipFrequency) { var skipLinePointsLength = Math.Min(realSkipFrequency + 1, TrimToValidBezierLength(range.Length - i)); if (skipLinePointsLength >= 4) { var skipLinePoints = new PointF[skipLinePointsLength]; Array.Copy(allLinePoints, range.LowerBound + i, skipLinePoints, 0, skipLinePointsLength); // Extract var gapAtStart = symbolGap(range.GetOriginalRowIndexFromPlotPointIndex(range.LowerBound + i)); var gapAtEnd = symbolGap(range.GetOriginalRowIndexFromPlotPointIndex(range.LowerBound + i + skipLinePointsLength - 1)); if (gapAtStart != 0 || gapAtEnd != 0) { skipLinePoints = GdiExtensionMethods.ShortenBezierCurve(skipLinePoints, gapAtStart / 2, gapAtEnd / 2); } if (null != skipLinePoints) { g.DrawBeziers(linePen, skipLinePoints); } } } } else // not circular without symbol gap { var trimmedLength = TrimToValidBezierLength(range.Length); var subLinePoints = new PointF[trimmedLength]; Array.Copy(allLinePoints, range.LowerBound, subLinePoints, 0, trimmedLength); // Extract g.DrawBeziers(linePen, subLinePoints); } } }
/// <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(); } }
protected void PaintOneRange(int axisNumber, Graphics g, IPlotArea layer, IPlotRange range, Processed2DPlotData pdata) { const double logicalClampMinimum = -10; const double logicalClampMaximum = 11; _skipFrequency = Math.Max(1, _skipFrequency); // Plot error bars for the dependent variable (y) var ptArray = pdata.PlotPointsInAbsoluteLayerCoordinates; var posErrCol = PositiveErrorColumn; var negErrCol = NegativeErrorColumn; if (null != posErrCol && !typeof(double).IsAssignableFrom(posErrCol.ItemType)) posErrCol = null; // TODO make this an runtime paint error to be reported if (null != negErrCol && !typeof(double).IsAssignableFrom(negErrCol.ItemType)) negErrCol = null; // TODO make this an runtime paint error to be reported if (posErrCol == null && negErrCol == null) return; // nothing to do if both error columns are null var strokePen = _pen.Clone(); System.Drawing.Drawing2D.GraphicsPath errorBarPath = new System.Drawing.Drawing2D.GraphicsPath(); Region oldClippingRegion = g.Clip; Region newClip = (Region)oldClippingRegion.Clone(); int lower = range.LowerBound; int upper = range.UpperBound; for (int j = lower; j < upper; j += _skipFrequency) { int originalRowIndex = range.GetOriginalRowIndexFromPlotPointIndex(j); double symbolSize = null == _cachedSymbolSizeForIndexFunction ? _symbolSize : _cachedSymbolSizeForIndexFunction(originalRowIndex); strokePen.Width = (_lineWidth1Offset + _lineWidth1Factor * symbolSize); if (null != _cachedColorForIndexFunction) strokePen.Color = GdiColorHelper.ToNamedColor(_cachedColorForIndexFunction(originalRowIndex), "VariableColor"); if (null != strokePen.EndCap) strokePen.EndCap = strokePen.EndCap.WithMinimumAbsoluteAndRelativeSize(symbolSize * _endCapSizeFactor + _endCapSizeOffset, 1 + 1E-6); AltaxoVariant vMeanPhysical = pdata.GetPhysical(axisNumber, originalRowIndex); Logical3D logicalMean = layer.GetLogical3D(pdata, originalRowIndex); logicalMean.RX += _cachedLogicalShiftX; logicalMean.RY += _cachedLogicalShiftY; if (!Calc.RMath.IsInIntervalCC(logicalMean.RX, logicalClampMinimum, logicalClampMaximum)) continue; if (!Calc.RMath.IsInIntervalCC(logicalMean.RY, logicalClampMinimum, logicalClampMaximum)) continue; var vMeanLogical = logicalMean.GetR(axisNumber); Logical3D logicalPos = logicalMean; Logical3D logicalNeg = logicalMean; bool logicalPosValid = false; bool logicalNegValid = false; switch (_meaningOfValues) { case ValueInterpretation.AbsoluteError: { if (posErrCol != null) { var vPosLogical = layer.Scales[axisNumber].PhysicalVariantToNormal(vMeanPhysical + Math.Abs(posErrCol[originalRowIndex])); vPosLogical = Calc.RMath.ClampToInterval(vPosLogical, logicalClampMinimum, logicalClampMaximum); logicalPos.SetR(axisNumber, vPosLogical); logicalPosValid = !logicalPos.IsNaN && vPosLogical != vMeanLogical; } if (negErrCol != null) { var vNegLogical = layer.Scales[axisNumber].PhysicalVariantToNormal(vMeanPhysical - Math.Abs(negErrCol[originalRowIndex])); vNegLogical = Calc.RMath.ClampToInterval(vNegLogical, logicalClampMinimum, logicalClampMaximum); logicalNeg.SetR(axisNumber, vNegLogical); logicalNegValid = !logicalNeg.IsNaN && vNegLogical != vMeanLogical; } } break; case ValueInterpretation.RelativeError: { if (posErrCol != null) { var vPosLogical = layer.Scales[axisNumber].PhysicalVariantToNormal(vMeanPhysical * (1 + Math.Abs(posErrCol[originalRowIndex]))); vPosLogical = Calc.RMath.ClampToInterval(vPosLogical, logicalClampMinimum, logicalClampMaximum); logicalPos.SetR(axisNumber, vPosLogical); logicalPosValid = !logicalPos.IsNaN && vPosLogical != vMeanLogical; } if (negErrCol != null) { var vNegLogical = layer.Scales[axisNumber].PhysicalVariantToNormal(vMeanPhysical * (1 - Math.Abs(negErrCol[originalRowIndex]))); vNegLogical = Calc.RMath.ClampToInterval(vNegLogical, logicalClampMinimum, logicalClampMaximum); logicalNeg.SetR(axisNumber, vNegLogical); logicalNegValid = !logicalNeg.IsNaN && vNegLogical != vMeanLogical; } } break; case ValueInterpretation.AbsoluteValue: { if (posErrCol != null) { var vPosLogical = layer.Scales[axisNumber].PhysicalVariantToNormal(posErrCol[originalRowIndex]); vPosLogical = Calc.RMath.ClampToInterval(vPosLogical, logicalClampMinimum, logicalClampMaximum); logicalPos.SetR(axisNumber, vPosLogical); logicalPosValid = !logicalPos.IsNaN && vPosLogical != vMeanLogical; } if (negErrCol != null) { var vNegLogical = layer.Scales[axisNumber].PhysicalVariantToNormal(negErrCol[originalRowIndex]); vNegLogical = Calc.RMath.ClampToInterval(vNegLogical, logicalClampMinimum, logicalClampMaximum); logicalNeg.SetR(axisNumber, vNegLogical); logicalNegValid = !logicalNeg.IsNaN && vNegLogical != vMeanLogical; } if (object.ReferenceEquals(negErrCol, posErrCol)) { logicalNegValid = false; // then we need only to plot the positive column, since both colums are identical } } break; } // end switch if (!(logicalPosValid || logicalNegValid)) continue; // nothing to do for this point if both pos and neg logical point are invalid. if (logicalNegValid) { errorBarPath.Reset(); layer.CoordinateSystem.GetIsoline(errorBarPath, logicalMean, logicalNeg); PointF[] shortenedPathPoints = null; bool shortenedPathPointsCalculated = false; if (_useSymbolGap) { double gap = _symbolGapOffset + _symbolGapFactor * symbolSize; if (gap > 0) { errorBarPath.Flatten(); var pathPoints = errorBarPath.PathPoints; shortenedPathPoints = GdiExtensionMethods.ShortenedBy(pathPoints, RADouble.NewAbs(gap / 2), RADouble.NewAbs(0)); shortenedPathPointsCalculated = true; if (null == shortenedPathPoints && _forceVisibilityOfEndCap && !(strokePen.EndCap is Altaxo.Graph.Gdi.LineCaps.FlatCap)) { var totalLineLength = GdiExtensionMethods.TotalLineLength(pathPoints); var shortTheLineBy = Math.Max(0, totalLineLength - 0.125 * strokePen.Width); shortenedPathPoints = GdiExtensionMethods.ShortenedBy(pathPoints, RADouble.NewAbs(shortTheLineBy), RADouble.NewAbs(0)); } } } if (shortenedPathPointsCalculated) { if (null != shortenedPathPoints) { g.DrawLines(strokePen, shortenedPathPoints); } } else { g.DrawPath(strokePen, errorBarPath); } } if (logicalPosValid) { errorBarPath.Reset(); layer.CoordinateSystem.GetIsoline(errorBarPath, logicalMean, logicalPos); PointF[] shortenedPathPoints = null; bool shortenedPathPointsCalculated = false; if (_useSymbolGap) { double gap = _symbolGapOffset + _symbolGapFactor * symbolSize; if (gap > 0) { errorBarPath.Flatten(); var pathPoints = errorBarPath.PathPoints; shortenedPathPoints = GdiExtensionMethods.ShortenedBy(pathPoints, RADouble.NewAbs(gap / 2), RADouble.NewAbs(0)); shortenedPathPointsCalculated = true; if (null == shortenedPathPoints && _forceVisibilityOfEndCap && !(strokePen.EndCap is Altaxo.Graph.Gdi.LineCaps.FlatCap)) { var totalLineLength = GdiExtensionMethods.TotalLineLength(pathPoints); var shortTheLineBy = Math.Max(0, totalLineLength - 0.125 * strokePen.Width); shortenedPathPoints = GdiExtensionMethods.ShortenedBy(pathPoints, RADouble.NewAbs(shortTheLineBy), RADouble.NewAbs(0)); } } } if (shortenedPathPointsCalculated) { if (null != shortenedPathPoints) { g.DrawLines(strokePen, shortenedPathPoints); } } else { g.DrawPath(strokePen, errorBarPath); } } } g.Clip = oldClippingRegion; }
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); } } } } }
protected void PaintOneRange(Graphics g, IPlotArea layer, IPlotRange range, Processed2DPlotData pdata) { const double logicalClampMinimum = -10; const double logicalClampMaximum = 11; // Plot error bars for the dependent variable (y) var columnX = ColumnX; var columnY = ColumnY; if (columnX == null || columnY == null) return; // nothing to do if both error columns are null if (!typeof(double).IsAssignableFrom(columnX.ItemType) || !typeof(double).IsAssignableFrom(columnY.ItemType)) return; // TODO make this an runtime paint error to be reported var strokePen = _strokePen.Clone(); using (GraphicsPath isoLine = new GraphicsPath()) { int lower = range.LowerBound; int upper = range.UpperBound; for (int j = lower; j < upper; j += _skipFrequency) { int originalRowIndex = range.GetOriginalRowIndexFromPlotPointIndex(j); double symbolSize = null == _cachedSymbolSizeForIndexFunction ? _symbolSize : _cachedSymbolSizeForIndexFunction(originalRowIndex); strokePen.Width = (_lineWidth1Offset + _lineWidth1Factor * symbolSize); if (null != _cachedColorForIndexFunction) strokePen.Color = GdiColorHelper.ToNamedColor(_cachedColorForIndexFunction(originalRowIndex), "VariableColor"); if (!(strokePen.EndCap is LineCaps.FlatCap)) strokePen.EndCap = strokePen.EndCap.WithMinimumAbsoluteAndRelativeSize(symbolSize * _endCapSizeFactor + _endCapSizeOffset, 1 + 1E-6); // Calculate target AltaxoVariant targetX, targetY; switch (_meaningOfValues) { case ValueInterpretation.AbsoluteDifference: { targetX = pdata.GetXPhysical(originalRowIndex) + columnX[originalRowIndex]; targetY = pdata.GetYPhysical(originalRowIndex) + columnY[originalRowIndex]; } break; case ValueInterpretation.AbsoluteValue: { targetX = columnX[originalRowIndex]; targetY = columnY[originalRowIndex]; } break; default: throw new NotImplementedException(nameof(_meaningOfValues)); } var logicalTarget = layer.GetLogical3D(targetX, targetY); var logicalOrigin = layer.GetLogical3D(pdata, originalRowIndex); if (!_independentOnShiftingGroupStyles && (0 != _cachedLogicalShiftX || 0 != _cachedLogicalShiftY)) { logicalOrigin.RX += _cachedLogicalShiftX; logicalOrigin.RY += _cachedLogicalShiftY; logicalTarget.RX += _cachedLogicalShiftX; logicalTarget.RY += _cachedLogicalShiftY; } if (!Calc.RMath.IsInIntervalCC(logicalOrigin.RX, logicalClampMinimum, logicalClampMaximum)) continue; if (!Calc.RMath.IsInIntervalCC(logicalOrigin.RY, logicalClampMinimum, logicalClampMaximum)) continue; if (!Calc.RMath.IsInIntervalCC(logicalOrigin.RZ, logicalClampMinimum, logicalClampMaximum)) continue; if (!Calc.RMath.IsInIntervalCC(logicalTarget.RX, logicalClampMinimum, logicalClampMaximum)) continue; if (!Calc.RMath.IsInIntervalCC(logicalTarget.RY, logicalClampMinimum, logicalClampMaximum)) continue; if (!Calc.RMath.IsInIntervalCC(logicalTarget.RZ, logicalClampMinimum, logicalClampMaximum)) continue; isoLine.Reset(); layer.CoordinateSystem.GetIsoline(isoLine, logicalOrigin, logicalTarget); if (null == isoLine) continue; PointF[] isoLinePathPoints = null; if (_useManualVectorLength) { isoLine.Flatten(); isoLinePathPoints = isoLine.PathPoints; double length = _vectorLengthOffset + _vectorLengthFactor * symbolSize; double isoLineLength = isoLinePathPoints.TotalLineLength(); isoLinePathPoints = isoLinePathPoints.ShortenedBy(RADouble.NewAbs(0), RADouble.NewAbs(isoLineLength - length)); if (null == isoLine) continue; } if (_useSymbolGap) { if (null == isoLinePathPoints) { isoLine.Flatten(); isoLinePathPoints = isoLine.PathPoints; } double gap = _symbolGapOffset + _symbolGapFactor * symbolSize; if (gap != 0) { isoLinePathPoints = isoLinePathPoints.ShortenedBy(RADouble.NewAbs(gap / 2), RADouble.NewAbs(0)); if (null == isoLine) continue; } } if (null != isoLinePathPoints) g.DrawLines(_strokePen, isoLinePathPoints); else g.DrawPath(strokePen, isoLine); } } }
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[] circularLinePoints; if (!connectCircular && range.LowerBound == 0 && range.UpperBound == allLinePoints.Length) { // under optimal conditions we can use allLinePoints directly circularLinePoints = allLinePoints; } else { // otherwise, make a new array circularLinePoints = new PointF[range.Length + (connectCircular ? 1 : 0)]; Array.Copy(allLinePoints, range.LowerBound, circularLinePoints, 0, range.Length); // Extract if (connectCircular) circularLinePoints[circularLinePoints.Length - 1] = circularLinePoints[0]; } int lastIdx = range.Length - 1 + (connectCircular ? 1 : 0); GraphicsPath gp = new GraphicsPath(); var layerSize = layer.Size; var rangeLowerBound = range.LowerBound; // 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 xdiff, ydiff, startx, starty, stopx, stopy; if (skipFrequency <= 1) // skip all scatter symbol gaps -> thus skipOffset can be ignored { for (int i = 0; i < lastIdx; i++) { xdiff = circularLinePoints[i + 1].X - circularLinePoints[i].X; ydiff = circularLinePoints[i + 1].Y - circularLinePoints[i].Y; var diffLength = System.Math.Sqrt(xdiff * xdiff + ydiff * ydiff); double gapAtStart = symbolGap(range.GetOriginalRowIndexFromPlotPointIndex(range.LowerBound + i)); double gapAtEnd; if (connectCircular && skipFrequency >= (range.Length - i)) gapAtEnd = symbolGap(range.OriginalFirstPoint); else if (skipFrequency <= (range.Length - 1 - i)) gapAtEnd = symbolGap(range.GetOriginalRowIndexFromPlotPointIndex(range.LowerBound + i + skipFrequency)); 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 = circularLinePoints[i].X + relAtStart * xdiff; starty = circularLinePoints[i].Y + relAtStart * ydiff; stopx = circularLinePoints[i + 1].X - relAtEnd * xdiff; stopy = circularLinePoints[i + 1].Y - relAtEnd * ydiff; gp.AddLine(startx, starty, stopx, stopy); gp.StartFigure(); } } // end for g.DrawPath(linePen, gp); gp.Reset(); } else // skipFrequency is > 1 { for (int i = 0; i < lastIdx; i += skipFrequency) { int subPointLengthM1 = Math.Min(skipFrequency, circularLinePoints.Length - 1 - i); double gapAtStart = symbolGap(range.GetOriginalRowIndexFromPlotPointIndex(range.LowerBound + i)); double gapAtEnd; if (connectCircular && skipFrequency >= (range.Length - i)) gapAtEnd = symbolGap(range.OriginalFirstPoint); else if (skipFrequency <= (range.Length - 1 - i)) gapAtEnd = symbolGap(range.GetOriginalRowIndexFromPlotPointIndex(range.LowerBound + i + skipFrequency)); else gapAtEnd = 0; if (subPointLengthM1 >= 1) { var polyline = circularLinePoints.ShortenPartialPolylineByDistanceFromStartAndEnd(i, i + subPointLengthM1, gapAtStart / 2, gapAtEnd / 2); if (null != polyline) g.DrawLines(linePen, polyline); } } // end for } } else // no line symbol gap required, so we can use DrawLines to draw the lines { if (circularLinePoints.Length > 1) // we don't want to have a drawing exception if number of points is only one { g.DrawLines(linePen, circularLinePoints); } } }
/// <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 (symbolGap != null) { // convert points to bezier segments var bezierSegments = GdiExtensionMethods.ClosedCardinalSplineToBezierSegments(subLinePoints, subLinePoints.Length); var subBezierSegments = new PointF[0]; int subPointLengthM1, subBezierLength; for (int i = 0; i < (range.Length); i += skipFrequency) { subPointLengthM1 = Math.Min(skipFrequency, range.Length - i); int originalIndexAtStart = range.GetOriginalRowIndexFromPlotPointIndex(i + range.LowerBound); double gapAtStart = symbolGap(originalIndexAtStart); int originalIndexAtEnd = ((i + skipFrequency) < range.Length) ? range.GetOriginalRowIndexFromPlotPointIndex(i + range.LowerBound + skipFrequency) : range.OriginalFirstPoint; double gapAtEnd = symbolGap(originalIndexAtEnd); subBezierLength = 3 * subPointLengthM1 + 1; if (subBezierSegments.Length != subBezierLength) subBezierSegments = new PointF[subBezierLength]; Array.Copy(bezierSegments, i * 3, subBezierSegments, 0, subBezierLength); var shortenedBezierSegments = GdiExtensionMethods.ShortenBezierCurve(subBezierSegments, gapAtStart / 2, gapAtEnd / 2); if (null != shortenedBezierSegments) { g.DrawBeziers(linePen, shortenedBezierSegments); } } } else { g.DrawClosedCurve(linePen, subLinePoints); } } else { if (symbolGap != null) { // convert points to bezier segments var bezierSegments = GdiExtensionMethods.OpenCardinalSplineToBezierSegments(subLinePoints, subLinePoints.Length); var subBezierSegments = new PointF[0]; int subPointLengthM1, subBezierLength; for (int i = 0; i < (range.Length - 1); i += skipFrequency) { subPointLengthM1 = Math.Min(skipFrequency, range.Length - 1 - i); int originalIndex = range.GetOriginalRowIndexFromPlotPointIndex(i + range.LowerBound); double gapAtStart = symbolGap(originalIndex); double gapAtEnd = subPointLengthM1 == skipFrequency ? symbolGap(range.GetOriginalRowIndexFromPlotPointIndex(i + range.LowerBound + skipFrequency)) : 0; subBezierLength = 3 * subPointLengthM1 + 1; if (subBezierSegments.Length != subBezierLength) subBezierSegments = new PointF[subBezierLength]; Array.Copy(bezierSegments, i * 3, subBezierSegments, 0, subBezierLength); var shortenedBezierSegments = GdiExtensionMethods.ShortenBezierCurve(subBezierSegments, gapAtStart / 2, gapAtEnd / 2); if (null != shortenedBezierSegments) { g.DrawBeziers(linePen, shortenedBezierSegments); } } } else { g.DrawCurve(linePen, subLinePoints); } } }
private void PaintOneRange( Graphics g, IPlotArea layer, PointF[] plotPositions, IPlotRange range, IScatterSymbol scatterSymbol, ref CachedPathData cachedPathData, ref CachedBrushData cachedBrushData) { var ptArray = plotPositions; float xpos = 0, ypos = 0; float xdiff, ydiff; int originalIndex; // save the graphics stat since we have to translate the origin System.Drawing.Drawing2D.GraphicsState gs = g.Save(); if (null == _cachedSymbolSizeForIndexFunction && null == _cachedColorForIndexFunction) // using a constant symbol size { // calculate the path only once CalculatePaths(scatterSymbol, _symbolSize, ref cachedPathData); CalculateBrushes(scatterSymbol, _color, cachedPathData, ref cachedBrushData); for (int plotPointIndex = range.LowerBound; plotPointIndex < range.UpperBound; plotPointIndex += _skipFreq) { xdiff = ptArray[plotPointIndex].X - xpos; ydiff = ptArray[plotPointIndex].Y - ypos; xpos = ptArray[plotPointIndex].X; ypos = ptArray[plotPointIndex].Y; g.TranslateTransform(xdiff, ydiff); if (null != cachedPathData.InsetPath) g.FillPath(cachedBrushData.InsetBrush, cachedPathData.InsetPath); if (null != cachedPathData.FillPath) g.FillPath(cachedBrushData.FillBrush, cachedPathData.FillPath); if (null != cachedPathData.FramePath) g.FillPath(cachedBrushData.FrameBrush, cachedPathData.FramePath); } // end for } else // using a variable symbol size or variable symbol color { CalculatePaths(scatterSymbol, _symbolSize, ref cachedPathData); CalculateBrushes(scatterSymbol, _color, cachedPathData, ref cachedBrushData); for (int plotPointIndex = range.LowerBound; plotPointIndex < range.UpperBound; plotPointIndex += _skipFreq) { originalIndex = range.GetOriginalRowIndexFromPlotPointIndex(plotPointIndex); if (null == _cachedColorForIndexFunction) { double customSymbolSize = _cachedSymbolSizeForIndexFunction(originalIndex); CalculatePaths(scatterSymbol, customSymbolSize, ref cachedPathData); } else { double customSymbolSize = null == _cachedSymbolSizeForIndexFunction ? _symbolSize : _cachedSymbolSizeForIndexFunction(originalIndex); var customSymbolColor = _cachedColorForIndexFunction(originalIndex); CalculatePaths(scatterSymbol, customSymbolSize, ref cachedPathData); CalculateBrushes(scatterSymbol, NamedColor.FromArgb(customSymbolColor.A, customSymbolColor.R, customSymbolColor.G, customSymbolColor.B), cachedPathData, ref cachedBrushData); } xdiff = ptArray[plotPointIndex].X - xpos; ydiff = ptArray[plotPointIndex].Y - ypos; xpos = ptArray[plotPointIndex].X; ypos = ptArray[plotPointIndex].Y; g.TranslateTransform(xdiff, ydiff); if (null != cachedPathData.InsetPath) g.FillPath(cachedBrushData.InsetBrush, cachedPathData.InsetPath); if (null != cachedPathData.FillPath) g.FillPath(cachedBrushData.FillBrush, cachedPathData.FillPath); if (null != cachedPathData.FramePath) g.FillPath(cachedBrushData.FrameBrush, cachedPathData.FramePath); } } g.Restore(gs); // Restore the graphics state }
private void PaintOneRange( Graphics g, IPlotArea layer, PointF[] plotPositions, IPlotRange range, IScatterSymbol scatterSymbol, ref CachedPathData cachedPathData, ref CachedBrushData cachedBrushData) { var ptArray = plotPositions; float xpos = 0, ypos = 0; float xdiff, ydiff; int originalIndex; // save the graphics stat since we have to translate the origin System.Drawing.Drawing2D.GraphicsState gs = g.Save(); if (null == _cachedSymbolSizeForIndexFunction && null == _cachedColorForIndexFunction) // using a constant symbol size { // calculate the path only once CalculatePaths(scatterSymbol, _symbolSize, ref cachedPathData); CalculateBrushes(scatterSymbol, _color, cachedPathData, ref cachedBrushData); for (int plotPointIndex = range.LowerBound; plotPointIndex < range.UpperBound; plotPointIndex += _skipFreq) { xdiff = ptArray[plotPointIndex].X - xpos; ydiff = ptArray[plotPointIndex].Y - ypos; xpos = ptArray[plotPointIndex].X; ypos = ptArray[plotPointIndex].Y; g.TranslateTransform(xdiff, ydiff); if (null != cachedPathData.InsetPath) { g.FillPath(cachedBrushData.InsetBrush, cachedPathData.InsetPath); } if (null != cachedPathData.FillPath) { g.FillPath(cachedBrushData.FillBrush, cachedPathData.FillPath); } if (null != cachedPathData.FramePath) { g.FillPath(cachedBrushData.FrameBrush, cachedPathData.FramePath); } } // end for } else // using a variable symbol size or variable symbol color { CalculatePaths(scatterSymbol, _symbolSize, ref cachedPathData); CalculateBrushes(scatterSymbol, _color, cachedPathData, ref cachedBrushData); for (int plotPointIndex = range.LowerBound; plotPointIndex < range.UpperBound; plotPointIndex += _skipFreq) { originalIndex = range.GetOriginalRowIndexFromPlotPointIndex(plotPointIndex); if (null == _cachedColorForIndexFunction) { double customSymbolSize = _cachedSymbolSizeForIndexFunction(originalIndex); CalculatePaths(scatterSymbol, customSymbolSize, ref cachedPathData); } else { double customSymbolSize = null == _cachedSymbolSizeForIndexFunction ? _symbolSize : _cachedSymbolSizeForIndexFunction(originalIndex); var customSymbolColor = _cachedColorForIndexFunction(originalIndex); CalculatePaths(scatterSymbol, customSymbolSize, ref cachedPathData); CalculateBrushes(scatterSymbol, NamedColor.FromArgb(customSymbolColor.A, customSymbolColor.R, customSymbolColor.G, customSymbolColor.B), cachedPathData, ref cachedBrushData); } xdiff = ptArray[plotPointIndex].X - xpos; ydiff = ptArray[plotPointIndex].Y - ypos; xpos = ptArray[plotPointIndex].X; ypos = ptArray[plotPointIndex].Y; g.TranslateTransform(xdiff, ydiff); if (null != cachedPathData.InsetPath) { g.FillPath(cachedBrushData.InsetBrush, cachedPathData.InsetPath); } if (null != cachedPathData.FillPath) { g.FillPath(cachedBrushData.FillBrush, cachedPathData.FillPath); } if (null != cachedPathData.FramePath) { g.FillPath(cachedBrushData.FrameBrush, cachedPathData.FramePath); } } } g.Restore(gs); // Restore the graphics state }
protected void PaintOneRange(Graphics g, IPlotArea layer, IPlotRange range, Processed2DPlotData pdata) { const double logicalClampMinimum = -10; const double logicalClampMaximum = 11; // Plot error bars for the dependent variable (y) var columnX = ColumnX; var columnY = ColumnY; if (columnX == null || columnY == null) { return; // nothing to do if both error columns are null } if (!typeof(double).IsAssignableFrom(columnX.ItemType) || !typeof(double).IsAssignableFrom(columnY.ItemType)) { return; // TODO make this an runtime paint error to be reported } var strokePen = _strokePen.Clone(); using (var isoLine = new GraphicsPath()) { int lower = range.LowerBound; int upper = range.UpperBound; for (int j = lower; j < upper; j += _skipFrequency) { int originalRowIndex = range.GetOriginalRowIndexFromPlotPointIndex(j); double symbolSize = null == _cachedSymbolSizeForIndexFunction ? _symbolSize : _cachedSymbolSizeForIndexFunction(originalRowIndex); strokePen.Width = (_lineWidth1Offset + _lineWidth1Factor * symbolSize); if (null != _cachedColorForIndexFunction) { strokePen.Color = GdiColorHelper.ToNamedColor(_cachedColorForIndexFunction(originalRowIndex), "VariableColor"); } if (!(strokePen.EndCap is LineCaps.FlatCap)) { strokePen.EndCap = strokePen.EndCap.WithMinimumAbsoluteAndRelativeSize(symbolSize * _endCapSizeFactor + _endCapSizeOffset, 1 + 1E-6); } // Calculate target AltaxoVariant targetX, targetY; switch (_meaningOfValues) { case ValueInterpretation.AbsoluteDifference: { targetX = pdata.GetXPhysical(originalRowIndex) + columnX[originalRowIndex]; targetY = pdata.GetYPhysical(originalRowIndex) + columnY[originalRowIndex]; } break; case ValueInterpretation.AbsoluteValue: { targetX = columnX[originalRowIndex]; targetY = columnY[originalRowIndex]; } break; default: throw new NotImplementedException(nameof(_meaningOfValues)); } var logicalTarget = layer.GetLogical3D(targetX, targetY); var logicalOrigin = layer.GetLogical3D(pdata, originalRowIndex); if (!_independentOnShiftingGroupStyles && (0 != _cachedLogicalShiftX || 0 != _cachedLogicalShiftY)) { logicalOrigin.RX += _cachedLogicalShiftX; logicalOrigin.RY += _cachedLogicalShiftY; logicalTarget.RX += _cachedLogicalShiftX; logicalTarget.RY += _cachedLogicalShiftY; } if (!Calc.RMath.IsInIntervalCC(logicalOrigin.RX, logicalClampMinimum, logicalClampMaximum)) { continue; } if (!Calc.RMath.IsInIntervalCC(logicalOrigin.RY, logicalClampMinimum, logicalClampMaximum)) { continue; } if (!Calc.RMath.IsInIntervalCC(logicalOrigin.RZ, logicalClampMinimum, logicalClampMaximum)) { continue; } if (!Calc.RMath.IsInIntervalCC(logicalTarget.RX, logicalClampMinimum, logicalClampMaximum)) { continue; } if (!Calc.RMath.IsInIntervalCC(logicalTarget.RY, logicalClampMinimum, logicalClampMaximum)) { continue; } if (!Calc.RMath.IsInIntervalCC(logicalTarget.RZ, logicalClampMinimum, logicalClampMaximum)) { continue; } isoLine.Reset(); layer.CoordinateSystem.GetIsoline(isoLine, logicalOrigin, logicalTarget); if (null == isoLine) { continue; } PointF[] isoLinePathPoints = null; if (_useManualVectorLength) { isoLine.Flatten(); isoLinePathPoints = isoLine.PathPoints; double length = _vectorLengthOffset + _vectorLengthFactor * symbolSize; double isoLineLength = isoLinePathPoints.TotalLineLength(); isoLinePathPoints = isoLinePathPoints.ShortenedBy(RADouble.NewAbs(0), RADouble.NewAbs(isoLineLength - length)); if (null == isoLine) { continue; } } if (_useSymbolGap) { if (null == isoLinePathPoints) { isoLine.Flatten(); isoLinePathPoints = isoLine.PathPoints; } double gap = _symbolGapOffset + _symbolGapFactor * symbolSize; if (gap != 0) { isoLinePathPoints = isoLinePathPoints.ShortenedBy(RADouble.NewAbs(gap / 2), RADouble.NewAbs(0)); if (null == isoLine) { continue; } } } if (null != isoLinePathPoints) { g.DrawLines(_strokePen, isoLinePathPoints); } else { g.DrawPath(strokePen, isoLine); } } } }