@photonviz/core 0.4.0 → 0.5.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.js CHANGED
@@ -1450,6 +1450,62 @@ var ANCHORS = {
1450
1450
  [0.99, 0.78, 0.15],
1451
1451
  [0.94, 0.98, 0.13]
1452
1452
  ],
1453
+ inferno: [
1454
+ [1e-3, 0, 0.014],
1455
+ [0.113, 0.045, 0.226],
1456
+ [0.267, 0.051, 0.327],
1457
+ [0.417, 0.09, 0.328],
1458
+ [0.578, 0.148, 0.284],
1459
+ [0.735, 0.216, 0.212],
1460
+ [0.865, 0.317, 0.126],
1461
+ [0.951, 0.462, 0.043],
1462
+ [0.988, 0.645, 0.04],
1463
+ [0.964, 0.843, 0.273],
1464
+ [0.988, 0.998, 0.645]
1465
+ ],
1466
+ magma: [
1467
+ [1e-3, 0, 0.014],
1468
+ [0.107, 0.047, 0.222],
1469
+ [0.255, 0.059, 0.367],
1470
+ [0.4, 0.096, 0.451],
1471
+ [0.55, 0.145, 0.47],
1472
+ [0.702, 0.196, 0.446],
1473
+ [0.847, 0.267, 0.38],
1474
+ [0.945, 0.396, 0.331],
1475
+ [0.986, 0.559, 0.415],
1476
+ [0.997, 0.727, 0.545],
1477
+ [0.987, 0.991, 0.749]
1478
+ ],
1479
+ cividis: [
1480
+ [0, 0.135, 0.305],
1481
+ [0, 0.204, 0.396],
1482
+ [0.086, 0.273, 0.432],
1483
+ [0.243, 0.339, 0.428],
1484
+ [0.353, 0.406, 0.437],
1485
+ [0.447, 0.474, 0.458],
1486
+ [0.542, 0.545, 0.469],
1487
+ [0.647, 0.62, 0.446],
1488
+ [0.76, 0.699, 0.399],
1489
+ [0.879, 0.783, 0.32],
1490
+ [0.995, 0.875, 0.203]
1491
+ ],
1492
+ turbo: [
1493
+ [0.19, 0.072, 0.232],
1494
+ [0.276, 0.394, 0.858],
1495
+ [0.144, 0.678, 0.933],
1496
+ [0.07, 0.886, 0.706],
1497
+ [0.336, 0.982, 0.404],
1498
+ [0.694, 0.994, 0.184],
1499
+ [0.917, 0.87, 0.196],
1500
+ [0.995, 0.646, 0.203],
1501
+ [0.958, 0.372, 0.106],
1502
+ [0.812, 0.166, 0.024],
1503
+ [0.48, 0.016, 0.011]
1504
+ ],
1505
+ grayscale: [
1506
+ [0.05, 0.05, 0.05],
1507
+ [0.95, 0.95, 0.95]
1508
+ ],
1453
1509
  coolwarm: [
1454
1510
  [0.23, 0.3, 0.75],
1455
1511
  [0.55, 0.69, 0.98],
@@ -1457,15 +1513,84 @@ var ANCHORS = {
1457
1513
  [0.96, 0.6, 0.48],
1458
1514
  [0.71, 0.02, 0.15]
1459
1515
  ],
1460
- grayscale: [
1461
- [0.05, 0.05, 0.05],
1462
- [0.95, 0.95, 0.95]
1516
+ RdBu: [
1517
+ [0.404, 0, 0.121],
1518
+ [0.698, 0.094, 0.168],
1519
+ [0.839, 0.376, 0.302],
1520
+ [0.957, 0.647, 0.51],
1521
+ [0.992, 0.859, 0.78],
1522
+ [0.969, 0.969, 0.969],
1523
+ [0.82, 0.898, 0.941],
1524
+ [0.573, 0.773, 0.871],
1525
+ [0.262, 0.576, 0.765],
1526
+ [0.129, 0.4, 0.674],
1527
+ [0.019, 0.188, 0.38]
1528
+ ],
1529
+ BrBG: [
1530
+ [0.329, 0.188, 0.02],
1531
+ [0.549, 0.317, 0.039],
1532
+ [0.749, 0.506, 0.176],
1533
+ [0.875, 0.761, 0.49],
1534
+ [0.965, 0.91, 0.764],
1535
+ [0.961, 0.961, 0.961],
1536
+ [0.78, 0.918, 0.898],
1537
+ [0.502, 0.804, 0.757],
1538
+ [0.208, 0.592, 0.561],
1539
+ [4e-3, 0.4, 0.369],
1540
+ [0, 0.235, 0.188]
1541
+ ],
1542
+ spectral: [
1543
+ [0.62, 4e-3, 0.259],
1544
+ [0.835, 0.243, 0.31],
1545
+ [0.957, 0.427, 0.263],
1546
+ [0.992, 0.682, 0.38],
1547
+ [0.996, 0.878, 0.545],
1548
+ [1, 1, 0.749],
1549
+ [0.902, 0.961, 0.596],
1550
+ [0.671, 0.867, 0.643],
1551
+ [0.4, 0.761, 0.647],
1552
+ [0.196, 0.533, 0.741],
1553
+ [0.369, 0.31, 0.635]
1554
+ ],
1555
+ twilight: [
1556
+ [0.886, 0.851, 0.887],
1557
+ [0.657, 0.749, 0.869],
1558
+ [0.4, 0.596, 0.784],
1559
+ [0.243, 0.427, 0.663],
1560
+ [0.196, 0.259, 0.478],
1561
+ [0.18, 0.129, 0.239],
1562
+ [0.318, 0.114, 0.216],
1563
+ [0.518, 0.18, 0.271],
1564
+ [0.694, 0.325, 0.353],
1565
+ [0.831, 0.545, 0.518],
1566
+ [0.886, 0.851, 0.887]
1463
1567
  ]
1464
1568
  };
1569
+ var COLORMAP_KIND = {
1570
+ viridis: "sequential",
1571
+ plasma: "sequential",
1572
+ inferno: "sequential",
1573
+ magma: "sequential",
1574
+ cividis: "sequential",
1575
+ turbo: "sequential",
1576
+ grayscale: "sequential",
1577
+ coolwarm: "diverging",
1578
+ RdBu: "diverging",
1579
+ BrBG: "diverging",
1580
+ spectral: "diverging",
1581
+ twilight: "cyclic"
1582
+ };
1583
+ function colormapNames() {
1584
+ return Object.keys(ANCHORS);
1585
+ }
1586
+ function registerColormap(name, stops) {
1587
+ if (stops.length < 2) throw new Error(`Colormap "${name}" needs at least 2 stops`);
1588
+ ANCHORS[name] = stops.map(toRgb);
1589
+ lutCache.delete(name);
1590
+ }
1465
1591
  var LUT_SIZE = 256;
1466
1592
  var lutCache = /* @__PURE__ */ new Map();
1467
- function buildLUT(name) {
1468
- const anchors = ANCHORS[name];
1593
+ function buildLUT(anchors) {
1469
1594
  const last = anchors.length - 1;
1470
1595
  const lut = new Float32Array(LUT_SIZE * 3);
1471
1596
  for (let s = 0; s < LUT_SIZE; s++) {
@@ -1480,22 +1605,64 @@ function buildLUT(name) {
1480
1605
  }
1481
1606
  return lut;
1482
1607
  }
1483
- function colormapLUT(name = "viridis") {
1484
- let lut = lutCache.get(name);
1608
+ function colormapLUT(spec = "viridis") {
1609
+ const key = typeof spec === "string" ? spec : `\0${spec.map((s) => typeof s === "string" ? s : s.join(",")).join("|")}`;
1610
+ let lut = lutCache.get(key);
1485
1611
  if (!lut) {
1486
- lut = buildLUT(name);
1487
- lutCache.set(name, lut);
1612
+ lut = buildLUT(typeof spec === "string" ? ANCHORS[spec] ?? ANCHORS.viridis : spec.map(toRgb));
1613
+ lutCache.set(key, lut);
1488
1614
  }
1489
1615
  return lut;
1490
1616
  }
1491
- function colormap(name = "viridis") {
1492
- const lut = colormapLUT(name);
1617
+ function sampler(lut) {
1493
1618
  return (t) => {
1494
1619
  const c = t <= 0 ? 0 : t >= 1 ? 1 : t;
1495
1620
  const j = (c * (LUT_SIZE - 1) | 0) * 3;
1496
1621
  return [lut[j], lut[j + 1], lut[j + 2]];
1497
1622
  };
1498
1623
  }
1624
+ function colormap(spec = "viridis") {
1625
+ return sampler(colormapLUT(spec));
1626
+ }
1627
+ function colormapFromStops(stops) {
1628
+ return sampler(buildLUT(stops.map(toRgb)));
1629
+ }
1630
+ function reverseColormap(spec = "viridis") {
1631
+ const lut = colormapLUT(spec);
1632
+ const flipped = new Float32Array(lut.length);
1633
+ for (let s = 0; s < LUT_SIZE; s++) {
1634
+ const src = (LUT_SIZE - 1 - s) * 3;
1635
+ flipped[s * 3] = lut[src];
1636
+ flipped[s * 3 + 1] = lut[src + 1];
1637
+ flipped[s * 3 + 2] = lut[src + 2];
1638
+ }
1639
+ return sampler(flipped);
1640
+ }
1641
+ function discreteColormap(spec, steps) {
1642
+ const sample = colormap(spec);
1643
+ const n = Math.max(1, Math.floor(steps));
1644
+ return (t) => {
1645
+ const c = t <= 0 ? 0 : t >= 1 ? 1 : t;
1646
+ const band = Math.min(n - 1, Math.floor(c * n));
1647
+ return sample(n === 1 ? 0.5 : band / (n - 1));
1648
+ };
1649
+ }
1650
+ function symmetricDomain(values, center = 0) {
1651
+ let reach = 0;
1652
+ for (let i = 0; i < values.length; i++) {
1653
+ const d = Math.abs(values[i] - center);
1654
+ if (d > reach && Number.isFinite(d)) reach = d;
1655
+ }
1656
+ if (reach === 0) reach = 1;
1657
+ return [center - reach, center + reach];
1658
+ }
1659
+ function toRgb(c) {
1660
+ if (typeof c !== "string") return c;
1661
+ let hex = c.trim().replace(/^#/, "");
1662
+ if (hex.length === 3) hex = hex.split("").map((ch) => ch + ch).join("");
1663
+ const n = parseInt(hex.slice(0, 6), 16);
1664
+ return [(n >> 16 & 255) / 255, (n >> 8 & 255) / 255, (n & 255) / 255];
1665
+ }
1499
1666
 
1500
1667
  // src/layers/contour.ts
1501
1668
  var VERT4 = (
@@ -1547,6 +1714,7 @@ var counter6 = 0;
1547
1714
  var ContourLayer = class {
1548
1715
  constructor(gl, opts) {
1549
1716
  this.vertexCount = 0;
1717
+ this.cInfo = null;
1550
1718
  this.id = `contour-${counter6++}`;
1551
1719
  this.gl = gl;
1552
1720
  this.program = getProgram5(gl);
@@ -1559,7 +1727,9 @@ var ContourLayer = class {
1559
1727
  this.xRef = x0;
1560
1728
  this.yRef = y0;
1561
1729
  this.levelsOpt = opts.levels;
1562
- this.cmap = colormap(opts.colormap ?? "viridis");
1730
+ this.cmapName = opts.colormap ?? "viridis";
1731
+ this.label = opts.name;
1732
+ this.cmap = colormap(this.cmapName);
1563
1733
  this.fixedColor = opts.color != null ? Array.isArray(opts.color) ? opts.color : parseColor(opts.color) : null;
1564
1734
  const data = this.build(opts.values, opts.cols, opts.rows);
1565
1735
  const vao = gl.createVertexArray();
@@ -1594,6 +1764,7 @@ var ContourLayer = class {
1594
1764
  const at = (c, r) => values[r * cols + c];
1595
1765
  const data = [];
1596
1766
  const lspan = vmax - vmin || 1;
1767
+ this.cInfo = this.fixedColor ? null : { colormap: this.cmapName, domain: [vmin, vmax], ...this.label ? { label: this.label } : {} };
1597
1768
  for (let li = 0; li < levels.length; li++) {
1598
1769
  const L = levels[li];
1599
1770
  const col = fixed ?? [...cmap((L - vmin) / lspan), 1];
@@ -1629,6 +1800,9 @@ var ContourLayer = class {
1629
1800
  this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffer);
1630
1801
  this.gl.bufferData(this.gl.ARRAY_BUFFER, data, this.usage);
1631
1802
  }
1803
+ colorInfo() {
1804
+ return this.cInfo;
1805
+ }
1632
1806
  bounds() {
1633
1807
  return { x: this.ext.x, y: this.ext.y };
1634
1808
  }
@@ -2156,6 +2330,7 @@ function getProgram6(gl) {
2156
2330
  var counter9 = 0;
2157
2331
  var HeatmapLayer = class {
2158
2332
  constructor(gl, opts) {
2333
+ this.cInfo = null;
2159
2334
  this.id = `heatmap-${counter9++}`;
2160
2335
  this.gl = gl;
2161
2336
  this.program = getProgram6(gl);
@@ -2165,7 +2340,9 @@ var HeatmapLayer = class {
2165
2340
  const [y0, y1] = opts.extent.y;
2166
2341
  this.xRef = x0;
2167
2342
  this.yRef = y0;
2168
- this.lut = colormapLUT(opts.colormap ?? "viridis");
2343
+ this.cmapName = opts.colormap ?? "viridis";
2344
+ this.label = opts.name;
2345
+ this.lut = colormapLUT(this.cmapName);
2169
2346
  this.fixedDomain = opts.domain;
2170
2347
  this.cols = opts.cols;
2171
2348
  this.rows = opts.rows;
@@ -2233,6 +2410,7 @@ var HeatmapLayer = class {
2233
2410
  }
2234
2411
  }
2235
2412
  const span = hi - lo || 1;
2413
+ this.cInfo = { colormap: this.cmapName, domain: [lo, hi], ...this.label ? { label: this.label } : {} };
2236
2414
  const pixels = new Uint8Array(cols * rows * 4);
2237
2415
  for (let i = 0; i < cols * rows; i++) {
2238
2416
  let t = (values[i] - lo) / span;
@@ -2254,6 +2432,9 @@ var HeatmapLayer = class {
2254
2432
  this.rows = rows;
2255
2433
  this.uploadValues(values, cols, rows);
2256
2434
  }
2435
+ colorInfo() {
2436
+ return this.cInfo;
2437
+ }
2257
2438
  bounds() {
2258
2439
  return { x: this.ext.x, y: this.ext.y };
2259
2440
  }
@@ -2329,12 +2510,15 @@ var HexbinLayer = class {
2329
2510
  this.yRef = 0;
2330
2511
  this.xBounds = [0, 0];
2331
2512
  this.yBounds = [0, 0];
2513
+ this.cInfo = null;
2332
2514
  this.id = `hexbin-${counter10++}`;
2333
2515
  this.gl = gl;
2334
2516
  this.program = getProgram7(gl);
2335
2517
  this.usage = bufferUsage(gl, opts.renderType);
2336
2518
  this.yAxis = opts.yAxis ?? "y";
2337
- this.cmap = colormap(opts.colormap ?? "viridis");
2519
+ this.cmapName = opts.colormap ?? "viridis";
2520
+ this.label = opts.name;
2521
+ this.cmap = colormap(this.cmapName);
2338
2522
  this.explicitRadius = opts.radius;
2339
2523
  this.domain = opts.domain;
2340
2524
  const { centers, colors } = this.build(opts.x, opts.y);
@@ -2402,6 +2586,7 @@ var HexbinLayer = class {
2402
2586
  const lo = this.domain?.[0] ?? 1;
2403
2587
  const hi = this.domain?.[1] ?? maxCount;
2404
2588
  const span = hi - lo || 1;
2589
+ this.cInfo = { colormap: this.cmapName, domain: [lo, hi], ...this.label ? { label: this.label } : {} };
2405
2590
  let k = 0;
2406
2591
  for (const cell of cells.values()) {
2407
2592
  centers[k * 2] = cell.cx - this.xRef;
@@ -2424,6 +2609,9 @@ var HexbinLayer = class {
2424
2609
  gl.bindBuffer(gl.ARRAY_BUFFER, this.buffers[2]);
2425
2610
  gl.bufferData(gl.ARRAY_BUFFER, colors, this.usage);
2426
2611
  }
2612
+ colorInfo() {
2613
+ return this.cInfo;
2614
+ }
2427
2615
  bounds() {
2428
2616
  if (this.cellCount === 0) return null;
2429
2617
  return { x: this.xBounds, y: this.yBounds };
@@ -2846,6 +3034,7 @@ precision highp float;
2846
3034
  layout(location = 0) in vec2 aCorner; // (along 0..1, side -1..1)
2847
3035
  layout(location = 1) in vec2 aP0;
2848
3036
  layout(location = 2) in vec2 aP1;
3037
+ layout(location = 3) in float aDist; // cumulative data-space length at P0
2849
3038
  uniform vec2 uResolution;
2850
3039
  uniform float uWidth;
2851
3040
  uniform float uRound;
@@ -2853,6 +3042,7 @@ ${TRANSFORM_GLSL}
2853
3042
  out vec2 vPix;
2854
3043
  out vec2 vS0;
2855
3044
  out vec2 vS1;
3045
+ out float vDash0;
2856
3046
  void main() {
2857
3047
  vec2 s0 = (dataToClip(aP0) * 0.5 + 0.5) * uResolution;
2858
3048
  vec2 s1 = (dataToClip(aP1) * 0.5 + 0.5) * uResolution;
@@ -2865,6 +3055,10 @@ void main() {
2865
3055
  vec2 endpoint = mix(s0, s1, aCorner.x);
2866
3056
  vec2 outward = (aCorner.x < 0.5 ? -dir : dir) * ext;
2867
3057
  vec2 pos = endpoint + outward + nrm * (aCorner.y * hw);
3058
+ // Convert the CPU-side data-space arc length into screen px using this
3059
+ // segment own scale, so the pattern stays continuous across vertices.
3060
+ float dataLen = length(aP1 - aP0);
3061
+ vDash0 = dataLen > 1e-12 ? aDist * (len / dataLen) : 0.0;
2868
3062
  vPix = pos; vS0 = s0; vS1 = s1;
2869
3063
  gl_Position = vec4((pos / uResolution) * 2.0 - 1.0, 0.0, 1.0);
2870
3064
  }`
@@ -2876,9 +3070,13 @@ precision highp float;
2876
3070
  in vec2 vPix;
2877
3071
  in vec2 vS0;
2878
3072
  in vec2 vS1;
3073
+ in float vDash0;
2879
3074
  uniform vec4 uColor;
2880
3075
  uniform float uWidth;
2881
3076
  uniform float uRound;
3077
+ uniform float uDash[8];
3078
+ uniform int uDashCount;
3079
+ uniform float uDashPeriod;
2882
3080
  out vec4 outColor;
2883
3081
  void main() {
2884
3082
  vec2 pa = vPix - vS0;
@@ -2892,6 +3090,17 @@ void main() {
2892
3090
  if (t < 0.0 || t > 1.0) discard; // butt caps
2893
3091
  d = length(pa - ba * t);
2894
3092
  }
3093
+ if (uDashCount > 0) {
3094
+ float phase = mod(vDash0 + clamp(t, 0.0, 1.0) * length(ba), uDashPeriod);
3095
+ float acc = 0.0;
3096
+ bool on = true;
3097
+ for (int i = 0; i < 8; i++) {
3098
+ if (i >= uDashCount) break;
3099
+ acc += uDash[i];
3100
+ if (phase < acc) { on = (i - (i / 2) * 2) == 0; break; }
3101
+ }
3102
+ if (!on) discard;
3103
+ }
2895
3104
  float hw = uWidth * 0.5;
2896
3105
  float alpha = 1.0 - smoothstep(hw - 1.0, hw + 1.0, d);
2897
3106
  if (alpha <= 0.0) discard;
@@ -3054,7 +3263,9 @@ var LineLayer = class {
3054
3263
  this.joinStyle = opts.join ?? "round";
3055
3264
  this.round = this.joinStyle === "round";
3056
3265
  this.miterLimit = opts.miterLimit ?? 4;
3057
- this.decimateOn = opts.decimate !== false;
3266
+ this.dash = (opts.dash ?? []).filter((v) => v > 0).slice(0, 8);
3267
+ this.dashPeriod = this.dash.reduce((a, b) => a + b, 0);
3268
+ this.decimateOn = opts.decimate !== false && this.dash.length === 0;
3058
3269
  this.step = opts.step;
3059
3270
  const colorInput = opts.color ?? "#3b82f6";
3060
3271
  this.color = Array.isArray(colorInput) ? colorInput : parseColor(colorInput);
@@ -3092,8 +3303,13 @@ var LineLayer = class {
3092
3303
  this.yBounds = [minY, maxY];
3093
3304
  this.count = n;
3094
3305
  this.monotonic = mono;
3306
+ const dists = new Float32Array(this.dash.length ? n : 0);
3307
+ for (let i = 1; i < dists.length; i++) {
3308
+ dists[i] = dists[i - 1] + Math.hypot(data[i * 2] - data[(i - 1) * 2], data[i * 2 + 1] - data[(i - 1) * 2 + 1]);
3309
+ }
3095
3310
  this.cornerBuf = gl.createBuffer();
3096
3311
  this.posBuf = gl.createBuffer();
3312
+ this.distBuf = gl.createBuffer();
3097
3313
  this.decBuf = gl.createBuffer();
3098
3314
  this.fullVao = gl.createVertexArray();
3099
3315
  this.decVao = gl.createVertexArray();
@@ -3103,6 +3319,8 @@ var LineLayer = class {
3103
3319
  gl.bufferData(gl.ARRAY_BUFFER, CORNERS2, gl.STATIC_DRAW);
3104
3320
  gl.bindBuffer(gl.ARRAY_BUFFER, this.posBuf);
3105
3321
  gl.bufferData(gl.ARRAY_BUFFER, data, this.usage);
3322
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.distBuf);
3323
+ gl.bufferData(gl.ARRAY_BUFFER, dists, this.usage);
3106
3324
  this.configureVao(this.fullVao, this.posBuf);
3107
3325
  this.configureVao(this.decVao, this.decBuf);
3108
3326
  this.configureJoinVao(this.joinFullVao, this.posBuf);
@@ -3112,7 +3330,10 @@ var LineLayer = class {
3112
3330
  "uColor",
3113
3331
  "uResolution",
3114
3332
  "uWidth",
3115
- "uRound"
3333
+ "uRound",
3334
+ "uDash[0]",
3335
+ "uDashCount",
3336
+ "uDashPeriod"
3116
3337
  ]);
3117
3338
  this.joinUniforms = uniformLocations(gl, this.joinProgram, [
3118
3339
  ...TRANSFORM_UNIFORMS,
@@ -3157,6 +3378,12 @@ var LineLayer = class {
3157
3378
  gl.enableVertexAttribArray(2);
3158
3379
  gl.vertexAttribPointer(2, 2, gl.FLOAT, false, 8, 8);
3159
3380
  gl.vertexAttribDivisor(2, 1);
3381
+ if (this.dash.length) {
3382
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.distBuf);
3383
+ gl.enableVertexAttribArray(3);
3384
+ gl.vertexAttribPointer(3, 1, gl.FLOAT, false, 4, 0);
3385
+ gl.vertexAttribDivisor(3, 1);
3386
+ }
3160
3387
  gl.bindVertexArray(null);
3161
3388
  }
3162
3389
  // Join instances read three consecutive points (prev, p0, next) from the same
@@ -3211,6 +3438,10 @@ var LineLayer = class {
3211
3438
  this.yBounds = [minY, maxY];
3212
3439
  this.count = n;
3213
3440
  this.monotonic = mono;
3441
+ const dists = new Float32Array(this.dash.length ? n : 0);
3442
+ for (let i = 1; i < dists.length; i++) {
3443
+ dists[i] = dists[i - 1] + Math.hypot(data[i * 2] - data[(i - 1) * 2], data[i * 2 + 1] - data[(i - 1) * 2 + 1]);
3444
+ }
3214
3445
  this.decKey = "";
3215
3446
  this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.posBuf);
3216
3447
  this.gl.bufferData(this.gl.ARRAY_BUFFER, data, this.usage);
@@ -3269,6 +3500,13 @@ var LineLayer = class {
3269
3500
  gl.uniform2f(this.uniforms.uResolution, state.pixelWidth, state.pixelHeight);
3270
3501
  gl.uniform1f(this.uniforms.uWidth, this.width * state.dpr);
3271
3502
  gl.uniform1f(this.uniforms.uRound, this.round ? 1 : 0);
3503
+ gl.uniform1i(this.uniforms.uDashCount, this.dash.length);
3504
+ if (this.dash.length) {
3505
+ const scaled = new Float32Array(8);
3506
+ for (let i = 0; i < this.dash.length; i++) scaled[i] = this.dash[i] * state.dpr;
3507
+ gl.uniform1fv(this.uniforms["uDash[0]"], scaled);
3508
+ gl.uniform1f(this.uniforms.uDashPeriod, this.dashPeriod * state.dpr);
3509
+ }
3272
3510
  gl.bindVertexArray(vao);
3273
3511
  gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, segments);
3274
3512
  gl.bindVertexArray(null);
@@ -3294,6 +3532,7 @@ var LineLayer = class {
3294
3532
  gl.deleteVertexArray(this.joinDecVao);
3295
3533
  gl.deleteBuffer(this.cornerBuf);
3296
3534
  gl.deleteBuffer(this.posBuf);
3535
+ gl.deleteBuffer(this.distBuf);
3297
3536
  gl.deleteBuffer(this.decBuf);
3298
3537
  }
3299
3538
  };
@@ -3759,6 +3998,7 @@ var PatchesLayer = class {
3759
3998
  this.yRef = 0;
3760
3999
  this.xBounds = [0, 0];
3761
4000
  this.yBounds = [0, 0];
4001
+ this.cInfo = null;
3762
4002
  this.id = `patches-${counter13++}`;
3763
4003
  this.gl = gl;
3764
4004
  this.program = getFillProgram(gl);
@@ -3768,6 +4008,7 @@ var PatchesLayer = class {
3768
4008
  this.defRgba = Array.isArray(defColor) ? defColor : parseColor(defColor);
3769
4009
  this.colorCss = typeof defColor === "string" ? defColor : "#3b82f6";
3770
4010
  this.opacity = opts.opacity ?? 1;
4011
+ this.cmapName = opts.colormap;
3771
4012
  this.cmap = opts.colormap ? colormap(opts.colormap) : null;
3772
4013
  this.domainOpt = opts.domain;
3773
4014
  this.usage = bufferUsage(gl, opts.renderType);
@@ -3803,6 +4044,7 @@ var PatchesLayer = class {
3803
4044
  }
3804
4045
  }
3805
4046
  const span = hi - lo || 1;
4047
+ this.cInfo = cmap && this.cmapName && lo <= hi ? { colormap: this.cmapName, domain: [lo, hi], ...this.name !== this.id ? { label: this.name } : {} } : null;
3806
4048
  this.xRef = 0;
3807
4049
  this.yRef = 0;
3808
4050
  for (const patch of patches) {
@@ -3862,6 +4104,9 @@ var PatchesLayer = class {
3862
4104
  gl.bindBuffer(gl.ARRAY_BUFFER, this.buffers[1]);
3863
4105
  gl.bufferData(gl.ARRAY_BUFFER, colors, this.usage);
3864
4106
  }
4107
+ colorInfo() {
4108
+ return this.cInfo;
4109
+ }
3865
4110
  bounds() {
3866
4111
  if (this.vertexCount === 0) return null;
3867
4112
  return { x: this.xBounds, y: this.yBounds };
@@ -4099,6 +4344,7 @@ var counter15 = 0;
4099
4344
  var QuiverLayer = class {
4100
4345
  constructor(gl, opts) {
4101
4346
  this.buffers = [];
4347
+ this.cInfo = null;
4102
4348
  this.xRef = 0;
4103
4349
  this.yRef = 0;
4104
4350
  this.xBounds = [0, 0];
@@ -4116,6 +4362,7 @@ var QuiverLayer = class {
4116
4362
  this.useVertexColor = opts.colorBy != null;
4117
4363
  this.explicitScale = opts.scale;
4118
4364
  this.colorBy = opts.colorBy;
4365
+ this.label = opts.name;
4119
4366
  this.usage = bufferUsage(gl, opts.renderType);
4120
4367
  const { arrows, colors } = this.build(opts.x, opts.y, opts.u, opts.v);
4121
4368
  const cornerBuf = gl.createBuffer();
@@ -4200,6 +4447,11 @@ var QuiverLayer = class {
4200
4447
  }
4201
4448
  }
4202
4449
  const span = hi - lo || 1;
4450
+ this.cInfo = this.useVertexColor ? {
4451
+ colormap: this.colorBy?.colormap ?? "viridis",
4452
+ domain: [lo, hi],
4453
+ ...this.label ? { label: this.label } : {}
4454
+ } : null;
4203
4455
  let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
4204
4456
  for (let i = 0; i < n; i++) {
4205
4457
  const xi = x[i], yi = y[i];
@@ -4234,6 +4486,9 @@ var QuiverLayer = class {
4234
4486
  gl.bindBuffer(gl.ARRAY_BUFFER, this.buffers[2]);
4235
4487
  gl.bufferData(gl.ARRAY_BUFFER, colors, this.usage);
4236
4488
  }
4489
+ colorInfo() {
4490
+ return this.cInfo;
4491
+ }
4237
4492
  bounds() {
4238
4493
  if (this.count === 0) return null;
4239
4494
  return { x: this.xBounds, y: this.yBounds };
@@ -4285,14 +4540,17 @@ precision highp float;
4285
4540
  layout(location = 0) in vec2 aCorner; // unit quad [-1,1]^2
4286
4541
  layout(location = 1) in vec2 aPos; // point (offset data space)
4287
4542
  layout(location = 2) in vec4 aColor; // per-point color (used if uUseVertexColor>0.5)
4543
+ layout(location = 3) in float aSize; // per-point diameter in CSS px (0 => uniform uSize)
4288
4544
  uniform vec2 uResolution;
4289
4545
  uniform float uSize; // radius in device px
4546
+ uniform float uDpr;
4290
4547
  ${TRANSFORM_GLSL}
4291
4548
  out vec2 vLocal;
4292
4549
  out vec4 vColor;
4293
4550
  void main() {
4294
4551
  vec2 center = (dataToClip(aPos) * 0.5 + 0.5) * uResolution;
4295
- vec2 pos = center + aCorner * uSize;
4552
+ float radius = aSize > 0.0 ? aSize * 0.5 * uDpr : uSize;
4553
+ vec2 pos = center + aCorner * radius;
4296
4554
  vLocal = aCorner;
4297
4555
  vColor = aColor;
4298
4556
  gl_Position = vec4((pos / uResolution) * 2.0 - 1.0, 0.0, 1.0);
@@ -4365,6 +4623,7 @@ var counter16 = 0;
4365
4623
  var ScatterLayer = class {
4366
4624
  constructor(gl, opts) {
4367
4625
  this.buffers = [];
4626
+ this.cInfo = null;
4368
4627
  this.xRef = 0;
4369
4628
  this.yRef = 0;
4370
4629
  this.xBounds = [0, 0];
@@ -4373,6 +4632,7 @@ var ScatterLayer = class {
4373
4632
  this.gl = gl;
4374
4633
  this.program = getProgram10(gl);
4375
4634
  this.size = opts.size ?? 5;
4635
+ this.maxSize = this.size;
4376
4636
  this.marker = MARKERS[opts.marker ?? "circle"];
4377
4637
  const colorInput = opts.color ?? "#3b82f6";
4378
4638
  this.color = Array.isArray(colorInput) ? colorInput : parseColor(colorInput);
@@ -4403,7 +4663,28 @@ var ScatterLayer = class {
4403
4663
  }
4404
4664
  this.xBounds = [minX, maxX];
4405
4665
  this.yBounds = [minY, maxY];
4666
+ const sizes = new Float32Array(n);
4667
+ if (opts.sizes) {
4668
+ let max = this.size;
4669
+ for (let i = 0; i < n; i++) {
4670
+ const s = opts.sizes[i] ?? 0;
4671
+ sizes[i] = s > 0 ? s : 0;
4672
+ if (s > max) max = s;
4673
+ }
4674
+ this.maxSize = max;
4675
+ }
4406
4676
  const colors = new Float32Array(n * 4);
4677
+ if (opts.colors && !opts.colorBy) {
4678
+ this.useVertexColor = true;
4679
+ for (let i = 0; i < n; i++) {
4680
+ const c = opts.colors[i];
4681
+ const rgba = c == null ? this.color : Array.isArray(c) ? c : parseColor(c);
4682
+ colors[i * 4] = rgba[0];
4683
+ colors[i * 4 + 1] = rgba[1];
4684
+ colors[i * 4 + 2] = rgba[2];
4685
+ colors[i * 4 + 3] = rgba[3];
4686
+ }
4687
+ }
4407
4688
  if (opts.colorBy) {
4408
4689
  const vals = opts.colorBy.values;
4409
4690
  const lut = colormapLUT(opts.colorBy.colormap ?? "viridis");
@@ -4417,6 +4698,11 @@ var ScatterLayer = class {
4417
4698
  }
4418
4699
  }
4419
4700
  const span = hi - lo || 1;
4701
+ this.cInfo = {
4702
+ colormap: opts.colorBy.colormap ?? "viridis",
4703
+ domain: [lo, hi],
4704
+ ...opts.name ? { label: opts.name } : {}
4705
+ };
4420
4706
  for (let i = 0; i < n; i++) {
4421
4707
  let t = (vals[i] - lo) / span;
4422
4708
  t = t <= 0 ? 0 : t >= 1 ? 1 : t;
@@ -4447,23 +4733,33 @@ var ScatterLayer = class {
4447
4733
  gl.enableVertexAttribArray(2);
4448
4734
  gl.vertexAttribPointer(2, 4, gl.FLOAT, false, 0, 0);
4449
4735
  gl.vertexAttribDivisor(2, 1);
4736
+ const sizeBuf = gl.createBuffer();
4737
+ gl.bindBuffer(gl.ARRAY_BUFFER, sizeBuf);
4738
+ gl.bufferData(gl.ARRAY_BUFFER, sizes, this.usage);
4739
+ gl.enableVertexAttribArray(3);
4740
+ gl.vertexAttribPointer(3, 1, gl.FLOAT, false, 0, 0);
4741
+ gl.vertexAttribDivisor(3, 1);
4450
4742
  gl.bindVertexArray(null);
4451
- this.buffers = [cornerBuf, posBuf, colorBuf];
4743
+ this.buffers = [cornerBuf, posBuf, colorBuf, sizeBuf];
4452
4744
  this.uniforms = uniformLocations(gl, this.program, [
4453
4745
  ...TRANSFORM_UNIFORMS,
4454
4746
  "uColor",
4455
4747
  "uResolution",
4456
4748
  "uSize",
4749
+ "uDpr",
4457
4750
  "uUseVertexColor",
4458
4751
  "uMarker"
4459
4752
  ]);
4460
4753
  }
4754
+ colorInfo() {
4755
+ return this.cInfo;
4756
+ }
4461
4757
  bounds() {
4462
4758
  if (this.count === 0) return null;
4463
4759
  return { x: this.xBounds, y: this.yBounds };
4464
4760
  }
4465
4761
  pick(mode, cursorPx, cursorPy, project) {
4466
- const gatePx = this.size / 2 + 4;
4762
+ const gatePx = this.maxSize / 2 + 4;
4467
4763
  return pickNearest(this.xs, this.ys, this.count, mode, cursorPx, cursorPy, project, gatePx);
4468
4764
  }
4469
4765
  /** User-supplied text for a point, shown when it is clicked. */
@@ -4495,8 +4791,12 @@ var ScatterLayer = class {
4495
4791
  this.xBounds = [minX, maxX];
4496
4792
  this.yBounds = [minY, maxY];
4497
4793
  this.useVertexColor = false;
4794
+ this.cInfo = null;
4795
+ this.maxSize = this.size;
4498
4796
  this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffers[1]);
4499
4797
  this.gl.bufferData(this.gl.ARRAY_BUFFER, pos, this.usage);
4798
+ this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffers[3]);
4799
+ this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(n), this.usage);
4500
4800
  }
4501
4801
  draw(state) {
4502
4802
  if (this.count === 0) return;
@@ -4506,6 +4806,7 @@ var ScatterLayer = class {
4506
4806
  gl.uniform4f(this.uniforms.uColor, this.color[0], this.color[1], this.color[2], this.color[3]);
4507
4807
  gl.uniform2f(this.uniforms.uResolution, state.pixelWidth, state.pixelHeight);
4508
4808
  gl.uniform1f(this.uniforms.uSize, this.size / 2 * state.dpr);
4809
+ gl.uniform1f(this.uniforms.uDpr, state.dpr);
4509
4810
  gl.uniform1f(this.uniforms.uUseVertexColor, this.useVertexColor ? 1 : 0);
4510
4811
  gl.uniform1i(this.uniforms.uMarker, this.marker);
4511
4812
  gl.bindVertexArray(this.vao);
@@ -4991,6 +5292,104 @@ async function copyCanvasToClipboard(canvas) {
4991
5292
  await navigator.clipboard.write([new CI({ "image/png": blob })]);
4992
5293
  }
4993
5294
 
5295
+ // src/render/colorbar.ts
5296
+ var GRADIENT_STOPS = 24;
5297
+ function colormapGradient(spec, stops = GRADIENT_STOPS) {
5298
+ const cmap = colormap(spec);
5299
+ const parts = [];
5300
+ for (let i = 0; i <= stops; i++) {
5301
+ const t = i / stops;
5302
+ const [r, g, b] = cmap(t);
5303
+ parts.push(
5304
+ `rgb(${Math.round(r * 255)},${Math.round(g * 255)},${Math.round(b * 255)}) ${(t * 100).toFixed(2)}%`
5305
+ );
5306
+ }
5307
+ return `linear-gradient(to top, ${parts.join(",")})`;
5308
+ }
5309
+ function barTicks(domain, target) {
5310
+ const [lo, hi] = domain;
5311
+ if (!Number.isFinite(lo) || !Number.isFinite(hi)) return [];
5312
+ if (lo === hi) return [lo];
5313
+ const ticks = autoTicks(lo, hi, target).map((t) => t.value).filter((v) => v >= lo && v <= hi);
5314
+ return ticks.length >= 2 ? ticks : [lo, hi];
5315
+ }
5316
+ function compact(value) {
5317
+ if (value === 0) return "0";
5318
+ const abs = Math.abs(value);
5319
+ if (abs >= 1e5 || abs < 1e-3) return value.toExponential(1).replace("e+", "e");
5320
+ return String(parseFloat(value.toPrecision(3)));
5321
+ }
5322
+ function buildBar(info, opts, theme, heightPx) {
5323
+ const wrap = document.createElement("div");
5324
+ Object.assign(wrap.style, {
5325
+ display: "flex",
5326
+ flexDirection: "column",
5327
+ alignItems: "flex-start",
5328
+ gap: "3px"
5329
+ });
5330
+ const label = opts.label ?? info.label;
5331
+ if (label) {
5332
+ const caption = document.createElement("div");
5333
+ caption.textContent = label;
5334
+ Object.assign(caption.style, {
5335
+ color: theme.text,
5336
+ font: theme.font,
5337
+ opacity: "0.85",
5338
+ whiteSpace: "nowrap"
5339
+ });
5340
+ wrap.appendChild(caption);
5341
+ }
5342
+ const row = document.createElement("div");
5343
+ Object.assign(row.style, { display: "flex", alignItems: "stretch", gap: "4px" });
5344
+ const barHeight = Math.max(24, heightPx);
5345
+ const bar = document.createElement("div");
5346
+ Object.assign(bar.style, {
5347
+ width: `${opts.width ?? 12}px`,
5348
+ height: `${barHeight}px`,
5349
+ borderRadius: "2px",
5350
+ background: colormapGradient(info.colormap),
5351
+ border: `1px solid ${theme.border}`,
5352
+ flex: "0 0 auto"
5353
+ });
5354
+ const format = opts.format ?? compact;
5355
+ const [lo, hi] = info.domain;
5356
+ const span = hi - lo || 1;
5357
+ const ticks = document.createElement("div");
5358
+ Object.assign(ticks.style, {
5359
+ position: "relative",
5360
+ height: `${barHeight}px`,
5361
+ minWidth: "1px",
5362
+ color: theme.text,
5363
+ font: theme.font,
5364
+ whiteSpace: "nowrap"
5365
+ });
5366
+ for (const v of barTicks(info.domain, opts.ticks ?? 5)) {
5367
+ const t = document.createElement("span");
5368
+ t.textContent = format(v);
5369
+ Object.assign(t.style, {
5370
+ position: "absolute",
5371
+ left: "0",
5372
+ top: `${(1 - (v - lo) / span) * barHeight}px`,
5373
+ transform: "translateY(-50%)"
5374
+ });
5375
+ ticks.appendChild(t);
5376
+ }
5377
+ row.append(bar, ticks);
5378
+ wrap.appendChild(row);
5379
+ return wrap;
5380
+ }
5381
+ function renderColorbars(host, infos, opts, theme, regionHeight) {
5382
+ if (infos.length === 0) {
5383
+ host.style.display = "none";
5384
+ return;
5385
+ }
5386
+ const fraction = opts.heightFraction ?? 0.72;
5387
+ const per = regionHeight * fraction / infos.length - (infos.length > 1 ? 22 : 0);
5388
+ host.replaceChildren();
5389
+ for (const info of infos) host.appendChild(buildBar(info, opts, theme, per));
5390
+ host.style.display = "flex";
5391
+ }
5392
+
4994
5393
  // src/scales/scale.ts
4995
5394
  var LinearScale = class {
4996
5395
  constructor(domain = [0, 1]) {
@@ -5432,6 +5831,7 @@ function createToolbar(container, host, dark) {
5432
5831
  // src/plot.ts
5433
5832
  var DEFAULT_MARGIN = { top: 16, right: 16, bottom: 40, left: 56 };
5434
5833
  var Y_AXIS_GAP = 52;
5834
+ var COLORBAR_GAP = 62;
5435
5835
  var TITLE_RESERVE = 28;
5436
5836
  function hexToRgba(hex, alpha) {
5437
5837
  const m = /^#?([0-9a-f]{6})$/i.exec(hex.trim());
@@ -5501,6 +5901,9 @@ var Plot = class {
5501
5901
  /** A point clicked to pin its details, until another click clears it. */
5502
5902
  this.selected = null;
5503
5903
  this.annotations = [];
5904
+ /** Ids of layers hidden via the legend / {@link setLayerVisible}. */
5905
+ this.hidden = /* @__PURE__ */ new Set();
5906
+ this.visibilityCbs = [];
5504
5907
  this.container = container;
5505
5908
  if (getComputedStyle(container).position === "static") {
5506
5909
  container.style.position = "relative";
@@ -5541,6 +5944,7 @@ var Plot = class {
5541
5944
  this.title = typeof options.title === "string" ? { text: options.title } : options.title ?? null;
5542
5945
  this.updateAria();
5543
5946
  this.legend = options.legend === true ? {} : options.legend || null;
5947
+ this.colorbar = options.colorbar === false ? null : options.colorbar === true || options.colorbar == null ? {} : options.colorbar;
5544
5948
  this.mode = options.mode ?? "pan";
5545
5949
  this.hoverEnabled = options.hover !== false;
5546
5950
  this.pickMode = options.pick ?? "x";
@@ -5609,6 +6013,17 @@ var Plot = class {
5609
6013
  border: `1px solid ${this.isDark ? "rgba(148,163,184,0.25)" : "rgba(100,116,139,0.2)"}`
5610
6014
  });
5611
6015
  container.appendChild(this.legendDiv);
6016
+ this.colorbarDiv = document.createElement("div");
6017
+ Object.assign(this.colorbarDiv.style, {
6018
+ position: "absolute",
6019
+ display: "none",
6020
+ zIndex: "3",
6021
+ pointerEvents: "none",
6022
+ flexDirection: "column",
6023
+ gap: "10px",
6024
+ justifyContent: "center"
6025
+ });
6026
+ container.appendChild(this.colorbarDiv);
5612
6027
  this.resizeObserver = new ResizeObserver(() => this.resize());
5613
6028
  this.resizeObserver.observe(container);
5614
6029
  this.resize();
@@ -5659,9 +6074,40 @@ var Plot = class {
5659
6074
  top: this.baseMargin.top + (this.title ? TITLE_RESERVE : 0),
5660
6075
  bottom: this.baseMargin.bottom,
5661
6076
  left: this.baseMargin.left + Math.max(0, leftCount - 1) * Y_AXIS_GAP,
5662
- right: this.baseMargin.right + rightCount * Y_AXIS_GAP
6077
+ right: this.baseMargin.right + rightCount * Y_AXIS_GAP + (this.colorInfos().length ? COLORBAR_GAP : 0)
5663
6078
  };
5664
6079
  }
6080
+ /** Colour scales reported by the layers, in draw order. */
6081
+ colorInfos() {
6082
+ if (!this.colorbar) return [];
6083
+ const out = [];
6084
+ for (const l of this.layers) {
6085
+ if (this.hidden.has(l.id)) continue;
6086
+ const info = l.colorInfo?.();
6087
+ if (info) out.push(info);
6088
+ }
6089
+ return out;
6090
+ }
6091
+ /** Rebuild + place the colorbar stack in the reserved side margin. */
6092
+ updateColorbars(region) {
6093
+ const infos = this.colorInfos();
6094
+ const cfg = this.colorbar;
6095
+ if (!cfg || infos.length === 0) {
6096
+ this.colorbarDiv.style.display = "none";
6097
+ return;
6098
+ }
6099
+ renderColorbars(this.colorbarDiv, infos, cfg, {
6100
+ text: cfg.textColor ?? this.theme.text,
6101
+ border: cfg.borderColor ?? this.theme.axis,
6102
+ font: cfg.font ?? "10px system-ui, -apple-system, sans-serif"
6103
+ }, region.height);
6104
+ let rightAxes = 0;
6105
+ for (const ya of this.yAxes.values()) if (ya.side === "right") rightAxes++;
6106
+ const left = cfg.position === "left" ? Math.max(4, region.left - COLORBAR_GAP + 8) : region.left + region.width + rightAxes * Y_AXIS_GAP + 12;
6107
+ this.colorbarDiv.style.left = `${left}px`;
6108
+ this.colorbarDiv.style.top = `${region.top}px`;
6109
+ this.colorbarDiv.style.height = `${region.height}px`;
6110
+ }
5665
6111
  layout() {
5666
6112
  return {
5667
6113
  cssWidth: this.container.clientWidth,
@@ -5728,6 +6174,14 @@ var Plot = class {
5728
6174
  get context() {
5729
6175
  return this.gl;
5730
6176
  }
6177
+ /**
6178
+ * The container this plot renders into (already `position: relative`), for
6179
+ * custom overlays — a chart-specific tooltip, a colorbar, a caption. Append
6180
+ * absolutely-positioned children with `zIndex >= 3` to sit above the canvases.
6181
+ */
6182
+ get element() {
6183
+ return this.container;
6184
+ }
5731
6185
  /**
5732
6186
  * Convert a client (screen) coordinate to data space — the shared x and the
5733
6187
  * primary y. Returns null if the point is outside the plot region. Useful for
@@ -6251,6 +6705,7 @@ var Plot = class {
6251
6705
  const i = this.layers.indexOf(layer);
6252
6706
  if (i >= 0) {
6253
6707
  this.layers.splice(i, 1);
6708
+ this.hidden.delete(layer.id);
6254
6709
  layer.dispose();
6255
6710
  this.autoscale();
6256
6711
  this.requestRender();
@@ -6387,6 +6842,7 @@ var Plot = class {
6387
6842
  if (this.autoX) {
6388
6843
  let minX = Infinity, maxX = -Infinity, any = false;
6389
6844
  for (const l of this.layers) {
6845
+ if (this.hidden.has(l.id)) continue;
6390
6846
  const b = l.bounds();
6391
6847
  if (!b) continue;
6392
6848
  any = true;
@@ -6399,7 +6855,7 @@ var Plot = class {
6399
6855
  if (!ya.auto) continue;
6400
6856
  let minY = Infinity, maxY = -Infinity, any = false;
6401
6857
  for (const l of this.layers) {
6402
- if (l.yAxis !== ya.id) continue;
6858
+ if (l.yAxis !== ya.id || this.hidden.has(l.id)) continue;
6403
6859
  const b = l.bounds();
6404
6860
  if (!b) continue;
6405
6861
  any = true;
@@ -6417,6 +6873,7 @@ var Plot = class {
6417
6873
  this.tooltip.remove();
6418
6874
  this.infoBox.remove();
6419
6875
  this.legendDiv.remove();
6876
+ this.colorbarDiv.remove();
6420
6877
  for (const l of this.layers) l.dispose();
6421
6878
  this.container.removeChild(this.gridCanvas);
6422
6879
  this.container.removeChild(this.dataCanvas);
@@ -6507,6 +6964,7 @@ var Plot = class {
6507
6964
  log: this.scaleX.log
6508
6965
  };
6509
6966
  for (const layer of this.layers) {
6967
+ if (this.hidden.has(layer.id)) continue;
6510
6968
  const ya = this.yAxes.get(layer.yAxis);
6511
6969
  layer.draw({
6512
6970
  gl,
@@ -6553,6 +7011,7 @@ var Plot = class {
6553
7011
  }
6554
7012
  this.updateInfoBox(region);
6555
7013
  this.updateLegend(region);
7014
+ this.updateColorbars(region);
6556
7015
  }
6557
7016
  /** Named series that can appear in the legend: any layer exposing name + colorCss. */
6558
7017
  legendEntries() {
@@ -6560,11 +7019,38 @@ var Plot = class {
6560
7019
  for (const l of this.layers) {
6561
7020
  const a = l;
6562
7021
  if (typeof a.name === "string" && a.name && a.name !== a.id && typeof a.colorCss === "string") {
6563
- out.push({ name: a.name, colorCss: a.colorCss });
7022
+ out.push({ layer: l, name: a.name, colorCss: a.colorCss });
6564
7023
  }
6565
7024
  }
6566
7025
  return out;
6567
7026
  }
7027
+ // ---- Series visibility ----------------------------------------------------
7028
+ /** Whether a layer is currently drawn (hidden layers also drop out of autoscale). */
7029
+ isLayerVisible(layer) {
7030
+ return !this.hidden.has(layer.id);
7031
+ }
7032
+ /** Show or hide a layer, re-fitting the auto axes to what remains visible. */
7033
+ setLayerVisible(layer, visible) {
7034
+ const changed = visible ? this.hidden.delete(layer.id) : (this.hidden.add(layer.id), true);
7035
+ if (!changed) return;
7036
+ this.autoscale();
7037
+ this.updateAria();
7038
+ this.requestRender();
7039
+ for (const cb of this.visibilityCbs) cb(layer, visible);
7040
+ }
7041
+ /** Flip a layer's visibility. Returns the new state. */
7042
+ toggleLayer(layer) {
7043
+ const next = !this.isLayerVisible(layer);
7044
+ this.setLayerVisible(layer, next);
7045
+ return next;
7046
+ }
7047
+ /** Subscribe to show/hide changes (legend clicks included). Returns an unsubscribe fn. */
7048
+ onVisibilityChange(cb) {
7049
+ this.visibilityCbs.push(cb);
7050
+ return () => {
7051
+ this.visibilityCbs = this.visibilityCbs.filter((f) => f !== cb);
7052
+ };
7053
+ }
6568
7054
  /** Rebuild and position the legend overlay (or hide it). */
6569
7055
  updateLegend(region) {
6570
7056
  const div = this.legendDiv;
@@ -6582,8 +7068,11 @@ var Plot = class {
6582
7068
  div.style.display = "flex";
6583
7069
  div.style.flexDirection = horizontal ? "row" : "column";
6584
7070
  div.style.gap = horizontal ? "12px" : "3px";
7071
+ const interactive = cfg.interactive !== false;
7072
+ div.style.pointerEvents = interactive ? "auto" : "none";
6585
7073
  div.replaceChildren();
6586
7074
  for (const e of entries) {
7075
+ const visible = this.isLayerVisible(e.layer);
6587
7076
  const row = document.createElement("div");
6588
7077
  row.style.display = "flex";
6589
7078
  row.style.alignItems = "center";
@@ -6593,13 +7082,31 @@ var Plot = class {
6593
7082
  width: "10px",
6594
7083
  height: "10px",
6595
7084
  borderRadius: "2px",
6596
- background: e.colorCss,
7085
+ background: visible ? e.colorCss : "transparent",
7086
+ border: `1px solid ${e.colorCss}`,
7087
+ boxSizing: "border-box",
6597
7088
  flex: "0 0 auto"
6598
7089
  });
6599
7090
  const label = document.createElement("span");
6600
7091
  label.textContent = e.name;
6601
7092
  row.appendChild(swatch);
6602
7093
  row.appendChild(label);
7094
+ if (interactive) {
7095
+ row.style.cursor = "pointer";
7096
+ row.style.opacity = visible ? "1" : "0.45";
7097
+ row.style.userSelect = "none";
7098
+ row.tabIndex = 0;
7099
+ row.setAttribute("role", "switch");
7100
+ row.setAttribute("aria-checked", String(visible));
7101
+ row.setAttribute("aria-label", `${e.name} series`);
7102
+ row.addEventListener("click", () => this.toggleLayer(e.layer));
7103
+ row.addEventListener("keydown", (ev) => {
7104
+ if (ev.key === "Enter" || ev.key === " ") {
7105
+ ev.preventDefault();
7106
+ this.toggleLayer(e.layer);
7107
+ }
7108
+ });
7109
+ }
6603
7110
  div.appendChild(row);
6604
7111
  }
6605
7112
  const inset = 8;
@@ -6772,7 +7279,7 @@ var Plot = class {
6772
7279
  }
6773
7280
  const rows = [];
6774
7281
  for (const layer of this.layers) {
6775
- if (!isPickable(layer)) continue;
7282
+ if (!isPickable(layer) || this.hidden.has(layer.id)) continue;
6776
7283
  const ya = this.yAxes.get(layer.yAxis);
6777
7284
  const project = (x, y) => [
6778
7285
  pxX(region, this.scaleX.norm(x)),
@@ -7111,7 +7618,7 @@ var Plot = class {
7111
7618
  let hit = null;
7112
7619
  let hitDist = Infinity;
7113
7620
  for (const layer of this.layers) {
7114
- if (!isPickable(layer)) continue;
7621
+ if (!isPickable(layer) || this.hidden.has(layer.id)) continue;
7115
7622
  const ya = this.yAxes.get(layer.yAxis);
7116
7623
  const project = (x, y) => [
7117
7624
  pxX(region, this.scaleX.norm(x)),
@@ -8123,6 +8630,183 @@ var Bar3DLayer = class {
8123
8630
  }
8124
8631
  };
8125
8632
 
8633
+ // src/plot3d/boxes3d.ts
8634
+ function buildCube2() {
8635
+ const faces = [
8636
+ { n: [1, 0, 0], v: [[0.5, -0.5, -0.5], [0.5, 0.5, -0.5], [0.5, 0.5, 0.5], [0.5, -0.5, -0.5], [0.5, 0.5, 0.5], [0.5, -0.5, 0.5]] },
8637
+ { n: [-1, 0, 0], v: [[-0.5, -0.5, 0.5], [-0.5, 0.5, 0.5], [-0.5, 0.5, -0.5], [-0.5, -0.5, 0.5], [-0.5, 0.5, -0.5], [-0.5, -0.5, -0.5]] },
8638
+ { n: [0, 1, 0], v: [[-0.5, 0.5, -0.5], [0.5, 0.5, -0.5], [0.5, 0.5, 0.5], [-0.5, 0.5, -0.5], [0.5, 0.5, 0.5], [-0.5, 0.5, 0.5]] },
8639
+ { n: [0, -1, 0], v: [[-0.5, -0.5, 0.5], [0.5, -0.5, 0.5], [0.5, -0.5, -0.5], [-0.5, -0.5, 0.5], [0.5, -0.5, -0.5], [-0.5, -0.5, -0.5]] },
8640
+ { n: [0, 0, 1], v: [[-0.5, -0.5, 0.5], [0.5, -0.5, 0.5], [0.5, 0.5, 0.5], [-0.5, -0.5, 0.5], [0.5, 0.5, 0.5], [-0.5, 0.5, 0.5]] },
8641
+ { n: [0, 0, -1], v: [[0.5, -0.5, -0.5], [-0.5, -0.5, -0.5], [-0.5, 0.5, -0.5], [0.5, -0.5, -0.5], [-0.5, 0.5, -0.5], [0.5, 0.5, -0.5]] }
8642
+ ];
8643
+ const out = [];
8644
+ for (const f of faces) for (const p of f.v) out.push(p[0], p[1], p[2], f.n[0], f.n[1], f.n[2]);
8645
+ return new Float32Array(out);
8646
+ }
8647
+ var CUBE2 = buildCube2();
8648
+ var CUBE_VERTS = CUBE2.length / 6;
8649
+ var VERT11 = (
8650
+ /* glsl */
8651
+ `#version 300 es
8652
+ precision highp float;
8653
+ layout(location = 0) in vec3 aCube; // unit cube centered on the origin
8654
+ layout(location = 1) in vec3 aNormal;
8655
+ layout(location = 2) in vec3 aCenter; // instance center
8656
+ layout(location = 3) in vec3 aSize; // instance full size
8657
+ layout(location = 4) in vec3 aColor;
8658
+ uniform mat4 uMVP;
8659
+ out vec3 vColor;
8660
+ out vec3 vN;
8661
+ void main() {
8662
+ vColor = aColor;
8663
+ // Box normals stay axis-aligned under a non-uniform scale, so pass them through.
8664
+ vN = aNormal;
8665
+ gl_Position = uMVP * vec4(aCenter + aCube * aSize, 1.0);
8666
+ }`
8667
+ );
8668
+ var FRAG14 = (
8669
+ /* glsl */
8670
+ `#version 300 es
8671
+ precision highp float;
8672
+ in vec3 vColor;
8673
+ in vec3 vN;
8674
+ uniform vec3 uLightDir;
8675
+ uniform float uAmbient;
8676
+ uniform float uOpacity;
8677
+ out vec4 outColor;
8678
+ void main() {
8679
+ float d = max(dot(normalize(vN), normalize(uLightDir)), 0.0);
8680
+ float shade = uAmbient + (1.0 - uAmbient) * d;
8681
+ outColor = vec4(vColor * shade * uOpacity, uOpacity);
8682
+ }`
8683
+ );
8684
+ var programCache15 = /* @__PURE__ */ new WeakMap();
8685
+ function getProgram12(gl) {
8686
+ let p = programCache15.get(gl);
8687
+ if (!p) {
8688
+ p = createProgram(gl, VERT11, FRAG14);
8689
+ programCache15.set(gl, p);
8690
+ }
8691
+ return p;
8692
+ }
8693
+ var INSTANCE_FLOATS = 9;
8694
+ var INSTANCE_STRIDE = INSTANCE_FLOATS * 4;
8695
+ var counter19 = 0;
8696
+ var Boxes3DLayer = class {
8697
+ constructor(gl, opts) {
8698
+ this.buffers = [];
8699
+ this.count = 0;
8700
+ this.b3 = { x: [0, 0], y: [0, 0], z: [0, 0] };
8701
+ this.positions = new Float32Array(0);
8702
+ this.labels = [];
8703
+ this.lightDir = [0.5, 1, 0.35];
8704
+ this.ambient = 0.35;
8705
+ this.id = `boxes3d-${counter19++}`;
8706
+ this.gl = gl;
8707
+ this.program = getProgram12(gl);
8708
+ this.name = opts.name;
8709
+ this.usage = bufferUsage(gl, opts.renderType);
8710
+ this.opacity = opts.opacity ?? 1;
8711
+ const ci = opts.color ?? "#60a5fa";
8712
+ this.base = Array.isArray(ci) ? ci : parseColor(ci);
8713
+ this.colorCss = typeof ci === "string" ? ci : toColorCss(this.base);
8714
+ this.vao = gl.createVertexArray();
8715
+ gl.bindVertexArray(this.vao);
8716
+ const cubeBuf = gl.createBuffer();
8717
+ gl.bindBuffer(gl.ARRAY_BUFFER, cubeBuf);
8718
+ gl.bufferData(gl.ARRAY_BUFFER, CUBE2, gl.STATIC_DRAW);
8719
+ gl.enableVertexAttribArray(0);
8720
+ gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 24, 0);
8721
+ gl.enableVertexAttribArray(1);
8722
+ gl.vertexAttribPointer(1, 3, gl.FLOAT, false, 24, 12);
8723
+ const instBuf = gl.createBuffer();
8724
+ gl.bindBuffer(gl.ARRAY_BUFFER, instBuf);
8725
+ for (let loc = 2; loc <= 4; loc++) {
8726
+ gl.enableVertexAttribArray(loc);
8727
+ gl.vertexAttribPointer(loc, 3, gl.FLOAT, false, INSTANCE_STRIDE, (loc - 2) * 12);
8728
+ gl.vertexAttribDivisor(loc, 1);
8729
+ }
8730
+ gl.bindVertexArray(null);
8731
+ this.buffers = [cubeBuf, instBuf];
8732
+ this.instBuf = instBuf;
8733
+ this.build(opts.boxes);
8734
+ this.uniforms = uniformLocations(gl, this.program, ["uMVP", "uLightDir", "uAmbient", "uOpacity"]);
8735
+ }
8736
+ /** Pack the instance attributes, recompute bounds/pick data, and upload. */
8737
+ build(boxes) {
8738
+ const n = boxes.length;
8739
+ this.count = n;
8740
+ const inst = new Float32Array(n * INSTANCE_FLOATS);
8741
+ this.positions = new Float32Array(n * 3);
8742
+ this.labels = new Array(n);
8743
+ let minX = Infinity, maxX = -Infinity;
8744
+ let minY = Infinity, maxY = -Infinity;
8745
+ let minZ = Infinity, maxZ = -Infinity;
8746
+ for (let i = 0; i < n; i++) {
8747
+ const b = boxes[i];
8748
+ const o = i * INSTANCE_FLOATS;
8749
+ inst[o] = b.x;
8750
+ inst[o + 1] = b.y;
8751
+ inst[o + 2] = b.z;
8752
+ inst[o + 3] = b.w;
8753
+ inst[o + 4] = b.h;
8754
+ inst[o + 5] = b.d;
8755
+ const rgba = b.color != null ? Array.isArray(b.color) ? b.color : parseColor(b.color) : this.base;
8756
+ inst[o + 6] = rgba[0];
8757
+ inst[o + 7] = rgba[1];
8758
+ inst[o + 8] = rgba[2];
8759
+ this.positions[i * 3] = b.x;
8760
+ this.positions[i * 3 + 1] = b.y;
8761
+ this.positions[i * 3 + 2] = b.z;
8762
+ this.labels[i] = b.label;
8763
+ const hw = b.w / 2, hh = b.h / 2, hd = b.d / 2;
8764
+ if (b.x - hw < minX) minX = b.x - hw;
8765
+ if (b.x + hw > maxX) maxX = b.x + hw;
8766
+ if (b.y - hh < minY) minY = b.y - hh;
8767
+ if (b.y + hh > maxY) maxY = b.y + hh;
8768
+ if (b.z - hd < minZ) minZ = b.z - hd;
8769
+ if (b.z + hd > maxZ) maxZ = b.z + hd;
8770
+ }
8771
+ this.b3 = n ? { x: [minX, maxX], y: [minY, maxY], z: [minZ, maxZ] } : { x: [0, 0], y: [0, 0], z: [0, 0] };
8772
+ this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.instBuf);
8773
+ this.gl.bufferData(this.gl.ARRAY_BUFFER, inst, this.usage);
8774
+ }
8775
+ /** Replace every box. Call `plot.refresh()` afterwards to re-fit + redraw. */
8776
+ setData(boxes) {
8777
+ this.build(boxes);
8778
+ }
8779
+ bounds3() {
8780
+ return this.count ? this.b3 : null;
8781
+ }
8782
+ pickData() {
8783
+ if (!this.count) return null;
8784
+ const labels = this.labels;
8785
+ const hasLabels = labels.some((l) => l != null);
8786
+ return hasLabels ? { positions: this.positions, label: (i) => labels[i] ?? "" } : { positions: this.positions };
8787
+ }
8788
+ /** Set the light direction (world space) and ambient term (0..1). */
8789
+ setLight(dir, ambient) {
8790
+ this.lightDir = dir;
8791
+ this.ambient = ambient;
8792
+ }
8793
+ draw(gl, mvp) {
8794
+ if (this.count === 0) return;
8795
+ gl.useProgram(this.program);
8796
+ gl.uniformMatrix4fv(this.uniforms.uMVP, false, mvp);
8797
+ gl.uniform3f(this.uniforms.uLightDir, this.lightDir[0], this.lightDir[1], this.lightDir[2]);
8798
+ gl.uniform1f(this.uniforms.uAmbient, this.ambient);
8799
+ gl.uniform1f(this.uniforms.uOpacity, this.opacity);
8800
+ gl.bindVertexArray(this.vao);
8801
+ gl.drawArraysInstanced(gl.TRIANGLES, 0, CUBE_VERTS, this.count);
8802
+ gl.bindVertexArray(null);
8803
+ }
8804
+ dispose() {
8805
+ this.gl.deleteVertexArray(this.vao);
8806
+ for (const b of this.buffers) this.gl.deleteBuffer(b);
8807
+ }
8808
+ };
8809
+
8126
8810
  // src/plot3d/contour3d.ts
8127
8811
  var CASES2 = [
8128
8812
  [],
@@ -8142,7 +8826,7 @@ var CASES2 = [
8142
8826
  [[0, 3]],
8143
8827
  []
8144
8828
  ];
8145
- var VERT11 = (
8829
+ var VERT12 = (
8146
8830
  /* glsl */
8147
8831
  `#version 300 es
8148
8832
  precision highp float;
@@ -8152,7 +8836,7 @@ uniform mat4 uMVP;
8152
8836
  out vec3 vColor;
8153
8837
  void main() { vColor = aColor; gl_Position = uMVP * vec4(aPos, 1.0); }`
8154
8838
  );
8155
- var FRAG14 = (
8839
+ var FRAG15 = (
8156
8840
  /* glsl */
8157
8841
  `#version 300 es
8158
8842
  precision highp float;
@@ -8160,22 +8844,22 @@ in vec3 vColor;
8160
8844
  out vec4 outColor;
8161
8845
  void main() { outColor = vec4(vColor, 1.0); }`
8162
8846
  );
8163
- var programCache15 = /* @__PURE__ */ new WeakMap();
8164
- function getProgram12(gl) {
8165
- let p = programCache15.get(gl);
8847
+ var programCache16 = /* @__PURE__ */ new WeakMap();
8848
+ function getProgram13(gl) {
8849
+ let p = programCache16.get(gl);
8166
8850
  if (!p) {
8167
- p = createProgram(gl, VERT11, FRAG14);
8168
- programCache15.set(gl, p);
8851
+ p = createProgram(gl, VERT12, FRAG15);
8852
+ programCache16.set(gl, p);
8169
8853
  }
8170
8854
  return p;
8171
8855
  }
8172
- var counter19 = 0;
8856
+ var counter20 = 0;
8173
8857
  var Contour3DLayer = class {
8174
8858
  constructor(gl, opts) {
8175
8859
  this.cInfo = null;
8176
- this.id = `contour3d-${counter19++}`;
8860
+ this.id = `contour3d-${counter20++}`;
8177
8861
  this.gl = gl;
8178
- this.program = getProgram12(gl);
8862
+ this.program = getProgram13(gl);
8179
8863
  this.name = opts.name;
8180
8864
  this.usage = bufferUsage(gl, opts.renderType);
8181
8865
  this.cmapName = opts.colormap ?? "viridis";
@@ -8885,7 +9569,7 @@ function marchingCubes(values, dims, isoLevel, extent) {
8885
9569
  }
8886
9570
 
8887
9571
  // src/plot3d/isosurface.ts
8888
- var VERT12 = (
9572
+ var VERT13 = (
8889
9573
  /* glsl */
8890
9574
  `#version 300 es
8891
9575
  precision highp float;
@@ -8895,7 +9579,7 @@ uniform mat4 uMVP;
8895
9579
  out vec3 vN;
8896
9580
  void main() { vN = aNormal; gl_Position = uMVP * vec4(aPos, 1.0); }`
8897
9581
  );
8898
- var FRAG15 = (
9582
+ var FRAG16 = (
8899
9583
  /* glsl */
8900
9584
  `#version 300 es
8901
9585
  precision highp float;
@@ -8912,23 +9596,23 @@ void main() {
8912
9596
  outColor = vec4(uColor * shade * uOpacity, uOpacity);
8913
9597
  }`
8914
9598
  );
8915
- var programCache16 = /* @__PURE__ */ new WeakMap();
8916
- function getProgram13(gl) {
8917
- let p = programCache16.get(gl);
9599
+ var programCache17 = /* @__PURE__ */ new WeakMap();
9600
+ function getProgram14(gl) {
9601
+ let p = programCache17.get(gl);
8918
9602
  if (!p) {
8919
- p = createProgram(gl, VERT12, FRAG15);
8920
- programCache16.set(gl, p);
9603
+ p = createProgram(gl, VERT13, FRAG16);
9604
+ programCache17.set(gl, p);
8921
9605
  }
8922
9606
  return p;
8923
9607
  }
8924
- var counter20 = 0;
9608
+ var counter21 = 0;
8925
9609
  var IsosurfaceLayer = class {
8926
9610
  constructor(gl, opts) {
8927
9611
  this.lightDir = [0.5, 1, 0.35];
8928
9612
  this.ambient = 0.35;
8929
- this.id = `isosurface-${counter20++}`;
9613
+ this.id = `isosurface-${counter21++}`;
8930
9614
  this.gl = gl;
8931
- this.program = getProgram13(gl);
9615
+ this.program = getProgram14(gl);
8932
9616
  this.name = opts.name;
8933
9617
  this.usage = bufferUsage(gl, opts.renderType);
8934
9618
  const ci = opts.color ?? "#38bdf8";
@@ -8998,7 +9682,7 @@ var IsosurfaceLayer = class {
8998
9682
  };
8999
9683
 
9000
9684
  // src/plot3d/line3d.ts
9001
- var VERT13 = (
9685
+ var VERT14 = (
9002
9686
  /* glsl */
9003
9687
  `#version 300 es
9004
9688
  precision highp float;
@@ -9006,7 +9690,7 @@ layout(location = 0) in vec3 aPos;
9006
9690
  uniform mat4 uMVP;
9007
9691
  void main() { gl_Position = uMVP * vec4(aPos, 1.0); }`
9008
9692
  );
9009
- var FRAG16 = (
9693
+ var FRAG17 = (
9010
9694
  /* glsl */
9011
9695
  `#version 300 es
9012
9696
  precision highp float;
@@ -9014,21 +9698,21 @@ uniform vec4 uColor;
9014
9698
  out vec4 outColor;
9015
9699
  void main() { outColor = uColor; }`
9016
9700
  );
9017
- var programCache17 = /* @__PURE__ */ new WeakMap();
9018
- function getProgram14(gl) {
9019
- let p = programCache17.get(gl);
9701
+ var programCache18 = /* @__PURE__ */ new WeakMap();
9702
+ function getProgram15(gl) {
9703
+ let p = programCache18.get(gl);
9020
9704
  if (!p) {
9021
- p = createProgram(gl, VERT13, FRAG16);
9022
- programCache17.set(gl, p);
9705
+ p = createProgram(gl, VERT14, FRAG17);
9706
+ programCache18.set(gl, p);
9023
9707
  }
9024
9708
  return p;
9025
9709
  }
9026
- var counter21 = 0;
9710
+ var counter22 = 0;
9027
9711
  var Line3DLayer = class {
9028
9712
  constructor(gl, opts) {
9029
- this.id = `line3d-${counter21++}`;
9713
+ this.id = `line3d-${counter22++}`;
9030
9714
  this.gl = gl;
9031
- this.program = getProgram14(gl);
9715
+ this.program = getProgram15(gl);
9032
9716
  this.usage = bufferUsage(gl, opts.renderType);
9033
9717
  this.name = opts.name;
9034
9718
  const ci = opts.color ?? "#38bdf8";
@@ -9134,6 +9818,15 @@ function perspective(fovy, aspect, near, far) {
9134
9818
  m[14] = 2 * far * near * nf;
9135
9819
  return m;
9136
9820
  }
9821
+ function orthographic(halfHeight, aspect, near, far) {
9822
+ const m = identity();
9823
+ const halfWidth = halfHeight * aspect;
9824
+ m[0] = 1 / halfWidth;
9825
+ m[5] = 1 / halfHeight;
9826
+ m[10] = 2 / (near - far);
9827
+ m[14] = (far + near) / (near - far);
9828
+ return m;
9829
+ }
9137
9830
  var sub = (a, b) => [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
9138
9831
  var cross = (a, b) => [
9139
9832
  a[1] * b[2] - a[2] * b[1],
@@ -9183,7 +9876,7 @@ function scaleTranslate(s, t) {
9183
9876
  }
9184
9877
 
9185
9878
  // src/plot3d/pointcloud.ts
9186
- var VERT14 = (
9879
+ var VERT15 = (
9187
9880
  /* glsl */
9188
9881
  `#version 300 es
9189
9882
  precision highp float;
@@ -9199,7 +9892,7 @@ void main() {
9199
9892
  gl_PointSize = aSize > 0.0 ? aSize : uSize;
9200
9893
  }`
9201
9894
  );
9202
- var FRAG17 = (
9895
+ var FRAG18 = (
9203
9896
  /* glsl */
9204
9897
  `#version 300 es
9205
9898
  precision highp float;
@@ -9211,23 +9904,23 @@ void main() {
9211
9904
  outColor = vec4(vColor, 1.0);
9212
9905
  }`
9213
9906
  );
9214
- var programCache18 = /* @__PURE__ */ new WeakMap();
9215
- function getProgram15(gl) {
9216
- let p = programCache18.get(gl);
9907
+ var programCache19 = /* @__PURE__ */ new WeakMap();
9908
+ function getProgram16(gl) {
9909
+ let p = programCache19.get(gl);
9217
9910
  if (!p) {
9218
- p = createProgram(gl, VERT14, FRAG17);
9219
- programCache18.set(gl, p);
9911
+ p = createProgram(gl, VERT15, FRAG18);
9912
+ programCache19.set(gl, p);
9220
9913
  }
9221
9914
  return p;
9222
9915
  }
9223
- var counter22 = 0;
9916
+ var counter23 = 0;
9224
9917
  var PointCloudLayer = class {
9225
9918
  constructor(gl, opts) {
9226
9919
  this.b3 = { x: [0, 0], y: [0, 0], z: [0, 0] };
9227
9920
  this.cInfo = null;
9228
- this.id = `points3d-${counter22++}`;
9921
+ this.id = `points3d-${counter23++}`;
9229
9922
  this.gl = gl;
9230
- this.program = getProgram15(gl);
9923
+ this.program = getProgram16(gl);
9231
9924
  this.usage = bufferUsage(gl, opts.renderType);
9232
9925
  this.size = opts.size ?? 4;
9233
9926
  this.name = opts.name;
@@ -9345,7 +10038,7 @@ var PointCloudLayer = class {
9345
10038
  };
9346
10039
 
9347
10040
  // src/plot3d/quiver3d.ts
9348
- var VERT15 = (
10041
+ var VERT16 = (
9349
10042
  /* glsl */
9350
10043
  `#version 300 es
9351
10044
  precision highp float;
@@ -9355,7 +10048,7 @@ uniform mat4 uMVP;
9355
10048
  out vec3 vColor;
9356
10049
  void main() { vColor = aColor; gl_Position = uMVP * vec4(aPos, 1.0); }`
9357
10050
  );
9358
- var FRAG18 = (
10051
+ var FRAG19 = (
9359
10052
  /* glsl */
9360
10053
  `#version 300 es
9361
10054
  precision highp float;
@@ -9363,12 +10056,12 @@ in vec3 vColor;
9363
10056
  out vec4 outColor;
9364
10057
  void main() { outColor = vec4(vColor, 1.0); }`
9365
10058
  );
9366
- var programCache19 = /* @__PURE__ */ new WeakMap();
9367
- function getProgram16(gl) {
9368
- let p = programCache19.get(gl);
10059
+ var programCache20 = /* @__PURE__ */ new WeakMap();
10060
+ function getProgram17(gl) {
10061
+ let p = programCache20.get(gl);
9369
10062
  if (!p) {
9370
- p = createProgram(gl, VERT15, FRAG18);
9371
- programCache19.set(gl, p);
10063
+ p = createProgram(gl, VERT16, FRAG19);
10064
+ programCache20.set(gl, p);
9372
10065
  }
9373
10066
  return p;
9374
10067
  }
@@ -9379,13 +10072,13 @@ function norm(x, y, z) {
9379
10072
  function cross2(a, b) {
9380
10073
  return [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]];
9381
10074
  }
9382
- var counter23 = 0;
10075
+ var counter24 = 0;
9383
10076
  var Quiver3DLayer = class {
9384
10077
  constructor(gl, opts) {
9385
10078
  this.cInfo = null;
9386
- this.id = `quiver3d-${counter23++}`;
10079
+ this.id = `quiver3d-${counter24++}`;
9387
10080
  this.gl = gl;
9388
- this.program = getProgram16(gl);
10081
+ this.program = getProgram17(gl);
9389
10082
  this.name = opts.name;
9390
10083
  this.usage = bufferUsage(gl, opts.renderType);
9391
10084
  this.scale = opts.scale ?? 1;
@@ -9495,7 +10188,7 @@ var Quiver3DLayer = class {
9495
10188
  };
9496
10189
 
9497
10190
  // src/plot3d/surface.ts
9498
- var VERT16 = (
10191
+ var VERT17 = (
9499
10192
  /* glsl */
9500
10193
  `#version 300 es
9501
10194
  precision highp float;
@@ -9507,7 +10200,7 @@ out vec3 vColor;
9507
10200
  out vec3 vN;
9508
10201
  void main() { vColor = aColor; vN = aNormal; gl_Position = uMVP * vec4(aPos, 1.0); }`
9509
10202
  );
9510
- var FRAG19 = (
10203
+ var FRAG20 = (
9511
10204
  /* glsl */
9512
10205
  `#version 300 es
9513
10206
  precision highp float;
@@ -9540,12 +10233,12 @@ in vec3 vColor;
9540
10233
  out vec4 outColor;
9541
10234
  void main() { outColor = vec4(vColor, 1.0); }`
9542
10235
  );
9543
- var programCache20 = /* @__PURE__ */ new WeakMap();
9544
- function getProgram17(gl) {
9545
- let p = programCache20.get(gl);
10236
+ var programCache21 = /* @__PURE__ */ new WeakMap();
10237
+ function getProgram18(gl) {
10238
+ let p = programCache21.get(gl);
9546
10239
  if (!p) {
9547
- p = createProgram(gl, VERT16, FRAG19);
9548
- programCache20.set(gl, p);
10240
+ p = createProgram(gl, VERT17, FRAG20);
10241
+ programCache21.set(gl, p);
9549
10242
  }
9550
10243
  return p;
9551
10244
  }
@@ -9558,16 +10251,16 @@ function getWireProgram(gl) {
9558
10251
  }
9559
10252
  return p;
9560
10253
  }
9561
- var counter24 = 0;
10254
+ var counter25 = 0;
9562
10255
  var SurfaceLayer = class {
9563
10256
  constructor(gl, opts) {
9564
10257
  this.vDomain = [0, 1];
9565
10258
  this.lightDir = [0.5, 1, 0.35];
9566
10259
  this.ambient = 0.35;
9567
- this.id = `surface-${counter24++}`;
10260
+ this.id = `surface-${counter25++}`;
9568
10261
  this.gl = gl;
9569
10262
  this.wireframe = opts.wireframe ?? false;
9570
- this.program = this.wireframe ? getWireProgram(gl) : getProgram17(gl);
10263
+ this.program = this.wireframe ? getWireProgram(gl) : getProgram18(gl);
9571
10264
  this.name = opts.name;
9572
10265
  this.cmapName = opts.colormap ?? "viridis";
9573
10266
  this.usage = bufferUsage(gl, opts.renderType);
@@ -9703,7 +10396,7 @@ var SurfaceLayer = class {
9703
10396
 
9704
10397
  // src/plot3d/volume.ts
9705
10398
  var STEPS = 160;
9706
- var VERT17 = (
10399
+ var VERT18 = (
9707
10400
  /* glsl */
9708
10401
  `#version 300 es
9709
10402
  precision highp float;
@@ -9713,7 +10406,7 @@ uniform mat4 uMVP;
9713
10406
  out vec3 vLocal;
9714
10407
  void main() { vLocal = aLocal; gl_Position = uMVP * vec4(aPos, 1.0); }`
9715
10408
  );
9716
- var FRAG20 = (
10409
+ var FRAG21 = (
9717
10410
  /* glsl */
9718
10411
  `#version 300 es
9719
10412
  precision highp float;
@@ -9751,12 +10444,12 @@ void main() {
9751
10444
  outColor = acc; // premultiplied
9752
10445
  }`
9753
10446
  );
9754
- var programCache21 = /* @__PURE__ */ new WeakMap();
9755
- function getProgram18(gl) {
9756
- let p = programCache21.get(gl);
10447
+ var programCache22 = /* @__PURE__ */ new WeakMap();
10448
+ function getProgram19(gl) {
10449
+ let p = programCache22.get(gl);
9757
10450
  if (!p) {
9758
- p = createProgram(gl, VERT17, FRAG20);
9759
- programCache21.set(gl, p);
10451
+ p = createProgram(gl, VERT18, FRAG21);
10452
+ programCache22.set(gl, p);
9760
10453
  }
9761
10454
  return p;
9762
10455
  }
@@ -9778,14 +10471,14 @@ var CUBE_FACES = [
9778
10471
  [0, 3, 7, 0, 7, 4],
9779
10472
  [1, 5, 6, 1, 6, 2]
9780
10473
  ];
9781
- var counter25 = 0;
10474
+ var counter26 = 0;
9782
10475
  var VolumeLayer = class {
9783
10476
  constructor(gl, opts) {
9784
10477
  this.vDomain = [0, 1];
9785
10478
  this.camLocal = [0.5, 0.5, 2];
9786
- this.id = `volume-${counter25++}`;
10479
+ this.id = `volume-${counter26++}`;
9787
10480
  this.gl = gl;
9788
- this.program = getProgram18(gl);
10481
+ this.program = getProgram19(gl);
9789
10482
  this.name = opts.name;
9790
10483
  this.usage = bufferUsage(gl, opts.renderType);
9791
10484
  this.density = opts.density ?? 1;
@@ -10018,6 +10711,8 @@ var Plot3D = class {
10018
10711
  this.frameRequested = false;
10019
10712
  this.tickCount = 0;
10020
10713
  this.labels = [];
10714
+ /** User labels anchored in data space (see {@link addLabel3D}). */
10715
+ this.dataLabels = [];
10021
10716
  /** Tick positions in cube space [-1,1], per axis, for the grid planes. */
10022
10717
  this.tickCube = { x: [], y: [], z: [] };
10023
10718
  this.gridVao = null;
@@ -10059,6 +10754,9 @@ var Plot3D = class {
10059
10754
  this.colorbarOpt = options.colorbar ?? true;
10060
10755
  this.hoverEnabled = options.hover !== false;
10061
10756
  this.gridPlanes = options.gridPlanes !== false;
10757
+ this.aspectMode = options.aspectMode ?? "cube";
10758
+ this.showAxes = options.showAxes !== false;
10759
+ this.projection = options.projection ?? "perspective";
10062
10760
  this.legendDiv = document.createElement("div");
10063
10761
  Object.assign(this.legendDiv.style, {
10064
10762
  position: "absolute",
@@ -10214,6 +10912,15 @@ var Plot3D = class {
10214
10912
  this.requestRender();
10215
10913
  return l;
10216
10914
  }
10915
+ /** Independently sized lit cuboids (voxels, bounding boxes, model layer blocks). */
10916
+ addBoxes3D(opts) {
10917
+ const l = new Boxes3DLayer(this.gl, opts);
10918
+ l.setLight(this.lightDir(), this.ambient);
10919
+ this.layers.push(l);
10920
+ this.recompute();
10921
+ this.requestRender();
10922
+ return l;
10923
+ }
10217
10924
  /** A 3D vector field (arrows), optionally colored by magnitude. */
10218
10925
  addQuiver3D(opts) {
10219
10926
  const l = new Quiver3DLayer(this.gl, opts);
@@ -10247,6 +10954,28 @@ var Plot3D = class {
10247
10954
  this.requestRender();
10248
10955
  return l;
10249
10956
  }
10957
+ /**
10958
+ * Pin a text label to a point in data space. It is re-projected every frame,
10959
+ * so it tracks the point as the camera orbits. Returns a disposer that removes
10960
+ * just this label.
10961
+ */
10962
+ addLabel3D(label) {
10963
+ this.dataLabels.push(label);
10964
+ this.requestRender();
10965
+ return () => {
10966
+ const i = this.dataLabels.indexOf(label);
10967
+ if (i >= 0) {
10968
+ this.dataLabels.splice(i, 1);
10969
+ this.requestRender();
10970
+ }
10971
+ };
10972
+ }
10973
+ /** Remove every data-space label. */
10974
+ clearLabels3D() {
10975
+ if (this.dataLabels.length === 0) return;
10976
+ this.dataLabels = [];
10977
+ this.requestRender();
10978
+ }
10250
10979
  /** Update the light direction (azimuth/elevation, radians) and ambient (0..1). */
10251
10980
  setLight(params) {
10252
10981
  if (params.azimuth != null) this.lightAz = params.azimuth;
@@ -10429,7 +11158,9 @@ var Plot3D = class {
10429
11158
  }
10430
11159
  if (!b) return;
10431
11160
  this.dataBounds = b;
11161
+ const shared2 = this.aspectMode === "data" ? 2 / (Math.max(b.x[1] - b.x[0], b.y[1] - b.y[0], b.z[1] - b.z[0]) || 1) : 0;
10432
11162
  const axis = (r) => {
11163
+ if (shared2) return [shared2, -shared2 * (r[0] + r[1]) / 2];
10433
11164
  const span = r[1] - r[0];
10434
11165
  return span === 0 ? [1, -r[0]] : [2 / span, -(r[1] + r[0]) / span];
10435
11166
  };
@@ -10491,12 +11222,13 @@ var Plot3D = class {
10491
11222
  }
10492
11223
  viewProj() {
10493
11224
  const aspect = this.canvas.width / Math.max(1, this.canvas.height);
10494
- const proj = perspective(50 * Math.PI / 180, aspect, 0.01, 100);
11225
+ const proj = this.projection === "orthographic" ? orthographic(this.distance / 2, aspect, -100, 100) : perspective(50 * Math.PI / 180, aspect, 0.01, 100);
10495
11226
  const el = Math.max(-1.5, Math.min(1.5, this.elevation));
11227
+ const radius = this.projection === "orthographic" ? 8 : this.distance;
10496
11228
  const eye = [
10497
- this.distance * Math.cos(el) * Math.sin(this.azimuth),
10498
- this.distance * Math.sin(el),
10499
- this.distance * Math.cos(el) * Math.cos(this.azimuth)
11229
+ radius * Math.cos(el) * Math.sin(this.azimuth),
11230
+ radius * Math.sin(el),
11231
+ radius * Math.cos(el) * Math.cos(this.azimuth)
10500
11232
  ];
10501
11233
  const view = lookAt(eye, [0, 0, 0], [0, 1, 0]);
10502
11234
  const vp = multiply(proj, view);
@@ -10538,13 +11270,15 @@ var Plot3D = class {
10538
11270
  gl.useProgram(this.lineProgram);
10539
11271
  if (this.gridPlanes) this.drawGridPlanes(vp, eye);
10540
11272
  gl.uniformMatrix4fv(this.lineUniforms.uVP, false, vp);
10541
- gl.uniform4f(this.lineUniforms.uColor, 0.6, 0.65, 0.75, 0.4);
10542
- gl.bindVertexArray(this.boxVao);
10543
- gl.drawArrays(gl.LINES, 0, BOX_EDGES.length / 3);
10544
- if (this.tickCount > 0) {
10545
- gl.uniform4f(this.lineUniforms.uColor, 0.75, 0.8, 0.9, 0.8);
10546
- gl.bindVertexArray(this.tickVao);
10547
- gl.drawArrays(gl.LINES, 0, this.tickCount);
11273
+ if (this.showAxes) {
11274
+ gl.uniform4f(this.lineUniforms.uColor, 0.6, 0.65, 0.75, 0.4);
11275
+ gl.bindVertexArray(this.boxVao);
11276
+ gl.drawArrays(gl.LINES, 0, BOX_EDGES.length / 3);
11277
+ if (this.tickCount > 0) {
11278
+ gl.uniform4f(this.lineUniforms.uColor, 0.75, 0.8, 0.9, 0.8);
11279
+ gl.bindVertexArray(this.tickVao);
11280
+ gl.drawArrays(gl.LINES, 0, this.tickCount);
11281
+ }
10548
11282
  }
10549
11283
  gl.bindVertexArray(null);
10550
11284
  const n = this.norm;
@@ -10556,6 +11290,7 @@ var Plot3D = class {
10556
11290
  this.displayCtx.clearRect(0, 0, w, h);
10557
11291
  this.displayCtx.drawImage(this.sharedCanvas, 0, 0);
10558
11292
  this.drawLabels(vp, w, h);
11293
+ this.drawDataLabels(mvp, w, h);
10559
11294
  if (this.title) {
10560
11295
  const ctx = this.displayCtx;
10561
11296
  ctx.save();
@@ -10661,8 +11396,38 @@ var Plot3D = class {
10661
11396
  div.appendChild(rowWrap);
10662
11397
  div.style.display = "block";
10663
11398
  }
11399
+ /**
11400
+ * Draw the user's data-space labels. Coordinates come out of the projection in
11401
+ * device px; we scale the context by dpr instead so the caller's `font` string
11402
+ * means CSS px, and outline each label so it stays readable over any fill.
11403
+ */
11404
+ drawDataLabels(mvp, w, h) {
11405
+ if (this.dataLabels.length === 0) return;
11406
+ const ctx = this.displayCtx;
11407
+ const dpr = this.dpr;
11408
+ ctx.save();
11409
+ ctx.scale(dpr, dpr);
11410
+ ctx.lineJoin = "round";
11411
+ for (const l of this.dataLabels) {
11412
+ const c = transformPoint(mvp, l.x, l.y, l.z);
11413
+ if (c[3] <= 0) continue;
11414
+ const sx = (c[0] / c[3] * 0.5 + 0.5) * w;
11415
+ const sy = (1 - (c[1] / c[3] * 0.5 + 0.5)) * h;
11416
+ ctx.font = l.font ?? "11px system-ui, -apple-system, sans-serif";
11417
+ ctx.textAlign = l.align ?? "center";
11418
+ ctx.textBaseline = l.baseline ?? "middle";
11419
+ const x = sx / dpr + (l.dx ?? 0);
11420
+ const y = sy / dpr + (l.dy ?? 0);
11421
+ ctx.lineWidth = 3;
11422
+ ctx.strokeStyle = "rgba(2,6,23,0.75)";
11423
+ ctx.strokeText(l.text, x, y);
11424
+ ctx.fillStyle = l.color ?? "#e2e8f0";
11425
+ ctx.fillText(l.text, x, y);
11426
+ }
11427
+ ctx.restore();
11428
+ }
10664
11429
  drawLabels(vp, w, h) {
10665
- if (this.labels.length === 0) return;
11430
+ if (!this.showAxes || this.labels.length === 0) return;
10666
11431
  const ctx = this.displayCtx;
10667
11432
  ctx.save();
10668
11433
  ctx.textAlign = "center";
@@ -10770,6 +11535,352 @@ var Plot3D = class {
10770
11535
  }
10771
11536
  };
10772
11537
 
11538
+ // src/stats/signal.ts
11539
+ function windowFunction(name, n) {
11540
+ const w = new Float64Array(n);
11541
+ if (n === 0) return w;
11542
+ if (n === 1) {
11543
+ w[0] = 1;
11544
+ return w;
11545
+ }
11546
+ const d = n - 1;
11547
+ for (let i = 0; i < n; i++) {
11548
+ const t = 2 * Math.PI * i / d;
11549
+ switch (name) {
11550
+ case "rectangular":
11551
+ w[i] = 1;
11552
+ break;
11553
+ case "hamming":
11554
+ w[i] = 0.54 - 0.46 * Math.cos(t);
11555
+ break;
11556
+ case "blackman":
11557
+ w[i] = 0.42 - 0.5 * Math.cos(t) + 0.08 * Math.cos(2 * t);
11558
+ break;
11559
+ case "bartlett":
11560
+ w[i] = 1 - Math.abs((i - d / 2) / (d / 2));
11561
+ break;
11562
+ default:
11563
+ w[i] = 0.5 - 0.5 * Math.cos(t);
11564
+ break;
11565
+ }
11566
+ }
11567
+ return w;
11568
+ }
11569
+ function welch(signal, opts = {}) {
11570
+ const sr = opts.sampleRate ?? 1;
11571
+ const requested = opts.segment ?? 256;
11572
+ let size = 1;
11573
+ while (size * 2 <= Math.min(requested, signal.length)) size *= 2;
11574
+ const bins = size >> 1;
11575
+ if (bins < 1) return { frequencies: new Float64Array(0), power: new Float64Array(0) };
11576
+ const overlap = Math.min(0.95, Math.max(0, opts.overlap ?? 0.5));
11577
+ const step = Math.max(1, Math.round(size * (1 - overlap)));
11578
+ const win = windowFunction(opts.window ?? "hann", size);
11579
+ let winPower = 0;
11580
+ for (let i = 0; i < size; i++) winPower += win[i] * win[i];
11581
+ const norm2 = 1 / (sr * winPower);
11582
+ const power = new Float64Array(bins);
11583
+ const re = new Float64Array(size);
11584
+ const im = new Float64Array(size);
11585
+ let segments = 0;
11586
+ for (let start = 0; start + size <= signal.length; start += step) {
11587
+ for (let i = 0; i < size; i++) {
11588
+ re[i] = signal[start + i] * win[i];
11589
+ im[i] = 0;
11590
+ }
11591
+ fft(re, im);
11592
+ for (let b = 0; b < bins; b++) {
11593
+ const scale = b === 0 ? 1 : 2;
11594
+ power[b] += (re[b] * re[b] + im[b] * im[b]) * norm2 * scale;
11595
+ }
11596
+ segments++;
11597
+ }
11598
+ if (segments > 1) for (let b = 0; b < bins; b++) power[b] /= segments;
11599
+ const frequencies = new Float64Array(bins);
11600
+ for (let b = 0; b < bins; b++) frequencies[b] = b * sr / size;
11601
+ return { frequencies, power };
11602
+ }
11603
+ function savitzkyGolay(values, window2 = 9, order = 2) {
11604
+ const n = values.length;
11605
+ const out = new Float64Array(n);
11606
+ let m = Math.max(3, Math.floor(window2));
11607
+ if (m % 2 === 0) m += 1;
11608
+ const half = m - 1 >> 1;
11609
+ const deg = Math.max(0, Math.min(order, m - 1));
11610
+ if (n === 0) return out;
11611
+ if (n < m) {
11612
+ for (let i = 0; i < n; i++) out[i] = values[i];
11613
+ return out;
11614
+ }
11615
+ const size = deg + 1;
11616
+ const ata = Array.from({ length: size }, () => new Array(size).fill(0));
11617
+ for (let t = -half; t <= half; t++) {
11618
+ const powers = new Array(size);
11619
+ powers[0] = 1;
11620
+ for (let p = 1; p < size; p++) powers[p] = powers[p - 1] * t;
11621
+ for (let r = 0; r < size; r++) for (let c = 0; c < size; c++) ata[r][c] += powers[r] * powers[c];
11622
+ }
11623
+ const aug = ata.map((row, r) => [...row, r === 0 ? 1 : 0]);
11624
+ for (let c = 0; c < size; c++) {
11625
+ let pivot = c;
11626
+ for (let r = c + 1; r < size; r++) if (Math.abs(aug[r][c]) > Math.abs(aug[pivot][c])) pivot = r;
11627
+ if (Math.abs(aug[pivot][c]) < 1e-12) continue;
11628
+ [aug[c], aug[pivot]] = [aug[pivot], aug[c]];
11629
+ const p = aug[c][c];
11630
+ for (let k = c; k <= size; k++) aug[c][k] /= p;
11631
+ for (let r = 0; r < size; r++) {
11632
+ if (r === c) continue;
11633
+ const f = aug[r][c];
11634
+ if (f === 0) continue;
11635
+ for (let k = c; k <= size; k++) aug[r][k] -= f * aug[c][k];
11636
+ }
11637
+ }
11638
+ const coef = aug.map((row) => row[size]);
11639
+ const weights = new Float64Array(m);
11640
+ for (let t = -half, i = 0; t <= half; t++, i++) {
11641
+ let w = 0;
11642
+ let tp = 1;
11643
+ for (let p = 0; p < size; p++) {
11644
+ w += coef[p] * tp;
11645
+ tp *= t;
11646
+ }
11647
+ weights[i] = w;
11648
+ }
11649
+ for (let i = 0; i < n; i++) {
11650
+ let acc = 0;
11651
+ for (let k = 0; k < m; k++) {
11652
+ const idx = Math.min(n - 1, Math.max(0, i + k - half));
11653
+ acc += weights[k] * values[idx];
11654
+ }
11655
+ out[i] = acc;
11656
+ }
11657
+ return out;
11658
+ }
11659
+ function crossCorrelate(a, b, maxLag = Math.min(a.length, b.length) - 1, normalize4 = true) {
11660
+ const n = Math.min(a.length, b.length);
11661
+ const lag = Math.max(0, Math.min(Math.floor(maxLag), n - 1));
11662
+ let meanA = 0;
11663
+ let meanB = 0;
11664
+ for (let i = 0; i < n; i++) {
11665
+ meanA += a[i];
11666
+ meanB += b[i];
11667
+ }
11668
+ meanA /= n || 1;
11669
+ meanB /= n || 1;
11670
+ let varA = 0;
11671
+ let varB = 0;
11672
+ for (let i = 0; i < n; i++) {
11673
+ varA += (a[i] - meanA) ** 2;
11674
+ varB += (b[i] - meanB) ** 2;
11675
+ }
11676
+ const denom = normalize4 ? Math.sqrt(varA * varB) || 1 : 1;
11677
+ const count = 2 * lag + 1;
11678
+ const lags = new Int32Array(count);
11679
+ const values = new Float64Array(count);
11680
+ for (let k = -lag, j = 0; k <= lag; k++, j++) {
11681
+ let acc = 0;
11682
+ for (let i = 0; i < n; i++) {
11683
+ const bi = i + k;
11684
+ if (bi < 0 || bi >= n) continue;
11685
+ acc += (a[i] - meanA) * (b[bi] - meanB);
11686
+ }
11687
+ lags[j] = k;
11688
+ values[j] = acc / denom;
11689
+ }
11690
+ return { lags, values };
11691
+ }
11692
+
11693
+ // src/stats/regression.ts
11694
+ function linearRegression(x, y) {
11695
+ const len = Math.min(x.length, y.length);
11696
+ let n = 0;
11697
+ let sx = 0;
11698
+ let sy = 0;
11699
+ for (let i = 0; i < len; i++) {
11700
+ const xi = x[i];
11701
+ const yi = y[i];
11702
+ if (!Number.isFinite(xi) || !Number.isFinite(yi)) continue;
11703
+ sx += xi;
11704
+ sy += yi;
11705
+ n++;
11706
+ }
11707
+ if (n < 2) {
11708
+ const intercept2 = n === 1 ? sy : 0;
11709
+ return { slope: 0, intercept: intercept2, r2: 0, stderr: 0, n, predict: () => intercept2 };
11710
+ }
11711
+ const mx = sx / n;
11712
+ const my = sy / n;
11713
+ let sxx = 0;
11714
+ let sxy = 0;
11715
+ let syy = 0;
11716
+ for (let i = 0; i < len; i++) {
11717
+ const xi = x[i];
11718
+ const yi = y[i];
11719
+ if (!Number.isFinite(xi) || !Number.isFinite(yi)) continue;
11720
+ const dx = xi - mx;
11721
+ const dy = yi - my;
11722
+ sxx += dx * dx;
11723
+ sxy += dx * dy;
11724
+ syy += dy * dy;
11725
+ }
11726
+ const slope = sxx === 0 ? 0 : sxy / sxx;
11727
+ const intercept = my - slope * mx;
11728
+ const ssRes = Math.max(0, syy - slope * sxy);
11729
+ const r22 = syy === 0 ? 1 : 1 - ssRes / syy;
11730
+ const stderr = n > 2 ? Math.sqrt(ssRes / (n - 2)) : 0;
11731
+ return { slope, intercept, r2: r22, stderr, n, predict: (v) => slope * v + intercept };
11732
+ }
11733
+ function linearTrend(x, y, opts = {}) {
11734
+ const fit = linearRegression(x, y);
11735
+ const points = Math.max(2, opts.points ?? 2);
11736
+ let lo = Infinity;
11737
+ let hi = -Infinity;
11738
+ for (let i = 0; i < x.length; i++) {
11739
+ const v = x[i];
11740
+ if (!Number.isFinite(v)) continue;
11741
+ if (v < lo) lo = v;
11742
+ if (v > hi) hi = v;
11743
+ }
11744
+ if (!Number.isFinite(lo)) {
11745
+ lo = 0;
11746
+ hi = 1;
11747
+ }
11748
+ const gx = new Float64Array(points);
11749
+ const gy = new Float64Array(points);
11750
+ for (let i = 0; i < points; i++) {
11751
+ gx[i] = lo + (hi - lo) * i / (points - 1);
11752
+ gy[i] = fit.predict(gx[i]);
11753
+ }
11754
+ if (!opts.band) return { x: gx, y: gy };
11755
+ const k = opts.band;
11756
+ const lower = new Float64Array(points);
11757
+ const upper = new Float64Array(points);
11758
+ for (let i = 0; i < points; i++) {
11759
+ lower[i] = gy[i] - k * fit.stderr;
11760
+ upper[i] = gy[i] + k * fit.stderr;
11761
+ }
11762
+ return { x: gx, y: gy, lower, upper };
11763
+ }
11764
+ function loess(x, y, opts = {}) {
11765
+ const len = Math.min(x.length, y.length);
11766
+ const pairs = [];
11767
+ for (let i = 0; i < len; i++) {
11768
+ const xi = x[i];
11769
+ const yi = y[i];
11770
+ if (Number.isFinite(xi) && Number.isFinite(yi)) pairs.push([xi, yi]);
11771
+ }
11772
+ pairs.sort((a, b) => a[0] - b[0]);
11773
+ const n = pairs.length;
11774
+ const grid = Math.max(2, Math.min(opts.points ?? 100, Math.max(2, n)));
11775
+ const gx = new Float64Array(grid);
11776
+ const gy = new Float64Array(grid);
11777
+ if (n === 0) return { x: gx, y: gy };
11778
+ const bandwidth = Math.min(1, Math.max(2 / n, opts.bandwidth ?? 0.3));
11779
+ const span = Math.max(2, Math.round(bandwidth * n));
11780
+ const lo = pairs[0][0];
11781
+ const hi = pairs[n - 1][0];
11782
+ for (let g = 0; g < grid; g++) {
11783
+ const x0 = grid === 1 ? lo : lo + (hi - lo) * g / (grid - 1);
11784
+ gx[g] = x0;
11785
+ const dists = pairs.map(([px], i) => [Math.abs(px - x0), i]);
11786
+ dists.sort((a, b) => a[0] - b[0]);
11787
+ const maxDist = dists[Math.min(span, n) - 1][0] || 1e-12;
11788
+ let sw = 0, swx = 0, swy = 0, swxx = 0, swxy = 0;
11789
+ for (let k = 0; k < Math.min(span, n); k++) {
11790
+ const [d, idx] = dists[k];
11791
+ const u = d / maxDist;
11792
+ const w = u >= 1 ? 0 : (1 - u * u * u) ** 3;
11793
+ if (w === 0) continue;
11794
+ const [px, py] = pairs[idx];
11795
+ sw += w;
11796
+ swx += w * px;
11797
+ swy += w * py;
11798
+ swxx += w * px * px;
11799
+ swxy += w * px * py;
11800
+ }
11801
+ if (sw === 0) {
11802
+ gy[g] = pairs[Math.min(n - 1, g)][1];
11803
+ continue;
11804
+ }
11805
+ const denom = sw * swxx - swx * swx;
11806
+ if (Math.abs(denom) < 1e-12) {
11807
+ gy[g] = swy / sw;
11808
+ continue;
11809
+ }
11810
+ const slope = (sw * swxy - swx * swy) / denom;
11811
+ const intercept = (swy - slope * swx) / sw;
11812
+ gy[g] = slope * x0 + intercept;
11813
+ }
11814
+ return { x: gx, y: gy };
11815
+ }
11816
+ function ecdf(values) {
11817
+ const xs = [];
11818
+ for (let i = 0; i < values.length; i++) if (Number.isFinite(values[i])) xs.push(values[i]);
11819
+ xs.sort((a, b) => a - b);
11820
+ const n = xs.length;
11821
+ const x = new Float64Array(n);
11822
+ const y = new Float64Array(n);
11823
+ for (let i = 0; i < n; i++) {
11824
+ x[i] = xs[i];
11825
+ y[i] = (i + 1) / n;
11826
+ }
11827
+ return { x, y };
11828
+ }
11829
+ function zscore(values) {
11830
+ const n = values.length;
11831
+ const out = new Float64Array(n);
11832
+ let count = 0;
11833
+ let mean = 0;
11834
+ for (let i = 0; i < n; i++) if (Number.isFinite(values[i])) {
11835
+ mean += values[i];
11836
+ count++;
11837
+ }
11838
+ if (count === 0) return out;
11839
+ mean /= count;
11840
+ let varSum = 0;
11841
+ for (let i = 0; i < n; i++) if (Number.isFinite(values[i])) varSum += (values[i] - mean) ** 2;
11842
+ const sd = Math.sqrt(varSum / count) || 1;
11843
+ for (let i = 0; i < n; i++) out[i] = Number.isFinite(values[i]) ? (values[i] - mean) / sd : values[i];
11844
+ return out;
11845
+ }
11846
+ function correlation(a, b) {
11847
+ const n = Math.min(a.length, b.length);
11848
+ let ma = 0, mb = 0, count = 0;
11849
+ for (let i = 0; i < n; i++) {
11850
+ if (!Number.isFinite(a[i]) || !Number.isFinite(b[i])) continue;
11851
+ ma += a[i];
11852
+ mb += b[i];
11853
+ count++;
11854
+ }
11855
+ if (count < 2) return 0;
11856
+ ma /= count;
11857
+ mb /= count;
11858
+ let saa = 0, sbb = 0, sab = 0;
11859
+ for (let i = 0; i < n; i++) {
11860
+ if (!Number.isFinite(a[i]) || !Number.isFinite(b[i])) continue;
11861
+ const da = a[i] - ma;
11862
+ const db = b[i] - mb;
11863
+ saa += da * da;
11864
+ sbb += db * db;
11865
+ sab += da * db;
11866
+ }
11867
+ const denom = Math.sqrt(saa * sbb);
11868
+ return denom === 0 ? 0 : sab / denom;
11869
+ }
11870
+ function corrMatrix(columns) {
11871
+ const k = columns.length;
11872
+ const values = new Float64Array(k * k);
11873
+ for (let i = 0; i < k; i++) {
11874
+ values[i * k + i] = 1;
11875
+ for (let j = i + 1; j < k; j++) {
11876
+ const r = correlation(columns[i], columns[j]);
11877
+ values[i * k + j] = r;
11878
+ values[j * k + i] = r;
11879
+ }
11880
+ }
11881
+ return { values, size: k };
11882
+ }
11883
+
10773
11884
  // src/finance/indicators.ts
10774
11885
  function sma(values, period) {
10775
11886
  const n = values.length;
@@ -11060,6 +12171,137 @@ function fibRetracements(high, low, ratios = [0, 0.236, 0.382, 0.5, 0.618, 0.786
11060
12171
  const span = high - low;
11061
12172
  return ratios.map((r) => ({ ratio: r, price: high - span * r }));
11062
12173
  }
12174
+ function cci(high, low, close, period = 20) {
12175
+ const n = Math.min(high.length, low.length, close.length);
12176
+ const out = new Float64Array(n).fill(NaN);
12177
+ if (period <= 0 || n < period) return out;
12178
+ const tp = new Float64Array(n);
12179
+ for (let i = 0; i < n; i++) tp[i] = (high[i] + low[i] + close[i]) / 3;
12180
+ const avg = sma(tp, period);
12181
+ for (let i = period - 1; i < n; i++) {
12182
+ let dev = 0;
12183
+ for (let k = i - period + 1; k <= i; k++) dev += Math.abs(tp[k] - avg[i]);
12184
+ dev /= period;
12185
+ out[i] = dev === 0 ? 0 : (tp[i] - avg[i]) / (0.015 * dev);
12186
+ }
12187
+ return out;
12188
+ }
12189
+ function mfi(high, low, close, volume, period = 14) {
12190
+ const n = Math.min(high.length, low.length, close.length, volume.length);
12191
+ const out = new Float64Array(n).fill(NaN);
12192
+ if (period <= 0 || n <= period) return out;
12193
+ const tp = new Float64Array(n);
12194
+ for (let i = 0; i < n; i++) tp[i] = (high[i] + low[i] + close[i]) / 3;
12195
+ for (let i = period; i < n; i++) {
12196
+ let pos = 0;
12197
+ let neg = 0;
12198
+ for (let k = i - period + 1; k <= i; k++) {
12199
+ const flow = tp[k] * volume[k];
12200
+ if (tp[k] > tp[k - 1]) pos += flow;
12201
+ else if (tp[k] < tp[k - 1]) neg += flow;
12202
+ }
12203
+ out[i] = neg === 0 ? 100 : 100 - 100 / (1 + pos / neg);
12204
+ }
12205
+ return out;
12206
+ }
12207
+ function williamsR(high, low, close, period = 14) {
12208
+ const n = Math.min(high.length, low.length, close.length);
12209
+ const out = new Float64Array(n).fill(NaN);
12210
+ if (period <= 0 || n < period) return out;
12211
+ for (let i = period - 1; i < n; i++) {
12212
+ let hh = -Infinity;
12213
+ let ll = Infinity;
12214
+ for (let k = i - period + 1; k <= i; k++) {
12215
+ if (high[k] > hh) hh = high[k];
12216
+ if (low[k] < ll) ll = low[k];
12217
+ }
12218
+ const span = hh - ll;
12219
+ out[i] = span === 0 ? 0 : (hh - close[i]) / span * -100;
12220
+ }
12221
+ return out;
12222
+ }
12223
+ function aroon(high, low, period = 25) {
12224
+ const n = Math.min(high.length, low.length);
12225
+ const up = new Float64Array(n).fill(NaN);
12226
+ const down = new Float64Array(n).fill(NaN);
12227
+ const oscillator = new Float64Array(n).fill(NaN);
12228
+ if (period <= 0 || n <= period) return { up, down, oscillator };
12229
+ for (let i = period; i < n; i++) {
12230
+ let hi = -Infinity, lo = Infinity, hIdx = i, lIdx = i;
12231
+ for (let k = i - period; k <= i; k++) {
12232
+ if (high[k] >= hi) {
12233
+ hi = high[k];
12234
+ hIdx = k;
12235
+ }
12236
+ if (low[k] <= lo) {
12237
+ lo = low[k];
12238
+ lIdx = k;
12239
+ }
12240
+ }
12241
+ up[i] = (period - (i - hIdx)) / period * 100;
12242
+ down[i] = (period - (i - lIdx)) / period * 100;
12243
+ oscillator[i] = up[i] - down[i];
12244
+ }
12245
+ return { up, down, oscillator };
12246
+ }
12247
+ function donchian(high, low, period = 20) {
12248
+ const n = Math.min(high.length, low.length);
12249
+ const upper = new Float64Array(n).fill(NaN);
12250
+ const lower = new Float64Array(n).fill(NaN);
12251
+ const middle = new Float64Array(n).fill(NaN);
12252
+ if (period <= 0 || n < period) return { middle, upper, lower };
12253
+ for (let i = period - 1; i < n; i++) {
12254
+ let hh = -Infinity;
12255
+ let ll = Infinity;
12256
+ for (let k = i - period + 1; k <= i; k++) {
12257
+ if (high[k] > hh) hh = high[k];
12258
+ if (low[k] < ll) ll = low[k];
12259
+ }
12260
+ upper[i] = hh;
12261
+ lower[i] = ll;
12262
+ middle[i] = (hh + ll) / 2;
12263
+ }
12264
+ return { middle, upper, lower };
12265
+ }
12266
+ function parabolicSar(high, low, step = 0.02, max = 0.2) {
12267
+ const n = Math.min(high.length, low.length);
12268
+ const out = new Float64Array(n).fill(NaN);
12269
+ if (n < 2) return out;
12270
+ let rising = high[1] >= high[0];
12271
+ let sar = rising ? low[0] : high[0];
12272
+ let extreme = rising ? high[1] : low[1];
12273
+ let accel = step;
12274
+ out[1] = sar;
12275
+ for (let i = 2; i < n; i++) {
12276
+ sar += accel * (extreme - sar);
12277
+ if (rising) sar = Math.min(sar, low[i - 1], low[i - 2]);
12278
+ else sar = Math.max(sar, high[i - 1], high[i - 2]);
12279
+ if (rising ? low[i] < sar : high[i] > sar) {
12280
+ rising = !rising;
12281
+ sar = extreme;
12282
+ extreme = rising ? high[i] : low[i];
12283
+ accel = step;
12284
+ } else if (rising ? high[i] > extreme : low[i] < extreme) {
12285
+ extreme = rising ? high[i] : low[i];
12286
+ accel = Math.min(max, accel + step);
12287
+ }
12288
+ out[i] = sar;
12289
+ }
12290
+ return out;
12291
+ }
12292
+ function pivotPoints(high, low, close) {
12293
+ const pivot = (high + low + close) / 3;
12294
+ const range = high - low;
12295
+ return {
12296
+ pivot,
12297
+ r1: 2 * pivot - low,
12298
+ s1: 2 * pivot - high,
12299
+ r2: pivot + range,
12300
+ s2: pivot - range,
12301
+ r3: high + 2 * (pivot - low),
12302
+ s3: low - 2 * (high - pivot)
12303
+ };
12304
+ }
11063
12305
 
11064
12306
  // src/finance/transforms.ts
11065
12307
  function heikinAshi(d) {
@@ -11203,6 +12445,72 @@ function depth(bids, asks) {
11203
12445
  }
11204
12446
  return { bidPrice, bidCum, askPrice, askCum };
11205
12447
  }
12448
+ function resampleOhlc(time, ohlc, bucketMs, volume) {
12449
+ const n = Math.min(time.length, ohlc.open.length, ohlc.high.length, ohlc.low.length, ohlc.close.length);
12450
+ const ts = [];
12451
+ const o = [];
12452
+ const h = [];
12453
+ const l = [];
12454
+ const c = [];
12455
+ const v = [];
12456
+ if (bucketMs <= 0 || n === 0) {
12457
+ const empty = new Float64Array(0);
12458
+ return { time: empty, open: empty, high: empty, low: empty, close: empty, ...volume ? { volume: empty } : {} };
12459
+ }
12460
+ let bucket = NaN;
12461
+ for (let i = 0; i < n; i++) {
12462
+ const b = Math.floor(time[i] / bucketMs) * bucketMs;
12463
+ if (b !== bucket) {
12464
+ bucket = b;
12465
+ ts.push(b);
12466
+ o.push(ohlc.open[i]);
12467
+ h.push(ohlc.high[i]);
12468
+ l.push(ohlc.low[i]);
12469
+ c.push(ohlc.close[i]);
12470
+ v.push(volume ? volume[i] : 0);
12471
+ continue;
12472
+ }
12473
+ const k = ts.length - 1;
12474
+ if (ohlc.high[i] > h[k]) h[k] = ohlc.high[i];
12475
+ if (ohlc.low[i] < l[k]) l[k] = ohlc.low[i];
12476
+ c[k] = ohlc.close[i];
12477
+ if (volume) v[k] += volume[i];
12478
+ }
12479
+ return {
12480
+ time: Float64Array.from(ts),
12481
+ open: Float64Array.from(o),
12482
+ high: Float64Array.from(h),
12483
+ low: Float64Array.from(l),
12484
+ close: Float64Array.from(c),
12485
+ ...volume ? { volume: Float64Array.from(v) } : {}
12486
+ };
12487
+ }
12488
+ function drawdown(equity) {
12489
+ const n = equity.length;
12490
+ const values = new Float64Array(n);
12491
+ const peak = new Float64Array(n);
12492
+ let high = -Infinity;
12493
+ let highIdx = -1;
12494
+ let maxDrawdown = 0;
12495
+ let troughIndex = -1;
12496
+ let peakIndex = -1;
12497
+ for (let i = 0; i < n; i++) {
12498
+ const e = equity[i];
12499
+ if (e > high) {
12500
+ high = e;
12501
+ highIdx = i;
12502
+ }
12503
+ peak[i] = high;
12504
+ const dd = high === 0 ? 0 : e / high - 1;
12505
+ values[i] = dd;
12506
+ if (dd < maxDrawdown) {
12507
+ maxDrawdown = dd;
12508
+ troughIndex = i;
12509
+ peakIndex = highIdx;
12510
+ }
12511
+ }
12512
+ return { values, peak, maxDrawdown, troughIndex, peakIndex };
12513
+ }
11206
12514
 
11207
12515
  // src/finance/charts.ts
11208
12516
  function addHeikinAshi(plot, opts) {
@@ -11295,14 +12603,44 @@ function addDepth(plot, opts) {
11295
12603
  })
11296
12604
  };
11297
12605
  }
11298
-
11299
- // src/data/csv.ts
11300
- function tokenize(text, delim) {
11301
- const rows = [];
11302
- let row = [];
11303
- let field = "";
11304
- let inQuotes = false;
11305
- for (let i = 0; i < text.length; i++) {
12606
+ function addDrawdown(plot, opts) {
12607
+ const stats = drawdown(opts.equity);
12608
+ const n = stats.values.length;
12609
+ const scale = opts.percent === false ? 1 : 100;
12610
+ const x = opts.x ?? Float64Array.from({ length: n }, (_, i) => i);
12611
+ const y = Float64Array.from(stats.values, (v) => v * scale);
12612
+ const color = opts.color ?? "#ef4444";
12613
+ const area2 = plot.addArea({
12614
+ x,
12615
+ y,
12616
+ base: 0,
12617
+ color,
12618
+ name: opts.name ?? `drawdown \xB7 max ${(stats.maxDrawdown * scale).toFixed(opts.percent === false ? 3 : 1)}${opts.percent === false ? "" : "%"}`,
12619
+ yAxis: opts.yAxis,
12620
+ renderType: opts.renderType
12621
+ });
12622
+ if ((opts.markMax ?? true) && stats.troughIndex >= 0) {
12623
+ const tx = x[stats.troughIndex];
12624
+ plot.addAnnotation({
12625
+ type: "span",
12626
+ dim: "x",
12627
+ value: tx,
12628
+ color,
12629
+ width: 1,
12630
+ dash: [4, 4],
12631
+ yAxis: opts.yAxis
12632
+ });
12633
+ }
12634
+ return { area: area2, stats };
12635
+ }
12636
+
12637
+ // src/data/csv.ts
12638
+ function tokenize(text, delim) {
12639
+ const rows = [];
12640
+ let row = [];
12641
+ let field = "";
12642
+ let inQuotes = false;
12643
+ for (let i = 0; i < text.length; i++) {
11306
12644
  const c = text[i];
11307
12645
  if (inQuotes) {
11308
12646
  if (c === '"') {
@@ -11550,6 +12888,154 @@ function emaSmooth(values, weight = 0.6) {
11550
12888
  }
11551
12889
  return out;
11552
12890
  }
12891
+ function mse(yTrue, yPred) {
12892
+ const n = Math.min(yTrue.length, yPred.length);
12893
+ if (n === 0) return NaN;
12894
+ let acc = 0;
12895
+ for (let i = 0; i < n; i++) acc += (yTrue[i] - yPred[i]) ** 2;
12896
+ return acc / n;
12897
+ }
12898
+ function rmse(yTrue, yPred) {
12899
+ return Math.sqrt(mse(yTrue, yPred));
12900
+ }
12901
+ function mae(yTrue, yPred) {
12902
+ const n = Math.min(yTrue.length, yPred.length);
12903
+ if (n === 0) return NaN;
12904
+ let acc = 0;
12905
+ for (let i = 0; i < n; i++) acc += Math.abs(yTrue[i] - yPred[i]);
12906
+ return acc / n;
12907
+ }
12908
+ function r2(yTrue, yPred) {
12909
+ const n = Math.min(yTrue.length, yPred.length);
12910
+ if (n === 0) return NaN;
12911
+ let mean = 0;
12912
+ for (let i = 0; i < n; i++) mean += yTrue[i];
12913
+ mean /= n;
12914
+ let ssRes = 0;
12915
+ let ssTot = 0;
12916
+ for (let i = 0; i < n; i++) {
12917
+ ssRes += (yTrue[i] - yPred[i]) ** 2;
12918
+ ssTot += (yTrue[i] - mean) ** 2;
12919
+ }
12920
+ return ssTot === 0 ? ssRes === 0 ? 1 : 0 : 1 - ssRes / ssTot;
12921
+ }
12922
+ function logLoss(probs, labels, eps = 1e-15) {
12923
+ const n = Math.min(probs.length, labels.length);
12924
+ if (n === 0) return NaN;
12925
+ let acc = 0;
12926
+ for (let i = 0; i < n; i++) {
12927
+ const p = Math.min(1 - eps, Math.max(eps, probs[i]));
12928
+ acc += labels[i] > 0 ? -Math.log(p) : -Math.log(1 - p);
12929
+ }
12930
+ return acc / n;
12931
+ }
12932
+ function brierScore(probs, labels) {
12933
+ const n = Math.min(probs.length, labels.length);
12934
+ if (n === 0) return NaN;
12935
+ let acc = 0;
12936
+ for (let i = 0; i < n; i++) acc += (probs[i] - (labels[i] > 0 ? 1 : 0)) ** 2;
12937
+ return acc / n;
12938
+ }
12939
+ function classificationReport(yTrue, yPred, classes) {
12940
+ const n = Math.min(yTrue.length, yPred.length);
12941
+ let k = classes ?? 0;
12942
+ if (!classes) for (let i = 0; i < n; i++) k = Math.max(k, yTrue[i] + 1, yPred[i] + 1);
12943
+ k = Math.max(0, Math.floor(k));
12944
+ const tp = new Float64Array(k);
12945
+ const fp = new Float64Array(k);
12946
+ const fn = new Float64Array(k);
12947
+ const support = new Float64Array(k);
12948
+ let correct = 0;
12949
+ for (let i = 0; i < n; i++) {
12950
+ const t = yTrue[i];
12951
+ const p = yPred[i];
12952
+ if (t >= 0 && t < k) support[t] += 1;
12953
+ if (t === p) {
12954
+ correct++;
12955
+ if (t >= 0 && t < k) tp[t] += 1;
12956
+ } else {
12957
+ if (p >= 0 && p < k) fp[p] += 1;
12958
+ if (t >= 0 && t < k) fn[t] += 1;
12959
+ }
12960
+ }
12961
+ const perClass = [];
12962
+ for (let c = 0; c < k; c++) {
12963
+ const precision = tp[c] + fp[c] === 0 ? 0 : tp[c] / (tp[c] + fp[c]);
12964
+ const recall = tp[c] + fn[c] === 0 ? 0 : tp[c] / (tp[c] + fn[c]);
12965
+ const f1 = precision + recall === 0 ? 0 : 2 * precision * recall / (precision + recall);
12966
+ perClass.push({ label: c, precision, recall, f1, support: support[c] });
12967
+ }
12968
+ const avg = (pick, weights) => {
12969
+ if (k === 0) return 0;
12970
+ if (!weights) {
12971
+ let acc2 = 0;
12972
+ for (const s of perClass) acc2 += pick(s);
12973
+ return acc2 / k;
12974
+ }
12975
+ let acc = 0;
12976
+ let total = 0;
12977
+ for (let c = 0; c < k; c++) {
12978
+ acc += pick(perClass[c]) * weights[c];
12979
+ total += weights[c];
12980
+ }
12981
+ return total === 0 ? 0 : acc / total;
12982
+ };
12983
+ return {
12984
+ perClass,
12985
+ accuracy: n === 0 ? 0 : correct / n,
12986
+ macro: { precision: avg((s) => s.precision), recall: avg((s) => s.recall), f1: avg((s) => s.f1) },
12987
+ weighted: {
12988
+ precision: avg((s) => s.precision, support),
12989
+ recall: avg((s) => s.recall, support),
12990
+ f1: avg((s) => s.f1, support)
12991
+ }
12992
+ };
12993
+ }
12994
+ function liftCurve(scores, labels) {
12995
+ const n = Math.min(scores.length, labels.length);
12996
+ const order = Array.from({ length: n }, (_, i) => i).sort((a, b) => scores[b] - scores[a]);
12997
+ let positives = 0;
12998
+ for (let i = 0; i < n; i++) if (labels[i] > 0) positives++;
12999
+ const fraction = new Float64Array(n + 1);
13000
+ const gain = new Float64Array(n + 1);
13001
+ const lift = new Float64Array(n + 1);
13002
+ let hits = 0;
13003
+ for (let i = 0; i < n; i++) {
13004
+ if (labels[order[i]] > 0) hits++;
13005
+ const f = (i + 1) / n;
13006
+ fraction[i + 1] = f;
13007
+ gain[i + 1] = positives === 0 ? 0 : hits / positives;
13008
+ lift[i + 1] = f === 0 ? 0 : gain[i + 1] / f;
13009
+ }
13010
+ lift[0] = n === 0 ? 0 : lift[1];
13011
+ return { fraction, gain, lift, positives };
13012
+ }
13013
+ function rocCurveOvR(scores, labels, classes) {
13014
+ const n = labels.length;
13015
+ const perClass = [];
13016
+ let macro = 0;
13017
+ const pooledScores = new Float64Array(n * classes);
13018
+ const pooledLabels = new Float64Array(n * classes);
13019
+ for (let c = 0; c < classes; c++) {
13020
+ const s = new Float64Array(n);
13021
+ const l = new Float64Array(n);
13022
+ for (let i = 0; i < n; i++) {
13023
+ s[i] = scores[i * classes + c];
13024
+ l[i] = labels[i] === c ? 1 : 0;
13025
+ pooledScores[c * n + i] = s[i];
13026
+ pooledLabels[c * n + i] = l[i];
13027
+ }
13028
+ const roc = rocCurve(s, l);
13029
+ perClass.push({ label: c, fpr: roc.fpr, tpr: roc.tpr, auc: roc.auc });
13030
+ macro += roc.auc;
13031
+ }
13032
+ const micro = rocCurve(pooledScores, pooledLabels);
13033
+ return {
13034
+ perClass,
13035
+ macroAuc: classes === 0 ? 0 : macro / classes,
13036
+ microAuc: micro.auc
13037
+ };
13038
+ }
11553
13039
 
11554
13040
  // src/ml/reduce.ts
11555
13041
  function standardize(data, n, d) {
@@ -11645,19 +13131,72 @@ function normalize3(vec, d) {
11645
13131
  return norm2;
11646
13132
  }
11647
13133
 
13134
+ // src/color/palettes.ts
13135
+ var PALETTES = {
13136
+ tableau10: [
13137
+ "#4e79a7",
13138
+ "#f28e2b",
13139
+ "#e15759",
13140
+ "#76b7b2",
13141
+ "#59a14f",
13142
+ "#edc948",
13143
+ "#b07aa1",
13144
+ "#ff9da7",
13145
+ "#9c755f",
13146
+ "#bab0ac"
13147
+ ],
13148
+ "okabe-ito": [
13149
+ "#0072b2",
13150
+ "#e69f00",
13151
+ "#009e73",
13152
+ "#cc79a7",
13153
+ "#56b4e9",
13154
+ "#d55e00",
13155
+ "#f0e442",
13156
+ "#000000"
13157
+ ],
13158
+ set2: [
13159
+ "#66c2a5",
13160
+ "#fc8d62",
13161
+ "#8da0cb",
13162
+ "#e78ac3",
13163
+ "#a6d854",
13164
+ "#ffd92f",
13165
+ "#e5c494",
13166
+ "#b3b3b3"
13167
+ ],
13168
+ bright: [
13169
+ "#60a5fa",
13170
+ "#f472b6",
13171
+ "#34d399",
13172
+ "#fbbf24",
13173
+ "#a78bfa",
13174
+ "#22d3ee",
13175
+ "#fb923c",
13176
+ "#f87171",
13177
+ "#c084fc",
13178
+ "#4ade80"
13179
+ ]
13180
+ };
13181
+ var DEFAULT_PALETTE = PALETTES.tableau10;
13182
+ function registerPalette(name, colors) {
13183
+ if (colors.length === 0) throw new Error(`Palette "${name}" needs at least one colour`);
13184
+ PALETTES[name] = colors;
13185
+ }
13186
+ function paletteNames() {
13187
+ return Object.keys(PALETTES);
13188
+ }
13189
+ function palette(spec = "tableau10") {
13190
+ if (typeof spec !== "string") return spec.length ? spec : DEFAULT_PALETTE;
13191
+ return PALETTES[spec] ?? DEFAULT_PALETTE;
13192
+ }
13193
+ function paletteColor(index, spec = "tableau10") {
13194
+ const p = palette(spec);
13195
+ return p[(index % p.length + p.length) % p.length];
13196
+ }
13197
+
11648
13198
  // src/ml/charts.ts
11649
- var ML_PALETTE = [
11650
- "#4e79a7",
11651
- "#f28e2b",
11652
- "#e15759",
11653
- "#76b7b2",
11654
- "#59a14f",
11655
- "#edc948",
11656
- "#b07aa1",
11657
- "#ff9da7",
11658
- "#9c755f",
11659
- "#bab0ac"
11660
- ];
13199
+ var ML_PALETTE = DEFAULT_PALETTE;
11661
13200
  function addConfusionMatrix(plot, opts) {
11662
13201
  const cm = confusionMatrix(opts.yTrue, opts.yPred, opts.classes);
11663
13202
  const C = cm.classes;
@@ -11753,7 +13292,7 @@ function addEmbedding(plot, opts) {
11753
13292
  })]
11754
13293
  };
11755
13294
  }
11756
- return { layers: [plot.addScatter({ x: opts.x, y: opts.y, size, color: (opts.palette ?? ML_PALETTE)[0], name: opts.name, labels: opts.text, renderType: opts.renderType })] };
13295
+ return { layers: [plot.addScatter({ x: opts.x, y: opts.y, size, color: palette(opts.palette ?? ML_PALETTE)[0], name: opts.name, labels: opts.text, renderType: opts.renderType })] };
11757
13296
  }
11758
13297
  function addDecisionBoundary(plot, opts) {
11759
13298
  const heatmap = plot.addHeatmap({
@@ -11868,14 +13407,14 @@ function addAttentionMap(plot, opts) {
11868
13407
  return { heatmap, queries: Q, keys: K };
11869
13408
  }
11870
13409
  function addTrainingCurves(plot, opts) {
11871
- const palette = opts.palette ?? ML_PALETTE;
13410
+ const palette2 = palette(opts.palette ?? ML_PALETTE);
11872
13411
  const smoothing = opts.smoothing ?? 0.6;
11873
13412
  const width = opts.width ?? 2;
11874
13413
  const raw = [], smoothed = [];
11875
13414
  opts.series.forEach((s, i) => {
11876
13415
  const n = s.y.length;
11877
13416
  const x = s.x ?? Float64Array.from({ length: n }, (_, j) => j);
11878
- const color = s.color ?? palette[i % palette.length];
13417
+ const color = s.color ?? palette2[i % palette2.length];
11879
13418
  if (smoothing > 0) {
11880
13419
  if (opts.showRaw ?? true) raw.push(plot.addLine({ x, y: s.y, color: withAlpha(color, 0.25), width: 1, renderType: opts.renderType }));
11881
13420
  smoothed.push(plot.addLine({ x, y: emaSmooth(s.y, smoothing), color, width, name: s.name, renderType: opts.renderType }));
@@ -11912,7 +13451,7 @@ function addRidgeline(plot, opts) {
11912
13451
  hi = (hi || 0) + 1;
11913
13452
  }
11914
13453
  const points = opts.points ?? 96;
11915
- const palette = opts.palette ?? ML_PALETTE;
13454
+ const palette2 = palette(opts.palette ?? ML_PALETTE);
11916
13455
  const height = 1 + Math.max(0, opts.overlap ?? 1);
11917
13456
  const areas = [], lines = [];
11918
13457
  g.forEach((grp, i) => {
@@ -11922,7 +13461,7 @@ function addRidgeline(plot, opts) {
11922
13461
  const scale = peak > 0 ? height / peak : 0;
11923
13462
  const y = new Float64Array(dens.ys.length);
11924
13463
  for (let j = 0; j < y.length; j++) y[j] = i + dens.ys[j] * scale;
11925
- const color = palette[i % palette.length];
13464
+ const color = palette2[i % palette2.length];
11926
13465
  if (opts.fill ?? true) areas.push(plot.addArea({ x: dens.xs, y, base: i, color: withAlpha(color, 0.55) }));
11927
13466
  lines.push(plot.addLine({ x: dens.xs, y, color, width: 1.5 }));
11928
13467
  if (grp.label) plot.addAnnotation({ type: "label", x: lo, y: i + 0.05, text: `${grp.label} `, align: "right", color: "#e5e7eb" });
@@ -11961,9 +13500,10 @@ function beeswarmLayout(x, opts = {}) {
11961
13500
  }
11962
13501
  return y;
11963
13502
  }
11964
- function scatterByClass(plot, x, y, labels, classNames, palette = ML_PALETTE, size = 4, text, renderType) {
13503
+ function scatterByClass(plot, x, y, labels, classNames, paletteSpec = ML_PALETTE, size = 4, text, renderType) {
13504
+ const palette2 = palette(paletteSpec);
11965
13505
  const n = Math.min(x.length, y.length);
11966
- if (!labels) return [plot.addScatter({ x, y, size, color: palette[0], labels: text, renderType })];
13506
+ if (!labels) return [plot.addScatter({ x, y, size, color: palette2[0], labels: text, renderType })];
11967
13507
  const keys = [];
11968
13508
  const seen = /* @__PURE__ */ new Set();
11969
13509
  for (let i = 0; i < n; i++) {
@@ -11985,7 +13525,7 @@ function scatterByClass(plot, x, y, labels, classNames, palette = ML_PALETTE, si
11985
13525
  if (text) st.push(String(text[i]));
11986
13526
  }
11987
13527
  const name = classNames && typeof key === "number" ? classNames[key] ?? String(key) : String(key);
11988
- layers.push(plot.addScatter({ x: sx, y: sy, size, color: palette[ci % palette.length], name, labels: text ? st : void 0, renderType }));
13528
+ layers.push(plot.addScatter({ x: sx, y: sy, size, color: palette2[ci % palette2.length], name, labels: text ? st : void 0, renderType }));
11989
13529
  });
11990
13530
  return layers;
11991
13531
  }
@@ -11997,21 +13537,968 @@ function withAlpha(color, alpha) {
11997
13537
  }
11998
13538
  return color;
11999
13539
  }
13540
+ function addPredVsActual(plot, opts) {
13541
+ const n = Math.min(opts.yTrue.length, opts.yPred.length);
13542
+ let lo = Infinity;
13543
+ let hi = -Infinity;
13544
+ for (let i = 0; i < n; i++) {
13545
+ lo = Math.min(lo, opts.yTrue[i], opts.yPred[i]);
13546
+ hi = Math.max(hi, opts.yTrue[i], opts.yPred[i]);
13547
+ }
13548
+ if (!Number.isFinite(lo)) {
13549
+ lo = 0;
13550
+ hi = 1;
13551
+ }
13552
+ const reference = plot.addLine({
13553
+ x: Float64Array.from([lo, hi]),
13554
+ y: Float64Array.from([lo, hi]),
13555
+ color: opts.referenceColor ?? "#64748b",
13556
+ width: 1.5,
13557
+ dash: [6, 4]
13558
+ });
13559
+ const points = plot.addScatter({
13560
+ x: opts.yTrue,
13561
+ y: opts.yPred,
13562
+ size: opts.size ?? 4,
13563
+ color: opts.color ?? ML_PALETTE[0],
13564
+ name: opts.name ?? `predictions \xB7 R\xB2=${r2(opts.yTrue, opts.yPred).toFixed(3)}`,
13565
+ renderType: opts.renderType
13566
+ });
13567
+ return { points, reference, r2: r2(opts.yTrue, opts.yPred) };
13568
+ }
13569
+ function addResiduals(plot, opts) {
13570
+ const n = Math.min(opts.yTrue.length, opts.yPred.length);
13571
+ const residuals = new Float64Array(n);
13572
+ const xs = new Float64Array(n);
13573
+ const byIndex = opts.against === "index";
13574
+ let lo = Infinity;
13575
+ let hi = -Infinity;
13576
+ for (let i = 0; i < n; i++) {
13577
+ residuals[i] = opts.yTrue[i] - opts.yPred[i];
13578
+ xs[i] = byIndex ? i : opts.yPred[i];
13579
+ if (xs[i] < lo) lo = xs[i];
13580
+ if (xs[i] > hi) hi = xs[i];
13581
+ }
13582
+ if (!Number.isFinite(lo)) {
13583
+ lo = 0;
13584
+ hi = 1;
13585
+ }
13586
+ const zero = plot.addLine({
13587
+ x: Float64Array.from([lo, hi]),
13588
+ y: Float64Array.from([0, 0]),
13589
+ color: "#64748b",
13590
+ width: 1.5,
13591
+ dash: [6, 4]
13592
+ });
13593
+ const points = plot.addScatter({
13594
+ x: xs,
13595
+ y: residuals,
13596
+ size: opts.size ?? 4,
13597
+ color: opts.color ?? ML_PALETTE[2],
13598
+ name: opts.name ?? "residuals",
13599
+ renderType: opts.renderType
13600
+ });
13601
+ return { points, zero, residuals };
13602
+ }
13603
+ function addLiftCurve(plot, opts) {
13604
+ const curve = liftCurve(opts.scores, opts.labels);
13605
+ const gainMode = (opts.mode ?? "gain") === "gain";
13606
+ const baseline = plot.addLine({
13607
+ x: Float64Array.from([0, 1]),
13608
+ y: gainMode ? Float64Array.from([0, 1]) : Float64Array.from([1, 1]),
13609
+ color: opts.baselineColor ?? "#64748b",
13610
+ width: 1.5,
13611
+ dash: [6, 4],
13612
+ name: "random"
13613
+ });
13614
+ const line = plot.addLine({
13615
+ x: curve.fraction,
13616
+ y: gainMode ? curve.gain : curve.lift,
13617
+ color: opts.color ?? ML_PALETTE[0],
13618
+ width: opts.width ?? 2,
13619
+ name: opts.name ?? (gainMode ? "cumulative gain" : "lift"),
13620
+ renderType: opts.renderType
13621
+ });
13622
+ return { line, baseline, positives: curve.positives };
13623
+ }
13624
+ function addLearningCurve(plot, opts) {
13625
+ const trainColor = opts.trainColor ?? ML_PALETTE[0];
13626
+ const valColor = opts.validationColor ?? ML_PALETTE[1];
13627
+ const bands = [];
13628
+ const addBand = (mean, std, color) => {
13629
+ if (!std) return;
13630
+ const n = Math.min(mean.length, std.length);
13631
+ const upper = new Float64Array(n);
13632
+ const lower = new Float64Array(n);
13633
+ for (let i = 0; i < n; i++) {
13634
+ upper[i] = mean[i] + std[i];
13635
+ lower[i] = mean[i] - std[i];
13636
+ }
13637
+ bands.push(plot.addArea({ x: opts.sizes, y: upper, base: lower, color: `${color}33`, renderType: opts.renderType }));
13638
+ };
13639
+ addBand(opts.train, opts.trainStd, trainColor);
13640
+ addBand(opts.validation, opts.validationStd, valColor);
13641
+ const train = plot.addLine({
13642
+ x: opts.sizes,
13643
+ y: opts.train,
13644
+ color: trainColor,
13645
+ width: opts.width ?? 2,
13646
+ name: "train",
13647
+ renderType: opts.renderType
13648
+ });
13649
+ const validation = plot.addLine({
13650
+ x: opts.sizes,
13651
+ y: opts.validation,
13652
+ color: valColor,
13653
+ width: opts.width ?? 2,
13654
+ name: "validation",
13655
+ renderType: opts.renderType
13656
+ });
13657
+ return { train, validation, bands };
13658
+ }
13659
+
13660
+ // src/ml/model.ts
13661
+ var LAYER_COLORS = {
13662
+ input: "#94a3b8",
13663
+ conv: "#60a5fa",
13664
+ linear: "#a78bfa",
13665
+ norm: "#34d399",
13666
+ activation: "#fbbf24",
13667
+ pool: "#22d3ee",
13668
+ dropout: "#64748b",
13669
+ attention: "#f472b6",
13670
+ embedding: "#c084fc",
13671
+ recurrent: "#fb923c",
13672
+ reshape: "#7dd3fc",
13673
+ merge: "#f87171",
13674
+ output: "#94a3b8",
13675
+ other: "#8ba3c7"
13676
+ };
13677
+ var CATEGORY_RULES = [
13678
+ ["input", ["input", "placeholder"]],
13679
+ ["output", ["output"]],
13680
+ ["conv", ["conv"]],
13681
+ ["attention", ["attention", "attn", "multihead", "mha"]],
13682
+ ["recurrent", ["lstm", "gru", "rnn"]],
13683
+ ["embedding", ["embed"]],
13684
+ ["norm", ["norm"]],
13685
+ ["pool", ["pool", "upsample", "interpolate", "resize"]],
13686
+ ["dropout", ["dropout"]],
13687
+ ["linear", ["linear", "dense", "gemm", "matmul", "fc"]],
13688
+ ["activation", [
13689
+ "relu",
13690
+ "gelu",
13691
+ "silu",
13692
+ "swish",
13693
+ "sigmoid",
13694
+ "tanh",
13695
+ "softmax",
13696
+ "softplus",
13697
+ "elu",
13698
+ "mish",
13699
+ "hardswish",
13700
+ "glu",
13701
+ "activation"
13702
+ ]],
13703
+ ["merge", ["concat", "cat", "add", "mul", "sum", "residual"]],
13704
+ ["reshape", ["flatten", "reshape", "view", "permute", "transpose", "squeeze"]]
13705
+ ];
13706
+ function layerCategory(type) {
13707
+ const t = type.toLowerCase();
13708
+ for (const [category, keys] of CATEGORY_RULES) {
13709
+ for (const key of keys) if (t.includes(key)) return category;
13710
+ }
13711
+ return "other";
13712
+ }
13713
+ function formatCount(n) {
13714
+ const abs = Math.abs(n);
13715
+ if (abs < 1e3) return String(Math.round(n));
13716
+ const units = [[1e9, "B"], [1e6, "M"], [1e3, "K"]];
13717
+ for (const [scale, suffix] of units) {
13718
+ if (abs >= scale) {
13719
+ const v = n / scale;
13720
+ return `${Math.abs(v) < 10 ? v.toFixed(1) : Math.round(v)}${suffix}`;
13721
+ }
13722
+ }
13723
+ return String(n);
13724
+ }
13725
+ function formatShape(shape) {
13726
+ return shape && shape.length ? shape.join("\xD7") : "";
13727
+ }
13728
+ function uniqueId(base, seen) {
13729
+ if (!seen.has(base)) {
13730
+ seen.add(base);
13731
+ return base;
13732
+ }
13733
+ let i = 1;
13734
+ while (seen.has(`${base}_${i}`)) i++;
13735
+ const id = `${base}_${i}`;
13736
+ seen.add(id);
13737
+ return id;
13738
+ }
13739
+ function sequentialModel(layers, name) {
13740
+ const seen = /* @__PURE__ */ new Set();
13741
+ const nodes = layers.map((l, i) => ({
13742
+ ...l,
13743
+ id: uniqueId(l.id ?? l.name ?? `${l.type}_${i}`, seen)
13744
+ }));
13745
+ const edges = [];
13746
+ for (let i = 1; i < nodes.length; i++) edges.push({ from: nodes[i - 1].id, to: nodes[i].id });
13747
+ return { name, nodes, edges };
13748
+ }
13749
+ function shortTarget(target) {
13750
+ const fn = /<built-in (?:function|method) ([^>]+)>/.exec(target);
13751
+ const raw = fn ? fn[1] : target;
13752
+ const parts = raw.split(".");
13753
+ return parts[parts.length - 1].trim() || raw;
13754
+ }
13755
+ function modelGraphFromTorchFx(nodes, opts = {}) {
13756
+ const skip = new Set(opts.skipOps ?? ["get_attr"]);
13757
+ const kept = nodes.filter((n) => !skip.has(n.op));
13758
+ const known = new Set(kept.map((n) => n.name));
13759
+ const out = kept.map((n) => ({
13760
+ id: n.name,
13761
+ name: n.op === "call_module" ? n.target ?? n.name : n.name,
13762
+ type: n.moduleType ?? (n.target ? shortTarget(n.target) : n.op),
13763
+ ...n.shape ? { shape: n.shape } : {},
13764
+ ...n.params != null ? { params: n.params } : {},
13765
+ ...n.flops != null ? { flops: n.flops } : {}
13766
+ }));
13767
+ const edges = [];
13768
+ for (const n of kept) {
13769
+ for (const a of n.args ?? []) if (known.has(a)) edges.push({ from: a, to: n.name });
13770
+ }
13771
+ return { name: opts.name, nodes: out, edges };
13772
+ }
13773
+ function modelGraphFromOnnx(graph, opts = {}) {
13774
+ const initializers = new Set((graph.initializer ?? []).map((i) => i.name));
13775
+ const shapes = opts.shapes ?? {};
13776
+ const paramCounts = opts.paramCounts ?? {};
13777
+ const nodes = [];
13778
+ const seen = /* @__PURE__ */ new Set();
13779
+ const producer = /* @__PURE__ */ new Map();
13780
+ for (const gi of graph.input ?? []) {
13781
+ if (initializers.has(gi.name)) continue;
13782
+ const id = uniqueId(gi.name, seen);
13783
+ nodes.push({
13784
+ id,
13785
+ name: gi.name,
13786
+ type: "Input",
13787
+ ...shapes[gi.name] ? { shape: shapes[gi.name] } : {}
13788
+ });
13789
+ producer.set(gi.name, id);
13790
+ }
13791
+ for (let i = 0; i < graph.node.length; i++) {
13792
+ const n = graph.node[i];
13793
+ const id = uniqueId(n.name || `${n.opType}_${i}`, seen);
13794
+ const outName = n.output?.[0];
13795
+ let params = 0;
13796
+ for (const inp of n.input ?? []) if (initializers.has(inp)) params += paramCounts[inp] ?? 0;
13797
+ nodes.push({
13798
+ id,
13799
+ name: n.name || n.opType,
13800
+ type: n.opType,
13801
+ ...outName && shapes[outName] ? { shape: shapes[outName] } : {},
13802
+ ...params > 0 ? { params } : {}
13803
+ });
13804
+ for (const o of n.output ?? []) producer.set(o, id);
13805
+ }
13806
+ const edges = [];
13807
+ for (let i = 0; i < graph.node.length; i++) {
13808
+ const n = graph.node[i];
13809
+ const to = producer.get(n.output?.[0] ?? "");
13810
+ if (!to) continue;
13811
+ for (const inp of n.input ?? []) {
13812
+ if (initializers.has(inp)) continue;
13813
+ const from = producer.get(inp);
13814
+ if (from && from !== to) edges.push({ from, to });
13815
+ }
13816
+ }
13817
+ return { name: opts.name ?? graph.name, nodes, edges };
13818
+ }
13819
+ function collectNames(value, known, out) {
13820
+ if (typeof value === "string") {
13821
+ if (known.has(value)) out.add(value);
13822
+ return;
13823
+ }
13824
+ if (Array.isArray(value)) {
13825
+ for (const v of value) collectNames(v, known, out);
13826
+ return;
13827
+ }
13828
+ if (value && typeof value === "object") {
13829
+ for (const v of Object.values(value)) collectNames(v, known, out);
13830
+ }
13831
+ }
13832
+ function modelGraphFromKeras(model, opts = {}) {
13833
+ const layers = model.config?.layers ?? model.layers ?? [];
13834
+ const shapes = opts.shapes ?? {};
13835
+ const params = opts.params ?? {};
13836
+ const seen = /* @__PURE__ */ new Set();
13837
+ const names = layers.map(
13838
+ (l, i) => uniqueId(l.config?.name ?? l.name ?? `${l.class_name}_${i}`, seen)
13839
+ );
13840
+ const known = new Set(names);
13841
+ const nodes = layers.map((l, i) => {
13842
+ const id = names[i];
13843
+ const batchShape = l.config?.batch_input_shape ?? l.config?.batch_shape;
13844
+ const inferred = batchShape ? batchShape.slice(1).filter((v) => typeof v === "number") : void 0;
13845
+ const shape = shapes[id] ?? (inferred && inferred.length ? inferred : void 0);
13846
+ return {
13847
+ id,
13848
+ name: id,
13849
+ type: l.class_name,
13850
+ ...shape ? { shape } : {},
13851
+ ...params[id] != null ? { params: params[id] } : {}
13852
+ };
13853
+ });
13854
+ const inbound = layers.map((l) => {
13855
+ const out = /* @__PURE__ */ new Set();
13856
+ collectNames(l.inbound_nodes, known, out);
13857
+ return out;
13858
+ });
13859
+ const wired = inbound.some((s) => s.size > 0);
13860
+ const edges = [];
13861
+ if (wired) {
13862
+ for (let i = 0; i < layers.length; i++) {
13863
+ for (const from of inbound[i]) if (from !== names[i]) edges.push({ from, to: names[i] });
13864
+ }
13865
+ } else {
13866
+ for (let i = 1; i < names.length; i++) edges.push({ from: names[i - 1], to: names[i] });
13867
+ }
13868
+ return { name: opts.name ?? model.config?.name, nodes, edges };
13869
+ }
13870
+ function modelGraphFromSklearn(root, opts = {}) {
13871
+ const nodes = [];
13872
+ const edges = [];
13873
+ const seen = /* @__PURE__ */ new Set();
13874
+ const walk = (step, path) => {
13875
+ const id = uniqueId(path ? `${path}.${step.name}` : step.name, seen);
13876
+ if (!step.steps || step.steps.length === 0) {
13877
+ const label = step.columns?.length ? `${step.name} [${step.columns.join(", ")}]` : step.name;
13878
+ nodes.push({
13879
+ id,
13880
+ name: label,
13881
+ type: step.type,
13882
+ ...step.shape ? { shape: step.shape } : {},
13883
+ ...step.params != null ? { params: step.params } : {},
13884
+ ...path ? { group: path } : {}
13885
+ });
13886
+ return { entries: [id], exits: [id] };
13887
+ }
13888
+ const childPath = path ? `${path}.${step.name}` : step.name;
13889
+ const children = step.steps.map((child) => walk(child, childPath));
13890
+ if ((step.mode ?? "sequential") === "parallel") {
13891
+ return {
13892
+ entries: children.flatMap((c) => c.entries),
13893
+ exits: children.flatMap((c) => c.exits)
13894
+ };
13895
+ }
13896
+ for (let i = 1; i < children.length; i++) {
13897
+ for (const from of children[i - 1].exits) {
13898
+ for (const to of children[i].entries) edges.push({ from, to });
13899
+ }
13900
+ }
13901
+ return { entries: children[0].entries, exits: children[children.length - 1].exits };
13902
+ };
13903
+ walk(root, "");
13904
+ return { name: opts.name ?? root.name, nodes, edges };
13905
+ }
13906
+ function mlpModel(layerSizes, opts = {}) {
13907
+ const activation = opts.activation ?? "ReLU";
13908
+ const layers = [];
13909
+ if (layerSizes.length === 0) return { name: opts.name, nodes: [], edges: [] };
13910
+ layers.push({ id: "input", name: "input", type: "Input", shape: [layerSizes[0]] });
13911
+ for (let i = 1; i < layerSizes.length; i++) {
13912
+ const nIn = layerSizes[i - 1];
13913
+ const nOut = layerSizes[i];
13914
+ const last = i === layerSizes.length - 1;
13915
+ layers.push({
13916
+ id: `dense_${i}`,
13917
+ name: last ? "output" : `dense_${i}`,
13918
+ type: "Linear",
13919
+ shape: [nOut],
13920
+ params: nIn * nOut + nOut
13921
+ });
13922
+ const act = last ? opts.outputActivation : activation;
13923
+ if (act) layers.push({ id: `${act.toLowerCase()}_${i}`, type: act, shape: [nOut] });
13924
+ }
13925
+ return sequentialModel(layers, opts.name);
13926
+ }
13927
+ function assignRanks(n, outAdj, inAdj) {
13928
+ const rank = new Array(n).fill(0);
13929
+ const indeg = new Array(n).fill(0);
13930
+ for (let i = 0; i < n; i++) indeg[i] = inAdj[i].length;
13931
+ const queue = [];
13932
+ for (let i = 0; i < n; i++) if (indeg[i] === 0) queue.push(i);
13933
+ const visited = new Uint8Array(n);
13934
+ let head = 0;
13935
+ while (head < queue.length) {
13936
+ const u = queue[head++];
13937
+ visited[u] = 1;
13938
+ for (const v of outAdj[u]) {
13939
+ if (rank[u] + 1 > rank[v]) rank[v] = rank[u] + 1;
13940
+ if (--indeg[v] === 0) queue.push(v);
13941
+ }
13942
+ }
13943
+ for (let i = 0; i < n; i++) {
13944
+ if (visited[i]) continue;
13945
+ let r = 0;
13946
+ for (const p of inAdj[i]) if (visited[p] && rank[p] + 1 > r) r = rank[p] + 1;
13947
+ rank[i] = r;
13948
+ visited[i] = 1;
13949
+ }
13950
+ return rank;
13951
+ }
13952
+ function orderRanks(byRank, outAdj, inAdj, sweeps) {
13953
+ const pos = /* @__PURE__ */ new Map();
13954
+ const reindex = () => {
13955
+ for (const rankNodes of byRank) {
13956
+ for (let i = 0; i < rankNodes.length; i++) pos.set(rankNodes[i], i);
13957
+ }
13958
+ };
13959
+ const barycenter = (v, neighbours) => {
13960
+ let sum = 0;
13961
+ let count = 0;
13962
+ for (const u of neighbours) {
13963
+ const p = pos.get(u);
13964
+ if (p != null) {
13965
+ sum += p;
13966
+ count++;
13967
+ }
13968
+ }
13969
+ return count === 0 ? pos.get(v) : sum / count;
13970
+ };
13971
+ reindex();
13972
+ for (let s = 0; s < sweeps; s++) {
13973
+ const down = s % 2 === 0;
13974
+ const order = down ? byRank.map((_, i) => i).slice(1) : byRank.map((_, i) => i).slice(0, -1).reverse();
13975
+ for (const r of order) {
13976
+ const rankNodes = byRank[r];
13977
+ const keys = /* @__PURE__ */ new Map();
13978
+ for (const v of rankNodes) keys.set(v, barycenter(v, down ? inAdj[v] : outAdj[v]));
13979
+ rankNodes.sort((a, b) => keys.get(a) - keys.get(b) || pos.get(a) - pos.get(b));
13980
+ reindex();
13981
+ }
13982
+ }
13983
+ }
13984
+ function modelLayout(graph, opts = {}) {
13985
+ const vertical = (opts.direction ?? "vertical") === "vertical";
13986
+ const nodeWidth = opts.nodeWidth ?? 2.6;
13987
+ const nodeHeight = opts.nodeHeight ?? 0.9;
13988
+ const rankGap = opts.rankGap ?? 0.7;
13989
+ const nodeGap = opts.nodeGap ?? 0.6;
13990
+ const sweeps = opts.sweeps ?? 4;
13991
+ const [minScale, maxScale] = opts.sizeRange ?? [0.55, 1.5];
13992
+ const n = graph.nodes.length;
13993
+ if (n === 0) {
13994
+ return { nodes: [], edges: [], extent: { x: [0, 1], y: [0, 1] }, ranks: 0 };
13995
+ }
13996
+ const index = /* @__PURE__ */ new Map();
13997
+ for (let i = 0; i < n; i++) index.set(graph.nodes[i].id, i);
13998
+ const outAdj = Array.from({ length: n }, () => []);
13999
+ const inAdj = Array.from({ length: n }, () => []);
14000
+ const edgeList = [];
14001
+ for (const e of graph.edges) {
14002
+ const from = index.get(e.from);
14003
+ const to = index.get(e.to);
14004
+ if (from == null || to == null || from === to) continue;
14005
+ outAdj[from].push(to);
14006
+ inAdj[to].push(from);
14007
+ edgeList.push({ edge: e, from, to });
14008
+ }
14009
+ const rank = assignRanks(n, outAdj, inAdj);
14010
+ const ranks = Math.max(...rank) + 1;
14011
+ const byRank = Array.from({ length: ranks }, () => []);
14012
+ for (let i = 0; i < n; i++) byRank[rank[i]].push(i);
14013
+ orderRanks(byRank, outAdj, inAdj, sweeps);
14014
+ const metric = opts.sizeBy && opts.sizeBy !== "none" ? graph.nodes.map((node) => Math.max(0, (opts.sizeBy === "flops" ? node.flops : node.params) ?? 0)) : null;
14015
+ let metricMax = 0;
14016
+ if (metric) {
14017
+ for (const v of metric) if (v > metricMax) metricMax = v;
14018
+ }
14019
+ const scaleOf = (i) => {
14020
+ if (!metric || metricMax <= 0) return 1;
14021
+ const t = Math.log1p(metric[i]) / Math.log1p(metricMax);
14022
+ return minScale + (maxScale - minScale) * t;
14023
+ };
14024
+ const boxes = new Array(n);
14025
+ const rankStep = (vertical ? nodeHeight : nodeWidth) + rankGap;
14026
+ for (let r = 0; r < ranks; r++) {
14027
+ const rankNodes = byRank[r];
14028
+ const sizes = rankNodes.map((i) => (vertical ? nodeWidth : nodeHeight) * scaleOf(i));
14029
+ let total = 0;
14030
+ for (const s of sizes) total += s;
14031
+ total += nodeGap * Math.max(0, rankNodes.length - 1);
14032
+ let cursor = -total / 2;
14033
+ for (let k = 0; k < rankNodes.length; k++) {
14034
+ const i = rankNodes[k];
14035
+ const across = sizes[k];
14036
+ const center = cursor + across / 2;
14037
+ cursor += across + nodeGap;
14038
+ const node = graph.nodes[i];
14039
+ const category = layerCategory(node.type);
14040
+ boxes[i] = {
14041
+ node,
14042
+ category,
14043
+ color: LAYER_COLORS[category],
14044
+ x: vertical ? center : r * rankStep,
14045
+ y: vertical ? -r * rankStep : -center,
14046
+ w: vertical ? across : nodeWidth,
14047
+ h: vertical ? nodeHeight : across,
14048
+ rank: r,
14049
+ order: k
14050
+ };
14051
+ }
14052
+ }
14053
+ const rankEdge = new Array(ranks).fill(vertical ? -Infinity : Infinity);
14054
+ for (const b of boxes) {
14055
+ rankEdge[b.rank] = vertical ? Math.max(rankEdge[b.rank], b.x + b.w / 2) : Math.min(rankEdge[b.rank], b.y - b.h / 2);
14056
+ }
14057
+ const lead = rankGap * 0.4;
14058
+ const laneFor = (ra, rb) => {
14059
+ const lo = Math.min(ra, rb);
14060
+ const hi = Math.max(ra, rb);
14061
+ let lane = rankEdge[lo];
14062
+ for (let r = lo + 1; r <= hi; r++) {
14063
+ lane = vertical ? Math.max(lane, rankEdge[r]) : Math.min(lane, rankEdge[r]);
14064
+ }
14065
+ return lane + (vertical ? 1 : -1) * nodeGap * 0.8;
14066
+ };
14067
+ const edges = [];
14068
+ for (const { edge, from, to } of edgeList) {
14069
+ const a = boxes[from];
14070
+ const b = boxes[to];
14071
+ const span = b.rank - a.rank;
14072
+ const points = [];
14073
+ if (vertical) {
14074
+ const y0 = a.y - a.h / 2;
14075
+ const y1 = b.y + b.h / 2;
14076
+ if (span === 1 && Math.abs(a.x - b.x) < 1e-9) {
14077
+ points.push({ x: a.x, y: y0 }, { x: b.x, y: y1 });
14078
+ } else if (span === 1) {
14079
+ const mid = (y0 + y1) / 2;
14080
+ points.push({ x: a.x, y: y0 }, { x: a.x, y: mid }, { x: b.x, y: mid }, { x: b.x, y: y1 });
14081
+ } else {
14082
+ const lane = laneFor(a.rank, b.rank);
14083
+ points.push(
14084
+ { x: a.x, y: y0 },
14085
+ { x: a.x, y: y0 - lead },
14086
+ { x: lane, y: y0 - lead },
14087
+ { x: lane, y: y1 + lead },
14088
+ { x: b.x, y: y1 + lead },
14089
+ { x: b.x, y: y1 }
14090
+ );
14091
+ }
14092
+ } else {
14093
+ const x0 = a.x + a.w / 2;
14094
+ const x1 = b.x - b.w / 2;
14095
+ if (span === 1 && Math.abs(a.y - b.y) < 1e-9) {
14096
+ points.push({ x: x0, y: a.y }, { x: x1, y: b.y });
14097
+ } else if (span === 1) {
14098
+ const mid = (x0 + x1) / 2;
14099
+ points.push({ x: x0, y: a.y }, { x: mid, y: a.y }, { x: mid, y: b.y }, { x: x1, y: b.y });
14100
+ } else {
14101
+ const lane = laneFor(a.rank, b.rank);
14102
+ points.push(
14103
+ { x: x0, y: a.y },
14104
+ { x: x0 + lead, y: a.y },
14105
+ { x: x0 + lead, y: lane },
14106
+ { x: x1 - lead, y: lane },
14107
+ { x: x1 - lead, y: b.y },
14108
+ { x: x1, y: b.y }
14109
+ );
14110
+ }
14111
+ }
14112
+ edges.push({ from: edge.from, to: edge.to, ...edge.label ? { label: edge.label } : {}, points });
14113
+ }
14114
+ let minX = Infinity;
14115
+ let maxX = -Infinity;
14116
+ let minY = Infinity;
14117
+ let maxY = -Infinity;
14118
+ const cover = (x, y) => {
14119
+ if (x < minX) minX = x;
14120
+ if (x > maxX) maxX = x;
14121
+ if (y < minY) minY = y;
14122
+ if (y > maxY) maxY = y;
14123
+ };
14124
+ for (const b of boxes) {
14125
+ cover(b.x - b.w / 2, b.y - b.h / 2);
14126
+ cover(b.x + b.w / 2, b.y + b.h / 2);
14127
+ }
14128
+ for (const e of edges) for (const p of e.points) cover(p.x, p.y);
14129
+ return { nodes: boxes, edges, extent: { x: [minX, maxX], y: [minY, maxY] }, ranks };
14130
+ }
14131
+ function shapeMetrics(shape) {
14132
+ if (!shape || shape.length === 0) return [1, 1, 1];
14133
+ if (shape.length === 1) return [1, Math.max(1, shape[0]), 1];
14134
+ const h = Math.max(1, shape[shape.length - 2]);
14135
+ const w = Math.max(1, shape[shape.length - 1]);
14136
+ let c = 1;
14137
+ for (let i = 0; i < shape.length - 2; i++) c *= Math.max(1, shape[i]);
14138
+ return [c, h, w];
14139
+ }
14140
+ function modelBoxDims(nodes, opts = {}) {
14141
+ const maxThickness = opts.maxThickness ?? 0.9;
14142
+ const maxFace = opts.maxFace ?? 2.2;
14143
+ const minSize = opts.minSize ?? 0.08;
14144
+ const scale = opts.sizeScale ?? "log";
14145
+ const f = scale === "log" ? (v) => Math.log1p(v) : scale === "sqrt" ? (v) => Math.sqrt(v) : (v) => v;
14146
+ const baseline = f(1);
14147
+ const raw = nodes.map(
14148
+ (node) => shapeMetrics(node.shape).map((v) => Math.max(0, f(v) - baseline))
14149
+ );
14150
+ let maxC = 0;
14151
+ let maxH = 0;
14152
+ let maxW = 0;
14153
+ for (const [c, h, w] of raw) {
14154
+ if (c > maxC) maxC = c;
14155
+ if (h > maxH) maxH = h;
14156
+ if (w > maxW) maxW = w;
14157
+ }
14158
+ const norm2 = (v, max, target) => max > 0 ? Math.max(minSize, v / max * target) : target;
14159
+ return raw.map(([c, h, w]) => [
14160
+ norm2(c, maxC, maxThickness),
14161
+ norm2(h, maxH, maxFace),
14162
+ norm2(w, maxW, maxFace)
14163
+ ]);
14164
+ }
14165
+
14166
+ // src/ml/model-chart.ts
14167
+ function colorTable(overrides) {
14168
+ return overrides ? { ...LAYER_COLORS, ...overrides } : LAYER_COLORS;
14169
+ }
14170
+ function describeNode(node) {
14171
+ const parts = [node.name && node.name !== node.type ? `${node.name} \xB7 ${node.type}` : node.type];
14172
+ const shape = formatShape(node.shape);
14173
+ if (shape) parts.push(shape);
14174
+ if (node.params) parts.push(`${formatCount(node.params)} params`);
14175
+ if (node.flops) parts.push(`${formatCount(node.flops)} FLOPs`);
14176
+ return parts.join(" \xB7 ");
14177
+ }
14178
+ function roundedRect(cx, cy, w, h, r, seg = 3) {
14179
+ const hw = w / 2;
14180
+ const hh = h / 2;
14181
+ const rad = Math.max(0, Math.min(r, hw, hh));
14182
+ if (rad === 0) {
14183
+ return { x: [cx - hw, cx + hw, cx + hw, cx - hw], y: [cy - hh, cy - hh, cy + hh, cy + hh] };
14184
+ }
14185
+ const corners = [
14186
+ [cx + hw - rad, cy + hh - rad, 0],
14187
+ [cx - hw + rad, cy + hh - rad, Math.PI / 2],
14188
+ [cx - hw + rad, cy - hh + rad, Math.PI],
14189
+ [cx + hw - rad, cy - hh + rad, 3 * Math.PI / 2]
14190
+ ];
14191
+ const x = [];
14192
+ const y = [];
14193
+ for (const [ccx, ccy, a0] of corners) {
14194
+ for (let i = 0; i <= seg; i++) {
14195
+ const a = a0 + Math.PI / 2 * (i / seg);
14196
+ x.push(ccx + rad * Math.cos(a));
14197
+ y.push(ccy + rad * Math.sin(a));
14198
+ }
14199
+ }
14200
+ return { x, y };
14201
+ }
14202
+ function segmentQuad(x0, y0, x1, y1, thickness) {
14203
+ const dx = x1 - x0;
14204
+ const dy = y1 - y0;
14205
+ const len = Math.hypot(dx, dy);
14206
+ if (len < 1e-12) return null;
14207
+ const ux = dx / len;
14208
+ const uy = dy / len;
14209
+ const half = thickness / 2;
14210
+ const ax = x0 - ux * half;
14211
+ const ay = y0 - uy * half;
14212
+ const bx = x1 + ux * half;
14213
+ const by = y1 + uy * half;
14214
+ const nx = -uy * half;
14215
+ const ny = ux * half;
14216
+ return {
14217
+ x: [ax + nx, bx + nx, bx - nx, ax - nx],
14218
+ y: [ay + ny, by + ny, by - ny, ay - ny]
14219
+ };
14220
+ }
14221
+ function arrowHead(x0, y0, x1, y1, size) {
14222
+ const dx = x1 - x0;
14223
+ const dy = y1 - y0;
14224
+ const len = Math.hypot(dx, dy);
14225
+ if (len < 1e-12 || size <= 0) return null;
14226
+ const ux = dx / len;
14227
+ const uy = dy / len;
14228
+ const bx = x1 - ux * size;
14229
+ const by = y1 - uy * size;
14230
+ const wx = -uy * size * 0.45;
14231
+ const wy = ux * size * 0.45;
14232
+ return { x: [x1, bx + wx, bx - wx], y: [y1, by + wy, by - wy] };
14233
+ }
14234
+ function addModelGraph(plot, opts) {
14235
+ const layout = modelLayout(opts.graph, opts);
14236
+ const colors = colorTable(opts.colors);
14237
+ const cornerRadius = opts.cornerRadius ?? 0.14;
14238
+ const edgeWidth = opts.edgeWidth ?? 0.05;
14239
+ const arrowSize = opts.arrowSize ?? 0.18;
14240
+ const dark = (opts.theme ?? "dark") === "dark";
14241
+ const edgeColor = opts.edgeColor ?? (dark ? "#64748b" : "#94a3b8");
14242
+ const labelMode = opts.labels ?? "full";
14243
+ const labelColor = opts.labelColor ?? (dark ? "#f1f5f9" : "#0f172a");
14244
+ const subLabelColor = opts.subLabelColor ?? (dark ? "#cbd5e1" : "#475569");
14245
+ const labelFont = opts.labelFont ?? "600 12px system-ui, -apple-system, sans-serif";
14246
+ const subLabelFont = opts.subLabelFont ?? "10px system-ui, -apple-system, sans-serif";
14247
+ const edgePatches = [];
14248
+ for (const e of layout.edges) {
14249
+ for (let i = 1; i < e.points.length; i++) {
14250
+ const a = e.points[i - 1];
14251
+ const b = e.points[i];
14252
+ const quad = segmentQuad(a.x, a.y, b.x, b.y, edgeWidth);
14253
+ if (quad) edgePatches.push({ ...quad, color: edgeColor });
14254
+ }
14255
+ const last = e.points[e.points.length - 1];
14256
+ const prev = e.points[e.points.length - 2];
14257
+ if (prev) {
14258
+ const head = arrowHead(prev.x, prev.y, last.x, last.y, arrowSize);
14259
+ if (head) edgePatches.push({ ...head, color: edgeColor });
14260
+ }
14261
+ }
14262
+ const edges = plot.addPatches({ patches: edgePatches, color: edgeColor });
14263
+ const nodePatches = layout.nodes.map((b) => ({
14264
+ ...roundedRect(b.x, b.y, b.w, b.h, cornerRadius),
14265
+ color: colors[b.category]
14266
+ }));
14267
+ const nodes = plot.addPatches({
14268
+ patches: nodePatches,
14269
+ opacity: opts.opacity ?? 0.9,
14270
+ ...opts.name ? { name: opts.name } : {}
14271
+ });
14272
+ const disposers = [];
14273
+ if (labelMode !== "none") {
14274
+ for (const b of layout.nodes) {
14275
+ const lines = [];
14276
+ if (labelMode === "name") {
14277
+ lines.push({ text: b.node.name ?? b.node.type, sub: false });
14278
+ } else {
14279
+ lines.push({ text: b.node.type, sub: false });
14280
+ const name = b.node.name ?? b.node.id;
14281
+ if (name && name !== b.node.type) lines.push({ text: name, sub: true });
14282
+ const detail = [formatShape(b.node.shape), b.node.params ? `${formatCount(b.node.params)}p` : ""].filter(Boolean).join(" ");
14283
+ if (detail) lines.push({ text: detail, sub: true });
14284
+ }
14285
+ const step = Math.min(b.h, opts.nodeHeight ?? 0.9) * 0.27;
14286
+ const top = (lines.length - 1) / 2 * step;
14287
+ for (let i = 0; i < lines.length; i++) {
14288
+ const line = lines[i];
14289
+ disposers.push(
14290
+ plot.addAnnotation({
14291
+ type: "label",
14292
+ x: b.x,
14293
+ y: b.y + top - i * step,
14294
+ text: line.text,
14295
+ align: "center",
14296
+ color: line.sub ? subLabelColor : labelColor,
14297
+ font: line.sub ? subLabelFont : labelFont
14298
+ })
14299
+ );
14300
+ }
14301
+ }
14302
+ }
14303
+ if (opts.hideAxes !== false) {
14304
+ plot.setAxis("x", { ticks: [], showAxisLine: false });
14305
+ plot.setAxis("y", { ticks: [], showAxisLine: false });
14306
+ }
14307
+ const nodeAt = (x, y) => {
14308
+ for (let i = layout.nodes.length - 1; i >= 0; i--) {
14309
+ const b = layout.nodes[i];
14310
+ if (Math.abs(x - b.x) <= b.w / 2 && Math.abs(y - b.y) <= b.h / 2) return b;
14311
+ }
14312
+ return null;
14313
+ };
14314
+ let teardownTooltip = () => {
14315
+ };
14316
+ if (opts.tooltip !== false) {
14317
+ const host = plot.element;
14318
+ const tip = document.createElement("div");
14319
+ Object.assign(tip.style, {
14320
+ position: "absolute",
14321
+ display: "none",
14322
+ zIndex: "7",
14323
+ pointerEvents: "none",
14324
+ padding: "6px 8px",
14325
+ borderRadius: "6px",
14326
+ font: "12px system-ui, -apple-system, sans-serif",
14327
+ lineHeight: "1.45",
14328
+ whiteSpace: "nowrap",
14329
+ background: dark ? "rgba(15,23,42,0.94)" : "rgba(255,255,255,0.97)",
14330
+ color: dark ? "#e2e8f0" : "#1e293b",
14331
+ border: `1px solid ${dark ? "rgba(148,163,184,0.3)" : "rgba(100,116,139,0.3)"}`,
14332
+ boxShadow: "0 6px 18px rgba(0,0,0,0.25)"
14333
+ });
14334
+ host.appendChild(tip);
14335
+ const hide = () => {
14336
+ tip.style.display = "none";
14337
+ };
14338
+ const move = (ev) => {
14339
+ const data = plot.dataAt(ev.clientX, ev.clientY);
14340
+ const box = data && nodeAt(data.x, data.y);
14341
+ if (!box) {
14342
+ hide();
14343
+ return;
14344
+ }
14345
+ tip.replaceChildren();
14346
+ const title = document.createElement("div");
14347
+ title.textContent = box.node.name ?? box.node.id;
14348
+ title.style.fontWeight = "600";
14349
+ tip.appendChild(title);
14350
+ const detail = document.createElement("div");
14351
+ detail.style.opacity = "0.75";
14352
+ detail.textContent = describeNode(box.node);
14353
+ tip.appendChild(detail);
14354
+ tip.style.display = "block";
14355
+ const rect = host.getBoundingClientRect();
14356
+ const lx = ev.clientX - rect.left;
14357
+ const ly = ev.clientY - rect.top;
14358
+ let left = lx + 14;
14359
+ if (left + tip.offsetWidth > host.clientWidth) left = lx - tip.offsetWidth - 14;
14360
+ let top = ly + 14;
14361
+ if (top + tip.offsetHeight > host.clientHeight) top = ly - tip.offsetHeight - 14;
14362
+ tip.style.left = `${Math.max(0, left)}px`;
14363
+ tip.style.top = `${Math.max(0, top)}px`;
14364
+ };
14365
+ host.addEventListener("pointermove", move);
14366
+ host.addEventListener("pointerleave", hide);
14367
+ teardownTooltip = () => {
14368
+ host.removeEventListener("pointermove", move);
14369
+ host.removeEventListener("pointerleave", hide);
14370
+ tip.remove();
14371
+ };
14372
+ }
14373
+ return {
14374
+ layout,
14375
+ nodes,
14376
+ edges,
14377
+ nodeAt,
14378
+ destroy() {
14379
+ for (const d of disposers) d();
14380
+ disposers.length = 0;
14381
+ teardownTooltip();
14382
+ teardownTooltip = () => {
14383
+ };
14384
+ }
14385
+ };
14386
+ }
14387
+ function addModelGraph3D(plot, opts) {
14388
+ const { graph } = opts;
14389
+ const layout = modelLayout(graph, opts);
14390
+ const dims = modelBoxDims(graph.nodes, opts);
14391
+ const colors = colorTable(opts.colors);
14392
+ const rankSpacing = opts.rankSpacing ?? 1.1;
14393
+ const branchSpacing = opts.branchSpacing ?? 3;
14394
+ const rankThickness = new Array(layout.ranks).fill(0);
14395
+ const rankCount = new Array(layout.ranks).fill(0);
14396
+ for (let i = 0; i < layout.nodes.length; i++) {
14397
+ const r = layout.nodes[i].rank;
14398
+ const t = dims[i][0];
14399
+ if (t > rankThickness[r]) rankThickness[r] = t;
14400
+ rankCount[r]++;
14401
+ }
14402
+ const rankX = new Array(layout.ranks).fill(0);
14403
+ for (let r = 1; r < layout.ranks; r++) {
14404
+ rankX[r] = rankX[r - 1] + rankThickness[r - 1] / 2 + rankSpacing + rankThickness[r] / 2;
14405
+ }
14406
+ const blocks = layout.nodes.map((b, i) => {
14407
+ const [w, h, d] = dims[i];
14408
+ const siblings = rankCount[b.rank];
14409
+ return {
14410
+ node: b.node,
14411
+ category: b.category,
14412
+ x: rankX[b.rank],
14413
+ y: 0,
14414
+ z: (b.order - (siblings - 1) / 2) * branchSpacing,
14415
+ w,
14416
+ h,
14417
+ d,
14418
+ rank: b.rank
14419
+ };
14420
+ });
14421
+ const boxes = plot.addBoxes3D({
14422
+ boxes: blocks.map((b) => ({
14423
+ x: b.x,
14424
+ y: b.y,
14425
+ z: b.z,
14426
+ w: b.w,
14427
+ h: b.h,
14428
+ d: b.d,
14429
+ color: colors[b.category],
14430
+ label: describeNode(b.node)
14431
+ })),
14432
+ opacity: opts.opacity ?? 1,
14433
+ ...opts.name ? { name: opts.name } : {}
14434
+ });
14435
+ const labelMode = opts.labels ?? "name";
14436
+ const disposers = [];
14437
+ if (labelMode !== "none") {
14438
+ const labelColor = opts.labelColor ?? "#e2e8f0";
14439
+ const subLabelColor = opts.subLabelColor ?? "#94a3b8";
14440
+ const labelFont = opts.labelFont ?? "600 11px system-ui, -apple-system, sans-serif";
14441
+ const subLabelFont = opts.subLabelFont ?? "9px system-ui, -apple-system, sans-serif";
14442
+ for (const b of blocks) {
14443
+ const head = labelMode === "type" ? b.node.type : b.node.name ?? b.node.id;
14444
+ disposers.push(plot.addLabel3D({
14445
+ x: b.x,
14446
+ y: b.y + b.h / 2,
14447
+ z: b.z,
14448
+ text: head,
14449
+ color: labelColor,
14450
+ font: labelFont,
14451
+ dy: -10,
14452
+ baseline: "bottom"
14453
+ }));
14454
+ const shape = labelMode === "full" ? formatShape(b.node.shape) : "";
14455
+ if (shape) {
14456
+ disposers.push(plot.addLabel3D({
14457
+ x: b.x,
14458
+ y: b.y - b.h / 2,
14459
+ z: b.z,
14460
+ text: shape,
14461
+ color: subLabelColor,
14462
+ font: subLabelFont,
14463
+ dy: 10,
14464
+ baseline: "top"
14465
+ }));
14466
+ }
14467
+ }
14468
+ }
14469
+ const connectors = [];
14470
+ if (opts.connectors !== false) {
14471
+ const byId = /* @__PURE__ */ new Map();
14472
+ for (const b of blocks) byId.set(b.node.id, b);
14473
+ const color = opts.connectorColor ?? "#64748b";
14474
+ for (const e of graph.edges) {
14475
+ const a = byId.get(e.from);
14476
+ const b = byId.get(e.to);
14477
+ if (!a || !b) continue;
14478
+ const x0 = a.x + a.w / 2;
14479
+ const x1 = b.x - b.w / 2;
14480
+ const mid = (x0 + x1) / 2;
14481
+ const straight = Math.abs(a.z - b.z) < 1e-9;
14482
+ const xs = straight ? [x0, x1] : [x0, mid, mid, x1];
14483
+ const ys = straight ? [a.y, b.y] : [a.y, a.y, b.y, b.y];
14484
+ const zs = straight ? [a.z, b.z] : [a.z, a.z, b.z, b.z];
14485
+ connectors.push(plot.addLine3D({ x: xs, y: ys, z: zs, color }));
14486
+ }
14487
+ }
14488
+ return {
14489
+ boxes,
14490
+ connectors,
14491
+ blocks,
14492
+ destroy() {
14493
+ for (const d of disposers) d();
14494
+ disposers.length = 0;
14495
+ }
14496
+ };
14497
+ }
12000
14498
 
12001
14499
  // src/charts/treemap.ts
12002
14500
  var DEFAULT_EXTENT = { x: [0, 1], y: [0, 1] };
12003
- var TREEMAP_PALETTE = [
12004
- "#4e79a7",
12005
- "#f28e2b",
12006
- "#e15759",
12007
- "#76b7b2",
12008
- "#59a14f",
12009
- "#edc948",
12010
- "#b07aa1",
12011
- "#ff9da7",
12012
- "#9c755f",
12013
- "#bab0ac"
12014
- ];
14501
+ var TREEMAP_PALETTE = DEFAULT_PALETTE;
12015
14502
  function worst(row, w, sum) {
12016
14503
  if (row.length === 0 || w === 0) return Infinity;
12017
14504
  let max = -Infinity, min = Infinity;
@@ -12097,12 +14584,12 @@ function treemapLayout(items, extent = DEFAULT_EXTENT) {
12097
14584
  var LABEL_MIN_FRAC = 0.04;
12098
14585
  function addTreemap(plot, opts) {
12099
14586
  const extent = opts.extent ?? DEFAULT_EXTENT;
12100
- const palette = opts.colors && opts.colors.length ? opts.colors : TREEMAP_PALETTE;
14587
+ const palette2 = palette(opts.colors ?? TREEMAP_PALETTE);
12101
14588
  const cells = treemapLayout(opts.items, extent);
12102
14589
  const patches = cells.map((c, i) => ({
12103
14590
  x: [c.x0, c.x1, c.x1, c.x0],
12104
14591
  y: [c.y0, c.y0, c.y1, c.y1],
12105
- color: c.color ?? palette[i % palette.length]
14592
+ color: c.color ?? palette2[i % palette2.length]
12106
14593
  }));
12107
14594
  const layer = plot.addPatches({
12108
14595
  patches,
@@ -12128,18 +14615,7 @@ function addTreemap(plot, opts) {
12128
14615
  }
12129
14616
 
12130
14617
  // src/charts/funnel.ts
12131
- var FUNNEL_PALETTE = [
12132
- "#4e79a7",
12133
- "#f28e2b",
12134
- "#e15759",
12135
- "#76b7b2",
12136
- "#59a14f",
12137
- "#edc948",
12138
- "#b07aa1",
12139
- "#ff9da7",
12140
- "#9c755f",
12141
- "#bab0ac"
12142
- ];
14618
+ var FUNNEL_PALETTE = DEFAULT_PALETTE;
12143
14619
  function funnelLayout(items, opts = {}) {
12144
14620
  const n = items.length;
12145
14621
  if (n === 0) return [];
@@ -12169,12 +14645,12 @@ function funnelLayout(items, opts = {}) {
12169
14645
  return stages;
12170
14646
  }
12171
14647
  function addFunnel(plot, opts) {
12172
- const palette = opts.colors && opts.colors.length ? opts.colors : FUNNEL_PALETTE;
14648
+ const palette2 = palette(opts.colors ?? FUNNEL_PALETTE);
12173
14649
  const stages = funnelLayout(opts.items, opts);
12174
14650
  const patches = stages.map((s, i) => ({
12175
14651
  x: s.poly.x,
12176
14652
  y: s.poly.y,
12177
- color: s.color ?? palette[i % palette.length]
14653
+ color: s.color ?? palette2[i % palette2.length]
12178
14654
  }));
12179
14655
  const layer = plot.addPatches({
12180
14656
  patches,
@@ -12250,11 +14726,11 @@ function arcRing(a0, a1, r0, r1, color) {
12250
14726
  return { x, y, color };
12251
14727
  }
12252
14728
  function addSunburst(plot, opts) {
12253
- const palette = opts.colors && opts.colors.length > 0 ? opts.colors : DEFAULT_COLORS2;
14729
+ const palette2 = opts.colors && opts.colors.length > 0 ? opts.colors : DEFAULT_COLORS2;
12254
14730
  const arcs = sunburstLayout(opts.root, { ringWidth: opts.ringWidth });
12255
14731
  const patches = arcs.map((arc, i) => {
12256
14732
  if (arc.a1 - arc.a0 <= 0) return { x: [], y: [] };
12257
- const color = arc.color ?? palette[i % palette.length];
14733
+ const color = arc.color ?? palette2[i % palette2.length];
12258
14734
  return arcRing(arc.a0, arc.a1, arc.r0, arc.r1, color);
12259
14735
  });
12260
14736
  return plot.addPatches({
@@ -12506,18 +14982,7 @@ function addSankey(plot, opts) {
12506
14982
  }
12507
14983
 
12508
14984
  // src/charts/chord.ts
12509
- var CHORD_PALETTE = [
12510
- "#4e79a7",
12511
- "#f28e2b",
12512
- "#e15759",
12513
- "#76b7b2",
12514
- "#59a14f",
12515
- "#edc948",
12516
- "#b07aa1",
12517
- "#ff9da7",
12518
- "#9c755f",
12519
- "#bab0ac"
12520
- ];
14985
+ var CHORD_PALETTE = DEFAULT_PALETTE;
12521
14986
  function onCircle(r, a) {
12522
14987
  return [r * Math.cos(a), r * Math.sin(a)];
12523
14988
  }
@@ -12615,10 +15080,10 @@ function chordLayout(matrix, opts = {}) {
12615
15080
  }
12616
15081
  function addChord(plot, opts) {
12617
15082
  const radius = opts.radius ?? 1;
12618
- const palette = opts.colors && opts.colors.length ? opts.colors : CHORD_PALETTE;
15083
+ const palette2 = palette(opts.colors ?? CHORD_PALETTE);
12619
15084
  const opacity = opts.opacity ?? 0.65;
12620
15085
  const { groupArcs, ribbons } = chordLayout(opts.matrix, { radius });
12621
- const color = (i) => palette[i % palette.length];
15086
+ const color = (i) => palette2[i % palette2.length];
12622
15087
  const patches = [];
12623
15088
  for (const rb of ribbons) {
12624
15089
  patches.push({ x: rb.x, y: rb.y, color: withAlpha2(color(rb.i), opacity) });
@@ -12672,18 +15137,7 @@ function withAlpha2(color, alpha) {
12672
15137
  }
12673
15138
 
12674
15139
  // src/charts/parallel.ts
12675
- var PARALLEL_PALETTE = [
12676
- "#4e79a7",
12677
- "#f28e2b",
12678
- "#e15759",
12679
- "#76b7b2",
12680
- "#59a14f",
12681
- "#edc948",
12682
- "#b07aa1",
12683
- "#ff9da7",
12684
- "#9c755f",
12685
- "#bab0ac"
12686
- ];
15140
+ var PARALLEL_PALETTE = DEFAULT_PALETTE;
12687
15141
  function parallelLayout(dimensions, rows) {
12688
15142
  const d = dimensions.length;
12689
15143
  const mins = new Array(d).fill(Infinity);
@@ -12718,7 +15172,7 @@ function parallelLayout(dimensions, rows) {
12718
15172
  return { axes, lines };
12719
15173
  }
12720
15174
  function addParallelCoordinates(plot, opts) {
12721
- const palette = opts.colors && opts.colors.length ? opts.colors : PARALLEL_PALETTE;
15175
+ const palette2 = opts.colors && opts.colors.length ? opts.colors : PARALLEL_PALETTE;
12722
15176
  const width = opts.width ?? 1;
12723
15177
  const opacity = opts.opacity ?? 0.7;
12724
15178
  const { axes, lines } = parallelLayout(opts.dimensions, opts.rows);
@@ -12735,10 +15189,10 @@ function addParallelCoordinates(plot, opts) {
12735
15189
  if (opts.colorBy && Number.isFinite(cbMin)) {
12736
15190
  const v = opts.colorBy[r];
12737
15191
  const t = v == null || !Number.isFinite(v) || cbSpan === 0 ? 0 : (v - cbMin) / cbSpan;
12738
- const band = Math.min(palette.length - 1, Math.max(0, Math.floor(t * palette.length)));
12739
- return palette[band];
15192
+ const band = Math.min(palette2.length - 1, Math.max(0, Math.floor(t * palette2.length)));
15193
+ return palette2[band];
12740
15194
  }
12741
- return palette[r % palette.length];
15195
+ return palette2[r % palette2.length];
12742
15196
  };
12743
15197
  const layers = lines.map(
12744
15198
  (ln, r) => plot.addLine({
@@ -12764,17 +15218,104 @@ function withAlpha3(color, alpha) {
12764
15218
  }
12765
15219
  return color;
12766
15220
  }
15221
+
15222
+ // src/stats/charts.ts
15223
+ function addRegression(plot, opts) {
15224
+ const method = opts.method ?? "ols";
15225
+ const color = opts.color ?? "#f472b6";
15226
+ const trend = method === "loess" ? loess(opts.x, opts.y, { bandwidth: opts.bandwidth, points: opts.points ?? 100 }) : linearTrend(opts.x, opts.y, { points: opts.points ?? 2, band: opts.band });
15227
+ const fit = method === "ols" ? linearRegression(opts.x, opts.y) : null;
15228
+ let band;
15229
+ if (trend.lower && trend.upper) {
15230
+ band = plot.addArea({
15231
+ x: trend.x,
15232
+ y: trend.upper,
15233
+ base: trend.lower,
15234
+ color: opts.bandColor ?? `${color}33`,
15235
+ yAxis: opts.yAxis,
15236
+ renderType: opts.renderType
15237
+ });
15238
+ }
15239
+ const label = opts.name ?? (fit ? `fit \xB7 r\xB2=${fit.r2.toFixed(3)}` : "loess");
15240
+ const line = plot.addLine({
15241
+ x: trend.x,
15242
+ y: trend.y,
15243
+ color,
15244
+ width: opts.width ?? 2,
15245
+ name: label,
15246
+ yAxis: opts.yAxis,
15247
+ renderType: opts.renderType
15248
+ });
15249
+ return { line, ...band ? { band } : {}, fit };
15250
+ }
15251
+ function addEcdf(plot, opts) {
15252
+ const { x, y } = ecdf(opts.values);
15253
+ return plot.addLine({
15254
+ x,
15255
+ y,
15256
+ step: "after",
15257
+ color: opts.color ?? "#60a5fa",
15258
+ width: opts.width ?? 2,
15259
+ name: opts.name,
15260
+ yAxis: opts.yAxis,
15261
+ renderType: opts.renderType
15262
+ });
15263
+ }
15264
+ function addCorrMatrix(plot, opts) {
15265
+ const { values, size } = corrMatrix(opts.columns);
15266
+ const heatmap = plot.addHeatmap({
15267
+ values,
15268
+ cols: size,
15269
+ rows: size,
15270
+ extent: { x: [-0.5, size - 0.5], y: [-0.5, size - 0.5] },
15271
+ colormap: opts.colormap ?? "RdBu",
15272
+ domain: [-1, 1],
15273
+ smooth: false,
15274
+ name: opts.name ?? "correlation",
15275
+ yAxis: opts.yAxis
15276
+ });
15277
+ if (opts.labelAxes !== false && opts.names?.length) {
15278
+ const names = opts.names;
15279
+ const ticks = names.map((_, i) => i);
15280
+ const format = (v) => names[Math.round(v)] ?? "";
15281
+ plot.setAxis("x", { ticks, format });
15282
+ plot.setAxis(opts.yAxis ?? "y", { ticks, format });
15283
+ }
15284
+ return { heatmap, values, size };
15285
+ }
15286
+ function addPsd(plot, opts) {
15287
+ const psd = welch(opts.signal, {
15288
+ sampleRate: opts.sampleRate,
15289
+ segment: opts.segment,
15290
+ overlap: opts.overlap,
15291
+ window: opts.window
15292
+ });
15293
+ const y = opts.decibels === false ? psd.power : Float64Array.from(psd.power, (p) => 10 * Math.log10(p + 1e-20));
15294
+ const line = plot.addLine({
15295
+ x: psd.frequencies,
15296
+ y,
15297
+ color: opts.color ?? "#38bdf8",
15298
+ width: opts.width ?? 1.5,
15299
+ name: opts.name ?? "PSD",
15300
+ yAxis: opts.yAxis,
15301
+ renderType: opts.renderType
15302
+ });
15303
+ return { line, frequencies: psd.frequencies, power: psd.power };
15304
+ }
12767
15305
  export {
12768
15306
  AreaLayer,
12769
15307
  Axis,
12770
15308
  Bar3DLayer,
12771
15309
  BarLayer,
12772
15310
  BoxLayer,
15311
+ Boxes3DLayer,
12773
15312
  CHORD_PALETTE,
15313
+ COLORMAP_KIND,
12774
15314
  CandlestickLayer,
12775
15315
  CategoricalScale,
12776
15316
  Contour3DLayer,
12777
15317
  ContourLayer,
15318
+ DEFAULT_PALETTE,
12778
15319
  ErrorBarLayer,
12779
15320
  FUNNEL_PALETTE,
12780
15321
  GraphLayer,
@@ -12782,6 +15323,7 @@ export {
12782
15323
  HexbinLayer,
12783
15324
  ImageLayer,
12784
15325
  IsosurfaceLayer,
15326
+ LAYER_COLORS,
12785
15327
  Line3DLayer,
12786
15328
  LineLayer,
12787
15329
  LinearScale,
@@ -12789,6 +15331,7 @@ export {
12789
15331
  ML_PALETTE,
12790
15332
  OhlcLayer,
12791
15333
  OrdinalTimeScale,
15334
+ PALETTES,
12792
15335
  PARALLEL_PALETTE,
12793
15336
  PatchesLayer,
12794
15337
  PieLayer,
@@ -12811,17 +15354,28 @@ export {
12811
15354
  addCalibration,
12812
15355
  addChord,
12813
15356
  addConfusionMatrix,
15357
+ addCorrMatrix,
12814
15358
  addDecisionBoundary,
12815
15359
  addDepth,
15360
+ addDrawdown,
15361
+ addEcdf,
12816
15362
  addEmbedding,
12817
15363
  addFeatureImportance,
12818
15364
  addFunnel,
12819
15365
  addGauge,
12820
15366
  addHeikinAshi,
15367
+ addLearningCurve,
15368
+ addLiftCurve,
15369
+ addModelGraph,
15370
+ addModelGraph3D,
12821
15371
  addParallelCoordinates,
12822
15372
  addPartialDependence,
12823
15373
  addPrCurve,
15374
+ addPredVsActual,
15375
+ addPsd,
15376
+ addRegression,
12824
15377
  addRenko,
15378
+ addResiduals,
12825
15379
  addRidgeline,
12826
15380
  addRocCurve,
12827
15381
  addSankey,
@@ -12831,32 +15385,48 @@ export {
12831
15385
  addTreemap,
12832
15386
  addVolumeProfile,
12833
15387
  adx,
15388
+ aroon,
12834
15389
  atr,
12835
15390
  autoTicks,
12836
15391
  beeswarmLayout,
12837
15392
  bollinger,
12838
15393
  boxStats,
15394
+ brierScore,
12839
15395
  bufferUsage,
12840
15396
  calibrationCurve,
12841
15397
  canvasToBlob,
15398
+ cci,
12842
15399
  chordLayout,
15400
+ classificationReport,
12843
15401
  colormap,
15402
+ colormapFromStops,
15403
+ colormapGradient,
12844
15404
  colormapLUT,
15405
+ colormapNames,
12845
15406
  confusionMatrix,
12846
15407
  copyCanvasToClipboard,
15408
+ corrMatrix,
15409
+ correlation,
12847
15410
  createProgram,
12848
15411
  createToolbar,
15412
+ crossCorrelate,
12849
15413
  darkTheme,
12850
15414
  defaultFormat,
12851
15415
  depth,
15416
+ discreteColormap,
15417
+ donchian,
12852
15418
  downloadCanvas,
15419
+ drawdown,
12853
15420
  earcut,
15421
+ ecdf,
12854
15422
  ema,
12855
15423
  emaSmooth,
12856
15424
  fft,
12857
15425
  fibRetracements,
12858
15426
  firstFinite,
12859
15427
  forceLayout,
15428
+ formatCount,
15429
+ formatShape,
12860
15430
  funnelLayout,
12861
15431
  gaugeLayout,
12862
15432
  heikinAshi,
@@ -12864,28 +15434,59 @@ export {
12864
15434
  ichimoku,
12865
15435
  kde,
12866
15436
  keltner,
15437
+ layerCategory,
15438
+ liftCurve,
12867
15439
  lightTheme,
12868
15440
  lineBreak,
15441
+ linearRegression,
15442
+ linearTrend,
12869
15443
  linkX,
15444
+ loess,
15445
+ logLoss,
12870
15446
  lttb,
12871
15447
  macd,
15448
+ mae,
12872
15449
  makeScale,
12873
15450
  marchingCubes,
15451
+ mfi,
15452
+ mlpModel,
15453
+ modelBoxDims,
15454
+ modelGraphFromKeras,
15455
+ modelGraphFromOnnx,
15456
+ modelGraphFromSklearn,
15457
+ modelGraphFromTorchFx,
15458
+ modelLayout,
15459
+ mse,
12874
15460
  obv,
15461
+ palette,
15462
+ paletteColor,
15463
+ paletteNames,
15464
+ parabolicSar,
12875
15465
  parallelLayout,
12876
15466
  parseCSV,
12877
15467
  parseColor,
12878
15468
  pca,
15469
+ pivotPoints,
12879
15470
  pointAndFigure,
12880
15471
  prCurve,
12881
15472
  quantileSorted,
15473
+ r2,
15474
+ registerColormap,
15475
+ registerPalette,
15476
+ renderColorbars,
12882
15477
  renko,
15478
+ resampleOhlc,
12883
15479
  resolveAxisStyle,
12884
15480
  resolveTicks,
15481
+ reverseColormap,
15482
+ rmse,
12885
15483
  rocCurve,
15484
+ rocCurveOvR,
12886
15485
  rollingStd,
12887
15486
  rsi,
12888
15487
  sankeyLayout,
15488
+ savitzkyGolay,
15489
+ sequentialModel,
12889
15490
  setTransformUniforms,
12890
15491
  sma,
12891
15492
  spectrogram,
@@ -12893,13 +15494,18 @@ export {
12893
15494
  stochastic,
12894
15495
  sunburstLayout,
12895
15496
  superTrend,
15497
+ symmetricDomain,
12896
15498
  toColorCss,
12897
15499
  treemapLayout,
12898
15500
  trueRange,
12899
15501
  uniformLocations,
12900
15502
  volumeProfile,
12901
15503
  vwap,
15504
+ welch,
15505
+ williamsR,
15506
+ windowFunction,
12902
15507
  withMinorTicks,
12903
- wma
15508
+ wma,
15509
+ zscore
12904
15510
  };
12905
15511
  //# sourceMappingURL=index.js.map