uplot-webgpu 0.1.0 → 0.2.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 (42) hide show
  1. package/index.js +0 -17
  2. package/index.ts +5 -0
  3. package/package.json +4 -69
  4. package/paths/ts/bars.ts +253 -0
  5. package/paths/ts/catmullRomCentrip.ts +127 -0
  6. package/paths/ts/index.ts +9 -0
  7. package/paths/ts/linear.ts +172 -0
  8. package/paths/ts/monotoneCubic.ts +70 -0
  9. package/paths/ts/points.ts +70 -0
  10. package/paths/ts/spline.ts +105 -0
  11. package/paths/ts/stepped.ts +126 -0
  12. package/paths/ts/types.ts +143 -0
  13. package/paths/ts/utils.ts +303 -0
  14. package/scripts/ts/uPlot.ts +3732 -0
  15. package/scripts/ts/utils/dom.ts +124 -0
  16. package/scripts/ts/utils/domClasses.ts +22 -0
  17. package/scripts/ts/utils/feats.ts +13 -0
  18. package/scripts/ts/utils/fmtDate.ts +398 -0
  19. package/scripts/ts/utils/opts.ts +844 -0
  20. package/scripts/ts/utils/strings.ts +22 -0
  21. package/scripts/ts/utils/sync.ts +27 -0
  22. package/scripts/ts/utils/utils.ts +692 -0
  23. package/scripts/{webgpu → ts/webgpu}/GPUPath.ts +92 -41
  24. package/scripts/{webgpu → ts/webgpu}/WebGPURenderer.ts +176 -84
  25. package/scripts/ts/webgpu/exporters.ts +221 -0
  26. package/scripts/ts/webgpu/index.ts +31 -0
  27. package/scripts/{webgpu → ts/webgpu}/shaders.ts +0 -1
  28. package/scripts/uPlot.js +0 -2
  29. package/scripts/webgpu/GPUPath.js +513 -606
  30. package/scripts/webgpu/WebGPURenderer.js +3484 -4018
  31. package/scripts/webgpu/exporters.js +191 -201
  32. package/scripts/webgpu/index.js +12 -0
  33. package/scripts/webgpu/shaders.js +6 -3
  34. package/tinybuild.config.js +6 -6
  35. package/tsconfig.json +64 -0
  36. package/scripts/uPlot.d.ts +0 -26
  37. package/scripts/webgpu/GPUPath.d.ts +0 -46
  38. package/scripts/webgpu/WebGPURenderer.d.ts +0 -176
  39. package/scripts/webgpu/exporters.d.ts +0 -8
  40. package/scripts/webgpu/shaders.d.ts +0 -2
  41. package/scripts/webgpu/smokeTest.d.ts +0 -2
  42. package/scripts/webgpu/webgpu-ambient.d.ts +0 -41
package/index.js CHANGED
@@ -1,20 +1,3 @@
1
- // Drop-in WebGPU uPlot entrypoint.
2
- //
3
- // Use this the same way as upstream uPlot:
4
- //
5
- // import uPlot from './index.js';
6
- // const chart = new uPlot(opts, data, target);
7
- //
8
- // WebGPURenderer and GPUPath are not optional app-side setup. They are embedded
9
- // into uPlot.js and are used automatically by the default constructor.
10
- //
11
- // Kept out of this entrypoint on purpose:
12
- // - Canvas2D reference build
13
- // - browser smoke tests
14
- // - benchmark/demo harnesses
15
-
16
- import css from './scripts/uPlot.css';
17
-
18
1
  export { default } from './scripts/uPlot.js';
19
2
  export { default as uPlot } from './scripts/uPlot.js';
20
3
  export { GPUPath } from './scripts/webgpu/GPUPath.js';
package/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ import './scripts/uPlot.css';
2
+
3
+ export { default } from './scripts/ts/uPlot';
4
+ export { default as uPlot } from './scripts/ts/uPlot';
5
+ export * from './scripts/ts/webgpu/index';
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "uplot-webgpu",
3
- "version": "0.1.0",
4
- "description": "Dummy tinybuild app, for building. Replace description before publishing.",
3
+ "version": "0.2.0",
4
+ "description": "uPlot on WebGPU",
5
5
  "main": "./dist/uPlot.js",
6
6
  "module": "./dist/uPlot.esm.js",
7
- "types": "./dist/uPlot.d.ts",
7
+ "types": "./dist/index.d.ts",
8
8
  "type": "module",
9
9
  "scripts": {
10
10
  "start": "tinybuild",
@@ -31,7 +31,7 @@
31
31
  "esbuild"
32
32
  ],
33
33
  "devDependencies": {
34
- "@webgpu/types": "~0.1.70"
34
+ "@webgpu/types": "^0.1.70"
35
35
  },
36
36
  "nodemonConfig": {
37
37
  "env": {
@@ -41,70 +41,5 @@
41
41
  "dist/",
42
42
  ".temp/"
43
43
  ]
44
- },
45
- "exports": {
46
- ".": {
47
- "types": "./index.d.ts",
48
- "import": "./index.js",
49
- "default": "./index.js"
50
- },
51
- "./uPlot": {
52
- "types": "./uPlot.d.ts",
53
- "import": "./uPlot.js",
54
- "default": "./uPlot.js"
55
- },
56
- "./webgpu": {
57
- "types": "./webgpu/WebGPURenderer.d.ts",
58
- "import": "./webgpu/WebGPURenderer.js",
59
- "default": "./webgpu/WebGPURenderer.js"
60
- },
61
- "./webgpu/WebGPURenderer": {
62
- "types": "./webgpu/WebGPURenderer.d.ts",
63
- "import": "./webgpu/WebGPURenderer.js",
64
- "default": "./webgpu/WebGPURenderer.js"
65
- },
66
- "./webgpu/GPUPath": {
67
- "types": "./webgpu/GPUPath.d.ts",
68
- "import": "./webgpu/GPUPath.js",
69
- "default": "./webgpu/GPUPath.js"
70
- },
71
- "./core": {
72
- "types": "./index.d.ts",
73
- "import": "./index.js",
74
- "default": "./index.js"
75
- },
76
- "./full": {
77
- "types": "./index.full.d.ts",
78
- "import": "./index.full.js",
79
- "default": "./index.full.js"
80
- },
81
- "./bench": {
82
- "types": "./index.bench.d.ts",
83
- "import": "./index.bench.js",
84
- "default": "./index.bench.js"
85
- },
86
- "./renderer": {
87
- "types": "./index.renderer.d.ts",
88
- "import": "./index.renderer.js",
89
- "default": "./index.renderer.js"
90
- },
91
- "./canvas2d-reference": {
92
- "types": "./old/uPlot.canvas2d.d.ts",
93
- "import": "./old/uPlot.canvas2d.js",
94
- "default": "./old/uPlot.canvas2d.js"
95
- },
96
- "./smoke": {
97
- "import": "./webgpu/smokeTest.js",
98
- "default": "./webgpu/smokeTest.js"
99
- },
100
- "./uPlot.css": {
101
- "import": "./uPlot.css",
102
- "default": "./uPlot.css"
103
- },
104
- "./webgpu/exporters": {
105
- "types": "./webgpu/exporters.d.ts",
106
- "import": "./webgpu/exporters.js",
107
- "default": "./webgpu/exporters.js"
108
- }
109
44
  }
110
45
  }
@@ -0,0 +1,253 @@
1
+ import { GPUPath } from '../../scripts/ts/webgpu/GPUPath.js';
2
+ import { abs, floor, min, max, inf, ifNull, EMPTY_OBJ, fnOrSelf, clamp, retArg0, EMPTY_ARR } from '../../scripts/ts/utils/utils.js';
3
+ import { orient, rectV, rectH } from './utils.js';
4
+ import type { BarPathOptions, PathBuildResult, PathBuilder, PathData, PathXData, ScaleLike, ValueToPos } from './types.js';
5
+
6
+ function findColWidth(dataX: PathXData, dataY: PathData, valToPosX: ValueToPos, scaleX: ScaleLike, xDim: number, xOff: number, colWid: number = inf): number {
7
+ if (dataX.length > 1) {
8
+ // prior index with non-undefined y data
9
+ let prevIdx = null;
10
+
11
+ // scan full dataset for smallest adjacent delta
12
+ // will not work properly for non-linear x scales, since does not do expensive valToPosX calcs till end
13
+ for (let i = 0, minDelta = Infinity; i < dataX.length; i++) {
14
+ if (dataY[i] !== undefined) {
15
+ if (prevIdx != null) {
16
+ let delta = abs(dataX[i] - dataX[prevIdx]);
17
+
18
+ if (delta < minDelta) {
19
+ minDelta = delta;
20
+ colWid = abs(valToPosX(dataX[i], scaleX, xDim, xOff) - valToPosX(dataX[prevIdx], scaleX, xDim, xOff));
21
+ }
22
+ }
23
+
24
+ prevIdx = i;
25
+ }
26
+ }
27
+ }
28
+
29
+ return colWid;
30
+ }
31
+
32
+ export function bars(opts: BarPathOptions = {}): PathBuilder {
33
+ const size = ifNull(opts.size, [0.6, inf, 1]);
34
+ const align = opts.align || 0;
35
+ const _extraGap = (opts.gap || 0);
36
+
37
+ let ro = opts.radius;
38
+
39
+ ro =
40
+ // [valueRadius, baselineRadius]
41
+ ro == null ? [0, 0] :
42
+ typeof ro == 'number' ? [ro, 0] : ro;
43
+
44
+ const radiusFn = fnOrSelf(ro);
45
+
46
+ const gapFactor = 1 - size[0];
47
+ const _maxWidth = ifNull(size[1], inf);
48
+ const _minWidth = ifNull(size[2], 1);
49
+
50
+ const disp = ifNull(opts.disp, EMPTY_OBJ);
51
+ const _each = ifNull(opts.each, _ => {});
52
+
53
+ const { fill: dispFills, stroke: dispStrokes } = disp;
54
+
55
+ return (u, seriesIdx, idx0, idx1) => {
56
+ let { pxRatio } = u;
57
+
58
+ return orient(u, seriesIdx, (series, dataX, dataY, scaleX, scaleY, valToPosX, valToPosY, xOff, yOff, xDim, yDim) => {
59
+ let pxRound = series.pxRound;
60
+ let _align = align;
61
+
62
+ let extraGap = _extraGap * pxRatio;
63
+ let maxWidth = _maxWidth * pxRatio;
64
+ let minWidth = _minWidth * pxRatio;
65
+
66
+ let valRadius, baseRadius;
67
+
68
+ if (scaleX.ori == 0)
69
+ [valRadius, baseRadius] = radiusFn(u, seriesIdx);
70
+ else
71
+ [baseRadius, valRadius] = radiusFn(u, seriesIdx);
72
+
73
+ const _dirX = scaleX.dir * (scaleX.ori == 0 ? 1 : -1);
74
+ // const _dirY = scaleY.dir * (scaleY.ori == 1 ? 1 : -1);
75
+
76
+ let rect = scaleX.ori == 0 ? rectH : rectV;
77
+
78
+ let each = scaleX.ori == 0 ? _each : (u, seriesIdx, i, top, lft, hgt, wid) => {
79
+ _each(u, seriesIdx, i, lft, top, wid, hgt);
80
+ };
81
+
82
+ // band where this series is the "from" edge
83
+ let band = ifNull(u.bands, EMPTY_ARR).find(b => b.series[0] == seriesIdx);
84
+
85
+ let fillDir = band != null ? band.dir : 0;
86
+ let fillTo = series.fillTo(u, seriesIdx, series.min, series.max, fillDir);
87
+ let fillToY = pxRound(valToPosY(fillTo, scaleY, yDim, yOff))
88
+
89
+ // barWid is to center of stroke
90
+ let xShift, barWid, fullGap, colWid = xDim;
91
+
92
+ let strokeWidth = pxRound(series.width * pxRatio);
93
+
94
+ let multiPath = false;
95
+
96
+ let fillColors = null;
97
+ let fillPaths = null;
98
+ let strokeColors = null;
99
+ let strokePaths = null;
100
+
101
+ if (dispFills != null && (strokeWidth == 0 || dispStrokes != null)) {
102
+ multiPath = true;
103
+
104
+ fillColors = dispFills.values(u, seriesIdx, idx0, idx1);
105
+ fillPaths = new Map();
106
+ (new Set(fillColors)).forEach(color => {
107
+ if (color != null)
108
+ fillPaths.set(color, new GPUPath());
109
+ });
110
+
111
+ if (strokeWidth > 0) {
112
+ strokeColors = dispStrokes.values(u, seriesIdx, idx0, idx1);
113
+ strokePaths = new Map();
114
+ (new Set(strokeColors)).forEach(color => {
115
+ if (color != null)
116
+ strokePaths.set(color, new GPUPath());
117
+ });
118
+ }
119
+ }
120
+
121
+ let { x0, size } = disp;
122
+
123
+ if (x0 != null && size != null) {
124
+ _align = 1;
125
+ dataX = x0.values(u, seriesIdx, idx0, idx1);
126
+
127
+ if (x0.unit == 2)
128
+ dataX = dataX.map(pct => u.posToVal(xOff + pct * xDim, scaleX.key, true));
129
+
130
+ // assumes uniform sizes, for now
131
+ let sizes = size.values(u, seriesIdx, idx0, idx1);
132
+
133
+ if (size.unit == 2)
134
+ barWid = sizes[0] * xDim;
135
+ else
136
+ barWid = valToPosX(sizes[0], scaleX, xDim, xOff) - valToPosX(0, scaleX, xDim, xOff); // assumes linear scale (delta from 0)
137
+
138
+ colWid = findColWidth(dataX, dataY, valToPosX, scaleX, xDim, xOff, colWid);
139
+
140
+ let gapWid = colWid - barWid;
141
+ fullGap = gapWid + extraGap;
142
+ }
143
+ else {
144
+ colWid = findColWidth(dataX, dataY, valToPosX, scaleX, xDim, xOff, colWid);
145
+
146
+ let gapWid = colWid * gapFactor;
147
+
148
+ fullGap = gapWid + extraGap;
149
+ barWid = colWid - fullGap;
150
+ }
151
+
152
+ if (fullGap < 1)
153
+ fullGap = 0;
154
+
155
+ if (strokeWidth >= barWid / 2)
156
+ strokeWidth = 0;
157
+
158
+ // for small gaps, disable pixel snapping since gap inconsistencies become noticible and annoying
159
+ if (fullGap < 5)
160
+ pxRound = retArg0;
161
+
162
+ let insetStroke = fullGap > 0;
163
+
164
+ let rawBarWid = colWid - fullGap - (insetStroke ? strokeWidth : 0);
165
+
166
+ barWid = pxRound(clamp(rawBarWid, minWidth, maxWidth));
167
+
168
+ xShift = (_align == 0 ? barWid / 2 : _align == _dirX ? 0 : barWid) - _align * _dirX * ((_align == 0 ? extraGap / 2 : 0) + (insetStroke ? strokeWidth / 2 : 0));
169
+
170
+
171
+ const _paths: PathBuildResult = {stroke: null, fill: null, clip: null, band: null, gaps: null, flags: 0}; // disp, geom
172
+
173
+ const stroke = multiPath ? null : new GPUPath();
174
+
175
+ let dataY0 = null;
176
+
177
+ if (band != null)
178
+ dataY0 = u.data[band.series[1]];
179
+ else {
180
+ let { y0, y1 } = disp;
181
+
182
+ if (y0 != null && y1 != null) {
183
+ dataY = y1.values(u, seriesIdx, idx0, idx1);
184
+ dataY0 = y0.values(u, seriesIdx, idx0, idx1);
185
+ }
186
+ }
187
+
188
+ let radVal = valRadius * barWid;
189
+ let radBase = baseRadius * barWid;
190
+
191
+ for (let i = _dirX == 1 ? idx0 : idx1; i >= idx0 && i <= idx1; i += _dirX) {
192
+ let yVal = dataY[i];
193
+
194
+ if (yVal == null)
195
+ continue;
196
+
197
+ if (dataY0 != null) {
198
+ let yVal0 = dataY0[i] ?? 0;
199
+
200
+ if (yVal - yVal0 == 0)
201
+ continue;
202
+
203
+ fillToY = valToPosY(yVal0, scaleY, yDim, yOff);
204
+ }
205
+
206
+ let xVal = scaleX.distr != 2 || disp != null ? dataX[i] : i;
207
+
208
+ // TODO: all xPos can be pre-computed once for all series in aligned set
209
+ let xPos = valToPosX(xVal, scaleX, xDim, xOff);
210
+ let yPos = valToPosY(ifNull(yVal, fillTo), scaleY, yDim, yOff);
211
+
212
+ let lft = pxRound(xPos - xShift);
213
+ let btm = pxRound(max(yPos, fillToY));
214
+ let top = pxRound(min(yPos, fillToY));
215
+ // this includes the stroke
216
+ let barHgt = btm - top;
217
+
218
+ if (yVal != null && yVal != fillTo) {
219
+ let rv = yVal < 0 ? radBase : radVal;
220
+ let rb = yVal < 0 ? radVal : radBase;
221
+
222
+ if (multiPath) {
223
+ if (strokeWidth > 0 && strokeColors[i] != null)
224
+ rect(strokePaths.get(strokeColors[i]), lft, top + floor(strokeWidth / 2), barWid, max(0, barHgt - strokeWidth), rv, rb);
225
+
226
+ if (fillColors[i] != null)
227
+ rect(fillPaths.get(fillColors[i]), lft, top + floor(strokeWidth / 2), barWid, max(0, barHgt - strokeWidth), rv, rb);
228
+ }
229
+ else
230
+ rect(stroke, lft, top + floor(strokeWidth / 2), barWid, max(0, barHgt - strokeWidth), rv, rb);
231
+
232
+ each(u, seriesIdx, i,
233
+ lft - strokeWidth / 2,
234
+ top,
235
+ barWid + strokeWidth,
236
+ barHgt,
237
+ );
238
+ }
239
+ }
240
+
241
+ if (strokeWidth > 0)
242
+ _paths.stroke = multiPath ? strokePaths : stroke;
243
+ else if (!multiPath) {
244
+ _paths._fill = series.width == 0 ? series._fill : series._stroke ?? series._fill;
245
+ _paths.width = 0;
246
+ }
247
+
248
+ _paths.fill = multiPath ? fillPaths : stroke;
249
+
250
+ return _paths;
251
+ });
252
+ };
253
+ }
@@ -0,0 +1,127 @@
1
+ import { GPUPath } from '../../scripts/ts/webgpu/GPUPath.js';
2
+ import { pow, sqrt } from '../../scripts/ts/utils/utils.js';
3
+ import { splineInterp } from "./spline.js";
4
+ import type { SplinePathOptions, PathBuilder, MoveToFn, LineToFn, BezierCurveToFn, PixelRound } from './types.js';
5
+
6
+ export function catmullRomCentrip(opts: SplinePathOptions = {}): PathBuilder {
7
+ return splineInterp(catmullRomFitting, opts);
8
+ }
9
+
10
+ // adapted from https://gist.github.com/nicholaswmin/c2661eb11cad5671d816 (MIT)
11
+ /**
12
+ * Interpolates a Catmull-Rom Spline through a series of x/y points
13
+ * Converts the CR Spline to Cubic Beziers for use with SVG items
14
+ *
15
+ * If 'alpha' is 0.5 then the 'Centripetal' variant is used
16
+ * If 'alpha' is 1 then the 'Chordal' variant is used
17
+ *
18
+ */
19
+ function catmullRomFitting(xCoords: number[], yCoords: number[], moveTo: MoveToFn, lineTo: LineToFn, bezierCurveTo: BezierCurveToFn, pxRound: PixelRound) {
20
+ const alpha = 0.5;
21
+
22
+ const path = new GPUPath();
23
+
24
+ const dataLen = xCoords.length;
25
+
26
+ let p0x,
27
+ p0y,
28
+ p1x,
29
+ p1y,
30
+ p2x,
31
+ p2y,
32
+ p3x,
33
+ p3y,
34
+ bp1x,
35
+ bp1y,
36
+ bp2x,
37
+ bp2y,
38
+ d1,
39
+ d2,
40
+ d3,
41
+ A,
42
+ B,
43
+ N,
44
+ M,
45
+ d3powA,
46
+ d2powA,
47
+ d3pow2A,
48
+ d2pow2A,
49
+ d1pow2A,
50
+ d1powA;
51
+
52
+ moveTo(path, pxRound(xCoords[0]), pxRound(yCoords[0]));
53
+
54
+ for (let i = 0; i < dataLen - 1; i++) {
55
+ let p0i = i == 0 ? 0 : i - 1;
56
+
57
+ p0x = xCoords[p0i];
58
+ p0y = yCoords[p0i];
59
+
60
+ p1x = xCoords[i];
61
+ p1y = yCoords[i];
62
+
63
+ p2x = xCoords[i + 1];
64
+ p2y = yCoords[i + 1];
65
+
66
+ if (i + 2 < dataLen) {
67
+ p3x = xCoords[i + 2];
68
+ p3y = yCoords[i + 2];
69
+ } else {
70
+ p3x = p2x;
71
+ p3y = p2y;
72
+ }
73
+
74
+ d1 = sqrt(pow(p0x - p1x, 2) + pow(p0y - p1y, 2));
75
+ d2 = sqrt(pow(p1x - p2x, 2) + pow(p1y - p2y, 2));
76
+ d3 = sqrt(pow(p2x - p3x, 2) + pow(p2y - p3y, 2));
77
+
78
+ // Catmull-Rom to Cubic Bezier conversion matrix
79
+
80
+ // A = 2d1^2a + 3d1^a * d2^a + d3^2a
81
+ // B = 2d3^2a + 3d3^a * d2^a + d2^2a
82
+
83
+ // [ 0 1 0 0 ]
84
+ // [ -d2^2a /N A/N d1^2a /N 0 ]
85
+ // [ 0 d3^2a /M B/M -d2^2a /M ]
86
+ // [ 0 0 1 0 ]
87
+
88
+ d3powA = pow(d3, alpha);
89
+ d3pow2A = pow(d3, alpha * 2);
90
+ d2powA = pow(d2, alpha);
91
+ d2pow2A = pow(d2, alpha * 2);
92
+ d1powA = pow(d1, alpha);
93
+ d1pow2A = pow(d1, alpha * 2);
94
+
95
+ A = 2 * d1pow2A + 3 * d1powA * d2powA + d2pow2A;
96
+ B = 2 * d3pow2A + 3 * d3powA * d2powA + d2pow2A;
97
+ N = 3 * d1powA * (d1powA + d2powA);
98
+
99
+ if (N > 0)
100
+ N = 1 / N;
101
+
102
+ M = 3 * d3powA * (d3powA + d2powA);
103
+
104
+ if (M > 0)
105
+ M = 1 / M;
106
+
107
+ bp1x = (-d2pow2A * p0x + A * p1x + d1pow2A * p2x) * N;
108
+ bp1y = (-d2pow2A * p0y + A * p1y + d1pow2A * p2y) * N;
109
+
110
+ bp2x = (d3pow2A * p1x + B * p2x - d2pow2A * p3x) * M;
111
+ bp2y = (d3pow2A * p1y + B * p2y - d2pow2A * p3y) * M;
112
+
113
+ if (bp1x == 0 && bp1y == 0) {
114
+ bp1x = p1x;
115
+ bp1y = p1y;
116
+ }
117
+
118
+ if (bp2x == 0 && bp2y == 0) {
119
+ bp2x = p2x;
120
+ bp2y = p2y;
121
+ }
122
+
123
+ bezierCurveTo(path, bp1x, bp1y, bp2x, bp2y, p2x, p2y);
124
+ }
125
+
126
+ return path;
127
+ }
@@ -0,0 +1,9 @@
1
+ export * from './bars.js';
2
+ export * from './catmullRomCentrip.js';
3
+ export * from './linear.js';
4
+ export * from './monotoneCubic.js';
5
+ export * from './points.js';
6
+ export * from './spline.js';
7
+ export * from './stepped.js';
8
+ export * from './utils.js';
9
+ export * from './types.js';
@@ -0,0 +1,172 @@
1
+ import { GPUPath } from '../../scripts/ts/webgpu/GPUPath.js';
2
+ import { nonNullIdxs, ifNull } from '../../scripts/ts/utils/utils.js';
3
+ import { orient, clipGaps, lineToH, lineToV, clipBandLine, BAND_CLIP_FILL, bandFillClipDirs, findGaps } from './utils.js';
4
+ import type { LineToFn, LinearPathOptions, PathBuilder } from './types.js';
5
+
6
+ function _drawAcc(lineTo: LineToFn) {
7
+ return (stroke: any, accX: number, minY: number, maxY: number, inY: number, outY: number) => {
8
+ if (minY != maxY) {
9
+ if (inY != minY && outY != minY)
10
+ lineTo(stroke, accX, minY);
11
+ if (inY != maxY && outY != maxY)
12
+ lineTo(stroke, accX, maxY);
13
+
14
+ lineTo(stroke, accX, outY);
15
+ }
16
+ };
17
+ }
18
+
19
+ const drawAccH = _drawAcc(lineToH);
20
+ const drawAccV = _drawAcc(lineToV);
21
+
22
+ export function linear(opts: LinearPathOptions = {}): PathBuilder {
23
+ return (u, seriesIdx, idx0, idx1) => {
24
+ return orient(u, seriesIdx, (series, dataX, dataY, scaleX, scaleY, valToPosX, valToPosY, xOff, yOff, xDim, yDim) => {
25
+ [idx0, idx1] = nonNullIdxs(dataY, idx0, idx1);
26
+
27
+ let pxRound = series.pxRound;
28
+
29
+ let alignGaps = opts?.alignGaps ?? series.alignGaps ?? 0;
30
+
31
+ let pixelForX = val => pxRound(valToPosX(val, scaleX, xDim, xOff));
32
+ let pixelForY = val => pxRound(valToPosY(val, scaleY, yDim, yOff));
33
+
34
+ let lineTo, drawAcc;
35
+
36
+ if (scaleX.ori == 0) {
37
+ lineTo = lineToH;
38
+ drawAcc = drawAccH;
39
+ }
40
+ else {
41
+ lineTo = lineToV;
42
+ drawAcc = drawAccV;
43
+ }
44
+
45
+ const dir = scaleX.dir * (scaleX.ori == 0 ? 1 : -1);
46
+
47
+ const _paths = {stroke: new GPUPath(), fill: null, clip: null, band: null, gaps: null, flags: BAND_CLIP_FILL};
48
+ const stroke = _paths.stroke;
49
+
50
+ let hasGap = false;
51
+
52
+ // decimate when number of points >= 4x available pixels
53
+ const decimate = idx1 - idx0 >= xDim * 4;
54
+
55
+ if (decimate) {
56
+ let xForPixel = pos => u.posToVal(pos, scaleX.key, true);
57
+
58
+ let minY = null,
59
+ maxY = null,
60
+ inY, outY, drawnAtX;
61
+
62
+ let accX = pixelForX(dataX[dir == 1 ? idx0 : idx1]);
63
+
64
+ let idx0px = pixelForX(dataX[idx0]);
65
+ let idx1px = pixelForX(dataX[idx1]);
66
+
67
+ // tracks limit of current x bucket to avoid having to get x pixel for every x value
68
+ let nextAccXVal = xForPixel(dir == 1 ? idx0px + 1 : idx1px - 1);
69
+
70
+ for (let i = dir == 1 ? idx0 : idx1; i >= idx0 && i <= idx1; i += dir) {
71
+ let xVal = dataX[i];
72
+ let reuseAccX = dir == 1 ? (xVal < nextAccXVal) : (xVal > nextAccXVal);
73
+ let x = reuseAccX ? accX : pixelForX(xVal);
74
+
75
+ let yVal = dataY[i];
76
+
77
+ if (x == accX) {
78
+ if (yVal != null) {
79
+ outY = yVal;
80
+
81
+ if (minY == null) {
82
+ lineTo(stroke, x, pixelForY(outY));
83
+ inY = minY = maxY = outY;
84
+ } else {
85
+ if (outY < minY)
86
+ minY = outY;
87
+ else if (outY > maxY)
88
+ maxY = outY;
89
+ }
90
+ }
91
+ else {
92
+ if (yVal === null)
93
+ hasGap = true;
94
+ }
95
+ }
96
+ else {
97
+ if (minY != null)
98
+ drawAcc(stroke, accX, pixelForY(minY), pixelForY(maxY), pixelForY(inY), pixelForY(outY));
99
+
100
+ if (yVal != null) {
101
+ outY = yVal;
102
+ lineTo(stroke, x, pixelForY(outY));
103
+ minY = maxY = inY = outY;
104
+ }
105
+ else {
106
+ minY = maxY = null;
107
+
108
+ if (yVal === null)
109
+ hasGap = true;
110
+ }
111
+
112
+ accX = x;
113
+ nextAccXVal = xForPixel(accX + dir);
114
+ }
115
+ }
116
+
117
+ if (minY != null && minY != maxY && drawnAtX != accX)
118
+ drawAcc(stroke, accX, pixelForY(minY), pixelForY(maxY), pixelForY(inY), pixelForY(outY));
119
+ }
120
+ else {
121
+ for (let i = dir == 1 ? idx0 : idx1; i >= idx0 && i <= idx1; i += dir) {
122
+ let yVal = dataY[i];
123
+
124
+ if (yVal === null)
125
+ hasGap = true;
126
+ else if (yVal != null)
127
+ lineTo(stroke, pixelForX(dataX[i]), pixelForY(yVal));
128
+ }
129
+ }
130
+
131
+ let [ bandFillDir, bandClipDir ] = bandFillClipDirs(u, seriesIdx);
132
+
133
+ if (series.fill != null || bandFillDir != 0) {
134
+ let fill = _paths.fill = new GPUPath(stroke);
135
+
136
+ let fillToVal = series.fillTo(u, seriesIdx, series.min, series.max, bandFillDir);
137
+ let fillToY = pixelForY(fillToVal);
138
+
139
+ let frX = pixelForX(dataX[idx0]);
140
+ let toX = pixelForX(dataX[idx1]);
141
+
142
+ if (dir == -1)
143
+ [toX, frX] = [frX, toX];
144
+
145
+ lineTo(fill, toX, fillToY);
146
+ lineTo(fill, frX, fillToY);
147
+ }
148
+
149
+ if (!series.spanGaps) { // skip in mode: 2?
150
+ // console.time('gaps');
151
+ let gaps = hasGap ? findGaps(dataX, dataY, idx0, idx1, dir, pixelForX, alignGaps) : [];
152
+
153
+ // console.timeEnd('gaps');
154
+
155
+ // console.log('gaps', JSON.stringify(gaps));
156
+
157
+ _paths.gaps = gaps = series.gaps(u, seriesIdx, idx0, idx1, gaps);
158
+
159
+ _paths.clip = clipGaps(gaps, scaleX.ori, xOff, yOff, xDim, yDim);
160
+ }
161
+
162
+ if (bandClipDir != 0) {
163
+ _paths.band = bandClipDir == 2 ? [
164
+ clipBandLine(u, seriesIdx, idx0, idx1, stroke, -1),
165
+ clipBandLine(u, seriesIdx, idx0, idx1, stroke, 1),
166
+ ] : clipBandLine(u, seriesIdx, idx0, idx1, stroke, bandClipDir);
167
+ }
168
+
169
+ return _paths;
170
+ });
171
+ };
172
+ }