resolvcharts 0.1.0 → 0.3.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/README.md +92 -26
- package/dist/index.cjs +290 -66
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +153 -28
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +153 -28
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +290 -67
- package/dist/index.mjs.map +1 -1
- package/dist/resolvcharts.global.min.js +195 -25
- package/dist/resolvcharts.global.min.js.map +1 -1
- package/dist/series-PmAjztEh.cjs +1191 -0
- package/dist/series-PmAjztEh.cjs.map +1 -0
- package/dist/series-wyqYpkXm.mjs +1191 -0
- package/dist/series-wyqYpkXm.mjs.map +1 -0
- package/package.json +21 -13
- package/dist/series-BdprL87H.mjs +0 -479
- package/dist/series-BdprL87H.mjs.map +0 -1
- package/dist/series-J_5Z6dah.cjs +0 -479
- package/dist/series-J_5Z6dah.cjs.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
//#region src/charts/bar.ts
|
|
3
|
-
const MAX_BAR_W$1 = 40;
|
|
4
|
-
const RADIUS$1 = 3;
|
|
5
2
|
function barGeometry(r, d, catIdx, seriesIdx, value) {
|
|
6
3
|
const cats = d.categories.length;
|
|
7
4
|
if (cats === 0) return null;
|
|
8
5
|
const slot = r.plot.w / cats;
|
|
9
|
-
const groupW = Math.min(slot * .8,
|
|
6
|
+
const groupW = Math.min(slot * .8, 40 * d.series.length);
|
|
10
7
|
const barW = groupW / d.series.length;
|
|
11
8
|
const x = r.plot.x + slot * catIdx + (slot - groupW) / 2 + barW * seriesIdx;
|
|
12
9
|
const base = r.yScale(0);
|
|
@@ -46,7 +43,7 @@ function stackedGeometry(r, d, catIdx, v0, v1) {
|
|
|
46
43
|
const cats = d.categories.length;
|
|
47
44
|
if (cats === 0) return null;
|
|
48
45
|
const slot = r.plot.w / cats;
|
|
49
|
-
const w = Math.min(slot * .8,
|
|
46
|
+
const w = Math.min(slot * .8, 40);
|
|
50
47
|
const x = r.plot.x + slot * catIdx + (slot - w) / 2;
|
|
51
48
|
const yTop = r.yScale(Math.max(v0, v1));
|
|
52
49
|
const yBot = r.yScale(Math.min(v0, v1));
|
|
@@ -69,7 +66,7 @@ function renderBar(r) {
|
|
|
69
66
|
const slot = r.plot.w / Math.max(1, cats);
|
|
70
67
|
const animate = r.t < 1;
|
|
71
68
|
if (r.opts.stacked) {
|
|
72
|
-
const w = Math.min(slot * .8,
|
|
69
|
+
const w = Math.min(slot * .8, 40);
|
|
73
70
|
for (let ci = 0; ci < cats; ci++) {
|
|
74
71
|
const x = r.plot.x + slot * ci + (slot - w) / 2;
|
|
75
72
|
let pos = 0;
|
|
@@ -92,7 +89,7 @@ function renderBar(r) {
|
|
|
92
89
|
}
|
|
93
90
|
}
|
|
94
91
|
} else {
|
|
95
|
-
const groupW = Math.min(slot * .8,
|
|
92
|
+
const groupW = Math.min(slot * .8, 40 * d.series.length);
|
|
96
93
|
const barW = groupW / d.series.length;
|
|
97
94
|
const base = r.yScale(0);
|
|
98
95
|
for (let si = 0; si < d.series.length; si++) {
|
|
@@ -113,7 +110,7 @@ function renderBar(r) {
|
|
|
113
110
|
y,
|
|
114
111
|
w: barW,
|
|
115
112
|
h
|
|
116
|
-
},
|
|
113
|
+
}, 3, v >= 0);
|
|
117
114
|
}
|
|
118
115
|
}
|
|
119
116
|
}
|
|
@@ -376,6 +373,7 @@ function decimateSeriesPx(xs, ys, prev, t, yFallback, xScale, yScale, pixelWidth
|
|
|
376
373
|
* ys must be pixel-space y coordinates of equal length.
|
|
377
374
|
*
|
|
378
375
|
* Returns interleaved [x, y] pairs ready for path construction.
|
|
376
|
+
* @category Utilities
|
|
379
377
|
*/
|
|
380
378
|
function decimateM4(xs, ys, pixelWidth) {
|
|
381
379
|
const n = Math.min(xs.length, ys.length);
|
|
@@ -437,6 +435,7 @@ function decimateM4(xs, ys, pixelWidth) {
|
|
|
437
435
|
/**
|
|
438
436
|
* Binary search for the index whose value is nearest to `target`.
|
|
439
437
|
* `xs` must be sorted ascending. Returns -1 for empty input.
|
|
438
|
+
* @category Utilities
|
|
440
439
|
*/
|
|
441
440
|
function nearestIndex(xs, target) {
|
|
442
441
|
const n = xs.length;
|
|
@@ -846,8 +845,151 @@ function hitCandle(r, px) {
|
|
|
846
845
|
};
|
|
847
846
|
}
|
|
848
847
|
//#endregion
|
|
848
|
+
//#region src/charts/combo.ts
|
|
849
|
+
/** pooled scratch for overlay [x, y] pairs — consumed within the call */
|
|
850
|
+
let ovScratch = /* @__PURE__ */ new Float64Array(0);
|
|
851
|
+
function overlayPath(r, pts, n) {
|
|
852
|
+
const path = new Path2D();
|
|
853
|
+
if (n === 0) return path;
|
|
854
|
+
const curve = r.opts.curve ?? "linear";
|
|
855
|
+
if (curve === "step") {
|
|
856
|
+
path.moveTo(pts[0], pts[1]);
|
|
857
|
+
for (let i = 1; i < n; i++) {
|
|
858
|
+
const x = pts[2 * i];
|
|
859
|
+
path.lineTo(x, pts[2 * i - 1]);
|
|
860
|
+
path.lineTo(x, pts[2 * i + 1]);
|
|
861
|
+
}
|
|
862
|
+
} else if (curve === "smooth") smoothPath(path, pts, n);
|
|
863
|
+
else {
|
|
864
|
+
path.moveTo(pts[0], pts[1]);
|
|
865
|
+
for (let i = 1; i < n; i++) path.lineTo(pts[2 * i], pts[2 * i + 1]);
|
|
866
|
+
}
|
|
867
|
+
return path;
|
|
868
|
+
}
|
|
869
|
+
function renderCombo(r) {
|
|
870
|
+
if (r.data.kind !== "category") return;
|
|
871
|
+
const d = r.data;
|
|
872
|
+
const ctx = r.ctx;
|
|
873
|
+
ctx.save();
|
|
874
|
+
ctx.beginPath();
|
|
875
|
+
ctx.rect(r.plot.x, r.plot.y, r.plot.w, r.plot.h + 1);
|
|
876
|
+
ctx.clip();
|
|
877
|
+
const cats = d.categories.length;
|
|
878
|
+
const slot = r.plot.w / Math.max(1, cats);
|
|
879
|
+
const animate = r.t < 1;
|
|
880
|
+
const valAt = (s, ci) => {
|
|
881
|
+
const cur = s.values[ci] ?? 0;
|
|
882
|
+
const prev = s.prevValues?.[ci] ?? 0;
|
|
883
|
+
return animate ? prev + (cur - prev) * r.t : cur;
|
|
884
|
+
};
|
|
885
|
+
let nCols = 0;
|
|
886
|
+
for (const s of d.series) if (s.kind === "column") nCols++;
|
|
887
|
+
if (r.opts.stacked) {
|
|
888
|
+
const w = Math.min(slot * .8, 40);
|
|
889
|
+
for (let ci = 0; ci < cats; ci++) {
|
|
890
|
+
const x = r.plot.x + slot * ci + (slot - w) / 2;
|
|
891
|
+
let pos = 0;
|
|
892
|
+
let neg = 0;
|
|
893
|
+
for (const s of d.series) {
|
|
894
|
+
if (s.kind !== "column") continue;
|
|
895
|
+
const v = valAt(s, ci);
|
|
896
|
+
const v0 = v >= 0 ? pos : neg + v;
|
|
897
|
+
const v1 = v >= 0 ? pos + v : neg;
|
|
898
|
+
if (v >= 0) pos = v1;
|
|
899
|
+
else neg = v0;
|
|
900
|
+
const yTop = r.yScale(Math.max(v0, v1));
|
|
901
|
+
const h = Math.abs(r.yScale(Math.min(v0, v1)) - yTop);
|
|
902
|
+
if (h <= 0) continue;
|
|
903
|
+
ctx.fillStyle = s.color;
|
|
904
|
+
ctx.fillRect(x, yTop, w, h);
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
} else {
|
|
908
|
+
const groupW = Math.min(slot * .8, 40 * nCols);
|
|
909
|
+
const barW = groupW / Math.max(1, nCols);
|
|
910
|
+
const base = r.yScale(0);
|
|
911
|
+
let k = 0;
|
|
912
|
+
for (const s of d.series) {
|
|
913
|
+
if (s.kind !== "column") continue;
|
|
914
|
+
ctx.fillStyle = s.color;
|
|
915
|
+
for (let ci = 0; ci < cats; ci++) {
|
|
916
|
+
const v = valAt(s, ci);
|
|
917
|
+
const x = r.plot.x + slot * ci + (slot - groupW) / 2 + barW * k;
|
|
918
|
+
const yv = r.yScale(v);
|
|
919
|
+
const y = Math.min(base, yv);
|
|
920
|
+
const h = Math.abs(base - yv);
|
|
921
|
+
if (h <= 0) continue;
|
|
922
|
+
topRounded(ctx, {
|
|
923
|
+
x,
|
|
924
|
+
y,
|
|
925
|
+
w: barW,
|
|
926
|
+
h
|
|
927
|
+
}, 3, v >= 0);
|
|
928
|
+
}
|
|
929
|
+
k++;
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
const base = r.yScale(Math.max(0, r.yScale.domain[0]));
|
|
933
|
+
if (ovScratch.length < cats * 2) ovScratch = new Float64Array(cats * 2);
|
|
934
|
+
const pts = ovScratch;
|
|
935
|
+
for (const s of d.series) {
|
|
936
|
+
if (s.kind === "column") continue;
|
|
937
|
+
for (let ci = 0; ci < cats; ci++) {
|
|
938
|
+
pts[2 * ci] = r.plot.x + slot * (ci + .5);
|
|
939
|
+
pts[2 * ci + 1] = r.yScale(valAt(s, ci));
|
|
940
|
+
}
|
|
941
|
+
const path = overlayPath(r, pts, cats);
|
|
942
|
+
if (s.kind === "area" && cats > 1) {
|
|
943
|
+
const area = new Path2D(path);
|
|
944
|
+
area.lineTo(pts[2 * (cats - 1)], base);
|
|
945
|
+
area.lineTo(pts[0], base);
|
|
946
|
+
area.closePath();
|
|
947
|
+
const g = ctx.createLinearGradient(0, r.plot.y, 0, r.plot.y + r.plot.h);
|
|
948
|
+
g.addColorStop(0, withAlpha(s.color, .16));
|
|
949
|
+
g.addColorStop(1, withAlpha(s.color, 0));
|
|
950
|
+
ctx.fillStyle = g;
|
|
951
|
+
ctx.fill(area);
|
|
952
|
+
}
|
|
953
|
+
ctx.strokeStyle = s.color;
|
|
954
|
+
ctx.lineWidth = 2;
|
|
955
|
+
ctx.lineJoin = "round";
|
|
956
|
+
ctx.lineCap = "round";
|
|
957
|
+
ctx.stroke(path);
|
|
958
|
+
}
|
|
959
|
+
ctx.restore();
|
|
960
|
+
}
|
|
961
|
+
function hitCombo(r, px) {
|
|
962
|
+
if (r.data.kind !== "category") return null;
|
|
963
|
+
const d = r.data;
|
|
964
|
+
const cats = d.categories.length;
|
|
965
|
+
if (cats === 0 || px < r.plot.x || px > r.plot.x + r.plot.w) return null;
|
|
966
|
+
const cat = Math.min(cats - 1, Math.max(0, Math.floor((px - r.plot.x) / r.plot.w * cats)));
|
|
967
|
+
const slot = r.plot.w / cats;
|
|
968
|
+
let py = r.plot.y + r.plot.h;
|
|
969
|
+
const values = d.series.map((s) => {
|
|
970
|
+
const y = s.values[cat] ?? 0;
|
|
971
|
+
const sy = r.yScale(y);
|
|
972
|
+
if (sy < py) py = sy;
|
|
973
|
+
return {
|
|
974
|
+
label: s.label,
|
|
975
|
+
color: s.color,
|
|
976
|
+
y
|
|
977
|
+
};
|
|
978
|
+
});
|
|
979
|
+
return {
|
|
980
|
+
kind: "point",
|
|
981
|
+
index: cat,
|
|
982
|
+
x: cat,
|
|
983
|
+
px: r.plot.x + slot * (cat + .5),
|
|
984
|
+
py,
|
|
985
|
+
values,
|
|
986
|
+
title: d.categories[cat] ?? ""
|
|
987
|
+
};
|
|
988
|
+
}
|
|
989
|
+
//#endregion
|
|
849
990
|
//#region src/core/format.ts
|
|
850
|
-
/**
|
|
991
|
+
/** @category Formatting
|
|
992
|
+
* Compact number formatting: integers verbatim, else 4 significant digits. */
|
|
851
993
|
function formatNumber(v) {
|
|
852
994
|
if (!Number.isFinite(v)) return "";
|
|
853
995
|
if (Number.isInteger(v) && Math.abs(v) < 0x38d7ea4c68000) return String(v);
|
|
@@ -858,7 +1000,8 @@ const MAGNITUDES = [
|
|
|
858
1000
|
[1e6, "M"],
|
|
859
1001
|
[1e3, "k"]
|
|
860
1002
|
];
|
|
861
|
-
/**
|
|
1003
|
+
/** @category Formatting
|
|
1004
|
+
* SI-suffixed formatting: `1.2k`, `3.4M`, `5B`. */
|
|
862
1005
|
function formatSI(v) {
|
|
863
1006
|
if (!Number.isFinite(v)) return "";
|
|
864
1007
|
const abs = Math.abs(v);
|
|
@@ -1110,13 +1253,13 @@ function makeCanvas(cssW, cssH) {
|
|
|
1110
1253
|
c.height = Math.max(1, Math.round(cssH));
|
|
1111
1254
|
return c;
|
|
1112
1255
|
}
|
|
1113
|
-
function setup2d(canvas,
|
|
1114
|
-
const ctx = canvas.getContext("2d",
|
|
1256
|
+
function setup2d(canvas, settings) {
|
|
1257
|
+
const ctx = canvas.getContext("2d", settings);
|
|
1115
1258
|
if (!ctx) throw new Error("resolvcharts: could not get 2d context");
|
|
1116
1259
|
return ctx;
|
|
1117
1260
|
}
|
|
1118
1261
|
var CanvasBackend = class {
|
|
1119
|
-
constructor(container, cssW, cssH) {
|
|
1262
|
+
constructor(container, cssW, cssH, settings = {}) {
|
|
1120
1263
|
this.kind = "canvas2d";
|
|
1121
1264
|
this.plot = {
|
|
1122
1265
|
x: 0,
|
|
@@ -1127,14 +1270,18 @@ var CanvasBackend = class {
|
|
|
1127
1270
|
this.gridCache = null;
|
|
1128
1271
|
this.gridCtx = null;
|
|
1129
1272
|
this.container = container;
|
|
1273
|
+
this.settings = {
|
|
1274
|
+
alpha: true,
|
|
1275
|
+
...settings
|
|
1276
|
+
};
|
|
1130
1277
|
this.dpr = Math.max(1, window.devicePixelRatio || 1);
|
|
1131
1278
|
this.width = cssW;
|
|
1132
1279
|
this.height = cssH;
|
|
1133
1280
|
this.mainCanvas = makeCanvas(cssW, cssH);
|
|
1134
1281
|
this.overlayCanvas = makeCanvas(cssW, cssH);
|
|
1135
1282
|
this.overlayCanvas.style.pointerEvents = "none";
|
|
1136
|
-
this.main = setup2d(this.mainCanvas,
|
|
1137
|
-
this.overlay = setup2d(this.overlayCanvas,
|
|
1283
|
+
this.main = setup2d(this.mainCanvas, this.settings);
|
|
1284
|
+
this.overlay = setup2d(this.overlayCanvas, this.settings);
|
|
1138
1285
|
container.append(this.mainCanvas, this.overlayCanvas);
|
|
1139
1286
|
this.applySize();
|
|
1140
1287
|
}
|
|
@@ -1158,7 +1305,7 @@ var CanvasBackend = class {
|
|
|
1158
1305
|
this.gridCache = document.createElement("canvas");
|
|
1159
1306
|
this.gridCache.width = Math.max(1, this.width * this.dpr);
|
|
1160
1307
|
this.gridCache.height = Math.max(1, this.height * this.dpr);
|
|
1161
|
-
this.gridCtx = setup2d(this.gridCache,
|
|
1308
|
+
this.gridCtx = setup2d(this.gridCache, this.settings);
|
|
1162
1309
|
this.gridCtx.setTransform(this.dpr, 0, 0, this.dpr, 0, 0);
|
|
1163
1310
|
}
|
|
1164
1311
|
const ctx = this.gridCtx;
|
|
@@ -1193,7 +1340,8 @@ var CanvasBackend = class {
|
|
|
1193
1340
|
};
|
|
1194
1341
|
//#endregion
|
|
1195
1342
|
//#region src/core/scales.ts
|
|
1196
|
-
/**
|
|
1343
|
+
/** @category Utilities
|
|
1344
|
+
* Linear scale from domain `[d0, d1]` to range `[r0, r1]`, with `invert`. */
|
|
1197
1345
|
function linearScale(d0, d1, r0, r1) {
|
|
1198
1346
|
const dd = d1 - d0 || 1;
|
|
1199
1347
|
const dr = r1 - r0;
|
|
@@ -1203,7 +1351,8 @@ function linearScale(d0, d1, r0, r1) {
|
|
|
1203
1351
|
fn.range = [r0, r1];
|
|
1204
1352
|
return fn;
|
|
1205
1353
|
}
|
|
1206
|
-
/**
|
|
1354
|
+
/** @category Utilities
|
|
1355
|
+
* `[min, max]` of `values`; degenerate inputs get a sane fallback domain. */
|
|
1207
1356
|
function extent(values) {
|
|
1208
1357
|
let min = Infinity;
|
|
1209
1358
|
let max = -Infinity;
|
|
@@ -1216,7 +1365,8 @@ function extent(values) {
|
|
|
1216
1365
|
if (min === max) return min === 0 ? [0, 1] : [min - Math.abs(min) * .1, max + Math.abs(max) * .1];
|
|
1217
1366
|
return [min, max];
|
|
1218
1367
|
}
|
|
1219
|
-
/**
|
|
1368
|
+
/** @category Utilities
|
|
1369
|
+
* Pad a domain by 5% on each end; `includeZero` anchors the axis at 0. */
|
|
1220
1370
|
function padDomain(min, max, includeZero = false) {
|
|
1221
1371
|
let lo = includeZero ? Math.min(0, min) : min;
|
|
1222
1372
|
let hi = includeZero ? Math.max(0, max) : max;
|
|
@@ -1231,6 +1381,11 @@ function padDomain(min, max, includeZero = false) {
|
|
|
1231
1381
|
}
|
|
1232
1382
|
return [lo, hi];
|
|
1233
1383
|
}
|
|
1384
|
+
/** @category Utilities
|
|
1385
|
+
* Pin domain ends — each defined bound overrides the fitted value. */
|
|
1386
|
+
function boundDomain(dom, min, max) {
|
|
1387
|
+
return [min ?? dom[0], max ?? dom[1]];
|
|
1388
|
+
}
|
|
1234
1389
|
//#endregion
|
|
1235
1390
|
//#region src/core/scheduler.ts
|
|
1236
1391
|
const raf = typeof requestAnimationFrame === "function" ? (cb) => requestAnimationFrame(cb) : (cb) => setTimeout(() => cb(Date.now()), 16);
|
|
@@ -1270,6 +1425,9 @@ const easeOutCubic = (t) => 1 - (1 - t) ** 3;
|
|
|
1270
1425
|
//#endregion
|
|
1271
1426
|
//#region src/core/theme.ts
|
|
1272
1427
|
const FONT = "Inter, system-ui, -apple-system, \"Segoe UI\", sans-serif";
|
|
1428
|
+
/** Built-in light theme.
|
|
1429
|
+
* @category Theming
|
|
1430
|
+
*/
|
|
1273
1431
|
const lightTheme = {
|
|
1274
1432
|
bg: "#ffffff",
|
|
1275
1433
|
text: "#1f1f1f",
|
|
@@ -1288,6 +1446,9 @@ const lightTheme = {
|
|
|
1288
1446
|
tooltipShadow: "0 4px 14px rgba(0,0,0,0.08)",
|
|
1289
1447
|
fontFamily: FONT
|
|
1290
1448
|
};
|
|
1449
|
+
/** Built-in dark theme.
|
|
1450
|
+
* @category Theming
|
|
1451
|
+
*/
|
|
1291
1452
|
const darkTheme = {
|
|
1292
1453
|
bg: "#0a0a0a",
|
|
1293
1454
|
text: "#ededed",
|
|
@@ -1318,6 +1479,9 @@ const categoricalDark = [
|
|
|
1318
1479
|
"#a97ef0",
|
|
1319
1480
|
"#f5b84d"
|
|
1320
1481
|
];
|
|
1482
|
+
/** Merge a theme option (`"light" | "dark" | partial tokens`) into full {@link ThemeTokens}.
|
|
1483
|
+
* @category Theming
|
|
1484
|
+
*/
|
|
1321
1485
|
function resolveTheme(opt, accent) {
|
|
1322
1486
|
const base = opt === "dark" ? {
|
|
1323
1487
|
...darkTheme,
|
|
@@ -1358,6 +1522,9 @@ function niceStep(range, target) {
|
|
|
1358
1522
|
}
|
|
1359
1523
|
return step * pow;
|
|
1360
1524
|
}
|
|
1525
|
+
/** "Nice" round tick values covering `[min, max]`, ~`target` count.
|
|
1526
|
+
* @category Utilities
|
|
1527
|
+
*/
|
|
1361
1528
|
function computeTicks(min, max, target = 5) {
|
|
1362
1529
|
if (!(min < max)) return [min];
|
|
1363
1530
|
const step = niceStep(max - min, target);
|
|
@@ -1480,6 +1647,12 @@ const MODULES = {
|
|
|
1480
1647
|
axes: true,
|
|
1481
1648
|
defaultTooltip: true
|
|
1482
1649
|
},
|
|
1650
|
+
combo: {
|
|
1651
|
+
render: renderCombo,
|
|
1652
|
+
hit: (r, px) => hitCombo(r, px),
|
|
1653
|
+
axes: true,
|
|
1654
|
+
defaultTooltip: true
|
|
1655
|
+
},
|
|
1483
1656
|
sparkline: {
|
|
1484
1657
|
render: renderSparkline,
|
|
1485
1658
|
hit: (r, px) => hitXY(r, px),
|
|
@@ -1528,6 +1701,11 @@ const GPU_KIND = {
|
|
|
1528
1701
|
};
|
|
1529
1702
|
/** points retained beyond the window's left edge so they slide off-screen instead of vanishing mid-plot */
|
|
1530
1703
|
const EDGE_KEEP = 3;
|
|
1704
|
+
/** default point count at which `renderer: "auto"` engages the GPU path */
|
|
1705
|
+
const GPU_THRESHOLD = 1e5;
|
|
1706
|
+
/** Chart instance. Create via {@link createChart}.
|
|
1707
|
+
* @category Charts
|
|
1708
|
+
*/
|
|
1531
1709
|
var Chart = class {
|
|
1532
1710
|
constructor(el, options) {
|
|
1533
1711
|
this.tooltip = null;
|
|
@@ -1550,6 +1728,8 @@ var Chart = class {
|
|
|
1550
1728
|
this.destroyed = false;
|
|
1551
1729
|
this.pendingView = false;
|
|
1552
1730
|
this.gpu = null;
|
|
1731
|
+
this.gpuInit = false;
|
|
1732
|
+
this.dataRev = 0;
|
|
1553
1733
|
this.lastAppendAt = 0;
|
|
1554
1734
|
this.estInterval = 400;
|
|
1555
1735
|
this.onScroll = () => {
|
|
@@ -1608,11 +1788,39 @@ var Chart = class {
|
|
|
1608
1788
|
if (options.height) el.style.height = `${options.height}px`;
|
|
1609
1789
|
const w = el.clientWidth || 320;
|
|
1610
1790
|
const h = el.clientHeight || (options.type === "sparkline" ? 32 : 240);
|
|
1611
|
-
this.backend = new CanvasBackend(el, w, h);
|
|
1791
|
+
this.backend = new CanvasBackend(el, w, h, options.canvas);
|
|
1612
1792
|
this.reduced = typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
1613
1793
|
if (options.tooltip ?? this.mod.defaultTooltip) this.tooltip = new Tooltip(el, this.theme);
|
|
1614
1794
|
this.data = this.normalize(options.data, null);
|
|
1615
|
-
|
|
1795
|
+
this.maybeInitGpu();
|
|
1796
|
+
this.bindEvents();
|
|
1797
|
+
const dom = this.layout();
|
|
1798
|
+
this.view = {
|
|
1799
|
+
x: [...dom.x],
|
|
1800
|
+
y: [...dom.y]
|
|
1801
|
+
};
|
|
1802
|
+
this.viewTo = {
|
|
1803
|
+
x: [...dom.x],
|
|
1804
|
+
y: [...dom.y]
|
|
1805
|
+
};
|
|
1806
|
+
this.startAnimation(dom);
|
|
1807
|
+
}
|
|
1808
|
+
/**
|
|
1809
|
+
* Lazily acquire the WebGPU backend. `"webgpu"` attaches unconditionally;
|
|
1810
|
+
* `"auto"` waits until the xy point count crosses `gpuThreshold` — small
|
|
1811
|
+
* charts never pay adapter/device acquisition.
|
|
1812
|
+
*/
|
|
1813
|
+
maybeInitGpu() {
|
|
1814
|
+
if (this.gpu || this.gpuInit || this.destroyed) return;
|
|
1815
|
+
if (!GPU_KIND[this.options.type]) return;
|
|
1816
|
+
const r = this.options.renderer ?? "auto";
|
|
1817
|
+
if (r !== "auto" && r !== "webgpu") return;
|
|
1818
|
+
if (r === "auto") {
|
|
1819
|
+
const d = this.data;
|
|
1820
|
+
if ((d.kind === "xy" ? d.sortedX.length * Math.max(1, d.series.length) : 0) < (this.options.gpuThreshold ?? GPU_THRESHOLD)) return;
|
|
1821
|
+
}
|
|
1822
|
+
this.gpuInit = true;
|
|
1823
|
+
Promise.resolve().then(() => require("./series-PmAjztEh.cjs")).then(({ GpuSeries }) => GpuSeries.create(this.options.gpu).then((g) => {
|
|
1616
1824
|
if (!g || this.destroyed) {
|
|
1617
1825
|
g?.destroy();
|
|
1618
1826
|
return;
|
|
@@ -1625,17 +1833,6 @@ var Chart = class {
|
|
|
1625
1833
|
return false;
|
|
1626
1834
|
});
|
|
1627
1835
|
}));
|
|
1628
|
-
this.bindEvents();
|
|
1629
|
-
const dom = this.layout();
|
|
1630
|
-
this.view = {
|
|
1631
|
-
x: [...dom.x],
|
|
1632
|
-
y: [...dom.y]
|
|
1633
|
-
};
|
|
1634
|
-
this.viewTo = {
|
|
1635
|
-
x: [...dom.x],
|
|
1636
|
-
y: [...dom.y]
|
|
1637
|
-
};
|
|
1638
|
-
this.startAnimation(dom);
|
|
1639
1836
|
}
|
|
1640
1837
|
normalize(data, prev) {
|
|
1641
1838
|
const t = this.options.type;
|
|
@@ -1719,7 +1916,7 @@ var Chart = class {
|
|
|
1719
1916
|
total: slices.reduce((a, s) => a + Math.max(0, s.value), 0)
|
|
1720
1917
|
};
|
|
1721
1918
|
}
|
|
1722
|
-
if (t === "bar") {
|
|
1919
|
+
if (t === "bar" || t === "combo") {
|
|
1723
1920
|
const d = data;
|
|
1724
1921
|
const prevSeries = prev?.kind === "category" ? prev.series : [];
|
|
1725
1922
|
return {
|
|
@@ -1728,6 +1925,7 @@ var Chart = class {
|
|
|
1728
1925
|
series: d.series.map((s, i) => ({
|
|
1729
1926
|
label: s.label ?? `Series ${i + 1}`,
|
|
1730
1927
|
color: seriesColor(this.theme, i, s.color),
|
|
1928
|
+
kind: s.kind ?? "column",
|
|
1731
1929
|
values: Float64Array.from(s.values),
|
|
1732
1930
|
prevValues: prevSeries[i]?.values ?? null
|
|
1733
1931
|
}))
|
|
@@ -1830,24 +2028,32 @@ var Chart = class {
|
|
|
1830
2028
|
const includeZero = this.options.type === "area" || this.options.type === "sparkline";
|
|
1831
2029
|
return {
|
|
1832
2030
|
x,
|
|
1833
|
-
y: padDomain(min, max, includeZero)
|
|
2031
|
+
y: this.yDomain(padDomain(min, max, includeZero))
|
|
1834
2032
|
};
|
|
1835
2033
|
}
|
|
1836
2034
|
if (d.kind === "category") {
|
|
1837
2035
|
let min = Infinity;
|
|
1838
2036
|
let max = -Infinity;
|
|
1839
|
-
if (this.options.stacked)
|
|
1840
|
-
let
|
|
1841
|
-
|
|
2037
|
+
if (this.options.stacked) {
|
|
2038
|
+
for (let ci = 0; ci < d.categories.length; ci++) {
|
|
2039
|
+
let pos = 0;
|
|
2040
|
+
let neg = 0;
|
|
2041
|
+
for (const s of d.series) {
|
|
2042
|
+
if (s.kind !== "column") continue;
|
|
2043
|
+
const v = s.values[ci] ?? 0;
|
|
2044
|
+
if (v >= 0) pos += v;
|
|
2045
|
+
else neg += v;
|
|
2046
|
+
}
|
|
2047
|
+
min = Math.min(min, neg);
|
|
2048
|
+
max = Math.max(max, pos);
|
|
2049
|
+
}
|
|
1842
2050
|
for (const s of d.series) {
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
2051
|
+
if (s.kind === "column") continue;
|
|
2052
|
+
const e = extent(s.values);
|
|
2053
|
+
min = Math.min(min, e[0]);
|
|
2054
|
+
max = Math.max(max, e[1]);
|
|
1846
2055
|
}
|
|
1847
|
-
|
|
1848
|
-
max = Math.max(max, pos);
|
|
1849
|
-
}
|
|
1850
|
-
else for (const s of d.series) {
|
|
2056
|
+
} else for (const s of d.series) {
|
|
1851
2057
|
const e = extent(s.values);
|
|
1852
2058
|
min = Math.min(min, e[0]);
|
|
1853
2059
|
max = Math.max(max, e[1]);
|
|
@@ -1858,7 +2064,7 @@ var Chart = class {
|
|
|
1858
2064
|
}
|
|
1859
2065
|
return {
|
|
1860
2066
|
x: [0, d.categories.length],
|
|
1861
|
-
y: padDomain(min, max, true)
|
|
2067
|
+
y: this.yDomain(padDomain(min, max, true))
|
|
1862
2068
|
};
|
|
1863
2069
|
}
|
|
1864
2070
|
if (d.kind === "ohlc") {
|
|
@@ -1877,7 +2083,7 @@ var Chart = class {
|
|
|
1877
2083
|
}
|
|
1878
2084
|
return {
|
|
1879
2085
|
x,
|
|
1880
|
-
y: padDomain(min, max)
|
|
2086
|
+
y: this.yDomain(padDomain(min, max))
|
|
1881
2087
|
};
|
|
1882
2088
|
}
|
|
1883
2089
|
if (d.kind === "waterfall") {
|
|
@@ -1893,14 +2099,17 @@ var Chart = class {
|
|
|
1893
2099
|
}
|
|
1894
2100
|
return {
|
|
1895
2101
|
x: [0, d.bars.length],
|
|
1896
|
-
y: padDomain(min, max, true)
|
|
2102
|
+
y: this.yDomain(padDomain(min, max, true))
|
|
1897
2103
|
};
|
|
1898
2104
|
}
|
|
1899
2105
|
return {
|
|
1900
2106
|
x: [0, 1],
|
|
1901
|
-
y: [0, 1]
|
|
2107
|
+
y: this.yDomain([0, 1])
|
|
1902
2108
|
};
|
|
1903
2109
|
}
|
|
2110
|
+
yDomain(y) {
|
|
2111
|
+
return boundDomain(y, this.options.yMin, this.options.yMax);
|
|
2112
|
+
}
|
|
1904
2113
|
lerpView(eased) {
|
|
1905
2114
|
const f = this.viewFrom;
|
|
1906
2115
|
const to = this.viewTo;
|
|
@@ -1981,7 +2190,7 @@ var Chart = class {
|
|
|
1981
2190
|
ctx.fillText(fmtX(t), px, plot.y + plot.h + 6);
|
|
1982
2191
|
}
|
|
1983
2192
|
}
|
|
1984
|
-
if (this.options.type === "bar") {
|
|
2193
|
+
if (this.options.type === "bar" || this.options.type === "combo") {
|
|
1985
2194
|
const base = Math.round(yScale(0)) + .5;
|
|
1986
2195
|
if (base >= plot.y && base <= plot.y + plot.h + 1) {
|
|
1987
2196
|
ctx.strokeStyle = this.theme.axisBaseline;
|
|
@@ -2002,30 +2211,35 @@ var Chart = class {
|
|
|
2002
2211
|
else this.mod.render(this.renderContext(t, scales));
|
|
2003
2212
|
if (this.hover) this.drawHover(this.hover, scales.yScale);
|
|
2004
2213
|
}
|
|
2005
|
-
/**
|
|
2214
|
+
/**
|
|
2215
|
+
* Series rasterization on the GPU; grid/labels/hover stay on Canvas2D.
|
|
2216
|
+
* Raw data lives in GPU buffers — the per-frame cost is uniform writes +
|
|
2217
|
+
* instanced draws, with compute decimation above the density threshold.
|
|
2218
|
+
*/
|
|
2006
2219
|
gpuRender(t, scales) {
|
|
2007
2220
|
const g = this.gpu;
|
|
2008
2221
|
const d = this.data;
|
|
2009
2222
|
const kind = GPU_KIND[this.options.type];
|
|
2010
|
-
if (!g || !kind || d.kind !== "xy") {
|
|
2223
|
+
if (!g || g.unavailable || !kind || d.kind !== "xy") {
|
|
2011
2224
|
this.mod.render(this.renderContext(t, scales));
|
|
2012
2225
|
return;
|
|
2013
2226
|
}
|
|
2014
|
-
|
|
2227
|
+
g.syncData(d, this.dataRev);
|
|
2228
|
+
const view = {
|
|
2229
|
+
x: scales.xScale.domain,
|
|
2230
|
+
y: scales.yScale.domain
|
|
2231
|
+
};
|
|
2015
2232
|
const flow = this.options.flow ? FLOW_RIPPLE : 0;
|
|
2016
|
-
g.begin(this.backend.plot);
|
|
2017
|
-
for (
|
|
2233
|
+
g.begin(this.backend.plot, t, flow);
|
|
2234
|
+
for (let i = 0; i < d.series.length; i++) {
|
|
2235
|
+
const s = d.series[i];
|
|
2236
|
+
if (!s) continue;
|
|
2018
2237
|
if (kind === "points") {
|
|
2019
|
-
|
|
2020
|
-
g.points(pts, s.color, 2.5, .65);
|
|
2238
|
+
g.points(i, s.color, 2.5, .65, view);
|
|
2021
2239
|
continue;
|
|
2022
2240
|
}
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
const base = scales.yScale(Math.max(0, scales.yScale.domain[0]));
|
|
2026
|
-
g.area(pts, s.color, base, this.backend.plot);
|
|
2027
|
-
}
|
|
2028
|
-
g.line(pts, s.color, 2);
|
|
2241
|
+
if (kind === "area") g.area(i, s.color, Math.max(0, scales.yScale.domain[0]), view);
|
|
2242
|
+
g.line(i, s.color, 2, view);
|
|
2029
2243
|
}
|
|
2030
2244
|
g.end();
|
|
2031
2245
|
}
|
|
@@ -2042,9 +2256,9 @@ var Chart = class {
|
|
|
2042
2256
|
o.lineTo(x, plot.y + plot.h);
|
|
2043
2257
|
o.stroke();
|
|
2044
2258
|
const d = this.data;
|
|
2045
|
-
if (d.kind === "xy"
|
|
2259
|
+
if (info.kind === "point" && (d.kind === "xy" || d.kind === "category")) for (const v of info.values) {
|
|
2046
2260
|
const s = d.series.find((s2) => s2.label === v.label && s2.color === v.color);
|
|
2047
|
-
const yv = s ? s.y[info.index] : v.y;
|
|
2261
|
+
const yv = s ? "y" in s ? s.y[info.index] : s.values[info.index] : v.y;
|
|
2048
2262
|
o.fillStyle = v.color;
|
|
2049
2263
|
o.strokeStyle = this.theme.bg;
|
|
2050
2264
|
o.lineWidth = 2;
|
|
@@ -2106,7 +2320,7 @@ var Chart = class {
|
|
|
2106
2320
|
color: v.color,
|
|
2107
2321
|
label: v.label,
|
|
2108
2322
|
value: fmtY(v.y)
|
|
2109
|
-
})), fmtX(info.x));
|
|
2323
|
+
})), info.title ?? fmtX(info.x));
|
|
2110
2324
|
} else if (info.kind === "bar") this.tooltip.show(info.px, info.py, [{
|
|
2111
2325
|
color: info.color,
|
|
2112
2326
|
label: info.label,
|
|
@@ -2160,6 +2374,8 @@ var Chart = class {
|
|
|
2160
2374
|
setData(data) {
|
|
2161
2375
|
const prev = this.data;
|
|
2162
2376
|
this.data = this.normalize(data, prev);
|
|
2377
|
+
this.dataRev++;
|
|
2378
|
+
this.maybeInitGpu();
|
|
2163
2379
|
const dom = this.layout();
|
|
2164
2380
|
this.startAnimation(dom);
|
|
2165
2381
|
}
|
|
@@ -2225,8 +2441,14 @@ var Chart = class {
|
|
|
2225
2441
|
sortedX,
|
|
2226
2442
|
series
|
|
2227
2443
|
};
|
|
2228
|
-
|
|
2444
|
+
const resort = m > 1 && x < sortedX[m - 2];
|
|
2445
|
+
if (resort) resortXY(nd);
|
|
2229
2446
|
this.data = nd;
|
|
2447
|
+
if (resort) this.dataRev++;
|
|
2448
|
+
else if (this.gpu && !this.gpu.unavailable) {
|
|
2449
|
+
if (!this.gpu.appended(x, d.series.map((s, i) => ys[i] ?? s.y[n - 1]), d.series.map((s) => s.y[n - 1]), drop)) this.dataRev++;
|
|
2450
|
+
}
|
|
2451
|
+
this.maybeInitGpu();
|
|
2230
2452
|
this.pendingView = true;
|
|
2231
2453
|
sharedScheduler.add(this.frame);
|
|
2232
2454
|
}
|
|
@@ -2283,6 +2505,7 @@ function resortXY(d) {
|
|
|
2283
2505
|
}
|
|
2284
2506
|
/**
|
|
2285
2507
|
* Create a chart inside `el`.
|
|
2508
|
+
* @category Charts
|
|
2286
2509
|
*
|
|
2287
2510
|
* @example
|
|
2288
2511
|
* ```ts
|
|
@@ -2301,6 +2524,7 @@ function createChart(el, options) {
|
|
|
2301
2524
|
}
|
|
2302
2525
|
//#endregion
|
|
2303
2526
|
exports.Chart = Chart;
|
|
2527
|
+
exports.boundDomain = boundDomain;
|
|
2304
2528
|
exports.computeTicks = computeTicks;
|
|
2305
2529
|
exports.createChart = createChart;
|
|
2306
2530
|
exports.darkTheme = darkTheme;
|