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/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, MAX_BAR_W$1 * d.series.length);
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, MAX_BAR_W$1);
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, MAX_BAR_W$1);
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, MAX_BAR_W$1 * d.series.length);
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
- }, RADIUS$1, v >= 0);
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
- /** Compact number formatting: integers verbatim, else 4 significant digits. */
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
- /** SI-suffixed formatting: `1.2k`, `3.4M`, `5B`. */
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, alpha) {
1114
- const ctx = canvas.getContext("2d", { alpha });
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, true);
1137
- this.overlay = setup2d(this.overlayCanvas, true);
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, true);
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
- /** Linear scale from domain `[d0, d1]` to range `[r0, r1]`, with `invert`. */
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
- /** `[min, max]` of `values`; degenerate inputs get a sane fallback domain. */
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
- /** Pad a domain by 5% on each end; `includeZero` anchors the axis at 0. */
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
- if (GPU_KIND[options.type] && (options.renderer === "webgpu" || options.renderer === "auto")) Promise.resolve().then(() => require("./series-J_5Z6dah.cjs")).then(({ GpuSeries }) => GpuSeries.create(options.gpu).then((g) => {
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) for (let ci = 0; ci < d.categories.length; ci++) {
1840
- let pos = 0;
1841
- let neg = 0;
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
- const v = s.values[ci] ?? 0;
1844
- if (v >= 0) pos += v;
1845
- else neg += v;
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
- min = Math.min(min, neg);
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
- /** series rasterization on the GPU; grid/labels/hover stay on Canvas2D */
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
- const r = this.renderContext(t, scales);
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 (const s of d.series) {
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
- const pts = mapSeriesPx(d.sortedX, s.y, s.prevY, t, scales.yScale.domain[0], scales.xScale, scales.yScale, flow);
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
- const pts = seriesPoints(r, s);
2024
- if (kind === "area") {
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" && info.kind === "point") for (const v of info.values) {
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
- if (m > 1 && x < sortedX[m - 2]) resortXY(nd);
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;