private void RenderDataSignal(Plottables.Signal signal) { // use the signal processing class to convert dense data to pixel column fills PixelColumn[] pxCols = SignalToPixelConverter(signal, settings); Pen linePen = new Pen(signal.style.lineColor); Brush brushPen = new SolidBrush(signal.style.lineColor); // disable anti-aliasing so art looks better var originalSmoothMode = gfxData.SmoothingMode; gfxData.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighSpeed; // for zoomed-in data, only individual points will be plotted var points = new List <Point>(); // plot one column at a time int skippedColumns = 0; for (int i = 0; i < pxCols.Length; i++) { if (pxCols[i] == null) { // this column contains no data skippedColumns += 1; } else if (pxCols[i].min == pxCols[i].max) { // this column contains a single pixel of data gfxData.FillRectangle(brushPen, i, pxCols[i].min, 1, 1); } else { // this column contains a span of data Point pt1 = new Point(i, pxCols[i].min); Point pt2 = new Point(i, pxCols[i].max); gfxData.DrawLine(linePen, pt1, pt2); } // if this is a new data point, remember it if (i > 0 && pxCols[i] != null && pxCols[i - 1] != null && pxCols[i].iLeft > pxCols[i - 1].iLeft) { points.Add(new Point(i, pxCols[i].min)); } } // draw markers at each individual point if zoomed-in6 if ((points.Count() + skippedColumns) < settings.dataPlotWidth / 3) { int ptSize = 2; foreach (var point in points) { gfxData.FillEllipse(brushPen, point.X - ptSize, point.Y - ptSize, 2 * ptSize, 2 * ptSize); } //gfxData.DrawLines(linePen, points.ToArray()); } // revert to how things were gfxData.SmoothingMode = originalSmoothMode; }
private PixelColumn[] SignalToPixelConverter(Plottables.Signal signal, Settings settings) { PixelColumn[] pxCols = new PixelColumn[settings.dataPlotWidth]; // theoretical minimum pixel if X1 were drawn int dataMinPx = (int)(settings.axisX.pxPerUnit * (-settings.axisX.x1)); // step column by column left to right for (int i = 0; i < settings.dataPlotWidth; i++) { // determine what index values of the signal correspond to this pixel column int iLeft = (int)(settings.axisX.unitsPerPx * signal.sampleRateHz * (i - dataMinPx)); int iRight = (int)(iLeft + settings.axisX.unitsPerPx * signal.sampleRateHz); // ensure indexes are valid and skip columns without data iLeft = Math.Max(iLeft, 0); iRight = Math.Min(signal.ys.Length - 1, iRight); iRight = Math.Max(iRight, 0); if (iRight == 0) { continue; } if (iRight < 0 || iLeft > iRight) { continue; } // pull iLeft of this column to the same iRight as the last if (i > 0 && pxCols[i - 1] != null) { iLeft = pxCols[i - 1].iRight - 1; } // determine vertical span of this range double valMin = ArraySubMin(signal.ys, iLeft, iRight); double valMax = ArraySubMax(signal.ys, iLeft, iRight); // TODO: make indexes perfect - they don't always match-up exactly //Console.WriteLine($"Index [{iLeft}:{iRight}] min/max = {valMin}/{valMax}"); // convert this value to a pixel location on screen int pxMin = (int)((settings.axisY.x2 - valMin) * settings.axisY.pxPerUnit) + 1; int pxMax = (int)((settings.axisY.x2 - valMax) * settings.axisY.pxPerUnit) + 1; // populate the object pxCols[i] = new PixelColumn(pxMin, pxMax, iLeft, iRight); } return(pxCols); }