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