@almadar/ui 5.148.0 → 5.150.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{GameAudioProvider-CPGwD49P.d.cts → GameAudioProvider-B48iXwz3.d.ts} +2 -50
- package/dist/{GameAudioProvider-CPGwD49P.d.ts → GameAudioProvider-CQAdPreB.d.cts} +2 -50
- package/dist/avl/index.cjs +2741 -158
- package/dist/avl/index.js +2741 -158
- package/dist/{avl-schema-parser-DVmrgdwc.d.cts → avl-schema-parser-CVzkNzg7.d.cts} +18 -9
- package/dist/{avl-schema-parser-BRJ77Yze.d.ts → avl-schema-parser-Cx_4SVg9.d.ts} +18 -9
- package/dist/{cn-BAn68sNO.d.cts → cn-BnAJZcNb.d.cts} +2 -8
- package/dist/{cn-CWxxLkri.d.ts → cn-DJTUjk1M.d.ts} +2 -8
- package/dist/components/index.cjs +2764 -158
- package/dist/components/index.d.cts +1077 -107
- package/dist/components/index.d.ts +1077 -107
- package/dist/components/index.js +2760 -160
- package/dist/hooks/index.cjs +1 -1
- package/dist/hooks/index.js +1 -1
- package/dist/lib/drawable/three/index.cjs +57 -30
- package/dist/lib/drawable/three/index.d.cts +4 -3
- package/dist/lib/drawable/three/index.d.ts +4 -3
- package/dist/lib/drawable/three/index.js +57 -30
- package/dist/lib/index.d.cts +3 -2
- package/dist/lib/index.d.ts +3 -2
- package/dist/marketing/index.cjs +1 -1
- package/dist/marketing/index.js +1 -1
- package/dist/{paintDispatch-Cb_hQj4Y.d.ts → paintDispatch-BjZjUbcb.d.cts} +63 -69
- package/dist/{paintDispatch-Cb_hQj4Y.d.cts → paintDispatch-CxdLjHQq.d.ts} +63 -69
- package/dist/providers/index.cjs +2734 -152
- package/dist/providers/index.d.cts +2 -1
- package/dist/providers/index.d.ts +2 -1
- package/dist/providers/index.js +2734 -152
- package/dist/runtime/index.cjs +2741 -158
- package/dist/runtime/index.d.cts +2 -2
- package/dist/runtime/index.d.ts +2 -2
- package/dist/runtime/index.js +2741 -158
- package/dist/types-B3nHgnMG.d.cts +51 -0
- package/dist/types-B3nHgnMG.d.ts +51 -0
- package/package.json +3 -3
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);
|
|
@@ -10301,9 +10301,10 @@ function Canvas({
|
|
|
10301
10301
|
drawables: [...drawables ?? [], ...childDrawables],
|
|
10302
10302
|
isLoading,
|
|
10303
10303
|
cameraMode: to3DCameraMode(camera?.mode),
|
|
10304
|
-
...zoom !== void 0 ? {
|
|
10304
|
+
...zoom !== void 0 ? { zoom } : {},
|
|
10305
10305
|
...camera?.fov !== void 0 ? { fov: camera.fov } : {},
|
|
10306
10306
|
...camera?.azimuth !== void 0 ? { azimuth: camera.azimuth } : {},
|
|
10307
|
+
...camera?.elevation !== void 0 ? { elevation: camera.elevation } : {},
|
|
10307
10308
|
...camera?.target !== void 0 ? { followTarget: camera.target } : {},
|
|
10308
10309
|
unitScale,
|
|
10309
10310
|
backgroundColor,
|
|
@@ -11571,6 +11572,14 @@ function shapeBounds(shape) {
|
|
|
11571
11572
|
w: shape.radius * 2 + 8,
|
|
11572
11573
|
h: shape.radius * 2 + 8
|
|
11573
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
|
+
};
|
|
11574
11583
|
case "rect":
|
|
11575
11584
|
if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) return null;
|
|
11576
11585
|
return { x: shape.x - 4, y: shape.y - 4, w: shape.width + 8, h: shape.height + 8 };
|
|
@@ -11604,13 +11613,14 @@ function drawArrowHead(ctx, x1, y1, x2, y2, size) {
|
|
|
11604
11613
|
ctx.closePath();
|
|
11605
11614
|
ctx.fill();
|
|
11606
11615
|
}
|
|
11607
|
-
function drawShape(ctx, shape, width, height) {
|
|
11616
|
+
function drawShape(ctx, shape, width, height, allShapes) {
|
|
11608
11617
|
ctx.save();
|
|
11609
11618
|
const opacity = shape.opacity ?? 1;
|
|
11610
11619
|
ctx.globalAlpha = opacity;
|
|
11611
11620
|
const stroke = resolveColor2(shape.color, ctx, "#333333");
|
|
11612
11621
|
const fill = shape.fill ? resolveColor2(shape.fill, ctx, "#cccccc") : void 0;
|
|
11613
11622
|
ctx.lineWidth = shape.lineWidth ?? 2;
|
|
11623
|
+
if (shape.dash) ctx.setLineDash([...DASH_PATTERNS[shape.dash]]);
|
|
11614
11624
|
switch (shape.type) {
|
|
11615
11625
|
case "grid": {
|
|
11616
11626
|
const step = shape.step ?? 40;
|
|
@@ -11676,6 +11686,20 @@ function drawShape(ctx, shape, width, height) {
|
|
|
11676
11686
|
ctx.stroke();
|
|
11677
11687
|
break;
|
|
11678
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
|
+
}
|
|
11679
11703
|
case "rect": {
|
|
11680
11704
|
if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) break;
|
|
11681
11705
|
if (fill) {
|
|
@@ -11722,21 +11746,153 @@ function drawShape(ctx, shape, width, height) {
|
|
|
11722
11746
|
ctx.fillText(shape.text, shape.x, shape.y);
|
|
11723
11747
|
break;
|
|
11724
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
|
+
}
|
|
11725
11782
|
}
|
|
11726
11783
|
ctx.restore();
|
|
11727
11784
|
}
|
|
11728
|
-
|
|
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;
|
|
11729
11881
|
var init_LearningCanvas = __esm({
|
|
11730
11882
|
"components/learning/atoms/LearningCanvas.tsx"() {
|
|
11731
11883
|
"use client";
|
|
11732
11884
|
init_cn();
|
|
11733
11885
|
init_useEventBus();
|
|
11886
|
+
DASH_PATTERNS = { dashed: [6, 4], dotted: [2, 3] };
|
|
11887
|
+
TRACE_SERIES_COLORS = ["#2563eb", "#dc2626", "#16a34a", "#f59e0b"];
|
|
11734
11888
|
LearningCanvas = ({
|
|
11735
11889
|
className,
|
|
11736
11890
|
width = 600,
|
|
11737
11891
|
height = 400,
|
|
11738
11892
|
backgroundColor,
|
|
11739
11893
|
shapes = [],
|
|
11894
|
+
readouts,
|
|
11895
|
+
traces,
|
|
11740
11896
|
interactive = false,
|
|
11741
11897
|
animate = false,
|
|
11742
11898
|
onShapeClick,
|
|
@@ -11762,6 +11918,12 @@ var init_LearningCanvas = __esm({
|
|
|
11762
11918
|
}
|
|
11763
11919
|
return -1;
|
|
11764
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]);
|
|
11765
11927
|
const draw = useCallback(() => {
|
|
11766
11928
|
const canvas = canvasRef.current;
|
|
11767
11929
|
if (!canvas) return;
|
|
@@ -11778,13 +11940,13 @@ var init_LearningCanvas = __esm({
|
|
|
11778
11940
|
ctx.fillStyle = backgroundColor;
|
|
11779
11941
|
ctx.fillRect(0, 0, width, height);
|
|
11780
11942
|
}
|
|
11781
|
-
for (const shape of
|
|
11782
|
-
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);
|
|
11783
11945
|
}
|
|
11784
|
-
for (const shape of
|
|
11785
|
-
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);
|
|
11786
11948
|
}
|
|
11787
|
-
}, [width, height, backgroundColor,
|
|
11949
|
+
}, [width, height, backgroundColor, derivedShapes]);
|
|
11788
11950
|
useEffect(() => {
|
|
11789
11951
|
draw();
|
|
11790
11952
|
}, [draw]);
|
|
@@ -13717,7 +13879,363 @@ var init_ComponentPatterns = __esm({
|
|
|
13717
13879
|
AlertPattern.displayName = "AlertPattern";
|
|
13718
13880
|
}
|
|
13719
13881
|
});
|
|
13720
|
-
|
|
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;
|
|
13721
14239
|
var init_AlgorithmCanvas = __esm({
|
|
13722
14240
|
"components/learning/molecules/AlgorithmCanvas.tsx"() {
|
|
13723
14241
|
"use client";
|
|
@@ -13729,6 +14247,43 @@ var init_AlgorithmCanvas = __esm({
|
|
|
13729
14247
|
DEFAULT_POINTER_COLOR = "#dc2626";
|
|
13730
14248
|
POINTER_BAND = 34;
|
|
13731
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;
|
|
13732
14287
|
AlgorithmCanvas = ({
|
|
13733
14288
|
className,
|
|
13734
14289
|
width = 600,
|
|
@@ -13738,6 +14293,14 @@ var init_AlgorithmCanvas = __esm({
|
|
|
13738
14293
|
bars = [],
|
|
13739
14294
|
cells = [],
|
|
13740
14295
|
pointers = [],
|
|
14296
|
+
ranges = [],
|
|
14297
|
+
slots = [],
|
|
14298
|
+
slotOrientation = "horizontal",
|
|
14299
|
+
frames = [],
|
|
14300
|
+
buckets = [],
|
|
14301
|
+
auxBars = [],
|
|
14302
|
+
rowLabels = [],
|
|
14303
|
+
colLabels = [],
|
|
13741
14304
|
shapes = [],
|
|
13742
14305
|
interactive = false,
|
|
13743
14306
|
animate = false,
|
|
@@ -13747,12 +14310,35 @@ var init_AlgorithmCanvas = __esm({
|
|
|
13747
14310
|
}) => {
|
|
13748
14311
|
const derivedShapes = useMemo(() => {
|
|
13749
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
|
+
});
|
|
13750
14330
|
if (bars.length > 0) {
|
|
14331
|
+
const panelYBars = panelY.bars;
|
|
13751
14332
|
const slot = width / bars.length;
|
|
13752
14333
|
const barW = slot * 0.8;
|
|
13753
14334
|
const gap = slot * 0.1;
|
|
13754
|
-
const
|
|
13755
|
-
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);
|
|
13756
14342
|
const maxV = Math.max(1, ...bars.map((b) => Number.isFinite(b.value) ? b.value : 0));
|
|
13757
14343
|
bars.forEach((bar, i) => {
|
|
13758
14344
|
const v = Number.isFinite(bar.value) ? bar.value : 0;
|
|
@@ -13782,6 +14368,89 @@ var init_AlgorithmCanvas = __esm({
|
|
|
13782
14368
|
});
|
|
13783
14369
|
}
|
|
13784
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
|
+
}
|
|
13785
14454
|
pointers.forEach((p) => {
|
|
13786
14455
|
if (p.index < 0 || p.index >= bars.length) return;
|
|
13787
14456
|
const cx = p.index * slot + slot / 2;
|
|
@@ -13789,7 +14458,7 @@ var init_AlgorithmCanvas = __esm({
|
|
|
13789
14458
|
out.push({
|
|
13790
14459
|
type: "arrow",
|
|
13791
14460
|
x1: cx,
|
|
13792
|
-
y1:
|
|
14461
|
+
y1: panelYBars + primaryH - 18,
|
|
13793
14462
|
x2: cx,
|
|
13794
14463
|
y2: baseline + 4,
|
|
13795
14464
|
color,
|
|
@@ -13799,7 +14468,7 @@ var init_AlgorithmCanvas = __esm({
|
|
|
13799
14468
|
out.push({
|
|
13800
14469
|
type: "text",
|
|
13801
14470
|
x: cx,
|
|
13802
|
-
y:
|
|
14471
|
+
y: panelYBars + primaryH - 8,
|
|
13803
14472
|
text: p.label,
|
|
13804
14473
|
color,
|
|
13805
14474
|
fontSize: 11,
|
|
@@ -13808,14 +14477,111 @@ var init_AlgorithmCanvas = __esm({
|
|
|
13808
14477
|
}
|
|
13809
14478
|
});
|
|
13810
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
|
+
}
|
|
13811
14572
|
if (cells.length > 0) {
|
|
14573
|
+
const panelYCells = panelY.cells;
|
|
13812
14574
|
const maxCol = Math.max(0, ...cells.map((c) => c.col)) + 1;
|
|
13813
14575
|
const maxRow = Math.max(0, ...cells.map((c) => c.row)) + 1;
|
|
13814
|
-
const
|
|
13815
|
-
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;
|
|
13816
14582
|
cells.forEach((c, i) => {
|
|
13817
|
-
const x = c.col * cw;
|
|
13818
|
-
const y = c.row * ch;
|
|
14583
|
+
const x = gridX0 + c.col * cw;
|
|
14584
|
+
const y = gridY0 + c.row * ch;
|
|
13819
14585
|
const color = c.color ?? DEFAULT_CELL_COLOR;
|
|
13820
14586
|
out.push({
|
|
13821
14587
|
type: "rect",
|
|
@@ -13839,11 +14605,207 @@ var init_AlgorithmCanvas = __esm({
|
|
|
13839
14605
|
align: "center"
|
|
13840
14606
|
});
|
|
13841
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
|
+
}
|
|
13842
14804
|
});
|
|
13843
14805
|
}
|
|
13844
14806
|
out.push(...shapes);
|
|
13845
14807
|
return out;
|
|
13846
|
-
}, [bars, cells, pointers, shapes, width, height]);
|
|
14808
|
+
}, [bars, cells, pointers, ranges, slots, slotOrientation, frames, buckets, auxBars, rowLabels, colLabels, shapes, width, height]);
|
|
13847
14809
|
return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
|
|
13848
14810
|
title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
|
|
13849
14811
|
/* @__PURE__ */ jsx(
|
|
@@ -14832,23 +15794,422 @@ var init_BehaviorView = __esm({
|
|
|
14832
15794
|
BehaviorView.displayName = "BehaviorView";
|
|
14833
15795
|
}
|
|
14834
15796
|
});
|
|
14835
|
-
|
|
15797
|
+
function LearningScene3D({
|
|
15798
|
+
className,
|
|
15799
|
+
width = 600,
|
|
15800
|
+
height = 400,
|
|
15801
|
+
title,
|
|
15802
|
+
backgroundColor,
|
|
15803
|
+
drawables,
|
|
15804
|
+
camera,
|
|
15805
|
+
lighting,
|
|
15806
|
+
post,
|
|
15807
|
+
showGrid = false,
|
|
15808
|
+
shadows,
|
|
15809
|
+
interactive,
|
|
15810
|
+
isLoading,
|
|
15811
|
+
error,
|
|
15812
|
+
onItemClick
|
|
15813
|
+
}) {
|
|
15814
|
+
const instanceId = useId().replace(/[^a-zA-Z0-9]/g, "");
|
|
15815
|
+
const clickEvent = onItemClick ? `LEARNING_SCENE_3D.ITEM_CLICK.${instanceId}` : void 0;
|
|
15816
|
+
const onItemClickRef = useRef(onItemClick);
|
|
15817
|
+
onItemClickRef.current = onItemClick;
|
|
15818
|
+
useEventListener(`UI:${clickEvent ?? "LEARNING_SCENE_3D.ITEM_CLICK.__disabled"}`, (event) => {
|
|
15819
|
+
const unitId = event.payload?.unitId;
|
|
15820
|
+
if (typeof unitId === "string") onItemClickRef.current?.(unitId);
|
|
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
|
+
}
|
|
15828
|
+
const props3d = {
|
|
15829
|
+
drawables,
|
|
15830
|
+
isLoading,
|
|
15831
|
+
error: error ? error.message : null,
|
|
15832
|
+
cameraMode: camera?.mode ?? "perspective",
|
|
15833
|
+
...camera?.zoom !== void 0 ? { zoom: camera.zoom } : {},
|
|
15834
|
+
...camera?.fov !== void 0 ? { fov: camera.fov } : {},
|
|
15835
|
+
...camera?.azimuth !== void 0 ? { azimuth: camera.azimuth } : {},
|
|
15836
|
+
...camera?.elevation !== void 0 ? { elevation: camera.elevation } : {},
|
|
15837
|
+
...camera?.target !== void 0 ? { followTarget: camera.target } : {},
|
|
15838
|
+
backgroundColor,
|
|
15839
|
+
showGrid,
|
|
15840
|
+
...shadows !== void 0 ? { shadows } : {},
|
|
15841
|
+
...interactive !== void 0 ? { controlsEnabled: interactive } : {},
|
|
15842
|
+
lighting,
|
|
15843
|
+
post,
|
|
15844
|
+
...clickEvent !== void 0 ? { unitClickEvent: clickEvent } : {}
|
|
15845
|
+
};
|
|
15846
|
+
return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
|
|
15847
|
+
title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
|
|
15848
|
+
/* @__PURE__ */ jsx("div", { style: { width, height, display: "flex" }, children: /* @__PURE__ */ jsx(Suspense, { fallback: null, children: /* @__PURE__ */ jsx(Canvas3DHost2, { ...props3d }) }) })
|
|
15849
|
+
] }) });
|
|
15850
|
+
}
|
|
15851
|
+
function meshSphere(id, x, y, z, radius, color, opts) {
|
|
15852
|
+
return {
|
|
15853
|
+
type: "draw-mesh",
|
|
15854
|
+
...id !== void 0 ? { id } : {},
|
|
15855
|
+
shape: opts?.shape ?? "sphere",
|
|
15856
|
+
position: { x, y, z },
|
|
15857
|
+
radius,
|
|
15858
|
+
pivot: "center",
|
|
15859
|
+
segments: opts?.segments ?? 24,
|
|
15860
|
+
material: { color, ...opts?.material },
|
|
15861
|
+
...opts?.opacity !== void 0 ? { opacity: opts.opacity } : {}
|
|
15862
|
+
};
|
|
15863
|
+
}
|
|
15864
|
+
function axisRotation(from, to) {
|
|
15865
|
+
const wx = to[0] - from[0];
|
|
15866
|
+
const wy = to[2] - from[2];
|
|
15867
|
+
const wz = to[1] - from[1];
|
|
15868
|
+
const len = Math.sqrt(wx * wx + wy * wy + wz * wz);
|
|
15869
|
+
const tilt = Math.acos(Math.min(1, Math.max(-1, len > 0 ? wy / len : 1)));
|
|
15870
|
+
return [0, Math.atan2(wz, -wx), tilt];
|
|
15871
|
+
}
|
|
15872
|
+
function segmentLength(from, to) {
|
|
15873
|
+
const dx = to[0] - from[0];
|
|
15874
|
+
const dy = to[1] - from[1];
|
|
15875
|
+
const dz = to[2] - from[2];
|
|
15876
|
+
return Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
15877
|
+
}
|
|
15878
|
+
function cylinderBetween(from, to, radius, color) {
|
|
15879
|
+
const len = segmentLength(from, to);
|
|
15880
|
+
if (len < 1e-6) return null;
|
|
15881
|
+
return {
|
|
15882
|
+
type: "draw-mesh",
|
|
15883
|
+
shape: "cylinder",
|
|
15884
|
+
position: { x: (from[0] + to[0]) / 2, y: (from[1] + to[1]) / 2, z: (from[2] + to[2]) / 2 },
|
|
15885
|
+
radius,
|
|
15886
|
+
height: len,
|
|
15887
|
+
rotation: axisRotation(from, to),
|
|
15888
|
+
pivot: "center",
|
|
15889
|
+
segments: 12,
|
|
15890
|
+
material: { color }
|
|
15891
|
+
};
|
|
15892
|
+
}
|
|
15893
|
+
function arrowBetween(from, to, color, shaftRadius = 0.08, id) {
|
|
15894
|
+
const len = segmentLength(from, to);
|
|
15895
|
+
if (len < 1e-6) return null;
|
|
15896
|
+
const tipLen = Math.min(shaftRadius * 8, len * 0.35);
|
|
15897
|
+
const k = (len - tipLen) / len;
|
|
15898
|
+
const delta = [to[0] - from[0], to[1] - from[1], to[2] - from[2]];
|
|
15899
|
+
const shaftEnd = [delta[0] * k, delta[1] * k, delta[2] * k];
|
|
15900
|
+
const tipStart = shaftEnd;
|
|
15901
|
+
const tipEnd = delta;
|
|
15902
|
+
const shaft = cylinderBetween([0, 0, 0], shaftEnd, shaftRadius, color);
|
|
15903
|
+
const tipLenActual = segmentLength(tipStart, tipEnd);
|
|
15904
|
+
const tip = {
|
|
15905
|
+
type: "draw-mesh",
|
|
15906
|
+
shape: "cone",
|
|
15907
|
+
position: {
|
|
15908
|
+
x: (tipStart[0] + tipEnd[0]) / 2,
|
|
15909
|
+
y: (tipStart[1] + tipEnd[1]) / 2,
|
|
15910
|
+
z: (tipStart[2] + tipEnd[2]) / 2
|
|
15911
|
+
},
|
|
15912
|
+
radius: shaftRadius * 3,
|
|
15913
|
+
height: tipLenActual,
|
|
15914
|
+
rotation: axisRotation(tipStart, tipEnd),
|
|
15915
|
+
pivot: "center",
|
|
15916
|
+
segments: 12,
|
|
15917
|
+
material: { color }
|
|
15918
|
+
};
|
|
15919
|
+
return {
|
|
15920
|
+
type: "draw-group",
|
|
15921
|
+
...id !== void 0 ? { id } : {},
|
|
15922
|
+
position: { x: from[0], y: from[1], z: from[2] },
|
|
15923
|
+
items: tipLenActual < 1e-6 ? shaft ? [shaft] : [] : shaft ? [shaft, tip] : [tip]
|
|
15924
|
+
};
|
|
15925
|
+
}
|
|
15926
|
+
function billboardLabel(text, x, y, z, opts) {
|
|
15927
|
+
return {
|
|
15928
|
+
type: "draw-text",
|
|
15929
|
+
text,
|
|
15930
|
+
position: { x, y, z },
|
|
15931
|
+
color: opts?.color ?? "#111827"
|
|
15932
|
+
};
|
|
15933
|
+
}
|
|
15934
|
+
function labelColorForBackground(backgroundColor) {
|
|
15935
|
+
const m = /^#(?:([0-9a-f]{3})|([0-9a-f]{6}))$/i.exec(backgroundColor ?? "");
|
|
15936
|
+
if (!m) return "#111827";
|
|
15937
|
+
const hex = m[1] !== void 0 ? m[1].split("").map((c) => c + c).join("") : m[2];
|
|
15938
|
+
const r = parseInt(hex.slice(0, 2), 16) / 255;
|
|
15939
|
+
const g = parseInt(hex.slice(2, 4), 16) / 255;
|
|
15940
|
+
const b = parseInt(hex.slice(4, 6), 16) / 255;
|
|
15941
|
+
const luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
15942
|
+
return luminance < 0.5 ? "#e5e7eb" : "#111827";
|
|
15943
|
+
}
|
|
15944
|
+
function get3DClickPayload(onShapeClick, idToIndex) {
|
|
15945
|
+
if (!onShapeClick) return void 0;
|
|
15946
|
+
return (id) => onShapeClick({ id, index: idToIndex.get(id) ?? -1 });
|
|
15947
|
+
}
|
|
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;
|
|
16155
|
+
var init_learningScene3D = __esm({
|
|
16156
|
+
"components/learning/molecules/learningScene3D.tsx"() {
|
|
16157
|
+
"use client";
|
|
16158
|
+
init_atoms();
|
|
16159
|
+
init_Stack();
|
|
16160
|
+
init_useEventBus();
|
|
16161
|
+
sceneLog = createLogger("almadar:ui:learning-scene-3d");
|
|
16162
|
+
KNOWN_BLOOM_KEYS = /* @__PURE__ */ new Set(["intensity", "threshold", "smoothing"]);
|
|
16163
|
+
Canvas3DHost2 = lazy(
|
|
16164
|
+
() => import('@almadar/ui/components/molecules/game/three').then((m) => ({ default: m.Canvas3DHost }))
|
|
16165
|
+
);
|
|
16166
|
+
LearningScene3D.displayName = "LearningScene3D";
|
|
16167
|
+
}
|
|
16168
|
+
});
|
|
16169
|
+
var biologyLog, BIO_BAND_COLORS, BIO_STAGE_FILL, BIO_STAGE_TEXT, BiologyCanvas;
|
|
14836
16170
|
var init_BiologyCanvas = __esm({
|
|
14837
16171
|
"components/learning/molecules/BiologyCanvas.tsx"() {
|
|
14838
16172
|
"use client";
|
|
14839
16173
|
init_atoms();
|
|
14840
16174
|
init_Stack();
|
|
14841
16175
|
init_LearningCanvas();
|
|
16176
|
+
init_learningScene3D();
|
|
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
|
+
};
|
|
14842
16189
|
BiologyCanvas = ({
|
|
14843
16190
|
className,
|
|
14844
16191
|
width = 600,
|
|
14845
16192
|
height = 400,
|
|
14846
16193
|
title,
|
|
14847
16194
|
backgroundColor,
|
|
16195
|
+
mode = "2d",
|
|
16196
|
+
camera,
|
|
16197
|
+
lighting,
|
|
16198
|
+
post,
|
|
14848
16199
|
nodes = [],
|
|
14849
16200
|
edges = [],
|
|
16201
|
+
compartments = [],
|
|
16202
|
+
bands = [],
|
|
16203
|
+
stages = [],
|
|
16204
|
+
stageStyle = "timeline",
|
|
16205
|
+
helix,
|
|
16206
|
+
helix3d,
|
|
14850
16207
|
shapes = [],
|
|
14851
|
-
|
|
16208
|
+
readouts,
|
|
16209
|
+
traces,
|
|
16210
|
+
showGrid,
|
|
16211
|
+
shadows,
|
|
16212
|
+
interactive,
|
|
14852
16213
|
animate = false,
|
|
14853
16214
|
onShapeClick,
|
|
14854
16215
|
isLoading,
|
|
@@ -14860,19 +16221,148 @@ var init_BiologyCanvas = __esm({
|
|
|
14860
16221
|
for (const n of nodes) {
|
|
14861
16222
|
if (n.id) nodeById.set(n.id, n);
|
|
14862
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
|
+
}
|
|
14863
16333
|
for (const e of edges) {
|
|
14864
16334
|
const a = nodeById.get(e.from);
|
|
14865
16335
|
const b = nodeById.get(e.to);
|
|
14866
16336
|
if (!a || !b) continue;
|
|
14867
|
-
|
|
14868
|
-
|
|
14869
|
-
|
|
14870
|
-
|
|
14871
|
-
|
|
14872
|
-
|
|
14873
|
-
|
|
14874
|
-
|
|
14875
|
-
|
|
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
|
+
}
|
|
14876
16366
|
if (e.label) {
|
|
14877
16367
|
out.push({
|
|
14878
16368
|
type: "text",
|
|
@@ -14885,6 +16375,8 @@ var init_BiologyCanvas = __esm({
|
|
|
14885
16375
|
}
|
|
14886
16376
|
}
|
|
14887
16377
|
for (const n of nodes) {
|
|
16378
|
+
const state = n.state ?? "default";
|
|
16379
|
+
const muted = state === "muted";
|
|
14888
16380
|
out.push({
|
|
14889
16381
|
type: "circle",
|
|
14890
16382
|
x: n.x,
|
|
@@ -14892,23 +16384,197 @@ var init_BiologyCanvas = __esm({
|
|
|
14892
16384
|
radius: n.radius ?? 16,
|
|
14893
16385
|
color: n.color ?? "#16a34a",
|
|
14894
16386
|
fill: `${n.color ?? "#16a34a"}33`,
|
|
14895
|
-
id: n.id
|
|
16387
|
+
id: n.id,
|
|
16388
|
+
...muted ? { opacity: 0.35 } : {}
|
|
14896
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
|
+
}
|
|
14897
16400
|
if (n.label) {
|
|
14898
16401
|
out.push({
|
|
14899
16402
|
type: "text",
|
|
14900
16403
|
x: n.x,
|
|
14901
16404
|
y: n.y + (n.radius ?? 16) + 14,
|
|
14902
16405
|
text: n.label,
|
|
16406
|
+
...muted ? { opacity: 0.35 } : {},
|
|
14903
16407
|
color: "#111827",
|
|
14904
16408
|
fontSize: 12,
|
|
14905
16409
|
align: "center"
|
|
14906
16410
|
});
|
|
14907
16411
|
}
|
|
14908
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
|
+
}
|
|
14909
16489
|
out.push(...shapes);
|
|
14910
16490
|
return out;
|
|
14911
|
-
}, [nodes, edges, shapes]);
|
|
16491
|
+
}, [nodes, edges, compartments, bands, stages, stageStyle, helix, shapes, width, height]);
|
|
16492
|
+
const drawables3D = useMemo(() => {
|
|
16493
|
+
if (mode !== "3d") return [];
|
|
16494
|
+
if (shapes.length > 0) {
|
|
16495
|
+
biologyLog.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
|
|
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
|
+
}
|
|
16508
|
+
const out = [];
|
|
16509
|
+
const labelColor = labelColorForBackground(backgroundColor);
|
|
16510
|
+
const nodeById = /* @__PURE__ */ new Map();
|
|
16511
|
+
for (const n of nodes) {
|
|
16512
|
+
if (n.id) nodeById.set(n.id, n);
|
|
16513
|
+
}
|
|
16514
|
+
for (const e of edges) {
|
|
16515
|
+
const a = nodeById.get(e.from);
|
|
16516
|
+
const b = nodeById.get(e.to);
|
|
16517
|
+
if (!a || !b) continue;
|
|
16518
|
+
const edgeRadius = Math.max(0.04, Math.min(a.radius ?? 0.5, b.radius ?? 0.5) * 0.12);
|
|
16519
|
+
const edge = cylinderBetween([a.x, a.y, a.z ?? 0], [b.x, b.y, b.z ?? 0], edgeRadius, e.color ?? "#9ca3af");
|
|
16520
|
+
if (edge) out.push(edge);
|
|
16521
|
+
if (e.label) {
|
|
16522
|
+
out.push(
|
|
16523
|
+
billboardLabel(
|
|
16524
|
+
e.label,
|
|
16525
|
+
(a.x + b.x) / 2,
|
|
16526
|
+
(a.y + b.y) / 2,
|
|
16527
|
+
((a.z ?? 0) + (b.z ?? 0)) / 2,
|
|
16528
|
+
{ color: labelColor }
|
|
16529
|
+
)
|
|
16530
|
+
);
|
|
16531
|
+
}
|
|
16532
|
+
}
|
|
16533
|
+
for (const n of nodes) {
|
|
16534
|
+
const radius = n.radius ?? 0.5;
|
|
16535
|
+
const nz = n.z ?? 0;
|
|
16536
|
+
out.push(meshSphere(n.id, n.x, n.y, nz, radius, n.color ?? "#16a34a", { shape: n.shape, ...n.opacity !== void 0 ? { opacity: n.opacity } : {} }));
|
|
16537
|
+
if (n.label) {
|
|
16538
|
+
out.push(billboardLabel(n.label, n.x, n.y, nz + radius, { color: labelColor }));
|
|
16539
|
+
}
|
|
16540
|
+
}
|
|
16541
|
+
if (helix3d) {
|
|
16542
|
+
out.push(...helixDrawables(helix3d, { labelColor }));
|
|
16543
|
+
}
|
|
16544
|
+
return out;
|
|
16545
|
+
}, [mode, nodes, edges, shapes, compartments, bands, stages, helix, helix3d, animate, backgroundColor]);
|
|
16546
|
+
const nodeIndexById = useMemo(() => {
|
|
16547
|
+
const m = /* @__PURE__ */ new Map();
|
|
16548
|
+
(helix3d?.rungs ?? []).forEach((rung, i) => {
|
|
16549
|
+
if (rung.id) m.set(rung.id, i);
|
|
16550
|
+
});
|
|
16551
|
+
nodes.forEach((n, i) => {
|
|
16552
|
+
if (n.id) m.set(n.id, i);
|
|
16553
|
+
});
|
|
16554
|
+
return m;
|
|
16555
|
+
}, [nodes, helix3d]);
|
|
16556
|
+
if (mode === "3d") {
|
|
16557
|
+
return /* @__PURE__ */ jsx(
|
|
16558
|
+
LearningScene3D,
|
|
16559
|
+
{
|
|
16560
|
+
className,
|
|
16561
|
+
width,
|
|
16562
|
+
height,
|
|
16563
|
+
title,
|
|
16564
|
+
backgroundColor,
|
|
16565
|
+
drawables: drawables3D,
|
|
16566
|
+
camera,
|
|
16567
|
+
lighting,
|
|
16568
|
+
post,
|
|
16569
|
+
showGrid,
|
|
16570
|
+
shadows,
|
|
16571
|
+
interactive,
|
|
16572
|
+
isLoading,
|
|
16573
|
+
error,
|
|
16574
|
+
onItemClick: get3DClickPayload(onShapeClick, nodeIndexById)
|
|
16575
|
+
}
|
|
16576
|
+
);
|
|
16577
|
+
}
|
|
14912
16578
|
return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
|
|
14913
16579
|
title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
|
|
14914
16580
|
/* @__PURE__ */ jsx(
|
|
@@ -14918,7 +16584,9 @@ var init_BiologyCanvas = __esm({
|
|
|
14918
16584
|
height,
|
|
14919
16585
|
backgroundColor,
|
|
14920
16586
|
shapes: derivedShapes,
|
|
14921
|
-
|
|
16587
|
+
readouts,
|
|
16588
|
+
traces,
|
|
16589
|
+
interactive: interactive ?? false,
|
|
14922
16590
|
animate,
|
|
14923
16591
|
onShapeClick,
|
|
14924
16592
|
isLoading,
|
|
@@ -21758,24 +23426,55 @@ var init_ChatBar = __esm({
|
|
|
21758
23426
|
ChatBar.displayName = "ChatBar";
|
|
21759
23427
|
}
|
|
21760
23428
|
});
|
|
21761
|
-
|
|
23429
|
+
function bondPerpendicular(a, b) {
|
|
23430
|
+
const dx = b[0] - a[0];
|
|
23431
|
+
const dy = b[1] - a[1];
|
|
23432
|
+
const px = dy;
|
|
23433
|
+
const py = -dx;
|
|
23434
|
+
const len = Math.sqrt(px * px + py * py);
|
|
23435
|
+
if (len < 1e-6) return [1, 0, 0];
|
|
23436
|
+
return [px / len, py / len, 0];
|
|
23437
|
+
}
|
|
23438
|
+
var chemistryLog, CHEM_BOND_STATE_COLOR, LONE_PAIR_ANGLES, ChemistryCanvas;
|
|
21762
23439
|
var init_ChemistryCanvas = __esm({
|
|
21763
23440
|
"components/learning/molecules/ChemistryCanvas.tsx"() {
|
|
21764
23441
|
"use client";
|
|
21765
23442
|
init_atoms();
|
|
21766
23443
|
init_Stack();
|
|
21767
23444
|
init_LearningCanvas();
|
|
23445
|
+
init_learningScene3D();
|
|
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];
|
|
21768
23454
|
ChemistryCanvas = ({
|
|
21769
23455
|
className,
|
|
21770
23456
|
width = 600,
|
|
21771
23457
|
height = 400,
|
|
21772
23458
|
title,
|
|
21773
23459
|
backgroundColor,
|
|
23460
|
+
mode = "2d",
|
|
23461
|
+
camera,
|
|
23462
|
+
lighting,
|
|
23463
|
+
post,
|
|
21774
23464
|
atoms = [],
|
|
21775
23465
|
bonds = [],
|
|
21776
23466
|
arrows = [],
|
|
23467
|
+
bondStyle = "thick",
|
|
23468
|
+
containers = [],
|
|
23469
|
+
equation,
|
|
23470
|
+
equationColor,
|
|
23471
|
+
lattice3d,
|
|
21777
23472
|
shapes = [],
|
|
21778
|
-
|
|
23473
|
+
readouts,
|
|
23474
|
+
traces,
|
|
23475
|
+
showGrid,
|
|
23476
|
+
shadows,
|
|
23477
|
+
interactive,
|
|
21779
23478
|
animate = false,
|
|
21780
23479
|
onShapeClick,
|
|
21781
23480
|
isLoading,
|
|
@@ -21787,21 +23486,118 @@ var init_ChemistryCanvas = __esm({
|
|
|
21787
23486
|
for (const a of atoms) {
|
|
21788
23487
|
if (a.id) atomById.set(a.id, a);
|
|
21789
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
|
+
}
|
|
21790
23560
|
for (const b of bonds) {
|
|
21791
23561
|
const a = atomById.get(b.from);
|
|
21792
23562
|
const c = atomById.get(b.to);
|
|
21793
23563
|
if (!a || !c) continue;
|
|
21794
|
-
const
|
|
21795
|
-
const
|
|
21796
|
-
|
|
21797
|
-
|
|
21798
|
-
|
|
21799
|
-
|
|
21800
|
-
|
|
21801
|
-
|
|
21802
|
-
|
|
21803
|
-
|
|
21804
|
-
|
|
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
|
+
}
|
|
21805
23601
|
}
|
|
21806
23602
|
for (const a of arrows) {
|
|
21807
23603
|
const angle = (a.angle ?? 0) * (Math.PI / 180);
|
|
@@ -21850,10 +23646,142 @@ var init_ChemistryCanvas = __esm({
|
|
|
21850
23646
|
align: "center"
|
|
21851
23647
|
});
|
|
21852
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
|
+
});
|
|
21853
23690
|
}
|
|
21854
23691
|
out.push(...shapes);
|
|
21855
23692
|
return out;
|
|
21856
|
-
}, [atoms, bonds, arrows, shapes]);
|
|
23693
|
+
}, [atoms, bonds, arrows, bondStyle, containers, equation, equationColor, shapes, width]);
|
|
23694
|
+
const drawables3D = useMemo(() => {
|
|
23695
|
+
if (mode !== "3d") return [];
|
|
23696
|
+
if (shapes.length > 0) {
|
|
23697
|
+
chemistryLog.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
|
|
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
|
+
}
|
|
23705
|
+
const out = [];
|
|
23706
|
+
const labelColor = labelColorForBackground(backgroundColor);
|
|
23707
|
+
const atomById = /* @__PURE__ */ new Map();
|
|
23708
|
+
for (const a of atoms) {
|
|
23709
|
+
if (a.id) atomById.set(a.id, a);
|
|
23710
|
+
}
|
|
23711
|
+
for (const b of bonds) {
|
|
23712
|
+
const a = atomById.get(b.from);
|
|
23713
|
+
const c = atomById.get(b.to);
|
|
23714
|
+
if (!a || !c) continue;
|
|
23715
|
+
const color = b.color ?? "#6b7280";
|
|
23716
|
+
const from = [a.x, a.y, a.z ?? 0];
|
|
23717
|
+
const to = [c.x, c.y, c.z ?? 0];
|
|
23718
|
+
const perp = bondPerpendicular(from, to);
|
|
23719
|
+
const bondRadius = Math.max(0.06, Math.min(a.radius ?? 0.45, c.radius ?? 0.45) * 0.22);
|
|
23720
|
+
const step = bondRadius * 2.2;
|
|
23721
|
+
const offsets = b.type === "double" ? [-step, step] : b.type === "triple" ? [-step, 0, step] : [0];
|
|
23722
|
+
for (const off of offsets) {
|
|
23723
|
+
const bond = cylinderBetween(
|
|
23724
|
+
[from[0] + perp[0] * off, from[1] + perp[1] * off, from[2] + perp[2] * off],
|
|
23725
|
+
[to[0] + perp[0] * off, to[1] + perp[1] * off, to[2] + perp[2] * off],
|
|
23726
|
+
bondRadius,
|
|
23727
|
+
color
|
|
23728
|
+
);
|
|
23729
|
+
if (bond) out.push(bond);
|
|
23730
|
+
}
|
|
23731
|
+
}
|
|
23732
|
+
for (const a of arrows) {
|
|
23733
|
+
const angle = (a.angle ?? 0) * (Math.PI / 180);
|
|
23734
|
+
const len = a.length ?? 60;
|
|
23735
|
+
const x2 = a.x + Math.cos(angle) * len;
|
|
23736
|
+
const y2 = a.y + Math.sin(angle) * len;
|
|
23737
|
+
const arrow = arrowBetween([a.x, a.y, 0], [x2, y2, 0], a.color ?? "#dc2626");
|
|
23738
|
+
if (arrow) out.push(arrow);
|
|
23739
|
+
if (a.label) {
|
|
23740
|
+
out.push(billboardLabel(a.label, (a.x + x2) / 2, (a.y + y2) / 2, 0, { color: labelColor }));
|
|
23741
|
+
}
|
|
23742
|
+
}
|
|
23743
|
+
for (const a of atoms) {
|
|
23744
|
+
const radius = a.radius ?? 0.45;
|
|
23745
|
+
const az = a.z ?? 0;
|
|
23746
|
+
out.push(meshSphere(a.id, a.x, a.y, az, radius, a.color ?? "#2563eb"));
|
|
23747
|
+
if (a.element) {
|
|
23748
|
+
out.push(billboardLabel(a.element, a.x, a.y, az + radius, { color: labelColor }));
|
|
23749
|
+
}
|
|
23750
|
+
}
|
|
23751
|
+
if (lattice3d) {
|
|
23752
|
+
out.push(...latticeDrawables(lattice3d, { labelColor }));
|
|
23753
|
+
}
|
|
23754
|
+
return out;
|
|
23755
|
+
}, [mode, atoms, bonds, arrows, shapes, containers, lattice3d, animate, backgroundColor]);
|
|
23756
|
+
const atomIndexById = useMemo(() => {
|
|
23757
|
+
const m = /* @__PURE__ */ new Map();
|
|
23758
|
+
atoms.forEach((a, i) => {
|
|
23759
|
+
if (a.id) m.set(a.id, i);
|
|
23760
|
+
});
|
|
23761
|
+
return m;
|
|
23762
|
+
}, [atoms]);
|
|
23763
|
+
if (mode === "3d") {
|
|
23764
|
+
return /* @__PURE__ */ jsx(
|
|
23765
|
+
LearningScene3D,
|
|
23766
|
+
{
|
|
23767
|
+
className,
|
|
23768
|
+
width,
|
|
23769
|
+
height,
|
|
23770
|
+
title,
|
|
23771
|
+
backgroundColor,
|
|
23772
|
+
drawables: drawables3D,
|
|
23773
|
+
camera,
|
|
23774
|
+
lighting,
|
|
23775
|
+
post,
|
|
23776
|
+
showGrid,
|
|
23777
|
+
shadows,
|
|
23778
|
+
interactive,
|
|
23779
|
+
isLoading,
|
|
23780
|
+
error,
|
|
23781
|
+
onItemClick: get3DClickPayload(onShapeClick, atomIndexById)
|
|
23782
|
+
}
|
|
23783
|
+
);
|
|
23784
|
+
}
|
|
21857
23785
|
return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
|
|
21858
23786
|
title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
|
|
21859
23787
|
/* @__PURE__ */ jsx(
|
|
@@ -21863,7 +23791,9 @@ var init_ChemistryCanvas = __esm({
|
|
|
21863
23791
|
height,
|
|
21864
23792
|
backgroundColor,
|
|
21865
23793
|
shapes: derivedShapes,
|
|
21866
|
-
|
|
23794
|
+
readouts,
|
|
23795
|
+
traces,
|
|
23796
|
+
interactive: interactive ?? false,
|
|
21867
23797
|
animate,
|
|
21868
23798
|
onShapeClick,
|
|
21869
23799
|
isLoading,
|
|
@@ -28052,6 +29982,10 @@ var init_ProgressDots = __esm({
|
|
|
28052
29982
|
ProgressDots.displayName = "ProgressDots";
|
|
28053
29983
|
}
|
|
28054
29984
|
});
|
|
29985
|
+
function formatTick(v) {
|
|
29986
|
+
if (Number.isInteger(v)) return String(v);
|
|
29987
|
+
return v.toFixed(2).replace(/0+$/, "").replace(/\.$/, "");
|
|
29988
|
+
}
|
|
28055
29989
|
var MathCanvas;
|
|
28056
29990
|
var init_MathCanvas = __esm({
|
|
28057
29991
|
"components/learning/molecules/MathCanvas.tsx"() {
|
|
@@ -28071,10 +30005,19 @@ var init_MathCanvas = __esm({
|
|
|
28071
30005
|
showAxes = true,
|
|
28072
30006
|
showGrid = true,
|
|
28073
30007
|
gridStep = 1,
|
|
30008
|
+
showTickLabels = false,
|
|
30009
|
+
showCurveLabels = false,
|
|
28074
30010
|
curves = [],
|
|
28075
30011
|
points = [],
|
|
28076
30012
|
vectors = [],
|
|
30013
|
+
regions = [],
|
|
30014
|
+
bars = [],
|
|
30015
|
+
guides = [],
|
|
30016
|
+
angles = [],
|
|
30017
|
+
hops = [],
|
|
28077
30018
|
shapes = [],
|
|
30019
|
+
readouts,
|
|
30020
|
+
traces,
|
|
28078
30021
|
interactive = false,
|
|
28079
30022
|
animate = false,
|
|
28080
30023
|
onShapeClick,
|
|
@@ -28088,6 +30031,8 @@ var init_MathCanvas = __esm({
|
|
|
28088
30031
|
const plotH = height - margin * 2;
|
|
28089
30032
|
const mapX = (x) => margin + (x - xMin) / (xMax - xMin) * plotW;
|
|
28090
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)));
|
|
28091
30036
|
if (showGrid) {
|
|
28092
30037
|
for (let x = Math.ceil(xMin / gridStep) * gridStep; x <= xMax; x += gridStep) {
|
|
28093
30038
|
const px = mapX(x);
|
|
@@ -28098,14 +30043,99 @@ var init_MathCanvas = __esm({
|
|
|
28098
30043
|
out.push({ type: "line", x1: margin, y1: py, x2: width - margin, y2: py, color: "#e5e7eb", lineWidth: 1 });
|
|
28099
30044
|
}
|
|
28100
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
|
+
}
|
|
28101
30113
|
if (showAxes) {
|
|
28102
|
-
const xAxisY = Math.max(margin, Math.min(height - margin, mapY(0)));
|
|
28103
|
-
const yAxisX = Math.max(margin, Math.min(width - margin, mapX(0)));
|
|
28104
30114
|
out.push({ type: "line", x1: margin, y1: xAxisY, x2: width - margin, y2: xAxisY, color: "#374151", lineWidth: 2 });
|
|
28105
30115
|
out.push({ type: "line", x1: yAxisX, y1: margin, x2: yAxisX, y2: height - margin, color: "#374151", lineWidth: 2 });
|
|
28106
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
|
+
}
|
|
28107
30136
|
for (const curve of curves) {
|
|
28108
30137
|
if (!curve.samples || curve.samples.length < 2) continue;
|
|
30138
|
+
let lastInRange;
|
|
28109
30139
|
for (let i = 1; i < curve.samples.length; i++) {
|
|
28110
30140
|
const a = curve.samples[i - 1];
|
|
28111
30141
|
const b = curve.samples[i];
|
|
@@ -28117,19 +30147,97 @@ var init_MathCanvas = __esm({
|
|
|
28117
30147
|
x2: mapX(b.x),
|
|
28118
30148
|
y2: mapY(b.y),
|
|
28119
30149
|
color: curve.color ?? "#2563eb",
|
|
28120
|
-
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"
|
|
28121
30228
|
});
|
|
28122
30229
|
}
|
|
28123
30230
|
}
|
|
28124
30231
|
for (const p of points) {
|
|
28125
30232
|
if (p.x < xMin || p.x > xMax || p.y < yMin || p.y > yMax) continue;
|
|
30233
|
+
const isOpen = p.style === "open";
|
|
28126
30234
|
out.push({
|
|
28127
30235
|
type: "circle",
|
|
28128
30236
|
x: mapX(p.x),
|
|
28129
30237
|
y: mapY(p.y),
|
|
28130
30238
|
radius: p.radius ?? 4,
|
|
28131
30239
|
color: p.color ?? "#dc2626",
|
|
28132
|
-
fill: p.color ?? "#dc2626"
|
|
30240
|
+
fill: isOpen ? "#ffffff" : p.color ?? "#dc2626"
|
|
28133
30241
|
});
|
|
28134
30242
|
if (p.label) {
|
|
28135
30243
|
out.push({ type: "text", x: mapX(p.x) + 8, y: mapY(p.y) - 8, text: p.label, color: "#111827", fontSize: 12 });
|
|
@@ -28148,7 +30256,28 @@ var init_MathCanvas = __esm({
|
|
|
28148
30256
|
}
|
|
28149
30257
|
out.push(...shapes);
|
|
28150
30258
|
return out;
|
|
28151
|
-
}, [
|
|
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
|
+
]);
|
|
28152
30281
|
return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
|
|
28153
30282
|
title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
|
|
28154
30283
|
/* @__PURE__ */ jsx(
|
|
@@ -28157,6 +30286,8 @@ var init_MathCanvas = __esm({
|
|
|
28157
30286
|
width,
|
|
28158
30287
|
height,
|
|
28159
30288
|
shapes: derivedShapes,
|
|
30289
|
+
readouts,
|
|
30290
|
+
traces,
|
|
28160
30291
|
interactive,
|
|
28161
30292
|
animate,
|
|
28162
30293
|
onShapeClick,
|
|
@@ -28168,27 +30299,355 @@ var init_MathCanvas = __esm({
|
|
|
28168
30299
|
};
|
|
28169
30300
|
}
|
|
28170
30301
|
});
|
|
28171
|
-
|
|
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;
|
|
28172
30611
|
var init_PhysicsCanvas = __esm({
|
|
28173
30612
|
"components/learning/molecules/PhysicsCanvas.tsx"() {
|
|
28174
30613
|
"use client";
|
|
28175
30614
|
init_atoms();
|
|
28176
30615
|
init_Stack();
|
|
28177
30616
|
init_LearningCanvas();
|
|
30617
|
+
init_learningScene3D();
|
|
30618
|
+
physicsLog2 = createLogger("almadar:ui:physics-canvas");
|
|
30619
|
+
PHYSICS_LABEL_COLOR = "#374151";
|
|
28178
30620
|
PhysicsCanvas = ({
|
|
28179
30621
|
className,
|
|
28180
30622
|
width = 600,
|
|
28181
30623
|
height = 400,
|
|
28182
30624
|
title,
|
|
28183
30625
|
backgroundColor,
|
|
30626
|
+
mode = "2d",
|
|
30627
|
+
camera,
|
|
30628
|
+
lighting,
|
|
30629
|
+
post,
|
|
28184
30630
|
bodies = [],
|
|
28185
30631
|
constraints = [],
|
|
28186
30632
|
showVelocity = true,
|
|
28187
30633
|
showForces = false,
|
|
28188
30634
|
velocityScale = 20,
|
|
28189
30635
|
forceScale = 20,
|
|
30636
|
+
sceneObjects = [],
|
|
30637
|
+
trails = [],
|
|
30638
|
+
vectors = [],
|
|
30639
|
+
surface3d,
|
|
30640
|
+
vectors3d = [],
|
|
30641
|
+
vectorScale = 1,
|
|
30642
|
+
angles = [],
|
|
30643
|
+
field,
|
|
30644
|
+
meters = [],
|
|
28190
30645
|
shapes = [],
|
|
28191
|
-
|
|
30646
|
+
readouts,
|
|
30647
|
+
traces,
|
|
30648
|
+
showGrid,
|
|
30649
|
+
shadows,
|
|
30650
|
+
interactive,
|
|
28192
30651
|
animate = false,
|
|
28193
30652
|
onShapeClick,
|
|
28194
30653
|
isLoading,
|
|
@@ -28200,19 +30659,14 @@ var init_PhysicsCanvas = __esm({
|
|
|
28200
30659
|
for (const b of bodies) {
|
|
28201
30660
|
if (b.id) bodyById.set(b.id, b);
|
|
28202
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));
|
|
28203
30665
|
for (const c of constraints) {
|
|
28204
30666
|
const a = bodyById.get(c.from);
|
|
28205
30667
|
const b = bodyById.get(c.to);
|
|
28206
30668
|
if (!a || !b) continue;
|
|
28207
|
-
out.push(
|
|
28208
|
-
type: "line",
|
|
28209
|
-
x1: a.x,
|
|
28210
|
-
y1: a.y,
|
|
28211
|
-
x2: b.x,
|
|
28212
|
-
y2: b.y,
|
|
28213
|
-
color: c.color ?? "#9ca3af",
|
|
28214
|
-
lineWidth: 2
|
|
28215
|
-
});
|
|
30669
|
+
out.push(...constraintShapes(c, a, b));
|
|
28216
30670
|
}
|
|
28217
30671
|
for (const b of bodies) {
|
|
28218
30672
|
out.push({
|
|
@@ -28257,9 +30711,181 @@ var init_PhysicsCanvas = __esm({
|
|
|
28257
30711
|
});
|
|
28258
30712
|
}
|
|
28259
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));
|
|
28260
30717
|
out.push(...shapes);
|
|
28261
30718
|
return out;
|
|
28262
|
-
}, [
|
|
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
|
+
]);
|
|
30736
|
+
const drawables3D = useMemo(() => {
|
|
30737
|
+
if (mode !== "3d") return [];
|
|
30738
|
+
if (shapes.length > 0) {
|
|
30739
|
+
physicsLog2.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
|
|
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
|
+
}
|
|
30759
|
+
const out = [];
|
|
30760
|
+
const labelColor = labelColorForBackground(backgroundColor);
|
|
30761
|
+
const bodyById = /* @__PURE__ */ new Map();
|
|
30762
|
+
for (const b of bodies) {
|
|
30763
|
+
if (b.id) bodyById.set(b.id, b);
|
|
30764
|
+
}
|
|
30765
|
+
for (const c of constraints) {
|
|
30766
|
+
const a = bodyById.get(c.from);
|
|
30767
|
+
const b = bodyById.get(c.to);
|
|
30768
|
+
if (!a || !b) continue;
|
|
30769
|
+
const rodRadius = Math.max(0.05, Math.min(a.radius ?? 0.5, b.radius ?? 0.5) * 0.15);
|
|
30770
|
+
const rod = cylinderBetween([a.x, a.y, a.z ?? 0], [b.x, b.y, b.z ?? 0], rodRadius, c.color ?? "#9ca3af");
|
|
30771
|
+
if (rod) out.push(rod);
|
|
30772
|
+
}
|
|
30773
|
+
for (const b of bodies) {
|
|
30774
|
+
const radius = b.radius ?? 0.5;
|
|
30775
|
+
const bz = b.z ?? 0;
|
|
30776
|
+
out.push(meshSphere(b.id, b.x, b.y, bz, radius, b.color ?? "#2563eb"));
|
|
30777
|
+
if (b.label) {
|
|
30778
|
+
out.push(billboardLabel(b.label, b.x, b.y, bz + radius, { color: labelColor }));
|
|
30779
|
+
}
|
|
30780
|
+
const vx = b.vx ?? 0;
|
|
30781
|
+
const vy = b.vy ?? 0;
|
|
30782
|
+
const vz = b.vz ?? 0;
|
|
30783
|
+
const arrowRadius = Math.max(0.05, radius * 0.15);
|
|
30784
|
+
if (showVelocity && (vx !== 0 || vy !== 0 || vz !== 0)) {
|
|
30785
|
+
const arrow = arrowBetween(
|
|
30786
|
+
[b.x, b.y, bz],
|
|
30787
|
+
[b.x + vx * velocityScale, b.y + vy * velocityScale, bz + vz * velocityScale],
|
|
30788
|
+
"#16a34a",
|
|
30789
|
+
arrowRadius
|
|
30790
|
+
);
|
|
30791
|
+
if (arrow) out.push(arrow);
|
|
30792
|
+
}
|
|
30793
|
+
const fx = b.fx ?? 0;
|
|
30794
|
+
const fy = b.fy ?? 0;
|
|
30795
|
+
const fz = b.fz ?? 0;
|
|
30796
|
+
if (showForces && (fx !== 0 || fy !== 0 || fz !== 0)) {
|
|
30797
|
+
const arrow = arrowBetween(
|
|
30798
|
+
[b.x, b.y, bz],
|
|
30799
|
+
[b.x + fx * forceScale, b.y + fy * forceScale, bz + fz * forceScale],
|
|
30800
|
+
"#dc2626",
|
|
30801
|
+
arrowRadius
|
|
30802
|
+
);
|
|
30803
|
+
if (arrow) out.push(arrow);
|
|
30804
|
+
}
|
|
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
|
+
}
|
|
30835
|
+
return out;
|
|
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
|
+
]);
|
|
30857
|
+
const bodyIndexById = useMemo(() => {
|
|
30858
|
+
const m = /* @__PURE__ */ new Map();
|
|
30859
|
+
vectors3d.forEach((v, i) => {
|
|
30860
|
+
if (v.id) m.set(v.id, i);
|
|
30861
|
+
});
|
|
30862
|
+
bodies.forEach((b, i) => {
|
|
30863
|
+
if (b.id) m.set(b.id, i);
|
|
30864
|
+
});
|
|
30865
|
+
return m;
|
|
30866
|
+
}, [bodies, vectors3d]);
|
|
30867
|
+
if (mode === "3d") {
|
|
30868
|
+
return /* @__PURE__ */ jsx(
|
|
30869
|
+
LearningScene3D,
|
|
30870
|
+
{
|
|
30871
|
+
className,
|
|
30872
|
+
width,
|
|
30873
|
+
height,
|
|
30874
|
+
title,
|
|
30875
|
+
backgroundColor,
|
|
30876
|
+
drawables: drawables3D,
|
|
30877
|
+
camera,
|
|
30878
|
+
lighting,
|
|
30879
|
+
post,
|
|
30880
|
+
showGrid,
|
|
30881
|
+
shadows,
|
|
30882
|
+
interactive,
|
|
30883
|
+
isLoading,
|
|
30884
|
+
error,
|
|
30885
|
+
onItemClick: get3DClickPayload(onShapeClick, bodyIndexById)
|
|
30886
|
+
}
|
|
30887
|
+
);
|
|
30888
|
+
}
|
|
28263
30889
|
return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
|
|
28264
30890
|
title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
|
|
28265
30891
|
/* @__PURE__ */ jsx(
|
|
@@ -28269,7 +30895,9 @@ var init_PhysicsCanvas = __esm({
|
|
|
28269
30895
|
height,
|
|
28270
30896
|
backgroundColor,
|
|
28271
30897
|
shapes: derivedShapes,
|
|
28272
|
-
|
|
30898
|
+
readouts,
|
|
30899
|
+
traces,
|
|
30900
|
+
interactive: interactive ?? false,
|
|
28273
30901
|
animate,
|
|
28274
30902
|
onShapeClick,
|
|
28275
30903
|
isLoading,
|
|
@@ -28419,7 +31047,7 @@ function layoutFlow(nodeIds, adjacency, roots, width, height, margin) {
|
|
|
28419
31047
|
}
|
|
28420
31048
|
return nodeIds.map((id) => positions.get(id));
|
|
28421
31049
|
}
|
|
28422
|
-
function
|
|
31050
|
+
function layoutTree2(nodeIds, adjacency, roots, width, height, margin) {
|
|
28423
31051
|
const effectiveRoots = roots.length > 0 ? roots : [nodeIds[0]];
|
|
28424
31052
|
const layers = assignLayers(nodeIds, adjacency, effectiveRoots);
|
|
28425
31053
|
const maxLayer = Math.max(...Array.from(layers.values()));
|
|
@@ -28475,7 +31103,7 @@ function computeStaticLayout(mode, input) {
|
|
|
28475
31103
|
const adjacency = buildAdjacency(nodeIds, edges);
|
|
28476
31104
|
const roots = findRoots(nodeIds, adjacency);
|
|
28477
31105
|
if (mode === "flow") return layoutFlow(nodeIds, adjacency, roots, width, height, margin);
|
|
28478
|
-
if (mode === "tree") return
|
|
31106
|
+
if (mode === "tree") return layoutTree2(nodeIds, adjacency, roots, width, height, margin);
|
|
28479
31107
|
return layoutRadial(nodeIds, adjacency, roots, width, height, margin);
|
|
28480
31108
|
}
|
|
28481
31109
|
var init_graphViewLayouts = __esm({
|
|
@@ -28887,12 +31515,12 @@ var init_MapView = __esm({
|
|
|
28887
31515
|
shadowSize: [41, 41]
|
|
28888
31516
|
});
|
|
28889
31517
|
L.Marker.prototype.options.icon = defaultIcon;
|
|
28890
|
-
const { useEffect: useEffect63, useRef:
|
|
31518
|
+
const { useEffect: useEffect63, useRef: useRef63, useCallback: useCallback96, useState: useState94 } = React87__default;
|
|
28891
31519
|
const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
|
|
28892
31520
|
const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
|
|
28893
31521
|
function MapUpdater({ centerLat, centerLng, zoom }) {
|
|
28894
31522
|
const map = useMap();
|
|
28895
|
-
const prevRef =
|
|
31523
|
+
const prevRef = useRef63({ centerLat, centerLng, zoom });
|
|
28896
31524
|
useEffect63(() => {
|
|
28897
31525
|
const prev = prevRef.current;
|
|
28898
31526
|
if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
|
|
@@ -29718,26 +32346,6 @@ var init_Lightbox = __esm({
|
|
|
29718
32346
|
Lightbox.displayName = "Lightbox";
|
|
29719
32347
|
}
|
|
29720
32348
|
});
|
|
29721
|
-
function useMediaQuery(query) {
|
|
29722
|
-
const subscribe = useCallback(
|
|
29723
|
-
(onChange) => {
|
|
29724
|
-
const mql = window.matchMedia(query);
|
|
29725
|
-
mql.addEventListener("change", onChange);
|
|
29726
|
-
return () => mql.removeEventListener("change", onChange);
|
|
29727
|
-
},
|
|
29728
|
-
[query]
|
|
29729
|
-
);
|
|
29730
|
-
return useSyncExternalStore(
|
|
29731
|
-
subscribe,
|
|
29732
|
-
() => window.matchMedia(query).matches,
|
|
29733
|
-
() => false
|
|
29734
|
-
);
|
|
29735
|
-
}
|
|
29736
|
-
var init_useMediaQuery = __esm({
|
|
29737
|
-
"hooks/useMediaQuery.ts"() {
|
|
29738
|
-
"use client";
|
|
29739
|
-
}
|
|
29740
|
-
});
|
|
29741
32349
|
function renderIconInput3(icon, props) {
|
|
29742
32350
|
return typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon, ...props }) : /* @__PURE__ */ jsx(Icon, { icon, ...props });
|
|
29743
32351
|
}
|
|
@@ -29776,8 +32384,8 @@ function TableView({
|
|
|
29776
32384
|
columns,
|
|
29777
32385
|
fields,
|
|
29778
32386
|
itemActions,
|
|
29779
|
-
maxInlineActions,
|
|
29780
|
-
itemClickEvent,
|
|
32387
|
+
maxInlineActions: _maxInlineActions,
|
|
32388
|
+
itemClickEvent = "",
|
|
29781
32389
|
selectable = false,
|
|
29782
32390
|
selectEvent,
|
|
29783
32391
|
selectedIds,
|
|
@@ -29827,7 +32435,6 @@ function TableView({
|
|
|
29827
32435
|
const hasMore = pageSize > 0 && visibleCount < ordered2.length;
|
|
29828
32436
|
const hasRenderProp = typeof children === "function";
|
|
29829
32437
|
const idField = dndItemIdField ?? "id";
|
|
29830
|
-
const isCoarsePointer = useMediaQuery("(pointer: coarse)");
|
|
29831
32438
|
React87__default.useEffect(() => {
|
|
29832
32439
|
tableViewLog.debug("render", {
|
|
29833
32440
|
rowCount: data.length,
|
|
@@ -29869,21 +32476,14 @@ function TableView({
|
|
|
29869
32476
|
const dir = sortColumn === (col.field ?? col.key) && sortDirection === "asc" ? "desc" : "asc";
|
|
29870
32477
|
eventBus.emit(`UI:${sortEvent}`, { column: col.field ?? col.key, direction: dir });
|
|
29871
32478
|
};
|
|
29872
|
-
const
|
|
29873
|
-
e.stopPropagation();
|
|
29874
|
-
const payload = {
|
|
29875
|
-
id: row.id,
|
|
29876
|
-
row
|
|
29877
|
-
};
|
|
29878
|
-
eventBus.emit(`UI:${action.event}`, payload);
|
|
29879
|
-
};
|
|
32479
|
+
const rowClickEvent = itemClickEvent || actionDefs.find((a) => a.variant !== "danger")?.event;
|
|
29880
32480
|
const handleRowClick = (row) => () => {
|
|
29881
|
-
if (!
|
|
32481
|
+
if (!rowClickEvent) return;
|
|
29882
32482
|
const payload = {
|
|
29883
32483
|
id: row.id,
|
|
29884
32484
|
row
|
|
29885
32485
|
};
|
|
29886
|
-
eventBus.emit(`UI:${
|
|
32486
|
+
eventBus.emit(`UI:${rowClickEvent}`, payload);
|
|
29887
32487
|
};
|
|
29888
32488
|
const colFloors = React87__default.useMemo(
|
|
29889
32489
|
() => colDefs.map((col) => {
|
|
@@ -29899,10 +32499,7 @@ function TableView({
|
|
|
29899
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;
|
|
29900
32500
|
const lk = LOOKS[look];
|
|
29901
32501
|
const hasActions = actionDefs.length > 0;
|
|
29902
|
-
const
|
|
29903
|
-
const inlineActionCount = hasActions ? effectiveMaxInline != null ? Math.min(actionDefs.length, effectiveMaxInline) : actionDefs.length : 0;
|
|
29904
|
-
const hasOverflowActions = hasActions && effectiveMaxInline != null && actionDefs.length > effectiveMaxInline;
|
|
29905
|
-
const actionsTrack = hasActions ? `${inlineActionCount * 6 + (hasOverflowActions ? 3 : 0)}rem` : null;
|
|
32502
|
+
const actionsTrack = hasActions ? "3rem" : null;
|
|
29906
32503
|
const gridTemplateColumns = [
|
|
29907
32504
|
selectable ? "auto" : null,
|
|
29908
32505
|
...colDefs.map((c, i) => c.width ?? `minmax(${colFloors[i]}ch, 1fr)`),
|
|
@@ -29949,7 +32546,7 @@ function TableView({
|
|
|
29949
32546
|
col.key
|
|
29950
32547
|
);
|
|
29951
32548
|
}),
|
|
29952
|
-
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" })
|
|
29953
32550
|
]
|
|
29954
32551
|
}
|
|
29955
32552
|
);
|
|
@@ -29961,12 +32558,12 @@ function TableView({
|
|
|
29961
32558
|
role: "row",
|
|
29962
32559
|
"data-entity-row": true,
|
|
29963
32560
|
"data-entity-id": id,
|
|
29964
|
-
onClick:
|
|
32561
|
+
onClick: rowClickEvent ? handleRowClick(row) : void 0,
|
|
29965
32562
|
style: !hasRenderProp ? { gridTemplateColumns } : void 0,
|
|
29966
32563
|
className: cn(
|
|
29967
32564
|
"group items-center gap-3 transition-colors duration-fast",
|
|
29968
32565
|
hasRenderProp ? "flex" : "grid",
|
|
29969
|
-
|
|
32566
|
+
rowClickEvent && "cursor-pointer",
|
|
29970
32567
|
lk.rowPad,
|
|
29971
32568
|
lk.divider && "border-b border-[var(--color-border)]",
|
|
29972
32569
|
lk.striped && index % 2 === 1 && "bg-[var(--color-surface-subtle)]",
|
|
@@ -29974,7 +32571,7 @@ function TableView({
|
|
|
29974
32571
|
look === "bordered" && "[&>*]:border-r [&>*]:border-[var(--color-border)] [&>*:last-child]:border-r-0"
|
|
29975
32572
|
),
|
|
29976
32573
|
children: [
|
|
29977
|
-
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(
|
|
29978
32575
|
Checkbox,
|
|
29979
32576
|
{
|
|
29980
32577
|
checked: selected.has(id),
|
|
@@ -29995,53 +32592,37 @@ function TableView({
|
|
|
29995
32592
|
}
|
|
29996
32593
|
return /* @__PURE__ */ jsx(Box, { role: "cell", className: cellBase, children: /* @__PURE__ */ jsx("span", { className: "truncate text-foreground", children: formatCell(raw, col.format) }) }, col.key);
|
|
29997
32594
|
}),
|
|
29998
|
-
hasActions && /* @__PURE__ */
|
|
32595
|
+
hasActions && /* @__PURE__ */ jsx(
|
|
29999
32596
|
HStack,
|
|
30000
32597
|
{
|
|
30001
32598
|
gap: "xs",
|
|
30002
|
-
onClick:
|
|
32599
|
+
onClick: rowClickEvent ? (e) => e.stopPropagation() : void 0,
|
|
30003
32600
|
className: cn(
|
|
30004
32601
|
// Pinned: the fixed column tracks routinely overflow the caller's
|
|
30005
|
-
// scroll container, which
|
|
30006
|
-
// 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.
|
|
30007
32605
|
"justify-end flex-shrink-0 sticky right-0 z-[1] transition-colors",
|
|
32606
|
+
"border-l border-[var(--color-border)]",
|
|
30008
32607
|
lk.striped && index % 2 === 1 ? "bg-[var(--color-surface-subtle)]" : "bg-[var(--color-card)] group-hover:bg-[var(--color-surface-subtle)]"
|
|
30009
32608
|
),
|
|
30010
|
-
children:
|
|
30011
|
-
|
|
30012
|
-
|
|
30013
|
-
|
|
30014
|
-
|
|
30015
|
-
|
|
30016
|
-
|
|
30017
|
-
|
|
30018
|
-
|
|
30019
|
-
|
|
30020
|
-
|
|
30021
|
-
|
|
30022
|
-
|
|
30023
|
-
|
|
30024
|
-
}
|
|
30025
|
-
|
|
30026
|
-
|
|
30027
|
-
effectiveMaxInline != null && actionDefs.length > effectiveMaxInline && /* @__PURE__ */ jsx(
|
|
30028
|
-
Menu,
|
|
30029
|
-
{
|
|
30030
|
-
position: "bottom-end",
|
|
30031
|
-
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" }) }),
|
|
30032
|
-
items: actionDefs.slice(effectiveMaxInline).map((action) => ({
|
|
30033
|
-
label: action.label,
|
|
30034
|
-
icon: action.icon,
|
|
30035
|
-
event: action.event,
|
|
30036
|
-
variant: action.variant === "danger" ? "danger" : "default",
|
|
30037
|
-
onClick: () => eventBus.emit(`UI:${action.event}`, {
|
|
30038
|
-
id: row.id,
|
|
30039
|
-
row
|
|
30040
|
-
})
|
|
30041
|
-
}))
|
|
30042
|
-
}
|
|
30043
|
-
)
|
|
30044
|
-
]
|
|
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
|
+
)
|
|
30045
32626
|
}
|
|
30046
32627
|
)
|
|
30047
32628
|
]
|
|
@@ -30084,7 +32665,6 @@ var init_TableView = __esm({
|
|
|
30084
32665
|
init_format();
|
|
30085
32666
|
init_getNestedValue();
|
|
30086
32667
|
init_useEventBus();
|
|
30087
|
-
init_useMediaQuery();
|
|
30088
32668
|
init_Box();
|
|
30089
32669
|
init_Stack();
|
|
30090
32670
|
init_Typography();
|
|
@@ -39715,7 +42295,7 @@ function getEnumOptions(field) {
|
|
|
39715
42295
|
}));
|
|
39716
42296
|
}
|
|
39717
42297
|
const validation = field.validation;
|
|
39718
|
-
if (validation?.enum &&
|
|
42298
|
+
if (validation?.enum && validation.enum.length > 0) {
|
|
39719
42299
|
return validation.enum.map((v) => ({
|
|
39720
42300
|
value: v,
|
|
39721
42301
|
label: v.charAt(0).toUpperCase() + v.slice(1).replace(/_/g, " ")
|
|
@@ -45143,6 +47723,7 @@ var init_component_registry_generated = __esm({
|
|
|
45143
47723
|
init_ActionTile();
|
|
45144
47724
|
init_ActivationBlock();
|
|
45145
47725
|
init_ComponentPatterns();
|
|
47726
|
+
init_AlgoGraphCanvas();
|
|
45146
47727
|
init_AlgorithmCanvas();
|
|
45147
47728
|
init_AnimatedCounter();
|
|
45148
47729
|
init_AnimatedGraphic();
|
|
@@ -45406,6 +47987,7 @@ var init_component_registry_generated = __esm({
|
|
|
45406
47987
|
"ActivationBlock": ActivationBlock,
|
|
45407
47988
|
"Alert": AlertPattern,
|
|
45408
47989
|
"AlertPattern": AlertPattern,
|
|
47990
|
+
"AlgoGraphCanvas": AlgoGraphCanvas,
|
|
45409
47991
|
"AlgorithmCanvas": AlgorithmCanvas,
|
|
45410
47992
|
"AnimatedCounter": AnimatedCounter,
|
|
45411
47993
|
"AnimatedGraphic": AnimatedGraphic,
|