/// <summary> /// Template to make a line draw. /// </summary> /// <param name="g">Graphics context.</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="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 end of the line is connected with the start of the line.</param> public abstract void Paint( IGraphicsContext3D g, Processed3DPlotData pdata, PlotRange range, IPlotArea layer, PenX3D pen, Func<int, double> symbolGap, int skipFrequency, bool connectCircular);
/// <summary> /// Template to make a line draw. /// </summary> /// <param name="g">Graphics context.</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="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 end of the line is connected with the start of the line.</param> public override void Paint( IGraphicsContext3D g, Processed3DPlotData pdata, PlotRange range, IPlotArea layer, PenX3D pen, Func<int, double> symbolGap, int skipFrequency, bool connectCircular) { var linePoints = pdata.PlotPointsInAbsoluteLayerCoordinates; var linepts = new PointD3D[range.Length + (connectCircular ? 1 : 0)]; Array.Copy(linePoints, range.LowerBound, linepts, 0, range.Length); // Extract if (connectCircular) linepts[linepts.Length - 1] = linepts[0]; int lastIdx = range.Length - 1 + (connectCircular ? 1 : 0); var layerSize = layer.Size; if (symbolGap != null) { if (skipFrequency <= 1) // skip all scatter symbol gaps -> thus skipOffset can be ignored { for (int i = 0; i < lastIdx; i++) { int originalIndex = range.OffsetToOriginal + i; var diff = linepts[i + 1] - linepts[i]; double gapAtStart = symbolGap(originalIndex); double gapAtEnd = i != (range.Length-1) ? symbolGap(originalIndex + 1) : symbolGap(range.OffsetToOriginal); var relAtStart = 0.5 * gapAtStart / diff.Length; // 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 = 0.5 * gapAtEnd / diff.Length; // 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 start = linepts[i] + relAtStart * diff; var stop = linepts[i + 1] - relAtEnd * diff; g.DrawLine(pen, start, stop); } } // end for } // skipFrequency was 1 else // skipFrequency is > 1 { for (int i = 0; i < lastIdx; i += skipFrequency) { int originalRowIndex = range.OriginalFirstPoint + i; double gapAtStart = symbolGap(originalRowIndex); double gapAtEnd = i != range.Length ? symbolGap(originalRowIndex + skipFrequency) : symbolGap(range.OffsetToOriginal); IPolylineD3D polyline = SharpPolylineD3D.FromPointsWithPossibleDublettes(linepts.Skip(i).Take(1 + skipFrequency)); polyline = polyline.ShortenedBy(RADouble.NewAbs(gapAtStart / 2), RADouble.NewAbs(gapAtEnd / 2)); if (null != polyline) g.DrawLine(pen, polyline); } // end for. } } else // no line symbol gap required, so we can use DrawLines to draw the lines { if (linepts.Length > 1) // we don't want to have a drawing exception if number of points is only one { g.DrawLine(pen, SharpPolylineD3D.FromPointsWithPossibleDublettes(linepts)); } } }
/// <summary> /// This will create a point list out of the data, which can be used to plot the data. In order to create this list, /// the function must have knowledge how to calculate the points out of the data. This will be done /// by a function provided by the calling function. /// </summary> /// <param name="layer">The plot layer.</param> /// <returns>An array of plot points in layer coordinates.</returns> public Processed3DPlotData GetRangesAndPoints( IPlotArea layer) { const double MaxRelativeValue = 1E2; Altaxo.Data.IReadableColumn xColumn = this.XColumn; Altaxo.Data.IReadableColumn yColumn = this.YColumn; Altaxo.Data.IReadableColumn zColumn = this.ZColumn; if (null == xColumn || null == yColumn || null == zColumn) return null; // this plotitem is only for x and y double columns var result = new Processed3DPlotData(); MyPlotData myPlotData = new MyPlotData(xColumn, yColumn, zColumn); result.XPhysicalAccessor = new IndexedPhysicalValueAccessor(myPlotData.GetXPhysical); result.YPhysicalAccessor = new IndexedPhysicalValueAccessor(myPlotData.GetYPhysical); result.ZPhysicalAccessor = new IndexedPhysicalValueAccessor(myPlotData.GetZPhysical); PlotRangeList rangeList = null; // allocate an array PointF to hold the line points // _tlsBufferedPlotData is a static buffer that is allocated per thread // and thus is only used temporary here in this routine if (null == _tlsBufferedPlotData) _tlsBufferedPlotData = new List<PointD3D>(); else _tlsBufferedPlotData.Clear(); // Fill the array with values // only the points where x and y are not NaNs are plotted! bool bInPlotSpace = true; int rangeStart = 0; int rangeOffset = 0; rangeList = new PlotRangeList(); result.RangeList = rangeList; Scale xAxis = layer.XAxis; Scale yAxis = layer.YAxis; Scale zAxis = layer.ZAxis; G3DCoordinateSystem coordsys = layer.CoordinateSystem; int maxRowIndex = GetMaximumRowIndexFromDataColumns(); int plotArrayIdx = 0; foreach (int dataRowIdx in _dataRowSelection.GetSelectedRowIndicesFromTo(0, maxRowIndex, _dataTable?.Document?.DataColumns, maxRowIndex)) { if (xColumn.IsElementEmpty(dataRowIdx) || yColumn.IsElementEmpty(dataRowIdx) || zColumn.IsElementEmpty(dataRowIdx)) { if (!bInPlotSpace) { bInPlotSpace = true; rangeList.Add(new PlotRange(rangeStart, plotArrayIdx, rangeOffset)); } continue; } double x_rel, y_rel, z_rel; PointD3D coord; x_rel = xAxis.PhysicalVariantToNormal(xColumn[dataRowIdx]); y_rel = yAxis.PhysicalVariantToNormal(yColumn[dataRowIdx]); z_rel = zAxis.PhysicalVariantToNormal(zColumn[dataRowIdx]); // chop relative values to an range of about -+ 10^6 if (x_rel > MaxRelativeValue) x_rel = MaxRelativeValue; if (x_rel < -MaxRelativeValue) x_rel = -MaxRelativeValue; if (y_rel > MaxRelativeValue) y_rel = MaxRelativeValue; if (y_rel < -MaxRelativeValue) y_rel = -MaxRelativeValue; if (z_rel > MaxRelativeValue) z_rel = MaxRelativeValue; if (z_rel < -MaxRelativeValue) z_rel = -MaxRelativeValue; // after the conversion to relative coordinates it is possible // that with the choosen axis the point is undefined // (for instance negative values on a logarithmic axis) // in this case the returned value is NaN if (coordsys.LogicalToLayerCoordinates(new Logical3D(x_rel, y_rel, z_rel), out coord)) { if (bInPlotSpace) { bInPlotSpace = false; rangeStart = plotArrayIdx; rangeOffset = dataRowIdx - plotArrayIdx; } _tlsBufferedPlotData.Add(coord); plotArrayIdx++; } else { if (!bInPlotSpace) { bInPlotSpace = true; rangeList.Add(new PlotRange(rangeStart, plotArrayIdx, rangeOffset)); } } } // end for if (!bInPlotSpace) { bInPlotSpace = true; rangeList.Add(new PlotRange(rangeStart, plotArrayIdx, rangeOffset)); // add the last range } result.PlotPointsInAbsoluteLayerCoordinates = _tlsBufferedPlotData.ToArray(); return result; }