@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/README.md +115 -4
- package/dist/index.d.ts +1297 -43
- package/dist/index.js +2802 -196
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
1461
|
-
[0.
|
|
1462
|
-
[0.
|
|
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(
|
|
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(
|
|
1484
|
-
|
|
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(
|
|
1487
|
-
lutCache.set(
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
8164
|
-
function
|
|
8165
|
-
let p =
|
|
8847
|
+
var programCache16 = /* @__PURE__ */ new WeakMap();
|
|
8848
|
+
function getProgram13(gl) {
|
|
8849
|
+
let p = programCache16.get(gl);
|
|
8166
8850
|
if (!p) {
|
|
8167
|
-
p = createProgram(gl,
|
|
8168
|
-
|
|
8851
|
+
p = createProgram(gl, VERT12, FRAG15);
|
|
8852
|
+
programCache16.set(gl, p);
|
|
8169
8853
|
}
|
|
8170
8854
|
return p;
|
|
8171
8855
|
}
|
|
8172
|
-
var
|
|
8856
|
+
var counter20 = 0;
|
|
8173
8857
|
var Contour3DLayer = class {
|
|
8174
8858
|
constructor(gl, opts) {
|
|
8175
8859
|
this.cInfo = null;
|
|
8176
|
-
this.id = `contour3d-${
|
|
8860
|
+
this.id = `contour3d-${counter20++}`;
|
|
8177
8861
|
this.gl = gl;
|
|
8178
|
-
this.program =
|
|
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
|
|
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
|
|
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
|
|
8916
|
-
function
|
|
8917
|
-
let p =
|
|
9599
|
+
var programCache17 = /* @__PURE__ */ new WeakMap();
|
|
9600
|
+
function getProgram14(gl) {
|
|
9601
|
+
let p = programCache17.get(gl);
|
|
8918
9602
|
if (!p) {
|
|
8919
|
-
p = createProgram(gl,
|
|
8920
|
-
|
|
9603
|
+
p = createProgram(gl, VERT13, FRAG16);
|
|
9604
|
+
programCache17.set(gl, p);
|
|
8921
9605
|
}
|
|
8922
9606
|
return p;
|
|
8923
9607
|
}
|
|
8924
|
-
var
|
|
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-${
|
|
9613
|
+
this.id = `isosurface-${counter21++}`;
|
|
8930
9614
|
this.gl = gl;
|
|
8931
|
-
this.program =
|
|
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
|
|
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
|
|
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
|
|
9018
|
-
function
|
|
9019
|
-
let p =
|
|
9701
|
+
var programCache18 = /* @__PURE__ */ new WeakMap();
|
|
9702
|
+
function getProgram15(gl) {
|
|
9703
|
+
let p = programCache18.get(gl);
|
|
9020
9704
|
if (!p) {
|
|
9021
|
-
p = createProgram(gl,
|
|
9022
|
-
|
|
9705
|
+
p = createProgram(gl, VERT14, FRAG17);
|
|
9706
|
+
programCache18.set(gl, p);
|
|
9023
9707
|
}
|
|
9024
9708
|
return p;
|
|
9025
9709
|
}
|
|
9026
|
-
var
|
|
9710
|
+
var counter22 = 0;
|
|
9027
9711
|
var Line3DLayer = class {
|
|
9028
9712
|
constructor(gl, opts) {
|
|
9029
|
-
this.id = `line3d-${
|
|
9713
|
+
this.id = `line3d-${counter22++}`;
|
|
9030
9714
|
this.gl = gl;
|
|
9031
|
-
this.program =
|
|
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
|
|
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
|
|
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
|
|
9215
|
-
function
|
|
9216
|
-
let p =
|
|
9907
|
+
var programCache19 = /* @__PURE__ */ new WeakMap();
|
|
9908
|
+
function getProgram16(gl) {
|
|
9909
|
+
let p = programCache19.get(gl);
|
|
9217
9910
|
if (!p) {
|
|
9218
|
-
p = createProgram(gl,
|
|
9219
|
-
|
|
9911
|
+
p = createProgram(gl, VERT15, FRAG18);
|
|
9912
|
+
programCache19.set(gl, p);
|
|
9220
9913
|
}
|
|
9221
9914
|
return p;
|
|
9222
9915
|
}
|
|
9223
|
-
var
|
|
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-${
|
|
9921
|
+
this.id = `points3d-${counter23++}`;
|
|
9229
9922
|
this.gl = gl;
|
|
9230
|
-
this.program =
|
|
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
|
|
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
|
|
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
|
|
9367
|
-
function
|
|
9368
|
-
let p =
|
|
10059
|
+
var programCache20 = /* @__PURE__ */ new WeakMap();
|
|
10060
|
+
function getProgram17(gl) {
|
|
10061
|
+
let p = programCache20.get(gl);
|
|
9369
10062
|
if (!p) {
|
|
9370
|
-
p = createProgram(gl,
|
|
9371
|
-
|
|
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
|
|
10075
|
+
var counter24 = 0;
|
|
9383
10076
|
var Quiver3DLayer = class {
|
|
9384
10077
|
constructor(gl, opts) {
|
|
9385
10078
|
this.cInfo = null;
|
|
9386
|
-
this.id = `quiver3d-${
|
|
10079
|
+
this.id = `quiver3d-${counter24++}`;
|
|
9387
10080
|
this.gl = gl;
|
|
9388
|
-
this.program =
|
|
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
|
|
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
|
|
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
|
|
9544
|
-
function
|
|
9545
|
-
let p =
|
|
10236
|
+
var programCache21 = /* @__PURE__ */ new WeakMap();
|
|
10237
|
+
function getProgram18(gl) {
|
|
10238
|
+
let p = programCache21.get(gl);
|
|
9546
10239
|
if (!p) {
|
|
9547
|
-
p = createProgram(gl,
|
|
9548
|
-
|
|
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
|
|
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-${
|
|
10260
|
+
this.id = `surface-${counter25++}`;
|
|
9568
10261
|
this.gl = gl;
|
|
9569
10262
|
this.wireframe = opts.wireframe ?? false;
|
|
9570
|
-
this.program = this.wireframe ? getWireProgram(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
|
|
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
|
|
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
|
|
9755
|
-
function
|
|
9756
|
-
let p =
|
|
10447
|
+
var programCache22 = /* @__PURE__ */ new WeakMap();
|
|
10448
|
+
function getProgram19(gl) {
|
|
10449
|
+
let p = programCache22.get(gl);
|
|
9757
10450
|
if (!p) {
|
|
9758
|
-
p = createProgram(gl,
|
|
9759
|
-
|
|
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
|
|
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-${
|
|
10479
|
+
this.id = `volume-${counter26++}`;
|
|
9787
10480
|
this.gl = gl;
|
|
9788
|
-
this.program =
|
|
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
|
-
|
|
10498
|
-
|
|
10499
|
-
|
|
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
|
-
|
|
10542
|
-
|
|
10543
|
-
|
|
10544
|
-
|
|
10545
|
-
|
|
10546
|
-
|
|
10547
|
-
|
|
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
|
-
|
|
11300
|
-
|
|
11301
|
-
const
|
|
11302
|
-
|
|
11303
|
-
|
|
11304
|
-
|
|
11305
|
-
|
|
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
|
|
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 ??
|
|
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
|
|
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 =
|
|
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,
|
|
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:
|
|
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:
|
|
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
|
|
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 ??
|
|
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
|
|
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 ??
|
|
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
|
|
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 ??
|
|
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
|
|
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) =>
|
|
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
|
|
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(
|
|
12739
|
-
return
|
|
15192
|
+
const band = Math.min(palette2.length - 1, Math.max(0, Math.floor(t * palette2.length)));
|
|
15193
|
+
return palette2[band];
|
|
12740
15194
|
}
|
|
12741
|
-
return
|
|
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
|