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.
- package/CANVAS_PROXY.md +602 -0
- package/README.md +854 -0
- package/favicon.ico +0 -0
- package/index.html +14 -0
- package/index.js +21 -0
- package/original/paths.canvas2d/bars.js +252 -0
- package/original/paths.canvas2d/catmullRomCentrip.js +125 -0
- package/original/paths.canvas2d/linear.js +170 -0
- package/original/paths.canvas2d/monotoneCubic.js +68 -0
- package/original/paths.canvas2d/points.js +66 -0
- package/original/paths.canvas2d/spline.js +103 -0
- package/original/paths.canvas2d/stepped.js +124 -0
- package/original/paths.canvas2d/utils.js +301 -0
- package/original/uPlot.canvas2d.js +3548 -0
- package/package.json +110 -0
- package/paths/bars.js +253 -0
- package/paths/catmullRomCentrip.js +126 -0
- package/paths/linear.js +171 -0
- package/paths/monotoneCubic.js +69 -0
- package/paths/points.js +67 -0
- package/paths/spline.js +104 -0
- package/paths/stepped.js +125 -0
- package/paths/utils.js +301 -0
- package/scripts/uPlot.css +168 -0
- package/scripts/uPlot.d.ts +26 -0
- package/scripts/uPlot.js +3687 -0
- package/scripts/utils/dom.js +124 -0
- package/scripts/utils/domClasses.js +22 -0
- package/scripts/utils/feats.js +13 -0
- package/scripts/utils/fmtDate.js +398 -0
- package/scripts/utils/opts.js +844 -0
- package/scripts/utils/strings.js +22 -0
- package/scripts/utils/sync.js +27 -0
- package/scripts/utils/utils.js +692 -0
- package/scripts/webgpu/GPUPath.d.ts +46 -0
- package/scripts/webgpu/GPUPath.js +633 -0
- package/scripts/webgpu/GPUPath.ts +634 -0
- package/scripts/webgpu/WebGPURenderer.d.ts +176 -0
- package/scripts/webgpu/WebGPURenderer.js +4256 -0
- package/scripts/webgpu/WebGPURenderer.ts +4257 -0
- package/scripts/webgpu/browserSmokeHarness.js +105 -0
- package/scripts/webgpu/exporters.d.ts +8 -0
- package/scripts/webgpu/exporters.js +212 -0
- package/scripts/webgpu/shaders.d.ts +2 -0
- package/scripts/webgpu/shaders.js +76 -0
- package/scripts/webgpu/shaders.ts +77 -0
- package/scripts/webgpu/smokeTest.d.ts +2 -0
- package/scripts/webgpu/smokeTest.js +144 -0
- package/scripts/webgpu/webgpu-ambient.d.ts +41 -0
- 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
|
+
}
|