resolvcharts 0.2.0 → 0.4.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.mjs CHANGED
@@ -462,12 +462,12 @@ let smScratch = /* @__PURE__ */ new Float64Array(0);
462
462
  * Monotone cubic path (Fritsch–Carlson tangents) — never overshoots data.
463
463
  * `pts` is interleaved [x0, y0, x1, y1, ...] with `n` points.
464
464
  */
465
- function smoothPath(ctx, pts, n) {
465
+ function smoothPath(ctx, pts, n, connect = false) {
466
466
  if (n < 3) {
467
467
  for (let i = 0; i < n; i++) {
468
468
  const x = pts[2 * i];
469
469
  const y = pts[2 * i + 1];
470
- if (i === 0) ctx.moveTo(x, y);
470
+ if (i === 0 && !connect) ctx.moveTo(x, y);
471
471
  else ctx.lineTo(x, y);
472
472
  }
473
473
  return;
@@ -498,7 +498,8 @@ function smoothPath(ctx, pts, n) {
498
498
  if (Math.abs(ti1) > lim) tang[i + 1] = Math.sign(ti1) * lim;
499
499
  }
500
500
  }
501
- ctx.moveTo(pts[0], pts[1]);
501
+ if (connect) ctx.lineTo(pts[0], pts[1]);
502
+ else ctx.moveTo(pts[0], pts[1]);
502
503
  for (let i = 0; i < segs; i++) {
503
504
  const x0 = pts[2 * i];
504
505
  const x1 = pts[2 * (i + 1)];
@@ -508,6 +509,27 @@ function smoothPath(ctx, pts, n) {
508
509
  ctx.bezierCurveTo(x0 + h, y0 + h * tang[i], x1 - h, y1 - h * tang[i + 1], x1, y1);
509
510
  }
510
511
  }
512
+ /**
513
+ * Trace `n` interleaved [x, y] pixel points into `path` honoring `curve`
514
+ * (linear / step / smooth). `connect` continues the current subpath with a
515
+ * lineTo instead of starting a new one — used for closed band shapes.
516
+ */
517
+ function traceCurve(path, pts, n, curve, connect = false) {
518
+ if (n === 0) return;
519
+ const join = (x, y) => connect ? path.lineTo(x, y) : path.moveTo(x, y);
520
+ if (curve === "step") {
521
+ join(pts[0], pts[1]);
522
+ for (let i = 1; i < n; i++) {
523
+ const x = pts[2 * i];
524
+ path.lineTo(x, pts[2 * i - 1]);
525
+ path.lineTo(x, pts[2 * i + 1]);
526
+ }
527
+ } else if (curve === "smooth") smoothPath(path, pts, n, connect);
528
+ else {
529
+ join(pts[0], pts[1]);
530
+ for (let i = 1; i < n; i++) path.lineTo(pts[2 * i], pts[2 * i + 1]);
531
+ }
532
+ }
511
533
  //#endregion
512
534
  //#region src/charts/line.ts
513
535
  /** fraction of the tween the trailing edge lags behind the leading point */
@@ -536,19 +558,7 @@ function buildPath(r, s, yFallback) {
536
558
  lastX: 0,
537
559
  lastY: 0
538
560
  };
539
- const curve = r.opts.curve ?? "linear";
540
- if (curve === "step") {
541
- path.moveTo(pts[0], pts[1]);
542
- for (let i = 1; i < dn; i++) {
543
- const x = pts[2 * i];
544
- path.lineTo(x, pts[2 * i - 1]);
545
- path.lineTo(x, pts[2 * i + 1]);
546
- }
547
- } else if (curve === "smooth") smoothPath(path, pts, dn);
548
- else {
549
- path.moveTo(pts[0], pts[1]);
550
- for (let i = 1; i < dn; i++) path.lineTo(pts[2 * i], pts[2 * i + 1]);
551
- }
561
+ traceCurve(path, pts, dn, r.opts.curve ?? "linear");
552
562
  return {
553
563
  path,
554
564
  firstX: pts[0],
@@ -675,6 +685,72 @@ function renderBaseline(r) {
675
685
  ctx.setLineDash([]);
676
686
  }
677
687
  //#endregion
688
+ //#region src/charts/bubble.ts
689
+ const MIN_R = 3;
690
+ const MAX_R = 16;
691
+ /**
692
+ * Bubble chart: scatter points sized by each series' `r` channel, mapped
693
+ * linearly to [MIN_R, MAX_R] px across the dataset's radius extent.
694
+ */
695
+ function renderBubble(r) {
696
+ if (!isXY(r.data)) return;
697
+ const d = r.data;
698
+ const ctx = r.ctx;
699
+ const t = r.t;
700
+ const lerp = t < 1;
701
+ const fallback = r.yScale.domain[0];
702
+ let lo = Infinity;
703
+ let hi = -Infinity;
704
+ for (const s of d.series) {
705
+ const rr = s.r;
706
+ if (!rr) continue;
707
+ for (let i = 0; i < rr.length; i++) {
708
+ const v = rr[i];
709
+ if (v < lo) lo = v;
710
+ if (v > hi) hi = v;
711
+ }
712
+ }
713
+ if (lo === Infinity) {
714
+ lo = 0;
715
+ hi = 1;
716
+ }
717
+ const span = hi - lo || 1;
718
+ const grow = lerp ? t : 1;
719
+ const rad = (v) => (MIN_R + (v - lo) / span * 13) * grow;
720
+ const xMin = r.plot.x - MAX_R;
721
+ const xMax = r.plot.x + r.plot.w + MAX_R;
722
+ ctx.save();
723
+ ctx.beginPath();
724
+ ctx.rect(r.plot.x, r.plot.y, r.plot.w, r.plot.h);
725
+ ctx.clip();
726
+ for (const s of d.series) {
727
+ const y = s.y;
728
+ const prev = s.prevY;
729
+ const rr = s.r;
730
+ ctx.fillStyle = withAlpha(s.color, .45);
731
+ ctx.strokeStyle = s.color;
732
+ ctx.lineWidth = 1.5;
733
+ ctx.beginPath();
734
+ for (let i = 0; i < d.sortedX.length; i++) {
735
+ const x = r.xScale(d.sortedX[i]);
736
+ if (x < xMin || x > xMax) continue;
737
+ let yv = y[i];
738
+ if (lerp) {
739
+ const p = prev ? prev[i] : fallback;
740
+ yv = p + (yv - p) * t;
741
+ }
742
+ const yp = r.yScale(yv);
743
+ const pr = rad(rr ? rr[i] : lo + span / 2);
744
+ if (pr <= 0) continue;
745
+ ctx.moveTo(x + pr, yp);
746
+ ctx.arc(x, yp, pr, 0, Math.PI * 2);
747
+ }
748
+ ctx.fill();
749
+ ctx.stroke();
750
+ }
751
+ ctx.restore();
752
+ }
753
+ //#endregion
678
754
  //#region src/charts/candle.ts
679
755
  function renderCandles(r, ohlcStyle) {
680
756
  if (r.data.kind !== "ohlc") return;
@@ -849,20 +925,7 @@ function hitCandle(r, px) {
849
925
  let ovScratch = /* @__PURE__ */ new Float64Array(0);
850
926
  function overlayPath(r, pts, n) {
851
927
  const path = new Path2D();
852
- if (n === 0) return path;
853
- const curve = r.opts.curve ?? "linear";
854
- if (curve === "step") {
855
- path.moveTo(pts[0], pts[1]);
856
- for (let i = 1; i < n; i++) {
857
- const x = pts[2 * i];
858
- path.lineTo(x, pts[2 * i - 1]);
859
- path.lineTo(x, pts[2 * i + 1]);
860
- }
861
- } else if (curve === "smooth") smoothPath(path, pts, n);
862
- else {
863
- path.moveTo(pts[0], pts[1]);
864
- for (let i = 1; i < n; i++) path.lineTo(pts[2 * i], pts[2 * i + 1]);
865
- }
928
+ traceCurve(path, pts, n, r.opts.curve ?? "linear");
866
929
  return path;
867
930
  }
868
931
  function renderCombo(r) {
@@ -1015,14 +1078,16 @@ function formatSI(v) {
1015
1078
  //#region src/charts/donut.ts
1016
1079
  const INNER = .65;
1017
1080
  const GAP = .015;
1018
- const START = -Math.PI / 2;
1081
+ const START$1 = -Math.PI / 2;
1082
+ /** donut keeps a center hole; pie fills to the middle */
1083
+ const innerRatio = (r) => r.opts.type === "pie" ? 0 : INNER;
1019
1084
  function donutGeometry(r, d, t) {
1020
1085
  const cx = r.plot.x + r.plot.w / 2;
1021
1086
  const cy = r.plot.y + r.plot.h / 2;
1022
1087
  const rOuter = Math.max(1, Math.min(r.plot.w, r.plot.h) / 2);
1023
- const rInner = rOuter * INNER;
1088
+ const rInner = rOuter * innerRatio(r);
1024
1089
  const angles = [];
1025
- let a = START;
1090
+ let a = START$1;
1026
1091
  const sweep = Math.PI * 2 * Math.min(1, Math.max(0, t));
1027
1092
  for (const s of d.slices) {
1028
1093
  const frac = d.total > 0 ? s.value / d.total : 0;
@@ -1055,12 +1120,13 @@ function renderDonut(r) {
1055
1120
  if (a1 <= a0) continue;
1056
1121
  ctx.beginPath();
1057
1122
  ctx.arc(geo.cx, geo.cy, geo.rOuter, a0, a1);
1058
- ctx.arc(geo.cx, geo.cy, geo.rInner, a1, a0, true);
1123
+ if (geo.rInner > 0) ctx.arc(geo.cx, geo.cy, geo.rInner, a1, a0, true);
1124
+ else ctx.lineTo(geo.cx, geo.cy);
1059
1125
  ctx.closePath();
1060
1126
  ctx.fillStyle = s.color;
1061
1127
  ctx.fill();
1062
1128
  }
1063
- if (d.slices.length > 0 && r.t > .5) {
1129
+ if (r.opts.type !== "pie" && d.slices.length > 0 && r.t > .5) {
1064
1130
  ctx.fillStyle = r.theme.text;
1065
1131
  ctx.font = `600 20px ${r.theme.fontFamily}`;
1066
1132
  ctx.textAlign = "center";
@@ -1077,8 +1143,8 @@ function hitDonut(r, px, py) {
1077
1143
  const dist = Math.hypot(dx, dy);
1078
1144
  if (dist < geo.rInner || dist > geo.rOuter) return null;
1079
1145
  let angle = Math.atan2(dy, dx);
1080
- while (angle < START) angle += Math.PI * 2;
1081
- while (angle >= START + Math.PI * 2) angle -= Math.PI * 2;
1146
+ while (angle < START$1) angle += Math.PI * 2;
1147
+ while (angle >= START$1 + Math.PI * 2) angle -= Math.PI * 2;
1082
1148
  for (let i = 0; i < geo.angles.length; i++) {
1083
1149
  const an = geo.angles[i];
1084
1150
  const s = d.slices[i];
@@ -1097,6 +1163,389 @@ function hitDonut(r, px, py) {
1097
1163
  return null;
1098
1164
  }
1099
1165
  //#endregion
1166
+ //#region src/core/theme.ts
1167
+ const FONT = "Inter, system-ui, -apple-system, \"Segoe UI\", sans-serif";
1168
+ /** Built-in light theme.
1169
+ * @category Theming
1170
+ */
1171
+ const lightTheme = {
1172
+ bg: "#ffffff",
1173
+ text: "#1f1f1f",
1174
+ textMuted: "#8a8a8a",
1175
+ grid: "rgba(0,0,0,0.07)",
1176
+ axisBaseline: "rgba(0,0,0,0.15)",
1177
+ crosshair: "rgba(0,0,0,0.25)",
1178
+ series: ["#0070f3"],
1179
+ contextGray: "#b0b0b0",
1180
+ up: "#0e9f6e",
1181
+ down: "#e5484d",
1182
+ tooltipBg: "#ffffff",
1183
+ tooltipText: "#1f1f1f",
1184
+ tooltipMuted: "#8a8a8a",
1185
+ tooltipBorder: "rgba(0,0,0,0.08)",
1186
+ tooltipShadow: "0 4px 14px rgba(0,0,0,0.08)",
1187
+ fontFamily: FONT
1188
+ };
1189
+ /** Built-in dark theme.
1190
+ * @category Theming
1191
+ */
1192
+ const darkTheme = {
1193
+ bg: "#0a0a0a",
1194
+ text: "#ededed",
1195
+ textMuted: "#8a8a8a",
1196
+ grid: "rgba(255,255,255,0.07)",
1197
+ axisBaseline: "rgba(255,255,255,0.18)",
1198
+ crosshair: "rgba(255,255,255,0.3)",
1199
+ series: ["#3b9dff"],
1200
+ contextGray: "#5c5c5c",
1201
+ up: "#34c98e",
1202
+ down: "#f2555a",
1203
+ tooltipBg: "#161616",
1204
+ tooltipText: "#ededed",
1205
+ tooltipMuted: "#8a8a8a",
1206
+ tooltipBorder: "rgba(255,255,255,0.1)",
1207
+ tooltipShadow: "0 4px 14px rgba(0,0,0,0.4)",
1208
+ fontFamily: FONT
1209
+ };
1210
+ const categoricalLight = [
1211
+ "#0070f3",
1212
+ "#0e9f6e",
1213
+ "#8e4ec6",
1214
+ "#f5a623"
1215
+ ];
1216
+ const categoricalDark = [
1217
+ "#3b9dff",
1218
+ "#34c98e",
1219
+ "#a97ef0",
1220
+ "#f5b84d"
1221
+ ];
1222
+ /** Merge a theme option (`"light" | "dark" | partial tokens`) into full {@link ThemeTokens}.
1223
+ * @category Theming
1224
+ */
1225
+ function resolveTheme(opt, accent) {
1226
+ const base = opt === "dark" ? {
1227
+ ...darkTheme,
1228
+ series: [...categoricalDark]
1229
+ } : opt === "light" || opt === void 0 ? {
1230
+ ...lightTheme,
1231
+ series: [...categoricalLight]
1232
+ } : {
1233
+ ...lightTheme,
1234
+ series: [...categoricalLight],
1235
+ ...opt
1236
+ };
1237
+ if (accent) base.series[0] = accent;
1238
+ return base;
1239
+ }
1240
+ function seriesColor(theme, index, explicit) {
1241
+ if (explicit) return explicit;
1242
+ return theme.series[index % theme.series.length];
1243
+ }
1244
+ //#endregion
1245
+ //#region src/charts/funnel.ts
1246
+ const ROW_GAP = 2;
1247
+ const LABEL_MIN_W = 72;
1248
+ /**
1249
+ * Funnel: one stage per `categories` entry using the first series' values.
1250
+ * Each stage is a trapezoid whose bottom edge matches the next stage's top
1251
+ * width — the classic funnel silhouette.
1252
+ */
1253
+ function stages(r, d, t) {
1254
+ const s = d.series[0];
1255
+ if (!s) return [];
1256
+ const n = d.categories.length;
1257
+ let max = 0;
1258
+ for (let i = 0; i < s.values.length; i++) {
1259
+ const v = s.values[i];
1260
+ if (v > max) max = v;
1261
+ }
1262
+ max ||= 1;
1263
+ const rowH = r.plot.h / Math.max(1, n);
1264
+ const out = [];
1265
+ for (let i = 0; i < n; i++) {
1266
+ const cur = s.values[i] ?? 0;
1267
+ const prev = s.prevValues?.[i] ?? 0;
1268
+ const v = t < 1 ? prev + (cur - prev) * t : cur;
1269
+ const cur1 = s.values[i + 1] ?? cur;
1270
+ const prev1 = s.prevValues?.[i + 1] ?? 0;
1271
+ const v1 = i + 1 < n ? t < 1 ? prev1 + (cur1 - prev1) * t : cur1 : v;
1272
+ out.push({
1273
+ cat: d.categories[i] ?? "",
1274
+ value: cur,
1275
+ top: r.plot.y + rowH * i,
1276
+ bot: r.plot.y + rowH * (i + 1) - ROW_GAP,
1277
+ w0: Math.max(0, v) / max * r.plot.w,
1278
+ w1: Math.max(0, v1) / max * r.plot.w,
1279
+ color: seriesColor(r.theme, i, void 0)
1280
+ });
1281
+ }
1282
+ return out;
1283
+ }
1284
+ function stageRect(r, st) {
1285
+ const cx = r.plot.x + r.plot.w / 2;
1286
+ const w = Math.max(st.w0, st.w1);
1287
+ return {
1288
+ x: cx - w / 2,
1289
+ y: st.top,
1290
+ w,
1291
+ h: st.bot - st.top
1292
+ };
1293
+ }
1294
+ function renderFunnel(r) {
1295
+ if (r.data.kind !== "category") return;
1296
+ const d = r.data;
1297
+ const ctx = r.ctx;
1298
+ const cx = r.plot.x + r.plot.w / 2;
1299
+ for (const st of stages(r, d, r.t)) {
1300
+ ctx.beginPath();
1301
+ ctx.moveTo(cx - st.w0 / 2, st.top);
1302
+ ctx.lineTo(cx + st.w0 / 2, st.top);
1303
+ ctx.lineTo(cx + st.w1 / 2, st.bot);
1304
+ ctx.lineTo(cx - st.w1 / 2, st.bot);
1305
+ ctx.closePath();
1306
+ ctx.fillStyle = st.color;
1307
+ ctx.fill();
1308
+ const label = `${st.cat} · ${formatSI(st.value)}`;
1309
+ ctx.font = `11px ${r.theme.fontFamily}`;
1310
+ ctx.textBaseline = "middle";
1311
+ if (Math.min(st.w0, st.w1) >= LABEL_MIN_W) {
1312
+ ctx.fillStyle = "#ffffff";
1313
+ ctx.textAlign = "center";
1314
+ ctx.fillText(label, cx, (st.top + st.bot) / 2);
1315
+ } else {
1316
+ ctx.fillStyle = r.theme.textMuted;
1317
+ ctx.textAlign = "left";
1318
+ ctx.fillText(label, cx + Math.max(st.w0, st.w1) / 2 + 8, (st.top + st.bot) / 2);
1319
+ }
1320
+ }
1321
+ }
1322
+ function hitFunnel(r, px, py) {
1323
+ if (r.data.kind !== "category") return null;
1324
+ const d = r.data;
1325
+ const s = d.series[0];
1326
+ if (!s) return null;
1327
+ const cx = r.plot.x + r.plot.w / 2;
1328
+ for (const st of stages(r, d, 1)) {
1329
+ if (py < st.top || py > st.bot) continue;
1330
+ const frac = st.bot - st.top > 0 ? (py - st.top) / (st.bot - st.top) : 0;
1331
+ const w = st.w0 + (st.w1 - st.w0) * frac;
1332
+ if (Math.abs(px - cx) > w / 2 + 4) continue;
1333
+ return {
1334
+ kind: "bar",
1335
+ category: st.cat,
1336
+ seriesIndex: 0,
1337
+ label: s.label,
1338
+ color: st.color,
1339
+ value: st.value,
1340
+ px: cx,
1341
+ py,
1342
+ rect: stageRect(r, st)
1343
+ };
1344
+ }
1345
+ return null;
1346
+ }
1347
+ //#endregion
1348
+ //#region src/charts/radar.ts
1349
+ const RINGS = 4;
1350
+ const LABEL_PAD = 16;
1351
+ const MIN_AXES = 3;
1352
+ const START = -Math.PI / 2;
1353
+ function radarGeo(r, d) {
1354
+ const cx = r.plot.x + r.plot.w / 2;
1355
+ const cy = r.plot.y + r.plot.h / 2;
1356
+ const radius = Math.max(1, Math.min(r.plot.w, r.plot.h) / 2 - LABEL_PAD);
1357
+ let max = 0;
1358
+ for (const s of d.series) for (let i = 0; i < s.values.length; i++) {
1359
+ const v = s.values[i];
1360
+ if (v > max) max = v;
1361
+ }
1362
+ return {
1363
+ cx,
1364
+ cy,
1365
+ radius,
1366
+ max: max || 1
1367
+ };
1368
+ }
1369
+ const angleOf = (i, n) => START + i / n * Math.PI * 2;
1370
+ function renderRadar(r) {
1371
+ if (r.data.kind !== "category") return;
1372
+ const d = r.data;
1373
+ const n = d.categories.length;
1374
+ if (n < MIN_AXES) return;
1375
+ const ctx = r.ctx;
1376
+ const { cx, cy, radius, max } = radarGeo(r, d);
1377
+ const animate = r.t < 1;
1378
+ ctx.strokeStyle = r.theme.grid;
1379
+ ctx.lineWidth = 1;
1380
+ for (let ring = 1; ring <= RINGS; ring++) {
1381
+ const rr = radius * ring / RINGS;
1382
+ ctx.beginPath();
1383
+ for (let i = 0; i <= n; i++) {
1384
+ const a = angleOf(i % n, n);
1385
+ const x = cx + Math.cos(a) * rr;
1386
+ const y = cy + Math.sin(a) * rr;
1387
+ if (i === 0) ctx.moveTo(x, y);
1388
+ else ctx.lineTo(x, y);
1389
+ }
1390
+ ctx.stroke();
1391
+ }
1392
+ ctx.beginPath();
1393
+ for (let i = 0; i < n; i++) {
1394
+ const a = angleOf(i, n);
1395
+ ctx.moveTo(cx, cy);
1396
+ ctx.lineTo(cx + Math.cos(a) * radius, cy + Math.sin(a) * radius);
1397
+ }
1398
+ ctx.stroke();
1399
+ for (const s of d.series) {
1400
+ ctx.beginPath();
1401
+ for (let i = 0; i < n; i++) {
1402
+ const cur = s.values[i] ?? 0;
1403
+ const prev = s.prevValues?.[i] ?? 0;
1404
+ const v = animate ? prev + (cur - prev) * r.t : cur;
1405
+ const rr = Math.max(0, v) / max * radius;
1406
+ const a = angleOf(i, n);
1407
+ const x = cx + Math.cos(a) * rr;
1408
+ const y = cy + Math.sin(a) * rr;
1409
+ if (i === 0) ctx.moveTo(x, y);
1410
+ else ctx.lineTo(x, y);
1411
+ }
1412
+ ctx.closePath();
1413
+ ctx.fillStyle = withAlpha(s.color, .16);
1414
+ ctx.fill();
1415
+ ctx.strokeStyle = s.color;
1416
+ ctx.lineWidth = 2;
1417
+ ctx.lineJoin = "round";
1418
+ ctx.stroke();
1419
+ ctx.fillStyle = s.color;
1420
+ ctx.beginPath();
1421
+ for (let i = 0; i < n; i++) {
1422
+ const cur = s.values[i] ?? 0;
1423
+ const prev = s.prevValues?.[i] ?? 0;
1424
+ const v = animate ? prev + (cur - prev) * r.t : cur;
1425
+ const rr = Math.max(0, v) / max * radius;
1426
+ const a = angleOf(i, n);
1427
+ const x = cx + Math.cos(a) * rr;
1428
+ const y = cy + Math.sin(a) * rr;
1429
+ ctx.moveTo(x + 2.5, y);
1430
+ ctx.arc(x, y, 2.5, 0, Math.PI * 2);
1431
+ }
1432
+ ctx.fill();
1433
+ }
1434
+ ctx.fillStyle = r.theme.textMuted;
1435
+ ctx.font = `11px ${r.theme.fontFamily}`;
1436
+ for (let i = 0; i < n; i++) {
1437
+ const a = angleOf(i, n);
1438
+ const lx = cx + Math.cos(a) * (radius + LABEL_PAD - 4);
1439
+ const ly = cy + Math.sin(a) * (radius + LABEL_PAD - 4);
1440
+ ctx.textAlign = Math.cos(a) > .3 ? "left" : Math.cos(a) < -.3 ? "right" : "center";
1441
+ ctx.textBaseline = Math.sin(a) > .3 ? "top" : Math.sin(a) < -.3 ? "bottom" : "middle";
1442
+ ctx.fillText(d.categories[i] ?? "", lx, ly);
1443
+ }
1444
+ }
1445
+ function hitRadar(r, px, py) {
1446
+ if (r.data.kind !== "category") return null;
1447
+ const d = r.data;
1448
+ const n = d.categories.length;
1449
+ if (n < MIN_AXES) return null;
1450
+ const { cx, cy, radius } = radarGeo(r, d);
1451
+ const dx = px - cx;
1452
+ const dy = py - cy;
1453
+ if (Math.hypot(dx, dy) > radius + LABEL_PAD) return null;
1454
+ let rel = Math.atan2(dy, dx) - START;
1455
+ while (rel < 0) rel += Math.PI * 2;
1456
+ while (rel >= Math.PI * 2) rel -= Math.PI * 2;
1457
+ const i = Math.round(rel / (Math.PI * 2) * n) % n;
1458
+ return {
1459
+ kind: "point",
1460
+ index: i,
1461
+ x: i,
1462
+ px,
1463
+ py,
1464
+ values: d.series.map((s) => ({
1465
+ label: s.label,
1466
+ color: s.color,
1467
+ y: s.values[i] ?? 0
1468
+ })),
1469
+ title: d.categories[i] ?? ""
1470
+ };
1471
+ }
1472
+ /** Hover accent: highlighted spoke + vertex dots (replaces the xy crosshair). */
1473
+ function drawRadarHover(r, info) {
1474
+ if (r.data.kind !== "category" || info.kind !== "point") return;
1475
+ const d = r.data;
1476
+ const n = d.categories.length;
1477
+ if (info.index < 0 || info.index >= n) return;
1478
+ const o = r.backend.overlay;
1479
+ const { cx, cy, radius, max } = radarGeo(r, d);
1480
+ const a = angleOf(info.index, n);
1481
+ o.strokeStyle = r.theme.crosshair;
1482
+ o.lineWidth = 1;
1483
+ o.beginPath();
1484
+ o.moveTo(cx, cy);
1485
+ o.lineTo(cx + Math.cos(a) * radius, cy + Math.sin(a) * radius);
1486
+ o.stroke();
1487
+ for (const s of d.series) {
1488
+ const v = s.values[info.index] ?? 0;
1489
+ const rr = Math.max(0, v) / max * radius;
1490
+ o.fillStyle = s.color;
1491
+ o.strokeStyle = r.theme.bg;
1492
+ o.lineWidth = 2;
1493
+ o.beginPath();
1494
+ o.arc(cx + Math.cos(a) * rr, cy + Math.sin(a) * rr, 4, 0, Math.PI * 2);
1495
+ o.fill();
1496
+ o.stroke();
1497
+ }
1498
+ }
1499
+ //#endregion
1500
+ //#region src/charts/rangearea.ts
1501
+ /** pooled scratch for the reversed lower edge — consumed within the call */
1502
+ let revScratch = /* @__PURE__ */ new Float64Array(0);
1503
+ /**
1504
+ * Range-area chart: a filled band between each series' `y` (lower edge) and
1505
+ * `y2` (upper edge) — confidence intervals, min/max envelopes. Series
1506
+ * without `y2` are skipped.
1507
+ */
1508
+ function renderRangeArea(r) {
1509
+ if (!isXY(r.data)) return;
1510
+ const d = r.data;
1511
+ const ctx = r.ctx;
1512
+ const curve = r.opts.curve ?? "linear";
1513
+ ctx.save();
1514
+ ctx.beginPath();
1515
+ ctx.rect(r.plot.x, r.plot.y, r.plot.w, r.plot.h);
1516
+ ctx.clip();
1517
+ for (const s of d.series) {
1518
+ if (!s.y2) continue;
1519
+ const hi = seriesPoints(r, {
1520
+ y: s.y2,
1521
+ prevY: s.prevY2
1522
+ });
1523
+ const lo = seriesPoints(r, s);
1524
+ const n = Math.min(hi.length, lo.length) / 2;
1525
+ if (n === 0) continue;
1526
+ if (revScratch.length < n * 2) revScratch = new Float64Array(n * 2);
1527
+ for (let i = 0; i < n; i++) {
1528
+ revScratch[2 * i] = lo[2 * (n - 1 - i)];
1529
+ revScratch[2 * i + 1] = lo[2 * (n - 1 - i) + 1];
1530
+ }
1531
+ const band = new Path2D();
1532
+ traceCurve(band, hi, n, curve);
1533
+ traceCurve(band, revScratch, n, curve, true);
1534
+ band.closePath();
1535
+ ctx.fillStyle = withAlpha(s.color, .2);
1536
+ ctx.fill(band);
1537
+ ctx.strokeStyle = s.color;
1538
+ ctx.lineWidth = 1.5;
1539
+ ctx.lineJoin = "round";
1540
+ ctx.lineCap = "round";
1541
+ const edges = new Path2D();
1542
+ traceCurve(edges, hi, n, curve);
1543
+ traceCurve(edges, lo, n, curve);
1544
+ ctx.stroke(edges);
1545
+ }
1546
+ ctx.restore();
1547
+ }
1548
+ //#endregion
1100
1549
  //#region src/charts/scatter.ts
1101
1550
  function renderScatter(r) {
1102
1551
  if (!isXY(r.data)) return;
@@ -1380,6 +1829,11 @@ function padDomain(min, max, includeZero = false) {
1380
1829
  }
1381
1830
  return [lo, hi];
1382
1831
  }
1832
+ /** @category Utilities
1833
+ * Pin domain ends — each defined bound overrides the fitted value. */
1834
+ function boundDomain(dom, min, max) {
1835
+ return [min ?? dom[0], max ?? dom[1]];
1836
+ }
1383
1837
  //#endregion
1384
1838
  //#region src/core/scheduler.ts
1385
1839
  const raf = typeof requestAnimationFrame === "function" ? (cb) => requestAnimationFrame(cb) : (cb) => setTimeout(() => cb(Date.now()), 16);
@@ -1417,85 +1871,6 @@ var Scheduler = class {
1417
1871
  const sharedScheduler = new Scheduler();
1418
1872
  const easeOutCubic = (t) => 1 - (1 - t) ** 3;
1419
1873
  //#endregion
1420
- //#region src/core/theme.ts
1421
- const FONT = "Inter, system-ui, -apple-system, \"Segoe UI\", sans-serif";
1422
- /** Built-in light theme.
1423
- * @category Theming
1424
- */
1425
- const lightTheme = {
1426
- bg: "#ffffff",
1427
- text: "#1f1f1f",
1428
- textMuted: "#8a8a8a",
1429
- grid: "rgba(0,0,0,0.07)",
1430
- axisBaseline: "rgba(0,0,0,0.15)",
1431
- crosshair: "rgba(0,0,0,0.25)",
1432
- series: ["#0070f3"],
1433
- contextGray: "#b0b0b0",
1434
- up: "#0e9f6e",
1435
- down: "#e5484d",
1436
- tooltipBg: "#ffffff",
1437
- tooltipText: "#1f1f1f",
1438
- tooltipMuted: "#8a8a8a",
1439
- tooltipBorder: "rgba(0,0,0,0.08)",
1440
- tooltipShadow: "0 4px 14px rgba(0,0,0,0.08)",
1441
- fontFamily: FONT
1442
- };
1443
- /** Built-in dark theme.
1444
- * @category Theming
1445
- */
1446
- const darkTheme = {
1447
- bg: "#0a0a0a",
1448
- text: "#ededed",
1449
- textMuted: "#8a8a8a",
1450
- grid: "rgba(255,255,255,0.07)",
1451
- axisBaseline: "rgba(255,255,255,0.18)",
1452
- crosshair: "rgba(255,255,255,0.3)",
1453
- series: ["#3b9dff"],
1454
- contextGray: "#5c5c5c",
1455
- up: "#34c98e",
1456
- down: "#f2555a",
1457
- tooltipBg: "#161616",
1458
- tooltipText: "#ededed",
1459
- tooltipMuted: "#8a8a8a",
1460
- tooltipBorder: "rgba(255,255,255,0.1)",
1461
- tooltipShadow: "0 4px 14px rgba(0,0,0,0.4)",
1462
- fontFamily: FONT
1463
- };
1464
- const categoricalLight = [
1465
- "#0070f3",
1466
- "#0e9f6e",
1467
- "#8e4ec6",
1468
- "#f5a623"
1469
- ];
1470
- const categoricalDark = [
1471
- "#3b9dff",
1472
- "#34c98e",
1473
- "#a97ef0",
1474
- "#f5b84d"
1475
- ];
1476
- /** Merge a theme option (`"light" | "dark" | partial tokens`) into full {@link ThemeTokens}.
1477
- * @category Theming
1478
- */
1479
- function resolveTheme(opt, accent) {
1480
- const base = opt === "dark" ? {
1481
- ...darkTheme,
1482
- series: [...categoricalDark]
1483
- } : opt === "light" || opt === void 0 ? {
1484
- ...lightTheme,
1485
- series: [...categoricalLight]
1486
- } : {
1487
- ...lightTheme,
1488
- series: [...categoricalLight],
1489
- ...opt
1490
- };
1491
- if (accent) base.series[0] = accent;
1492
- return base;
1493
- }
1494
- function seriesColor(theme, index, explicit) {
1495
- if (explicit) return explicit;
1496
- return theme.series[index % theme.series.length];
1497
- }
1498
- //#endregion
1499
1874
  //#region src/core/ticks.ts
1500
1875
  const STEPS = [
1501
1876
  1,
@@ -1635,6 +2010,36 @@ const MODULES = {
1635
2010
  axes: true,
1636
2011
  defaultTooltip: true
1637
2012
  },
2013
+ bubble: {
2014
+ render: renderBubble,
2015
+ hit: (r, px) => hitXY(r, px),
2016
+ axes: true,
2017
+ defaultTooltip: true
2018
+ },
2019
+ rangearea: {
2020
+ render: renderRangeArea,
2021
+ hit: (r, px) => hitXY(r, px),
2022
+ axes: true,
2023
+ defaultTooltip: true
2024
+ },
2025
+ pie: {
2026
+ render: renderDonut,
2027
+ hit: hitDonut,
2028
+ axes: false,
2029
+ defaultTooltip: true
2030
+ },
2031
+ radar: {
2032
+ render: renderRadar,
2033
+ hit: hitRadar,
2034
+ axes: false,
2035
+ defaultTooltip: true
2036
+ },
2037
+ funnel: {
2038
+ render: renderFunnel,
2039
+ hit: hitFunnel,
2040
+ axes: false,
2041
+ defaultTooltip: true
2042
+ },
1638
2043
  bar: {
1639
2044
  render: renderBar,
1640
2045
  hit: hitBar,
@@ -1695,6 +2100,8 @@ const GPU_KIND = {
1695
2100
  };
1696
2101
  /** points retained beyond the window's left edge so they slide off-screen instead of vanishing mid-plot */
1697
2102
  const EDGE_KEEP = 3;
2103
+ /** default point count at which `renderer: "auto"` engages the GPU path */
2104
+ const GPU_THRESHOLD = 1e5;
1698
2105
  /** Chart instance. Create via {@link createChart}.
1699
2106
  * @category Charts
1700
2107
  */
@@ -1720,6 +2127,8 @@ var Chart = class {
1720
2127
  this.destroyed = false;
1721
2128
  this.pendingView = false;
1722
2129
  this.gpu = null;
2130
+ this.gpuInit = false;
2131
+ this.dataRev = 0;
1723
2132
  this.lastAppendAt = 0;
1724
2133
  this.estInterval = 400;
1725
2134
  this.onScroll = () => {
@@ -1782,7 +2191,35 @@ var Chart = class {
1782
2191
  this.reduced = typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
1783
2192
  if (options.tooltip ?? this.mod.defaultTooltip) this.tooltip = new Tooltip(el, this.theme);
1784
2193
  this.data = this.normalize(options.data, null);
1785
- if (GPU_KIND[options.type] && (options.renderer === "webgpu" || options.renderer === "auto")) import("./series-BdprL87H.mjs").then(({ GpuSeries }) => GpuSeries.create(options.gpu).then((g) => {
2194
+ this.maybeInitGpu();
2195
+ this.bindEvents();
2196
+ const dom = this.layout();
2197
+ this.view = {
2198
+ x: [...dom.x],
2199
+ y: [...dom.y]
2200
+ };
2201
+ this.viewTo = {
2202
+ x: [...dom.x],
2203
+ y: [...dom.y]
2204
+ };
2205
+ this.startAnimation(dom);
2206
+ }
2207
+ /**
2208
+ * Lazily acquire the WebGPU backend. `"webgpu"` attaches unconditionally;
2209
+ * `"auto"` waits until the xy point count crosses `gpuThreshold` — small
2210
+ * charts never pay adapter/device acquisition.
2211
+ */
2212
+ maybeInitGpu() {
2213
+ if (this.gpu || this.gpuInit || this.destroyed) return;
2214
+ if (!GPU_KIND[this.options.type]) return;
2215
+ const r = this.options.renderer ?? "auto";
2216
+ if (r !== "auto" && r !== "webgpu") return;
2217
+ if (r === "auto") {
2218
+ const d = this.data;
2219
+ if ((d.kind === "xy" ? d.sortedX.length * Math.max(1, d.series.length) : 0) < (this.options.gpuThreshold ?? GPU_THRESHOLD)) return;
2220
+ }
2221
+ this.gpuInit = true;
2222
+ import("./series-wyqYpkXm.mjs").then(({ GpuSeries }) => GpuSeries.create(this.options.gpu).then((g) => {
1786
2223
  if (!g || this.destroyed) {
1787
2224
  g?.destroy();
1788
2225
  return;
@@ -1795,17 +2232,6 @@ var Chart = class {
1795
2232
  return false;
1796
2233
  });
1797
2234
  }));
1798
- this.bindEvents();
1799
- const dom = this.layout();
1800
- this.view = {
1801
- x: [...dom.x],
1802
- y: [...dom.y]
1803
- };
1804
- this.viewTo = {
1805
- x: [...dom.x],
1806
- y: [...dom.y]
1807
- };
1808
- this.startAnimation(dom);
1809
2235
  }
1810
2236
  normalize(data, prev) {
1811
2237
  const t = this.options.type;
@@ -1877,7 +2303,7 @@ var Chart = class {
1877
2303
  bars
1878
2304
  };
1879
2305
  }
1880
- if (t === "donut") {
2306
+ if (t === "donut" || t === "pie") {
1881
2307
  const slices = data.slices.map((s, i) => ({
1882
2308
  label: s.label,
1883
2309
  value: s.value,
@@ -1889,7 +2315,7 @@ var Chart = class {
1889
2315
  total: slices.reduce((a, s) => a + Math.max(0, s.value), 0)
1890
2316
  };
1891
2317
  }
1892
- if (t === "bar" || t === "combo") {
2318
+ if (t === "bar" || t === "combo" || t === "radar" || t === "funnel") {
1893
2319
  const d = data;
1894
2320
  const prevSeries = prev?.kind === "category" ? prev.series : [];
1895
2321
  return {
@@ -1921,15 +2347,22 @@ var Chart = class {
1921
2347
  kind: "xy",
1922
2348
  sortedX,
1923
2349
  series: d.series.map((s, i) => {
1924
- const y = new Float64Array(n);
1925
- const src = s.y;
1926
- if (idx) for (let k = 0; k < n; k++) y[k] = src[idx[k]];
1927
- else for (let k = 0; k < n; k++) y[k] = src[k];
2350
+ const chan = (src) => {
2351
+ if (!src) return null;
2352
+ const out = new Float64Array(n);
2353
+ if (idx) for (let k = 0; k < n; k++) out[k] = src[idx[k]];
2354
+ else for (let k = 0; k < n; k++) out[k] = src[k];
2355
+ return out;
2356
+ };
2357
+ const y = chan(s.y) ?? new Float64Array(n);
1928
2358
  return {
1929
2359
  label: s.label ?? `Series ${i + 1}`,
1930
2360
  color: seriesColor(this.theme, i, s.color),
1931
2361
  y,
1932
2362
  prevY: prevSeries[i]?.y ?? null,
2363
+ r: chan(s.r),
2364
+ y2: chan(s.y2),
2365
+ prevY2: prevSeries[i]?.y2 ?? null,
1933
2366
  pathCache: null
1934
2367
  };
1935
2368
  })
@@ -1993,6 +2426,12 @@ var Chart = class {
1993
2426
  if (v < min) min = v;
1994
2427
  if (v > max) max = v;
1995
2428
  }
2429
+ const y2 = s.y2;
2430
+ if (y2) for (let i = Math.min(x0, y2.length); i < y2.length; i++) {
2431
+ const v = y2[i];
2432
+ if (v < min) min = v;
2433
+ if (v > max) max = v;
2434
+ }
1996
2435
  }
1997
2436
  if (min === Infinity) {
1998
2437
  min = 0;
@@ -2001,7 +2440,7 @@ var Chart = class {
2001
2440
  const includeZero = this.options.type === "area" || this.options.type === "sparkline";
2002
2441
  return {
2003
2442
  x,
2004
- y: padDomain(min, max, includeZero)
2443
+ y: this.yDomain(padDomain(min, max, includeZero))
2005
2444
  };
2006
2445
  }
2007
2446
  if (d.kind === "category") {
@@ -2037,7 +2476,7 @@ var Chart = class {
2037
2476
  }
2038
2477
  return {
2039
2478
  x: [0, d.categories.length],
2040
- y: padDomain(min, max, true)
2479
+ y: this.yDomain(padDomain(min, max, true))
2041
2480
  };
2042
2481
  }
2043
2482
  if (d.kind === "ohlc") {
@@ -2056,7 +2495,7 @@ var Chart = class {
2056
2495
  }
2057
2496
  return {
2058
2497
  x,
2059
- y: padDomain(min, max)
2498
+ y: this.yDomain(padDomain(min, max))
2060
2499
  };
2061
2500
  }
2062
2501
  if (d.kind === "waterfall") {
@@ -2072,14 +2511,17 @@ var Chart = class {
2072
2511
  }
2073
2512
  return {
2074
2513
  x: [0, d.bars.length],
2075
- y: padDomain(min, max, true)
2514
+ y: this.yDomain(padDomain(min, max, true))
2076
2515
  };
2077
2516
  }
2078
2517
  return {
2079
2518
  x: [0, 1],
2080
- y: [0, 1]
2519
+ y: this.yDomain([0, 1])
2081
2520
  };
2082
2521
  }
2522
+ yDomain(y) {
2523
+ return boundDomain(y, this.options.yMin, this.options.yMax);
2524
+ }
2083
2525
  lerpView(eased) {
2084
2526
  const f = this.viewFrom;
2085
2527
  const to = this.viewTo;
@@ -2181,30 +2623,35 @@ var Chart = class {
2181
2623
  else this.mod.render(this.renderContext(t, scales));
2182
2624
  if (this.hover) this.drawHover(this.hover, scales.yScale);
2183
2625
  }
2184
- /** series rasterization on the GPU; grid/labels/hover stay on Canvas2D */
2626
+ /**
2627
+ * Series rasterization on the GPU; grid/labels/hover stay on Canvas2D.
2628
+ * Raw data lives in GPU buffers — the per-frame cost is uniform writes +
2629
+ * instanced draws, with compute decimation above the density threshold.
2630
+ */
2185
2631
  gpuRender(t, scales) {
2186
2632
  const g = this.gpu;
2187
2633
  const d = this.data;
2188
2634
  const kind = GPU_KIND[this.options.type];
2189
- if (!g || !kind || d.kind !== "xy") {
2635
+ if (!g || g.unavailable || !kind || d.kind !== "xy") {
2190
2636
  this.mod.render(this.renderContext(t, scales));
2191
2637
  return;
2192
2638
  }
2193
- const r = this.renderContext(t, scales);
2639
+ g.syncData(d, this.dataRev);
2640
+ const view = {
2641
+ x: scales.xScale.domain,
2642
+ y: scales.yScale.domain
2643
+ };
2194
2644
  const flow = this.options.flow ? FLOW_RIPPLE : 0;
2195
- g.begin(this.backend.plot);
2196
- for (const s of d.series) {
2645
+ g.begin(this.backend.plot, t, flow);
2646
+ for (let i = 0; i < d.series.length; i++) {
2647
+ const s = d.series[i];
2648
+ if (!s) continue;
2197
2649
  if (kind === "points") {
2198
- const pts = mapSeriesPx(d.sortedX, s.y, s.prevY, t, scales.yScale.domain[0], scales.xScale, scales.yScale, flow);
2199
- g.points(pts, s.color, 2.5, .65);
2650
+ g.points(i, s.color, 2.5, .65, view);
2200
2651
  continue;
2201
2652
  }
2202
- const pts = seriesPoints(r, s);
2203
- if (kind === "area") {
2204
- const base = scales.yScale(Math.max(0, scales.yScale.domain[0]));
2205
- g.area(pts, s.color, base, this.backend.plot);
2206
- }
2207
- g.line(pts, s.color, 2);
2653
+ if (kind === "area") g.area(i, s.color, Math.max(0, scales.yScale.domain[0]), view);
2654
+ g.line(i, s.color, 2, view);
2208
2655
  }
2209
2656
  g.end();
2210
2657
  }
@@ -2212,6 +2659,10 @@ var Chart = class {
2212
2659
  const o = this.backend.overlay;
2213
2660
  const plot = this.backend.plot;
2214
2661
  this.backend.beginOverlay();
2662
+ if (info.kind === "point" && this.options.type === "radar") {
2663
+ drawRadarHover(this.renderContext(1), info);
2664
+ return;
2665
+ }
2215
2666
  if (info.kind === "point" || info.kind === "candle") {
2216
2667
  o.strokeStyle = this.theme.crosshair;
2217
2668
  o.lineWidth = 1;
@@ -2242,7 +2693,7 @@ var Chart = class {
2242
2693
  const cx = plot.x + plot.w / 2;
2243
2694
  const cy = plot.y + plot.h / 2;
2244
2695
  const rOuter = Math.min(plot.w, plot.h) / 2;
2245
- const rInner = rOuter * .65;
2696
+ const rInner = this.options.type === "pie" ? 0 : rOuter * .65;
2246
2697
  let a = -Math.PI / 2;
2247
2698
  for (let i = 0; i < d.slices.length; i++) {
2248
2699
  const s = d.slices[i];
@@ -2339,6 +2790,8 @@ var Chart = class {
2339
2790
  setData(data) {
2340
2791
  const prev = this.data;
2341
2792
  this.data = this.normalize(data, prev);
2793
+ this.dataRev++;
2794
+ this.maybeInitGpu();
2342
2795
  const dom = this.layout();
2343
2796
  this.startAnimation(dom);
2344
2797
  }
@@ -2349,7 +2802,7 @@ var Chart = class {
2349
2802
  */
2350
2803
  append(x, y) {
2351
2804
  const d = this.data;
2352
- if (d.kind !== "xy") throw new Error("[resolvcharts] append() requires xy data (line/area/scatter/sparkline)");
2805
+ if (d.kind !== "xy") throw new Error("[resolvcharts] append() requires xy data (line/area/scatter/bubble/rangearea/sparkline)");
2353
2806
  const nowT = typeof performance === "undefined" ? Date.now() : performance.now();
2354
2807
  if (this.lastAppendAt > 0) {
2355
2808
  const dt = nowT - this.lastAppendAt;
@@ -2391,11 +2844,30 @@ var Chart = class {
2391
2844
  prevY.set(s.y.subarray(drop));
2392
2845
  prevY[m - 1] = m > 1 ? yNew[m - 2] : yNew[m - 1];
2393
2846
  }
2847
+ let rNew = null;
2848
+ if (s.r) {
2849
+ rNew = new Float64Array(m);
2850
+ rNew.set(s.r.subarray(drop));
2851
+ rNew[m - 1] = s.r[n - 1];
2852
+ }
2853
+ let y2New = null;
2854
+ let prevY2 = null;
2855
+ if (s.y2) {
2856
+ y2New = new Float64Array(m);
2857
+ y2New.set(s.y2.subarray(drop));
2858
+ y2New[m - 1] = s.y2[n - 1];
2859
+ prevY2 = new Float64Array(m);
2860
+ prevY2.set(s.y2.subarray(drop));
2861
+ prevY2[m - 1] = m > 1 ? y2New[m - 2] : y2New[m - 1];
2862
+ }
2394
2863
  return {
2395
2864
  label: s.label,
2396
2865
  color: s.color,
2397
2866
  y: yNew,
2398
2867
  prevY,
2868
+ r: rNew,
2869
+ y2: y2New,
2870
+ prevY2,
2399
2871
  pathCache: null
2400
2872
  };
2401
2873
  });
@@ -2404,8 +2876,14 @@ var Chart = class {
2404
2876
  sortedX,
2405
2877
  series
2406
2878
  };
2407
- if (m > 1 && x < sortedX[m - 2]) resortXY(nd);
2879
+ const resort = m > 1 && x < sortedX[m - 2];
2880
+ if (resort) resortXY(nd);
2408
2881
  this.data = nd;
2882
+ if (resort) this.dataRev++;
2883
+ else if (this.gpu && !this.gpu.unavailable) {
2884
+ 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++;
2885
+ }
2886
+ this.maybeInitGpu();
2409
2887
  this.pendingView = true;
2410
2888
  sharedScheduler.add(this.frame);
2411
2889
  }
@@ -2451,13 +2929,22 @@ function resortXY(d) {
2451
2929
  for (const s of d.series) {
2452
2930
  const ny = new Float64Array(n);
2453
2931
  const np = s.prevY ? new Float64Array(n) : null;
2932
+ const nr = s.r ? new Float64Array(n) : null;
2933
+ const ny2 = s.y2 ? new Float64Array(n) : null;
2934
+ const np2 = s.prevY2 ? new Float64Array(n) : null;
2454
2935
  for (let i = 0; i < n; i++) {
2455
2936
  const k = idx[i];
2456
2937
  ny[i] = s.y[k];
2457
2938
  if (np && s.prevY) np[i] = s.prevY[k];
2939
+ if (nr && s.r) nr[i] = s.r[k];
2940
+ if (ny2 && s.y2) ny2[i] = s.y2[k];
2941
+ if (np2 && s.prevY2) np2[i] = s.prevY2[k];
2458
2942
  }
2459
2943
  s.y = ny;
2460
2944
  s.prevY = np;
2945
+ s.r = nr;
2946
+ s.y2 = ny2;
2947
+ s.prevY2 = np2;
2461
2948
  }
2462
2949
  }
2463
2950
  /**
@@ -2480,6 +2967,6 @@ function createChart(el, options) {
2480
2967
  return new Chart(el, options);
2481
2968
  }
2482
2969
  //#endregion
2483
- export { Chart, computeTicks, createChart, darkTheme, decimateM4, extent, formatNumber, formatSI, lightTheme, linearScale, nearestIndex, padDomain, resolveTheme };
2970
+ export { Chart, boundDomain, computeTicks, createChart, darkTheme, decimateM4, extent, formatNumber, formatSI, lightTheme, linearScale, nearestIndex, padDomain, resolveTheme };
2484
2971
 
2485
2972
  //# sourceMappingURL=index.mjs.map