/// <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="pen">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, the line is connected circular, and the area is the polygon inside of that circular connection.</param> /// <param name="linePlotStyle">The line plot style.</param> public abstract void PaintOneRange( Graphics g, PointF[] allLinePoints, IPlotRange range, IPlotArea layer, PenX pen, Func <int, double> symbolGap, int skipFrequency, bool connectCircular, LinePlotStyle linePlotStyle );
/// <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="pen">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, the line is connected circular, and the area is the polygon inside of that circular connection.</param> /// <param name="linePlotStyle">The line plot style.</param> public abstract void PaintOneRange( Graphics g, PointF[] allLinePoints, IPlotRange range, IPlotArea layer, PenX pen, Func<int, double> symbolGap, int skipFrequency, bool connectCircular, LinePlotStyle linePlotStyle );
public XYPlotLineStyleController(LinePlotStyle doc) { if (doc == null) { throw new ArgumentNullException("doc is null"); } if (!InitializeDocument(doc)) { throw new ApplicationException("Programming error"); } }
private void cmdOK_Click(object sender, EventArgs e) { if (cboScale.SelectedIndex > -1) { color = pictureBoxColor.BackColor; scale = double.Parse(cboScale.Items[cboScale.SelectedIndex].ToString()); plotStyle = (LinePlotStyle)cboPlotStyle.SelectedIndex; dashStyle = (System.Drawing.Drawing2D.DashStyle)cboDashStyle.SelectedIndex; DialogResult = DialogResult.OK; Close(); } }
public static G2DPlotStyleCollection PlotStyle_LineArea(Altaxo.Main.Properties.IReadOnlyPropertyBag context) { var result = new G2DPlotStyleCollection(); var ps1 = new LinePlotStyle(context); var ps2 = new DropAreaPlotStyle(context) { FillDirection = Graph.CSPlaneID.Bottom }; result.Add(ps1); result.Add(ps2); return(result); }
/// <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 <= 1) { return; // seems to be only a single point, thus no connection possible } PointF[] stepPolylinePoints = GetStepPolylinePoints(allLinePoints, range, layer, connectCircular, out var numberOfPointsPerOriginalPoint, out var lastIdx); if (null != symbolGap) { 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.DrawPolygon(linePen, stepPolylinePoints); } else { int plotIndexAtStart = segmentRange.IndexAtSubRangeStart * numberOfPointsPerOriginalPoint; int plotIndexAtEnd = segmentRange.IndexAtSubRangeEnd * numberOfPointsPerOriginalPoint; var shortenedPolyline = stepPolylinePoints.ShortenPartialPolylineByDistanceFromStartAndEnd(plotIndexAtStart, plotIndexAtEnd, segmentRange.GapAtSubRangeStart / 2, segmentRange.GapAtSubRangeEnd / 2); if (null != shortenedPolyline) { g.DrawLines(linePen, shortenedPolyline); } } } } else { if (connectCircular) { g.DrawPolygon(linePen, stepPolylinePoints); } else { g.DrawLines(linePen, stepPolylinePoints); } } }
public bool InitializeDocument(params object[] args) { if (args.Length == 0 || !(args[0] is LinePlotStyle)) { return(false); } bool isFirstTime = (null == _doc); _doc = (LinePlotStyle)args[0]; _tempDoc = _useDocumentCopy == UseDocument.Directly ? _doc : (LinePlotStyle)_doc.Clone(); Initialize(isFirstTime); return(true); }
public ILine AddLine(string nameId, Color color, double scale, LinePlotStyle plotStyle, DashStyle dashStyle) { if (LineExists(nameId)) { return(GetLine(nameId)); } ILine line = new FlowLine(nameId, color, scale); line.Color = color; line.Scale = scale; line.PlotStyle = plotStyle; line.DashStyle = dashStyle; lines.Add(line); return(line); }
private void DrawLineMarker(ref Graphics g, Color lineColor, int x, int y, LinePlotStyle style) { using (Pen plotPen = new Pen(lineColor, 2)) { switch (style) { case LinePlotStyle.Dots: g.DrawEllipse(plotPen, x - 2, y - 2, 4, 4); break; case LinePlotStyle.Cross: g.DrawLine(plotPen, x, y - 3, x, y + 3); g.DrawLine(plotPen, x - 3, y, x + 3, y); break; case LinePlotStyle.Ex: g.DrawLine(plotPen, x - 3, y - 3, x + 3, y + 3); g.DrawLine(plotPen, x - 3, y + 3, x + 3, y - 3); break; case LinePlotStyle.Box: g.DrawLine(plotPen, x - 3, y - 3, x + 3, y - 3); g.DrawLine(plotPen, x + 3, y - 3, x + 3, y + 3); g.DrawLine(plotPen, x + 3, y + 3, x - 3, y + 3); g.DrawLine(plotPen, x - 3, y + 3, x - 3, y - 3); break; case LinePlotStyle.Triangle: g.DrawLine(plotPen, x, y - 3, x + 3, y + 3); g.DrawLine(plotPen, x + 3, y + 3, x - 3, y + 3); g.DrawLine(plotPen, x - 3, y + 3, x, y - 3); break; default: break; } } }
/// <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); } }
/// <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(); } }
/// <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); } } }
/// <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) { if (range.Length <= 1) { return; // seems to be only a single point, thus no connection possible } PointF[] circularLinePoints; int indexBasePlotPoints; // index of the first plot point of this range in circularLinePoints array if (connectCircular) // we have to copy the array in order to append the first point to the end { // otherwise, make a new array circularLinePoints = new PointF[range.Length + 1]; Array.Copy(allLinePoints, range.LowerBound, circularLinePoints, 0, range.Length); // Extract circularLinePoints[circularLinePoints.Length - 1] = circularLinePoints[0]; indexBasePlotPoints = 0; } else // use the array directly without copying { circularLinePoints = allLinePoints; indexBasePlotPoints = range.LowerBound; } if (null != symbolGap) { 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.DrawPolygon(linePen, circularLinePoints); } else if (segmentRange.Length == 1) // special case only one line segment { int plotIndexAtStart = segmentRange.IndexAtSubRangeStart + indexBasePlotPoints; int plotIndexAtEnd = segmentRange.IndexAtSubRangeEnd + indexBasePlotPoints; var xdiff = circularLinePoints[plotIndexAtEnd].X - circularLinePoints[plotIndexAtStart].X; var ydiff = circularLinePoints[plotIndexAtEnd].Y - circularLinePoints[plotIndexAtStart].Y; var diffLength = System.Math.Sqrt(xdiff * xdiff + ydiff * ydiff); var relAtStart = (float)(0.5 * segmentRange.GapAtSubRangeStart / 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 * segmentRange.GapAtSubRangeEnd / 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 { var startx = circularLinePoints[plotIndexAtStart].X + relAtStart * xdiff; var starty = circularLinePoints[plotIndexAtStart].Y + relAtStart * ydiff; var stopx = circularLinePoints[plotIndexAtEnd].X - relAtEnd * xdiff; var stopy = circularLinePoints[plotIndexAtEnd].Y - relAtEnd * ydiff; g.DrawLine(linePen, startx, starty, stopx, stopy); } } else { int plotIndexAtStart = segmentRange.IndexAtSubRangeStart + indexBasePlotPoints; int plotIndexAtEnd = segmentRange.IndexAtSubRangeEnd + indexBasePlotPoints; var shortenedPolyline = circularLinePoints.ShortenPartialPolylineByDistanceFromStartAndEnd(plotIndexAtStart, plotIndexAtEnd, segmentRange.GapAtSubRangeStart / 2, segmentRange.GapAtSubRangeEnd / 2); if (null != shortenedPolyline) { g.DrawLines(linePen, shortenedPolyline); } } } // end for } else // no line symbol gap required, so we can use DrawLines or DrawPolygon to draw the lines { if (connectCircular) // array was already copied from original array { g.DrawPolygon(linePen, circularLinePoints); } else if (indexBasePlotPoints == 0 && range.Length == circularLinePoints.Length) // can use original array directly { g.DrawLines(linePen, circularLinePoints); } else { circularLinePoints = new PointF[range.Length]; Array.Copy(allLinePoints, range.LowerBound, circularLinePoints, 0, range.Length); 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); } } }
/// <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(); } }
void InitializeStyles() { // Clear the previous controller cache _additionalPlotStyle = null; if (_combinedScatterLineGroupController != null) { _combinedScatterLineGroupController.ChildControlChanged -= EhView_ActiveChildControlChanged; _combinedScatterLineGroupController = null; } _styleControllerList.Clear(); // start to create new controllers if (_tempdoc.Style.Count > 0) { bool addHelperStyle = NeedsHelperStyle(); bool useCombinedTab = UseCombinedScatterLineGroupTab(); if (useCombinedTab) { List <ControlViewElement> combList = new List <ControlViewElement>(); // create the controllers IMVCANController ct1 = GetStyleController(_tempdoc.Style[0]); _styleControllerList.Add(ct1); if (addHelperStyle) { IPlotArea layer = Main.DocumentPath.GetRootNodeImplementing <IPlotArea>(_doc); // add either line or scatter if (_tempdoc.Style[0] is LinePlotStyle) { ScatterPlotStyle scatterStyle = new ScatterPlotStyle(); scatterStyle.ParentObject = _tempdoc.Style; _tempdoc.Style.PrepareNewSubStyle(scatterStyle, layer, _doc.GetRangesAndPoints(layer)); _additionalPlotStyle = scatterStyle; scatterStyle.Shape = Altaxo.Graph.Gdi.Plot.Styles.XYPlotScatterStyles.Shape.NoSymbol; _additionalPlotStyleController = GetStyleController(_additionalPlotStyle); combList.Add(new ControlViewElement("Symbol", _additionalPlotStyleController)); combList.Add(new ControlViewElement("Line", ct1)); } else { LinePlotStyle lineStyle = new LinePlotStyle(); lineStyle.ParentObject = _tempdoc.Style; _tempdoc.Style.PrepareNewSubStyle(lineStyle, layer, _doc.GetRangesAndPoints(layer)); _additionalPlotStyle = lineStyle; lineStyle.Connection = Altaxo.Graph.Gdi.Plot.Styles.XYPlotLineStyles.ConnectionStyle.NoLine; _additionalPlotStyleController = GetStyleController(_additionalPlotStyle); combList.Add(new ControlViewElement("Symbol", ct1)); combList.Add(new ControlViewElement("Line", _additionalPlotStyleController)); } } else // no helper style, i.e. second style is line style { // create the controllers IMVCANController ct2 = GetStyleController(_tempdoc.Style[1]); _styleControllerList.Add(ct2); combList.Add(new ControlViewElement("Symbol", ct1)); combList.Add(new ControlViewElement("Line", ct2)); } combList.Add(new ControlViewElement(string.Empty, this, this._plotGroupView)); _combinedScatterLineGroupController = new Common.MultiChildController(combList.ToArray(), true); Current.Gui.FindAndAttachControlTo(_combinedScatterLineGroupController); string title; if (null != _additionalPlotStyle) { title = string.Format("#{0}:{1}", 1, Current.Gui.GetUserFriendlyClassName(_tempdoc.Style[0].GetType())); } else { title = "#1&&2:Symbol&&Line"; } AddTab(title, _combinedScatterLineGroupController, _combinedScatterLineGroupController.ViewObject); _combinedScatterLineGroupController.ChildControlChanged += this.EhView_ActiveChildControlChanged; } // if use CombinedTab // now the remaining styles int start = useCombinedTab ? (addHelperStyle ? 1 : 2) : 0; for (int i = start; i < _tempdoc.Style.Count; i++) { IMVCANController ctrl = GetStyleController(_tempdoc.Style[i]); _styleControllerList.Add(ctrl); string title = string.Format("#{0}:{1}", (i + 1), Current.Gui.GetUserFriendlyClassName(_tempdoc.Style[i].GetType())); AddTab(title, ctrl, ctrl != null ? ctrl.ViewObject : null); } } base.SetElements(false); }
/// <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); } } }