@almadar/ui 5.149.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/avl/index.cjs +2313 -155
- package/dist/avl/index.js +2313 -155
- package/dist/components/index.cjs +2327 -153
- package/dist/components/index.d.cts +811 -15
- package/dist/components/index.d.ts +811 -15
- package/dist/components/index.js +2329 -155
- package/dist/hooks/index.cjs +1 -1
- package/dist/hooks/index.js +1 -1
- package/dist/lib/drawable/three/index.cjs +1 -1
- package/dist/lib/drawable/three/index.js +1 -1
- package/dist/marketing/index.cjs +1 -1
- package/dist/marketing/index.js +1 -1
- package/dist/providers/index.cjs +2306 -149
- package/dist/providers/index.js +2306 -149
- package/dist/runtime/index.cjs +2313 -155
- package/dist/runtime/index.d.cts +2 -2
- package/dist/runtime/index.d.ts +2 -2
- package/dist/runtime/index.js +2313 -155
- 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(
|
|
@@ -9903,6 +10864,12 @@ function LearningScene3D({
|
|
|
9903
10864
|
const unitId = event.payload?.unitId;
|
|
9904
10865
|
if (typeof unitId === "string") onItemClickRef.current?.(unitId);
|
|
9905
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
|
+
}
|
|
9906
10873
|
const props3d = {
|
|
9907
10874
|
drawables,
|
|
9908
10875
|
isLoading,
|
|
@@ -9968,7 +10935,7 @@ function cylinderBetween(from, to, radius, color) {
|
|
|
9968
10935
|
material: { color }
|
|
9969
10936
|
};
|
|
9970
10937
|
}
|
|
9971
|
-
function arrowBetween(from, to, color, shaftRadius = 0.08) {
|
|
10938
|
+
function arrowBetween(from, to, color, shaftRadius = 0.08, id) {
|
|
9972
10939
|
const len = segmentLength(from, to);
|
|
9973
10940
|
if (len < 1e-6) return null;
|
|
9974
10941
|
const tipLen = Math.min(shaftRadius * 8, len * 0.35);
|
|
@@ -9996,6 +10963,7 @@ function arrowBetween(from, to, color, shaftRadius = 0.08) {
|
|
|
9996
10963
|
};
|
|
9997
10964
|
return {
|
|
9998
10965
|
type: "draw-group",
|
|
10966
|
+
...id !== void 0 ? { id } : {},
|
|
9999
10967
|
position: { x: from[0], y: from[1], z: from[2] },
|
|
10000
10968
|
items: tipLenActual < 1e-6 ? shaft ? [shaft] : [] : shaft ? [shaft, tip] : [tip]
|
|
10001
10969
|
};
|
|
@@ -10022,20 +10990,228 @@ function get3DClickPayload(onShapeClick, idToIndex) {
|
|
|
10022
10990
|
if (!onShapeClick) return void 0;
|
|
10023
10991
|
return (id) => onShapeClick({ id, index: idToIndex.get(id) ?? -1 });
|
|
10024
10992
|
}
|
|
10025
|
-
|
|
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;
|
|
10026
11200
|
var init_learningScene3D = __esm({
|
|
10027
11201
|
"components/learning/molecules/learningScene3D.tsx"() {
|
|
10028
11202
|
"use client";
|
|
10029
11203
|
init_atoms();
|
|
10030
11204
|
init_Stack();
|
|
10031
11205
|
init_useEventBus();
|
|
11206
|
+
sceneLog = logger.createLogger("almadar:ui:learning-scene-3d");
|
|
11207
|
+
KNOWN_BLOOM_KEYS = /* @__PURE__ */ new Set(["intensity", "threshold", "smoothing"]);
|
|
10032
11208
|
Canvas3DHost = React77.lazy(
|
|
10033
11209
|
() => import('@almadar/ui/components/molecules/game/three').then((m) => ({ default: m.Canvas3DHost }))
|
|
10034
11210
|
);
|
|
10035
11211
|
LearningScene3D.displayName = "LearningScene3D";
|
|
10036
11212
|
}
|
|
10037
11213
|
});
|
|
10038
|
-
var biologyLog; exports.BiologyCanvas = void 0;
|
|
11214
|
+
var biologyLog, BIO_BAND_COLORS, BIO_STAGE_FILL, BIO_STAGE_TEXT; exports.BiologyCanvas = void 0;
|
|
10039
11215
|
var init_BiologyCanvas = __esm({
|
|
10040
11216
|
"components/learning/molecules/BiologyCanvas.tsx"() {
|
|
10041
11217
|
"use client";
|
|
@@ -10044,6 +11220,17 @@ var init_BiologyCanvas = __esm({
|
|
|
10044
11220
|
init_LearningCanvas();
|
|
10045
11221
|
init_learningScene3D();
|
|
10046
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
|
+
};
|
|
10047
11234
|
exports.BiologyCanvas = ({
|
|
10048
11235
|
className,
|
|
10049
11236
|
width = 600,
|
|
@@ -10056,7 +11243,15 @@ var init_BiologyCanvas = __esm({
|
|
|
10056
11243
|
post,
|
|
10057
11244
|
nodes = [],
|
|
10058
11245
|
edges = [],
|
|
11246
|
+
compartments = [],
|
|
11247
|
+
bands = [],
|
|
11248
|
+
stages = [],
|
|
11249
|
+
stageStyle = "timeline",
|
|
11250
|
+
helix,
|
|
11251
|
+
helix3d,
|
|
10059
11252
|
shapes = [],
|
|
11253
|
+
readouts,
|
|
11254
|
+
traces,
|
|
10060
11255
|
showGrid,
|
|
10061
11256
|
shadows,
|
|
10062
11257
|
interactive,
|
|
@@ -10071,19 +11266,148 @@ var init_BiologyCanvas = __esm({
|
|
|
10071
11266
|
for (const n of nodes) {
|
|
10072
11267
|
if (n.id) nodeById.set(n.id, n);
|
|
10073
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
|
+
}
|
|
10074
11378
|
for (const e of edges) {
|
|
10075
11379
|
const a = nodeById.get(e.from);
|
|
10076
11380
|
const b = nodeById.get(e.to);
|
|
10077
11381
|
if (!a || !b) continue;
|
|
10078
|
-
|
|
10079
|
-
|
|
10080
|
-
|
|
10081
|
-
|
|
10082
|
-
|
|
10083
|
-
|
|
10084
|
-
|
|
10085
|
-
|
|
10086
|
-
|
|
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
|
+
}
|
|
10087
11411
|
if (e.label) {
|
|
10088
11412
|
out.push({
|
|
10089
11413
|
type: "text",
|
|
@@ -10096,6 +11420,8 @@ var init_BiologyCanvas = __esm({
|
|
|
10096
11420
|
}
|
|
10097
11421
|
}
|
|
10098
11422
|
for (const n of nodes) {
|
|
11423
|
+
const state = n.state ?? "default";
|
|
11424
|
+
const muted = state === "muted";
|
|
10099
11425
|
out.push({
|
|
10100
11426
|
type: "circle",
|
|
10101
11427
|
x: n.x,
|
|
@@ -10103,28 +11429,127 @@ var init_BiologyCanvas = __esm({
|
|
|
10103
11429
|
radius: n.radius ?? 16,
|
|
10104
11430
|
color: n.color ?? "#16a34a",
|
|
10105
11431
|
fill: `${n.color ?? "#16a34a"}33`,
|
|
10106
|
-
id: n.id
|
|
11432
|
+
id: n.id,
|
|
11433
|
+
...muted ? { opacity: 0.35 } : {}
|
|
10107
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
|
+
}
|
|
10108
11445
|
if (n.label) {
|
|
10109
11446
|
out.push({
|
|
10110
11447
|
type: "text",
|
|
10111
11448
|
x: n.x,
|
|
10112
11449
|
y: n.y + (n.radius ?? 16) + 14,
|
|
10113
11450
|
text: n.label,
|
|
11451
|
+
...muted ? { opacity: 0.35 } : {},
|
|
10114
11452
|
color: "#111827",
|
|
10115
11453
|
fontSize: 12,
|
|
10116
11454
|
align: "center"
|
|
10117
11455
|
});
|
|
10118
11456
|
}
|
|
10119
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
|
+
}
|
|
10120
11534
|
out.push(...shapes);
|
|
10121
11535
|
return out;
|
|
10122
|
-
}, [nodes, edges, shapes]);
|
|
11536
|
+
}, [nodes, edges, compartments, bands, stages, stageStyle, helix, shapes, width, height]);
|
|
10123
11537
|
const drawables3D = React77.useMemo(() => {
|
|
10124
11538
|
if (mode !== "3d") return [];
|
|
10125
11539
|
if (shapes.length > 0) {
|
|
10126
11540
|
biologyLog.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
|
|
10127
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
|
+
}
|
|
10128
11553
|
const out = [];
|
|
10129
11554
|
const labelColor = labelColorForBackground(backgroundColor);
|
|
10130
11555
|
const nodeById = /* @__PURE__ */ new Map();
|
|
@@ -10158,15 +11583,21 @@ var init_BiologyCanvas = __esm({
|
|
|
10158
11583
|
out.push(billboardLabel(n.label, n.x, n.y, nz + radius, { color: labelColor }));
|
|
10159
11584
|
}
|
|
10160
11585
|
}
|
|
11586
|
+
if (helix3d) {
|
|
11587
|
+
out.push(...helixDrawables(helix3d, { labelColor }));
|
|
11588
|
+
}
|
|
10161
11589
|
return out;
|
|
10162
|
-
}, [mode, nodes, edges, shapes, backgroundColor]);
|
|
11590
|
+
}, [mode, nodes, edges, shapes, compartments, bands, stages, helix, helix3d, animate, backgroundColor]);
|
|
10163
11591
|
const nodeIndexById = React77.useMemo(() => {
|
|
10164
11592
|
const m = /* @__PURE__ */ new Map();
|
|
11593
|
+
(helix3d?.rungs ?? []).forEach((rung, i) => {
|
|
11594
|
+
if (rung.id) m.set(rung.id, i);
|
|
11595
|
+
});
|
|
10165
11596
|
nodes.forEach((n, i) => {
|
|
10166
11597
|
if (n.id) m.set(n.id, i);
|
|
10167
11598
|
});
|
|
10168
11599
|
return m;
|
|
10169
|
-
}, [nodes]);
|
|
11600
|
+
}, [nodes, helix3d]);
|
|
10170
11601
|
if (mode === "3d") {
|
|
10171
11602
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
10172
11603
|
LearningScene3D,
|
|
@@ -10198,6 +11629,8 @@ var init_BiologyCanvas = __esm({
|
|
|
10198
11629
|
height,
|
|
10199
11630
|
backgroundColor,
|
|
10200
11631
|
shapes: derivedShapes,
|
|
11632
|
+
readouts,
|
|
11633
|
+
traces,
|
|
10201
11634
|
interactive: interactive ?? false,
|
|
10202
11635
|
animate,
|
|
10203
11636
|
onShapeClick,
|
|
@@ -19388,7 +20821,7 @@ function bondPerpendicular(a, b) {
|
|
|
19388
20821
|
if (len < 1e-6) return [1, 0, 0];
|
|
19389
20822
|
return [px / len, py / len, 0];
|
|
19390
20823
|
}
|
|
19391
|
-
var chemistryLog; exports.ChemistryCanvas = void 0;
|
|
20824
|
+
var chemistryLog, CHEM_BOND_STATE_COLOR, LONE_PAIR_ANGLES; exports.ChemistryCanvas = void 0;
|
|
19392
20825
|
var init_ChemistryCanvas = __esm({
|
|
19393
20826
|
"components/learning/molecules/ChemistryCanvas.tsx"() {
|
|
19394
20827
|
"use client";
|
|
@@ -19397,6 +20830,13 @@ var init_ChemistryCanvas = __esm({
|
|
|
19397
20830
|
init_LearningCanvas();
|
|
19398
20831
|
init_learningScene3D();
|
|
19399
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];
|
|
19400
20840
|
exports.ChemistryCanvas = ({
|
|
19401
20841
|
className,
|
|
19402
20842
|
width = 600,
|
|
@@ -19410,7 +20850,14 @@ var init_ChemistryCanvas = __esm({
|
|
|
19410
20850
|
atoms = [],
|
|
19411
20851
|
bonds = [],
|
|
19412
20852
|
arrows = [],
|
|
20853
|
+
bondStyle = "thick",
|
|
20854
|
+
containers = [],
|
|
20855
|
+
equation,
|
|
20856
|
+
equationColor,
|
|
20857
|
+
lattice3d,
|
|
19413
20858
|
shapes = [],
|
|
20859
|
+
readouts,
|
|
20860
|
+
traces,
|
|
19414
20861
|
showGrid,
|
|
19415
20862
|
shadows,
|
|
19416
20863
|
interactive,
|
|
@@ -19425,21 +20872,118 @@ var init_ChemistryCanvas = __esm({
|
|
|
19425
20872
|
for (const a of atoms) {
|
|
19426
20873
|
if (a.id) atomById.set(a.id, a);
|
|
19427
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
|
+
}
|
|
19428
20946
|
for (const b of bonds) {
|
|
19429
20947
|
const a = atomById.get(b.from);
|
|
19430
20948
|
const c = atomById.get(b.to);
|
|
19431
20949
|
if (!a || !c) continue;
|
|
19432
|
-
const
|
|
19433
|
-
const
|
|
19434
|
-
|
|
19435
|
-
|
|
19436
|
-
|
|
19437
|
-
|
|
19438
|
-
|
|
19439
|
-
|
|
19440
|
-
|
|
19441
|
-
|
|
19442
|
-
|
|
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
|
+
}
|
|
19443
20987
|
}
|
|
19444
20988
|
for (const a of arrows) {
|
|
19445
20989
|
const angle = (a.angle ?? 0) * (Math.PI / 180);
|
|
@@ -19488,15 +21032,62 @@ var init_ChemistryCanvas = __esm({
|
|
|
19488
21032
|
align: "center"
|
|
19489
21033
|
});
|
|
19490
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
|
+
});
|
|
19491
21076
|
}
|
|
19492
21077
|
out.push(...shapes);
|
|
19493
21078
|
return out;
|
|
19494
|
-
}, [atoms, bonds, arrows, shapes]);
|
|
21079
|
+
}, [atoms, bonds, arrows, bondStyle, containers, equation, equationColor, shapes, width]);
|
|
19495
21080
|
const drawables3D = React77.useMemo(() => {
|
|
19496
21081
|
if (mode !== "3d") return [];
|
|
19497
21082
|
if (shapes.length > 0) {
|
|
19498
21083
|
chemistryLog.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
|
|
19499
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
|
+
}
|
|
19500
21091
|
const out = [];
|
|
19501
21092
|
const labelColor = labelColorForBackground(backgroundColor);
|
|
19502
21093
|
const atomById = /* @__PURE__ */ new Map();
|
|
@@ -19543,8 +21134,11 @@ var init_ChemistryCanvas = __esm({
|
|
|
19543
21134
|
out.push(billboardLabel(a.element, a.x, a.y, az + radius, { color: labelColor }));
|
|
19544
21135
|
}
|
|
19545
21136
|
}
|
|
21137
|
+
if (lattice3d) {
|
|
21138
|
+
out.push(...latticeDrawables(lattice3d, { labelColor }));
|
|
21139
|
+
}
|
|
19546
21140
|
return out;
|
|
19547
|
-
}, [mode, atoms, bonds, arrows, shapes, backgroundColor]);
|
|
21141
|
+
}, [mode, atoms, bonds, arrows, shapes, containers, lattice3d, animate, backgroundColor]);
|
|
19548
21142
|
const atomIndexById = React77.useMemo(() => {
|
|
19549
21143
|
const m = /* @__PURE__ */ new Map();
|
|
19550
21144
|
atoms.forEach((a, i) => {
|
|
@@ -19583,6 +21177,8 @@ var init_ChemistryCanvas = __esm({
|
|
|
19583
21177
|
height,
|
|
19584
21178
|
backgroundColor,
|
|
19585
21179
|
shapes: derivedShapes,
|
|
21180
|
+
readouts,
|
|
21181
|
+
traces,
|
|
19586
21182
|
interactive: interactive ?? false,
|
|
19587
21183
|
animate,
|
|
19588
21184
|
onShapeClick,
|
|
@@ -29495,6 +31091,10 @@ var init_molecules = __esm({
|
|
|
29495
31091
|
init_GameShell();
|
|
29496
31092
|
}
|
|
29497
31093
|
});
|
|
31094
|
+
function formatTick(v) {
|
|
31095
|
+
if (Number.isInteger(v)) return String(v);
|
|
31096
|
+
return v.toFixed(2).replace(/0+$/, "").replace(/\.$/, "");
|
|
31097
|
+
}
|
|
29498
31098
|
exports.MathCanvas = void 0;
|
|
29499
31099
|
var init_MathCanvas = __esm({
|
|
29500
31100
|
"components/learning/molecules/MathCanvas.tsx"() {
|
|
@@ -29514,10 +31114,19 @@ var init_MathCanvas = __esm({
|
|
|
29514
31114
|
showAxes = true,
|
|
29515
31115
|
showGrid = true,
|
|
29516
31116
|
gridStep = 1,
|
|
31117
|
+
showTickLabels = false,
|
|
31118
|
+
showCurveLabels = false,
|
|
29517
31119
|
curves = [],
|
|
29518
31120
|
points = [],
|
|
29519
31121
|
vectors = [],
|
|
31122
|
+
regions = [],
|
|
31123
|
+
bars = [],
|
|
31124
|
+
guides = [],
|
|
31125
|
+
angles = [],
|
|
31126
|
+
hops = [],
|
|
29520
31127
|
shapes = [],
|
|
31128
|
+
readouts,
|
|
31129
|
+
traces,
|
|
29521
31130
|
interactive = false,
|
|
29522
31131
|
animate = false,
|
|
29523
31132
|
onShapeClick,
|
|
@@ -29531,6 +31140,8 @@ var init_MathCanvas = __esm({
|
|
|
29531
31140
|
const plotH = height - margin * 2;
|
|
29532
31141
|
const mapX = (x) => margin + (x - xMin) / (xMax - xMin) * plotW;
|
|
29533
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)));
|
|
29534
31145
|
if (showGrid) {
|
|
29535
31146
|
for (let x = Math.ceil(xMin / gridStep) * gridStep; x <= xMax; x += gridStep) {
|
|
29536
31147
|
const px = mapX(x);
|
|
@@ -29541,14 +31152,99 @@ var init_MathCanvas = __esm({
|
|
|
29541
31152
|
out.push({ type: "line", x1: margin, y1: py, x2: width - margin, y2: py, color: "#e5e7eb", lineWidth: 1 });
|
|
29542
31153
|
}
|
|
29543
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
|
+
}
|
|
29544
31222
|
if (showAxes) {
|
|
29545
|
-
const xAxisY = Math.max(margin, Math.min(height - margin, mapY(0)));
|
|
29546
|
-
const yAxisX = Math.max(margin, Math.min(width - margin, mapX(0)));
|
|
29547
31223
|
out.push({ type: "line", x1: margin, y1: xAxisY, x2: width - margin, y2: xAxisY, color: "#374151", lineWidth: 2 });
|
|
29548
31224
|
out.push({ type: "line", x1: yAxisX, y1: margin, x2: yAxisX, y2: height - margin, color: "#374151", lineWidth: 2 });
|
|
29549
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
|
+
}
|
|
29550
31245
|
for (const curve of curves) {
|
|
29551
31246
|
if (!curve.samples || curve.samples.length < 2) continue;
|
|
31247
|
+
let lastInRange;
|
|
29552
31248
|
for (let i = 1; i < curve.samples.length; i++) {
|
|
29553
31249
|
const a = curve.samples[i - 1];
|
|
29554
31250
|
const b = curve.samples[i];
|
|
@@ -29560,19 +31256,97 @@ var init_MathCanvas = __esm({
|
|
|
29560
31256
|
x2: mapX(b.x),
|
|
29561
31257
|
y2: mapY(b.y),
|
|
29562
31258
|
color: curve.color ?? "#2563eb",
|
|
29563
|
-
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"
|
|
29564
31337
|
});
|
|
29565
31338
|
}
|
|
29566
31339
|
}
|
|
29567
31340
|
for (const p of points) {
|
|
29568
31341
|
if (p.x < xMin || p.x > xMax || p.y < yMin || p.y > yMax) continue;
|
|
31342
|
+
const isOpen = p.style === "open";
|
|
29569
31343
|
out.push({
|
|
29570
31344
|
type: "circle",
|
|
29571
31345
|
x: mapX(p.x),
|
|
29572
31346
|
y: mapY(p.y),
|
|
29573
31347
|
radius: p.radius ?? 4,
|
|
29574
31348
|
color: p.color ?? "#dc2626",
|
|
29575
|
-
fill: p.color ?? "#dc2626"
|
|
31349
|
+
fill: isOpen ? "#ffffff" : p.color ?? "#dc2626"
|
|
29576
31350
|
});
|
|
29577
31351
|
if (p.label) {
|
|
29578
31352
|
out.push({ type: "text", x: mapX(p.x) + 8, y: mapY(p.y) - 8, text: p.label, color: "#111827", fontSize: 12 });
|
|
@@ -29591,7 +31365,28 @@ var init_MathCanvas = __esm({
|
|
|
29591
31365
|
}
|
|
29592
31366
|
out.push(...shapes);
|
|
29593
31367
|
return out;
|
|
29594
|
-
}, [
|
|
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
|
+
]);
|
|
29595
31390
|
return /* @__PURE__ */ jsxRuntime.jsx(exports.Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(exports.VStack, { gap: "sm", children: [
|
|
29596
31391
|
title ? /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "h4", children: title }) : null,
|
|
29597
31392
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -29600,6 +31395,8 @@ var init_MathCanvas = __esm({
|
|
|
29600
31395
|
width,
|
|
29601
31396
|
height,
|
|
29602
31397
|
shapes: derivedShapes,
|
|
31398
|
+
readouts,
|
|
31399
|
+
traces,
|
|
29603
31400
|
interactive,
|
|
29604
31401
|
animate,
|
|
29605
31402
|
onShapeClick,
|
|
@@ -29611,7 +31408,315 @@ var init_MathCanvas = __esm({
|
|
|
29611
31408
|
};
|
|
29612
31409
|
}
|
|
29613
31410
|
});
|
|
29614
|
-
|
|
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;
|
|
29615
31720
|
var init_PhysicsCanvas = __esm({
|
|
29616
31721
|
"components/learning/molecules/PhysicsCanvas.tsx"() {
|
|
29617
31722
|
"use client";
|
|
@@ -29620,6 +31725,7 @@ var init_PhysicsCanvas = __esm({
|
|
|
29620
31725
|
init_LearningCanvas();
|
|
29621
31726
|
init_learningScene3D();
|
|
29622
31727
|
physicsLog2 = logger.createLogger("almadar:ui:physics-canvas");
|
|
31728
|
+
PHYSICS_LABEL_COLOR = "#374151";
|
|
29623
31729
|
exports.PhysicsCanvas = ({
|
|
29624
31730
|
className,
|
|
29625
31731
|
width = 600,
|
|
@@ -29636,7 +31742,18 @@ var init_PhysicsCanvas = __esm({
|
|
|
29636
31742
|
showForces = false,
|
|
29637
31743
|
velocityScale = 20,
|
|
29638
31744
|
forceScale = 20,
|
|
31745
|
+
sceneObjects = [],
|
|
31746
|
+
trails = [],
|
|
31747
|
+
vectors = [],
|
|
31748
|
+
surface3d,
|
|
31749
|
+
vectors3d = [],
|
|
31750
|
+
vectorScale = 1,
|
|
31751
|
+
angles = [],
|
|
31752
|
+
field,
|
|
31753
|
+
meters = [],
|
|
29639
31754
|
shapes = [],
|
|
31755
|
+
readouts,
|
|
31756
|
+
traces,
|
|
29640
31757
|
showGrid,
|
|
29641
31758
|
shadows,
|
|
29642
31759
|
interactive,
|
|
@@ -29651,19 +31768,14 @@ var init_PhysicsCanvas = __esm({
|
|
|
29651
31768
|
for (const b of bodies) {
|
|
29652
31769
|
if (b.id) bodyById.set(b.id, b);
|
|
29653
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));
|
|
29654
31774
|
for (const c of constraints) {
|
|
29655
31775
|
const a = bodyById.get(c.from);
|
|
29656
31776
|
const b = bodyById.get(c.to);
|
|
29657
31777
|
if (!a || !b) continue;
|
|
29658
|
-
out.push(
|
|
29659
|
-
type: "line",
|
|
29660
|
-
x1: a.x,
|
|
29661
|
-
y1: a.y,
|
|
29662
|
-
x2: b.x,
|
|
29663
|
-
y2: b.y,
|
|
29664
|
-
color: c.color ?? "#9ca3af",
|
|
29665
|
-
lineWidth: 2
|
|
29666
|
-
});
|
|
31778
|
+
out.push(...constraintShapes(c, a, b));
|
|
29667
31779
|
}
|
|
29668
31780
|
for (const b of bodies) {
|
|
29669
31781
|
out.push({
|
|
@@ -29708,14 +31820,51 @@ var init_PhysicsCanvas = __esm({
|
|
|
29708
31820
|
});
|
|
29709
31821
|
}
|
|
29710
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));
|
|
29711
31826
|
out.push(...shapes);
|
|
29712
31827
|
return out;
|
|
29713
|
-
}, [
|
|
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
|
+
]);
|
|
29714
31845
|
const drawables3D = React77.useMemo(() => {
|
|
29715
31846
|
if (mode !== "3d") return [];
|
|
29716
31847
|
if (shapes.length > 0) {
|
|
29717
31848
|
physicsLog2.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
|
|
29718
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
|
+
}
|
|
29719
31868
|
const out = [];
|
|
29720
31869
|
const labelColor = labelColorForBackground(backgroundColor);
|
|
29721
31870
|
const bodyById = /* @__PURE__ */ new Map();
|
|
@@ -29763,15 +31912,67 @@ var init_PhysicsCanvas = __esm({
|
|
|
29763
31912
|
if (arrow) out.push(arrow);
|
|
29764
31913
|
}
|
|
29765
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
|
+
}
|
|
29766
31944
|
return out;
|
|
29767
|
-
}, [
|
|
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
|
+
]);
|
|
29768
31966
|
const bodyIndexById = React77.useMemo(() => {
|
|
29769
31967
|
const m = /* @__PURE__ */ new Map();
|
|
31968
|
+
vectors3d.forEach((v, i) => {
|
|
31969
|
+
if (v.id) m.set(v.id, i);
|
|
31970
|
+
});
|
|
29770
31971
|
bodies.forEach((b, i) => {
|
|
29771
31972
|
if (b.id) m.set(b.id, i);
|
|
29772
31973
|
});
|
|
29773
31974
|
return m;
|
|
29774
|
-
}, [bodies]);
|
|
31975
|
+
}, [bodies, vectors3d]);
|
|
29775
31976
|
if (mode === "3d") {
|
|
29776
31977
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
29777
31978
|
LearningScene3D,
|
|
@@ -29803,6 +32004,8 @@ var init_PhysicsCanvas = __esm({
|
|
|
29803
32004
|
height,
|
|
29804
32005
|
backgroundColor,
|
|
29805
32006
|
shapes: derivedShapes,
|
|
32007
|
+
readouts,
|
|
32008
|
+
traces,
|
|
29806
32009
|
interactive: interactive ?? false,
|
|
29807
32010
|
animate,
|
|
29808
32011
|
onShapeClick,
|
|
@@ -29953,7 +32156,7 @@ function layoutFlow(nodeIds, adjacency, roots, width, height, margin) {
|
|
|
29953
32156
|
}
|
|
29954
32157
|
return nodeIds.map((id) => positions.get(id));
|
|
29955
32158
|
}
|
|
29956
|
-
function
|
|
32159
|
+
function layoutTree2(nodeIds, adjacency, roots, width, height, margin) {
|
|
29957
32160
|
const effectiveRoots = roots.length > 0 ? roots : [nodeIds[0]];
|
|
29958
32161
|
const layers = assignLayers(nodeIds, adjacency, effectiveRoots);
|
|
29959
32162
|
const maxLayer = Math.max(...Array.from(layers.values()));
|
|
@@ -30009,7 +32212,7 @@ function computeStaticLayout(mode, input) {
|
|
|
30009
32212
|
const adjacency = buildAdjacency(nodeIds, edges);
|
|
30010
32213
|
const roots = findRoots(nodeIds, adjacency);
|
|
30011
32214
|
if (mode === "flow") return layoutFlow(nodeIds, adjacency, roots, width, height, margin);
|
|
30012
|
-
if (mode === "tree") return
|
|
32215
|
+
if (mode === "tree") return layoutTree2(nodeIds, adjacency, roots, width, height, margin);
|
|
30013
32216
|
return layoutRadial(nodeIds, adjacency, roots, width, height, margin);
|
|
30014
32217
|
}
|
|
30015
32218
|
var init_graphViewLayouts = __esm({
|
|
@@ -30421,7 +32624,7 @@ var init_MapView = __esm({
|
|
|
30421
32624
|
shadowSize: [41, 41]
|
|
30422
32625
|
});
|
|
30423
32626
|
L.Marker.prototype.options.icon = defaultIcon;
|
|
30424
|
-
const { useEffect: useEffect66, useRef: useRef65, useCallback:
|
|
32627
|
+
const { useEffect: useEffect66, useRef: useRef65, useCallback: useCallback108, useState: useState104 } = React77__namespace.default;
|
|
30425
32628
|
const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
|
|
30426
32629
|
const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
|
|
30427
32630
|
function MapUpdater({ centerLat, centerLng, zoom }) {
|
|
@@ -30467,7 +32670,7 @@ var init_MapView = __esm({
|
|
|
30467
32670
|
}) {
|
|
30468
32671
|
const eventBus = useEventBus2();
|
|
30469
32672
|
const [clickedPosition, setClickedPosition] = useState104(null);
|
|
30470
|
-
const handleMapClick =
|
|
32673
|
+
const handleMapClick = useCallback108((lat, lng) => {
|
|
30471
32674
|
if (showClickedPin) {
|
|
30472
32675
|
setClickedPosition({ lat, lng });
|
|
30473
32676
|
}
|
|
@@ -30476,7 +32679,7 @@ var init_MapView = __esm({
|
|
|
30476
32679
|
eventBus.emit(`UI:${mapClickEvent}`, { latitude: lat, longitude: lng });
|
|
30477
32680
|
}
|
|
30478
32681
|
}, [onMapClick, mapClickEvent, eventBus, showClickedPin]);
|
|
30479
|
-
const handleMarkerClick =
|
|
32682
|
+
const handleMarkerClick = useCallback108((marker) => {
|
|
30480
32683
|
onMarkerClick?.(marker);
|
|
30481
32684
|
if (markerClickEvent) {
|
|
30482
32685
|
eventBus.emit(`UI:${markerClickEvent}`, { ...marker });
|
|
@@ -31252,26 +33455,6 @@ var init_Lightbox = __esm({
|
|
|
31252
33455
|
exports.Lightbox.displayName = "Lightbox";
|
|
31253
33456
|
}
|
|
31254
33457
|
});
|
|
31255
|
-
function useMediaQuery(query) {
|
|
31256
|
-
const subscribe = React77.useCallback(
|
|
31257
|
-
(onChange) => {
|
|
31258
|
-
const mql = window.matchMedia(query);
|
|
31259
|
-
mql.addEventListener("change", onChange);
|
|
31260
|
-
return () => mql.removeEventListener("change", onChange);
|
|
31261
|
-
},
|
|
31262
|
-
[query]
|
|
31263
|
-
);
|
|
31264
|
-
return React77.useSyncExternalStore(
|
|
31265
|
-
subscribe,
|
|
31266
|
-
() => window.matchMedia(query).matches,
|
|
31267
|
-
() => false
|
|
31268
|
-
);
|
|
31269
|
-
}
|
|
31270
|
-
var init_useMediaQuery = __esm({
|
|
31271
|
-
"hooks/useMediaQuery.ts"() {
|
|
31272
|
-
"use client";
|
|
31273
|
-
}
|
|
31274
|
-
});
|
|
31275
33458
|
function renderIconInput3(icon, props) {
|
|
31276
33459
|
return typeof icon === "string" ? /* @__PURE__ */ jsxRuntime.jsx(exports.Icon, { name: icon, ...props }) : /* @__PURE__ */ jsxRuntime.jsx(exports.Icon, { icon, ...props });
|
|
31277
33460
|
}
|
|
@@ -31310,8 +33493,8 @@ function TableView({
|
|
|
31310
33493
|
columns,
|
|
31311
33494
|
fields,
|
|
31312
33495
|
itemActions,
|
|
31313
|
-
maxInlineActions,
|
|
31314
|
-
itemClickEvent,
|
|
33496
|
+
maxInlineActions: _maxInlineActions,
|
|
33497
|
+
itemClickEvent = "",
|
|
31315
33498
|
selectable = false,
|
|
31316
33499
|
selectEvent,
|
|
31317
33500
|
selectedIds,
|
|
@@ -31361,7 +33544,6 @@ function TableView({
|
|
|
31361
33544
|
const hasMore = pageSize > 0 && visibleCount < ordered2.length;
|
|
31362
33545
|
const hasRenderProp = typeof children === "function";
|
|
31363
33546
|
const idField = dndItemIdField ?? "id";
|
|
31364
|
-
const isCoarsePointer = useMediaQuery("(pointer: coarse)");
|
|
31365
33547
|
React77__namespace.default.useEffect(() => {
|
|
31366
33548
|
tableViewLog.debug("render", {
|
|
31367
33549
|
rowCount: data.length,
|
|
@@ -31403,21 +33585,14 @@ function TableView({
|
|
|
31403
33585
|
const dir = sortColumn === (col.field ?? col.key) && sortDirection === "asc" ? "desc" : "asc";
|
|
31404
33586
|
eventBus.emit(`UI:${sortEvent}`, { column: col.field ?? col.key, direction: dir });
|
|
31405
33587
|
};
|
|
31406
|
-
const
|
|
31407
|
-
e.stopPropagation();
|
|
31408
|
-
const payload = {
|
|
31409
|
-
id: row.id,
|
|
31410
|
-
row
|
|
31411
|
-
};
|
|
31412
|
-
eventBus.emit(`UI:${action.event}`, payload);
|
|
31413
|
-
};
|
|
33588
|
+
const rowClickEvent = itemClickEvent || actionDefs.find((a) => a.variant !== "danger")?.event;
|
|
31414
33589
|
const handleRowClick = (row) => () => {
|
|
31415
|
-
if (!
|
|
33590
|
+
if (!rowClickEvent) return;
|
|
31416
33591
|
const payload = {
|
|
31417
33592
|
id: row.id,
|
|
31418
33593
|
row
|
|
31419
33594
|
};
|
|
31420
|
-
eventBus.emit(`UI:${
|
|
33595
|
+
eventBus.emit(`UI:${rowClickEvent}`, payload);
|
|
31421
33596
|
};
|
|
31422
33597
|
const colFloors = React77__namespace.default.useMemo(
|
|
31423
33598
|
() => colDefs.map((col) => {
|
|
@@ -31433,10 +33608,7 @@ function TableView({
|
|
|
31433
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;
|
|
31434
33609
|
const lk = LOOKS[look];
|
|
31435
33610
|
const hasActions = actionDefs.length > 0;
|
|
31436
|
-
const
|
|
31437
|
-
const inlineActionCount = hasActions ? effectiveMaxInline != null ? Math.min(actionDefs.length, effectiveMaxInline) : actionDefs.length : 0;
|
|
31438
|
-
const hasOverflowActions = hasActions && effectiveMaxInline != null && actionDefs.length > effectiveMaxInline;
|
|
31439
|
-
const actionsTrack = hasActions ? `${inlineActionCount * 6 + (hasOverflowActions ? 3 : 0)}rem` : null;
|
|
33611
|
+
const actionsTrack = hasActions ? "3rem" : null;
|
|
31440
33612
|
const gridTemplateColumns = [
|
|
31441
33613
|
selectable ? "auto" : null,
|
|
31442
33614
|
...colDefs.map((c, i) => c.width ?? `minmax(${colFloors[i]}ch, 1fr)`),
|
|
@@ -31483,7 +33655,7 @@ function TableView({
|
|
|
31483
33655
|
col.key
|
|
31484
33656
|
);
|
|
31485
33657
|
}),
|
|
31486
|
-
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" })
|
|
31487
33659
|
]
|
|
31488
33660
|
}
|
|
31489
33661
|
);
|
|
@@ -31495,12 +33667,12 @@ function TableView({
|
|
|
31495
33667
|
role: "row",
|
|
31496
33668
|
"data-entity-row": true,
|
|
31497
33669
|
"data-entity-id": id,
|
|
31498
|
-
onClick:
|
|
33670
|
+
onClick: rowClickEvent ? handleRowClick(row) : void 0,
|
|
31499
33671
|
style: !hasRenderProp ? { gridTemplateColumns } : void 0,
|
|
31500
33672
|
className: cn(
|
|
31501
33673
|
"group items-center gap-3 transition-colors duration-fast",
|
|
31502
33674
|
hasRenderProp ? "flex" : "grid",
|
|
31503
|
-
|
|
33675
|
+
rowClickEvent && "cursor-pointer",
|
|
31504
33676
|
lk.rowPad,
|
|
31505
33677
|
lk.divider && "border-b border-[var(--color-border)]",
|
|
31506
33678
|
lk.striped && index % 2 === 1 && "bg-[var(--color-surface-subtle)]",
|
|
@@ -31508,7 +33680,7 @@ function TableView({
|
|
|
31508
33680
|
look === "bordered" && "[&>*]:border-r [&>*]:border-[var(--color-border)] [&>*:last-child]:border-r-0"
|
|
31509
33681
|
),
|
|
31510
33682
|
children: [
|
|
31511
|
-
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(
|
|
31512
33684
|
exports.Checkbox,
|
|
31513
33685
|
{
|
|
31514
33686
|
checked: selected.has(id),
|
|
@@ -31529,53 +33701,37 @@ function TableView({
|
|
|
31529
33701
|
}
|
|
31530
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);
|
|
31531
33703
|
}),
|
|
31532
|
-
hasActions && /* @__PURE__ */ jsxRuntime.
|
|
33704
|
+
hasActions && /* @__PURE__ */ jsxRuntime.jsx(
|
|
31533
33705
|
exports.HStack,
|
|
31534
33706
|
{
|
|
31535
33707
|
gap: "xs",
|
|
31536
|
-
onClick:
|
|
33708
|
+
onClick: rowClickEvent ? (e) => e.stopPropagation() : void 0,
|
|
31537
33709
|
className: cn(
|
|
31538
33710
|
// Pinned: the fixed column tracks routinely overflow the caller's
|
|
31539
|
-
// scroll container, which
|
|
31540
|
-
// 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.
|
|
31541
33714
|
"justify-end flex-shrink-0 sticky right-0 z-[1] transition-colors",
|
|
33715
|
+
"border-l border-[var(--color-border)]",
|
|
31542
33716
|
lk.striped && index % 2 === 1 ? "bg-[var(--color-surface-subtle)]" : "bg-[var(--color-card)] group-hover:bg-[var(--color-surface-subtle)]"
|
|
31543
33717
|
),
|
|
31544
|
-
children:
|
|
31545
|
-
|
|
31546
|
-
|
|
31547
|
-
|
|
31548
|
-
|
|
31549
|
-
|
|
31550
|
-
|
|
31551
|
-
|
|
31552
|
-
|
|
31553
|
-
|
|
31554
|
-
|
|
31555
|
-
|
|
31556
|
-
|
|
31557
|
-
|
|
31558
|
-
}
|
|
31559
|
-
|
|
31560
|
-
|
|
31561
|
-
effectiveMaxInline != null && actionDefs.length > effectiveMaxInline && /* @__PURE__ */ jsxRuntime.jsx(
|
|
31562
|
-
exports.Menu,
|
|
31563
|
-
{
|
|
31564
|
-
position: "bottom-end",
|
|
31565
|
-
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" }) }),
|
|
31566
|
-
items: actionDefs.slice(effectiveMaxInline).map((action) => ({
|
|
31567
|
-
label: action.label,
|
|
31568
|
-
icon: action.icon,
|
|
31569
|
-
event: action.event,
|
|
31570
|
-
variant: action.variant === "danger" ? "danger" : "default",
|
|
31571
|
-
onClick: () => eventBus.emit(`UI:${action.event}`, {
|
|
31572
|
-
id: row.id,
|
|
31573
|
-
row
|
|
31574
|
-
})
|
|
31575
|
-
}))
|
|
31576
|
-
}
|
|
31577
|
-
)
|
|
31578
|
-
]
|
|
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
|
+
)
|
|
31579
33735
|
}
|
|
31580
33736
|
)
|
|
31581
33737
|
]
|
|
@@ -31618,7 +33774,6 @@ var init_TableView = __esm({
|
|
|
31618
33774
|
init_format();
|
|
31619
33775
|
init_getNestedValue();
|
|
31620
33776
|
init_useEventBus();
|
|
31621
|
-
init_useMediaQuery();
|
|
31622
33777
|
init_Box();
|
|
31623
33778
|
init_Stack();
|
|
31624
33779
|
init_Typography();
|
|
@@ -40218,6 +42373,7 @@ var init_molecules2 = __esm({
|
|
|
40218
42373
|
init_BiologyCanvas();
|
|
40219
42374
|
init_ChemistryCanvas();
|
|
40220
42375
|
init_AlgorithmCanvas();
|
|
42376
|
+
init_AlgoGraphCanvas();
|
|
40221
42377
|
init_learningScene3D();
|
|
40222
42378
|
init_GraphView();
|
|
40223
42379
|
init_MapView();
|
|
@@ -46900,6 +49056,7 @@ var init_component_registry_generated = __esm({
|
|
|
46900
49056
|
init_ActionTile();
|
|
46901
49057
|
init_ActivationBlock();
|
|
46902
49058
|
init_ComponentPatterns();
|
|
49059
|
+
init_AlgoGraphCanvas();
|
|
46903
49060
|
init_AlgorithmCanvas();
|
|
46904
49061
|
init_AnimatedCounter();
|
|
46905
49062
|
init_AnimatedGraphic();
|
|
@@ -47163,6 +49320,7 @@ var init_component_registry_generated = __esm({
|
|
|
47163
49320
|
"ActivationBlock": exports.ActivationBlock,
|
|
47164
49321
|
"Alert": AlertPattern,
|
|
47165
49322
|
"AlertPattern": AlertPattern,
|
|
49323
|
+
"AlgoGraphCanvas": exports.AlgoGraphCanvas,
|
|
47166
49324
|
"AlgorithmCanvas": exports.AlgorithmCanvas,
|
|
47167
49325
|
"AnimatedCounter": exports.AnimatedCounter,
|
|
47168
49326
|
"AnimatedGraphic": exports.AnimatedGraphic,
|
|
@@ -50635,7 +52793,23 @@ init_useAuthContext();
|
|
|
50635
52793
|
init_useSwipeGesture();
|
|
50636
52794
|
init_useLongPress();
|
|
50637
52795
|
init_useDragReorder();
|
|
50638
|
-
|
|
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
|
|
50639
52813
|
init_useInfiniteScroll();
|
|
50640
52814
|
init_usePullToRefresh();
|
|
50641
52815
|
init_useCanvasGestures();
|