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/package.json
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "uplot-webgpu",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Dummy tinybuild app, for building. Replace description before publishing.",
|
|
5
|
+
"main": "./dist/uPlot.js",
|
|
6
|
+
"module": "./dist/uPlot.esm.js",
|
|
7
|
+
"types": "./dist/uPlot.d.ts",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"scripts": {
|
|
10
|
+
"start": "tinybuild",
|
|
11
|
+
"build": "tinybuild build",
|
|
12
|
+
"serve": "tinybuild serve",
|
|
13
|
+
"init": "node tinybuild/init.js",
|
|
14
|
+
"concurrent": "concurrently \"npm run python\" \"npm start\"",
|
|
15
|
+
"dev": "npm run pip && npm i --save-dev concurrently && npm i --save-dev nodemon && npm run concurrent",
|
|
16
|
+
"startdev": "nodemon --exec \"node tinybuild.js\" -e ejs,js,ts,jsx,tsx,css,html,jpg,png,scss,txt,csv",
|
|
17
|
+
"python": "python python/server.py",
|
|
18
|
+
"pip": "pip install quart && pip install websockets",
|
|
19
|
+
"pwa": "npm i workbox-cli && workbox generateSW node_server/pwa/workbox-config.js && npm run build && npm start",
|
|
20
|
+
"electron": "electron ./electron",
|
|
21
|
+
"android": "npx cap open android",
|
|
22
|
+
"ios": "npx cap open ios",
|
|
23
|
+
"tauri": "tauri",
|
|
24
|
+
"parity": "node scripts/parity_check.mjs",
|
|
25
|
+
"dropin": "node scripts/dropin_check.mjs",
|
|
26
|
+
"size:audit": "node scripts/size_audit.mjs"
|
|
27
|
+
},
|
|
28
|
+
"author": "",
|
|
29
|
+
"license": "",
|
|
30
|
+
"keywords": [
|
|
31
|
+
"esbuild"
|
|
32
|
+
],
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@webgpu/types": "~0.1.70"
|
|
35
|
+
},
|
|
36
|
+
"nodemonConfig": {
|
|
37
|
+
"env": {
|
|
38
|
+
"NODEMON": true
|
|
39
|
+
},
|
|
40
|
+
"ignore": [
|
|
41
|
+
"dist/",
|
|
42
|
+
".temp/"
|
|
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
|
+
}
|
|
110
|
+
}
|
package/paths/bars.js
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import { GPUPath } from '../scripts/webgpu/GPUPath.js';
|
|
2
|
+
import { abs, floor, min, max, inf, ifNull, EMPTY_OBJ, fnOrSelf, clamp, retArg0, EMPTY_ARR } from '../scripts/utils/utils.js';
|
|
3
|
+
import { orient, rectV, rectH } from './utils.js';
|
|
4
|
+
|
|
5
|
+
function findColWidth(dataX, dataY, valToPosX, scaleX, xDim, xOff, colWid = inf) {
|
|
6
|
+
if (dataX.length > 1) {
|
|
7
|
+
// prior index with non-undefined y data
|
|
8
|
+
let prevIdx = null;
|
|
9
|
+
|
|
10
|
+
// scan full dataset for smallest adjacent delta
|
|
11
|
+
// will not work properly for non-linear x scales, since does not do expensive valToPosX calcs till end
|
|
12
|
+
for (let i = 0, minDelta = Infinity; i < dataX.length; i++) {
|
|
13
|
+
if (dataY[i] !== undefined) {
|
|
14
|
+
if (prevIdx != null) {
|
|
15
|
+
let delta = abs(dataX[i] - dataX[prevIdx]);
|
|
16
|
+
|
|
17
|
+
if (delta < minDelta) {
|
|
18
|
+
minDelta = delta;
|
|
19
|
+
colWid = abs(valToPosX(dataX[i], scaleX, xDim, xOff) - valToPosX(dataX[prevIdx], scaleX, xDim, xOff));
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
prevIdx = i;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return colWid;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function bars(opts) {
|
|
32
|
+
opts = opts || EMPTY_OBJ;
|
|
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 = {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,126 @@
|
|
|
1
|
+
import { GPUPath } from '../scripts/webgpu/GPUPath.js';
|
|
2
|
+
import { pow, sqrt } from '../scripts/utils/utils.js';
|
|
3
|
+
import { splineInterp } from "./spline.js";
|
|
4
|
+
|
|
5
|
+
export function catmullRomCentrip(opts) {
|
|
6
|
+
return splineInterp(catmullRomFitting, opts);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// adapted from https://gist.github.com/nicholaswmin/c2661eb11cad5671d816 (MIT)
|
|
10
|
+
/**
|
|
11
|
+
* Interpolates a Catmull-Rom Spline through a series of x/y points
|
|
12
|
+
* Converts the CR Spline to Cubic Beziers for use with SVG items
|
|
13
|
+
*
|
|
14
|
+
* If 'alpha' is 0.5 then the 'Centripetal' variant is used
|
|
15
|
+
* If 'alpha' is 1 then the 'Chordal' variant is used
|
|
16
|
+
*
|
|
17
|
+
*/
|
|
18
|
+
function catmullRomFitting(xCoords, yCoords, moveTo, lineTo, bezierCurveTo, pxRound) {
|
|
19
|
+
const alpha = 0.5;
|
|
20
|
+
|
|
21
|
+
const path = new GPUPath();
|
|
22
|
+
|
|
23
|
+
const dataLen = xCoords.length;
|
|
24
|
+
|
|
25
|
+
let p0x,
|
|
26
|
+
p0y,
|
|
27
|
+
p1x,
|
|
28
|
+
p1y,
|
|
29
|
+
p2x,
|
|
30
|
+
p2y,
|
|
31
|
+
p3x,
|
|
32
|
+
p3y,
|
|
33
|
+
bp1x,
|
|
34
|
+
bp1y,
|
|
35
|
+
bp2x,
|
|
36
|
+
bp2y,
|
|
37
|
+
d1,
|
|
38
|
+
d2,
|
|
39
|
+
d3,
|
|
40
|
+
A,
|
|
41
|
+
B,
|
|
42
|
+
N,
|
|
43
|
+
M,
|
|
44
|
+
d3powA,
|
|
45
|
+
d2powA,
|
|
46
|
+
d3pow2A,
|
|
47
|
+
d2pow2A,
|
|
48
|
+
d1pow2A,
|
|
49
|
+
d1powA;
|
|
50
|
+
|
|
51
|
+
moveTo(path, pxRound(xCoords[0]), pxRound(yCoords[0]));
|
|
52
|
+
|
|
53
|
+
for (let i = 0; i < dataLen - 1; i++) {
|
|
54
|
+
let p0i = i == 0 ? 0 : i - 1;
|
|
55
|
+
|
|
56
|
+
p0x = xCoords[p0i];
|
|
57
|
+
p0y = yCoords[p0i];
|
|
58
|
+
|
|
59
|
+
p1x = xCoords[i];
|
|
60
|
+
p1y = yCoords[i];
|
|
61
|
+
|
|
62
|
+
p2x = xCoords[i + 1];
|
|
63
|
+
p2y = yCoords[i + 1];
|
|
64
|
+
|
|
65
|
+
if (i + 2 < dataLen) {
|
|
66
|
+
p3x = xCoords[i + 2];
|
|
67
|
+
p3y = yCoords[i + 2];
|
|
68
|
+
} else {
|
|
69
|
+
p3x = p2x;
|
|
70
|
+
p3y = p2y;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
d1 = sqrt(pow(p0x - p1x, 2) + pow(p0y - p1y, 2));
|
|
74
|
+
d2 = sqrt(pow(p1x - p2x, 2) + pow(p1y - p2y, 2));
|
|
75
|
+
d3 = sqrt(pow(p2x - p3x, 2) + pow(p2y - p3y, 2));
|
|
76
|
+
|
|
77
|
+
// Catmull-Rom to Cubic Bezier conversion matrix
|
|
78
|
+
|
|
79
|
+
// A = 2d1^2a + 3d1^a * d2^a + d3^2a
|
|
80
|
+
// B = 2d3^2a + 3d3^a * d2^a + d2^2a
|
|
81
|
+
|
|
82
|
+
// [ 0 1 0 0 ]
|
|
83
|
+
// [ -d2^2a /N A/N d1^2a /N 0 ]
|
|
84
|
+
// [ 0 d3^2a /M B/M -d2^2a /M ]
|
|
85
|
+
// [ 0 0 1 0 ]
|
|
86
|
+
|
|
87
|
+
d3powA = pow(d3, alpha);
|
|
88
|
+
d3pow2A = pow(d3, alpha * 2);
|
|
89
|
+
d2powA = pow(d2, alpha);
|
|
90
|
+
d2pow2A = pow(d2, alpha * 2);
|
|
91
|
+
d1powA = pow(d1, alpha);
|
|
92
|
+
d1pow2A = pow(d1, alpha * 2);
|
|
93
|
+
|
|
94
|
+
A = 2 * d1pow2A + 3 * d1powA * d2powA + d2pow2A;
|
|
95
|
+
B = 2 * d3pow2A + 3 * d3powA * d2powA + d2pow2A;
|
|
96
|
+
N = 3 * d1powA * (d1powA + d2powA);
|
|
97
|
+
|
|
98
|
+
if (N > 0)
|
|
99
|
+
N = 1 / N;
|
|
100
|
+
|
|
101
|
+
M = 3 * d3powA * (d3powA + d2powA);
|
|
102
|
+
|
|
103
|
+
if (M > 0)
|
|
104
|
+
M = 1 / M;
|
|
105
|
+
|
|
106
|
+
bp1x = (-d2pow2A * p0x + A * p1x + d1pow2A * p2x) * N;
|
|
107
|
+
bp1y = (-d2pow2A * p0y + A * p1y + d1pow2A * p2y) * N;
|
|
108
|
+
|
|
109
|
+
bp2x = (d3pow2A * p1x + B * p2x - d2pow2A * p3x) * M;
|
|
110
|
+
bp2y = (d3pow2A * p1y + B * p2y - d2pow2A * p3y) * M;
|
|
111
|
+
|
|
112
|
+
if (bp1x == 0 && bp1y == 0) {
|
|
113
|
+
bp1x = p1x;
|
|
114
|
+
bp1y = p1y;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (bp2x == 0 && bp2y == 0) {
|
|
118
|
+
bp2x = p2x;
|
|
119
|
+
bp2y = p2y;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
bezierCurveTo(path, bp1x, bp1y, bp2x, bp2y, p2x, p2y);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return path;
|
|
126
|
+
}
|
package/paths/linear.js
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { GPUPath } from '../scripts/webgpu/GPUPath.js';
|
|
2
|
+
import { nonNullIdxs, ifNull } from '../scripts/utils/utils.js';
|
|
3
|
+
import { orient, clipGaps, lineToH, lineToV, clipBandLine, BAND_CLIP_FILL, bandFillClipDirs, findGaps } from './utils.js';
|
|
4
|
+
|
|
5
|
+
function _drawAcc(lineTo) {
|
|
6
|
+
return (stroke, accX, minY, maxY, inY, outY) => {
|
|
7
|
+
if (minY != maxY) {
|
|
8
|
+
if (inY != minY && outY != minY)
|
|
9
|
+
lineTo(stroke, accX, minY);
|
|
10
|
+
if (inY != maxY && outY != maxY)
|
|
11
|
+
lineTo(stroke, accX, maxY);
|
|
12
|
+
|
|
13
|
+
lineTo(stroke, accX, outY);
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const drawAccH = _drawAcc(lineToH);
|
|
19
|
+
const drawAccV = _drawAcc(lineToV);
|
|
20
|
+
|
|
21
|
+
export function linear(opts) {
|
|
22
|
+
return (u, seriesIdx, idx0, idx1) => {
|
|
23
|
+
return orient(u, seriesIdx, (series, dataX, dataY, scaleX, scaleY, valToPosX, valToPosY, xOff, yOff, xDim, yDim) => {
|
|
24
|
+
[idx0, idx1] = nonNullIdxs(dataY, idx0, idx1);
|
|
25
|
+
|
|
26
|
+
let pxRound = series.pxRound;
|
|
27
|
+
|
|
28
|
+
let alignGaps = opts?.alignGaps ?? series.alignGaps ?? 0;
|
|
29
|
+
|
|
30
|
+
let pixelForX = val => pxRound(valToPosX(val, scaleX, xDim, xOff));
|
|
31
|
+
let pixelForY = val => pxRound(valToPosY(val, scaleY, yDim, yOff));
|
|
32
|
+
|
|
33
|
+
let lineTo, drawAcc;
|
|
34
|
+
|
|
35
|
+
if (scaleX.ori == 0) {
|
|
36
|
+
lineTo = lineToH;
|
|
37
|
+
drawAcc = drawAccH;
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
lineTo = lineToV;
|
|
41
|
+
drawAcc = drawAccV;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const dir = scaleX.dir * (scaleX.ori == 0 ? 1 : -1);
|
|
45
|
+
|
|
46
|
+
const _paths = {stroke: new GPUPath(), fill: null, clip: null, band: null, gaps: null, flags: BAND_CLIP_FILL};
|
|
47
|
+
const stroke = _paths.stroke;
|
|
48
|
+
|
|
49
|
+
let hasGap = false;
|
|
50
|
+
|
|
51
|
+
// decimate when number of points >= 4x available pixels
|
|
52
|
+
const decimate = idx1 - idx0 >= xDim * 4;
|
|
53
|
+
|
|
54
|
+
if (decimate) {
|
|
55
|
+
let xForPixel = pos => u.posToVal(pos, scaleX.key, true);
|
|
56
|
+
|
|
57
|
+
let minY = null,
|
|
58
|
+
maxY = null,
|
|
59
|
+
inY, outY, drawnAtX;
|
|
60
|
+
|
|
61
|
+
let accX = pixelForX(dataX[dir == 1 ? idx0 : idx1]);
|
|
62
|
+
|
|
63
|
+
let idx0px = pixelForX(dataX[idx0]);
|
|
64
|
+
let idx1px = pixelForX(dataX[idx1]);
|
|
65
|
+
|
|
66
|
+
// tracks limit of current x bucket to avoid having to get x pixel for every x value
|
|
67
|
+
let nextAccXVal = xForPixel(dir == 1 ? idx0px + 1 : idx1px - 1);
|
|
68
|
+
|
|
69
|
+
for (let i = dir == 1 ? idx0 : idx1; i >= idx0 && i <= idx1; i += dir) {
|
|
70
|
+
let xVal = dataX[i];
|
|
71
|
+
let reuseAccX = dir == 1 ? (xVal < nextAccXVal) : (xVal > nextAccXVal);
|
|
72
|
+
let x = reuseAccX ? accX : pixelForX(xVal);
|
|
73
|
+
|
|
74
|
+
let yVal = dataY[i];
|
|
75
|
+
|
|
76
|
+
if (x == accX) {
|
|
77
|
+
if (yVal != null) {
|
|
78
|
+
outY = yVal;
|
|
79
|
+
|
|
80
|
+
if (minY == null) {
|
|
81
|
+
lineTo(stroke, x, pixelForY(outY));
|
|
82
|
+
inY = minY = maxY = outY;
|
|
83
|
+
} else {
|
|
84
|
+
if (outY < minY)
|
|
85
|
+
minY = outY;
|
|
86
|
+
else if (outY > maxY)
|
|
87
|
+
maxY = outY;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
if (yVal === null)
|
|
92
|
+
hasGap = true;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
if (minY != null)
|
|
97
|
+
drawAcc(stroke, accX, pixelForY(minY), pixelForY(maxY), pixelForY(inY), pixelForY(outY));
|
|
98
|
+
|
|
99
|
+
if (yVal != null) {
|
|
100
|
+
outY = yVal;
|
|
101
|
+
lineTo(stroke, x, pixelForY(outY));
|
|
102
|
+
minY = maxY = inY = outY;
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
minY = maxY = null;
|
|
106
|
+
|
|
107
|
+
if (yVal === null)
|
|
108
|
+
hasGap = true;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
accX = x;
|
|
112
|
+
nextAccXVal = xForPixel(accX + dir);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (minY != null && minY != maxY && drawnAtX != accX)
|
|
117
|
+
drawAcc(stroke, accX, pixelForY(minY), pixelForY(maxY), pixelForY(inY), pixelForY(outY));
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
for (let i = dir == 1 ? idx0 : idx1; i >= idx0 && i <= idx1; i += dir) {
|
|
121
|
+
let yVal = dataY[i];
|
|
122
|
+
|
|
123
|
+
if (yVal === null)
|
|
124
|
+
hasGap = true;
|
|
125
|
+
else if (yVal != null)
|
|
126
|
+
lineTo(stroke, pixelForX(dataX[i]), pixelForY(yVal));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
let [ bandFillDir, bandClipDir ] = bandFillClipDirs(u, seriesIdx);
|
|
131
|
+
|
|
132
|
+
if (series.fill != null || bandFillDir != 0) {
|
|
133
|
+
let fill = _paths.fill = new GPUPath(stroke);
|
|
134
|
+
|
|
135
|
+
let fillToVal = series.fillTo(u, seriesIdx, series.min, series.max, bandFillDir);
|
|
136
|
+
let fillToY = pixelForY(fillToVal);
|
|
137
|
+
|
|
138
|
+
let frX = pixelForX(dataX[idx0]);
|
|
139
|
+
let toX = pixelForX(dataX[idx1]);
|
|
140
|
+
|
|
141
|
+
if (dir == -1)
|
|
142
|
+
[toX, frX] = [frX, toX];
|
|
143
|
+
|
|
144
|
+
lineTo(fill, toX, fillToY);
|
|
145
|
+
lineTo(fill, frX, fillToY);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (!series.spanGaps) { // skip in mode: 2?
|
|
149
|
+
// console.time('gaps');
|
|
150
|
+
let gaps = hasGap ? findGaps(dataX, dataY, idx0, idx1, dir, pixelForX, alignGaps) : [];
|
|
151
|
+
|
|
152
|
+
// console.timeEnd('gaps');
|
|
153
|
+
|
|
154
|
+
// console.log('gaps', JSON.stringify(gaps));
|
|
155
|
+
|
|
156
|
+
_paths.gaps = gaps = series.gaps(u, seriesIdx, idx0, idx1, gaps);
|
|
157
|
+
|
|
158
|
+
_paths.clip = clipGaps(gaps, scaleX.ori, xOff, yOff, xDim, yDim);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (bandClipDir != 0) {
|
|
162
|
+
_paths.band = bandClipDir == 2 ? [
|
|
163
|
+
clipBandLine(u, seriesIdx, idx0, idx1, stroke, -1),
|
|
164
|
+
clipBandLine(u, seriesIdx, idx0, idx1, stroke, 1),
|
|
165
|
+
] : clipBandLine(u, seriesIdx, idx0, idx1, stroke, bandClipDir);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return _paths;
|
|
169
|
+
});
|
|
170
|
+
};
|
|
171
|
+
}
|