/// <summary> /// Template to get a fill path. /// </summary> /// <param name="gp">Graphics path to fill with data.</param> /// <param name="pdata">The plot data. Do not use the plot point positions from here, since they are not shifted when shifting group styles are present.</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="ignoreMissingDataPoints">If true, missing data points are ignored.</param> /// <param name="connectCircular">If true, the line is connected circular, and the area is the polygon inside of that circular connection.</param> /// <param name="allLinePointsShiftedAlready">The plot positions, already shifted when a logical shift needed to be applied. Don't use the Range property of the pdata, since it is overriden by the next argument.</param> /// <param name="logicalShiftX">The logical shift in x-direction.</param> /// <param name="logicalShiftY">The logical shift in x-direction.</param> public abstract void FillOneRange( GraphicsPath gp, Processed2DPlotData pdata, IPlotRange range, IPlotArea layer, CSPlaneID fillDirection, bool ignoreMissingDataPoints, bool connectCircular, PointF[] allLinePointsShiftedAlready, double logicalShiftX, double logicalShiftY );
/// <summary> /// Shift the plot points provided in <paramref name="pdata"/> with a logical shift in x- and/or y- direction. /// </summary> /// <param name="pdata">The plot point position data.</param> /// <param name="layer">The plot layer for which the data are intended.</param> /// <param name="logicalShiftX">The logical shift in x-direction.</param> /// <param name="logicalShiftY">The logical shift in y-direction.</param> /// <returns>Array of plot point positions, but now shifted by logical values in x-direction (<paramref name="logicalShiftX"/>) and y-direction (<paramref name="logicalShiftY"/>).</returns> public static PointF[] GetPlotPointsInAbsoluteLayerCoordinatesWithShift(Processed2DPlotData pdata, IPlotArea layer, double logicalShiftX, double logicalShiftY) { var result = new PointF[pdata.PlotPointsInAbsoluteLayerCoordinates.Length]; foreach (PlotRange r in pdata.RangeList) { int lower = r.LowerBound; int upper = r.UpperBound; int offset = r.OffsetToOriginal; for (int j = lower; j < upper; ++j) { int originalRow = j + offset; Logical3D logicalMean = layer.GetLogical3D(pdata, originalRow); logicalMean.RX += logicalShiftX; logicalMean.RY += logicalShiftY; layer.CoordinateSystem.LogicalToLayerCoordinates(logicalMean, out var x, out var y); result[j] = new PointF((float)x, (float)y); } } return(result); }
/// <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(); } }
/// <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> /// <param name="allLinePointsShiftedAlready">The plot positions, already shifted when a logical shift needed to be applied. Don't use the Range property of the pdata, since it is overriden by the next argument.</param> /// <param name="logicalShiftX">The logical shift in x-direction.</param> /// <param name="logicalShiftY">The logical shift in x-direction.</param> public virtual void FillOneRange( GraphicsPath gp, Processed2DPlotData pdata, IPlotRange range, IPlotArea layer, CSPlaneID fillDirection, PointF[] linePoints, bool connectCircular, PointF[] allLinePointsShiftedAlready, double logicalShiftX, double logicalShiftY ) { if (connectCircular) { gp.AddLines(linePoints); gp.CloseFigure(); } else { Logical3D r0 = layer.GetLogical3D(pdata, range.OriginalFirstPoint); r0.RX += logicalShiftX; r0.RY += logicalShiftY; layer.CoordinateSystem.GetIsolineFromPlaneToPoint(gp, fillDirection, r0); gp.AddLines(linePoints); Logical3D r1 = layer.GetLogical3D(pdata, range.OriginalLastPoint); r1.RX += logicalShiftX; r1.RY += logicalShiftY; layer.CoordinateSystem.GetIsolineFromPointToPlane(gp, r1, fillDirection); layer.CoordinateSystem.GetIsolineOnPlane(gp, fillDirection, r1, r0); gp.CloseFigure(); } }
/// <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 < 2) return; int lastIdx; int numberOfPointsPerOriginalPoint; PointF[] linepts = GetStepPolylinePoints(allLinePoints, range, layer, connectCircular, out numberOfPointsPerOriginalPoint, out lastIdx); FillOneRange(gp, pdata, range, layer, fillDirection, linepts, connectCircular, allLinePoints, logicalShiftX, logicalShiftY); }
/// <summary> /// Shift the plot points provided in <paramref name="pdata"/> with a logical shift in x- and/or y- direction. /// </summary> /// <param name="pdata">The plot point position data.</param> /// <param name="layer">The plot layer for which the data are intended.</param> /// <param name="logicalShiftX">The logical shift in x-direction.</param> /// <param name="logicalShiftY">The logical shift in y-direction.</param> /// <returns>Array of plot point positions, but now shifted by logical values in x-direction (<paramref name="logicalShiftX"/>) and y-direction (<paramref name="logicalShiftY"/>).</returns> public static PointF[] GetPlotPointsInAbsoluteLayerCoordinatesWithShift(Processed2DPlotData pdata, IPlotArea layer, double logicalShiftX, double logicalShiftY) { var result = new PointF[pdata.PlotPointsInAbsoluteLayerCoordinates.Length]; foreach (PlotRange r in pdata.RangeList) { int lower = r.LowerBound; int upper = r.UpperBound; int offset = r.OffsetToOriginal; for (int j = lower; j < upper; ++j) { int originalRow = j + offset; Logical3D logicalMean = layer.GetLogical3D(pdata, originalRow); logicalMean.RX += logicalShiftX; logicalMean.RY += logicalShiftY; double x, y; layer.CoordinateSystem.LogicalToLayerCoordinates(logicalMean, out x, out y); result[j] = new PointF((float)x, (float)y); } } return result; }
public void Paint(System.Drawing.Graphics g, IPlotArea layer, Altaxo.Graph.Gdi.Plot.Data.Processed2DPlotData pdata, Processed2DPlotData prevItemData, Processed2DPlotData nextItemData) { // adjust the skip frequency if it was not set appropriate if (_skipFrequency <= 0) _skipFrequency = 1; if (_independentOnShiftingGroupStyles) { _cachedLogicalShiftX = _cachedLogicalShiftY = 0; } PlotRangeList rangeList = pdata.RangeList; if (this._ignoreMissingDataPoints) { // in case we ignore the missing points, all ranges can be plotted // as one range, i.e. continuously // for this, we create the totalRange, which contains all ranges var totalRange = new PlotRangeCompound(rangeList); this.PaintOneRange(AxisNumber, g, layer, totalRange, pdata); } else // we not ignore missing points, so plot all ranges separately { for (int i = 0; i < rangeList.Count; i++) { this.PaintOneRange(AxisNumber, g, layer, rangeList[i], pdata); } } }
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; }
/// <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 ) { PointF[] 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]; if (connectCircular) { gp.AddLines(circularLinePoints); gp.CloseFigure(); } else // not circular { Logical3D r0 = layer.GetLogical3D(pdata, range.OriginalFirstPoint); r0.RX += logicalShiftX; r0.RY += logicalShiftY; layer.CoordinateSystem.GetIsolineFromPlaneToPoint(gp, fillDirection, r0); gp.AddLines(circularLinePoints); Logical3D r1 = layer.GetLogical3D(pdata, connectCircular ? range.OriginalFirstPoint : range.OriginalLastPoint); r1.RX += logicalShiftX; r1.RY += logicalShiftY; layer.CoordinateSystem.GetIsolineFromPointToPlane(gp, r1, fillDirection); layer.CoordinateSystem.GetIsolineOnPlane(gp, fillDirection, r1, r0); 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(); } }
/// <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); } }