import { nonNullIdxs } from '../utils.js'; import { orient, clipGaps, lineToH, lineToV, clipBandLine, BAND_CLIP_FILL, bandFillClipDirs, findGaps } from './utils.js'; function _drawAcc(lineTo) { return (stroke, accX, minY, maxY, inY, outY) => { if (minY != maxY) { if (inY != minY && outY != minY) lineTo(stroke, accX, minY); if (inY != maxY && outY != maxY) lineTo(stroke, accX, maxY); lineTo(stroke, accX, outY); } }; } const drawAccH = _drawAcc(lineToH); const drawAccV = _drawAcc(lineToV); export function linear(opts) { return (u, seriesIdx, idx0, idx1) => { return orient(u, seriesIdx, (series, dataX, dataY, scaleX, scaleY, valToPosX, valToPosY, xOff, yOff, xDim, yDim) => { [idx0, idx1] = nonNullIdxs(dataY, idx0, idx1); if (idx0 == -1) return null; let pxRound = series.pxRound; let alignGaps = opts?.alignGaps ?? series.alignGaps ?? 0; let pixelForX = val => pxRound(valToPosX(val, scaleX, xDim, xOff)); let pixelForY = val => pxRound(valToPosY(val, scaleY, yDim, yOff)); let lineTo, drawAcc; if (scaleX.ori == 0) { lineTo = lineToH; drawAcc = drawAccH; } else { lineTo = lineToV; drawAcc = drawAccV; } const dir = scaleX.dir * (scaleX.ori == 0 ? 1 : -1); const _paths = {stroke: new Path2D(), fill: null, clip: null, band: null, gaps: null, flags: BAND_CLIP_FILL}; const stroke = _paths.stroke; let hasGap = false; // decimate when number of points >= 4x available pixels const decimate = idx1 - idx0 >= xDim * 4; if (decimate) { let xForPixel = pos => u.posToVal(pos, scaleX.key, true); let minY = null, maxY = null, inY, outY, drawnAtX; let accX = pixelForX(dataX[dir == 1 ? idx0 : idx1]); let idx0px = pixelForX(dataX[idx0]); let idx1px = pixelForX(dataX[idx1]); // tracks limit of current x bucket to avoid having to get x pixel for every x value let nextAccXVal = xForPixel(dir == 1 ? idx0px + 1 : idx1px - 1); for (let i = dir == 1 ? idx0 : idx1; i >= idx0 && i <= idx1; i += dir) { let xVal = dataX[i]; let reuseAccX = dir == 1 ? (xVal < nextAccXVal) : (xVal > nextAccXVal); let x = reuseAccX ? accX : pixelForX(xVal); let yVal = dataY[i]; if (x == accX) { if (yVal != null) { outY = yVal; if (minY == null) { lineTo(stroke, x, pixelForY(outY)); inY = minY = maxY = outY; } else { if (outY < minY) minY = outY; else if (outY > maxY) maxY = outY; } } else { if (yVal === null) hasGap = true; } } else { if (minY != null) drawAcc(stroke, accX, pixelForY(minY), pixelForY(maxY), pixelForY(inY), pixelForY(outY)); if (yVal != null) { outY = yVal; lineTo(stroke, x, pixelForY(outY)); minY = maxY = inY = outY; } else { minY = maxY = null; if (yVal === null) hasGap = true; } accX = x; nextAccXVal = xForPixel(accX + dir); } } if (minY != null && minY != maxY && drawnAtX != accX) drawAcc(stroke, accX, pixelForY(minY), pixelForY(maxY), pixelForY(inY), pixelForY(outY)); } else { for (let i = dir == 1 ? idx0 : idx1; i >= idx0 && i <= idx1; i += dir) { let yVal = dataY[i]; if (yVal === null) hasGap = true; else if (yVal != null) lineTo(stroke, pixelForX(dataX[i]), pixelForY(yVal)); } } let [ bandFillDir, bandClipDir ] = bandFillClipDirs(u, seriesIdx); if (series.fill != null || bandFillDir != 0) { let fill = _paths.fill = new Path2D(stroke); let fillToVal = series.fillTo(u, seriesIdx, series.min, series.max, bandFillDir); let fillToY = pixelForY(fillToVal); let frX = pixelForX(dataX[idx0]); let toX = pixelForX(dataX[idx1]); if (dir == -1) [toX, frX] = [frX, toX]; lineTo(fill, toX, fillToY); lineTo(fill, frX, fillToY); } if (!series.spanGaps) { // skip in mode: 2? // console.time('gaps'); let gaps = hasGap ? findGaps(dataX, dataY, idx0, idx1, dir, pixelForX, alignGaps) : []; // console.timeEnd('gaps'); // console.log('gaps', JSON.stringify(gaps)); _paths.gaps = gaps = series.gaps(u, seriesIdx, idx0, idx1, gaps); _paths.clip = clipGaps(gaps, scaleX.ori, xOff, yOff, xDim, yDim); } if (bandClipDir != 0) { _paths.band = bandClipDir == 2 ? [ clipBandLine(u, seriesIdx, idx0, idx1, stroke, -1), clipBandLine(u, seriesIdx, idx0, idx1, stroke, 1), ] : clipBandLine(u, seriesIdx, idx0, idx1, stroke, bandClipDir); } return _paths; }); }; }