uplot-webgpu 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/CANVAS_PROXY.md +602 -0
  2. package/README.md +854 -0
  3. package/favicon.ico +0 -0
  4. package/index.html +14 -0
  5. package/index.js +21 -0
  6. package/original/paths.canvas2d/bars.js +252 -0
  7. package/original/paths.canvas2d/catmullRomCentrip.js +125 -0
  8. package/original/paths.canvas2d/linear.js +170 -0
  9. package/original/paths.canvas2d/monotoneCubic.js +68 -0
  10. package/original/paths.canvas2d/points.js +66 -0
  11. package/original/paths.canvas2d/spline.js +103 -0
  12. package/original/paths.canvas2d/stepped.js +124 -0
  13. package/original/paths.canvas2d/utils.js +301 -0
  14. package/original/uPlot.canvas2d.js +3548 -0
  15. package/package.json +110 -0
  16. package/paths/bars.js +253 -0
  17. package/paths/catmullRomCentrip.js +126 -0
  18. package/paths/linear.js +171 -0
  19. package/paths/monotoneCubic.js +69 -0
  20. package/paths/points.js +67 -0
  21. package/paths/spline.js +104 -0
  22. package/paths/stepped.js +125 -0
  23. package/paths/utils.js +301 -0
  24. package/scripts/uPlot.css +168 -0
  25. package/scripts/uPlot.d.ts +26 -0
  26. package/scripts/uPlot.js +3687 -0
  27. package/scripts/utils/dom.js +124 -0
  28. package/scripts/utils/domClasses.js +22 -0
  29. package/scripts/utils/feats.js +13 -0
  30. package/scripts/utils/fmtDate.js +398 -0
  31. package/scripts/utils/opts.js +844 -0
  32. package/scripts/utils/strings.js +22 -0
  33. package/scripts/utils/sync.js +27 -0
  34. package/scripts/utils/utils.js +692 -0
  35. package/scripts/webgpu/GPUPath.d.ts +46 -0
  36. package/scripts/webgpu/GPUPath.js +633 -0
  37. package/scripts/webgpu/GPUPath.ts +634 -0
  38. package/scripts/webgpu/WebGPURenderer.d.ts +176 -0
  39. package/scripts/webgpu/WebGPURenderer.js +4256 -0
  40. package/scripts/webgpu/WebGPURenderer.ts +4257 -0
  41. package/scripts/webgpu/browserSmokeHarness.js +105 -0
  42. package/scripts/webgpu/exporters.d.ts +8 -0
  43. package/scripts/webgpu/exporters.js +212 -0
  44. package/scripts/webgpu/shaders.d.ts +2 -0
  45. package/scripts/webgpu/shaders.js +76 -0
  46. package/scripts/webgpu/shaders.ts +77 -0
  47. package/scripts/webgpu/smokeTest.d.ts +2 -0
  48. package/scripts/webgpu/smokeTest.js +144 -0
  49. package/scripts/webgpu/webgpu-ambient.d.ts +41 -0
  50. package/tinybuild.config.js +109 -0
@@ -0,0 +1,69 @@
1
+ import { GPUPath } from '../scripts/webgpu/GPUPath.js';
2
+ import { splineInterp } from "./spline.js";
3
+
4
+ export function monotoneCubic(opts) {
5
+ return splineInterp(_monotoneCubic, opts);
6
+ }
7
+
8
+ // Monotone Cubic Spline interpolation, adapted from the Chartist.js implementation:
9
+ // https://github.com/gionkunz/chartist-js/blob/e7e78201bffe9609915e5e53cfafa29a5d6c49f9/src/scripts/interpolation.js#L240-L369
10
+ function _monotoneCubic(xs, ys, moveTo, lineTo, bezierCurveTo, pxRound) {
11
+ const n = xs.length;
12
+
13
+ if (n < 2)
14
+ return null;
15
+
16
+ const path = new GPUPath();
17
+
18
+ moveTo(path, xs[0], ys[0]);
19
+
20
+ if (n == 2)
21
+ lineTo(path, xs[1], ys[1]);
22
+ else {
23
+ let ms = Array(n),
24
+ ds = Array(n - 1),
25
+ dys = Array(n - 1),
26
+ dxs = Array(n - 1);
27
+
28
+ // calc deltas and derivative
29
+ for (let i = 0; i < n - 1; i++) {
30
+ dys[i] = ys[i + 1] - ys[i];
31
+ dxs[i] = xs[i + 1] - xs[i];
32
+ ds[i] = dys[i] / dxs[i];
33
+ }
34
+
35
+ // determine desired slope (m) at each point using Fritsch-Carlson method
36
+ // http://math.stackexchange.com/questions/45218/implementation-of-monotone-cubic-interpolation
37
+ ms[0] = ds[0];
38
+
39
+ for (let i = 1; i < n - 1; i++) {
40
+ if (ds[i] === 0 || ds[i - 1] === 0 || (ds[i - 1] > 0) !== (ds[i] > 0))
41
+ ms[i] = 0;
42
+ else {
43
+ ms[i] = 3 * (dxs[i - 1] + dxs[i]) / (
44
+ (2 * dxs[i] + dxs[i - 1]) / ds[i - 1] +
45
+ (dxs[i] + 2 * dxs[i - 1]) / ds[i]
46
+ );
47
+
48
+ if (!isFinite(ms[i]))
49
+ ms[i] = 0;
50
+ }
51
+ }
52
+
53
+ ms[n - 1] = ds[n - 2];
54
+
55
+ for (let i = 0; i < n - 1; i++) {
56
+ bezierCurveTo(
57
+ path,
58
+ xs[i] + dxs[i] / 3,
59
+ ys[i] + ms[i] * dxs[i] / 3,
60
+ xs[i + 1] - dxs[i] / 3,
61
+ ys[i + 1] - ms[i + 1] * dxs[i] / 3,
62
+ xs[i + 1],
63
+ ys[i + 1],
64
+ );
65
+ }
66
+ }
67
+
68
+ return path;
69
+ }
@@ -0,0 +1,67 @@
1
+ import { GPUPath } from '../scripts/webgpu/GPUPath.js';
2
+ import { orient, moveToH, moveToV, rectH, arcH, arcV, BAND_CLIP_FILL, BAND_CLIP_STROKE } from './utils.js';
3
+ import { roundDec, PI } from '../scripts/utils/utils.js';
4
+
5
+ // TODO: drawWrap(seriesIdx, drawPoints) (save, restore, translate, clip)
6
+ export function points(opts) {
7
+ return (u, seriesIdx, idx0, idx1, filtIdxs) => {
8
+ // log("drawPoints()", arguments);
9
+ let { pxRatio } = u;
10
+
11
+ return orient(u, seriesIdx, (series, dataX, dataY, scaleX, scaleY, valToPosX, valToPosY, xOff, yOff, xDim, yDim) => {
12
+ let { pxRound, points } = series;
13
+
14
+ let moveTo, arc;
15
+
16
+ if (scaleX.ori == 0) {
17
+ moveTo = moveToH;
18
+ arc = arcH;
19
+ }
20
+ else {
21
+ moveTo = moveToV;
22
+ arc = arcV;
23
+ }
24
+
25
+ const width = roundDec(points.width * pxRatio, 3);
26
+
27
+ let rad = (points.size - points.width) / 2 * pxRatio;
28
+ let dia = roundDec(rad * 2, 3);
29
+
30
+ let fill = new GPUPath();
31
+ let clip = new GPUPath();
32
+
33
+ let { left: lft, top: top, width: wid, height: hgt } = u.bbox;
34
+
35
+ rectH(clip,
36
+ lft - dia,
37
+ top - dia,
38
+ wid + dia * 2,
39
+ hgt + dia * 2,
40
+ );
41
+
42
+ const drawPoint = pi => {
43
+ if (dataY[pi] != null) {
44
+ let x = pxRound(valToPosX(dataX[pi], scaleX, xDim, xOff));
45
+ let y = pxRound(valToPosY(dataY[pi], scaleY, yDim, yOff));
46
+
47
+ moveTo(fill, x + rad, y);
48
+ arc(fill, x, y, rad, 0, PI * 2);
49
+ }
50
+ };
51
+
52
+ if (filtIdxs)
53
+ filtIdxs.forEach(drawPoint);
54
+ else {
55
+ for (let pi = idx0; pi <= idx1; pi++)
56
+ drawPoint(pi);
57
+ }
58
+
59
+ return {
60
+ stroke: width > 0 ? fill : null,
61
+ fill,
62
+ clip,
63
+ flags: BAND_CLIP_FILL | BAND_CLIP_STROKE,
64
+ };
65
+ });
66
+ };
67
+ }
@@ -0,0 +1,104 @@
1
+ import { GPUPath } from '../scripts/webgpu/GPUPath.js';
2
+ import { ifNull, nonNullIdxs } from '../scripts/utils/utils.js';
3
+ import { orient, clipGaps, moveToH, moveToV, lineToH, lineToV, bezierCurveToH, bezierCurveToV, clipBandLine, BAND_CLIP_FILL, bandFillClipDirs, findGaps } from './utils.js';
4
+
5
+ export function splineInterp(interp, opts) {
6
+ return (u, seriesIdx, idx0, idx1) => {
7
+ return orient(u, seriesIdx, (series, dataX, dataY, scaleX, scaleY, valToPosX, valToPosY, xOff, yOff, xDim, yDim) => {
8
+ [idx0, idx1] = nonNullIdxs(dataY, idx0, idx1);
9
+
10
+ let pxRound = series.pxRound;
11
+
12
+ let alignGaps = opts?.alignGaps ?? series.alignGaps ?? 0;
13
+
14
+ let pixelForX = val => pxRound(valToPosX(val, scaleX, xDim, xOff));
15
+ let pixelForY = val => pxRound(valToPosY(val, scaleY, yDim, yOff));
16
+
17
+ let moveTo, bezierCurveTo, lineTo;
18
+
19
+ if (scaleX.ori == 0) {
20
+ moveTo = moveToH;
21
+ lineTo = lineToH;
22
+ bezierCurveTo = bezierCurveToH;
23
+ }
24
+ else {
25
+ moveTo = moveToV;
26
+ lineTo = lineToV;
27
+ bezierCurveTo = bezierCurveToV;
28
+ }
29
+
30
+ const dir = scaleX.dir * (scaleX.ori == 0 ? 1 : -1);
31
+
32
+ let firstXPos = pixelForX(dataX[dir == 1 ? idx0 : idx1]);
33
+ let prevXPos = firstXPos;
34
+
35
+ let xCoords = [];
36
+ let yCoords = [];
37
+
38
+ let hasGap = false;
39
+
40
+ for (let i = dir == 1 ? idx0 : idx1; i >= idx0 && i <= idx1; i += dir) {
41
+ let yVal = dataY[i];
42
+
43
+ if (yVal != null) {
44
+ let xVal = dataX[i];
45
+ let xPos = pixelForX(xVal);
46
+
47
+ xCoords.push(prevXPos = xPos);
48
+ yCoords.push(pixelForY(dataY[i]));
49
+ }
50
+ else if (yVal === null)
51
+ hasGap = true;
52
+ }
53
+
54
+ const _paths = {stroke: interp(xCoords, yCoords, moveTo, lineTo, bezierCurveTo, pxRound), fill: null, clip: null, band: null, gaps: null, flags: BAND_CLIP_FILL};
55
+ const stroke = _paths.stroke;
56
+
57
+ let [ bandFillDir, bandClipDir ] = bandFillClipDirs(u, seriesIdx);
58
+
59
+ if (series.fill != null || bandFillDir != 0) {
60
+ let fill = _paths.fill = new GPUPath(stroke);
61
+
62
+ let fillTo = series.fillTo(u, seriesIdx, series.min, series.max, bandFillDir);
63
+ let fillToY = pixelForY(fillTo);
64
+
65
+ lineTo(fill, prevXPos, fillToY);
66
+ lineTo(fill, firstXPos, fillToY);
67
+ }
68
+
69
+ if (!series.spanGaps) {
70
+ // console.time('gaps');
71
+ let gaps = hasGap ? findGaps(dataX, dataY, idx0, idx1, dir, pixelForX, alignGaps) : [];
72
+
73
+ // console.timeEnd('gaps');
74
+
75
+ // console.log('gaps', JSON.stringify(gaps));
76
+
77
+ _paths.gaps = gaps = series.gaps(u, seriesIdx, idx0, idx1, gaps);
78
+
79
+ _paths.clip = clipGaps(gaps, scaleX.ori, xOff, yOff, xDim, yDim);
80
+ }
81
+
82
+ if (bandClipDir != 0) {
83
+ _paths.band = bandClipDir == 2 ? [
84
+ clipBandLine(u, seriesIdx, idx0, idx1, stroke, -1),
85
+ clipBandLine(u, seriesIdx, idx0, idx1, stroke, 1),
86
+ ] : clipBandLine(u, seriesIdx, idx0, idx1, stroke, bandClipDir);
87
+ }
88
+
89
+ return _paths;
90
+
91
+ // if FEAT_PATHS: false in rollup.config.js
92
+ // u.ctx.save();
93
+ // u.ctx.beginPath();
94
+ // u.ctx.rect(u.bbox.left, u.bbox.top, u.bbox.width, u.bbox.height);
95
+ // u.ctx.clip();
96
+ // u.ctx.strokeStyle = u.series[sidx].stroke;
97
+ // u.ctx.stroke(stroke);
98
+ // u.ctx.fillStyle = u.series[sidx].fill;
99
+ // u.ctx.fill(fill);
100
+ // u.ctx.restore();
101
+ // return null;
102
+ });
103
+ };
104
+ }
@@ -0,0 +1,125 @@
1
+ import { GPUPath } from '../scripts/webgpu/GPUPath.js';
2
+ import { ifNull, nonNullIdxs } from '../scripts/utils/utils.js';
3
+ import { orient, clipGaps, lineToH, lineToV, clipBandLine, BAND_CLIP_FILL, bandFillClipDirs, findGaps } from './utils.js';
4
+
5
+ // BUG: align: -1 behaves like align: 1 when scale.dir: -1
6
+ export function stepped(opts) {
7
+ const align = ifNull(opts.align, 1);
8
+ // whether to draw ascenders/descenders at null/gap bondaries
9
+ const ascDesc = ifNull(opts.ascDesc, false);
10
+ const extend = ifNull(opts.extend, false);
11
+
12
+ return (u, seriesIdx, idx0, idx1) => {
13
+ let { pxRatio } = u;
14
+
15
+ return orient(u, seriesIdx, (series, dataX, dataY, scaleX, scaleY, valToPosX, valToPosY, xOff, yOff, xDim, yDim) => {
16
+ [idx0, idx1] = nonNullIdxs(dataY, idx0, idx1);
17
+
18
+ let pxRound = series.pxRound;
19
+
20
+ let alignGaps = opts?.alignGaps ?? series.alignGaps ?? 0;
21
+
22
+ let { left, width } = u.bbox;
23
+
24
+ let pixelForX = val => pxRound(valToPosX(val, scaleX, xDim, xOff));
25
+ let pixelForY = val => pxRound(valToPosY(val, scaleY, yDim, yOff));
26
+
27
+ let lineTo = scaleX.ori == 0 ? lineToH : lineToV;
28
+
29
+ const _paths = {stroke: new GPUPath(), fill: null, clip: null, band: null, gaps: null, flags: BAND_CLIP_FILL};
30
+ const stroke = _paths.stroke;
31
+
32
+ const dir = scaleX.dir * (scaleX.ori == 0 ? 1 : -1);
33
+
34
+ let hasGap = false;
35
+
36
+ let prevYPos = pixelForY(dataY[dir == 1 ? idx0 : idx1]);
37
+ let firstXPos = pixelForX(dataX[dir == 1 ? idx0 : idx1]);
38
+ let prevXPos = firstXPos;
39
+
40
+ let firstXPosExt = firstXPos;
41
+
42
+ if (extend && align == -1) {
43
+ firstXPosExt = left;
44
+ lineTo(stroke, firstXPosExt, prevYPos);
45
+ }
46
+
47
+ lineTo(stroke, firstXPos, prevYPos);
48
+
49
+ for (let i = dir == 1 ? idx0 : idx1; i >= idx0 && i <= idx1; i += dir) {
50
+ let yVal1 = dataY[i];
51
+
52
+ if (yVal1 == null) {
53
+ if (yVal1 === null)
54
+ hasGap = true;
55
+
56
+ continue;
57
+ }
58
+
59
+ let x1 = pixelForX(dataX[i]);
60
+ let y1 = pixelForY(yVal1);
61
+
62
+ if (align == 1)
63
+ lineTo(stroke, x1, prevYPos);
64
+ else
65
+ lineTo(stroke, prevXPos, y1);
66
+
67
+ lineTo(stroke, x1, y1);
68
+
69
+ prevYPos = y1;
70
+ prevXPos = x1;
71
+ }
72
+
73
+ let prevXPosExt = prevXPos;
74
+
75
+ if (extend && align == 1) {
76
+ prevXPosExt = left + width;
77
+ lineTo(stroke, prevXPosExt, prevYPos);
78
+ }
79
+
80
+ let [ bandFillDir, bandClipDir ] = bandFillClipDirs(u, seriesIdx);
81
+
82
+ if (series.fill != null || bandFillDir != 0) {
83
+ let fill = _paths.fill = new GPUPath(stroke);
84
+
85
+ let fillTo = series.fillTo(u, seriesIdx, series.min, series.max, bandFillDir);
86
+ let fillToY = pixelForY(fillTo);
87
+
88
+ lineTo(fill, prevXPosExt, fillToY);
89
+ lineTo(fill, firstXPosExt, fillToY);
90
+ }
91
+
92
+ if (!series.spanGaps) {
93
+ // console.time('gaps');
94
+ let gaps = hasGap ? findGaps(dataX, dataY, idx0, idx1, dir, pixelForX, alignGaps) : [];
95
+
96
+ // console.timeEnd('gaps');
97
+
98
+ // console.log('gaps', JSON.stringify(gaps));
99
+
100
+ // expand/contract clips for ascenders/descenders
101
+ let halfStroke = (series.width * pxRatio) / 2;
102
+ let startsOffset = (ascDesc || align == 1) ? halfStroke : -halfStroke;
103
+ let endsOffset = (ascDesc || align == -1) ? -halfStroke : halfStroke;
104
+
105
+ gaps.forEach(g => {
106
+ g[0] += startsOffset;
107
+ g[1] += endsOffset;
108
+ });
109
+
110
+ _paths.gaps = gaps = series.gaps(u, seriesIdx, idx0, idx1, gaps);
111
+
112
+ _paths.clip = clipGaps(gaps, scaleX.ori, xOff, yOff, xDim, yDim);
113
+ }
114
+
115
+ if (bandClipDir != 0) {
116
+ _paths.band = bandClipDir == 2 ? [
117
+ clipBandLine(u, seriesIdx, idx0, idx1, stroke, -1),
118
+ clipBandLine(u, seriesIdx, idx0, idx1, stroke, 1),
119
+ ] : clipBandLine(u, seriesIdx, idx0, idx1, stroke, bandClipDir);
120
+ }
121
+
122
+ return _paths;
123
+ });
124
+ };
125
+ }
package/paths/utils.js ADDED
@@ -0,0 +1,301 @@
1
+ import { GPUPath } from '../scripts/webgpu/GPUPath.js';
2
+ import { round, incrRound, retArg0, min, EMPTY_ARR, ifNull } from "../scripts/utils/utils.js";
3
+
4
+ export const BAND_CLIP_FILL = 1 << 0;
5
+ export const BAND_CLIP_STROKE = 1 << 1;
6
+
7
+ export function orient(u, seriesIdx, cb) {
8
+ const mode = u.mode;
9
+ const series = u.series[seriesIdx];
10
+ const data = mode == 2 ? u._data[seriesIdx] : u._data;
11
+ const scales = u.scales;
12
+ const bbox = u.bbox;
13
+
14
+ let dx = data[0],
15
+ dy = mode == 2 ? data[1] : data[seriesIdx],
16
+ sx = mode == 2 ? scales[series.facets[0].scale] : scales[u.series[0].scale],
17
+ sy = mode == 2 ? scales[series.facets[1].scale] : scales[series.scale],
18
+ l = bbox.left,
19
+ t = bbox.top,
20
+ w = bbox.width,
21
+ h = bbox.height,
22
+ H = u.valToPosH,
23
+ V = u.valToPosV;
24
+
25
+ return (sx.ori == 0
26
+ ? cb(
27
+ series,
28
+ dx,
29
+ dy,
30
+ sx,
31
+ sy,
32
+ H,
33
+ V,
34
+ l,
35
+ t,
36
+ w,
37
+ h,
38
+ moveToH,
39
+ lineToH,
40
+ rectH,
41
+ arcH,
42
+ bezierCurveToH,
43
+ )
44
+ : cb(
45
+ series,
46
+ dx,
47
+ dy,
48
+ sx,
49
+ sy,
50
+ V,
51
+ H,
52
+ t,
53
+ l,
54
+ h,
55
+ w,
56
+ moveToV,
57
+ lineToV,
58
+ rectV,
59
+ arcV,
60
+ bezierCurveToV,
61
+ )
62
+ );
63
+ }
64
+
65
+ export function bandFillClipDirs(self, seriesIdx) {
66
+ let fillDir = 0;
67
+
68
+ // 2 bits, -1 | 1
69
+ let clipDirs = 0;
70
+
71
+ let bands = ifNull(self.bands, EMPTY_ARR);
72
+
73
+ for (let i = 0; i < bands.length; i++) {
74
+ let b = bands[i];
75
+
76
+ // is a "from" band edge
77
+ if (b.series[0] == seriesIdx)
78
+ fillDir = b.dir;
79
+ // is a "to" band edge
80
+ else if (b.series[1] == seriesIdx) {
81
+ if (b.dir == 1)
82
+ clipDirs |= 1;
83
+ else
84
+ clipDirs |= 2;
85
+ }
86
+ }
87
+
88
+ return [
89
+ fillDir,
90
+ (
91
+ clipDirs == 1 ? -1 : // neg only
92
+ clipDirs == 2 ? 1 : // pos only
93
+ clipDirs == 3 ? 2 : // both
94
+ 0 // neither
95
+ )
96
+ ];
97
+ }
98
+
99
+ export function seriesFillTo(self, seriesIdx, dataMin, dataMax, bandFillDir) {
100
+ let mode = self.mode;
101
+ let series = self.series[seriesIdx];
102
+ let scaleKey = mode == 2 ? series.facets[1].scale : series.scale;
103
+ let scale = self.scales[scaleKey];
104
+
105
+ return (
106
+ bandFillDir == -1 ? scale.min :
107
+ bandFillDir == 1 ? scale.max :
108
+ scale.distr == 3 ? (
109
+ scale.dir == 1 ? scale.min :
110
+ scale.max
111
+ ) : 0
112
+ );
113
+ }
114
+
115
+ // creates inverted band clip path (from stroke path -> yMax || yMin)
116
+ // clipDir is always inverse of fillDir
117
+ // default clip dir is upwards (1), since default band fill is downwards/fillBelowTo (-1) (highIdx -> lowIdx)
118
+ export function clipBandLine(self, seriesIdx, idx0, idx1, strokePath, clipDir) {
119
+ return orient(self, seriesIdx, (series, dataX, dataY, scaleX, scaleY, valToPosX, valToPosY, xOff, yOff, xDim, yDim) => {
120
+ let pxRound = series.pxRound;
121
+
122
+ const dir = scaleX.dir * (scaleX.ori == 0 ? 1 : -1);
123
+ const lineTo = scaleX.ori == 0 ? lineToH : lineToV;
124
+
125
+ let frIdx, toIdx;
126
+
127
+ if (dir == 1) {
128
+ frIdx = idx0;
129
+ toIdx = idx1;
130
+ }
131
+ else {
132
+ frIdx = idx1;
133
+ toIdx = idx0;
134
+ }
135
+
136
+ // path start
137
+ let x0 = pxRound(valToPosX(dataX[frIdx], scaleX, xDim, xOff));
138
+ let y0 = pxRound(valToPosY(dataY[frIdx], scaleY, yDim, yOff));
139
+ // path end x
140
+ let x1 = pxRound(valToPosX(dataX[toIdx], scaleX, xDim, xOff));
141
+ // upper or lower y limit
142
+ let yLimit = pxRound(valToPosY(clipDir == 1 ? scaleY.max : scaleY.min, scaleY, yDim, yOff));
143
+
144
+ let clip = new GPUPath(strokePath);
145
+
146
+ lineTo(clip, x1, yLimit);
147
+ lineTo(clip, x0, yLimit);
148
+ lineTo(clip, x0, y0);
149
+
150
+ return clip;
151
+ });
152
+ }
153
+
154
+ export function clipGaps(gaps, ori, plotLft, plotTop, plotWid, plotHgt) {
155
+ let clip = null;
156
+
157
+ // create clip path (invert gaps and non-gaps)
158
+ if (gaps.length > 0) {
159
+ clip = new GPUPath();
160
+
161
+ const rect = ori == 0 ? rectH : rectV;
162
+
163
+ let prevGapEnd = plotLft;
164
+
165
+ for (let i = 0; i < gaps.length; i++) {
166
+ let g = gaps[i];
167
+
168
+ if (g[1] > g[0]) {
169
+ let w = g[0] - prevGapEnd;
170
+
171
+ w > 0 && rect(clip, prevGapEnd, plotTop, w, plotTop + plotHgt);
172
+
173
+ prevGapEnd = g[1];
174
+ }
175
+ }
176
+
177
+ let w = plotLft + plotWid - prevGapEnd;
178
+
179
+ // hack to ensure we expand the clip enough to avoid cutting off strokes at edges
180
+ let maxStrokeWidth = 10;
181
+
182
+ w > 0 && rect(clip, prevGapEnd, plotTop - maxStrokeWidth / 2, w, plotTop + plotHgt + maxStrokeWidth);
183
+ }
184
+
185
+ return clip;
186
+ }
187
+
188
+ export function addGap(gaps, fromX, toX) {
189
+ let prevGap = gaps[gaps.length - 1];
190
+
191
+ if (prevGap && prevGap[0] == fromX) // TODO: gaps must be encoded at stroke widths?
192
+ prevGap[1] = toX;
193
+ else
194
+ gaps.push([fromX, toX]);
195
+ }
196
+
197
+ export function findGaps(xs, ys, idx0, idx1, dir, pixelForX, align) {
198
+ let gaps = [];
199
+ let len = xs.length;
200
+
201
+ for (let i = dir == 1 ? idx0 : idx1; i >= idx0 && i <= idx1; i += dir) {
202
+ let yVal = ys[i];
203
+
204
+ if (yVal === null) {
205
+ let fr = i, to = i;
206
+
207
+ if (dir == 1) {
208
+ while (++i <= idx1 && ys[i] === null)
209
+ to = i;
210
+ }
211
+ else {
212
+ while (--i >= idx0 && ys[i] === null)
213
+ to = i;
214
+ }
215
+
216
+ let frPx = pixelForX(xs[fr]);
217
+ let toPx = to == fr ? frPx : pixelForX(xs[to]);
218
+
219
+ // if value adjacent to edge null is same pixel, then it's partially
220
+ // filled and gap should start at next pixel
221
+ let fri2 = fr - dir;
222
+ let frPx2 = align <= 0 && fri2 >= 0 && fri2 < len ? pixelForX(xs[fri2]) : frPx;
223
+ // if (frPx2 == frPx)
224
+ // frPx++;
225
+ // else
226
+ frPx = frPx2;
227
+
228
+ let toi2 = to + dir;
229
+ let toPx2 = align >= 0 && toi2 >= 0 && toi2 < len ? pixelForX(xs[toi2]) : toPx;
230
+ // if (toPx2 == toPx)
231
+ // toPx--;
232
+ // else
233
+ toPx = toPx2;
234
+
235
+ if (toPx >= frPx)
236
+ gaps.push([frPx, toPx]); // addGap
237
+ }
238
+ }
239
+
240
+ return gaps;
241
+ }
242
+
243
+ export function pxRoundGen(pxAlign) {
244
+ return pxAlign == 0 ? retArg0 : pxAlign == 1 ? round : v => incrRound(v, pxAlign);
245
+ }
246
+
247
+ /*
248
+ // inefficient linear interpolation that does bi-directinal scans on each call
249
+ export function costlyLerp(i, idx0, idx1, _dirX, dataY) {
250
+ let prevNonNull = nonNullIdx(dataY, _dirX == 1 ? idx0 : idx1, i, -_dirX);
251
+ let nextNonNull = nonNullIdx(dataY, i, _dirX == 1 ? idx1 : idx0, _dirX);
252
+
253
+ let prevVal = dataY[prevNonNull];
254
+ let nextVal = dataY[nextNonNull];
255
+
256
+ return prevVal + (i - prevNonNull) / (nextNonNull - prevNonNull) * (nextVal - prevVal);
257
+ }
258
+ */
259
+
260
+ function rect(ori) {
261
+ let moveTo = ori == 0 ?
262
+ moveToH :
263
+ moveToV;
264
+
265
+ let arcTo = ori == 0 ?
266
+ (p, x1, y1, x2, y2, r) => { p.arcTo(x1, y1, x2, y2, r) } :
267
+ (p, y1, x1, y2, x2, r) => { p.arcTo(x1, y1, x2, y2, r) };
268
+
269
+ let rect = ori == 0 ?
270
+ (p, x, y, w, h) => { p.rect(x, y, w, h); } :
271
+ (p, y, x, h, w) => { p.rect(x, y, w, h); };
272
+
273
+ return (p, x, y, w, h, endRad = 0, baseRad = 0) => {
274
+ if (endRad == 0 && baseRad == 0)
275
+ rect(p, x, y, w, h);
276
+ else {
277
+ endRad = min(endRad, w / 2, h / 2);
278
+ baseRad = min(baseRad, w / 2, h / 2);
279
+
280
+ // adapted from https://stackoverflow.com/questions/1255512/how-to-draw-a-rounded-rectangle-using-html-canvas/7838871#7838871
281
+ moveTo(p, x + endRad, y);
282
+ arcTo(p, x + w, y, x + w, y + h, endRad);
283
+ arcTo(p, x + w, y + h, x, y + h, baseRad);
284
+ arcTo(p, x, y + h, x, y, baseRad);
285
+ arcTo(p, x, y, x + w, y, endRad);
286
+ p.closePath();
287
+ }
288
+ };
289
+ }
290
+
291
+ // orientation-inverting canvas functions
292
+ export const moveToH = (p, x, y) => { p.moveTo(x, y); }
293
+ export const moveToV = (p, y, x) => { p.moveTo(x, y); }
294
+ export const lineToH = (p, x, y) => { p.lineTo(x, y); }
295
+ export const lineToV = (p, y, x) => { p.lineTo(x, y); }
296
+ export const rectH = rect(0);
297
+ export const rectV = rect(1);
298
+ export const arcH = (p, x, y, r, startAngle, endAngle) => { p.arc(x, y, r, startAngle, endAngle); }
299
+ export const arcV = (p, y, x, r, startAngle, endAngle) => { p.arc(x, y, r, startAngle, endAngle); }
300
+ export const bezierCurveToH = (p, bp1x, bp1y, bp2x, bp2y, p2x, p2y) => { p.bezierCurveTo(bp1x, bp1y, bp2x, bp2y, p2x, p2y); };
301
+ export const bezierCurveToV = (p, bp1y, bp1x, bp2y, bp2x, p2y, p2x) => { p.bezierCurveTo(bp1x, bp1y, bp2x, bp2y, p2x, p2y); };