@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
package/dist/providers/index.js
CHANGED
|
@@ -133,7 +133,7 @@ function useEventBus() {
|
|
|
133
133
|
return {
|
|
134
134
|
...baseBus,
|
|
135
135
|
emit: (type, payload, source) => {
|
|
136
|
-
if (typeof type === "string" && type.startsWith("UI:") && !type.slice(3).includes(".")) {
|
|
136
|
+
if (typeof type === "string" && type.startsWith("UI:") && !type.slice(3).includes(".") && !source?.trait) {
|
|
137
137
|
scopeLog.warn("emit:bare-key-no-scope", { type });
|
|
138
138
|
}
|
|
139
139
|
baseBus.emit(type, payload, source);
|
|
@@ -11572,6 +11572,14 @@ function shapeBounds(shape) {
|
|
|
11572
11572
|
w: shape.radius * 2 + 8,
|
|
11573
11573
|
h: shape.radius * 2 + 8
|
|
11574
11574
|
};
|
|
11575
|
+
case "ellipse":
|
|
11576
|
+
if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) return null;
|
|
11577
|
+
return {
|
|
11578
|
+
x: shape.x - shape.width / 2 - 4,
|
|
11579
|
+
y: shape.y - shape.height / 2 - 4,
|
|
11580
|
+
w: shape.width + 8,
|
|
11581
|
+
h: shape.height + 8
|
|
11582
|
+
};
|
|
11575
11583
|
case "rect":
|
|
11576
11584
|
if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) return null;
|
|
11577
11585
|
return { x: shape.x - 4, y: shape.y - 4, w: shape.width + 8, h: shape.height + 8 };
|
|
@@ -11605,13 +11613,14 @@ function drawArrowHead(ctx, x1, y1, x2, y2, size) {
|
|
|
11605
11613
|
ctx.closePath();
|
|
11606
11614
|
ctx.fill();
|
|
11607
11615
|
}
|
|
11608
|
-
function drawShape(ctx, shape, width, height) {
|
|
11616
|
+
function drawShape(ctx, shape, width, height, allShapes) {
|
|
11609
11617
|
ctx.save();
|
|
11610
11618
|
const opacity = shape.opacity ?? 1;
|
|
11611
11619
|
ctx.globalAlpha = opacity;
|
|
11612
11620
|
const stroke = resolveColor2(shape.color, ctx, "#333333");
|
|
11613
11621
|
const fill = shape.fill ? resolveColor2(shape.fill, ctx, "#cccccc") : void 0;
|
|
11614
11622
|
ctx.lineWidth = shape.lineWidth ?? 2;
|
|
11623
|
+
if (shape.dash) ctx.setLineDash([...DASH_PATTERNS[shape.dash]]);
|
|
11615
11624
|
switch (shape.type) {
|
|
11616
11625
|
case "grid": {
|
|
11617
11626
|
const step = shape.step ?? 40;
|
|
@@ -11677,6 +11686,20 @@ function drawShape(ctx, shape, width, height) {
|
|
|
11677
11686
|
ctx.stroke();
|
|
11678
11687
|
break;
|
|
11679
11688
|
}
|
|
11689
|
+
case "ellipse": {
|
|
11690
|
+
if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) break;
|
|
11691
|
+
const startAngle = (shape.startAngle ?? 0) * Math.PI / 180;
|
|
11692
|
+
const endAngle = (shape.endAngle ?? 360) * Math.PI / 180;
|
|
11693
|
+
ctx.beginPath();
|
|
11694
|
+
ctx.ellipse(shape.x, shape.y, shape.width / 2, shape.height / 2, 0, startAngle, endAngle);
|
|
11695
|
+
if (fill) {
|
|
11696
|
+
ctx.fillStyle = fill;
|
|
11697
|
+
ctx.fill();
|
|
11698
|
+
}
|
|
11699
|
+
ctx.strokeStyle = stroke;
|
|
11700
|
+
ctx.stroke();
|
|
11701
|
+
break;
|
|
11702
|
+
}
|
|
11680
11703
|
case "rect": {
|
|
11681
11704
|
if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) break;
|
|
11682
11705
|
if (fill) {
|
|
@@ -11723,21 +11746,153 @@ function drawShape(ctx, shape, width, height) {
|
|
|
11723
11746
|
ctx.fillText(shape.text, shape.x, shape.y);
|
|
11724
11747
|
break;
|
|
11725
11748
|
}
|
|
11749
|
+
case "venn-region": {
|
|
11750
|
+
const resolveCircles = (ids) => (ids ?? []).flatMap((id) => {
|
|
11751
|
+
const c = allShapes.find((s) => s.type === "circle" && s.id === id);
|
|
11752
|
+
return c && c.x != null && c.y != null && c.radius != null ? [{ x: c.x, y: c.y, radius: c.radius }] : [];
|
|
11753
|
+
});
|
|
11754
|
+
const inside = resolveCircles(shape.inside);
|
|
11755
|
+
if (inside.length === 0) break;
|
|
11756
|
+
const outside = resolveCircles(shape.outside);
|
|
11757
|
+
const off = document.createElement("canvas");
|
|
11758
|
+
off.width = ctx.canvas.width;
|
|
11759
|
+
off.height = ctx.canvas.height;
|
|
11760
|
+
const octx = off.getContext("2d");
|
|
11761
|
+
if (!octx) break;
|
|
11762
|
+
octx.setTransform(ctx.getTransform());
|
|
11763
|
+
for (const c of inside) {
|
|
11764
|
+
const p = new Path2D();
|
|
11765
|
+
p.arc(c.x, c.y, c.radius, 0, Math.PI * 2);
|
|
11766
|
+
octx.clip(p);
|
|
11767
|
+
}
|
|
11768
|
+
octx.fillStyle = fill ?? stroke;
|
|
11769
|
+
octx.fillRect(0, 0, width, height);
|
|
11770
|
+
octx.globalCompositeOperation = "destination-out";
|
|
11771
|
+
for (const c of outside) {
|
|
11772
|
+
const p = new Path2D();
|
|
11773
|
+
p.arc(c.x, c.y, c.radius, 0, Math.PI * 2);
|
|
11774
|
+
octx.fill(p);
|
|
11775
|
+
}
|
|
11776
|
+
ctx.save();
|
|
11777
|
+
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
11778
|
+
ctx.drawImage(off, 0, 0);
|
|
11779
|
+
ctx.restore();
|
|
11780
|
+
break;
|
|
11781
|
+
}
|
|
11726
11782
|
}
|
|
11727
11783
|
ctx.restore();
|
|
11728
11784
|
}
|
|
11729
|
-
|
|
11785
|
+
function readoutShapes(readouts, width) {
|
|
11786
|
+
const out = [];
|
|
11787
|
+
const chipH = 18;
|
|
11788
|
+
const gap = 6;
|
|
11789
|
+
let rightEdge = width - 6;
|
|
11790
|
+
let rowY = 6;
|
|
11791
|
+
for (const readout of readouts) {
|
|
11792
|
+
const text = `${readout.label}: ${String(readout.value)}`;
|
|
11793
|
+
const chipW = Math.min(170, Math.max(34, text.length * 6 + 12));
|
|
11794
|
+
let chipX = rightEdge - chipW;
|
|
11795
|
+
if (chipX < 4) {
|
|
11796
|
+
rowY += chipH + 4;
|
|
11797
|
+
rightEdge = width - 6;
|
|
11798
|
+
chipX = rightEdge - chipW;
|
|
11799
|
+
}
|
|
11800
|
+
const color = readout.color ?? "#334155";
|
|
11801
|
+
out.push({ type: "rect", x: chipX, y: rowY, width: chipW, height: chipH, color, fill: color });
|
|
11802
|
+
out.push({
|
|
11803
|
+
type: "text",
|
|
11804
|
+
x: chipX + chipW / 2,
|
|
11805
|
+
y: rowY + chipH / 2,
|
|
11806
|
+
text,
|
|
11807
|
+
color: "#ffffff",
|
|
11808
|
+
fontSize: 10,
|
|
11809
|
+
align: "center"
|
|
11810
|
+
});
|
|
11811
|
+
rightEdge = chipX - gap;
|
|
11812
|
+
}
|
|
11813
|
+
return out;
|
|
11814
|
+
}
|
|
11815
|
+
function traceShapes(panel, k, width, height) {
|
|
11816
|
+
const w = panel.width ?? Math.round(width * 0.32);
|
|
11817
|
+
const h = panel.height ?? Math.round(height * 0.28);
|
|
11818
|
+
const x = panel.x ?? width - w - 8;
|
|
11819
|
+
const y = panel.y ?? height - h - 8 - k * (h + 8);
|
|
11820
|
+
const allSamples = panel.series.flatMap((series) => series.samples);
|
|
11821
|
+
let xLo = Math.min(...allSamples.map((p) => p.x));
|
|
11822
|
+
let xHi = Math.max(...allSamples.map((p) => p.x));
|
|
11823
|
+
let yLo = Math.min(...allSamples.map((p) => p.y));
|
|
11824
|
+
let yHi = Math.max(...allSamples.map((p) => p.y));
|
|
11825
|
+
if (xLo === xHi) {
|
|
11826
|
+
xLo -= 1;
|
|
11827
|
+
xHi += 1;
|
|
11828
|
+
}
|
|
11829
|
+
if (yLo === yHi) {
|
|
11830
|
+
yLo -= 1;
|
|
11831
|
+
yHi += 1;
|
|
11832
|
+
}
|
|
11833
|
+
const backgroundColor = panel.backgroundColor ?? "#ffffff";
|
|
11834
|
+
const frameColor = panel.frameColor ?? "#94a3b8";
|
|
11835
|
+
const out = [];
|
|
11836
|
+
out.push({
|
|
11837
|
+
type: "rect",
|
|
11838
|
+
x,
|
|
11839
|
+
y,
|
|
11840
|
+
width: w,
|
|
11841
|
+
height: h,
|
|
11842
|
+
color: backgroundColor,
|
|
11843
|
+
fill: backgroundColor,
|
|
11844
|
+
opacity: panel.backgroundOpacity ?? 0.85
|
|
11845
|
+
});
|
|
11846
|
+
out.push({ type: "rect", x, y, width: w, height: h, color: frameColor, lineWidth: 1 });
|
|
11847
|
+
panel.series.forEach((series, j) => {
|
|
11848
|
+
const color = series.color ?? TRACE_SERIES_COLORS[j % TRACE_SERIES_COLORS.length];
|
|
11849
|
+
const mapped = series.samples.map((p) => ({
|
|
11850
|
+
x: x + 4 + (p.x - xLo) / (xHi - xLo) * (w - 8),
|
|
11851
|
+
y: y + h - 4 - (p.y - yLo) / (yHi - yLo) * (h - 8)
|
|
11852
|
+
}));
|
|
11853
|
+
for (let i = 1; i < mapped.length; i++) {
|
|
11854
|
+
out.push({
|
|
11855
|
+
type: "line",
|
|
11856
|
+
x1: mapped[i - 1].x,
|
|
11857
|
+
y1: mapped[i - 1].y,
|
|
11858
|
+
x2: mapped[i].x,
|
|
11859
|
+
y2: mapped[i].y,
|
|
11860
|
+
color,
|
|
11861
|
+
lineWidth: 1.5
|
|
11862
|
+
});
|
|
11863
|
+
}
|
|
11864
|
+
if (mapped.length > 0) {
|
|
11865
|
+
const last = mapped[mapped.length - 1];
|
|
11866
|
+
out.push({ type: "circle", x: last.x, y: last.y, radius: 2, color, fill: color });
|
|
11867
|
+
}
|
|
11868
|
+
if (series.label) {
|
|
11869
|
+
out.push({ type: "text", x: x + 6, y: y + 10 + 11 * j, text: series.label, color, fontSize: 9 });
|
|
11870
|
+
}
|
|
11871
|
+
});
|
|
11872
|
+
if (panel.yLabel) {
|
|
11873
|
+
out.push({ type: "text", x: x + w - 6, y: y + 10, text: panel.yLabel, color: "#6b7280", fontSize: 9, align: "right" });
|
|
11874
|
+
}
|
|
11875
|
+
if (panel.xLabel) {
|
|
11876
|
+
out.push({ type: "text", x: x + w - 6, y: y + h - 6, text: panel.xLabel, color: "#6b7280", fontSize: 9, align: "right" });
|
|
11877
|
+
}
|
|
11878
|
+
return out;
|
|
11879
|
+
}
|
|
11880
|
+
var DASH_PATTERNS, TRACE_SERIES_COLORS, LearningCanvas;
|
|
11730
11881
|
var init_LearningCanvas = __esm({
|
|
11731
11882
|
"components/learning/atoms/LearningCanvas.tsx"() {
|
|
11732
11883
|
"use client";
|
|
11733
11884
|
init_cn();
|
|
11734
11885
|
init_useEventBus();
|
|
11886
|
+
DASH_PATTERNS = { dashed: [6, 4], dotted: [2, 3] };
|
|
11887
|
+
TRACE_SERIES_COLORS = ["#2563eb", "#dc2626", "#16a34a", "#f59e0b"];
|
|
11735
11888
|
LearningCanvas = ({
|
|
11736
11889
|
className,
|
|
11737
11890
|
width = 600,
|
|
11738
11891
|
height = 400,
|
|
11739
11892
|
backgroundColor,
|
|
11740
11893
|
shapes = [],
|
|
11894
|
+
readouts,
|
|
11895
|
+
traces,
|
|
11741
11896
|
interactive = false,
|
|
11742
11897
|
animate = false,
|
|
11743
11898
|
onShapeClick,
|
|
@@ -11763,6 +11918,12 @@ var init_LearningCanvas = __esm({
|
|
|
11763
11918
|
}
|
|
11764
11919
|
return -1;
|
|
11765
11920
|
}, [shapes]);
|
|
11921
|
+
const derivedShapes = useMemo(() => {
|
|
11922
|
+
if (!traces?.length && !readouts?.length) return shapes;
|
|
11923
|
+
const traceOut = (traces ?? []).flatMap((panel, k) => traceShapes(panel, k, width, height));
|
|
11924
|
+
const readoutOut = readouts?.length ? readoutShapes(readouts, width) : [];
|
|
11925
|
+
return [...shapes, ...traceOut, ...readoutOut];
|
|
11926
|
+
}, [shapes, traces, readouts, width, height]);
|
|
11766
11927
|
const draw = useCallback(() => {
|
|
11767
11928
|
const canvas = canvasRef.current;
|
|
11768
11929
|
if (!canvas) return;
|
|
@@ -11779,13 +11940,13 @@ var init_LearningCanvas = __esm({
|
|
|
11779
11940
|
ctx.fillStyle = backgroundColor;
|
|
11780
11941
|
ctx.fillRect(0, 0, width, height);
|
|
11781
11942
|
}
|
|
11782
|
-
for (const shape of
|
|
11783
|
-
if (shape.type !== "text") drawShape(ctx, shape, width, height);
|
|
11943
|
+
for (const shape of derivedShapes) {
|
|
11944
|
+
if (shape.type !== "text") drawShape(ctx, shape, width, height, derivedShapes);
|
|
11784
11945
|
}
|
|
11785
|
-
for (const shape of
|
|
11786
|
-
if (shape.type === "text") drawShape(ctx, shape, width, height);
|
|
11946
|
+
for (const shape of derivedShapes) {
|
|
11947
|
+
if (shape.type === "text") drawShape(ctx, shape, width, height, derivedShapes);
|
|
11787
11948
|
}
|
|
11788
|
-
}, [width, height, backgroundColor,
|
|
11949
|
+
}, [width, height, backgroundColor, derivedShapes]);
|
|
11789
11950
|
useEffect(() => {
|
|
11790
11951
|
draw();
|
|
11791
11952
|
}, [draw]);
|
|
@@ -13718,7 +13879,363 @@ var init_ComponentPatterns = __esm({
|
|
|
13718
13879
|
AlertPattern.displayName = "AlertPattern";
|
|
13719
13880
|
}
|
|
13720
13881
|
});
|
|
13721
|
-
|
|
13882
|
+
function layoutCircle(nodes, width, height) {
|
|
13883
|
+
const cx = width / 2;
|
|
13884
|
+
const cy = height / 2;
|
|
13885
|
+
const radius = Math.max(10, Math.min(cx, cy) - 40);
|
|
13886
|
+
const positions = /* @__PURE__ */ new Map();
|
|
13887
|
+
const n = nodes.length;
|
|
13888
|
+
nodes.forEach((node, i) => {
|
|
13889
|
+
const angle = 2 * Math.PI * i / Math.max(n, 1) - Math.PI / 2;
|
|
13890
|
+
positions.set(node.id, { x: cx + radius * Math.cos(angle), y: cy + radius * Math.sin(angle) });
|
|
13891
|
+
});
|
|
13892
|
+
return positions;
|
|
13893
|
+
}
|
|
13894
|
+
function layoutTree(nodes, edges, root, width, height) {
|
|
13895
|
+
const nodeIds = nodes.map((n) => n.id);
|
|
13896
|
+
const idSet = new Set(nodeIds);
|
|
13897
|
+
const childrenOf = /* @__PURE__ */ new Map();
|
|
13898
|
+
const hasIncoming = /* @__PURE__ */ new Set();
|
|
13899
|
+
for (const e of edges) {
|
|
13900
|
+
if (!idSet.has(e.from) || !idSet.has(e.to)) continue;
|
|
13901
|
+
const list = childrenOf.get(e.from) ?? [];
|
|
13902
|
+
list.push(e.to);
|
|
13903
|
+
childrenOf.set(e.from, list);
|
|
13904
|
+
hasIncoming.add(e.to);
|
|
13905
|
+
}
|
|
13906
|
+
const depth = /* @__PURE__ */ new Map();
|
|
13907
|
+
const treeChildren = /* @__PURE__ */ new Map();
|
|
13908
|
+
const visited = /* @__PURE__ */ new Set();
|
|
13909
|
+
const bfsFrom = (start) => {
|
|
13910
|
+
if (visited.has(start)) return;
|
|
13911
|
+
visited.add(start);
|
|
13912
|
+
depth.set(start, 0);
|
|
13913
|
+
const queue = [start];
|
|
13914
|
+
while (queue.length > 0) {
|
|
13915
|
+
const u = queue.shift();
|
|
13916
|
+
for (const v of childrenOf.get(u) ?? []) {
|
|
13917
|
+
if (visited.has(v)) continue;
|
|
13918
|
+
visited.add(v);
|
|
13919
|
+
depth.set(v, (depth.get(u) ?? 0) + 1);
|
|
13920
|
+
const list = treeChildren.get(u) ?? [];
|
|
13921
|
+
list.push(v);
|
|
13922
|
+
treeChildren.set(u, list);
|
|
13923
|
+
queue.push(v);
|
|
13924
|
+
}
|
|
13925
|
+
}
|
|
13926
|
+
};
|
|
13927
|
+
const primaryRoot = root && idSet.has(root) ? root : nodeIds.find((id) => !hasIncoming.has(id)) ?? nodeIds[0];
|
|
13928
|
+
const rootsOrder = [];
|
|
13929
|
+
if (primaryRoot !== void 0) {
|
|
13930
|
+
bfsFrom(primaryRoot);
|
|
13931
|
+
rootsOrder.push(primaryRoot);
|
|
13932
|
+
}
|
|
13933
|
+
for (const id of nodeIds) {
|
|
13934
|
+
if (!visited.has(id)) {
|
|
13935
|
+
bfsFrom(id);
|
|
13936
|
+
rootsOrder.push(id);
|
|
13937
|
+
}
|
|
13938
|
+
}
|
|
13939
|
+
let leafCounter = 0;
|
|
13940
|
+
const xSlot = /* @__PURE__ */ new Map();
|
|
13941
|
+
const assignXSlot = (u) => {
|
|
13942
|
+
const children = treeChildren.get(u) ?? [];
|
|
13943
|
+
if (children.length === 0) {
|
|
13944
|
+
const slot = leafCounter++;
|
|
13945
|
+
xSlot.set(u, slot);
|
|
13946
|
+
return slot;
|
|
13947
|
+
}
|
|
13948
|
+
const childSlots = children.map(assignXSlot);
|
|
13949
|
+
const avg = childSlots.reduce((a, b) => a + b, 0) / childSlots.length;
|
|
13950
|
+
xSlot.set(u, avg);
|
|
13951
|
+
return avg;
|
|
13952
|
+
};
|
|
13953
|
+
for (const r of rootsOrder) assignXSlot(r);
|
|
13954
|
+
let maxDepth = 0;
|
|
13955
|
+
for (const d of depth.values()) maxDepth = Math.max(maxDepth, d);
|
|
13956
|
+
const colWidth = width / Math.max(1, leafCounter);
|
|
13957
|
+
const rowHeight = height / (maxDepth + 1);
|
|
13958
|
+
const positions = /* @__PURE__ */ new Map();
|
|
13959
|
+
for (const id of nodeIds) {
|
|
13960
|
+
const slot = xSlot.get(id) ?? 0;
|
|
13961
|
+
const d = depth.get(id) ?? 0;
|
|
13962
|
+
positions.set(id, { x: slot * colWidth + colWidth / 2, y: d * rowHeight + rowHeight / 2 });
|
|
13963
|
+
}
|
|
13964
|
+
return positions;
|
|
13965
|
+
}
|
|
13966
|
+
function layoutLayered(nodes, edges, width, height) {
|
|
13967
|
+
const nodeIds = nodes.map((n) => n.id);
|
|
13968
|
+
const idSet = new Set(nodeIds);
|
|
13969
|
+
const adj = /* @__PURE__ */ new Map();
|
|
13970
|
+
const remainingIndegree = /* @__PURE__ */ new Map();
|
|
13971
|
+
for (const id of nodeIds) remainingIndegree.set(id, 0);
|
|
13972
|
+
for (const e of edges) {
|
|
13973
|
+
if (!idSet.has(e.from) || !idSet.has(e.to)) continue;
|
|
13974
|
+
const list = adj.get(e.from) ?? [];
|
|
13975
|
+
list.push(e.to);
|
|
13976
|
+
adj.set(e.from, list);
|
|
13977
|
+
remainingIndegree.set(e.to, (remainingIndegree.get(e.to) ?? 0) + 1);
|
|
13978
|
+
}
|
|
13979
|
+
const layer = /* @__PURE__ */ new Map();
|
|
13980
|
+
const dequeued = /* @__PURE__ */ new Set();
|
|
13981
|
+
const queue = [];
|
|
13982
|
+
for (const id of nodeIds) {
|
|
13983
|
+
if ((remainingIndegree.get(id) ?? 0) === 0) {
|
|
13984
|
+
layer.set(id, 0);
|
|
13985
|
+
queue.push(id);
|
|
13986
|
+
}
|
|
13987
|
+
}
|
|
13988
|
+
while (queue.length > 0) {
|
|
13989
|
+
const u = queue.shift();
|
|
13990
|
+
dequeued.add(u);
|
|
13991
|
+
for (const v of adj.get(u) ?? []) {
|
|
13992
|
+
const candidate = (layer.get(u) ?? 0) + 1;
|
|
13993
|
+
layer.set(v, Math.max(layer.get(v) ?? 0, candidate));
|
|
13994
|
+
remainingIndegree.set(v, (remainingIndegree.get(v) ?? 0) - 1);
|
|
13995
|
+
if ((remainingIndegree.get(v) ?? 0) === 0 && !dequeued.has(v)) {
|
|
13996
|
+
queue.push(v);
|
|
13997
|
+
}
|
|
13998
|
+
}
|
|
13999
|
+
}
|
|
14000
|
+
let baseMaxLayer = 0;
|
|
14001
|
+
for (const id of nodeIds) {
|
|
14002
|
+
if (dequeued.has(id)) baseMaxLayer = Math.max(baseMaxLayer, layer.get(id) ?? 0);
|
|
14003
|
+
}
|
|
14004
|
+
const cycleLayer = baseMaxLayer + 1;
|
|
14005
|
+
let maxLayer = baseMaxLayer;
|
|
14006
|
+
for (const id of nodeIds) {
|
|
14007
|
+
if (!dequeued.has(id)) {
|
|
14008
|
+
layer.set(id, cycleLayer);
|
|
14009
|
+
maxLayer = cycleLayer;
|
|
14010
|
+
}
|
|
14011
|
+
}
|
|
14012
|
+
const colWidth = width / Math.max(1, maxLayer + 1);
|
|
14013
|
+
const byLayer = /* @__PURE__ */ new Map();
|
|
14014
|
+
for (const id of nodeIds) {
|
|
14015
|
+
const l = layer.get(id) ?? 0;
|
|
14016
|
+
const list = byLayer.get(l) ?? [];
|
|
14017
|
+
list.push(id);
|
|
14018
|
+
byLayer.set(l, list);
|
|
14019
|
+
}
|
|
14020
|
+
const positions = /* @__PURE__ */ new Map();
|
|
14021
|
+
for (const [l, ids] of byLayer) {
|
|
14022
|
+
const rowHeight = height / ids.length;
|
|
14023
|
+
ids.forEach((id, i) => {
|
|
14024
|
+
positions.set(id, { x: l * colWidth + colWidth / 2, y: i * rowHeight + rowHeight / 2 });
|
|
14025
|
+
});
|
|
14026
|
+
}
|
|
14027
|
+
return positions;
|
|
14028
|
+
}
|
|
14029
|
+
function computePositions(nodes, edges, layout, root, width, height) {
|
|
14030
|
+
switch (layout) {
|
|
14031
|
+
case "circle":
|
|
14032
|
+
return layoutCircle(nodes, width, height);
|
|
14033
|
+
case "tree":
|
|
14034
|
+
return layoutTree(nodes, edges, root, width, height);
|
|
14035
|
+
case "layered":
|
|
14036
|
+
return layoutLayered(nodes, edges, width, height);
|
|
14037
|
+
case "manual":
|
|
14038
|
+
default: {
|
|
14039
|
+
const positions = /* @__PURE__ */ new Map();
|
|
14040
|
+
for (const n of nodes) positions.set(n.id, { x: n.x ?? 0, y: n.y ?? 0 });
|
|
14041
|
+
return positions;
|
|
14042
|
+
}
|
|
14043
|
+
}
|
|
14044
|
+
}
|
|
14045
|
+
var NODE_STATE_COLOR, EDGE_STATE_COLOR, DEFAULT_NODE_RADIUS, AlgoGraphCanvas;
|
|
14046
|
+
var init_AlgoGraphCanvas = __esm({
|
|
14047
|
+
"components/learning/molecules/AlgoGraphCanvas.tsx"() {
|
|
14048
|
+
"use client";
|
|
14049
|
+
init_atoms();
|
|
14050
|
+
init_Stack();
|
|
14051
|
+
init_LearningCanvas();
|
|
14052
|
+
NODE_STATE_COLOR = {
|
|
14053
|
+
unvisited: "#cbd5e1",
|
|
14054
|
+
frontier: "#f59e0b",
|
|
14055
|
+
current: "#ef4444",
|
|
14056
|
+
visited: "#22c55e",
|
|
14057
|
+
goal: "#8b5cf6",
|
|
14058
|
+
path: "#0ea5e9"
|
|
14059
|
+
};
|
|
14060
|
+
EDGE_STATE_COLOR = {
|
|
14061
|
+
default: "#9ca3af",
|
|
14062
|
+
tree: "#16a34a",
|
|
14063
|
+
relaxed: "#f59e0b",
|
|
14064
|
+
candidate: "#38bdf8",
|
|
14065
|
+
path: "#dc2626"
|
|
14066
|
+
};
|
|
14067
|
+
DEFAULT_NODE_RADIUS = 18;
|
|
14068
|
+
AlgoGraphCanvas = ({
|
|
14069
|
+
className,
|
|
14070
|
+
width = 600,
|
|
14071
|
+
height = 400,
|
|
14072
|
+
title,
|
|
14073
|
+
backgroundColor,
|
|
14074
|
+
nodes = [],
|
|
14075
|
+
edges = [],
|
|
14076
|
+
layout = "manual",
|
|
14077
|
+
root,
|
|
14078
|
+
shapes = [],
|
|
14079
|
+
interactive = false,
|
|
14080
|
+
animate = false,
|
|
14081
|
+
onShapeClick,
|
|
14082
|
+
onNodeClick,
|
|
14083
|
+
isLoading,
|
|
14084
|
+
error
|
|
14085
|
+
}) => {
|
|
14086
|
+
const nodeById = useMemo(() => {
|
|
14087
|
+
const m = /* @__PURE__ */ new Map();
|
|
14088
|
+
for (const n of nodes) m.set(n.id, n);
|
|
14089
|
+
return m;
|
|
14090
|
+
}, [nodes]);
|
|
14091
|
+
const nodeIndexById = useMemo(() => {
|
|
14092
|
+
const m = /* @__PURE__ */ new Map();
|
|
14093
|
+
nodes.forEach((n, i) => m.set(n.id, i));
|
|
14094
|
+
return m;
|
|
14095
|
+
}, [nodes]);
|
|
14096
|
+
const derivedShapes = useMemo(() => {
|
|
14097
|
+
const out = [];
|
|
14098
|
+
const positions = computePositions(nodes, edges, layout, root, width, height);
|
|
14099
|
+
const edgeGeoms = [];
|
|
14100
|
+
for (const e of edges) {
|
|
14101
|
+
const a = nodeById.get(e.from);
|
|
14102
|
+
const b = nodeById.get(e.to);
|
|
14103
|
+
const posA = positions.get(e.from);
|
|
14104
|
+
const posB = positions.get(e.to);
|
|
14105
|
+
if (!a || !b || !posA || !posB) continue;
|
|
14106
|
+
const rA = a.radius ?? DEFAULT_NODE_RADIUS;
|
|
14107
|
+
const rB = b.radius ?? DEFAULT_NODE_RADIUS;
|
|
14108
|
+
const dx = posB.x - posA.x;
|
|
14109
|
+
const dy = posB.y - posA.y;
|
|
14110
|
+
const dist = Math.max(1e-6, Math.hypot(dx, dy));
|
|
14111
|
+
const ux = dx / dist;
|
|
14112
|
+
const uy = dy / dist;
|
|
14113
|
+
edgeGeoms.push({
|
|
14114
|
+
directed: e.directed ?? false,
|
|
14115
|
+
x1: posA.x + ux * rA,
|
|
14116
|
+
y1: posA.y + uy * rA,
|
|
14117
|
+
x2: posB.x - ux * rB,
|
|
14118
|
+
y2: posB.y - uy * rB,
|
|
14119
|
+
color: e.color ?? EDGE_STATE_COLOR[e.state ?? "default"],
|
|
14120
|
+
label: e.label ?? (e.weight != null ? String(e.weight) : void 0)
|
|
14121
|
+
});
|
|
14122
|
+
}
|
|
14123
|
+
for (const g of edgeGeoms) {
|
|
14124
|
+
out.push({
|
|
14125
|
+
type: g.directed ? "arrow" : "line",
|
|
14126
|
+
x1: g.x1,
|
|
14127
|
+
y1: g.y1,
|
|
14128
|
+
x2: g.x2,
|
|
14129
|
+
y2: g.y2,
|
|
14130
|
+
color: g.color,
|
|
14131
|
+
lineWidth: 2
|
|
14132
|
+
});
|
|
14133
|
+
}
|
|
14134
|
+
for (const g of edgeGeoms) {
|
|
14135
|
+
if (g.label === void 0) continue;
|
|
14136
|
+
const midX = (g.x1 + g.x2) / 2;
|
|
14137
|
+
const midY = (g.y1 + g.y2) / 2;
|
|
14138
|
+
const dx = g.x2 - g.x1;
|
|
14139
|
+
const dy = g.y2 - g.y1;
|
|
14140
|
+
const dist = Math.max(1e-6, Math.hypot(dx, dy));
|
|
14141
|
+
const perpX = -(dy / dist);
|
|
14142
|
+
const perpY = dx / dist;
|
|
14143
|
+
out.push({
|
|
14144
|
+
type: "text",
|
|
14145
|
+
x: midX + perpX * 10,
|
|
14146
|
+
y: midY + perpY * 10,
|
|
14147
|
+
text: g.label,
|
|
14148
|
+
fontSize: 11,
|
|
14149
|
+
align: "center",
|
|
14150
|
+
color: "#374151"
|
|
14151
|
+
});
|
|
14152
|
+
}
|
|
14153
|
+
const nodeGeoms = [];
|
|
14154
|
+
for (const n of nodes) {
|
|
14155
|
+
const pos = positions.get(n.id);
|
|
14156
|
+
if (!pos) continue;
|
|
14157
|
+
nodeGeoms.push({
|
|
14158
|
+
id: n.id,
|
|
14159
|
+
x: pos.x,
|
|
14160
|
+
y: pos.y,
|
|
14161
|
+
radius: n.radius ?? DEFAULT_NODE_RADIUS,
|
|
14162
|
+
color: n.color ?? NODE_STATE_COLOR[n.state ?? "unvisited"],
|
|
14163
|
+
label: n.label,
|
|
14164
|
+
badge: n.badge
|
|
14165
|
+
});
|
|
14166
|
+
}
|
|
14167
|
+
for (const g of nodeGeoms) {
|
|
14168
|
+
out.push({ type: "circle", id: g.id, x: g.x, y: g.y, radius: g.radius, color: g.color, fill: `${g.color}33` });
|
|
14169
|
+
}
|
|
14170
|
+
const badgeGeoms = [];
|
|
14171
|
+
for (const g of nodeGeoms) {
|
|
14172
|
+
if (!g.badge) continue;
|
|
14173
|
+
const w = Math.min(42, Math.max(18, g.badge.text.length * 6 + 10));
|
|
14174
|
+
badgeGeoms.push({
|
|
14175
|
+
cx: g.x + g.radius * 0.75,
|
|
14176
|
+
cy: g.y - g.radius * 0.75,
|
|
14177
|
+
w,
|
|
14178
|
+
h: 14,
|
|
14179
|
+
// Borderless pill: same color drives both stroke and fill.
|
|
14180
|
+
color: g.badge.color ?? "#1e293b",
|
|
14181
|
+
text: g.badge.text
|
|
14182
|
+
});
|
|
14183
|
+
}
|
|
14184
|
+
for (const b of badgeGeoms) {
|
|
14185
|
+
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 });
|
|
14186
|
+
}
|
|
14187
|
+
for (const b of badgeGeoms) {
|
|
14188
|
+
out.push({ type: "text", x: b.cx, y: b.cy, text: b.text, fontSize: 9, align: "center", color: "#ffffff" });
|
|
14189
|
+
}
|
|
14190
|
+
for (const g of nodeGeoms) {
|
|
14191
|
+
if (g.label === void 0) continue;
|
|
14192
|
+
out.push({
|
|
14193
|
+
type: "text",
|
|
14194
|
+
x: g.x,
|
|
14195
|
+
y: g.y + g.radius + 14,
|
|
14196
|
+
text: g.label,
|
|
14197
|
+
fontSize: 12,
|
|
14198
|
+
align: "center",
|
|
14199
|
+
color: "#111827"
|
|
14200
|
+
});
|
|
14201
|
+
}
|
|
14202
|
+
out.push(...shapes);
|
|
14203
|
+
return out;
|
|
14204
|
+
}, [nodes, edges, layout, root, width, height, nodeById, shapes]);
|
|
14205
|
+
const handleShapeClick = useCallback(
|
|
14206
|
+
(payload) => {
|
|
14207
|
+
if (payload.type === "circle" && payload.id) {
|
|
14208
|
+
const node = nodeById.get(payload.id);
|
|
14209
|
+
const idx = nodeIndexById.get(payload.id);
|
|
14210
|
+
if (node && idx !== void 0) {
|
|
14211
|
+
onNodeClick?.({ id: node.id, label: node.label, index: idx });
|
|
14212
|
+
}
|
|
14213
|
+
}
|
|
14214
|
+
onShapeClick?.(payload);
|
|
14215
|
+
},
|
|
14216
|
+
[nodeById, nodeIndexById, onNodeClick, onShapeClick]
|
|
14217
|
+
);
|
|
14218
|
+
return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
|
|
14219
|
+
title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
|
|
14220
|
+
/* @__PURE__ */ jsx(
|
|
14221
|
+
LearningCanvas,
|
|
14222
|
+
{
|
|
14223
|
+
width,
|
|
14224
|
+
height,
|
|
14225
|
+
backgroundColor,
|
|
14226
|
+
shapes: derivedShapes,
|
|
14227
|
+
interactive,
|
|
14228
|
+
animate,
|
|
14229
|
+
onShapeClick: onShapeClick || onNodeClick ? handleShapeClick : void 0,
|
|
14230
|
+
isLoading,
|
|
14231
|
+
error
|
|
14232
|
+
}
|
|
14233
|
+
)
|
|
14234
|
+
] }) });
|
|
14235
|
+
};
|
|
14236
|
+
}
|
|
14237
|
+
});
|
|
14238
|
+
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, AlgorithmCanvas;
|
|
13722
14239
|
var init_AlgorithmCanvas = __esm({
|
|
13723
14240
|
"components/learning/molecules/AlgorithmCanvas.tsx"() {
|
|
13724
14241
|
"use client";
|
|
@@ -13730,6 +14247,43 @@ var init_AlgorithmCanvas = __esm({
|
|
|
13730
14247
|
DEFAULT_POINTER_COLOR = "#dc2626";
|
|
13731
14248
|
POINTER_BAND = 34;
|
|
13732
14249
|
TOP_PAD = 26;
|
|
14250
|
+
PANEL_FAMILY_ORDER = ["bars", "slots", "cells", "buckets", "frames"];
|
|
14251
|
+
RANGE_COLOR_DEFAULT = "#3b82f6";
|
|
14252
|
+
RANGE_FILL_OPACITY = 0.15;
|
|
14253
|
+
BRACKET_TOP_OFFSET = 16;
|
|
14254
|
+
BRACKET_ROW_H = 14;
|
|
14255
|
+
BRACKET_TICK_H = 6;
|
|
14256
|
+
BRACKET_LABEL_OFFSET = 6;
|
|
14257
|
+
SLOT_EMPTY_FILL = "#f1f5f9";
|
|
14258
|
+
SLOT_EMPTY_STROKE = "#cbd5e1";
|
|
14259
|
+
SLOT_FILLED_STROKE = "#9ca3af";
|
|
14260
|
+
SLOT_HIGHLIGHT_DEFAULT = "#f59e0b";
|
|
14261
|
+
SLOT_VALUE_TEXT_COLOR = "#ffffff";
|
|
14262
|
+
FRAME_ACTIVE_COLOR = "#3b82f6";
|
|
14263
|
+
FRAME_RETURNING_COLOR = "#f59e0b";
|
|
14264
|
+
FRAME_DONE_COLOR = "#94a3b8";
|
|
14265
|
+
FRAME_LABEL_COLOR = "#ffffff";
|
|
14266
|
+
FRAME_DETAIL_COLOR = "#e2e8f0";
|
|
14267
|
+
FRAME_TWO_LINE_MIN_H = 22;
|
|
14268
|
+
BUCKET_INDEX_FILL = "#e2e8f0";
|
|
14269
|
+
BUCKET_INDEX_STROKE = "#9ca3af";
|
|
14270
|
+
BUCKET_INDEX_TEXT = "#374151";
|
|
14271
|
+
BUCKET_ENTRY_TEXT = "#ffffff";
|
|
14272
|
+
BUCKET_ENTRY_DEFAULT = "#3b82f6";
|
|
14273
|
+
BUCKET_ENTRY_HIGHLIGHT = "#f59e0b";
|
|
14274
|
+
BUCKET_ENTRY_PROBING = "#38bdf8";
|
|
14275
|
+
BUCKET_ENTRY_MIN_W = 24;
|
|
14276
|
+
BUCKET_ENTRY_MAX_W = 64;
|
|
14277
|
+
AXIS_LABEL_COLOR = "#6b7280";
|
|
14278
|
+
AXIS_LABEL_FONT_SIZE = 10;
|
|
14279
|
+
CORNER_TEXT_COLOR = "#111827";
|
|
14280
|
+
CORNER_FONT_SIZE = 7;
|
|
14281
|
+
CORNER_MIN_CELL = 28;
|
|
14282
|
+
CORNER_INSET_X = 3;
|
|
14283
|
+
CORNER_INSET_Y = 6;
|
|
14284
|
+
AUX_PRIMARY_RATIO = 0.6;
|
|
14285
|
+
AUX_LABEL_BAND = 18;
|
|
14286
|
+
AUX_BASELINE_PAD = 8;
|
|
13733
14287
|
AlgorithmCanvas = ({
|
|
13734
14288
|
className,
|
|
13735
14289
|
width = 600,
|
|
@@ -13739,6 +14293,14 @@ var init_AlgorithmCanvas = __esm({
|
|
|
13739
14293
|
bars = [],
|
|
13740
14294
|
cells = [],
|
|
13741
14295
|
pointers = [],
|
|
14296
|
+
ranges = [],
|
|
14297
|
+
slots = [],
|
|
14298
|
+
slotOrientation = "horizontal",
|
|
14299
|
+
frames = [],
|
|
14300
|
+
buckets = [],
|
|
14301
|
+
auxBars = [],
|
|
14302
|
+
rowLabels = [],
|
|
14303
|
+
colLabels = [],
|
|
13742
14304
|
shapes = [],
|
|
13743
14305
|
interactive = false,
|
|
13744
14306
|
animate = false,
|
|
@@ -13748,12 +14310,35 @@ var init_AlgorithmCanvas = __esm({
|
|
|
13748
14310
|
}) => {
|
|
13749
14311
|
const derivedShapes = useMemo(() => {
|
|
13750
14312
|
const out = [];
|
|
14313
|
+
const presence = {
|
|
14314
|
+
bars: bars.length > 0,
|
|
14315
|
+
slots: slots.length > 0,
|
|
14316
|
+
cells: cells.length > 0,
|
|
14317
|
+
buckets: buckets.length > 0,
|
|
14318
|
+
frames: frames.length > 0
|
|
14319
|
+
};
|
|
14320
|
+
const panelCount = PANEL_FAMILY_ORDER.filter((f3) => presence[f3]).length;
|
|
14321
|
+
const panelHeight = height / Math.max(1, panelCount);
|
|
14322
|
+
const panelY = { bars: 0, slots: 0, cells: 0, buckets: 0, frames: 0 };
|
|
14323
|
+
let compactIndex = 0;
|
|
14324
|
+
PANEL_FAMILY_ORDER.forEach((f3) => {
|
|
14325
|
+
if (presence[f3]) {
|
|
14326
|
+
panelY[f3] = compactIndex * panelHeight;
|
|
14327
|
+
compactIndex += 1;
|
|
14328
|
+
}
|
|
14329
|
+
});
|
|
13751
14330
|
if (bars.length > 0) {
|
|
14331
|
+
const panelYBars = panelY.bars;
|
|
13752
14332
|
const slot = width / bars.length;
|
|
13753
14333
|
const barW = slot * 0.8;
|
|
13754
14334
|
const gap = slot * 0.1;
|
|
13755
|
-
const
|
|
13756
|
-
const
|
|
14335
|
+
const bracketRanges = ranges.filter((r) => r.kind === "bracket");
|
|
14336
|
+
const bracketCount = bracketRanges.length;
|
|
14337
|
+
const bracketHeadroom = bracketCount > 0 ? BRACKET_TOP_OFFSET + bracketCount * BRACKET_ROW_H : 0;
|
|
14338
|
+
const hasAux = auxBars.length > 0;
|
|
14339
|
+
const primaryH = hasAux ? panelHeight * AUX_PRIMARY_RATIO : panelHeight;
|
|
14340
|
+
const baseline = panelYBars + primaryH - POINTER_BAND;
|
|
14341
|
+
const usableH = baseline - (panelYBars + TOP_PAD + bracketHeadroom);
|
|
13757
14342
|
const maxV = Math.max(1, ...bars.map((b) => Number.isFinite(b.value) ? b.value : 0));
|
|
13758
14343
|
bars.forEach((bar, i) => {
|
|
13759
14344
|
const v = Number.isFinite(bar.value) ? bar.value : 0;
|
|
@@ -13783,6 +14368,89 @@ var init_AlgorithmCanvas = __esm({
|
|
|
13783
14368
|
});
|
|
13784
14369
|
}
|
|
13785
14370
|
});
|
|
14371
|
+
ranges.forEach((r) => {
|
|
14372
|
+
const kind = r.kind ?? "fill";
|
|
14373
|
+
if (kind !== "fill") return;
|
|
14374
|
+
const color = r.color ?? RANGE_COLOR_DEFAULT;
|
|
14375
|
+
out.push({
|
|
14376
|
+
type: "rect",
|
|
14377
|
+
x: r.from * slot,
|
|
14378
|
+
y: panelYBars,
|
|
14379
|
+
width: (r.to - r.from + 1) * slot,
|
|
14380
|
+
height: primaryH,
|
|
14381
|
+
color,
|
|
14382
|
+
fill: color,
|
|
14383
|
+
opacity: RANGE_FILL_OPACITY
|
|
14384
|
+
});
|
|
14385
|
+
if (r.label) {
|
|
14386
|
+
out.push({
|
|
14387
|
+
type: "text",
|
|
14388
|
+
x: r.from * slot + 4,
|
|
14389
|
+
// Sits below the bracket block (if any) so fill and bracket labels never collide.
|
|
14390
|
+
y: panelYBars + 10 + bracketHeadroom,
|
|
14391
|
+
text: r.label,
|
|
14392
|
+
color,
|
|
14393
|
+
fontSize: 10,
|
|
14394
|
+
align: "left"
|
|
14395
|
+
});
|
|
14396
|
+
}
|
|
14397
|
+
});
|
|
14398
|
+
bracketRanges.forEach((r, i) => {
|
|
14399
|
+
const bracketY = panelYBars + BRACKET_TOP_OFFSET + i * BRACKET_ROW_H;
|
|
14400
|
+
const x1 = r.from * slot + slot * 0.1;
|
|
14401
|
+
const x2 = (r.to + 1) * slot - slot * 0.1;
|
|
14402
|
+
const color = r.color ?? RANGE_COLOR_DEFAULT;
|
|
14403
|
+
out.push({ type: "line", x1, y1: bracketY, x2, y2: bracketY, color, lineWidth: 2 });
|
|
14404
|
+
out.push({ type: "line", x1, y1: bracketY, x2: x1, y2: bracketY + BRACKET_TICK_H, color, lineWidth: 2 });
|
|
14405
|
+
out.push({ type: "line", x1: x2, y1: bracketY, x2, y2: bracketY + BRACKET_TICK_H, color, lineWidth: 2 });
|
|
14406
|
+
if (r.label) {
|
|
14407
|
+
out.push({
|
|
14408
|
+
type: "text",
|
|
14409
|
+
x: (x1 + x2) / 2,
|
|
14410
|
+
y: bracketY - BRACKET_LABEL_OFFSET,
|
|
14411
|
+
text: r.label,
|
|
14412
|
+
color,
|
|
14413
|
+
fontSize: 10,
|
|
14414
|
+
align: "center"
|
|
14415
|
+
});
|
|
14416
|
+
}
|
|
14417
|
+
});
|
|
14418
|
+
if (hasAux) {
|
|
14419
|
+
const auxH = panelHeight - primaryH;
|
|
14420
|
+
const slot2 = width / auxBars.length;
|
|
14421
|
+
const auxBaseline = panelYBars + primaryH + auxH - AUX_BASELINE_PAD;
|
|
14422
|
+
const auxUsableH = auxBaseline - (panelYBars + primaryH + AUX_LABEL_BAND);
|
|
14423
|
+
const maxAuxV = Math.max(1, ...auxBars.map((b) => Number.isFinite(b.value) ? b.value : 0));
|
|
14424
|
+
auxBars.forEach((bar, i) => {
|
|
14425
|
+
const v = Number.isFinite(bar.value) ? bar.value : 0;
|
|
14426
|
+
const bh = Math.max(0, v / maxAuxV * auxUsableH);
|
|
14427
|
+
const x = i * slot2 + slot2 * 0.1;
|
|
14428
|
+
const w = slot2 * 0.8;
|
|
14429
|
+
const color = bar.color ?? DEFAULT_BAR_COLOR;
|
|
14430
|
+
out.push({
|
|
14431
|
+
type: "rect",
|
|
14432
|
+
id: `auxbar-${i}`,
|
|
14433
|
+
x,
|
|
14434
|
+
y: auxBaseline - bh,
|
|
14435
|
+
width: w,
|
|
14436
|
+
height: bh,
|
|
14437
|
+
color,
|
|
14438
|
+
fill: color
|
|
14439
|
+
});
|
|
14440
|
+
const label = bar.label ?? (auxBars.length <= 24 ? String(v) : void 0);
|
|
14441
|
+
if (label) {
|
|
14442
|
+
out.push({
|
|
14443
|
+
type: "text",
|
|
14444
|
+
x: x + w / 2,
|
|
14445
|
+
y: auxBaseline - bh - 8,
|
|
14446
|
+
text: label,
|
|
14447
|
+
color: "#374151",
|
|
14448
|
+
fontSize: 11,
|
|
14449
|
+
align: "center"
|
|
14450
|
+
});
|
|
14451
|
+
}
|
|
14452
|
+
});
|
|
14453
|
+
}
|
|
13786
14454
|
pointers.forEach((p) => {
|
|
13787
14455
|
if (p.index < 0 || p.index >= bars.length) return;
|
|
13788
14456
|
const cx = p.index * slot + slot / 2;
|
|
@@ -13790,7 +14458,7 @@ var init_AlgorithmCanvas = __esm({
|
|
|
13790
14458
|
out.push({
|
|
13791
14459
|
type: "arrow",
|
|
13792
14460
|
x1: cx,
|
|
13793
|
-
y1:
|
|
14461
|
+
y1: panelYBars + primaryH - 18,
|
|
13794
14462
|
x2: cx,
|
|
13795
14463
|
y2: baseline + 4,
|
|
13796
14464
|
color,
|
|
@@ -13800,7 +14468,7 @@ var init_AlgorithmCanvas = __esm({
|
|
|
13800
14468
|
out.push({
|
|
13801
14469
|
type: "text",
|
|
13802
14470
|
x: cx,
|
|
13803
|
-
y:
|
|
14471
|
+
y: panelYBars + primaryH - 8,
|
|
13804
14472
|
text: p.label,
|
|
13805
14473
|
color,
|
|
13806
14474
|
fontSize: 11,
|
|
@@ -13809,14 +14477,111 @@ var init_AlgorithmCanvas = __esm({
|
|
|
13809
14477
|
}
|
|
13810
14478
|
});
|
|
13811
14479
|
}
|
|
14480
|
+
if (slots.length > 0) {
|
|
14481
|
+
const panelYSlots = panelY.slots;
|
|
14482
|
+
const n = slots.length;
|
|
14483
|
+
const vertical = slotOrientation === "vertical";
|
|
14484
|
+
const vBoxH = panelHeight / n;
|
|
14485
|
+
const vBoxW = Math.min(width * 0.5, 120);
|
|
14486
|
+
const vBoxX = (width - vBoxW) / 2;
|
|
14487
|
+
const hCellW = width / n;
|
|
14488
|
+
const hBoxW = hCellW * 0.82;
|
|
14489
|
+
const hBoxH = Math.min(panelHeight * 0.6, 48);
|
|
14490
|
+
const hBoxY = panelYSlots + (panelHeight - hBoxH) / 2;
|
|
14491
|
+
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 };
|
|
14492
|
+
slots.forEach((s, i) => {
|
|
14493
|
+
const box = slotBox(i);
|
|
14494
|
+
const state = s.state ?? "filled";
|
|
14495
|
+
const fill = state === "empty" ? SLOT_EMPTY_FILL : state === "highlight" ? s.color ?? SLOT_HIGHLIGHT_DEFAULT : s.color ?? DEFAULT_BAR_COLOR;
|
|
14496
|
+
const stroke = state === "empty" ? SLOT_EMPTY_STROKE : SLOT_FILLED_STROKE;
|
|
14497
|
+
out.push({
|
|
14498
|
+
type: "rect",
|
|
14499
|
+
id: `slot-${i}`,
|
|
14500
|
+
x: box.x,
|
|
14501
|
+
y: box.y,
|
|
14502
|
+
width: box.width,
|
|
14503
|
+
height: box.height,
|
|
14504
|
+
color: stroke,
|
|
14505
|
+
fill
|
|
14506
|
+
});
|
|
14507
|
+
if (s.value != null && state !== "empty") {
|
|
14508
|
+
out.push({
|
|
14509
|
+
type: "text",
|
|
14510
|
+
x: box.x + box.width / 2,
|
|
14511
|
+
y: box.y + box.height / 2,
|
|
14512
|
+
text: String(s.value),
|
|
14513
|
+
color: SLOT_VALUE_TEXT_COLOR,
|
|
14514
|
+
fontSize: 12,
|
|
14515
|
+
align: "center"
|
|
14516
|
+
});
|
|
14517
|
+
}
|
|
14518
|
+
});
|
|
14519
|
+
if (bars.length === 0) {
|
|
14520
|
+
pointers.forEach((p) => {
|
|
14521
|
+
if (p.index < 0 || p.index >= slots.length) return;
|
|
14522
|
+
const box = slotBox(p.index);
|
|
14523
|
+
const color = p.color ?? DEFAULT_POINTER_COLOR;
|
|
14524
|
+
if (vertical) {
|
|
14525
|
+
const cy = box.y + box.height / 2;
|
|
14526
|
+
out.push({
|
|
14527
|
+
type: "arrow",
|
|
14528
|
+
x1: box.x + box.width + 34,
|
|
14529
|
+
y1: cy,
|
|
14530
|
+
x2: box.x + box.width + 4,
|
|
14531
|
+
y2: cy,
|
|
14532
|
+
color,
|
|
14533
|
+
lineWidth: 2
|
|
14534
|
+
});
|
|
14535
|
+
if (p.label) {
|
|
14536
|
+
out.push({
|
|
14537
|
+
type: "text",
|
|
14538
|
+
x: box.x + box.width + 38,
|
|
14539
|
+
y: cy,
|
|
14540
|
+
text: p.label,
|
|
14541
|
+
color,
|
|
14542
|
+
fontSize: 11,
|
|
14543
|
+
align: "left"
|
|
14544
|
+
});
|
|
14545
|
+
}
|
|
14546
|
+
} else {
|
|
14547
|
+
const cx = box.x + box.width / 2;
|
|
14548
|
+
out.push({
|
|
14549
|
+
type: "arrow",
|
|
14550
|
+
x1: cx,
|
|
14551
|
+
y1: panelYSlots + panelHeight - 18,
|
|
14552
|
+
x2: cx,
|
|
14553
|
+
y2: box.y + box.height + 4,
|
|
14554
|
+
color,
|
|
14555
|
+
lineWidth: 2
|
|
14556
|
+
});
|
|
14557
|
+
if (p.label) {
|
|
14558
|
+
out.push({
|
|
14559
|
+
type: "text",
|
|
14560
|
+
x: cx,
|
|
14561
|
+
y: panelYSlots + panelHeight - 8,
|
|
14562
|
+
text: p.label,
|
|
14563
|
+
color,
|
|
14564
|
+
fontSize: 11,
|
|
14565
|
+
align: "center"
|
|
14566
|
+
});
|
|
14567
|
+
}
|
|
14568
|
+
}
|
|
14569
|
+
});
|
|
14570
|
+
}
|
|
14571
|
+
}
|
|
13812
14572
|
if (cells.length > 0) {
|
|
14573
|
+
const panelYCells = panelY.cells;
|
|
13813
14574
|
const maxCol = Math.max(0, ...cells.map((c) => c.col)) + 1;
|
|
13814
14575
|
const maxRow = Math.max(0, ...cells.map((c) => c.row)) + 1;
|
|
13815
|
-
const
|
|
13816
|
-
const
|
|
14576
|
+
const colLabelH = colLabels.length > 0 ? 16 : 0;
|
|
14577
|
+
const rowLabelW = rowLabels.length > 0 ? 20 : 0;
|
|
14578
|
+
const gridX0 = rowLabelW;
|
|
14579
|
+
const gridY0 = panelYCells + colLabelH;
|
|
14580
|
+
const cw = (width - rowLabelW) / maxCol;
|
|
14581
|
+
const ch = (panelHeight - colLabelH) / maxRow;
|
|
13817
14582
|
cells.forEach((c, i) => {
|
|
13818
|
-
const x = c.col * cw;
|
|
13819
|
-
const y = c.row * ch;
|
|
14583
|
+
const x = gridX0 + c.col * cw;
|
|
14584
|
+
const y = gridY0 + c.row * ch;
|
|
13820
14585
|
const color = c.color ?? DEFAULT_CELL_COLOR;
|
|
13821
14586
|
out.push({
|
|
13822
14587
|
type: "rect",
|
|
@@ -13840,11 +14605,207 @@ var init_AlgorithmCanvas = __esm({
|
|
|
13840
14605
|
align: "center"
|
|
13841
14606
|
});
|
|
13842
14607
|
}
|
|
14608
|
+
if (c.corner && cw >= CORNER_MIN_CELL && ch >= CORNER_MIN_CELL) {
|
|
14609
|
+
const { tl, tr, bl, br } = c.corner;
|
|
14610
|
+
if (tl) {
|
|
14611
|
+
out.push({
|
|
14612
|
+
type: "text",
|
|
14613
|
+
x: x + CORNER_INSET_X,
|
|
14614
|
+
y: y + CORNER_INSET_Y,
|
|
14615
|
+
text: tl,
|
|
14616
|
+
color: CORNER_TEXT_COLOR,
|
|
14617
|
+
fontSize: CORNER_FONT_SIZE,
|
|
14618
|
+
align: "left"
|
|
14619
|
+
});
|
|
14620
|
+
}
|
|
14621
|
+
if (tr) {
|
|
14622
|
+
out.push({
|
|
14623
|
+
type: "text",
|
|
14624
|
+
x: x + cw - CORNER_INSET_X,
|
|
14625
|
+
y: y + CORNER_INSET_Y,
|
|
14626
|
+
text: tr,
|
|
14627
|
+
color: CORNER_TEXT_COLOR,
|
|
14628
|
+
fontSize: CORNER_FONT_SIZE,
|
|
14629
|
+
align: "right"
|
|
14630
|
+
});
|
|
14631
|
+
}
|
|
14632
|
+
if (bl) {
|
|
14633
|
+
out.push({
|
|
14634
|
+
type: "text",
|
|
14635
|
+
x: x + CORNER_INSET_X,
|
|
14636
|
+
y: y + ch - CORNER_INSET_Y,
|
|
14637
|
+
text: bl,
|
|
14638
|
+
color: CORNER_TEXT_COLOR,
|
|
14639
|
+
fontSize: CORNER_FONT_SIZE,
|
|
14640
|
+
align: "left"
|
|
14641
|
+
});
|
|
14642
|
+
}
|
|
14643
|
+
if (br) {
|
|
14644
|
+
out.push({
|
|
14645
|
+
type: "text",
|
|
14646
|
+
x: x + cw - CORNER_INSET_X,
|
|
14647
|
+
y: y + ch - CORNER_INSET_Y,
|
|
14648
|
+
text: br,
|
|
14649
|
+
color: CORNER_TEXT_COLOR,
|
|
14650
|
+
fontSize: CORNER_FONT_SIZE,
|
|
14651
|
+
align: "right"
|
|
14652
|
+
});
|
|
14653
|
+
}
|
|
14654
|
+
}
|
|
14655
|
+
});
|
|
14656
|
+
colLabels.forEach((l) => {
|
|
14657
|
+
out.push({
|
|
14658
|
+
type: "text",
|
|
14659
|
+
x: gridX0 + l.index * cw + cw / 2,
|
|
14660
|
+
y: panelYCells + colLabelH / 2,
|
|
14661
|
+
text: l.text,
|
|
14662
|
+
color: l.color ?? AXIS_LABEL_COLOR,
|
|
14663
|
+
fontSize: AXIS_LABEL_FONT_SIZE,
|
|
14664
|
+
align: "center"
|
|
14665
|
+
});
|
|
14666
|
+
});
|
|
14667
|
+
rowLabels.forEach((l) => {
|
|
14668
|
+
out.push({
|
|
14669
|
+
type: "text",
|
|
14670
|
+
x: rowLabelW - 6,
|
|
14671
|
+
y: gridY0 + l.index * ch + ch / 2,
|
|
14672
|
+
text: l.text,
|
|
14673
|
+
color: l.color ?? AXIS_LABEL_COLOR,
|
|
14674
|
+
fontSize: AXIS_LABEL_FONT_SIZE,
|
|
14675
|
+
align: "right"
|
|
14676
|
+
});
|
|
14677
|
+
});
|
|
14678
|
+
}
|
|
14679
|
+
if (buckets.length > 0) {
|
|
14680
|
+
const panelYBuckets = panelY.buckets;
|
|
14681
|
+
const bucketCount = Math.max(0, ...buckets.map((b) => b.index)) + 1;
|
|
14682
|
+
const rowH = panelHeight / bucketCount;
|
|
14683
|
+
const indexColW = Math.min(width * 0.12, 40);
|
|
14684
|
+
const maxChainLen = Math.max(1, ...buckets.map((b) => b.entries.length));
|
|
14685
|
+
const entryW = Math.min(BUCKET_ENTRY_MAX_W, Math.max(BUCKET_ENTRY_MIN_W, (width - indexColW - 8) / maxChainLen));
|
|
14686
|
+
const maxVisible = Math.floor((width - indexColW - 4) / entryW);
|
|
14687
|
+
buckets.forEach((b) => {
|
|
14688
|
+
const rowY = panelYBuckets + b.index * rowH;
|
|
14689
|
+
out.push({
|
|
14690
|
+
type: "rect",
|
|
14691
|
+
id: `bucket-index-${b.index}`,
|
|
14692
|
+
x: 2,
|
|
14693
|
+
y: rowY + 2,
|
|
14694
|
+
width: indexColW - 4,
|
|
14695
|
+
height: rowH - 4,
|
|
14696
|
+
color: BUCKET_INDEX_STROKE,
|
|
14697
|
+
fill: BUCKET_INDEX_FILL
|
|
14698
|
+
});
|
|
14699
|
+
out.push({
|
|
14700
|
+
type: "text",
|
|
14701
|
+
x: 2 + (indexColW - 4) / 2,
|
|
14702
|
+
y: rowY + rowH / 2,
|
|
14703
|
+
text: String(b.index),
|
|
14704
|
+
color: BUCKET_INDEX_TEXT,
|
|
14705
|
+
fontSize: 10,
|
|
14706
|
+
align: "center"
|
|
14707
|
+
});
|
|
14708
|
+
const overflow = b.entries.length > maxVisible;
|
|
14709
|
+
const visibleCount = overflow ? Math.max(0, maxVisible - 1) : b.entries.length;
|
|
14710
|
+
for (let j = 0; j < visibleCount; j++) {
|
|
14711
|
+
const entry = b.entries[j];
|
|
14712
|
+
const ex = indexColW + 4 + j * entryW;
|
|
14713
|
+
const state = entry.state ?? "default";
|
|
14714
|
+
const fill = state === "highlight" ? entry.color ?? BUCKET_ENTRY_HIGHLIGHT : state === "probing" ? entry.color ?? BUCKET_ENTRY_PROBING : entry.color ?? BUCKET_ENTRY_DEFAULT;
|
|
14715
|
+
out.push({
|
|
14716
|
+
type: "rect",
|
|
14717
|
+
id: `bucket-${b.index}-${j}`,
|
|
14718
|
+
x: ex,
|
|
14719
|
+
y: rowY + 2,
|
|
14720
|
+
width: entryW - 2,
|
|
14721
|
+
height: rowH - 4,
|
|
14722
|
+
color: fill,
|
|
14723
|
+
fill
|
|
14724
|
+
});
|
|
14725
|
+
if (entryW >= 20 && rowH >= 16) {
|
|
14726
|
+
out.push({
|
|
14727
|
+
type: "text",
|
|
14728
|
+
x: ex + (entryW - 2) / 2,
|
|
14729
|
+
y: rowY + rowH / 2,
|
|
14730
|
+
text: entry.label,
|
|
14731
|
+
color: BUCKET_ENTRY_TEXT,
|
|
14732
|
+
fontSize: 10,
|
|
14733
|
+
align: "center"
|
|
14734
|
+
});
|
|
14735
|
+
}
|
|
14736
|
+
}
|
|
14737
|
+
if (overflow) {
|
|
14738
|
+
const ex = indexColW + 4 + visibleCount * entryW;
|
|
14739
|
+
out.push({
|
|
14740
|
+
type: "rect",
|
|
14741
|
+
id: `bucket-${b.index}-overflow`,
|
|
14742
|
+
x: ex,
|
|
14743
|
+
y: rowY + 2,
|
|
14744
|
+
width: entryW - 2,
|
|
14745
|
+
height: rowH - 4,
|
|
14746
|
+
color: BUCKET_ENTRY_DEFAULT,
|
|
14747
|
+
fill: BUCKET_ENTRY_DEFAULT
|
|
14748
|
+
});
|
|
14749
|
+
out.push({
|
|
14750
|
+
type: "text",
|
|
14751
|
+
x: ex + (entryW - 2) / 2,
|
|
14752
|
+
y: rowY + rowH / 2,
|
|
14753
|
+
text: `+${b.entries.length - visibleCount}`,
|
|
14754
|
+
color: BUCKET_ENTRY_TEXT,
|
|
14755
|
+
fontSize: 10,
|
|
14756
|
+
align: "center"
|
|
14757
|
+
});
|
|
14758
|
+
}
|
|
14759
|
+
});
|
|
14760
|
+
}
|
|
14761
|
+
if (frames.length > 0) {
|
|
14762
|
+
const panelYFrames = panelY.frames;
|
|
14763
|
+
const n = frames.length;
|
|
14764
|
+
const frameH = panelHeight / n;
|
|
14765
|
+
const x = 8;
|
|
14766
|
+
const w = width - 16;
|
|
14767
|
+
frames.forEach((f3, i) => {
|
|
14768
|
+
const y = panelYFrames + panelHeight - (i + 1) * frameH;
|
|
14769
|
+
const state = f3.state ?? "active";
|
|
14770
|
+
const fill = state === "returning" ? f3.color ?? FRAME_RETURNING_COLOR : state === "done" ? f3.color ?? FRAME_DONE_COLOR : f3.color ?? FRAME_ACTIVE_COLOR;
|
|
14771
|
+
out.push({ type: "rect", id: `frame-${i}`, x, y, width: w, height: frameH, color: fill, fill });
|
|
14772
|
+
if (frameH >= FRAME_TWO_LINE_MIN_H) {
|
|
14773
|
+
out.push({
|
|
14774
|
+
type: "text",
|
|
14775
|
+
x: 16,
|
|
14776
|
+
y: y + frameH * 0.35,
|
|
14777
|
+
text: f3.label,
|
|
14778
|
+
color: FRAME_LABEL_COLOR,
|
|
14779
|
+
fontSize: 10,
|
|
14780
|
+
align: "left"
|
|
14781
|
+
});
|
|
14782
|
+
if (f3.detail) {
|
|
14783
|
+
out.push({
|
|
14784
|
+
type: "text",
|
|
14785
|
+
x: 16,
|
|
14786
|
+
y: y + frameH * 0.7,
|
|
14787
|
+
text: f3.detail,
|
|
14788
|
+
color: FRAME_DETAIL_COLOR,
|
|
14789
|
+
fontSize: 10,
|
|
14790
|
+
align: "left"
|
|
14791
|
+
});
|
|
14792
|
+
}
|
|
14793
|
+
} else {
|
|
14794
|
+
out.push({
|
|
14795
|
+
type: "text",
|
|
14796
|
+
x: 16,
|
|
14797
|
+
y: y + frameH / 2,
|
|
14798
|
+
text: f3.label,
|
|
14799
|
+
color: FRAME_LABEL_COLOR,
|
|
14800
|
+
fontSize: 10,
|
|
14801
|
+
align: "left"
|
|
14802
|
+
});
|
|
14803
|
+
}
|
|
13843
14804
|
});
|
|
13844
14805
|
}
|
|
13845
14806
|
out.push(...shapes);
|
|
13846
14807
|
return out;
|
|
13847
|
-
}, [bars, cells, pointers, shapes, width, height]);
|
|
14808
|
+
}, [bars, cells, pointers, ranges, slots, slotOrientation, frames, buckets, auxBars, rowLabels, colLabels, shapes, width, height]);
|
|
13848
14809
|
return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
|
|
13849
14810
|
title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
|
|
13850
14811
|
/* @__PURE__ */ jsx(
|
|
@@ -14858,6 +15819,12 @@ function LearningScene3D({
|
|
|
14858
15819
|
const unitId = event.payload?.unitId;
|
|
14859
15820
|
if (typeof unitId === "string") onItemClickRef.current?.(unitId);
|
|
14860
15821
|
});
|
|
15822
|
+
if (typeof process !== "undefined" && process.env && process.env.NODE_ENV !== "production" && post?.bloom) {
|
|
15823
|
+
const unknownKeys = Object.keys(post.bloom).filter((k) => !KNOWN_BLOOM_KEYS.has(k));
|
|
15824
|
+
if (unknownKeys.length > 0) {
|
|
15825
|
+
sceneLog.debug("post.bloom has unrecognized keys \u2014 only intensity/threshold/smoothing are read", { unknownKeys });
|
|
15826
|
+
}
|
|
15827
|
+
}
|
|
14861
15828
|
const props3d = {
|
|
14862
15829
|
drawables,
|
|
14863
15830
|
isLoading,
|
|
@@ -14923,7 +15890,7 @@ function cylinderBetween(from, to, radius, color) {
|
|
|
14923
15890
|
material: { color }
|
|
14924
15891
|
};
|
|
14925
15892
|
}
|
|
14926
|
-
function arrowBetween(from, to, color, shaftRadius = 0.08) {
|
|
15893
|
+
function arrowBetween(from, to, color, shaftRadius = 0.08, id) {
|
|
14927
15894
|
const len = segmentLength(from, to);
|
|
14928
15895
|
if (len < 1e-6) return null;
|
|
14929
15896
|
const tipLen = Math.min(shaftRadius * 8, len * 0.35);
|
|
@@ -14951,6 +15918,7 @@ function arrowBetween(from, to, color, shaftRadius = 0.08) {
|
|
|
14951
15918
|
};
|
|
14952
15919
|
return {
|
|
14953
15920
|
type: "draw-group",
|
|
15921
|
+
...id !== void 0 ? { id } : {},
|
|
14954
15922
|
position: { x: from[0], y: from[1], z: from[2] },
|
|
14955
15923
|
items: tipLenActual < 1e-6 ? shaft ? [shaft] : [] : shaft ? [shaft, tip] : [tip]
|
|
14956
15924
|
};
|
|
@@ -14977,20 +15945,228 @@ function get3DClickPayload(onShapeClick, idToIndex) {
|
|
|
14977
15945
|
if (!onShapeClick) return void 0;
|
|
14978
15946
|
return (id) => onShapeClick({ id, index: idToIndex.get(id) ?? -1 });
|
|
14979
15947
|
}
|
|
14980
|
-
|
|
15948
|
+
function polylineTube(points, radius, color, opts) {
|
|
15949
|
+
const maxSegments = opts?.maxSegments ?? 128;
|
|
15950
|
+
let pts = points;
|
|
15951
|
+
if (pts.length - 1 > maxSegments) {
|
|
15952
|
+
const step = (pts.length - 1) / maxSegments;
|
|
15953
|
+
const kept = [pts[0]];
|
|
15954
|
+
for (let s = 1; s < maxSegments; s++) kept.push(pts[Math.round(s * step)]);
|
|
15955
|
+
kept.push(pts[pts.length - 1]);
|
|
15956
|
+
pts = kept;
|
|
15957
|
+
}
|
|
15958
|
+
const out = [];
|
|
15959
|
+
for (let i = 0; i < pts.length - 1; i++) {
|
|
15960
|
+
const seg = cylinderBetween(pts[i], pts[i + 1], radius, color);
|
|
15961
|
+
if (seg) out.push(opts?.opacity !== void 0 ? { ...seg, opacity: opts.opacity } : seg);
|
|
15962
|
+
}
|
|
15963
|
+
return out;
|
|
15964
|
+
}
|
|
15965
|
+
function heightFieldMesh(spec) {
|
|
15966
|
+
const { nx, ny, heights, spacing = 1, x = 0, y = 0 } = spec;
|
|
15967
|
+
const flatShading = spec.flatShading ?? true;
|
|
15968
|
+
const vertices = [];
|
|
15969
|
+
for (let iy = 0; iy < ny; iy++) {
|
|
15970
|
+
for (let ix = 0; ix < nx; ix++) {
|
|
15971
|
+
vertices.push([
|
|
15972
|
+
x + (ix - (nx - 1) / 2) * spacing,
|
|
15973
|
+
y + (iy - (ny - 1) / 2) * spacing,
|
|
15974
|
+
heights[iy * nx + ix] ?? 0
|
|
15975
|
+
]);
|
|
15976
|
+
}
|
|
15977
|
+
}
|
|
15978
|
+
const bands = [...spec.bands ?? []].sort((a, b) => (a.min ?? -Infinity) - (b.min ?? -Infinity));
|
|
15979
|
+
const facesByBand = /* @__PURE__ */ new Map();
|
|
15980
|
+
for (let iy = 0; iy < ny - 1; iy++) {
|
|
15981
|
+
for (let ix = 0; ix < nx - 1; ix++) {
|
|
15982
|
+
const v00 = iy * nx + ix;
|
|
15983
|
+
const v10 = iy * nx + ix + 1;
|
|
15984
|
+
const v01 = (iy + 1) * nx + ix;
|
|
15985
|
+
const v11 = (iy + 1) * nx + ix + 1;
|
|
15986
|
+
for (const face of [[v00, v10, v01], [v10, v11, v01]]) {
|
|
15987
|
+
const centroid = (vertices[face[0]][2] + vertices[face[1]][2] + vertices[face[2]][2]) / 3;
|
|
15988
|
+
let band = null;
|
|
15989
|
+
for (const b of bands) {
|
|
15990
|
+
if ((b.min ?? -Infinity) <= centroid) band = b;
|
|
15991
|
+
}
|
|
15992
|
+
const key = bands.length > 0 ? band : null;
|
|
15993
|
+
const list = facesByBand.get(key) ?? [];
|
|
15994
|
+
list.push(face);
|
|
15995
|
+
facesByBand.set(key, list);
|
|
15996
|
+
}
|
|
15997
|
+
}
|
|
15998
|
+
}
|
|
15999
|
+
const out = [];
|
|
16000
|
+
for (const [band, faces] of facesByBand) {
|
|
16001
|
+
if (faces.length === 0) continue;
|
|
16002
|
+
out.push({
|
|
16003
|
+
type: "draw-mesh",
|
|
16004
|
+
shape: "polyhedron",
|
|
16005
|
+
position: { x: 0, y: 0, z: 0 },
|
|
16006
|
+
vertices,
|
|
16007
|
+
faces,
|
|
16008
|
+
pivot: "center",
|
|
16009
|
+
material: { color: band?.color ?? spec.color ?? "#64748b", flatShading, side: "double" },
|
|
16010
|
+
...spec.opacity !== void 0 ? { opacity: spec.opacity } : {}
|
|
16011
|
+
});
|
|
16012
|
+
}
|
|
16013
|
+
return out;
|
|
16014
|
+
}
|
|
16015
|
+
function arrowField(vectors, opts) {
|
|
16016
|
+
const scale = opts?.scale ?? 1;
|
|
16017
|
+
const out = [];
|
|
16018
|
+
for (const v of vectors) {
|
|
16019
|
+
const to = [
|
|
16020
|
+
v.from[0] + v.delta[0] * scale,
|
|
16021
|
+
v.from[1] + v.delta[1] * scale,
|
|
16022
|
+
v.from[2] + v.delta[2] * scale
|
|
16023
|
+
];
|
|
16024
|
+
const arrow = arrowBetween(v.from, to, v.color ?? "#dc2626", v.width, v.id);
|
|
16025
|
+
if (arrow) out.push(arrow);
|
|
16026
|
+
if (v.label) out.push(billboardLabel(v.label, to[0], to[1], to[2], { color: opts?.labelColor }));
|
|
16027
|
+
}
|
|
16028
|
+
return out;
|
|
16029
|
+
}
|
|
16030
|
+
function helixDrawables(spec, opts) {
|
|
16031
|
+
const count = spec.count ?? spec.rungs?.length ?? 0;
|
|
16032
|
+
const rungs = Array.from({ length: count }, (_, i) => spec.rungs?.[i] ?? {});
|
|
16033
|
+
const radius = spec.radius ?? 1;
|
|
16034
|
+
const rise = spec.rise ?? 0.34;
|
|
16035
|
+
const twistRad = (spec.twistDeg ?? 36) * (Math.PI / 180);
|
|
16036
|
+
const strandAColor = spec.strandAColor ?? "#38bdf8";
|
|
16037
|
+
const strandBColor = spec.strandBColor ?? "#fb923c";
|
|
16038
|
+
const backboneRadius = spec.backboneRadius ?? 0.16;
|
|
16039
|
+
const rungRadius = spec.rungRadius ?? 0.12;
|
|
16040
|
+
const cx = spec.x ?? 0;
|
|
16041
|
+
const cy = spec.y ?? 0;
|
|
16042
|
+
const cz = spec.z ?? 0;
|
|
16043
|
+
const unwoundCount = spec.unwoundCount ?? 0;
|
|
16044
|
+
const unwindSpread = spec.unwindSpread ?? 1.8;
|
|
16045
|
+
const strandA = [];
|
|
16046
|
+
const strandB = [];
|
|
16047
|
+
for (let i = 0; i < count; i++) {
|
|
16048
|
+
const yi = cy + (i - (count - 1) / 2) * rise;
|
|
16049
|
+
const theta = i * twistRad;
|
|
16050
|
+
const s = i < unwoundCount ? unwindSpread : 1;
|
|
16051
|
+
strandA.push([cx + s * radius * Math.cos(theta), yi, cz + s * radius * Math.sin(theta)]);
|
|
16052
|
+
strandB.push([cx + s * radius * Math.cos(theta + Math.PI), yi, cz + s * radius * Math.sin(theta + Math.PI)]);
|
|
16053
|
+
}
|
|
16054
|
+
const out = [];
|
|
16055
|
+
for (let i = 0; i < count; i++) {
|
|
16056
|
+
out.push(meshSphere(`hx-a-${i}`, strandA[i][0], strandA[i][1], strandA[i][2], backboneRadius, strandAColor));
|
|
16057
|
+
out.push(meshSphere(`hx-b-${i}`, strandB[i][0], strandB[i][1], strandB[i][2], backboneRadius, strandBColor));
|
|
16058
|
+
if (i > 0) {
|
|
16059
|
+
const segA = cylinderBetween(strandA[i - 1], strandA[i], backboneRadius, strandAColor);
|
|
16060
|
+
if (segA) out.push(segA);
|
|
16061
|
+
const segB = cylinderBetween(strandB[i - 1], strandB[i], backboneRadius, strandBColor);
|
|
16062
|
+
if (segB) out.push(segB);
|
|
16063
|
+
}
|
|
16064
|
+
const rung = rungs[i];
|
|
16065
|
+
const rungColor = rung.color ?? "#94a3b8";
|
|
16066
|
+
const rod = cylinderBetween(strandA[i], strandB[i], rungRadius, rungColor);
|
|
16067
|
+
if (rod) out.push(rod);
|
|
16068
|
+
const mid = [
|
|
16069
|
+
(strandA[i][0] + strandB[i][0]) / 2,
|
|
16070
|
+
(strandA[i][1] + strandB[i][1]) / 2,
|
|
16071
|
+
(strandA[i][2] + strandB[i][2]) / 2
|
|
16072
|
+
];
|
|
16073
|
+
const markerRadius = rung.radius ?? rungRadius;
|
|
16074
|
+
out.push(meshSphere(rung.id, mid[0], mid[1], mid[2], markerRadius, rungColor));
|
|
16075
|
+
if (rung.label) out.push(billboardLabel(rung.label, mid[0], mid[1], mid[2] + markerRadius, { color: opts?.labelColor }));
|
|
16076
|
+
}
|
|
16077
|
+
return out;
|
|
16078
|
+
}
|
|
16079
|
+
function latticeDrawables(spec, opts) {
|
|
16080
|
+
const nx = spec.nx ?? 2;
|
|
16081
|
+
const ny = spec.ny ?? 2;
|
|
16082
|
+
const nz = spec.nz ?? 2;
|
|
16083
|
+
const latticeConstant = spec.latticeConstant ?? 2;
|
|
16084
|
+
const bondRadius = spec.bondRadius ?? 0.06;
|
|
16085
|
+
const highlightCell = spec.highlightCell ?? false;
|
|
16086
|
+
const dimColor = spec.dimColor ?? "#475569";
|
|
16087
|
+
const showLabels = spec.showLabels ?? false;
|
|
16088
|
+
const selectedColor = spec.selectedColor ?? "#f59e0b";
|
|
16089
|
+
const posByKey = /* @__PURE__ */ new Map();
|
|
16090
|
+
const inCellByKey = /* @__PURE__ */ new Map();
|
|
16091
|
+
const out = [];
|
|
16092
|
+
for (const site of spec.basis) {
|
|
16093
|
+
const snx = site.xEdge ? nx + 1 : nx;
|
|
16094
|
+
const sny = site.yEdge ? ny + 1 : ny;
|
|
16095
|
+
const snz = site.zEdge ? nz + 1 : nz;
|
|
16096
|
+
for (let i = 0; i < snx; i++) {
|
|
16097
|
+
for (let j = 0; j < sny; j++) {
|
|
16098
|
+
for (let k = 0; k < snz; k++) {
|
|
16099
|
+
const key = `${site.key}-${i}-${j}-${k}`;
|
|
16100
|
+
const inCell = i + site.dx <= 1 && j + site.dy <= 1 && k + site.dz <= 1;
|
|
16101
|
+
const pos = [
|
|
16102
|
+
(i + site.dx) * latticeConstant - nx * latticeConstant / 2,
|
|
16103
|
+
(j + site.dy) * latticeConstant - ny * latticeConstant / 2,
|
|
16104
|
+
(k + site.dz) * latticeConstant - nz * latticeConstant / 2
|
|
16105
|
+
];
|
|
16106
|
+
posByKey.set(key, pos);
|
|
16107
|
+
inCellByKey.set(key, inCell);
|
|
16108
|
+
const isSelected = spec.selectedId === `lat-${key}`;
|
|
16109
|
+
const color = isSelected ? selectedColor : highlightCell && !inCell ? dimColor : site.color ?? "#2563eb";
|
|
16110
|
+
const radius = (site.radius ?? 0.3) * (isSelected ? 1.4 : 1);
|
|
16111
|
+
out.push(meshSphere(`lat-${key}`, pos[0], pos[1], pos[2], radius, color));
|
|
16112
|
+
if (showLabels && site.element) {
|
|
16113
|
+
out.push(billboardLabel(site.element, pos[0], pos[1], pos[2] + radius, { color: opts?.labelColor }));
|
|
16114
|
+
}
|
|
16115
|
+
}
|
|
16116
|
+
}
|
|
16117
|
+
}
|
|
16118
|
+
}
|
|
16119
|
+
const basisByKey = new Map(spec.basis.map((s) => [s.key, s]));
|
|
16120
|
+
for (const bond of spec.bonds ?? []) {
|
|
16121
|
+
const fromSite = basisByKey.get(bond.from);
|
|
16122
|
+
const toSite = basisByKey.get(bond.to);
|
|
16123
|
+
if (!fromSite || !toSite) continue;
|
|
16124
|
+
const fnx = fromSite.xEdge ? nx + 1 : nx;
|
|
16125
|
+
const fny = fromSite.yEdge ? ny + 1 : ny;
|
|
16126
|
+
const fnz = fromSite.zEdge ? nz + 1 : nz;
|
|
16127
|
+
const tnx = toSite.xEdge ? nx + 1 : nx;
|
|
16128
|
+
const tny = toSite.yEdge ? ny + 1 : ny;
|
|
16129
|
+
const tnz = toSite.zEdge ? nz + 1 : nz;
|
|
16130
|
+
const bdx = bond.dx ?? 0;
|
|
16131
|
+
const bdy = bond.dy ?? 0;
|
|
16132
|
+
const bdz = bond.dz ?? 0;
|
|
16133
|
+
for (let i = 0; i < fnx; i++) {
|
|
16134
|
+
for (let j = 0; j < fny; j++) {
|
|
16135
|
+
for (let k = 0; k < fnz; k++) {
|
|
16136
|
+
const ti = i + bdx;
|
|
16137
|
+
const tj = j + bdy;
|
|
16138
|
+
const tk = k + bdz;
|
|
16139
|
+
if (ti < 0 || ti >= tnx || tj < 0 || tj >= tny || tk < 0 || tk >= tnz) continue;
|
|
16140
|
+
const fromKey = `${fromSite.key}-${i}-${j}-${k}`;
|
|
16141
|
+
const toKey = `${toSite.key}-${ti}-${tj}-${tk}`;
|
|
16142
|
+
const fromPos = posByKey.get(fromKey);
|
|
16143
|
+
const toPos = posByKey.get(toKey);
|
|
16144
|
+
if (!fromPos || !toPos) continue;
|
|
16145
|
+
const dimmed = highlightCell && !(inCellByKey.get(fromKey) && inCellByKey.get(toKey));
|
|
16146
|
+
const seg = cylinderBetween(fromPos, toPos, bondRadius, dimmed ? dimColor : bond.color ?? "#6b7280");
|
|
16147
|
+
if (seg) out.push(seg);
|
|
16148
|
+
}
|
|
16149
|
+
}
|
|
16150
|
+
}
|
|
16151
|
+
}
|
|
16152
|
+
return out;
|
|
16153
|
+
}
|
|
16154
|
+
var sceneLog, KNOWN_BLOOM_KEYS, Canvas3DHost2;
|
|
14981
16155
|
var init_learningScene3D = __esm({
|
|
14982
16156
|
"components/learning/molecules/learningScene3D.tsx"() {
|
|
14983
16157
|
"use client";
|
|
14984
16158
|
init_atoms();
|
|
14985
16159
|
init_Stack();
|
|
14986
16160
|
init_useEventBus();
|
|
16161
|
+
sceneLog = createLogger("almadar:ui:learning-scene-3d");
|
|
16162
|
+
KNOWN_BLOOM_KEYS = /* @__PURE__ */ new Set(["intensity", "threshold", "smoothing"]);
|
|
14987
16163
|
Canvas3DHost2 = lazy(
|
|
14988
16164
|
() => import('@almadar/ui/components/molecules/game/three').then((m) => ({ default: m.Canvas3DHost }))
|
|
14989
16165
|
);
|
|
14990
16166
|
LearningScene3D.displayName = "LearningScene3D";
|
|
14991
16167
|
}
|
|
14992
16168
|
});
|
|
14993
|
-
var biologyLog, BiologyCanvas;
|
|
16169
|
+
var biologyLog, BIO_BAND_COLORS, BIO_STAGE_FILL, BIO_STAGE_TEXT, BiologyCanvas;
|
|
14994
16170
|
var init_BiologyCanvas = __esm({
|
|
14995
16171
|
"components/learning/molecules/BiologyCanvas.tsx"() {
|
|
14996
16172
|
"use client";
|
|
@@ -14999,6 +16175,17 @@ var init_BiologyCanvas = __esm({
|
|
|
14999
16175
|
init_LearningCanvas();
|
|
15000
16176
|
init_learningScene3D();
|
|
15001
16177
|
biologyLog = createLogger("almadar:ui:biology-canvas");
|
|
16178
|
+
BIO_BAND_COLORS = ["#dcfce7", "#fef9c3", "#fee2e2", "#e0e7ff"];
|
|
16179
|
+
BIO_STAGE_FILL = {
|
|
16180
|
+
pending: "#e2e8f0",
|
|
16181
|
+
active: "#3b82f6",
|
|
16182
|
+
done: "#94a3b8"
|
|
16183
|
+
};
|
|
16184
|
+
BIO_STAGE_TEXT = {
|
|
16185
|
+
pending: "#64748b",
|
|
16186
|
+
active: "#ffffff",
|
|
16187
|
+
done: "#ffffff"
|
|
16188
|
+
};
|
|
15002
16189
|
BiologyCanvas = ({
|
|
15003
16190
|
className,
|
|
15004
16191
|
width = 600,
|
|
@@ -15011,7 +16198,15 @@ var init_BiologyCanvas = __esm({
|
|
|
15011
16198
|
post,
|
|
15012
16199
|
nodes = [],
|
|
15013
16200
|
edges = [],
|
|
16201
|
+
compartments = [],
|
|
16202
|
+
bands = [],
|
|
16203
|
+
stages = [],
|
|
16204
|
+
stageStyle = "timeline",
|
|
16205
|
+
helix,
|
|
16206
|
+
helix3d,
|
|
15014
16207
|
shapes = [],
|
|
16208
|
+
readouts,
|
|
16209
|
+
traces,
|
|
15015
16210
|
showGrid,
|
|
15016
16211
|
shadows,
|
|
15017
16212
|
interactive,
|
|
@@ -15026,19 +16221,148 @@ var init_BiologyCanvas = __esm({
|
|
|
15026
16221
|
for (const n of nodes) {
|
|
15027
16222
|
if (n.id) nodeById.set(n.id, n);
|
|
15028
16223
|
}
|
|
16224
|
+
const bandCount = bands.length;
|
|
16225
|
+
for (let i = 0; i < bandCount; i++) {
|
|
16226
|
+
const band = bands[i];
|
|
16227
|
+
const bandColor = band.color ?? BIO_BAND_COLORS[i % BIO_BAND_COLORS.length];
|
|
16228
|
+
const bandY = i * height / bandCount;
|
|
16229
|
+
const bandH = height / bandCount;
|
|
16230
|
+
out.push({
|
|
16231
|
+
type: "rect",
|
|
16232
|
+
x: 0,
|
|
16233
|
+
y: bandY,
|
|
16234
|
+
width,
|
|
16235
|
+
height: bandH,
|
|
16236
|
+
color: bandColor,
|
|
16237
|
+
fill: bandColor,
|
|
16238
|
+
opacity: 0.45
|
|
16239
|
+
});
|
|
16240
|
+
if (band.label) {
|
|
16241
|
+
out.push({
|
|
16242
|
+
type: "text",
|
|
16243
|
+
x: 8,
|
|
16244
|
+
y: bandY + 14,
|
|
16245
|
+
text: band.label,
|
|
16246
|
+
color: "#6b7280",
|
|
16247
|
+
fontSize: 10
|
|
16248
|
+
});
|
|
16249
|
+
}
|
|
16250
|
+
}
|
|
16251
|
+
for (const c of compartments) {
|
|
16252
|
+
const color = c.color ?? "#16a34a";
|
|
16253
|
+
out.push({
|
|
16254
|
+
type: "ellipse",
|
|
16255
|
+
x: c.x,
|
|
16256
|
+
y: c.y,
|
|
16257
|
+
width: c.width,
|
|
16258
|
+
height: c.height,
|
|
16259
|
+
color,
|
|
16260
|
+
fill: c.fill ?? `${color}1A`,
|
|
16261
|
+
lineWidth: c.lineWidth ?? 2,
|
|
16262
|
+
...c.dash ? { dash: c.dash } : {}
|
|
16263
|
+
});
|
|
16264
|
+
if (c.label) {
|
|
16265
|
+
out.push({
|
|
16266
|
+
type: "text",
|
|
16267
|
+
x: c.x,
|
|
16268
|
+
y: c.y - c.height / 2 + 14,
|
|
16269
|
+
text: c.label,
|
|
16270
|
+
color: "#111827",
|
|
16271
|
+
fontSize: 11,
|
|
16272
|
+
align: "center"
|
|
16273
|
+
});
|
|
16274
|
+
}
|
|
16275
|
+
}
|
|
16276
|
+
if (helix) {
|
|
16277
|
+
const hx = helix.x ?? 24;
|
|
16278
|
+
const hy = helix.y ?? height * 0.25;
|
|
16279
|
+
const hw = helix.width ?? width - 48;
|
|
16280
|
+
const hh = helix.height ?? height * 0.5;
|
|
16281
|
+
const rungs = helix.rungs;
|
|
16282
|
+
const n = rungs.length;
|
|
16283
|
+
const cy = hy + hh / 2;
|
|
16284
|
+
const colorA = helix.colorA ?? "#2563eb";
|
|
16285
|
+
const colorB = helix.colorB ?? "#dc2626";
|
|
16286
|
+
const rungColor = helix.rungColor ?? "#94a3b8";
|
|
16287
|
+
const fork = helix.fork ?? 0;
|
|
16288
|
+
const maxSep = Math.min(hh - 8, 96);
|
|
16289
|
+
const strandA = [];
|
|
16290
|
+
const strandB = [];
|
|
16291
|
+
const rungGeoms = [];
|
|
16292
|
+
for (let i = 0; i < n; i++) {
|
|
16293
|
+
const rx = hx + (i + 0.5) * hw / n;
|
|
16294
|
+
const t = (i + 0.5) / n;
|
|
16295
|
+
const paired = t >= fork;
|
|
16296
|
+
const sep = paired ? 28 : 28 + (maxSep - 28) * ((fork - t) / fork);
|
|
16297
|
+
strandA.push({ x: rx, y: cy - sep / 2 });
|
|
16298
|
+
strandB.push({ x: rx, y: cy + sep / 2 });
|
|
16299
|
+
rungGeoms.push({ rx, sep, rung: rungs[i], paired });
|
|
16300
|
+
}
|
|
16301
|
+
for (let i = 1; i < n; i++) {
|
|
16302
|
+
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 });
|
|
16303
|
+
}
|
|
16304
|
+
for (let i = 1; i < n; i++) {
|
|
16305
|
+
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 });
|
|
16306
|
+
}
|
|
16307
|
+
for (const g of rungGeoms) {
|
|
16308
|
+
const rColor = g.rung.color ?? (g.rung.state === "new" ? "#16a34a" : rungColor);
|
|
16309
|
+
const topY = cy - g.sep / 2;
|
|
16310
|
+
const bottomY = cy + g.sep / 2;
|
|
16311
|
+
if (g.paired) {
|
|
16312
|
+
out.push({ type: "line", x1: g.rx, y1: topY, x2: g.rx, y2: bottomY, color: rColor });
|
|
16313
|
+
if (g.rung.a) {
|
|
16314
|
+
out.push({ type: "text", x: g.rx, y: cy - g.sep / 4, text: g.rung.a, fontSize: 9, align: "center", color: "#374151" });
|
|
16315
|
+
}
|
|
16316
|
+
if (g.rung.b) {
|
|
16317
|
+
out.push({ type: "text", x: g.rx, y: cy + g.sep / 4, text: g.rung.b, fontSize: 9, align: "center", color: "#374151" });
|
|
16318
|
+
}
|
|
16319
|
+
} else {
|
|
16320
|
+
const stubTopY = topY + 8;
|
|
16321
|
+
const stubBottomY = bottomY - 8;
|
|
16322
|
+
out.push({ type: "line", x1: g.rx, y1: topY, x2: g.rx, y2: stubTopY, color: rColor });
|
|
16323
|
+
out.push({ type: "line", x1: g.rx, y1: bottomY, x2: g.rx, y2: stubBottomY, color: rColor });
|
|
16324
|
+
if (g.rung.a) {
|
|
16325
|
+
out.push({ type: "text", x: g.rx, y: stubTopY + 6, text: g.rung.a, fontSize: 9, align: "center", color: "#374151" });
|
|
16326
|
+
}
|
|
16327
|
+
if (g.rung.b) {
|
|
16328
|
+
out.push({ type: "text", x: g.rx, y: stubBottomY - 6, text: g.rung.b, fontSize: 9, align: "center", color: "#374151" });
|
|
16329
|
+
}
|
|
16330
|
+
}
|
|
16331
|
+
}
|
|
16332
|
+
}
|
|
15029
16333
|
for (const e of edges) {
|
|
15030
16334
|
const a = nodeById.get(e.from);
|
|
15031
16335
|
const b = nodeById.get(e.to);
|
|
15032
16336
|
if (!a || !b) continue;
|
|
15033
|
-
|
|
15034
|
-
|
|
15035
|
-
|
|
15036
|
-
|
|
15037
|
-
|
|
15038
|
-
|
|
15039
|
-
|
|
15040
|
-
|
|
15041
|
-
|
|
16337
|
+
const color = e.color ?? "#9ca3af";
|
|
16338
|
+
if (e.directed) {
|
|
16339
|
+
const rA = a.radius ?? 16;
|
|
16340
|
+
const rB = b.radius ?? 16;
|
|
16341
|
+
const dx = b.x - a.x;
|
|
16342
|
+
const dy = b.y - a.y;
|
|
16343
|
+
const dist = Math.max(1e-6, Math.hypot(dx, dy));
|
|
16344
|
+
const ux = dx / dist;
|
|
16345
|
+
const uy = dy / dist;
|
|
16346
|
+
out.push({
|
|
16347
|
+
type: "arrow",
|
|
16348
|
+
x1: a.x + ux * rA,
|
|
16349
|
+
y1: a.y + uy * rA,
|
|
16350
|
+
x2: b.x - ux * rB,
|
|
16351
|
+
y2: b.y - uy * rB,
|
|
16352
|
+
color,
|
|
16353
|
+
lineWidth: 2
|
|
16354
|
+
});
|
|
16355
|
+
} else {
|
|
16356
|
+
out.push({
|
|
16357
|
+
type: "line",
|
|
16358
|
+
x1: a.x,
|
|
16359
|
+
y1: a.y,
|
|
16360
|
+
x2: b.x,
|
|
16361
|
+
y2: b.y,
|
|
16362
|
+
color,
|
|
16363
|
+
lineWidth: 2
|
|
16364
|
+
});
|
|
16365
|
+
}
|
|
15042
16366
|
if (e.label) {
|
|
15043
16367
|
out.push({
|
|
15044
16368
|
type: "text",
|
|
@@ -15051,6 +16375,8 @@ var init_BiologyCanvas = __esm({
|
|
|
15051
16375
|
}
|
|
15052
16376
|
}
|
|
15053
16377
|
for (const n of nodes) {
|
|
16378
|
+
const state = n.state ?? "default";
|
|
16379
|
+
const muted = state === "muted";
|
|
15054
16380
|
out.push({
|
|
15055
16381
|
type: "circle",
|
|
15056
16382
|
x: n.x,
|
|
@@ -15058,28 +16384,127 @@ var init_BiologyCanvas = __esm({
|
|
|
15058
16384
|
radius: n.radius ?? 16,
|
|
15059
16385
|
color: n.color ?? "#16a34a",
|
|
15060
16386
|
fill: `${n.color ?? "#16a34a"}33`,
|
|
15061
|
-
id: n.id
|
|
16387
|
+
id: n.id,
|
|
16388
|
+
...muted ? { opacity: 0.35 } : {}
|
|
15062
16389
|
});
|
|
16390
|
+
if (state === "highlight") {
|
|
16391
|
+
out.push({
|
|
16392
|
+
type: "circle",
|
|
16393
|
+
x: n.x,
|
|
16394
|
+
y: n.y,
|
|
16395
|
+
radius: (n.radius ?? 16) + 4,
|
|
16396
|
+
color: "#f59e0b",
|
|
16397
|
+
lineWidth: 2
|
|
16398
|
+
});
|
|
16399
|
+
}
|
|
15063
16400
|
if (n.label) {
|
|
15064
16401
|
out.push({
|
|
15065
16402
|
type: "text",
|
|
15066
16403
|
x: n.x,
|
|
15067
16404
|
y: n.y + (n.radius ?? 16) + 14,
|
|
15068
16405
|
text: n.label,
|
|
16406
|
+
...muted ? { opacity: 0.35 } : {},
|
|
15069
16407
|
color: "#111827",
|
|
15070
16408
|
fontSize: 12,
|
|
15071
16409
|
align: "center"
|
|
15072
16410
|
});
|
|
15073
16411
|
}
|
|
15074
16412
|
}
|
|
16413
|
+
const stageCount = stages.length;
|
|
16414
|
+
if (stageCount > 0) {
|
|
16415
|
+
if (stageStyle === "ring") {
|
|
16416
|
+
const cx = width / 2;
|
|
16417
|
+
const cy = height / 2;
|
|
16418
|
+
const R = Math.min(width, height) / 2 - 48;
|
|
16419
|
+
const ringPoints = [];
|
|
16420
|
+
for (let i = 0; i < stageCount; i++) {
|
|
16421
|
+
const angleRad = (-90 + 360 * i / stageCount) * Math.PI / 180;
|
|
16422
|
+
ringPoints.push({ x: cx + R * Math.cos(angleRad), y: cy + R * Math.sin(angleRad) });
|
|
16423
|
+
}
|
|
16424
|
+
if (stageCount >= 2) {
|
|
16425
|
+
for (let i = 0; i < stageCount - 1; i++) {
|
|
16426
|
+
const p1 = ringPoints[i];
|
|
16427
|
+
const p2 = ringPoints[i + 1];
|
|
16428
|
+
const dx = p2.x - p1.x;
|
|
16429
|
+
const dy = p2.y - p1.y;
|
|
16430
|
+
const dist = Math.max(1e-6, Math.hypot(dx, dy));
|
|
16431
|
+
const ux = dx / dist;
|
|
16432
|
+
const uy = dy / dist;
|
|
16433
|
+
out.push({
|
|
16434
|
+
type: "arrow",
|
|
16435
|
+
x1: p1.x + ux * 46,
|
|
16436
|
+
y1: p1.y + uy * 46,
|
|
16437
|
+
x2: p2.x - ux * 46,
|
|
16438
|
+
y2: p2.y - uy * 46,
|
|
16439
|
+
color: "#94a3b8"
|
|
16440
|
+
});
|
|
16441
|
+
}
|
|
16442
|
+
}
|
|
16443
|
+
for (let i = 0; i < stageCount; i++) {
|
|
16444
|
+
const stage = stages[i];
|
|
16445
|
+
const state = stage.state ?? "pending";
|
|
16446
|
+
const fill = stage.color ?? BIO_STAGE_FILL[state];
|
|
16447
|
+
const w = Math.max(26, Math.min(84, stage.label.length * 6 + 10));
|
|
16448
|
+
const h = 18;
|
|
16449
|
+
const p = ringPoints[i];
|
|
16450
|
+
out.push({ type: "rect", x: p.x - w / 2, y: p.y - h / 2, width: w, height: h, color: fill, fill });
|
|
16451
|
+
out.push({ type: "text", x: p.x, y: p.y, text: stage.label, color: BIO_STAGE_TEXT[state], fontSize: 10, align: "center" });
|
|
16452
|
+
}
|
|
16453
|
+
} else {
|
|
16454
|
+
const stripY = height - 32;
|
|
16455
|
+
const slotW = (width - 16) / stageCount;
|
|
16456
|
+
const chipGeoms = [];
|
|
16457
|
+
for (let i = 0; i < stageCount; i++) {
|
|
16458
|
+
chipGeoms.push({ x: 8 + i * slotW + 5, w: slotW - 10 });
|
|
16459
|
+
}
|
|
16460
|
+
for (let i = 0; i < stageCount - 1; i++) {
|
|
16461
|
+
const midY = stripY + 13;
|
|
16462
|
+
out.push({
|
|
16463
|
+
type: "arrow",
|
|
16464
|
+
x1: chipGeoms[i].x + chipGeoms[i].w,
|
|
16465
|
+
y1: midY,
|
|
16466
|
+
x2: chipGeoms[i + 1].x,
|
|
16467
|
+
y2: midY,
|
|
16468
|
+
color: "#94a3b8"
|
|
16469
|
+
});
|
|
16470
|
+
}
|
|
16471
|
+
for (let i = 0; i < stageCount; i++) {
|
|
16472
|
+
const stage = stages[i];
|
|
16473
|
+
const state = stage.state ?? "pending";
|
|
16474
|
+
const fill = stage.color ?? BIO_STAGE_FILL[state];
|
|
16475
|
+
const g = chipGeoms[i];
|
|
16476
|
+
out.push({ type: "rect", x: g.x, y: stripY, width: g.w, height: 26, color: fill, fill });
|
|
16477
|
+
out.push({
|
|
16478
|
+
type: "text",
|
|
16479
|
+
x: g.x + g.w / 2,
|
|
16480
|
+
y: stripY + 13,
|
|
16481
|
+
text: stage.label,
|
|
16482
|
+
color: BIO_STAGE_TEXT[state],
|
|
16483
|
+
fontSize: 10,
|
|
16484
|
+
align: "center"
|
|
16485
|
+
});
|
|
16486
|
+
}
|
|
16487
|
+
}
|
|
16488
|
+
}
|
|
15075
16489
|
out.push(...shapes);
|
|
15076
16490
|
return out;
|
|
15077
|
-
}, [nodes, edges, shapes]);
|
|
16491
|
+
}, [nodes, edges, compartments, bands, stages, stageStyle, helix, shapes, width, height]);
|
|
15078
16492
|
const drawables3D = useMemo(() => {
|
|
15079
16493
|
if (mode !== "3d") return [];
|
|
15080
16494
|
if (shapes.length > 0) {
|
|
15081
16495
|
biologyLog.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
|
|
15082
16496
|
}
|
|
16497
|
+
if (compartments.length > 0 || bands.length > 0 || stages.length > 0 || helix) {
|
|
16498
|
+
biologyLog.debug("2D-only families ignored in 3D mode (pixel-authored 2D vocabulary)", {
|
|
16499
|
+
compartments: compartments.length,
|
|
16500
|
+
bands: bands.length,
|
|
16501
|
+
stages: stages.length,
|
|
16502
|
+
helix: helix != null
|
|
16503
|
+
});
|
|
16504
|
+
}
|
|
16505
|
+
if (animate) {
|
|
16506
|
+
biologyLog.debug("animate ignored in 3D mode (motion is entity-state driven)");
|
|
16507
|
+
}
|
|
15083
16508
|
const out = [];
|
|
15084
16509
|
const labelColor = labelColorForBackground(backgroundColor);
|
|
15085
16510
|
const nodeById = /* @__PURE__ */ new Map();
|
|
@@ -15113,15 +16538,21 @@ var init_BiologyCanvas = __esm({
|
|
|
15113
16538
|
out.push(billboardLabel(n.label, n.x, n.y, nz + radius, { color: labelColor }));
|
|
15114
16539
|
}
|
|
15115
16540
|
}
|
|
16541
|
+
if (helix3d) {
|
|
16542
|
+
out.push(...helixDrawables(helix3d, { labelColor }));
|
|
16543
|
+
}
|
|
15116
16544
|
return out;
|
|
15117
|
-
}, [mode, nodes, edges, shapes, backgroundColor]);
|
|
16545
|
+
}, [mode, nodes, edges, shapes, compartments, bands, stages, helix, helix3d, animate, backgroundColor]);
|
|
15118
16546
|
const nodeIndexById = useMemo(() => {
|
|
15119
16547
|
const m = /* @__PURE__ */ new Map();
|
|
16548
|
+
(helix3d?.rungs ?? []).forEach((rung, i) => {
|
|
16549
|
+
if (rung.id) m.set(rung.id, i);
|
|
16550
|
+
});
|
|
15120
16551
|
nodes.forEach((n, i) => {
|
|
15121
16552
|
if (n.id) m.set(n.id, i);
|
|
15122
16553
|
});
|
|
15123
16554
|
return m;
|
|
15124
|
-
}, [nodes]);
|
|
16555
|
+
}, [nodes, helix3d]);
|
|
15125
16556
|
if (mode === "3d") {
|
|
15126
16557
|
return /* @__PURE__ */ jsx(
|
|
15127
16558
|
LearningScene3D,
|
|
@@ -15153,6 +16584,8 @@ var init_BiologyCanvas = __esm({
|
|
|
15153
16584
|
height,
|
|
15154
16585
|
backgroundColor,
|
|
15155
16586
|
shapes: derivedShapes,
|
|
16587
|
+
readouts,
|
|
16588
|
+
traces,
|
|
15156
16589
|
interactive: interactive ?? false,
|
|
15157
16590
|
animate,
|
|
15158
16591
|
onShapeClick,
|
|
@@ -22002,7 +23435,7 @@ function bondPerpendicular(a, b) {
|
|
|
22002
23435
|
if (len < 1e-6) return [1, 0, 0];
|
|
22003
23436
|
return [px / len, py / len, 0];
|
|
22004
23437
|
}
|
|
22005
|
-
var chemistryLog, ChemistryCanvas;
|
|
23438
|
+
var chemistryLog, CHEM_BOND_STATE_COLOR, LONE_PAIR_ANGLES, ChemistryCanvas;
|
|
22006
23439
|
var init_ChemistryCanvas = __esm({
|
|
22007
23440
|
"components/learning/molecules/ChemistryCanvas.tsx"() {
|
|
22008
23441
|
"use client";
|
|
@@ -22011,6 +23444,13 @@ var init_ChemistryCanvas = __esm({
|
|
|
22011
23444
|
init_LearningCanvas();
|
|
22012
23445
|
init_learningScene3D();
|
|
22013
23446
|
chemistryLog = createLogger("almadar:ui:chemistry-canvas");
|
|
23447
|
+
CHEM_BOND_STATE_COLOR = {
|
|
23448
|
+
default: "#6b7280",
|
|
23449
|
+
forming: "#16a34a",
|
|
23450
|
+
breaking: "#dc2626",
|
|
23451
|
+
highlight: "#f59e0b"
|
|
23452
|
+
};
|
|
23453
|
+
LONE_PAIR_ANGLES = [-90, 0, 90, 180];
|
|
22014
23454
|
ChemistryCanvas = ({
|
|
22015
23455
|
className,
|
|
22016
23456
|
width = 600,
|
|
@@ -22024,7 +23464,14 @@ var init_ChemistryCanvas = __esm({
|
|
|
22024
23464
|
atoms = [],
|
|
22025
23465
|
bonds = [],
|
|
22026
23466
|
arrows = [],
|
|
23467
|
+
bondStyle = "thick",
|
|
23468
|
+
containers = [],
|
|
23469
|
+
equation,
|
|
23470
|
+
equationColor,
|
|
23471
|
+
lattice3d,
|
|
22027
23472
|
shapes = [],
|
|
23473
|
+
readouts,
|
|
23474
|
+
traces,
|
|
22028
23475
|
showGrid,
|
|
22029
23476
|
shadows,
|
|
22030
23477
|
interactive,
|
|
@@ -22039,21 +23486,118 @@ var init_ChemistryCanvas = __esm({
|
|
|
22039
23486
|
for (const a of atoms) {
|
|
22040
23487
|
if (a.id) atomById.set(a.id, a);
|
|
22041
23488
|
}
|
|
23489
|
+
for (const c of containers) {
|
|
23490
|
+
const color = c.color ?? "#64748b";
|
|
23491
|
+
if (c.level != null) {
|
|
23492
|
+
const lv = c.level;
|
|
23493
|
+
out.push({
|
|
23494
|
+
type: "rect",
|
|
23495
|
+
x: c.x + 1,
|
|
23496
|
+
y: c.y + c.height * (1 - lv),
|
|
23497
|
+
width: c.width - 2,
|
|
23498
|
+
height: c.height * lv - 1,
|
|
23499
|
+
color: c.levelColor ?? "#60a5fa",
|
|
23500
|
+
fill: c.levelColor ?? "#60a5fa",
|
|
23501
|
+
opacity: 0.5
|
|
23502
|
+
});
|
|
23503
|
+
}
|
|
23504
|
+
out.push({
|
|
23505
|
+
type: "rect",
|
|
23506
|
+
x: c.x,
|
|
23507
|
+
y: c.y,
|
|
23508
|
+
width: c.width,
|
|
23509
|
+
height: c.height,
|
|
23510
|
+
color,
|
|
23511
|
+
fill: c.fill,
|
|
23512
|
+
lineWidth: c.lineWidth ?? 2
|
|
23513
|
+
});
|
|
23514
|
+
const divider = c.divider ?? "none";
|
|
23515
|
+
if (divider !== "none") {
|
|
23516
|
+
out.push({
|
|
23517
|
+
type: "line",
|
|
23518
|
+
x1: c.x + c.width / 2,
|
|
23519
|
+
y1: c.y,
|
|
23520
|
+
x2: c.x + c.width / 2,
|
|
23521
|
+
y2: c.y + c.height,
|
|
23522
|
+
color: c.dividerColor ?? color,
|
|
23523
|
+
...divider === "dashed" || divider === "dotted" ? { dash: divider } : {}
|
|
23524
|
+
});
|
|
23525
|
+
}
|
|
23526
|
+
if (c.leftLabel) {
|
|
23527
|
+
out.push({
|
|
23528
|
+
type: "text",
|
|
23529
|
+
x: c.x + c.width * 0.25,
|
|
23530
|
+
y: c.y + 12,
|
|
23531
|
+
text: c.leftLabel,
|
|
23532
|
+
color: "#374151",
|
|
23533
|
+
fontSize: 11,
|
|
23534
|
+
align: "center"
|
|
23535
|
+
});
|
|
23536
|
+
}
|
|
23537
|
+
if (c.rightLabel) {
|
|
23538
|
+
out.push({
|
|
23539
|
+
type: "text",
|
|
23540
|
+
x: c.x + c.width * 0.75,
|
|
23541
|
+
y: c.y + 12,
|
|
23542
|
+
text: c.rightLabel,
|
|
23543
|
+
color: "#374151",
|
|
23544
|
+
fontSize: 11,
|
|
23545
|
+
align: "center"
|
|
23546
|
+
});
|
|
23547
|
+
}
|
|
23548
|
+
if (c.label) {
|
|
23549
|
+
out.push({
|
|
23550
|
+
type: "text",
|
|
23551
|
+
x: c.x + c.width / 2,
|
|
23552
|
+
y: c.y + c.height + 12,
|
|
23553
|
+
text: c.label,
|
|
23554
|
+
color: "#111827",
|
|
23555
|
+
fontSize: 12,
|
|
23556
|
+
align: "center"
|
|
23557
|
+
});
|
|
23558
|
+
}
|
|
23559
|
+
}
|
|
22042
23560
|
for (const b of bonds) {
|
|
22043
23561
|
const a = atomById.get(b.from);
|
|
22044
23562
|
const c = atomById.get(b.to);
|
|
22045
23563
|
if (!a || !c) continue;
|
|
22046
|
-
const
|
|
22047
|
-
const
|
|
22048
|
-
|
|
22049
|
-
|
|
22050
|
-
|
|
22051
|
-
|
|
22052
|
-
|
|
22053
|
-
|
|
22054
|
-
|
|
22055
|
-
|
|
22056
|
-
|
|
23564
|
+
const state = b.state ?? "default";
|
|
23565
|
+
const color = b.color ?? CHEM_BOND_STATE_COLOR[state];
|
|
23566
|
+
const dash = state === "forming" || state === "breaking" ? "dashed" : void 0;
|
|
23567
|
+
if (bondStyle === "parallel") {
|
|
23568
|
+
const dx = c.x - a.x;
|
|
23569
|
+
const dy = c.y - a.y;
|
|
23570
|
+
const dist = Math.max(1e-6, Math.hypot(dx, dy));
|
|
23571
|
+
const ux = dx / dist;
|
|
23572
|
+
const uy = dy / dist;
|
|
23573
|
+
const px = -uy;
|
|
23574
|
+
const py = ux;
|
|
23575
|
+
const offsets = b.type === "double" ? [-3, 3] : b.type === "triple" ? [-4, 0, 4] : [0];
|
|
23576
|
+
for (const off of offsets) {
|
|
23577
|
+
out.push({
|
|
23578
|
+
type: "line",
|
|
23579
|
+
x1: a.x + px * off,
|
|
23580
|
+
y1: a.y + py * off,
|
|
23581
|
+
x2: c.x + px * off,
|
|
23582
|
+
y2: c.y + py * off,
|
|
23583
|
+
color,
|
|
23584
|
+
lineWidth: 2,
|
|
23585
|
+
...dash ? { dash } : {}
|
|
23586
|
+
});
|
|
23587
|
+
}
|
|
23588
|
+
} else {
|
|
23589
|
+
const strokeWidth = b.type === "double" ? 4 : b.type === "triple" ? 6 : 2;
|
|
23590
|
+
out.push({
|
|
23591
|
+
type: "line",
|
|
23592
|
+
x1: a.x,
|
|
23593
|
+
y1: a.y,
|
|
23594
|
+
x2: c.x,
|
|
23595
|
+
y2: c.y,
|
|
23596
|
+
color,
|
|
23597
|
+
lineWidth: strokeWidth,
|
|
23598
|
+
...dash ? { dash } : {}
|
|
23599
|
+
});
|
|
23600
|
+
}
|
|
22057
23601
|
}
|
|
22058
23602
|
for (const a of arrows) {
|
|
22059
23603
|
const angle = (a.angle ?? 0) * (Math.PI / 180);
|
|
@@ -22102,15 +23646,62 @@ var init_ChemistryCanvas = __esm({
|
|
|
22102
23646
|
align: "center"
|
|
22103
23647
|
});
|
|
22104
23648
|
}
|
|
23649
|
+
const r = a.radius ?? 14;
|
|
23650
|
+
if (a.charge) {
|
|
23651
|
+
out.push({
|
|
23652
|
+
type: "text",
|
|
23653
|
+
x: a.x + r * 0.85,
|
|
23654
|
+
y: a.y - r * 0.85,
|
|
23655
|
+
text: a.charge,
|
|
23656
|
+
color: "#111827",
|
|
23657
|
+
fontSize: 9,
|
|
23658
|
+
align: "left"
|
|
23659
|
+
});
|
|
23660
|
+
}
|
|
23661
|
+
const lonePairs = Math.max(0, Math.min(4, a.lonePairs ?? 0));
|
|
23662
|
+
for (let k = 0; k < lonePairs; k++) {
|
|
23663
|
+
const angleRad = LONE_PAIR_ANGLES[k] * Math.PI / 180;
|
|
23664
|
+
const cx = a.x + (r + 6) * Math.cos(angleRad);
|
|
23665
|
+
const cy = a.y + (r + 6) * Math.sin(angleRad);
|
|
23666
|
+
const perpX = -Math.sin(angleRad);
|
|
23667
|
+
const perpY = Math.cos(angleRad);
|
|
23668
|
+
for (const sign of [1, -1]) {
|
|
23669
|
+
out.push({
|
|
23670
|
+
type: "circle",
|
|
23671
|
+
x: cx + perpX * 2.5 * sign,
|
|
23672
|
+
y: cy + perpY * 2.5 * sign,
|
|
23673
|
+
radius: 1.5,
|
|
23674
|
+
color: "#374151",
|
|
23675
|
+
fill: "#374151"
|
|
23676
|
+
});
|
|
23677
|
+
}
|
|
23678
|
+
}
|
|
23679
|
+
}
|
|
23680
|
+
if (equation) {
|
|
23681
|
+
out.push({
|
|
23682
|
+
type: "text",
|
|
23683
|
+
x: width / 2,
|
|
23684
|
+
y: 14,
|
|
23685
|
+
text: equation,
|
|
23686
|
+
color: equationColor ?? "#111827",
|
|
23687
|
+
fontSize: 13,
|
|
23688
|
+
align: "center"
|
|
23689
|
+
});
|
|
22105
23690
|
}
|
|
22106
23691
|
out.push(...shapes);
|
|
22107
23692
|
return out;
|
|
22108
|
-
}, [atoms, bonds, arrows, shapes]);
|
|
23693
|
+
}, [atoms, bonds, arrows, bondStyle, containers, equation, equationColor, shapes, width]);
|
|
22109
23694
|
const drawables3D = useMemo(() => {
|
|
22110
23695
|
if (mode !== "3d") return [];
|
|
22111
23696
|
if (shapes.length > 0) {
|
|
22112
23697
|
chemistryLog.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
|
|
22113
23698
|
}
|
|
23699
|
+
if (containers.length > 0) {
|
|
23700
|
+
chemistryLog.debug("containers ignored in 3D mode (pixel-authored 2D vocabulary)", { count: containers.length });
|
|
23701
|
+
}
|
|
23702
|
+
if (animate) {
|
|
23703
|
+
chemistryLog.debug("animate ignored in 3D mode (motion is entity-state driven)");
|
|
23704
|
+
}
|
|
22114
23705
|
const out = [];
|
|
22115
23706
|
const labelColor = labelColorForBackground(backgroundColor);
|
|
22116
23707
|
const atomById = /* @__PURE__ */ new Map();
|
|
@@ -22157,8 +23748,11 @@ var init_ChemistryCanvas = __esm({
|
|
|
22157
23748
|
out.push(billboardLabel(a.element, a.x, a.y, az + radius, { color: labelColor }));
|
|
22158
23749
|
}
|
|
22159
23750
|
}
|
|
23751
|
+
if (lattice3d) {
|
|
23752
|
+
out.push(...latticeDrawables(lattice3d, { labelColor }));
|
|
23753
|
+
}
|
|
22160
23754
|
return out;
|
|
22161
|
-
}, [mode, atoms, bonds, arrows, shapes, backgroundColor]);
|
|
23755
|
+
}, [mode, atoms, bonds, arrows, shapes, containers, lattice3d, animate, backgroundColor]);
|
|
22162
23756
|
const atomIndexById = useMemo(() => {
|
|
22163
23757
|
const m = /* @__PURE__ */ new Map();
|
|
22164
23758
|
atoms.forEach((a, i) => {
|
|
@@ -22197,6 +23791,8 @@ var init_ChemistryCanvas = __esm({
|
|
|
22197
23791
|
height,
|
|
22198
23792
|
backgroundColor,
|
|
22199
23793
|
shapes: derivedShapes,
|
|
23794
|
+
readouts,
|
|
23795
|
+
traces,
|
|
22200
23796
|
interactive: interactive ?? false,
|
|
22201
23797
|
animate,
|
|
22202
23798
|
onShapeClick,
|
|
@@ -28386,6 +29982,10 @@ var init_ProgressDots = __esm({
|
|
|
28386
29982
|
ProgressDots.displayName = "ProgressDots";
|
|
28387
29983
|
}
|
|
28388
29984
|
});
|
|
29985
|
+
function formatTick(v) {
|
|
29986
|
+
if (Number.isInteger(v)) return String(v);
|
|
29987
|
+
return v.toFixed(2).replace(/0+$/, "").replace(/\.$/, "");
|
|
29988
|
+
}
|
|
28389
29989
|
var MathCanvas;
|
|
28390
29990
|
var init_MathCanvas = __esm({
|
|
28391
29991
|
"components/learning/molecules/MathCanvas.tsx"() {
|
|
@@ -28405,10 +30005,19 @@ var init_MathCanvas = __esm({
|
|
|
28405
30005
|
showAxes = true,
|
|
28406
30006
|
showGrid = true,
|
|
28407
30007
|
gridStep = 1,
|
|
30008
|
+
showTickLabels = false,
|
|
30009
|
+
showCurveLabels = false,
|
|
28408
30010
|
curves = [],
|
|
28409
30011
|
points = [],
|
|
28410
30012
|
vectors = [],
|
|
30013
|
+
regions = [],
|
|
30014
|
+
bars = [],
|
|
30015
|
+
guides = [],
|
|
30016
|
+
angles = [],
|
|
30017
|
+
hops = [],
|
|
28411
30018
|
shapes = [],
|
|
30019
|
+
readouts,
|
|
30020
|
+
traces,
|
|
28412
30021
|
interactive = false,
|
|
28413
30022
|
animate = false,
|
|
28414
30023
|
onShapeClick,
|
|
@@ -28422,6 +30031,8 @@ var init_MathCanvas = __esm({
|
|
|
28422
30031
|
const plotH = height - margin * 2;
|
|
28423
30032
|
const mapX = (x) => margin + (x - xMin) / (xMax - xMin) * plotW;
|
|
28424
30033
|
const mapY = (y) => height - (margin + (y - yMin) / (yMax - yMin) * plotH);
|
|
30034
|
+
const xAxisY = Math.max(margin, Math.min(height - margin, mapY(0)));
|
|
30035
|
+
const yAxisX = Math.max(margin, Math.min(width - margin, mapX(0)));
|
|
28425
30036
|
if (showGrid) {
|
|
28426
30037
|
for (let x = Math.ceil(xMin / gridStep) * gridStep; x <= xMax; x += gridStep) {
|
|
28427
30038
|
const px = mapX(x);
|
|
@@ -28432,14 +30043,99 @@ var init_MathCanvas = __esm({
|
|
|
28432
30043
|
out.push({ type: "line", x1: margin, y1: py, x2: width - margin, y2: py, color: "#e5e7eb", lineWidth: 1 });
|
|
28433
30044
|
}
|
|
28434
30045
|
}
|
|
30046
|
+
if (showTickLabels) {
|
|
30047
|
+
const labelEveryX = Math.max(1, Math.ceil((xMax - xMin) / gridStep / Math.floor(plotW / 40)));
|
|
30048
|
+
let kx = 0;
|
|
30049
|
+
for (let x = Math.ceil(xMin / gridStep) * gridStep; x <= xMax; x += gridStep, kx++) {
|
|
30050
|
+
if (kx % labelEveryX === 0 && x !== 0) {
|
|
30051
|
+
out.push({ type: "text", x: mapX(x), y: xAxisY + 12, text: formatTick(x), color: "#6b7280", fontSize: 10, align: "center" });
|
|
30052
|
+
}
|
|
30053
|
+
}
|
|
30054
|
+
const labelEveryY = Math.max(1, Math.ceil((yMax - yMin) / gridStep / Math.floor(plotH / 28)));
|
|
30055
|
+
let ky = 0;
|
|
30056
|
+
for (let y = Math.ceil(yMin / gridStep) * gridStep; y <= yMax; y += gridStep, ky++) {
|
|
30057
|
+
if (ky % labelEveryY === 0 && y !== 0) {
|
|
30058
|
+
out.push({ type: "text", x: yAxisX - 6, y: mapY(y), text: formatTick(y), color: "#6b7280", fontSize: 10, align: "right" });
|
|
30059
|
+
}
|
|
30060
|
+
}
|
|
30061
|
+
if (xMin <= 0 && xMax >= 0 && yMin <= 0 && yMax >= 0) {
|
|
30062
|
+
out.push({ type: "text", x: yAxisX - 6, y: xAxisY + 12, text: "0", color: "#6b7280", fontSize: 10, align: "right" });
|
|
30063
|
+
}
|
|
30064
|
+
}
|
|
30065
|
+
for (const region of regions) {
|
|
30066
|
+
if (!region.samples || region.samples.length === 0) continue;
|
|
30067
|
+
const baseline = region.baseline ?? 0;
|
|
30068
|
+
const clampedPoint = (p) => ({
|
|
30069
|
+
x: mapX(Math.min(xMax, Math.max(xMin, p.x))),
|
|
30070
|
+
y: mapY(Math.min(yMax, Math.max(yMin, p.y)))
|
|
30071
|
+
});
|
|
30072
|
+
const upper = region.samples.map(clampedPoint);
|
|
30073
|
+
const first = region.samples[0];
|
|
30074
|
+
const last = region.samples[region.samples.length - 1];
|
|
30075
|
+
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 })];
|
|
30076
|
+
const color = region.color ?? "#2563eb";
|
|
30077
|
+
out.push({
|
|
30078
|
+
type: "polygon",
|
|
30079
|
+
points: [...upper, ...closing],
|
|
30080
|
+
fill: color,
|
|
30081
|
+
color,
|
|
30082
|
+
opacity: region.opacity ?? 0.2,
|
|
30083
|
+
lineWidth: 1
|
|
30084
|
+
});
|
|
30085
|
+
if (region.label) {
|
|
30086
|
+
const mid = Math.floor(region.samples.length / 2);
|
|
30087
|
+
out.push({
|
|
30088
|
+
type: "text",
|
|
30089
|
+
x: mapX((first.x + last.x) / 2),
|
|
30090
|
+
y: (mapY(region.samples[mid].y) + mapY(baseline)) / 2,
|
|
30091
|
+
text: region.label,
|
|
30092
|
+
color: "#111827",
|
|
30093
|
+
fontSize: 11
|
|
30094
|
+
});
|
|
30095
|
+
}
|
|
30096
|
+
}
|
|
30097
|
+
for (const bar of bars) {
|
|
30098
|
+
if (bar.x + bar.width < xMin || bar.x > xMax) continue;
|
|
30099
|
+
const y0 = bar.y0 ?? 0;
|
|
30100
|
+
const color = bar.color ?? "#93c5fd";
|
|
30101
|
+
out.push({
|
|
30102
|
+
type: "rect",
|
|
30103
|
+
x: mapX(bar.x),
|
|
30104
|
+
y: mapY(Math.max(y0, bar.y1)),
|
|
30105
|
+
width: mapX(bar.x + bar.width) - mapX(bar.x),
|
|
30106
|
+
height: Math.abs(mapY(bar.y1) - mapY(y0)),
|
|
30107
|
+
color,
|
|
30108
|
+
fill: color,
|
|
30109
|
+
opacity: bar.opacity ?? 0.5,
|
|
30110
|
+
lineWidth: 1
|
|
30111
|
+
});
|
|
30112
|
+
}
|
|
28435
30113
|
if (showAxes) {
|
|
28436
|
-
const xAxisY = Math.max(margin, Math.min(height - margin, mapY(0)));
|
|
28437
|
-
const yAxisX = Math.max(margin, Math.min(width - margin, mapX(0)));
|
|
28438
30114
|
out.push({ type: "line", x1: margin, y1: xAxisY, x2: width - margin, y2: xAxisY, color: "#374151", lineWidth: 2 });
|
|
28439
30115
|
out.push({ type: "line", x1: yAxisX, y1: margin, x2: yAxisX, y2: height - margin, color: "#374151", lineWidth: 2 });
|
|
28440
30116
|
}
|
|
30117
|
+
for (const guide of guides) {
|
|
30118
|
+
const color = guide.color ?? "#9ca3af";
|
|
30119
|
+
const dash = guide.dash ?? "dashed";
|
|
30120
|
+
if (guide.kind === "vline") {
|
|
30121
|
+
if (guide.at < xMin || guide.at > xMax) continue;
|
|
30122
|
+
const px = mapX(guide.at);
|
|
30123
|
+
out.push({ type: "line", x1: px, y1: margin, x2: px, y2: height - margin, color, dash });
|
|
30124
|
+
if (guide.label) {
|
|
30125
|
+
out.push({ type: "text", x: px + 4, y: margin + 10, text: guide.label, color: "#111827", fontSize: 11 });
|
|
30126
|
+
}
|
|
30127
|
+
} else {
|
|
30128
|
+
if (guide.at < yMin || guide.at > yMax) continue;
|
|
30129
|
+
const py = mapY(guide.at);
|
|
30130
|
+
out.push({ type: "line", x1: margin, y1: py, x2: width - margin, y2: py, color, dash });
|
|
30131
|
+
if (guide.label) {
|
|
30132
|
+
out.push({ type: "text", x: width - margin - 4, y: py - 8, text: guide.label, color: "#111827", fontSize: 11, align: "right" });
|
|
30133
|
+
}
|
|
30134
|
+
}
|
|
30135
|
+
}
|
|
28441
30136
|
for (const curve of curves) {
|
|
28442
30137
|
if (!curve.samples || curve.samples.length < 2) continue;
|
|
30138
|
+
let lastInRange;
|
|
28443
30139
|
for (let i = 1; i < curve.samples.length; i++) {
|
|
28444
30140
|
const a = curve.samples[i - 1];
|
|
28445
30141
|
const b = curve.samples[i];
|
|
@@ -28451,19 +30147,97 @@ var init_MathCanvas = __esm({
|
|
|
28451
30147
|
x2: mapX(b.x),
|
|
28452
30148
|
y2: mapY(b.y),
|
|
28453
30149
|
color: curve.color ?? "#2563eb",
|
|
28454
|
-
lineWidth: 2
|
|
30150
|
+
lineWidth: 2,
|
|
30151
|
+
dash: curve.dash
|
|
30152
|
+
});
|
|
30153
|
+
lastInRange = b;
|
|
30154
|
+
}
|
|
30155
|
+
if (showCurveLabels && curve.label && lastInRange) {
|
|
30156
|
+
out.push({
|
|
30157
|
+
type: "text",
|
|
30158
|
+
x: mapX(lastInRange.x) + 6,
|
|
30159
|
+
y: mapY(lastInRange.y) - 6,
|
|
30160
|
+
text: curve.label,
|
|
30161
|
+
color: curve.color ?? "#2563eb",
|
|
30162
|
+
fontSize: 11
|
|
30163
|
+
});
|
|
30164
|
+
}
|
|
30165
|
+
}
|
|
30166
|
+
for (const hop of hops) {
|
|
30167
|
+
const x1 = mapX(hop.from);
|
|
30168
|
+
const x2 = mapX(hop.to);
|
|
30169
|
+
const peak = Math.min(36, plotH * 0.3);
|
|
30170
|
+
const color = hop.color ?? "#7c3aed";
|
|
30171
|
+
out.push({
|
|
30172
|
+
type: "ellipse",
|
|
30173
|
+
x: (x1 + x2) / 2,
|
|
30174
|
+
y: xAxisY,
|
|
30175
|
+
width: Math.abs(x2 - x1),
|
|
30176
|
+
height: 2 * peak,
|
|
30177
|
+
startAngle: 180,
|
|
30178
|
+
endAngle: 360,
|
|
30179
|
+
color
|
|
30180
|
+
});
|
|
30181
|
+
const s = Math.sign(hop.to - hop.from);
|
|
30182
|
+
out.push({
|
|
30183
|
+
type: "polygon",
|
|
30184
|
+
points: [
|
|
30185
|
+
{ x: x2, y: xAxisY },
|
|
30186
|
+
{ x: x2 - 4 * s, y: xAxisY - 7 },
|
|
30187
|
+
{ x: x2 + 2 * s, y: xAxisY - 7 }
|
|
30188
|
+
],
|
|
30189
|
+
fill: color,
|
|
30190
|
+
color
|
|
30191
|
+
});
|
|
30192
|
+
if (hop.label) {
|
|
30193
|
+
out.push({
|
|
30194
|
+
type: "text",
|
|
30195
|
+
x: (x1 + x2) / 2,
|
|
30196
|
+
y: xAxisY - peak - 8,
|
|
30197
|
+
text: hop.label,
|
|
30198
|
+
color: "#111827",
|
|
30199
|
+
fontSize: 10,
|
|
30200
|
+
align: "center"
|
|
30201
|
+
});
|
|
30202
|
+
}
|
|
30203
|
+
}
|
|
30204
|
+
for (const angle of angles) {
|
|
30205
|
+
const radius = angle.radius ?? 0.8;
|
|
30206
|
+
const color = angle.color ?? "#0ea5e9";
|
|
30207
|
+
out.push({
|
|
30208
|
+
type: "ellipse",
|
|
30209
|
+
x: mapX(angle.x),
|
|
30210
|
+
y: mapY(angle.y),
|
|
30211
|
+
width: 2 * radius * plotW / (xMax - xMin),
|
|
30212
|
+
height: 2 * radius * plotH / (yMax - yMin),
|
|
30213
|
+
startAngle: -angle.to,
|
|
30214
|
+
endAngle: -angle.from,
|
|
30215
|
+
color
|
|
30216
|
+
});
|
|
30217
|
+
if (angle.label) {
|
|
30218
|
+
const mid = (angle.from + angle.to) / 2;
|
|
30219
|
+
const rad = mid * Math.PI / 180;
|
|
30220
|
+
out.push({
|
|
30221
|
+
type: "text",
|
|
30222
|
+
x: mapX(angle.x + 1.35 * radius * Math.cos(rad)),
|
|
30223
|
+
y: mapY(angle.y + 1.35 * radius * Math.sin(rad)),
|
|
30224
|
+
text: angle.label,
|
|
30225
|
+
color: "#111827",
|
|
30226
|
+
fontSize: 11,
|
|
30227
|
+
align: "center"
|
|
28455
30228
|
});
|
|
28456
30229
|
}
|
|
28457
30230
|
}
|
|
28458
30231
|
for (const p of points) {
|
|
28459
30232
|
if (p.x < xMin || p.x > xMax || p.y < yMin || p.y > yMax) continue;
|
|
30233
|
+
const isOpen = p.style === "open";
|
|
28460
30234
|
out.push({
|
|
28461
30235
|
type: "circle",
|
|
28462
30236
|
x: mapX(p.x),
|
|
28463
30237
|
y: mapY(p.y),
|
|
28464
30238
|
radius: p.radius ?? 4,
|
|
28465
30239
|
color: p.color ?? "#dc2626",
|
|
28466
|
-
fill: p.color ?? "#dc2626"
|
|
30240
|
+
fill: isOpen ? "#ffffff" : p.color ?? "#dc2626"
|
|
28467
30241
|
});
|
|
28468
30242
|
if (p.label) {
|
|
28469
30243
|
out.push({ type: "text", x: mapX(p.x) + 8, y: mapY(p.y) - 8, text: p.label, color: "#111827", fontSize: 12 });
|
|
@@ -28482,7 +30256,28 @@ var init_MathCanvas = __esm({
|
|
|
28482
30256
|
}
|
|
28483
30257
|
out.push(...shapes);
|
|
28484
30258
|
return out;
|
|
28485
|
-
}, [
|
|
30259
|
+
}, [
|
|
30260
|
+
width,
|
|
30261
|
+
height,
|
|
30262
|
+
xMin,
|
|
30263
|
+
xMax,
|
|
30264
|
+
yMin,
|
|
30265
|
+
yMax,
|
|
30266
|
+
showAxes,
|
|
30267
|
+
showGrid,
|
|
30268
|
+
gridStep,
|
|
30269
|
+
showTickLabels,
|
|
30270
|
+
showCurveLabels,
|
|
30271
|
+
curves,
|
|
30272
|
+
points,
|
|
30273
|
+
vectors,
|
|
30274
|
+
regions,
|
|
30275
|
+
bars,
|
|
30276
|
+
guides,
|
|
30277
|
+
angles,
|
|
30278
|
+
hops,
|
|
30279
|
+
shapes
|
|
30280
|
+
]);
|
|
28486
30281
|
return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
|
|
28487
30282
|
title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
|
|
28488
30283
|
/* @__PURE__ */ jsx(
|
|
@@ -28491,6 +30286,8 @@ var init_MathCanvas = __esm({
|
|
|
28491
30286
|
width,
|
|
28492
30287
|
height,
|
|
28493
30288
|
shapes: derivedShapes,
|
|
30289
|
+
readouts,
|
|
30290
|
+
traces,
|
|
28494
30291
|
interactive,
|
|
28495
30292
|
animate,
|
|
28496
30293
|
onShapeClick,
|
|
@@ -28502,7 +30299,315 @@ var init_MathCanvas = __esm({
|
|
|
28502
30299
|
};
|
|
28503
30300
|
}
|
|
28504
30301
|
});
|
|
28505
|
-
|
|
30302
|
+
function formatMeterValue(v) {
|
|
30303
|
+
return Number.isInteger(v) ? String(v) : String(Number(v.toFixed(2)));
|
|
30304
|
+
}
|
|
30305
|
+
function sceneObjectShapes(obj, canvasWidth, canvasHeight) {
|
|
30306
|
+
const out = [];
|
|
30307
|
+
const color = obj.color ?? "#334155";
|
|
30308
|
+
switch (obj.kind) {
|
|
30309
|
+
case "ground": {
|
|
30310
|
+
const xStart = obj.x1 ?? 0;
|
|
30311
|
+
const xEnd = obj.x2 ?? canvasWidth;
|
|
30312
|
+
const y = obj.y ?? 0;
|
|
30313
|
+
out.push({ type: "line", x1: xStart, y1: y, x2: xEnd, y2: y, color, lineWidth: 2 });
|
|
30314
|
+
for (let hx = xStart + 7; hx <= xEnd; hx += 14) {
|
|
30315
|
+
out.push({ type: "line", x1: hx, y1: y, x2: hx - 7, y2: y + 7, color, lineWidth: 1 });
|
|
30316
|
+
}
|
|
30317
|
+
if (obj.label) {
|
|
30318
|
+
out.push({
|
|
30319
|
+
type: "text",
|
|
30320
|
+
x: (xStart + xEnd) / 2,
|
|
30321
|
+
y: y - 10,
|
|
30322
|
+
text: obj.label,
|
|
30323
|
+
color: PHYSICS_LABEL_COLOR,
|
|
30324
|
+
fontSize: 11,
|
|
30325
|
+
align: "center"
|
|
30326
|
+
});
|
|
30327
|
+
}
|
|
30328
|
+
break;
|
|
30329
|
+
}
|
|
30330
|
+
case "wall": {
|
|
30331
|
+
const yStart = obj.y1 ?? 0;
|
|
30332
|
+
const yEnd = obj.y2 ?? canvasHeight;
|
|
30333
|
+
const x = obj.x ?? 0;
|
|
30334
|
+
out.push({ type: "line", x1: x, y1: yStart, x2: x, y2: yEnd, color, lineWidth: 2 });
|
|
30335
|
+
for (let hy = yStart + 7; hy <= yEnd; hy += 14) {
|
|
30336
|
+
out.push({ type: "line", x1: x, y1: hy, x2: x - 7, y2: hy + 7, color, lineWidth: 1 });
|
|
30337
|
+
}
|
|
30338
|
+
if (obj.label) {
|
|
30339
|
+
out.push({
|
|
30340
|
+
type: "text",
|
|
30341
|
+
x: x + 12,
|
|
30342
|
+
y: (yStart + yEnd) / 2,
|
|
30343
|
+
text: obj.label,
|
|
30344
|
+
color: PHYSICS_LABEL_COLOR,
|
|
30345
|
+
fontSize: 11,
|
|
30346
|
+
align: "left"
|
|
30347
|
+
});
|
|
30348
|
+
}
|
|
30349
|
+
break;
|
|
30350
|
+
}
|
|
30351
|
+
case "ramp": {
|
|
30352
|
+
const x1 = obj.x1 ?? 0;
|
|
30353
|
+
const y1 = obj.y1 ?? 0;
|
|
30354
|
+
const x2 = obj.x2 ?? canvasWidth;
|
|
30355
|
+
const y2 = obj.y2 ?? canvasHeight;
|
|
30356
|
+
out.push({
|
|
30357
|
+
type: "polygon",
|
|
30358
|
+
points: [
|
|
30359
|
+
{ x: x1, y: y1 },
|
|
30360
|
+
{ x: x2, y: y2 },
|
|
30361
|
+
{ x: x1, y: y2 }
|
|
30362
|
+
],
|
|
30363
|
+
color,
|
|
30364
|
+
fill: obj.fill ?? "#e2e8f0",
|
|
30365
|
+
lineWidth: 2
|
|
30366
|
+
});
|
|
30367
|
+
if (obj.label) {
|
|
30368
|
+
out.push({
|
|
30369
|
+
type: "text",
|
|
30370
|
+
x: (2 * x1 + x2) / 3,
|
|
30371
|
+
y: (y1 + 2 * y2) / 3,
|
|
30372
|
+
text: obj.label,
|
|
30373
|
+
color: PHYSICS_LABEL_COLOR,
|
|
30374
|
+
fontSize: 11,
|
|
30375
|
+
align: "center"
|
|
30376
|
+
});
|
|
30377
|
+
}
|
|
30378
|
+
break;
|
|
30379
|
+
}
|
|
30380
|
+
case "box": {
|
|
30381
|
+
const x = obj.x ?? 0;
|
|
30382
|
+
const y = obj.y ?? 0;
|
|
30383
|
+
const w = obj.width ?? 40;
|
|
30384
|
+
const h = obj.height ?? 40;
|
|
30385
|
+
out.push({ type: "rect", x, y, width: w, height: h, color, fill: obj.fill, lineWidth: 2 });
|
|
30386
|
+
if (obj.label) {
|
|
30387
|
+
out.push({
|
|
30388
|
+
type: "text",
|
|
30389
|
+
x: x + w / 2,
|
|
30390
|
+
y: y + h / 2,
|
|
30391
|
+
text: obj.label,
|
|
30392
|
+
color: PHYSICS_LABEL_COLOR,
|
|
30393
|
+
fontSize: 11,
|
|
30394
|
+
align: "center"
|
|
30395
|
+
});
|
|
30396
|
+
}
|
|
30397
|
+
break;
|
|
30398
|
+
}
|
|
30399
|
+
case "pivot": {
|
|
30400
|
+
const x = obj.x ?? 0;
|
|
30401
|
+
const y = obj.y ?? 0;
|
|
30402
|
+
out.push({ type: "circle", x, y, radius: 5, color, fill: color });
|
|
30403
|
+
out.push({ type: "line", x1: x - 14, y1: y - 8, x2: x + 14, y2: y - 8, color, lineWidth: 1 });
|
|
30404
|
+
for (let k = 0; k < 5; k++) {
|
|
30405
|
+
const hx = x - 14 + 7 * k;
|
|
30406
|
+
out.push({ type: "line", x1: hx, y1: y - 8, x2: hx - 6, y2: y - 14, color, lineWidth: 1 });
|
|
30407
|
+
}
|
|
30408
|
+
if (obj.label) {
|
|
30409
|
+
out.push({
|
|
30410
|
+
type: "text",
|
|
30411
|
+
x,
|
|
30412
|
+
y: y - 20,
|
|
30413
|
+
text: obj.label,
|
|
30414
|
+
color: PHYSICS_LABEL_COLOR,
|
|
30415
|
+
fontSize: 11,
|
|
30416
|
+
align: "center"
|
|
30417
|
+
});
|
|
30418
|
+
}
|
|
30419
|
+
break;
|
|
30420
|
+
}
|
|
30421
|
+
}
|
|
30422
|
+
return out;
|
|
30423
|
+
}
|
|
30424
|
+
function trailShapes(trail) {
|
|
30425
|
+
const n = trail.points.length;
|
|
30426
|
+
if (n < 2) return [];
|
|
30427
|
+
const color = trail.color ?? "#94a3b8";
|
|
30428
|
+
const lineWidth = trail.width ?? 2;
|
|
30429
|
+
const fade = trail.fade ?? true;
|
|
30430
|
+
const globalOpacity = trail.opacity ?? 1;
|
|
30431
|
+
const out = [];
|
|
30432
|
+
for (let i = 0; i < n - 1; i++) {
|
|
30433
|
+
const a = trail.points[i];
|
|
30434
|
+
const b = trail.points[i + 1];
|
|
30435
|
+
const segmentOpacity = fade ? 0.12 + 0.68 * i / (n - 1) : 0.6;
|
|
30436
|
+
out.push({
|
|
30437
|
+
type: "line",
|
|
30438
|
+
x1: a.x,
|
|
30439
|
+
y1: a.y,
|
|
30440
|
+
x2: b.x,
|
|
30441
|
+
y2: b.y,
|
|
30442
|
+
color,
|
|
30443
|
+
lineWidth,
|
|
30444
|
+
opacity: segmentOpacity * globalOpacity
|
|
30445
|
+
});
|
|
30446
|
+
}
|
|
30447
|
+
return out;
|
|
30448
|
+
}
|
|
30449
|
+
function constraintShapes(c, a, b) {
|
|
30450
|
+
const color = c.color ?? "#9ca3af";
|
|
30451
|
+
const kind = c.kind ?? "rod";
|
|
30452
|
+
if (kind === "rod") {
|
|
30453
|
+
return [{ type: "line", x1: a.x, y1: a.y, x2: b.x, y2: b.y, color, lineWidth: 2 }];
|
|
30454
|
+
}
|
|
30455
|
+
if (kind === "string") {
|
|
30456
|
+
return [{ type: "line", x1: a.x, y1: a.y, x2: b.x, y2: b.y, color, lineWidth: 2, dash: "dashed" }];
|
|
30457
|
+
}
|
|
30458
|
+
const COILS = 8;
|
|
30459
|
+
const AMP = 7;
|
|
30460
|
+
const LEAD = 10;
|
|
30461
|
+
const dx = b.x - a.x;
|
|
30462
|
+
const dy = b.y - a.y;
|
|
30463
|
+
const dist = Math.max(1e-6, Math.hypot(dx, dy));
|
|
30464
|
+
const ux = dx / dist;
|
|
30465
|
+
const uy = dy / dist;
|
|
30466
|
+
const perpX = -uy;
|
|
30467
|
+
const perpY = ux;
|
|
30468
|
+
const aPrime = { x: a.x + LEAD * ux, y: a.y + LEAD * uy };
|
|
30469
|
+
const bPrime = { x: b.x - LEAD * ux, y: b.y - LEAD * uy };
|
|
30470
|
+
const m = 2 * COILS;
|
|
30471
|
+
const polyline = [{ x: a.x, y: a.y }, aPrime];
|
|
30472
|
+
for (let j = 1; j <= m; j++) {
|
|
30473
|
+
const t = j / (m + 1);
|
|
30474
|
+
const baseX = aPrime.x + t * (bPrime.x - aPrime.x);
|
|
30475
|
+
const baseY = aPrime.y + t * (bPrime.y - aPrime.y);
|
|
30476
|
+
const sign = j % 2 === 0 ? 1 : -1;
|
|
30477
|
+
polyline.push({ x: baseX + sign * AMP * perpX, y: baseY + sign * AMP * perpY });
|
|
30478
|
+
}
|
|
30479
|
+
polyline.push(bPrime, { x: b.x, y: b.y });
|
|
30480
|
+
const out = [];
|
|
30481
|
+
for (let i = 1; i < polyline.length; i++) {
|
|
30482
|
+
out.push({
|
|
30483
|
+
type: "line",
|
|
30484
|
+
x1: polyline[i - 1].x,
|
|
30485
|
+
y1: polyline[i - 1].y,
|
|
30486
|
+
x2: polyline[i].x,
|
|
30487
|
+
y2: polyline[i].y,
|
|
30488
|
+
color,
|
|
30489
|
+
lineWidth: 2
|
|
30490
|
+
});
|
|
30491
|
+
}
|
|
30492
|
+
return out;
|
|
30493
|
+
}
|
|
30494
|
+
function vectorShapes(v, bodyById) {
|
|
30495
|
+
let ax;
|
|
30496
|
+
let ay;
|
|
30497
|
+
if (v.body) {
|
|
30498
|
+
const anchor = bodyById.get(v.body);
|
|
30499
|
+
if (!anchor) return [];
|
|
30500
|
+
ax = anchor.x;
|
|
30501
|
+
ay = anchor.y;
|
|
30502
|
+
} else {
|
|
30503
|
+
ax = v.x ?? 0;
|
|
30504
|
+
ay = v.y ?? 0;
|
|
30505
|
+
}
|
|
30506
|
+
const scale = v.scale ?? 1;
|
|
30507
|
+
const color = v.color ?? "#dc2626";
|
|
30508
|
+
const tx = ax + v.dx * scale;
|
|
30509
|
+
const ty = ay + v.dy * scale;
|
|
30510
|
+
const out = [{ type: "arrow", x1: ax, y1: ay, x2: tx, y2: ty, color, lineWidth: 2, dash: v.dash }];
|
|
30511
|
+
if (v.label) {
|
|
30512
|
+
const dist = Math.max(1e-6, Math.hypot(tx - ax, ty - ay));
|
|
30513
|
+
const ux = (tx - ax) / dist;
|
|
30514
|
+
const uy = (ty - ay) / dist;
|
|
30515
|
+
out.push({
|
|
30516
|
+
type: "text",
|
|
30517
|
+
x: tx + 8 * ux,
|
|
30518
|
+
y: ty + 8 * uy,
|
|
30519
|
+
text: v.label,
|
|
30520
|
+
color,
|
|
30521
|
+
fontSize: 11,
|
|
30522
|
+
align: "center"
|
|
30523
|
+
});
|
|
30524
|
+
}
|
|
30525
|
+
return out;
|
|
30526
|
+
}
|
|
30527
|
+
function angleMarkerShapes(a) {
|
|
30528
|
+
const radius = a.radius ?? 26;
|
|
30529
|
+
const color = a.color ?? "#0ea5e9";
|
|
30530
|
+
const out = [
|
|
30531
|
+
{
|
|
30532
|
+
type: "ellipse",
|
|
30533
|
+
x: a.x,
|
|
30534
|
+
y: a.y,
|
|
30535
|
+
width: radius * 2,
|
|
30536
|
+
height: radius * 2,
|
|
30537
|
+
startAngle: a.from,
|
|
30538
|
+
endAngle: a.to,
|
|
30539
|
+
color,
|
|
30540
|
+
lineWidth: 2
|
|
30541
|
+
}
|
|
30542
|
+
];
|
|
30543
|
+
if (a.label) {
|
|
30544
|
+
const mid = (a.from + a.to) / 2 * (Math.PI / 180);
|
|
30545
|
+
out.push({
|
|
30546
|
+
type: "text",
|
|
30547
|
+
x: a.x + (radius + 13) * Math.cos(mid),
|
|
30548
|
+
y: a.y + (radius + 13) * Math.sin(mid),
|
|
30549
|
+
text: a.label,
|
|
30550
|
+
color,
|
|
30551
|
+
fontSize: 11,
|
|
30552
|
+
align: "center"
|
|
30553
|
+
});
|
|
30554
|
+
}
|
|
30555
|
+
return out;
|
|
30556
|
+
}
|
|
30557
|
+
function fieldShapes(field, canvasWidth, canvasHeight) {
|
|
30558
|
+
const spacing = field.spacing ?? 48;
|
|
30559
|
+
const size = field.size ?? 14;
|
|
30560
|
+
const color = field.color ?? "#94a3b8";
|
|
30561
|
+
const regionX = field.x ?? 0;
|
|
30562
|
+
const regionY = field.y ?? 0;
|
|
30563
|
+
const regionW = field.width ?? canvasWidth;
|
|
30564
|
+
const regionH = field.height ?? canvasHeight;
|
|
30565
|
+
const out = [];
|
|
30566
|
+
for (let gx = regionX + spacing / 2; gx < regionX + regionW; gx += spacing) {
|
|
30567
|
+
for (let gy = regionY + spacing / 2; gy < regionY + regionH; gy += spacing) {
|
|
30568
|
+
if (field.kind === "arrows") {
|
|
30569
|
+
const rad = (field.angle ?? 0) * Math.PI / 180;
|
|
30570
|
+
const hx = Math.cos(rad) * size / 2;
|
|
30571
|
+
const hy = Math.sin(rad) * size / 2;
|
|
30572
|
+
out.push({ type: "arrow", x1: gx - hx, y1: gy - hy, x2: gx + hx, y2: gy + hy, color, lineWidth: 2 });
|
|
30573
|
+
} else if (field.kind === "into") {
|
|
30574
|
+
const r = size / 3;
|
|
30575
|
+
const d = 0.6 * r * Math.SQRT1_2;
|
|
30576
|
+
out.push({ type: "circle", x: gx, y: gy, radius: r, color });
|
|
30577
|
+
out.push({ type: "line", x1: gx - d, y1: gy - d, x2: gx + d, y2: gy + d, color, lineWidth: 1 });
|
|
30578
|
+
out.push({ type: "line", x1: gx - d, y1: gy + d, x2: gx + d, y2: gy - d, color, lineWidth: 1 });
|
|
30579
|
+
} else {
|
|
30580
|
+
const r = size / 3;
|
|
30581
|
+
out.push({ type: "circle", x: gx, y: gy, radius: r, color });
|
|
30582
|
+
out.push({ type: "circle", x: gx, y: gy, radius: 1.5, color, fill: color });
|
|
30583
|
+
}
|
|
30584
|
+
}
|
|
30585
|
+
}
|
|
30586
|
+
return out;
|
|
30587
|
+
}
|
|
30588
|
+
function meterShapes(meters, canvasHeight) {
|
|
30589
|
+
const n = meters.length;
|
|
30590
|
+
const out = [];
|
|
30591
|
+
const sharedMax = Math.max(1e-6, ...meters.map((m) => m.value));
|
|
30592
|
+
meters.forEach((meter, i) => {
|
|
30593
|
+
const rowY = canvasHeight - 10 - 16 * (n - i);
|
|
30594
|
+
const color = meter.color ?? "#3b82f6";
|
|
30595
|
+
const M = meter.max ?? sharedMax;
|
|
30596
|
+
const w = Math.round(Math.min(1, Math.max(0, meter.value / M)) * 110);
|
|
30597
|
+
out.push({ type: "text", x: 8, y: rowY + 8, text: meter.label, color: PHYSICS_LABEL_COLOR, fontSize: 10 });
|
|
30598
|
+
out.push({ type: "rect", x: 52, y: rowY, width: w, height: 10, color, fill: color });
|
|
30599
|
+
out.push({
|
|
30600
|
+
type: "text",
|
|
30601
|
+
x: 166,
|
|
30602
|
+
y: rowY + 8,
|
|
30603
|
+
text: formatMeterValue(meter.value),
|
|
30604
|
+
color: "#6b7280",
|
|
30605
|
+
fontSize: 9
|
|
30606
|
+
});
|
|
30607
|
+
});
|
|
30608
|
+
return out;
|
|
30609
|
+
}
|
|
30610
|
+
var physicsLog2, PHYSICS_LABEL_COLOR, PhysicsCanvas;
|
|
28506
30611
|
var init_PhysicsCanvas = __esm({
|
|
28507
30612
|
"components/learning/molecules/PhysicsCanvas.tsx"() {
|
|
28508
30613
|
"use client";
|
|
@@ -28511,6 +30616,7 @@ var init_PhysicsCanvas = __esm({
|
|
|
28511
30616
|
init_LearningCanvas();
|
|
28512
30617
|
init_learningScene3D();
|
|
28513
30618
|
physicsLog2 = createLogger("almadar:ui:physics-canvas");
|
|
30619
|
+
PHYSICS_LABEL_COLOR = "#374151";
|
|
28514
30620
|
PhysicsCanvas = ({
|
|
28515
30621
|
className,
|
|
28516
30622
|
width = 600,
|
|
@@ -28527,7 +30633,18 @@ var init_PhysicsCanvas = __esm({
|
|
|
28527
30633
|
showForces = false,
|
|
28528
30634
|
velocityScale = 20,
|
|
28529
30635
|
forceScale = 20,
|
|
30636
|
+
sceneObjects = [],
|
|
30637
|
+
trails = [],
|
|
30638
|
+
vectors = [],
|
|
30639
|
+
surface3d,
|
|
30640
|
+
vectors3d = [],
|
|
30641
|
+
vectorScale = 1,
|
|
30642
|
+
angles = [],
|
|
30643
|
+
field,
|
|
30644
|
+
meters = [],
|
|
28530
30645
|
shapes = [],
|
|
30646
|
+
readouts,
|
|
30647
|
+
traces,
|
|
28531
30648
|
showGrid,
|
|
28532
30649
|
shadows,
|
|
28533
30650
|
interactive,
|
|
@@ -28542,19 +30659,14 @@ var init_PhysicsCanvas = __esm({
|
|
|
28542
30659
|
for (const b of bodies) {
|
|
28543
30660
|
if (b.id) bodyById.set(b.id, b);
|
|
28544
30661
|
}
|
|
30662
|
+
if (field) out.push(...fieldShapes(field, width, height));
|
|
30663
|
+
for (const obj of sceneObjects) out.push(...sceneObjectShapes(obj, width, height));
|
|
30664
|
+
for (const trail of trails) out.push(...trailShapes(trail));
|
|
28545
30665
|
for (const c of constraints) {
|
|
28546
30666
|
const a = bodyById.get(c.from);
|
|
28547
30667
|
const b = bodyById.get(c.to);
|
|
28548
30668
|
if (!a || !b) continue;
|
|
28549
|
-
out.push(
|
|
28550
|
-
type: "line",
|
|
28551
|
-
x1: a.x,
|
|
28552
|
-
y1: a.y,
|
|
28553
|
-
x2: b.x,
|
|
28554
|
-
y2: b.y,
|
|
28555
|
-
color: c.color ?? "#9ca3af",
|
|
28556
|
-
lineWidth: 2
|
|
28557
|
-
});
|
|
30669
|
+
out.push(...constraintShapes(c, a, b));
|
|
28558
30670
|
}
|
|
28559
30671
|
for (const b of bodies) {
|
|
28560
30672
|
out.push({
|
|
@@ -28599,14 +30711,51 @@ var init_PhysicsCanvas = __esm({
|
|
|
28599
30711
|
});
|
|
28600
30712
|
}
|
|
28601
30713
|
}
|
|
30714
|
+
for (const v of vectors) out.push(...vectorShapes(v, bodyById));
|
|
30715
|
+
for (const a of angles) out.push(...angleMarkerShapes(a));
|
|
30716
|
+
if (meters.length > 0) out.push(...meterShapes(meters, height));
|
|
28602
30717
|
out.push(...shapes);
|
|
28603
30718
|
return out;
|
|
28604
|
-
}, [
|
|
30719
|
+
}, [
|
|
30720
|
+
bodies,
|
|
30721
|
+
constraints,
|
|
30722
|
+
showVelocity,
|
|
30723
|
+
showForces,
|
|
30724
|
+
velocityScale,
|
|
30725
|
+
forceScale,
|
|
30726
|
+
sceneObjects,
|
|
30727
|
+
trails,
|
|
30728
|
+
vectors,
|
|
30729
|
+
angles,
|
|
30730
|
+
field,
|
|
30731
|
+
meters,
|
|
30732
|
+
shapes,
|
|
30733
|
+
width,
|
|
30734
|
+
height
|
|
30735
|
+
]);
|
|
28605
30736
|
const drawables3D = useMemo(() => {
|
|
28606
30737
|
if (mode !== "3d") return [];
|
|
28607
30738
|
if (shapes.length > 0) {
|
|
28608
30739
|
physicsLog2.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
|
|
28609
30740
|
}
|
|
30741
|
+
if (sceneObjects.length > 0) {
|
|
30742
|
+
physicsLog2.debug("sceneObjects ignored in 3D mode (pixel-authored 2D vocabulary)", { count: sceneObjects.length });
|
|
30743
|
+
}
|
|
30744
|
+
if (vectors.length > 0) {
|
|
30745
|
+
physicsLog2.debug("vectors ignored in 3D mode (pixel-authored 2D vocabulary)", { count: vectors.length });
|
|
30746
|
+
}
|
|
30747
|
+
if (angles.length > 0) {
|
|
30748
|
+
physicsLog2.debug("angles ignored in 3D mode (pixel-authored 2D vocabulary)", { count: angles.length });
|
|
30749
|
+
}
|
|
30750
|
+
if (field) {
|
|
30751
|
+
physicsLog2.debug("field ignored in 3D mode (pixel-authored 2D vocabulary)");
|
|
30752
|
+
}
|
|
30753
|
+
if (meters.length > 0) {
|
|
30754
|
+
physicsLog2.debug("meters ignored in 3D mode (pixel-authored 2D vocabulary)", { count: meters.length });
|
|
30755
|
+
}
|
|
30756
|
+
if (animate) {
|
|
30757
|
+
physicsLog2.debug("animate ignored in 3D mode (motion is entity-state driven)");
|
|
30758
|
+
}
|
|
28610
30759
|
const out = [];
|
|
28611
30760
|
const labelColor = labelColorForBackground(backgroundColor);
|
|
28612
30761
|
const bodyById = /* @__PURE__ */ new Map();
|
|
@@ -28654,15 +30803,67 @@ var init_PhysicsCanvas = __esm({
|
|
|
28654
30803
|
if (arrow) out.push(arrow);
|
|
28655
30804
|
}
|
|
28656
30805
|
}
|
|
30806
|
+
for (const trail of trails) {
|
|
30807
|
+
if (trail.fade !== void 0) {
|
|
30808
|
+
physicsLog2.debug("trail.fade ignored in 3D mode (2D-only fade curve \u2014 3D draws an opaque tube)", { id: trail.id });
|
|
30809
|
+
}
|
|
30810
|
+
const points = trail.points.map((p) => [p.x, p.y, p.z ?? 0]);
|
|
30811
|
+
out.push(
|
|
30812
|
+
...polylineTube(points, trail.width ?? 0.05, trail.color ?? "#94a3b8", {
|
|
30813
|
+
...trail.opacity !== void 0 ? { opacity: trail.opacity } : {}
|
|
30814
|
+
})
|
|
30815
|
+
);
|
|
30816
|
+
}
|
|
30817
|
+
if (surface3d) {
|
|
30818
|
+
out.push(...heightFieldMesh(surface3d));
|
|
30819
|
+
}
|
|
30820
|
+
if (vectors3d.length > 0) {
|
|
30821
|
+
out.push(
|
|
30822
|
+
...arrowField(
|
|
30823
|
+
vectors3d.map((v) => ({
|
|
30824
|
+
id: v.id,
|
|
30825
|
+
from: [v.x, v.y, v.z ?? 0],
|
|
30826
|
+
delta: [v.dx, v.dy, v.dz ?? 0],
|
|
30827
|
+
color: v.color,
|
|
30828
|
+
label: v.label,
|
|
30829
|
+
width: v.width
|
|
30830
|
+
})),
|
|
30831
|
+
{ scale: vectorScale, labelColor }
|
|
30832
|
+
)
|
|
30833
|
+
);
|
|
30834
|
+
}
|
|
28657
30835
|
return out;
|
|
28658
|
-
}, [
|
|
30836
|
+
}, [
|
|
30837
|
+
mode,
|
|
30838
|
+
bodies,
|
|
30839
|
+
constraints,
|
|
30840
|
+
showVelocity,
|
|
30841
|
+
showForces,
|
|
30842
|
+
velocityScale,
|
|
30843
|
+
forceScale,
|
|
30844
|
+
shapes,
|
|
30845
|
+
sceneObjects,
|
|
30846
|
+
trails,
|
|
30847
|
+
vectors,
|
|
30848
|
+
surface3d,
|
|
30849
|
+
vectors3d,
|
|
30850
|
+
vectorScale,
|
|
30851
|
+
angles,
|
|
30852
|
+
field,
|
|
30853
|
+
meters,
|
|
30854
|
+
animate,
|
|
30855
|
+
backgroundColor
|
|
30856
|
+
]);
|
|
28659
30857
|
const bodyIndexById = useMemo(() => {
|
|
28660
30858
|
const m = /* @__PURE__ */ new Map();
|
|
30859
|
+
vectors3d.forEach((v, i) => {
|
|
30860
|
+
if (v.id) m.set(v.id, i);
|
|
30861
|
+
});
|
|
28661
30862
|
bodies.forEach((b, i) => {
|
|
28662
30863
|
if (b.id) m.set(b.id, i);
|
|
28663
30864
|
});
|
|
28664
30865
|
return m;
|
|
28665
|
-
}, [bodies]);
|
|
30866
|
+
}, [bodies, vectors3d]);
|
|
28666
30867
|
if (mode === "3d") {
|
|
28667
30868
|
return /* @__PURE__ */ jsx(
|
|
28668
30869
|
LearningScene3D,
|
|
@@ -28694,6 +30895,8 @@ var init_PhysicsCanvas = __esm({
|
|
|
28694
30895
|
height,
|
|
28695
30896
|
backgroundColor,
|
|
28696
30897
|
shapes: derivedShapes,
|
|
30898
|
+
readouts,
|
|
30899
|
+
traces,
|
|
28697
30900
|
interactive: interactive ?? false,
|
|
28698
30901
|
animate,
|
|
28699
30902
|
onShapeClick,
|
|
@@ -28844,7 +31047,7 @@ function layoutFlow(nodeIds, adjacency, roots, width, height, margin) {
|
|
|
28844
31047
|
}
|
|
28845
31048
|
return nodeIds.map((id) => positions.get(id));
|
|
28846
31049
|
}
|
|
28847
|
-
function
|
|
31050
|
+
function layoutTree2(nodeIds, adjacency, roots, width, height, margin) {
|
|
28848
31051
|
const effectiveRoots = roots.length > 0 ? roots : [nodeIds[0]];
|
|
28849
31052
|
const layers = assignLayers(nodeIds, adjacency, effectiveRoots);
|
|
28850
31053
|
const maxLayer = Math.max(...Array.from(layers.values()));
|
|
@@ -28900,7 +31103,7 @@ function computeStaticLayout(mode, input) {
|
|
|
28900
31103
|
const adjacency = buildAdjacency(nodeIds, edges);
|
|
28901
31104
|
const roots = findRoots(nodeIds, adjacency);
|
|
28902
31105
|
if (mode === "flow") return layoutFlow(nodeIds, adjacency, roots, width, height, margin);
|
|
28903
|
-
if (mode === "tree") return
|
|
31106
|
+
if (mode === "tree") return layoutTree2(nodeIds, adjacency, roots, width, height, margin);
|
|
28904
31107
|
return layoutRadial(nodeIds, adjacency, roots, width, height, margin);
|
|
28905
31108
|
}
|
|
28906
31109
|
var init_graphViewLayouts = __esm({
|
|
@@ -30143,26 +32346,6 @@ var init_Lightbox = __esm({
|
|
|
30143
32346
|
Lightbox.displayName = "Lightbox";
|
|
30144
32347
|
}
|
|
30145
32348
|
});
|
|
30146
|
-
function useMediaQuery(query) {
|
|
30147
|
-
const subscribe = useCallback(
|
|
30148
|
-
(onChange) => {
|
|
30149
|
-
const mql = window.matchMedia(query);
|
|
30150
|
-
mql.addEventListener("change", onChange);
|
|
30151
|
-
return () => mql.removeEventListener("change", onChange);
|
|
30152
|
-
},
|
|
30153
|
-
[query]
|
|
30154
|
-
);
|
|
30155
|
-
return useSyncExternalStore(
|
|
30156
|
-
subscribe,
|
|
30157
|
-
() => window.matchMedia(query).matches,
|
|
30158
|
-
() => false
|
|
30159
|
-
);
|
|
30160
|
-
}
|
|
30161
|
-
var init_useMediaQuery = __esm({
|
|
30162
|
-
"hooks/useMediaQuery.ts"() {
|
|
30163
|
-
"use client";
|
|
30164
|
-
}
|
|
30165
|
-
});
|
|
30166
32349
|
function renderIconInput3(icon, props) {
|
|
30167
32350
|
return typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon, ...props }) : /* @__PURE__ */ jsx(Icon, { icon, ...props });
|
|
30168
32351
|
}
|
|
@@ -30201,8 +32384,8 @@ function TableView({
|
|
|
30201
32384
|
columns,
|
|
30202
32385
|
fields,
|
|
30203
32386
|
itemActions,
|
|
30204
|
-
maxInlineActions,
|
|
30205
|
-
itemClickEvent,
|
|
32387
|
+
maxInlineActions: _maxInlineActions,
|
|
32388
|
+
itemClickEvent = "",
|
|
30206
32389
|
selectable = false,
|
|
30207
32390
|
selectEvent,
|
|
30208
32391
|
selectedIds,
|
|
@@ -30252,7 +32435,6 @@ function TableView({
|
|
|
30252
32435
|
const hasMore = pageSize > 0 && visibleCount < ordered2.length;
|
|
30253
32436
|
const hasRenderProp = typeof children === "function";
|
|
30254
32437
|
const idField = dndItemIdField ?? "id";
|
|
30255
|
-
const isCoarsePointer = useMediaQuery("(pointer: coarse)");
|
|
30256
32438
|
React87__default.useEffect(() => {
|
|
30257
32439
|
tableViewLog.debug("render", {
|
|
30258
32440
|
rowCount: data.length,
|
|
@@ -30294,21 +32476,14 @@ function TableView({
|
|
|
30294
32476
|
const dir = sortColumn === (col.field ?? col.key) && sortDirection === "asc" ? "desc" : "asc";
|
|
30295
32477
|
eventBus.emit(`UI:${sortEvent}`, { column: col.field ?? col.key, direction: dir });
|
|
30296
32478
|
};
|
|
30297
|
-
const
|
|
30298
|
-
e.stopPropagation();
|
|
30299
|
-
const payload = {
|
|
30300
|
-
id: row.id,
|
|
30301
|
-
row
|
|
30302
|
-
};
|
|
30303
|
-
eventBus.emit(`UI:${action.event}`, payload);
|
|
30304
|
-
};
|
|
32479
|
+
const rowClickEvent = itemClickEvent || actionDefs.find((a) => a.variant !== "danger")?.event;
|
|
30305
32480
|
const handleRowClick = (row) => () => {
|
|
30306
|
-
if (!
|
|
32481
|
+
if (!rowClickEvent) return;
|
|
30307
32482
|
const payload = {
|
|
30308
32483
|
id: row.id,
|
|
30309
32484
|
row
|
|
30310
32485
|
};
|
|
30311
|
-
eventBus.emit(`UI:${
|
|
32486
|
+
eventBus.emit(`UI:${rowClickEvent}`, payload);
|
|
30312
32487
|
};
|
|
30313
32488
|
const colFloors = React87__default.useMemo(
|
|
30314
32489
|
() => colDefs.map((col) => {
|
|
@@ -30324,10 +32499,7 @@ function TableView({
|
|
|
30324
32499
|
const statusNode = isLoading ? /* @__PURE__ */ jsx(Box, { className: "text-center py-8", children: /* @__PURE__ */ jsx(Typography, { variant: "body", color: "secondary", children: t("loading.items") }) }) : error ? /* @__PURE__ */ jsx(Box, { className: "text-center py-8", children: /* @__PURE__ */ jsx(Typography, { variant: "body", color: "error", children: error.message }) }) : data.length === 0 ? /* @__PURE__ */ jsx(Box, { className: "text-center py-12", children: /* @__PURE__ */ jsx(Typography, { variant: "body", color: "secondary", children: emptyMessage || t("empty.noItems") }) }) : null;
|
|
30325
32500
|
const lk = LOOKS[look];
|
|
30326
32501
|
const hasActions = actionDefs.length > 0;
|
|
30327
|
-
const
|
|
30328
|
-
const inlineActionCount = hasActions ? effectiveMaxInline != null ? Math.min(actionDefs.length, effectiveMaxInline) : actionDefs.length : 0;
|
|
30329
|
-
const hasOverflowActions = hasActions && effectiveMaxInline != null && actionDefs.length > effectiveMaxInline;
|
|
30330
|
-
const actionsTrack = hasActions ? `${inlineActionCount * 6 + (hasOverflowActions ? 3 : 0)}rem` : null;
|
|
32502
|
+
const actionsTrack = hasActions ? "3rem" : null;
|
|
30331
32503
|
const gridTemplateColumns = [
|
|
30332
32504
|
selectable ? "auto" : null,
|
|
30333
32505
|
...colDefs.map((c, i) => c.width ?? `minmax(${colFloors[i]}ch, 1fr)`),
|
|
@@ -30374,7 +32546,7 @@ function TableView({
|
|
|
30374
32546
|
col.key
|
|
30375
32547
|
);
|
|
30376
32548
|
}),
|
|
30377
|
-
hasActions && /* @__PURE__ */ jsx(Box, { "aria-hidden": true, className: "sticky right-0 bg-[var(--color-surface-subtle)]" })
|
|
32549
|
+
hasActions && /* @__PURE__ */ jsx(Box, { "aria-hidden": true, className: "sticky right-0 bg-[var(--color-surface-subtle)] border-l border-[var(--color-border)] h-full" })
|
|
30378
32550
|
]
|
|
30379
32551
|
}
|
|
30380
32552
|
);
|
|
@@ -30386,12 +32558,12 @@ function TableView({
|
|
|
30386
32558
|
role: "row",
|
|
30387
32559
|
"data-entity-row": true,
|
|
30388
32560
|
"data-entity-id": id,
|
|
30389
|
-
onClick:
|
|
32561
|
+
onClick: rowClickEvent ? handleRowClick(row) : void 0,
|
|
30390
32562
|
style: !hasRenderProp ? { gridTemplateColumns } : void 0,
|
|
30391
32563
|
className: cn(
|
|
30392
32564
|
"group items-center gap-3 transition-colors duration-fast",
|
|
30393
32565
|
hasRenderProp ? "flex" : "grid",
|
|
30394
|
-
|
|
32566
|
+
rowClickEvent && "cursor-pointer",
|
|
30395
32567
|
lk.rowPad,
|
|
30396
32568
|
lk.divider && "border-b border-[var(--color-border)]",
|
|
30397
32569
|
lk.striped && index % 2 === 1 && "bg-[var(--color-surface-subtle)]",
|
|
@@ -30399,7 +32571,7 @@ function TableView({
|
|
|
30399
32571
|
look === "bordered" && "[&>*]:border-r [&>*]:border-[var(--color-border)] [&>*:last-child]:border-r-0"
|
|
30400
32572
|
),
|
|
30401
32573
|
children: [
|
|
30402
|
-
selectable && /* @__PURE__ */ jsx(Box, { className: "flex items-center", onClick:
|
|
32574
|
+
selectable && /* @__PURE__ */ jsx(Box, { className: "flex items-center", onClick: rowClickEvent ? (e) => e.stopPropagation() : void 0, children: /* @__PURE__ */ jsx(
|
|
30403
32575
|
Checkbox,
|
|
30404
32576
|
{
|
|
30405
32577
|
checked: selected.has(id),
|
|
@@ -30420,53 +32592,37 @@ function TableView({
|
|
|
30420
32592
|
}
|
|
30421
32593
|
return /* @__PURE__ */ jsx(Box, { role: "cell", className: cellBase, children: /* @__PURE__ */ jsx("span", { className: "truncate text-foreground", children: formatCell(raw, col.format) }) }, col.key);
|
|
30422
32594
|
}),
|
|
30423
|
-
hasActions && /* @__PURE__ */
|
|
32595
|
+
hasActions && /* @__PURE__ */ jsx(
|
|
30424
32596
|
HStack,
|
|
30425
32597
|
{
|
|
30426
32598
|
gap: "xs",
|
|
30427
|
-
onClick:
|
|
32599
|
+
onClick: rowClickEvent ? (e) => e.stopPropagation() : void 0,
|
|
30428
32600
|
className: cn(
|
|
30429
32601
|
// Pinned: the fixed column tracks routinely overflow the caller's
|
|
30430
|
-
// scroll container, which
|
|
30431
|
-
// Opaque so
|
|
32602
|
+
// scroll container, which would leave the kebab off-screen.
|
|
32603
|
+
// Opaque + hairline edge so it reads as a pinned column, not a
|
|
32604
|
+
// floating control, while scrolled cells pass underneath.
|
|
30432
32605
|
"justify-end flex-shrink-0 sticky right-0 z-[1] transition-colors",
|
|
32606
|
+
"border-l border-[var(--color-border)]",
|
|
30433
32607
|
lk.striped && index % 2 === 1 ? "bg-[var(--color-surface-subtle)]" : "bg-[var(--color-card)] group-hover:bg-[var(--color-surface-subtle)]"
|
|
30434
32608
|
),
|
|
30435
|
-
children:
|
|
30436
|
-
|
|
30437
|
-
|
|
30438
|
-
|
|
30439
|
-
|
|
30440
|
-
|
|
30441
|
-
|
|
30442
|
-
|
|
30443
|
-
|
|
30444
|
-
|
|
30445
|
-
|
|
30446
|
-
|
|
30447
|
-
|
|
30448
|
-
|
|
30449
|
-
}
|
|
30450
|
-
|
|
30451
|
-
|
|
30452
|
-
effectiveMaxInline != null && actionDefs.length > effectiveMaxInline && /* @__PURE__ */ jsx(
|
|
30453
|
-
Menu,
|
|
30454
|
-
{
|
|
30455
|
-
position: "bottom-end",
|
|
30456
|
-
trigger: /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", children: /* @__PURE__ */ jsx(Icon, { name: "more-horizontal", size: "xs" }) }),
|
|
30457
|
-
items: actionDefs.slice(effectiveMaxInline).map((action) => ({
|
|
30458
|
-
label: action.label,
|
|
30459
|
-
icon: action.icon,
|
|
30460
|
-
event: action.event,
|
|
30461
|
-
variant: action.variant === "danger" ? "danger" : "default",
|
|
30462
|
-
onClick: () => eventBus.emit(`UI:${action.event}`, {
|
|
30463
|
-
id: row.id,
|
|
30464
|
-
row
|
|
30465
|
-
})
|
|
30466
|
-
}))
|
|
30467
|
-
}
|
|
30468
|
-
)
|
|
30469
|
-
]
|
|
32609
|
+
children: /* @__PURE__ */ jsx(
|
|
32610
|
+
Menu,
|
|
32611
|
+
{
|
|
32612
|
+
position: "bottom-end",
|
|
32613
|
+
trigger: /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", "data-row-id": String(row.id), children: /* @__PURE__ */ jsx(Icon, { name: "more-horizontal", size: "xs" }) }),
|
|
32614
|
+
items: actionDefs.map((action) => ({
|
|
32615
|
+
label: action.label,
|
|
32616
|
+
icon: action.icon,
|
|
32617
|
+
event: action.event,
|
|
32618
|
+
variant: action.variant === "danger" ? "danger" : "default",
|
|
32619
|
+
onClick: () => eventBus.emit(`UI:${action.event}`, {
|
|
32620
|
+
id: row.id,
|
|
32621
|
+
row
|
|
32622
|
+
})
|
|
32623
|
+
}))
|
|
32624
|
+
}
|
|
32625
|
+
)
|
|
30470
32626
|
}
|
|
30471
32627
|
)
|
|
30472
32628
|
]
|
|
@@ -30509,7 +32665,6 @@ var init_TableView = __esm({
|
|
|
30509
32665
|
init_format();
|
|
30510
32666
|
init_getNestedValue();
|
|
30511
32667
|
init_useEventBus();
|
|
30512
|
-
init_useMediaQuery();
|
|
30513
32668
|
init_Box();
|
|
30514
32669
|
init_Stack();
|
|
30515
32670
|
init_Typography();
|
|
@@ -45568,6 +47723,7 @@ var init_component_registry_generated = __esm({
|
|
|
45568
47723
|
init_ActionTile();
|
|
45569
47724
|
init_ActivationBlock();
|
|
45570
47725
|
init_ComponentPatterns();
|
|
47726
|
+
init_AlgoGraphCanvas();
|
|
45571
47727
|
init_AlgorithmCanvas();
|
|
45572
47728
|
init_AnimatedCounter();
|
|
45573
47729
|
init_AnimatedGraphic();
|
|
@@ -45831,6 +47987,7 @@ var init_component_registry_generated = __esm({
|
|
|
45831
47987
|
"ActivationBlock": ActivationBlock,
|
|
45832
47988
|
"Alert": AlertPattern,
|
|
45833
47989
|
"AlertPattern": AlertPattern,
|
|
47990
|
+
"AlgoGraphCanvas": AlgoGraphCanvas,
|
|
45834
47991
|
"AlgorithmCanvas": AlgorithmCanvas,
|
|
45835
47992
|
"AnimatedCounter": AnimatedCounter,
|
|
45836
47993
|
"AnimatedGraphic": AnimatedGraphic,
|