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
package/favicon.ico ADDED
Binary file
package/index.html ADDED
@@ -0,0 +1,14 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <meta name="color-scheme" content="light" />
7
+ <meta name="supported-color-schemes" content="light" />
8
+ <title>uPlot WebGPU bench demo</title>
9
+ <link rel="stylesheet" href="dist/demo.css">
10
+ </head>
11
+ <body>
12
+ <script type="module" src="./dist/demo.js"></script>
13
+ </body>
14
+ </html>
package/index.js ADDED
@@ -0,0 +1,21 @@
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
+ export { default } from './scripts/uPlot.js';
19
+ export { default as uPlot } from './scripts/uPlot.js';
20
+ export { GPUPath } from './scripts/webgpu/GPUPath.js';
21
+ export { WebGPURenderer, WebGPURendererInternals } from './scripts/webgpu/WebGPURenderer.js';
@@ -0,0 +1,252 @@
1
+ import { abs, floor, min, max, inf, ifNull, EMPTY_OBJ, fnOrSelf, clamp, retArg0, EMPTY_ARR } from '../../scripts/utils/utils.js';
2
+ import { orient, rectV, rectH } from './utils.js';
3
+
4
+ function findColWidth(dataX, dataY, valToPosX, scaleX, xDim, xOff, colWid = inf) {
5
+ if (dataX.length > 1) {
6
+ // prior index with non-undefined y data
7
+ let prevIdx = null;
8
+
9
+ // scan full dataset for smallest adjacent delta
10
+ // will not work properly for non-linear x scales, since does not do expensive valToPosX calcs till end
11
+ for (let i = 0, minDelta = Infinity; i < dataX.length; i++) {
12
+ if (dataY[i] !== undefined) {
13
+ if (prevIdx != null) {
14
+ let delta = abs(dataX[i] - dataX[prevIdx]);
15
+
16
+ if (delta < minDelta) {
17
+ minDelta = delta;
18
+ colWid = abs(valToPosX(dataX[i], scaleX, xDim, xOff) - valToPosX(dataX[prevIdx], scaleX, xDim, xOff));
19
+ }
20
+ }
21
+
22
+ prevIdx = i;
23
+ }
24
+ }
25
+ }
26
+
27
+ return colWid;
28
+ }
29
+
30
+ export function bars(opts) {
31
+ opts = opts || EMPTY_OBJ;
32
+ const size = ifNull(opts.size, [0.6, inf, 1]);
33
+ const align = opts.align || 0;
34
+ const _extraGap = (opts.gap || 0);
35
+
36
+ let ro = opts.radius;
37
+
38
+ ro =
39
+ // [valueRadius, baselineRadius]
40
+ ro == null ? [0, 0] :
41
+ typeof ro == 'number' ? [ro, 0] : ro;
42
+
43
+ const radiusFn = fnOrSelf(ro);
44
+
45
+ const gapFactor = 1 - size[0];
46
+ const _maxWidth = ifNull(size[1], inf);
47
+ const _minWidth = ifNull(size[2], 1);
48
+
49
+ const disp = ifNull(opts.disp, EMPTY_OBJ);
50
+ const _each = ifNull(opts.each, _ => {});
51
+
52
+ const { fill: dispFills, stroke: dispStrokes } = disp;
53
+
54
+ return (u, seriesIdx, idx0, idx1) => {
55
+ let { pxRatio } = u;
56
+
57
+ return orient(u, seriesIdx, (series, dataX, dataY, scaleX, scaleY, valToPosX, valToPosY, xOff, yOff, xDim, yDim) => {
58
+ let pxRound = series.pxRound;
59
+ let _align = align;
60
+
61
+ let extraGap = _extraGap * pxRatio;
62
+ let maxWidth = _maxWidth * pxRatio;
63
+ let minWidth = _minWidth * pxRatio;
64
+
65
+ let valRadius, baseRadius;
66
+
67
+ if (scaleX.ori == 0)
68
+ [valRadius, baseRadius] = radiusFn(u, seriesIdx);
69
+ else
70
+ [baseRadius, valRadius] = radiusFn(u, seriesIdx);
71
+
72
+ const _dirX = scaleX.dir * (scaleX.ori == 0 ? 1 : -1);
73
+ // const _dirY = scaleY.dir * (scaleY.ori == 1 ? 1 : -1);
74
+
75
+ let rect = scaleX.ori == 0 ? rectH : rectV;
76
+
77
+ let each = scaleX.ori == 0 ? _each : (u, seriesIdx, i, top, lft, hgt, wid) => {
78
+ _each(u, seriesIdx, i, lft, top, wid, hgt);
79
+ };
80
+
81
+ // band where this series is the "from" edge
82
+ let band = ifNull(u.bands, EMPTY_ARR).find(b => b.series[0] == seriesIdx);
83
+
84
+ let fillDir = band != null ? band.dir : 0;
85
+ let fillTo = series.fillTo(u, seriesIdx, series.min, series.max, fillDir);
86
+ let fillToY = pxRound(valToPosY(fillTo, scaleY, yDim, yOff))
87
+
88
+ // barWid is to center of stroke
89
+ let xShift, barWid, fullGap, colWid = xDim;
90
+
91
+ let strokeWidth = pxRound(series.width * pxRatio);
92
+
93
+ let multiPath = false;
94
+
95
+ let fillColors = null;
96
+ let fillPaths = null;
97
+ let strokeColors = null;
98
+ let strokePaths = null;
99
+
100
+ if (dispFills != null && (strokeWidth == 0 || dispStrokes != null)) {
101
+ multiPath = true;
102
+
103
+ fillColors = dispFills.values(u, seriesIdx, idx0, idx1);
104
+ fillPaths = new Map();
105
+ (new Set(fillColors)).forEach(color => {
106
+ if (color != null)
107
+ fillPaths.set(color, new Path2D());
108
+ });
109
+
110
+ if (strokeWidth > 0) {
111
+ strokeColors = dispStrokes.values(u, seriesIdx, idx0, idx1);
112
+ strokePaths = new Map();
113
+ (new Set(strokeColors)).forEach(color => {
114
+ if (color != null)
115
+ strokePaths.set(color, new Path2D());
116
+ });
117
+ }
118
+ }
119
+
120
+ let { x0, size } = disp;
121
+
122
+ if (x0 != null && size != null) {
123
+ _align = 1;
124
+ dataX = x0.values(u, seriesIdx, idx0, idx1);
125
+
126
+ if (x0.unit == 2)
127
+ dataX = dataX.map(pct => u.posToVal(xOff + pct * xDim, scaleX.key, true));
128
+
129
+ // assumes uniform sizes, for now
130
+ let sizes = size.values(u, seriesIdx, idx0, idx1);
131
+
132
+ if (size.unit == 2)
133
+ barWid = sizes[0] * xDim;
134
+ else
135
+ barWid = valToPosX(sizes[0], scaleX, xDim, xOff) - valToPosX(0, scaleX, xDim, xOff); // assumes linear scale (delta from 0)
136
+
137
+ colWid = findColWidth(dataX, dataY, valToPosX, scaleX, xDim, xOff, colWid);
138
+
139
+ let gapWid = colWid - barWid;
140
+ fullGap = gapWid + extraGap;
141
+ }
142
+ else {
143
+ colWid = findColWidth(dataX, dataY, valToPosX, scaleX, xDim, xOff, colWid);
144
+
145
+ let gapWid = colWid * gapFactor;
146
+
147
+ fullGap = gapWid + extraGap;
148
+ barWid = colWid - fullGap;
149
+ }
150
+
151
+ if (fullGap < 1)
152
+ fullGap = 0;
153
+
154
+ if (strokeWidth >= barWid / 2)
155
+ strokeWidth = 0;
156
+
157
+ // for small gaps, disable pixel snapping since gap inconsistencies become noticible and annoying
158
+ if (fullGap < 5)
159
+ pxRound = retArg0;
160
+
161
+ let insetStroke = fullGap > 0;
162
+
163
+ let rawBarWid = colWid - fullGap - (insetStroke ? strokeWidth : 0);
164
+
165
+ barWid = pxRound(clamp(rawBarWid, minWidth, maxWidth));
166
+
167
+ xShift = (_align == 0 ? barWid / 2 : _align == _dirX ? 0 : barWid) - _align * _dirX * ((_align == 0 ? extraGap / 2 : 0) + (insetStroke ? strokeWidth / 2 : 0));
168
+
169
+
170
+ const _paths = {stroke: null, fill: null, clip: null, band: null, gaps: null, flags: 0}; // disp, geom
171
+
172
+ const stroke = multiPath ? null : new Path2D();
173
+
174
+ let dataY0 = null;
175
+
176
+ if (band != null)
177
+ dataY0 = u.data[band.series[1]];
178
+ else {
179
+ let { y0, y1 } = disp;
180
+
181
+ if (y0 != null && y1 != null) {
182
+ dataY = y1.values(u, seriesIdx, idx0, idx1);
183
+ dataY0 = y0.values(u, seriesIdx, idx0, idx1);
184
+ }
185
+ }
186
+
187
+ let radVal = valRadius * barWid;
188
+ let radBase = baseRadius * barWid;
189
+
190
+ for (let i = _dirX == 1 ? idx0 : idx1; i >= idx0 && i <= idx1; i += _dirX) {
191
+ let yVal = dataY[i];
192
+
193
+ if (yVal == null)
194
+ continue;
195
+
196
+ if (dataY0 != null) {
197
+ let yVal0 = dataY0[i] ?? 0;
198
+
199
+ if (yVal - yVal0 == 0)
200
+ continue;
201
+
202
+ fillToY = valToPosY(yVal0, scaleY, yDim, yOff);
203
+ }
204
+
205
+ let xVal = scaleX.distr != 2 || disp != null ? dataX[i] : i;
206
+
207
+ // TODO: all xPos can be pre-computed once for all series in aligned set
208
+ let xPos = valToPosX(xVal, scaleX, xDim, xOff);
209
+ let yPos = valToPosY(ifNull(yVal, fillTo), scaleY, yDim, yOff);
210
+
211
+ let lft = pxRound(xPos - xShift);
212
+ let btm = pxRound(max(yPos, fillToY));
213
+ let top = pxRound(min(yPos, fillToY));
214
+ // this includes the stroke
215
+ let barHgt = btm - top;
216
+
217
+ if (yVal != null && yVal != fillTo) {
218
+ let rv = yVal < 0 ? radBase : radVal;
219
+ let rb = yVal < 0 ? radVal : radBase;
220
+
221
+ if (multiPath) {
222
+ if (strokeWidth > 0 && strokeColors[i] != null)
223
+ rect(strokePaths.get(strokeColors[i]), lft, top + floor(strokeWidth / 2), barWid, max(0, barHgt - strokeWidth), rv, rb);
224
+
225
+ if (fillColors[i] != null)
226
+ rect(fillPaths.get(fillColors[i]), lft, top + floor(strokeWidth / 2), barWid, max(0, barHgt - strokeWidth), rv, rb);
227
+ }
228
+ else
229
+ rect(stroke, lft, top + floor(strokeWidth / 2), barWid, max(0, barHgt - strokeWidth), rv, rb);
230
+
231
+ each(u, seriesIdx, i,
232
+ lft - strokeWidth / 2,
233
+ top,
234
+ barWid + strokeWidth,
235
+ barHgt,
236
+ );
237
+ }
238
+ }
239
+
240
+ if (strokeWidth > 0)
241
+ _paths.stroke = multiPath ? strokePaths : stroke;
242
+ else if (!multiPath) {
243
+ _paths._fill = series.width == 0 ? series._fill : series._stroke ?? series._fill;
244
+ _paths.width = 0;
245
+ }
246
+
247
+ _paths.fill = multiPath ? fillPaths : stroke;
248
+
249
+ return _paths;
250
+ });
251
+ };
252
+ }
@@ -0,0 +1,125 @@
1
+ import { pow, sqrt } from '../../scripts/utils/utils.js';
2
+ import { splineInterp } from "./spline.js";
3
+
4
+ export function catmullRomCentrip(opts) {
5
+ return splineInterp(catmullRomFitting, opts);
6
+ }
7
+
8
+ // adapted from https://gist.github.com/nicholaswmin/c2661eb11cad5671d816 (MIT)
9
+ /**
10
+ * Interpolates a Catmull-Rom Spline through a series of x/y points
11
+ * Converts the CR Spline to Cubic Beziers for use with SVG items
12
+ *
13
+ * If 'alpha' is 0.5 then the 'Centripetal' variant is used
14
+ * If 'alpha' is 1 then the 'Chordal' variant is used
15
+ *
16
+ */
17
+ function catmullRomFitting(xCoords, yCoords, moveTo, lineTo, bezierCurveTo, pxRound) {
18
+ const alpha = 0.5;
19
+
20
+ const path = new Path2D();
21
+
22
+ const dataLen = xCoords.length;
23
+
24
+ let p0x,
25
+ p0y,
26
+ p1x,
27
+ p1y,
28
+ p2x,
29
+ p2y,
30
+ p3x,
31
+ p3y,
32
+ bp1x,
33
+ bp1y,
34
+ bp2x,
35
+ bp2y,
36
+ d1,
37
+ d2,
38
+ d3,
39
+ A,
40
+ B,
41
+ N,
42
+ M,
43
+ d3powA,
44
+ d2powA,
45
+ d3pow2A,
46
+ d2pow2A,
47
+ d1pow2A,
48
+ d1powA;
49
+
50
+ moveTo(path, pxRound(xCoords[0]), pxRound(yCoords[0]));
51
+
52
+ for (let i = 0; i < dataLen - 1; i++) {
53
+ let p0i = i == 0 ? 0 : i - 1;
54
+
55
+ p0x = xCoords[p0i];
56
+ p0y = yCoords[p0i];
57
+
58
+ p1x = xCoords[i];
59
+ p1y = yCoords[i];
60
+
61
+ p2x = xCoords[i + 1];
62
+ p2y = yCoords[i + 1];
63
+
64
+ if (i + 2 < dataLen) {
65
+ p3x = xCoords[i + 2];
66
+ p3y = yCoords[i + 2];
67
+ } else {
68
+ p3x = p2x;
69
+ p3y = p2y;
70
+ }
71
+
72
+ d1 = sqrt(pow(p0x - p1x, 2) + pow(p0y - p1y, 2));
73
+ d2 = sqrt(pow(p1x - p2x, 2) + pow(p1y - p2y, 2));
74
+ d3 = sqrt(pow(p2x - p3x, 2) + pow(p2y - p3y, 2));
75
+
76
+ // Catmull-Rom to Cubic Bezier conversion matrix
77
+
78
+ // A = 2d1^2a + 3d1^a * d2^a + d3^2a
79
+ // B = 2d3^2a + 3d3^a * d2^a + d2^2a
80
+
81
+ // [ 0 1 0 0 ]
82
+ // [ -d2^2a /N A/N d1^2a /N 0 ]
83
+ // [ 0 d3^2a /M B/M -d2^2a /M ]
84
+ // [ 0 0 1 0 ]
85
+
86
+ d3powA = pow(d3, alpha);
87
+ d3pow2A = pow(d3, alpha * 2);
88
+ d2powA = pow(d2, alpha);
89
+ d2pow2A = pow(d2, alpha * 2);
90
+ d1powA = pow(d1, alpha);
91
+ d1pow2A = pow(d1, alpha * 2);
92
+
93
+ A = 2 * d1pow2A + 3 * d1powA * d2powA + d2pow2A;
94
+ B = 2 * d3pow2A + 3 * d3powA * d2powA + d2pow2A;
95
+ N = 3 * d1powA * (d1powA + d2powA);
96
+
97
+ if (N > 0)
98
+ N = 1 / N;
99
+
100
+ M = 3 * d3powA * (d3powA + d2powA);
101
+
102
+ if (M > 0)
103
+ M = 1 / M;
104
+
105
+ bp1x = (-d2pow2A * p0x + A * p1x + d1pow2A * p2x) * N;
106
+ bp1y = (-d2pow2A * p0y + A * p1y + d1pow2A * p2y) * N;
107
+
108
+ bp2x = (d3pow2A * p1x + B * p2x - d2pow2A * p3x) * M;
109
+ bp2y = (d3pow2A * p1y + B * p2y - d2pow2A * p3y) * M;
110
+
111
+ if (bp1x == 0 && bp1y == 0) {
112
+ bp1x = p1x;
113
+ bp1y = p1y;
114
+ }
115
+
116
+ if (bp2x == 0 && bp2y == 0) {
117
+ bp2x = p2x;
118
+ bp2y = p2y;
119
+ }
120
+
121
+ bezierCurveTo(path, bp1x, bp1y, bp2x, bp2y, p2x, p2y);
122
+ }
123
+
124
+ return path;
125
+ }
@@ -0,0 +1,170 @@
1
+ import { nonNullIdxs, ifNull } from '../../scripts/utils/utils.js';
2
+ import { orient, clipGaps, lineToH, lineToV, clipBandLine, BAND_CLIP_FILL, bandFillClipDirs, findGaps } from './utils.js';
3
+
4
+ function _drawAcc(lineTo) {
5
+ return (stroke, accX, minY, maxY, inY, outY) => {
6
+ if (minY != maxY) {
7
+ if (inY != minY && outY != minY)
8
+ lineTo(stroke, accX, minY);
9
+ if (inY != maxY && outY != maxY)
10
+ lineTo(stroke, accX, maxY);
11
+
12
+ lineTo(stroke, accX, outY);
13
+ }
14
+ };
15
+ }
16
+
17
+ const drawAccH = _drawAcc(lineToH);
18
+ const drawAccV = _drawAcc(lineToV);
19
+
20
+ export function linear(opts) {
21
+ return (u, seriesIdx, idx0, idx1) => {
22
+ return orient(u, seriesIdx, (series, dataX, dataY, scaleX, scaleY, valToPosX, valToPosY, xOff, yOff, xDim, yDim) => {
23
+ [idx0, idx1] = nonNullIdxs(dataY, idx0, idx1);
24
+
25
+ let pxRound = series.pxRound;
26
+
27
+ let alignGaps = opts?.alignGaps ?? series.alignGaps ?? 0;
28
+
29
+ let pixelForX = val => pxRound(valToPosX(val, scaleX, xDim, xOff));
30
+ let pixelForY = val => pxRound(valToPosY(val, scaleY, yDim, yOff));
31
+
32
+ let lineTo, drawAcc;
33
+
34
+ if (scaleX.ori == 0) {
35
+ lineTo = lineToH;
36
+ drawAcc = drawAccH;
37
+ }
38
+ else {
39
+ lineTo = lineToV;
40
+ drawAcc = drawAccV;
41
+ }
42
+
43
+ const dir = scaleX.dir * (scaleX.ori == 0 ? 1 : -1);
44
+
45
+ const _paths = {stroke: new Path2D(), fill: null, clip: null, band: null, gaps: null, flags: BAND_CLIP_FILL};
46
+ const stroke = _paths.stroke;
47
+
48
+ let hasGap = false;
49
+
50
+ // decimate when number of points >= 4x available pixels
51
+ const decimate = idx1 - idx0 >= xDim * 4;
52
+
53
+ if (decimate) {
54
+ let xForPixel = pos => u.posToVal(pos, scaleX.key, true);
55
+
56
+ let minY = null,
57
+ maxY = null,
58
+ inY, outY, drawnAtX;
59
+
60
+ let accX = pixelForX(dataX[dir == 1 ? idx0 : idx1]);
61
+
62
+ let idx0px = pixelForX(dataX[idx0]);
63
+ let idx1px = pixelForX(dataX[idx1]);
64
+
65
+ // tracks limit of current x bucket to avoid having to get x pixel for every x value
66
+ let nextAccXVal = xForPixel(dir == 1 ? idx0px + 1 : idx1px - 1);
67
+
68
+ for (let i = dir == 1 ? idx0 : idx1; i >= idx0 && i <= idx1; i += dir) {
69
+ let xVal = dataX[i];
70
+ let reuseAccX = dir == 1 ? (xVal < nextAccXVal) : (xVal > nextAccXVal);
71
+ let x = reuseAccX ? accX : pixelForX(xVal);
72
+
73
+ let yVal = dataY[i];
74
+
75
+ if (x == accX) {
76
+ if (yVal != null) {
77
+ outY = yVal;
78
+
79
+ if (minY == null) {
80
+ lineTo(stroke, x, pixelForY(outY));
81
+ inY = minY = maxY = outY;
82
+ } else {
83
+ if (outY < minY)
84
+ minY = outY;
85
+ else if (outY > maxY)
86
+ maxY = outY;
87
+ }
88
+ }
89
+ else {
90
+ if (yVal === null)
91
+ hasGap = true;
92
+ }
93
+ }
94
+ else {
95
+ if (minY != null)
96
+ drawAcc(stroke, accX, pixelForY(minY), pixelForY(maxY), pixelForY(inY), pixelForY(outY));
97
+
98
+ if (yVal != null) {
99
+ outY = yVal;
100
+ lineTo(stroke, x, pixelForY(outY));
101
+ minY = maxY = inY = outY;
102
+ }
103
+ else {
104
+ minY = maxY = null;
105
+
106
+ if (yVal === null)
107
+ hasGap = true;
108
+ }
109
+
110
+ accX = x;
111
+ nextAccXVal = xForPixel(accX + dir);
112
+ }
113
+ }
114
+
115
+ if (minY != null && minY != maxY && drawnAtX != accX)
116
+ drawAcc(stroke, accX, pixelForY(minY), pixelForY(maxY), pixelForY(inY), pixelForY(outY));
117
+ }
118
+ else {
119
+ for (let i = dir == 1 ? idx0 : idx1; i >= idx0 && i <= idx1; i += dir) {
120
+ let yVal = dataY[i];
121
+
122
+ if (yVal === null)
123
+ hasGap = true;
124
+ else if (yVal != null)
125
+ lineTo(stroke, pixelForX(dataX[i]), pixelForY(yVal));
126
+ }
127
+ }
128
+
129
+ let [ bandFillDir, bandClipDir ] = bandFillClipDirs(u, seriesIdx);
130
+
131
+ if (series.fill != null || bandFillDir != 0) {
132
+ let fill = _paths.fill = new Path2D(stroke);
133
+
134
+ let fillToVal = series.fillTo(u, seriesIdx, series.min, series.max, bandFillDir);
135
+ let fillToY = pixelForY(fillToVal);
136
+
137
+ let frX = pixelForX(dataX[idx0]);
138
+ let toX = pixelForX(dataX[idx1]);
139
+
140
+ if (dir == -1)
141
+ [toX, frX] = [frX, toX];
142
+
143
+ lineTo(fill, toX, fillToY);
144
+ lineTo(fill, frX, fillToY);
145
+ }
146
+
147
+ if (!series.spanGaps) { // skip in mode: 2?
148
+ // console.time('gaps');
149
+ let gaps = hasGap ? findGaps(dataX, dataY, idx0, idx1, dir, pixelForX, alignGaps) : [];
150
+
151
+ // console.timeEnd('gaps');
152
+
153
+ // console.log('gaps', JSON.stringify(gaps));
154
+
155
+ _paths.gaps = gaps = series.gaps(u, seriesIdx, idx0, idx1, gaps);
156
+
157
+ _paths.clip = clipGaps(gaps, scaleX.ori, xOff, yOff, xDim, yDim);
158
+ }
159
+
160
+ if (bandClipDir != 0) {
161
+ _paths.band = bandClipDir == 2 ? [
162
+ clipBandLine(u, seriesIdx, idx0, idx1, stroke, -1),
163
+ clipBandLine(u, seriesIdx, idx0, idx1, stroke, 1),
164
+ ] : clipBandLine(u, seriesIdx, idx0, idx1, stroke, bandClipDir);
165
+ }
166
+
167
+ return _paths;
168
+ });
169
+ };
170
+ }
@@ -0,0 +1,68 @@
1
+ import { splineInterp } from "./spline.js";
2
+
3
+ export function monotoneCubic(opts) {
4
+ return splineInterp(_monotoneCubic, opts);
5
+ }
6
+
7
+ // Monotone Cubic Spline interpolation, adapted from the Chartist.js implementation:
8
+ // https://github.com/gionkunz/chartist-js/blob/e7e78201bffe9609915e5e53cfafa29a5d6c49f9/src/scripts/interpolation.js#L240-L369
9
+ function _monotoneCubic(xs, ys, moveTo, lineTo, bezierCurveTo, pxRound) {
10
+ const n = xs.length;
11
+
12
+ if (n < 2)
13
+ return null;
14
+
15
+ const path = new Path2D();
16
+
17
+ moveTo(path, xs[0], ys[0]);
18
+
19
+ if (n == 2)
20
+ lineTo(path, xs[1], ys[1]);
21
+ else {
22
+ let ms = Array(n),
23
+ ds = Array(n - 1),
24
+ dys = Array(n - 1),
25
+ dxs = Array(n - 1);
26
+
27
+ // calc deltas and derivative
28
+ for (let i = 0; i < n - 1; i++) {
29
+ dys[i] = ys[i + 1] - ys[i];
30
+ dxs[i] = xs[i + 1] - xs[i];
31
+ ds[i] = dys[i] / dxs[i];
32
+ }
33
+
34
+ // determine desired slope (m) at each point using Fritsch-Carlson method
35
+ // http://math.stackexchange.com/questions/45218/implementation-of-monotone-cubic-interpolation
36
+ ms[0] = ds[0];
37
+
38
+ for (let i = 1; i < n - 1; i++) {
39
+ if (ds[i] === 0 || ds[i - 1] === 0 || (ds[i - 1] > 0) !== (ds[i] > 0))
40
+ ms[i] = 0;
41
+ else {
42
+ ms[i] = 3 * (dxs[i - 1] + dxs[i]) / (
43
+ (2 * dxs[i] + dxs[i - 1]) / ds[i - 1] +
44
+ (dxs[i] + 2 * dxs[i - 1]) / ds[i]
45
+ );
46
+
47
+ if (!isFinite(ms[i]))
48
+ ms[i] = 0;
49
+ }
50
+ }
51
+
52
+ ms[n - 1] = ds[n - 2];
53
+
54
+ for (let i = 0; i < n - 1; i++) {
55
+ bezierCurveTo(
56
+ path,
57
+ xs[i] + dxs[i] / 3,
58
+ ys[i] + ms[i] * dxs[i] / 3,
59
+ xs[i + 1] - dxs[i] / 3,
60
+ ys[i + 1] - ms[i + 1] * dxs[i] / 3,
61
+ xs[i + 1],
62
+ ys[i + 1],
63
+ );
64
+ }
65
+ }
66
+
67
+ return path;
68
+ }