@almadar/ui 5.148.0 → 5.150.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{GameAudioProvider-CPGwD49P.d.cts → GameAudioProvider-B48iXwz3.d.ts} +2 -50
- package/dist/{GameAudioProvider-CPGwD49P.d.ts → GameAudioProvider-CQAdPreB.d.cts} +2 -50
- package/dist/avl/index.cjs +2741 -158
- package/dist/avl/index.js +2741 -158
- package/dist/{avl-schema-parser-DVmrgdwc.d.cts → avl-schema-parser-CVzkNzg7.d.cts} +18 -9
- package/dist/{avl-schema-parser-BRJ77Yze.d.ts → avl-schema-parser-Cx_4SVg9.d.ts} +18 -9
- package/dist/{cn-BAn68sNO.d.cts → cn-BnAJZcNb.d.cts} +2 -8
- package/dist/{cn-CWxxLkri.d.ts → cn-DJTUjk1M.d.ts} +2 -8
- package/dist/components/index.cjs +2764 -158
- package/dist/components/index.d.cts +1077 -107
- package/dist/components/index.d.ts +1077 -107
- package/dist/components/index.js +2760 -160
- package/dist/hooks/index.cjs +1 -1
- package/dist/hooks/index.js +1 -1
- package/dist/lib/drawable/three/index.cjs +57 -30
- package/dist/lib/drawable/three/index.d.cts +4 -3
- package/dist/lib/drawable/three/index.d.ts +4 -3
- package/dist/lib/drawable/three/index.js +57 -30
- package/dist/lib/index.d.cts +3 -2
- package/dist/lib/index.d.ts +3 -2
- package/dist/marketing/index.cjs +1 -1
- package/dist/marketing/index.js +1 -1
- package/dist/{paintDispatch-Cb_hQj4Y.d.ts → paintDispatch-BjZjUbcb.d.cts} +63 -69
- package/dist/{paintDispatch-Cb_hQj4Y.d.cts → paintDispatch-CxdLjHQq.d.ts} +63 -69
- package/dist/providers/index.cjs +2734 -152
- package/dist/providers/index.d.cts +2 -1
- package/dist/providers/index.d.ts +2 -1
- package/dist/providers/index.js +2734 -152
- package/dist/runtime/index.cjs +2741 -158
- package/dist/runtime/index.d.cts +2 -2
- package/dist/runtime/index.d.ts +2 -2
- package/dist/runtime/index.js +2741 -158
- package/dist/types-B3nHgnMG.d.cts +51 -0
- package/dist/types-B3nHgnMG.d.ts +51 -0
- package/package.json +3 -3
|
@@ -1156,7 +1156,7 @@ function useEventBus() {
|
|
|
1156
1156
|
return {
|
|
1157
1157
|
...baseBus,
|
|
1158
1158
|
emit: (type, payload, source) => {
|
|
1159
|
-
if (typeof type === "string" && type.startsWith("UI:") && !type.slice(3).includes(".")) {
|
|
1159
|
+
if (typeof type === "string" && type.startsWith("UI:") && !type.slice(3).includes(".") && !source?.trait) {
|
|
1160
1160
|
scopeLog.warn("emit:bare-key-no-scope", { type });
|
|
1161
1161
|
}
|
|
1162
1162
|
baseBus.emit(type, payload, source);
|
|
@@ -8482,6 +8482,14 @@ function shapeBounds(shape) {
|
|
|
8482
8482
|
w: shape.radius * 2 + 8,
|
|
8483
8483
|
h: shape.radius * 2 + 8
|
|
8484
8484
|
};
|
|
8485
|
+
case "ellipse":
|
|
8486
|
+
if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) return null;
|
|
8487
|
+
return {
|
|
8488
|
+
x: shape.x - shape.width / 2 - 4,
|
|
8489
|
+
y: shape.y - shape.height / 2 - 4,
|
|
8490
|
+
w: shape.width + 8,
|
|
8491
|
+
h: shape.height + 8
|
|
8492
|
+
};
|
|
8485
8493
|
case "rect":
|
|
8486
8494
|
if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) return null;
|
|
8487
8495
|
return { x: shape.x - 4, y: shape.y - 4, w: shape.width + 8, h: shape.height + 8 };
|
|
@@ -8515,13 +8523,14 @@ function drawArrowHead(ctx, x1, y1, x2, y2, size) {
|
|
|
8515
8523
|
ctx.closePath();
|
|
8516
8524
|
ctx.fill();
|
|
8517
8525
|
}
|
|
8518
|
-
function drawShape(ctx, shape, width, height) {
|
|
8526
|
+
function drawShape(ctx, shape, width, height, allShapes) {
|
|
8519
8527
|
ctx.save();
|
|
8520
8528
|
const opacity = shape.opacity ?? 1;
|
|
8521
8529
|
ctx.globalAlpha = opacity;
|
|
8522
8530
|
const stroke = resolveColor2(shape.color, ctx, "#333333");
|
|
8523
8531
|
const fill = shape.fill ? resolveColor2(shape.fill, ctx, "#cccccc") : void 0;
|
|
8524
8532
|
ctx.lineWidth = shape.lineWidth ?? 2;
|
|
8533
|
+
if (shape.dash) ctx.setLineDash([...DASH_PATTERNS[shape.dash]]);
|
|
8525
8534
|
switch (shape.type) {
|
|
8526
8535
|
case "grid": {
|
|
8527
8536
|
const step = shape.step ?? 40;
|
|
@@ -8587,6 +8596,20 @@ function drawShape(ctx, shape, width, height) {
|
|
|
8587
8596
|
ctx.stroke();
|
|
8588
8597
|
break;
|
|
8589
8598
|
}
|
|
8599
|
+
case "ellipse": {
|
|
8600
|
+
if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) break;
|
|
8601
|
+
const startAngle = (shape.startAngle ?? 0) * Math.PI / 180;
|
|
8602
|
+
const endAngle = (shape.endAngle ?? 360) * Math.PI / 180;
|
|
8603
|
+
ctx.beginPath();
|
|
8604
|
+
ctx.ellipse(shape.x, shape.y, shape.width / 2, shape.height / 2, 0, startAngle, endAngle);
|
|
8605
|
+
if (fill) {
|
|
8606
|
+
ctx.fillStyle = fill;
|
|
8607
|
+
ctx.fill();
|
|
8608
|
+
}
|
|
8609
|
+
ctx.strokeStyle = stroke;
|
|
8610
|
+
ctx.stroke();
|
|
8611
|
+
break;
|
|
8612
|
+
}
|
|
8590
8613
|
case "rect": {
|
|
8591
8614
|
if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) break;
|
|
8592
8615
|
if (fill) {
|
|
@@ -8633,21 +8656,153 @@ function drawShape(ctx, shape, width, height) {
|
|
|
8633
8656
|
ctx.fillText(shape.text, shape.x, shape.y);
|
|
8634
8657
|
break;
|
|
8635
8658
|
}
|
|
8659
|
+
case "venn-region": {
|
|
8660
|
+
const resolveCircles = (ids) => (ids ?? []).flatMap((id) => {
|
|
8661
|
+
const c = allShapes.find((s) => s.type === "circle" && s.id === id);
|
|
8662
|
+
return c && c.x != null && c.y != null && c.radius != null ? [{ x: c.x, y: c.y, radius: c.radius }] : [];
|
|
8663
|
+
});
|
|
8664
|
+
const inside = resolveCircles(shape.inside);
|
|
8665
|
+
if (inside.length === 0) break;
|
|
8666
|
+
const outside = resolveCircles(shape.outside);
|
|
8667
|
+
const off = document.createElement("canvas");
|
|
8668
|
+
off.width = ctx.canvas.width;
|
|
8669
|
+
off.height = ctx.canvas.height;
|
|
8670
|
+
const octx = off.getContext("2d");
|
|
8671
|
+
if (!octx) break;
|
|
8672
|
+
octx.setTransform(ctx.getTransform());
|
|
8673
|
+
for (const c of inside) {
|
|
8674
|
+
const p = new Path2D();
|
|
8675
|
+
p.arc(c.x, c.y, c.radius, 0, Math.PI * 2);
|
|
8676
|
+
octx.clip(p);
|
|
8677
|
+
}
|
|
8678
|
+
octx.fillStyle = fill ?? stroke;
|
|
8679
|
+
octx.fillRect(0, 0, width, height);
|
|
8680
|
+
octx.globalCompositeOperation = "destination-out";
|
|
8681
|
+
for (const c of outside) {
|
|
8682
|
+
const p = new Path2D();
|
|
8683
|
+
p.arc(c.x, c.y, c.radius, 0, Math.PI * 2);
|
|
8684
|
+
octx.fill(p);
|
|
8685
|
+
}
|
|
8686
|
+
ctx.save();
|
|
8687
|
+
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
8688
|
+
ctx.drawImage(off, 0, 0);
|
|
8689
|
+
ctx.restore();
|
|
8690
|
+
break;
|
|
8691
|
+
}
|
|
8636
8692
|
}
|
|
8637
8693
|
ctx.restore();
|
|
8638
8694
|
}
|
|
8639
|
-
|
|
8695
|
+
function readoutShapes(readouts, width) {
|
|
8696
|
+
const out = [];
|
|
8697
|
+
const chipH = 18;
|
|
8698
|
+
const gap = 6;
|
|
8699
|
+
let rightEdge = width - 6;
|
|
8700
|
+
let rowY = 6;
|
|
8701
|
+
for (const readout of readouts) {
|
|
8702
|
+
const text = `${readout.label}: ${String(readout.value)}`;
|
|
8703
|
+
const chipW = Math.min(170, Math.max(34, text.length * 6 + 12));
|
|
8704
|
+
let chipX = rightEdge - chipW;
|
|
8705
|
+
if (chipX < 4) {
|
|
8706
|
+
rowY += chipH + 4;
|
|
8707
|
+
rightEdge = width - 6;
|
|
8708
|
+
chipX = rightEdge - chipW;
|
|
8709
|
+
}
|
|
8710
|
+
const color = readout.color ?? "#334155";
|
|
8711
|
+
out.push({ type: "rect", x: chipX, y: rowY, width: chipW, height: chipH, color, fill: color });
|
|
8712
|
+
out.push({
|
|
8713
|
+
type: "text",
|
|
8714
|
+
x: chipX + chipW / 2,
|
|
8715
|
+
y: rowY + chipH / 2,
|
|
8716
|
+
text,
|
|
8717
|
+
color: "#ffffff",
|
|
8718
|
+
fontSize: 10,
|
|
8719
|
+
align: "center"
|
|
8720
|
+
});
|
|
8721
|
+
rightEdge = chipX - gap;
|
|
8722
|
+
}
|
|
8723
|
+
return out;
|
|
8724
|
+
}
|
|
8725
|
+
function traceShapes(panel, k, width, height) {
|
|
8726
|
+
const w = panel.width ?? Math.round(width * 0.32);
|
|
8727
|
+
const h = panel.height ?? Math.round(height * 0.28);
|
|
8728
|
+
const x = panel.x ?? width - w - 8;
|
|
8729
|
+
const y = panel.y ?? height - h - 8 - k * (h + 8);
|
|
8730
|
+
const allSamples = panel.series.flatMap((series) => series.samples);
|
|
8731
|
+
let xLo = Math.min(...allSamples.map((p) => p.x));
|
|
8732
|
+
let xHi = Math.max(...allSamples.map((p) => p.x));
|
|
8733
|
+
let yLo = Math.min(...allSamples.map((p) => p.y));
|
|
8734
|
+
let yHi = Math.max(...allSamples.map((p) => p.y));
|
|
8735
|
+
if (xLo === xHi) {
|
|
8736
|
+
xLo -= 1;
|
|
8737
|
+
xHi += 1;
|
|
8738
|
+
}
|
|
8739
|
+
if (yLo === yHi) {
|
|
8740
|
+
yLo -= 1;
|
|
8741
|
+
yHi += 1;
|
|
8742
|
+
}
|
|
8743
|
+
const backgroundColor = panel.backgroundColor ?? "#ffffff";
|
|
8744
|
+
const frameColor = panel.frameColor ?? "#94a3b8";
|
|
8745
|
+
const out = [];
|
|
8746
|
+
out.push({
|
|
8747
|
+
type: "rect",
|
|
8748
|
+
x,
|
|
8749
|
+
y,
|
|
8750
|
+
width: w,
|
|
8751
|
+
height: h,
|
|
8752
|
+
color: backgroundColor,
|
|
8753
|
+
fill: backgroundColor,
|
|
8754
|
+
opacity: panel.backgroundOpacity ?? 0.85
|
|
8755
|
+
});
|
|
8756
|
+
out.push({ type: "rect", x, y, width: w, height: h, color: frameColor, lineWidth: 1 });
|
|
8757
|
+
panel.series.forEach((series, j) => {
|
|
8758
|
+
const color = series.color ?? TRACE_SERIES_COLORS[j % TRACE_SERIES_COLORS.length];
|
|
8759
|
+
const mapped = series.samples.map((p) => ({
|
|
8760
|
+
x: x + 4 + (p.x - xLo) / (xHi - xLo) * (w - 8),
|
|
8761
|
+
y: y + h - 4 - (p.y - yLo) / (yHi - yLo) * (h - 8)
|
|
8762
|
+
}));
|
|
8763
|
+
for (let i = 1; i < mapped.length; i++) {
|
|
8764
|
+
out.push({
|
|
8765
|
+
type: "line",
|
|
8766
|
+
x1: mapped[i - 1].x,
|
|
8767
|
+
y1: mapped[i - 1].y,
|
|
8768
|
+
x2: mapped[i].x,
|
|
8769
|
+
y2: mapped[i].y,
|
|
8770
|
+
color,
|
|
8771
|
+
lineWidth: 1.5
|
|
8772
|
+
});
|
|
8773
|
+
}
|
|
8774
|
+
if (mapped.length > 0) {
|
|
8775
|
+
const last = mapped[mapped.length - 1];
|
|
8776
|
+
out.push({ type: "circle", x: last.x, y: last.y, radius: 2, color, fill: color });
|
|
8777
|
+
}
|
|
8778
|
+
if (series.label) {
|
|
8779
|
+
out.push({ type: "text", x: x + 6, y: y + 10 + 11 * j, text: series.label, color, fontSize: 9 });
|
|
8780
|
+
}
|
|
8781
|
+
});
|
|
8782
|
+
if (panel.yLabel) {
|
|
8783
|
+
out.push({ type: "text", x: x + w - 6, y: y + 10, text: panel.yLabel, color: "#6b7280", fontSize: 9, align: "right" });
|
|
8784
|
+
}
|
|
8785
|
+
if (panel.xLabel) {
|
|
8786
|
+
out.push({ type: "text", x: x + w - 6, y: y + h - 6, text: panel.xLabel, color: "#6b7280", fontSize: 9, align: "right" });
|
|
8787
|
+
}
|
|
8788
|
+
return out;
|
|
8789
|
+
}
|
|
8790
|
+
var DASH_PATTERNS, TRACE_SERIES_COLORS; exports.LearningCanvas = void 0;
|
|
8640
8791
|
var init_LearningCanvas = __esm({
|
|
8641
8792
|
"components/learning/atoms/LearningCanvas.tsx"() {
|
|
8642
8793
|
"use client";
|
|
8643
8794
|
init_cn();
|
|
8644
8795
|
init_useEventBus();
|
|
8796
|
+
DASH_PATTERNS = { dashed: [6, 4], dotted: [2, 3] };
|
|
8797
|
+
TRACE_SERIES_COLORS = ["#2563eb", "#dc2626", "#16a34a", "#f59e0b"];
|
|
8645
8798
|
exports.LearningCanvas = ({
|
|
8646
8799
|
className,
|
|
8647
8800
|
width = 600,
|
|
8648
8801
|
height = 400,
|
|
8649
8802
|
backgroundColor,
|
|
8650
8803
|
shapes = [],
|
|
8804
|
+
readouts,
|
|
8805
|
+
traces,
|
|
8651
8806
|
interactive = false,
|
|
8652
8807
|
animate = false,
|
|
8653
8808
|
onShapeClick,
|
|
@@ -8673,6 +8828,12 @@ var init_LearningCanvas = __esm({
|
|
|
8673
8828
|
}
|
|
8674
8829
|
return -1;
|
|
8675
8830
|
}, [shapes]);
|
|
8831
|
+
const derivedShapes = React77.useMemo(() => {
|
|
8832
|
+
if (!traces?.length && !readouts?.length) return shapes;
|
|
8833
|
+
const traceOut = (traces ?? []).flatMap((panel, k) => traceShapes(panel, k, width, height));
|
|
8834
|
+
const readoutOut = readouts?.length ? readoutShapes(readouts, width) : [];
|
|
8835
|
+
return [...shapes, ...traceOut, ...readoutOut];
|
|
8836
|
+
}, [shapes, traces, readouts, width, height]);
|
|
8676
8837
|
const draw = React77.useCallback(() => {
|
|
8677
8838
|
const canvas = canvasRef.current;
|
|
8678
8839
|
if (!canvas) return;
|
|
@@ -8689,13 +8850,13 @@ var init_LearningCanvas = __esm({
|
|
|
8689
8850
|
ctx.fillStyle = backgroundColor;
|
|
8690
8851
|
ctx.fillRect(0, 0, width, height);
|
|
8691
8852
|
}
|
|
8692
|
-
for (const shape of
|
|
8693
|
-
if (shape.type !== "text") drawShape(ctx, shape, width, height);
|
|
8853
|
+
for (const shape of derivedShapes) {
|
|
8854
|
+
if (shape.type !== "text") drawShape(ctx, shape, width, height, derivedShapes);
|
|
8694
8855
|
}
|
|
8695
|
-
for (const shape of
|
|
8696
|
-
if (shape.type === "text") drawShape(ctx, shape, width, height);
|
|
8856
|
+
for (const shape of derivedShapes) {
|
|
8857
|
+
if (shape.type === "text") drawShape(ctx, shape, width, height, derivedShapes);
|
|
8697
8858
|
}
|
|
8698
|
-
}, [width, height, backgroundColor,
|
|
8859
|
+
}, [width, height, backgroundColor, derivedShapes]);
|
|
8699
8860
|
React77.useEffect(() => {
|
|
8700
8861
|
draw();
|
|
8701
8862
|
}, [draw]);
|
|
@@ -8763,7 +8924,363 @@ var init_LearningCanvas = __esm({
|
|
|
8763
8924
|
};
|
|
8764
8925
|
}
|
|
8765
8926
|
});
|
|
8766
|
-
|
|
8927
|
+
function layoutCircle(nodes, width, height) {
|
|
8928
|
+
const cx = width / 2;
|
|
8929
|
+
const cy = height / 2;
|
|
8930
|
+
const radius = Math.max(10, Math.min(cx, cy) - 40);
|
|
8931
|
+
const positions = /* @__PURE__ */ new Map();
|
|
8932
|
+
const n = nodes.length;
|
|
8933
|
+
nodes.forEach((node, i) => {
|
|
8934
|
+
const angle = 2 * Math.PI * i / Math.max(n, 1) - Math.PI / 2;
|
|
8935
|
+
positions.set(node.id, { x: cx + radius * Math.cos(angle), y: cy + radius * Math.sin(angle) });
|
|
8936
|
+
});
|
|
8937
|
+
return positions;
|
|
8938
|
+
}
|
|
8939
|
+
function layoutTree(nodes, edges, root, width, height) {
|
|
8940
|
+
const nodeIds = nodes.map((n) => n.id);
|
|
8941
|
+
const idSet = new Set(nodeIds);
|
|
8942
|
+
const childrenOf = /* @__PURE__ */ new Map();
|
|
8943
|
+
const hasIncoming = /* @__PURE__ */ new Set();
|
|
8944
|
+
for (const e of edges) {
|
|
8945
|
+
if (!idSet.has(e.from) || !idSet.has(e.to)) continue;
|
|
8946
|
+
const list = childrenOf.get(e.from) ?? [];
|
|
8947
|
+
list.push(e.to);
|
|
8948
|
+
childrenOf.set(e.from, list);
|
|
8949
|
+
hasIncoming.add(e.to);
|
|
8950
|
+
}
|
|
8951
|
+
const depth = /* @__PURE__ */ new Map();
|
|
8952
|
+
const treeChildren = /* @__PURE__ */ new Map();
|
|
8953
|
+
const visited = /* @__PURE__ */ new Set();
|
|
8954
|
+
const bfsFrom = (start) => {
|
|
8955
|
+
if (visited.has(start)) return;
|
|
8956
|
+
visited.add(start);
|
|
8957
|
+
depth.set(start, 0);
|
|
8958
|
+
const queue = [start];
|
|
8959
|
+
while (queue.length > 0) {
|
|
8960
|
+
const u = queue.shift();
|
|
8961
|
+
for (const v of childrenOf.get(u) ?? []) {
|
|
8962
|
+
if (visited.has(v)) continue;
|
|
8963
|
+
visited.add(v);
|
|
8964
|
+
depth.set(v, (depth.get(u) ?? 0) + 1);
|
|
8965
|
+
const list = treeChildren.get(u) ?? [];
|
|
8966
|
+
list.push(v);
|
|
8967
|
+
treeChildren.set(u, list);
|
|
8968
|
+
queue.push(v);
|
|
8969
|
+
}
|
|
8970
|
+
}
|
|
8971
|
+
};
|
|
8972
|
+
const primaryRoot = root && idSet.has(root) ? root : nodeIds.find((id) => !hasIncoming.has(id)) ?? nodeIds[0];
|
|
8973
|
+
const rootsOrder = [];
|
|
8974
|
+
if (primaryRoot !== void 0) {
|
|
8975
|
+
bfsFrom(primaryRoot);
|
|
8976
|
+
rootsOrder.push(primaryRoot);
|
|
8977
|
+
}
|
|
8978
|
+
for (const id of nodeIds) {
|
|
8979
|
+
if (!visited.has(id)) {
|
|
8980
|
+
bfsFrom(id);
|
|
8981
|
+
rootsOrder.push(id);
|
|
8982
|
+
}
|
|
8983
|
+
}
|
|
8984
|
+
let leafCounter = 0;
|
|
8985
|
+
const xSlot = /* @__PURE__ */ new Map();
|
|
8986
|
+
const assignXSlot = (u) => {
|
|
8987
|
+
const children = treeChildren.get(u) ?? [];
|
|
8988
|
+
if (children.length === 0) {
|
|
8989
|
+
const slot = leafCounter++;
|
|
8990
|
+
xSlot.set(u, slot);
|
|
8991
|
+
return slot;
|
|
8992
|
+
}
|
|
8993
|
+
const childSlots = children.map(assignXSlot);
|
|
8994
|
+
const avg = childSlots.reduce((a, b) => a + b, 0) / childSlots.length;
|
|
8995
|
+
xSlot.set(u, avg);
|
|
8996
|
+
return avg;
|
|
8997
|
+
};
|
|
8998
|
+
for (const r of rootsOrder) assignXSlot(r);
|
|
8999
|
+
let maxDepth = 0;
|
|
9000
|
+
for (const d of depth.values()) maxDepth = Math.max(maxDepth, d);
|
|
9001
|
+
const colWidth = width / Math.max(1, leafCounter);
|
|
9002
|
+
const rowHeight = height / (maxDepth + 1);
|
|
9003
|
+
const positions = /* @__PURE__ */ new Map();
|
|
9004
|
+
for (const id of nodeIds) {
|
|
9005
|
+
const slot = xSlot.get(id) ?? 0;
|
|
9006
|
+
const d = depth.get(id) ?? 0;
|
|
9007
|
+
positions.set(id, { x: slot * colWidth + colWidth / 2, y: d * rowHeight + rowHeight / 2 });
|
|
9008
|
+
}
|
|
9009
|
+
return positions;
|
|
9010
|
+
}
|
|
9011
|
+
function layoutLayered(nodes, edges, width, height) {
|
|
9012
|
+
const nodeIds = nodes.map((n) => n.id);
|
|
9013
|
+
const idSet = new Set(nodeIds);
|
|
9014
|
+
const adj = /* @__PURE__ */ new Map();
|
|
9015
|
+
const remainingIndegree = /* @__PURE__ */ new Map();
|
|
9016
|
+
for (const id of nodeIds) remainingIndegree.set(id, 0);
|
|
9017
|
+
for (const e of edges) {
|
|
9018
|
+
if (!idSet.has(e.from) || !idSet.has(e.to)) continue;
|
|
9019
|
+
const list = adj.get(e.from) ?? [];
|
|
9020
|
+
list.push(e.to);
|
|
9021
|
+
adj.set(e.from, list);
|
|
9022
|
+
remainingIndegree.set(e.to, (remainingIndegree.get(e.to) ?? 0) + 1);
|
|
9023
|
+
}
|
|
9024
|
+
const layer = /* @__PURE__ */ new Map();
|
|
9025
|
+
const dequeued = /* @__PURE__ */ new Set();
|
|
9026
|
+
const queue = [];
|
|
9027
|
+
for (const id of nodeIds) {
|
|
9028
|
+
if ((remainingIndegree.get(id) ?? 0) === 0) {
|
|
9029
|
+
layer.set(id, 0);
|
|
9030
|
+
queue.push(id);
|
|
9031
|
+
}
|
|
9032
|
+
}
|
|
9033
|
+
while (queue.length > 0) {
|
|
9034
|
+
const u = queue.shift();
|
|
9035
|
+
dequeued.add(u);
|
|
9036
|
+
for (const v of adj.get(u) ?? []) {
|
|
9037
|
+
const candidate = (layer.get(u) ?? 0) + 1;
|
|
9038
|
+
layer.set(v, Math.max(layer.get(v) ?? 0, candidate));
|
|
9039
|
+
remainingIndegree.set(v, (remainingIndegree.get(v) ?? 0) - 1);
|
|
9040
|
+
if ((remainingIndegree.get(v) ?? 0) === 0 && !dequeued.has(v)) {
|
|
9041
|
+
queue.push(v);
|
|
9042
|
+
}
|
|
9043
|
+
}
|
|
9044
|
+
}
|
|
9045
|
+
let baseMaxLayer = 0;
|
|
9046
|
+
for (const id of nodeIds) {
|
|
9047
|
+
if (dequeued.has(id)) baseMaxLayer = Math.max(baseMaxLayer, layer.get(id) ?? 0);
|
|
9048
|
+
}
|
|
9049
|
+
const cycleLayer = baseMaxLayer + 1;
|
|
9050
|
+
let maxLayer = baseMaxLayer;
|
|
9051
|
+
for (const id of nodeIds) {
|
|
9052
|
+
if (!dequeued.has(id)) {
|
|
9053
|
+
layer.set(id, cycleLayer);
|
|
9054
|
+
maxLayer = cycleLayer;
|
|
9055
|
+
}
|
|
9056
|
+
}
|
|
9057
|
+
const colWidth = width / Math.max(1, maxLayer + 1);
|
|
9058
|
+
const byLayer = /* @__PURE__ */ new Map();
|
|
9059
|
+
for (const id of nodeIds) {
|
|
9060
|
+
const l = layer.get(id) ?? 0;
|
|
9061
|
+
const list = byLayer.get(l) ?? [];
|
|
9062
|
+
list.push(id);
|
|
9063
|
+
byLayer.set(l, list);
|
|
9064
|
+
}
|
|
9065
|
+
const positions = /* @__PURE__ */ new Map();
|
|
9066
|
+
for (const [l, ids] of byLayer) {
|
|
9067
|
+
const rowHeight = height / ids.length;
|
|
9068
|
+
ids.forEach((id, i) => {
|
|
9069
|
+
positions.set(id, { x: l * colWidth + colWidth / 2, y: i * rowHeight + rowHeight / 2 });
|
|
9070
|
+
});
|
|
9071
|
+
}
|
|
9072
|
+
return positions;
|
|
9073
|
+
}
|
|
9074
|
+
function computePositions(nodes, edges, layout, root, width, height) {
|
|
9075
|
+
switch (layout) {
|
|
9076
|
+
case "circle":
|
|
9077
|
+
return layoutCircle(nodes, width, height);
|
|
9078
|
+
case "tree":
|
|
9079
|
+
return layoutTree(nodes, edges, root, width, height);
|
|
9080
|
+
case "layered":
|
|
9081
|
+
return layoutLayered(nodes, edges, width, height);
|
|
9082
|
+
case "manual":
|
|
9083
|
+
default: {
|
|
9084
|
+
const positions = /* @__PURE__ */ new Map();
|
|
9085
|
+
for (const n of nodes) positions.set(n.id, { x: n.x ?? 0, y: n.y ?? 0 });
|
|
9086
|
+
return positions;
|
|
9087
|
+
}
|
|
9088
|
+
}
|
|
9089
|
+
}
|
|
9090
|
+
var NODE_STATE_COLOR, EDGE_STATE_COLOR, DEFAULT_NODE_RADIUS; exports.AlgoGraphCanvas = void 0;
|
|
9091
|
+
var init_AlgoGraphCanvas = __esm({
|
|
9092
|
+
"components/learning/molecules/AlgoGraphCanvas.tsx"() {
|
|
9093
|
+
"use client";
|
|
9094
|
+
init_atoms();
|
|
9095
|
+
init_Stack();
|
|
9096
|
+
init_LearningCanvas();
|
|
9097
|
+
NODE_STATE_COLOR = {
|
|
9098
|
+
unvisited: "#cbd5e1",
|
|
9099
|
+
frontier: "#f59e0b",
|
|
9100
|
+
current: "#ef4444",
|
|
9101
|
+
visited: "#22c55e",
|
|
9102
|
+
goal: "#8b5cf6",
|
|
9103
|
+
path: "#0ea5e9"
|
|
9104
|
+
};
|
|
9105
|
+
EDGE_STATE_COLOR = {
|
|
9106
|
+
default: "#9ca3af",
|
|
9107
|
+
tree: "#16a34a",
|
|
9108
|
+
relaxed: "#f59e0b",
|
|
9109
|
+
candidate: "#38bdf8",
|
|
9110
|
+
path: "#dc2626"
|
|
9111
|
+
};
|
|
9112
|
+
DEFAULT_NODE_RADIUS = 18;
|
|
9113
|
+
exports.AlgoGraphCanvas = ({
|
|
9114
|
+
className,
|
|
9115
|
+
width = 600,
|
|
9116
|
+
height = 400,
|
|
9117
|
+
title,
|
|
9118
|
+
backgroundColor,
|
|
9119
|
+
nodes = [],
|
|
9120
|
+
edges = [],
|
|
9121
|
+
layout = "manual",
|
|
9122
|
+
root,
|
|
9123
|
+
shapes = [],
|
|
9124
|
+
interactive = false,
|
|
9125
|
+
animate = false,
|
|
9126
|
+
onShapeClick,
|
|
9127
|
+
onNodeClick,
|
|
9128
|
+
isLoading,
|
|
9129
|
+
error
|
|
9130
|
+
}) => {
|
|
9131
|
+
const nodeById = React77.useMemo(() => {
|
|
9132
|
+
const m = /* @__PURE__ */ new Map();
|
|
9133
|
+
for (const n of nodes) m.set(n.id, n);
|
|
9134
|
+
return m;
|
|
9135
|
+
}, [nodes]);
|
|
9136
|
+
const nodeIndexById = React77.useMemo(() => {
|
|
9137
|
+
const m = /* @__PURE__ */ new Map();
|
|
9138
|
+
nodes.forEach((n, i) => m.set(n.id, i));
|
|
9139
|
+
return m;
|
|
9140
|
+
}, [nodes]);
|
|
9141
|
+
const derivedShapes = React77.useMemo(() => {
|
|
9142
|
+
const out = [];
|
|
9143
|
+
const positions = computePositions(nodes, edges, layout, root, width, height);
|
|
9144
|
+
const edgeGeoms = [];
|
|
9145
|
+
for (const e of edges) {
|
|
9146
|
+
const a = nodeById.get(e.from);
|
|
9147
|
+
const b = nodeById.get(e.to);
|
|
9148
|
+
const posA = positions.get(e.from);
|
|
9149
|
+
const posB = positions.get(e.to);
|
|
9150
|
+
if (!a || !b || !posA || !posB) continue;
|
|
9151
|
+
const rA = a.radius ?? DEFAULT_NODE_RADIUS;
|
|
9152
|
+
const rB = b.radius ?? DEFAULT_NODE_RADIUS;
|
|
9153
|
+
const dx = posB.x - posA.x;
|
|
9154
|
+
const dy = posB.y - posA.y;
|
|
9155
|
+
const dist = Math.max(1e-6, Math.hypot(dx, dy));
|
|
9156
|
+
const ux = dx / dist;
|
|
9157
|
+
const uy = dy / dist;
|
|
9158
|
+
edgeGeoms.push({
|
|
9159
|
+
directed: e.directed ?? false,
|
|
9160
|
+
x1: posA.x + ux * rA,
|
|
9161
|
+
y1: posA.y + uy * rA,
|
|
9162
|
+
x2: posB.x - ux * rB,
|
|
9163
|
+
y2: posB.y - uy * rB,
|
|
9164
|
+
color: e.color ?? EDGE_STATE_COLOR[e.state ?? "default"],
|
|
9165
|
+
label: e.label ?? (e.weight != null ? String(e.weight) : void 0)
|
|
9166
|
+
});
|
|
9167
|
+
}
|
|
9168
|
+
for (const g of edgeGeoms) {
|
|
9169
|
+
out.push({
|
|
9170
|
+
type: g.directed ? "arrow" : "line",
|
|
9171
|
+
x1: g.x1,
|
|
9172
|
+
y1: g.y1,
|
|
9173
|
+
x2: g.x2,
|
|
9174
|
+
y2: g.y2,
|
|
9175
|
+
color: g.color,
|
|
9176
|
+
lineWidth: 2
|
|
9177
|
+
});
|
|
9178
|
+
}
|
|
9179
|
+
for (const g of edgeGeoms) {
|
|
9180
|
+
if (g.label === void 0) continue;
|
|
9181
|
+
const midX = (g.x1 + g.x2) / 2;
|
|
9182
|
+
const midY = (g.y1 + g.y2) / 2;
|
|
9183
|
+
const dx = g.x2 - g.x1;
|
|
9184
|
+
const dy = g.y2 - g.y1;
|
|
9185
|
+
const dist = Math.max(1e-6, Math.hypot(dx, dy));
|
|
9186
|
+
const perpX = -(dy / dist);
|
|
9187
|
+
const perpY = dx / dist;
|
|
9188
|
+
out.push({
|
|
9189
|
+
type: "text",
|
|
9190
|
+
x: midX + perpX * 10,
|
|
9191
|
+
y: midY + perpY * 10,
|
|
9192
|
+
text: g.label,
|
|
9193
|
+
fontSize: 11,
|
|
9194
|
+
align: "center",
|
|
9195
|
+
color: "#374151"
|
|
9196
|
+
});
|
|
9197
|
+
}
|
|
9198
|
+
const nodeGeoms = [];
|
|
9199
|
+
for (const n of nodes) {
|
|
9200
|
+
const pos = positions.get(n.id);
|
|
9201
|
+
if (!pos) continue;
|
|
9202
|
+
nodeGeoms.push({
|
|
9203
|
+
id: n.id,
|
|
9204
|
+
x: pos.x,
|
|
9205
|
+
y: pos.y,
|
|
9206
|
+
radius: n.radius ?? DEFAULT_NODE_RADIUS,
|
|
9207
|
+
color: n.color ?? NODE_STATE_COLOR[n.state ?? "unvisited"],
|
|
9208
|
+
label: n.label,
|
|
9209
|
+
badge: n.badge
|
|
9210
|
+
});
|
|
9211
|
+
}
|
|
9212
|
+
for (const g of nodeGeoms) {
|
|
9213
|
+
out.push({ type: "circle", id: g.id, x: g.x, y: g.y, radius: g.radius, color: g.color, fill: `${g.color}33` });
|
|
9214
|
+
}
|
|
9215
|
+
const badgeGeoms = [];
|
|
9216
|
+
for (const g of nodeGeoms) {
|
|
9217
|
+
if (!g.badge) continue;
|
|
9218
|
+
const w = Math.min(42, Math.max(18, g.badge.text.length * 6 + 10));
|
|
9219
|
+
badgeGeoms.push({
|
|
9220
|
+
cx: g.x + g.radius * 0.75,
|
|
9221
|
+
cy: g.y - g.radius * 0.75,
|
|
9222
|
+
w,
|
|
9223
|
+
h: 14,
|
|
9224
|
+
// Borderless pill: same color drives both stroke and fill.
|
|
9225
|
+
color: g.badge.color ?? "#1e293b",
|
|
9226
|
+
text: g.badge.text
|
|
9227
|
+
});
|
|
9228
|
+
}
|
|
9229
|
+
for (const b of badgeGeoms) {
|
|
9230
|
+
out.push({ type: "rect", x: b.cx - b.w / 2, y: b.cy - b.h / 2, width: b.w, height: b.h, color: b.color, fill: b.color });
|
|
9231
|
+
}
|
|
9232
|
+
for (const b of badgeGeoms) {
|
|
9233
|
+
out.push({ type: "text", x: b.cx, y: b.cy, text: b.text, fontSize: 9, align: "center", color: "#ffffff" });
|
|
9234
|
+
}
|
|
9235
|
+
for (const g of nodeGeoms) {
|
|
9236
|
+
if (g.label === void 0) continue;
|
|
9237
|
+
out.push({
|
|
9238
|
+
type: "text",
|
|
9239
|
+
x: g.x,
|
|
9240
|
+
y: g.y + g.radius + 14,
|
|
9241
|
+
text: g.label,
|
|
9242
|
+
fontSize: 12,
|
|
9243
|
+
align: "center",
|
|
9244
|
+
color: "#111827"
|
|
9245
|
+
});
|
|
9246
|
+
}
|
|
9247
|
+
out.push(...shapes);
|
|
9248
|
+
return out;
|
|
9249
|
+
}, [nodes, edges, layout, root, width, height, nodeById, shapes]);
|
|
9250
|
+
const handleShapeClick = React77.useCallback(
|
|
9251
|
+
(payload) => {
|
|
9252
|
+
if (payload.type === "circle" && payload.id) {
|
|
9253
|
+
const node = nodeById.get(payload.id);
|
|
9254
|
+
const idx = nodeIndexById.get(payload.id);
|
|
9255
|
+
if (node && idx !== void 0) {
|
|
9256
|
+
onNodeClick?.({ id: node.id, label: node.label, index: idx });
|
|
9257
|
+
}
|
|
9258
|
+
}
|
|
9259
|
+
onShapeClick?.(payload);
|
|
9260
|
+
},
|
|
9261
|
+
[nodeById, nodeIndexById, onNodeClick, onShapeClick]
|
|
9262
|
+
);
|
|
9263
|
+
return /* @__PURE__ */ jsxRuntime.jsx(exports.Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(exports.VStack, { gap: "sm", children: [
|
|
9264
|
+
title ? /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "h4", children: title }) : null,
|
|
9265
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
9266
|
+
exports.LearningCanvas,
|
|
9267
|
+
{
|
|
9268
|
+
width,
|
|
9269
|
+
height,
|
|
9270
|
+
backgroundColor,
|
|
9271
|
+
shapes: derivedShapes,
|
|
9272
|
+
interactive,
|
|
9273
|
+
animate,
|
|
9274
|
+
onShapeClick: onShapeClick || onNodeClick ? handleShapeClick : void 0,
|
|
9275
|
+
isLoading,
|
|
9276
|
+
error
|
|
9277
|
+
}
|
|
9278
|
+
)
|
|
9279
|
+
] }) });
|
|
9280
|
+
};
|
|
9281
|
+
}
|
|
9282
|
+
});
|
|
9283
|
+
var DEFAULT_BAR_COLOR, DEFAULT_CELL_COLOR, DEFAULT_POINTER_COLOR, POINTER_BAND, TOP_PAD, PANEL_FAMILY_ORDER, RANGE_COLOR_DEFAULT, RANGE_FILL_OPACITY, BRACKET_TOP_OFFSET, BRACKET_ROW_H, BRACKET_TICK_H, BRACKET_LABEL_OFFSET, SLOT_EMPTY_FILL, SLOT_EMPTY_STROKE, SLOT_FILLED_STROKE, SLOT_HIGHLIGHT_DEFAULT, SLOT_VALUE_TEXT_COLOR, FRAME_ACTIVE_COLOR, FRAME_RETURNING_COLOR, FRAME_DONE_COLOR, FRAME_LABEL_COLOR, FRAME_DETAIL_COLOR, FRAME_TWO_LINE_MIN_H, BUCKET_INDEX_FILL, BUCKET_INDEX_STROKE, BUCKET_INDEX_TEXT, BUCKET_ENTRY_TEXT, BUCKET_ENTRY_DEFAULT, BUCKET_ENTRY_HIGHLIGHT, BUCKET_ENTRY_PROBING, BUCKET_ENTRY_MIN_W, BUCKET_ENTRY_MAX_W, AXIS_LABEL_COLOR, AXIS_LABEL_FONT_SIZE, CORNER_TEXT_COLOR, CORNER_FONT_SIZE, CORNER_MIN_CELL, CORNER_INSET_X, CORNER_INSET_Y, AUX_PRIMARY_RATIO, AUX_LABEL_BAND, AUX_BASELINE_PAD; exports.AlgorithmCanvas = void 0;
|
|
8767
9284
|
var init_AlgorithmCanvas = __esm({
|
|
8768
9285
|
"components/learning/molecules/AlgorithmCanvas.tsx"() {
|
|
8769
9286
|
"use client";
|
|
@@ -8775,6 +9292,43 @@ var init_AlgorithmCanvas = __esm({
|
|
|
8775
9292
|
DEFAULT_POINTER_COLOR = "#dc2626";
|
|
8776
9293
|
POINTER_BAND = 34;
|
|
8777
9294
|
TOP_PAD = 26;
|
|
9295
|
+
PANEL_FAMILY_ORDER = ["bars", "slots", "cells", "buckets", "frames"];
|
|
9296
|
+
RANGE_COLOR_DEFAULT = "#3b82f6";
|
|
9297
|
+
RANGE_FILL_OPACITY = 0.15;
|
|
9298
|
+
BRACKET_TOP_OFFSET = 16;
|
|
9299
|
+
BRACKET_ROW_H = 14;
|
|
9300
|
+
BRACKET_TICK_H = 6;
|
|
9301
|
+
BRACKET_LABEL_OFFSET = 6;
|
|
9302
|
+
SLOT_EMPTY_FILL = "#f1f5f9";
|
|
9303
|
+
SLOT_EMPTY_STROKE = "#cbd5e1";
|
|
9304
|
+
SLOT_FILLED_STROKE = "#9ca3af";
|
|
9305
|
+
SLOT_HIGHLIGHT_DEFAULT = "#f59e0b";
|
|
9306
|
+
SLOT_VALUE_TEXT_COLOR = "#ffffff";
|
|
9307
|
+
FRAME_ACTIVE_COLOR = "#3b82f6";
|
|
9308
|
+
FRAME_RETURNING_COLOR = "#f59e0b";
|
|
9309
|
+
FRAME_DONE_COLOR = "#94a3b8";
|
|
9310
|
+
FRAME_LABEL_COLOR = "#ffffff";
|
|
9311
|
+
FRAME_DETAIL_COLOR = "#e2e8f0";
|
|
9312
|
+
FRAME_TWO_LINE_MIN_H = 22;
|
|
9313
|
+
BUCKET_INDEX_FILL = "#e2e8f0";
|
|
9314
|
+
BUCKET_INDEX_STROKE = "#9ca3af";
|
|
9315
|
+
BUCKET_INDEX_TEXT = "#374151";
|
|
9316
|
+
BUCKET_ENTRY_TEXT = "#ffffff";
|
|
9317
|
+
BUCKET_ENTRY_DEFAULT = "#3b82f6";
|
|
9318
|
+
BUCKET_ENTRY_HIGHLIGHT = "#f59e0b";
|
|
9319
|
+
BUCKET_ENTRY_PROBING = "#38bdf8";
|
|
9320
|
+
BUCKET_ENTRY_MIN_W = 24;
|
|
9321
|
+
BUCKET_ENTRY_MAX_W = 64;
|
|
9322
|
+
AXIS_LABEL_COLOR = "#6b7280";
|
|
9323
|
+
AXIS_LABEL_FONT_SIZE = 10;
|
|
9324
|
+
CORNER_TEXT_COLOR = "#111827";
|
|
9325
|
+
CORNER_FONT_SIZE = 7;
|
|
9326
|
+
CORNER_MIN_CELL = 28;
|
|
9327
|
+
CORNER_INSET_X = 3;
|
|
9328
|
+
CORNER_INSET_Y = 6;
|
|
9329
|
+
AUX_PRIMARY_RATIO = 0.6;
|
|
9330
|
+
AUX_LABEL_BAND = 18;
|
|
9331
|
+
AUX_BASELINE_PAD = 8;
|
|
8778
9332
|
exports.AlgorithmCanvas = ({
|
|
8779
9333
|
className,
|
|
8780
9334
|
width = 600,
|
|
@@ -8784,6 +9338,14 @@ var init_AlgorithmCanvas = __esm({
|
|
|
8784
9338
|
bars = [],
|
|
8785
9339
|
cells = [],
|
|
8786
9340
|
pointers = [],
|
|
9341
|
+
ranges = [],
|
|
9342
|
+
slots = [],
|
|
9343
|
+
slotOrientation = "horizontal",
|
|
9344
|
+
frames = [],
|
|
9345
|
+
buckets = [],
|
|
9346
|
+
auxBars = [],
|
|
9347
|
+
rowLabels = [],
|
|
9348
|
+
colLabels = [],
|
|
8787
9349
|
shapes = [],
|
|
8788
9350
|
interactive = false,
|
|
8789
9351
|
animate = false,
|
|
@@ -8793,12 +9355,35 @@ var init_AlgorithmCanvas = __esm({
|
|
|
8793
9355
|
}) => {
|
|
8794
9356
|
const derivedShapes = React77.useMemo(() => {
|
|
8795
9357
|
const out = [];
|
|
9358
|
+
const presence = {
|
|
9359
|
+
bars: bars.length > 0,
|
|
9360
|
+
slots: slots.length > 0,
|
|
9361
|
+
cells: cells.length > 0,
|
|
9362
|
+
buckets: buckets.length > 0,
|
|
9363
|
+
frames: frames.length > 0
|
|
9364
|
+
};
|
|
9365
|
+
const panelCount = PANEL_FAMILY_ORDER.filter((f3) => presence[f3]).length;
|
|
9366
|
+
const panelHeight = height / Math.max(1, panelCount);
|
|
9367
|
+
const panelY = { bars: 0, slots: 0, cells: 0, buckets: 0, frames: 0 };
|
|
9368
|
+
let compactIndex = 0;
|
|
9369
|
+
PANEL_FAMILY_ORDER.forEach((f3) => {
|
|
9370
|
+
if (presence[f3]) {
|
|
9371
|
+
panelY[f3] = compactIndex * panelHeight;
|
|
9372
|
+
compactIndex += 1;
|
|
9373
|
+
}
|
|
9374
|
+
});
|
|
8796
9375
|
if (bars.length > 0) {
|
|
9376
|
+
const panelYBars = panelY.bars;
|
|
8797
9377
|
const slot = width / bars.length;
|
|
8798
9378
|
const barW = slot * 0.8;
|
|
8799
9379
|
const gap = slot * 0.1;
|
|
8800
|
-
const
|
|
8801
|
-
const
|
|
9380
|
+
const bracketRanges = ranges.filter((r) => r.kind === "bracket");
|
|
9381
|
+
const bracketCount = bracketRanges.length;
|
|
9382
|
+
const bracketHeadroom = bracketCount > 0 ? BRACKET_TOP_OFFSET + bracketCount * BRACKET_ROW_H : 0;
|
|
9383
|
+
const hasAux = auxBars.length > 0;
|
|
9384
|
+
const primaryH = hasAux ? panelHeight * AUX_PRIMARY_RATIO : panelHeight;
|
|
9385
|
+
const baseline = panelYBars + primaryH - POINTER_BAND;
|
|
9386
|
+
const usableH = baseline - (panelYBars + TOP_PAD + bracketHeadroom);
|
|
8802
9387
|
const maxV = Math.max(1, ...bars.map((b) => Number.isFinite(b.value) ? b.value : 0));
|
|
8803
9388
|
bars.forEach((bar, i) => {
|
|
8804
9389
|
const v = Number.isFinite(bar.value) ? bar.value : 0;
|
|
@@ -8828,6 +9413,89 @@ var init_AlgorithmCanvas = __esm({
|
|
|
8828
9413
|
});
|
|
8829
9414
|
}
|
|
8830
9415
|
});
|
|
9416
|
+
ranges.forEach((r) => {
|
|
9417
|
+
const kind = r.kind ?? "fill";
|
|
9418
|
+
if (kind !== "fill") return;
|
|
9419
|
+
const color = r.color ?? RANGE_COLOR_DEFAULT;
|
|
9420
|
+
out.push({
|
|
9421
|
+
type: "rect",
|
|
9422
|
+
x: r.from * slot,
|
|
9423
|
+
y: panelYBars,
|
|
9424
|
+
width: (r.to - r.from + 1) * slot,
|
|
9425
|
+
height: primaryH,
|
|
9426
|
+
color,
|
|
9427
|
+
fill: color,
|
|
9428
|
+
opacity: RANGE_FILL_OPACITY
|
|
9429
|
+
});
|
|
9430
|
+
if (r.label) {
|
|
9431
|
+
out.push({
|
|
9432
|
+
type: "text",
|
|
9433
|
+
x: r.from * slot + 4,
|
|
9434
|
+
// Sits below the bracket block (if any) so fill and bracket labels never collide.
|
|
9435
|
+
y: panelYBars + 10 + bracketHeadroom,
|
|
9436
|
+
text: r.label,
|
|
9437
|
+
color,
|
|
9438
|
+
fontSize: 10,
|
|
9439
|
+
align: "left"
|
|
9440
|
+
});
|
|
9441
|
+
}
|
|
9442
|
+
});
|
|
9443
|
+
bracketRanges.forEach((r, i) => {
|
|
9444
|
+
const bracketY = panelYBars + BRACKET_TOP_OFFSET + i * BRACKET_ROW_H;
|
|
9445
|
+
const x1 = r.from * slot + slot * 0.1;
|
|
9446
|
+
const x2 = (r.to + 1) * slot - slot * 0.1;
|
|
9447
|
+
const color = r.color ?? RANGE_COLOR_DEFAULT;
|
|
9448
|
+
out.push({ type: "line", x1, y1: bracketY, x2, y2: bracketY, color, lineWidth: 2 });
|
|
9449
|
+
out.push({ type: "line", x1, y1: bracketY, x2: x1, y2: bracketY + BRACKET_TICK_H, color, lineWidth: 2 });
|
|
9450
|
+
out.push({ type: "line", x1: x2, y1: bracketY, x2, y2: bracketY + BRACKET_TICK_H, color, lineWidth: 2 });
|
|
9451
|
+
if (r.label) {
|
|
9452
|
+
out.push({
|
|
9453
|
+
type: "text",
|
|
9454
|
+
x: (x1 + x2) / 2,
|
|
9455
|
+
y: bracketY - BRACKET_LABEL_OFFSET,
|
|
9456
|
+
text: r.label,
|
|
9457
|
+
color,
|
|
9458
|
+
fontSize: 10,
|
|
9459
|
+
align: "center"
|
|
9460
|
+
});
|
|
9461
|
+
}
|
|
9462
|
+
});
|
|
9463
|
+
if (hasAux) {
|
|
9464
|
+
const auxH = panelHeight - primaryH;
|
|
9465
|
+
const slot2 = width / auxBars.length;
|
|
9466
|
+
const auxBaseline = panelYBars + primaryH + auxH - AUX_BASELINE_PAD;
|
|
9467
|
+
const auxUsableH = auxBaseline - (panelYBars + primaryH + AUX_LABEL_BAND);
|
|
9468
|
+
const maxAuxV = Math.max(1, ...auxBars.map((b) => Number.isFinite(b.value) ? b.value : 0));
|
|
9469
|
+
auxBars.forEach((bar, i) => {
|
|
9470
|
+
const v = Number.isFinite(bar.value) ? bar.value : 0;
|
|
9471
|
+
const bh = Math.max(0, v / maxAuxV * auxUsableH);
|
|
9472
|
+
const x = i * slot2 + slot2 * 0.1;
|
|
9473
|
+
const w = slot2 * 0.8;
|
|
9474
|
+
const color = bar.color ?? DEFAULT_BAR_COLOR;
|
|
9475
|
+
out.push({
|
|
9476
|
+
type: "rect",
|
|
9477
|
+
id: `auxbar-${i}`,
|
|
9478
|
+
x,
|
|
9479
|
+
y: auxBaseline - bh,
|
|
9480
|
+
width: w,
|
|
9481
|
+
height: bh,
|
|
9482
|
+
color,
|
|
9483
|
+
fill: color
|
|
9484
|
+
});
|
|
9485
|
+
const label = bar.label ?? (auxBars.length <= 24 ? String(v) : void 0);
|
|
9486
|
+
if (label) {
|
|
9487
|
+
out.push({
|
|
9488
|
+
type: "text",
|
|
9489
|
+
x: x + w / 2,
|
|
9490
|
+
y: auxBaseline - bh - 8,
|
|
9491
|
+
text: label,
|
|
9492
|
+
color: "#374151",
|
|
9493
|
+
fontSize: 11,
|
|
9494
|
+
align: "center"
|
|
9495
|
+
});
|
|
9496
|
+
}
|
|
9497
|
+
});
|
|
9498
|
+
}
|
|
8831
9499
|
pointers.forEach((p) => {
|
|
8832
9500
|
if (p.index < 0 || p.index >= bars.length) return;
|
|
8833
9501
|
const cx = p.index * slot + slot / 2;
|
|
@@ -8835,7 +9503,7 @@ var init_AlgorithmCanvas = __esm({
|
|
|
8835
9503
|
out.push({
|
|
8836
9504
|
type: "arrow",
|
|
8837
9505
|
x1: cx,
|
|
8838
|
-
y1:
|
|
9506
|
+
y1: panelYBars + primaryH - 18,
|
|
8839
9507
|
x2: cx,
|
|
8840
9508
|
y2: baseline + 4,
|
|
8841
9509
|
color,
|
|
@@ -8845,7 +9513,7 @@ var init_AlgorithmCanvas = __esm({
|
|
|
8845
9513
|
out.push({
|
|
8846
9514
|
type: "text",
|
|
8847
9515
|
x: cx,
|
|
8848
|
-
y:
|
|
9516
|
+
y: panelYBars + primaryH - 8,
|
|
8849
9517
|
text: p.label,
|
|
8850
9518
|
color,
|
|
8851
9519
|
fontSize: 11,
|
|
@@ -8854,14 +9522,111 @@ var init_AlgorithmCanvas = __esm({
|
|
|
8854
9522
|
}
|
|
8855
9523
|
});
|
|
8856
9524
|
}
|
|
9525
|
+
if (slots.length > 0) {
|
|
9526
|
+
const panelYSlots = panelY.slots;
|
|
9527
|
+
const n = slots.length;
|
|
9528
|
+
const vertical = slotOrientation === "vertical";
|
|
9529
|
+
const vBoxH = panelHeight / n;
|
|
9530
|
+
const vBoxW = Math.min(width * 0.5, 120);
|
|
9531
|
+
const vBoxX = (width - vBoxW) / 2;
|
|
9532
|
+
const hCellW = width / n;
|
|
9533
|
+
const hBoxW = hCellW * 0.82;
|
|
9534
|
+
const hBoxH = Math.min(panelHeight * 0.6, 48);
|
|
9535
|
+
const hBoxY = panelYSlots + (panelHeight - hBoxH) / 2;
|
|
9536
|
+
const slotBox = (i) => vertical ? { x: vBoxX, y: panelYSlots + panelHeight - (i + 1) * vBoxH, width: vBoxW, height: vBoxH } : { x: i * hCellW + (hCellW - hBoxW) / 2, y: hBoxY, width: hBoxW, height: hBoxH };
|
|
9537
|
+
slots.forEach((s, i) => {
|
|
9538
|
+
const box = slotBox(i);
|
|
9539
|
+
const state = s.state ?? "filled";
|
|
9540
|
+
const fill = state === "empty" ? SLOT_EMPTY_FILL : state === "highlight" ? s.color ?? SLOT_HIGHLIGHT_DEFAULT : s.color ?? DEFAULT_BAR_COLOR;
|
|
9541
|
+
const stroke = state === "empty" ? SLOT_EMPTY_STROKE : SLOT_FILLED_STROKE;
|
|
9542
|
+
out.push({
|
|
9543
|
+
type: "rect",
|
|
9544
|
+
id: `slot-${i}`,
|
|
9545
|
+
x: box.x,
|
|
9546
|
+
y: box.y,
|
|
9547
|
+
width: box.width,
|
|
9548
|
+
height: box.height,
|
|
9549
|
+
color: stroke,
|
|
9550
|
+
fill
|
|
9551
|
+
});
|
|
9552
|
+
if (s.value != null && state !== "empty") {
|
|
9553
|
+
out.push({
|
|
9554
|
+
type: "text",
|
|
9555
|
+
x: box.x + box.width / 2,
|
|
9556
|
+
y: box.y + box.height / 2,
|
|
9557
|
+
text: String(s.value),
|
|
9558
|
+
color: SLOT_VALUE_TEXT_COLOR,
|
|
9559
|
+
fontSize: 12,
|
|
9560
|
+
align: "center"
|
|
9561
|
+
});
|
|
9562
|
+
}
|
|
9563
|
+
});
|
|
9564
|
+
if (bars.length === 0) {
|
|
9565
|
+
pointers.forEach((p) => {
|
|
9566
|
+
if (p.index < 0 || p.index >= slots.length) return;
|
|
9567
|
+
const box = slotBox(p.index);
|
|
9568
|
+
const color = p.color ?? DEFAULT_POINTER_COLOR;
|
|
9569
|
+
if (vertical) {
|
|
9570
|
+
const cy = box.y + box.height / 2;
|
|
9571
|
+
out.push({
|
|
9572
|
+
type: "arrow",
|
|
9573
|
+
x1: box.x + box.width + 34,
|
|
9574
|
+
y1: cy,
|
|
9575
|
+
x2: box.x + box.width + 4,
|
|
9576
|
+
y2: cy,
|
|
9577
|
+
color,
|
|
9578
|
+
lineWidth: 2
|
|
9579
|
+
});
|
|
9580
|
+
if (p.label) {
|
|
9581
|
+
out.push({
|
|
9582
|
+
type: "text",
|
|
9583
|
+
x: box.x + box.width + 38,
|
|
9584
|
+
y: cy,
|
|
9585
|
+
text: p.label,
|
|
9586
|
+
color,
|
|
9587
|
+
fontSize: 11,
|
|
9588
|
+
align: "left"
|
|
9589
|
+
});
|
|
9590
|
+
}
|
|
9591
|
+
} else {
|
|
9592
|
+
const cx = box.x + box.width / 2;
|
|
9593
|
+
out.push({
|
|
9594
|
+
type: "arrow",
|
|
9595
|
+
x1: cx,
|
|
9596
|
+
y1: panelYSlots + panelHeight - 18,
|
|
9597
|
+
x2: cx,
|
|
9598
|
+
y2: box.y + box.height + 4,
|
|
9599
|
+
color,
|
|
9600
|
+
lineWidth: 2
|
|
9601
|
+
});
|
|
9602
|
+
if (p.label) {
|
|
9603
|
+
out.push({
|
|
9604
|
+
type: "text",
|
|
9605
|
+
x: cx,
|
|
9606
|
+
y: panelYSlots + panelHeight - 8,
|
|
9607
|
+
text: p.label,
|
|
9608
|
+
color,
|
|
9609
|
+
fontSize: 11,
|
|
9610
|
+
align: "center"
|
|
9611
|
+
});
|
|
9612
|
+
}
|
|
9613
|
+
}
|
|
9614
|
+
});
|
|
9615
|
+
}
|
|
9616
|
+
}
|
|
8857
9617
|
if (cells.length > 0) {
|
|
9618
|
+
const panelYCells = panelY.cells;
|
|
8858
9619
|
const maxCol = Math.max(0, ...cells.map((c) => c.col)) + 1;
|
|
8859
9620
|
const maxRow = Math.max(0, ...cells.map((c) => c.row)) + 1;
|
|
8860
|
-
const
|
|
8861
|
-
const
|
|
9621
|
+
const colLabelH = colLabels.length > 0 ? 16 : 0;
|
|
9622
|
+
const rowLabelW = rowLabels.length > 0 ? 20 : 0;
|
|
9623
|
+
const gridX0 = rowLabelW;
|
|
9624
|
+
const gridY0 = panelYCells + colLabelH;
|
|
9625
|
+
const cw = (width - rowLabelW) / maxCol;
|
|
9626
|
+
const ch = (panelHeight - colLabelH) / maxRow;
|
|
8862
9627
|
cells.forEach((c, i) => {
|
|
8863
|
-
const x = c.col * cw;
|
|
8864
|
-
const y = c.row * ch;
|
|
9628
|
+
const x = gridX0 + c.col * cw;
|
|
9629
|
+
const y = gridY0 + c.row * ch;
|
|
8865
9630
|
const color = c.color ?? DEFAULT_CELL_COLOR;
|
|
8866
9631
|
out.push({
|
|
8867
9632
|
type: "rect",
|
|
@@ -8885,11 +9650,207 @@ var init_AlgorithmCanvas = __esm({
|
|
|
8885
9650
|
align: "center"
|
|
8886
9651
|
});
|
|
8887
9652
|
}
|
|
9653
|
+
if (c.corner && cw >= CORNER_MIN_CELL && ch >= CORNER_MIN_CELL) {
|
|
9654
|
+
const { tl, tr, bl, br } = c.corner;
|
|
9655
|
+
if (tl) {
|
|
9656
|
+
out.push({
|
|
9657
|
+
type: "text",
|
|
9658
|
+
x: x + CORNER_INSET_X,
|
|
9659
|
+
y: y + CORNER_INSET_Y,
|
|
9660
|
+
text: tl,
|
|
9661
|
+
color: CORNER_TEXT_COLOR,
|
|
9662
|
+
fontSize: CORNER_FONT_SIZE,
|
|
9663
|
+
align: "left"
|
|
9664
|
+
});
|
|
9665
|
+
}
|
|
9666
|
+
if (tr) {
|
|
9667
|
+
out.push({
|
|
9668
|
+
type: "text",
|
|
9669
|
+
x: x + cw - CORNER_INSET_X,
|
|
9670
|
+
y: y + CORNER_INSET_Y,
|
|
9671
|
+
text: tr,
|
|
9672
|
+
color: CORNER_TEXT_COLOR,
|
|
9673
|
+
fontSize: CORNER_FONT_SIZE,
|
|
9674
|
+
align: "right"
|
|
9675
|
+
});
|
|
9676
|
+
}
|
|
9677
|
+
if (bl) {
|
|
9678
|
+
out.push({
|
|
9679
|
+
type: "text",
|
|
9680
|
+
x: x + CORNER_INSET_X,
|
|
9681
|
+
y: y + ch - CORNER_INSET_Y,
|
|
9682
|
+
text: bl,
|
|
9683
|
+
color: CORNER_TEXT_COLOR,
|
|
9684
|
+
fontSize: CORNER_FONT_SIZE,
|
|
9685
|
+
align: "left"
|
|
9686
|
+
});
|
|
9687
|
+
}
|
|
9688
|
+
if (br) {
|
|
9689
|
+
out.push({
|
|
9690
|
+
type: "text",
|
|
9691
|
+
x: x + cw - CORNER_INSET_X,
|
|
9692
|
+
y: y + ch - CORNER_INSET_Y,
|
|
9693
|
+
text: br,
|
|
9694
|
+
color: CORNER_TEXT_COLOR,
|
|
9695
|
+
fontSize: CORNER_FONT_SIZE,
|
|
9696
|
+
align: "right"
|
|
9697
|
+
});
|
|
9698
|
+
}
|
|
9699
|
+
}
|
|
9700
|
+
});
|
|
9701
|
+
colLabels.forEach((l) => {
|
|
9702
|
+
out.push({
|
|
9703
|
+
type: "text",
|
|
9704
|
+
x: gridX0 + l.index * cw + cw / 2,
|
|
9705
|
+
y: panelYCells + colLabelH / 2,
|
|
9706
|
+
text: l.text,
|
|
9707
|
+
color: l.color ?? AXIS_LABEL_COLOR,
|
|
9708
|
+
fontSize: AXIS_LABEL_FONT_SIZE,
|
|
9709
|
+
align: "center"
|
|
9710
|
+
});
|
|
9711
|
+
});
|
|
9712
|
+
rowLabels.forEach((l) => {
|
|
9713
|
+
out.push({
|
|
9714
|
+
type: "text",
|
|
9715
|
+
x: rowLabelW - 6,
|
|
9716
|
+
y: gridY0 + l.index * ch + ch / 2,
|
|
9717
|
+
text: l.text,
|
|
9718
|
+
color: l.color ?? AXIS_LABEL_COLOR,
|
|
9719
|
+
fontSize: AXIS_LABEL_FONT_SIZE,
|
|
9720
|
+
align: "right"
|
|
9721
|
+
});
|
|
9722
|
+
});
|
|
9723
|
+
}
|
|
9724
|
+
if (buckets.length > 0) {
|
|
9725
|
+
const panelYBuckets = panelY.buckets;
|
|
9726
|
+
const bucketCount = Math.max(0, ...buckets.map((b) => b.index)) + 1;
|
|
9727
|
+
const rowH = panelHeight / bucketCount;
|
|
9728
|
+
const indexColW = Math.min(width * 0.12, 40);
|
|
9729
|
+
const maxChainLen = Math.max(1, ...buckets.map((b) => b.entries.length));
|
|
9730
|
+
const entryW = Math.min(BUCKET_ENTRY_MAX_W, Math.max(BUCKET_ENTRY_MIN_W, (width - indexColW - 8) / maxChainLen));
|
|
9731
|
+
const maxVisible = Math.floor((width - indexColW - 4) / entryW);
|
|
9732
|
+
buckets.forEach((b) => {
|
|
9733
|
+
const rowY = panelYBuckets + b.index * rowH;
|
|
9734
|
+
out.push({
|
|
9735
|
+
type: "rect",
|
|
9736
|
+
id: `bucket-index-${b.index}`,
|
|
9737
|
+
x: 2,
|
|
9738
|
+
y: rowY + 2,
|
|
9739
|
+
width: indexColW - 4,
|
|
9740
|
+
height: rowH - 4,
|
|
9741
|
+
color: BUCKET_INDEX_STROKE,
|
|
9742
|
+
fill: BUCKET_INDEX_FILL
|
|
9743
|
+
});
|
|
9744
|
+
out.push({
|
|
9745
|
+
type: "text",
|
|
9746
|
+
x: 2 + (indexColW - 4) / 2,
|
|
9747
|
+
y: rowY + rowH / 2,
|
|
9748
|
+
text: String(b.index),
|
|
9749
|
+
color: BUCKET_INDEX_TEXT,
|
|
9750
|
+
fontSize: 10,
|
|
9751
|
+
align: "center"
|
|
9752
|
+
});
|
|
9753
|
+
const overflow = b.entries.length > maxVisible;
|
|
9754
|
+
const visibleCount = overflow ? Math.max(0, maxVisible - 1) : b.entries.length;
|
|
9755
|
+
for (let j = 0; j < visibleCount; j++) {
|
|
9756
|
+
const entry = b.entries[j];
|
|
9757
|
+
const ex = indexColW + 4 + j * entryW;
|
|
9758
|
+
const state = entry.state ?? "default";
|
|
9759
|
+
const fill = state === "highlight" ? entry.color ?? BUCKET_ENTRY_HIGHLIGHT : state === "probing" ? entry.color ?? BUCKET_ENTRY_PROBING : entry.color ?? BUCKET_ENTRY_DEFAULT;
|
|
9760
|
+
out.push({
|
|
9761
|
+
type: "rect",
|
|
9762
|
+
id: `bucket-${b.index}-${j}`,
|
|
9763
|
+
x: ex,
|
|
9764
|
+
y: rowY + 2,
|
|
9765
|
+
width: entryW - 2,
|
|
9766
|
+
height: rowH - 4,
|
|
9767
|
+
color: fill,
|
|
9768
|
+
fill
|
|
9769
|
+
});
|
|
9770
|
+
if (entryW >= 20 && rowH >= 16) {
|
|
9771
|
+
out.push({
|
|
9772
|
+
type: "text",
|
|
9773
|
+
x: ex + (entryW - 2) / 2,
|
|
9774
|
+
y: rowY + rowH / 2,
|
|
9775
|
+
text: entry.label,
|
|
9776
|
+
color: BUCKET_ENTRY_TEXT,
|
|
9777
|
+
fontSize: 10,
|
|
9778
|
+
align: "center"
|
|
9779
|
+
});
|
|
9780
|
+
}
|
|
9781
|
+
}
|
|
9782
|
+
if (overflow) {
|
|
9783
|
+
const ex = indexColW + 4 + visibleCount * entryW;
|
|
9784
|
+
out.push({
|
|
9785
|
+
type: "rect",
|
|
9786
|
+
id: `bucket-${b.index}-overflow`,
|
|
9787
|
+
x: ex,
|
|
9788
|
+
y: rowY + 2,
|
|
9789
|
+
width: entryW - 2,
|
|
9790
|
+
height: rowH - 4,
|
|
9791
|
+
color: BUCKET_ENTRY_DEFAULT,
|
|
9792
|
+
fill: BUCKET_ENTRY_DEFAULT
|
|
9793
|
+
});
|
|
9794
|
+
out.push({
|
|
9795
|
+
type: "text",
|
|
9796
|
+
x: ex + (entryW - 2) / 2,
|
|
9797
|
+
y: rowY + rowH / 2,
|
|
9798
|
+
text: `+${b.entries.length - visibleCount}`,
|
|
9799
|
+
color: BUCKET_ENTRY_TEXT,
|
|
9800
|
+
fontSize: 10,
|
|
9801
|
+
align: "center"
|
|
9802
|
+
});
|
|
9803
|
+
}
|
|
9804
|
+
});
|
|
9805
|
+
}
|
|
9806
|
+
if (frames.length > 0) {
|
|
9807
|
+
const panelYFrames = panelY.frames;
|
|
9808
|
+
const n = frames.length;
|
|
9809
|
+
const frameH = panelHeight / n;
|
|
9810
|
+
const x = 8;
|
|
9811
|
+
const w = width - 16;
|
|
9812
|
+
frames.forEach((f3, i) => {
|
|
9813
|
+
const y = panelYFrames + panelHeight - (i + 1) * frameH;
|
|
9814
|
+
const state = f3.state ?? "active";
|
|
9815
|
+
const fill = state === "returning" ? f3.color ?? FRAME_RETURNING_COLOR : state === "done" ? f3.color ?? FRAME_DONE_COLOR : f3.color ?? FRAME_ACTIVE_COLOR;
|
|
9816
|
+
out.push({ type: "rect", id: `frame-${i}`, x, y, width: w, height: frameH, color: fill, fill });
|
|
9817
|
+
if (frameH >= FRAME_TWO_LINE_MIN_H) {
|
|
9818
|
+
out.push({
|
|
9819
|
+
type: "text",
|
|
9820
|
+
x: 16,
|
|
9821
|
+
y: y + frameH * 0.35,
|
|
9822
|
+
text: f3.label,
|
|
9823
|
+
color: FRAME_LABEL_COLOR,
|
|
9824
|
+
fontSize: 10,
|
|
9825
|
+
align: "left"
|
|
9826
|
+
});
|
|
9827
|
+
if (f3.detail) {
|
|
9828
|
+
out.push({
|
|
9829
|
+
type: "text",
|
|
9830
|
+
x: 16,
|
|
9831
|
+
y: y + frameH * 0.7,
|
|
9832
|
+
text: f3.detail,
|
|
9833
|
+
color: FRAME_DETAIL_COLOR,
|
|
9834
|
+
fontSize: 10,
|
|
9835
|
+
align: "left"
|
|
9836
|
+
});
|
|
9837
|
+
}
|
|
9838
|
+
} else {
|
|
9839
|
+
out.push({
|
|
9840
|
+
type: "text",
|
|
9841
|
+
x: 16,
|
|
9842
|
+
y: y + frameH / 2,
|
|
9843
|
+
text: f3.label,
|
|
9844
|
+
color: FRAME_LABEL_COLOR,
|
|
9845
|
+
fontSize: 10,
|
|
9846
|
+
align: "left"
|
|
9847
|
+
});
|
|
9848
|
+
}
|
|
8888
9849
|
});
|
|
8889
9850
|
}
|
|
8890
9851
|
out.push(...shapes);
|
|
8891
9852
|
return out;
|
|
8892
|
-
}, [bars, cells, pointers, shapes, width, height]);
|
|
9853
|
+
}, [bars, cells, pointers, ranges, slots, slotOrientation, frames, buckets, auxBars, rowLabels, colLabels, shapes, width, height]);
|
|
8893
9854
|
return /* @__PURE__ */ jsxRuntime.jsx(exports.Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(exports.VStack, { gap: "sm", children: [
|
|
8894
9855
|
title ? /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "h4", children: title }) : null,
|
|
8895
9856
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -9878,23 +10839,422 @@ var init_BehaviorView = __esm({
|
|
|
9878
10839
|
exports.BehaviorView.displayName = "BehaviorView";
|
|
9879
10840
|
}
|
|
9880
10841
|
});
|
|
9881
|
-
|
|
10842
|
+
function LearningScene3D({
|
|
10843
|
+
className,
|
|
10844
|
+
width = 600,
|
|
10845
|
+
height = 400,
|
|
10846
|
+
title,
|
|
10847
|
+
backgroundColor,
|
|
10848
|
+
drawables,
|
|
10849
|
+
camera,
|
|
10850
|
+
lighting,
|
|
10851
|
+
post,
|
|
10852
|
+
showGrid = false,
|
|
10853
|
+
shadows,
|
|
10854
|
+
interactive,
|
|
10855
|
+
isLoading,
|
|
10856
|
+
error,
|
|
10857
|
+
onItemClick
|
|
10858
|
+
}) {
|
|
10859
|
+
const instanceId = React77.useId().replace(/[^a-zA-Z0-9]/g, "");
|
|
10860
|
+
const clickEvent = onItemClick ? `LEARNING_SCENE_3D.ITEM_CLICK.${instanceId}` : void 0;
|
|
10861
|
+
const onItemClickRef = React77.useRef(onItemClick);
|
|
10862
|
+
onItemClickRef.current = onItemClick;
|
|
10863
|
+
useEventListener(`UI:${clickEvent ?? "LEARNING_SCENE_3D.ITEM_CLICK.__disabled"}`, (event) => {
|
|
10864
|
+
const unitId = event.payload?.unitId;
|
|
10865
|
+
if (typeof unitId === "string") onItemClickRef.current?.(unitId);
|
|
10866
|
+
});
|
|
10867
|
+
if (typeof process !== "undefined" && process.env && process.env.NODE_ENV !== "production" && post?.bloom) {
|
|
10868
|
+
const unknownKeys = Object.keys(post.bloom).filter((k) => !KNOWN_BLOOM_KEYS.has(k));
|
|
10869
|
+
if (unknownKeys.length > 0) {
|
|
10870
|
+
sceneLog.debug("post.bloom has unrecognized keys \u2014 only intensity/threshold/smoothing are read", { unknownKeys });
|
|
10871
|
+
}
|
|
10872
|
+
}
|
|
10873
|
+
const props3d = {
|
|
10874
|
+
drawables,
|
|
10875
|
+
isLoading,
|
|
10876
|
+
error: error ? error.message : null,
|
|
10877
|
+
cameraMode: camera?.mode ?? "perspective",
|
|
10878
|
+
...camera?.zoom !== void 0 ? { zoom: camera.zoom } : {},
|
|
10879
|
+
...camera?.fov !== void 0 ? { fov: camera.fov } : {},
|
|
10880
|
+
...camera?.azimuth !== void 0 ? { azimuth: camera.azimuth } : {},
|
|
10881
|
+
...camera?.elevation !== void 0 ? { elevation: camera.elevation } : {},
|
|
10882
|
+
...camera?.target !== void 0 ? { followTarget: camera.target } : {},
|
|
10883
|
+
backgroundColor,
|
|
10884
|
+
showGrid,
|
|
10885
|
+
...shadows !== void 0 ? { shadows } : {},
|
|
10886
|
+
...interactive !== void 0 ? { controlsEnabled: interactive } : {},
|
|
10887
|
+
lighting,
|
|
10888
|
+
post,
|
|
10889
|
+
...clickEvent !== void 0 ? { unitClickEvent: clickEvent } : {}
|
|
10890
|
+
};
|
|
10891
|
+
return /* @__PURE__ */ jsxRuntime.jsx(exports.Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(exports.VStack, { gap: "sm", children: [
|
|
10892
|
+
title ? /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "h4", children: title }) : null,
|
|
10893
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { width, height, display: "flex" }, children: /* @__PURE__ */ jsxRuntime.jsx(React77.Suspense, { fallback: null, children: /* @__PURE__ */ jsxRuntime.jsx(Canvas3DHost, { ...props3d }) }) })
|
|
10894
|
+
] }) });
|
|
10895
|
+
}
|
|
10896
|
+
function meshSphere(id, x, y, z, radius, color, opts) {
|
|
10897
|
+
return {
|
|
10898
|
+
type: "draw-mesh",
|
|
10899
|
+
...id !== void 0 ? { id } : {},
|
|
10900
|
+
shape: opts?.shape ?? "sphere",
|
|
10901
|
+
position: { x, y, z },
|
|
10902
|
+
radius,
|
|
10903
|
+
pivot: "center",
|
|
10904
|
+
segments: opts?.segments ?? 24,
|
|
10905
|
+
material: { color, ...opts?.material },
|
|
10906
|
+
...opts?.opacity !== void 0 ? { opacity: opts.opacity } : {}
|
|
10907
|
+
};
|
|
10908
|
+
}
|
|
10909
|
+
function axisRotation(from, to) {
|
|
10910
|
+
const wx = to[0] - from[0];
|
|
10911
|
+
const wy = to[2] - from[2];
|
|
10912
|
+
const wz = to[1] - from[1];
|
|
10913
|
+
const len = Math.sqrt(wx * wx + wy * wy + wz * wz);
|
|
10914
|
+
const tilt = Math.acos(Math.min(1, Math.max(-1, len > 0 ? wy / len : 1)));
|
|
10915
|
+
return [0, Math.atan2(wz, -wx), tilt];
|
|
10916
|
+
}
|
|
10917
|
+
function segmentLength(from, to) {
|
|
10918
|
+
const dx = to[0] - from[0];
|
|
10919
|
+
const dy = to[1] - from[1];
|
|
10920
|
+
const dz = to[2] - from[2];
|
|
10921
|
+
return Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
10922
|
+
}
|
|
10923
|
+
function cylinderBetween(from, to, radius, color) {
|
|
10924
|
+
const len = segmentLength(from, to);
|
|
10925
|
+
if (len < 1e-6) return null;
|
|
10926
|
+
return {
|
|
10927
|
+
type: "draw-mesh",
|
|
10928
|
+
shape: "cylinder",
|
|
10929
|
+
position: { x: (from[0] + to[0]) / 2, y: (from[1] + to[1]) / 2, z: (from[2] + to[2]) / 2 },
|
|
10930
|
+
radius,
|
|
10931
|
+
height: len,
|
|
10932
|
+
rotation: axisRotation(from, to),
|
|
10933
|
+
pivot: "center",
|
|
10934
|
+
segments: 12,
|
|
10935
|
+
material: { color }
|
|
10936
|
+
};
|
|
10937
|
+
}
|
|
10938
|
+
function arrowBetween(from, to, color, shaftRadius = 0.08, id) {
|
|
10939
|
+
const len = segmentLength(from, to);
|
|
10940
|
+
if (len < 1e-6) return null;
|
|
10941
|
+
const tipLen = Math.min(shaftRadius * 8, len * 0.35);
|
|
10942
|
+
const k = (len - tipLen) / len;
|
|
10943
|
+
const delta = [to[0] - from[0], to[1] - from[1], to[2] - from[2]];
|
|
10944
|
+
const shaftEnd = [delta[0] * k, delta[1] * k, delta[2] * k];
|
|
10945
|
+
const tipStart = shaftEnd;
|
|
10946
|
+
const tipEnd = delta;
|
|
10947
|
+
const shaft = cylinderBetween([0, 0, 0], shaftEnd, shaftRadius, color);
|
|
10948
|
+
const tipLenActual = segmentLength(tipStart, tipEnd);
|
|
10949
|
+
const tip = {
|
|
10950
|
+
type: "draw-mesh",
|
|
10951
|
+
shape: "cone",
|
|
10952
|
+
position: {
|
|
10953
|
+
x: (tipStart[0] + tipEnd[0]) / 2,
|
|
10954
|
+
y: (tipStart[1] + tipEnd[1]) / 2,
|
|
10955
|
+
z: (tipStart[2] + tipEnd[2]) / 2
|
|
10956
|
+
},
|
|
10957
|
+
radius: shaftRadius * 3,
|
|
10958
|
+
height: tipLenActual,
|
|
10959
|
+
rotation: axisRotation(tipStart, tipEnd),
|
|
10960
|
+
pivot: "center",
|
|
10961
|
+
segments: 12,
|
|
10962
|
+
material: { color }
|
|
10963
|
+
};
|
|
10964
|
+
return {
|
|
10965
|
+
type: "draw-group",
|
|
10966
|
+
...id !== void 0 ? { id } : {},
|
|
10967
|
+
position: { x: from[0], y: from[1], z: from[2] },
|
|
10968
|
+
items: tipLenActual < 1e-6 ? shaft ? [shaft] : [] : shaft ? [shaft, tip] : [tip]
|
|
10969
|
+
};
|
|
10970
|
+
}
|
|
10971
|
+
function billboardLabel(text, x, y, z, opts) {
|
|
10972
|
+
return {
|
|
10973
|
+
type: "draw-text",
|
|
10974
|
+
text,
|
|
10975
|
+
position: { x, y, z },
|
|
10976
|
+
color: opts?.color ?? "#111827"
|
|
10977
|
+
};
|
|
10978
|
+
}
|
|
10979
|
+
function labelColorForBackground(backgroundColor) {
|
|
10980
|
+
const m = /^#(?:([0-9a-f]{3})|([0-9a-f]{6}))$/i.exec(backgroundColor ?? "");
|
|
10981
|
+
if (!m) return "#111827";
|
|
10982
|
+
const hex = m[1] !== void 0 ? m[1].split("").map((c) => c + c).join("") : m[2];
|
|
10983
|
+
const r = parseInt(hex.slice(0, 2), 16) / 255;
|
|
10984
|
+
const g = parseInt(hex.slice(2, 4), 16) / 255;
|
|
10985
|
+
const b = parseInt(hex.slice(4, 6), 16) / 255;
|
|
10986
|
+
const luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
10987
|
+
return luminance < 0.5 ? "#e5e7eb" : "#111827";
|
|
10988
|
+
}
|
|
10989
|
+
function get3DClickPayload(onShapeClick, idToIndex) {
|
|
10990
|
+
if (!onShapeClick) return void 0;
|
|
10991
|
+
return (id) => onShapeClick({ id, index: idToIndex.get(id) ?? -1 });
|
|
10992
|
+
}
|
|
10993
|
+
function polylineTube(points, radius, color, opts) {
|
|
10994
|
+
const maxSegments = opts?.maxSegments ?? 128;
|
|
10995
|
+
let pts = points;
|
|
10996
|
+
if (pts.length - 1 > maxSegments) {
|
|
10997
|
+
const step = (pts.length - 1) / maxSegments;
|
|
10998
|
+
const kept = [pts[0]];
|
|
10999
|
+
for (let s = 1; s < maxSegments; s++) kept.push(pts[Math.round(s * step)]);
|
|
11000
|
+
kept.push(pts[pts.length - 1]);
|
|
11001
|
+
pts = kept;
|
|
11002
|
+
}
|
|
11003
|
+
const out = [];
|
|
11004
|
+
for (let i = 0; i < pts.length - 1; i++) {
|
|
11005
|
+
const seg = cylinderBetween(pts[i], pts[i + 1], radius, color);
|
|
11006
|
+
if (seg) out.push(opts?.opacity !== void 0 ? { ...seg, opacity: opts.opacity } : seg);
|
|
11007
|
+
}
|
|
11008
|
+
return out;
|
|
11009
|
+
}
|
|
11010
|
+
function heightFieldMesh(spec) {
|
|
11011
|
+
const { nx, ny, heights, spacing = 1, x = 0, y = 0 } = spec;
|
|
11012
|
+
const flatShading = spec.flatShading ?? true;
|
|
11013
|
+
const vertices = [];
|
|
11014
|
+
for (let iy = 0; iy < ny; iy++) {
|
|
11015
|
+
for (let ix = 0; ix < nx; ix++) {
|
|
11016
|
+
vertices.push([
|
|
11017
|
+
x + (ix - (nx - 1) / 2) * spacing,
|
|
11018
|
+
y + (iy - (ny - 1) / 2) * spacing,
|
|
11019
|
+
heights[iy * nx + ix] ?? 0
|
|
11020
|
+
]);
|
|
11021
|
+
}
|
|
11022
|
+
}
|
|
11023
|
+
const bands = [...spec.bands ?? []].sort((a, b) => (a.min ?? -Infinity) - (b.min ?? -Infinity));
|
|
11024
|
+
const facesByBand = /* @__PURE__ */ new Map();
|
|
11025
|
+
for (let iy = 0; iy < ny - 1; iy++) {
|
|
11026
|
+
for (let ix = 0; ix < nx - 1; ix++) {
|
|
11027
|
+
const v00 = iy * nx + ix;
|
|
11028
|
+
const v10 = iy * nx + ix + 1;
|
|
11029
|
+
const v01 = (iy + 1) * nx + ix;
|
|
11030
|
+
const v11 = (iy + 1) * nx + ix + 1;
|
|
11031
|
+
for (const face of [[v00, v10, v01], [v10, v11, v01]]) {
|
|
11032
|
+
const centroid = (vertices[face[0]][2] + vertices[face[1]][2] + vertices[face[2]][2]) / 3;
|
|
11033
|
+
let band = null;
|
|
11034
|
+
for (const b of bands) {
|
|
11035
|
+
if ((b.min ?? -Infinity) <= centroid) band = b;
|
|
11036
|
+
}
|
|
11037
|
+
const key = bands.length > 0 ? band : null;
|
|
11038
|
+
const list = facesByBand.get(key) ?? [];
|
|
11039
|
+
list.push(face);
|
|
11040
|
+
facesByBand.set(key, list);
|
|
11041
|
+
}
|
|
11042
|
+
}
|
|
11043
|
+
}
|
|
11044
|
+
const out = [];
|
|
11045
|
+
for (const [band, faces] of facesByBand) {
|
|
11046
|
+
if (faces.length === 0) continue;
|
|
11047
|
+
out.push({
|
|
11048
|
+
type: "draw-mesh",
|
|
11049
|
+
shape: "polyhedron",
|
|
11050
|
+
position: { x: 0, y: 0, z: 0 },
|
|
11051
|
+
vertices,
|
|
11052
|
+
faces,
|
|
11053
|
+
pivot: "center",
|
|
11054
|
+
material: { color: band?.color ?? spec.color ?? "#64748b", flatShading, side: "double" },
|
|
11055
|
+
...spec.opacity !== void 0 ? { opacity: spec.opacity } : {}
|
|
11056
|
+
});
|
|
11057
|
+
}
|
|
11058
|
+
return out;
|
|
11059
|
+
}
|
|
11060
|
+
function arrowField(vectors, opts) {
|
|
11061
|
+
const scale = opts?.scale ?? 1;
|
|
11062
|
+
const out = [];
|
|
11063
|
+
for (const v of vectors) {
|
|
11064
|
+
const to = [
|
|
11065
|
+
v.from[0] + v.delta[0] * scale,
|
|
11066
|
+
v.from[1] + v.delta[1] * scale,
|
|
11067
|
+
v.from[2] + v.delta[2] * scale
|
|
11068
|
+
];
|
|
11069
|
+
const arrow = arrowBetween(v.from, to, v.color ?? "#dc2626", v.width, v.id);
|
|
11070
|
+
if (arrow) out.push(arrow);
|
|
11071
|
+
if (v.label) out.push(billboardLabel(v.label, to[0], to[1], to[2], { color: opts?.labelColor }));
|
|
11072
|
+
}
|
|
11073
|
+
return out;
|
|
11074
|
+
}
|
|
11075
|
+
function helixDrawables(spec, opts) {
|
|
11076
|
+
const count = spec.count ?? spec.rungs?.length ?? 0;
|
|
11077
|
+
const rungs = Array.from({ length: count }, (_, i) => spec.rungs?.[i] ?? {});
|
|
11078
|
+
const radius = spec.radius ?? 1;
|
|
11079
|
+
const rise = spec.rise ?? 0.34;
|
|
11080
|
+
const twistRad = (spec.twistDeg ?? 36) * (Math.PI / 180);
|
|
11081
|
+
const strandAColor = spec.strandAColor ?? "#38bdf8";
|
|
11082
|
+
const strandBColor = spec.strandBColor ?? "#fb923c";
|
|
11083
|
+
const backboneRadius = spec.backboneRadius ?? 0.16;
|
|
11084
|
+
const rungRadius = spec.rungRadius ?? 0.12;
|
|
11085
|
+
const cx = spec.x ?? 0;
|
|
11086
|
+
const cy = spec.y ?? 0;
|
|
11087
|
+
const cz = spec.z ?? 0;
|
|
11088
|
+
const unwoundCount = spec.unwoundCount ?? 0;
|
|
11089
|
+
const unwindSpread = spec.unwindSpread ?? 1.8;
|
|
11090
|
+
const strandA = [];
|
|
11091
|
+
const strandB = [];
|
|
11092
|
+
for (let i = 0; i < count; i++) {
|
|
11093
|
+
const yi = cy + (i - (count - 1) / 2) * rise;
|
|
11094
|
+
const theta = i * twistRad;
|
|
11095
|
+
const s = i < unwoundCount ? unwindSpread : 1;
|
|
11096
|
+
strandA.push([cx + s * radius * Math.cos(theta), yi, cz + s * radius * Math.sin(theta)]);
|
|
11097
|
+
strandB.push([cx + s * radius * Math.cos(theta + Math.PI), yi, cz + s * radius * Math.sin(theta + Math.PI)]);
|
|
11098
|
+
}
|
|
11099
|
+
const out = [];
|
|
11100
|
+
for (let i = 0; i < count; i++) {
|
|
11101
|
+
out.push(meshSphere(`hx-a-${i}`, strandA[i][0], strandA[i][1], strandA[i][2], backboneRadius, strandAColor));
|
|
11102
|
+
out.push(meshSphere(`hx-b-${i}`, strandB[i][0], strandB[i][1], strandB[i][2], backboneRadius, strandBColor));
|
|
11103
|
+
if (i > 0) {
|
|
11104
|
+
const segA = cylinderBetween(strandA[i - 1], strandA[i], backboneRadius, strandAColor);
|
|
11105
|
+
if (segA) out.push(segA);
|
|
11106
|
+
const segB = cylinderBetween(strandB[i - 1], strandB[i], backboneRadius, strandBColor);
|
|
11107
|
+
if (segB) out.push(segB);
|
|
11108
|
+
}
|
|
11109
|
+
const rung = rungs[i];
|
|
11110
|
+
const rungColor = rung.color ?? "#94a3b8";
|
|
11111
|
+
const rod = cylinderBetween(strandA[i], strandB[i], rungRadius, rungColor);
|
|
11112
|
+
if (rod) out.push(rod);
|
|
11113
|
+
const mid = [
|
|
11114
|
+
(strandA[i][0] + strandB[i][0]) / 2,
|
|
11115
|
+
(strandA[i][1] + strandB[i][1]) / 2,
|
|
11116
|
+
(strandA[i][2] + strandB[i][2]) / 2
|
|
11117
|
+
];
|
|
11118
|
+
const markerRadius = rung.radius ?? rungRadius;
|
|
11119
|
+
out.push(meshSphere(rung.id, mid[0], mid[1], mid[2], markerRadius, rungColor));
|
|
11120
|
+
if (rung.label) out.push(billboardLabel(rung.label, mid[0], mid[1], mid[2] + markerRadius, { color: opts?.labelColor }));
|
|
11121
|
+
}
|
|
11122
|
+
return out;
|
|
11123
|
+
}
|
|
11124
|
+
function latticeDrawables(spec, opts) {
|
|
11125
|
+
const nx = spec.nx ?? 2;
|
|
11126
|
+
const ny = spec.ny ?? 2;
|
|
11127
|
+
const nz = spec.nz ?? 2;
|
|
11128
|
+
const latticeConstant = spec.latticeConstant ?? 2;
|
|
11129
|
+
const bondRadius = spec.bondRadius ?? 0.06;
|
|
11130
|
+
const highlightCell = spec.highlightCell ?? false;
|
|
11131
|
+
const dimColor = spec.dimColor ?? "#475569";
|
|
11132
|
+
const showLabels = spec.showLabels ?? false;
|
|
11133
|
+
const selectedColor = spec.selectedColor ?? "#f59e0b";
|
|
11134
|
+
const posByKey = /* @__PURE__ */ new Map();
|
|
11135
|
+
const inCellByKey = /* @__PURE__ */ new Map();
|
|
11136
|
+
const out = [];
|
|
11137
|
+
for (const site of spec.basis) {
|
|
11138
|
+
const snx = site.xEdge ? nx + 1 : nx;
|
|
11139
|
+
const sny = site.yEdge ? ny + 1 : ny;
|
|
11140
|
+
const snz = site.zEdge ? nz + 1 : nz;
|
|
11141
|
+
for (let i = 0; i < snx; i++) {
|
|
11142
|
+
for (let j = 0; j < sny; j++) {
|
|
11143
|
+
for (let k = 0; k < snz; k++) {
|
|
11144
|
+
const key = `${site.key}-${i}-${j}-${k}`;
|
|
11145
|
+
const inCell = i + site.dx <= 1 && j + site.dy <= 1 && k + site.dz <= 1;
|
|
11146
|
+
const pos = [
|
|
11147
|
+
(i + site.dx) * latticeConstant - nx * latticeConstant / 2,
|
|
11148
|
+
(j + site.dy) * latticeConstant - ny * latticeConstant / 2,
|
|
11149
|
+
(k + site.dz) * latticeConstant - nz * latticeConstant / 2
|
|
11150
|
+
];
|
|
11151
|
+
posByKey.set(key, pos);
|
|
11152
|
+
inCellByKey.set(key, inCell);
|
|
11153
|
+
const isSelected = spec.selectedId === `lat-${key}`;
|
|
11154
|
+
const color = isSelected ? selectedColor : highlightCell && !inCell ? dimColor : site.color ?? "#2563eb";
|
|
11155
|
+
const radius = (site.radius ?? 0.3) * (isSelected ? 1.4 : 1);
|
|
11156
|
+
out.push(meshSphere(`lat-${key}`, pos[0], pos[1], pos[2], radius, color));
|
|
11157
|
+
if (showLabels && site.element) {
|
|
11158
|
+
out.push(billboardLabel(site.element, pos[0], pos[1], pos[2] + radius, { color: opts?.labelColor }));
|
|
11159
|
+
}
|
|
11160
|
+
}
|
|
11161
|
+
}
|
|
11162
|
+
}
|
|
11163
|
+
}
|
|
11164
|
+
const basisByKey = new Map(spec.basis.map((s) => [s.key, s]));
|
|
11165
|
+
for (const bond of spec.bonds ?? []) {
|
|
11166
|
+
const fromSite = basisByKey.get(bond.from);
|
|
11167
|
+
const toSite = basisByKey.get(bond.to);
|
|
11168
|
+
if (!fromSite || !toSite) continue;
|
|
11169
|
+
const fnx = fromSite.xEdge ? nx + 1 : nx;
|
|
11170
|
+
const fny = fromSite.yEdge ? ny + 1 : ny;
|
|
11171
|
+
const fnz = fromSite.zEdge ? nz + 1 : nz;
|
|
11172
|
+
const tnx = toSite.xEdge ? nx + 1 : nx;
|
|
11173
|
+
const tny = toSite.yEdge ? ny + 1 : ny;
|
|
11174
|
+
const tnz = toSite.zEdge ? nz + 1 : nz;
|
|
11175
|
+
const bdx = bond.dx ?? 0;
|
|
11176
|
+
const bdy = bond.dy ?? 0;
|
|
11177
|
+
const bdz = bond.dz ?? 0;
|
|
11178
|
+
for (let i = 0; i < fnx; i++) {
|
|
11179
|
+
for (let j = 0; j < fny; j++) {
|
|
11180
|
+
for (let k = 0; k < fnz; k++) {
|
|
11181
|
+
const ti = i + bdx;
|
|
11182
|
+
const tj = j + bdy;
|
|
11183
|
+
const tk = k + bdz;
|
|
11184
|
+
if (ti < 0 || ti >= tnx || tj < 0 || tj >= tny || tk < 0 || tk >= tnz) continue;
|
|
11185
|
+
const fromKey = `${fromSite.key}-${i}-${j}-${k}`;
|
|
11186
|
+
const toKey = `${toSite.key}-${ti}-${tj}-${tk}`;
|
|
11187
|
+
const fromPos = posByKey.get(fromKey);
|
|
11188
|
+
const toPos = posByKey.get(toKey);
|
|
11189
|
+
if (!fromPos || !toPos) continue;
|
|
11190
|
+
const dimmed = highlightCell && !(inCellByKey.get(fromKey) && inCellByKey.get(toKey));
|
|
11191
|
+
const seg = cylinderBetween(fromPos, toPos, bondRadius, dimmed ? dimColor : bond.color ?? "#6b7280");
|
|
11192
|
+
if (seg) out.push(seg);
|
|
11193
|
+
}
|
|
11194
|
+
}
|
|
11195
|
+
}
|
|
11196
|
+
}
|
|
11197
|
+
return out;
|
|
11198
|
+
}
|
|
11199
|
+
var sceneLog, KNOWN_BLOOM_KEYS, Canvas3DHost;
|
|
11200
|
+
var init_learningScene3D = __esm({
|
|
11201
|
+
"components/learning/molecules/learningScene3D.tsx"() {
|
|
11202
|
+
"use client";
|
|
11203
|
+
init_atoms();
|
|
11204
|
+
init_Stack();
|
|
11205
|
+
init_useEventBus();
|
|
11206
|
+
sceneLog = logger.createLogger("almadar:ui:learning-scene-3d");
|
|
11207
|
+
KNOWN_BLOOM_KEYS = /* @__PURE__ */ new Set(["intensity", "threshold", "smoothing"]);
|
|
11208
|
+
Canvas3DHost = React77.lazy(
|
|
11209
|
+
() => import('@almadar/ui/components/molecules/game/three').then((m) => ({ default: m.Canvas3DHost }))
|
|
11210
|
+
);
|
|
11211
|
+
LearningScene3D.displayName = "LearningScene3D";
|
|
11212
|
+
}
|
|
11213
|
+
});
|
|
11214
|
+
var biologyLog, BIO_BAND_COLORS, BIO_STAGE_FILL, BIO_STAGE_TEXT; exports.BiologyCanvas = void 0;
|
|
9882
11215
|
var init_BiologyCanvas = __esm({
|
|
9883
11216
|
"components/learning/molecules/BiologyCanvas.tsx"() {
|
|
9884
11217
|
"use client";
|
|
9885
11218
|
init_atoms();
|
|
9886
11219
|
init_Stack();
|
|
9887
11220
|
init_LearningCanvas();
|
|
11221
|
+
init_learningScene3D();
|
|
11222
|
+
biologyLog = logger.createLogger("almadar:ui:biology-canvas");
|
|
11223
|
+
BIO_BAND_COLORS = ["#dcfce7", "#fef9c3", "#fee2e2", "#e0e7ff"];
|
|
11224
|
+
BIO_STAGE_FILL = {
|
|
11225
|
+
pending: "#e2e8f0",
|
|
11226
|
+
active: "#3b82f6",
|
|
11227
|
+
done: "#94a3b8"
|
|
11228
|
+
};
|
|
11229
|
+
BIO_STAGE_TEXT = {
|
|
11230
|
+
pending: "#64748b",
|
|
11231
|
+
active: "#ffffff",
|
|
11232
|
+
done: "#ffffff"
|
|
11233
|
+
};
|
|
9888
11234
|
exports.BiologyCanvas = ({
|
|
9889
11235
|
className,
|
|
9890
11236
|
width = 600,
|
|
9891
11237
|
height = 400,
|
|
9892
11238
|
title,
|
|
9893
11239
|
backgroundColor,
|
|
11240
|
+
mode = "2d",
|
|
11241
|
+
camera,
|
|
11242
|
+
lighting,
|
|
11243
|
+
post,
|
|
9894
11244
|
nodes = [],
|
|
9895
11245
|
edges = [],
|
|
11246
|
+
compartments = [],
|
|
11247
|
+
bands = [],
|
|
11248
|
+
stages = [],
|
|
11249
|
+
stageStyle = "timeline",
|
|
11250
|
+
helix,
|
|
11251
|
+
helix3d,
|
|
9896
11252
|
shapes = [],
|
|
9897
|
-
|
|
11253
|
+
readouts,
|
|
11254
|
+
traces,
|
|
11255
|
+
showGrid,
|
|
11256
|
+
shadows,
|
|
11257
|
+
interactive,
|
|
9898
11258
|
animate = false,
|
|
9899
11259
|
onShapeClick,
|
|
9900
11260
|
isLoading,
|
|
@@ -9906,19 +11266,148 @@ var init_BiologyCanvas = __esm({
|
|
|
9906
11266
|
for (const n of nodes) {
|
|
9907
11267
|
if (n.id) nodeById.set(n.id, n);
|
|
9908
11268
|
}
|
|
11269
|
+
const bandCount = bands.length;
|
|
11270
|
+
for (let i = 0; i < bandCount; i++) {
|
|
11271
|
+
const band = bands[i];
|
|
11272
|
+
const bandColor = band.color ?? BIO_BAND_COLORS[i % BIO_BAND_COLORS.length];
|
|
11273
|
+
const bandY = i * height / bandCount;
|
|
11274
|
+
const bandH = height / bandCount;
|
|
11275
|
+
out.push({
|
|
11276
|
+
type: "rect",
|
|
11277
|
+
x: 0,
|
|
11278
|
+
y: bandY,
|
|
11279
|
+
width,
|
|
11280
|
+
height: bandH,
|
|
11281
|
+
color: bandColor,
|
|
11282
|
+
fill: bandColor,
|
|
11283
|
+
opacity: 0.45
|
|
11284
|
+
});
|
|
11285
|
+
if (band.label) {
|
|
11286
|
+
out.push({
|
|
11287
|
+
type: "text",
|
|
11288
|
+
x: 8,
|
|
11289
|
+
y: bandY + 14,
|
|
11290
|
+
text: band.label,
|
|
11291
|
+
color: "#6b7280",
|
|
11292
|
+
fontSize: 10
|
|
11293
|
+
});
|
|
11294
|
+
}
|
|
11295
|
+
}
|
|
11296
|
+
for (const c of compartments) {
|
|
11297
|
+
const color = c.color ?? "#16a34a";
|
|
11298
|
+
out.push({
|
|
11299
|
+
type: "ellipse",
|
|
11300
|
+
x: c.x,
|
|
11301
|
+
y: c.y,
|
|
11302
|
+
width: c.width,
|
|
11303
|
+
height: c.height,
|
|
11304
|
+
color,
|
|
11305
|
+
fill: c.fill ?? `${color}1A`,
|
|
11306
|
+
lineWidth: c.lineWidth ?? 2,
|
|
11307
|
+
...c.dash ? { dash: c.dash } : {}
|
|
11308
|
+
});
|
|
11309
|
+
if (c.label) {
|
|
11310
|
+
out.push({
|
|
11311
|
+
type: "text",
|
|
11312
|
+
x: c.x,
|
|
11313
|
+
y: c.y - c.height / 2 + 14,
|
|
11314
|
+
text: c.label,
|
|
11315
|
+
color: "#111827",
|
|
11316
|
+
fontSize: 11,
|
|
11317
|
+
align: "center"
|
|
11318
|
+
});
|
|
11319
|
+
}
|
|
11320
|
+
}
|
|
11321
|
+
if (helix) {
|
|
11322
|
+
const hx = helix.x ?? 24;
|
|
11323
|
+
const hy = helix.y ?? height * 0.25;
|
|
11324
|
+
const hw = helix.width ?? width - 48;
|
|
11325
|
+
const hh = helix.height ?? height * 0.5;
|
|
11326
|
+
const rungs = helix.rungs;
|
|
11327
|
+
const n = rungs.length;
|
|
11328
|
+
const cy = hy + hh / 2;
|
|
11329
|
+
const colorA = helix.colorA ?? "#2563eb";
|
|
11330
|
+
const colorB = helix.colorB ?? "#dc2626";
|
|
11331
|
+
const rungColor = helix.rungColor ?? "#94a3b8";
|
|
11332
|
+
const fork = helix.fork ?? 0;
|
|
11333
|
+
const maxSep = Math.min(hh - 8, 96);
|
|
11334
|
+
const strandA = [];
|
|
11335
|
+
const strandB = [];
|
|
11336
|
+
const rungGeoms = [];
|
|
11337
|
+
for (let i = 0; i < n; i++) {
|
|
11338
|
+
const rx = hx + (i + 0.5) * hw / n;
|
|
11339
|
+
const t = (i + 0.5) / n;
|
|
11340
|
+
const paired = t >= fork;
|
|
11341
|
+
const sep = paired ? 28 : 28 + (maxSep - 28) * ((fork - t) / fork);
|
|
11342
|
+
strandA.push({ x: rx, y: cy - sep / 2 });
|
|
11343
|
+
strandB.push({ x: rx, y: cy + sep / 2 });
|
|
11344
|
+
rungGeoms.push({ rx, sep, rung: rungs[i], paired });
|
|
11345
|
+
}
|
|
11346
|
+
for (let i = 1; i < n; i++) {
|
|
11347
|
+
out.push({ type: "line", x1: strandA[i - 1].x, y1: strandA[i - 1].y, x2: strandA[i].x, y2: strandA[i].y, color: colorA, lineWidth: 3 });
|
|
11348
|
+
}
|
|
11349
|
+
for (let i = 1; i < n; i++) {
|
|
11350
|
+
out.push({ type: "line", x1: strandB[i - 1].x, y1: strandB[i - 1].y, x2: strandB[i].x, y2: strandB[i].y, color: colorB, lineWidth: 3 });
|
|
11351
|
+
}
|
|
11352
|
+
for (const g of rungGeoms) {
|
|
11353
|
+
const rColor = g.rung.color ?? (g.rung.state === "new" ? "#16a34a" : rungColor);
|
|
11354
|
+
const topY = cy - g.sep / 2;
|
|
11355
|
+
const bottomY = cy + g.sep / 2;
|
|
11356
|
+
if (g.paired) {
|
|
11357
|
+
out.push({ type: "line", x1: g.rx, y1: topY, x2: g.rx, y2: bottomY, color: rColor });
|
|
11358
|
+
if (g.rung.a) {
|
|
11359
|
+
out.push({ type: "text", x: g.rx, y: cy - g.sep / 4, text: g.rung.a, fontSize: 9, align: "center", color: "#374151" });
|
|
11360
|
+
}
|
|
11361
|
+
if (g.rung.b) {
|
|
11362
|
+
out.push({ type: "text", x: g.rx, y: cy + g.sep / 4, text: g.rung.b, fontSize: 9, align: "center", color: "#374151" });
|
|
11363
|
+
}
|
|
11364
|
+
} else {
|
|
11365
|
+
const stubTopY = topY + 8;
|
|
11366
|
+
const stubBottomY = bottomY - 8;
|
|
11367
|
+
out.push({ type: "line", x1: g.rx, y1: topY, x2: g.rx, y2: stubTopY, color: rColor });
|
|
11368
|
+
out.push({ type: "line", x1: g.rx, y1: bottomY, x2: g.rx, y2: stubBottomY, color: rColor });
|
|
11369
|
+
if (g.rung.a) {
|
|
11370
|
+
out.push({ type: "text", x: g.rx, y: stubTopY + 6, text: g.rung.a, fontSize: 9, align: "center", color: "#374151" });
|
|
11371
|
+
}
|
|
11372
|
+
if (g.rung.b) {
|
|
11373
|
+
out.push({ type: "text", x: g.rx, y: stubBottomY - 6, text: g.rung.b, fontSize: 9, align: "center", color: "#374151" });
|
|
11374
|
+
}
|
|
11375
|
+
}
|
|
11376
|
+
}
|
|
11377
|
+
}
|
|
9909
11378
|
for (const e of edges) {
|
|
9910
11379
|
const a = nodeById.get(e.from);
|
|
9911
11380
|
const b = nodeById.get(e.to);
|
|
9912
11381
|
if (!a || !b) continue;
|
|
9913
|
-
|
|
9914
|
-
|
|
9915
|
-
|
|
9916
|
-
|
|
9917
|
-
|
|
9918
|
-
|
|
9919
|
-
|
|
9920
|
-
|
|
9921
|
-
|
|
11382
|
+
const color = e.color ?? "#9ca3af";
|
|
11383
|
+
if (e.directed) {
|
|
11384
|
+
const rA = a.radius ?? 16;
|
|
11385
|
+
const rB = b.radius ?? 16;
|
|
11386
|
+
const dx = b.x - a.x;
|
|
11387
|
+
const dy = b.y - a.y;
|
|
11388
|
+
const dist = Math.max(1e-6, Math.hypot(dx, dy));
|
|
11389
|
+
const ux = dx / dist;
|
|
11390
|
+
const uy = dy / dist;
|
|
11391
|
+
out.push({
|
|
11392
|
+
type: "arrow",
|
|
11393
|
+
x1: a.x + ux * rA,
|
|
11394
|
+
y1: a.y + uy * rA,
|
|
11395
|
+
x2: b.x - ux * rB,
|
|
11396
|
+
y2: b.y - uy * rB,
|
|
11397
|
+
color,
|
|
11398
|
+
lineWidth: 2
|
|
11399
|
+
});
|
|
11400
|
+
} else {
|
|
11401
|
+
out.push({
|
|
11402
|
+
type: "line",
|
|
11403
|
+
x1: a.x,
|
|
11404
|
+
y1: a.y,
|
|
11405
|
+
x2: b.x,
|
|
11406
|
+
y2: b.y,
|
|
11407
|
+
color,
|
|
11408
|
+
lineWidth: 2
|
|
11409
|
+
});
|
|
11410
|
+
}
|
|
9922
11411
|
if (e.label) {
|
|
9923
11412
|
out.push({
|
|
9924
11413
|
type: "text",
|
|
@@ -9931,6 +11420,8 @@ var init_BiologyCanvas = __esm({
|
|
|
9931
11420
|
}
|
|
9932
11421
|
}
|
|
9933
11422
|
for (const n of nodes) {
|
|
11423
|
+
const state = n.state ?? "default";
|
|
11424
|
+
const muted = state === "muted";
|
|
9934
11425
|
out.push({
|
|
9935
11426
|
type: "circle",
|
|
9936
11427
|
x: n.x,
|
|
@@ -9938,23 +11429,197 @@ var init_BiologyCanvas = __esm({
|
|
|
9938
11429
|
radius: n.radius ?? 16,
|
|
9939
11430
|
color: n.color ?? "#16a34a",
|
|
9940
11431
|
fill: `${n.color ?? "#16a34a"}33`,
|
|
9941
|
-
id: n.id
|
|
11432
|
+
id: n.id,
|
|
11433
|
+
...muted ? { opacity: 0.35 } : {}
|
|
9942
11434
|
});
|
|
11435
|
+
if (state === "highlight") {
|
|
11436
|
+
out.push({
|
|
11437
|
+
type: "circle",
|
|
11438
|
+
x: n.x,
|
|
11439
|
+
y: n.y,
|
|
11440
|
+
radius: (n.radius ?? 16) + 4,
|
|
11441
|
+
color: "#f59e0b",
|
|
11442
|
+
lineWidth: 2
|
|
11443
|
+
});
|
|
11444
|
+
}
|
|
9943
11445
|
if (n.label) {
|
|
9944
11446
|
out.push({
|
|
9945
11447
|
type: "text",
|
|
9946
11448
|
x: n.x,
|
|
9947
11449
|
y: n.y + (n.radius ?? 16) + 14,
|
|
9948
11450
|
text: n.label,
|
|
11451
|
+
...muted ? { opacity: 0.35 } : {},
|
|
9949
11452
|
color: "#111827",
|
|
9950
11453
|
fontSize: 12,
|
|
9951
11454
|
align: "center"
|
|
9952
11455
|
});
|
|
9953
11456
|
}
|
|
9954
11457
|
}
|
|
11458
|
+
const stageCount = stages.length;
|
|
11459
|
+
if (stageCount > 0) {
|
|
11460
|
+
if (stageStyle === "ring") {
|
|
11461
|
+
const cx = width / 2;
|
|
11462
|
+
const cy = height / 2;
|
|
11463
|
+
const R = Math.min(width, height) / 2 - 48;
|
|
11464
|
+
const ringPoints = [];
|
|
11465
|
+
for (let i = 0; i < stageCount; i++) {
|
|
11466
|
+
const angleRad = (-90 + 360 * i / stageCount) * Math.PI / 180;
|
|
11467
|
+
ringPoints.push({ x: cx + R * Math.cos(angleRad), y: cy + R * Math.sin(angleRad) });
|
|
11468
|
+
}
|
|
11469
|
+
if (stageCount >= 2) {
|
|
11470
|
+
for (let i = 0; i < stageCount - 1; i++) {
|
|
11471
|
+
const p1 = ringPoints[i];
|
|
11472
|
+
const p2 = ringPoints[i + 1];
|
|
11473
|
+
const dx = p2.x - p1.x;
|
|
11474
|
+
const dy = p2.y - p1.y;
|
|
11475
|
+
const dist = Math.max(1e-6, Math.hypot(dx, dy));
|
|
11476
|
+
const ux = dx / dist;
|
|
11477
|
+
const uy = dy / dist;
|
|
11478
|
+
out.push({
|
|
11479
|
+
type: "arrow",
|
|
11480
|
+
x1: p1.x + ux * 46,
|
|
11481
|
+
y1: p1.y + uy * 46,
|
|
11482
|
+
x2: p2.x - ux * 46,
|
|
11483
|
+
y2: p2.y - uy * 46,
|
|
11484
|
+
color: "#94a3b8"
|
|
11485
|
+
});
|
|
11486
|
+
}
|
|
11487
|
+
}
|
|
11488
|
+
for (let i = 0; i < stageCount; i++) {
|
|
11489
|
+
const stage = stages[i];
|
|
11490
|
+
const state = stage.state ?? "pending";
|
|
11491
|
+
const fill = stage.color ?? BIO_STAGE_FILL[state];
|
|
11492
|
+
const w = Math.max(26, Math.min(84, stage.label.length * 6 + 10));
|
|
11493
|
+
const h = 18;
|
|
11494
|
+
const p = ringPoints[i];
|
|
11495
|
+
out.push({ type: "rect", x: p.x - w / 2, y: p.y - h / 2, width: w, height: h, color: fill, fill });
|
|
11496
|
+
out.push({ type: "text", x: p.x, y: p.y, text: stage.label, color: BIO_STAGE_TEXT[state], fontSize: 10, align: "center" });
|
|
11497
|
+
}
|
|
11498
|
+
} else {
|
|
11499
|
+
const stripY = height - 32;
|
|
11500
|
+
const slotW = (width - 16) / stageCount;
|
|
11501
|
+
const chipGeoms = [];
|
|
11502
|
+
for (let i = 0; i < stageCount; i++) {
|
|
11503
|
+
chipGeoms.push({ x: 8 + i * slotW + 5, w: slotW - 10 });
|
|
11504
|
+
}
|
|
11505
|
+
for (let i = 0; i < stageCount - 1; i++) {
|
|
11506
|
+
const midY = stripY + 13;
|
|
11507
|
+
out.push({
|
|
11508
|
+
type: "arrow",
|
|
11509
|
+
x1: chipGeoms[i].x + chipGeoms[i].w,
|
|
11510
|
+
y1: midY,
|
|
11511
|
+
x2: chipGeoms[i + 1].x,
|
|
11512
|
+
y2: midY,
|
|
11513
|
+
color: "#94a3b8"
|
|
11514
|
+
});
|
|
11515
|
+
}
|
|
11516
|
+
for (let i = 0; i < stageCount; i++) {
|
|
11517
|
+
const stage = stages[i];
|
|
11518
|
+
const state = stage.state ?? "pending";
|
|
11519
|
+
const fill = stage.color ?? BIO_STAGE_FILL[state];
|
|
11520
|
+
const g = chipGeoms[i];
|
|
11521
|
+
out.push({ type: "rect", x: g.x, y: stripY, width: g.w, height: 26, color: fill, fill });
|
|
11522
|
+
out.push({
|
|
11523
|
+
type: "text",
|
|
11524
|
+
x: g.x + g.w / 2,
|
|
11525
|
+
y: stripY + 13,
|
|
11526
|
+
text: stage.label,
|
|
11527
|
+
color: BIO_STAGE_TEXT[state],
|
|
11528
|
+
fontSize: 10,
|
|
11529
|
+
align: "center"
|
|
11530
|
+
});
|
|
11531
|
+
}
|
|
11532
|
+
}
|
|
11533
|
+
}
|
|
9955
11534
|
out.push(...shapes);
|
|
9956
11535
|
return out;
|
|
9957
|
-
}, [nodes, edges, shapes]);
|
|
11536
|
+
}, [nodes, edges, compartments, bands, stages, stageStyle, helix, shapes, width, height]);
|
|
11537
|
+
const drawables3D = React77.useMemo(() => {
|
|
11538
|
+
if (mode !== "3d") return [];
|
|
11539
|
+
if (shapes.length > 0) {
|
|
11540
|
+
biologyLog.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
|
|
11541
|
+
}
|
|
11542
|
+
if (compartments.length > 0 || bands.length > 0 || stages.length > 0 || helix) {
|
|
11543
|
+
biologyLog.debug("2D-only families ignored in 3D mode (pixel-authored 2D vocabulary)", {
|
|
11544
|
+
compartments: compartments.length,
|
|
11545
|
+
bands: bands.length,
|
|
11546
|
+
stages: stages.length,
|
|
11547
|
+
helix: helix != null
|
|
11548
|
+
});
|
|
11549
|
+
}
|
|
11550
|
+
if (animate) {
|
|
11551
|
+
biologyLog.debug("animate ignored in 3D mode (motion is entity-state driven)");
|
|
11552
|
+
}
|
|
11553
|
+
const out = [];
|
|
11554
|
+
const labelColor = labelColorForBackground(backgroundColor);
|
|
11555
|
+
const nodeById = /* @__PURE__ */ new Map();
|
|
11556
|
+
for (const n of nodes) {
|
|
11557
|
+
if (n.id) nodeById.set(n.id, n);
|
|
11558
|
+
}
|
|
11559
|
+
for (const e of edges) {
|
|
11560
|
+
const a = nodeById.get(e.from);
|
|
11561
|
+
const b = nodeById.get(e.to);
|
|
11562
|
+
if (!a || !b) continue;
|
|
11563
|
+
const edgeRadius = Math.max(0.04, Math.min(a.radius ?? 0.5, b.radius ?? 0.5) * 0.12);
|
|
11564
|
+
const edge = cylinderBetween([a.x, a.y, a.z ?? 0], [b.x, b.y, b.z ?? 0], edgeRadius, e.color ?? "#9ca3af");
|
|
11565
|
+
if (edge) out.push(edge);
|
|
11566
|
+
if (e.label) {
|
|
11567
|
+
out.push(
|
|
11568
|
+
billboardLabel(
|
|
11569
|
+
e.label,
|
|
11570
|
+
(a.x + b.x) / 2,
|
|
11571
|
+
(a.y + b.y) / 2,
|
|
11572
|
+
((a.z ?? 0) + (b.z ?? 0)) / 2,
|
|
11573
|
+
{ color: labelColor }
|
|
11574
|
+
)
|
|
11575
|
+
);
|
|
11576
|
+
}
|
|
11577
|
+
}
|
|
11578
|
+
for (const n of nodes) {
|
|
11579
|
+
const radius = n.radius ?? 0.5;
|
|
11580
|
+
const nz = n.z ?? 0;
|
|
11581
|
+
out.push(meshSphere(n.id, n.x, n.y, nz, radius, n.color ?? "#16a34a", { shape: n.shape, ...n.opacity !== void 0 ? { opacity: n.opacity } : {} }));
|
|
11582
|
+
if (n.label) {
|
|
11583
|
+
out.push(billboardLabel(n.label, n.x, n.y, nz + radius, { color: labelColor }));
|
|
11584
|
+
}
|
|
11585
|
+
}
|
|
11586
|
+
if (helix3d) {
|
|
11587
|
+
out.push(...helixDrawables(helix3d, { labelColor }));
|
|
11588
|
+
}
|
|
11589
|
+
return out;
|
|
11590
|
+
}, [mode, nodes, edges, shapes, compartments, bands, stages, helix, helix3d, animate, backgroundColor]);
|
|
11591
|
+
const nodeIndexById = React77.useMemo(() => {
|
|
11592
|
+
const m = /* @__PURE__ */ new Map();
|
|
11593
|
+
(helix3d?.rungs ?? []).forEach((rung, i) => {
|
|
11594
|
+
if (rung.id) m.set(rung.id, i);
|
|
11595
|
+
});
|
|
11596
|
+
nodes.forEach((n, i) => {
|
|
11597
|
+
if (n.id) m.set(n.id, i);
|
|
11598
|
+
});
|
|
11599
|
+
return m;
|
|
11600
|
+
}, [nodes, helix3d]);
|
|
11601
|
+
if (mode === "3d") {
|
|
11602
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
11603
|
+
LearningScene3D,
|
|
11604
|
+
{
|
|
11605
|
+
className,
|
|
11606
|
+
width,
|
|
11607
|
+
height,
|
|
11608
|
+
title,
|
|
11609
|
+
backgroundColor,
|
|
11610
|
+
drawables: drawables3D,
|
|
11611
|
+
camera,
|
|
11612
|
+
lighting,
|
|
11613
|
+
post,
|
|
11614
|
+
showGrid,
|
|
11615
|
+
shadows,
|
|
11616
|
+
interactive,
|
|
11617
|
+
isLoading,
|
|
11618
|
+
error,
|
|
11619
|
+
onItemClick: get3DClickPayload(onShapeClick, nodeIndexById)
|
|
11620
|
+
}
|
|
11621
|
+
);
|
|
11622
|
+
}
|
|
9958
11623
|
return /* @__PURE__ */ jsxRuntime.jsx(exports.Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(exports.VStack, { gap: "sm", children: [
|
|
9959
11624
|
title ? /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "h4", children: title }) : null,
|
|
9960
11625
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -9964,7 +11629,9 @@ var init_BiologyCanvas = __esm({
|
|
|
9964
11629
|
height,
|
|
9965
11630
|
backgroundColor,
|
|
9966
11631
|
shapes: derivedShapes,
|
|
9967
|
-
|
|
11632
|
+
readouts,
|
|
11633
|
+
traces,
|
|
11634
|
+
interactive: interactive ?? false,
|
|
9968
11635
|
animate,
|
|
9969
11636
|
onShapeClick,
|
|
9970
11637
|
isLoading,
|
|
@@ -17356,9 +19023,10 @@ function Canvas({
|
|
|
17356
19023
|
drawables: [...drawables ?? [], ...childDrawables],
|
|
17357
19024
|
isLoading,
|
|
17358
19025
|
cameraMode: to3DCameraMode(camera?.mode),
|
|
17359
|
-
...zoom !== void 0 ? {
|
|
19026
|
+
...zoom !== void 0 ? { zoom } : {},
|
|
17360
19027
|
...camera?.fov !== void 0 ? { fov: camera.fov } : {},
|
|
17361
19028
|
...camera?.azimuth !== void 0 ? { azimuth: camera.azimuth } : {},
|
|
19029
|
+
...camera?.elevation !== void 0 ? { elevation: camera.elevation } : {},
|
|
17362
19030
|
...camera?.target !== void 0 ? { followTarget: camera.target } : {},
|
|
17363
19031
|
unitScale,
|
|
17364
19032
|
backgroundColor,
|
|
@@ -17381,7 +19049,7 @@ function Canvas({
|
|
|
17381
19049
|
keyUpMap
|
|
17382
19050
|
};
|
|
17383
19051
|
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
17384
|
-
/* @__PURE__ */ jsxRuntime.jsx(React77.Suspense, { fallback: null, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
19052
|
+
/* @__PURE__ */ jsxRuntime.jsx(React77.Suspense, { fallback: null, children: /* @__PURE__ */ jsxRuntime.jsx(Canvas3DHost2, { ...props3d }) }),
|
|
17385
19053
|
React77__namespace.Children.count(children) > 0 && /* @__PURE__ */ jsxRuntime.jsx(DrawableRegistryContext.Provider, { value: registerChildDrawable, children: /* @__PURE__ */ jsxRuntime.jsx("div", { "aria-hidden": "true", style: { position: "absolute", width: 0, height: 0, overflow: "hidden" }, children }) })
|
|
17386
19054
|
] });
|
|
17387
19055
|
}
|
|
@@ -17411,13 +19079,13 @@ function Canvas({
|
|
|
17411
19079
|
}
|
|
17412
19080
|
);
|
|
17413
19081
|
}
|
|
17414
|
-
var
|
|
19082
|
+
var Canvas3DHost2, canvasLog;
|
|
17415
19083
|
var init_Canvas = __esm({
|
|
17416
19084
|
"components/game/molecules/Canvas.tsx"() {
|
|
17417
19085
|
"use client";
|
|
17418
19086
|
init_registry();
|
|
17419
19087
|
init_Canvas2D();
|
|
17420
|
-
|
|
19088
|
+
Canvas3DHost2 = React77.lazy(
|
|
17421
19089
|
() => import('@almadar/ui/components/molecules/game/three').then((m) => ({ default: m.Canvas3DHost }))
|
|
17422
19090
|
);
|
|
17423
19091
|
canvasLog = logger.createLogger("almadar:ui:game-canvas");
|
|
@@ -19144,24 +20812,55 @@ var init_ChatBar = __esm({
|
|
|
19144
20812
|
ChatBar.displayName = "ChatBar";
|
|
19145
20813
|
}
|
|
19146
20814
|
});
|
|
19147
|
-
|
|
20815
|
+
function bondPerpendicular(a, b) {
|
|
20816
|
+
const dx = b[0] - a[0];
|
|
20817
|
+
const dy = b[1] - a[1];
|
|
20818
|
+
const px = dy;
|
|
20819
|
+
const py = -dx;
|
|
20820
|
+
const len = Math.sqrt(px * px + py * py);
|
|
20821
|
+
if (len < 1e-6) return [1, 0, 0];
|
|
20822
|
+
return [px / len, py / len, 0];
|
|
20823
|
+
}
|
|
20824
|
+
var chemistryLog, CHEM_BOND_STATE_COLOR, LONE_PAIR_ANGLES; exports.ChemistryCanvas = void 0;
|
|
19148
20825
|
var init_ChemistryCanvas = __esm({
|
|
19149
20826
|
"components/learning/molecules/ChemistryCanvas.tsx"() {
|
|
19150
20827
|
"use client";
|
|
19151
20828
|
init_atoms();
|
|
19152
20829
|
init_Stack();
|
|
19153
20830
|
init_LearningCanvas();
|
|
20831
|
+
init_learningScene3D();
|
|
20832
|
+
chemistryLog = logger.createLogger("almadar:ui:chemistry-canvas");
|
|
20833
|
+
CHEM_BOND_STATE_COLOR = {
|
|
20834
|
+
default: "#6b7280",
|
|
20835
|
+
forming: "#16a34a",
|
|
20836
|
+
breaking: "#dc2626",
|
|
20837
|
+
highlight: "#f59e0b"
|
|
20838
|
+
};
|
|
20839
|
+
LONE_PAIR_ANGLES = [-90, 0, 90, 180];
|
|
19154
20840
|
exports.ChemistryCanvas = ({
|
|
19155
20841
|
className,
|
|
19156
20842
|
width = 600,
|
|
19157
20843
|
height = 400,
|
|
19158
20844
|
title,
|
|
19159
20845
|
backgroundColor,
|
|
20846
|
+
mode = "2d",
|
|
20847
|
+
camera,
|
|
20848
|
+
lighting,
|
|
20849
|
+
post,
|
|
19160
20850
|
atoms = [],
|
|
19161
20851
|
bonds = [],
|
|
19162
20852
|
arrows = [],
|
|
20853
|
+
bondStyle = "thick",
|
|
20854
|
+
containers = [],
|
|
20855
|
+
equation,
|
|
20856
|
+
equationColor,
|
|
20857
|
+
lattice3d,
|
|
19163
20858
|
shapes = [],
|
|
19164
|
-
|
|
20859
|
+
readouts,
|
|
20860
|
+
traces,
|
|
20861
|
+
showGrid,
|
|
20862
|
+
shadows,
|
|
20863
|
+
interactive,
|
|
19165
20864
|
animate = false,
|
|
19166
20865
|
onShapeClick,
|
|
19167
20866
|
isLoading,
|
|
@@ -19173,21 +20872,118 @@ var init_ChemistryCanvas = __esm({
|
|
|
19173
20872
|
for (const a of atoms) {
|
|
19174
20873
|
if (a.id) atomById.set(a.id, a);
|
|
19175
20874
|
}
|
|
20875
|
+
for (const c of containers) {
|
|
20876
|
+
const color = c.color ?? "#64748b";
|
|
20877
|
+
if (c.level != null) {
|
|
20878
|
+
const lv = c.level;
|
|
20879
|
+
out.push({
|
|
20880
|
+
type: "rect",
|
|
20881
|
+
x: c.x + 1,
|
|
20882
|
+
y: c.y + c.height * (1 - lv),
|
|
20883
|
+
width: c.width - 2,
|
|
20884
|
+
height: c.height * lv - 1,
|
|
20885
|
+
color: c.levelColor ?? "#60a5fa",
|
|
20886
|
+
fill: c.levelColor ?? "#60a5fa",
|
|
20887
|
+
opacity: 0.5
|
|
20888
|
+
});
|
|
20889
|
+
}
|
|
20890
|
+
out.push({
|
|
20891
|
+
type: "rect",
|
|
20892
|
+
x: c.x,
|
|
20893
|
+
y: c.y,
|
|
20894
|
+
width: c.width,
|
|
20895
|
+
height: c.height,
|
|
20896
|
+
color,
|
|
20897
|
+
fill: c.fill,
|
|
20898
|
+
lineWidth: c.lineWidth ?? 2
|
|
20899
|
+
});
|
|
20900
|
+
const divider = c.divider ?? "none";
|
|
20901
|
+
if (divider !== "none") {
|
|
20902
|
+
out.push({
|
|
20903
|
+
type: "line",
|
|
20904
|
+
x1: c.x + c.width / 2,
|
|
20905
|
+
y1: c.y,
|
|
20906
|
+
x2: c.x + c.width / 2,
|
|
20907
|
+
y2: c.y + c.height,
|
|
20908
|
+
color: c.dividerColor ?? color,
|
|
20909
|
+
...divider === "dashed" || divider === "dotted" ? { dash: divider } : {}
|
|
20910
|
+
});
|
|
20911
|
+
}
|
|
20912
|
+
if (c.leftLabel) {
|
|
20913
|
+
out.push({
|
|
20914
|
+
type: "text",
|
|
20915
|
+
x: c.x + c.width * 0.25,
|
|
20916
|
+
y: c.y + 12,
|
|
20917
|
+
text: c.leftLabel,
|
|
20918
|
+
color: "#374151",
|
|
20919
|
+
fontSize: 11,
|
|
20920
|
+
align: "center"
|
|
20921
|
+
});
|
|
20922
|
+
}
|
|
20923
|
+
if (c.rightLabel) {
|
|
20924
|
+
out.push({
|
|
20925
|
+
type: "text",
|
|
20926
|
+
x: c.x + c.width * 0.75,
|
|
20927
|
+
y: c.y + 12,
|
|
20928
|
+
text: c.rightLabel,
|
|
20929
|
+
color: "#374151",
|
|
20930
|
+
fontSize: 11,
|
|
20931
|
+
align: "center"
|
|
20932
|
+
});
|
|
20933
|
+
}
|
|
20934
|
+
if (c.label) {
|
|
20935
|
+
out.push({
|
|
20936
|
+
type: "text",
|
|
20937
|
+
x: c.x + c.width / 2,
|
|
20938
|
+
y: c.y + c.height + 12,
|
|
20939
|
+
text: c.label,
|
|
20940
|
+
color: "#111827",
|
|
20941
|
+
fontSize: 12,
|
|
20942
|
+
align: "center"
|
|
20943
|
+
});
|
|
20944
|
+
}
|
|
20945
|
+
}
|
|
19176
20946
|
for (const b of bonds) {
|
|
19177
20947
|
const a = atomById.get(b.from);
|
|
19178
20948
|
const c = atomById.get(b.to);
|
|
19179
20949
|
if (!a || !c) continue;
|
|
19180
|
-
const
|
|
19181
|
-
const
|
|
19182
|
-
|
|
19183
|
-
|
|
19184
|
-
|
|
19185
|
-
|
|
19186
|
-
|
|
19187
|
-
|
|
19188
|
-
|
|
19189
|
-
|
|
19190
|
-
|
|
20950
|
+
const state = b.state ?? "default";
|
|
20951
|
+
const color = b.color ?? CHEM_BOND_STATE_COLOR[state];
|
|
20952
|
+
const dash = state === "forming" || state === "breaking" ? "dashed" : void 0;
|
|
20953
|
+
if (bondStyle === "parallel") {
|
|
20954
|
+
const dx = c.x - a.x;
|
|
20955
|
+
const dy = c.y - a.y;
|
|
20956
|
+
const dist = Math.max(1e-6, Math.hypot(dx, dy));
|
|
20957
|
+
const ux = dx / dist;
|
|
20958
|
+
const uy = dy / dist;
|
|
20959
|
+
const px = -uy;
|
|
20960
|
+
const py = ux;
|
|
20961
|
+
const offsets = b.type === "double" ? [-3, 3] : b.type === "triple" ? [-4, 0, 4] : [0];
|
|
20962
|
+
for (const off of offsets) {
|
|
20963
|
+
out.push({
|
|
20964
|
+
type: "line",
|
|
20965
|
+
x1: a.x + px * off,
|
|
20966
|
+
y1: a.y + py * off,
|
|
20967
|
+
x2: c.x + px * off,
|
|
20968
|
+
y2: c.y + py * off,
|
|
20969
|
+
color,
|
|
20970
|
+
lineWidth: 2,
|
|
20971
|
+
...dash ? { dash } : {}
|
|
20972
|
+
});
|
|
20973
|
+
}
|
|
20974
|
+
} else {
|
|
20975
|
+
const strokeWidth = b.type === "double" ? 4 : b.type === "triple" ? 6 : 2;
|
|
20976
|
+
out.push({
|
|
20977
|
+
type: "line",
|
|
20978
|
+
x1: a.x,
|
|
20979
|
+
y1: a.y,
|
|
20980
|
+
x2: c.x,
|
|
20981
|
+
y2: c.y,
|
|
20982
|
+
color,
|
|
20983
|
+
lineWidth: strokeWidth,
|
|
20984
|
+
...dash ? { dash } : {}
|
|
20985
|
+
});
|
|
20986
|
+
}
|
|
19191
20987
|
}
|
|
19192
20988
|
for (const a of arrows) {
|
|
19193
20989
|
const angle = (a.angle ?? 0) * (Math.PI / 180);
|
|
@@ -19236,10 +21032,142 @@ var init_ChemistryCanvas = __esm({
|
|
|
19236
21032
|
align: "center"
|
|
19237
21033
|
});
|
|
19238
21034
|
}
|
|
21035
|
+
const r = a.radius ?? 14;
|
|
21036
|
+
if (a.charge) {
|
|
21037
|
+
out.push({
|
|
21038
|
+
type: "text",
|
|
21039
|
+
x: a.x + r * 0.85,
|
|
21040
|
+
y: a.y - r * 0.85,
|
|
21041
|
+
text: a.charge,
|
|
21042
|
+
color: "#111827",
|
|
21043
|
+
fontSize: 9,
|
|
21044
|
+
align: "left"
|
|
21045
|
+
});
|
|
21046
|
+
}
|
|
21047
|
+
const lonePairs = Math.max(0, Math.min(4, a.lonePairs ?? 0));
|
|
21048
|
+
for (let k = 0; k < lonePairs; k++) {
|
|
21049
|
+
const angleRad = LONE_PAIR_ANGLES[k] * Math.PI / 180;
|
|
21050
|
+
const cx = a.x + (r + 6) * Math.cos(angleRad);
|
|
21051
|
+
const cy = a.y + (r + 6) * Math.sin(angleRad);
|
|
21052
|
+
const perpX = -Math.sin(angleRad);
|
|
21053
|
+
const perpY = Math.cos(angleRad);
|
|
21054
|
+
for (const sign of [1, -1]) {
|
|
21055
|
+
out.push({
|
|
21056
|
+
type: "circle",
|
|
21057
|
+
x: cx + perpX * 2.5 * sign,
|
|
21058
|
+
y: cy + perpY * 2.5 * sign,
|
|
21059
|
+
radius: 1.5,
|
|
21060
|
+
color: "#374151",
|
|
21061
|
+
fill: "#374151"
|
|
21062
|
+
});
|
|
21063
|
+
}
|
|
21064
|
+
}
|
|
21065
|
+
}
|
|
21066
|
+
if (equation) {
|
|
21067
|
+
out.push({
|
|
21068
|
+
type: "text",
|
|
21069
|
+
x: width / 2,
|
|
21070
|
+
y: 14,
|
|
21071
|
+
text: equation,
|
|
21072
|
+
color: equationColor ?? "#111827",
|
|
21073
|
+
fontSize: 13,
|
|
21074
|
+
align: "center"
|
|
21075
|
+
});
|
|
19239
21076
|
}
|
|
19240
21077
|
out.push(...shapes);
|
|
19241
21078
|
return out;
|
|
19242
|
-
}, [atoms, bonds, arrows, shapes]);
|
|
21079
|
+
}, [atoms, bonds, arrows, bondStyle, containers, equation, equationColor, shapes, width]);
|
|
21080
|
+
const drawables3D = React77.useMemo(() => {
|
|
21081
|
+
if (mode !== "3d") return [];
|
|
21082
|
+
if (shapes.length > 0) {
|
|
21083
|
+
chemistryLog.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
|
|
21084
|
+
}
|
|
21085
|
+
if (containers.length > 0) {
|
|
21086
|
+
chemistryLog.debug("containers ignored in 3D mode (pixel-authored 2D vocabulary)", { count: containers.length });
|
|
21087
|
+
}
|
|
21088
|
+
if (animate) {
|
|
21089
|
+
chemistryLog.debug("animate ignored in 3D mode (motion is entity-state driven)");
|
|
21090
|
+
}
|
|
21091
|
+
const out = [];
|
|
21092
|
+
const labelColor = labelColorForBackground(backgroundColor);
|
|
21093
|
+
const atomById = /* @__PURE__ */ new Map();
|
|
21094
|
+
for (const a of atoms) {
|
|
21095
|
+
if (a.id) atomById.set(a.id, a);
|
|
21096
|
+
}
|
|
21097
|
+
for (const b of bonds) {
|
|
21098
|
+
const a = atomById.get(b.from);
|
|
21099
|
+
const c = atomById.get(b.to);
|
|
21100
|
+
if (!a || !c) continue;
|
|
21101
|
+
const color = b.color ?? "#6b7280";
|
|
21102
|
+
const from = [a.x, a.y, a.z ?? 0];
|
|
21103
|
+
const to = [c.x, c.y, c.z ?? 0];
|
|
21104
|
+
const perp = bondPerpendicular(from, to);
|
|
21105
|
+
const bondRadius = Math.max(0.06, Math.min(a.radius ?? 0.45, c.radius ?? 0.45) * 0.22);
|
|
21106
|
+
const step = bondRadius * 2.2;
|
|
21107
|
+
const offsets = b.type === "double" ? [-step, step] : b.type === "triple" ? [-step, 0, step] : [0];
|
|
21108
|
+
for (const off of offsets) {
|
|
21109
|
+
const bond = cylinderBetween(
|
|
21110
|
+
[from[0] + perp[0] * off, from[1] + perp[1] * off, from[2] + perp[2] * off],
|
|
21111
|
+
[to[0] + perp[0] * off, to[1] + perp[1] * off, to[2] + perp[2] * off],
|
|
21112
|
+
bondRadius,
|
|
21113
|
+
color
|
|
21114
|
+
);
|
|
21115
|
+
if (bond) out.push(bond);
|
|
21116
|
+
}
|
|
21117
|
+
}
|
|
21118
|
+
for (const a of arrows) {
|
|
21119
|
+
const angle = (a.angle ?? 0) * (Math.PI / 180);
|
|
21120
|
+
const len = a.length ?? 60;
|
|
21121
|
+
const x2 = a.x + Math.cos(angle) * len;
|
|
21122
|
+
const y2 = a.y + Math.sin(angle) * len;
|
|
21123
|
+
const arrow = arrowBetween([a.x, a.y, 0], [x2, y2, 0], a.color ?? "#dc2626");
|
|
21124
|
+
if (arrow) out.push(arrow);
|
|
21125
|
+
if (a.label) {
|
|
21126
|
+
out.push(billboardLabel(a.label, (a.x + x2) / 2, (a.y + y2) / 2, 0, { color: labelColor }));
|
|
21127
|
+
}
|
|
21128
|
+
}
|
|
21129
|
+
for (const a of atoms) {
|
|
21130
|
+
const radius = a.radius ?? 0.45;
|
|
21131
|
+
const az = a.z ?? 0;
|
|
21132
|
+
out.push(meshSphere(a.id, a.x, a.y, az, radius, a.color ?? "#2563eb"));
|
|
21133
|
+
if (a.element) {
|
|
21134
|
+
out.push(billboardLabel(a.element, a.x, a.y, az + radius, { color: labelColor }));
|
|
21135
|
+
}
|
|
21136
|
+
}
|
|
21137
|
+
if (lattice3d) {
|
|
21138
|
+
out.push(...latticeDrawables(lattice3d, { labelColor }));
|
|
21139
|
+
}
|
|
21140
|
+
return out;
|
|
21141
|
+
}, [mode, atoms, bonds, arrows, shapes, containers, lattice3d, animate, backgroundColor]);
|
|
21142
|
+
const atomIndexById = React77.useMemo(() => {
|
|
21143
|
+
const m = /* @__PURE__ */ new Map();
|
|
21144
|
+
atoms.forEach((a, i) => {
|
|
21145
|
+
if (a.id) m.set(a.id, i);
|
|
21146
|
+
});
|
|
21147
|
+
return m;
|
|
21148
|
+
}, [atoms]);
|
|
21149
|
+
if (mode === "3d") {
|
|
21150
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
21151
|
+
LearningScene3D,
|
|
21152
|
+
{
|
|
21153
|
+
className,
|
|
21154
|
+
width,
|
|
21155
|
+
height,
|
|
21156
|
+
title,
|
|
21157
|
+
backgroundColor,
|
|
21158
|
+
drawables: drawables3D,
|
|
21159
|
+
camera,
|
|
21160
|
+
lighting,
|
|
21161
|
+
post,
|
|
21162
|
+
showGrid,
|
|
21163
|
+
shadows,
|
|
21164
|
+
interactive,
|
|
21165
|
+
isLoading,
|
|
21166
|
+
error,
|
|
21167
|
+
onItemClick: get3DClickPayload(onShapeClick, atomIndexById)
|
|
21168
|
+
}
|
|
21169
|
+
);
|
|
21170
|
+
}
|
|
19243
21171
|
return /* @__PURE__ */ jsxRuntime.jsx(exports.Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(exports.VStack, { gap: "sm", children: [
|
|
19244
21172
|
title ? /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "h4", children: title }) : null,
|
|
19245
21173
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -19249,7 +21177,9 @@ var init_ChemistryCanvas = __esm({
|
|
|
19249
21177
|
height,
|
|
19250
21178
|
backgroundColor,
|
|
19251
21179
|
shapes: derivedShapes,
|
|
19252
|
-
|
|
21180
|
+
readouts,
|
|
21181
|
+
traces,
|
|
21182
|
+
interactive: interactive ?? false,
|
|
19253
21183
|
animate,
|
|
19254
21184
|
onShapeClick,
|
|
19255
21185
|
isLoading,
|
|
@@ -29161,6 +31091,10 @@ var init_molecules = __esm({
|
|
|
29161
31091
|
init_GameShell();
|
|
29162
31092
|
}
|
|
29163
31093
|
});
|
|
31094
|
+
function formatTick(v) {
|
|
31095
|
+
if (Number.isInteger(v)) return String(v);
|
|
31096
|
+
return v.toFixed(2).replace(/0+$/, "").replace(/\.$/, "");
|
|
31097
|
+
}
|
|
29164
31098
|
exports.MathCanvas = void 0;
|
|
29165
31099
|
var init_MathCanvas = __esm({
|
|
29166
31100
|
"components/learning/molecules/MathCanvas.tsx"() {
|
|
@@ -29180,10 +31114,19 @@ var init_MathCanvas = __esm({
|
|
|
29180
31114
|
showAxes = true,
|
|
29181
31115
|
showGrid = true,
|
|
29182
31116
|
gridStep = 1,
|
|
31117
|
+
showTickLabels = false,
|
|
31118
|
+
showCurveLabels = false,
|
|
29183
31119
|
curves = [],
|
|
29184
31120
|
points = [],
|
|
29185
31121
|
vectors = [],
|
|
31122
|
+
regions = [],
|
|
31123
|
+
bars = [],
|
|
31124
|
+
guides = [],
|
|
31125
|
+
angles = [],
|
|
31126
|
+
hops = [],
|
|
29186
31127
|
shapes = [],
|
|
31128
|
+
readouts,
|
|
31129
|
+
traces,
|
|
29187
31130
|
interactive = false,
|
|
29188
31131
|
animate = false,
|
|
29189
31132
|
onShapeClick,
|
|
@@ -29197,6 +31140,8 @@ var init_MathCanvas = __esm({
|
|
|
29197
31140
|
const plotH = height - margin * 2;
|
|
29198
31141
|
const mapX = (x) => margin + (x - xMin) / (xMax - xMin) * plotW;
|
|
29199
31142
|
const mapY = (y) => height - (margin + (y - yMin) / (yMax - yMin) * plotH);
|
|
31143
|
+
const xAxisY = Math.max(margin, Math.min(height - margin, mapY(0)));
|
|
31144
|
+
const yAxisX = Math.max(margin, Math.min(width - margin, mapX(0)));
|
|
29200
31145
|
if (showGrid) {
|
|
29201
31146
|
for (let x = Math.ceil(xMin / gridStep) * gridStep; x <= xMax; x += gridStep) {
|
|
29202
31147
|
const px = mapX(x);
|
|
@@ -29207,14 +31152,99 @@ var init_MathCanvas = __esm({
|
|
|
29207
31152
|
out.push({ type: "line", x1: margin, y1: py, x2: width - margin, y2: py, color: "#e5e7eb", lineWidth: 1 });
|
|
29208
31153
|
}
|
|
29209
31154
|
}
|
|
31155
|
+
if (showTickLabels) {
|
|
31156
|
+
const labelEveryX = Math.max(1, Math.ceil((xMax - xMin) / gridStep / Math.floor(plotW / 40)));
|
|
31157
|
+
let kx = 0;
|
|
31158
|
+
for (let x = Math.ceil(xMin / gridStep) * gridStep; x <= xMax; x += gridStep, kx++) {
|
|
31159
|
+
if (kx % labelEveryX === 0 && x !== 0) {
|
|
31160
|
+
out.push({ type: "text", x: mapX(x), y: xAxisY + 12, text: formatTick(x), color: "#6b7280", fontSize: 10, align: "center" });
|
|
31161
|
+
}
|
|
31162
|
+
}
|
|
31163
|
+
const labelEveryY = Math.max(1, Math.ceil((yMax - yMin) / gridStep / Math.floor(plotH / 28)));
|
|
31164
|
+
let ky = 0;
|
|
31165
|
+
for (let y = Math.ceil(yMin / gridStep) * gridStep; y <= yMax; y += gridStep, ky++) {
|
|
31166
|
+
if (ky % labelEveryY === 0 && y !== 0) {
|
|
31167
|
+
out.push({ type: "text", x: yAxisX - 6, y: mapY(y), text: formatTick(y), color: "#6b7280", fontSize: 10, align: "right" });
|
|
31168
|
+
}
|
|
31169
|
+
}
|
|
31170
|
+
if (xMin <= 0 && xMax >= 0 && yMin <= 0 && yMax >= 0) {
|
|
31171
|
+
out.push({ type: "text", x: yAxisX - 6, y: xAxisY + 12, text: "0", color: "#6b7280", fontSize: 10, align: "right" });
|
|
31172
|
+
}
|
|
31173
|
+
}
|
|
31174
|
+
for (const region of regions) {
|
|
31175
|
+
if (!region.samples || region.samples.length === 0) continue;
|
|
31176
|
+
const baseline = region.baseline ?? 0;
|
|
31177
|
+
const clampedPoint = (p) => ({
|
|
31178
|
+
x: mapX(Math.min(xMax, Math.max(xMin, p.x))),
|
|
31179
|
+
y: mapY(Math.min(yMax, Math.max(yMin, p.y)))
|
|
31180
|
+
});
|
|
31181
|
+
const upper = region.samples.map(clampedPoint);
|
|
31182
|
+
const first = region.samples[0];
|
|
31183
|
+
const last = region.samples[region.samples.length - 1];
|
|
31184
|
+
const closing = region.samples2 && region.samples2.length > 0 ? [...region.samples2].reverse().map(clampedPoint) : [clampedPoint({ x: last.x, y: baseline }), clampedPoint({ x: first.x, y: baseline })];
|
|
31185
|
+
const color = region.color ?? "#2563eb";
|
|
31186
|
+
out.push({
|
|
31187
|
+
type: "polygon",
|
|
31188
|
+
points: [...upper, ...closing],
|
|
31189
|
+
fill: color,
|
|
31190
|
+
color,
|
|
31191
|
+
opacity: region.opacity ?? 0.2,
|
|
31192
|
+
lineWidth: 1
|
|
31193
|
+
});
|
|
31194
|
+
if (region.label) {
|
|
31195
|
+
const mid = Math.floor(region.samples.length / 2);
|
|
31196
|
+
out.push({
|
|
31197
|
+
type: "text",
|
|
31198
|
+
x: mapX((first.x + last.x) / 2),
|
|
31199
|
+
y: (mapY(region.samples[mid].y) + mapY(baseline)) / 2,
|
|
31200
|
+
text: region.label,
|
|
31201
|
+
color: "#111827",
|
|
31202
|
+
fontSize: 11
|
|
31203
|
+
});
|
|
31204
|
+
}
|
|
31205
|
+
}
|
|
31206
|
+
for (const bar of bars) {
|
|
31207
|
+
if (bar.x + bar.width < xMin || bar.x > xMax) continue;
|
|
31208
|
+
const y0 = bar.y0 ?? 0;
|
|
31209
|
+
const color = bar.color ?? "#93c5fd";
|
|
31210
|
+
out.push({
|
|
31211
|
+
type: "rect",
|
|
31212
|
+
x: mapX(bar.x),
|
|
31213
|
+
y: mapY(Math.max(y0, bar.y1)),
|
|
31214
|
+
width: mapX(bar.x + bar.width) - mapX(bar.x),
|
|
31215
|
+
height: Math.abs(mapY(bar.y1) - mapY(y0)),
|
|
31216
|
+
color,
|
|
31217
|
+
fill: color,
|
|
31218
|
+
opacity: bar.opacity ?? 0.5,
|
|
31219
|
+
lineWidth: 1
|
|
31220
|
+
});
|
|
31221
|
+
}
|
|
29210
31222
|
if (showAxes) {
|
|
29211
|
-
const xAxisY = Math.max(margin, Math.min(height - margin, mapY(0)));
|
|
29212
|
-
const yAxisX = Math.max(margin, Math.min(width - margin, mapX(0)));
|
|
29213
31223
|
out.push({ type: "line", x1: margin, y1: xAxisY, x2: width - margin, y2: xAxisY, color: "#374151", lineWidth: 2 });
|
|
29214
31224
|
out.push({ type: "line", x1: yAxisX, y1: margin, x2: yAxisX, y2: height - margin, color: "#374151", lineWidth: 2 });
|
|
29215
31225
|
}
|
|
31226
|
+
for (const guide of guides) {
|
|
31227
|
+
const color = guide.color ?? "#9ca3af";
|
|
31228
|
+
const dash = guide.dash ?? "dashed";
|
|
31229
|
+
if (guide.kind === "vline") {
|
|
31230
|
+
if (guide.at < xMin || guide.at > xMax) continue;
|
|
31231
|
+
const px = mapX(guide.at);
|
|
31232
|
+
out.push({ type: "line", x1: px, y1: margin, x2: px, y2: height - margin, color, dash });
|
|
31233
|
+
if (guide.label) {
|
|
31234
|
+
out.push({ type: "text", x: px + 4, y: margin + 10, text: guide.label, color: "#111827", fontSize: 11 });
|
|
31235
|
+
}
|
|
31236
|
+
} else {
|
|
31237
|
+
if (guide.at < yMin || guide.at > yMax) continue;
|
|
31238
|
+
const py = mapY(guide.at);
|
|
31239
|
+
out.push({ type: "line", x1: margin, y1: py, x2: width - margin, y2: py, color, dash });
|
|
31240
|
+
if (guide.label) {
|
|
31241
|
+
out.push({ type: "text", x: width - margin - 4, y: py - 8, text: guide.label, color: "#111827", fontSize: 11, align: "right" });
|
|
31242
|
+
}
|
|
31243
|
+
}
|
|
31244
|
+
}
|
|
29216
31245
|
for (const curve of curves) {
|
|
29217
31246
|
if (!curve.samples || curve.samples.length < 2) continue;
|
|
31247
|
+
let lastInRange;
|
|
29218
31248
|
for (let i = 1; i < curve.samples.length; i++) {
|
|
29219
31249
|
const a = curve.samples[i - 1];
|
|
29220
31250
|
const b = curve.samples[i];
|
|
@@ -29226,19 +31256,97 @@ var init_MathCanvas = __esm({
|
|
|
29226
31256
|
x2: mapX(b.x),
|
|
29227
31257
|
y2: mapY(b.y),
|
|
29228
31258
|
color: curve.color ?? "#2563eb",
|
|
29229
|
-
lineWidth: 2
|
|
31259
|
+
lineWidth: 2,
|
|
31260
|
+
dash: curve.dash
|
|
31261
|
+
});
|
|
31262
|
+
lastInRange = b;
|
|
31263
|
+
}
|
|
31264
|
+
if (showCurveLabels && curve.label && lastInRange) {
|
|
31265
|
+
out.push({
|
|
31266
|
+
type: "text",
|
|
31267
|
+
x: mapX(lastInRange.x) + 6,
|
|
31268
|
+
y: mapY(lastInRange.y) - 6,
|
|
31269
|
+
text: curve.label,
|
|
31270
|
+
color: curve.color ?? "#2563eb",
|
|
31271
|
+
fontSize: 11
|
|
31272
|
+
});
|
|
31273
|
+
}
|
|
31274
|
+
}
|
|
31275
|
+
for (const hop of hops) {
|
|
31276
|
+
const x1 = mapX(hop.from);
|
|
31277
|
+
const x2 = mapX(hop.to);
|
|
31278
|
+
const peak = Math.min(36, plotH * 0.3);
|
|
31279
|
+
const color = hop.color ?? "#7c3aed";
|
|
31280
|
+
out.push({
|
|
31281
|
+
type: "ellipse",
|
|
31282
|
+
x: (x1 + x2) / 2,
|
|
31283
|
+
y: xAxisY,
|
|
31284
|
+
width: Math.abs(x2 - x1),
|
|
31285
|
+
height: 2 * peak,
|
|
31286
|
+
startAngle: 180,
|
|
31287
|
+
endAngle: 360,
|
|
31288
|
+
color
|
|
31289
|
+
});
|
|
31290
|
+
const s = Math.sign(hop.to - hop.from);
|
|
31291
|
+
out.push({
|
|
31292
|
+
type: "polygon",
|
|
31293
|
+
points: [
|
|
31294
|
+
{ x: x2, y: xAxisY },
|
|
31295
|
+
{ x: x2 - 4 * s, y: xAxisY - 7 },
|
|
31296
|
+
{ x: x2 + 2 * s, y: xAxisY - 7 }
|
|
31297
|
+
],
|
|
31298
|
+
fill: color,
|
|
31299
|
+
color
|
|
31300
|
+
});
|
|
31301
|
+
if (hop.label) {
|
|
31302
|
+
out.push({
|
|
31303
|
+
type: "text",
|
|
31304
|
+
x: (x1 + x2) / 2,
|
|
31305
|
+
y: xAxisY - peak - 8,
|
|
31306
|
+
text: hop.label,
|
|
31307
|
+
color: "#111827",
|
|
31308
|
+
fontSize: 10,
|
|
31309
|
+
align: "center"
|
|
31310
|
+
});
|
|
31311
|
+
}
|
|
31312
|
+
}
|
|
31313
|
+
for (const angle of angles) {
|
|
31314
|
+
const radius = angle.radius ?? 0.8;
|
|
31315
|
+
const color = angle.color ?? "#0ea5e9";
|
|
31316
|
+
out.push({
|
|
31317
|
+
type: "ellipse",
|
|
31318
|
+
x: mapX(angle.x),
|
|
31319
|
+
y: mapY(angle.y),
|
|
31320
|
+
width: 2 * radius * plotW / (xMax - xMin),
|
|
31321
|
+
height: 2 * radius * plotH / (yMax - yMin),
|
|
31322
|
+
startAngle: -angle.to,
|
|
31323
|
+
endAngle: -angle.from,
|
|
31324
|
+
color
|
|
31325
|
+
});
|
|
31326
|
+
if (angle.label) {
|
|
31327
|
+
const mid = (angle.from + angle.to) / 2;
|
|
31328
|
+
const rad = mid * Math.PI / 180;
|
|
31329
|
+
out.push({
|
|
31330
|
+
type: "text",
|
|
31331
|
+
x: mapX(angle.x + 1.35 * radius * Math.cos(rad)),
|
|
31332
|
+
y: mapY(angle.y + 1.35 * radius * Math.sin(rad)),
|
|
31333
|
+
text: angle.label,
|
|
31334
|
+
color: "#111827",
|
|
31335
|
+
fontSize: 11,
|
|
31336
|
+
align: "center"
|
|
29230
31337
|
});
|
|
29231
31338
|
}
|
|
29232
31339
|
}
|
|
29233
31340
|
for (const p of points) {
|
|
29234
31341
|
if (p.x < xMin || p.x > xMax || p.y < yMin || p.y > yMax) continue;
|
|
31342
|
+
const isOpen = p.style === "open";
|
|
29235
31343
|
out.push({
|
|
29236
31344
|
type: "circle",
|
|
29237
31345
|
x: mapX(p.x),
|
|
29238
31346
|
y: mapY(p.y),
|
|
29239
31347
|
radius: p.radius ?? 4,
|
|
29240
31348
|
color: p.color ?? "#dc2626",
|
|
29241
|
-
fill: p.color ?? "#dc2626"
|
|
31349
|
+
fill: isOpen ? "#ffffff" : p.color ?? "#dc2626"
|
|
29242
31350
|
});
|
|
29243
31351
|
if (p.label) {
|
|
29244
31352
|
out.push({ type: "text", x: mapX(p.x) + 8, y: mapY(p.y) - 8, text: p.label, color: "#111827", fontSize: 12 });
|
|
@@ -29257,7 +31365,28 @@ var init_MathCanvas = __esm({
|
|
|
29257
31365
|
}
|
|
29258
31366
|
out.push(...shapes);
|
|
29259
31367
|
return out;
|
|
29260
|
-
}, [
|
|
31368
|
+
}, [
|
|
31369
|
+
width,
|
|
31370
|
+
height,
|
|
31371
|
+
xMin,
|
|
31372
|
+
xMax,
|
|
31373
|
+
yMin,
|
|
31374
|
+
yMax,
|
|
31375
|
+
showAxes,
|
|
31376
|
+
showGrid,
|
|
31377
|
+
gridStep,
|
|
31378
|
+
showTickLabels,
|
|
31379
|
+
showCurveLabels,
|
|
31380
|
+
curves,
|
|
31381
|
+
points,
|
|
31382
|
+
vectors,
|
|
31383
|
+
regions,
|
|
31384
|
+
bars,
|
|
31385
|
+
guides,
|
|
31386
|
+
angles,
|
|
31387
|
+
hops,
|
|
31388
|
+
shapes
|
|
31389
|
+
]);
|
|
29261
31390
|
return /* @__PURE__ */ jsxRuntime.jsx(exports.Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(exports.VStack, { gap: "sm", children: [
|
|
29262
31391
|
title ? /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "h4", children: title }) : null,
|
|
29263
31392
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -29266,6 +31395,8 @@ var init_MathCanvas = __esm({
|
|
|
29266
31395
|
width,
|
|
29267
31396
|
height,
|
|
29268
31397
|
shapes: derivedShapes,
|
|
31398
|
+
readouts,
|
|
31399
|
+
traces,
|
|
29269
31400
|
interactive,
|
|
29270
31401
|
animate,
|
|
29271
31402
|
onShapeClick,
|
|
@@ -29277,27 +31408,355 @@ var init_MathCanvas = __esm({
|
|
|
29277
31408
|
};
|
|
29278
31409
|
}
|
|
29279
31410
|
});
|
|
29280
|
-
|
|
31411
|
+
function formatMeterValue(v) {
|
|
31412
|
+
return Number.isInteger(v) ? String(v) : String(Number(v.toFixed(2)));
|
|
31413
|
+
}
|
|
31414
|
+
function sceneObjectShapes(obj, canvasWidth, canvasHeight) {
|
|
31415
|
+
const out = [];
|
|
31416
|
+
const color = obj.color ?? "#334155";
|
|
31417
|
+
switch (obj.kind) {
|
|
31418
|
+
case "ground": {
|
|
31419
|
+
const xStart = obj.x1 ?? 0;
|
|
31420
|
+
const xEnd = obj.x2 ?? canvasWidth;
|
|
31421
|
+
const y = obj.y ?? 0;
|
|
31422
|
+
out.push({ type: "line", x1: xStart, y1: y, x2: xEnd, y2: y, color, lineWidth: 2 });
|
|
31423
|
+
for (let hx = xStart + 7; hx <= xEnd; hx += 14) {
|
|
31424
|
+
out.push({ type: "line", x1: hx, y1: y, x2: hx - 7, y2: y + 7, color, lineWidth: 1 });
|
|
31425
|
+
}
|
|
31426
|
+
if (obj.label) {
|
|
31427
|
+
out.push({
|
|
31428
|
+
type: "text",
|
|
31429
|
+
x: (xStart + xEnd) / 2,
|
|
31430
|
+
y: y - 10,
|
|
31431
|
+
text: obj.label,
|
|
31432
|
+
color: PHYSICS_LABEL_COLOR,
|
|
31433
|
+
fontSize: 11,
|
|
31434
|
+
align: "center"
|
|
31435
|
+
});
|
|
31436
|
+
}
|
|
31437
|
+
break;
|
|
31438
|
+
}
|
|
31439
|
+
case "wall": {
|
|
31440
|
+
const yStart = obj.y1 ?? 0;
|
|
31441
|
+
const yEnd = obj.y2 ?? canvasHeight;
|
|
31442
|
+
const x = obj.x ?? 0;
|
|
31443
|
+
out.push({ type: "line", x1: x, y1: yStart, x2: x, y2: yEnd, color, lineWidth: 2 });
|
|
31444
|
+
for (let hy = yStart + 7; hy <= yEnd; hy += 14) {
|
|
31445
|
+
out.push({ type: "line", x1: x, y1: hy, x2: x - 7, y2: hy + 7, color, lineWidth: 1 });
|
|
31446
|
+
}
|
|
31447
|
+
if (obj.label) {
|
|
31448
|
+
out.push({
|
|
31449
|
+
type: "text",
|
|
31450
|
+
x: x + 12,
|
|
31451
|
+
y: (yStart + yEnd) / 2,
|
|
31452
|
+
text: obj.label,
|
|
31453
|
+
color: PHYSICS_LABEL_COLOR,
|
|
31454
|
+
fontSize: 11,
|
|
31455
|
+
align: "left"
|
|
31456
|
+
});
|
|
31457
|
+
}
|
|
31458
|
+
break;
|
|
31459
|
+
}
|
|
31460
|
+
case "ramp": {
|
|
31461
|
+
const x1 = obj.x1 ?? 0;
|
|
31462
|
+
const y1 = obj.y1 ?? 0;
|
|
31463
|
+
const x2 = obj.x2 ?? canvasWidth;
|
|
31464
|
+
const y2 = obj.y2 ?? canvasHeight;
|
|
31465
|
+
out.push({
|
|
31466
|
+
type: "polygon",
|
|
31467
|
+
points: [
|
|
31468
|
+
{ x: x1, y: y1 },
|
|
31469
|
+
{ x: x2, y: y2 },
|
|
31470
|
+
{ x: x1, y: y2 }
|
|
31471
|
+
],
|
|
31472
|
+
color,
|
|
31473
|
+
fill: obj.fill ?? "#e2e8f0",
|
|
31474
|
+
lineWidth: 2
|
|
31475
|
+
});
|
|
31476
|
+
if (obj.label) {
|
|
31477
|
+
out.push({
|
|
31478
|
+
type: "text",
|
|
31479
|
+
x: (2 * x1 + x2) / 3,
|
|
31480
|
+
y: (y1 + 2 * y2) / 3,
|
|
31481
|
+
text: obj.label,
|
|
31482
|
+
color: PHYSICS_LABEL_COLOR,
|
|
31483
|
+
fontSize: 11,
|
|
31484
|
+
align: "center"
|
|
31485
|
+
});
|
|
31486
|
+
}
|
|
31487
|
+
break;
|
|
31488
|
+
}
|
|
31489
|
+
case "box": {
|
|
31490
|
+
const x = obj.x ?? 0;
|
|
31491
|
+
const y = obj.y ?? 0;
|
|
31492
|
+
const w = obj.width ?? 40;
|
|
31493
|
+
const h = obj.height ?? 40;
|
|
31494
|
+
out.push({ type: "rect", x, y, width: w, height: h, color, fill: obj.fill, lineWidth: 2 });
|
|
31495
|
+
if (obj.label) {
|
|
31496
|
+
out.push({
|
|
31497
|
+
type: "text",
|
|
31498
|
+
x: x + w / 2,
|
|
31499
|
+
y: y + h / 2,
|
|
31500
|
+
text: obj.label,
|
|
31501
|
+
color: PHYSICS_LABEL_COLOR,
|
|
31502
|
+
fontSize: 11,
|
|
31503
|
+
align: "center"
|
|
31504
|
+
});
|
|
31505
|
+
}
|
|
31506
|
+
break;
|
|
31507
|
+
}
|
|
31508
|
+
case "pivot": {
|
|
31509
|
+
const x = obj.x ?? 0;
|
|
31510
|
+
const y = obj.y ?? 0;
|
|
31511
|
+
out.push({ type: "circle", x, y, radius: 5, color, fill: color });
|
|
31512
|
+
out.push({ type: "line", x1: x - 14, y1: y - 8, x2: x + 14, y2: y - 8, color, lineWidth: 1 });
|
|
31513
|
+
for (let k = 0; k < 5; k++) {
|
|
31514
|
+
const hx = x - 14 + 7 * k;
|
|
31515
|
+
out.push({ type: "line", x1: hx, y1: y - 8, x2: hx - 6, y2: y - 14, color, lineWidth: 1 });
|
|
31516
|
+
}
|
|
31517
|
+
if (obj.label) {
|
|
31518
|
+
out.push({
|
|
31519
|
+
type: "text",
|
|
31520
|
+
x,
|
|
31521
|
+
y: y - 20,
|
|
31522
|
+
text: obj.label,
|
|
31523
|
+
color: PHYSICS_LABEL_COLOR,
|
|
31524
|
+
fontSize: 11,
|
|
31525
|
+
align: "center"
|
|
31526
|
+
});
|
|
31527
|
+
}
|
|
31528
|
+
break;
|
|
31529
|
+
}
|
|
31530
|
+
}
|
|
31531
|
+
return out;
|
|
31532
|
+
}
|
|
31533
|
+
function trailShapes(trail) {
|
|
31534
|
+
const n = trail.points.length;
|
|
31535
|
+
if (n < 2) return [];
|
|
31536
|
+
const color = trail.color ?? "#94a3b8";
|
|
31537
|
+
const lineWidth = trail.width ?? 2;
|
|
31538
|
+
const fade = trail.fade ?? true;
|
|
31539
|
+
const globalOpacity = trail.opacity ?? 1;
|
|
31540
|
+
const out = [];
|
|
31541
|
+
for (let i = 0; i < n - 1; i++) {
|
|
31542
|
+
const a = trail.points[i];
|
|
31543
|
+
const b = trail.points[i + 1];
|
|
31544
|
+
const segmentOpacity = fade ? 0.12 + 0.68 * i / (n - 1) : 0.6;
|
|
31545
|
+
out.push({
|
|
31546
|
+
type: "line",
|
|
31547
|
+
x1: a.x,
|
|
31548
|
+
y1: a.y,
|
|
31549
|
+
x2: b.x,
|
|
31550
|
+
y2: b.y,
|
|
31551
|
+
color,
|
|
31552
|
+
lineWidth,
|
|
31553
|
+
opacity: segmentOpacity * globalOpacity
|
|
31554
|
+
});
|
|
31555
|
+
}
|
|
31556
|
+
return out;
|
|
31557
|
+
}
|
|
31558
|
+
function constraintShapes(c, a, b) {
|
|
31559
|
+
const color = c.color ?? "#9ca3af";
|
|
31560
|
+
const kind = c.kind ?? "rod";
|
|
31561
|
+
if (kind === "rod") {
|
|
31562
|
+
return [{ type: "line", x1: a.x, y1: a.y, x2: b.x, y2: b.y, color, lineWidth: 2 }];
|
|
31563
|
+
}
|
|
31564
|
+
if (kind === "string") {
|
|
31565
|
+
return [{ type: "line", x1: a.x, y1: a.y, x2: b.x, y2: b.y, color, lineWidth: 2, dash: "dashed" }];
|
|
31566
|
+
}
|
|
31567
|
+
const COILS = 8;
|
|
31568
|
+
const AMP = 7;
|
|
31569
|
+
const LEAD = 10;
|
|
31570
|
+
const dx = b.x - a.x;
|
|
31571
|
+
const dy = b.y - a.y;
|
|
31572
|
+
const dist = Math.max(1e-6, Math.hypot(dx, dy));
|
|
31573
|
+
const ux = dx / dist;
|
|
31574
|
+
const uy = dy / dist;
|
|
31575
|
+
const perpX = -uy;
|
|
31576
|
+
const perpY = ux;
|
|
31577
|
+
const aPrime = { x: a.x + LEAD * ux, y: a.y + LEAD * uy };
|
|
31578
|
+
const bPrime = { x: b.x - LEAD * ux, y: b.y - LEAD * uy };
|
|
31579
|
+
const m = 2 * COILS;
|
|
31580
|
+
const polyline = [{ x: a.x, y: a.y }, aPrime];
|
|
31581
|
+
for (let j = 1; j <= m; j++) {
|
|
31582
|
+
const t = j / (m + 1);
|
|
31583
|
+
const baseX = aPrime.x + t * (bPrime.x - aPrime.x);
|
|
31584
|
+
const baseY = aPrime.y + t * (bPrime.y - aPrime.y);
|
|
31585
|
+
const sign = j % 2 === 0 ? 1 : -1;
|
|
31586
|
+
polyline.push({ x: baseX + sign * AMP * perpX, y: baseY + sign * AMP * perpY });
|
|
31587
|
+
}
|
|
31588
|
+
polyline.push(bPrime, { x: b.x, y: b.y });
|
|
31589
|
+
const out = [];
|
|
31590
|
+
for (let i = 1; i < polyline.length; i++) {
|
|
31591
|
+
out.push({
|
|
31592
|
+
type: "line",
|
|
31593
|
+
x1: polyline[i - 1].x,
|
|
31594
|
+
y1: polyline[i - 1].y,
|
|
31595
|
+
x2: polyline[i].x,
|
|
31596
|
+
y2: polyline[i].y,
|
|
31597
|
+
color,
|
|
31598
|
+
lineWidth: 2
|
|
31599
|
+
});
|
|
31600
|
+
}
|
|
31601
|
+
return out;
|
|
31602
|
+
}
|
|
31603
|
+
function vectorShapes(v, bodyById) {
|
|
31604
|
+
let ax;
|
|
31605
|
+
let ay;
|
|
31606
|
+
if (v.body) {
|
|
31607
|
+
const anchor = bodyById.get(v.body);
|
|
31608
|
+
if (!anchor) return [];
|
|
31609
|
+
ax = anchor.x;
|
|
31610
|
+
ay = anchor.y;
|
|
31611
|
+
} else {
|
|
31612
|
+
ax = v.x ?? 0;
|
|
31613
|
+
ay = v.y ?? 0;
|
|
31614
|
+
}
|
|
31615
|
+
const scale = v.scale ?? 1;
|
|
31616
|
+
const color = v.color ?? "#dc2626";
|
|
31617
|
+
const tx = ax + v.dx * scale;
|
|
31618
|
+
const ty = ay + v.dy * scale;
|
|
31619
|
+
const out = [{ type: "arrow", x1: ax, y1: ay, x2: tx, y2: ty, color, lineWidth: 2, dash: v.dash }];
|
|
31620
|
+
if (v.label) {
|
|
31621
|
+
const dist = Math.max(1e-6, Math.hypot(tx - ax, ty - ay));
|
|
31622
|
+
const ux = (tx - ax) / dist;
|
|
31623
|
+
const uy = (ty - ay) / dist;
|
|
31624
|
+
out.push({
|
|
31625
|
+
type: "text",
|
|
31626
|
+
x: tx + 8 * ux,
|
|
31627
|
+
y: ty + 8 * uy,
|
|
31628
|
+
text: v.label,
|
|
31629
|
+
color,
|
|
31630
|
+
fontSize: 11,
|
|
31631
|
+
align: "center"
|
|
31632
|
+
});
|
|
31633
|
+
}
|
|
31634
|
+
return out;
|
|
31635
|
+
}
|
|
31636
|
+
function angleMarkerShapes(a) {
|
|
31637
|
+
const radius = a.radius ?? 26;
|
|
31638
|
+
const color = a.color ?? "#0ea5e9";
|
|
31639
|
+
const out = [
|
|
31640
|
+
{
|
|
31641
|
+
type: "ellipse",
|
|
31642
|
+
x: a.x,
|
|
31643
|
+
y: a.y,
|
|
31644
|
+
width: radius * 2,
|
|
31645
|
+
height: radius * 2,
|
|
31646
|
+
startAngle: a.from,
|
|
31647
|
+
endAngle: a.to,
|
|
31648
|
+
color,
|
|
31649
|
+
lineWidth: 2
|
|
31650
|
+
}
|
|
31651
|
+
];
|
|
31652
|
+
if (a.label) {
|
|
31653
|
+
const mid = (a.from + a.to) / 2 * (Math.PI / 180);
|
|
31654
|
+
out.push({
|
|
31655
|
+
type: "text",
|
|
31656
|
+
x: a.x + (radius + 13) * Math.cos(mid),
|
|
31657
|
+
y: a.y + (radius + 13) * Math.sin(mid),
|
|
31658
|
+
text: a.label,
|
|
31659
|
+
color,
|
|
31660
|
+
fontSize: 11,
|
|
31661
|
+
align: "center"
|
|
31662
|
+
});
|
|
31663
|
+
}
|
|
31664
|
+
return out;
|
|
31665
|
+
}
|
|
31666
|
+
function fieldShapes(field, canvasWidth, canvasHeight) {
|
|
31667
|
+
const spacing = field.spacing ?? 48;
|
|
31668
|
+
const size = field.size ?? 14;
|
|
31669
|
+
const color = field.color ?? "#94a3b8";
|
|
31670
|
+
const regionX = field.x ?? 0;
|
|
31671
|
+
const regionY = field.y ?? 0;
|
|
31672
|
+
const regionW = field.width ?? canvasWidth;
|
|
31673
|
+
const regionH = field.height ?? canvasHeight;
|
|
31674
|
+
const out = [];
|
|
31675
|
+
for (let gx = regionX + spacing / 2; gx < regionX + regionW; gx += spacing) {
|
|
31676
|
+
for (let gy = regionY + spacing / 2; gy < regionY + regionH; gy += spacing) {
|
|
31677
|
+
if (field.kind === "arrows") {
|
|
31678
|
+
const rad = (field.angle ?? 0) * Math.PI / 180;
|
|
31679
|
+
const hx = Math.cos(rad) * size / 2;
|
|
31680
|
+
const hy = Math.sin(rad) * size / 2;
|
|
31681
|
+
out.push({ type: "arrow", x1: gx - hx, y1: gy - hy, x2: gx + hx, y2: gy + hy, color, lineWidth: 2 });
|
|
31682
|
+
} else if (field.kind === "into") {
|
|
31683
|
+
const r = size / 3;
|
|
31684
|
+
const d = 0.6 * r * Math.SQRT1_2;
|
|
31685
|
+
out.push({ type: "circle", x: gx, y: gy, radius: r, color });
|
|
31686
|
+
out.push({ type: "line", x1: gx - d, y1: gy - d, x2: gx + d, y2: gy + d, color, lineWidth: 1 });
|
|
31687
|
+
out.push({ type: "line", x1: gx - d, y1: gy + d, x2: gx + d, y2: gy - d, color, lineWidth: 1 });
|
|
31688
|
+
} else {
|
|
31689
|
+
const r = size / 3;
|
|
31690
|
+
out.push({ type: "circle", x: gx, y: gy, radius: r, color });
|
|
31691
|
+
out.push({ type: "circle", x: gx, y: gy, radius: 1.5, color, fill: color });
|
|
31692
|
+
}
|
|
31693
|
+
}
|
|
31694
|
+
}
|
|
31695
|
+
return out;
|
|
31696
|
+
}
|
|
31697
|
+
function meterShapes(meters, canvasHeight) {
|
|
31698
|
+
const n = meters.length;
|
|
31699
|
+
const out = [];
|
|
31700
|
+
const sharedMax = Math.max(1e-6, ...meters.map((m) => m.value));
|
|
31701
|
+
meters.forEach((meter, i) => {
|
|
31702
|
+
const rowY = canvasHeight - 10 - 16 * (n - i);
|
|
31703
|
+
const color = meter.color ?? "#3b82f6";
|
|
31704
|
+
const M = meter.max ?? sharedMax;
|
|
31705
|
+
const w = Math.round(Math.min(1, Math.max(0, meter.value / M)) * 110);
|
|
31706
|
+
out.push({ type: "text", x: 8, y: rowY + 8, text: meter.label, color: PHYSICS_LABEL_COLOR, fontSize: 10 });
|
|
31707
|
+
out.push({ type: "rect", x: 52, y: rowY, width: w, height: 10, color, fill: color });
|
|
31708
|
+
out.push({
|
|
31709
|
+
type: "text",
|
|
31710
|
+
x: 166,
|
|
31711
|
+
y: rowY + 8,
|
|
31712
|
+
text: formatMeterValue(meter.value),
|
|
31713
|
+
color: "#6b7280",
|
|
31714
|
+
fontSize: 9
|
|
31715
|
+
});
|
|
31716
|
+
});
|
|
31717
|
+
return out;
|
|
31718
|
+
}
|
|
31719
|
+
var physicsLog2, PHYSICS_LABEL_COLOR; exports.PhysicsCanvas = void 0;
|
|
29281
31720
|
var init_PhysicsCanvas = __esm({
|
|
29282
31721
|
"components/learning/molecules/PhysicsCanvas.tsx"() {
|
|
29283
31722
|
"use client";
|
|
29284
31723
|
init_atoms();
|
|
29285
31724
|
init_Stack();
|
|
29286
31725
|
init_LearningCanvas();
|
|
31726
|
+
init_learningScene3D();
|
|
31727
|
+
physicsLog2 = logger.createLogger("almadar:ui:physics-canvas");
|
|
31728
|
+
PHYSICS_LABEL_COLOR = "#374151";
|
|
29287
31729
|
exports.PhysicsCanvas = ({
|
|
29288
31730
|
className,
|
|
29289
31731
|
width = 600,
|
|
29290
31732
|
height = 400,
|
|
29291
31733
|
title,
|
|
29292
31734
|
backgroundColor,
|
|
31735
|
+
mode = "2d",
|
|
31736
|
+
camera,
|
|
31737
|
+
lighting,
|
|
31738
|
+
post,
|
|
29293
31739
|
bodies = [],
|
|
29294
31740
|
constraints = [],
|
|
29295
31741
|
showVelocity = true,
|
|
29296
31742
|
showForces = false,
|
|
29297
31743
|
velocityScale = 20,
|
|
29298
31744
|
forceScale = 20,
|
|
31745
|
+
sceneObjects = [],
|
|
31746
|
+
trails = [],
|
|
31747
|
+
vectors = [],
|
|
31748
|
+
surface3d,
|
|
31749
|
+
vectors3d = [],
|
|
31750
|
+
vectorScale = 1,
|
|
31751
|
+
angles = [],
|
|
31752
|
+
field,
|
|
31753
|
+
meters = [],
|
|
29299
31754
|
shapes = [],
|
|
29300
|
-
|
|
31755
|
+
readouts,
|
|
31756
|
+
traces,
|
|
31757
|
+
showGrid,
|
|
31758
|
+
shadows,
|
|
31759
|
+
interactive,
|
|
29301
31760
|
animate = false,
|
|
29302
31761
|
onShapeClick,
|
|
29303
31762
|
isLoading,
|
|
@@ -29309,19 +31768,14 @@ var init_PhysicsCanvas = __esm({
|
|
|
29309
31768
|
for (const b of bodies) {
|
|
29310
31769
|
if (b.id) bodyById.set(b.id, b);
|
|
29311
31770
|
}
|
|
31771
|
+
if (field) out.push(...fieldShapes(field, width, height));
|
|
31772
|
+
for (const obj of sceneObjects) out.push(...sceneObjectShapes(obj, width, height));
|
|
31773
|
+
for (const trail of trails) out.push(...trailShapes(trail));
|
|
29312
31774
|
for (const c of constraints) {
|
|
29313
31775
|
const a = bodyById.get(c.from);
|
|
29314
31776
|
const b = bodyById.get(c.to);
|
|
29315
31777
|
if (!a || !b) continue;
|
|
29316
|
-
out.push(
|
|
29317
|
-
type: "line",
|
|
29318
|
-
x1: a.x,
|
|
29319
|
-
y1: a.y,
|
|
29320
|
-
x2: b.x,
|
|
29321
|
-
y2: b.y,
|
|
29322
|
-
color: c.color ?? "#9ca3af",
|
|
29323
|
-
lineWidth: 2
|
|
29324
|
-
});
|
|
31778
|
+
out.push(...constraintShapes(c, a, b));
|
|
29325
31779
|
}
|
|
29326
31780
|
for (const b of bodies) {
|
|
29327
31781
|
out.push({
|
|
@@ -29366,9 +31820,181 @@ var init_PhysicsCanvas = __esm({
|
|
|
29366
31820
|
});
|
|
29367
31821
|
}
|
|
29368
31822
|
}
|
|
31823
|
+
for (const v of vectors) out.push(...vectorShapes(v, bodyById));
|
|
31824
|
+
for (const a of angles) out.push(...angleMarkerShapes(a));
|
|
31825
|
+
if (meters.length > 0) out.push(...meterShapes(meters, height));
|
|
29369
31826
|
out.push(...shapes);
|
|
29370
31827
|
return out;
|
|
29371
|
-
}, [
|
|
31828
|
+
}, [
|
|
31829
|
+
bodies,
|
|
31830
|
+
constraints,
|
|
31831
|
+
showVelocity,
|
|
31832
|
+
showForces,
|
|
31833
|
+
velocityScale,
|
|
31834
|
+
forceScale,
|
|
31835
|
+
sceneObjects,
|
|
31836
|
+
trails,
|
|
31837
|
+
vectors,
|
|
31838
|
+
angles,
|
|
31839
|
+
field,
|
|
31840
|
+
meters,
|
|
31841
|
+
shapes,
|
|
31842
|
+
width,
|
|
31843
|
+
height
|
|
31844
|
+
]);
|
|
31845
|
+
const drawables3D = React77.useMemo(() => {
|
|
31846
|
+
if (mode !== "3d") return [];
|
|
31847
|
+
if (shapes.length > 0) {
|
|
31848
|
+
physicsLog2.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
|
|
31849
|
+
}
|
|
31850
|
+
if (sceneObjects.length > 0) {
|
|
31851
|
+
physicsLog2.debug("sceneObjects ignored in 3D mode (pixel-authored 2D vocabulary)", { count: sceneObjects.length });
|
|
31852
|
+
}
|
|
31853
|
+
if (vectors.length > 0) {
|
|
31854
|
+
physicsLog2.debug("vectors ignored in 3D mode (pixel-authored 2D vocabulary)", { count: vectors.length });
|
|
31855
|
+
}
|
|
31856
|
+
if (angles.length > 0) {
|
|
31857
|
+
physicsLog2.debug("angles ignored in 3D mode (pixel-authored 2D vocabulary)", { count: angles.length });
|
|
31858
|
+
}
|
|
31859
|
+
if (field) {
|
|
31860
|
+
physicsLog2.debug("field ignored in 3D mode (pixel-authored 2D vocabulary)");
|
|
31861
|
+
}
|
|
31862
|
+
if (meters.length > 0) {
|
|
31863
|
+
physicsLog2.debug("meters ignored in 3D mode (pixel-authored 2D vocabulary)", { count: meters.length });
|
|
31864
|
+
}
|
|
31865
|
+
if (animate) {
|
|
31866
|
+
physicsLog2.debug("animate ignored in 3D mode (motion is entity-state driven)");
|
|
31867
|
+
}
|
|
31868
|
+
const out = [];
|
|
31869
|
+
const labelColor = labelColorForBackground(backgroundColor);
|
|
31870
|
+
const bodyById = /* @__PURE__ */ new Map();
|
|
31871
|
+
for (const b of bodies) {
|
|
31872
|
+
if (b.id) bodyById.set(b.id, b);
|
|
31873
|
+
}
|
|
31874
|
+
for (const c of constraints) {
|
|
31875
|
+
const a = bodyById.get(c.from);
|
|
31876
|
+
const b = bodyById.get(c.to);
|
|
31877
|
+
if (!a || !b) continue;
|
|
31878
|
+
const rodRadius = Math.max(0.05, Math.min(a.radius ?? 0.5, b.radius ?? 0.5) * 0.15);
|
|
31879
|
+
const rod = cylinderBetween([a.x, a.y, a.z ?? 0], [b.x, b.y, b.z ?? 0], rodRadius, c.color ?? "#9ca3af");
|
|
31880
|
+
if (rod) out.push(rod);
|
|
31881
|
+
}
|
|
31882
|
+
for (const b of bodies) {
|
|
31883
|
+
const radius = b.radius ?? 0.5;
|
|
31884
|
+
const bz = b.z ?? 0;
|
|
31885
|
+
out.push(meshSphere(b.id, b.x, b.y, bz, radius, b.color ?? "#2563eb"));
|
|
31886
|
+
if (b.label) {
|
|
31887
|
+
out.push(billboardLabel(b.label, b.x, b.y, bz + radius, { color: labelColor }));
|
|
31888
|
+
}
|
|
31889
|
+
const vx = b.vx ?? 0;
|
|
31890
|
+
const vy = b.vy ?? 0;
|
|
31891
|
+
const vz = b.vz ?? 0;
|
|
31892
|
+
const arrowRadius = Math.max(0.05, radius * 0.15);
|
|
31893
|
+
if (showVelocity && (vx !== 0 || vy !== 0 || vz !== 0)) {
|
|
31894
|
+
const arrow = arrowBetween(
|
|
31895
|
+
[b.x, b.y, bz],
|
|
31896
|
+
[b.x + vx * velocityScale, b.y + vy * velocityScale, bz + vz * velocityScale],
|
|
31897
|
+
"#16a34a",
|
|
31898
|
+
arrowRadius
|
|
31899
|
+
);
|
|
31900
|
+
if (arrow) out.push(arrow);
|
|
31901
|
+
}
|
|
31902
|
+
const fx = b.fx ?? 0;
|
|
31903
|
+
const fy = b.fy ?? 0;
|
|
31904
|
+
const fz = b.fz ?? 0;
|
|
31905
|
+
if (showForces && (fx !== 0 || fy !== 0 || fz !== 0)) {
|
|
31906
|
+
const arrow = arrowBetween(
|
|
31907
|
+
[b.x, b.y, bz],
|
|
31908
|
+
[b.x + fx * forceScale, b.y + fy * forceScale, bz + fz * forceScale],
|
|
31909
|
+
"#dc2626",
|
|
31910
|
+
arrowRadius
|
|
31911
|
+
);
|
|
31912
|
+
if (arrow) out.push(arrow);
|
|
31913
|
+
}
|
|
31914
|
+
}
|
|
31915
|
+
for (const trail of trails) {
|
|
31916
|
+
if (trail.fade !== void 0) {
|
|
31917
|
+
physicsLog2.debug("trail.fade ignored in 3D mode (2D-only fade curve \u2014 3D draws an opaque tube)", { id: trail.id });
|
|
31918
|
+
}
|
|
31919
|
+
const points = trail.points.map((p) => [p.x, p.y, p.z ?? 0]);
|
|
31920
|
+
out.push(
|
|
31921
|
+
...polylineTube(points, trail.width ?? 0.05, trail.color ?? "#94a3b8", {
|
|
31922
|
+
...trail.opacity !== void 0 ? { opacity: trail.opacity } : {}
|
|
31923
|
+
})
|
|
31924
|
+
);
|
|
31925
|
+
}
|
|
31926
|
+
if (surface3d) {
|
|
31927
|
+
out.push(...heightFieldMesh(surface3d));
|
|
31928
|
+
}
|
|
31929
|
+
if (vectors3d.length > 0) {
|
|
31930
|
+
out.push(
|
|
31931
|
+
...arrowField(
|
|
31932
|
+
vectors3d.map((v) => ({
|
|
31933
|
+
id: v.id,
|
|
31934
|
+
from: [v.x, v.y, v.z ?? 0],
|
|
31935
|
+
delta: [v.dx, v.dy, v.dz ?? 0],
|
|
31936
|
+
color: v.color,
|
|
31937
|
+
label: v.label,
|
|
31938
|
+
width: v.width
|
|
31939
|
+
})),
|
|
31940
|
+
{ scale: vectorScale, labelColor }
|
|
31941
|
+
)
|
|
31942
|
+
);
|
|
31943
|
+
}
|
|
31944
|
+
return out;
|
|
31945
|
+
}, [
|
|
31946
|
+
mode,
|
|
31947
|
+
bodies,
|
|
31948
|
+
constraints,
|
|
31949
|
+
showVelocity,
|
|
31950
|
+
showForces,
|
|
31951
|
+
velocityScale,
|
|
31952
|
+
forceScale,
|
|
31953
|
+
shapes,
|
|
31954
|
+
sceneObjects,
|
|
31955
|
+
trails,
|
|
31956
|
+
vectors,
|
|
31957
|
+
surface3d,
|
|
31958
|
+
vectors3d,
|
|
31959
|
+
vectorScale,
|
|
31960
|
+
angles,
|
|
31961
|
+
field,
|
|
31962
|
+
meters,
|
|
31963
|
+
animate,
|
|
31964
|
+
backgroundColor
|
|
31965
|
+
]);
|
|
31966
|
+
const bodyIndexById = React77.useMemo(() => {
|
|
31967
|
+
const m = /* @__PURE__ */ new Map();
|
|
31968
|
+
vectors3d.forEach((v, i) => {
|
|
31969
|
+
if (v.id) m.set(v.id, i);
|
|
31970
|
+
});
|
|
31971
|
+
bodies.forEach((b, i) => {
|
|
31972
|
+
if (b.id) m.set(b.id, i);
|
|
31973
|
+
});
|
|
31974
|
+
return m;
|
|
31975
|
+
}, [bodies, vectors3d]);
|
|
31976
|
+
if (mode === "3d") {
|
|
31977
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
31978
|
+
LearningScene3D,
|
|
31979
|
+
{
|
|
31980
|
+
className,
|
|
31981
|
+
width,
|
|
31982
|
+
height,
|
|
31983
|
+
title,
|
|
31984
|
+
backgroundColor,
|
|
31985
|
+
drawables: drawables3D,
|
|
31986
|
+
camera,
|
|
31987
|
+
lighting,
|
|
31988
|
+
post,
|
|
31989
|
+
showGrid,
|
|
31990
|
+
shadows,
|
|
31991
|
+
interactive,
|
|
31992
|
+
isLoading,
|
|
31993
|
+
error,
|
|
31994
|
+
onItemClick: get3DClickPayload(onShapeClick, bodyIndexById)
|
|
31995
|
+
}
|
|
31996
|
+
);
|
|
31997
|
+
}
|
|
29372
31998
|
return /* @__PURE__ */ jsxRuntime.jsx(exports.Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(exports.VStack, { gap: "sm", children: [
|
|
29373
31999
|
title ? /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "h4", children: title }) : null,
|
|
29374
32000
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -29378,7 +32004,9 @@ var init_PhysicsCanvas = __esm({
|
|
|
29378
32004
|
height,
|
|
29379
32005
|
backgroundColor,
|
|
29380
32006
|
shapes: derivedShapes,
|
|
29381
|
-
|
|
32007
|
+
readouts,
|
|
32008
|
+
traces,
|
|
32009
|
+
interactive: interactive ?? false,
|
|
29382
32010
|
animate,
|
|
29383
32011
|
onShapeClick,
|
|
29384
32012
|
isLoading,
|
|
@@ -29528,7 +32156,7 @@ function layoutFlow(nodeIds, adjacency, roots, width, height, margin) {
|
|
|
29528
32156
|
}
|
|
29529
32157
|
return nodeIds.map((id) => positions.get(id));
|
|
29530
32158
|
}
|
|
29531
|
-
function
|
|
32159
|
+
function layoutTree2(nodeIds, adjacency, roots, width, height, margin) {
|
|
29532
32160
|
const effectiveRoots = roots.length > 0 ? roots : [nodeIds[0]];
|
|
29533
32161
|
const layers = assignLayers(nodeIds, adjacency, effectiveRoots);
|
|
29534
32162
|
const maxLayer = Math.max(...Array.from(layers.values()));
|
|
@@ -29584,7 +32212,7 @@ function computeStaticLayout(mode, input) {
|
|
|
29584
32212
|
const adjacency = buildAdjacency(nodeIds, edges);
|
|
29585
32213
|
const roots = findRoots(nodeIds, adjacency);
|
|
29586
32214
|
if (mode === "flow") return layoutFlow(nodeIds, adjacency, roots, width, height, margin);
|
|
29587
|
-
if (mode === "tree") return
|
|
32215
|
+
if (mode === "tree") return layoutTree2(nodeIds, adjacency, roots, width, height, margin);
|
|
29588
32216
|
return layoutRadial(nodeIds, adjacency, roots, width, height, margin);
|
|
29589
32217
|
}
|
|
29590
32218
|
var init_graphViewLayouts = __esm({
|
|
@@ -29996,12 +32624,12 @@ var init_MapView = __esm({
|
|
|
29996
32624
|
shadowSize: [41, 41]
|
|
29997
32625
|
});
|
|
29998
32626
|
L.Marker.prototype.options.icon = defaultIcon;
|
|
29999
|
-
const { useEffect: useEffect66, useRef:
|
|
32627
|
+
const { useEffect: useEffect66, useRef: useRef65, useCallback: useCallback108, useState: useState104 } = React77__namespace.default;
|
|
30000
32628
|
const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
|
|
30001
32629
|
const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
|
|
30002
32630
|
function MapUpdater({ centerLat, centerLng, zoom }) {
|
|
30003
32631
|
const map = useMap();
|
|
30004
|
-
const prevRef =
|
|
32632
|
+
const prevRef = useRef65({ centerLat, centerLng, zoom });
|
|
30005
32633
|
useEffect66(() => {
|
|
30006
32634
|
const prev = prevRef.current;
|
|
30007
32635
|
if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
|
|
@@ -30042,7 +32670,7 @@ var init_MapView = __esm({
|
|
|
30042
32670
|
}) {
|
|
30043
32671
|
const eventBus = useEventBus2();
|
|
30044
32672
|
const [clickedPosition, setClickedPosition] = useState104(null);
|
|
30045
|
-
const handleMapClick =
|
|
32673
|
+
const handleMapClick = useCallback108((lat, lng) => {
|
|
30046
32674
|
if (showClickedPin) {
|
|
30047
32675
|
setClickedPosition({ lat, lng });
|
|
30048
32676
|
}
|
|
@@ -30051,7 +32679,7 @@ var init_MapView = __esm({
|
|
|
30051
32679
|
eventBus.emit(`UI:${mapClickEvent}`, { latitude: lat, longitude: lng });
|
|
30052
32680
|
}
|
|
30053
32681
|
}, [onMapClick, mapClickEvent, eventBus, showClickedPin]);
|
|
30054
|
-
const handleMarkerClick =
|
|
32682
|
+
const handleMarkerClick = useCallback108((marker) => {
|
|
30055
32683
|
onMarkerClick?.(marker);
|
|
30056
32684
|
if (markerClickEvent) {
|
|
30057
32685
|
eventBus.emit(`UI:${markerClickEvent}`, { ...marker });
|
|
@@ -30827,26 +33455,6 @@ var init_Lightbox = __esm({
|
|
|
30827
33455
|
exports.Lightbox.displayName = "Lightbox";
|
|
30828
33456
|
}
|
|
30829
33457
|
});
|
|
30830
|
-
function useMediaQuery(query) {
|
|
30831
|
-
const subscribe = React77.useCallback(
|
|
30832
|
-
(onChange) => {
|
|
30833
|
-
const mql = window.matchMedia(query);
|
|
30834
|
-
mql.addEventListener("change", onChange);
|
|
30835
|
-
return () => mql.removeEventListener("change", onChange);
|
|
30836
|
-
},
|
|
30837
|
-
[query]
|
|
30838
|
-
);
|
|
30839
|
-
return React77.useSyncExternalStore(
|
|
30840
|
-
subscribe,
|
|
30841
|
-
() => window.matchMedia(query).matches,
|
|
30842
|
-
() => false
|
|
30843
|
-
);
|
|
30844
|
-
}
|
|
30845
|
-
var init_useMediaQuery = __esm({
|
|
30846
|
-
"hooks/useMediaQuery.ts"() {
|
|
30847
|
-
"use client";
|
|
30848
|
-
}
|
|
30849
|
-
});
|
|
30850
33458
|
function renderIconInput3(icon, props) {
|
|
30851
33459
|
return typeof icon === "string" ? /* @__PURE__ */ jsxRuntime.jsx(exports.Icon, { name: icon, ...props }) : /* @__PURE__ */ jsxRuntime.jsx(exports.Icon, { icon, ...props });
|
|
30852
33460
|
}
|
|
@@ -30885,8 +33493,8 @@ function TableView({
|
|
|
30885
33493
|
columns,
|
|
30886
33494
|
fields,
|
|
30887
33495
|
itemActions,
|
|
30888
|
-
maxInlineActions,
|
|
30889
|
-
itemClickEvent,
|
|
33496
|
+
maxInlineActions: _maxInlineActions,
|
|
33497
|
+
itemClickEvent = "",
|
|
30890
33498
|
selectable = false,
|
|
30891
33499
|
selectEvent,
|
|
30892
33500
|
selectedIds,
|
|
@@ -30936,7 +33544,6 @@ function TableView({
|
|
|
30936
33544
|
const hasMore = pageSize > 0 && visibleCount < ordered2.length;
|
|
30937
33545
|
const hasRenderProp = typeof children === "function";
|
|
30938
33546
|
const idField = dndItemIdField ?? "id";
|
|
30939
|
-
const isCoarsePointer = useMediaQuery("(pointer: coarse)");
|
|
30940
33547
|
React77__namespace.default.useEffect(() => {
|
|
30941
33548
|
tableViewLog.debug("render", {
|
|
30942
33549
|
rowCount: data.length,
|
|
@@ -30978,21 +33585,14 @@ function TableView({
|
|
|
30978
33585
|
const dir = sortColumn === (col.field ?? col.key) && sortDirection === "asc" ? "desc" : "asc";
|
|
30979
33586
|
eventBus.emit(`UI:${sortEvent}`, { column: col.field ?? col.key, direction: dir });
|
|
30980
33587
|
};
|
|
30981
|
-
const
|
|
30982
|
-
e.stopPropagation();
|
|
30983
|
-
const payload = {
|
|
30984
|
-
id: row.id,
|
|
30985
|
-
row
|
|
30986
|
-
};
|
|
30987
|
-
eventBus.emit(`UI:${action.event}`, payload);
|
|
30988
|
-
};
|
|
33588
|
+
const rowClickEvent = itemClickEvent || actionDefs.find((a) => a.variant !== "danger")?.event;
|
|
30989
33589
|
const handleRowClick = (row) => () => {
|
|
30990
|
-
if (!
|
|
33590
|
+
if (!rowClickEvent) return;
|
|
30991
33591
|
const payload = {
|
|
30992
33592
|
id: row.id,
|
|
30993
33593
|
row
|
|
30994
33594
|
};
|
|
30995
|
-
eventBus.emit(`UI:${
|
|
33595
|
+
eventBus.emit(`UI:${rowClickEvent}`, payload);
|
|
30996
33596
|
};
|
|
30997
33597
|
const colFloors = React77__namespace.default.useMemo(
|
|
30998
33598
|
() => colDefs.map((col) => {
|
|
@@ -31008,10 +33608,7 @@ function TableView({
|
|
|
31008
33608
|
const statusNode = isLoading ? /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { className: "text-center py-8", children: /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "body", color: "secondary", children: t("loading.items") }) }) : error ? /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { className: "text-center py-8", children: /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "body", color: "error", children: error.message }) }) : data.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { className: "text-center py-12", children: /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "body", color: "secondary", children: emptyMessage || t("empty.noItems") }) }) : null;
|
|
31009
33609
|
const lk = LOOKS[look];
|
|
31010
33610
|
const hasActions = actionDefs.length > 0;
|
|
31011
|
-
const
|
|
31012
|
-
const inlineActionCount = hasActions ? effectiveMaxInline != null ? Math.min(actionDefs.length, effectiveMaxInline) : actionDefs.length : 0;
|
|
31013
|
-
const hasOverflowActions = hasActions && effectiveMaxInline != null && actionDefs.length > effectiveMaxInline;
|
|
31014
|
-
const actionsTrack = hasActions ? `${inlineActionCount * 6 + (hasOverflowActions ? 3 : 0)}rem` : null;
|
|
33611
|
+
const actionsTrack = hasActions ? "3rem" : null;
|
|
31015
33612
|
const gridTemplateColumns = [
|
|
31016
33613
|
selectable ? "auto" : null,
|
|
31017
33614
|
...colDefs.map((c, i) => c.width ?? `minmax(${colFloors[i]}ch, 1fr)`),
|
|
@@ -31058,7 +33655,7 @@ function TableView({
|
|
|
31058
33655
|
col.key
|
|
31059
33656
|
);
|
|
31060
33657
|
}),
|
|
31061
|
-
hasActions && /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { "aria-hidden": true, className: "sticky right-0 bg-[var(--color-surface-subtle)]" })
|
|
33658
|
+
hasActions && /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { "aria-hidden": true, className: "sticky right-0 bg-[var(--color-surface-subtle)] border-l border-[var(--color-border)] h-full" })
|
|
31062
33659
|
]
|
|
31063
33660
|
}
|
|
31064
33661
|
);
|
|
@@ -31070,12 +33667,12 @@ function TableView({
|
|
|
31070
33667
|
role: "row",
|
|
31071
33668
|
"data-entity-row": true,
|
|
31072
33669
|
"data-entity-id": id,
|
|
31073
|
-
onClick:
|
|
33670
|
+
onClick: rowClickEvent ? handleRowClick(row) : void 0,
|
|
31074
33671
|
style: !hasRenderProp ? { gridTemplateColumns } : void 0,
|
|
31075
33672
|
className: cn(
|
|
31076
33673
|
"group items-center gap-3 transition-colors duration-fast",
|
|
31077
33674
|
hasRenderProp ? "flex" : "grid",
|
|
31078
|
-
|
|
33675
|
+
rowClickEvent && "cursor-pointer",
|
|
31079
33676
|
lk.rowPad,
|
|
31080
33677
|
lk.divider && "border-b border-[var(--color-border)]",
|
|
31081
33678
|
lk.striped && index % 2 === 1 && "bg-[var(--color-surface-subtle)]",
|
|
@@ -31083,7 +33680,7 @@ function TableView({
|
|
|
31083
33680
|
look === "bordered" && "[&>*]:border-r [&>*]:border-[var(--color-border)] [&>*:last-child]:border-r-0"
|
|
31084
33681
|
),
|
|
31085
33682
|
children: [
|
|
31086
|
-
selectable && /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { className: "flex items-center", onClick:
|
|
33683
|
+
selectable && /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { className: "flex items-center", onClick: rowClickEvent ? (e) => e.stopPropagation() : void 0, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
31087
33684
|
exports.Checkbox,
|
|
31088
33685
|
{
|
|
31089
33686
|
checked: selected.has(id),
|
|
@@ -31104,53 +33701,37 @@ function TableView({
|
|
|
31104
33701
|
}
|
|
31105
33702
|
return /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { role: "cell", className: cellBase, children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-foreground", children: formatCell(raw, col.format) }) }, col.key);
|
|
31106
33703
|
}),
|
|
31107
|
-
hasActions && /* @__PURE__ */ jsxRuntime.
|
|
33704
|
+
hasActions && /* @__PURE__ */ jsxRuntime.jsx(
|
|
31108
33705
|
exports.HStack,
|
|
31109
33706
|
{
|
|
31110
33707
|
gap: "xs",
|
|
31111
|
-
onClick:
|
|
33708
|
+
onClick: rowClickEvent ? (e) => e.stopPropagation() : void 0,
|
|
31112
33709
|
className: cn(
|
|
31113
33710
|
// Pinned: the fixed column tracks routinely overflow the caller's
|
|
31114
|
-
// scroll container, which
|
|
31115
|
-
// Opaque so
|
|
33711
|
+
// scroll container, which would leave the kebab off-screen.
|
|
33712
|
+
// Opaque + hairline edge so it reads as a pinned column, not a
|
|
33713
|
+
// floating control, while scrolled cells pass underneath.
|
|
31116
33714
|
"justify-end flex-shrink-0 sticky right-0 z-[1] transition-colors",
|
|
33715
|
+
"border-l border-[var(--color-border)]",
|
|
31117
33716
|
lk.striped && index % 2 === 1 ? "bg-[var(--color-surface-subtle)]" : "bg-[var(--color-card)] group-hover:bg-[var(--color-surface-subtle)]"
|
|
31118
33717
|
),
|
|
31119
|
-
children:
|
|
31120
|
-
|
|
31121
|
-
|
|
31122
|
-
|
|
31123
|
-
|
|
31124
|
-
|
|
31125
|
-
|
|
31126
|
-
|
|
31127
|
-
|
|
31128
|
-
|
|
31129
|
-
|
|
31130
|
-
|
|
31131
|
-
|
|
31132
|
-
|
|
31133
|
-
}
|
|
31134
|
-
|
|
31135
|
-
|
|
31136
|
-
effectiveMaxInline != null && actionDefs.length > effectiveMaxInline && /* @__PURE__ */ jsxRuntime.jsx(
|
|
31137
|
-
exports.Menu,
|
|
31138
|
-
{
|
|
31139
|
-
position: "bottom-end",
|
|
31140
|
-
trigger: /* @__PURE__ */ jsxRuntime.jsx(exports.Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", children: /* @__PURE__ */ jsxRuntime.jsx(exports.Icon, { name: "more-horizontal", size: "xs" }) }),
|
|
31141
|
-
items: actionDefs.slice(effectiveMaxInline).map((action) => ({
|
|
31142
|
-
label: action.label,
|
|
31143
|
-
icon: action.icon,
|
|
31144
|
-
event: action.event,
|
|
31145
|
-
variant: action.variant === "danger" ? "danger" : "default",
|
|
31146
|
-
onClick: () => eventBus.emit(`UI:${action.event}`, {
|
|
31147
|
-
id: row.id,
|
|
31148
|
-
row
|
|
31149
|
-
})
|
|
31150
|
-
}))
|
|
31151
|
-
}
|
|
31152
|
-
)
|
|
31153
|
-
]
|
|
33718
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
33719
|
+
exports.Menu,
|
|
33720
|
+
{
|
|
33721
|
+
position: "bottom-end",
|
|
33722
|
+
trigger: /* @__PURE__ */ jsxRuntime.jsx(exports.Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", "data-row-id": String(row.id), children: /* @__PURE__ */ jsxRuntime.jsx(exports.Icon, { name: "more-horizontal", size: "xs" }) }),
|
|
33723
|
+
items: actionDefs.map((action) => ({
|
|
33724
|
+
label: action.label,
|
|
33725
|
+
icon: action.icon,
|
|
33726
|
+
event: action.event,
|
|
33727
|
+
variant: action.variant === "danger" ? "danger" : "default",
|
|
33728
|
+
onClick: () => eventBus.emit(`UI:${action.event}`, {
|
|
33729
|
+
id: row.id,
|
|
33730
|
+
row
|
|
33731
|
+
})
|
|
33732
|
+
}))
|
|
33733
|
+
}
|
|
33734
|
+
)
|
|
31154
33735
|
}
|
|
31155
33736
|
)
|
|
31156
33737
|
]
|
|
@@ -31193,7 +33774,6 @@ var init_TableView = __esm({
|
|
|
31193
33774
|
init_format();
|
|
31194
33775
|
init_getNestedValue();
|
|
31195
33776
|
init_useEventBus();
|
|
31196
|
-
init_useMediaQuery();
|
|
31197
33777
|
init_Box();
|
|
31198
33778
|
init_Stack();
|
|
31199
33779
|
init_Typography();
|
|
@@ -39793,6 +42373,8 @@ var init_molecules2 = __esm({
|
|
|
39793
42373
|
init_BiologyCanvas();
|
|
39794
42374
|
init_ChemistryCanvas();
|
|
39795
42375
|
init_AlgorithmCanvas();
|
|
42376
|
+
init_AlgoGraphCanvas();
|
|
42377
|
+
init_learningScene3D();
|
|
39796
42378
|
init_GraphView();
|
|
39797
42379
|
init_MapView();
|
|
39798
42380
|
init_NumberStepper();
|
|
@@ -41046,7 +43628,7 @@ function getEnumOptions(field) {
|
|
|
41046
43628
|
}));
|
|
41047
43629
|
}
|
|
41048
43630
|
const validation = field.validation;
|
|
41049
|
-
if (validation?.enum &&
|
|
43631
|
+
if (validation?.enum && validation.enum.length > 0) {
|
|
41050
43632
|
return validation.enum.map((v) => ({
|
|
41051
43633
|
value: v,
|
|
41052
43634
|
label: v.charAt(0).toUpperCase() + v.slice(1).replace(/_/g, " ")
|
|
@@ -46474,6 +49056,7 @@ var init_component_registry_generated = __esm({
|
|
|
46474
49056
|
init_ActionTile();
|
|
46475
49057
|
init_ActivationBlock();
|
|
46476
49058
|
init_ComponentPatterns();
|
|
49059
|
+
init_AlgoGraphCanvas();
|
|
46477
49060
|
init_AlgorithmCanvas();
|
|
46478
49061
|
init_AnimatedCounter();
|
|
46479
49062
|
init_AnimatedGraphic();
|
|
@@ -46737,6 +49320,7 @@ var init_component_registry_generated = __esm({
|
|
|
46737
49320
|
"ActivationBlock": exports.ActivationBlock,
|
|
46738
49321
|
"Alert": AlertPattern,
|
|
46739
49322
|
"AlertPattern": AlertPattern,
|
|
49323
|
+
"AlgoGraphCanvas": exports.AlgoGraphCanvas,
|
|
46740
49324
|
"AlgorithmCanvas": exports.AlgorithmCanvas,
|
|
46741
49325
|
"AnimatedCounter": exports.AnimatedCounter,
|
|
46742
49326
|
"AnimatedGraphic": exports.AnimatedGraphic,
|
|
@@ -50209,7 +52793,23 @@ init_useAuthContext();
|
|
|
50209
52793
|
init_useSwipeGesture();
|
|
50210
52794
|
init_useLongPress();
|
|
50211
52795
|
init_useDragReorder();
|
|
50212
|
-
|
|
52796
|
+
function useMediaQuery(query) {
|
|
52797
|
+
const subscribe = React77.useCallback(
|
|
52798
|
+
(onChange) => {
|
|
52799
|
+
const mql = window.matchMedia(query);
|
|
52800
|
+
mql.addEventListener("change", onChange);
|
|
52801
|
+
return () => mql.removeEventListener("change", onChange);
|
|
52802
|
+
},
|
|
52803
|
+
[query]
|
|
52804
|
+
);
|
|
52805
|
+
return React77.useSyncExternalStore(
|
|
52806
|
+
subscribe,
|
|
52807
|
+
() => window.matchMedia(query).matches,
|
|
52808
|
+
() => false
|
|
52809
|
+
);
|
|
52810
|
+
}
|
|
52811
|
+
|
|
52812
|
+
// hooks/index.ts
|
|
50213
52813
|
init_useInfiniteScroll();
|
|
50214
52814
|
init_usePullToRefresh();
|
|
50215
52815
|
init_useCanvasGestures();
|
|
@@ -50501,6 +53101,7 @@ exports.GameIcon = GameIcon;
|
|
|
50501
53101
|
exports.GameMenu = GameMenu;
|
|
50502
53102
|
exports.HealthBar = HealthBar;
|
|
50503
53103
|
exports.I18nProvider = I18nProvider;
|
|
53104
|
+
exports.LearningScene3D = LearningScene3D;
|
|
50504
53105
|
exports.MapView = MapView;
|
|
50505
53106
|
exports.MasterDetail = MasterDetail;
|
|
50506
53107
|
exports.NotifyListener = NotifyListener;
|
|
@@ -50526,6 +53127,8 @@ exports.TraitStateViewer = TraitStateViewer;
|
|
|
50526
53127
|
exports.TransitionArrow = TransitionArrow;
|
|
50527
53128
|
exports.UISlotComponent = UISlotComponent;
|
|
50528
53129
|
exports.UISlotRenderer = UISlotRenderer;
|
|
53130
|
+
exports.arrowBetween = arrowBetween;
|
|
53131
|
+
exports.billboardLabel = billboardLabel;
|
|
50529
53132
|
exports.boardEntity = boardEntity;
|
|
50530
53133
|
exports.bool = bool;
|
|
50531
53134
|
exports.calculateAttackTargets = calculateAttackTargets;
|
|
@@ -50535,6 +53138,8 @@ exports.createInitialGameState = createInitialGameState;
|
|
|
50535
53138
|
exports.createSharedEntityStore = createSharedEntityStore;
|
|
50536
53139
|
exports.createTranslate = createTranslate;
|
|
50537
53140
|
exports.createUnitAnimationState = createUnitAnimationState;
|
|
53141
|
+
exports.cylinderBetween = cylinderBetween;
|
|
53142
|
+
exports.get3DClickPayload = get3DClickPayload;
|
|
50538
53143
|
exports.getCurrentFrame = getCurrentFrame;
|
|
50539
53144
|
exports.getTileDimensions = getTileDimensions;
|
|
50540
53145
|
exports.inferDirection = inferDirection;
|
|
@@ -50542,6 +53147,7 @@ exports.isoToScreen = isoToScreen;
|
|
|
50542
53147
|
exports.makeAsset = makeAsset;
|
|
50543
53148
|
exports.makeAssetMap = makeAssetMap;
|
|
50544
53149
|
exports.mapBookData = mapBookData;
|
|
53150
|
+
exports.meshSphere = meshSphere;
|
|
50545
53151
|
exports.num = num;
|
|
50546
53152
|
exports.objAvailableActions = objAvailableActions;
|
|
50547
53153
|
exports.objAvailableEvents = objAvailableEvents;
|