@photonviz/core 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +70 -0
- package/dist/index.d.ts +900 -4
- package/dist/index.js +3850 -316
- package/dist/index.js.map +1 -1
- package/package.json +5 -2
package/dist/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
// src/axes/ticks.ts
|
|
2
2
|
function niceStep(rawStep) {
|
|
3
3
|
const mag = Math.pow(10, Math.floor(Math.log10(rawStep)));
|
|
4
|
-
const
|
|
4
|
+
const norm2 = rawStep / mag;
|
|
5
5
|
let nice;
|
|
6
|
-
if (
|
|
7
|
-
else if (
|
|
8
|
-
else if (
|
|
6
|
+
if (norm2 < 1.5) nice = 1;
|
|
7
|
+
else if (norm2 < 3) nice = 2;
|
|
8
|
+
else if (norm2 < 7) nice = 5;
|
|
9
9
|
else nice = 10;
|
|
10
10
|
return nice * mag;
|
|
11
11
|
}
|
|
@@ -408,6 +408,7 @@ var BarLayer = class {
|
|
|
408
408
|
this.count = n;
|
|
409
409
|
this.barWidth = opts.width ?? medianSpacing(opts.x, n) * 0.8;
|
|
410
410
|
this.offset = opts.offset ?? 0;
|
|
411
|
+
this.orientation = opts.orientation ?? "v";
|
|
411
412
|
const rects = this.buildRects(opts.x, opts.y, opts.base, n);
|
|
412
413
|
const vao = gl.createVertexArray();
|
|
413
414
|
this.vao = vao;
|
|
@@ -429,23 +430,30 @@ var BarLayer = class {
|
|
|
429
430
|
}
|
|
430
431
|
buildRects(x, y, base, n) {
|
|
431
432
|
const width = this.barWidth, off = this.offset;
|
|
433
|
+
const horizontal = this.orientation === "h";
|
|
432
434
|
const baseAt = (i) => base == null ? 0 : typeof base === "number" ? base : base[i];
|
|
433
|
-
|
|
434
|
-
|
|
435
|
+
const posRef = n > 0 ? x[0] : 0;
|
|
436
|
+
const valRef = n > 0 ? y[0] : 0;
|
|
437
|
+
this.xRef = horizontal ? valRef : posRef;
|
|
438
|
+
this.yRef = horizontal ? posRef : valRef;
|
|
435
439
|
const rects = new Float32Array(n * 4);
|
|
436
440
|
let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
|
|
437
441
|
for (let i = 0; i < n; i++) {
|
|
438
|
-
const
|
|
439
|
-
const
|
|
440
|
-
const b = baseAt(i),
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
442
|
+
const cpos = x[i] + off;
|
|
443
|
+
const p0 = cpos - width / 2, p1 = cpos + width / 2;
|
|
444
|
+
const b = baseAt(i), val = y[i];
|
|
445
|
+
const ax0 = horizontal ? b : p0;
|
|
446
|
+
const ax1 = horizontal ? val : p1;
|
|
447
|
+
const ay0 = horizontal ? p0 : b;
|
|
448
|
+
const ay1 = horizontal ? p1 : val;
|
|
449
|
+
rects[i * 4] = ax0 - this.xRef;
|
|
450
|
+
rects[i * 4 + 1] = ay0 - this.yRef;
|
|
451
|
+
rects[i * 4 + 2] = ax1 - this.xRef;
|
|
452
|
+
rects[i * 4 + 3] = ay1 - this.yRef;
|
|
453
|
+
minX = Math.min(minX, ax0, ax1);
|
|
454
|
+
maxX = Math.max(maxX, ax0, ax1);
|
|
455
|
+
minY = Math.min(minY, ay0, ay1);
|
|
456
|
+
maxY = Math.max(maxY, ay0, ay1);
|
|
449
457
|
}
|
|
450
458
|
this.xBounds = [minX, maxX];
|
|
451
459
|
this.yBounds = [minY, maxY];
|
|
@@ -638,7 +646,7 @@ function kde(values, lo, hi, points = 64, bandwidth) {
|
|
|
638
646
|
const h = bandwidth ?? 1.06 * sd * Math.pow(Math.max(1, n), -0.2);
|
|
639
647
|
const xs = new Float64Array(points);
|
|
640
648
|
const ys = new Float64Array(points);
|
|
641
|
-
const
|
|
649
|
+
const norm2 = 1 / (n * h * Math.sqrt(2 * Math.PI));
|
|
642
650
|
for (let p = 0; p < points; p++) {
|
|
643
651
|
const x = lo + (hi - lo) * p / (points - 1);
|
|
644
652
|
let sum = 0;
|
|
@@ -647,7 +655,7 @@ function kde(values, lo, hi, points = 64, bandwidth) {
|
|
|
647
655
|
sum += Math.exp(-0.5 * u * u);
|
|
648
656
|
}
|
|
649
657
|
xs[p] = x;
|
|
650
|
-
ys[p] = sum *
|
|
658
|
+
ys[p] = sum * norm2;
|
|
651
659
|
}
|
|
652
660
|
return { xs, ys };
|
|
653
661
|
}
|
|
@@ -1082,17 +1090,38 @@ var ANCHORS = {
|
|
|
1082
1090
|
[0.95, 0.95, 0.95]
|
|
1083
1091
|
]
|
|
1084
1092
|
};
|
|
1085
|
-
|
|
1093
|
+
var LUT_SIZE = 256;
|
|
1094
|
+
var lutCache = /* @__PURE__ */ new Map();
|
|
1095
|
+
function buildLUT(name) {
|
|
1086
1096
|
const anchors = ANCHORS[name];
|
|
1087
1097
|
const last = anchors.length - 1;
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
const pos =
|
|
1098
|
+
const lut = new Float32Array(LUT_SIZE * 3);
|
|
1099
|
+
for (let s = 0; s < LUT_SIZE; s++) {
|
|
1100
|
+
const pos = s / (LUT_SIZE - 1) * last;
|
|
1091
1101
|
const i = Math.min(last - 1, Math.floor(pos));
|
|
1092
1102
|
const f = pos - i;
|
|
1093
1103
|
const a = anchors[i];
|
|
1094
1104
|
const b = anchors[i + 1];
|
|
1095
|
-
|
|
1105
|
+
lut[s * 3] = a[0] + (b[0] - a[0]) * f;
|
|
1106
|
+
lut[s * 3 + 1] = a[1] + (b[1] - a[1]) * f;
|
|
1107
|
+
lut[s * 3 + 2] = a[2] + (b[2] - a[2]) * f;
|
|
1108
|
+
}
|
|
1109
|
+
return lut;
|
|
1110
|
+
}
|
|
1111
|
+
function colormapLUT(name = "viridis") {
|
|
1112
|
+
let lut = lutCache.get(name);
|
|
1113
|
+
if (!lut) {
|
|
1114
|
+
lut = buildLUT(name);
|
|
1115
|
+
lutCache.set(name, lut);
|
|
1116
|
+
}
|
|
1117
|
+
return lut;
|
|
1118
|
+
}
|
|
1119
|
+
function colormap(name = "viridis") {
|
|
1120
|
+
const lut = colormapLUT(name);
|
|
1121
|
+
return (t) => {
|
|
1122
|
+
const c = t <= 0 ? 0 : t >= 1 ? 1 : t;
|
|
1123
|
+
const j = (c * (LUT_SIZE - 1) | 0) * 3;
|
|
1124
|
+
return [lut[j], lut[j + 1], lut[j + 2]];
|
|
1096
1125
|
};
|
|
1097
1126
|
}
|
|
1098
1127
|
|
|
@@ -1450,6 +1479,148 @@ var ErrorBarLayer = class {
|
|
|
1450
1479
|
}
|
|
1451
1480
|
};
|
|
1452
1481
|
|
|
1482
|
+
// src/layers/graph.ts
|
|
1483
|
+
var EDGE_VERT = (
|
|
1484
|
+
/* glsl */
|
|
1485
|
+
`#version 300 es
|
|
1486
|
+
precision highp float;
|
|
1487
|
+
layout(location = 0) in vec2 aPos;
|
|
1488
|
+
${TRANSFORM_GLSL}
|
|
1489
|
+
void main() { gl_Position = vec4(dataToClip(aPos), 0.0, 1.0); }`
|
|
1490
|
+
);
|
|
1491
|
+
var SOLID_FRAG2 = (
|
|
1492
|
+
/* glsl */
|
|
1493
|
+
`#version 300 es
|
|
1494
|
+
precision highp float;
|
|
1495
|
+
uniform vec4 uColor;
|
|
1496
|
+
out vec4 outColor;
|
|
1497
|
+
void main() { outColor = vec4(uColor.rgb * uColor.a, uColor.a); }`
|
|
1498
|
+
);
|
|
1499
|
+
var NODE_VERT = (
|
|
1500
|
+
/* glsl */
|
|
1501
|
+
`#version 300 es
|
|
1502
|
+
precision highp float;
|
|
1503
|
+
layout(location = 0) in vec2 aPos;
|
|
1504
|
+
uniform float uSize;
|
|
1505
|
+
${TRANSFORM_GLSL}
|
|
1506
|
+
void main() {
|
|
1507
|
+
gl_Position = vec4(dataToClip(aPos), 0.0, 1.0);
|
|
1508
|
+
gl_PointSize = uSize;
|
|
1509
|
+
}`
|
|
1510
|
+
);
|
|
1511
|
+
var NODE_FRAG = (
|
|
1512
|
+
/* glsl */
|
|
1513
|
+
`#version 300 es
|
|
1514
|
+
precision highp float;
|
|
1515
|
+
uniform vec4 uColor;
|
|
1516
|
+
out vec4 outColor;
|
|
1517
|
+
void main() {
|
|
1518
|
+
vec2 p = gl_PointCoord * 2.0 - 1.0;
|
|
1519
|
+
float r = length(p);
|
|
1520
|
+
if (r > 1.0) discard;
|
|
1521
|
+
float a = smoothstep(1.0, 0.82, r);
|
|
1522
|
+
outColor = vec4(uColor.rgb * uColor.a * a, uColor.a * a);
|
|
1523
|
+
}`
|
|
1524
|
+
);
|
|
1525
|
+
var programCache5 = /* @__PURE__ */ new WeakMap();
|
|
1526
|
+
function getPrograms(gl) {
|
|
1527
|
+
let p = programCache5.get(gl);
|
|
1528
|
+
if (!p) {
|
|
1529
|
+
p = { edge: createProgram(gl, EDGE_VERT, SOLID_FRAG2), node: createProgram(gl, NODE_VERT, NODE_FRAG) };
|
|
1530
|
+
programCache5.set(gl, p);
|
|
1531
|
+
}
|
|
1532
|
+
return p;
|
|
1533
|
+
}
|
|
1534
|
+
var counter7 = 0;
|
|
1535
|
+
var GraphLayer = class {
|
|
1536
|
+
constructor(gl, opts) {
|
|
1537
|
+
this.buffers = [];
|
|
1538
|
+
this.xRef = 0;
|
|
1539
|
+
this.yRef = 0;
|
|
1540
|
+
this.xBounds = [0, 0];
|
|
1541
|
+
this.yBounds = [0, 0];
|
|
1542
|
+
this.id = `graph-${counter7++}`;
|
|
1543
|
+
this.gl = gl;
|
|
1544
|
+
this.progs = getPrograms(gl);
|
|
1545
|
+
this.name = opts.name ?? this.id;
|
|
1546
|
+
this.yAxis = opts.yAxis ?? "y";
|
|
1547
|
+
const nc = opts.nodeColor ?? "#60a5fa";
|
|
1548
|
+
this.nodeColor = Array.isArray(nc) ? nc : parseColor(nc);
|
|
1549
|
+
this.colorCss = typeof nc === "string" ? nc : toColorCss(this.nodeColor);
|
|
1550
|
+
const ec = opts.edgeColor ?? "rgba(148,163,184,0.5)";
|
|
1551
|
+
this.edgeColor = Array.isArray(ec) ? ec : parseColor(ec);
|
|
1552
|
+
this.nodeSize = opts.nodeSize ?? 10;
|
|
1553
|
+
const n = Math.min(opts.x.length, opts.y.length);
|
|
1554
|
+
this.nodeCount = n;
|
|
1555
|
+
this.xRef = n > 0 ? opts.x[0] : 0;
|
|
1556
|
+
this.yRef = n > 0 ? opts.y[0] : 0;
|
|
1557
|
+
const nodePos = new Float32Array(n * 2);
|
|
1558
|
+
let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
|
|
1559
|
+
for (let i = 0; i < n; i++) {
|
|
1560
|
+
const px = opts.x[i], py = opts.y[i];
|
|
1561
|
+
nodePos[i * 2] = px - this.xRef;
|
|
1562
|
+
nodePos[i * 2 + 1] = py - this.yRef;
|
|
1563
|
+
if (px < minX) minX = px;
|
|
1564
|
+
if (px > maxX) maxX = px;
|
|
1565
|
+
if (py < minY) minY = py;
|
|
1566
|
+
if (py > maxY) maxY = py;
|
|
1567
|
+
}
|
|
1568
|
+
this.xBounds = [minX, maxX];
|
|
1569
|
+
this.yBounds = [minY, maxY];
|
|
1570
|
+
const edgePos = [];
|
|
1571
|
+
for (const [a, b] of opts.edges) {
|
|
1572
|
+
if (a < 0 || b < 0 || a >= n || b >= n) continue;
|
|
1573
|
+
edgePos.push(opts.x[a] - this.xRef, opts.y[a] - this.yRef, opts.x[b] - this.xRef, opts.y[b] - this.yRef);
|
|
1574
|
+
}
|
|
1575
|
+
this.edgeVerts = edgePos.length / 2;
|
|
1576
|
+
const nodeBuf = gl.createBuffer();
|
|
1577
|
+
const edgeBuf = gl.createBuffer();
|
|
1578
|
+
this.buffers = [nodeBuf, edgeBuf];
|
|
1579
|
+
this.nodeVao = gl.createVertexArray();
|
|
1580
|
+
gl.bindVertexArray(this.nodeVao);
|
|
1581
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, nodeBuf);
|
|
1582
|
+
gl.bufferData(gl.ARRAY_BUFFER, nodePos, gl.STATIC_DRAW);
|
|
1583
|
+
gl.enableVertexAttribArray(0);
|
|
1584
|
+
gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
|
|
1585
|
+
this.edgeVao = gl.createVertexArray();
|
|
1586
|
+
gl.bindVertexArray(this.edgeVao);
|
|
1587
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, edgeBuf);
|
|
1588
|
+
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(edgePos), gl.STATIC_DRAW);
|
|
1589
|
+
gl.enableVertexAttribArray(0);
|
|
1590
|
+
gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
|
|
1591
|
+
gl.bindVertexArray(null);
|
|
1592
|
+
this.edgeUniforms = uniformLocations(gl, this.progs.edge, [...TRANSFORM_UNIFORMS, "uColor"]);
|
|
1593
|
+
this.nodeUniforms = uniformLocations(gl, this.progs.node, [...TRANSFORM_UNIFORMS, "uColor", "uSize"]);
|
|
1594
|
+
}
|
|
1595
|
+
bounds() {
|
|
1596
|
+
if (this.nodeCount === 0) return null;
|
|
1597
|
+
return { x: this.xBounds, y: this.yBounds };
|
|
1598
|
+
}
|
|
1599
|
+
draw(state) {
|
|
1600
|
+
if (this.nodeCount === 0) return;
|
|
1601
|
+
const gl = state.gl;
|
|
1602
|
+
if (this.edgeVerts > 0) {
|
|
1603
|
+
gl.useProgram(this.progs.edge);
|
|
1604
|
+
setTransformUniforms(gl, this.edgeUniforms, state.x, state.y, this.xRef, this.yRef);
|
|
1605
|
+
gl.uniform4f(this.edgeUniforms.uColor, this.edgeColor[0], this.edgeColor[1], this.edgeColor[2], this.edgeColor[3]);
|
|
1606
|
+
gl.bindVertexArray(this.edgeVao);
|
|
1607
|
+
gl.drawArrays(gl.LINES, 0, this.edgeVerts);
|
|
1608
|
+
}
|
|
1609
|
+
gl.useProgram(this.progs.node);
|
|
1610
|
+
setTransformUniforms(gl, this.nodeUniforms, state.x, state.y, this.xRef, this.yRef);
|
|
1611
|
+
gl.uniform4f(this.nodeUniforms.uColor, this.nodeColor[0], this.nodeColor[1], this.nodeColor[2], this.nodeColor[3]);
|
|
1612
|
+
gl.uniform1f(this.nodeUniforms.uSize, this.nodeSize * state.dpr);
|
|
1613
|
+
gl.bindVertexArray(this.nodeVao);
|
|
1614
|
+
gl.drawArrays(gl.POINTS, 0, this.nodeCount);
|
|
1615
|
+
gl.bindVertexArray(null);
|
|
1616
|
+
}
|
|
1617
|
+
dispose() {
|
|
1618
|
+
this.gl.deleteVertexArray(this.nodeVao);
|
|
1619
|
+
this.gl.deleteVertexArray(this.edgeVao);
|
|
1620
|
+
for (const b of this.buffers) this.gl.deleteBuffer(b);
|
|
1621
|
+
}
|
|
1622
|
+
};
|
|
1623
|
+
|
|
1453
1624
|
// src/layers/heatmap.ts
|
|
1454
1625
|
var VERT5 = (
|
|
1455
1626
|
/* glsl */
|
|
@@ -1476,19 +1647,19 @@ void main() {
|
|
|
1476
1647
|
outColor = vec4(c.rgb * c.a, c.a);
|
|
1477
1648
|
}`
|
|
1478
1649
|
);
|
|
1479
|
-
var
|
|
1650
|
+
var programCache6 = /* @__PURE__ */ new WeakMap();
|
|
1480
1651
|
function getProgram5(gl) {
|
|
1481
|
-
let p =
|
|
1652
|
+
let p = programCache6.get(gl);
|
|
1482
1653
|
if (!p) {
|
|
1483
1654
|
p = createProgram(gl, VERT5, FRAG6);
|
|
1484
|
-
|
|
1655
|
+
programCache6.set(gl, p);
|
|
1485
1656
|
}
|
|
1486
1657
|
return p;
|
|
1487
1658
|
}
|
|
1488
|
-
var
|
|
1659
|
+
var counter8 = 0;
|
|
1489
1660
|
var HeatmapLayer = class {
|
|
1490
1661
|
constructor(gl, opts) {
|
|
1491
|
-
this.id = `heatmap-${
|
|
1662
|
+
this.id = `heatmap-${counter8++}`;
|
|
1492
1663
|
this.gl = gl;
|
|
1493
1664
|
this.program = getProgram5(gl);
|
|
1494
1665
|
this.yAxis = opts.yAxis ?? "y";
|
|
@@ -1498,7 +1669,7 @@ var HeatmapLayer = class {
|
|
|
1498
1669
|
this.xRef = x0;
|
|
1499
1670
|
this.yRef = y0;
|
|
1500
1671
|
const { cols, rows, values } = opts;
|
|
1501
|
-
const
|
|
1672
|
+
const lut = colormapLUT(opts.colormap ?? "viridis");
|
|
1502
1673
|
let lo = opts.domain?.[0] ?? Infinity;
|
|
1503
1674
|
let hi = opts.domain?.[1] ?? -Infinity;
|
|
1504
1675
|
if (!opts.domain) {
|
|
@@ -1511,10 +1682,12 @@ var HeatmapLayer = class {
|
|
|
1511
1682
|
const span = hi - lo || 1;
|
|
1512
1683
|
const pixels = new Uint8Array(cols * rows * 4);
|
|
1513
1684
|
for (let i = 0; i < cols * rows; i++) {
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
pixels[i * 4
|
|
1685
|
+
let t = (values[i] - lo) / span;
|
|
1686
|
+
t = t <= 0 ? 0 : t >= 1 ? 1 : t;
|
|
1687
|
+
const j = (t * 255 | 0) * 3;
|
|
1688
|
+
pixels[i * 4] = lut[j] * 255 + 0.5 | 0;
|
|
1689
|
+
pixels[i * 4 + 1] = lut[j + 1] * 255 + 0.5 | 0;
|
|
1690
|
+
pixels[i * 4 + 2] = lut[j + 2] * 255 + 0.5 | 0;
|
|
1518
1691
|
pixels[i * 4 + 3] = 255;
|
|
1519
1692
|
}
|
|
1520
1693
|
const texture = gl.createTexture();
|
|
@@ -1624,16 +1797,16 @@ var HEX = (() => {
|
|
|
1624
1797
|
}
|
|
1625
1798
|
return new Float32Array(verts);
|
|
1626
1799
|
})();
|
|
1627
|
-
var
|
|
1800
|
+
var programCache7 = /* @__PURE__ */ new WeakMap();
|
|
1628
1801
|
function getProgram6(gl) {
|
|
1629
|
-
let p =
|
|
1802
|
+
let p = programCache7.get(gl);
|
|
1630
1803
|
if (!p) {
|
|
1631
1804
|
p = createProgram(gl, VERT6, FRAG7);
|
|
1632
|
-
|
|
1805
|
+
programCache7.set(gl, p);
|
|
1633
1806
|
}
|
|
1634
1807
|
return p;
|
|
1635
1808
|
}
|
|
1636
|
-
var
|
|
1809
|
+
var counter9 = 0;
|
|
1637
1810
|
var HexbinLayer = class {
|
|
1638
1811
|
constructor(gl, opts) {
|
|
1639
1812
|
this.buffers = [];
|
|
@@ -1641,7 +1814,7 @@ var HexbinLayer = class {
|
|
|
1641
1814
|
this.yRef = 0;
|
|
1642
1815
|
this.xBounds = [0, 0];
|
|
1643
1816
|
this.yBounds = [0, 0];
|
|
1644
|
-
this.id = `hexbin-${
|
|
1817
|
+
this.id = `hexbin-${counter9++}`;
|
|
1645
1818
|
this.gl = gl;
|
|
1646
1819
|
this.program = getProgram6(gl);
|
|
1647
1820
|
this.yAxis = opts.yAxis ?? "y";
|
|
@@ -1739,6 +1912,155 @@ var HexbinLayer = class {
|
|
|
1739
1912
|
}
|
|
1740
1913
|
};
|
|
1741
1914
|
|
|
1915
|
+
// src/layers/image.ts
|
|
1916
|
+
var VERT7 = (
|
|
1917
|
+
/* glsl */
|
|
1918
|
+
`#version 300 es
|
|
1919
|
+
precision highp float;
|
|
1920
|
+
layout(location = 0) in vec2 aPos; // offset data space
|
|
1921
|
+
layout(location = 1) in vec2 aUV;
|
|
1922
|
+
${TRANSFORM_GLSL}
|
|
1923
|
+
out vec2 vUV;
|
|
1924
|
+
void main() {
|
|
1925
|
+
vUV = aUV;
|
|
1926
|
+
gl_Position = vec4(dataToClip(aPos), 0.0, 1.0);
|
|
1927
|
+
}`
|
|
1928
|
+
);
|
|
1929
|
+
var FRAG8 = (
|
|
1930
|
+
/* glsl */
|
|
1931
|
+
`#version 300 es
|
|
1932
|
+
precision highp float;
|
|
1933
|
+
in vec2 vUV;
|
|
1934
|
+
uniform sampler2D uTex;
|
|
1935
|
+
uniform float uOpacity;
|
|
1936
|
+
out vec4 outColor;
|
|
1937
|
+
void main() {
|
|
1938
|
+
vec4 c = texture(uTex, vUV);
|
|
1939
|
+
float a = c.a * uOpacity;
|
|
1940
|
+
outColor = vec4(c.rgb * a, a);
|
|
1941
|
+
}`
|
|
1942
|
+
);
|
|
1943
|
+
var programCache8 = /* @__PURE__ */ new WeakMap();
|
|
1944
|
+
function getProgram7(gl) {
|
|
1945
|
+
let p = programCache8.get(gl);
|
|
1946
|
+
if (!p) {
|
|
1947
|
+
p = createProgram(gl, VERT7, FRAG8);
|
|
1948
|
+
programCache8.set(gl, p);
|
|
1949
|
+
}
|
|
1950
|
+
return p;
|
|
1951
|
+
}
|
|
1952
|
+
var counter10 = 0;
|
|
1953
|
+
var ImageLayer = class {
|
|
1954
|
+
constructor(gl, opts) {
|
|
1955
|
+
this.colorCss = "#94a3b8";
|
|
1956
|
+
this.ready = false;
|
|
1957
|
+
this.img = null;
|
|
1958
|
+
this.id = `image-${counter10++}`;
|
|
1959
|
+
this.gl = gl;
|
|
1960
|
+
this.program = getProgram7(gl);
|
|
1961
|
+
this.name = opts.name ?? this.id;
|
|
1962
|
+
this.yAxis = opts.yAxis ?? "y";
|
|
1963
|
+
this.ext = opts.extent;
|
|
1964
|
+
this.opacity = opts.opacity ?? 1;
|
|
1965
|
+
this.smooth = opts.smooth !== false;
|
|
1966
|
+
const [x0, x1] = opts.extent.x;
|
|
1967
|
+
const [y0, y1] = opts.extent.y;
|
|
1968
|
+
this.xRef = x0;
|
|
1969
|
+
this.yRef = y0;
|
|
1970
|
+
this.texture = gl.createTexture();
|
|
1971
|
+
gl.bindTexture(gl.TEXTURE_2D, this.texture);
|
|
1972
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([0, 0, 0, 0]));
|
|
1973
|
+
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
1974
|
+
if (typeof opts.source === "string") {
|
|
1975
|
+
const img = new Image();
|
|
1976
|
+
this.img = img;
|
|
1977
|
+
img.crossOrigin = "anonymous";
|
|
1978
|
+
img.onload = () => {
|
|
1979
|
+
this.upload(img);
|
|
1980
|
+
opts.onLoad?.();
|
|
1981
|
+
};
|
|
1982
|
+
img.src = opts.source;
|
|
1983
|
+
} else {
|
|
1984
|
+
this.upload(opts.source);
|
|
1985
|
+
}
|
|
1986
|
+
const data = new Float32Array([
|
|
1987
|
+
x0 - this.xRef,
|
|
1988
|
+
y0 - this.yRef,
|
|
1989
|
+
0,
|
|
1990
|
+
0,
|
|
1991
|
+
x1 - this.xRef,
|
|
1992
|
+
y0 - this.yRef,
|
|
1993
|
+
1,
|
|
1994
|
+
0,
|
|
1995
|
+
x1 - this.xRef,
|
|
1996
|
+
y1 - this.yRef,
|
|
1997
|
+
1,
|
|
1998
|
+
1,
|
|
1999
|
+
x0 - this.xRef,
|
|
2000
|
+
y0 - this.yRef,
|
|
2001
|
+
0,
|
|
2002
|
+
0,
|
|
2003
|
+
x1 - this.xRef,
|
|
2004
|
+
y1 - this.yRef,
|
|
2005
|
+
1,
|
|
2006
|
+
1,
|
|
2007
|
+
x0 - this.xRef,
|
|
2008
|
+
y1 - this.yRef,
|
|
2009
|
+
0,
|
|
2010
|
+
1
|
|
2011
|
+
]);
|
|
2012
|
+
const vao = gl.createVertexArray();
|
|
2013
|
+
const buffer = gl.createBuffer();
|
|
2014
|
+
this.vao = vao;
|
|
2015
|
+
this.buffer = buffer;
|
|
2016
|
+
gl.bindVertexArray(vao);
|
|
2017
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
|
2018
|
+
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
|
|
2019
|
+
gl.enableVertexAttribArray(0);
|
|
2020
|
+
gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 16, 0);
|
|
2021
|
+
gl.enableVertexAttribArray(1);
|
|
2022
|
+
gl.vertexAttribPointer(1, 2, gl.FLOAT, false, 16, 8);
|
|
2023
|
+
gl.bindVertexArray(null);
|
|
2024
|
+
this.uniforms = uniformLocations(gl, this.program, [...TRANSFORM_UNIFORMS, "uTex", "uOpacity"]);
|
|
2025
|
+
}
|
|
2026
|
+
upload(src) {
|
|
2027
|
+
const gl = this.gl;
|
|
2028
|
+
gl.bindTexture(gl.TEXTURE_2D, this.texture);
|
|
2029
|
+
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
|
|
2030
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, src);
|
|
2031
|
+
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
|
|
2032
|
+
const filter = this.smooth ? gl.LINEAR : gl.NEAREST;
|
|
2033
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, filter);
|
|
2034
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, filter);
|
|
2035
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
2036
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
2037
|
+
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
2038
|
+
this.ready = true;
|
|
2039
|
+
}
|
|
2040
|
+
bounds() {
|
|
2041
|
+
return { x: this.ext.x, y: this.ext.y };
|
|
2042
|
+
}
|
|
2043
|
+
draw(state) {
|
|
2044
|
+
if (!this.ready) return;
|
|
2045
|
+
const gl = state.gl;
|
|
2046
|
+
gl.useProgram(this.program);
|
|
2047
|
+
setTransformUniforms(gl, this.uniforms, state.x, state.y, this.xRef, this.yRef);
|
|
2048
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
2049
|
+
gl.bindTexture(gl.TEXTURE_2D, this.texture);
|
|
2050
|
+
gl.uniform1i(this.uniforms.uTex, 0);
|
|
2051
|
+
gl.uniform1f(this.uniforms.uOpacity, this.opacity);
|
|
2052
|
+
gl.bindVertexArray(this.vao);
|
|
2053
|
+
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
|
2054
|
+
gl.bindVertexArray(null);
|
|
2055
|
+
}
|
|
2056
|
+
dispose() {
|
|
2057
|
+
if (this.img) this.img.onload = null;
|
|
2058
|
+
this.gl.deleteVertexArray(this.vao);
|
|
2059
|
+
this.gl.deleteBuffer(this.buffer);
|
|
2060
|
+
this.gl.deleteTexture(this.texture);
|
|
2061
|
+
}
|
|
2062
|
+
};
|
|
2063
|
+
|
|
1742
2064
|
// src/layers/line-util.ts
|
|
1743
2065
|
function decimateIndices(ys, i0, i1, cols) {
|
|
1744
2066
|
const out = [i0];
|
|
@@ -1832,9 +2154,9 @@ ${log}`);
|
|
|
1832
2154
|
}
|
|
1833
2155
|
return sh;
|
|
1834
2156
|
}
|
|
1835
|
-
var
|
|
2157
|
+
var programCache9 = /* @__PURE__ */ new WeakMap();
|
|
1836
2158
|
function getDecProgram(gl) {
|
|
1837
|
-
if (
|
|
2159
|
+
if (programCache9.has(gl)) return programCache9.get(gl);
|
|
1838
2160
|
let result = null;
|
|
1839
2161
|
try {
|
|
1840
2162
|
const vs = compile(gl, gl.VERTEX_SHADER, DEC_VERT);
|
|
@@ -1858,7 +2180,7 @@ function getDecProgram(gl) {
|
|
|
1858
2180
|
} catch {
|
|
1859
2181
|
result = null;
|
|
1860
2182
|
}
|
|
1861
|
-
|
|
2183
|
+
programCache9.set(gl, result);
|
|
1862
2184
|
return result;
|
|
1863
2185
|
}
|
|
1864
2186
|
var GpuDecimator = class {
|
|
@@ -1975,7 +2297,7 @@ function pickNearest(xs, ys, count, mode, cursorPx, cursorPy, project, gatePx =
|
|
|
1975
2297
|
|
|
1976
2298
|
// src/layers/line.ts
|
|
1977
2299
|
var GPU_DECIMATE_MIN = 2e5;
|
|
1978
|
-
var
|
|
2300
|
+
var VERT8 = (
|
|
1979
2301
|
/* glsl */
|
|
1980
2302
|
`#version 300 es
|
|
1981
2303
|
precision highp float;
|
|
@@ -2005,7 +2327,7 @@ void main() {
|
|
|
2005
2327
|
gl_Position = vec4((pos / uResolution) * 2.0 - 1.0, 0.0, 1.0);
|
|
2006
2328
|
}`
|
|
2007
2329
|
);
|
|
2008
|
-
var
|
|
2330
|
+
var FRAG9 = (
|
|
2009
2331
|
/* glsl */
|
|
2010
2332
|
`#version 300 es
|
|
2011
2333
|
precision highp float;
|
|
@@ -2125,12 +2447,12 @@ void main() {
|
|
|
2125
2447
|
}`
|
|
2126
2448
|
);
|
|
2127
2449
|
var CORNERS2 = new Float32Array([0, -1, 1, -1, 1, 1, 0, -1, 1, 1, 0, 1]);
|
|
2128
|
-
var
|
|
2129
|
-
function
|
|
2130
|
-
let p =
|
|
2450
|
+
var programCache10 = /* @__PURE__ */ new WeakMap();
|
|
2451
|
+
function getProgram8(gl) {
|
|
2452
|
+
let p = programCache10.get(gl);
|
|
2131
2453
|
if (!p) {
|
|
2132
|
-
p = createProgram(gl,
|
|
2133
|
-
|
|
2454
|
+
p = createProgram(gl, VERT8, FRAG9);
|
|
2455
|
+
programCache10.set(gl, p);
|
|
2134
2456
|
}
|
|
2135
2457
|
return p;
|
|
2136
2458
|
}
|
|
@@ -2172,7 +2494,7 @@ function upperBound(a, v) {
|
|
|
2172
2494
|
}
|
|
2173
2495
|
return lo;
|
|
2174
2496
|
}
|
|
2175
|
-
var
|
|
2497
|
+
var counter11 = 0;
|
|
2176
2498
|
var LineLayer = class {
|
|
2177
2499
|
constructor(gl, opts) {
|
|
2178
2500
|
this.gpuDec = null;
|
|
@@ -2182,9 +2504,9 @@ var LineLayer = class {
|
|
|
2182
2504
|
this.yBounds = [0, 0];
|
|
2183
2505
|
this.decKey = "";
|
|
2184
2506
|
this.decSegments = 0;
|
|
2185
|
-
this.id = `line-${
|
|
2507
|
+
this.id = `line-${counter11++}`;
|
|
2186
2508
|
this.gl = gl;
|
|
2187
|
-
this.program =
|
|
2509
|
+
this.program = getProgram8(gl);
|
|
2188
2510
|
this.joinProgram = getJoinProgram(gl);
|
|
2189
2511
|
this.width = opts.width ?? 1.5;
|
|
2190
2512
|
this.joinStyle = opts.join ?? "round";
|
|
@@ -2433,14 +2755,757 @@ var LineLayer = class {
|
|
|
2433
2755
|
}
|
|
2434
2756
|
};
|
|
2435
2757
|
|
|
2436
|
-
// src/
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2758
|
+
// src/graph/force.ts
|
|
2759
|
+
function forceLayout(nodeCount, edges, opts = {}) {
|
|
2760
|
+
const n = nodeCount;
|
|
2761
|
+
const x = new Float64Array(n);
|
|
2762
|
+
const y = new Float64Array(n);
|
|
2763
|
+
for (let i = 0; i < n; i++) {
|
|
2764
|
+
const a = i / n * Math.PI * 2;
|
|
2765
|
+
x[i] = Math.cos(a);
|
|
2766
|
+
y[i] = Math.sin(a);
|
|
2767
|
+
}
|
|
2768
|
+
if (n < 2) return { x, y };
|
|
2769
|
+
const iterations = opts.iterations ?? 300;
|
|
2770
|
+
const area2 = opts.area ?? 1;
|
|
2771
|
+
const k = Math.sqrt(area2 / n);
|
|
2772
|
+
const gravity = opts.gravity ?? 0.05;
|
|
2773
|
+
const dispX = new Float64Array(n);
|
|
2774
|
+
const dispY = new Float64Array(n);
|
|
2775
|
+
let temp = 0.1;
|
|
2776
|
+
for (let it = 0; it < iterations; it++) {
|
|
2777
|
+
dispX.fill(0);
|
|
2778
|
+
dispY.fill(0);
|
|
2779
|
+
for (let i = 0; i < n; i++) {
|
|
2780
|
+
for (let j = i + 1; j < n; j++) {
|
|
2781
|
+
const dx = x[i] - x[j];
|
|
2782
|
+
const dy = y[i] - y[j];
|
|
2783
|
+
const d = Math.hypot(dx, dy) || 1e-6;
|
|
2784
|
+
const f = k * k / d;
|
|
2785
|
+
const ux = dx / d, uy = dy / d;
|
|
2786
|
+
dispX[i] += ux * f;
|
|
2787
|
+
dispY[i] += uy * f;
|
|
2788
|
+
dispX[j] -= ux * f;
|
|
2789
|
+
dispY[j] -= uy * f;
|
|
2790
|
+
}
|
|
2791
|
+
}
|
|
2792
|
+
for (const [a, b] of edges) {
|
|
2793
|
+
const dx = x[a] - x[b];
|
|
2794
|
+
const dy = y[a] - y[b];
|
|
2795
|
+
const d = Math.hypot(dx, dy) || 1e-6;
|
|
2796
|
+
const f = d * d / k;
|
|
2797
|
+
const ux = dx / d, uy = dy / d;
|
|
2798
|
+
dispX[a] -= ux * f;
|
|
2799
|
+
dispY[a] -= uy * f;
|
|
2800
|
+
dispX[b] += ux * f;
|
|
2801
|
+
dispY[b] += uy * f;
|
|
2802
|
+
}
|
|
2803
|
+
for (let i = 0; i < n; i++) {
|
|
2804
|
+
dispX[i] -= x[i] * gravity;
|
|
2805
|
+
dispY[i] -= y[i] * gravity;
|
|
2806
|
+
const d = Math.hypot(dispX[i], dispY[i]) || 1e-6;
|
|
2807
|
+
const lim = Math.min(d, temp);
|
|
2808
|
+
x[i] += dispX[i] / d * lim;
|
|
2809
|
+
y[i] += dispY[i] / d * lim;
|
|
2810
|
+
}
|
|
2811
|
+
temp *= 0.99;
|
|
2812
|
+
}
|
|
2813
|
+
return { x, y };
|
|
2814
|
+
}
|
|
2815
|
+
|
|
2816
|
+
// src/geo/earcut.ts
|
|
2817
|
+
var Z_ORDER_THRESHOLD = 80;
|
|
2818
|
+
var Node = class {
|
|
2819
|
+
constructor(i, x, y) {
|
|
2820
|
+
this.i = i;
|
|
2821
|
+
this.x = x;
|
|
2822
|
+
this.y = y;
|
|
2823
|
+
this.prev = this;
|
|
2824
|
+
this.next = this;
|
|
2825
|
+
this.steiner = false;
|
|
2826
|
+
/** Morton (z-order) code; 0 until {@link indexCurve} assigns it. */
|
|
2827
|
+
this.z = 0;
|
|
2828
|
+
/** Neighbours in the z-order-sorted list (null outside the hashed path). */
|
|
2829
|
+
this.prevZ = null;
|
|
2830
|
+
this.nextZ = null;
|
|
2831
|
+
}
|
|
2832
|
+
};
|
|
2833
|
+
function earcut(data, holeIndices, dim = 2) {
|
|
2834
|
+
const hasHoles = holeIndices != null && holeIndices.length > 0;
|
|
2835
|
+
const outerLen = hasHoles ? holeIndices[0] * dim : data.length;
|
|
2836
|
+
let outerNode = linkedList(data, 0, outerLen, dim, true);
|
|
2837
|
+
const triangles = [];
|
|
2838
|
+
if (!outerNode || outerNode.next === outerNode.prev) return triangles;
|
|
2839
|
+
if (hasHoles) outerNode = eliminateHoles(data, holeIndices, outerNode, dim);
|
|
2840
|
+
let minX = 0;
|
|
2841
|
+
let minY = 0;
|
|
2842
|
+
let invSize = 0;
|
|
2843
|
+
if (data.length > Z_ORDER_THRESHOLD * dim) {
|
|
2844
|
+
minX = Infinity;
|
|
2845
|
+
minY = Infinity;
|
|
2846
|
+
let maxX = -Infinity;
|
|
2847
|
+
let maxY = -Infinity;
|
|
2848
|
+
for (let i = 0; i < outerLen; i += dim) {
|
|
2849
|
+
const x = data[i];
|
|
2850
|
+
const y = data[i + 1];
|
|
2851
|
+
if (x < minX) minX = x;
|
|
2852
|
+
if (y < minY) minY = y;
|
|
2853
|
+
if (x > maxX) maxX = x;
|
|
2854
|
+
if (y > maxY) maxY = y;
|
|
2855
|
+
}
|
|
2856
|
+
const size = Math.max(maxX - minX, maxY - minY);
|
|
2857
|
+
invSize = size !== 0 ? 32767 / size : 0;
|
|
2858
|
+
}
|
|
2859
|
+
earcutLinked(outerNode, triangles, dim, minX, minY, invSize, 0);
|
|
2860
|
+
return triangles;
|
|
2861
|
+
}
|
|
2862
|
+
function linkedList(data, start, end, dim, clockwise) {
|
|
2863
|
+
let last = null;
|
|
2864
|
+
if (clockwise === ringArea(data, start, end, dim) > 0) {
|
|
2865
|
+
for (let i = start; i < end; i += dim) last = insertNode(i, data[i], data[i + 1], last);
|
|
2866
|
+
} else {
|
|
2867
|
+
for (let i = end - dim; i >= start; i -= dim) last = insertNode(i, data[i], data[i + 1], last);
|
|
2868
|
+
}
|
|
2869
|
+
if (last && equals(last, last.next)) {
|
|
2870
|
+
removeNode(last);
|
|
2871
|
+
last = last.next;
|
|
2872
|
+
}
|
|
2873
|
+
return last;
|
|
2874
|
+
}
|
|
2875
|
+
function filterPoints(start, end) {
|
|
2876
|
+
if (!start) return start;
|
|
2877
|
+
let e = end ?? start;
|
|
2878
|
+
let p = start;
|
|
2879
|
+
let again;
|
|
2880
|
+
do {
|
|
2881
|
+
again = false;
|
|
2882
|
+
if (!p.steiner && (equals(p, p.next) || area(p.prev, p, p.next) === 0)) {
|
|
2883
|
+
removeNode(p);
|
|
2884
|
+
p = e = p.prev;
|
|
2885
|
+
if (p === p.next) break;
|
|
2886
|
+
again = true;
|
|
2887
|
+
} else {
|
|
2888
|
+
p = p.next;
|
|
2889
|
+
}
|
|
2890
|
+
} while (again || p !== e);
|
|
2891
|
+
return e;
|
|
2892
|
+
}
|
|
2893
|
+
function earcutLinked(earStart, triangles, dim, minX, minY, invSize, pass) {
|
|
2894
|
+
if (!earStart) return;
|
|
2895
|
+
let ear = earStart;
|
|
2896
|
+
if (pass === 0 && invSize) indexCurve(ear, minX, minY, invSize);
|
|
2897
|
+
let stop = earStart;
|
|
2898
|
+
while (ear.prev !== ear.next) {
|
|
2899
|
+
const prev = ear.prev;
|
|
2900
|
+
const next = ear.next;
|
|
2901
|
+
const found = invSize ? isEarHashed(ear, minX, minY, invSize) : isEar(ear);
|
|
2902
|
+
if (found) {
|
|
2903
|
+
triangles.push(prev.i / dim | 0, ear.i / dim | 0, next.i / dim | 0);
|
|
2904
|
+
removeNode(ear);
|
|
2905
|
+
ear = next.next;
|
|
2906
|
+
stop = next.next;
|
|
2907
|
+
continue;
|
|
2908
|
+
}
|
|
2909
|
+
ear = next;
|
|
2910
|
+
if (ear === stop) {
|
|
2911
|
+
if (pass === 0) earcutLinked(filterPoints(ear), triangles, dim, minX, minY, invSize, 1);
|
|
2912
|
+
else if (pass === 1) {
|
|
2913
|
+
const cured = cureLocalIntersections(filterPoints(ear), triangles, dim);
|
|
2914
|
+
earcutLinked(cured, triangles, dim, minX, minY, invSize, 2);
|
|
2915
|
+
} else if (pass === 2) splitEarcut(ear, triangles, dim, minX, minY, invSize);
|
|
2916
|
+
break;
|
|
2917
|
+
}
|
|
2918
|
+
}
|
|
2919
|
+
}
|
|
2920
|
+
function isEar(ear) {
|
|
2921
|
+
const a = ear.prev;
|
|
2922
|
+
const b = ear;
|
|
2923
|
+
const c = ear.next;
|
|
2924
|
+
if (area(a, b, c) >= 0) return false;
|
|
2925
|
+
let p = ear.next.next;
|
|
2926
|
+
while (p !== ear.prev) {
|
|
2927
|
+
if (pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) && area(p.prev, p, p.next) >= 0) {
|
|
2928
|
+
return false;
|
|
2929
|
+
}
|
|
2930
|
+
p = p.next;
|
|
2931
|
+
}
|
|
2932
|
+
return true;
|
|
2933
|
+
}
|
|
2934
|
+
function isEarHashed(ear, minX, minY, invSize) {
|
|
2935
|
+
const a = ear.prev;
|
|
2936
|
+
const b = ear;
|
|
2937
|
+
const c = ear.next;
|
|
2938
|
+
if (area(a, b, c) >= 0) return false;
|
|
2939
|
+
const ax = a.x;
|
|
2940
|
+
const bx = b.x;
|
|
2941
|
+
const cx = c.x;
|
|
2942
|
+
const ay = a.y;
|
|
2943
|
+
const by = b.y;
|
|
2944
|
+
const cy = c.y;
|
|
2945
|
+
const x0 = Math.min(ax, bx, cx);
|
|
2946
|
+
const y0 = Math.min(ay, by, cy);
|
|
2947
|
+
const x1 = Math.max(ax, bx, cx);
|
|
2948
|
+
const y1 = Math.max(ay, by, cy);
|
|
2949
|
+
const minZ = zOrder(x0, y0, minX, minY, invSize);
|
|
2950
|
+
const maxZ = zOrder(x1, y1, minX, minY, invSize);
|
|
2951
|
+
let p = ear.prevZ;
|
|
2952
|
+
let n = ear.nextZ;
|
|
2953
|
+
const blocks = (q) => q.x >= x0 && q.x <= x1 && q.y >= y0 && q.y <= y1 && q !== a && q !== c && pointInTriangle(ax, ay, bx, by, cx, cy, q.x, q.y) && area(q.prev, q, q.next) >= 0;
|
|
2954
|
+
while (p && p.z >= minZ && n && n.z <= maxZ) {
|
|
2955
|
+
if (blocks(p)) return false;
|
|
2956
|
+
p = p.prevZ;
|
|
2957
|
+
if (blocks(n)) return false;
|
|
2958
|
+
n = n.nextZ;
|
|
2959
|
+
}
|
|
2960
|
+
while (p && p.z >= minZ) {
|
|
2961
|
+
if (blocks(p)) return false;
|
|
2962
|
+
p = p.prevZ;
|
|
2963
|
+
}
|
|
2964
|
+
while (n && n.z <= maxZ) {
|
|
2965
|
+
if (blocks(n)) return false;
|
|
2966
|
+
n = n.nextZ;
|
|
2967
|
+
}
|
|
2968
|
+
return true;
|
|
2969
|
+
}
|
|
2970
|
+
function eliminateHoles(data, holeIndices, outerNode, dim) {
|
|
2971
|
+
const queue = [];
|
|
2972
|
+
for (let i = 0, len = holeIndices.length; i < len; i++) {
|
|
2973
|
+
const start = holeIndices[i] * dim;
|
|
2974
|
+
const end = i < len - 1 ? holeIndices[i + 1] * dim : data.length;
|
|
2975
|
+
const list = linkedList(data, start, end, dim, false);
|
|
2976
|
+
if (list) {
|
|
2977
|
+
if (list === list.next) list.steiner = true;
|
|
2978
|
+
queue.push(getLeftmost(list));
|
|
2979
|
+
}
|
|
2980
|
+
}
|
|
2981
|
+
queue.sort((a, b) => a.x - b.x);
|
|
2982
|
+
let node = outerNode;
|
|
2983
|
+
for (const hole of queue) node = eliminateHole(hole, node);
|
|
2984
|
+
return node;
|
|
2985
|
+
}
|
|
2986
|
+
function eliminateHole(hole, outerNode) {
|
|
2987
|
+
const bridge = findHoleBridge(hole, outerNode);
|
|
2988
|
+
if (!bridge) return outerNode;
|
|
2989
|
+
const bridgeReverse = splitPolygon(bridge, hole);
|
|
2990
|
+
filterPoints(bridgeReverse, bridgeReverse.next);
|
|
2991
|
+
return filterPoints(bridge, bridge.next);
|
|
2992
|
+
}
|
|
2993
|
+
function findHoleBridge(hole, outerNode) {
|
|
2994
|
+
let p = outerNode;
|
|
2995
|
+
const hx = hole.x;
|
|
2996
|
+
const hy = hole.y;
|
|
2997
|
+
let qx = -Infinity;
|
|
2998
|
+
let m = null;
|
|
2999
|
+
do {
|
|
3000
|
+
if (hy <= p.y && hy >= p.next.y && p.next.y !== p.y) {
|
|
3001
|
+
const x = p.x + (hy - p.y) / (p.next.y - p.y) * (p.next.x - p.x);
|
|
3002
|
+
if (x <= hx && x > qx) {
|
|
3003
|
+
qx = x;
|
|
3004
|
+
m = p.x < p.next.x ? p : p.next;
|
|
3005
|
+
if (x === hx) return m;
|
|
3006
|
+
}
|
|
3007
|
+
}
|
|
3008
|
+
p = p.next;
|
|
3009
|
+
} while (p !== outerNode);
|
|
3010
|
+
if (!m) return null;
|
|
3011
|
+
const stop = m;
|
|
3012
|
+
const mx = m.x;
|
|
3013
|
+
const my = m.y;
|
|
3014
|
+
let tanMin = Infinity;
|
|
3015
|
+
p = m;
|
|
3016
|
+
do {
|
|
3017
|
+
if (hx >= p.x && p.x >= mx && hx !== p.x && pointInTriangle(hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy, p.x, p.y)) {
|
|
3018
|
+
const tan = Math.abs(hy - p.y) / (hx - p.x);
|
|
3019
|
+
if (locallyInside(p, hole) && (tan < tanMin || tan === tanMin && (p.x > m.x || p.x === m.x && sectorContainsSector(m, p)))) {
|
|
3020
|
+
m = p;
|
|
3021
|
+
tanMin = tan;
|
|
3022
|
+
}
|
|
3023
|
+
}
|
|
3024
|
+
p = p.next;
|
|
3025
|
+
} while (p !== stop);
|
|
3026
|
+
return m;
|
|
3027
|
+
}
|
|
3028
|
+
function cureLocalIntersections(start, triangles, dim) {
|
|
3029
|
+
let p = start;
|
|
3030
|
+
do {
|
|
3031
|
+
const a = p.prev;
|
|
3032
|
+
const b = p.next.next;
|
|
3033
|
+
if (!equals(a, b) && intersects(a, p, p.next, b) && locallyInside(a, b) && locallyInside(b, a)) {
|
|
3034
|
+
triangles.push(a.i / dim | 0, p.i / dim | 0, b.i / dim | 0);
|
|
3035
|
+
removeNode(p);
|
|
3036
|
+
removeNode(p.next);
|
|
3037
|
+
p = start = b;
|
|
3038
|
+
}
|
|
3039
|
+
p = p.next;
|
|
3040
|
+
} while (p !== start);
|
|
3041
|
+
return filterPoints(p);
|
|
3042
|
+
}
|
|
3043
|
+
function splitEarcut(start, triangles, dim, minX, minY, invSize) {
|
|
3044
|
+
let a = start;
|
|
3045
|
+
do {
|
|
3046
|
+
let b = a.next.next;
|
|
3047
|
+
while (b !== a.prev) {
|
|
3048
|
+
if (a.i !== b.i && isValidDiagonal(a, b)) {
|
|
3049
|
+
let c = splitPolygon(a, b);
|
|
3050
|
+
const a2 = filterPoints(a, a.next);
|
|
3051
|
+
c = filterPoints(c, c.next);
|
|
3052
|
+
earcutLinked(a2, triangles, dim, minX, minY, invSize, 0);
|
|
3053
|
+
earcutLinked(c, triangles, dim, minX, minY, invSize, 0);
|
|
3054
|
+
return;
|
|
3055
|
+
}
|
|
3056
|
+
b = b.next;
|
|
3057
|
+
}
|
|
3058
|
+
a = a.next;
|
|
3059
|
+
} while (a !== start);
|
|
3060
|
+
}
|
|
3061
|
+
function area(p, q, r) {
|
|
3062
|
+
return (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y);
|
|
3063
|
+
}
|
|
3064
|
+
function equals(a, b) {
|
|
3065
|
+
return a.x === b.x && a.y === b.y;
|
|
3066
|
+
}
|
|
3067
|
+
function pointInTriangle(ax, ay, bx, by, cx, cy, px, py) {
|
|
3068
|
+
return (cx - px) * (ay - py) - (ax - px) * (cy - py) >= 0 && (ax - px) * (by - py) - (bx - px) * (ay - py) >= 0 && (bx - px) * (cy - py) - (cx - px) * (by - py) >= 0;
|
|
3069
|
+
}
|
|
3070
|
+
function isValidDiagonal(a, b) {
|
|
3071
|
+
return a.next.i !== b.i && a.prev.i !== b.i && !intersectsPolygon(a, b) && (locallyInside(a, b) && locallyInside(b, a) && middleInside(a, b) && (area(a.prev, a, b.prev) !== 0 || area(a, b.prev, b) !== 0) || equals(a, b) && area(a.prev, a, a.next) > 0 && area(b.prev, b, b.next) > 0);
|
|
3072
|
+
}
|
|
3073
|
+
function onSegment(p, q, r) {
|
|
3074
|
+
return q.x <= Math.max(p.x, r.x) && q.x >= Math.min(p.x, r.x) && q.y <= Math.max(p.y, r.y) && q.y >= Math.min(p.y, r.y);
|
|
3075
|
+
}
|
|
3076
|
+
function sign(n) {
|
|
3077
|
+
return n > 0 ? 1 : n < 0 ? -1 : 0;
|
|
3078
|
+
}
|
|
3079
|
+
function intersects(p1, q1, p2, q2) {
|
|
3080
|
+
const o1 = sign(area(p1, q1, p2));
|
|
3081
|
+
const o2 = sign(area(p1, q1, q2));
|
|
3082
|
+
const o3 = sign(area(p2, q2, p1));
|
|
3083
|
+
const o4 = sign(area(p2, q2, q1));
|
|
3084
|
+
if (o1 !== o2 && o3 !== o4) return true;
|
|
3085
|
+
if (o1 === 0 && onSegment(p1, p2, q1)) return true;
|
|
3086
|
+
if (o2 === 0 && onSegment(p1, q2, q1)) return true;
|
|
3087
|
+
if (o3 === 0 && onSegment(p2, p1, q2)) return true;
|
|
3088
|
+
if (o4 === 0 && onSegment(p2, q1, q2)) return true;
|
|
3089
|
+
return false;
|
|
3090
|
+
}
|
|
3091
|
+
function intersectsPolygon(a, b) {
|
|
3092
|
+
let p = a;
|
|
3093
|
+
do {
|
|
3094
|
+
if (p.i !== a.i && p.next.i !== a.i && p.i !== b.i && p.next.i !== b.i && intersects(p, p.next, a, b)) {
|
|
3095
|
+
return true;
|
|
3096
|
+
}
|
|
3097
|
+
p = p.next;
|
|
3098
|
+
} while (p !== a);
|
|
3099
|
+
return false;
|
|
3100
|
+
}
|
|
3101
|
+
function locallyInside(a, b) {
|
|
3102
|
+
return area(a.prev, a, a.next) < 0 ? area(a, b, a.next) >= 0 && area(a, a.prev, b) >= 0 : area(a, b, a.prev) < 0 || area(a, a.next, b) < 0;
|
|
3103
|
+
}
|
|
3104
|
+
function middleInside(a, b) {
|
|
3105
|
+
let p = a;
|
|
3106
|
+
let inside = false;
|
|
3107
|
+
const px = (a.x + b.x) / 2;
|
|
3108
|
+
const py = (a.y + b.y) / 2;
|
|
3109
|
+
do {
|
|
3110
|
+
if (p.y > py !== p.next.y > py && p.next.y !== p.y && px < (p.next.x - p.x) * (py - p.y) / (p.next.y - p.y) + p.x) {
|
|
3111
|
+
inside = !inside;
|
|
3112
|
+
}
|
|
3113
|
+
p = p.next;
|
|
3114
|
+
} while (p !== a);
|
|
3115
|
+
return inside;
|
|
3116
|
+
}
|
|
3117
|
+
function sectorContainsSector(m, p) {
|
|
3118
|
+
return area(m.prev, m, p.prev) < 0 && area(p.next, m, m.next) < 0;
|
|
3119
|
+
}
|
|
3120
|
+
function splitPolygon(a, b) {
|
|
3121
|
+
const a2 = new Node(a.i, a.x, a.y);
|
|
3122
|
+
const b2 = new Node(b.i, b.x, b.y);
|
|
3123
|
+
const an = a.next;
|
|
3124
|
+
const bp = b.prev;
|
|
3125
|
+
a.next = b;
|
|
3126
|
+
b.prev = a;
|
|
3127
|
+
a2.next = an;
|
|
3128
|
+
an.prev = a2;
|
|
3129
|
+
b2.next = a2;
|
|
3130
|
+
a2.prev = b2;
|
|
3131
|
+
bp.next = b2;
|
|
3132
|
+
b2.prev = bp;
|
|
3133
|
+
return b2;
|
|
3134
|
+
}
|
|
3135
|
+
function insertNode(i, x, y, last) {
|
|
3136
|
+
const p = new Node(i, x, y);
|
|
3137
|
+
if (!last) {
|
|
3138
|
+
p.prev = p;
|
|
3139
|
+
p.next = p;
|
|
3140
|
+
} else {
|
|
3141
|
+
p.next = last.next;
|
|
3142
|
+
p.prev = last;
|
|
3143
|
+
last.next.prev = p;
|
|
3144
|
+
last.next = p;
|
|
3145
|
+
}
|
|
3146
|
+
return p;
|
|
3147
|
+
}
|
|
3148
|
+
function removeNode(p) {
|
|
3149
|
+
p.next.prev = p.prev;
|
|
3150
|
+
p.prev.next = p.next;
|
|
3151
|
+
if (p.prevZ) p.prevZ.nextZ = p.nextZ;
|
|
3152
|
+
if (p.nextZ) p.nextZ.prevZ = p.prevZ;
|
|
3153
|
+
}
|
|
3154
|
+
function getLeftmost(start) {
|
|
3155
|
+
let p = start;
|
|
3156
|
+
let leftmost = start;
|
|
3157
|
+
do {
|
|
3158
|
+
if (p.x < leftmost.x || p.x === leftmost.x && p.y < leftmost.y) leftmost = p;
|
|
3159
|
+
p = p.next;
|
|
3160
|
+
} while (p !== start);
|
|
3161
|
+
return leftmost;
|
|
3162
|
+
}
|
|
3163
|
+
function ringArea(data, start, end, dim) {
|
|
3164
|
+
let sum = 0;
|
|
3165
|
+
for (let i = start, j = end - dim; i < end; i += dim) {
|
|
3166
|
+
sum += (data[j] - data[i]) * (data[i + 1] + data[j + 1]);
|
|
3167
|
+
j = i;
|
|
3168
|
+
}
|
|
3169
|
+
return sum;
|
|
3170
|
+
}
|
|
3171
|
+
function indexCurve(start, minX, minY, invSize) {
|
|
3172
|
+
let p = start;
|
|
3173
|
+
do {
|
|
3174
|
+
if (p.z === 0) p.z = zOrder(p.x, p.y, minX, minY, invSize);
|
|
3175
|
+
p.prevZ = p.prev;
|
|
3176
|
+
p.nextZ = p.next;
|
|
3177
|
+
p = p.next;
|
|
3178
|
+
} while (p !== start);
|
|
3179
|
+
p.prevZ.nextZ = null;
|
|
3180
|
+
p.prevZ = null;
|
|
3181
|
+
sortLinked(p);
|
|
3182
|
+
}
|
|
3183
|
+
function sortLinked(list) {
|
|
3184
|
+
let inSize = 1;
|
|
3185
|
+
let numMerges;
|
|
3186
|
+
do {
|
|
3187
|
+
let p = list;
|
|
3188
|
+
list = null;
|
|
3189
|
+
let tail = null;
|
|
3190
|
+
numMerges = 0;
|
|
3191
|
+
while (p) {
|
|
3192
|
+
numMerges++;
|
|
3193
|
+
let q = p;
|
|
3194
|
+
let pSize = 0;
|
|
3195
|
+
for (let i = 0; i < inSize; i++) {
|
|
3196
|
+
pSize++;
|
|
3197
|
+
q = q.nextZ;
|
|
3198
|
+
if (!q) break;
|
|
3199
|
+
}
|
|
3200
|
+
let qSize = inSize;
|
|
3201
|
+
while (pSize > 0 || qSize > 0 && q) {
|
|
3202
|
+
let e;
|
|
3203
|
+
if (pSize !== 0 && (qSize === 0 || !q || p.z <= q.z)) {
|
|
3204
|
+
e = p;
|
|
3205
|
+
p = p.nextZ;
|
|
3206
|
+
pSize--;
|
|
3207
|
+
} else {
|
|
3208
|
+
e = q;
|
|
3209
|
+
q = q.nextZ;
|
|
3210
|
+
qSize--;
|
|
3211
|
+
}
|
|
3212
|
+
if (tail) tail.nextZ = e;
|
|
3213
|
+
else list = e;
|
|
3214
|
+
e.prevZ = tail;
|
|
3215
|
+
tail = e;
|
|
3216
|
+
}
|
|
3217
|
+
p = q;
|
|
3218
|
+
}
|
|
3219
|
+
tail.nextZ = null;
|
|
3220
|
+
inSize *= 2;
|
|
3221
|
+
} while (numMerges > 1);
|
|
3222
|
+
}
|
|
3223
|
+
function zOrder(x, y, minX, minY, invSize) {
|
|
3224
|
+
let ix = (x - minX) * invSize | 0;
|
|
3225
|
+
let iy = (y - minY) * invSize | 0;
|
|
3226
|
+
ix = (ix | ix << 8) & 16711935;
|
|
3227
|
+
ix = (ix | ix << 4) & 252645135;
|
|
3228
|
+
ix = (ix | ix << 2) & 858993459;
|
|
3229
|
+
ix = (ix | ix << 1) & 1431655765;
|
|
3230
|
+
iy = (iy | iy << 8) & 16711935;
|
|
3231
|
+
iy = (iy | iy << 4) & 252645135;
|
|
3232
|
+
iy = (iy | iy << 2) & 858993459;
|
|
3233
|
+
iy = (iy | iy << 1) & 1431655765;
|
|
3234
|
+
return ix | iy << 1;
|
|
3235
|
+
}
|
|
3236
|
+
|
|
3237
|
+
// src/layers/patches.ts
|
|
3238
|
+
var FILL_VERT = (
|
|
3239
|
+
/* glsl */
|
|
3240
|
+
`#version 300 es
|
|
3241
|
+
precision highp float;
|
|
3242
|
+
layout(location = 0) in vec2 aPos; // offset data-space position
|
|
3243
|
+
layout(location = 1) in vec4 aColor; // per-vertex color (premultiplied on output)
|
|
3244
|
+
${TRANSFORM_GLSL}
|
|
3245
|
+
out vec4 vColor;
|
|
3246
|
+
void main() {
|
|
3247
|
+
vColor = aColor;
|
|
3248
|
+
gl_Position = vec4(dataToClip(aPos), 0.0, 1.0);
|
|
3249
|
+
}`
|
|
3250
|
+
);
|
|
3251
|
+
var FILL_FRAG = (
|
|
3252
|
+
/* glsl */
|
|
3253
|
+
`#version 300 es
|
|
3254
|
+
precision highp float;
|
|
3255
|
+
in vec4 vColor;
|
|
3256
|
+
out vec4 outColor;
|
|
3257
|
+
void main() { outColor = vec4(vColor.rgb * vColor.a, vColor.a); }`
|
|
3258
|
+
);
|
|
3259
|
+
var programCache11 = /* @__PURE__ */ new WeakMap();
|
|
3260
|
+
function getFillProgram(gl) {
|
|
3261
|
+
let p = programCache11.get(gl);
|
|
3262
|
+
if (!p) {
|
|
3263
|
+
p = createProgram(gl, FILL_VERT, FILL_FRAG);
|
|
3264
|
+
programCache11.set(gl, p);
|
|
3265
|
+
}
|
|
3266
|
+
return p;
|
|
3267
|
+
}
|
|
3268
|
+
var counter12 = 0;
|
|
3269
|
+
var PatchesLayer = class {
|
|
3270
|
+
constructor(gl, opts) {
|
|
3271
|
+
this.buffers = [];
|
|
3272
|
+
this.vertexCount = 0;
|
|
3273
|
+
this.xRef = 0;
|
|
3274
|
+
this.yRef = 0;
|
|
3275
|
+
this.xBounds = [0, 0];
|
|
3276
|
+
this.yBounds = [0, 0];
|
|
3277
|
+
this.id = `patches-${counter12++}`;
|
|
3278
|
+
this.gl = gl;
|
|
3279
|
+
this.program = getFillProgram(gl);
|
|
3280
|
+
this.name = opts.name ?? this.id;
|
|
3281
|
+
this.yAxis = opts.yAxis ?? "y";
|
|
3282
|
+
const defColor = opts.color ?? "#3b82f6";
|
|
3283
|
+
const defRgba = Array.isArray(defColor) ? defColor : parseColor(defColor);
|
|
3284
|
+
this.colorCss = typeof defColor === "string" ? defColor : "#3b82f6";
|
|
3285
|
+
const opacity = opts.opacity ?? 1;
|
|
3286
|
+
const cmap = opts.colormap ? colormap(opts.colormap) : null;
|
|
3287
|
+
let lo = opts.domain?.[0] ?? Infinity;
|
|
3288
|
+
let hi = opts.domain?.[1] ?? -Infinity;
|
|
3289
|
+
if (cmap && !opts.domain) {
|
|
3290
|
+
for (const patch of opts.patches) {
|
|
3291
|
+
const v = patch.value;
|
|
3292
|
+
if (v == null) continue;
|
|
3293
|
+
if (v < lo) lo = v;
|
|
3294
|
+
if (v > hi) hi = v;
|
|
3295
|
+
}
|
|
3296
|
+
}
|
|
3297
|
+
const span = hi - lo || 1;
|
|
3298
|
+
for (const patch of opts.patches) {
|
|
3299
|
+
if (patch.x.length > 0) {
|
|
3300
|
+
this.xRef = patch.x[0];
|
|
3301
|
+
this.yRef = patch.y[0];
|
|
3302
|
+
break;
|
|
3303
|
+
}
|
|
3304
|
+
}
|
|
3305
|
+
const positions = [];
|
|
3306
|
+
const colors = [];
|
|
3307
|
+
let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
|
|
3308
|
+
for (const patch of opts.patches) {
|
|
3309
|
+
const n = Math.min(patch.x.length, patch.y.length);
|
|
3310
|
+
if (n < 3) continue;
|
|
3311
|
+
let rgba;
|
|
3312
|
+
if (patch.color != null) {
|
|
3313
|
+
rgba = Array.isArray(patch.color) ? patch.color : parseColor(patch.color);
|
|
3314
|
+
} else if (cmap && patch.value != null) {
|
|
3315
|
+
const [r, g, b] = cmap((patch.value - lo) / span);
|
|
3316
|
+
rgba = [r, g, b, 1];
|
|
3317
|
+
} else {
|
|
3318
|
+
rgba = defRgba;
|
|
3319
|
+
}
|
|
3320
|
+
const a = rgba[3] * opacity;
|
|
3321
|
+
const flat = new Array(n * 2);
|
|
3322
|
+
for (let i = 0; i < n; i++) {
|
|
3323
|
+
flat[i * 2] = patch.x[i];
|
|
3324
|
+
flat[i * 2 + 1] = patch.y[i];
|
|
3325
|
+
}
|
|
3326
|
+
const tris = earcut(flat, patch.holes && patch.holes.length ? patch.holes : void 0, 2);
|
|
3327
|
+
for (const idx of tris) {
|
|
3328
|
+
const px = flat[idx * 2];
|
|
3329
|
+
const py = flat[idx * 2 + 1];
|
|
3330
|
+
positions.push(px - this.xRef, py - this.yRef);
|
|
3331
|
+
colors.push(rgba[0], rgba[1], rgba[2], a);
|
|
3332
|
+
}
|
|
3333
|
+
for (let i = 0; i < n; i++) {
|
|
3334
|
+
const px = patch.x[i], py = patch.y[i];
|
|
3335
|
+
if (px < minX) minX = px;
|
|
3336
|
+
if (px > maxX) maxX = px;
|
|
3337
|
+
if (py < minY) minY = py;
|
|
3338
|
+
if (py > maxY) maxY = py;
|
|
3339
|
+
}
|
|
3340
|
+
}
|
|
3341
|
+
this.vertexCount = positions.length / 2;
|
|
3342
|
+
this.xBounds = minX <= maxX ? [minX, maxX] : [0, 0];
|
|
3343
|
+
this.yBounds = minY <= maxY ? [minY, maxY] : [0, 0];
|
|
3344
|
+
const vao = gl.createVertexArray();
|
|
3345
|
+
this.vao = vao;
|
|
3346
|
+
gl.bindVertexArray(vao);
|
|
3347
|
+
const posBuf = gl.createBuffer();
|
|
3348
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, posBuf);
|
|
3349
|
+
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
|
|
3350
|
+
gl.enableVertexAttribArray(0);
|
|
3351
|
+
gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
|
|
3352
|
+
const colBuf = gl.createBuffer();
|
|
3353
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, colBuf);
|
|
3354
|
+
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW);
|
|
3355
|
+
gl.enableVertexAttribArray(1);
|
|
3356
|
+
gl.vertexAttribPointer(1, 4, gl.FLOAT, false, 0, 0);
|
|
3357
|
+
gl.bindVertexArray(null);
|
|
3358
|
+
this.buffers = [posBuf, colBuf];
|
|
3359
|
+
this.uniforms = uniformLocations(gl, this.program, [...TRANSFORM_UNIFORMS]);
|
|
3360
|
+
}
|
|
3361
|
+
bounds() {
|
|
3362
|
+
if (this.vertexCount === 0) return null;
|
|
3363
|
+
return { x: this.xBounds, y: this.yBounds };
|
|
3364
|
+
}
|
|
3365
|
+
draw(state) {
|
|
3366
|
+
if (this.vertexCount === 0) return;
|
|
3367
|
+
const gl = state.gl;
|
|
3368
|
+
gl.useProgram(this.program);
|
|
3369
|
+
setTransformUniforms(gl, this.uniforms, state.x, state.y, this.xRef, this.yRef);
|
|
3370
|
+
gl.bindVertexArray(this.vao);
|
|
3371
|
+
gl.drawArrays(gl.TRIANGLES, 0, this.vertexCount);
|
|
3372
|
+
gl.bindVertexArray(null);
|
|
3373
|
+
}
|
|
3374
|
+
dispose() {
|
|
3375
|
+
this.gl.deleteVertexArray(this.vao);
|
|
3376
|
+
for (const b of this.buffers) this.gl.deleteBuffer(b);
|
|
3377
|
+
}
|
|
3378
|
+
};
|
|
3379
|
+
|
|
3380
|
+
// src/layers/pie.ts
|
|
3381
|
+
var DEFAULT_COLORS = [
|
|
3382
|
+
"#3b82f6",
|
|
3383
|
+
"#f472b6",
|
|
3384
|
+
"#22d3ee",
|
|
3385
|
+
"#a3e635",
|
|
3386
|
+
"#fbbf24",
|
|
3387
|
+
"#a78bfa",
|
|
3388
|
+
"#34d399",
|
|
3389
|
+
"#fb7185",
|
|
3390
|
+
"#60a5fa",
|
|
3391
|
+
"#f59e0b"
|
|
3392
|
+
];
|
|
3393
|
+
var counter13 = 0;
|
|
3394
|
+
var PieLayer = class {
|
|
3395
|
+
constructor(gl, opts) {
|
|
3396
|
+
this.buffers = [];
|
|
3397
|
+
this.vertexCount = 0;
|
|
3398
|
+
this.id = `pie-${counter13++}`;
|
|
3399
|
+
this.gl = gl;
|
|
3400
|
+
this.program = getFillProgram(gl);
|
|
3401
|
+
this.name = opts.name ?? this.id;
|
|
3402
|
+
this.yAxis = opts.yAxis ?? "y";
|
|
3403
|
+
const [cx, cy] = opts.center ?? [0, 0];
|
|
3404
|
+
const R = opts.radius ?? 1;
|
|
3405
|
+
const rIn = opts.innerRadius ?? 0;
|
|
3406
|
+
const start = opts.startAngle ?? Math.PI / 2;
|
|
3407
|
+
this.xRef = cx;
|
|
3408
|
+
this.yRef = cy;
|
|
3409
|
+
this.xBounds = [cx - R, cx + R];
|
|
3410
|
+
this.yBounds = [cy - R, cy + R];
|
|
3411
|
+
this.colorCss = "#3b82f6";
|
|
3412
|
+
const n = opts.values.length;
|
|
3413
|
+
let total = 0;
|
|
3414
|
+
for (let i = 0; i < n; i++) total += Math.max(0, opts.values[i]);
|
|
3415
|
+
if (total <= 0) total = 1;
|
|
3416
|
+
const cmap = opts.colormap ? colormap(opts.colormap) : null;
|
|
3417
|
+
const colorAt = (i) => {
|
|
3418
|
+
if (opts.colors?.[i] != null) {
|
|
3419
|
+
const c = opts.colors[i];
|
|
3420
|
+
return Array.isArray(c) ? c : parseColor(c);
|
|
3421
|
+
}
|
|
3422
|
+
if (cmap) {
|
|
3423
|
+
const [r, g, b] = cmap(n > 1 ? i / (n - 1) : 0);
|
|
3424
|
+
return [r, g, b, 1];
|
|
3425
|
+
}
|
|
3426
|
+
return parseColor(DEFAULT_COLORS[i % DEFAULT_COLORS.length]);
|
|
3427
|
+
};
|
|
3428
|
+
const positions = [];
|
|
3429
|
+
const colors = [];
|
|
3430
|
+
const push = (x, y, c) => {
|
|
3431
|
+
positions.push(x - cx, y - cy);
|
|
3432
|
+
colors.push(c[0], c[1], c[2], c[3]);
|
|
3433
|
+
};
|
|
3434
|
+
let a0 = start;
|
|
3435
|
+
for (let i = 0; i < n; i++) {
|
|
3436
|
+
const frac = Math.max(0, opts.values[i]) / total;
|
|
3437
|
+
const span = frac * Math.PI * 2;
|
|
3438
|
+
if (span <= 0) continue;
|
|
3439
|
+
const a1 = a0 - span;
|
|
3440
|
+
const c = colorAt(i);
|
|
3441
|
+
const segs = Math.max(2, Math.ceil(span / (Math.PI / 64)));
|
|
3442
|
+
for (let s = 0; s < segs; s++) {
|
|
3443
|
+
const t0 = a0 + (a1 - a0) * s / segs;
|
|
3444
|
+
const t1 = a0 + (a1 - a0) * (s + 1) / segs;
|
|
3445
|
+
const ox0 = cx + R * Math.cos(t0), oy0 = cy + R * Math.sin(t0);
|
|
3446
|
+
const ox1 = cx + R * Math.cos(t1), oy1 = cy + R * Math.sin(t1);
|
|
3447
|
+
if (rIn <= 0) {
|
|
3448
|
+
push(cx, cy, c);
|
|
3449
|
+
push(ox0, oy0, c);
|
|
3450
|
+
push(ox1, oy1, c);
|
|
3451
|
+
} else {
|
|
3452
|
+
const ix0 = cx + rIn * Math.cos(t0), iy0 = cy + rIn * Math.sin(t0);
|
|
3453
|
+
const ix1 = cx + rIn * Math.cos(t1), iy1 = cy + rIn * Math.sin(t1);
|
|
3454
|
+
push(ix0, iy0, c);
|
|
3455
|
+
push(ox0, oy0, c);
|
|
3456
|
+
push(ox1, oy1, c);
|
|
3457
|
+
push(ix0, iy0, c);
|
|
3458
|
+
push(ox1, oy1, c);
|
|
3459
|
+
push(ix1, iy1, c);
|
|
3460
|
+
}
|
|
3461
|
+
}
|
|
3462
|
+
a0 = a1;
|
|
3463
|
+
}
|
|
3464
|
+
this.vertexCount = positions.length / 2;
|
|
3465
|
+
const vao = gl.createVertexArray();
|
|
3466
|
+
this.vao = vao;
|
|
3467
|
+
gl.bindVertexArray(vao);
|
|
3468
|
+
const posBuf = gl.createBuffer();
|
|
3469
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, posBuf);
|
|
3470
|
+
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
|
|
3471
|
+
gl.enableVertexAttribArray(0);
|
|
3472
|
+
gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
|
|
3473
|
+
const colBuf = gl.createBuffer();
|
|
3474
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, colBuf);
|
|
3475
|
+
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW);
|
|
3476
|
+
gl.enableVertexAttribArray(1);
|
|
3477
|
+
gl.vertexAttribPointer(1, 4, gl.FLOAT, false, 0, 0);
|
|
3478
|
+
gl.bindVertexArray(null);
|
|
3479
|
+
this.buffers = [posBuf, colBuf];
|
|
3480
|
+
this.uniforms = uniformLocations(gl, this.program, [...TRANSFORM_UNIFORMS]);
|
|
3481
|
+
}
|
|
3482
|
+
bounds() {
|
|
3483
|
+
if (this.vertexCount === 0) return null;
|
|
3484
|
+
return { x: this.xBounds, y: this.yBounds };
|
|
3485
|
+
}
|
|
3486
|
+
draw(state) {
|
|
3487
|
+
if (this.vertexCount === 0) return;
|
|
3488
|
+
const gl = state.gl;
|
|
3489
|
+
gl.useProgram(this.program);
|
|
3490
|
+
setTransformUniforms(gl, this.uniforms, state.x, state.y, this.xRef, this.yRef);
|
|
3491
|
+
gl.bindVertexArray(this.vao);
|
|
3492
|
+
gl.drawArrays(gl.TRIANGLES, 0, this.vertexCount);
|
|
3493
|
+
gl.bindVertexArray(null);
|
|
3494
|
+
}
|
|
3495
|
+
dispose() {
|
|
3496
|
+
this.gl.deleteVertexArray(this.vao);
|
|
3497
|
+
for (const b of this.buffers) this.gl.deleteBuffer(b);
|
|
3498
|
+
}
|
|
3499
|
+
};
|
|
3500
|
+
|
|
3501
|
+
// src/layers/quiver.ts
|
|
3502
|
+
var SHAFT_VERT = (
|
|
3503
|
+
/* glsl */
|
|
3504
|
+
`#version 300 es
|
|
3505
|
+
precision highp float;
|
|
3506
|
+
layout(location = 0) in vec2 aCorner; // (along 0..1, side -1..1)
|
|
3507
|
+
layout(location = 1) in vec4 aArrow; // (bx,by,tx,ty) offset data space
|
|
3508
|
+
layout(location = 2) in vec4 aColor;
|
|
2444
3509
|
uniform vec2 uResolution;
|
|
2445
3510
|
uniform float uWidth;
|
|
2446
3511
|
${TRANSFORM_GLSL}
|
|
@@ -2483,7 +3548,7 @@ void main() {
|
|
|
2483
3548
|
gl_Position = vec4((pos / uResolution) * 2.0 - 1.0, 0.0, 1.0);
|
|
2484
3549
|
}`
|
|
2485
3550
|
);
|
|
2486
|
-
var
|
|
3551
|
+
var FRAG10 = (
|
|
2487
3552
|
/* glsl */
|
|
2488
3553
|
`#version 300 es
|
|
2489
3554
|
precision highp float;
|
|
@@ -2501,12 +3566,12 @@ var cache3 = /* @__PURE__ */ new WeakMap();
|
|
|
2501
3566
|
function programs3(gl) {
|
|
2502
3567
|
let p = cache3.get(gl);
|
|
2503
3568
|
if (!p) {
|
|
2504
|
-
p = { shaft: createProgram(gl, SHAFT_VERT,
|
|
3569
|
+
p = { shaft: createProgram(gl, SHAFT_VERT, FRAG10), head: createProgram(gl, HEAD_VERT, FRAG10) };
|
|
2505
3570
|
cache3.set(gl, p);
|
|
2506
3571
|
}
|
|
2507
3572
|
return p;
|
|
2508
3573
|
}
|
|
2509
|
-
var
|
|
3574
|
+
var counter14 = 0;
|
|
2510
3575
|
var QuiverLayer = class {
|
|
2511
3576
|
constructor(gl, opts) {
|
|
2512
3577
|
this.buffers = [];
|
|
@@ -2514,7 +3579,7 @@ var QuiverLayer = class {
|
|
|
2514
3579
|
this.yRef = 0;
|
|
2515
3580
|
this.xBounds = [0, 0];
|
|
2516
3581
|
this.yBounds = [0, 0];
|
|
2517
|
-
this.id = `quiver-${
|
|
3582
|
+
this.id = `quiver-${counter14++}`;
|
|
2518
3583
|
this.gl = gl;
|
|
2519
3584
|
this.progs = programs3(gl);
|
|
2520
3585
|
const colorInput = opts.color ?? "#3b82f6";
|
|
@@ -2664,7 +3729,15 @@ var QuiverLayer = class {
|
|
|
2664
3729
|
};
|
|
2665
3730
|
|
|
2666
3731
|
// src/layers/scatter.ts
|
|
2667
|
-
var
|
|
3732
|
+
var MARKERS = {
|
|
3733
|
+
circle: 0,
|
|
3734
|
+
square: 1,
|
|
3735
|
+
triangle: 2,
|
|
3736
|
+
diamond: 3,
|
|
3737
|
+
cross: 4,
|
|
3738
|
+
plus: 5
|
|
3739
|
+
};
|
|
3740
|
+
var VERT9 = (
|
|
2668
3741
|
/* glsl */
|
|
2669
3742
|
`#version 300 es
|
|
2670
3743
|
precision highp float;
|
|
@@ -2684,7 +3757,7 @@ void main() {
|
|
|
2684
3757
|
gl_Position = vec4((pos / uResolution) * 2.0 - 1.0, 0.0, 1.0);
|
|
2685
3758
|
}`
|
|
2686
3759
|
);
|
|
2687
|
-
var
|
|
3760
|
+
var FRAG11 = (
|
|
2688
3761
|
/* glsl */
|
|
2689
3762
|
`#version 300 es
|
|
2690
3763
|
precision highp float;
|
|
@@ -2692,26 +3765,62 @@ in vec2 vLocal;
|
|
|
2692
3765
|
in vec4 vColor;
|
|
2693
3766
|
uniform vec4 uColor;
|
|
2694
3767
|
uniform float uUseVertexColor;
|
|
3768
|
+
uniform int uMarker;
|
|
2695
3769
|
out vec4 outColor;
|
|
3770
|
+
|
|
3771
|
+
float sdBox(vec2 p, vec2 b) {
|
|
3772
|
+
vec2 d = abs(p) - b;
|
|
3773
|
+
return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0);
|
|
3774
|
+
}
|
|
3775
|
+
// iq's equilateral-triangle SDF (apex toward +y).
|
|
3776
|
+
float sdTri(vec2 p) {
|
|
3777
|
+
const float k = sqrt(3.0);
|
|
3778
|
+
p.x = abs(p.x) - 1.0;
|
|
3779
|
+
p.y = p.y + 1.0 / k;
|
|
3780
|
+
if (p.x + k * p.y > 0.0) p = vec2(p.x - k * p.y, -k * p.x - p.y) / 2.0;
|
|
3781
|
+
p.x -= clamp(p.x, -2.0, 0.0);
|
|
3782
|
+
return -length(p) * sign(p.y);
|
|
3783
|
+
}
|
|
3784
|
+
|
|
2696
3785
|
void main() {
|
|
2697
|
-
|
|
2698
|
-
if (
|
|
2699
|
-
|
|
3786
|
+
vec2 p = vLocal;
|
|
3787
|
+
if (uMarker == 0) {
|
|
3788
|
+
// Circle \u2014 original soft-edged path, unchanged.
|
|
3789
|
+
float r = length(p);
|
|
3790
|
+
if (r > 1.0) discard;
|
|
3791
|
+
float alpha = smoothstep(1.0, 1.0 - 0.15, r);
|
|
3792
|
+
vec4 c0 = uUseVertexColor > 0.5 ? vColor : uColor;
|
|
3793
|
+
outColor = vec4(c0.rgb * c0.a * alpha, c0.a * alpha);
|
|
3794
|
+
return;
|
|
3795
|
+
}
|
|
3796
|
+
float d;
|
|
3797
|
+
if (uMarker == 1) d = sdBox(p, vec2(0.88)); // square
|
|
3798
|
+
else if (uMarker == 2) d = sdTri(p); // triangle (point up)
|
|
3799
|
+
else if (uMarker == 3) d = abs(p.x) + abs(p.y) - 1.0; // diamond
|
|
3800
|
+
else if (uMarker == 4) { // cross (\xD7)
|
|
3801
|
+
vec2 q = vec2(p.x + p.y, p.x - p.y) * 0.70710678;
|
|
3802
|
+
d = min(sdBox(q, vec2(1.0, 0.30)), sdBox(q, vec2(0.30, 1.0)));
|
|
3803
|
+
} else { // plus (+)
|
|
3804
|
+
d = min(sdBox(p, vec2(1.0, 0.30)), sdBox(p, vec2(0.30, 1.0)));
|
|
3805
|
+
}
|
|
3806
|
+
float aa = fwidth(d) + 1e-4;
|
|
3807
|
+
float alpha = 1.0 - smoothstep(-aa, aa, d);
|
|
3808
|
+
if (alpha <= 0.0) discard;
|
|
2700
3809
|
vec4 c = uUseVertexColor > 0.5 ? vColor : uColor;
|
|
2701
3810
|
outColor = vec4(c.rgb * c.a * alpha, c.a * alpha);
|
|
2702
3811
|
}`
|
|
2703
3812
|
);
|
|
2704
3813
|
var CORNERS3 = new Float32Array([-1, -1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1]);
|
|
2705
|
-
var
|
|
2706
|
-
function
|
|
2707
|
-
let p =
|
|
3814
|
+
var programCache12 = /* @__PURE__ */ new WeakMap();
|
|
3815
|
+
function getProgram9(gl) {
|
|
3816
|
+
let p = programCache12.get(gl);
|
|
2708
3817
|
if (!p) {
|
|
2709
|
-
p = createProgram(gl,
|
|
2710
|
-
|
|
3818
|
+
p = createProgram(gl, VERT9, FRAG11);
|
|
3819
|
+
programCache12.set(gl, p);
|
|
2711
3820
|
}
|
|
2712
3821
|
return p;
|
|
2713
3822
|
}
|
|
2714
|
-
var
|
|
3823
|
+
var counter15 = 0;
|
|
2715
3824
|
var ScatterLayer = class {
|
|
2716
3825
|
constructor(gl, opts) {
|
|
2717
3826
|
this.buffers = [];
|
|
@@ -2719,10 +3828,11 @@ var ScatterLayer = class {
|
|
|
2719
3828
|
this.yRef = 0;
|
|
2720
3829
|
this.xBounds = [0, 0];
|
|
2721
3830
|
this.yBounds = [0, 0];
|
|
2722
|
-
this.id = `scatter-${
|
|
3831
|
+
this.id = `scatter-${counter15++}`;
|
|
2723
3832
|
this.gl = gl;
|
|
2724
|
-
this.program =
|
|
3833
|
+
this.program = getProgram9(gl);
|
|
2725
3834
|
this.size = opts.size ?? 5;
|
|
3835
|
+
this.marker = MARKERS[opts.marker ?? "circle"];
|
|
2726
3836
|
const colorInput = opts.color ?? "#3b82f6";
|
|
2727
3837
|
this.color = Array.isArray(colorInput) ? colorInput : parseColor(colorInput);
|
|
2728
3838
|
this.colorCss = typeof colorInput === "string" ? colorInput : toColorCss(this.color);
|
|
@@ -2754,7 +3864,7 @@ var ScatterLayer = class {
|
|
|
2754
3864
|
const colors = new Float32Array(n * 4);
|
|
2755
3865
|
if (opts.colorBy) {
|
|
2756
3866
|
const vals = opts.colorBy.values;
|
|
2757
|
-
const
|
|
3867
|
+
const lut = colormapLUT(opts.colorBy.colormap ?? "viridis");
|
|
2758
3868
|
let lo = opts.colorBy.domain?.[0] ?? Infinity;
|
|
2759
3869
|
let hi = opts.colorBy.domain?.[1] ?? -Infinity;
|
|
2760
3870
|
if (!opts.colorBy.domain) {
|
|
@@ -2766,10 +3876,12 @@ var ScatterLayer = class {
|
|
|
2766
3876
|
}
|
|
2767
3877
|
const span = hi - lo || 1;
|
|
2768
3878
|
for (let i = 0; i < n; i++) {
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
colors[i * 4
|
|
3879
|
+
let t = (vals[i] - lo) / span;
|
|
3880
|
+
t = t <= 0 ? 0 : t >= 1 ? 1 : t;
|
|
3881
|
+
const j = (t * 255 | 0) * 3;
|
|
3882
|
+
colors[i * 4] = lut[j];
|
|
3883
|
+
colors[i * 4 + 1] = lut[j + 1];
|
|
3884
|
+
colors[i * 4 + 2] = lut[j + 2];
|
|
2773
3885
|
colors[i * 4 + 3] = 1;
|
|
2774
3886
|
}
|
|
2775
3887
|
}
|
|
@@ -2800,7 +3912,8 @@ var ScatterLayer = class {
|
|
|
2800
3912
|
"uColor",
|
|
2801
3913
|
"uResolution",
|
|
2802
3914
|
"uSize",
|
|
2803
|
-
"uUseVertexColor"
|
|
3915
|
+
"uUseVertexColor",
|
|
3916
|
+
"uMarker"
|
|
2804
3917
|
]);
|
|
2805
3918
|
}
|
|
2806
3919
|
bounds() {
|
|
@@ -2852,6 +3965,7 @@ var ScatterLayer = class {
|
|
|
2852
3965
|
gl.uniform2f(this.uniforms.uResolution, state.pixelWidth, state.pixelHeight);
|
|
2853
3966
|
gl.uniform1f(this.uniforms.uSize, this.size / 2 * state.dpr);
|
|
2854
3967
|
gl.uniform1f(this.uniforms.uUseVertexColor, this.useVertexColor ? 1 : 0);
|
|
3968
|
+
gl.uniform1i(this.uniforms.uMarker, this.marker);
|
|
2855
3969
|
gl.bindVertexArray(this.vao);
|
|
2856
3970
|
gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, this.count);
|
|
2857
3971
|
gl.bindVertexArray(null);
|
|
@@ -2900,7 +4014,7 @@ void main() {
|
|
|
2900
4014
|
gl_Position = vec4((pos / uResolution) * 2.0 - 1.0, 0.0, 1.0);
|
|
2901
4015
|
}`
|
|
2902
4016
|
);
|
|
2903
|
-
var
|
|
4017
|
+
var SOLID_FRAG3 = (
|
|
2904
4018
|
/* glsl */
|
|
2905
4019
|
`#version 300 es
|
|
2906
4020
|
precision highp float;
|
|
@@ -2928,12 +4042,12 @@ var cache4 = /* @__PURE__ */ new WeakMap();
|
|
|
2928
4042
|
function programs4(gl) {
|
|
2929
4043
|
let p = cache4.get(gl);
|
|
2930
4044
|
if (!p) {
|
|
2931
|
-
p = { stem: createProgram(gl, STEM_VERT,
|
|
4045
|
+
p = { stem: createProgram(gl, STEM_VERT, SOLID_FRAG3), marker: createProgram(gl, MARKER_VERT, DISC_FRAG) };
|
|
2932
4046
|
cache4.set(gl, p);
|
|
2933
4047
|
}
|
|
2934
4048
|
return p;
|
|
2935
4049
|
}
|
|
2936
|
-
var
|
|
4050
|
+
var counter16 = 0;
|
|
2937
4051
|
var StemLayer = class {
|
|
2938
4052
|
constructor(gl, opts) {
|
|
2939
4053
|
this.buffers = [];
|
|
@@ -2941,7 +4055,7 @@ var StemLayer = class {
|
|
|
2941
4055
|
this.yRef = 0;
|
|
2942
4056
|
this.xBounds = [0, 0];
|
|
2943
4057
|
this.yBounds = [0, 0];
|
|
2944
|
-
this.id = `stem-${
|
|
4058
|
+
this.id = `stem-${counter16++}`;
|
|
2945
4059
|
this.gl = gl;
|
|
2946
4060
|
this.progs = programs4(gl);
|
|
2947
4061
|
const colorInput = opts.color ?? "#3b82f6";
|
|
@@ -3075,89 +4189,173 @@ var darkTheme = {
|
|
|
3075
4189
|
text: "#94a3b8",
|
|
3076
4190
|
font: "12px system-ui, -apple-system, sans-serif"
|
|
3077
4191
|
};
|
|
3078
|
-
function
|
|
3079
|
-
|
|
4192
|
+
function resolveAxisStyle(cfg, theme, colorOverride) {
|
|
4193
|
+
const line = colorOverride ?? theme.axis;
|
|
4194
|
+
const text = colorOverride ?? theme.text;
|
|
4195
|
+
return {
|
|
4196
|
+
showAxisLine: cfg.showAxisLine ?? true,
|
|
4197
|
+
axisLineColor: cfg.axisLineColor ?? line,
|
|
4198
|
+
axisLineWidth: cfg.axisLineWidth ?? 1,
|
|
4199
|
+
showTicks: cfg.showTicks ?? true,
|
|
4200
|
+
tickColor: cfg.tickColor ?? line,
|
|
4201
|
+
tickLength: cfg.tickLength ?? 5,
|
|
4202
|
+
tickMinorLength: 3,
|
|
4203
|
+
tickWidth: cfg.tickWidth ?? 1,
|
|
4204
|
+
labelColor: cfg.labelColor ?? text,
|
|
4205
|
+
labelFont: cfg.labelFont ?? theme.font,
|
|
4206
|
+
labelRotation: cfg.labelRotation ?? 0,
|
|
4207
|
+
labelStandoff: cfg.labelStandoff ?? 3,
|
|
4208
|
+
titleColor: cfg.titleColor ?? text,
|
|
4209
|
+
titleFont: cfg.titleFont ?? theme.font,
|
|
4210
|
+
showGrid: cfg.showGrid ?? true,
|
|
4211
|
+
gridColor: cfg.gridColor ?? theme.grid,
|
|
4212
|
+
gridMinorColor: cfg.gridMinorColor ?? theme.gridMinor,
|
|
4213
|
+
gridWidth: cfg.gridWidth ?? 1,
|
|
4214
|
+
gridDash: cfg.gridDash ?? []
|
|
4215
|
+
};
|
|
3080
4216
|
}
|
|
3081
|
-
function
|
|
4217
|
+
function drawTitle(ctx, region, title, isDark) {
|
|
4218
|
+
ctx.save();
|
|
4219
|
+
ctx.font = title.font ?? "600 15px system-ui, -apple-system, sans-serif";
|
|
4220
|
+
ctx.fillStyle = title.color ?? (isDark ? "#e2e8f0" : "#1e293b");
|
|
4221
|
+
ctx.textBaseline = "middle";
|
|
4222
|
+
const align = title.align ?? "center";
|
|
4223
|
+
const y = region.top / 2;
|
|
4224
|
+
let x;
|
|
4225
|
+
if (align === "left") {
|
|
4226
|
+
ctx.textAlign = "left";
|
|
4227
|
+
x = region.left;
|
|
4228
|
+
} else if (align === "right") {
|
|
4229
|
+
ctx.textAlign = "right";
|
|
4230
|
+
x = region.left + region.width;
|
|
4231
|
+
} else {
|
|
4232
|
+
ctx.textAlign = "center";
|
|
4233
|
+
x = region.left + region.width / 2;
|
|
4234
|
+
}
|
|
4235
|
+
ctx.fillText(title.text, x, y);
|
|
4236
|
+
ctx.restore();
|
|
4237
|
+
}
|
|
4238
|
+
function pxX(region, t) {
|
|
4239
|
+
return region.left + t * region.width;
|
|
4240
|
+
}
|
|
4241
|
+
function pxY(region, t) {
|
|
3082
4242
|
return region.top + (1 - t) * region.height;
|
|
3083
4243
|
}
|
|
3084
|
-
function drawGrid(ctx, region, scaleX, scaleY, ticksX, ticksY,
|
|
4244
|
+
function drawGrid(ctx, region, scaleX, scaleY, ticksX, ticksY, styleX, styleY) {
|
|
3085
4245
|
ctx.save();
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
const
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
4246
|
+
if (styleX.showGrid) {
|
|
4247
|
+
ctx.lineWidth = styleX.gridWidth;
|
|
4248
|
+
ctx.setLineDash(styleX.gridDash);
|
|
4249
|
+
for (const t of ticksX) {
|
|
4250
|
+
if (!t.grid) continue;
|
|
4251
|
+
const x = Math.round(pxX(region, scaleX.norm(t.value))) + 0.5;
|
|
4252
|
+
ctx.strokeStyle = t.minor ? styleX.gridMinorColor : styleX.gridColor;
|
|
4253
|
+
ctx.beginPath();
|
|
4254
|
+
ctx.moveTo(x, region.top);
|
|
4255
|
+
ctx.lineTo(x, region.top + region.height);
|
|
4256
|
+
ctx.stroke();
|
|
4257
|
+
}
|
|
3095
4258
|
}
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
4259
|
+
if (styleY.showGrid) {
|
|
4260
|
+
ctx.lineWidth = styleY.gridWidth;
|
|
4261
|
+
ctx.setLineDash(styleY.gridDash);
|
|
4262
|
+
for (const t of ticksY) {
|
|
4263
|
+
if (!t.grid) continue;
|
|
4264
|
+
const y = Math.round(pxY(region, scaleY.norm(t.value))) + 0.5;
|
|
4265
|
+
ctx.strokeStyle = t.minor ? styleY.gridMinorColor : styleY.gridColor;
|
|
4266
|
+
ctx.beginPath();
|
|
4267
|
+
ctx.moveTo(region.left, y);
|
|
4268
|
+
ctx.lineTo(region.left + region.width, y);
|
|
4269
|
+
ctx.stroke();
|
|
4270
|
+
}
|
|
3104
4271
|
}
|
|
3105
4272
|
ctx.restore();
|
|
3106
4273
|
}
|
|
3107
|
-
function drawXAxis(ctx, region, scaleX, ticksX,
|
|
4274
|
+
function drawXAxis(ctx, region, scaleX, ticksX, style, title) {
|
|
3108
4275
|
ctx.save();
|
|
3109
|
-
ctx.
|
|
3110
|
-
ctx.fillStyle = theme.text;
|
|
3111
|
-
ctx.font = theme.font;
|
|
3112
|
-
ctx.lineWidth = 1;
|
|
4276
|
+
ctx.lineWidth = style.axisLineWidth;
|
|
3113
4277
|
const bottom = region.top + region.height;
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
ctx.lineTo(region.left + region.width, bottom + 0.5);
|
|
3117
|
-
ctx.stroke();
|
|
3118
|
-
ctx.textAlign = "center";
|
|
3119
|
-
ctx.textBaseline = "top";
|
|
3120
|
-
for (const t of ticksX) {
|
|
3121
|
-
const x = Math.round(pxX(region, scaleX.norm(t.value))) + 0.5;
|
|
3122
|
-
const len = t.minor ? 3 : 5;
|
|
4278
|
+
if (style.showAxisLine) {
|
|
4279
|
+
ctx.strokeStyle = style.axisLineColor;
|
|
3123
4280
|
ctx.beginPath();
|
|
3124
|
-
ctx.moveTo(
|
|
3125
|
-
ctx.lineTo(
|
|
4281
|
+
ctx.moveTo(region.left, bottom + 0.5);
|
|
4282
|
+
ctx.lineTo(region.left + region.width, bottom + 0.5);
|
|
3126
4283
|
ctx.stroke();
|
|
3127
|
-
|
|
4284
|
+
}
|
|
4285
|
+
ctx.fillStyle = style.labelColor;
|
|
4286
|
+
ctx.font = style.labelFont;
|
|
4287
|
+
const rot = style.labelRotation * Math.PI / 180;
|
|
4288
|
+
for (const t of ticksX) {
|
|
4289
|
+
const x = Math.round(pxX(region, scaleX.norm(t.value))) + 0.5;
|
|
4290
|
+
const len = t.minor ? style.tickMinorLength : style.tickLength;
|
|
4291
|
+
if (style.showTicks) {
|
|
4292
|
+
ctx.strokeStyle = style.tickColor;
|
|
4293
|
+
ctx.lineWidth = style.tickWidth;
|
|
4294
|
+
ctx.beginPath();
|
|
4295
|
+
ctx.moveTo(x, bottom);
|
|
4296
|
+
ctx.lineTo(x, bottom + len);
|
|
4297
|
+
ctx.stroke();
|
|
4298
|
+
}
|
|
4299
|
+
if (!t.label) continue;
|
|
4300
|
+
const ly = bottom + (style.showTicks ? len : 0) + style.labelStandoff;
|
|
4301
|
+
if (rot !== 0) {
|
|
4302
|
+
ctx.save();
|
|
4303
|
+
ctx.translate(x, ly);
|
|
4304
|
+
ctx.rotate(rot);
|
|
4305
|
+
ctx.textAlign = "right";
|
|
4306
|
+
ctx.textBaseline = "middle";
|
|
4307
|
+
ctx.fillText(t.label, 0, 0);
|
|
4308
|
+
ctx.restore();
|
|
4309
|
+
} else {
|
|
4310
|
+
ctx.textAlign = "center";
|
|
4311
|
+
ctx.textBaseline = "top";
|
|
4312
|
+
ctx.fillText(t.label, x, ly);
|
|
4313
|
+
}
|
|
3128
4314
|
}
|
|
3129
4315
|
if (title) {
|
|
4316
|
+
ctx.fillStyle = style.titleColor;
|
|
4317
|
+
ctx.font = style.titleFont;
|
|
4318
|
+
ctx.textAlign = "center";
|
|
3130
4319
|
ctx.textBaseline = "bottom";
|
|
3131
4320
|
ctx.fillText(title, region.left + region.width / 2, bottom + 34);
|
|
3132
4321
|
}
|
|
3133
4322
|
ctx.restore();
|
|
3134
4323
|
}
|
|
3135
|
-
function drawYAxis(ctx, region, scaleY, ticksY,
|
|
4324
|
+
function drawYAxis(ctx, region, scaleY, ticksY, style, opts) {
|
|
3136
4325
|
ctx.save();
|
|
3137
|
-
|
|
3138
|
-
ctx.
|
|
3139
|
-
ctx.fillStyle = opts.color ?? theme.text;
|
|
3140
|
-
ctx.font = theme.font;
|
|
3141
|
-
ctx.lineWidth = 1;
|
|
4326
|
+
ctx.font = style.labelFont;
|
|
4327
|
+
ctx.lineWidth = style.axisLineWidth;
|
|
3142
4328
|
const ax = Math.round(opts.x) + 0.5;
|
|
3143
4329
|
const dir = opts.side === "left" ? -1 : 1;
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
4330
|
+
if (style.showAxisLine) {
|
|
4331
|
+
ctx.strokeStyle = style.axisLineColor;
|
|
4332
|
+
ctx.beginPath();
|
|
4333
|
+
ctx.moveTo(ax, region.top);
|
|
4334
|
+
ctx.lineTo(ax, region.top + region.height);
|
|
4335
|
+
ctx.stroke();
|
|
4336
|
+
}
|
|
4337
|
+
ctx.fillStyle = style.labelColor;
|
|
3148
4338
|
ctx.textAlign = opts.side === "left" ? "right" : "left";
|
|
3149
4339
|
ctx.textBaseline = "middle";
|
|
3150
4340
|
for (const t of ticksY) {
|
|
3151
4341
|
const y = Math.round(pxY(region, scaleY.norm(t.value))) + 0.5;
|
|
3152
|
-
const len = t.minor ?
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
4342
|
+
const len = t.minor ? style.tickMinorLength : style.tickLength;
|
|
4343
|
+
if (style.showTicks) {
|
|
4344
|
+
ctx.strokeStyle = style.tickColor;
|
|
4345
|
+
ctx.lineWidth = style.tickWidth;
|
|
4346
|
+
ctx.beginPath();
|
|
4347
|
+
ctx.moveTo(ax, y);
|
|
4348
|
+
ctx.lineTo(ax + dir * len, y);
|
|
4349
|
+
ctx.stroke();
|
|
4350
|
+
}
|
|
4351
|
+
if (t.label) {
|
|
4352
|
+
ctx.fillText(t.label, ax + dir * ((style.showTicks ? len : 0) + style.labelStandoff + 1), y);
|
|
4353
|
+
}
|
|
3158
4354
|
}
|
|
3159
4355
|
if (opts.title) {
|
|
3160
4356
|
ctx.save();
|
|
4357
|
+
ctx.fillStyle = style.titleColor;
|
|
4358
|
+
ctx.font = style.titleFont;
|
|
3161
4359
|
const tx = opts.titleX ?? (opts.side === "left" ? 12 : region.left + region.width + 36);
|
|
3162
4360
|
ctx.translate(tx, region.top + region.height / 2);
|
|
3163
4361
|
ctx.rotate(-Math.PI / 2);
|
|
@@ -3345,7 +4543,32 @@ var TimeScale = class {
|
|
|
3345
4543
|
return `${d.getFullYear()}`;
|
|
3346
4544
|
}
|
|
3347
4545
|
};
|
|
3348
|
-
|
|
4546
|
+
var CategoricalScale = class {
|
|
4547
|
+
constructor(factors = []) {
|
|
4548
|
+
this.type = "categorical";
|
|
4549
|
+
this.log = false;
|
|
4550
|
+
this.factors = factors;
|
|
4551
|
+
const n = factors.length;
|
|
4552
|
+
this.domain = n > 0 ? [-0.5, n - 0.5] : [-0.5, 0.5];
|
|
4553
|
+
}
|
|
4554
|
+
norm(value) {
|
|
4555
|
+
const [a, b] = this.domain;
|
|
4556
|
+
return b === a ? 0 : (value - a) / (b - a);
|
|
4557
|
+
}
|
|
4558
|
+
// Continuous linear inverse (satisfies the Scale contract used by pan/box math).
|
|
4559
|
+
// The nearest factor index is `Math.round(scale.invert(t))`.
|
|
4560
|
+
invert(t) {
|
|
4561
|
+
const [a, b] = this.domain;
|
|
4562
|
+
return a + t * (b - a);
|
|
4563
|
+
}
|
|
4564
|
+
ticks() {
|
|
4565
|
+
return this.factors.map((f, i) => ({ value: i, label: f, grid: false }));
|
|
4566
|
+
}
|
|
4567
|
+
formatTick(value) {
|
|
4568
|
+
return this.factors[Math.round(value)] ?? "";
|
|
4569
|
+
}
|
|
4570
|
+
};
|
|
4571
|
+
function makeScale(type, domain, factors) {
|
|
3349
4572
|
switch (type) {
|
|
3350
4573
|
case "linear":
|
|
3351
4574
|
return new LinearScale(domain);
|
|
@@ -3353,6 +4576,8 @@ function makeScale(type, domain) {
|
|
|
3353
4576
|
return new LogScale(domain);
|
|
3354
4577
|
case "time":
|
|
3355
4578
|
return new TimeScale(domain);
|
|
4579
|
+
case "categorical":
|
|
4580
|
+
return new CategoricalScale(factors ?? []);
|
|
3356
4581
|
default:
|
|
3357
4582
|
throw new Error(`Unknown scale type: ${type}`);
|
|
3358
4583
|
}
|
|
@@ -3460,6 +4685,7 @@ function createToolbar(container, host, dark) {
|
|
|
3460
4685
|
// src/plot.ts
|
|
3461
4686
|
var DEFAULT_MARGIN = { top: 16, right: 16, bottom: 40, left: 56 };
|
|
3462
4687
|
var Y_AXIS_GAP = 52;
|
|
4688
|
+
var TITLE_RESERVE = 28;
|
|
3463
4689
|
function isPickable(layer) {
|
|
3464
4690
|
return typeof layer.pick === "function";
|
|
3465
4691
|
}
|
|
@@ -3500,6 +4726,7 @@ var Plot = class {
|
|
|
3500
4726
|
this.pressPx = null;
|
|
3501
4727
|
/** A point clicked to pin its details, until another click clears it. */
|
|
3502
4728
|
this.selected = null;
|
|
4729
|
+
this.annotations = [];
|
|
3503
4730
|
this.container = container;
|
|
3504
4731
|
if (getComputedStyle(container).position === "static") {
|
|
3505
4732
|
container.style.position = "relative";
|
|
@@ -3515,21 +4742,28 @@ var Plot = class {
|
|
|
3515
4742
|
this.sharedCanvas = s.canvas;
|
|
3516
4743
|
const sx = options.scales?.x ?? {};
|
|
3517
4744
|
const sy = options.scales?.y ?? {};
|
|
3518
|
-
|
|
3519
|
-
this.
|
|
3520
|
-
this.
|
|
4745
|
+
const xCat = sx.type === "categorical";
|
|
4746
|
+
this.scaleX = makeScale(sx.type ?? "linear", sx.domain ?? [0, 1], sx.factors);
|
|
4747
|
+
this.autoX = !xCat && sx.domain == null;
|
|
4748
|
+
this.initialX = xCat ? this.scaleX.domain : sx.domain ?? null;
|
|
3521
4749
|
this.axisX = new Axis(options.axes?.x);
|
|
4750
|
+
const yCat = sy.type === "categorical";
|
|
4751
|
+
const yScale = makeScale(sy.type ?? "linear", sy.domain ?? [0, 1], sy.factors);
|
|
3522
4752
|
this.yAxes.set("y", {
|
|
3523
4753
|
id: "y",
|
|
3524
|
-
scale:
|
|
4754
|
+
scale: yScale,
|
|
3525
4755
|
axis: new Axis(options.axes?.y),
|
|
3526
4756
|
side: "left",
|
|
3527
|
-
auto: sy.domain == null,
|
|
3528
|
-
initial: sy.domain ?? null
|
|
4757
|
+
auto: !yCat && sy.domain == null,
|
|
4758
|
+
initial: yCat ? yScale.domain : sy.domain ?? null
|
|
3529
4759
|
});
|
|
3530
4760
|
this.isDark = options.theme === "dark";
|
|
3531
4761
|
this.theme = options.theme === "dark" ? darkTheme : options.theme === "light" || options.theme == null ? lightTheme : options.theme;
|
|
3532
4762
|
this.baseMargin = { ...DEFAULT_MARGIN, ...options.margin };
|
|
4763
|
+
this.bgFill = options.background;
|
|
4764
|
+
this.borderFill = options.border;
|
|
4765
|
+
this.title = typeof options.title === "string" ? { text: options.title } : options.title ?? null;
|
|
4766
|
+
this.legend = options.legend === true ? {} : options.legend || null;
|
|
3533
4767
|
this.mode = options.mode ?? "pan";
|
|
3534
4768
|
this.hoverEnabled = options.hover !== false;
|
|
3535
4769
|
this.pickMode = options.pick ?? "x";
|
|
@@ -3583,6 +4817,21 @@ var Plot = class {
|
|
|
3583
4817
|
boxShadow: "0 6px 18px rgba(0,0,0,0.25)"
|
|
3584
4818
|
});
|
|
3585
4819
|
container.appendChild(this.infoBox);
|
|
4820
|
+
this.legendDiv = document.createElement("div");
|
|
4821
|
+
Object.assign(this.legendDiv.style, {
|
|
4822
|
+
position: "absolute",
|
|
4823
|
+
display: "none",
|
|
4824
|
+
zIndex: "5",
|
|
4825
|
+
pointerEvents: "none",
|
|
4826
|
+
padding: "6px 8px",
|
|
4827
|
+
borderRadius: "6px",
|
|
4828
|
+
font: "12px system-ui, -apple-system, sans-serif",
|
|
4829
|
+
lineHeight: "1.5",
|
|
4830
|
+
background: this.isDark ? "rgba(15,23,42,0.85)" : "rgba(255,255,255,0.9)",
|
|
4831
|
+
color: this.isDark ? "#e2e8f0" : "#1e293b",
|
|
4832
|
+
border: `1px solid ${this.isDark ? "rgba(148,163,184,0.25)" : "rgba(100,116,139,0.2)"}`
|
|
4833
|
+
});
|
|
4834
|
+
container.appendChild(this.legendDiv);
|
|
3586
4835
|
this.resizeObserver = new ResizeObserver(() => this.resize());
|
|
3587
4836
|
this.resizeObserver.observe(container);
|
|
3588
4837
|
this.resize();
|
|
@@ -3623,7 +4872,7 @@ var Plot = class {
|
|
|
3623
4872
|
else rightCount++;
|
|
3624
4873
|
}
|
|
3625
4874
|
return {
|
|
3626
|
-
top: this.baseMargin.top,
|
|
4875
|
+
top: this.baseMargin.top + (this.title ? TITLE_RESERVE : 0),
|
|
3627
4876
|
bottom: this.baseMargin.bottom,
|
|
3628
4877
|
left: this.baseMargin.left + Math.max(0, leftCount - 1) * Y_AXIS_GAP,
|
|
3629
4878
|
right: this.baseMargin.right + rightCount * Y_AXIS_GAP
|
|
@@ -3702,6 +4951,67 @@ var Plot = class {
|
|
|
3702
4951
|
addBar(opts) {
|
|
3703
4952
|
return this.register(new BarLayer(this.gl, opts));
|
|
3704
4953
|
}
|
|
4954
|
+
/**
|
|
4955
|
+
* Grouped (clustered) bars: one {@link BarLayer} per series, each shifted within
|
|
4956
|
+
* its category group so the bars sit side by side. Returns the layers in order.
|
|
4957
|
+
*/
|
|
4958
|
+
addGroupedBars(opts) {
|
|
4959
|
+
const m = opts.series.length;
|
|
4960
|
+
if (m === 0) return [];
|
|
4961
|
+
const groupWidth = opts.groupWidth ?? 0.8;
|
|
4962
|
+
const slot = groupWidth / m;
|
|
4963
|
+
const barWidth = slot * (1 - (opts.gap ?? 0.1));
|
|
4964
|
+
const layers = [];
|
|
4965
|
+
for (let s = 0; s < m; s++) {
|
|
4966
|
+
const ser = opts.series[s];
|
|
4967
|
+
const off = (s - (m - 1) / 2) * slot;
|
|
4968
|
+
layers.push(
|
|
4969
|
+
new BarLayer(this.gl, {
|
|
4970
|
+
x: opts.x,
|
|
4971
|
+
y: ser.y,
|
|
4972
|
+
width: barWidth,
|
|
4973
|
+
offset: off,
|
|
4974
|
+
color: ser.color,
|
|
4975
|
+
name: ser.name,
|
|
4976
|
+
orientation: opts.orientation,
|
|
4977
|
+
yAxis: opts.yAxis
|
|
4978
|
+
})
|
|
4979
|
+
);
|
|
4980
|
+
}
|
|
4981
|
+
for (const l of layers) this.register(l);
|
|
4982
|
+
return layers;
|
|
4983
|
+
}
|
|
4984
|
+
/**
|
|
4985
|
+
* Stacked bars: each series is drawn from the running cumulative total of the
|
|
4986
|
+
* ones before it, so they stack. Returns the layers bottom-to-top.
|
|
4987
|
+
*/
|
|
4988
|
+
addStackedBars(opts) {
|
|
4989
|
+
const n = opts.x.length;
|
|
4990
|
+
const cum = new Float64Array(n);
|
|
4991
|
+
const layers = [];
|
|
4992
|
+
for (const ser of opts.series) {
|
|
4993
|
+
const base = Float64Array.from(cum);
|
|
4994
|
+
const top = new Float64Array(n);
|
|
4995
|
+
for (let i = 0; i < n; i++) {
|
|
4996
|
+
top[i] = cum[i] + (ser.y[i] ?? 0);
|
|
4997
|
+
cum[i] = top[i];
|
|
4998
|
+
}
|
|
4999
|
+
layers.push(
|
|
5000
|
+
new BarLayer(this.gl, {
|
|
5001
|
+
x: opts.x,
|
|
5002
|
+
y: top,
|
|
5003
|
+
base,
|
|
5004
|
+
width: opts.width,
|
|
5005
|
+
color: ser.color,
|
|
5006
|
+
name: ser.name,
|
|
5007
|
+
orientation: opts.orientation,
|
|
5008
|
+
yAxis: opts.yAxis
|
|
5009
|
+
})
|
|
5010
|
+
);
|
|
5011
|
+
}
|
|
5012
|
+
for (const l of layers) this.register(l);
|
|
5013
|
+
return layers;
|
|
5014
|
+
}
|
|
3705
5015
|
addArea(opts) {
|
|
3706
5016
|
return this.register(new AreaLayer(this.gl, opts));
|
|
3707
5017
|
}
|
|
@@ -3729,6 +5039,83 @@ var Plot = class {
|
|
|
3729
5039
|
addCandlestick(opts) {
|
|
3730
5040
|
return this.register(new CandlestickLayer(this.gl, opts));
|
|
3731
5041
|
}
|
|
5042
|
+
/** Filled polygons (choropleth-capable). Rings are triangulated with earcut. */
|
|
5043
|
+
addPatches(opts) {
|
|
5044
|
+
return this.register(new PatchesLayer(this.gl, opts));
|
|
5045
|
+
}
|
|
5046
|
+
/** A pie / donut chart. Set `equalAspect: true` on the plot so it stays circular. */
|
|
5047
|
+
addPie(opts) {
|
|
5048
|
+
return this.register(new PieLayer(this.gl, opts));
|
|
5049
|
+
}
|
|
5050
|
+
/** An RGBA image / URL drawn over a data-space extent. URLs redraw on load. */
|
|
5051
|
+
addImage(opts) {
|
|
5052
|
+
return this.register(
|
|
5053
|
+
new ImageLayer(this.gl, {
|
|
5054
|
+
...opts,
|
|
5055
|
+
onLoad: () => {
|
|
5056
|
+
this.requestRender();
|
|
5057
|
+
opts.onLoad?.();
|
|
5058
|
+
}
|
|
5059
|
+
})
|
|
5060
|
+
);
|
|
5061
|
+
}
|
|
5062
|
+
/**
|
|
5063
|
+
* A node-link graph. Provide node `x`/`y`, or omit them (with `nodes`, or let the
|
|
5064
|
+
* count be inferred from the edges) to auto-place with a force-directed layout.
|
|
5065
|
+
*/
|
|
5066
|
+
addGraph(opts) {
|
|
5067
|
+
let { x, y } = opts;
|
|
5068
|
+
if (!x || !y) {
|
|
5069
|
+
const n = opts.nodes ?? opts.edges.reduce((m, [a, b]) => Math.max(m, a, b), -1) + 1;
|
|
5070
|
+
const layout = forceLayout(n, opts.edges);
|
|
5071
|
+
x = layout.x;
|
|
5072
|
+
y = layout.y;
|
|
5073
|
+
}
|
|
5074
|
+
return this.register(new GraphLayer(this.gl, { ...opts, x, y }));
|
|
5075
|
+
}
|
|
5076
|
+
/**
|
|
5077
|
+
* Stacked area: each series is filled from the running cumulative total of the
|
|
5078
|
+
* ones before it. Returns the layers bottom-to-top.
|
|
5079
|
+
*/
|
|
5080
|
+
addStackedArea(opts) {
|
|
5081
|
+
const n = opts.x.length;
|
|
5082
|
+
const cum = new Float64Array(n);
|
|
5083
|
+
const layers = [];
|
|
5084
|
+
for (const ser of opts.series) {
|
|
5085
|
+
const base = Float64Array.from(cum);
|
|
5086
|
+
const top = new Float64Array(n);
|
|
5087
|
+
for (let i = 0; i < n; i++) {
|
|
5088
|
+
top[i] = cum[i] + (ser.y[i] ?? 0);
|
|
5089
|
+
cum[i] = top[i];
|
|
5090
|
+
}
|
|
5091
|
+
layers.push(
|
|
5092
|
+
new AreaLayer(this.gl, { x: opts.x, y: top, base, color: ser.color, name: ser.name, yAxis: opts.yAxis })
|
|
5093
|
+
);
|
|
5094
|
+
}
|
|
5095
|
+
for (const l of layers) this.register(l);
|
|
5096
|
+
return layers;
|
|
5097
|
+
}
|
|
5098
|
+
/**
|
|
5099
|
+
* Add a Canvas2D annotation (span / band / box / label) drawn above the data.
|
|
5100
|
+
* Returns a disposer that removes just this annotation.
|
|
5101
|
+
*/
|
|
5102
|
+
addAnnotation(a) {
|
|
5103
|
+
this.annotations.push(a);
|
|
5104
|
+
this.requestRender();
|
|
5105
|
+
return () => {
|
|
5106
|
+
const i = this.annotations.indexOf(a);
|
|
5107
|
+
if (i >= 0) {
|
|
5108
|
+
this.annotations.splice(i, 1);
|
|
5109
|
+
this.requestRender();
|
|
5110
|
+
}
|
|
5111
|
+
};
|
|
5112
|
+
}
|
|
5113
|
+
/** Remove all annotations. */
|
|
5114
|
+
clearAnnotations() {
|
|
5115
|
+
if (this.annotations.length === 0) return;
|
|
5116
|
+
this.annotations = [];
|
|
5117
|
+
this.requestRender();
|
|
5118
|
+
}
|
|
3732
5119
|
/** Compute an STFT of `signal` and render it as a heatmap (time × frequency). */
|
|
3733
5120
|
addHeatmapSpectrogram(signal, opts = {}) {
|
|
3734
5121
|
const s = spectrogram(signal, opts);
|
|
@@ -3759,14 +5146,16 @@ var Plot = class {
|
|
|
3759
5146
|
/** Register an additional named y axis. Series opt in via `addLine({ yAxis })`. */
|
|
3760
5147
|
addYAxis(id, opts = {}) {
|
|
3761
5148
|
if (this.yAxes.has(id)) throw new Error(`Y axis "${id}" already exists`);
|
|
3762
|
-
const { type, domain, side, color, ...axisConfig } = opts;
|
|
5149
|
+
const { type, domain, factors, side, color, ...axisConfig } = opts;
|
|
5150
|
+
const cat = type === "categorical";
|
|
5151
|
+
const scale = makeScale(type ?? "linear", domain ?? [0, 1], factors);
|
|
3763
5152
|
this.yAxes.set(id, {
|
|
3764
5153
|
id,
|
|
3765
|
-
scale
|
|
5154
|
+
scale,
|
|
3766
5155
|
axis: new Axis(axisConfig),
|
|
3767
5156
|
side: side ?? "right",
|
|
3768
|
-
auto: domain == null,
|
|
3769
|
-
initial: domain ?? null,
|
|
5157
|
+
auto: !cat && domain == null,
|
|
5158
|
+
initial: cat ? scale.domain : domain ?? null,
|
|
3770
5159
|
color
|
|
3771
5160
|
});
|
|
3772
5161
|
this.autoscale();
|
|
@@ -3875,6 +5264,7 @@ var Plot = class {
|
|
|
3875
5264
|
this.selectionDiv.remove();
|
|
3876
5265
|
this.tooltip.remove();
|
|
3877
5266
|
this.infoBox.remove();
|
|
5267
|
+
this.legendDiv.remove();
|
|
3878
5268
|
for (const l of this.layers) l.dispose();
|
|
3879
5269
|
this.container.removeChild(this.gridCanvas);
|
|
3880
5270
|
this.container.removeChild(this.dataCanvas);
|
|
@@ -3914,8 +5304,18 @@ var Plot = class {
|
|
|
3914
5304
|
const primary = this.primaryY();
|
|
3915
5305
|
const ticksX = this.axisX.resolve(this.scaleX);
|
|
3916
5306
|
const ticksYPrimary = primary.axis.resolve(primary.scale);
|
|
5307
|
+
const styleX = resolveAxisStyle(this.axisX.config, this.theme);
|
|
5308
|
+
const styleYPrimary = resolveAxisStyle(primary.axis.config, this.theme, primary.color);
|
|
3917
5309
|
this.gridCtx.clearRect(0, 0, layout.cssWidth, layout.cssHeight);
|
|
3918
|
-
|
|
5310
|
+
if (this.borderFill) {
|
|
5311
|
+
this.gridCtx.fillStyle = this.borderFill;
|
|
5312
|
+
this.gridCtx.fillRect(0, 0, layout.cssWidth, layout.cssHeight);
|
|
5313
|
+
}
|
|
5314
|
+
if (this.bgFill) {
|
|
5315
|
+
this.gridCtx.fillStyle = this.bgFill;
|
|
5316
|
+
this.gridCtx.fillRect(region.left, region.top, region.width, region.height);
|
|
5317
|
+
}
|
|
5318
|
+
drawGrid(this.gridCtx, region, this.scaleX, primary.scale, ticksX, ticksYPrimary, styleX, styleYPrimary);
|
|
3919
5319
|
const gl = this.gl;
|
|
3920
5320
|
const devW = this.dataCanvas.width;
|
|
3921
5321
|
const devH = this.dataCanvas.height;
|
|
@@ -3953,19 +5353,21 @@ var Plot = class {
|
|
|
3953
5353
|
this.dataCtx.clearRect(0, 0, layout.cssWidth, layout.cssHeight);
|
|
3954
5354
|
this.dataCtx.drawImage(this.sharedCanvas, 0, 0, layout.cssWidth, layout.cssHeight);
|
|
3955
5355
|
this.axisCtx.clearRect(0, 0, layout.cssWidth, layout.cssHeight);
|
|
3956
|
-
drawXAxis(this.axisCtx, region, this.scaleX, ticksX,
|
|
5356
|
+
drawXAxis(this.axisCtx, region, this.scaleX, ticksX, styleX, this.axisX.config.title);
|
|
3957
5357
|
const positions = this.yAxisPositions();
|
|
3958
5358
|
for (const ya of this.yAxes.values()) {
|
|
3959
5359
|
const pos = positions.get(ya.id);
|
|
3960
5360
|
const ticks = ya.axis.resolve(ya.scale);
|
|
3961
|
-
|
|
5361
|
+
const styleY = ya === primary ? styleYPrimary : resolveAxisStyle(ya.axis.config, this.theme, ya.color);
|
|
5362
|
+
drawYAxis(this.axisCtx, region, ya.scale, ticks, styleY, {
|
|
3962
5363
|
x: pos.x,
|
|
3963
5364
|
side: ya.side,
|
|
3964
5365
|
title: ya.axis.config.title,
|
|
3965
|
-
color: ya.color,
|
|
3966
5366
|
titleX: pos.titleX
|
|
3967
5367
|
});
|
|
3968
5368
|
}
|
|
5369
|
+
if (this.title) drawTitle(this.axisCtx, region, this.title, this.isDark);
|
|
5370
|
+
if (this.annotations.length) this.renderAnnotations(region);
|
|
3969
5371
|
if (this.crosshair && this.pressPx) {
|
|
3970
5372
|
drawCrosshairXY(this.axisCtx, region, this.pressPx.x, this.pressPx.y, this.theme);
|
|
3971
5373
|
}
|
|
@@ -3976,6 +5378,129 @@ var Plot = class {
|
|
|
3976
5378
|
if (this.pointInfo === "hover") this.selected = null;
|
|
3977
5379
|
}
|
|
3978
5380
|
this.updateInfoBox(region);
|
|
5381
|
+
this.updateLegend(region);
|
|
5382
|
+
}
|
|
5383
|
+
/** Named series that can appear in the legend: any layer exposing name + colorCss. */
|
|
5384
|
+
legendEntries() {
|
|
5385
|
+
const out = [];
|
|
5386
|
+
for (const l of this.layers) {
|
|
5387
|
+
const a = l;
|
|
5388
|
+
if (typeof a.name === "string" && a.name && typeof a.colorCss === "string") {
|
|
5389
|
+
out.push({ name: a.name, colorCss: a.colorCss });
|
|
5390
|
+
}
|
|
5391
|
+
}
|
|
5392
|
+
return out;
|
|
5393
|
+
}
|
|
5394
|
+
/** Rebuild and position the legend overlay (or hide it). */
|
|
5395
|
+
updateLegend(region) {
|
|
5396
|
+
const div = this.legendDiv;
|
|
5397
|
+
const entries = this.legend ? this.legendEntries() : [];
|
|
5398
|
+
if (!this.legend || entries.length === 0) {
|
|
5399
|
+
div.style.display = "none";
|
|
5400
|
+
return;
|
|
5401
|
+
}
|
|
5402
|
+
const cfg = this.legend;
|
|
5403
|
+
if (cfg.background) div.style.background = cfg.background;
|
|
5404
|
+
if (cfg.border) div.style.border = `1px solid ${cfg.border}`;
|
|
5405
|
+
if (cfg.textColor) div.style.color = cfg.textColor;
|
|
5406
|
+
if (cfg.font) div.style.font = cfg.font;
|
|
5407
|
+
const horizontal = cfg.orientation === "horizontal";
|
|
5408
|
+
div.style.display = "flex";
|
|
5409
|
+
div.style.flexDirection = horizontal ? "row" : "column";
|
|
5410
|
+
div.style.gap = horizontal ? "12px" : "3px";
|
|
5411
|
+
div.replaceChildren();
|
|
5412
|
+
for (const e of entries) {
|
|
5413
|
+
const row = document.createElement("div");
|
|
5414
|
+
row.style.display = "flex";
|
|
5415
|
+
row.style.alignItems = "center";
|
|
5416
|
+
row.style.gap = "6px";
|
|
5417
|
+
const swatch = document.createElement("span");
|
|
5418
|
+
Object.assign(swatch.style, {
|
|
5419
|
+
width: "10px",
|
|
5420
|
+
height: "10px",
|
|
5421
|
+
borderRadius: "2px",
|
|
5422
|
+
background: e.colorCss,
|
|
5423
|
+
flex: "0 0 auto"
|
|
5424
|
+
});
|
|
5425
|
+
const label = document.createElement("span");
|
|
5426
|
+
label.textContent = e.name;
|
|
5427
|
+
row.appendChild(swatch);
|
|
5428
|
+
row.appendChild(label);
|
|
5429
|
+
div.appendChild(row);
|
|
5430
|
+
}
|
|
5431
|
+
const inset = 8;
|
|
5432
|
+
const pos = cfg.position ?? "top-right";
|
|
5433
|
+
const w = div.offsetWidth;
|
|
5434
|
+
const h = div.offsetHeight;
|
|
5435
|
+
const left = pos.endsWith("left") ? region.left + inset : region.left + region.width - w - inset;
|
|
5436
|
+
const top = pos.startsWith("top") ? region.top + inset : region.top + region.height - h - inset;
|
|
5437
|
+
div.style.left = `${Math.max(0, left)}px`;
|
|
5438
|
+
div.style.top = `${Math.max(0, top)}px`;
|
|
5439
|
+
}
|
|
5440
|
+
/** Draw all annotations, projected through the scales and clipped to the region. */
|
|
5441
|
+
renderAnnotations(region) {
|
|
5442
|
+
const ctx = this.axisCtx;
|
|
5443
|
+
const left = region.left, right = region.left + region.width;
|
|
5444
|
+
const top = region.top, bottom = region.top + region.height;
|
|
5445
|
+
const yScaleOf = (id) => (this.yAxes.get(id ?? "y") ?? this.primaryY()).scale;
|
|
5446
|
+
const px = (v) => pxX(region, this.scaleX.norm(v));
|
|
5447
|
+
const py = (s, v) => pxY(region, s.norm(v));
|
|
5448
|
+
ctx.save();
|
|
5449
|
+
ctx.beginPath();
|
|
5450
|
+
ctx.rect(left, top, right - left, bottom - top);
|
|
5451
|
+
ctx.clip();
|
|
5452
|
+
for (const a of this.annotations) {
|
|
5453
|
+
ctx.setLineDash([]);
|
|
5454
|
+
if (a.type === "span") {
|
|
5455
|
+
ctx.strokeStyle = a.color ?? this.theme.axis;
|
|
5456
|
+
ctx.lineWidth = a.width ?? 1;
|
|
5457
|
+
if (a.dash) ctx.setLineDash(a.dash);
|
|
5458
|
+
ctx.beginPath();
|
|
5459
|
+
if (a.dim === "x") {
|
|
5460
|
+
const x = Math.round(px(a.value)) + 0.5;
|
|
5461
|
+
ctx.moveTo(x, top);
|
|
5462
|
+
ctx.lineTo(x, bottom);
|
|
5463
|
+
} else {
|
|
5464
|
+
const y = Math.round(py(yScaleOf(a.yAxis), a.value)) + 0.5;
|
|
5465
|
+
ctx.moveTo(left, y);
|
|
5466
|
+
ctx.lineTo(right, y);
|
|
5467
|
+
}
|
|
5468
|
+
ctx.stroke();
|
|
5469
|
+
} else if (a.type === "band") {
|
|
5470
|
+
ctx.fillStyle = a.color ?? "rgba(59,130,246,0.15)";
|
|
5471
|
+
if (a.dim === "x") {
|
|
5472
|
+
const x0 = px(a.from), x1 = px(a.to);
|
|
5473
|
+
ctx.fillRect(Math.min(x0, x1), top, Math.abs(x1 - x0), bottom - top);
|
|
5474
|
+
} else {
|
|
5475
|
+
const s = yScaleOf(a.yAxis);
|
|
5476
|
+
const y0 = py(s, a.from), y1 = py(s, a.to);
|
|
5477
|
+
ctx.fillRect(left, Math.min(y0, y1), right - left, Math.abs(y1 - y0));
|
|
5478
|
+
}
|
|
5479
|
+
} else if (a.type === "box") {
|
|
5480
|
+
const s = yScaleOf(a.yAxis);
|
|
5481
|
+
const x0 = px(a.x[0]), x1 = px(a.x[1]);
|
|
5482
|
+
const y0 = py(s, a.y[0]), y1 = py(s, a.y[1]);
|
|
5483
|
+
const rx = Math.min(x0, x1), ry = Math.min(y0, y1);
|
|
5484
|
+
const rw = Math.abs(x1 - x0), rh = Math.abs(y1 - y0);
|
|
5485
|
+
if (a.color) {
|
|
5486
|
+
ctx.fillStyle = a.color;
|
|
5487
|
+
ctx.fillRect(rx, ry, rw, rh);
|
|
5488
|
+
}
|
|
5489
|
+
if (a.border) {
|
|
5490
|
+
ctx.strokeStyle = a.border;
|
|
5491
|
+
ctx.lineWidth = 1;
|
|
5492
|
+
ctx.strokeRect(rx + 0.5, ry + 0.5, rw, rh);
|
|
5493
|
+
}
|
|
5494
|
+
} else {
|
|
5495
|
+
const s = yScaleOf(a.yAxis);
|
|
5496
|
+
ctx.fillStyle = a.color ?? this.theme.text;
|
|
5497
|
+
ctx.font = a.font ?? this.theme.font;
|
|
5498
|
+
ctx.textAlign = a.align ?? "left";
|
|
5499
|
+
ctx.textBaseline = "middle";
|
|
5500
|
+
ctx.fillText(a.text, px(a.x), py(s, a.y));
|
|
5501
|
+
}
|
|
5502
|
+
}
|
|
5503
|
+
ctx.restore();
|
|
3979
5504
|
}
|
|
3980
5505
|
renderHover(region) {
|
|
3981
5506
|
const cursor = this.hoverPx;
|
|
@@ -4999,154 +6524,1076 @@ function fmt(v) {
|
|
|
4999
6524
|
return String(Math.round(v * 1e3) / 1e3);
|
|
5000
6525
|
}
|
|
5001
6526
|
|
|
5002
|
-
// src/plot3d/
|
|
5003
|
-
function
|
|
5004
|
-
const
|
|
5005
|
-
|
|
5006
|
-
|
|
5007
|
-
}
|
|
5008
|
-
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
for (let r = 0; r < 4; r++) {
|
|
5012
|
-
let s = 0;
|
|
5013
|
-
for (let k = 0; k < 4; k++) s += a[k * 4 + r] * b[c * 4 + k];
|
|
5014
|
-
o[c * 4 + r] = s;
|
|
5015
|
-
}
|
|
5016
|
-
}
|
|
5017
|
-
return o;
|
|
5018
|
-
}
|
|
5019
|
-
function perspective(fovy, aspect, near, far) {
|
|
5020
|
-
const f = 1 / Math.tan(fovy / 2);
|
|
5021
|
-
const nf = 1 / (near - far);
|
|
5022
|
-
const m = new Float32Array(16);
|
|
5023
|
-
m[0] = f / aspect;
|
|
5024
|
-
m[5] = f;
|
|
5025
|
-
m[10] = (far + near) * nf;
|
|
5026
|
-
m[11] = -1;
|
|
5027
|
-
m[14] = 2 * far * near * nf;
|
|
5028
|
-
return m;
|
|
5029
|
-
}
|
|
5030
|
-
var sub = (a, b) => [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
|
|
5031
|
-
var cross = (a, b) => [
|
|
5032
|
-
a[1] * b[2] - a[2] * b[1],
|
|
5033
|
-
a[2] * b[0] - a[0] * b[2],
|
|
5034
|
-
a[0] * b[1] - a[1] * b[0]
|
|
5035
|
-
];
|
|
5036
|
-
function normalize2(v) {
|
|
5037
|
-
const l = Math.hypot(v[0], v[1], v[2]) || 1;
|
|
5038
|
-
return [v[0] / l, v[1] / l, v[2] / l];
|
|
5039
|
-
}
|
|
5040
|
-
function lookAt(eye, center, up) {
|
|
5041
|
-
const z = normalize2(sub(eye, center));
|
|
5042
|
-
const x = normalize2(cross(up, z));
|
|
5043
|
-
const y = cross(z, x);
|
|
5044
|
-
const m = identity();
|
|
5045
|
-
m[0] = x[0];
|
|
5046
|
-
m[4] = x[1];
|
|
5047
|
-
m[8] = x[2];
|
|
5048
|
-
m[1] = y[0];
|
|
5049
|
-
m[5] = y[1];
|
|
5050
|
-
m[9] = y[2];
|
|
5051
|
-
m[2] = z[0];
|
|
5052
|
-
m[6] = z[1];
|
|
5053
|
-
m[10] = z[2];
|
|
5054
|
-
m[12] = -(x[0] * eye[0] + x[1] * eye[1] + x[2] * eye[2]);
|
|
5055
|
-
m[13] = -(y[0] * eye[0] + y[1] * eye[1] + y[2] * eye[2]);
|
|
5056
|
-
m[14] = -(z[0] * eye[0] + z[1] * eye[1] + z[2] * eye[2]);
|
|
5057
|
-
return m;
|
|
5058
|
-
}
|
|
5059
|
-
function transformPoint(m, x, y, z) {
|
|
5060
|
-
return [
|
|
5061
|
-
m[0] * x + m[4] * y + m[8] * z + m[12],
|
|
5062
|
-
m[1] * x + m[5] * y + m[9] * z + m[13],
|
|
5063
|
-
m[2] * x + m[6] * y + m[10] * z + m[14],
|
|
5064
|
-
m[3] * x + m[7] * y + m[11] * z + m[15]
|
|
6527
|
+
// src/plot3d/bar3d.ts
|
|
6528
|
+
function buildCube() {
|
|
6529
|
+
const faces = [
|
|
6530
|
+
{ n: [1, 0, 0], v: [[0.5, 0, -0.5], [0.5, 1, -0.5], [0.5, 1, 0.5], [0.5, 0, -0.5], [0.5, 1, 0.5], [0.5, 0, 0.5]] },
|
|
6531
|
+
{ n: [-1, 0, 0], v: [[-0.5, 0, 0.5], [-0.5, 1, 0.5], [-0.5, 1, -0.5], [-0.5, 0, 0.5], [-0.5, 1, -0.5], [-0.5, 0, -0.5]] },
|
|
6532
|
+
{ n: [0, 1, 0], v: [[-0.5, 1, -0.5], [0.5, 1, -0.5], [0.5, 1, 0.5], [-0.5, 1, -0.5], [0.5, 1, 0.5], [-0.5, 1, 0.5]] },
|
|
6533
|
+
{ n: [0, -1, 0], v: [[-0.5, 0, 0.5], [0.5, 0, 0.5], [0.5, 0, -0.5], [-0.5, 0, 0.5], [0.5, 0, -0.5], [-0.5, 0, -0.5]] },
|
|
6534
|
+
{ n: [0, 0, 1], v: [[-0.5, 0, 0.5], [0.5, 0, 0.5], [0.5, 1, 0.5], [-0.5, 0, 0.5], [0.5, 1, 0.5], [-0.5, 1, 0.5]] },
|
|
6535
|
+
{ n: [0, 0, -1], v: [[0.5, 0, -0.5], [-0.5, 0, -0.5], [-0.5, 1, -0.5], [0.5, 0, -0.5], [-0.5, 1, -0.5], [0.5, 1, -0.5]] }
|
|
5065
6536
|
];
|
|
6537
|
+
const out = [];
|
|
6538
|
+
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]);
|
|
6539
|
+
return new Float32Array(out);
|
|
5066
6540
|
}
|
|
5067
|
-
|
|
5068
|
-
|
|
5069
|
-
m[0] = s[0];
|
|
5070
|
-
m[5] = s[1];
|
|
5071
|
-
m[10] = s[2];
|
|
5072
|
-
m[12] = t[0];
|
|
5073
|
-
m[13] = t[1];
|
|
5074
|
-
m[14] = t[2];
|
|
5075
|
-
return m;
|
|
5076
|
-
}
|
|
5077
|
-
|
|
5078
|
-
// src/plot3d/pointcloud.ts
|
|
5079
|
-
var VERT9 = (
|
|
6541
|
+
var CUBE = buildCube();
|
|
6542
|
+
var VERT10 = (
|
|
5080
6543
|
/* glsl */
|
|
5081
6544
|
`#version 300 es
|
|
5082
6545
|
precision highp float;
|
|
5083
|
-
layout(location = 0) in vec3
|
|
5084
|
-
layout(location = 1) in vec3
|
|
6546
|
+
layout(location = 0) in vec3 aCube; // unit cube (y in [0,1])
|
|
6547
|
+
layout(location = 1) in vec3 aNormal;
|
|
6548
|
+
layout(location = 2) in vec2 aBase; // instance x,z
|
|
6549
|
+
layout(location = 3) in float aHeight;
|
|
6550
|
+
layout(location = 4) in vec3 aColor;
|
|
5085
6551
|
uniform mat4 uMVP;
|
|
5086
|
-
uniform float
|
|
6552
|
+
uniform float uWidth;
|
|
5087
6553
|
out vec3 vColor;
|
|
6554
|
+
out vec3 vN;
|
|
5088
6555
|
void main() {
|
|
6556
|
+
vec3 world = vec3(aBase.x + aCube.x * uWidth, aCube.y * aHeight, aBase.y + aCube.z * uWidth);
|
|
5089
6557
|
vColor = aColor;
|
|
5090
|
-
|
|
5091
|
-
|
|
6558
|
+
vN = aNormal;
|
|
6559
|
+
gl_Position = uMVP * vec4(world, 1.0);
|
|
5092
6560
|
}`
|
|
5093
6561
|
);
|
|
5094
|
-
var
|
|
6562
|
+
var FRAG12 = (
|
|
5095
6563
|
/* glsl */
|
|
5096
6564
|
`#version 300 es
|
|
5097
6565
|
precision highp float;
|
|
5098
6566
|
in vec3 vColor;
|
|
6567
|
+
in vec3 vN;
|
|
6568
|
+
uniform vec3 uLightDir;
|
|
6569
|
+
uniform float uAmbient;
|
|
5099
6570
|
out vec4 outColor;
|
|
5100
6571
|
void main() {
|
|
5101
|
-
|
|
5102
|
-
|
|
5103
|
-
outColor = vec4(vColor, 1.0);
|
|
6572
|
+
float d = max(dot(normalize(vN), normalize(uLightDir)), 0.0);
|
|
6573
|
+
float shade = uAmbient + (1.0 - uAmbient) * d;
|
|
6574
|
+
outColor = vec4(vColor * shade, 1.0);
|
|
5104
6575
|
}`
|
|
5105
6576
|
);
|
|
5106
|
-
var
|
|
5107
|
-
function
|
|
5108
|
-
let p =
|
|
6577
|
+
var programCache13 = /* @__PURE__ */ new WeakMap();
|
|
6578
|
+
function getProgram10(gl) {
|
|
6579
|
+
let p = programCache13.get(gl);
|
|
5109
6580
|
if (!p) {
|
|
5110
|
-
p = createProgram(gl,
|
|
5111
|
-
|
|
6581
|
+
p = createProgram(gl, VERT10, FRAG12);
|
|
6582
|
+
programCache13.set(gl, p);
|
|
5112
6583
|
}
|
|
5113
6584
|
return p;
|
|
5114
6585
|
}
|
|
5115
|
-
|
|
5116
|
-
|
|
5117
|
-
|
|
5118
|
-
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
|
|
6586
|
+
function medianSpacing3(vals, n) {
|
|
6587
|
+
const s = Array.from({ length: n }, (_, i) => vals[i]).sort((a, b) => a - b);
|
|
6588
|
+
const diffs = [];
|
|
6589
|
+
for (let i = 1; i < n; i++) {
|
|
6590
|
+
const d = s[i] - s[i - 1];
|
|
6591
|
+
if (d > 1e-9) diffs.push(d);
|
|
6592
|
+
}
|
|
6593
|
+
if (!diffs.length) return 1;
|
|
6594
|
+
diffs.sort((a, b) => a - b);
|
|
6595
|
+
return diffs[Math.floor(diffs.length / 2)];
|
|
6596
|
+
}
|
|
6597
|
+
var counter17 = 0;
|
|
6598
|
+
var Bar3DLayer = class {
|
|
6599
|
+
constructor(gl, opts) {
|
|
6600
|
+
this.buffers = [];
|
|
6601
|
+
this.cInfo = null;
|
|
6602
|
+
this.lightDir = [0.5, 1, 0.35];
|
|
6603
|
+
this.ambient = 0.35;
|
|
6604
|
+
this.id = `bar3d-${counter17++}`;
|
|
6605
|
+
this.gl = gl;
|
|
6606
|
+
this.program = getProgram10(gl);
|
|
6607
|
+
this.name = opts.name;
|
|
5122
6608
|
const n = Math.min(opts.x.length, opts.y.length, opts.z.length);
|
|
5123
6609
|
this.count = n;
|
|
5124
|
-
const
|
|
6610
|
+
const dx = medianSpacing3(opts.x, n), dz = medianSpacing3(opts.z, n);
|
|
6611
|
+
this.width = opts.width ?? Math.min(dx, dz) * 0.7;
|
|
6612
|
+
const base = opts.color != null ? Array.isArray(opts.color) ? opts.color : parseColor(opts.color) : [0.24, 0.55, 0.96, 1];
|
|
6613
|
+
const cvals = opts.colorBy?.values ?? (opts.colorBy ? opts.y : null);
|
|
5125
6614
|
const cmap = opts.colorBy ? colormap(opts.colorBy.colormap ?? "viridis") : null;
|
|
5126
6615
|
let lo = opts.colorBy?.domain?.[0] ?? Infinity;
|
|
5127
6616
|
let hi = opts.colorBy?.domain?.[1] ?? -Infinity;
|
|
5128
|
-
if (
|
|
5129
|
-
const v = opts.colorBy.values;
|
|
6617
|
+
if (cmap && cvals && !opts.colorBy?.domain) {
|
|
5130
6618
|
for (let i = 0; i < n; i++) {
|
|
5131
|
-
const
|
|
5132
|
-
if (
|
|
5133
|
-
if (
|
|
6619
|
+
const v = cvals[i];
|
|
6620
|
+
if (v < lo) lo = v;
|
|
6621
|
+
if (v > hi) hi = v;
|
|
5134
6622
|
}
|
|
5135
6623
|
}
|
|
5136
6624
|
const span = hi - lo || 1;
|
|
5137
|
-
|
|
5138
|
-
|
|
6625
|
+
if (cmap) this.cInfo = { colormap: opts.colorBy.colormap ?? "viridis", domain: [lo, hi], label: opts.name };
|
|
6626
|
+
else this.colorCss = opts.color != null && typeof opts.color === "string" ? opts.color : "#3b82f6";
|
|
6627
|
+
const inst = new Float32Array(n * 6);
|
|
6628
|
+
this.positions = new Float32Array(n * 3);
|
|
6629
|
+
let minX = Infinity, maxX = -Infinity, minZ = Infinity, maxZ = -Infinity, minY = 0, maxY = 0;
|
|
6630
|
+
const hw = this.width / 2;
|
|
5139
6631
|
for (let i = 0; i < n; i++) {
|
|
5140
|
-
const x = opts.x[i],
|
|
5141
|
-
|
|
5142
|
-
|
|
5143
|
-
|
|
6632
|
+
const x = opts.x[i], z = opts.z[i], h = opts.y[i];
|
|
6633
|
+
inst[i * 6] = x;
|
|
6634
|
+
inst[i * 6 + 1] = z;
|
|
6635
|
+
inst[i * 6 + 2] = h;
|
|
6636
|
+
this.positions[i * 3] = x;
|
|
6637
|
+
this.positions[i * 3 + 1] = h;
|
|
6638
|
+
this.positions[i * 3 + 2] = z;
|
|
5144
6639
|
let c;
|
|
5145
|
-
if (cmap &&
|
|
6640
|
+
if (cmap && cvals) c = cmap((cvals[i] - lo) / span);
|
|
5146
6641
|
else c = [base[0], base[1], base[2]];
|
|
5147
|
-
|
|
5148
|
-
|
|
5149
|
-
|
|
6642
|
+
inst[i * 6 + 3] = c[0];
|
|
6643
|
+
inst[i * 6 + 4] = c[1];
|
|
6644
|
+
inst[i * 6 + 5] = c[2];
|
|
6645
|
+
if (x - hw < minX) minX = x - hw;
|
|
6646
|
+
if (x + hw > maxX) maxX = x + hw;
|
|
6647
|
+
if (z - hw < minZ) minZ = z - hw;
|
|
6648
|
+
if (z + hw > maxZ) maxZ = z + hw;
|
|
6649
|
+
if (h < minY) minY = h;
|
|
6650
|
+
if (h > maxY) maxY = h;
|
|
6651
|
+
}
|
|
6652
|
+
this.b3 = { x: [minX, maxX], y: [minY, maxY], z: [minZ, maxZ] };
|
|
6653
|
+
this.vao = gl.createVertexArray();
|
|
6654
|
+
gl.bindVertexArray(this.vao);
|
|
6655
|
+
const cubeBuf = gl.createBuffer();
|
|
6656
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, cubeBuf);
|
|
6657
|
+
gl.bufferData(gl.ARRAY_BUFFER, CUBE, gl.STATIC_DRAW);
|
|
6658
|
+
gl.enableVertexAttribArray(0);
|
|
6659
|
+
gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 24, 0);
|
|
6660
|
+
gl.enableVertexAttribArray(1);
|
|
6661
|
+
gl.vertexAttribPointer(1, 3, gl.FLOAT, false, 24, 12);
|
|
6662
|
+
const instBuf = gl.createBuffer();
|
|
6663
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, instBuf);
|
|
6664
|
+
gl.bufferData(gl.ARRAY_BUFFER, inst, gl.STATIC_DRAW);
|
|
6665
|
+
gl.enableVertexAttribArray(2);
|
|
6666
|
+
gl.vertexAttribPointer(2, 2, gl.FLOAT, false, 24, 0);
|
|
6667
|
+
gl.vertexAttribDivisor(2, 1);
|
|
6668
|
+
gl.enableVertexAttribArray(3);
|
|
6669
|
+
gl.vertexAttribPointer(3, 1, gl.FLOAT, false, 24, 8);
|
|
6670
|
+
gl.vertexAttribDivisor(3, 1);
|
|
6671
|
+
gl.enableVertexAttribArray(4);
|
|
6672
|
+
gl.vertexAttribPointer(4, 3, gl.FLOAT, false, 24, 12);
|
|
6673
|
+
gl.vertexAttribDivisor(4, 1);
|
|
6674
|
+
gl.bindVertexArray(null);
|
|
6675
|
+
this.buffers = [cubeBuf, instBuf];
|
|
6676
|
+
this.uniforms = uniformLocations(gl, this.program, ["uMVP", "uWidth", "uLightDir", "uAmbient"]);
|
|
6677
|
+
}
|
|
6678
|
+
bounds3() {
|
|
6679
|
+
return this.count ? this.b3 : null;
|
|
6680
|
+
}
|
|
6681
|
+
colorInfo() {
|
|
6682
|
+
return this.cInfo;
|
|
6683
|
+
}
|
|
6684
|
+
pickData() {
|
|
6685
|
+
return this.count ? { positions: this.positions } : null;
|
|
6686
|
+
}
|
|
6687
|
+
/** Set the light direction (world space) and ambient term (0..1). */
|
|
6688
|
+
setLight(dir, ambient) {
|
|
6689
|
+
this.lightDir = dir;
|
|
6690
|
+
this.ambient = ambient;
|
|
6691
|
+
}
|
|
6692
|
+
draw(gl, mvp) {
|
|
6693
|
+
if (this.count === 0) return;
|
|
6694
|
+
gl.useProgram(this.program);
|
|
6695
|
+
gl.uniformMatrix4fv(this.uniforms.uMVP, false, mvp);
|
|
6696
|
+
gl.uniform1f(this.uniforms.uWidth, this.width);
|
|
6697
|
+
gl.uniform3f(this.uniforms.uLightDir, this.lightDir[0], this.lightDir[1], this.lightDir[2]);
|
|
6698
|
+
gl.uniform1f(this.uniforms.uAmbient, this.ambient);
|
|
6699
|
+
gl.bindVertexArray(this.vao);
|
|
6700
|
+
gl.drawArraysInstanced(gl.TRIANGLES, 0, 36, this.count);
|
|
6701
|
+
gl.bindVertexArray(null);
|
|
6702
|
+
}
|
|
6703
|
+
dispose() {
|
|
6704
|
+
this.gl.deleteVertexArray(this.vao);
|
|
6705
|
+
for (const b of this.buffers) this.gl.deleteBuffer(b);
|
|
6706
|
+
}
|
|
6707
|
+
};
|
|
6708
|
+
|
|
6709
|
+
// src/plot3d/contour3d.ts
|
|
6710
|
+
var CASES2 = [
|
|
6711
|
+
[],
|
|
6712
|
+
[[3, 0]],
|
|
6713
|
+
[[0, 1]],
|
|
6714
|
+
[[3, 1]],
|
|
6715
|
+
[[1, 2]],
|
|
6716
|
+
[[3, 0], [1, 2]],
|
|
6717
|
+
[[0, 2]],
|
|
6718
|
+
[[3, 2]],
|
|
6719
|
+
[[2, 3]],
|
|
6720
|
+
[[2, 0]],
|
|
6721
|
+
[[0, 1], [2, 3]],
|
|
6722
|
+
[[2, 1]],
|
|
6723
|
+
[[1, 3]],
|
|
6724
|
+
[[1, 0]],
|
|
6725
|
+
[[0, 3]],
|
|
6726
|
+
[]
|
|
6727
|
+
];
|
|
6728
|
+
var VERT11 = (
|
|
6729
|
+
/* glsl */
|
|
6730
|
+
`#version 300 es
|
|
6731
|
+
precision highp float;
|
|
6732
|
+
layout(location = 0) in vec3 aPos;
|
|
6733
|
+
layout(location = 1) in vec3 aColor;
|
|
6734
|
+
uniform mat4 uMVP;
|
|
6735
|
+
out vec3 vColor;
|
|
6736
|
+
void main() { vColor = aColor; gl_Position = uMVP * vec4(aPos, 1.0); }`
|
|
6737
|
+
);
|
|
6738
|
+
var FRAG13 = (
|
|
6739
|
+
/* glsl */
|
|
6740
|
+
`#version 300 es
|
|
6741
|
+
precision highp float;
|
|
6742
|
+
in vec3 vColor;
|
|
6743
|
+
out vec4 outColor;
|
|
6744
|
+
void main() { outColor = vec4(vColor, 1.0); }`
|
|
6745
|
+
);
|
|
6746
|
+
var programCache14 = /* @__PURE__ */ new WeakMap();
|
|
6747
|
+
function getProgram11(gl) {
|
|
6748
|
+
let p = programCache14.get(gl);
|
|
6749
|
+
if (!p) {
|
|
6750
|
+
p = createProgram(gl, VERT11, FRAG13);
|
|
6751
|
+
programCache14.set(gl, p);
|
|
6752
|
+
}
|
|
6753
|
+
return p;
|
|
6754
|
+
}
|
|
6755
|
+
var counter18 = 0;
|
|
6756
|
+
var Contour3DLayer = class {
|
|
6757
|
+
constructor(gl, opts) {
|
|
6758
|
+
this.cInfo = null;
|
|
6759
|
+
this.id = `contour3d-${counter18++}`;
|
|
6760
|
+
this.gl = gl;
|
|
6761
|
+
this.program = getProgram11(gl);
|
|
6762
|
+
this.name = opts.name;
|
|
6763
|
+
const { cols, rows, values } = opts;
|
|
6764
|
+
const [x0, x1] = opts.extentX ?? [0, cols - 1];
|
|
6765
|
+
const [z0, z1] = opts.extentZ ?? [0, rows - 1];
|
|
6766
|
+
let vmin = Infinity, vmax = -Infinity;
|
|
6767
|
+
for (let i = 0; i < values.length; i++) {
|
|
6768
|
+
const v = values[i];
|
|
6769
|
+
if (v < vmin) vmin = v;
|
|
6770
|
+
if (v > vmax) vmax = v;
|
|
6771
|
+
}
|
|
6772
|
+
const lspan = vmax - vmin || 1;
|
|
6773
|
+
const count = typeof opts.levels === "number" ? opts.levels : 10;
|
|
6774
|
+
const levels = Array.isArray(opts.levels) ? opts.levels : Array.from({ length: count }, (_, i) => vmin + lspan * (i + 1) / (count + 1));
|
|
6775
|
+
const cmap = colormap(opts.colormap ?? "viridis");
|
|
6776
|
+
const fixed = opts.color != null ? Array.isArray(opts.color) ? opts.color : parseColor(opts.color) : null;
|
|
6777
|
+
if (!fixed) this.cInfo = { colormap: opts.colormap ?? "viridis", domain: [vmin, vmax], label: opts.name };
|
|
6778
|
+
const wx = (c) => x0 + c / (cols - 1) * (x1 - x0);
|
|
6779
|
+
const wz = (r) => z0 + r / (rows - 1) * (z1 - z0);
|
|
6780
|
+
const at = (c, r) => values[r * cols + c];
|
|
6781
|
+
const data = [];
|
|
6782
|
+
for (const L of levels) {
|
|
6783
|
+
const col = fixed ? [fixed[0], fixed[1], fixed[2]] : cmap((L - vmin) / lspan);
|
|
6784
|
+
for (let r = 0; r < rows - 1; r++) {
|
|
6785
|
+
for (let c = 0; c < cols - 1; c++) {
|
|
6786
|
+
const v0 = at(c, r), v1 = at(c + 1, r), v2 = at(c + 1, r + 1), v3 = at(c, r + 1);
|
|
6787
|
+
const idx = (v0 >= L ? 1 : 0) | (v1 >= L ? 2 : 0) | (v2 >= L ? 4 : 0) | (v3 >= L ? 8 : 0);
|
|
6788
|
+
const segs = CASES2[idx];
|
|
6789
|
+
if (segs.length === 0) continue;
|
|
6790
|
+
const lerp = (t, ax, az, bx, bz) => [ax + (bx - ax) * t, az + (bz - az) * t];
|
|
6791
|
+
const edge = (e) => {
|
|
6792
|
+
if (e === 0) return lerp((L - v0) / (v1 - v0 || 1e-9), wx(c), wz(r), wx(c + 1), wz(r));
|
|
6793
|
+
if (e === 1) return lerp((L - v1) / (v2 - v1 || 1e-9), wx(c + 1), wz(r), wx(c + 1), wz(r + 1));
|
|
6794
|
+
if (e === 2) return lerp((L - v2) / (v3 - v2 || 1e-9), wx(c + 1), wz(r + 1), wx(c), wz(r + 1));
|
|
6795
|
+
return lerp((L - v3) / (v0 - v3 || 1e-9), wx(c), wz(r + 1), wx(c), wz(r));
|
|
6796
|
+
};
|
|
6797
|
+
for (const [ea, eb] of segs) {
|
|
6798
|
+
const pa = edge(ea), pb = edge(eb);
|
|
6799
|
+
data.push(pa[0], L, pa[1], col[0], col[1], col[2]);
|
|
6800
|
+
data.push(pb[0], L, pb[1], col[0], col[1], col[2]);
|
|
6801
|
+
}
|
|
6802
|
+
}
|
|
6803
|
+
}
|
|
6804
|
+
}
|
|
6805
|
+
this.vertCount = data.length / 6;
|
|
6806
|
+
this.b3 = { x: [x0, x1], y: [vmin, vmax], z: [z0, z1] };
|
|
6807
|
+
this.vao = gl.createVertexArray();
|
|
6808
|
+
this.buffer = gl.createBuffer();
|
|
6809
|
+
gl.bindVertexArray(this.vao);
|
|
6810
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
|
|
6811
|
+
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW);
|
|
6812
|
+
gl.enableVertexAttribArray(0);
|
|
6813
|
+
gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 24, 0);
|
|
6814
|
+
gl.enableVertexAttribArray(1);
|
|
6815
|
+
gl.vertexAttribPointer(1, 3, gl.FLOAT, false, 24, 12);
|
|
6816
|
+
gl.bindVertexArray(null);
|
|
6817
|
+
this.uniforms = uniformLocations(gl, this.program, ["uMVP"]);
|
|
6818
|
+
}
|
|
6819
|
+
bounds3() {
|
|
6820
|
+
return this.vertCount ? this.b3 : null;
|
|
6821
|
+
}
|
|
6822
|
+
colorInfo() {
|
|
6823
|
+
return this.cInfo;
|
|
6824
|
+
}
|
|
6825
|
+
draw(gl, mvp) {
|
|
6826
|
+
if (this.vertCount === 0) return;
|
|
6827
|
+
gl.useProgram(this.program);
|
|
6828
|
+
gl.uniformMatrix4fv(this.uniforms.uMVP, false, mvp);
|
|
6829
|
+
gl.bindVertexArray(this.vao);
|
|
6830
|
+
gl.drawArrays(gl.LINES, 0, this.vertCount);
|
|
6831
|
+
gl.bindVertexArray(null);
|
|
6832
|
+
}
|
|
6833
|
+
dispose() {
|
|
6834
|
+
this.gl.deleteVertexArray(this.vao);
|
|
6835
|
+
this.gl.deleteBuffer(this.buffer);
|
|
6836
|
+
}
|
|
6837
|
+
};
|
|
6838
|
+
|
|
6839
|
+
// src/plot3d/marching-cubes.ts
|
|
6840
|
+
var EDGE_TABLE = [
|
|
6841
|
+
0,
|
|
6842
|
+
265,
|
|
6843
|
+
515,
|
|
6844
|
+
778,
|
|
6845
|
+
1030,
|
|
6846
|
+
1295,
|
|
6847
|
+
1541,
|
|
6848
|
+
1804,
|
|
6849
|
+
2060,
|
|
6850
|
+
2309,
|
|
6851
|
+
2575,
|
|
6852
|
+
2822,
|
|
6853
|
+
3082,
|
|
6854
|
+
3331,
|
|
6855
|
+
3593,
|
|
6856
|
+
3840,
|
|
6857
|
+
400,
|
|
6858
|
+
153,
|
|
6859
|
+
915,
|
|
6860
|
+
666,
|
|
6861
|
+
1430,
|
|
6862
|
+
1183,
|
|
6863
|
+
1941,
|
|
6864
|
+
1692,
|
|
6865
|
+
2460,
|
|
6866
|
+
2197,
|
|
6867
|
+
2975,
|
|
6868
|
+
2710,
|
|
6869
|
+
3482,
|
|
6870
|
+
3219,
|
|
6871
|
+
3993,
|
|
6872
|
+
3728,
|
|
6873
|
+
560,
|
|
6874
|
+
825,
|
|
6875
|
+
51,
|
|
6876
|
+
314,
|
|
6877
|
+
1590,
|
|
6878
|
+
1855,
|
|
6879
|
+
1077,
|
|
6880
|
+
1340,
|
|
6881
|
+
2620,
|
|
6882
|
+
2869,
|
|
6883
|
+
2111,
|
|
6884
|
+
2358,
|
|
6885
|
+
3642,
|
|
6886
|
+
3891,
|
|
6887
|
+
3129,
|
|
6888
|
+
3376,
|
|
6889
|
+
928,
|
|
6890
|
+
681,
|
|
6891
|
+
419,
|
|
6892
|
+
170,
|
|
6893
|
+
1958,
|
|
6894
|
+
1711,
|
|
6895
|
+
1445,
|
|
6896
|
+
1196,
|
|
6897
|
+
2988,
|
|
6898
|
+
2725,
|
|
6899
|
+
2479,
|
|
6900
|
+
2214,
|
|
6901
|
+
4010,
|
|
6902
|
+
3747,
|
|
6903
|
+
3497,
|
|
6904
|
+
3232,
|
|
6905
|
+
1120,
|
|
6906
|
+
1385,
|
|
6907
|
+
1635,
|
|
6908
|
+
1898,
|
|
6909
|
+
102,
|
|
6910
|
+
367,
|
|
6911
|
+
613,
|
|
6912
|
+
876,
|
|
6913
|
+
3180,
|
|
6914
|
+
3429,
|
|
6915
|
+
3695,
|
|
6916
|
+
3942,
|
|
6917
|
+
2154,
|
|
6918
|
+
2403,
|
|
6919
|
+
2665,
|
|
6920
|
+
2912,
|
|
6921
|
+
1520,
|
|
6922
|
+
1273,
|
|
6923
|
+
2035,
|
|
6924
|
+
1786,
|
|
6925
|
+
502,
|
|
6926
|
+
255,
|
|
6927
|
+
1013,
|
|
6928
|
+
764,
|
|
6929
|
+
3580,
|
|
6930
|
+
3317,
|
|
6931
|
+
4095,
|
|
6932
|
+
3830,
|
|
6933
|
+
2554,
|
|
6934
|
+
2291,
|
|
6935
|
+
3065,
|
|
6936
|
+
2800,
|
|
6937
|
+
1616,
|
|
6938
|
+
1881,
|
|
6939
|
+
1107,
|
|
6940
|
+
1370,
|
|
6941
|
+
598,
|
|
6942
|
+
863,
|
|
6943
|
+
85,
|
|
6944
|
+
348,
|
|
6945
|
+
3676,
|
|
6946
|
+
3925,
|
|
6947
|
+
3167,
|
|
6948
|
+
3414,
|
|
6949
|
+
2650,
|
|
6950
|
+
2899,
|
|
6951
|
+
2137,
|
|
6952
|
+
2384,
|
|
6953
|
+
1984,
|
|
6954
|
+
1737,
|
|
6955
|
+
1475,
|
|
6956
|
+
1226,
|
|
6957
|
+
966,
|
|
6958
|
+
719,
|
|
6959
|
+
453,
|
|
6960
|
+
204,
|
|
6961
|
+
4044,
|
|
6962
|
+
3781,
|
|
6963
|
+
3535,
|
|
6964
|
+
3270,
|
|
6965
|
+
3018,
|
|
6966
|
+
2755,
|
|
6967
|
+
2505,
|
|
6968
|
+
2240,
|
|
6969
|
+
2240,
|
|
6970
|
+
2505,
|
|
6971
|
+
2755,
|
|
6972
|
+
3018,
|
|
6973
|
+
3270,
|
|
6974
|
+
3535,
|
|
6975
|
+
3781,
|
|
6976
|
+
4044,
|
|
6977
|
+
204,
|
|
6978
|
+
453,
|
|
6979
|
+
719,
|
|
6980
|
+
966,
|
|
6981
|
+
1226,
|
|
6982
|
+
1475,
|
|
6983
|
+
1737,
|
|
6984
|
+
1984,
|
|
6985
|
+
2384,
|
|
6986
|
+
2137,
|
|
6987
|
+
2899,
|
|
6988
|
+
2650,
|
|
6989
|
+
3414,
|
|
6990
|
+
3167,
|
|
6991
|
+
3925,
|
|
6992
|
+
3676,
|
|
6993
|
+
348,
|
|
6994
|
+
85,
|
|
6995
|
+
863,
|
|
6996
|
+
598,
|
|
6997
|
+
1370,
|
|
6998
|
+
1107,
|
|
6999
|
+
1881,
|
|
7000
|
+
1616,
|
|
7001
|
+
2800,
|
|
7002
|
+
3065,
|
|
7003
|
+
2291,
|
|
7004
|
+
2554,
|
|
7005
|
+
3830,
|
|
7006
|
+
4095,
|
|
7007
|
+
3317,
|
|
7008
|
+
3580,
|
|
7009
|
+
764,
|
|
7010
|
+
1013,
|
|
7011
|
+
255,
|
|
7012
|
+
502,
|
|
7013
|
+
1786,
|
|
7014
|
+
2035,
|
|
7015
|
+
1273,
|
|
7016
|
+
1520,
|
|
7017
|
+
2912,
|
|
7018
|
+
2665,
|
|
7019
|
+
2403,
|
|
7020
|
+
2154,
|
|
7021
|
+
3942,
|
|
7022
|
+
3695,
|
|
7023
|
+
3429,
|
|
7024
|
+
3180,
|
|
7025
|
+
876,
|
|
7026
|
+
613,
|
|
7027
|
+
367,
|
|
7028
|
+
102,
|
|
7029
|
+
1898,
|
|
7030
|
+
1635,
|
|
7031
|
+
1385,
|
|
7032
|
+
1120,
|
|
7033
|
+
3232,
|
|
7034
|
+
3497,
|
|
7035
|
+
3747,
|
|
7036
|
+
4010,
|
|
7037
|
+
2214,
|
|
7038
|
+
2479,
|
|
7039
|
+
2725,
|
|
7040
|
+
2988,
|
|
7041
|
+
1196,
|
|
7042
|
+
1445,
|
|
7043
|
+
1711,
|
|
7044
|
+
1958,
|
|
7045
|
+
170,
|
|
7046
|
+
419,
|
|
7047
|
+
681,
|
|
7048
|
+
928,
|
|
7049
|
+
3376,
|
|
7050
|
+
3129,
|
|
7051
|
+
3891,
|
|
7052
|
+
3642,
|
|
7053
|
+
2358,
|
|
7054
|
+
2111,
|
|
7055
|
+
2869,
|
|
7056
|
+
2620,
|
|
7057
|
+
1340,
|
|
7058
|
+
1077,
|
|
7059
|
+
1855,
|
|
7060
|
+
1590,
|
|
7061
|
+
314,
|
|
7062
|
+
51,
|
|
7063
|
+
825,
|
|
7064
|
+
560,
|
|
7065
|
+
3728,
|
|
7066
|
+
3993,
|
|
7067
|
+
3219,
|
|
7068
|
+
3482,
|
|
7069
|
+
2710,
|
|
7070
|
+
2975,
|
|
7071
|
+
2197,
|
|
7072
|
+
2460,
|
|
7073
|
+
1692,
|
|
7074
|
+
1941,
|
|
7075
|
+
1183,
|
|
7076
|
+
1430,
|
|
7077
|
+
666,
|
|
7078
|
+
915,
|
|
7079
|
+
153,
|
|
7080
|
+
400,
|
|
7081
|
+
3840,
|
|
7082
|
+
3593,
|
|
7083
|
+
3331,
|
|
7084
|
+
3082,
|
|
7085
|
+
2822,
|
|
7086
|
+
2575,
|
|
7087
|
+
2309,
|
|
7088
|
+
2060,
|
|
7089
|
+
1804,
|
|
7090
|
+
1541,
|
|
7091
|
+
1295,
|
|
7092
|
+
1030,
|
|
7093
|
+
778,
|
|
7094
|
+
515,
|
|
7095
|
+
265,
|
|
7096
|
+
0
|
|
7097
|
+
];
|
|
7098
|
+
var TRI_TABLE = [
|
|
7099
|
+
[],
|
|
7100
|
+
[0, 8, 3],
|
|
7101
|
+
[0, 1, 9],
|
|
7102
|
+
[1, 8, 3, 9, 8, 1],
|
|
7103
|
+
[1, 2, 10],
|
|
7104
|
+
[0, 8, 3, 1, 2, 10],
|
|
7105
|
+
[9, 2, 10, 0, 2, 9],
|
|
7106
|
+
[2, 8, 3, 2, 10, 8, 10, 9, 8],
|
|
7107
|
+
[3, 11, 2],
|
|
7108
|
+
[0, 11, 2, 8, 11, 0],
|
|
7109
|
+
[1, 9, 0, 2, 3, 11],
|
|
7110
|
+
[1, 11, 2, 1, 9, 11, 9, 8, 11],
|
|
7111
|
+
[3, 10, 1, 11, 10, 3],
|
|
7112
|
+
[0, 10, 1, 0, 8, 10, 8, 11, 10],
|
|
7113
|
+
[3, 9, 0, 3, 11, 9, 11, 10, 9],
|
|
7114
|
+
[9, 8, 10, 10, 8, 11],
|
|
7115
|
+
[4, 7, 8],
|
|
7116
|
+
[4, 3, 0, 7, 3, 4],
|
|
7117
|
+
[0, 1, 9, 8, 4, 7],
|
|
7118
|
+
[4, 1, 9, 4, 7, 1, 7, 3, 1],
|
|
7119
|
+
[1, 2, 10, 8, 4, 7],
|
|
7120
|
+
[3, 4, 7, 3, 0, 4, 1, 2, 10],
|
|
7121
|
+
[9, 2, 10, 9, 0, 2, 8, 4, 7],
|
|
7122
|
+
[2, 10, 9, 2, 9, 7, 2, 7, 3, 7, 9, 4],
|
|
7123
|
+
[8, 4, 7, 3, 11, 2],
|
|
7124
|
+
[11, 4, 7, 11, 2, 4, 2, 0, 4],
|
|
7125
|
+
[9, 0, 1, 8, 4, 7, 2, 3, 11],
|
|
7126
|
+
[4, 7, 11, 9, 4, 11, 9, 11, 2, 9, 2, 1],
|
|
7127
|
+
[3, 10, 1, 3, 11, 10, 7, 8, 4],
|
|
7128
|
+
[1, 11, 10, 1, 4, 11, 1, 0, 4, 7, 11, 4],
|
|
7129
|
+
[4, 7, 8, 9, 0, 11, 9, 11, 10, 11, 0, 3],
|
|
7130
|
+
[4, 7, 11, 4, 11, 9, 9, 11, 10],
|
|
7131
|
+
[9, 5, 4],
|
|
7132
|
+
[9, 5, 4, 0, 8, 3],
|
|
7133
|
+
[0, 5, 4, 1, 5, 0],
|
|
7134
|
+
[8, 5, 4, 8, 3, 5, 3, 1, 5],
|
|
7135
|
+
[1, 2, 10, 9, 5, 4],
|
|
7136
|
+
[3, 0, 8, 1, 2, 10, 4, 9, 5],
|
|
7137
|
+
[5, 2, 10, 5, 4, 2, 4, 0, 2],
|
|
7138
|
+
[2, 10, 5, 3, 2, 5, 3, 5, 4, 3, 4, 8],
|
|
7139
|
+
[9, 5, 4, 2, 3, 11],
|
|
7140
|
+
[0, 11, 2, 0, 8, 11, 4, 9, 5],
|
|
7141
|
+
[0, 5, 4, 0, 1, 5, 2, 3, 11],
|
|
7142
|
+
[2, 1, 5, 2, 5, 8, 2, 8, 11, 4, 8, 5],
|
|
7143
|
+
[10, 3, 11, 10, 1, 3, 9, 5, 4],
|
|
7144
|
+
[4, 9, 5, 0, 8, 1, 8, 10, 1, 8, 11, 10],
|
|
7145
|
+
[5, 4, 0, 5, 0, 11, 5, 11, 10, 11, 0, 3],
|
|
7146
|
+
[5, 4, 8, 5, 8, 10, 10, 8, 11],
|
|
7147
|
+
[9, 7, 8, 5, 7, 9],
|
|
7148
|
+
[9, 3, 0, 9, 5, 3, 5, 7, 3],
|
|
7149
|
+
[0, 7, 8, 0, 1, 7, 1, 5, 7],
|
|
7150
|
+
[1, 5, 3, 3, 5, 7],
|
|
7151
|
+
[9, 7, 8, 9, 5, 7, 10, 1, 2],
|
|
7152
|
+
[10, 1, 2, 9, 5, 0, 5, 3, 0, 5, 7, 3],
|
|
7153
|
+
[8, 0, 2, 8, 2, 5, 8, 5, 7, 10, 5, 2],
|
|
7154
|
+
[2, 10, 5, 2, 5, 3, 3, 5, 7],
|
|
7155
|
+
[7, 9, 5, 7, 8, 9, 3, 11, 2],
|
|
7156
|
+
[9, 5, 7, 9, 7, 2, 9, 2, 0, 2, 7, 11],
|
|
7157
|
+
[2, 3, 11, 0, 1, 8, 1, 7, 8, 1, 5, 7],
|
|
7158
|
+
[11, 2, 1, 11, 1, 7, 7, 1, 5],
|
|
7159
|
+
[9, 5, 8, 8, 5, 7, 10, 1, 3, 10, 3, 11],
|
|
7160
|
+
[5, 7, 0, 5, 0, 9, 7, 11, 0, 1, 0, 10, 11, 10, 0],
|
|
7161
|
+
[11, 10, 0, 11, 0, 3, 10, 5, 0, 8, 0, 7, 5, 7, 0],
|
|
7162
|
+
[11, 10, 5, 7, 11, 5],
|
|
7163
|
+
[10, 6, 5],
|
|
7164
|
+
[0, 8, 3, 5, 10, 6],
|
|
7165
|
+
[9, 0, 1, 5, 10, 6],
|
|
7166
|
+
[1, 8, 3, 1, 9, 8, 5, 10, 6],
|
|
7167
|
+
[1, 6, 5, 2, 6, 1],
|
|
7168
|
+
[1, 6, 5, 1, 2, 6, 3, 0, 8],
|
|
7169
|
+
[9, 6, 5, 9, 0, 6, 0, 2, 6],
|
|
7170
|
+
[5, 9, 8, 5, 8, 2, 5, 2, 6, 3, 2, 8],
|
|
7171
|
+
[2, 3, 11, 10, 6, 5],
|
|
7172
|
+
[11, 0, 8, 11, 2, 0, 10, 6, 5],
|
|
7173
|
+
[0, 1, 9, 2, 3, 11, 5, 10, 6],
|
|
7174
|
+
[5, 10, 6, 1, 9, 2, 9, 11, 2, 9, 8, 11],
|
|
7175
|
+
[6, 3, 11, 6, 5, 3, 5, 1, 3],
|
|
7176
|
+
[0, 8, 11, 0, 11, 5, 0, 5, 1, 5, 11, 6],
|
|
7177
|
+
[3, 11, 6, 0, 3, 6, 0, 6, 5, 0, 5, 9],
|
|
7178
|
+
[6, 5, 9, 6, 9, 11, 11, 9, 8],
|
|
7179
|
+
[5, 10, 6, 4, 7, 8],
|
|
7180
|
+
[4, 3, 0, 4, 7, 3, 6, 5, 10],
|
|
7181
|
+
[1, 9, 0, 5, 10, 6, 8, 4, 7],
|
|
7182
|
+
[10, 6, 5, 1, 9, 7, 1, 7, 3, 7, 9, 4],
|
|
7183
|
+
[6, 1, 2, 6, 5, 1, 4, 7, 8],
|
|
7184
|
+
[1, 2, 5, 5, 2, 6, 3, 0, 4, 3, 4, 7],
|
|
7185
|
+
[8, 4, 7, 9, 0, 5, 0, 6, 5, 0, 2, 6],
|
|
7186
|
+
[7, 3, 9, 7, 9, 4, 3, 2, 9, 5, 9, 6, 2, 6, 9],
|
|
7187
|
+
[3, 11, 2, 7, 8, 4, 10, 6, 5],
|
|
7188
|
+
[5, 10, 6, 4, 7, 2, 4, 2, 0, 2, 7, 11],
|
|
7189
|
+
[0, 1, 9, 4, 7, 8, 2, 3, 11, 5, 10, 6],
|
|
7190
|
+
[9, 2, 1, 9, 11, 2, 9, 4, 11, 7, 11, 4, 5, 10, 6],
|
|
7191
|
+
[8, 4, 7, 3, 11, 5, 3, 5, 1, 5, 11, 6],
|
|
7192
|
+
[5, 1, 11, 5, 11, 6, 1, 0, 11, 7, 11, 4, 0, 4, 11],
|
|
7193
|
+
[0, 5, 9, 0, 6, 5, 0, 3, 6, 11, 6, 3, 8, 4, 7],
|
|
7194
|
+
[6, 5, 9, 6, 9, 11, 4, 7, 9, 7, 11, 9],
|
|
7195
|
+
[10, 4, 9, 6, 4, 10],
|
|
7196
|
+
[4, 10, 6, 4, 9, 10, 0, 8, 3],
|
|
7197
|
+
[10, 0, 1, 10, 6, 0, 6, 4, 0],
|
|
7198
|
+
[8, 3, 1, 8, 1, 6, 8, 6, 4, 6, 1, 10],
|
|
7199
|
+
[1, 4, 9, 1, 2, 4, 2, 6, 4],
|
|
7200
|
+
[3, 0, 8, 1, 2, 9, 2, 4, 9, 2, 6, 4],
|
|
7201
|
+
[0, 2, 4, 4, 2, 6],
|
|
7202
|
+
[8, 3, 2, 8, 2, 4, 4, 2, 6],
|
|
7203
|
+
[10, 4, 9, 10, 6, 4, 11, 2, 3],
|
|
7204
|
+
[0, 8, 2, 2, 8, 11, 4, 9, 10, 4, 10, 6],
|
|
7205
|
+
[3, 11, 2, 0, 1, 6, 0, 6, 4, 6, 1, 10],
|
|
7206
|
+
[6, 4, 1, 6, 1, 10, 4, 8, 1, 2, 1, 11, 8, 11, 1],
|
|
7207
|
+
[9, 6, 4, 9, 3, 6, 9, 1, 3, 11, 6, 3],
|
|
7208
|
+
[8, 11, 1, 8, 1, 0, 11, 6, 1, 9, 1, 4, 6, 4, 1],
|
|
7209
|
+
[3, 11, 6, 3, 6, 0, 0, 6, 4],
|
|
7210
|
+
[6, 4, 8, 11, 6, 8],
|
|
7211
|
+
[7, 10, 6, 7, 8, 10, 8, 9, 10],
|
|
7212
|
+
[0, 7, 3, 0, 10, 7, 0, 9, 10, 6, 7, 10],
|
|
7213
|
+
[10, 6, 7, 1, 10, 7, 1, 7, 8, 1, 8, 0],
|
|
7214
|
+
[10, 6, 7, 10, 7, 1, 1, 7, 3],
|
|
7215
|
+
[1, 2, 6, 1, 6, 8, 1, 8, 9, 8, 6, 7],
|
|
7216
|
+
[2, 6, 9, 2, 9, 1, 6, 7, 9, 0, 9, 3, 7, 3, 9],
|
|
7217
|
+
[7, 8, 0, 7, 0, 6, 6, 0, 2],
|
|
7218
|
+
[7, 3, 2, 6, 7, 2],
|
|
7219
|
+
[2, 3, 11, 10, 6, 8, 10, 8, 9, 8, 6, 7],
|
|
7220
|
+
[2, 0, 7, 2, 7, 11, 0, 9, 7, 6, 7, 10, 9, 10, 7],
|
|
7221
|
+
[1, 8, 0, 1, 7, 8, 1, 10, 7, 6, 7, 10, 2, 3, 11],
|
|
7222
|
+
[11, 2, 1, 11, 1, 7, 10, 6, 1, 6, 7, 1],
|
|
7223
|
+
[8, 9, 6, 8, 6, 7, 9, 1, 6, 11, 6, 3, 1, 3, 6],
|
|
7224
|
+
[0, 9, 1, 11, 6, 7],
|
|
7225
|
+
[7, 8, 0, 7, 0, 6, 3, 11, 0, 11, 6, 0],
|
|
7226
|
+
[7, 11, 6],
|
|
7227
|
+
[7, 6, 11],
|
|
7228
|
+
[3, 0, 8, 11, 7, 6],
|
|
7229
|
+
[0, 1, 9, 11, 7, 6],
|
|
7230
|
+
[8, 1, 9, 8, 3, 1, 11, 7, 6],
|
|
7231
|
+
[10, 1, 2, 6, 11, 7],
|
|
7232
|
+
[1, 2, 10, 3, 0, 8, 6, 11, 7],
|
|
7233
|
+
[2, 9, 0, 2, 10, 9, 6, 11, 7],
|
|
7234
|
+
[6, 11, 7, 2, 10, 3, 10, 8, 3, 10, 9, 8],
|
|
7235
|
+
[7, 2, 3, 6, 2, 7],
|
|
7236
|
+
[7, 0, 8, 7, 6, 0, 6, 2, 0],
|
|
7237
|
+
[2, 7, 6, 2, 3, 7, 0, 1, 9],
|
|
7238
|
+
[1, 6, 2, 1, 8, 6, 1, 9, 8, 8, 7, 6],
|
|
7239
|
+
[10, 7, 6, 10, 1, 7, 1, 3, 7],
|
|
7240
|
+
[10, 7, 6, 1, 7, 10, 1, 8, 7, 1, 0, 8],
|
|
7241
|
+
[0, 3, 7, 0, 7, 10, 0, 10, 9, 6, 10, 7],
|
|
7242
|
+
[7, 6, 10, 7, 10, 8, 8, 10, 9],
|
|
7243
|
+
[6, 8, 4, 11, 8, 6],
|
|
7244
|
+
[3, 6, 11, 3, 0, 6, 0, 4, 6],
|
|
7245
|
+
[8, 6, 11, 8, 4, 6, 9, 0, 1],
|
|
7246
|
+
[9, 4, 6, 9, 6, 3, 9, 3, 1, 11, 3, 6],
|
|
7247
|
+
[6, 8, 4, 6, 11, 8, 2, 10, 1],
|
|
7248
|
+
[1, 2, 10, 3, 0, 11, 0, 6, 11, 0, 4, 6],
|
|
7249
|
+
[4, 11, 8, 4, 6, 11, 0, 2, 9, 2, 10, 9],
|
|
7250
|
+
[10, 9, 3, 10, 3, 2, 9, 4, 3, 11, 3, 6, 4, 6, 3],
|
|
7251
|
+
[8, 2, 3, 8, 4, 2, 4, 6, 2],
|
|
7252
|
+
[0, 4, 2, 4, 6, 2],
|
|
7253
|
+
[1, 9, 0, 2, 3, 4, 2, 4, 6, 4, 3, 8],
|
|
7254
|
+
[1, 9, 4, 1, 4, 2, 2, 4, 6],
|
|
7255
|
+
[8, 1, 3, 8, 6, 1, 8, 4, 6, 6, 10, 1],
|
|
7256
|
+
[10, 1, 0, 10, 0, 6, 6, 0, 4],
|
|
7257
|
+
[4, 6, 3, 4, 3, 8, 6, 10, 3, 0, 3, 9, 10, 9, 3],
|
|
7258
|
+
[10, 9, 4, 6, 10, 4],
|
|
7259
|
+
[4, 9, 5, 7, 6, 11],
|
|
7260
|
+
[0, 8, 3, 4, 9, 5, 11, 7, 6],
|
|
7261
|
+
[5, 0, 1, 5, 4, 0, 7, 6, 11],
|
|
7262
|
+
[11, 7, 6, 8, 3, 4, 3, 5, 4, 3, 1, 5],
|
|
7263
|
+
[9, 5, 4, 10, 1, 2, 7, 6, 11],
|
|
7264
|
+
[6, 11, 7, 1, 2, 10, 0, 8, 3, 4, 9, 5],
|
|
7265
|
+
[7, 6, 11, 5, 4, 10, 4, 2, 10, 4, 0, 2],
|
|
7266
|
+
[3, 4, 8, 3, 5, 4, 3, 2, 5, 10, 5, 2, 11, 7, 6],
|
|
7267
|
+
[7, 2, 3, 7, 6, 2, 5, 4, 9],
|
|
7268
|
+
[9, 5, 4, 0, 8, 6, 0, 6, 2, 6, 8, 7],
|
|
7269
|
+
[3, 6, 2, 3, 7, 6, 1, 5, 0, 5, 4, 0],
|
|
7270
|
+
[6, 2, 8, 6, 8, 7, 2, 1, 8, 4, 8, 5, 1, 5, 8],
|
|
7271
|
+
[9, 5, 4, 10, 1, 6, 1, 7, 6, 1, 3, 7],
|
|
7272
|
+
[1, 6, 10, 1, 7, 6, 1, 0, 7, 8, 7, 0, 9, 5, 4],
|
|
7273
|
+
[4, 0, 10, 4, 10, 5, 0, 3, 10, 6, 10, 7, 3, 7, 10],
|
|
7274
|
+
[7, 6, 10, 7, 10, 8, 5, 4, 10, 4, 8, 10],
|
|
7275
|
+
[6, 9, 5, 6, 11, 9, 11, 8, 9],
|
|
7276
|
+
[3, 6, 11, 0, 6, 3, 0, 5, 6, 0, 9, 5],
|
|
7277
|
+
[0, 11, 8, 0, 5, 11, 0, 1, 5, 5, 6, 11],
|
|
7278
|
+
[6, 11, 3, 6, 3, 5, 5, 3, 1],
|
|
7279
|
+
[1, 2, 10, 9, 5, 11, 9, 11, 8, 11, 5, 6],
|
|
7280
|
+
[0, 11, 3, 0, 6, 11, 0, 9, 6, 5, 6, 9, 1, 2, 10],
|
|
7281
|
+
[11, 8, 5, 11, 5, 6, 8, 0, 5, 10, 5, 2, 0, 2, 5],
|
|
7282
|
+
[6, 11, 3, 6, 3, 5, 2, 10, 3, 10, 5, 3],
|
|
7283
|
+
[5, 8, 9, 5, 2, 8, 5, 6, 2, 3, 8, 2],
|
|
7284
|
+
[9, 5, 6, 9, 6, 0, 0, 6, 2],
|
|
7285
|
+
[1, 5, 8, 1, 8, 0, 5, 6, 8, 3, 8, 2, 6, 2, 8],
|
|
7286
|
+
[1, 5, 6, 2, 1, 6],
|
|
7287
|
+
[1, 3, 6, 1, 6, 10, 3, 8, 6, 5, 6, 9, 8, 9, 6],
|
|
7288
|
+
[10, 1, 0, 10, 0, 6, 9, 5, 0, 5, 6, 0],
|
|
7289
|
+
[0, 3, 8, 5, 6, 10],
|
|
7290
|
+
[10, 5, 6],
|
|
7291
|
+
[11, 5, 10, 7, 5, 11],
|
|
7292
|
+
[11, 5, 10, 11, 7, 5, 8, 3, 0],
|
|
7293
|
+
[5, 11, 7, 5, 10, 11, 1, 9, 0],
|
|
7294
|
+
[10, 7, 5, 10, 11, 7, 9, 8, 1, 8, 3, 1],
|
|
7295
|
+
[11, 1, 2, 11, 7, 1, 7, 5, 1],
|
|
7296
|
+
[0, 8, 3, 1, 2, 7, 1, 7, 5, 7, 2, 11],
|
|
7297
|
+
[9, 7, 5, 9, 2, 7, 9, 0, 2, 2, 11, 7],
|
|
7298
|
+
[7, 5, 2, 7, 2, 11, 5, 9, 2, 3, 2, 8, 9, 8, 2],
|
|
7299
|
+
[2, 5, 10, 2, 3, 5, 3, 7, 5],
|
|
7300
|
+
[8, 2, 0, 8, 5, 2, 8, 7, 5, 10, 2, 5],
|
|
7301
|
+
[9, 0, 1, 5, 10, 3, 5, 3, 7, 3, 10, 2],
|
|
7302
|
+
[9, 8, 2, 9, 2, 1, 8, 7, 2, 10, 2, 5, 7, 5, 2],
|
|
7303
|
+
[1, 3, 5, 3, 7, 5],
|
|
7304
|
+
[0, 8, 7, 0, 7, 1, 1, 7, 5],
|
|
7305
|
+
[9, 0, 3, 9, 3, 5, 5, 3, 7],
|
|
7306
|
+
[9, 8, 7, 5, 9, 7],
|
|
7307
|
+
[5, 8, 4, 5, 10, 8, 10, 11, 8],
|
|
7308
|
+
[5, 0, 4, 5, 11, 0, 5, 10, 11, 11, 3, 0],
|
|
7309
|
+
[0, 1, 9, 8, 4, 10, 8, 10, 11, 10, 4, 5],
|
|
7310
|
+
[10, 11, 4, 10, 4, 5, 11, 3, 4, 9, 4, 1, 3, 1, 4],
|
|
7311
|
+
[2, 5, 1, 2, 8, 5, 2, 11, 8, 4, 5, 8],
|
|
7312
|
+
[0, 4, 11, 0, 11, 3, 4, 5, 11, 2, 11, 1, 5, 1, 11],
|
|
7313
|
+
[0, 2, 5, 0, 5, 9, 2, 11, 5, 4, 5, 8, 11, 8, 5],
|
|
7314
|
+
[9, 4, 5, 2, 11, 3],
|
|
7315
|
+
[2, 5, 10, 3, 5, 2, 3, 4, 5, 3, 8, 4],
|
|
7316
|
+
[5, 10, 2, 5, 2, 4, 4, 2, 0],
|
|
7317
|
+
[3, 10, 2, 3, 5, 10, 3, 8, 5, 4, 5, 8, 0, 1, 9],
|
|
7318
|
+
[5, 10, 2, 5, 2, 4, 1, 9, 2, 9, 4, 2],
|
|
7319
|
+
[8, 4, 5, 8, 5, 3, 3, 5, 1],
|
|
7320
|
+
[0, 4, 5, 1, 0, 5],
|
|
7321
|
+
[8, 4, 5, 8, 5, 3, 9, 0, 5, 0, 3, 5],
|
|
7322
|
+
[9, 4, 5],
|
|
7323
|
+
[4, 11, 7, 4, 9, 11, 9, 10, 11],
|
|
7324
|
+
[0, 8, 3, 4, 9, 7, 9, 11, 7, 9, 10, 11],
|
|
7325
|
+
[1, 10, 11, 1, 11, 4, 1, 4, 0, 7, 4, 11],
|
|
7326
|
+
[3, 1, 4, 3, 4, 8, 1, 10, 4, 7, 4, 11, 10, 11, 4],
|
|
7327
|
+
[4, 11, 7, 9, 11, 4, 9, 2, 11, 9, 1, 2],
|
|
7328
|
+
[9, 7, 4, 9, 11, 7, 9, 1, 11, 2, 11, 1, 0, 8, 3],
|
|
7329
|
+
[11, 7, 4, 11, 4, 2, 2, 4, 0],
|
|
7330
|
+
[11, 7, 4, 11, 4, 2, 8, 3, 4, 3, 2, 4],
|
|
7331
|
+
[2, 9, 10, 2, 7, 9, 2, 3, 7, 7, 4, 9],
|
|
7332
|
+
[9, 10, 7, 9, 7, 4, 10, 2, 7, 8, 7, 0, 2, 0, 7],
|
|
7333
|
+
[3, 7, 10, 3, 10, 2, 7, 4, 10, 1, 10, 0, 4, 0, 10],
|
|
7334
|
+
[1, 10, 2, 8, 7, 4],
|
|
7335
|
+
[4, 9, 1, 4, 1, 7, 7, 1, 3],
|
|
7336
|
+
[4, 9, 1, 4, 1, 7, 0, 8, 1, 8, 7, 1],
|
|
7337
|
+
[4, 0, 3, 7, 4, 3],
|
|
7338
|
+
[4, 8, 7],
|
|
7339
|
+
[9, 10, 8, 10, 11, 8],
|
|
7340
|
+
[3, 0, 9, 3, 9, 11, 11, 9, 10],
|
|
7341
|
+
[0, 1, 10, 0, 10, 8, 8, 10, 11],
|
|
7342
|
+
[3, 1, 10, 11, 3, 10],
|
|
7343
|
+
[1, 2, 11, 1, 11, 9, 9, 11, 8],
|
|
7344
|
+
[3, 0, 9, 3, 9, 11, 1, 2, 9, 2, 11, 9],
|
|
7345
|
+
[0, 2, 11, 8, 0, 11],
|
|
7346
|
+
[3, 2, 11],
|
|
7347
|
+
[2, 3, 8, 2, 8, 10, 10, 8, 9],
|
|
7348
|
+
[9, 10, 2, 0, 9, 2],
|
|
7349
|
+
[2, 3, 8, 2, 8, 10, 0, 1, 8, 1, 10, 8],
|
|
7350
|
+
[1, 10, 2],
|
|
7351
|
+
[1, 3, 8, 9, 1, 8],
|
|
7352
|
+
[0, 9, 1],
|
|
7353
|
+
[0, 3, 8],
|
|
7354
|
+
[]
|
|
7355
|
+
];
|
|
7356
|
+
var EDGE_CORNERS = [
|
|
7357
|
+
[0, 1],
|
|
7358
|
+
[1, 2],
|
|
7359
|
+
[2, 3],
|
|
7360
|
+
[3, 0],
|
|
7361
|
+
[4, 5],
|
|
7362
|
+
[5, 6],
|
|
7363
|
+
[6, 7],
|
|
7364
|
+
[7, 4],
|
|
7365
|
+
[0, 4],
|
|
7366
|
+
[1, 5],
|
|
7367
|
+
[2, 6],
|
|
7368
|
+
[3, 7]
|
|
7369
|
+
];
|
|
7370
|
+
var CORNER = [
|
|
7371
|
+
[0, 0, 0],
|
|
7372
|
+
[1, 0, 0],
|
|
7373
|
+
[1, 1, 0],
|
|
7374
|
+
[0, 1, 0],
|
|
7375
|
+
[0, 0, 1],
|
|
7376
|
+
[1, 0, 1],
|
|
7377
|
+
[1, 1, 1],
|
|
7378
|
+
[0, 1, 1]
|
|
7379
|
+
];
|
|
7380
|
+
function marchingCubes(values, dims, isoLevel, extent) {
|
|
7381
|
+
const [nx, ny, nz] = dims;
|
|
7382
|
+
const ex = extent?.x ?? [0, nx - 1];
|
|
7383
|
+
const ez = extent?.z ?? [0, nz - 1];
|
|
7384
|
+
const ey = extent?.y ?? [0, ny - 1];
|
|
7385
|
+
const wx = (x) => ex[0] + x / Math.max(1, nx - 1) * (ex[1] - ex[0]);
|
|
7386
|
+
const wy = (y) => ey[0] + y / Math.max(1, ny - 1) * (ey[1] - ey[0]);
|
|
7387
|
+
const wz = (z) => ez[0] + z / Math.max(1, nz - 1) * (ez[1] - ez[0]);
|
|
7388
|
+
const idx = (x, y, z) => x + y * nx + z * nx * ny;
|
|
7389
|
+
const val = (x, y, z) => values[idx(x, y, z)];
|
|
7390
|
+
const grad = (x, y, z) => {
|
|
7391
|
+
const gx = val(Math.min(nx - 1, x + 1), y, z) - val(Math.max(0, x - 1), y, z);
|
|
7392
|
+
const gy = val(x, Math.min(ny - 1, y + 1), z) - val(x, Math.max(0, y - 1), z);
|
|
7393
|
+
const gz = val(x, y, Math.min(nz - 1, z + 1)) - val(x, y, Math.max(0, z - 1));
|
|
7394
|
+
return [gx, gy, gz];
|
|
7395
|
+
};
|
|
7396
|
+
const positions = [];
|
|
7397
|
+
const normals = [];
|
|
7398
|
+
const cornerVal = new Array(8);
|
|
7399
|
+
const edgeP = new Array(12);
|
|
7400
|
+
const edgeN = new Array(12);
|
|
7401
|
+
for (let z = 0; z < nz - 1; z++) {
|
|
7402
|
+
for (let y = 0; y < ny - 1; y++) {
|
|
7403
|
+
for (let x = 0; x < nx - 1; x++) {
|
|
7404
|
+
let cubeIndex = 0;
|
|
7405
|
+
for (let i = 0; i < 8; i++) {
|
|
7406
|
+
const co = CORNER[i];
|
|
7407
|
+
cornerVal[i] = val(x + co[0], y + co[1], z + co[2]);
|
|
7408
|
+
if (cornerVal[i] < isoLevel) cubeIndex |= 1 << i;
|
|
7409
|
+
}
|
|
7410
|
+
const edges = EDGE_TABLE[cubeIndex];
|
|
7411
|
+
if (edges === 0) continue;
|
|
7412
|
+
for (let e = 0; e < 12; e++) {
|
|
7413
|
+
if (!(edges & 1 << e)) continue;
|
|
7414
|
+
const [a, b] = EDGE_CORNERS[e];
|
|
7415
|
+
const va = cornerVal[a], vb = cornerVal[b];
|
|
7416
|
+
const t = Math.abs(vb - va) < 1e-9 ? 0.5 : (isoLevel - va) / (vb - va);
|
|
7417
|
+
const ca = CORNER[a], cb = CORNER[b];
|
|
7418
|
+
const px = x + ca[0] + (cb[0] - ca[0]) * t;
|
|
7419
|
+
const py = y + ca[1] + (cb[1] - ca[1]) * t;
|
|
7420
|
+
const pz = z + ca[2] + (cb[2] - ca[2]) * t;
|
|
7421
|
+
edgeP[e] = [wx(px), wy(py), wz(pz)];
|
|
7422
|
+
const ga = grad(x + ca[0], y + ca[1], z + ca[2]);
|
|
7423
|
+
const gb = grad(x + cb[0], y + cb[1], z + cb[2]);
|
|
7424
|
+
let nxn = -(ga[0] + (gb[0] - ga[0]) * t);
|
|
7425
|
+
let nyn = -(ga[1] + (gb[1] - ga[1]) * t);
|
|
7426
|
+
let nzn = -(ga[2] + (gb[2] - ga[2]) * t);
|
|
7427
|
+
const nl = Math.hypot(nxn, nyn, nzn) || 1;
|
|
7428
|
+
nxn /= nl;
|
|
7429
|
+
nyn /= nl;
|
|
7430
|
+
nzn /= nl;
|
|
7431
|
+
edgeN[e] = [nxn, nyn, nzn];
|
|
7432
|
+
}
|
|
7433
|
+
const tris = TRI_TABLE[cubeIndex];
|
|
7434
|
+
for (let i = 0; i < tris.length; i += 3) {
|
|
7435
|
+
for (let k = 0; k < 3; k++) {
|
|
7436
|
+
const p = edgeP[tris[i + k]];
|
|
7437
|
+
const nrm = edgeN[tris[i + k]];
|
|
7438
|
+
positions.push(p[0], p[1], p[2]);
|
|
7439
|
+
normals.push(nrm[0], nrm[1], nrm[2]);
|
|
7440
|
+
}
|
|
7441
|
+
}
|
|
7442
|
+
}
|
|
7443
|
+
}
|
|
7444
|
+
}
|
|
7445
|
+
return { positions: new Float32Array(positions), normals: new Float32Array(normals) };
|
|
7446
|
+
}
|
|
7447
|
+
|
|
7448
|
+
// src/plot3d/isosurface.ts
|
|
7449
|
+
var VERT12 = (
|
|
7450
|
+
/* glsl */
|
|
7451
|
+
`#version 300 es
|
|
7452
|
+
precision highp float;
|
|
7453
|
+
layout(location = 0) in vec3 aPos;
|
|
7454
|
+
layout(location = 1) in vec3 aNormal;
|
|
7455
|
+
uniform mat4 uMVP;
|
|
7456
|
+
out vec3 vN;
|
|
7457
|
+
void main() { vN = aNormal; gl_Position = uMVP * vec4(aPos, 1.0); }`
|
|
7458
|
+
);
|
|
7459
|
+
var FRAG14 = (
|
|
7460
|
+
/* glsl */
|
|
7461
|
+
`#version 300 es
|
|
7462
|
+
precision highp float;
|
|
7463
|
+
in vec3 vN;
|
|
7464
|
+
uniform vec3 uColor;
|
|
7465
|
+
uniform vec3 uLightDir;
|
|
7466
|
+
uniform float uAmbient;
|
|
7467
|
+
uniform float uOpacity;
|
|
7468
|
+
out vec4 outColor;
|
|
7469
|
+
void main() {
|
|
7470
|
+
// Two-sided: isosurfaces are viewed from inside and out.
|
|
7471
|
+
float d = abs(dot(normalize(vN), normalize(uLightDir)));
|
|
7472
|
+
float shade = uAmbient + (1.0 - uAmbient) * d;
|
|
7473
|
+
outColor = vec4(uColor * shade * uOpacity, uOpacity);
|
|
7474
|
+
}`
|
|
7475
|
+
);
|
|
7476
|
+
var programCache15 = /* @__PURE__ */ new WeakMap();
|
|
7477
|
+
function getProgram12(gl) {
|
|
7478
|
+
let p = programCache15.get(gl);
|
|
7479
|
+
if (!p) {
|
|
7480
|
+
p = createProgram(gl, VERT12, FRAG14);
|
|
7481
|
+
programCache15.set(gl, p);
|
|
7482
|
+
}
|
|
7483
|
+
return p;
|
|
7484
|
+
}
|
|
7485
|
+
var counter19 = 0;
|
|
7486
|
+
var IsosurfaceLayer = class {
|
|
7487
|
+
constructor(gl, opts) {
|
|
7488
|
+
this.lightDir = [0.5, 1, 0.35];
|
|
7489
|
+
this.ambient = 0.35;
|
|
7490
|
+
this.id = `isosurface-${counter19++}`;
|
|
7491
|
+
this.gl = gl;
|
|
7492
|
+
this.program = getProgram12(gl);
|
|
7493
|
+
this.name = opts.name;
|
|
7494
|
+
const ci = opts.color ?? "#38bdf8";
|
|
7495
|
+
this.color = Array.isArray(ci) ? ci : parseColor(ci);
|
|
7496
|
+
this.colorCss = typeof ci === "string" ? ci : toColorCss(this.color);
|
|
7497
|
+
this.opacity = opts.opacity ?? 1;
|
|
7498
|
+
const { positions, normals } = marchingCubes(opts.values, opts.dims, opts.isoLevel, opts.extent);
|
|
7499
|
+
this.vertCount = positions.length / 3;
|
|
7500
|
+
const ex = opts.extent?.x ?? [0, opts.dims[0] - 1];
|
|
7501
|
+
const ey = opts.extent?.y ?? [0, opts.dims[1] - 1];
|
|
7502
|
+
const ez = opts.extent?.z ?? [0, opts.dims[2] - 1];
|
|
7503
|
+
this.b3 = { x: ex, y: ey, z: ez };
|
|
7504
|
+
const data = new Float32Array(this.vertCount * 6);
|
|
7505
|
+
for (let i = 0; i < this.vertCount; i++) {
|
|
7506
|
+
data[i * 6] = positions[i * 3];
|
|
7507
|
+
data[i * 6 + 1] = positions[i * 3 + 1];
|
|
7508
|
+
data[i * 6 + 2] = positions[i * 3 + 2];
|
|
7509
|
+
data[i * 6 + 3] = normals[i * 3];
|
|
7510
|
+
data[i * 6 + 4] = normals[i * 3 + 1];
|
|
7511
|
+
data[i * 6 + 5] = normals[i * 3 + 2];
|
|
7512
|
+
}
|
|
7513
|
+
this.vao = gl.createVertexArray();
|
|
7514
|
+
this.buffer = gl.createBuffer();
|
|
7515
|
+
gl.bindVertexArray(this.vao);
|
|
7516
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
|
|
7517
|
+
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
|
|
7518
|
+
gl.enableVertexAttribArray(0);
|
|
7519
|
+
gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 24, 0);
|
|
7520
|
+
gl.enableVertexAttribArray(1);
|
|
7521
|
+
gl.vertexAttribPointer(1, 3, gl.FLOAT, false, 24, 12);
|
|
7522
|
+
gl.bindVertexArray(null);
|
|
7523
|
+
this.uniforms = uniformLocations(gl, this.program, ["uMVP", "uColor", "uLightDir", "uAmbient", "uOpacity"]);
|
|
7524
|
+
}
|
|
7525
|
+
bounds3() {
|
|
7526
|
+
return this.vertCount ? this.b3 : null;
|
|
7527
|
+
}
|
|
7528
|
+
/** Set the light direction (world space) and ambient term (0..1). */
|
|
7529
|
+
setLight(dir, ambient) {
|
|
7530
|
+
this.lightDir = dir;
|
|
7531
|
+
this.ambient = ambient;
|
|
7532
|
+
}
|
|
7533
|
+
draw(gl, mvp) {
|
|
7534
|
+
if (this.vertCount === 0) return;
|
|
7535
|
+
gl.useProgram(this.program);
|
|
7536
|
+
gl.uniformMatrix4fv(this.uniforms.uMVP, false, mvp);
|
|
7537
|
+
gl.uniform3f(this.uniforms.uColor, this.color[0], this.color[1], this.color[2]);
|
|
7538
|
+
gl.uniform3f(this.uniforms.uLightDir, this.lightDir[0], this.lightDir[1], this.lightDir[2]);
|
|
7539
|
+
gl.uniform1f(this.uniforms.uAmbient, this.ambient);
|
|
7540
|
+
gl.uniform1f(this.uniforms.uOpacity, this.opacity);
|
|
7541
|
+
gl.bindVertexArray(this.vao);
|
|
7542
|
+
gl.drawArrays(gl.TRIANGLES, 0, this.vertCount);
|
|
7543
|
+
gl.bindVertexArray(null);
|
|
7544
|
+
}
|
|
7545
|
+
dispose() {
|
|
7546
|
+
this.gl.deleteVertexArray(this.vao);
|
|
7547
|
+
this.gl.deleteBuffer(this.buffer);
|
|
7548
|
+
}
|
|
7549
|
+
};
|
|
7550
|
+
|
|
7551
|
+
// src/plot3d/line3d.ts
|
|
7552
|
+
var VERT13 = (
|
|
7553
|
+
/* glsl */
|
|
7554
|
+
`#version 300 es
|
|
7555
|
+
precision highp float;
|
|
7556
|
+
layout(location = 0) in vec3 aPos;
|
|
7557
|
+
uniform mat4 uMVP;
|
|
7558
|
+
void main() { gl_Position = uMVP * vec4(aPos, 1.0); }`
|
|
7559
|
+
);
|
|
7560
|
+
var FRAG15 = (
|
|
7561
|
+
/* glsl */
|
|
7562
|
+
`#version 300 es
|
|
7563
|
+
precision highp float;
|
|
7564
|
+
uniform vec4 uColor;
|
|
7565
|
+
out vec4 outColor;
|
|
7566
|
+
void main() { outColor = uColor; }`
|
|
7567
|
+
);
|
|
7568
|
+
var programCache16 = /* @__PURE__ */ new WeakMap();
|
|
7569
|
+
function getProgram13(gl) {
|
|
7570
|
+
let p = programCache16.get(gl);
|
|
7571
|
+
if (!p) {
|
|
7572
|
+
p = createProgram(gl, VERT13, FRAG15);
|
|
7573
|
+
programCache16.set(gl, p);
|
|
7574
|
+
}
|
|
7575
|
+
return p;
|
|
7576
|
+
}
|
|
7577
|
+
var counter20 = 0;
|
|
7578
|
+
var Line3DLayer = class {
|
|
7579
|
+
constructor(gl, opts) {
|
|
7580
|
+
this.id = `line3d-${counter20++}`;
|
|
7581
|
+
this.gl = gl;
|
|
7582
|
+
this.program = getProgram13(gl);
|
|
7583
|
+
this.name = opts.name;
|
|
7584
|
+
const ci = opts.color ?? "#38bdf8";
|
|
7585
|
+
this.color = Array.isArray(ci) ? ci : parseColor(ci);
|
|
7586
|
+
this.colorCss = typeof ci === "string" ? ci : toColorCss(this.color);
|
|
7587
|
+
const n = Math.min(opts.x.length, opts.y.length, opts.z.length);
|
|
7588
|
+
this.count = n;
|
|
7589
|
+
const data = new Float32Array(n * 3);
|
|
7590
|
+
this.positions = data;
|
|
7591
|
+
let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity, minZ = Infinity, maxZ = -Infinity;
|
|
7592
|
+
for (let i = 0; i < n; i++) {
|
|
7593
|
+
const x = opts.x[i], y = opts.y[i], z = opts.z[i];
|
|
7594
|
+
data[i * 3] = x;
|
|
7595
|
+
data[i * 3 + 1] = y;
|
|
7596
|
+
data[i * 3 + 2] = z;
|
|
5150
7597
|
if (x < minX) minX = x;
|
|
5151
7598
|
if (x > maxX) maxX = x;
|
|
5152
7599
|
if (y < minY) minY = y;
|
|
@@ -5155,30 +7602,425 @@ var PointCloudLayer = class {
|
|
|
5155
7602
|
if (z > maxZ) maxZ = z;
|
|
5156
7603
|
}
|
|
5157
7604
|
this.b3 = { x: [minX, maxX], y: [minY, maxY], z: [minZ, maxZ] };
|
|
7605
|
+
this.vao = gl.createVertexArray();
|
|
7606
|
+
this.buffer = gl.createBuffer();
|
|
7607
|
+
gl.bindVertexArray(this.vao);
|
|
7608
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
|
|
7609
|
+
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
|
|
7610
|
+
gl.enableVertexAttribArray(0);
|
|
7611
|
+
gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0);
|
|
7612
|
+
gl.bindVertexArray(null);
|
|
7613
|
+
this.uniforms = uniformLocations(gl, this.program, ["uMVP", "uColor"]);
|
|
7614
|
+
}
|
|
7615
|
+
bounds3() {
|
|
7616
|
+
return this.count ? this.b3 : null;
|
|
7617
|
+
}
|
|
7618
|
+
pickData() {
|
|
7619
|
+
return this.count ? { positions: this.positions } : null;
|
|
7620
|
+
}
|
|
7621
|
+
/** Stream a new path. Call `plot.refresh()` afterwards to re-fit + redraw. */
|
|
7622
|
+
setData(x, y, z) {
|
|
7623
|
+
const n = Math.min(x.length, y.length, z.length);
|
|
7624
|
+
this.count = n;
|
|
7625
|
+
const data = new Float32Array(n * 3);
|
|
7626
|
+
this.positions = data;
|
|
7627
|
+
let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity, minZ = Infinity, maxZ = -Infinity;
|
|
7628
|
+
for (let i = 0; i < n; i++) {
|
|
7629
|
+
const px = x[i], py = y[i], pz = z[i];
|
|
7630
|
+
data[i * 3] = px;
|
|
7631
|
+
data[i * 3 + 1] = py;
|
|
7632
|
+
data[i * 3 + 2] = pz;
|
|
7633
|
+
if (px < minX) minX = px;
|
|
7634
|
+
if (px > maxX) maxX = px;
|
|
7635
|
+
if (py < minY) minY = py;
|
|
7636
|
+
if (py > maxY) maxY = py;
|
|
7637
|
+
if (pz < minZ) minZ = pz;
|
|
7638
|
+
if (pz > maxZ) maxZ = pz;
|
|
7639
|
+
}
|
|
7640
|
+
this.b3 = { x: [minX, maxX], y: [minY, maxY], z: [minZ, maxZ] };
|
|
7641
|
+
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffer);
|
|
7642
|
+
this.gl.bufferData(this.gl.ARRAY_BUFFER, data, this.gl.DYNAMIC_DRAW);
|
|
7643
|
+
}
|
|
7644
|
+
draw(gl, mvp) {
|
|
7645
|
+
if (this.count < 2) return;
|
|
7646
|
+
gl.useProgram(this.program);
|
|
7647
|
+
gl.uniformMatrix4fv(this.uniforms.uMVP, false, mvp);
|
|
7648
|
+
gl.uniform4f(this.uniforms.uColor, this.color[0], this.color[1], this.color[2], this.color[3]);
|
|
7649
|
+
gl.bindVertexArray(this.vao);
|
|
7650
|
+
gl.drawArrays(gl.LINE_STRIP, 0, this.count);
|
|
7651
|
+
gl.bindVertexArray(null);
|
|
7652
|
+
}
|
|
7653
|
+
dispose() {
|
|
7654
|
+
this.gl.deleteVertexArray(this.vao);
|
|
7655
|
+
this.gl.deleteBuffer(this.buffer);
|
|
7656
|
+
}
|
|
7657
|
+
};
|
|
7658
|
+
|
|
7659
|
+
// src/plot3d/mat4.ts
|
|
7660
|
+
function identity() {
|
|
7661
|
+
const m = new Float32Array(16);
|
|
7662
|
+
m[0] = m[5] = m[10] = m[15] = 1;
|
|
7663
|
+
return m;
|
|
7664
|
+
}
|
|
7665
|
+
function multiply(a, b) {
|
|
7666
|
+
const o = new Float32Array(16);
|
|
7667
|
+
for (let c = 0; c < 4; c++) {
|
|
7668
|
+
for (let r = 0; r < 4; r++) {
|
|
7669
|
+
let s = 0;
|
|
7670
|
+
for (let k = 0; k < 4; k++) s += a[k * 4 + r] * b[c * 4 + k];
|
|
7671
|
+
o[c * 4 + r] = s;
|
|
7672
|
+
}
|
|
7673
|
+
}
|
|
7674
|
+
return o;
|
|
7675
|
+
}
|
|
7676
|
+
function perspective(fovy, aspect, near, far) {
|
|
7677
|
+
const f = 1 / Math.tan(fovy / 2);
|
|
7678
|
+
const nf = 1 / (near - far);
|
|
7679
|
+
const m = new Float32Array(16);
|
|
7680
|
+
m[0] = f / aspect;
|
|
7681
|
+
m[5] = f;
|
|
7682
|
+
m[10] = (far + near) * nf;
|
|
7683
|
+
m[11] = -1;
|
|
7684
|
+
m[14] = 2 * far * near * nf;
|
|
7685
|
+
return m;
|
|
7686
|
+
}
|
|
7687
|
+
var sub = (a, b) => [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
|
|
7688
|
+
var cross = (a, b) => [
|
|
7689
|
+
a[1] * b[2] - a[2] * b[1],
|
|
7690
|
+
a[2] * b[0] - a[0] * b[2],
|
|
7691
|
+
a[0] * b[1] - a[1] * b[0]
|
|
7692
|
+
];
|
|
7693
|
+
function normalize2(v) {
|
|
7694
|
+
const l = Math.hypot(v[0], v[1], v[2]) || 1;
|
|
7695
|
+
return [v[0] / l, v[1] / l, v[2] / l];
|
|
7696
|
+
}
|
|
7697
|
+
function lookAt(eye, center, up) {
|
|
7698
|
+
const z = normalize2(sub(eye, center));
|
|
7699
|
+
const x = normalize2(cross(up, z));
|
|
7700
|
+
const y = cross(z, x);
|
|
7701
|
+
const m = identity();
|
|
7702
|
+
m[0] = x[0];
|
|
7703
|
+
m[4] = x[1];
|
|
7704
|
+
m[8] = x[2];
|
|
7705
|
+
m[1] = y[0];
|
|
7706
|
+
m[5] = y[1];
|
|
7707
|
+
m[9] = y[2];
|
|
7708
|
+
m[2] = z[0];
|
|
7709
|
+
m[6] = z[1];
|
|
7710
|
+
m[10] = z[2];
|
|
7711
|
+
m[12] = -(x[0] * eye[0] + x[1] * eye[1] + x[2] * eye[2]);
|
|
7712
|
+
m[13] = -(y[0] * eye[0] + y[1] * eye[1] + y[2] * eye[2]);
|
|
7713
|
+
m[14] = -(z[0] * eye[0] + z[1] * eye[1] + z[2] * eye[2]);
|
|
7714
|
+
return m;
|
|
7715
|
+
}
|
|
7716
|
+
function transformPoint(m, x, y, z) {
|
|
7717
|
+
return [
|
|
7718
|
+
m[0] * x + m[4] * y + m[8] * z + m[12],
|
|
7719
|
+
m[1] * x + m[5] * y + m[9] * z + m[13],
|
|
7720
|
+
m[2] * x + m[6] * y + m[10] * z + m[14],
|
|
7721
|
+
m[3] * x + m[7] * y + m[11] * z + m[15]
|
|
7722
|
+
];
|
|
7723
|
+
}
|
|
7724
|
+
function scaleTranslate(s, t) {
|
|
7725
|
+
const m = identity();
|
|
7726
|
+
m[0] = s[0];
|
|
7727
|
+
m[5] = s[1];
|
|
7728
|
+
m[10] = s[2];
|
|
7729
|
+
m[12] = t[0];
|
|
7730
|
+
m[13] = t[1];
|
|
7731
|
+
m[14] = t[2];
|
|
7732
|
+
return m;
|
|
7733
|
+
}
|
|
7734
|
+
|
|
7735
|
+
// src/plot3d/pointcloud.ts
|
|
7736
|
+
var VERT14 = (
|
|
7737
|
+
/* glsl */
|
|
7738
|
+
`#version 300 es
|
|
7739
|
+
precision highp float;
|
|
7740
|
+
layout(location = 0) in vec3 aPos;
|
|
7741
|
+
layout(location = 1) in vec3 aColor;
|
|
7742
|
+
layout(location = 2) in float aSize;
|
|
7743
|
+
uniform mat4 uMVP;
|
|
7744
|
+
uniform float uSize;
|
|
7745
|
+
out vec3 vColor;
|
|
7746
|
+
void main() {
|
|
7747
|
+
vColor = aColor;
|
|
7748
|
+
gl_Position = uMVP * vec4(aPos, 1.0);
|
|
7749
|
+
gl_PointSize = aSize > 0.0 ? aSize : uSize;
|
|
7750
|
+
}`
|
|
7751
|
+
);
|
|
7752
|
+
var FRAG16 = (
|
|
7753
|
+
/* glsl */
|
|
7754
|
+
`#version 300 es
|
|
7755
|
+
precision highp float;
|
|
7756
|
+
in vec3 vColor;
|
|
7757
|
+
out vec4 outColor;
|
|
7758
|
+
void main() {
|
|
7759
|
+
vec2 d = gl_PointCoord - 0.5;
|
|
7760
|
+
if (length(d) > 0.5) discard;
|
|
7761
|
+
outColor = vec4(vColor, 1.0);
|
|
7762
|
+
}`
|
|
7763
|
+
);
|
|
7764
|
+
var programCache17 = /* @__PURE__ */ new WeakMap();
|
|
7765
|
+
function getProgram14(gl) {
|
|
7766
|
+
let p = programCache17.get(gl);
|
|
7767
|
+
if (!p) {
|
|
7768
|
+
p = createProgram(gl, VERT14, FRAG16);
|
|
7769
|
+
programCache17.set(gl, p);
|
|
7770
|
+
}
|
|
7771
|
+
return p;
|
|
7772
|
+
}
|
|
7773
|
+
var counter21 = 0;
|
|
7774
|
+
var PointCloudLayer = class {
|
|
7775
|
+
constructor(gl, opts) {
|
|
7776
|
+
this.b3 = { x: [0, 0], y: [0, 0], z: [0, 0] };
|
|
7777
|
+
this.cInfo = null;
|
|
7778
|
+
this.id = `points3d-${counter21++}`;
|
|
7779
|
+
this.gl = gl;
|
|
7780
|
+
this.program = getProgram14(gl);
|
|
7781
|
+
this.size = opts.size ?? 4;
|
|
7782
|
+
this.name = opts.name;
|
|
7783
|
+
this.labels = opts.labels;
|
|
7784
|
+
const n = Math.min(opts.x.length, opts.y.length, opts.z.length);
|
|
7785
|
+
this.count = n;
|
|
7786
|
+
const base = opts.color != null ? Array.isArray(opts.color) ? opts.color : parseColor(opts.color) : [0.4, 0.7, 1, 1];
|
|
7787
|
+
const lut = opts.colorBy ? colormapLUT(opts.colorBy.colormap ?? "viridis") : null;
|
|
7788
|
+
let lo = opts.colorBy?.domain?.[0] ?? Infinity;
|
|
7789
|
+
let hi = opts.colorBy?.domain?.[1] ?? -Infinity;
|
|
7790
|
+
if (opts.colorBy && !opts.colorBy.domain) {
|
|
7791
|
+
const v = opts.colorBy.values;
|
|
7792
|
+
for (let i = 0; i < n; i++) {
|
|
7793
|
+
const t = v[i];
|
|
7794
|
+
if (t < lo) lo = t;
|
|
7795
|
+
if (t > hi) hi = t;
|
|
7796
|
+
}
|
|
7797
|
+
}
|
|
7798
|
+
const span = hi - lo || 1;
|
|
7799
|
+
if (opts.colorBy) this.cInfo = { colormap: opts.colorBy.colormap ?? "viridis", domain: [lo, hi], label: opts.name };
|
|
7800
|
+
else this.colorCss = toColorCss(base);
|
|
7801
|
+
this.data = new Float32Array(n * 7);
|
|
7802
|
+
this.positions = new Float32Array(n * 3);
|
|
7803
|
+
for (let i = 0; i < n; i++) {
|
|
7804
|
+
const x = opts.x[i], y = opts.y[i], z = opts.z[i];
|
|
7805
|
+
this.data[i * 7] = x;
|
|
7806
|
+
this.data[i * 7 + 1] = y;
|
|
7807
|
+
this.data[i * 7 + 2] = z;
|
|
7808
|
+
this.positions[i * 3] = x;
|
|
7809
|
+
this.positions[i * 3 + 1] = y;
|
|
7810
|
+
this.positions[i * 3 + 2] = z;
|
|
7811
|
+
if (lut && opts.colorBy) {
|
|
7812
|
+
let t = (opts.colorBy.values[i] - lo) / span;
|
|
7813
|
+
t = t <= 0 ? 0 : t >= 1 ? 1 : t;
|
|
7814
|
+
const j = (t * 255 | 0) * 3;
|
|
7815
|
+
this.data[i * 7 + 3] = lut[j];
|
|
7816
|
+
this.data[i * 7 + 4] = lut[j + 1];
|
|
7817
|
+
this.data[i * 7 + 5] = lut[j + 2];
|
|
7818
|
+
} else {
|
|
7819
|
+
this.data[i * 7 + 3] = base[0];
|
|
7820
|
+
this.data[i * 7 + 4] = base[1];
|
|
7821
|
+
this.data[i * 7 + 5] = base[2];
|
|
7822
|
+
}
|
|
7823
|
+
this.data[i * 7 + 6] = opts.sizes ? opts.sizes[i] : 0;
|
|
7824
|
+
}
|
|
7825
|
+
this.updateBounds();
|
|
5158
7826
|
const vao = gl.createVertexArray();
|
|
5159
7827
|
const buffer = gl.createBuffer();
|
|
5160
7828
|
this.vao = vao;
|
|
5161
7829
|
this.buffer = buffer;
|
|
5162
7830
|
gl.bindVertexArray(vao);
|
|
5163
7831
|
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
|
5164
|
-
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
|
|
7832
|
+
gl.bufferData(gl.ARRAY_BUFFER, this.data, gl.STATIC_DRAW);
|
|
7833
|
+
gl.enableVertexAttribArray(0);
|
|
7834
|
+
gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 28, 0);
|
|
7835
|
+
gl.enableVertexAttribArray(1);
|
|
7836
|
+
gl.vertexAttribPointer(1, 3, gl.FLOAT, false, 28, 12);
|
|
7837
|
+
gl.enableVertexAttribArray(2);
|
|
7838
|
+
gl.vertexAttribPointer(2, 1, gl.FLOAT, false, 28, 24);
|
|
7839
|
+
gl.bindVertexArray(null);
|
|
7840
|
+
this.uniforms = uniformLocations(gl, this.program, ["uMVP", "uSize"]);
|
|
7841
|
+
}
|
|
7842
|
+
updateBounds() {
|
|
7843
|
+
let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity, minZ = Infinity, maxZ = -Infinity;
|
|
7844
|
+
for (let i = 0; i < this.count; i++) {
|
|
7845
|
+
const x = this.positions[i * 3], y = this.positions[i * 3 + 1], z = this.positions[i * 3 + 2];
|
|
7846
|
+
if (x < minX) minX = x;
|
|
7847
|
+
if (x > maxX) maxX = x;
|
|
7848
|
+
if (y < minY) minY = y;
|
|
7849
|
+
if (y > maxY) maxY = y;
|
|
7850
|
+
if (z < minZ) minZ = z;
|
|
7851
|
+
if (z > maxZ) maxZ = z;
|
|
7852
|
+
}
|
|
7853
|
+
this.b3 = { x: [minX, maxX], y: [minY, maxY], z: [minZ, maxZ] };
|
|
7854
|
+
}
|
|
7855
|
+
/** Stream new point positions (same count keeps colors/sizes). Call `plot.refresh()` after. */
|
|
7856
|
+
setData(x, y, z) {
|
|
7857
|
+
const n = Math.min(x.length, y.length, z.length, this.count);
|
|
7858
|
+
for (let i = 0; i < n; i++) {
|
|
7859
|
+
const px = x[i], py = y[i], pz = z[i];
|
|
7860
|
+
this.data[i * 7] = px;
|
|
7861
|
+
this.data[i * 7 + 1] = py;
|
|
7862
|
+
this.data[i * 7 + 2] = pz;
|
|
7863
|
+
this.positions[i * 3] = px;
|
|
7864
|
+
this.positions[i * 3 + 1] = py;
|
|
7865
|
+
this.positions[i * 3 + 2] = pz;
|
|
7866
|
+
}
|
|
7867
|
+
this.updateBounds();
|
|
7868
|
+
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffer);
|
|
7869
|
+
this.gl.bufferData(this.gl.ARRAY_BUFFER, this.data, this.gl.DYNAMIC_DRAW);
|
|
7870
|
+
}
|
|
7871
|
+
bounds3() {
|
|
7872
|
+
return this.count ? this.b3 : null;
|
|
7873
|
+
}
|
|
7874
|
+
colorInfo() {
|
|
7875
|
+
return this.cInfo;
|
|
7876
|
+
}
|
|
7877
|
+
pickData() {
|
|
7878
|
+
if (!this.count) return null;
|
|
7879
|
+
return { positions: this.positions, label: this.labels ? (i) => String(this.labels[i]) : void 0 };
|
|
7880
|
+
}
|
|
7881
|
+
draw(gl, mvp) {
|
|
7882
|
+
if (this.count === 0) return;
|
|
7883
|
+
gl.useProgram(this.program);
|
|
7884
|
+
gl.uniformMatrix4fv(this.uniforms.uMVP, false, mvp);
|
|
7885
|
+
gl.uniform1f(this.uniforms.uSize, this.size);
|
|
7886
|
+
gl.bindVertexArray(this.vao);
|
|
7887
|
+
gl.drawArrays(gl.POINTS, 0, this.count);
|
|
7888
|
+
gl.bindVertexArray(null);
|
|
7889
|
+
}
|
|
7890
|
+
dispose() {
|
|
7891
|
+
this.gl.deleteVertexArray(this.vao);
|
|
7892
|
+
this.gl.deleteBuffer(this.buffer);
|
|
7893
|
+
}
|
|
7894
|
+
};
|
|
7895
|
+
|
|
7896
|
+
// src/plot3d/quiver3d.ts
|
|
7897
|
+
var VERT15 = (
|
|
7898
|
+
/* glsl */
|
|
7899
|
+
`#version 300 es
|
|
7900
|
+
precision highp float;
|
|
7901
|
+
layout(location = 0) in vec3 aPos;
|
|
7902
|
+
layout(location = 1) in vec3 aColor;
|
|
7903
|
+
uniform mat4 uMVP;
|
|
7904
|
+
out vec3 vColor;
|
|
7905
|
+
void main() { vColor = aColor; gl_Position = uMVP * vec4(aPos, 1.0); }`
|
|
7906
|
+
);
|
|
7907
|
+
var FRAG17 = (
|
|
7908
|
+
/* glsl */
|
|
7909
|
+
`#version 300 es
|
|
7910
|
+
precision highp float;
|
|
7911
|
+
in vec3 vColor;
|
|
7912
|
+
out vec4 outColor;
|
|
7913
|
+
void main() { outColor = vec4(vColor, 1.0); }`
|
|
7914
|
+
);
|
|
7915
|
+
var programCache18 = /* @__PURE__ */ new WeakMap();
|
|
7916
|
+
function getProgram15(gl) {
|
|
7917
|
+
let p = programCache18.get(gl);
|
|
7918
|
+
if (!p) {
|
|
7919
|
+
p = createProgram(gl, VERT15, FRAG17);
|
|
7920
|
+
programCache18.set(gl, p);
|
|
7921
|
+
}
|
|
7922
|
+
return p;
|
|
7923
|
+
}
|
|
7924
|
+
function norm(x, y, z) {
|
|
7925
|
+
const l = Math.hypot(x, y, z) || 1;
|
|
7926
|
+
return [x / l, y / l, z / l];
|
|
7927
|
+
}
|
|
7928
|
+
function cross2(a, b) {
|
|
7929
|
+
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]];
|
|
7930
|
+
}
|
|
7931
|
+
var counter22 = 0;
|
|
7932
|
+
var Quiver3DLayer = class {
|
|
7933
|
+
constructor(gl, opts) {
|
|
7934
|
+
this.cInfo = null;
|
|
7935
|
+
this.id = `quiver3d-${counter22++}`;
|
|
7936
|
+
this.gl = gl;
|
|
7937
|
+
this.program = getProgram15(gl);
|
|
7938
|
+
this.name = opts.name;
|
|
7939
|
+
const scale = opts.scale ?? 1;
|
|
7940
|
+
const headSize = opts.headSize ?? 0.28;
|
|
7941
|
+
const n = Math.min(opts.x.length, opts.y.length, opts.z.length, opts.u.length, opts.v.length, opts.w.length);
|
|
7942
|
+
const base = opts.color != null ? Array.isArray(opts.color) ? opts.color : parseColor(opts.color) : [0.5, 0.8, 1, 1];
|
|
7943
|
+
const cmap = opts.colorBy ? colormap(opts.colorBy.colormap ?? "viridis") : null;
|
|
7944
|
+
let lo = opts.colorBy?.domain?.[0] ?? Infinity;
|
|
7945
|
+
let hi = opts.colorBy?.domain?.[1] ?? -Infinity;
|
|
7946
|
+
const mag = (i) => Math.hypot(opts.u[i], opts.v[i], opts.w[i]);
|
|
7947
|
+
if (cmap && !opts.colorBy?.domain) {
|
|
7948
|
+
for (let i = 0; i < n; i++) {
|
|
7949
|
+
const m = mag(i);
|
|
7950
|
+
if (m < lo) lo = m;
|
|
7951
|
+
if (m > hi) hi = m;
|
|
7952
|
+
}
|
|
7953
|
+
}
|
|
7954
|
+
const span = hi - lo || 1;
|
|
7955
|
+
if (cmap) this.cInfo = { colormap: opts.colorBy.colormap ?? "viridis", domain: [lo, hi], label: opts.name };
|
|
7956
|
+
else this.colorCss = typeof opts.color === "string" ? opts.color : toColorCss(base);
|
|
7957
|
+
const data = [];
|
|
7958
|
+
this.positions = new Float32Array(n * 3);
|
|
7959
|
+
let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity, minZ = Infinity, maxZ = -Infinity;
|
|
7960
|
+
const bump = (x, y, z) => {
|
|
7961
|
+
if (x < minX) minX = x;
|
|
7962
|
+
if (x > maxX) maxX = x;
|
|
7963
|
+
if (y < minY) minY = y;
|
|
7964
|
+
if (y > maxY) maxY = y;
|
|
7965
|
+
if (z < minZ) minZ = z;
|
|
7966
|
+
if (z > maxZ) maxZ = z;
|
|
7967
|
+
};
|
|
7968
|
+
for (let i = 0; i < n; i++) {
|
|
7969
|
+
const bx = opts.x[i], by = opts.y[i], bz = opts.z[i];
|
|
7970
|
+
const ux = opts.u[i] * scale, uy = opts.v[i] * scale, uz = opts.w[i] * scale;
|
|
7971
|
+
const tx = bx + ux, ty = by + uy, tz = bz + uz;
|
|
7972
|
+
this.positions[i * 3] = bx;
|
|
7973
|
+
this.positions[i * 3 + 1] = by;
|
|
7974
|
+
this.positions[i * 3 + 2] = bz;
|
|
7975
|
+
const c = cmap ? cmap((mag(i) - lo) / span) : [base[0], base[1], base[2]];
|
|
7976
|
+
const seg = (x0, y0, z0, x1, y1, z1) => {
|
|
7977
|
+
data.push(x0, y0, z0, c[0], c[1], c[2], x1, y1, z1, c[0], c[1], c[2]);
|
|
7978
|
+
};
|
|
7979
|
+
seg(bx, by, bz, tx, ty, tz);
|
|
7980
|
+
bump(bx, by, bz);
|
|
7981
|
+
bump(tx, ty, tz);
|
|
7982
|
+
const len = Math.hypot(ux, uy, uz);
|
|
7983
|
+
if (len > 1e-6) {
|
|
7984
|
+
const dir = norm(ux, uy, uz);
|
|
7985
|
+
const ref = Math.abs(dir[1]) > 0.9 ? [1, 0, 0] : [0, 1, 0];
|
|
7986
|
+
const p1 = norm(...cross2(dir, ref));
|
|
7987
|
+
const p2 = norm(...cross2(dir, p1));
|
|
7988
|
+
const hl = len * headSize, hw = hl * 0.5;
|
|
7989
|
+
const backX = tx - dir[0] * hl, backY = ty - dir[1] * hl, backZ = tz - dir[2] * hl;
|
|
7990
|
+
for (const p of [p1, p2, [-p1[0], -p1[1], -p1[2]], [-p2[0], -p2[1], -p2[2]]]) {
|
|
7991
|
+
seg(tx, ty, tz, backX + p[0] * hw, backY + p[1] * hw, backZ + p[2] * hw);
|
|
7992
|
+
}
|
|
7993
|
+
}
|
|
7994
|
+
}
|
|
7995
|
+
this.vertCount = data.length / 6;
|
|
7996
|
+
this.b3 = { x: [minX, maxX], y: [minY, maxY], z: [minZ, maxZ] };
|
|
7997
|
+
this.vao = gl.createVertexArray();
|
|
7998
|
+
this.buffer = gl.createBuffer();
|
|
7999
|
+
gl.bindVertexArray(this.vao);
|
|
8000
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
|
|
8001
|
+
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW);
|
|
5165
8002
|
gl.enableVertexAttribArray(0);
|
|
5166
8003
|
gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 24, 0);
|
|
5167
8004
|
gl.enableVertexAttribArray(1);
|
|
5168
8005
|
gl.vertexAttribPointer(1, 3, gl.FLOAT, false, 24, 12);
|
|
5169
8006
|
gl.bindVertexArray(null);
|
|
5170
|
-
this.uniforms = uniformLocations(gl, this.program, ["uMVP"
|
|
8007
|
+
this.uniforms = uniformLocations(gl, this.program, ["uMVP"]);
|
|
5171
8008
|
}
|
|
5172
8009
|
bounds3() {
|
|
5173
|
-
return this.
|
|
8010
|
+
return this.vertCount ? this.b3 : null;
|
|
8011
|
+
}
|
|
8012
|
+
colorInfo() {
|
|
8013
|
+
return this.cInfo;
|
|
8014
|
+
}
|
|
8015
|
+
pickData() {
|
|
8016
|
+
return this.positions.length ? { positions: this.positions } : null;
|
|
5174
8017
|
}
|
|
5175
8018
|
draw(gl, mvp) {
|
|
5176
|
-
if (this.
|
|
8019
|
+
if (this.vertCount === 0) return;
|
|
5177
8020
|
gl.useProgram(this.program);
|
|
5178
8021
|
gl.uniformMatrix4fv(this.uniforms.uMVP, false, mvp);
|
|
5179
|
-
gl.uniform1f(this.uniforms.uSize, this.size);
|
|
5180
8022
|
gl.bindVertexArray(this.vao);
|
|
5181
|
-
gl.drawArrays(gl.
|
|
8023
|
+
gl.drawArrays(gl.LINES, 0, this.vertCount);
|
|
5182
8024
|
gl.bindVertexArray(null);
|
|
5183
8025
|
}
|
|
5184
8026
|
dispose() {
|
|
@@ -5188,7 +8030,7 @@ var PointCloudLayer = class {
|
|
|
5188
8030
|
};
|
|
5189
8031
|
|
|
5190
8032
|
// src/plot3d/surface.ts
|
|
5191
|
-
var
|
|
8033
|
+
var VERT16 = (
|
|
5192
8034
|
/* glsl */
|
|
5193
8035
|
`#version 300 es
|
|
5194
8036
|
precision highp float;
|
|
@@ -5200,7 +8042,7 @@ out vec3 vColor;
|
|
|
5200
8042
|
out vec3 vN;
|
|
5201
8043
|
void main() { vColor = aColor; vN = aNormal; gl_Position = uMVP * vec4(aPos, 1.0); }`
|
|
5202
8044
|
);
|
|
5203
|
-
var
|
|
8045
|
+
var FRAG18 = (
|
|
5204
8046
|
/* glsl */
|
|
5205
8047
|
`#version 300 es
|
|
5206
8048
|
precision highp float;
|
|
@@ -5215,23 +8057,54 @@ void main() {
|
|
|
5215
8057
|
outColor = vec4(vColor * shade, 1.0);
|
|
5216
8058
|
}`
|
|
5217
8059
|
);
|
|
5218
|
-
var
|
|
5219
|
-
|
|
5220
|
-
|
|
8060
|
+
var WF_VERT = (
|
|
8061
|
+
/* glsl */
|
|
8062
|
+
`#version 300 es
|
|
8063
|
+
precision highp float;
|
|
8064
|
+
layout(location = 0) in vec3 aPos;
|
|
8065
|
+
layout(location = 1) in vec3 aColor;
|
|
8066
|
+
uniform mat4 uMVP;
|
|
8067
|
+
out vec3 vColor;
|
|
8068
|
+
void main() { vColor = aColor; gl_Position = uMVP * vec4(aPos, 1.0); }`
|
|
8069
|
+
);
|
|
8070
|
+
var WF_FRAG = (
|
|
8071
|
+
/* glsl */
|
|
8072
|
+
`#version 300 es
|
|
8073
|
+
precision highp float;
|
|
8074
|
+
in vec3 vColor;
|
|
8075
|
+
out vec4 outColor;
|
|
8076
|
+
void main() { outColor = vec4(vColor, 1.0); }`
|
|
8077
|
+
);
|
|
8078
|
+
var programCache19 = /* @__PURE__ */ new WeakMap();
|
|
8079
|
+
function getProgram16(gl) {
|
|
8080
|
+
let p = programCache19.get(gl);
|
|
5221
8081
|
if (!p) {
|
|
5222
|
-
p = createProgram(gl,
|
|
5223
|
-
|
|
8082
|
+
p = createProgram(gl, VERT16, FRAG18);
|
|
8083
|
+
programCache19.set(gl, p);
|
|
5224
8084
|
}
|
|
5225
8085
|
return p;
|
|
5226
8086
|
}
|
|
5227
|
-
var
|
|
8087
|
+
var wireCache = /* @__PURE__ */ new WeakMap();
|
|
8088
|
+
function getWireProgram(gl) {
|
|
8089
|
+
let p = wireCache.get(gl);
|
|
8090
|
+
if (!p) {
|
|
8091
|
+
p = createProgram(gl, WF_VERT, WF_FRAG);
|
|
8092
|
+
wireCache.set(gl, p);
|
|
8093
|
+
}
|
|
8094
|
+
return p;
|
|
8095
|
+
}
|
|
8096
|
+
var counter23 = 0;
|
|
5228
8097
|
var SurfaceLayer = class {
|
|
5229
8098
|
constructor(gl, opts) {
|
|
8099
|
+
this.vDomain = [0, 1];
|
|
5230
8100
|
this.lightDir = [0.5, 1, 0.35];
|
|
5231
8101
|
this.ambient = 0.35;
|
|
5232
|
-
this.id = `surface-${
|
|
8102
|
+
this.id = `surface-${counter23++}`;
|
|
5233
8103
|
this.gl = gl;
|
|
5234
|
-
this.
|
|
8104
|
+
this.wireframe = opts.wireframe ?? false;
|
|
8105
|
+
this.program = this.wireframe ? getWireProgram(gl) : getProgram16(gl);
|
|
8106
|
+
this.name = opts.name;
|
|
8107
|
+
this.cmapName = opts.colormap ?? "viridis";
|
|
5235
8108
|
const { cols, rows, values } = opts;
|
|
5236
8109
|
const [x0, x1] = opts.extentX ?? [0, cols - 1];
|
|
5237
8110
|
const [z0, z1] = opts.extentZ ?? [0, rows - 1];
|
|
@@ -5256,42 +8129,71 @@ var SurfaceLayer = class {
|
|
|
5256
8129
|
return [-dzdx / nl, 1 / nl, -dzdz / nl];
|
|
5257
8130
|
};
|
|
5258
8131
|
const data = [];
|
|
5259
|
-
const vert = (c, r) => {
|
|
5260
|
-
const [nx, ny, nz] = normalAt(c, r);
|
|
5261
|
-
const [cr, cg, cb] = cmap((at(c, r) - vmin) / span);
|
|
5262
|
-
data.push(wx(c), at(c, r), wz(r), nx, ny, nz, cr, cg, cb);
|
|
5263
|
-
};
|
|
5264
|
-
for (let r = 0; r < rows - 1; r++) {
|
|
5265
|
-
for (let c = 0; c < cols - 1; c++) {
|
|
5266
|
-
vert(c, r);
|
|
5267
|
-
vert(c + 1, r);
|
|
5268
|
-
vert(c + 1, r + 1);
|
|
5269
|
-
vert(c, r);
|
|
5270
|
-
vert(c + 1, r + 1);
|
|
5271
|
-
vert(c, r + 1);
|
|
5272
|
-
}
|
|
5273
|
-
}
|
|
5274
|
-
this.vertexCount = data.length / 9;
|
|
5275
8132
|
this.b3 = { x: [x0, x1], y: [vmin, vmax], z: [z0, z1] };
|
|
8133
|
+
this.vDomain = [vmin, vmax];
|
|
5276
8134
|
const vao = gl.createVertexArray();
|
|
5277
8135
|
const buffer = gl.createBuffer();
|
|
5278
8136
|
this.vao = vao;
|
|
5279
8137
|
this.buffer = buffer;
|
|
5280
8138
|
gl.bindVertexArray(vao);
|
|
5281
8139
|
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
|
5282
|
-
|
|
5283
|
-
|
|
5284
|
-
|
|
5285
|
-
|
|
5286
|
-
|
|
5287
|
-
|
|
5288
|
-
|
|
8140
|
+
if (this.wireframe) {
|
|
8141
|
+
const vertL = (c, r) => {
|
|
8142
|
+
const [cr, cg, cb] = cmap((at(c, r) - vmin) / span);
|
|
8143
|
+
data.push(wx(c), at(c, r), wz(r), cr, cg, cb);
|
|
8144
|
+
};
|
|
8145
|
+
for (let r = 0; r < rows; r++) {
|
|
8146
|
+
for (let c = 0; c < cols; c++) {
|
|
8147
|
+
if (c < cols - 1) {
|
|
8148
|
+
vertL(c, r);
|
|
8149
|
+
vertL(c + 1, r);
|
|
8150
|
+
}
|
|
8151
|
+
if (r < rows - 1) {
|
|
8152
|
+
vertL(c, r);
|
|
8153
|
+
vertL(c, r + 1);
|
|
8154
|
+
}
|
|
8155
|
+
}
|
|
8156
|
+
}
|
|
8157
|
+
this.vertexCount = data.length / 6;
|
|
8158
|
+
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW);
|
|
8159
|
+
gl.enableVertexAttribArray(0);
|
|
8160
|
+
gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 24, 0);
|
|
8161
|
+
gl.enableVertexAttribArray(1);
|
|
8162
|
+
gl.vertexAttribPointer(1, 3, gl.FLOAT, false, 24, 12);
|
|
8163
|
+
} else {
|
|
8164
|
+
const vert = (c, r) => {
|
|
8165
|
+
const [nx, ny, nz] = normalAt(c, r);
|
|
8166
|
+
const [cr, cg, cb] = cmap((at(c, r) - vmin) / span);
|
|
8167
|
+
data.push(wx(c), at(c, r), wz(r), nx, ny, nz, cr, cg, cb);
|
|
8168
|
+
};
|
|
8169
|
+
for (let r = 0; r < rows - 1; r++) {
|
|
8170
|
+
for (let c = 0; c < cols - 1; c++) {
|
|
8171
|
+
vert(c, r);
|
|
8172
|
+
vert(c + 1, r);
|
|
8173
|
+
vert(c + 1, r + 1);
|
|
8174
|
+
vert(c, r);
|
|
8175
|
+
vert(c + 1, r + 1);
|
|
8176
|
+
vert(c, r + 1);
|
|
8177
|
+
}
|
|
8178
|
+
}
|
|
8179
|
+
this.vertexCount = data.length / 9;
|
|
8180
|
+
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW);
|
|
8181
|
+
gl.enableVertexAttribArray(0);
|
|
8182
|
+
gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 36, 0);
|
|
8183
|
+
gl.enableVertexAttribArray(1);
|
|
8184
|
+
gl.vertexAttribPointer(1, 3, gl.FLOAT, false, 36, 12);
|
|
8185
|
+
gl.enableVertexAttribArray(2);
|
|
8186
|
+
gl.vertexAttribPointer(2, 3, gl.FLOAT, false, 36, 24);
|
|
8187
|
+
}
|
|
5289
8188
|
gl.bindVertexArray(null);
|
|
5290
8189
|
this.uniforms = uniformLocations(gl, this.program, ["uMVP", "uLightDir", "uAmbient"]);
|
|
5291
8190
|
}
|
|
5292
8191
|
bounds3() {
|
|
5293
8192
|
return this.b3;
|
|
5294
8193
|
}
|
|
8194
|
+
colorInfo() {
|
|
8195
|
+
return { colormap: this.cmapName, domain: this.vDomain, label: this.name };
|
|
8196
|
+
}
|
|
5295
8197
|
/** Set the light direction (world space) and ambient term (0..1). */
|
|
5296
8198
|
setLight(dir, ambient) {
|
|
5297
8199
|
this.lightDir = dir;
|
|
@@ -5300,15 +8202,221 @@ var SurfaceLayer = class {
|
|
|
5300
8202
|
draw(gl, mvp) {
|
|
5301
8203
|
gl.useProgram(this.program);
|
|
5302
8204
|
gl.uniformMatrix4fv(this.uniforms.uMVP, false, mvp);
|
|
5303
|
-
|
|
5304
|
-
|
|
8205
|
+
if (this.wireframe) {
|
|
8206
|
+
gl.bindVertexArray(this.vao);
|
|
8207
|
+
gl.drawArrays(gl.LINES, 0, this.vertexCount);
|
|
8208
|
+
} else {
|
|
8209
|
+
gl.uniform3f(this.uniforms.uLightDir, this.lightDir[0], this.lightDir[1], this.lightDir[2]);
|
|
8210
|
+
gl.uniform1f(this.uniforms.uAmbient, this.ambient);
|
|
8211
|
+
gl.bindVertexArray(this.vao);
|
|
8212
|
+
gl.drawArrays(gl.TRIANGLES, 0, this.vertexCount);
|
|
8213
|
+
}
|
|
8214
|
+
gl.bindVertexArray(null);
|
|
8215
|
+
}
|
|
8216
|
+
dispose() {
|
|
8217
|
+
this.gl.deleteVertexArray(this.vao);
|
|
8218
|
+
this.gl.deleteBuffer(this.buffer);
|
|
8219
|
+
}
|
|
8220
|
+
};
|
|
8221
|
+
|
|
8222
|
+
// src/plot3d/volume.ts
|
|
8223
|
+
var STEPS = 160;
|
|
8224
|
+
var VERT17 = (
|
|
8225
|
+
/* glsl */
|
|
8226
|
+
`#version 300 es
|
|
8227
|
+
precision highp float;
|
|
8228
|
+
layout(location = 0) in vec3 aPos; // world position of the box corner
|
|
8229
|
+
layout(location = 1) in vec3 aLocal; // [0,1]^3 texture coord of the corner
|
|
8230
|
+
uniform mat4 uMVP;
|
|
8231
|
+
out vec3 vLocal;
|
|
8232
|
+
void main() { vLocal = aLocal; gl_Position = uMVP * vec4(aPos, 1.0); }`
|
|
8233
|
+
);
|
|
8234
|
+
var FRAG19 = (
|
|
8235
|
+
/* glsl */
|
|
8236
|
+
`#version 300 es
|
|
8237
|
+
precision highp float;
|
|
8238
|
+
in vec3 vLocal;
|
|
8239
|
+
uniform highp sampler3D uVolume;
|
|
8240
|
+
uniform sampler2D uLUT;
|
|
8241
|
+
uniform vec3 uCamLocal;
|
|
8242
|
+
uniform float uDensity;
|
|
8243
|
+
out vec4 outColor;
|
|
8244
|
+
|
|
8245
|
+
void main() {
|
|
8246
|
+
vec3 dir = normalize(vLocal - uCamLocal);
|
|
8247
|
+
vec3 invDir = 1.0 / dir;
|
|
8248
|
+
vec3 t0 = (vec3(0.0) - uCamLocal) * invDir;
|
|
8249
|
+
vec3 t1 = (vec3(1.0) - uCamLocal) * invDir;
|
|
8250
|
+
vec3 tmin = min(t0, t1), tmax = max(t0, t1);
|
|
8251
|
+
float tNear = max(max(tmin.x, tmin.y), tmin.z);
|
|
8252
|
+
float tFar = min(min(tmax.x, tmax.y), tmax.z);
|
|
8253
|
+
tNear = max(tNear, 0.0);
|
|
8254
|
+
if (tFar <= tNear) discard;
|
|
8255
|
+
|
|
8256
|
+
float dt = (tFar - tNear) / float(${STEPS});
|
|
8257
|
+
vec4 acc = vec4(0.0);
|
|
8258
|
+
for (int i = 0; i < ${STEPS}; i++) {
|
|
8259
|
+
float t = tNear + (float(i) + 0.5) * dt;
|
|
8260
|
+
vec3 p = uCamLocal + dir * t;
|
|
8261
|
+
float v = texture(uVolume, p).r; // normalized 0..1
|
|
8262
|
+
vec3 col = texture(uLUT, vec2(v, 0.5)).rgb;
|
|
8263
|
+
float a = clamp(v * uDensity * dt * 120.0, 0.0, 1.0);
|
|
8264
|
+
acc.rgb += (1.0 - acc.a) * col * a;
|
|
8265
|
+
acc.a += (1.0 - acc.a) * a;
|
|
8266
|
+
if (acc.a > 0.98) break;
|
|
8267
|
+
}
|
|
8268
|
+
if (acc.a < 0.001) discard;
|
|
8269
|
+
outColor = acc; // premultiplied
|
|
8270
|
+
}`
|
|
8271
|
+
);
|
|
8272
|
+
var programCache20 = /* @__PURE__ */ new WeakMap();
|
|
8273
|
+
function getProgram17(gl) {
|
|
8274
|
+
let p = programCache20.get(gl);
|
|
8275
|
+
if (!p) {
|
|
8276
|
+
p = createProgram(gl, VERT17, FRAG19);
|
|
8277
|
+
programCache20.set(gl, p);
|
|
8278
|
+
}
|
|
8279
|
+
return p;
|
|
8280
|
+
}
|
|
8281
|
+
var CUBE_CORNERS = [
|
|
8282
|
+
[0, 0, 0],
|
|
8283
|
+
[1, 0, 0],
|
|
8284
|
+
[1, 1, 0],
|
|
8285
|
+
[0, 1, 0],
|
|
8286
|
+
[0, 0, 1],
|
|
8287
|
+
[1, 0, 1],
|
|
8288
|
+
[1, 1, 1],
|
|
8289
|
+
[0, 1, 1]
|
|
8290
|
+
];
|
|
8291
|
+
var CUBE_FACES = [
|
|
8292
|
+
[0, 1, 2, 0, 2, 3],
|
|
8293
|
+
[4, 6, 5, 4, 7, 6],
|
|
8294
|
+
[0, 4, 5, 0, 5, 1],
|
|
8295
|
+
[3, 2, 6, 3, 6, 7],
|
|
8296
|
+
[0, 3, 7, 0, 7, 4],
|
|
8297
|
+
[1, 5, 6, 1, 6, 2]
|
|
8298
|
+
];
|
|
8299
|
+
var counter24 = 0;
|
|
8300
|
+
var VolumeLayer = class {
|
|
8301
|
+
constructor(gl, opts) {
|
|
8302
|
+
this.camLocal = [0.5, 0.5, 2];
|
|
8303
|
+
this.id = `volume-${counter24++}`;
|
|
8304
|
+
this.gl = gl;
|
|
8305
|
+
this.program = getProgram17(gl);
|
|
8306
|
+
this.name = opts.name;
|
|
8307
|
+
this.density = opts.density ?? 1;
|
|
8308
|
+
this.cmapName = opts.colormap ?? "viridis";
|
|
8309
|
+
const [nx, ny, nz] = opts.dims;
|
|
8310
|
+
const ex = opts.extent?.x ?? [0, nx - 1];
|
|
8311
|
+
const ey = opts.extent?.y ?? [0, ny - 1];
|
|
8312
|
+
const ez = opts.extent?.z ?? [0, nz - 1];
|
|
8313
|
+
this.ext = { x: ex, y: ey, z: ez };
|
|
8314
|
+
this.b3 = { x: ex, y: ey, z: ez };
|
|
8315
|
+
let vmin = opts.domain?.[0] ?? Infinity;
|
|
8316
|
+
let vmax = opts.domain?.[1] ?? -Infinity;
|
|
8317
|
+
if (!opts.domain) {
|
|
8318
|
+
for (let i = 0; i < opts.values.length; i++) {
|
|
8319
|
+
const v = opts.values[i];
|
|
8320
|
+
if (v < vmin) vmin = v;
|
|
8321
|
+
if (v > vmax) vmax = v;
|
|
8322
|
+
}
|
|
8323
|
+
}
|
|
8324
|
+
this.vDomain = [vmin, vmax];
|
|
8325
|
+
const span = vmax - vmin || 1;
|
|
8326
|
+
const voxels = new Uint8Array(nx * ny * nz);
|
|
8327
|
+
for (let i = 0; i < voxels.length; i++) voxels[i] = Math.max(0, Math.min(255, Math.round((opts.values[i] - vmin) / span * 255)));
|
|
8328
|
+
this.tex = gl.createTexture();
|
|
8329
|
+
gl.bindTexture(gl.TEXTURE_3D, this.tex);
|
|
8330
|
+
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
|
|
8331
|
+
gl.texImage3D(gl.TEXTURE_3D, 0, gl.R8, nx, ny, nz, 0, gl.RED, gl.UNSIGNED_BYTE, voxels);
|
|
8332
|
+
gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
8333
|
+
gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
8334
|
+
gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
8335
|
+
gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
8336
|
+
gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_WRAP_R, gl.CLAMP_TO_EDGE);
|
|
8337
|
+
gl.bindTexture(gl.TEXTURE_3D, null);
|
|
8338
|
+
const cmap = colormap(this.cmapName);
|
|
8339
|
+
const lutPix = new Uint8Array(256 * 4);
|
|
8340
|
+
for (let i = 0; i < 256; i++) {
|
|
8341
|
+
const [r, g, b] = cmap(i / 255);
|
|
8342
|
+
lutPix[i * 4] = Math.round(r * 255);
|
|
8343
|
+
lutPix[i * 4 + 1] = Math.round(g * 255);
|
|
8344
|
+
lutPix[i * 4 + 2] = Math.round(b * 255);
|
|
8345
|
+
lutPix[i * 4 + 3] = 255;
|
|
8346
|
+
}
|
|
8347
|
+
this.lut = gl.createTexture();
|
|
8348
|
+
gl.bindTexture(gl.TEXTURE_2D, this.lut);
|
|
8349
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 256, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, lutPix);
|
|
8350
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
8351
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
8352
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
8353
|
+
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
8354
|
+
const data = [];
|
|
8355
|
+
for (const face of CUBE_FACES) {
|
|
8356
|
+
for (const ci of face) {
|
|
8357
|
+
const c = CUBE_CORNERS[ci];
|
|
8358
|
+
const wxp = ex[0] + c[0] * (ex[1] - ex[0]);
|
|
8359
|
+
const wyp = ey[0] + c[1] * (ey[1] - ey[0]);
|
|
8360
|
+
const wzp = ez[0] + c[2] * (ez[1] - ez[0]);
|
|
8361
|
+
data.push(wxp, wyp, wzp, c[0], c[1], c[2]);
|
|
8362
|
+
}
|
|
8363
|
+
}
|
|
8364
|
+
this.vao = gl.createVertexArray();
|
|
8365
|
+
this.buffer = gl.createBuffer();
|
|
5305
8366
|
gl.bindVertexArray(this.vao);
|
|
5306
|
-
gl.
|
|
8367
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
|
|
8368
|
+
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW);
|
|
8369
|
+
gl.enableVertexAttribArray(0);
|
|
8370
|
+
gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 24, 0);
|
|
8371
|
+
gl.enableVertexAttribArray(1);
|
|
8372
|
+
gl.vertexAttribPointer(1, 3, gl.FLOAT, false, 24, 12);
|
|
8373
|
+
gl.bindVertexArray(null);
|
|
8374
|
+
this.uniforms = uniformLocations(gl, this.program, ["uMVP", "uVolume", "uLUT", "uCamLocal", "uDensity"]);
|
|
8375
|
+
}
|
|
8376
|
+
bounds3() {
|
|
8377
|
+
return this.b3;
|
|
8378
|
+
}
|
|
8379
|
+
colorInfo() {
|
|
8380
|
+
return { colormap: this.cmapName, domain: this.vDomain, label: this.name };
|
|
8381
|
+
}
|
|
8382
|
+
/** Set the camera position in world space; converted to the volume's [0,1] local space. */
|
|
8383
|
+
setCamera(worldEye) {
|
|
8384
|
+
const { x, y, z } = this.ext;
|
|
8385
|
+
this.camLocal = [
|
|
8386
|
+
(worldEye[0] - x[0]) / (x[1] - x[0] || 1),
|
|
8387
|
+
(worldEye[1] - y[0]) / (y[1] - y[0] || 1),
|
|
8388
|
+
(worldEye[2] - z[0]) / (z[1] - z[0] || 1)
|
|
8389
|
+
];
|
|
8390
|
+
}
|
|
8391
|
+
draw(gl, mvp) {
|
|
8392
|
+
gl.useProgram(this.program);
|
|
8393
|
+
gl.uniformMatrix4fv(this.uniforms.uMVP, false, mvp);
|
|
8394
|
+
gl.uniform3f(this.uniforms.uCamLocal, this.camLocal[0], this.camLocal[1], this.camLocal[2]);
|
|
8395
|
+
gl.uniform1f(this.uniforms.uDensity, this.density);
|
|
8396
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
8397
|
+
gl.bindTexture(gl.TEXTURE_3D, this.tex);
|
|
8398
|
+
gl.uniform1i(this.uniforms.uVolume, 0);
|
|
8399
|
+
gl.activeTexture(gl.TEXTURE1);
|
|
8400
|
+
gl.bindTexture(gl.TEXTURE_2D, this.lut);
|
|
8401
|
+
gl.uniform1i(this.uniforms.uLUT, 1);
|
|
8402
|
+
gl.enable(gl.BLEND);
|
|
8403
|
+
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
|
8404
|
+
gl.depthMask(false);
|
|
8405
|
+
gl.enable(gl.CULL_FACE);
|
|
8406
|
+
gl.cullFace(gl.FRONT);
|
|
8407
|
+
gl.bindVertexArray(this.vao);
|
|
8408
|
+
gl.drawArrays(gl.TRIANGLES, 0, 36);
|
|
5307
8409
|
gl.bindVertexArray(null);
|
|
8410
|
+
gl.disable(gl.CULL_FACE);
|
|
8411
|
+
gl.depthMask(true);
|
|
8412
|
+
gl.disable(gl.BLEND);
|
|
8413
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
5308
8414
|
}
|
|
5309
8415
|
dispose() {
|
|
5310
8416
|
this.gl.deleteVertexArray(this.vao);
|
|
5311
8417
|
this.gl.deleteBuffer(this.buffer);
|
|
8418
|
+
this.gl.deleteTexture(this.tex);
|
|
8419
|
+
this.gl.deleteTexture(this.lut);
|
|
5312
8420
|
}
|
|
5313
8421
|
};
|
|
5314
8422
|
|
|
@@ -5412,11 +8520,24 @@ var Plot3D = class {
|
|
|
5412
8520
|
this.frameRequested = false;
|
|
5413
8521
|
this.tickCount = 0;
|
|
5414
8522
|
this.labels = [];
|
|
8523
|
+
/** Tick positions in cube space [-1,1], per axis, for the grid planes. */
|
|
8524
|
+
this.tickCube = { x: [], y: [], z: [] };
|
|
8525
|
+
this.gridVao = null;
|
|
8526
|
+
this.gridBuf = null;
|
|
8527
|
+
/** Normalize params (world→cube: `cube = s*world + t`), for the volume camera. */
|
|
8528
|
+
this.norm = { sx: 1, sy: 1, sz: 1, tx: 0, ty: 0, tz: 0 };
|
|
8529
|
+
/** Auto-orbit speed (rad/frame); 0 = off. Paused while the pointer is down. */
|
|
8530
|
+
this.autoRotateSpeed = 0;
|
|
8531
|
+
this.rotating = false;
|
|
8532
|
+
this.interacting = false;
|
|
5415
8533
|
// Lighting.
|
|
5416
8534
|
this.lightAz = 0.9;
|
|
5417
8535
|
this.lightEl = 0.9;
|
|
5418
8536
|
this.ambient = 0.35;
|
|
5419
8537
|
this.controlsEl = null;
|
|
8538
|
+
this.resetBtn = null;
|
|
8539
|
+
/** Screen position (device px) of the picked point, for the highlight ring. */
|
|
8540
|
+
this.hoverHit = null;
|
|
5420
8541
|
this.container = container;
|
|
5421
8542
|
if (getComputedStyle(container).position === "static") container.style.position = "relative";
|
|
5422
8543
|
this.canvas = document.createElement("canvas");
|
|
@@ -5430,7 +8551,86 @@ var Plot3D = class {
|
|
|
5430
8551
|
this.azimuth = options.azimuth ?? 0.7;
|
|
5431
8552
|
this.elevation = options.elevation ?? 0.5;
|
|
5432
8553
|
this.distance = options.distance ?? 3.6;
|
|
8554
|
+
this.initialAz = this.azimuth;
|
|
8555
|
+
this.initialEl = this.elevation;
|
|
8556
|
+
this.initialDist = this.distance;
|
|
5433
8557
|
this.axisLabels = options.axisLabels ?? {};
|
|
8558
|
+
this.title = options.title;
|
|
8559
|
+
this.showLegend = options.legend ?? false;
|
|
8560
|
+
this.colorbarOpt = options.colorbar ?? true;
|
|
8561
|
+
this.hoverEnabled = options.hover !== false;
|
|
8562
|
+
this.gridPlanes = options.gridPlanes !== false;
|
|
8563
|
+
this.legendDiv = document.createElement("div");
|
|
8564
|
+
Object.assign(this.legendDiv.style, {
|
|
8565
|
+
position: "absolute",
|
|
8566
|
+
top: "8px",
|
|
8567
|
+
right: "8px",
|
|
8568
|
+
zIndex: "5",
|
|
8569
|
+
display: "none",
|
|
8570
|
+
pointerEvents: "none",
|
|
8571
|
+
padding: "6px 8px",
|
|
8572
|
+
borderRadius: "6px",
|
|
8573
|
+
font: "12px system-ui, sans-serif",
|
|
8574
|
+
lineHeight: "1.5",
|
|
8575
|
+
background: "rgba(15,23,42,0.8)",
|
|
8576
|
+
color: "#e2e8f0",
|
|
8577
|
+
border: "1px solid rgba(148,163,184,0.25)"
|
|
8578
|
+
});
|
|
8579
|
+
container.appendChild(this.legendDiv);
|
|
8580
|
+
this.colorbarDiv = document.createElement("div");
|
|
8581
|
+
Object.assign(this.colorbarDiv.style, {
|
|
8582
|
+
position: "absolute",
|
|
8583
|
+
bottom: "12px",
|
|
8584
|
+
right: "10px",
|
|
8585
|
+
zIndex: "5",
|
|
8586
|
+
display: "none",
|
|
8587
|
+
pointerEvents: "none",
|
|
8588
|
+
font: "10px system-ui, sans-serif",
|
|
8589
|
+
color: "#cbd5e1",
|
|
8590
|
+
textAlign: "center"
|
|
8591
|
+
});
|
|
8592
|
+
container.appendChild(this.colorbarDiv);
|
|
8593
|
+
this.tooltip = document.createElement("div");
|
|
8594
|
+
Object.assign(this.tooltip.style, {
|
|
8595
|
+
position: "absolute",
|
|
8596
|
+
display: "none",
|
|
8597
|
+
zIndex: "6",
|
|
8598
|
+
pointerEvents: "none",
|
|
8599
|
+
padding: "5px 7px",
|
|
8600
|
+
borderRadius: "6px",
|
|
8601
|
+
font: "12px system-ui, sans-serif",
|
|
8602
|
+
whiteSpace: "nowrap",
|
|
8603
|
+
background: "rgba(15,23,42,0.92)",
|
|
8604
|
+
color: "#e2e8f0",
|
|
8605
|
+
border: "1px solid rgba(148,163,184,0.3)",
|
|
8606
|
+
boxShadow: "0 4px 12px rgba(0,0,0,0.3)"
|
|
8607
|
+
});
|
|
8608
|
+
container.appendChild(this.tooltip);
|
|
8609
|
+
if (options.resetButton !== false) {
|
|
8610
|
+
const btn = document.createElement("button");
|
|
8611
|
+
btn.type = "button";
|
|
8612
|
+
btn.title = "Reset view";
|
|
8613
|
+
btn.textContent = "\u2302";
|
|
8614
|
+
Object.assign(btn.style, {
|
|
8615
|
+
position: "absolute",
|
|
8616
|
+
bottom: "8px",
|
|
8617
|
+
left: "8px",
|
|
8618
|
+
zIndex: "6",
|
|
8619
|
+
width: "26px",
|
|
8620
|
+
height: "26px",
|
|
8621
|
+
padding: "0",
|
|
8622
|
+
cursor: "pointer",
|
|
8623
|
+
borderRadius: "6px",
|
|
8624
|
+
font: "15px system-ui, sans-serif",
|
|
8625
|
+
lineHeight: "24px",
|
|
8626
|
+
background: "rgba(15,23,42,0.8)",
|
|
8627
|
+
color: "#cbd5e1",
|
|
8628
|
+
border: "1px solid rgba(148,163,184,0.25)"
|
|
8629
|
+
});
|
|
8630
|
+
btn.addEventListener("click", () => this.resetView());
|
|
8631
|
+
container.appendChild(btn);
|
|
8632
|
+
this.resetBtn = btn;
|
|
8633
|
+
}
|
|
5434
8634
|
this.lineProgram = createProgram(this.gl, LINE_VERT, LINE_FRAG);
|
|
5435
8635
|
this.lineUniforms = uniformLocations(this.gl, this.lineProgram, ["uVP", "uColor"]);
|
|
5436
8636
|
this.boxVao = this.gl.createVertexArray();
|
|
@@ -5439,11 +8639,15 @@ var Plot3D = class {
|
|
|
5439
8639
|
this.tickVao = this.gl.createVertexArray();
|
|
5440
8640
|
this.tickBuf = this.gl.createBuffer();
|
|
5441
8641
|
this.bindLineVao(this.tickVao, this.tickBuf, new Float32Array(0));
|
|
8642
|
+
this.gridVao = this.gl.createVertexArray();
|
|
8643
|
+
this.gridBuf = this.gl.createBuffer();
|
|
8644
|
+
this.bindLineVao(this.gridVao, this.gridBuf, new Float32Array(0));
|
|
5442
8645
|
if (options.lightControls) this.buildControls();
|
|
5443
8646
|
this.resizeObserver = new ResizeObserver(() => this.resize());
|
|
5444
8647
|
this.resizeObserver.observe(container);
|
|
5445
8648
|
this.attachControls();
|
|
5446
8649
|
this.resize();
|
|
8650
|
+
if (options.autoRotate) this.setAutoRotate(options.autoRotate);
|
|
5447
8651
|
}
|
|
5448
8652
|
bindLineVao(vao, buf, data) {
|
|
5449
8653
|
const gl = this.gl;
|
|
@@ -5469,6 +8673,56 @@ var Plot3D = class {
|
|
|
5469
8673
|
this.requestRender();
|
|
5470
8674
|
return l;
|
|
5471
8675
|
}
|
|
8676
|
+
/** A 3D polyline / path. */
|
|
8677
|
+
addLine3D(opts) {
|
|
8678
|
+
const l = new Line3DLayer(this.gl, opts);
|
|
8679
|
+
this.layers.push(l);
|
|
8680
|
+
this.recompute();
|
|
8681
|
+
this.requestRender();
|
|
8682
|
+
return l;
|
|
8683
|
+
}
|
|
8684
|
+
/** 3D bars (columns) on an x/z grid, lit and optionally colormapped. */
|
|
8685
|
+
addBar3D(opts) {
|
|
8686
|
+
const l = new Bar3DLayer(this.gl, opts);
|
|
8687
|
+
l.setLight(this.lightDir(), this.ambient);
|
|
8688
|
+
this.layers.push(l);
|
|
8689
|
+
this.recompute();
|
|
8690
|
+
this.requestRender();
|
|
8691
|
+
return l;
|
|
8692
|
+
}
|
|
8693
|
+
/** A 3D vector field (arrows), optionally colored by magnitude. */
|
|
8694
|
+
addQuiver3D(opts) {
|
|
8695
|
+
const l = new Quiver3DLayer(this.gl, opts);
|
|
8696
|
+
this.layers.push(l);
|
|
8697
|
+
this.recompute();
|
|
8698
|
+
this.requestRender();
|
|
8699
|
+
return l;
|
|
8700
|
+
}
|
|
8701
|
+
/** 3D iso-height contour lines of a grid, stacked at their level heights. */
|
|
8702
|
+
addContour3D(opts) {
|
|
8703
|
+
const l = new Contour3DLayer(this.gl, opts);
|
|
8704
|
+
this.layers.push(l);
|
|
8705
|
+
this.recompute();
|
|
8706
|
+
this.requestRender();
|
|
8707
|
+
return l;
|
|
8708
|
+
}
|
|
8709
|
+
/** A marching-cubes isosurface of a 3D scalar volume (lit, solid-colored). */
|
|
8710
|
+
addIsosurface(opts) {
|
|
8711
|
+
const l = new IsosurfaceLayer(this.gl, opts);
|
|
8712
|
+
l.setLight(this.lightDir(), this.ambient);
|
|
8713
|
+
this.layers.push(l);
|
|
8714
|
+
this.recompute();
|
|
8715
|
+
this.requestRender();
|
|
8716
|
+
return l;
|
|
8717
|
+
}
|
|
8718
|
+
/** Direct volume rendering (GPU raymarch) of a 3D scalar field. */
|
|
8719
|
+
addVolume(opts) {
|
|
8720
|
+
const l = new VolumeLayer(this.gl, opts);
|
|
8721
|
+
this.layers.push(l);
|
|
8722
|
+
this.recompute();
|
|
8723
|
+
this.requestRender();
|
|
8724
|
+
return l;
|
|
8725
|
+
}
|
|
5472
8726
|
/** Update the light direction (azimuth/elevation, radians) and ambient (0..1). */
|
|
5473
8727
|
setLight(params) {
|
|
5474
8728
|
if (params.azimuth != null) this.lightAz = params.azimuth;
|
|
@@ -5484,15 +8738,125 @@ var Plot3D = class {
|
|
|
5484
8738
|
const el = this.lightEl, az = this.lightAz;
|
|
5485
8739
|
return [Math.cos(el) * Math.cos(az), Math.sin(el), Math.cos(el) * Math.sin(az)];
|
|
5486
8740
|
}
|
|
8741
|
+
/** Auto-orbit the camera. `true` = default speed, number = radians/frame, `false`/0 stops. */
|
|
8742
|
+
setAutoRotate(speed) {
|
|
8743
|
+
this.autoRotateSpeed = speed === true ? 6e-3 : speed === false ? 0 : speed;
|
|
8744
|
+
if (this.autoRotateSpeed !== 0 && !this.rotating) {
|
|
8745
|
+
this.rotating = true;
|
|
8746
|
+
const loop = () => {
|
|
8747
|
+
if (this.autoRotateSpeed === 0) {
|
|
8748
|
+
this.rotating = false;
|
|
8749
|
+
return;
|
|
8750
|
+
}
|
|
8751
|
+
if (!this.interacting) {
|
|
8752
|
+
this.azimuth += this.autoRotateSpeed;
|
|
8753
|
+
this.render();
|
|
8754
|
+
}
|
|
8755
|
+
requestAnimationFrame(loop);
|
|
8756
|
+
};
|
|
8757
|
+
requestAnimationFrame(loop);
|
|
8758
|
+
}
|
|
8759
|
+
}
|
|
8760
|
+
/** Restore the initial camera orientation and zoom. */
|
|
8761
|
+
resetView() {
|
|
8762
|
+
this.azimuth = this.initialAz;
|
|
8763
|
+
this.elevation = this.initialEl;
|
|
8764
|
+
this.distance = this.initialDist;
|
|
8765
|
+
this.requestRender();
|
|
8766
|
+
}
|
|
8767
|
+
/** Nearest pickable point to the cursor. Screen coords are device px. */
|
|
8768
|
+
pick(clientX, clientY) {
|
|
8769
|
+
const { mvp } = this.viewProj();
|
|
8770
|
+
const w = this.canvas.width, h = this.canvas.height;
|
|
8771
|
+
const rect = this.canvas.getBoundingClientRect();
|
|
8772
|
+
const cx = (clientX - rect.left) * this.dpr;
|
|
8773
|
+
const cy = (clientY - rect.top) * this.dpr;
|
|
8774
|
+
const gate = 14 * this.dpr;
|
|
8775
|
+
let bestD = gate * gate;
|
|
8776
|
+
let best = null;
|
|
8777
|
+
for (const l of this.layers) {
|
|
8778
|
+
const pd = l.pickData?.();
|
|
8779
|
+
if (!pd) continue;
|
|
8780
|
+
const pos = pd.positions;
|
|
8781
|
+
const n = pos.length / 3;
|
|
8782
|
+
for (let i = 0; i < n; i++) {
|
|
8783
|
+
const c = transformPoint(mvp, pos[i * 3], pos[i * 3 + 1], pos[i * 3 + 2]);
|
|
8784
|
+
if (c[3] <= 0) continue;
|
|
8785
|
+
const sx = (c[0] / c[3] * 0.5 + 0.5) * w;
|
|
8786
|
+
const sy = (1 - (c[1] / c[3] * 0.5 + 0.5)) * h;
|
|
8787
|
+
const dx = sx - cx, dy = sy - cy;
|
|
8788
|
+
const d = dx * dx + dy * dy;
|
|
8789
|
+
if (d < bestD) {
|
|
8790
|
+
bestD = d;
|
|
8791
|
+
best = {
|
|
8792
|
+
sx,
|
|
8793
|
+
sy,
|
|
8794
|
+
label: pd.label ? pd.label(i) : `${defaultFormat(pos[i * 3])}, ${defaultFormat(pos[i * 3 + 1])}, ${defaultFormat(pos[i * 3 + 2])}`
|
|
8795
|
+
};
|
|
8796
|
+
}
|
|
8797
|
+
}
|
|
8798
|
+
}
|
|
8799
|
+
return best;
|
|
8800
|
+
}
|
|
8801
|
+
/** Show/hide the hover tooltip + highlight ring for the point under the cursor. */
|
|
8802
|
+
updateHover(clientX, clientY) {
|
|
8803
|
+
if (!this.hoverEnabled) return;
|
|
8804
|
+
const hit = this.pick(clientX, clientY);
|
|
8805
|
+
const had = this.hoverHit !== null;
|
|
8806
|
+
if (!hit) {
|
|
8807
|
+
this.tooltip.style.display = "none";
|
|
8808
|
+
if (had) {
|
|
8809
|
+
this.hoverHit = null;
|
|
8810
|
+
this.requestRender();
|
|
8811
|
+
}
|
|
8812
|
+
return;
|
|
8813
|
+
}
|
|
8814
|
+
this.hoverHit = { sx: hit.sx, sy: hit.sy };
|
|
8815
|
+
this.requestRender();
|
|
8816
|
+
const rect = this.canvas.getBoundingClientRect();
|
|
8817
|
+
this.tooltip.textContent = hit.label;
|
|
8818
|
+
this.tooltip.style.display = "block";
|
|
8819
|
+
const lx = clientX - rect.left, ly = clientY - rect.top;
|
|
8820
|
+
const cw = this.container.clientWidth, ch = this.container.clientHeight;
|
|
8821
|
+
const tw = this.tooltip.offsetWidth, th = this.tooltip.offsetHeight;
|
|
8822
|
+
let left = lx + 14;
|
|
8823
|
+
if (left + tw > cw) left = lx - tw - 14;
|
|
8824
|
+
let top = ly + 14;
|
|
8825
|
+
if (top + th > ch) top = ly - th - 14;
|
|
8826
|
+
this.tooltip.style.left = `${Math.max(0, left)}px`;
|
|
8827
|
+
this.tooltip.style.top = `${Math.max(0, top)}px`;
|
|
8828
|
+
}
|
|
8829
|
+
/** Remove a layer, re-fit, and redraw. */
|
|
8830
|
+
removeLayer(layer) {
|
|
8831
|
+
const i = this.layers.indexOf(layer);
|
|
8832
|
+
if (i >= 0) {
|
|
8833
|
+
this.layers.splice(i, 1);
|
|
8834
|
+
layer.dispose();
|
|
8835
|
+
this.recompute();
|
|
8836
|
+
this.requestRender();
|
|
8837
|
+
}
|
|
8838
|
+
}
|
|
8839
|
+
/** Re-fit the axes to the data and redraw — call after a layer's `setData(...)`. */
|
|
8840
|
+
refresh() {
|
|
8841
|
+
this.recompute();
|
|
8842
|
+
this.requestRender();
|
|
8843
|
+
}
|
|
5487
8844
|
destroy() {
|
|
5488
8845
|
this.resizeObserver.disconnect();
|
|
8846
|
+
this.autoRotateSpeed = 0;
|
|
5489
8847
|
for (const l of this.layers) l.dispose();
|
|
5490
8848
|
this.gl.deleteProgram(this.lineProgram);
|
|
5491
8849
|
this.gl.deleteVertexArray(this.boxVao);
|
|
5492
8850
|
this.gl.deleteVertexArray(this.tickVao);
|
|
8851
|
+
if (this.gridVao) this.gl.deleteVertexArray(this.gridVao);
|
|
5493
8852
|
this.gl.deleteBuffer(this.boxBuf);
|
|
5494
8853
|
this.gl.deleteBuffer(this.tickBuf);
|
|
8854
|
+
if (this.gridBuf) this.gl.deleteBuffer(this.gridBuf);
|
|
5495
8855
|
this.controlsEl?.remove();
|
|
8856
|
+
this.legendDiv.remove();
|
|
8857
|
+
this.colorbarDiv.remove();
|
|
8858
|
+
this.tooltip.remove();
|
|
8859
|
+
this.resetBtn?.remove();
|
|
5496
8860
|
this.container.removeChild(this.canvas);
|
|
5497
8861
|
}
|
|
5498
8862
|
recompute() {
|
|
@@ -5514,6 +8878,7 @@ var Plot3D = class {
|
|
|
5514
8878
|
};
|
|
5515
8879
|
const [sx, tx] = axis(b.x), [sy, ty] = axis(b.y), [sz, tz] = axis(b.z);
|
|
5516
8880
|
this.normalize = scaleTranslate([sx, sy, sz], [tx, ty, tz]);
|
|
8881
|
+
this.norm = { sx, sy, sz, tx, ty, tz };
|
|
5517
8882
|
this.buildAxes();
|
|
5518
8883
|
}
|
|
5519
8884
|
/** Build tick-mark line geometry + tick/title labels from the data bounds. */
|
|
@@ -5523,22 +8888,27 @@ var Plot3D = class {
|
|
|
5523
8888
|
const cube = (r, v) => 2 * (v - r[0]) / (r[1] - r[0] || 1) - 1;
|
|
5524
8889
|
const seg = [];
|
|
5525
8890
|
const labels = [];
|
|
8891
|
+
const tc = { x: [], y: [], z: [] };
|
|
5526
8892
|
const mark = (x0, y0, z0, x1, y1, z1) => seg.push(x0, y0, z0, x1, y1, z1);
|
|
5527
8893
|
for (const t of autoTicks(b.x[0], b.x[1], 5)) {
|
|
5528
8894
|
const cx = cube(b.x, t.value);
|
|
8895
|
+
tc.x.push(cx);
|
|
5529
8896
|
mark(cx, -1, -1, cx, -1, -1.06);
|
|
5530
8897
|
labels.push({ p: [cx, -1, -1.16], text: defaultFormat(t.value) });
|
|
5531
8898
|
}
|
|
5532
8899
|
for (const t of autoTicks(b.y[0], b.y[1], 5)) {
|
|
5533
8900
|
const cy = cube(b.y, t.value);
|
|
8901
|
+
tc.y.push(cy);
|
|
5534
8902
|
mark(-1, cy, -1, -1.06, cy, -1);
|
|
5535
8903
|
labels.push({ p: [-1.16, cy, -1], text: defaultFormat(t.value) });
|
|
5536
8904
|
}
|
|
5537
8905
|
for (const t of autoTicks(b.z[0], b.z[1], 5)) {
|
|
5538
8906
|
const cz = cube(b.z, t.value);
|
|
8907
|
+
tc.z.push(cz);
|
|
5539
8908
|
mark(-1, -1, cz, -1.06, -1, cz);
|
|
5540
8909
|
labels.push({ p: [-1.16, -1, cz], text: defaultFormat(t.value) });
|
|
5541
8910
|
}
|
|
8911
|
+
this.tickCube = tc;
|
|
5542
8912
|
if (this.axisLabels.x) labels.push({ p: [0, -1.25, -1.3], text: this.axisLabels.x, title: true });
|
|
5543
8913
|
if (this.axisLabels.y) labels.push({ p: [-1.35, 0, -1], text: this.axisLabels.y, title: true });
|
|
5544
8914
|
if (this.axisLabels.z) labels.push({ p: [-1.3, -1.25, 0], text: this.axisLabels.z, title: true });
|
|
@@ -5573,7 +8943,31 @@ var Plot3D = class {
|
|
|
5573
8943
|
];
|
|
5574
8944
|
const view = lookAt(eye, [0, 0, 0], [0, 1, 0]);
|
|
5575
8945
|
const vp = multiply(proj, view);
|
|
5576
|
-
return { vp, mvp: multiply(vp, this.normalize) };
|
|
8946
|
+
return { vp, mvp: multiply(vp, this.normalize), eye };
|
|
8947
|
+
}
|
|
8948
|
+
/** Build + draw grid lines on the 3 back walls (cube space) for the current eye. */
|
|
8949
|
+
drawGridPlanes(vp, eye) {
|
|
8950
|
+
const tc = this.tickCube;
|
|
8951
|
+
if (!this.gridVao || tc.x.length + tc.y.length + tc.z.length === 0) return;
|
|
8952
|
+
const sx = eye[0] > 0 ? -1 : 1;
|
|
8953
|
+
const sy = eye[1] > 0 ? -1 : 1;
|
|
8954
|
+
const sz = eye[2] > 0 ? -1 : 1;
|
|
8955
|
+
const seg = [];
|
|
8956
|
+
const L = (x0, y0, z0, x1, y1, z1) => seg.push(x0, y0, z0, x1, y1, z1);
|
|
8957
|
+
for (const cy of tc.y) L(sx, cy, -1, sx, cy, 1);
|
|
8958
|
+
for (const cz of tc.z) L(sx, -1, cz, sx, 1, cz);
|
|
8959
|
+
for (const cx of tc.x) L(cx, sy, -1, cx, sy, 1);
|
|
8960
|
+
for (const cz of tc.z) L(-1, sy, cz, 1, sy, cz);
|
|
8961
|
+
for (const cx of tc.x) L(cx, -1, sz, cx, 1, sz);
|
|
8962
|
+
for (const cy of tc.y) L(-1, cy, sz, 1, cy, sz);
|
|
8963
|
+
const gl = this.gl;
|
|
8964
|
+
gl.bindVertexArray(this.gridVao);
|
|
8965
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.gridBuf);
|
|
8966
|
+
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(seg), gl.DYNAMIC_DRAW);
|
|
8967
|
+
gl.uniformMatrix4fv(this.lineUniforms.uVP, false, vp);
|
|
8968
|
+
gl.uniform4f(this.lineUniforms.uColor, 0.55, 0.6, 0.72, 0.22);
|
|
8969
|
+
gl.drawArrays(gl.LINES, 0, seg.length / 3);
|
|
8970
|
+
gl.bindVertexArray(null);
|
|
5577
8971
|
}
|
|
5578
8972
|
render() {
|
|
5579
8973
|
const gl = this.gl;
|
|
@@ -5583,8 +8977,9 @@ var Plot3D = class {
|
|
|
5583
8977
|
gl.viewport(0, 0, w, h);
|
|
5584
8978
|
gl.clearColor(this.bg[0], this.bg[1], this.bg[2], this.bg[3]);
|
|
5585
8979
|
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
|
|
5586
|
-
const { vp, mvp } = this.viewProj();
|
|
8980
|
+
const { vp, mvp, eye } = this.viewProj();
|
|
5587
8981
|
gl.useProgram(this.lineProgram);
|
|
8982
|
+
if (this.gridPlanes) this.drawGridPlanes(vp, eye);
|
|
5588
8983
|
gl.uniformMatrix4fv(this.lineUniforms.uVP, false, vp);
|
|
5589
8984
|
gl.uniform4f(this.lineUniforms.uColor, 0.6, 0.65, 0.75, 0.4);
|
|
5590
8985
|
gl.bindVertexArray(this.boxVao);
|
|
@@ -5595,10 +8990,119 @@ var Plot3D = class {
|
|
|
5595
8990
|
gl.drawArrays(gl.LINES, 0, this.tickCount);
|
|
5596
8991
|
}
|
|
5597
8992
|
gl.bindVertexArray(null);
|
|
5598
|
-
|
|
8993
|
+
const n = this.norm;
|
|
8994
|
+
const worldEye = [(eye[0] - n.tx) / n.sx, (eye[1] - n.ty) / n.sy, (eye[2] - n.tz) / n.sz];
|
|
8995
|
+
for (const l of this.layers) {
|
|
8996
|
+
if ("setCamera" in l) l.setCamera(worldEye);
|
|
8997
|
+
l.draw(gl, mvp);
|
|
8998
|
+
}
|
|
5599
8999
|
this.displayCtx.clearRect(0, 0, w, h);
|
|
5600
9000
|
this.displayCtx.drawImage(this.sharedCanvas, 0, 0);
|
|
5601
9001
|
this.drawLabels(vp, w, h);
|
|
9002
|
+
if (this.title) {
|
|
9003
|
+
const ctx = this.displayCtx;
|
|
9004
|
+
ctx.save();
|
|
9005
|
+
ctx.font = `600 ${15 * this.dpr}px system-ui, sans-serif`;
|
|
9006
|
+
ctx.fillStyle = "#e2e8f0";
|
|
9007
|
+
ctx.textAlign = "center";
|
|
9008
|
+
ctx.textBaseline = "top";
|
|
9009
|
+
ctx.fillText(this.title, w / 2, 10 * this.dpr);
|
|
9010
|
+
ctx.restore();
|
|
9011
|
+
}
|
|
9012
|
+
if (this.hoverHit) {
|
|
9013
|
+
const ctx = this.displayCtx;
|
|
9014
|
+
ctx.save();
|
|
9015
|
+
ctx.strokeStyle = "#e2e8f0";
|
|
9016
|
+
ctx.lineWidth = 2 * this.dpr;
|
|
9017
|
+
ctx.beginPath();
|
|
9018
|
+
ctx.arc(this.hoverHit.sx, this.hoverHit.sy, 7 * this.dpr, 0, Math.PI * 2);
|
|
9019
|
+
ctx.stroke();
|
|
9020
|
+
ctx.restore();
|
|
9021
|
+
}
|
|
9022
|
+
this.updateLegend();
|
|
9023
|
+
this.updateColorbar();
|
|
9024
|
+
}
|
|
9025
|
+
/** DOM legend of named, solid-colored layers. */
|
|
9026
|
+
updateLegend() {
|
|
9027
|
+
const div = this.legendDiv;
|
|
9028
|
+
const entries = this.showLegend ? this.layers.filter((l) => typeof l.name === "string" && !!l.name && typeof l.colorCss === "string") : [];
|
|
9029
|
+
if (entries.length === 0) {
|
|
9030
|
+
div.style.display = "none";
|
|
9031
|
+
return;
|
|
9032
|
+
}
|
|
9033
|
+
div.replaceChildren();
|
|
9034
|
+
for (const e of entries) {
|
|
9035
|
+
const row = document.createElement("div");
|
|
9036
|
+
Object.assign(row.style, { display: "flex", alignItems: "center", gap: "6px" });
|
|
9037
|
+
const sw = document.createElement("span");
|
|
9038
|
+
Object.assign(sw.style, { width: "10px", height: "10px", borderRadius: "2px", background: e.colorCss, flex: "0 0 auto" });
|
|
9039
|
+
const label = document.createElement("span");
|
|
9040
|
+
label.textContent = e.name;
|
|
9041
|
+
row.appendChild(sw);
|
|
9042
|
+
row.appendChild(label);
|
|
9043
|
+
div.appendChild(row);
|
|
9044
|
+
}
|
|
9045
|
+
div.style.display = "block";
|
|
9046
|
+
}
|
|
9047
|
+
/** DOM colorbar for the first colormapped layer (gradient + value range). */
|
|
9048
|
+
updateColorbar() {
|
|
9049
|
+
const div = this.colorbarDiv;
|
|
9050
|
+
if (this.colorbarOpt === false) {
|
|
9051
|
+
div.style.display = "none";
|
|
9052
|
+
return;
|
|
9053
|
+
}
|
|
9054
|
+
let info = null;
|
|
9055
|
+
for (const l of this.layers) {
|
|
9056
|
+
const ci = l.colorInfo?.();
|
|
9057
|
+
if (ci) {
|
|
9058
|
+
info = ci;
|
|
9059
|
+
break;
|
|
9060
|
+
}
|
|
9061
|
+
}
|
|
9062
|
+
if (!info) {
|
|
9063
|
+
div.style.display = "none";
|
|
9064
|
+
return;
|
|
9065
|
+
}
|
|
9066
|
+
const cmap = colormap(info.colormap);
|
|
9067
|
+
const stops = [];
|
|
9068
|
+
for (let i = 0; i <= 12; i++) {
|
|
9069
|
+
const t = i / 12;
|
|
9070
|
+
const [r, g, b] = cmap(t);
|
|
9071
|
+
stops.push(`rgb(${Math.round(r * 255)},${Math.round(g * 255)},${Math.round(b * 255)}) ${t * 100}%`);
|
|
9072
|
+
}
|
|
9073
|
+
const label = (typeof this.colorbarOpt === "object" ? this.colorbarOpt.label : void 0) ?? info.label;
|
|
9074
|
+
div.replaceChildren();
|
|
9075
|
+
if (label) {
|
|
9076
|
+
const cap = document.createElement("div");
|
|
9077
|
+
cap.textContent = label;
|
|
9078
|
+
cap.style.marginBottom = "3px";
|
|
9079
|
+
div.appendChild(cap);
|
|
9080
|
+
}
|
|
9081
|
+
const rowWrap = document.createElement("div");
|
|
9082
|
+
Object.assign(rowWrap.style, { display: "flex", alignItems: "stretch", gap: "4px", justifyContent: "flex-end" });
|
|
9083
|
+
const bar = document.createElement("div");
|
|
9084
|
+
Object.assign(bar.style, {
|
|
9085
|
+
width: "10px",
|
|
9086
|
+
height: "90px",
|
|
9087
|
+
borderRadius: "2px",
|
|
9088
|
+
background: `linear-gradient(to top, ${stops.join(",")})`,
|
|
9089
|
+
border: "1px solid rgba(148,163,184,0.3)"
|
|
9090
|
+
});
|
|
9091
|
+
const ticks = document.createElement("div");
|
|
9092
|
+
Object.assign(ticks.style, { display: "flex", flexDirection: "column", justifyContent: "space-between" });
|
|
9093
|
+
const hi = document.createElement("span");
|
|
9094
|
+
hi.textContent = defaultFormat(info.domain[1]);
|
|
9095
|
+
const mid = document.createElement("span");
|
|
9096
|
+
mid.textContent = defaultFormat((info.domain[0] + info.domain[1]) / 2);
|
|
9097
|
+
const lo = document.createElement("span");
|
|
9098
|
+
lo.textContent = defaultFormat(info.domain[0]);
|
|
9099
|
+
ticks.appendChild(hi);
|
|
9100
|
+
ticks.appendChild(mid);
|
|
9101
|
+
ticks.appendChild(lo);
|
|
9102
|
+
rowWrap.appendChild(ticks);
|
|
9103
|
+
rowWrap.appendChild(bar);
|
|
9104
|
+
div.appendChild(rowWrap);
|
|
9105
|
+
div.style.display = "block";
|
|
5602
9106
|
}
|
|
5603
9107
|
drawLabels(vp, w, h) {
|
|
5604
9108
|
if (this.labels.length === 0) return;
|
|
@@ -5625,21 +9129,35 @@ var Plot3D = class {
|
|
|
5625
9129
|
let dragging = false, lastX = 0, lastY = 0;
|
|
5626
9130
|
el.addEventListener("pointerdown", (e) => {
|
|
5627
9131
|
dragging = true;
|
|
9132
|
+
this.interacting = true;
|
|
5628
9133
|
lastX = e.clientX;
|
|
5629
9134
|
lastY = e.clientY;
|
|
5630
9135
|
el.setPointerCapture(e.pointerId);
|
|
5631
9136
|
el.style.cursor = "grabbing";
|
|
9137
|
+
this.tooltip.style.display = "none";
|
|
9138
|
+
this.hoverHit = null;
|
|
5632
9139
|
});
|
|
5633
9140
|
el.addEventListener("pointermove", (e) => {
|
|
5634
|
-
if (!dragging)
|
|
9141
|
+
if (!dragging) {
|
|
9142
|
+
this.updateHover(e.clientX, e.clientY);
|
|
9143
|
+
return;
|
|
9144
|
+
}
|
|
5635
9145
|
this.azimuth -= (e.clientX - lastX) * 0.01;
|
|
5636
9146
|
this.elevation += (e.clientY - lastY) * 0.01;
|
|
5637
9147
|
lastX = e.clientX;
|
|
5638
9148
|
lastY = e.clientY;
|
|
5639
9149
|
this.requestRender();
|
|
5640
9150
|
});
|
|
9151
|
+
el.addEventListener("pointerleave", () => {
|
|
9152
|
+
this.tooltip.style.display = "none";
|
|
9153
|
+
if (this.hoverHit) {
|
|
9154
|
+
this.hoverHit = null;
|
|
9155
|
+
this.requestRender();
|
|
9156
|
+
}
|
|
9157
|
+
});
|
|
5641
9158
|
const end = (e) => {
|
|
5642
9159
|
dragging = false;
|
|
9160
|
+
this.interacting = false;
|
|
5643
9161
|
if (el.hasPointerCapture(e.pointerId)) el.releasePointerCapture(e.pointerId);
|
|
5644
9162
|
el.style.cursor = "grab";
|
|
5645
9163
|
};
|
|
@@ -5697,20 +9215,30 @@ var Plot3D = class {
|
|
|
5697
9215
|
export {
|
|
5698
9216
|
AreaLayer,
|
|
5699
9217
|
Axis,
|
|
9218
|
+
Bar3DLayer,
|
|
5700
9219
|
BarLayer,
|
|
5701
9220
|
BoxLayer,
|
|
5702
9221
|
CandlestickLayer,
|
|
9222
|
+
CategoricalScale,
|
|
9223
|
+
Contour3DLayer,
|
|
5703
9224
|
ContourLayer,
|
|
5704
9225
|
ErrorBarLayer,
|
|
9226
|
+
GraphLayer,
|
|
5705
9227
|
HeatmapLayer,
|
|
5706
9228
|
HexbinLayer,
|
|
9229
|
+
ImageLayer,
|
|
9230
|
+
IsosurfaceLayer,
|
|
9231
|
+
Line3DLayer,
|
|
5707
9232
|
LineLayer,
|
|
5708
9233
|
LinearScale,
|
|
5709
9234
|
LogScale,
|
|
9235
|
+
PatchesLayer,
|
|
9236
|
+
PieLayer,
|
|
5710
9237
|
Plot,
|
|
5711
9238
|
Plot3D,
|
|
5712
9239
|
PointCloudLayer,
|
|
5713
9240
|
PolarPlot,
|
|
9241
|
+
Quiver3DLayer,
|
|
5714
9242
|
QuiverLayer,
|
|
5715
9243
|
ScatterLayer,
|
|
5716
9244
|
StemLayer,
|
|
@@ -5718,20 +9246,26 @@ export {
|
|
|
5718
9246
|
TRANSFORM_GLSL,
|
|
5719
9247
|
TRANSFORM_UNIFORMS,
|
|
5720
9248
|
TimeScale,
|
|
9249
|
+
VolumeLayer,
|
|
5721
9250
|
autoTicks,
|
|
5722
9251
|
boxStats,
|
|
5723
9252
|
colormap,
|
|
9253
|
+
colormapLUT,
|
|
5724
9254
|
createProgram,
|
|
5725
9255
|
createToolbar,
|
|
5726
9256
|
darkTheme,
|
|
5727
9257
|
defaultFormat,
|
|
9258
|
+
earcut,
|
|
5728
9259
|
fft,
|
|
9260
|
+
forceLayout,
|
|
5729
9261
|
histogram,
|
|
5730
9262
|
kde,
|
|
5731
9263
|
lightTheme,
|
|
5732
9264
|
makeScale,
|
|
9265
|
+
marchingCubes,
|
|
5733
9266
|
parseColor,
|
|
5734
9267
|
quantileSorted,
|
|
9268
|
+
resolveAxisStyle,
|
|
5735
9269
|
resolveTicks,
|
|
5736
9270
|
setTransformUniforms,
|
|
5737
9271
|
spectrogram,
|