@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/runtime/index.js
CHANGED
|
@@ -135,7 +135,7 @@ function useEventBus() {
|
|
|
135
135
|
return {
|
|
136
136
|
...baseBus,
|
|
137
137
|
emit: (type, payload, source) => {
|
|
138
|
-
if (typeof type === "string" && type.startsWith("UI:") && !type.slice(3).includes(".")) {
|
|
138
|
+
if (typeof type === "string" && type.startsWith("UI:") && !type.slice(3).includes(".") && !source?.trait) {
|
|
139
139
|
scopeLog.warn("emit:bare-key-no-scope", { type });
|
|
140
140
|
}
|
|
141
141
|
baseBus.emit(type, payload, source);
|
|
@@ -539,26 +539,6 @@ var init_useDragReorder = __esm({
|
|
|
539
539
|
"use client";
|
|
540
540
|
}
|
|
541
541
|
});
|
|
542
|
-
function useMediaQuery(query) {
|
|
543
|
-
const subscribe = useCallback(
|
|
544
|
-
(onChange) => {
|
|
545
|
-
const mql = window.matchMedia(query);
|
|
546
|
-
mql.addEventListener("change", onChange);
|
|
547
|
-
return () => mql.removeEventListener("change", onChange);
|
|
548
|
-
},
|
|
549
|
-
[query]
|
|
550
|
-
);
|
|
551
|
-
return useSyncExternalStore(
|
|
552
|
-
subscribe,
|
|
553
|
-
() => window.matchMedia(query).matches,
|
|
554
|
-
() => false
|
|
555
|
-
);
|
|
556
|
-
}
|
|
557
|
-
var init_useMediaQuery = __esm({
|
|
558
|
-
"hooks/useMediaQuery.ts"() {
|
|
559
|
-
"use client";
|
|
560
|
-
}
|
|
561
|
-
});
|
|
562
542
|
function useInfiniteScroll(onLoadMore, options = {}) {
|
|
563
543
|
const { rootMargin = "200px", hasMore = true, isLoading = false } = options;
|
|
564
544
|
const observerRef = useRef(null);
|
|
@@ -10655,9 +10635,10 @@ function Canvas({
|
|
|
10655
10635
|
drawables: [...drawables ?? [], ...childDrawables],
|
|
10656
10636
|
isLoading,
|
|
10657
10637
|
cameraMode: to3DCameraMode(camera?.mode),
|
|
10658
|
-
...zoom !== void 0 ? {
|
|
10638
|
+
...zoom !== void 0 ? { zoom } : {},
|
|
10659
10639
|
...camera?.fov !== void 0 ? { fov: camera.fov } : {},
|
|
10660
10640
|
...camera?.azimuth !== void 0 ? { azimuth: camera.azimuth } : {},
|
|
10641
|
+
...camera?.elevation !== void 0 ? { elevation: camera.elevation } : {},
|
|
10661
10642
|
...camera?.target !== void 0 ? { followTarget: camera.target } : {},
|
|
10662
10643
|
unitScale,
|
|
10663
10644
|
backgroundColor,
|
|
@@ -11658,6 +11639,14 @@ function shapeBounds(shape) {
|
|
|
11658
11639
|
w: shape.radius * 2 + 8,
|
|
11659
11640
|
h: shape.radius * 2 + 8
|
|
11660
11641
|
};
|
|
11642
|
+
case "ellipse":
|
|
11643
|
+
if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) return null;
|
|
11644
|
+
return {
|
|
11645
|
+
x: shape.x - shape.width / 2 - 4,
|
|
11646
|
+
y: shape.y - shape.height / 2 - 4,
|
|
11647
|
+
w: shape.width + 8,
|
|
11648
|
+
h: shape.height + 8
|
|
11649
|
+
};
|
|
11661
11650
|
case "rect":
|
|
11662
11651
|
if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) return null;
|
|
11663
11652
|
return { x: shape.x - 4, y: shape.y - 4, w: shape.width + 8, h: shape.height + 8 };
|
|
@@ -11691,13 +11680,14 @@ function drawArrowHead(ctx, x1, y1, x2, y2, size) {
|
|
|
11691
11680
|
ctx.closePath();
|
|
11692
11681
|
ctx.fill();
|
|
11693
11682
|
}
|
|
11694
|
-
function drawShape(ctx, shape, width, height) {
|
|
11683
|
+
function drawShape(ctx, shape, width, height, allShapes) {
|
|
11695
11684
|
ctx.save();
|
|
11696
11685
|
const opacity = shape.opacity ?? 1;
|
|
11697
11686
|
ctx.globalAlpha = opacity;
|
|
11698
11687
|
const stroke = resolveColor2(shape.color, ctx, "#333333");
|
|
11699
11688
|
const fill = shape.fill ? resolveColor2(shape.fill, ctx, "#cccccc") : void 0;
|
|
11700
11689
|
ctx.lineWidth = shape.lineWidth ?? 2;
|
|
11690
|
+
if (shape.dash) ctx.setLineDash([...DASH_PATTERNS[shape.dash]]);
|
|
11701
11691
|
switch (shape.type) {
|
|
11702
11692
|
case "grid": {
|
|
11703
11693
|
const step = shape.step ?? 40;
|
|
@@ -11763,6 +11753,20 @@ function drawShape(ctx, shape, width, height) {
|
|
|
11763
11753
|
ctx.stroke();
|
|
11764
11754
|
break;
|
|
11765
11755
|
}
|
|
11756
|
+
case "ellipse": {
|
|
11757
|
+
if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) break;
|
|
11758
|
+
const startAngle = (shape.startAngle ?? 0) * Math.PI / 180;
|
|
11759
|
+
const endAngle = (shape.endAngle ?? 360) * Math.PI / 180;
|
|
11760
|
+
ctx.beginPath();
|
|
11761
|
+
ctx.ellipse(shape.x, shape.y, shape.width / 2, shape.height / 2, 0, startAngle, endAngle);
|
|
11762
|
+
if (fill) {
|
|
11763
|
+
ctx.fillStyle = fill;
|
|
11764
|
+
ctx.fill();
|
|
11765
|
+
}
|
|
11766
|
+
ctx.strokeStyle = stroke;
|
|
11767
|
+
ctx.stroke();
|
|
11768
|
+
break;
|
|
11769
|
+
}
|
|
11766
11770
|
case "rect": {
|
|
11767
11771
|
if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) break;
|
|
11768
11772
|
if (fill) {
|
|
@@ -11809,21 +11813,153 @@ function drawShape(ctx, shape, width, height) {
|
|
|
11809
11813
|
ctx.fillText(shape.text, shape.x, shape.y);
|
|
11810
11814
|
break;
|
|
11811
11815
|
}
|
|
11816
|
+
case "venn-region": {
|
|
11817
|
+
const resolveCircles = (ids) => (ids ?? []).flatMap((id) => {
|
|
11818
|
+
const c = allShapes.find((s) => s.type === "circle" && s.id === id);
|
|
11819
|
+
return c && c.x != null && c.y != null && c.radius != null ? [{ x: c.x, y: c.y, radius: c.radius }] : [];
|
|
11820
|
+
});
|
|
11821
|
+
const inside = resolveCircles(shape.inside);
|
|
11822
|
+
if (inside.length === 0) break;
|
|
11823
|
+
const outside = resolveCircles(shape.outside);
|
|
11824
|
+
const off = document.createElement("canvas");
|
|
11825
|
+
off.width = ctx.canvas.width;
|
|
11826
|
+
off.height = ctx.canvas.height;
|
|
11827
|
+
const octx = off.getContext("2d");
|
|
11828
|
+
if (!octx) break;
|
|
11829
|
+
octx.setTransform(ctx.getTransform());
|
|
11830
|
+
for (const c of inside) {
|
|
11831
|
+
const p = new Path2D();
|
|
11832
|
+
p.arc(c.x, c.y, c.radius, 0, Math.PI * 2);
|
|
11833
|
+
octx.clip(p);
|
|
11834
|
+
}
|
|
11835
|
+
octx.fillStyle = fill ?? stroke;
|
|
11836
|
+
octx.fillRect(0, 0, width, height);
|
|
11837
|
+
octx.globalCompositeOperation = "destination-out";
|
|
11838
|
+
for (const c of outside) {
|
|
11839
|
+
const p = new Path2D();
|
|
11840
|
+
p.arc(c.x, c.y, c.radius, 0, Math.PI * 2);
|
|
11841
|
+
octx.fill(p);
|
|
11842
|
+
}
|
|
11843
|
+
ctx.save();
|
|
11844
|
+
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
11845
|
+
ctx.drawImage(off, 0, 0);
|
|
11846
|
+
ctx.restore();
|
|
11847
|
+
break;
|
|
11848
|
+
}
|
|
11812
11849
|
}
|
|
11813
11850
|
ctx.restore();
|
|
11814
11851
|
}
|
|
11815
|
-
|
|
11852
|
+
function readoutShapes(readouts, width) {
|
|
11853
|
+
const out = [];
|
|
11854
|
+
const chipH = 18;
|
|
11855
|
+
const gap = 6;
|
|
11856
|
+
let rightEdge = width - 6;
|
|
11857
|
+
let rowY = 6;
|
|
11858
|
+
for (const readout of readouts) {
|
|
11859
|
+
const text = `${readout.label}: ${String(readout.value)}`;
|
|
11860
|
+
const chipW = Math.min(170, Math.max(34, text.length * 6 + 12));
|
|
11861
|
+
let chipX = rightEdge - chipW;
|
|
11862
|
+
if (chipX < 4) {
|
|
11863
|
+
rowY += chipH + 4;
|
|
11864
|
+
rightEdge = width - 6;
|
|
11865
|
+
chipX = rightEdge - chipW;
|
|
11866
|
+
}
|
|
11867
|
+
const color = readout.color ?? "#334155";
|
|
11868
|
+
out.push({ type: "rect", x: chipX, y: rowY, width: chipW, height: chipH, color, fill: color });
|
|
11869
|
+
out.push({
|
|
11870
|
+
type: "text",
|
|
11871
|
+
x: chipX + chipW / 2,
|
|
11872
|
+
y: rowY + chipH / 2,
|
|
11873
|
+
text,
|
|
11874
|
+
color: "#ffffff",
|
|
11875
|
+
fontSize: 10,
|
|
11876
|
+
align: "center"
|
|
11877
|
+
});
|
|
11878
|
+
rightEdge = chipX - gap;
|
|
11879
|
+
}
|
|
11880
|
+
return out;
|
|
11881
|
+
}
|
|
11882
|
+
function traceShapes(panel, k, width, height) {
|
|
11883
|
+
const w = panel.width ?? Math.round(width * 0.32);
|
|
11884
|
+
const h = panel.height ?? Math.round(height * 0.28);
|
|
11885
|
+
const x = panel.x ?? width - w - 8;
|
|
11886
|
+
const y = panel.y ?? height - h - 8 - k * (h + 8);
|
|
11887
|
+
const allSamples = panel.series.flatMap((series) => series.samples);
|
|
11888
|
+
let xLo = Math.min(...allSamples.map((p) => p.x));
|
|
11889
|
+
let xHi = Math.max(...allSamples.map((p) => p.x));
|
|
11890
|
+
let yLo = Math.min(...allSamples.map((p) => p.y));
|
|
11891
|
+
let yHi = Math.max(...allSamples.map((p) => p.y));
|
|
11892
|
+
if (xLo === xHi) {
|
|
11893
|
+
xLo -= 1;
|
|
11894
|
+
xHi += 1;
|
|
11895
|
+
}
|
|
11896
|
+
if (yLo === yHi) {
|
|
11897
|
+
yLo -= 1;
|
|
11898
|
+
yHi += 1;
|
|
11899
|
+
}
|
|
11900
|
+
const backgroundColor = panel.backgroundColor ?? "#ffffff";
|
|
11901
|
+
const frameColor = panel.frameColor ?? "#94a3b8";
|
|
11902
|
+
const out = [];
|
|
11903
|
+
out.push({
|
|
11904
|
+
type: "rect",
|
|
11905
|
+
x,
|
|
11906
|
+
y,
|
|
11907
|
+
width: w,
|
|
11908
|
+
height: h,
|
|
11909
|
+
color: backgroundColor,
|
|
11910
|
+
fill: backgroundColor,
|
|
11911
|
+
opacity: panel.backgroundOpacity ?? 0.85
|
|
11912
|
+
});
|
|
11913
|
+
out.push({ type: "rect", x, y, width: w, height: h, color: frameColor, lineWidth: 1 });
|
|
11914
|
+
panel.series.forEach((series, j) => {
|
|
11915
|
+
const color = series.color ?? TRACE_SERIES_COLORS[j % TRACE_SERIES_COLORS.length];
|
|
11916
|
+
const mapped = series.samples.map((p) => ({
|
|
11917
|
+
x: x + 4 + (p.x - xLo) / (xHi - xLo) * (w - 8),
|
|
11918
|
+
y: y + h - 4 - (p.y - yLo) / (yHi - yLo) * (h - 8)
|
|
11919
|
+
}));
|
|
11920
|
+
for (let i = 1; i < mapped.length; i++) {
|
|
11921
|
+
out.push({
|
|
11922
|
+
type: "line",
|
|
11923
|
+
x1: mapped[i - 1].x,
|
|
11924
|
+
y1: mapped[i - 1].y,
|
|
11925
|
+
x2: mapped[i].x,
|
|
11926
|
+
y2: mapped[i].y,
|
|
11927
|
+
color,
|
|
11928
|
+
lineWidth: 1.5
|
|
11929
|
+
});
|
|
11930
|
+
}
|
|
11931
|
+
if (mapped.length > 0) {
|
|
11932
|
+
const last = mapped[mapped.length - 1];
|
|
11933
|
+
out.push({ type: "circle", x: last.x, y: last.y, radius: 2, color, fill: color });
|
|
11934
|
+
}
|
|
11935
|
+
if (series.label) {
|
|
11936
|
+
out.push({ type: "text", x: x + 6, y: y + 10 + 11 * j, text: series.label, color, fontSize: 9 });
|
|
11937
|
+
}
|
|
11938
|
+
});
|
|
11939
|
+
if (panel.yLabel) {
|
|
11940
|
+
out.push({ type: "text", x: x + w - 6, y: y + 10, text: panel.yLabel, color: "#6b7280", fontSize: 9, align: "right" });
|
|
11941
|
+
}
|
|
11942
|
+
if (panel.xLabel) {
|
|
11943
|
+
out.push({ type: "text", x: x + w - 6, y: y + h - 6, text: panel.xLabel, color: "#6b7280", fontSize: 9, align: "right" });
|
|
11944
|
+
}
|
|
11945
|
+
return out;
|
|
11946
|
+
}
|
|
11947
|
+
var DASH_PATTERNS, TRACE_SERIES_COLORS, LearningCanvas;
|
|
11816
11948
|
var init_LearningCanvas = __esm({
|
|
11817
11949
|
"components/learning/atoms/LearningCanvas.tsx"() {
|
|
11818
11950
|
"use client";
|
|
11819
11951
|
init_cn();
|
|
11820
11952
|
init_useEventBus();
|
|
11953
|
+
DASH_PATTERNS = { dashed: [6, 4], dotted: [2, 3] };
|
|
11954
|
+
TRACE_SERIES_COLORS = ["#2563eb", "#dc2626", "#16a34a", "#f59e0b"];
|
|
11821
11955
|
LearningCanvas = ({
|
|
11822
11956
|
className,
|
|
11823
11957
|
width = 600,
|
|
11824
11958
|
height = 400,
|
|
11825
11959
|
backgroundColor,
|
|
11826
11960
|
shapes = [],
|
|
11961
|
+
readouts,
|
|
11962
|
+
traces,
|
|
11827
11963
|
interactive = false,
|
|
11828
11964
|
animate = false,
|
|
11829
11965
|
onShapeClick,
|
|
@@ -11849,6 +11985,12 @@ var init_LearningCanvas = __esm({
|
|
|
11849
11985
|
}
|
|
11850
11986
|
return -1;
|
|
11851
11987
|
}, [shapes]);
|
|
11988
|
+
const derivedShapes = useMemo(() => {
|
|
11989
|
+
if (!traces?.length && !readouts?.length) return shapes;
|
|
11990
|
+
const traceOut = (traces ?? []).flatMap((panel, k) => traceShapes(panel, k, width, height));
|
|
11991
|
+
const readoutOut = readouts?.length ? readoutShapes(readouts, width) : [];
|
|
11992
|
+
return [...shapes, ...traceOut, ...readoutOut];
|
|
11993
|
+
}, [shapes, traces, readouts, width, height]);
|
|
11852
11994
|
const draw = useCallback(() => {
|
|
11853
11995
|
const canvas = canvasRef.current;
|
|
11854
11996
|
if (!canvas) return;
|
|
@@ -11865,13 +12007,13 @@ var init_LearningCanvas = __esm({
|
|
|
11865
12007
|
ctx.fillStyle = backgroundColor;
|
|
11866
12008
|
ctx.fillRect(0, 0, width, height);
|
|
11867
12009
|
}
|
|
11868
|
-
for (const shape of
|
|
11869
|
-
if (shape.type !== "text") drawShape(ctx, shape, width, height);
|
|
12010
|
+
for (const shape of derivedShapes) {
|
|
12011
|
+
if (shape.type !== "text") drawShape(ctx, shape, width, height, derivedShapes);
|
|
11870
12012
|
}
|
|
11871
|
-
for (const shape of
|
|
11872
|
-
if (shape.type === "text") drawShape(ctx, shape, width, height);
|
|
12013
|
+
for (const shape of derivedShapes) {
|
|
12014
|
+
if (shape.type === "text") drawShape(ctx, shape, width, height, derivedShapes);
|
|
11873
12015
|
}
|
|
11874
|
-
}, [width, height, backgroundColor,
|
|
12016
|
+
}, [width, height, backgroundColor, derivedShapes]);
|
|
11875
12017
|
useEffect(() => {
|
|
11876
12018
|
draw();
|
|
11877
12019
|
}, [draw]);
|
|
@@ -13421,7 +13563,363 @@ var init_ComponentPatterns = __esm({
|
|
|
13421
13563
|
AlertPattern.displayName = "AlertPattern";
|
|
13422
13564
|
}
|
|
13423
13565
|
});
|
|
13424
|
-
|
|
13566
|
+
function layoutCircle(nodes, width, height) {
|
|
13567
|
+
const cx = width / 2;
|
|
13568
|
+
const cy = height / 2;
|
|
13569
|
+
const radius = Math.max(10, Math.min(cx, cy) - 40);
|
|
13570
|
+
const positions = /* @__PURE__ */ new Map();
|
|
13571
|
+
const n = nodes.length;
|
|
13572
|
+
nodes.forEach((node, i) => {
|
|
13573
|
+
const angle = 2 * Math.PI * i / Math.max(n, 1) - Math.PI / 2;
|
|
13574
|
+
positions.set(node.id, { x: cx + radius * Math.cos(angle), y: cy + radius * Math.sin(angle) });
|
|
13575
|
+
});
|
|
13576
|
+
return positions;
|
|
13577
|
+
}
|
|
13578
|
+
function layoutTree(nodes, edges, root, width, height) {
|
|
13579
|
+
const nodeIds = nodes.map((n) => n.id);
|
|
13580
|
+
const idSet = new Set(nodeIds);
|
|
13581
|
+
const childrenOf = /* @__PURE__ */ new Map();
|
|
13582
|
+
const hasIncoming = /* @__PURE__ */ new Set();
|
|
13583
|
+
for (const e of edges) {
|
|
13584
|
+
if (!idSet.has(e.from) || !idSet.has(e.to)) continue;
|
|
13585
|
+
const list = childrenOf.get(e.from) ?? [];
|
|
13586
|
+
list.push(e.to);
|
|
13587
|
+
childrenOf.set(e.from, list);
|
|
13588
|
+
hasIncoming.add(e.to);
|
|
13589
|
+
}
|
|
13590
|
+
const depth = /* @__PURE__ */ new Map();
|
|
13591
|
+
const treeChildren = /* @__PURE__ */ new Map();
|
|
13592
|
+
const visited = /* @__PURE__ */ new Set();
|
|
13593
|
+
const bfsFrom = (start) => {
|
|
13594
|
+
if (visited.has(start)) return;
|
|
13595
|
+
visited.add(start);
|
|
13596
|
+
depth.set(start, 0);
|
|
13597
|
+
const queue = [start];
|
|
13598
|
+
while (queue.length > 0) {
|
|
13599
|
+
const u = queue.shift();
|
|
13600
|
+
for (const v of childrenOf.get(u) ?? []) {
|
|
13601
|
+
if (visited.has(v)) continue;
|
|
13602
|
+
visited.add(v);
|
|
13603
|
+
depth.set(v, (depth.get(u) ?? 0) + 1);
|
|
13604
|
+
const list = treeChildren.get(u) ?? [];
|
|
13605
|
+
list.push(v);
|
|
13606
|
+
treeChildren.set(u, list);
|
|
13607
|
+
queue.push(v);
|
|
13608
|
+
}
|
|
13609
|
+
}
|
|
13610
|
+
};
|
|
13611
|
+
const primaryRoot = root && idSet.has(root) ? root : nodeIds.find((id) => !hasIncoming.has(id)) ?? nodeIds[0];
|
|
13612
|
+
const rootsOrder = [];
|
|
13613
|
+
if (primaryRoot !== void 0) {
|
|
13614
|
+
bfsFrom(primaryRoot);
|
|
13615
|
+
rootsOrder.push(primaryRoot);
|
|
13616
|
+
}
|
|
13617
|
+
for (const id of nodeIds) {
|
|
13618
|
+
if (!visited.has(id)) {
|
|
13619
|
+
bfsFrom(id);
|
|
13620
|
+
rootsOrder.push(id);
|
|
13621
|
+
}
|
|
13622
|
+
}
|
|
13623
|
+
let leafCounter = 0;
|
|
13624
|
+
const xSlot = /* @__PURE__ */ new Map();
|
|
13625
|
+
const assignXSlot = (u) => {
|
|
13626
|
+
const children = treeChildren.get(u) ?? [];
|
|
13627
|
+
if (children.length === 0) {
|
|
13628
|
+
const slot = leafCounter++;
|
|
13629
|
+
xSlot.set(u, slot);
|
|
13630
|
+
return slot;
|
|
13631
|
+
}
|
|
13632
|
+
const childSlots = children.map(assignXSlot);
|
|
13633
|
+
const avg = childSlots.reduce((a, b) => a + b, 0) / childSlots.length;
|
|
13634
|
+
xSlot.set(u, avg);
|
|
13635
|
+
return avg;
|
|
13636
|
+
};
|
|
13637
|
+
for (const r of rootsOrder) assignXSlot(r);
|
|
13638
|
+
let maxDepth = 0;
|
|
13639
|
+
for (const d of depth.values()) maxDepth = Math.max(maxDepth, d);
|
|
13640
|
+
const colWidth = width / Math.max(1, leafCounter);
|
|
13641
|
+
const rowHeight = height / (maxDepth + 1);
|
|
13642
|
+
const positions = /* @__PURE__ */ new Map();
|
|
13643
|
+
for (const id of nodeIds) {
|
|
13644
|
+
const slot = xSlot.get(id) ?? 0;
|
|
13645
|
+
const d = depth.get(id) ?? 0;
|
|
13646
|
+
positions.set(id, { x: slot * colWidth + colWidth / 2, y: d * rowHeight + rowHeight / 2 });
|
|
13647
|
+
}
|
|
13648
|
+
return positions;
|
|
13649
|
+
}
|
|
13650
|
+
function layoutLayered(nodes, edges, width, height) {
|
|
13651
|
+
const nodeIds = nodes.map((n) => n.id);
|
|
13652
|
+
const idSet = new Set(nodeIds);
|
|
13653
|
+
const adj = /* @__PURE__ */ new Map();
|
|
13654
|
+
const remainingIndegree = /* @__PURE__ */ new Map();
|
|
13655
|
+
for (const id of nodeIds) remainingIndegree.set(id, 0);
|
|
13656
|
+
for (const e of edges) {
|
|
13657
|
+
if (!idSet.has(e.from) || !idSet.has(e.to)) continue;
|
|
13658
|
+
const list = adj.get(e.from) ?? [];
|
|
13659
|
+
list.push(e.to);
|
|
13660
|
+
adj.set(e.from, list);
|
|
13661
|
+
remainingIndegree.set(e.to, (remainingIndegree.get(e.to) ?? 0) + 1);
|
|
13662
|
+
}
|
|
13663
|
+
const layer = /* @__PURE__ */ new Map();
|
|
13664
|
+
const dequeued = /* @__PURE__ */ new Set();
|
|
13665
|
+
const queue = [];
|
|
13666
|
+
for (const id of nodeIds) {
|
|
13667
|
+
if ((remainingIndegree.get(id) ?? 0) === 0) {
|
|
13668
|
+
layer.set(id, 0);
|
|
13669
|
+
queue.push(id);
|
|
13670
|
+
}
|
|
13671
|
+
}
|
|
13672
|
+
while (queue.length > 0) {
|
|
13673
|
+
const u = queue.shift();
|
|
13674
|
+
dequeued.add(u);
|
|
13675
|
+
for (const v of adj.get(u) ?? []) {
|
|
13676
|
+
const candidate = (layer.get(u) ?? 0) + 1;
|
|
13677
|
+
layer.set(v, Math.max(layer.get(v) ?? 0, candidate));
|
|
13678
|
+
remainingIndegree.set(v, (remainingIndegree.get(v) ?? 0) - 1);
|
|
13679
|
+
if ((remainingIndegree.get(v) ?? 0) === 0 && !dequeued.has(v)) {
|
|
13680
|
+
queue.push(v);
|
|
13681
|
+
}
|
|
13682
|
+
}
|
|
13683
|
+
}
|
|
13684
|
+
let baseMaxLayer = 0;
|
|
13685
|
+
for (const id of nodeIds) {
|
|
13686
|
+
if (dequeued.has(id)) baseMaxLayer = Math.max(baseMaxLayer, layer.get(id) ?? 0);
|
|
13687
|
+
}
|
|
13688
|
+
const cycleLayer = baseMaxLayer + 1;
|
|
13689
|
+
let maxLayer = baseMaxLayer;
|
|
13690
|
+
for (const id of nodeIds) {
|
|
13691
|
+
if (!dequeued.has(id)) {
|
|
13692
|
+
layer.set(id, cycleLayer);
|
|
13693
|
+
maxLayer = cycleLayer;
|
|
13694
|
+
}
|
|
13695
|
+
}
|
|
13696
|
+
const colWidth = width / Math.max(1, maxLayer + 1);
|
|
13697
|
+
const byLayer = /* @__PURE__ */ new Map();
|
|
13698
|
+
for (const id of nodeIds) {
|
|
13699
|
+
const l = layer.get(id) ?? 0;
|
|
13700
|
+
const list = byLayer.get(l) ?? [];
|
|
13701
|
+
list.push(id);
|
|
13702
|
+
byLayer.set(l, list);
|
|
13703
|
+
}
|
|
13704
|
+
const positions = /* @__PURE__ */ new Map();
|
|
13705
|
+
for (const [l, ids] of byLayer) {
|
|
13706
|
+
const rowHeight = height / ids.length;
|
|
13707
|
+
ids.forEach((id, i) => {
|
|
13708
|
+
positions.set(id, { x: l * colWidth + colWidth / 2, y: i * rowHeight + rowHeight / 2 });
|
|
13709
|
+
});
|
|
13710
|
+
}
|
|
13711
|
+
return positions;
|
|
13712
|
+
}
|
|
13713
|
+
function computePositions(nodes, edges, layout, root, width, height) {
|
|
13714
|
+
switch (layout) {
|
|
13715
|
+
case "circle":
|
|
13716
|
+
return layoutCircle(nodes, width, height);
|
|
13717
|
+
case "tree":
|
|
13718
|
+
return layoutTree(nodes, edges, root, width, height);
|
|
13719
|
+
case "layered":
|
|
13720
|
+
return layoutLayered(nodes, edges, width, height);
|
|
13721
|
+
case "manual":
|
|
13722
|
+
default: {
|
|
13723
|
+
const positions = /* @__PURE__ */ new Map();
|
|
13724
|
+
for (const n of nodes) positions.set(n.id, { x: n.x ?? 0, y: n.y ?? 0 });
|
|
13725
|
+
return positions;
|
|
13726
|
+
}
|
|
13727
|
+
}
|
|
13728
|
+
}
|
|
13729
|
+
var NODE_STATE_COLOR, EDGE_STATE_COLOR, DEFAULT_NODE_RADIUS, AlgoGraphCanvas;
|
|
13730
|
+
var init_AlgoGraphCanvas = __esm({
|
|
13731
|
+
"components/learning/molecules/AlgoGraphCanvas.tsx"() {
|
|
13732
|
+
"use client";
|
|
13733
|
+
init_atoms();
|
|
13734
|
+
init_Stack();
|
|
13735
|
+
init_LearningCanvas();
|
|
13736
|
+
NODE_STATE_COLOR = {
|
|
13737
|
+
unvisited: "#cbd5e1",
|
|
13738
|
+
frontier: "#f59e0b",
|
|
13739
|
+
current: "#ef4444",
|
|
13740
|
+
visited: "#22c55e",
|
|
13741
|
+
goal: "#8b5cf6",
|
|
13742
|
+
path: "#0ea5e9"
|
|
13743
|
+
};
|
|
13744
|
+
EDGE_STATE_COLOR = {
|
|
13745
|
+
default: "#9ca3af",
|
|
13746
|
+
tree: "#16a34a",
|
|
13747
|
+
relaxed: "#f59e0b",
|
|
13748
|
+
candidate: "#38bdf8",
|
|
13749
|
+
path: "#dc2626"
|
|
13750
|
+
};
|
|
13751
|
+
DEFAULT_NODE_RADIUS = 18;
|
|
13752
|
+
AlgoGraphCanvas = ({
|
|
13753
|
+
className,
|
|
13754
|
+
width = 600,
|
|
13755
|
+
height = 400,
|
|
13756
|
+
title,
|
|
13757
|
+
backgroundColor,
|
|
13758
|
+
nodes = [],
|
|
13759
|
+
edges = [],
|
|
13760
|
+
layout = "manual",
|
|
13761
|
+
root,
|
|
13762
|
+
shapes = [],
|
|
13763
|
+
interactive = false,
|
|
13764
|
+
animate = false,
|
|
13765
|
+
onShapeClick,
|
|
13766
|
+
onNodeClick,
|
|
13767
|
+
isLoading,
|
|
13768
|
+
error
|
|
13769
|
+
}) => {
|
|
13770
|
+
const nodeById = useMemo(() => {
|
|
13771
|
+
const m = /* @__PURE__ */ new Map();
|
|
13772
|
+
for (const n of nodes) m.set(n.id, n);
|
|
13773
|
+
return m;
|
|
13774
|
+
}, [nodes]);
|
|
13775
|
+
const nodeIndexById = useMemo(() => {
|
|
13776
|
+
const m = /* @__PURE__ */ new Map();
|
|
13777
|
+
nodes.forEach((n, i) => m.set(n.id, i));
|
|
13778
|
+
return m;
|
|
13779
|
+
}, [nodes]);
|
|
13780
|
+
const derivedShapes = useMemo(() => {
|
|
13781
|
+
const out = [];
|
|
13782
|
+
const positions = computePositions(nodes, edges, layout, root, width, height);
|
|
13783
|
+
const edgeGeoms = [];
|
|
13784
|
+
for (const e of edges) {
|
|
13785
|
+
const a = nodeById.get(e.from);
|
|
13786
|
+
const b = nodeById.get(e.to);
|
|
13787
|
+
const posA = positions.get(e.from);
|
|
13788
|
+
const posB = positions.get(e.to);
|
|
13789
|
+
if (!a || !b || !posA || !posB) continue;
|
|
13790
|
+
const rA = a.radius ?? DEFAULT_NODE_RADIUS;
|
|
13791
|
+
const rB = b.radius ?? DEFAULT_NODE_RADIUS;
|
|
13792
|
+
const dx = posB.x - posA.x;
|
|
13793
|
+
const dy = posB.y - posA.y;
|
|
13794
|
+
const dist = Math.max(1e-6, Math.hypot(dx, dy));
|
|
13795
|
+
const ux = dx / dist;
|
|
13796
|
+
const uy = dy / dist;
|
|
13797
|
+
edgeGeoms.push({
|
|
13798
|
+
directed: e.directed ?? false,
|
|
13799
|
+
x1: posA.x + ux * rA,
|
|
13800
|
+
y1: posA.y + uy * rA,
|
|
13801
|
+
x2: posB.x - ux * rB,
|
|
13802
|
+
y2: posB.y - uy * rB,
|
|
13803
|
+
color: e.color ?? EDGE_STATE_COLOR[e.state ?? "default"],
|
|
13804
|
+
label: e.label ?? (e.weight != null ? String(e.weight) : void 0)
|
|
13805
|
+
});
|
|
13806
|
+
}
|
|
13807
|
+
for (const g of edgeGeoms) {
|
|
13808
|
+
out.push({
|
|
13809
|
+
type: g.directed ? "arrow" : "line",
|
|
13810
|
+
x1: g.x1,
|
|
13811
|
+
y1: g.y1,
|
|
13812
|
+
x2: g.x2,
|
|
13813
|
+
y2: g.y2,
|
|
13814
|
+
color: g.color,
|
|
13815
|
+
lineWidth: 2
|
|
13816
|
+
});
|
|
13817
|
+
}
|
|
13818
|
+
for (const g of edgeGeoms) {
|
|
13819
|
+
if (g.label === void 0) continue;
|
|
13820
|
+
const midX = (g.x1 + g.x2) / 2;
|
|
13821
|
+
const midY = (g.y1 + g.y2) / 2;
|
|
13822
|
+
const dx = g.x2 - g.x1;
|
|
13823
|
+
const dy = g.y2 - g.y1;
|
|
13824
|
+
const dist = Math.max(1e-6, Math.hypot(dx, dy));
|
|
13825
|
+
const perpX = -(dy / dist);
|
|
13826
|
+
const perpY = dx / dist;
|
|
13827
|
+
out.push({
|
|
13828
|
+
type: "text",
|
|
13829
|
+
x: midX + perpX * 10,
|
|
13830
|
+
y: midY + perpY * 10,
|
|
13831
|
+
text: g.label,
|
|
13832
|
+
fontSize: 11,
|
|
13833
|
+
align: "center",
|
|
13834
|
+
color: "#374151"
|
|
13835
|
+
});
|
|
13836
|
+
}
|
|
13837
|
+
const nodeGeoms = [];
|
|
13838
|
+
for (const n of nodes) {
|
|
13839
|
+
const pos = positions.get(n.id);
|
|
13840
|
+
if (!pos) continue;
|
|
13841
|
+
nodeGeoms.push({
|
|
13842
|
+
id: n.id,
|
|
13843
|
+
x: pos.x,
|
|
13844
|
+
y: pos.y,
|
|
13845
|
+
radius: n.radius ?? DEFAULT_NODE_RADIUS,
|
|
13846
|
+
color: n.color ?? NODE_STATE_COLOR[n.state ?? "unvisited"],
|
|
13847
|
+
label: n.label,
|
|
13848
|
+
badge: n.badge
|
|
13849
|
+
});
|
|
13850
|
+
}
|
|
13851
|
+
for (const g of nodeGeoms) {
|
|
13852
|
+
out.push({ type: "circle", id: g.id, x: g.x, y: g.y, radius: g.radius, color: g.color, fill: `${g.color}33` });
|
|
13853
|
+
}
|
|
13854
|
+
const badgeGeoms = [];
|
|
13855
|
+
for (const g of nodeGeoms) {
|
|
13856
|
+
if (!g.badge) continue;
|
|
13857
|
+
const w = Math.min(42, Math.max(18, g.badge.text.length * 6 + 10));
|
|
13858
|
+
badgeGeoms.push({
|
|
13859
|
+
cx: g.x + g.radius * 0.75,
|
|
13860
|
+
cy: g.y - g.radius * 0.75,
|
|
13861
|
+
w,
|
|
13862
|
+
h: 14,
|
|
13863
|
+
// Borderless pill: same color drives both stroke and fill.
|
|
13864
|
+
color: g.badge.color ?? "#1e293b",
|
|
13865
|
+
text: g.badge.text
|
|
13866
|
+
});
|
|
13867
|
+
}
|
|
13868
|
+
for (const b of badgeGeoms) {
|
|
13869
|
+
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 });
|
|
13870
|
+
}
|
|
13871
|
+
for (const b of badgeGeoms) {
|
|
13872
|
+
out.push({ type: "text", x: b.cx, y: b.cy, text: b.text, fontSize: 9, align: "center", color: "#ffffff" });
|
|
13873
|
+
}
|
|
13874
|
+
for (const g of nodeGeoms) {
|
|
13875
|
+
if (g.label === void 0) continue;
|
|
13876
|
+
out.push({
|
|
13877
|
+
type: "text",
|
|
13878
|
+
x: g.x,
|
|
13879
|
+
y: g.y + g.radius + 14,
|
|
13880
|
+
text: g.label,
|
|
13881
|
+
fontSize: 12,
|
|
13882
|
+
align: "center",
|
|
13883
|
+
color: "#111827"
|
|
13884
|
+
});
|
|
13885
|
+
}
|
|
13886
|
+
out.push(...shapes);
|
|
13887
|
+
return out;
|
|
13888
|
+
}, [nodes, edges, layout, root, width, height, nodeById, shapes]);
|
|
13889
|
+
const handleShapeClick = useCallback(
|
|
13890
|
+
(payload) => {
|
|
13891
|
+
if (payload.type === "circle" && payload.id) {
|
|
13892
|
+
const node = nodeById.get(payload.id);
|
|
13893
|
+
const idx = nodeIndexById.get(payload.id);
|
|
13894
|
+
if (node && idx !== void 0) {
|
|
13895
|
+
onNodeClick?.({ id: node.id, label: node.label, index: idx });
|
|
13896
|
+
}
|
|
13897
|
+
}
|
|
13898
|
+
onShapeClick?.(payload);
|
|
13899
|
+
},
|
|
13900
|
+
[nodeById, nodeIndexById, onNodeClick, onShapeClick]
|
|
13901
|
+
);
|
|
13902
|
+
return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
|
|
13903
|
+
title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
|
|
13904
|
+
/* @__PURE__ */ jsx(
|
|
13905
|
+
LearningCanvas,
|
|
13906
|
+
{
|
|
13907
|
+
width,
|
|
13908
|
+
height,
|
|
13909
|
+
backgroundColor,
|
|
13910
|
+
shapes: derivedShapes,
|
|
13911
|
+
interactive,
|
|
13912
|
+
animate,
|
|
13913
|
+
onShapeClick: onShapeClick || onNodeClick ? handleShapeClick : void 0,
|
|
13914
|
+
isLoading,
|
|
13915
|
+
error
|
|
13916
|
+
}
|
|
13917
|
+
)
|
|
13918
|
+
] }) });
|
|
13919
|
+
};
|
|
13920
|
+
}
|
|
13921
|
+
});
|
|
13922
|
+
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;
|
|
13425
13923
|
var init_AlgorithmCanvas = __esm({
|
|
13426
13924
|
"components/learning/molecules/AlgorithmCanvas.tsx"() {
|
|
13427
13925
|
"use client";
|
|
@@ -13433,6 +13931,43 @@ var init_AlgorithmCanvas = __esm({
|
|
|
13433
13931
|
DEFAULT_POINTER_COLOR = "#dc2626";
|
|
13434
13932
|
POINTER_BAND = 34;
|
|
13435
13933
|
TOP_PAD = 26;
|
|
13934
|
+
PANEL_FAMILY_ORDER = ["bars", "slots", "cells", "buckets", "frames"];
|
|
13935
|
+
RANGE_COLOR_DEFAULT = "#3b82f6";
|
|
13936
|
+
RANGE_FILL_OPACITY = 0.15;
|
|
13937
|
+
BRACKET_TOP_OFFSET = 16;
|
|
13938
|
+
BRACKET_ROW_H = 14;
|
|
13939
|
+
BRACKET_TICK_H = 6;
|
|
13940
|
+
BRACKET_LABEL_OFFSET = 6;
|
|
13941
|
+
SLOT_EMPTY_FILL = "#f1f5f9";
|
|
13942
|
+
SLOT_EMPTY_STROKE = "#cbd5e1";
|
|
13943
|
+
SLOT_FILLED_STROKE = "#9ca3af";
|
|
13944
|
+
SLOT_HIGHLIGHT_DEFAULT = "#f59e0b";
|
|
13945
|
+
SLOT_VALUE_TEXT_COLOR = "#ffffff";
|
|
13946
|
+
FRAME_ACTIVE_COLOR = "#3b82f6";
|
|
13947
|
+
FRAME_RETURNING_COLOR = "#f59e0b";
|
|
13948
|
+
FRAME_DONE_COLOR = "#94a3b8";
|
|
13949
|
+
FRAME_LABEL_COLOR = "#ffffff";
|
|
13950
|
+
FRAME_DETAIL_COLOR = "#e2e8f0";
|
|
13951
|
+
FRAME_TWO_LINE_MIN_H = 22;
|
|
13952
|
+
BUCKET_INDEX_FILL = "#e2e8f0";
|
|
13953
|
+
BUCKET_INDEX_STROKE = "#9ca3af";
|
|
13954
|
+
BUCKET_INDEX_TEXT = "#374151";
|
|
13955
|
+
BUCKET_ENTRY_TEXT = "#ffffff";
|
|
13956
|
+
BUCKET_ENTRY_DEFAULT = "#3b82f6";
|
|
13957
|
+
BUCKET_ENTRY_HIGHLIGHT = "#f59e0b";
|
|
13958
|
+
BUCKET_ENTRY_PROBING = "#38bdf8";
|
|
13959
|
+
BUCKET_ENTRY_MIN_W = 24;
|
|
13960
|
+
BUCKET_ENTRY_MAX_W = 64;
|
|
13961
|
+
AXIS_LABEL_COLOR = "#6b7280";
|
|
13962
|
+
AXIS_LABEL_FONT_SIZE = 10;
|
|
13963
|
+
CORNER_TEXT_COLOR = "#111827";
|
|
13964
|
+
CORNER_FONT_SIZE = 7;
|
|
13965
|
+
CORNER_MIN_CELL = 28;
|
|
13966
|
+
CORNER_INSET_X = 3;
|
|
13967
|
+
CORNER_INSET_Y = 6;
|
|
13968
|
+
AUX_PRIMARY_RATIO = 0.6;
|
|
13969
|
+
AUX_LABEL_BAND = 18;
|
|
13970
|
+
AUX_BASELINE_PAD = 8;
|
|
13436
13971
|
AlgorithmCanvas = ({
|
|
13437
13972
|
className,
|
|
13438
13973
|
width = 600,
|
|
@@ -13442,6 +13977,14 @@ var init_AlgorithmCanvas = __esm({
|
|
|
13442
13977
|
bars = [],
|
|
13443
13978
|
cells = [],
|
|
13444
13979
|
pointers = [],
|
|
13980
|
+
ranges = [],
|
|
13981
|
+
slots = [],
|
|
13982
|
+
slotOrientation = "horizontal",
|
|
13983
|
+
frames = [],
|
|
13984
|
+
buckets = [],
|
|
13985
|
+
auxBars = [],
|
|
13986
|
+
rowLabels = [],
|
|
13987
|
+
colLabels = [],
|
|
13445
13988
|
shapes = [],
|
|
13446
13989
|
interactive = false,
|
|
13447
13990
|
animate = false,
|
|
@@ -13451,12 +13994,35 @@ var init_AlgorithmCanvas = __esm({
|
|
|
13451
13994
|
}) => {
|
|
13452
13995
|
const derivedShapes = useMemo(() => {
|
|
13453
13996
|
const out = [];
|
|
13997
|
+
const presence = {
|
|
13998
|
+
bars: bars.length > 0,
|
|
13999
|
+
slots: slots.length > 0,
|
|
14000
|
+
cells: cells.length > 0,
|
|
14001
|
+
buckets: buckets.length > 0,
|
|
14002
|
+
frames: frames.length > 0
|
|
14003
|
+
};
|
|
14004
|
+
const panelCount = PANEL_FAMILY_ORDER.filter((f3) => presence[f3]).length;
|
|
14005
|
+
const panelHeight = height / Math.max(1, panelCount);
|
|
14006
|
+
const panelY = { bars: 0, slots: 0, cells: 0, buckets: 0, frames: 0 };
|
|
14007
|
+
let compactIndex = 0;
|
|
14008
|
+
PANEL_FAMILY_ORDER.forEach((f3) => {
|
|
14009
|
+
if (presence[f3]) {
|
|
14010
|
+
panelY[f3] = compactIndex * panelHeight;
|
|
14011
|
+
compactIndex += 1;
|
|
14012
|
+
}
|
|
14013
|
+
});
|
|
13454
14014
|
if (bars.length > 0) {
|
|
14015
|
+
const panelYBars = panelY.bars;
|
|
13455
14016
|
const slot = width / bars.length;
|
|
13456
14017
|
const barW = slot * 0.8;
|
|
13457
14018
|
const gap = slot * 0.1;
|
|
13458
|
-
const
|
|
13459
|
-
const
|
|
14019
|
+
const bracketRanges = ranges.filter((r) => r.kind === "bracket");
|
|
14020
|
+
const bracketCount = bracketRanges.length;
|
|
14021
|
+
const bracketHeadroom = bracketCount > 0 ? BRACKET_TOP_OFFSET + bracketCount * BRACKET_ROW_H : 0;
|
|
14022
|
+
const hasAux = auxBars.length > 0;
|
|
14023
|
+
const primaryH = hasAux ? panelHeight * AUX_PRIMARY_RATIO : panelHeight;
|
|
14024
|
+
const baseline = panelYBars + primaryH - POINTER_BAND;
|
|
14025
|
+
const usableH = baseline - (panelYBars + TOP_PAD + bracketHeadroom);
|
|
13460
14026
|
const maxV = Math.max(1, ...bars.map((b) => Number.isFinite(b.value) ? b.value : 0));
|
|
13461
14027
|
bars.forEach((bar, i) => {
|
|
13462
14028
|
const v = Number.isFinite(bar.value) ? bar.value : 0;
|
|
@@ -13486,6 +14052,89 @@ var init_AlgorithmCanvas = __esm({
|
|
|
13486
14052
|
});
|
|
13487
14053
|
}
|
|
13488
14054
|
});
|
|
14055
|
+
ranges.forEach((r) => {
|
|
14056
|
+
const kind = r.kind ?? "fill";
|
|
14057
|
+
if (kind !== "fill") return;
|
|
14058
|
+
const color = r.color ?? RANGE_COLOR_DEFAULT;
|
|
14059
|
+
out.push({
|
|
14060
|
+
type: "rect",
|
|
14061
|
+
x: r.from * slot,
|
|
14062
|
+
y: panelYBars,
|
|
14063
|
+
width: (r.to - r.from + 1) * slot,
|
|
14064
|
+
height: primaryH,
|
|
14065
|
+
color,
|
|
14066
|
+
fill: color,
|
|
14067
|
+
opacity: RANGE_FILL_OPACITY
|
|
14068
|
+
});
|
|
14069
|
+
if (r.label) {
|
|
14070
|
+
out.push({
|
|
14071
|
+
type: "text",
|
|
14072
|
+
x: r.from * slot + 4,
|
|
14073
|
+
// Sits below the bracket block (if any) so fill and bracket labels never collide.
|
|
14074
|
+
y: panelYBars + 10 + bracketHeadroom,
|
|
14075
|
+
text: r.label,
|
|
14076
|
+
color,
|
|
14077
|
+
fontSize: 10,
|
|
14078
|
+
align: "left"
|
|
14079
|
+
});
|
|
14080
|
+
}
|
|
14081
|
+
});
|
|
14082
|
+
bracketRanges.forEach((r, i) => {
|
|
14083
|
+
const bracketY = panelYBars + BRACKET_TOP_OFFSET + i * BRACKET_ROW_H;
|
|
14084
|
+
const x1 = r.from * slot + slot * 0.1;
|
|
14085
|
+
const x2 = (r.to + 1) * slot - slot * 0.1;
|
|
14086
|
+
const color = r.color ?? RANGE_COLOR_DEFAULT;
|
|
14087
|
+
out.push({ type: "line", x1, y1: bracketY, x2, y2: bracketY, color, lineWidth: 2 });
|
|
14088
|
+
out.push({ type: "line", x1, y1: bracketY, x2: x1, y2: bracketY + BRACKET_TICK_H, color, lineWidth: 2 });
|
|
14089
|
+
out.push({ type: "line", x1: x2, y1: bracketY, x2, y2: bracketY + BRACKET_TICK_H, color, lineWidth: 2 });
|
|
14090
|
+
if (r.label) {
|
|
14091
|
+
out.push({
|
|
14092
|
+
type: "text",
|
|
14093
|
+
x: (x1 + x2) / 2,
|
|
14094
|
+
y: bracketY - BRACKET_LABEL_OFFSET,
|
|
14095
|
+
text: r.label,
|
|
14096
|
+
color,
|
|
14097
|
+
fontSize: 10,
|
|
14098
|
+
align: "center"
|
|
14099
|
+
});
|
|
14100
|
+
}
|
|
14101
|
+
});
|
|
14102
|
+
if (hasAux) {
|
|
14103
|
+
const auxH = panelHeight - primaryH;
|
|
14104
|
+
const slot2 = width / auxBars.length;
|
|
14105
|
+
const auxBaseline = panelYBars + primaryH + auxH - AUX_BASELINE_PAD;
|
|
14106
|
+
const auxUsableH = auxBaseline - (panelYBars + primaryH + AUX_LABEL_BAND);
|
|
14107
|
+
const maxAuxV = Math.max(1, ...auxBars.map((b) => Number.isFinite(b.value) ? b.value : 0));
|
|
14108
|
+
auxBars.forEach((bar, i) => {
|
|
14109
|
+
const v = Number.isFinite(bar.value) ? bar.value : 0;
|
|
14110
|
+
const bh = Math.max(0, v / maxAuxV * auxUsableH);
|
|
14111
|
+
const x = i * slot2 + slot2 * 0.1;
|
|
14112
|
+
const w = slot2 * 0.8;
|
|
14113
|
+
const color = bar.color ?? DEFAULT_BAR_COLOR;
|
|
14114
|
+
out.push({
|
|
14115
|
+
type: "rect",
|
|
14116
|
+
id: `auxbar-${i}`,
|
|
14117
|
+
x,
|
|
14118
|
+
y: auxBaseline - bh,
|
|
14119
|
+
width: w,
|
|
14120
|
+
height: bh,
|
|
14121
|
+
color,
|
|
14122
|
+
fill: color
|
|
14123
|
+
});
|
|
14124
|
+
const label = bar.label ?? (auxBars.length <= 24 ? String(v) : void 0);
|
|
14125
|
+
if (label) {
|
|
14126
|
+
out.push({
|
|
14127
|
+
type: "text",
|
|
14128
|
+
x: x + w / 2,
|
|
14129
|
+
y: auxBaseline - bh - 8,
|
|
14130
|
+
text: label,
|
|
14131
|
+
color: "#374151",
|
|
14132
|
+
fontSize: 11,
|
|
14133
|
+
align: "center"
|
|
14134
|
+
});
|
|
14135
|
+
}
|
|
14136
|
+
});
|
|
14137
|
+
}
|
|
13489
14138
|
pointers.forEach((p) => {
|
|
13490
14139
|
if (p.index < 0 || p.index >= bars.length) return;
|
|
13491
14140
|
const cx = p.index * slot + slot / 2;
|
|
@@ -13493,7 +14142,7 @@ var init_AlgorithmCanvas = __esm({
|
|
|
13493
14142
|
out.push({
|
|
13494
14143
|
type: "arrow",
|
|
13495
14144
|
x1: cx,
|
|
13496
|
-
y1:
|
|
14145
|
+
y1: panelYBars + primaryH - 18,
|
|
13497
14146
|
x2: cx,
|
|
13498
14147
|
y2: baseline + 4,
|
|
13499
14148
|
color,
|
|
@@ -13503,7 +14152,7 @@ var init_AlgorithmCanvas = __esm({
|
|
|
13503
14152
|
out.push({
|
|
13504
14153
|
type: "text",
|
|
13505
14154
|
x: cx,
|
|
13506
|
-
y:
|
|
14155
|
+
y: panelYBars + primaryH - 8,
|
|
13507
14156
|
text: p.label,
|
|
13508
14157
|
color,
|
|
13509
14158
|
fontSize: 11,
|
|
@@ -13512,14 +14161,111 @@ var init_AlgorithmCanvas = __esm({
|
|
|
13512
14161
|
}
|
|
13513
14162
|
});
|
|
13514
14163
|
}
|
|
14164
|
+
if (slots.length > 0) {
|
|
14165
|
+
const panelYSlots = panelY.slots;
|
|
14166
|
+
const n = slots.length;
|
|
14167
|
+
const vertical = slotOrientation === "vertical";
|
|
14168
|
+
const vBoxH = panelHeight / n;
|
|
14169
|
+
const vBoxW = Math.min(width * 0.5, 120);
|
|
14170
|
+
const vBoxX = (width - vBoxW) / 2;
|
|
14171
|
+
const hCellW = width / n;
|
|
14172
|
+
const hBoxW = hCellW * 0.82;
|
|
14173
|
+
const hBoxH = Math.min(panelHeight * 0.6, 48);
|
|
14174
|
+
const hBoxY = panelYSlots + (panelHeight - hBoxH) / 2;
|
|
14175
|
+
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 };
|
|
14176
|
+
slots.forEach((s, i) => {
|
|
14177
|
+
const box = slotBox(i);
|
|
14178
|
+
const state = s.state ?? "filled";
|
|
14179
|
+
const fill = state === "empty" ? SLOT_EMPTY_FILL : state === "highlight" ? s.color ?? SLOT_HIGHLIGHT_DEFAULT : s.color ?? DEFAULT_BAR_COLOR;
|
|
14180
|
+
const stroke = state === "empty" ? SLOT_EMPTY_STROKE : SLOT_FILLED_STROKE;
|
|
14181
|
+
out.push({
|
|
14182
|
+
type: "rect",
|
|
14183
|
+
id: `slot-${i}`,
|
|
14184
|
+
x: box.x,
|
|
14185
|
+
y: box.y,
|
|
14186
|
+
width: box.width,
|
|
14187
|
+
height: box.height,
|
|
14188
|
+
color: stroke,
|
|
14189
|
+
fill
|
|
14190
|
+
});
|
|
14191
|
+
if (s.value != null && state !== "empty") {
|
|
14192
|
+
out.push({
|
|
14193
|
+
type: "text",
|
|
14194
|
+
x: box.x + box.width / 2,
|
|
14195
|
+
y: box.y + box.height / 2,
|
|
14196
|
+
text: String(s.value),
|
|
14197
|
+
color: SLOT_VALUE_TEXT_COLOR,
|
|
14198
|
+
fontSize: 12,
|
|
14199
|
+
align: "center"
|
|
14200
|
+
});
|
|
14201
|
+
}
|
|
14202
|
+
});
|
|
14203
|
+
if (bars.length === 0) {
|
|
14204
|
+
pointers.forEach((p) => {
|
|
14205
|
+
if (p.index < 0 || p.index >= slots.length) return;
|
|
14206
|
+
const box = slotBox(p.index);
|
|
14207
|
+
const color = p.color ?? DEFAULT_POINTER_COLOR;
|
|
14208
|
+
if (vertical) {
|
|
14209
|
+
const cy = box.y + box.height / 2;
|
|
14210
|
+
out.push({
|
|
14211
|
+
type: "arrow",
|
|
14212
|
+
x1: box.x + box.width + 34,
|
|
14213
|
+
y1: cy,
|
|
14214
|
+
x2: box.x + box.width + 4,
|
|
14215
|
+
y2: cy,
|
|
14216
|
+
color,
|
|
14217
|
+
lineWidth: 2
|
|
14218
|
+
});
|
|
14219
|
+
if (p.label) {
|
|
14220
|
+
out.push({
|
|
14221
|
+
type: "text",
|
|
14222
|
+
x: box.x + box.width + 38,
|
|
14223
|
+
y: cy,
|
|
14224
|
+
text: p.label,
|
|
14225
|
+
color,
|
|
14226
|
+
fontSize: 11,
|
|
14227
|
+
align: "left"
|
|
14228
|
+
});
|
|
14229
|
+
}
|
|
14230
|
+
} else {
|
|
14231
|
+
const cx = box.x + box.width / 2;
|
|
14232
|
+
out.push({
|
|
14233
|
+
type: "arrow",
|
|
14234
|
+
x1: cx,
|
|
14235
|
+
y1: panelYSlots + panelHeight - 18,
|
|
14236
|
+
x2: cx,
|
|
14237
|
+
y2: box.y + box.height + 4,
|
|
14238
|
+
color,
|
|
14239
|
+
lineWidth: 2
|
|
14240
|
+
});
|
|
14241
|
+
if (p.label) {
|
|
14242
|
+
out.push({
|
|
14243
|
+
type: "text",
|
|
14244
|
+
x: cx,
|
|
14245
|
+
y: panelYSlots + panelHeight - 8,
|
|
14246
|
+
text: p.label,
|
|
14247
|
+
color,
|
|
14248
|
+
fontSize: 11,
|
|
14249
|
+
align: "center"
|
|
14250
|
+
});
|
|
14251
|
+
}
|
|
14252
|
+
}
|
|
14253
|
+
});
|
|
14254
|
+
}
|
|
14255
|
+
}
|
|
13515
14256
|
if (cells.length > 0) {
|
|
14257
|
+
const panelYCells = panelY.cells;
|
|
13516
14258
|
const maxCol = Math.max(0, ...cells.map((c) => c.col)) + 1;
|
|
13517
14259
|
const maxRow = Math.max(0, ...cells.map((c) => c.row)) + 1;
|
|
13518
|
-
const
|
|
13519
|
-
const
|
|
14260
|
+
const colLabelH = colLabels.length > 0 ? 16 : 0;
|
|
14261
|
+
const rowLabelW = rowLabels.length > 0 ? 20 : 0;
|
|
14262
|
+
const gridX0 = rowLabelW;
|
|
14263
|
+
const gridY0 = panelYCells + colLabelH;
|
|
14264
|
+
const cw = (width - rowLabelW) / maxCol;
|
|
14265
|
+
const ch = (panelHeight - colLabelH) / maxRow;
|
|
13520
14266
|
cells.forEach((c, i) => {
|
|
13521
|
-
const x = c.col * cw;
|
|
13522
|
-
const y = c.row * ch;
|
|
14267
|
+
const x = gridX0 + c.col * cw;
|
|
14268
|
+
const y = gridY0 + c.row * ch;
|
|
13523
14269
|
const color = c.color ?? DEFAULT_CELL_COLOR;
|
|
13524
14270
|
out.push({
|
|
13525
14271
|
type: "rect",
|
|
@@ -13543,11 +14289,207 @@ var init_AlgorithmCanvas = __esm({
|
|
|
13543
14289
|
align: "center"
|
|
13544
14290
|
});
|
|
13545
14291
|
}
|
|
14292
|
+
if (c.corner && cw >= CORNER_MIN_CELL && ch >= CORNER_MIN_CELL) {
|
|
14293
|
+
const { tl, tr, bl, br } = c.corner;
|
|
14294
|
+
if (tl) {
|
|
14295
|
+
out.push({
|
|
14296
|
+
type: "text",
|
|
14297
|
+
x: x + CORNER_INSET_X,
|
|
14298
|
+
y: y + CORNER_INSET_Y,
|
|
14299
|
+
text: tl,
|
|
14300
|
+
color: CORNER_TEXT_COLOR,
|
|
14301
|
+
fontSize: CORNER_FONT_SIZE,
|
|
14302
|
+
align: "left"
|
|
14303
|
+
});
|
|
14304
|
+
}
|
|
14305
|
+
if (tr) {
|
|
14306
|
+
out.push({
|
|
14307
|
+
type: "text",
|
|
14308
|
+
x: x + cw - CORNER_INSET_X,
|
|
14309
|
+
y: y + CORNER_INSET_Y,
|
|
14310
|
+
text: tr,
|
|
14311
|
+
color: CORNER_TEXT_COLOR,
|
|
14312
|
+
fontSize: CORNER_FONT_SIZE,
|
|
14313
|
+
align: "right"
|
|
14314
|
+
});
|
|
14315
|
+
}
|
|
14316
|
+
if (bl) {
|
|
14317
|
+
out.push({
|
|
14318
|
+
type: "text",
|
|
14319
|
+
x: x + CORNER_INSET_X,
|
|
14320
|
+
y: y + ch - CORNER_INSET_Y,
|
|
14321
|
+
text: bl,
|
|
14322
|
+
color: CORNER_TEXT_COLOR,
|
|
14323
|
+
fontSize: CORNER_FONT_SIZE,
|
|
14324
|
+
align: "left"
|
|
14325
|
+
});
|
|
14326
|
+
}
|
|
14327
|
+
if (br) {
|
|
14328
|
+
out.push({
|
|
14329
|
+
type: "text",
|
|
14330
|
+
x: x + cw - CORNER_INSET_X,
|
|
14331
|
+
y: y + ch - CORNER_INSET_Y,
|
|
14332
|
+
text: br,
|
|
14333
|
+
color: CORNER_TEXT_COLOR,
|
|
14334
|
+
fontSize: CORNER_FONT_SIZE,
|
|
14335
|
+
align: "right"
|
|
14336
|
+
});
|
|
14337
|
+
}
|
|
14338
|
+
}
|
|
14339
|
+
});
|
|
14340
|
+
colLabels.forEach((l) => {
|
|
14341
|
+
out.push({
|
|
14342
|
+
type: "text",
|
|
14343
|
+
x: gridX0 + l.index * cw + cw / 2,
|
|
14344
|
+
y: panelYCells + colLabelH / 2,
|
|
14345
|
+
text: l.text,
|
|
14346
|
+
color: l.color ?? AXIS_LABEL_COLOR,
|
|
14347
|
+
fontSize: AXIS_LABEL_FONT_SIZE,
|
|
14348
|
+
align: "center"
|
|
14349
|
+
});
|
|
14350
|
+
});
|
|
14351
|
+
rowLabels.forEach((l) => {
|
|
14352
|
+
out.push({
|
|
14353
|
+
type: "text",
|
|
14354
|
+
x: rowLabelW - 6,
|
|
14355
|
+
y: gridY0 + l.index * ch + ch / 2,
|
|
14356
|
+
text: l.text,
|
|
14357
|
+
color: l.color ?? AXIS_LABEL_COLOR,
|
|
14358
|
+
fontSize: AXIS_LABEL_FONT_SIZE,
|
|
14359
|
+
align: "right"
|
|
14360
|
+
});
|
|
14361
|
+
});
|
|
14362
|
+
}
|
|
14363
|
+
if (buckets.length > 0) {
|
|
14364
|
+
const panelYBuckets = panelY.buckets;
|
|
14365
|
+
const bucketCount = Math.max(0, ...buckets.map((b) => b.index)) + 1;
|
|
14366
|
+
const rowH = panelHeight / bucketCount;
|
|
14367
|
+
const indexColW = Math.min(width * 0.12, 40);
|
|
14368
|
+
const maxChainLen = Math.max(1, ...buckets.map((b) => b.entries.length));
|
|
14369
|
+
const entryW = Math.min(BUCKET_ENTRY_MAX_W, Math.max(BUCKET_ENTRY_MIN_W, (width - indexColW - 8) / maxChainLen));
|
|
14370
|
+
const maxVisible = Math.floor((width - indexColW - 4) / entryW);
|
|
14371
|
+
buckets.forEach((b) => {
|
|
14372
|
+
const rowY = panelYBuckets + b.index * rowH;
|
|
14373
|
+
out.push({
|
|
14374
|
+
type: "rect",
|
|
14375
|
+
id: `bucket-index-${b.index}`,
|
|
14376
|
+
x: 2,
|
|
14377
|
+
y: rowY + 2,
|
|
14378
|
+
width: indexColW - 4,
|
|
14379
|
+
height: rowH - 4,
|
|
14380
|
+
color: BUCKET_INDEX_STROKE,
|
|
14381
|
+
fill: BUCKET_INDEX_FILL
|
|
14382
|
+
});
|
|
14383
|
+
out.push({
|
|
14384
|
+
type: "text",
|
|
14385
|
+
x: 2 + (indexColW - 4) / 2,
|
|
14386
|
+
y: rowY + rowH / 2,
|
|
14387
|
+
text: String(b.index),
|
|
14388
|
+
color: BUCKET_INDEX_TEXT,
|
|
14389
|
+
fontSize: 10,
|
|
14390
|
+
align: "center"
|
|
14391
|
+
});
|
|
14392
|
+
const overflow = b.entries.length > maxVisible;
|
|
14393
|
+
const visibleCount = overflow ? Math.max(0, maxVisible - 1) : b.entries.length;
|
|
14394
|
+
for (let j = 0; j < visibleCount; j++) {
|
|
14395
|
+
const entry = b.entries[j];
|
|
14396
|
+
const ex = indexColW + 4 + j * entryW;
|
|
14397
|
+
const state = entry.state ?? "default";
|
|
14398
|
+
const fill = state === "highlight" ? entry.color ?? BUCKET_ENTRY_HIGHLIGHT : state === "probing" ? entry.color ?? BUCKET_ENTRY_PROBING : entry.color ?? BUCKET_ENTRY_DEFAULT;
|
|
14399
|
+
out.push({
|
|
14400
|
+
type: "rect",
|
|
14401
|
+
id: `bucket-${b.index}-${j}`,
|
|
14402
|
+
x: ex,
|
|
14403
|
+
y: rowY + 2,
|
|
14404
|
+
width: entryW - 2,
|
|
14405
|
+
height: rowH - 4,
|
|
14406
|
+
color: fill,
|
|
14407
|
+
fill
|
|
14408
|
+
});
|
|
14409
|
+
if (entryW >= 20 && rowH >= 16) {
|
|
14410
|
+
out.push({
|
|
14411
|
+
type: "text",
|
|
14412
|
+
x: ex + (entryW - 2) / 2,
|
|
14413
|
+
y: rowY + rowH / 2,
|
|
14414
|
+
text: entry.label,
|
|
14415
|
+
color: BUCKET_ENTRY_TEXT,
|
|
14416
|
+
fontSize: 10,
|
|
14417
|
+
align: "center"
|
|
14418
|
+
});
|
|
14419
|
+
}
|
|
14420
|
+
}
|
|
14421
|
+
if (overflow) {
|
|
14422
|
+
const ex = indexColW + 4 + visibleCount * entryW;
|
|
14423
|
+
out.push({
|
|
14424
|
+
type: "rect",
|
|
14425
|
+
id: `bucket-${b.index}-overflow`,
|
|
14426
|
+
x: ex,
|
|
14427
|
+
y: rowY + 2,
|
|
14428
|
+
width: entryW - 2,
|
|
14429
|
+
height: rowH - 4,
|
|
14430
|
+
color: BUCKET_ENTRY_DEFAULT,
|
|
14431
|
+
fill: BUCKET_ENTRY_DEFAULT
|
|
14432
|
+
});
|
|
14433
|
+
out.push({
|
|
14434
|
+
type: "text",
|
|
14435
|
+
x: ex + (entryW - 2) / 2,
|
|
14436
|
+
y: rowY + rowH / 2,
|
|
14437
|
+
text: `+${b.entries.length - visibleCount}`,
|
|
14438
|
+
color: BUCKET_ENTRY_TEXT,
|
|
14439
|
+
fontSize: 10,
|
|
14440
|
+
align: "center"
|
|
14441
|
+
});
|
|
14442
|
+
}
|
|
14443
|
+
});
|
|
14444
|
+
}
|
|
14445
|
+
if (frames.length > 0) {
|
|
14446
|
+
const panelYFrames = panelY.frames;
|
|
14447
|
+
const n = frames.length;
|
|
14448
|
+
const frameH = panelHeight / n;
|
|
14449
|
+
const x = 8;
|
|
14450
|
+
const w = width - 16;
|
|
14451
|
+
frames.forEach((f3, i) => {
|
|
14452
|
+
const y = panelYFrames + panelHeight - (i + 1) * frameH;
|
|
14453
|
+
const state = f3.state ?? "active";
|
|
14454
|
+
const fill = state === "returning" ? f3.color ?? FRAME_RETURNING_COLOR : state === "done" ? f3.color ?? FRAME_DONE_COLOR : f3.color ?? FRAME_ACTIVE_COLOR;
|
|
14455
|
+
out.push({ type: "rect", id: `frame-${i}`, x, y, width: w, height: frameH, color: fill, fill });
|
|
14456
|
+
if (frameH >= FRAME_TWO_LINE_MIN_H) {
|
|
14457
|
+
out.push({
|
|
14458
|
+
type: "text",
|
|
14459
|
+
x: 16,
|
|
14460
|
+
y: y + frameH * 0.35,
|
|
14461
|
+
text: f3.label,
|
|
14462
|
+
color: FRAME_LABEL_COLOR,
|
|
14463
|
+
fontSize: 10,
|
|
14464
|
+
align: "left"
|
|
14465
|
+
});
|
|
14466
|
+
if (f3.detail) {
|
|
14467
|
+
out.push({
|
|
14468
|
+
type: "text",
|
|
14469
|
+
x: 16,
|
|
14470
|
+
y: y + frameH * 0.7,
|
|
14471
|
+
text: f3.detail,
|
|
14472
|
+
color: FRAME_DETAIL_COLOR,
|
|
14473
|
+
fontSize: 10,
|
|
14474
|
+
align: "left"
|
|
14475
|
+
});
|
|
14476
|
+
}
|
|
14477
|
+
} else {
|
|
14478
|
+
out.push({
|
|
14479
|
+
type: "text",
|
|
14480
|
+
x: 16,
|
|
14481
|
+
y: y + frameH / 2,
|
|
14482
|
+
text: f3.label,
|
|
14483
|
+
color: FRAME_LABEL_COLOR,
|
|
14484
|
+
fontSize: 10,
|
|
14485
|
+
align: "left"
|
|
14486
|
+
});
|
|
14487
|
+
}
|
|
13546
14488
|
});
|
|
13547
14489
|
}
|
|
13548
14490
|
out.push(...shapes);
|
|
13549
14491
|
return out;
|
|
13550
|
-
}, [bars, cells, pointers, shapes, width, height]);
|
|
14492
|
+
}, [bars, cells, pointers, ranges, slots, slotOrientation, frames, buckets, auxBars, rowLabels, colLabels, shapes, width, height]);
|
|
13551
14493
|
return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
|
|
13552
14494
|
title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
|
|
13553
14495
|
/* @__PURE__ */ jsx(
|
|
@@ -14536,23 +15478,422 @@ var init_BehaviorView = __esm({
|
|
|
14536
15478
|
BehaviorView.displayName = "BehaviorView";
|
|
14537
15479
|
}
|
|
14538
15480
|
});
|
|
14539
|
-
|
|
15481
|
+
function LearningScene3D({
|
|
15482
|
+
className,
|
|
15483
|
+
width = 600,
|
|
15484
|
+
height = 400,
|
|
15485
|
+
title,
|
|
15486
|
+
backgroundColor,
|
|
15487
|
+
drawables,
|
|
15488
|
+
camera,
|
|
15489
|
+
lighting,
|
|
15490
|
+
post,
|
|
15491
|
+
showGrid = false,
|
|
15492
|
+
shadows,
|
|
15493
|
+
interactive,
|
|
15494
|
+
isLoading,
|
|
15495
|
+
error,
|
|
15496
|
+
onItemClick
|
|
15497
|
+
}) {
|
|
15498
|
+
const instanceId = useId().replace(/[^a-zA-Z0-9]/g, "");
|
|
15499
|
+
const clickEvent = onItemClick ? `LEARNING_SCENE_3D.ITEM_CLICK.${instanceId}` : void 0;
|
|
15500
|
+
const onItemClickRef = useRef(onItemClick);
|
|
15501
|
+
onItemClickRef.current = onItemClick;
|
|
15502
|
+
useEventListener(`UI:${clickEvent ?? "LEARNING_SCENE_3D.ITEM_CLICK.__disabled"}`, (event) => {
|
|
15503
|
+
const unitId = event.payload?.unitId;
|
|
15504
|
+
if (typeof unitId === "string") onItemClickRef.current?.(unitId);
|
|
15505
|
+
});
|
|
15506
|
+
if (typeof process !== "undefined" && process.env && process.env.NODE_ENV !== "production" && post?.bloom) {
|
|
15507
|
+
const unknownKeys = Object.keys(post.bloom).filter((k) => !KNOWN_BLOOM_KEYS.has(k));
|
|
15508
|
+
if (unknownKeys.length > 0) {
|
|
15509
|
+
sceneLog.debug("post.bloom has unrecognized keys \u2014 only intensity/threshold/smoothing are read", { unknownKeys });
|
|
15510
|
+
}
|
|
15511
|
+
}
|
|
15512
|
+
const props3d = {
|
|
15513
|
+
drawables,
|
|
15514
|
+
isLoading,
|
|
15515
|
+
error: error ? error.message : null,
|
|
15516
|
+
cameraMode: camera?.mode ?? "perspective",
|
|
15517
|
+
...camera?.zoom !== void 0 ? { zoom: camera.zoom } : {},
|
|
15518
|
+
...camera?.fov !== void 0 ? { fov: camera.fov } : {},
|
|
15519
|
+
...camera?.azimuth !== void 0 ? { azimuth: camera.azimuth } : {},
|
|
15520
|
+
...camera?.elevation !== void 0 ? { elevation: camera.elevation } : {},
|
|
15521
|
+
...camera?.target !== void 0 ? { followTarget: camera.target } : {},
|
|
15522
|
+
backgroundColor,
|
|
15523
|
+
showGrid,
|
|
15524
|
+
...shadows !== void 0 ? { shadows } : {},
|
|
15525
|
+
...interactive !== void 0 ? { controlsEnabled: interactive } : {},
|
|
15526
|
+
lighting,
|
|
15527
|
+
post,
|
|
15528
|
+
...clickEvent !== void 0 ? { unitClickEvent: clickEvent } : {}
|
|
15529
|
+
};
|
|
15530
|
+
return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
|
|
15531
|
+
title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
|
|
15532
|
+
/* @__PURE__ */ jsx("div", { style: { width, height, display: "flex" }, children: /* @__PURE__ */ jsx(Suspense, { fallback: null, children: /* @__PURE__ */ jsx(Canvas3DHost2, { ...props3d }) }) })
|
|
15533
|
+
] }) });
|
|
15534
|
+
}
|
|
15535
|
+
function meshSphere(id, x, y, z, radius, color, opts) {
|
|
15536
|
+
return {
|
|
15537
|
+
type: "draw-mesh",
|
|
15538
|
+
...id !== void 0 ? { id } : {},
|
|
15539
|
+
shape: opts?.shape ?? "sphere",
|
|
15540
|
+
position: { x, y, z },
|
|
15541
|
+
radius,
|
|
15542
|
+
pivot: "center",
|
|
15543
|
+
segments: opts?.segments ?? 24,
|
|
15544
|
+
material: { color, ...opts?.material },
|
|
15545
|
+
...opts?.opacity !== void 0 ? { opacity: opts.opacity } : {}
|
|
15546
|
+
};
|
|
15547
|
+
}
|
|
15548
|
+
function axisRotation(from, to) {
|
|
15549
|
+
const wx = to[0] - from[0];
|
|
15550
|
+
const wy = to[2] - from[2];
|
|
15551
|
+
const wz = to[1] - from[1];
|
|
15552
|
+
const len = Math.sqrt(wx * wx + wy * wy + wz * wz);
|
|
15553
|
+
const tilt = Math.acos(Math.min(1, Math.max(-1, len > 0 ? wy / len : 1)));
|
|
15554
|
+
return [0, Math.atan2(wz, -wx), tilt];
|
|
15555
|
+
}
|
|
15556
|
+
function segmentLength(from, to) {
|
|
15557
|
+
const dx = to[0] - from[0];
|
|
15558
|
+
const dy = to[1] - from[1];
|
|
15559
|
+
const dz = to[2] - from[2];
|
|
15560
|
+
return Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
15561
|
+
}
|
|
15562
|
+
function cylinderBetween(from, to, radius, color) {
|
|
15563
|
+
const len = segmentLength(from, to);
|
|
15564
|
+
if (len < 1e-6) return null;
|
|
15565
|
+
return {
|
|
15566
|
+
type: "draw-mesh",
|
|
15567
|
+
shape: "cylinder",
|
|
15568
|
+
position: { x: (from[0] + to[0]) / 2, y: (from[1] + to[1]) / 2, z: (from[2] + to[2]) / 2 },
|
|
15569
|
+
radius,
|
|
15570
|
+
height: len,
|
|
15571
|
+
rotation: axisRotation(from, to),
|
|
15572
|
+
pivot: "center",
|
|
15573
|
+
segments: 12,
|
|
15574
|
+
material: { color }
|
|
15575
|
+
};
|
|
15576
|
+
}
|
|
15577
|
+
function arrowBetween(from, to, color, shaftRadius = 0.08, id) {
|
|
15578
|
+
const len = segmentLength(from, to);
|
|
15579
|
+
if (len < 1e-6) return null;
|
|
15580
|
+
const tipLen = Math.min(shaftRadius * 8, len * 0.35);
|
|
15581
|
+
const k = (len - tipLen) / len;
|
|
15582
|
+
const delta = [to[0] - from[0], to[1] - from[1], to[2] - from[2]];
|
|
15583
|
+
const shaftEnd = [delta[0] * k, delta[1] * k, delta[2] * k];
|
|
15584
|
+
const tipStart = shaftEnd;
|
|
15585
|
+
const tipEnd = delta;
|
|
15586
|
+
const shaft = cylinderBetween([0, 0, 0], shaftEnd, shaftRadius, color);
|
|
15587
|
+
const tipLenActual = segmentLength(tipStart, tipEnd);
|
|
15588
|
+
const tip = {
|
|
15589
|
+
type: "draw-mesh",
|
|
15590
|
+
shape: "cone",
|
|
15591
|
+
position: {
|
|
15592
|
+
x: (tipStart[0] + tipEnd[0]) / 2,
|
|
15593
|
+
y: (tipStart[1] + tipEnd[1]) / 2,
|
|
15594
|
+
z: (tipStart[2] + tipEnd[2]) / 2
|
|
15595
|
+
},
|
|
15596
|
+
radius: shaftRadius * 3,
|
|
15597
|
+
height: tipLenActual,
|
|
15598
|
+
rotation: axisRotation(tipStart, tipEnd),
|
|
15599
|
+
pivot: "center",
|
|
15600
|
+
segments: 12,
|
|
15601
|
+
material: { color }
|
|
15602
|
+
};
|
|
15603
|
+
return {
|
|
15604
|
+
type: "draw-group",
|
|
15605
|
+
...id !== void 0 ? { id } : {},
|
|
15606
|
+
position: { x: from[0], y: from[1], z: from[2] },
|
|
15607
|
+
items: tipLenActual < 1e-6 ? shaft ? [shaft] : [] : shaft ? [shaft, tip] : [tip]
|
|
15608
|
+
};
|
|
15609
|
+
}
|
|
15610
|
+
function billboardLabel(text, x, y, z, opts) {
|
|
15611
|
+
return {
|
|
15612
|
+
type: "draw-text",
|
|
15613
|
+
text,
|
|
15614
|
+
position: { x, y, z },
|
|
15615
|
+
color: opts?.color ?? "#111827"
|
|
15616
|
+
};
|
|
15617
|
+
}
|
|
15618
|
+
function labelColorForBackground(backgroundColor) {
|
|
15619
|
+
const m = /^#(?:([0-9a-f]{3})|([0-9a-f]{6}))$/i.exec(backgroundColor ?? "");
|
|
15620
|
+
if (!m) return "#111827";
|
|
15621
|
+
const hex = m[1] !== void 0 ? m[1].split("").map((c) => c + c).join("") : m[2];
|
|
15622
|
+
const r = parseInt(hex.slice(0, 2), 16) / 255;
|
|
15623
|
+
const g = parseInt(hex.slice(2, 4), 16) / 255;
|
|
15624
|
+
const b = parseInt(hex.slice(4, 6), 16) / 255;
|
|
15625
|
+
const luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
15626
|
+
return luminance < 0.5 ? "#e5e7eb" : "#111827";
|
|
15627
|
+
}
|
|
15628
|
+
function get3DClickPayload(onShapeClick, idToIndex) {
|
|
15629
|
+
if (!onShapeClick) return void 0;
|
|
15630
|
+
return (id) => onShapeClick({ id, index: idToIndex.get(id) ?? -1 });
|
|
15631
|
+
}
|
|
15632
|
+
function polylineTube(points, radius, color, opts) {
|
|
15633
|
+
const maxSegments = opts?.maxSegments ?? 128;
|
|
15634
|
+
let pts = points;
|
|
15635
|
+
if (pts.length - 1 > maxSegments) {
|
|
15636
|
+
const step = (pts.length - 1) / maxSegments;
|
|
15637
|
+
const kept = [pts[0]];
|
|
15638
|
+
for (let s = 1; s < maxSegments; s++) kept.push(pts[Math.round(s * step)]);
|
|
15639
|
+
kept.push(pts[pts.length - 1]);
|
|
15640
|
+
pts = kept;
|
|
15641
|
+
}
|
|
15642
|
+
const out = [];
|
|
15643
|
+
for (let i = 0; i < pts.length - 1; i++) {
|
|
15644
|
+
const seg = cylinderBetween(pts[i], pts[i + 1], radius, color);
|
|
15645
|
+
if (seg) out.push(opts?.opacity !== void 0 ? { ...seg, opacity: opts.opacity } : seg);
|
|
15646
|
+
}
|
|
15647
|
+
return out;
|
|
15648
|
+
}
|
|
15649
|
+
function heightFieldMesh(spec) {
|
|
15650
|
+
const { nx, ny, heights, spacing = 1, x = 0, y = 0 } = spec;
|
|
15651
|
+
const flatShading = spec.flatShading ?? true;
|
|
15652
|
+
const vertices = [];
|
|
15653
|
+
for (let iy = 0; iy < ny; iy++) {
|
|
15654
|
+
for (let ix = 0; ix < nx; ix++) {
|
|
15655
|
+
vertices.push([
|
|
15656
|
+
x + (ix - (nx - 1) / 2) * spacing,
|
|
15657
|
+
y + (iy - (ny - 1) / 2) * spacing,
|
|
15658
|
+
heights[iy * nx + ix] ?? 0
|
|
15659
|
+
]);
|
|
15660
|
+
}
|
|
15661
|
+
}
|
|
15662
|
+
const bands = [...spec.bands ?? []].sort((a, b) => (a.min ?? -Infinity) - (b.min ?? -Infinity));
|
|
15663
|
+
const facesByBand = /* @__PURE__ */ new Map();
|
|
15664
|
+
for (let iy = 0; iy < ny - 1; iy++) {
|
|
15665
|
+
for (let ix = 0; ix < nx - 1; ix++) {
|
|
15666
|
+
const v00 = iy * nx + ix;
|
|
15667
|
+
const v10 = iy * nx + ix + 1;
|
|
15668
|
+
const v01 = (iy + 1) * nx + ix;
|
|
15669
|
+
const v11 = (iy + 1) * nx + ix + 1;
|
|
15670
|
+
for (const face of [[v00, v10, v01], [v10, v11, v01]]) {
|
|
15671
|
+
const centroid = (vertices[face[0]][2] + vertices[face[1]][2] + vertices[face[2]][2]) / 3;
|
|
15672
|
+
let band = null;
|
|
15673
|
+
for (const b of bands) {
|
|
15674
|
+
if ((b.min ?? -Infinity) <= centroid) band = b;
|
|
15675
|
+
}
|
|
15676
|
+
const key = bands.length > 0 ? band : null;
|
|
15677
|
+
const list = facesByBand.get(key) ?? [];
|
|
15678
|
+
list.push(face);
|
|
15679
|
+
facesByBand.set(key, list);
|
|
15680
|
+
}
|
|
15681
|
+
}
|
|
15682
|
+
}
|
|
15683
|
+
const out = [];
|
|
15684
|
+
for (const [band, faces] of facesByBand) {
|
|
15685
|
+
if (faces.length === 0) continue;
|
|
15686
|
+
out.push({
|
|
15687
|
+
type: "draw-mesh",
|
|
15688
|
+
shape: "polyhedron",
|
|
15689
|
+
position: { x: 0, y: 0, z: 0 },
|
|
15690
|
+
vertices,
|
|
15691
|
+
faces,
|
|
15692
|
+
pivot: "center",
|
|
15693
|
+
material: { color: band?.color ?? spec.color ?? "#64748b", flatShading, side: "double" },
|
|
15694
|
+
...spec.opacity !== void 0 ? { opacity: spec.opacity } : {}
|
|
15695
|
+
});
|
|
15696
|
+
}
|
|
15697
|
+
return out;
|
|
15698
|
+
}
|
|
15699
|
+
function arrowField(vectors, opts) {
|
|
15700
|
+
const scale = opts?.scale ?? 1;
|
|
15701
|
+
const out = [];
|
|
15702
|
+
for (const v of vectors) {
|
|
15703
|
+
const to = [
|
|
15704
|
+
v.from[0] + v.delta[0] * scale,
|
|
15705
|
+
v.from[1] + v.delta[1] * scale,
|
|
15706
|
+
v.from[2] + v.delta[2] * scale
|
|
15707
|
+
];
|
|
15708
|
+
const arrow = arrowBetween(v.from, to, v.color ?? "#dc2626", v.width, v.id);
|
|
15709
|
+
if (arrow) out.push(arrow);
|
|
15710
|
+
if (v.label) out.push(billboardLabel(v.label, to[0], to[1], to[2], { color: opts?.labelColor }));
|
|
15711
|
+
}
|
|
15712
|
+
return out;
|
|
15713
|
+
}
|
|
15714
|
+
function helixDrawables(spec, opts) {
|
|
15715
|
+
const count = spec.count ?? spec.rungs?.length ?? 0;
|
|
15716
|
+
const rungs = Array.from({ length: count }, (_, i) => spec.rungs?.[i] ?? {});
|
|
15717
|
+
const radius = spec.radius ?? 1;
|
|
15718
|
+
const rise = spec.rise ?? 0.34;
|
|
15719
|
+
const twistRad = (spec.twistDeg ?? 36) * (Math.PI / 180);
|
|
15720
|
+
const strandAColor = spec.strandAColor ?? "#38bdf8";
|
|
15721
|
+
const strandBColor = spec.strandBColor ?? "#fb923c";
|
|
15722
|
+
const backboneRadius = spec.backboneRadius ?? 0.16;
|
|
15723
|
+
const rungRadius = spec.rungRadius ?? 0.12;
|
|
15724
|
+
const cx = spec.x ?? 0;
|
|
15725
|
+
const cy = spec.y ?? 0;
|
|
15726
|
+
const cz = spec.z ?? 0;
|
|
15727
|
+
const unwoundCount = spec.unwoundCount ?? 0;
|
|
15728
|
+
const unwindSpread = spec.unwindSpread ?? 1.8;
|
|
15729
|
+
const strandA = [];
|
|
15730
|
+
const strandB = [];
|
|
15731
|
+
for (let i = 0; i < count; i++) {
|
|
15732
|
+
const yi = cy + (i - (count - 1) / 2) * rise;
|
|
15733
|
+
const theta = i * twistRad;
|
|
15734
|
+
const s = i < unwoundCount ? unwindSpread : 1;
|
|
15735
|
+
strandA.push([cx + s * radius * Math.cos(theta), yi, cz + s * radius * Math.sin(theta)]);
|
|
15736
|
+
strandB.push([cx + s * radius * Math.cos(theta + Math.PI), yi, cz + s * radius * Math.sin(theta + Math.PI)]);
|
|
15737
|
+
}
|
|
15738
|
+
const out = [];
|
|
15739
|
+
for (let i = 0; i < count; i++) {
|
|
15740
|
+
out.push(meshSphere(`hx-a-${i}`, strandA[i][0], strandA[i][1], strandA[i][2], backboneRadius, strandAColor));
|
|
15741
|
+
out.push(meshSphere(`hx-b-${i}`, strandB[i][0], strandB[i][1], strandB[i][2], backboneRadius, strandBColor));
|
|
15742
|
+
if (i > 0) {
|
|
15743
|
+
const segA = cylinderBetween(strandA[i - 1], strandA[i], backboneRadius, strandAColor);
|
|
15744
|
+
if (segA) out.push(segA);
|
|
15745
|
+
const segB = cylinderBetween(strandB[i - 1], strandB[i], backboneRadius, strandBColor);
|
|
15746
|
+
if (segB) out.push(segB);
|
|
15747
|
+
}
|
|
15748
|
+
const rung = rungs[i];
|
|
15749
|
+
const rungColor = rung.color ?? "#94a3b8";
|
|
15750
|
+
const rod = cylinderBetween(strandA[i], strandB[i], rungRadius, rungColor);
|
|
15751
|
+
if (rod) out.push(rod);
|
|
15752
|
+
const mid = [
|
|
15753
|
+
(strandA[i][0] + strandB[i][0]) / 2,
|
|
15754
|
+
(strandA[i][1] + strandB[i][1]) / 2,
|
|
15755
|
+
(strandA[i][2] + strandB[i][2]) / 2
|
|
15756
|
+
];
|
|
15757
|
+
const markerRadius = rung.radius ?? rungRadius;
|
|
15758
|
+
out.push(meshSphere(rung.id, mid[0], mid[1], mid[2], markerRadius, rungColor));
|
|
15759
|
+
if (rung.label) out.push(billboardLabel(rung.label, mid[0], mid[1], mid[2] + markerRadius, { color: opts?.labelColor }));
|
|
15760
|
+
}
|
|
15761
|
+
return out;
|
|
15762
|
+
}
|
|
15763
|
+
function latticeDrawables(spec, opts) {
|
|
15764
|
+
const nx = spec.nx ?? 2;
|
|
15765
|
+
const ny = spec.ny ?? 2;
|
|
15766
|
+
const nz = spec.nz ?? 2;
|
|
15767
|
+
const latticeConstant = spec.latticeConstant ?? 2;
|
|
15768
|
+
const bondRadius = spec.bondRadius ?? 0.06;
|
|
15769
|
+
const highlightCell = spec.highlightCell ?? false;
|
|
15770
|
+
const dimColor = spec.dimColor ?? "#475569";
|
|
15771
|
+
const showLabels = spec.showLabels ?? false;
|
|
15772
|
+
const selectedColor = spec.selectedColor ?? "#f59e0b";
|
|
15773
|
+
const posByKey = /* @__PURE__ */ new Map();
|
|
15774
|
+
const inCellByKey = /* @__PURE__ */ new Map();
|
|
15775
|
+
const out = [];
|
|
15776
|
+
for (const site of spec.basis) {
|
|
15777
|
+
const snx = site.xEdge ? nx + 1 : nx;
|
|
15778
|
+
const sny = site.yEdge ? ny + 1 : ny;
|
|
15779
|
+
const snz = site.zEdge ? nz + 1 : nz;
|
|
15780
|
+
for (let i = 0; i < snx; i++) {
|
|
15781
|
+
for (let j = 0; j < sny; j++) {
|
|
15782
|
+
for (let k = 0; k < snz; k++) {
|
|
15783
|
+
const key = `${site.key}-${i}-${j}-${k}`;
|
|
15784
|
+
const inCell = i + site.dx <= 1 && j + site.dy <= 1 && k + site.dz <= 1;
|
|
15785
|
+
const pos = [
|
|
15786
|
+
(i + site.dx) * latticeConstant - nx * latticeConstant / 2,
|
|
15787
|
+
(j + site.dy) * latticeConstant - ny * latticeConstant / 2,
|
|
15788
|
+
(k + site.dz) * latticeConstant - nz * latticeConstant / 2
|
|
15789
|
+
];
|
|
15790
|
+
posByKey.set(key, pos);
|
|
15791
|
+
inCellByKey.set(key, inCell);
|
|
15792
|
+
const isSelected = spec.selectedId === `lat-${key}`;
|
|
15793
|
+
const color = isSelected ? selectedColor : highlightCell && !inCell ? dimColor : site.color ?? "#2563eb";
|
|
15794
|
+
const radius = (site.radius ?? 0.3) * (isSelected ? 1.4 : 1);
|
|
15795
|
+
out.push(meshSphere(`lat-${key}`, pos[0], pos[1], pos[2], radius, color));
|
|
15796
|
+
if (showLabels && site.element) {
|
|
15797
|
+
out.push(billboardLabel(site.element, pos[0], pos[1], pos[2] + radius, { color: opts?.labelColor }));
|
|
15798
|
+
}
|
|
15799
|
+
}
|
|
15800
|
+
}
|
|
15801
|
+
}
|
|
15802
|
+
}
|
|
15803
|
+
const basisByKey = new Map(spec.basis.map((s) => [s.key, s]));
|
|
15804
|
+
for (const bond of spec.bonds ?? []) {
|
|
15805
|
+
const fromSite = basisByKey.get(bond.from);
|
|
15806
|
+
const toSite = basisByKey.get(bond.to);
|
|
15807
|
+
if (!fromSite || !toSite) continue;
|
|
15808
|
+
const fnx = fromSite.xEdge ? nx + 1 : nx;
|
|
15809
|
+
const fny = fromSite.yEdge ? ny + 1 : ny;
|
|
15810
|
+
const fnz = fromSite.zEdge ? nz + 1 : nz;
|
|
15811
|
+
const tnx = toSite.xEdge ? nx + 1 : nx;
|
|
15812
|
+
const tny = toSite.yEdge ? ny + 1 : ny;
|
|
15813
|
+
const tnz = toSite.zEdge ? nz + 1 : nz;
|
|
15814
|
+
const bdx = bond.dx ?? 0;
|
|
15815
|
+
const bdy = bond.dy ?? 0;
|
|
15816
|
+
const bdz = bond.dz ?? 0;
|
|
15817
|
+
for (let i = 0; i < fnx; i++) {
|
|
15818
|
+
for (let j = 0; j < fny; j++) {
|
|
15819
|
+
for (let k = 0; k < fnz; k++) {
|
|
15820
|
+
const ti = i + bdx;
|
|
15821
|
+
const tj = j + bdy;
|
|
15822
|
+
const tk = k + bdz;
|
|
15823
|
+
if (ti < 0 || ti >= tnx || tj < 0 || tj >= tny || tk < 0 || tk >= tnz) continue;
|
|
15824
|
+
const fromKey = `${fromSite.key}-${i}-${j}-${k}`;
|
|
15825
|
+
const toKey = `${toSite.key}-${ti}-${tj}-${tk}`;
|
|
15826
|
+
const fromPos = posByKey.get(fromKey);
|
|
15827
|
+
const toPos = posByKey.get(toKey);
|
|
15828
|
+
if (!fromPos || !toPos) continue;
|
|
15829
|
+
const dimmed = highlightCell && !(inCellByKey.get(fromKey) && inCellByKey.get(toKey));
|
|
15830
|
+
const seg = cylinderBetween(fromPos, toPos, bondRadius, dimmed ? dimColor : bond.color ?? "#6b7280");
|
|
15831
|
+
if (seg) out.push(seg);
|
|
15832
|
+
}
|
|
15833
|
+
}
|
|
15834
|
+
}
|
|
15835
|
+
}
|
|
15836
|
+
return out;
|
|
15837
|
+
}
|
|
15838
|
+
var sceneLog, KNOWN_BLOOM_KEYS, Canvas3DHost2;
|
|
15839
|
+
var init_learningScene3D = __esm({
|
|
15840
|
+
"components/learning/molecules/learningScene3D.tsx"() {
|
|
15841
|
+
"use client";
|
|
15842
|
+
init_atoms();
|
|
15843
|
+
init_Stack();
|
|
15844
|
+
init_useEventBus();
|
|
15845
|
+
sceneLog = createLogger("almadar:ui:learning-scene-3d");
|
|
15846
|
+
KNOWN_BLOOM_KEYS = /* @__PURE__ */ new Set(["intensity", "threshold", "smoothing"]);
|
|
15847
|
+
Canvas3DHost2 = lazy(
|
|
15848
|
+
() => import('@almadar/ui/components/molecules/game/three').then((m) => ({ default: m.Canvas3DHost }))
|
|
15849
|
+
);
|
|
15850
|
+
LearningScene3D.displayName = "LearningScene3D";
|
|
15851
|
+
}
|
|
15852
|
+
});
|
|
15853
|
+
var biologyLog, BIO_BAND_COLORS, BIO_STAGE_FILL, BIO_STAGE_TEXT, BiologyCanvas;
|
|
14540
15854
|
var init_BiologyCanvas = __esm({
|
|
14541
15855
|
"components/learning/molecules/BiologyCanvas.tsx"() {
|
|
14542
15856
|
"use client";
|
|
14543
15857
|
init_atoms();
|
|
14544
15858
|
init_Stack();
|
|
14545
15859
|
init_LearningCanvas();
|
|
15860
|
+
init_learningScene3D();
|
|
15861
|
+
biologyLog = createLogger("almadar:ui:biology-canvas");
|
|
15862
|
+
BIO_BAND_COLORS = ["#dcfce7", "#fef9c3", "#fee2e2", "#e0e7ff"];
|
|
15863
|
+
BIO_STAGE_FILL = {
|
|
15864
|
+
pending: "#e2e8f0",
|
|
15865
|
+
active: "#3b82f6",
|
|
15866
|
+
done: "#94a3b8"
|
|
15867
|
+
};
|
|
15868
|
+
BIO_STAGE_TEXT = {
|
|
15869
|
+
pending: "#64748b",
|
|
15870
|
+
active: "#ffffff",
|
|
15871
|
+
done: "#ffffff"
|
|
15872
|
+
};
|
|
14546
15873
|
BiologyCanvas = ({
|
|
14547
15874
|
className,
|
|
14548
15875
|
width = 600,
|
|
14549
15876
|
height = 400,
|
|
14550
15877
|
title,
|
|
14551
15878
|
backgroundColor,
|
|
15879
|
+
mode = "2d",
|
|
15880
|
+
camera,
|
|
15881
|
+
lighting,
|
|
15882
|
+
post,
|
|
14552
15883
|
nodes = [],
|
|
14553
15884
|
edges = [],
|
|
15885
|
+
compartments = [],
|
|
15886
|
+
bands = [],
|
|
15887
|
+
stages = [],
|
|
15888
|
+
stageStyle = "timeline",
|
|
15889
|
+
helix,
|
|
15890
|
+
helix3d,
|
|
14554
15891
|
shapes = [],
|
|
14555
|
-
|
|
15892
|
+
readouts,
|
|
15893
|
+
traces,
|
|
15894
|
+
showGrid,
|
|
15895
|
+
shadows,
|
|
15896
|
+
interactive,
|
|
14556
15897
|
animate = false,
|
|
14557
15898
|
onShapeClick,
|
|
14558
15899
|
isLoading,
|
|
@@ -14564,19 +15905,148 @@ var init_BiologyCanvas = __esm({
|
|
|
14564
15905
|
for (const n of nodes) {
|
|
14565
15906
|
if (n.id) nodeById.set(n.id, n);
|
|
14566
15907
|
}
|
|
15908
|
+
const bandCount = bands.length;
|
|
15909
|
+
for (let i = 0; i < bandCount; i++) {
|
|
15910
|
+
const band = bands[i];
|
|
15911
|
+
const bandColor = band.color ?? BIO_BAND_COLORS[i % BIO_BAND_COLORS.length];
|
|
15912
|
+
const bandY = i * height / bandCount;
|
|
15913
|
+
const bandH = height / bandCount;
|
|
15914
|
+
out.push({
|
|
15915
|
+
type: "rect",
|
|
15916
|
+
x: 0,
|
|
15917
|
+
y: bandY,
|
|
15918
|
+
width,
|
|
15919
|
+
height: bandH,
|
|
15920
|
+
color: bandColor,
|
|
15921
|
+
fill: bandColor,
|
|
15922
|
+
opacity: 0.45
|
|
15923
|
+
});
|
|
15924
|
+
if (band.label) {
|
|
15925
|
+
out.push({
|
|
15926
|
+
type: "text",
|
|
15927
|
+
x: 8,
|
|
15928
|
+
y: bandY + 14,
|
|
15929
|
+
text: band.label,
|
|
15930
|
+
color: "#6b7280",
|
|
15931
|
+
fontSize: 10
|
|
15932
|
+
});
|
|
15933
|
+
}
|
|
15934
|
+
}
|
|
15935
|
+
for (const c of compartments) {
|
|
15936
|
+
const color = c.color ?? "#16a34a";
|
|
15937
|
+
out.push({
|
|
15938
|
+
type: "ellipse",
|
|
15939
|
+
x: c.x,
|
|
15940
|
+
y: c.y,
|
|
15941
|
+
width: c.width,
|
|
15942
|
+
height: c.height,
|
|
15943
|
+
color,
|
|
15944
|
+
fill: c.fill ?? `${color}1A`,
|
|
15945
|
+
lineWidth: c.lineWidth ?? 2,
|
|
15946
|
+
...c.dash ? { dash: c.dash } : {}
|
|
15947
|
+
});
|
|
15948
|
+
if (c.label) {
|
|
15949
|
+
out.push({
|
|
15950
|
+
type: "text",
|
|
15951
|
+
x: c.x,
|
|
15952
|
+
y: c.y - c.height / 2 + 14,
|
|
15953
|
+
text: c.label,
|
|
15954
|
+
color: "#111827",
|
|
15955
|
+
fontSize: 11,
|
|
15956
|
+
align: "center"
|
|
15957
|
+
});
|
|
15958
|
+
}
|
|
15959
|
+
}
|
|
15960
|
+
if (helix) {
|
|
15961
|
+
const hx = helix.x ?? 24;
|
|
15962
|
+
const hy = helix.y ?? height * 0.25;
|
|
15963
|
+
const hw = helix.width ?? width - 48;
|
|
15964
|
+
const hh = helix.height ?? height * 0.5;
|
|
15965
|
+
const rungs = helix.rungs;
|
|
15966
|
+
const n = rungs.length;
|
|
15967
|
+
const cy = hy + hh / 2;
|
|
15968
|
+
const colorA = helix.colorA ?? "#2563eb";
|
|
15969
|
+
const colorB = helix.colorB ?? "#dc2626";
|
|
15970
|
+
const rungColor = helix.rungColor ?? "#94a3b8";
|
|
15971
|
+
const fork = helix.fork ?? 0;
|
|
15972
|
+
const maxSep = Math.min(hh - 8, 96);
|
|
15973
|
+
const strandA = [];
|
|
15974
|
+
const strandB = [];
|
|
15975
|
+
const rungGeoms = [];
|
|
15976
|
+
for (let i = 0; i < n; i++) {
|
|
15977
|
+
const rx = hx + (i + 0.5) * hw / n;
|
|
15978
|
+
const t = (i + 0.5) / n;
|
|
15979
|
+
const paired = t >= fork;
|
|
15980
|
+
const sep = paired ? 28 : 28 + (maxSep - 28) * ((fork - t) / fork);
|
|
15981
|
+
strandA.push({ x: rx, y: cy - sep / 2 });
|
|
15982
|
+
strandB.push({ x: rx, y: cy + sep / 2 });
|
|
15983
|
+
rungGeoms.push({ rx, sep, rung: rungs[i], paired });
|
|
15984
|
+
}
|
|
15985
|
+
for (let i = 1; i < n; i++) {
|
|
15986
|
+
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 });
|
|
15987
|
+
}
|
|
15988
|
+
for (let i = 1; i < n; i++) {
|
|
15989
|
+
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 });
|
|
15990
|
+
}
|
|
15991
|
+
for (const g of rungGeoms) {
|
|
15992
|
+
const rColor = g.rung.color ?? (g.rung.state === "new" ? "#16a34a" : rungColor);
|
|
15993
|
+
const topY = cy - g.sep / 2;
|
|
15994
|
+
const bottomY = cy + g.sep / 2;
|
|
15995
|
+
if (g.paired) {
|
|
15996
|
+
out.push({ type: "line", x1: g.rx, y1: topY, x2: g.rx, y2: bottomY, color: rColor });
|
|
15997
|
+
if (g.rung.a) {
|
|
15998
|
+
out.push({ type: "text", x: g.rx, y: cy - g.sep / 4, text: g.rung.a, fontSize: 9, align: "center", color: "#374151" });
|
|
15999
|
+
}
|
|
16000
|
+
if (g.rung.b) {
|
|
16001
|
+
out.push({ type: "text", x: g.rx, y: cy + g.sep / 4, text: g.rung.b, fontSize: 9, align: "center", color: "#374151" });
|
|
16002
|
+
}
|
|
16003
|
+
} else {
|
|
16004
|
+
const stubTopY = topY + 8;
|
|
16005
|
+
const stubBottomY = bottomY - 8;
|
|
16006
|
+
out.push({ type: "line", x1: g.rx, y1: topY, x2: g.rx, y2: stubTopY, color: rColor });
|
|
16007
|
+
out.push({ type: "line", x1: g.rx, y1: bottomY, x2: g.rx, y2: stubBottomY, color: rColor });
|
|
16008
|
+
if (g.rung.a) {
|
|
16009
|
+
out.push({ type: "text", x: g.rx, y: stubTopY + 6, text: g.rung.a, fontSize: 9, align: "center", color: "#374151" });
|
|
16010
|
+
}
|
|
16011
|
+
if (g.rung.b) {
|
|
16012
|
+
out.push({ type: "text", x: g.rx, y: stubBottomY - 6, text: g.rung.b, fontSize: 9, align: "center", color: "#374151" });
|
|
16013
|
+
}
|
|
16014
|
+
}
|
|
16015
|
+
}
|
|
16016
|
+
}
|
|
14567
16017
|
for (const e of edges) {
|
|
14568
16018
|
const a = nodeById.get(e.from);
|
|
14569
16019
|
const b = nodeById.get(e.to);
|
|
14570
16020
|
if (!a || !b) continue;
|
|
14571
|
-
|
|
14572
|
-
|
|
14573
|
-
|
|
14574
|
-
|
|
14575
|
-
|
|
14576
|
-
|
|
14577
|
-
|
|
14578
|
-
|
|
14579
|
-
|
|
16021
|
+
const color = e.color ?? "#9ca3af";
|
|
16022
|
+
if (e.directed) {
|
|
16023
|
+
const rA = a.radius ?? 16;
|
|
16024
|
+
const rB = b.radius ?? 16;
|
|
16025
|
+
const dx = b.x - a.x;
|
|
16026
|
+
const dy = b.y - a.y;
|
|
16027
|
+
const dist = Math.max(1e-6, Math.hypot(dx, dy));
|
|
16028
|
+
const ux = dx / dist;
|
|
16029
|
+
const uy = dy / dist;
|
|
16030
|
+
out.push({
|
|
16031
|
+
type: "arrow",
|
|
16032
|
+
x1: a.x + ux * rA,
|
|
16033
|
+
y1: a.y + uy * rA,
|
|
16034
|
+
x2: b.x - ux * rB,
|
|
16035
|
+
y2: b.y - uy * rB,
|
|
16036
|
+
color,
|
|
16037
|
+
lineWidth: 2
|
|
16038
|
+
});
|
|
16039
|
+
} else {
|
|
16040
|
+
out.push({
|
|
16041
|
+
type: "line",
|
|
16042
|
+
x1: a.x,
|
|
16043
|
+
y1: a.y,
|
|
16044
|
+
x2: b.x,
|
|
16045
|
+
y2: b.y,
|
|
16046
|
+
color,
|
|
16047
|
+
lineWidth: 2
|
|
16048
|
+
});
|
|
16049
|
+
}
|
|
14580
16050
|
if (e.label) {
|
|
14581
16051
|
out.push({
|
|
14582
16052
|
type: "text",
|
|
@@ -14589,6 +16059,8 @@ var init_BiologyCanvas = __esm({
|
|
|
14589
16059
|
}
|
|
14590
16060
|
}
|
|
14591
16061
|
for (const n of nodes) {
|
|
16062
|
+
const state = n.state ?? "default";
|
|
16063
|
+
const muted = state === "muted";
|
|
14592
16064
|
out.push({
|
|
14593
16065
|
type: "circle",
|
|
14594
16066
|
x: n.x,
|
|
@@ -14596,23 +16068,197 @@ var init_BiologyCanvas = __esm({
|
|
|
14596
16068
|
radius: n.radius ?? 16,
|
|
14597
16069
|
color: n.color ?? "#16a34a",
|
|
14598
16070
|
fill: `${n.color ?? "#16a34a"}33`,
|
|
14599
|
-
id: n.id
|
|
16071
|
+
id: n.id,
|
|
16072
|
+
...muted ? { opacity: 0.35 } : {}
|
|
14600
16073
|
});
|
|
16074
|
+
if (state === "highlight") {
|
|
16075
|
+
out.push({
|
|
16076
|
+
type: "circle",
|
|
16077
|
+
x: n.x,
|
|
16078
|
+
y: n.y,
|
|
16079
|
+
radius: (n.radius ?? 16) + 4,
|
|
16080
|
+
color: "#f59e0b",
|
|
16081
|
+
lineWidth: 2
|
|
16082
|
+
});
|
|
16083
|
+
}
|
|
14601
16084
|
if (n.label) {
|
|
14602
16085
|
out.push({
|
|
14603
16086
|
type: "text",
|
|
14604
16087
|
x: n.x,
|
|
14605
16088
|
y: n.y + (n.radius ?? 16) + 14,
|
|
14606
16089
|
text: n.label,
|
|
16090
|
+
...muted ? { opacity: 0.35 } : {},
|
|
14607
16091
|
color: "#111827",
|
|
14608
16092
|
fontSize: 12,
|
|
14609
16093
|
align: "center"
|
|
14610
16094
|
});
|
|
14611
16095
|
}
|
|
14612
16096
|
}
|
|
16097
|
+
const stageCount = stages.length;
|
|
16098
|
+
if (stageCount > 0) {
|
|
16099
|
+
if (stageStyle === "ring") {
|
|
16100
|
+
const cx = width / 2;
|
|
16101
|
+
const cy = height / 2;
|
|
16102
|
+
const R = Math.min(width, height) / 2 - 48;
|
|
16103
|
+
const ringPoints = [];
|
|
16104
|
+
for (let i = 0; i < stageCount; i++) {
|
|
16105
|
+
const angleRad = (-90 + 360 * i / stageCount) * Math.PI / 180;
|
|
16106
|
+
ringPoints.push({ x: cx + R * Math.cos(angleRad), y: cy + R * Math.sin(angleRad) });
|
|
16107
|
+
}
|
|
16108
|
+
if (stageCount >= 2) {
|
|
16109
|
+
for (let i = 0; i < stageCount - 1; i++) {
|
|
16110
|
+
const p1 = ringPoints[i];
|
|
16111
|
+
const p2 = ringPoints[i + 1];
|
|
16112
|
+
const dx = p2.x - p1.x;
|
|
16113
|
+
const dy = p2.y - p1.y;
|
|
16114
|
+
const dist = Math.max(1e-6, Math.hypot(dx, dy));
|
|
16115
|
+
const ux = dx / dist;
|
|
16116
|
+
const uy = dy / dist;
|
|
16117
|
+
out.push({
|
|
16118
|
+
type: "arrow",
|
|
16119
|
+
x1: p1.x + ux * 46,
|
|
16120
|
+
y1: p1.y + uy * 46,
|
|
16121
|
+
x2: p2.x - ux * 46,
|
|
16122
|
+
y2: p2.y - uy * 46,
|
|
16123
|
+
color: "#94a3b8"
|
|
16124
|
+
});
|
|
16125
|
+
}
|
|
16126
|
+
}
|
|
16127
|
+
for (let i = 0; i < stageCount; i++) {
|
|
16128
|
+
const stage = stages[i];
|
|
16129
|
+
const state = stage.state ?? "pending";
|
|
16130
|
+
const fill = stage.color ?? BIO_STAGE_FILL[state];
|
|
16131
|
+
const w = Math.max(26, Math.min(84, stage.label.length * 6 + 10));
|
|
16132
|
+
const h = 18;
|
|
16133
|
+
const p = ringPoints[i];
|
|
16134
|
+
out.push({ type: "rect", x: p.x - w / 2, y: p.y - h / 2, width: w, height: h, color: fill, fill });
|
|
16135
|
+
out.push({ type: "text", x: p.x, y: p.y, text: stage.label, color: BIO_STAGE_TEXT[state], fontSize: 10, align: "center" });
|
|
16136
|
+
}
|
|
16137
|
+
} else {
|
|
16138
|
+
const stripY = height - 32;
|
|
16139
|
+
const slotW = (width - 16) / stageCount;
|
|
16140
|
+
const chipGeoms = [];
|
|
16141
|
+
for (let i = 0; i < stageCount; i++) {
|
|
16142
|
+
chipGeoms.push({ x: 8 + i * slotW + 5, w: slotW - 10 });
|
|
16143
|
+
}
|
|
16144
|
+
for (let i = 0; i < stageCount - 1; i++) {
|
|
16145
|
+
const midY = stripY + 13;
|
|
16146
|
+
out.push({
|
|
16147
|
+
type: "arrow",
|
|
16148
|
+
x1: chipGeoms[i].x + chipGeoms[i].w,
|
|
16149
|
+
y1: midY,
|
|
16150
|
+
x2: chipGeoms[i + 1].x,
|
|
16151
|
+
y2: midY,
|
|
16152
|
+
color: "#94a3b8"
|
|
16153
|
+
});
|
|
16154
|
+
}
|
|
16155
|
+
for (let i = 0; i < stageCount; i++) {
|
|
16156
|
+
const stage = stages[i];
|
|
16157
|
+
const state = stage.state ?? "pending";
|
|
16158
|
+
const fill = stage.color ?? BIO_STAGE_FILL[state];
|
|
16159
|
+
const g = chipGeoms[i];
|
|
16160
|
+
out.push({ type: "rect", x: g.x, y: stripY, width: g.w, height: 26, color: fill, fill });
|
|
16161
|
+
out.push({
|
|
16162
|
+
type: "text",
|
|
16163
|
+
x: g.x + g.w / 2,
|
|
16164
|
+
y: stripY + 13,
|
|
16165
|
+
text: stage.label,
|
|
16166
|
+
color: BIO_STAGE_TEXT[state],
|
|
16167
|
+
fontSize: 10,
|
|
16168
|
+
align: "center"
|
|
16169
|
+
});
|
|
16170
|
+
}
|
|
16171
|
+
}
|
|
16172
|
+
}
|
|
14613
16173
|
out.push(...shapes);
|
|
14614
16174
|
return out;
|
|
14615
|
-
}, [nodes, edges, shapes]);
|
|
16175
|
+
}, [nodes, edges, compartments, bands, stages, stageStyle, helix, shapes, width, height]);
|
|
16176
|
+
const drawables3D = useMemo(() => {
|
|
16177
|
+
if (mode !== "3d") return [];
|
|
16178
|
+
if (shapes.length > 0) {
|
|
16179
|
+
biologyLog.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
|
|
16180
|
+
}
|
|
16181
|
+
if (compartments.length > 0 || bands.length > 0 || stages.length > 0 || helix) {
|
|
16182
|
+
biologyLog.debug("2D-only families ignored in 3D mode (pixel-authored 2D vocabulary)", {
|
|
16183
|
+
compartments: compartments.length,
|
|
16184
|
+
bands: bands.length,
|
|
16185
|
+
stages: stages.length,
|
|
16186
|
+
helix: helix != null
|
|
16187
|
+
});
|
|
16188
|
+
}
|
|
16189
|
+
if (animate) {
|
|
16190
|
+
biologyLog.debug("animate ignored in 3D mode (motion is entity-state driven)");
|
|
16191
|
+
}
|
|
16192
|
+
const out = [];
|
|
16193
|
+
const labelColor = labelColorForBackground(backgroundColor);
|
|
16194
|
+
const nodeById = /* @__PURE__ */ new Map();
|
|
16195
|
+
for (const n of nodes) {
|
|
16196
|
+
if (n.id) nodeById.set(n.id, n);
|
|
16197
|
+
}
|
|
16198
|
+
for (const e of edges) {
|
|
16199
|
+
const a = nodeById.get(e.from);
|
|
16200
|
+
const b = nodeById.get(e.to);
|
|
16201
|
+
if (!a || !b) continue;
|
|
16202
|
+
const edgeRadius = Math.max(0.04, Math.min(a.radius ?? 0.5, b.radius ?? 0.5) * 0.12);
|
|
16203
|
+
const edge = cylinderBetween([a.x, a.y, a.z ?? 0], [b.x, b.y, b.z ?? 0], edgeRadius, e.color ?? "#9ca3af");
|
|
16204
|
+
if (edge) out.push(edge);
|
|
16205
|
+
if (e.label) {
|
|
16206
|
+
out.push(
|
|
16207
|
+
billboardLabel(
|
|
16208
|
+
e.label,
|
|
16209
|
+
(a.x + b.x) / 2,
|
|
16210
|
+
(a.y + b.y) / 2,
|
|
16211
|
+
((a.z ?? 0) + (b.z ?? 0)) / 2,
|
|
16212
|
+
{ color: labelColor }
|
|
16213
|
+
)
|
|
16214
|
+
);
|
|
16215
|
+
}
|
|
16216
|
+
}
|
|
16217
|
+
for (const n of nodes) {
|
|
16218
|
+
const radius = n.radius ?? 0.5;
|
|
16219
|
+
const nz = n.z ?? 0;
|
|
16220
|
+
out.push(meshSphere(n.id, n.x, n.y, nz, radius, n.color ?? "#16a34a", { shape: n.shape, ...n.opacity !== void 0 ? { opacity: n.opacity } : {} }));
|
|
16221
|
+
if (n.label) {
|
|
16222
|
+
out.push(billboardLabel(n.label, n.x, n.y, nz + radius, { color: labelColor }));
|
|
16223
|
+
}
|
|
16224
|
+
}
|
|
16225
|
+
if (helix3d) {
|
|
16226
|
+
out.push(...helixDrawables(helix3d, { labelColor }));
|
|
16227
|
+
}
|
|
16228
|
+
return out;
|
|
16229
|
+
}, [mode, nodes, edges, shapes, compartments, bands, stages, helix, helix3d, animate, backgroundColor]);
|
|
16230
|
+
const nodeIndexById = useMemo(() => {
|
|
16231
|
+
const m = /* @__PURE__ */ new Map();
|
|
16232
|
+
(helix3d?.rungs ?? []).forEach((rung, i) => {
|
|
16233
|
+
if (rung.id) m.set(rung.id, i);
|
|
16234
|
+
});
|
|
16235
|
+
nodes.forEach((n, i) => {
|
|
16236
|
+
if (n.id) m.set(n.id, i);
|
|
16237
|
+
});
|
|
16238
|
+
return m;
|
|
16239
|
+
}, [nodes, helix3d]);
|
|
16240
|
+
if (mode === "3d") {
|
|
16241
|
+
return /* @__PURE__ */ jsx(
|
|
16242
|
+
LearningScene3D,
|
|
16243
|
+
{
|
|
16244
|
+
className,
|
|
16245
|
+
width,
|
|
16246
|
+
height,
|
|
16247
|
+
title,
|
|
16248
|
+
backgroundColor,
|
|
16249
|
+
drawables: drawables3D,
|
|
16250
|
+
camera,
|
|
16251
|
+
lighting,
|
|
16252
|
+
post,
|
|
16253
|
+
showGrid,
|
|
16254
|
+
shadows,
|
|
16255
|
+
interactive,
|
|
16256
|
+
isLoading,
|
|
16257
|
+
error,
|
|
16258
|
+
onItemClick: get3DClickPayload(onShapeClick, nodeIndexById)
|
|
16259
|
+
}
|
|
16260
|
+
);
|
|
16261
|
+
}
|
|
14616
16262
|
return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
|
|
14617
16263
|
title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
|
|
14618
16264
|
/* @__PURE__ */ jsx(
|
|
@@ -14622,7 +16268,9 @@ var init_BiologyCanvas = __esm({
|
|
|
14622
16268
|
height,
|
|
14623
16269
|
backgroundColor,
|
|
14624
16270
|
shapes: derivedShapes,
|
|
14625
|
-
|
|
16271
|
+
readouts,
|
|
16272
|
+
traces,
|
|
16273
|
+
interactive: interactive ?? false,
|
|
14626
16274
|
animate,
|
|
14627
16275
|
onShapeClick,
|
|
14628
16276
|
isLoading,
|
|
@@ -21345,24 +22993,55 @@ var init_ChatBar = __esm({
|
|
|
21345
22993
|
ChatBar.displayName = "ChatBar";
|
|
21346
22994
|
}
|
|
21347
22995
|
});
|
|
21348
|
-
|
|
22996
|
+
function bondPerpendicular(a, b) {
|
|
22997
|
+
const dx = b[0] - a[0];
|
|
22998
|
+
const dy = b[1] - a[1];
|
|
22999
|
+
const px = dy;
|
|
23000
|
+
const py = -dx;
|
|
23001
|
+
const len = Math.sqrt(px * px + py * py);
|
|
23002
|
+
if (len < 1e-6) return [1, 0, 0];
|
|
23003
|
+
return [px / len, py / len, 0];
|
|
23004
|
+
}
|
|
23005
|
+
var chemistryLog, CHEM_BOND_STATE_COLOR, LONE_PAIR_ANGLES, ChemistryCanvas;
|
|
21349
23006
|
var init_ChemistryCanvas = __esm({
|
|
21350
23007
|
"components/learning/molecules/ChemistryCanvas.tsx"() {
|
|
21351
23008
|
"use client";
|
|
21352
23009
|
init_atoms();
|
|
21353
23010
|
init_Stack();
|
|
21354
23011
|
init_LearningCanvas();
|
|
23012
|
+
init_learningScene3D();
|
|
23013
|
+
chemistryLog = createLogger("almadar:ui:chemistry-canvas");
|
|
23014
|
+
CHEM_BOND_STATE_COLOR = {
|
|
23015
|
+
default: "#6b7280",
|
|
23016
|
+
forming: "#16a34a",
|
|
23017
|
+
breaking: "#dc2626",
|
|
23018
|
+
highlight: "#f59e0b"
|
|
23019
|
+
};
|
|
23020
|
+
LONE_PAIR_ANGLES = [-90, 0, 90, 180];
|
|
21355
23021
|
ChemistryCanvas = ({
|
|
21356
23022
|
className,
|
|
21357
23023
|
width = 600,
|
|
21358
23024
|
height = 400,
|
|
21359
23025
|
title,
|
|
21360
23026
|
backgroundColor,
|
|
23027
|
+
mode = "2d",
|
|
23028
|
+
camera,
|
|
23029
|
+
lighting,
|
|
23030
|
+
post,
|
|
21361
23031
|
atoms = [],
|
|
21362
23032
|
bonds = [],
|
|
21363
23033
|
arrows = [],
|
|
23034
|
+
bondStyle = "thick",
|
|
23035
|
+
containers = [],
|
|
23036
|
+
equation,
|
|
23037
|
+
equationColor,
|
|
23038
|
+
lattice3d,
|
|
21364
23039
|
shapes = [],
|
|
21365
|
-
|
|
23040
|
+
readouts,
|
|
23041
|
+
traces,
|
|
23042
|
+
showGrid,
|
|
23043
|
+
shadows,
|
|
23044
|
+
interactive,
|
|
21366
23045
|
animate = false,
|
|
21367
23046
|
onShapeClick,
|
|
21368
23047
|
isLoading,
|
|
@@ -21374,21 +23053,118 @@ var init_ChemistryCanvas = __esm({
|
|
|
21374
23053
|
for (const a of atoms) {
|
|
21375
23054
|
if (a.id) atomById.set(a.id, a);
|
|
21376
23055
|
}
|
|
23056
|
+
for (const c of containers) {
|
|
23057
|
+
const color = c.color ?? "#64748b";
|
|
23058
|
+
if (c.level != null) {
|
|
23059
|
+
const lv = c.level;
|
|
23060
|
+
out.push({
|
|
23061
|
+
type: "rect",
|
|
23062
|
+
x: c.x + 1,
|
|
23063
|
+
y: c.y + c.height * (1 - lv),
|
|
23064
|
+
width: c.width - 2,
|
|
23065
|
+
height: c.height * lv - 1,
|
|
23066
|
+
color: c.levelColor ?? "#60a5fa",
|
|
23067
|
+
fill: c.levelColor ?? "#60a5fa",
|
|
23068
|
+
opacity: 0.5
|
|
23069
|
+
});
|
|
23070
|
+
}
|
|
23071
|
+
out.push({
|
|
23072
|
+
type: "rect",
|
|
23073
|
+
x: c.x,
|
|
23074
|
+
y: c.y,
|
|
23075
|
+
width: c.width,
|
|
23076
|
+
height: c.height,
|
|
23077
|
+
color,
|
|
23078
|
+
fill: c.fill,
|
|
23079
|
+
lineWidth: c.lineWidth ?? 2
|
|
23080
|
+
});
|
|
23081
|
+
const divider = c.divider ?? "none";
|
|
23082
|
+
if (divider !== "none") {
|
|
23083
|
+
out.push({
|
|
23084
|
+
type: "line",
|
|
23085
|
+
x1: c.x + c.width / 2,
|
|
23086
|
+
y1: c.y,
|
|
23087
|
+
x2: c.x + c.width / 2,
|
|
23088
|
+
y2: c.y + c.height,
|
|
23089
|
+
color: c.dividerColor ?? color,
|
|
23090
|
+
...divider === "dashed" || divider === "dotted" ? { dash: divider } : {}
|
|
23091
|
+
});
|
|
23092
|
+
}
|
|
23093
|
+
if (c.leftLabel) {
|
|
23094
|
+
out.push({
|
|
23095
|
+
type: "text",
|
|
23096
|
+
x: c.x + c.width * 0.25,
|
|
23097
|
+
y: c.y + 12,
|
|
23098
|
+
text: c.leftLabel,
|
|
23099
|
+
color: "#374151",
|
|
23100
|
+
fontSize: 11,
|
|
23101
|
+
align: "center"
|
|
23102
|
+
});
|
|
23103
|
+
}
|
|
23104
|
+
if (c.rightLabel) {
|
|
23105
|
+
out.push({
|
|
23106
|
+
type: "text",
|
|
23107
|
+
x: c.x + c.width * 0.75,
|
|
23108
|
+
y: c.y + 12,
|
|
23109
|
+
text: c.rightLabel,
|
|
23110
|
+
color: "#374151",
|
|
23111
|
+
fontSize: 11,
|
|
23112
|
+
align: "center"
|
|
23113
|
+
});
|
|
23114
|
+
}
|
|
23115
|
+
if (c.label) {
|
|
23116
|
+
out.push({
|
|
23117
|
+
type: "text",
|
|
23118
|
+
x: c.x + c.width / 2,
|
|
23119
|
+
y: c.y + c.height + 12,
|
|
23120
|
+
text: c.label,
|
|
23121
|
+
color: "#111827",
|
|
23122
|
+
fontSize: 12,
|
|
23123
|
+
align: "center"
|
|
23124
|
+
});
|
|
23125
|
+
}
|
|
23126
|
+
}
|
|
21377
23127
|
for (const b of bonds) {
|
|
21378
23128
|
const a = atomById.get(b.from);
|
|
21379
23129
|
const c = atomById.get(b.to);
|
|
21380
23130
|
if (!a || !c) continue;
|
|
21381
|
-
const
|
|
21382
|
-
const
|
|
21383
|
-
|
|
21384
|
-
|
|
21385
|
-
|
|
21386
|
-
|
|
21387
|
-
|
|
21388
|
-
|
|
21389
|
-
|
|
21390
|
-
|
|
21391
|
-
|
|
23131
|
+
const state = b.state ?? "default";
|
|
23132
|
+
const color = b.color ?? CHEM_BOND_STATE_COLOR[state];
|
|
23133
|
+
const dash = state === "forming" || state === "breaking" ? "dashed" : void 0;
|
|
23134
|
+
if (bondStyle === "parallel") {
|
|
23135
|
+
const dx = c.x - a.x;
|
|
23136
|
+
const dy = c.y - a.y;
|
|
23137
|
+
const dist = Math.max(1e-6, Math.hypot(dx, dy));
|
|
23138
|
+
const ux = dx / dist;
|
|
23139
|
+
const uy = dy / dist;
|
|
23140
|
+
const px = -uy;
|
|
23141
|
+
const py = ux;
|
|
23142
|
+
const offsets = b.type === "double" ? [-3, 3] : b.type === "triple" ? [-4, 0, 4] : [0];
|
|
23143
|
+
for (const off of offsets) {
|
|
23144
|
+
out.push({
|
|
23145
|
+
type: "line",
|
|
23146
|
+
x1: a.x + px * off,
|
|
23147
|
+
y1: a.y + py * off,
|
|
23148
|
+
x2: c.x + px * off,
|
|
23149
|
+
y2: c.y + py * off,
|
|
23150
|
+
color,
|
|
23151
|
+
lineWidth: 2,
|
|
23152
|
+
...dash ? { dash } : {}
|
|
23153
|
+
});
|
|
23154
|
+
}
|
|
23155
|
+
} else {
|
|
23156
|
+
const strokeWidth = b.type === "double" ? 4 : b.type === "triple" ? 6 : 2;
|
|
23157
|
+
out.push({
|
|
23158
|
+
type: "line",
|
|
23159
|
+
x1: a.x,
|
|
23160
|
+
y1: a.y,
|
|
23161
|
+
x2: c.x,
|
|
23162
|
+
y2: c.y,
|
|
23163
|
+
color,
|
|
23164
|
+
lineWidth: strokeWidth,
|
|
23165
|
+
...dash ? { dash } : {}
|
|
23166
|
+
});
|
|
23167
|
+
}
|
|
21392
23168
|
}
|
|
21393
23169
|
for (const a of arrows) {
|
|
21394
23170
|
const angle = (a.angle ?? 0) * (Math.PI / 180);
|
|
@@ -21437,10 +23213,142 @@ var init_ChemistryCanvas = __esm({
|
|
|
21437
23213
|
align: "center"
|
|
21438
23214
|
});
|
|
21439
23215
|
}
|
|
23216
|
+
const r = a.radius ?? 14;
|
|
23217
|
+
if (a.charge) {
|
|
23218
|
+
out.push({
|
|
23219
|
+
type: "text",
|
|
23220
|
+
x: a.x + r * 0.85,
|
|
23221
|
+
y: a.y - r * 0.85,
|
|
23222
|
+
text: a.charge,
|
|
23223
|
+
color: "#111827",
|
|
23224
|
+
fontSize: 9,
|
|
23225
|
+
align: "left"
|
|
23226
|
+
});
|
|
23227
|
+
}
|
|
23228
|
+
const lonePairs = Math.max(0, Math.min(4, a.lonePairs ?? 0));
|
|
23229
|
+
for (let k = 0; k < lonePairs; k++) {
|
|
23230
|
+
const angleRad = LONE_PAIR_ANGLES[k] * Math.PI / 180;
|
|
23231
|
+
const cx = a.x + (r + 6) * Math.cos(angleRad);
|
|
23232
|
+
const cy = a.y + (r + 6) * Math.sin(angleRad);
|
|
23233
|
+
const perpX = -Math.sin(angleRad);
|
|
23234
|
+
const perpY = Math.cos(angleRad);
|
|
23235
|
+
for (const sign of [1, -1]) {
|
|
23236
|
+
out.push({
|
|
23237
|
+
type: "circle",
|
|
23238
|
+
x: cx + perpX * 2.5 * sign,
|
|
23239
|
+
y: cy + perpY * 2.5 * sign,
|
|
23240
|
+
radius: 1.5,
|
|
23241
|
+
color: "#374151",
|
|
23242
|
+
fill: "#374151"
|
|
23243
|
+
});
|
|
23244
|
+
}
|
|
23245
|
+
}
|
|
23246
|
+
}
|
|
23247
|
+
if (equation) {
|
|
23248
|
+
out.push({
|
|
23249
|
+
type: "text",
|
|
23250
|
+
x: width / 2,
|
|
23251
|
+
y: 14,
|
|
23252
|
+
text: equation,
|
|
23253
|
+
color: equationColor ?? "#111827",
|
|
23254
|
+
fontSize: 13,
|
|
23255
|
+
align: "center"
|
|
23256
|
+
});
|
|
21440
23257
|
}
|
|
21441
23258
|
out.push(...shapes);
|
|
21442
23259
|
return out;
|
|
21443
|
-
}, [atoms, bonds, arrows, shapes]);
|
|
23260
|
+
}, [atoms, bonds, arrows, bondStyle, containers, equation, equationColor, shapes, width]);
|
|
23261
|
+
const drawables3D = useMemo(() => {
|
|
23262
|
+
if (mode !== "3d") return [];
|
|
23263
|
+
if (shapes.length > 0) {
|
|
23264
|
+
chemistryLog.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
|
|
23265
|
+
}
|
|
23266
|
+
if (containers.length > 0) {
|
|
23267
|
+
chemistryLog.debug("containers ignored in 3D mode (pixel-authored 2D vocabulary)", { count: containers.length });
|
|
23268
|
+
}
|
|
23269
|
+
if (animate) {
|
|
23270
|
+
chemistryLog.debug("animate ignored in 3D mode (motion is entity-state driven)");
|
|
23271
|
+
}
|
|
23272
|
+
const out = [];
|
|
23273
|
+
const labelColor = labelColorForBackground(backgroundColor);
|
|
23274
|
+
const atomById = /* @__PURE__ */ new Map();
|
|
23275
|
+
for (const a of atoms) {
|
|
23276
|
+
if (a.id) atomById.set(a.id, a);
|
|
23277
|
+
}
|
|
23278
|
+
for (const b of bonds) {
|
|
23279
|
+
const a = atomById.get(b.from);
|
|
23280
|
+
const c = atomById.get(b.to);
|
|
23281
|
+
if (!a || !c) continue;
|
|
23282
|
+
const color = b.color ?? "#6b7280";
|
|
23283
|
+
const from = [a.x, a.y, a.z ?? 0];
|
|
23284
|
+
const to = [c.x, c.y, c.z ?? 0];
|
|
23285
|
+
const perp = bondPerpendicular(from, to);
|
|
23286
|
+
const bondRadius = Math.max(0.06, Math.min(a.radius ?? 0.45, c.radius ?? 0.45) * 0.22);
|
|
23287
|
+
const step = bondRadius * 2.2;
|
|
23288
|
+
const offsets = b.type === "double" ? [-step, step] : b.type === "triple" ? [-step, 0, step] : [0];
|
|
23289
|
+
for (const off of offsets) {
|
|
23290
|
+
const bond = cylinderBetween(
|
|
23291
|
+
[from[0] + perp[0] * off, from[1] + perp[1] * off, from[2] + perp[2] * off],
|
|
23292
|
+
[to[0] + perp[0] * off, to[1] + perp[1] * off, to[2] + perp[2] * off],
|
|
23293
|
+
bondRadius,
|
|
23294
|
+
color
|
|
23295
|
+
);
|
|
23296
|
+
if (bond) out.push(bond);
|
|
23297
|
+
}
|
|
23298
|
+
}
|
|
23299
|
+
for (const a of arrows) {
|
|
23300
|
+
const angle = (a.angle ?? 0) * (Math.PI / 180);
|
|
23301
|
+
const len = a.length ?? 60;
|
|
23302
|
+
const x2 = a.x + Math.cos(angle) * len;
|
|
23303
|
+
const y2 = a.y + Math.sin(angle) * len;
|
|
23304
|
+
const arrow = arrowBetween([a.x, a.y, 0], [x2, y2, 0], a.color ?? "#dc2626");
|
|
23305
|
+
if (arrow) out.push(arrow);
|
|
23306
|
+
if (a.label) {
|
|
23307
|
+
out.push(billboardLabel(a.label, (a.x + x2) / 2, (a.y + y2) / 2, 0, { color: labelColor }));
|
|
23308
|
+
}
|
|
23309
|
+
}
|
|
23310
|
+
for (const a of atoms) {
|
|
23311
|
+
const radius = a.radius ?? 0.45;
|
|
23312
|
+
const az = a.z ?? 0;
|
|
23313
|
+
out.push(meshSphere(a.id, a.x, a.y, az, radius, a.color ?? "#2563eb"));
|
|
23314
|
+
if (a.element) {
|
|
23315
|
+
out.push(billboardLabel(a.element, a.x, a.y, az + radius, { color: labelColor }));
|
|
23316
|
+
}
|
|
23317
|
+
}
|
|
23318
|
+
if (lattice3d) {
|
|
23319
|
+
out.push(...latticeDrawables(lattice3d, { labelColor }));
|
|
23320
|
+
}
|
|
23321
|
+
return out;
|
|
23322
|
+
}, [mode, atoms, bonds, arrows, shapes, containers, lattice3d, animate, backgroundColor]);
|
|
23323
|
+
const atomIndexById = useMemo(() => {
|
|
23324
|
+
const m = /* @__PURE__ */ new Map();
|
|
23325
|
+
atoms.forEach((a, i) => {
|
|
23326
|
+
if (a.id) m.set(a.id, i);
|
|
23327
|
+
});
|
|
23328
|
+
return m;
|
|
23329
|
+
}, [atoms]);
|
|
23330
|
+
if (mode === "3d") {
|
|
23331
|
+
return /* @__PURE__ */ jsx(
|
|
23332
|
+
LearningScene3D,
|
|
23333
|
+
{
|
|
23334
|
+
className,
|
|
23335
|
+
width,
|
|
23336
|
+
height,
|
|
23337
|
+
title,
|
|
23338
|
+
backgroundColor,
|
|
23339
|
+
drawables: drawables3D,
|
|
23340
|
+
camera,
|
|
23341
|
+
lighting,
|
|
23342
|
+
post,
|
|
23343
|
+
showGrid,
|
|
23344
|
+
shadows,
|
|
23345
|
+
interactive,
|
|
23346
|
+
isLoading,
|
|
23347
|
+
error,
|
|
23348
|
+
onItemClick: get3DClickPayload(onShapeClick, atomIndexById)
|
|
23349
|
+
}
|
|
23350
|
+
);
|
|
23351
|
+
}
|
|
21444
23352
|
return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
|
|
21445
23353
|
title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
|
|
21446
23354
|
/* @__PURE__ */ jsx(
|
|
@@ -21450,7 +23358,9 @@ var init_ChemistryCanvas = __esm({
|
|
|
21450
23358
|
height,
|
|
21451
23359
|
backgroundColor,
|
|
21452
23360
|
shapes: derivedShapes,
|
|
21453
|
-
|
|
23361
|
+
readouts,
|
|
23362
|
+
traces,
|
|
23363
|
+
interactive: interactive ?? false,
|
|
21454
23364
|
animate,
|
|
21455
23365
|
onShapeClick,
|
|
21456
23366
|
isLoading,
|
|
@@ -27561,6 +29471,10 @@ var init_ProgressDots = __esm({
|
|
|
27561
29471
|
ProgressDots.displayName = "ProgressDots";
|
|
27562
29472
|
}
|
|
27563
29473
|
});
|
|
29474
|
+
function formatTick(v) {
|
|
29475
|
+
if (Number.isInteger(v)) return String(v);
|
|
29476
|
+
return v.toFixed(2).replace(/0+$/, "").replace(/\.$/, "");
|
|
29477
|
+
}
|
|
27564
29478
|
var MathCanvas;
|
|
27565
29479
|
var init_MathCanvas = __esm({
|
|
27566
29480
|
"components/learning/molecules/MathCanvas.tsx"() {
|
|
@@ -27580,10 +29494,19 @@ var init_MathCanvas = __esm({
|
|
|
27580
29494
|
showAxes = true,
|
|
27581
29495
|
showGrid = true,
|
|
27582
29496
|
gridStep = 1,
|
|
29497
|
+
showTickLabels = false,
|
|
29498
|
+
showCurveLabels = false,
|
|
27583
29499
|
curves = [],
|
|
27584
29500
|
points = [],
|
|
27585
29501
|
vectors = [],
|
|
29502
|
+
regions = [],
|
|
29503
|
+
bars = [],
|
|
29504
|
+
guides = [],
|
|
29505
|
+
angles = [],
|
|
29506
|
+
hops = [],
|
|
27586
29507
|
shapes = [],
|
|
29508
|
+
readouts,
|
|
29509
|
+
traces,
|
|
27587
29510
|
interactive = false,
|
|
27588
29511
|
animate = false,
|
|
27589
29512
|
onShapeClick,
|
|
@@ -27597,6 +29520,8 @@ var init_MathCanvas = __esm({
|
|
|
27597
29520
|
const plotH = height - margin * 2;
|
|
27598
29521
|
const mapX = (x) => margin + (x - xMin) / (xMax - xMin) * plotW;
|
|
27599
29522
|
const mapY = (y) => height - (margin + (y - yMin) / (yMax - yMin) * plotH);
|
|
29523
|
+
const xAxisY = Math.max(margin, Math.min(height - margin, mapY(0)));
|
|
29524
|
+
const yAxisX = Math.max(margin, Math.min(width - margin, mapX(0)));
|
|
27600
29525
|
if (showGrid) {
|
|
27601
29526
|
for (let x = Math.ceil(xMin / gridStep) * gridStep; x <= xMax; x += gridStep) {
|
|
27602
29527
|
const px = mapX(x);
|
|
@@ -27607,14 +29532,99 @@ var init_MathCanvas = __esm({
|
|
|
27607
29532
|
out.push({ type: "line", x1: margin, y1: py, x2: width - margin, y2: py, color: "#e5e7eb", lineWidth: 1 });
|
|
27608
29533
|
}
|
|
27609
29534
|
}
|
|
29535
|
+
if (showTickLabels) {
|
|
29536
|
+
const labelEveryX = Math.max(1, Math.ceil((xMax - xMin) / gridStep / Math.floor(plotW / 40)));
|
|
29537
|
+
let kx = 0;
|
|
29538
|
+
for (let x = Math.ceil(xMin / gridStep) * gridStep; x <= xMax; x += gridStep, kx++) {
|
|
29539
|
+
if (kx % labelEveryX === 0 && x !== 0) {
|
|
29540
|
+
out.push({ type: "text", x: mapX(x), y: xAxisY + 12, text: formatTick(x), color: "#6b7280", fontSize: 10, align: "center" });
|
|
29541
|
+
}
|
|
29542
|
+
}
|
|
29543
|
+
const labelEveryY = Math.max(1, Math.ceil((yMax - yMin) / gridStep / Math.floor(plotH / 28)));
|
|
29544
|
+
let ky = 0;
|
|
29545
|
+
for (let y = Math.ceil(yMin / gridStep) * gridStep; y <= yMax; y += gridStep, ky++) {
|
|
29546
|
+
if (ky % labelEveryY === 0 && y !== 0) {
|
|
29547
|
+
out.push({ type: "text", x: yAxisX - 6, y: mapY(y), text: formatTick(y), color: "#6b7280", fontSize: 10, align: "right" });
|
|
29548
|
+
}
|
|
29549
|
+
}
|
|
29550
|
+
if (xMin <= 0 && xMax >= 0 && yMin <= 0 && yMax >= 0) {
|
|
29551
|
+
out.push({ type: "text", x: yAxisX - 6, y: xAxisY + 12, text: "0", color: "#6b7280", fontSize: 10, align: "right" });
|
|
29552
|
+
}
|
|
29553
|
+
}
|
|
29554
|
+
for (const region of regions) {
|
|
29555
|
+
if (!region.samples || region.samples.length === 0) continue;
|
|
29556
|
+
const baseline = region.baseline ?? 0;
|
|
29557
|
+
const clampedPoint = (p) => ({
|
|
29558
|
+
x: mapX(Math.min(xMax, Math.max(xMin, p.x))),
|
|
29559
|
+
y: mapY(Math.min(yMax, Math.max(yMin, p.y)))
|
|
29560
|
+
});
|
|
29561
|
+
const upper = region.samples.map(clampedPoint);
|
|
29562
|
+
const first = region.samples[0];
|
|
29563
|
+
const last = region.samples[region.samples.length - 1];
|
|
29564
|
+
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 })];
|
|
29565
|
+
const color = region.color ?? "#2563eb";
|
|
29566
|
+
out.push({
|
|
29567
|
+
type: "polygon",
|
|
29568
|
+
points: [...upper, ...closing],
|
|
29569
|
+
fill: color,
|
|
29570
|
+
color,
|
|
29571
|
+
opacity: region.opacity ?? 0.2,
|
|
29572
|
+
lineWidth: 1
|
|
29573
|
+
});
|
|
29574
|
+
if (region.label) {
|
|
29575
|
+
const mid = Math.floor(region.samples.length / 2);
|
|
29576
|
+
out.push({
|
|
29577
|
+
type: "text",
|
|
29578
|
+
x: mapX((first.x + last.x) / 2),
|
|
29579
|
+
y: (mapY(region.samples[mid].y) + mapY(baseline)) / 2,
|
|
29580
|
+
text: region.label,
|
|
29581
|
+
color: "#111827",
|
|
29582
|
+
fontSize: 11
|
|
29583
|
+
});
|
|
29584
|
+
}
|
|
29585
|
+
}
|
|
29586
|
+
for (const bar of bars) {
|
|
29587
|
+
if (bar.x + bar.width < xMin || bar.x > xMax) continue;
|
|
29588
|
+
const y0 = bar.y0 ?? 0;
|
|
29589
|
+
const color = bar.color ?? "#93c5fd";
|
|
29590
|
+
out.push({
|
|
29591
|
+
type: "rect",
|
|
29592
|
+
x: mapX(bar.x),
|
|
29593
|
+
y: mapY(Math.max(y0, bar.y1)),
|
|
29594
|
+
width: mapX(bar.x + bar.width) - mapX(bar.x),
|
|
29595
|
+
height: Math.abs(mapY(bar.y1) - mapY(y0)),
|
|
29596
|
+
color,
|
|
29597
|
+
fill: color,
|
|
29598
|
+
opacity: bar.opacity ?? 0.5,
|
|
29599
|
+
lineWidth: 1
|
|
29600
|
+
});
|
|
29601
|
+
}
|
|
27610
29602
|
if (showAxes) {
|
|
27611
|
-
const xAxisY = Math.max(margin, Math.min(height - margin, mapY(0)));
|
|
27612
|
-
const yAxisX = Math.max(margin, Math.min(width - margin, mapX(0)));
|
|
27613
29603
|
out.push({ type: "line", x1: margin, y1: xAxisY, x2: width - margin, y2: xAxisY, color: "#374151", lineWidth: 2 });
|
|
27614
29604
|
out.push({ type: "line", x1: yAxisX, y1: margin, x2: yAxisX, y2: height - margin, color: "#374151", lineWidth: 2 });
|
|
27615
29605
|
}
|
|
29606
|
+
for (const guide of guides) {
|
|
29607
|
+
const color = guide.color ?? "#9ca3af";
|
|
29608
|
+
const dash = guide.dash ?? "dashed";
|
|
29609
|
+
if (guide.kind === "vline") {
|
|
29610
|
+
if (guide.at < xMin || guide.at > xMax) continue;
|
|
29611
|
+
const px = mapX(guide.at);
|
|
29612
|
+
out.push({ type: "line", x1: px, y1: margin, x2: px, y2: height - margin, color, dash });
|
|
29613
|
+
if (guide.label) {
|
|
29614
|
+
out.push({ type: "text", x: px + 4, y: margin + 10, text: guide.label, color: "#111827", fontSize: 11 });
|
|
29615
|
+
}
|
|
29616
|
+
} else {
|
|
29617
|
+
if (guide.at < yMin || guide.at > yMax) continue;
|
|
29618
|
+
const py = mapY(guide.at);
|
|
29619
|
+
out.push({ type: "line", x1: margin, y1: py, x2: width - margin, y2: py, color, dash });
|
|
29620
|
+
if (guide.label) {
|
|
29621
|
+
out.push({ type: "text", x: width - margin - 4, y: py - 8, text: guide.label, color: "#111827", fontSize: 11, align: "right" });
|
|
29622
|
+
}
|
|
29623
|
+
}
|
|
29624
|
+
}
|
|
27616
29625
|
for (const curve of curves) {
|
|
27617
29626
|
if (!curve.samples || curve.samples.length < 2) continue;
|
|
29627
|
+
let lastInRange;
|
|
27618
29628
|
for (let i = 1; i < curve.samples.length; i++) {
|
|
27619
29629
|
const a = curve.samples[i - 1];
|
|
27620
29630
|
const b = curve.samples[i];
|
|
@@ -27626,19 +29636,97 @@ var init_MathCanvas = __esm({
|
|
|
27626
29636
|
x2: mapX(b.x),
|
|
27627
29637
|
y2: mapY(b.y),
|
|
27628
29638
|
color: curve.color ?? "#2563eb",
|
|
27629
|
-
lineWidth: 2
|
|
29639
|
+
lineWidth: 2,
|
|
29640
|
+
dash: curve.dash
|
|
29641
|
+
});
|
|
29642
|
+
lastInRange = b;
|
|
29643
|
+
}
|
|
29644
|
+
if (showCurveLabels && curve.label && lastInRange) {
|
|
29645
|
+
out.push({
|
|
29646
|
+
type: "text",
|
|
29647
|
+
x: mapX(lastInRange.x) + 6,
|
|
29648
|
+
y: mapY(lastInRange.y) - 6,
|
|
29649
|
+
text: curve.label,
|
|
29650
|
+
color: curve.color ?? "#2563eb",
|
|
29651
|
+
fontSize: 11
|
|
29652
|
+
});
|
|
29653
|
+
}
|
|
29654
|
+
}
|
|
29655
|
+
for (const hop of hops) {
|
|
29656
|
+
const x1 = mapX(hop.from);
|
|
29657
|
+
const x2 = mapX(hop.to);
|
|
29658
|
+
const peak = Math.min(36, plotH * 0.3);
|
|
29659
|
+
const color = hop.color ?? "#7c3aed";
|
|
29660
|
+
out.push({
|
|
29661
|
+
type: "ellipse",
|
|
29662
|
+
x: (x1 + x2) / 2,
|
|
29663
|
+
y: xAxisY,
|
|
29664
|
+
width: Math.abs(x2 - x1),
|
|
29665
|
+
height: 2 * peak,
|
|
29666
|
+
startAngle: 180,
|
|
29667
|
+
endAngle: 360,
|
|
29668
|
+
color
|
|
29669
|
+
});
|
|
29670
|
+
const s = Math.sign(hop.to - hop.from);
|
|
29671
|
+
out.push({
|
|
29672
|
+
type: "polygon",
|
|
29673
|
+
points: [
|
|
29674
|
+
{ x: x2, y: xAxisY },
|
|
29675
|
+
{ x: x2 - 4 * s, y: xAxisY - 7 },
|
|
29676
|
+
{ x: x2 + 2 * s, y: xAxisY - 7 }
|
|
29677
|
+
],
|
|
29678
|
+
fill: color,
|
|
29679
|
+
color
|
|
29680
|
+
});
|
|
29681
|
+
if (hop.label) {
|
|
29682
|
+
out.push({
|
|
29683
|
+
type: "text",
|
|
29684
|
+
x: (x1 + x2) / 2,
|
|
29685
|
+
y: xAxisY - peak - 8,
|
|
29686
|
+
text: hop.label,
|
|
29687
|
+
color: "#111827",
|
|
29688
|
+
fontSize: 10,
|
|
29689
|
+
align: "center"
|
|
29690
|
+
});
|
|
29691
|
+
}
|
|
29692
|
+
}
|
|
29693
|
+
for (const angle of angles) {
|
|
29694
|
+
const radius = angle.radius ?? 0.8;
|
|
29695
|
+
const color = angle.color ?? "#0ea5e9";
|
|
29696
|
+
out.push({
|
|
29697
|
+
type: "ellipse",
|
|
29698
|
+
x: mapX(angle.x),
|
|
29699
|
+
y: mapY(angle.y),
|
|
29700
|
+
width: 2 * radius * plotW / (xMax - xMin),
|
|
29701
|
+
height: 2 * radius * plotH / (yMax - yMin),
|
|
29702
|
+
startAngle: -angle.to,
|
|
29703
|
+
endAngle: -angle.from,
|
|
29704
|
+
color
|
|
29705
|
+
});
|
|
29706
|
+
if (angle.label) {
|
|
29707
|
+
const mid = (angle.from + angle.to) / 2;
|
|
29708
|
+
const rad = mid * Math.PI / 180;
|
|
29709
|
+
out.push({
|
|
29710
|
+
type: "text",
|
|
29711
|
+
x: mapX(angle.x + 1.35 * radius * Math.cos(rad)),
|
|
29712
|
+
y: mapY(angle.y + 1.35 * radius * Math.sin(rad)),
|
|
29713
|
+
text: angle.label,
|
|
29714
|
+
color: "#111827",
|
|
29715
|
+
fontSize: 11,
|
|
29716
|
+
align: "center"
|
|
27630
29717
|
});
|
|
27631
29718
|
}
|
|
27632
29719
|
}
|
|
27633
29720
|
for (const p of points) {
|
|
27634
29721
|
if (p.x < xMin || p.x > xMax || p.y < yMin || p.y > yMax) continue;
|
|
29722
|
+
const isOpen = p.style === "open";
|
|
27635
29723
|
out.push({
|
|
27636
29724
|
type: "circle",
|
|
27637
29725
|
x: mapX(p.x),
|
|
27638
29726
|
y: mapY(p.y),
|
|
27639
29727
|
radius: p.radius ?? 4,
|
|
27640
29728
|
color: p.color ?? "#dc2626",
|
|
27641
|
-
fill: p.color ?? "#dc2626"
|
|
29729
|
+
fill: isOpen ? "#ffffff" : p.color ?? "#dc2626"
|
|
27642
29730
|
});
|
|
27643
29731
|
if (p.label) {
|
|
27644
29732
|
out.push({ type: "text", x: mapX(p.x) + 8, y: mapY(p.y) - 8, text: p.label, color: "#111827", fontSize: 12 });
|
|
@@ -27657,7 +29745,28 @@ var init_MathCanvas = __esm({
|
|
|
27657
29745
|
}
|
|
27658
29746
|
out.push(...shapes);
|
|
27659
29747
|
return out;
|
|
27660
|
-
}, [
|
|
29748
|
+
}, [
|
|
29749
|
+
width,
|
|
29750
|
+
height,
|
|
29751
|
+
xMin,
|
|
29752
|
+
xMax,
|
|
29753
|
+
yMin,
|
|
29754
|
+
yMax,
|
|
29755
|
+
showAxes,
|
|
29756
|
+
showGrid,
|
|
29757
|
+
gridStep,
|
|
29758
|
+
showTickLabels,
|
|
29759
|
+
showCurveLabels,
|
|
29760
|
+
curves,
|
|
29761
|
+
points,
|
|
29762
|
+
vectors,
|
|
29763
|
+
regions,
|
|
29764
|
+
bars,
|
|
29765
|
+
guides,
|
|
29766
|
+
angles,
|
|
29767
|
+
hops,
|
|
29768
|
+
shapes
|
|
29769
|
+
]);
|
|
27661
29770
|
return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
|
|
27662
29771
|
title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
|
|
27663
29772
|
/* @__PURE__ */ jsx(
|
|
@@ -27666,6 +29775,8 @@ var init_MathCanvas = __esm({
|
|
|
27666
29775
|
width,
|
|
27667
29776
|
height,
|
|
27668
29777
|
shapes: derivedShapes,
|
|
29778
|
+
readouts,
|
|
29779
|
+
traces,
|
|
27669
29780
|
interactive,
|
|
27670
29781
|
animate,
|
|
27671
29782
|
onShapeClick,
|
|
@@ -27677,27 +29788,355 @@ var init_MathCanvas = __esm({
|
|
|
27677
29788
|
};
|
|
27678
29789
|
}
|
|
27679
29790
|
});
|
|
27680
|
-
|
|
29791
|
+
function formatMeterValue(v) {
|
|
29792
|
+
return Number.isInteger(v) ? String(v) : String(Number(v.toFixed(2)));
|
|
29793
|
+
}
|
|
29794
|
+
function sceneObjectShapes(obj, canvasWidth, canvasHeight) {
|
|
29795
|
+
const out = [];
|
|
29796
|
+
const color = obj.color ?? "#334155";
|
|
29797
|
+
switch (obj.kind) {
|
|
29798
|
+
case "ground": {
|
|
29799
|
+
const xStart = obj.x1 ?? 0;
|
|
29800
|
+
const xEnd = obj.x2 ?? canvasWidth;
|
|
29801
|
+
const y = obj.y ?? 0;
|
|
29802
|
+
out.push({ type: "line", x1: xStart, y1: y, x2: xEnd, y2: y, color, lineWidth: 2 });
|
|
29803
|
+
for (let hx = xStart + 7; hx <= xEnd; hx += 14) {
|
|
29804
|
+
out.push({ type: "line", x1: hx, y1: y, x2: hx - 7, y2: y + 7, color, lineWidth: 1 });
|
|
29805
|
+
}
|
|
29806
|
+
if (obj.label) {
|
|
29807
|
+
out.push({
|
|
29808
|
+
type: "text",
|
|
29809
|
+
x: (xStart + xEnd) / 2,
|
|
29810
|
+
y: y - 10,
|
|
29811
|
+
text: obj.label,
|
|
29812
|
+
color: PHYSICS_LABEL_COLOR,
|
|
29813
|
+
fontSize: 11,
|
|
29814
|
+
align: "center"
|
|
29815
|
+
});
|
|
29816
|
+
}
|
|
29817
|
+
break;
|
|
29818
|
+
}
|
|
29819
|
+
case "wall": {
|
|
29820
|
+
const yStart = obj.y1 ?? 0;
|
|
29821
|
+
const yEnd = obj.y2 ?? canvasHeight;
|
|
29822
|
+
const x = obj.x ?? 0;
|
|
29823
|
+
out.push({ type: "line", x1: x, y1: yStart, x2: x, y2: yEnd, color, lineWidth: 2 });
|
|
29824
|
+
for (let hy = yStart + 7; hy <= yEnd; hy += 14) {
|
|
29825
|
+
out.push({ type: "line", x1: x, y1: hy, x2: x - 7, y2: hy + 7, color, lineWidth: 1 });
|
|
29826
|
+
}
|
|
29827
|
+
if (obj.label) {
|
|
29828
|
+
out.push({
|
|
29829
|
+
type: "text",
|
|
29830
|
+
x: x + 12,
|
|
29831
|
+
y: (yStart + yEnd) / 2,
|
|
29832
|
+
text: obj.label,
|
|
29833
|
+
color: PHYSICS_LABEL_COLOR,
|
|
29834
|
+
fontSize: 11,
|
|
29835
|
+
align: "left"
|
|
29836
|
+
});
|
|
29837
|
+
}
|
|
29838
|
+
break;
|
|
29839
|
+
}
|
|
29840
|
+
case "ramp": {
|
|
29841
|
+
const x1 = obj.x1 ?? 0;
|
|
29842
|
+
const y1 = obj.y1 ?? 0;
|
|
29843
|
+
const x2 = obj.x2 ?? canvasWidth;
|
|
29844
|
+
const y2 = obj.y2 ?? canvasHeight;
|
|
29845
|
+
out.push({
|
|
29846
|
+
type: "polygon",
|
|
29847
|
+
points: [
|
|
29848
|
+
{ x: x1, y: y1 },
|
|
29849
|
+
{ x: x2, y: y2 },
|
|
29850
|
+
{ x: x1, y: y2 }
|
|
29851
|
+
],
|
|
29852
|
+
color,
|
|
29853
|
+
fill: obj.fill ?? "#e2e8f0",
|
|
29854
|
+
lineWidth: 2
|
|
29855
|
+
});
|
|
29856
|
+
if (obj.label) {
|
|
29857
|
+
out.push({
|
|
29858
|
+
type: "text",
|
|
29859
|
+
x: (2 * x1 + x2) / 3,
|
|
29860
|
+
y: (y1 + 2 * y2) / 3,
|
|
29861
|
+
text: obj.label,
|
|
29862
|
+
color: PHYSICS_LABEL_COLOR,
|
|
29863
|
+
fontSize: 11,
|
|
29864
|
+
align: "center"
|
|
29865
|
+
});
|
|
29866
|
+
}
|
|
29867
|
+
break;
|
|
29868
|
+
}
|
|
29869
|
+
case "box": {
|
|
29870
|
+
const x = obj.x ?? 0;
|
|
29871
|
+
const y = obj.y ?? 0;
|
|
29872
|
+
const w = obj.width ?? 40;
|
|
29873
|
+
const h = obj.height ?? 40;
|
|
29874
|
+
out.push({ type: "rect", x, y, width: w, height: h, color, fill: obj.fill, lineWidth: 2 });
|
|
29875
|
+
if (obj.label) {
|
|
29876
|
+
out.push({
|
|
29877
|
+
type: "text",
|
|
29878
|
+
x: x + w / 2,
|
|
29879
|
+
y: y + h / 2,
|
|
29880
|
+
text: obj.label,
|
|
29881
|
+
color: PHYSICS_LABEL_COLOR,
|
|
29882
|
+
fontSize: 11,
|
|
29883
|
+
align: "center"
|
|
29884
|
+
});
|
|
29885
|
+
}
|
|
29886
|
+
break;
|
|
29887
|
+
}
|
|
29888
|
+
case "pivot": {
|
|
29889
|
+
const x = obj.x ?? 0;
|
|
29890
|
+
const y = obj.y ?? 0;
|
|
29891
|
+
out.push({ type: "circle", x, y, radius: 5, color, fill: color });
|
|
29892
|
+
out.push({ type: "line", x1: x - 14, y1: y - 8, x2: x + 14, y2: y - 8, color, lineWidth: 1 });
|
|
29893
|
+
for (let k = 0; k < 5; k++) {
|
|
29894
|
+
const hx = x - 14 + 7 * k;
|
|
29895
|
+
out.push({ type: "line", x1: hx, y1: y - 8, x2: hx - 6, y2: y - 14, color, lineWidth: 1 });
|
|
29896
|
+
}
|
|
29897
|
+
if (obj.label) {
|
|
29898
|
+
out.push({
|
|
29899
|
+
type: "text",
|
|
29900
|
+
x,
|
|
29901
|
+
y: y - 20,
|
|
29902
|
+
text: obj.label,
|
|
29903
|
+
color: PHYSICS_LABEL_COLOR,
|
|
29904
|
+
fontSize: 11,
|
|
29905
|
+
align: "center"
|
|
29906
|
+
});
|
|
29907
|
+
}
|
|
29908
|
+
break;
|
|
29909
|
+
}
|
|
29910
|
+
}
|
|
29911
|
+
return out;
|
|
29912
|
+
}
|
|
29913
|
+
function trailShapes(trail) {
|
|
29914
|
+
const n = trail.points.length;
|
|
29915
|
+
if (n < 2) return [];
|
|
29916
|
+
const color = trail.color ?? "#94a3b8";
|
|
29917
|
+
const lineWidth = trail.width ?? 2;
|
|
29918
|
+
const fade = trail.fade ?? true;
|
|
29919
|
+
const globalOpacity = trail.opacity ?? 1;
|
|
29920
|
+
const out = [];
|
|
29921
|
+
for (let i = 0; i < n - 1; i++) {
|
|
29922
|
+
const a = trail.points[i];
|
|
29923
|
+
const b = trail.points[i + 1];
|
|
29924
|
+
const segmentOpacity = fade ? 0.12 + 0.68 * i / (n - 1) : 0.6;
|
|
29925
|
+
out.push({
|
|
29926
|
+
type: "line",
|
|
29927
|
+
x1: a.x,
|
|
29928
|
+
y1: a.y,
|
|
29929
|
+
x2: b.x,
|
|
29930
|
+
y2: b.y,
|
|
29931
|
+
color,
|
|
29932
|
+
lineWidth,
|
|
29933
|
+
opacity: segmentOpacity * globalOpacity
|
|
29934
|
+
});
|
|
29935
|
+
}
|
|
29936
|
+
return out;
|
|
29937
|
+
}
|
|
29938
|
+
function constraintShapes(c, a, b) {
|
|
29939
|
+
const color = c.color ?? "#9ca3af";
|
|
29940
|
+
const kind = c.kind ?? "rod";
|
|
29941
|
+
if (kind === "rod") {
|
|
29942
|
+
return [{ type: "line", x1: a.x, y1: a.y, x2: b.x, y2: b.y, color, lineWidth: 2 }];
|
|
29943
|
+
}
|
|
29944
|
+
if (kind === "string") {
|
|
29945
|
+
return [{ type: "line", x1: a.x, y1: a.y, x2: b.x, y2: b.y, color, lineWidth: 2, dash: "dashed" }];
|
|
29946
|
+
}
|
|
29947
|
+
const COILS = 8;
|
|
29948
|
+
const AMP = 7;
|
|
29949
|
+
const LEAD = 10;
|
|
29950
|
+
const dx = b.x - a.x;
|
|
29951
|
+
const dy = b.y - a.y;
|
|
29952
|
+
const dist = Math.max(1e-6, Math.hypot(dx, dy));
|
|
29953
|
+
const ux = dx / dist;
|
|
29954
|
+
const uy = dy / dist;
|
|
29955
|
+
const perpX = -uy;
|
|
29956
|
+
const perpY = ux;
|
|
29957
|
+
const aPrime = { x: a.x + LEAD * ux, y: a.y + LEAD * uy };
|
|
29958
|
+
const bPrime = { x: b.x - LEAD * ux, y: b.y - LEAD * uy };
|
|
29959
|
+
const m = 2 * COILS;
|
|
29960
|
+
const polyline = [{ x: a.x, y: a.y }, aPrime];
|
|
29961
|
+
for (let j = 1; j <= m; j++) {
|
|
29962
|
+
const t = j / (m + 1);
|
|
29963
|
+
const baseX = aPrime.x + t * (bPrime.x - aPrime.x);
|
|
29964
|
+
const baseY = aPrime.y + t * (bPrime.y - aPrime.y);
|
|
29965
|
+
const sign = j % 2 === 0 ? 1 : -1;
|
|
29966
|
+
polyline.push({ x: baseX + sign * AMP * perpX, y: baseY + sign * AMP * perpY });
|
|
29967
|
+
}
|
|
29968
|
+
polyline.push(bPrime, { x: b.x, y: b.y });
|
|
29969
|
+
const out = [];
|
|
29970
|
+
for (let i = 1; i < polyline.length; i++) {
|
|
29971
|
+
out.push({
|
|
29972
|
+
type: "line",
|
|
29973
|
+
x1: polyline[i - 1].x,
|
|
29974
|
+
y1: polyline[i - 1].y,
|
|
29975
|
+
x2: polyline[i].x,
|
|
29976
|
+
y2: polyline[i].y,
|
|
29977
|
+
color,
|
|
29978
|
+
lineWidth: 2
|
|
29979
|
+
});
|
|
29980
|
+
}
|
|
29981
|
+
return out;
|
|
29982
|
+
}
|
|
29983
|
+
function vectorShapes(v, bodyById) {
|
|
29984
|
+
let ax;
|
|
29985
|
+
let ay;
|
|
29986
|
+
if (v.body) {
|
|
29987
|
+
const anchor = bodyById.get(v.body);
|
|
29988
|
+
if (!anchor) return [];
|
|
29989
|
+
ax = anchor.x;
|
|
29990
|
+
ay = anchor.y;
|
|
29991
|
+
} else {
|
|
29992
|
+
ax = v.x ?? 0;
|
|
29993
|
+
ay = v.y ?? 0;
|
|
29994
|
+
}
|
|
29995
|
+
const scale = v.scale ?? 1;
|
|
29996
|
+
const color = v.color ?? "#dc2626";
|
|
29997
|
+
const tx = ax + v.dx * scale;
|
|
29998
|
+
const ty = ay + v.dy * scale;
|
|
29999
|
+
const out = [{ type: "arrow", x1: ax, y1: ay, x2: tx, y2: ty, color, lineWidth: 2, dash: v.dash }];
|
|
30000
|
+
if (v.label) {
|
|
30001
|
+
const dist = Math.max(1e-6, Math.hypot(tx - ax, ty - ay));
|
|
30002
|
+
const ux = (tx - ax) / dist;
|
|
30003
|
+
const uy = (ty - ay) / dist;
|
|
30004
|
+
out.push({
|
|
30005
|
+
type: "text",
|
|
30006
|
+
x: tx + 8 * ux,
|
|
30007
|
+
y: ty + 8 * uy,
|
|
30008
|
+
text: v.label,
|
|
30009
|
+
color,
|
|
30010
|
+
fontSize: 11,
|
|
30011
|
+
align: "center"
|
|
30012
|
+
});
|
|
30013
|
+
}
|
|
30014
|
+
return out;
|
|
30015
|
+
}
|
|
30016
|
+
function angleMarkerShapes(a) {
|
|
30017
|
+
const radius = a.radius ?? 26;
|
|
30018
|
+
const color = a.color ?? "#0ea5e9";
|
|
30019
|
+
const out = [
|
|
30020
|
+
{
|
|
30021
|
+
type: "ellipse",
|
|
30022
|
+
x: a.x,
|
|
30023
|
+
y: a.y,
|
|
30024
|
+
width: radius * 2,
|
|
30025
|
+
height: radius * 2,
|
|
30026
|
+
startAngle: a.from,
|
|
30027
|
+
endAngle: a.to,
|
|
30028
|
+
color,
|
|
30029
|
+
lineWidth: 2
|
|
30030
|
+
}
|
|
30031
|
+
];
|
|
30032
|
+
if (a.label) {
|
|
30033
|
+
const mid = (a.from + a.to) / 2 * (Math.PI / 180);
|
|
30034
|
+
out.push({
|
|
30035
|
+
type: "text",
|
|
30036
|
+
x: a.x + (radius + 13) * Math.cos(mid),
|
|
30037
|
+
y: a.y + (radius + 13) * Math.sin(mid),
|
|
30038
|
+
text: a.label,
|
|
30039
|
+
color,
|
|
30040
|
+
fontSize: 11,
|
|
30041
|
+
align: "center"
|
|
30042
|
+
});
|
|
30043
|
+
}
|
|
30044
|
+
return out;
|
|
30045
|
+
}
|
|
30046
|
+
function fieldShapes(field, canvasWidth, canvasHeight) {
|
|
30047
|
+
const spacing = field.spacing ?? 48;
|
|
30048
|
+
const size = field.size ?? 14;
|
|
30049
|
+
const color = field.color ?? "#94a3b8";
|
|
30050
|
+
const regionX = field.x ?? 0;
|
|
30051
|
+
const regionY = field.y ?? 0;
|
|
30052
|
+
const regionW = field.width ?? canvasWidth;
|
|
30053
|
+
const regionH = field.height ?? canvasHeight;
|
|
30054
|
+
const out = [];
|
|
30055
|
+
for (let gx = regionX + spacing / 2; gx < regionX + regionW; gx += spacing) {
|
|
30056
|
+
for (let gy = regionY + spacing / 2; gy < regionY + regionH; gy += spacing) {
|
|
30057
|
+
if (field.kind === "arrows") {
|
|
30058
|
+
const rad = (field.angle ?? 0) * Math.PI / 180;
|
|
30059
|
+
const hx = Math.cos(rad) * size / 2;
|
|
30060
|
+
const hy = Math.sin(rad) * size / 2;
|
|
30061
|
+
out.push({ type: "arrow", x1: gx - hx, y1: gy - hy, x2: gx + hx, y2: gy + hy, color, lineWidth: 2 });
|
|
30062
|
+
} else if (field.kind === "into") {
|
|
30063
|
+
const r = size / 3;
|
|
30064
|
+
const d = 0.6 * r * Math.SQRT1_2;
|
|
30065
|
+
out.push({ type: "circle", x: gx, y: gy, radius: r, color });
|
|
30066
|
+
out.push({ type: "line", x1: gx - d, y1: gy - d, x2: gx + d, y2: gy + d, color, lineWidth: 1 });
|
|
30067
|
+
out.push({ type: "line", x1: gx - d, y1: gy + d, x2: gx + d, y2: gy - d, color, lineWidth: 1 });
|
|
30068
|
+
} else {
|
|
30069
|
+
const r = size / 3;
|
|
30070
|
+
out.push({ type: "circle", x: gx, y: gy, radius: r, color });
|
|
30071
|
+
out.push({ type: "circle", x: gx, y: gy, radius: 1.5, color, fill: color });
|
|
30072
|
+
}
|
|
30073
|
+
}
|
|
30074
|
+
}
|
|
30075
|
+
return out;
|
|
30076
|
+
}
|
|
30077
|
+
function meterShapes(meters, canvasHeight) {
|
|
30078
|
+
const n = meters.length;
|
|
30079
|
+
const out = [];
|
|
30080
|
+
const sharedMax = Math.max(1e-6, ...meters.map((m) => m.value));
|
|
30081
|
+
meters.forEach((meter, i) => {
|
|
30082
|
+
const rowY = canvasHeight - 10 - 16 * (n - i);
|
|
30083
|
+
const color = meter.color ?? "#3b82f6";
|
|
30084
|
+
const M = meter.max ?? sharedMax;
|
|
30085
|
+
const w = Math.round(Math.min(1, Math.max(0, meter.value / M)) * 110);
|
|
30086
|
+
out.push({ type: "text", x: 8, y: rowY + 8, text: meter.label, color: PHYSICS_LABEL_COLOR, fontSize: 10 });
|
|
30087
|
+
out.push({ type: "rect", x: 52, y: rowY, width: w, height: 10, color, fill: color });
|
|
30088
|
+
out.push({
|
|
30089
|
+
type: "text",
|
|
30090
|
+
x: 166,
|
|
30091
|
+
y: rowY + 8,
|
|
30092
|
+
text: formatMeterValue(meter.value),
|
|
30093
|
+
color: "#6b7280",
|
|
30094
|
+
fontSize: 9
|
|
30095
|
+
});
|
|
30096
|
+
});
|
|
30097
|
+
return out;
|
|
30098
|
+
}
|
|
30099
|
+
var physicsLog2, PHYSICS_LABEL_COLOR, PhysicsCanvas;
|
|
27681
30100
|
var init_PhysicsCanvas = __esm({
|
|
27682
30101
|
"components/learning/molecules/PhysicsCanvas.tsx"() {
|
|
27683
30102
|
"use client";
|
|
27684
30103
|
init_atoms();
|
|
27685
30104
|
init_Stack();
|
|
27686
30105
|
init_LearningCanvas();
|
|
30106
|
+
init_learningScene3D();
|
|
30107
|
+
physicsLog2 = createLogger("almadar:ui:physics-canvas");
|
|
30108
|
+
PHYSICS_LABEL_COLOR = "#374151";
|
|
27687
30109
|
PhysicsCanvas = ({
|
|
27688
30110
|
className,
|
|
27689
30111
|
width = 600,
|
|
27690
30112
|
height = 400,
|
|
27691
30113
|
title,
|
|
27692
30114
|
backgroundColor,
|
|
30115
|
+
mode = "2d",
|
|
30116
|
+
camera,
|
|
30117
|
+
lighting,
|
|
30118
|
+
post,
|
|
27693
30119
|
bodies = [],
|
|
27694
30120
|
constraints = [],
|
|
27695
30121
|
showVelocity = true,
|
|
27696
30122
|
showForces = false,
|
|
27697
30123
|
velocityScale = 20,
|
|
27698
30124
|
forceScale = 20,
|
|
30125
|
+
sceneObjects = [],
|
|
30126
|
+
trails = [],
|
|
30127
|
+
vectors = [],
|
|
30128
|
+
surface3d,
|
|
30129
|
+
vectors3d = [],
|
|
30130
|
+
vectorScale = 1,
|
|
30131
|
+
angles = [],
|
|
30132
|
+
field,
|
|
30133
|
+
meters = [],
|
|
27699
30134
|
shapes = [],
|
|
27700
|
-
|
|
30135
|
+
readouts,
|
|
30136
|
+
traces,
|
|
30137
|
+
showGrid,
|
|
30138
|
+
shadows,
|
|
30139
|
+
interactive,
|
|
27701
30140
|
animate = false,
|
|
27702
30141
|
onShapeClick,
|
|
27703
30142
|
isLoading,
|
|
@@ -27709,19 +30148,14 @@ var init_PhysicsCanvas = __esm({
|
|
|
27709
30148
|
for (const b of bodies) {
|
|
27710
30149
|
if (b.id) bodyById.set(b.id, b);
|
|
27711
30150
|
}
|
|
30151
|
+
if (field) out.push(...fieldShapes(field, width, height));
|
|
30152
|
+
for (const obj of sceneObjects) out.push(...sceneObjectShapes(obj, width, height));
|
|
30153
|
+
for (const trail of trails) out.push(...trailShapes(trail));
|
|
27712
30154
|
for (const c of constraints) {
|
|
27713
30155
|
const a = bodyById.get(c.from);
|
|
27714
30156
|
const b = bodyById.get(c.to);
|
|
27715
30157
|
if (!a || !b) continue;
|
|
27716
|
-
out.push(
|
|
27717
|
-
type: "line",
|
|
27718
|
-
x1: a.x,
|
|
27719
|
-
y1: a.y,
|
|
27720
|
-
x2: b.x,
|
|
27721
|
-
y2: b.y,
|
|
27722
|
-
color: c.color ?? "#9ca3af",
|
|
27723
|
-
lineWidth: 2
|
|
27724
|
-
});
|
|
30158
|
+
out.push(...constraintShapes(c, a, b));
|
|
27725
30159
|
}
|
|
27726
30160
|
for (const b of bodies) {
|
|
27727
30161
|
out.push({
|
|
@@ -27766,9 +30200,181 @@ var init_PhysicsCanvas = __esm({
|
|
|
27766
30200
|
});
|
|
27767
30201
|
}
|
|
27768
30202
|
}
|
|
30203
|
+
for (const v of vectors) out.push(...vectorShapes(v, bodyById));
|
|
30204
|
+
for (const a of angles) out.push(...angleMarkerShapes(a));
|
|
30205
|
+
if (meters.length > 0) out.push(...meterShapes(meters, height));
|
|
27769
30206
|
out.push(...shapes);
|
|
27770
30207
|
return out;
|
|
27771
|
-
}, [
|
|
30208
|
+
}, [
|
|
30209
|
+
bodies,
|
|
30210
|
+
constraints,
|
|
30211
|
+
showVelocity,
|
|
30212
|
+
showForces,
|
|
30213
|
+
velocityScale,
|
|
30214
|
+
forceScale,
|
|
30215
|
+
sceneObjects,
|
|
30216
|
+
trails,
|
|
30217
|
+
vectors,
|
|
30218
|
+
angles,
|
|
30219
|
+
field,
|
|
30220
|
+
meters,
|
|
30221
|
+
shapes,
|
|
30222
|
+
width,
|
|
30223
|
+
height
|
|
30224
|
+
]);
|
|
30225
|
+
const drawables3D = useMemo(() => {
|
|
30226
|
+
if (mode !== "3d") return [];
|
|
30227
|
+
if (shapes.length > 0) {
|
|
30228
|
+
physicsLog2.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
|
|
30229
|
+
}
|
|
30230
|
+
if (sceneObjects.length > 0) {
|
|
30231
|
+
physicsLog2.debug("sceneObjects ignored in 3D mode (pixel-authored 2D vocabulary)", { count: sceneObjects.length });
|
|
30232
|
+
}
|
|
30233
|
+
if (vectors.length > 0) {
|
|
30234
|
+
physicsLog2.debug("vectors ignored in 3D mode (pixel-authored 2D vocabulary)", { count: vectors.length });
|
|
30235
|
+
}
|
|
30236
|
+
if (angles.length > 0) {
|
|
30237
|
+
physicsLog2.debug("angles ignored in 3D mode (pixel-authored 2D vocabulary)", { count: angles.length });
|
|
30238
|
+
}
|
|
30239
|
+
if (field) {
|
|
30240
|
+
physicsLog2.debug("field ignored in 3D mode (pixel-authored 2D vocabulary)");
|
|
30241
|
+
}
|
|
30242
|
+
if (meters.length > 0) {
|
|
30243
|
+
physicsLog2.debug("meters ignored in 3D mode (pixel-authored 2D vocabulary)", { count: meters.length });
|
|
30244
|
+
}
|
|
30245
|
+
if (animate) {
|
|
30246
|
+
physicsLog2.debug("animate ignored in 3D mode (motion is entity-state driven)");
|
|
30247
|
+
}
|
|
30248
|
+
const out = [];
|
|
30249
|
+
const labelColor = labelColorForBackground(backgroundColor);
|
|
30250
|
+
const bodyById = /* @__PURE__ */ new Map();
|
|
30251
|
+
for (const b of bodies) {
|
|
30252
|
+
if (b.id) bodyById.set(b.id, b);
|
|
30253
|
+
}
|
|
30254
|
+
for (const c of constraints) {
|
|
30255
|
+
const a = bodyById.get(c.from);
|
|
30256
|
+
const b = bodyById.get(c.to);
|
|
30257
|
+
if (!a || !b) continue;
|
|
30258
|
+
const rodRadius = Math.max(0.05, Math.min(a.radius ?? 0.5, b.radius ?? 0.5) * 0.15);
|
|
30259
|
+
const rod = cylinderBetween([a.x, a.y, a.z ?? 0], [b.x, b.y, b.z ?? 0], rodRadius, c.color ?? "#9ca3af");
|
|
30260
|
+
if (rod) out.push(rod);
|
|
30261
|
+
}
|
|
30262
|
+
for (const b of bodies) {
|
|
30263
|
+
const radius = b.radius ?? 0.5;
|
|
30264
|
+
const bz = b.z ?? 0;
|
|
30265
|
+
out.push(meshSphere(b.id, b.x, b.y, bz, radius, b.color ?? "#2563eb"));
|
|
30266
|
+
if (b.label) {
|
|
30267
|
+
out.push(billboardLabel(b.label, b.x, b.y, bz + radius, { color: labelColor }));
|
|
30268
|
+
}
|
|
30269
|
+
const vx = b.vx ?? 0;
|
|
30270
|
+
const vy = b.vy ?? 0;
|
|
30271
|
+
const vz = b.vz ?? 0;
|
|
30272
|
+
const arrowRadius = Math.max(0.05, radius * 0.15);
|
|
30273
|
+
if (showVelocity && (vx !== 0 || vy !== 0 || vz !== 0)) {
|
|
30274
|
+
const arrow = arrowBetween(
|
|
30275
|
+
[b.x, b.y, bz],
|
|
30276
|
+
[b.x + vx * velocityScale, b.y + vy * velocityScale, bz + vz * velocityScale],
|
|
30277
|
+
"#16a34a",
|
|
30278
|
+
arrowRadius
|
|
30279
|
+
);
|
|
30280
|
+
if (arrow) out.push(arrow);
|
|
30281
|
+
}
|
|
30282
|
+
const fx = b.fx ?? 0;
|
|
30283
|
+
const fy = b.fy ?? 0;
|
|
30284
|
+
const fz = b.fz ?? 0;
|
|
30285
|
+
if (showForces && (fx !== 0 || fy !== 0 || fz !== 0)) {
|
|
30286
|
+
const arrow = arrowBetween(
|
|
30287
|
+
[b.x, b.y, bz],
|
|
30288
|
+
[b.x + fx * forceScale, b.y + fy * forceScale, bz + fz * forceScale],
|
|
30289
|
+
"#dc2626",
|
|
30290
|
+
arrowRadius
|
|
30291
|
+
);
|
|
30292
|
+
if (arrow) out.push(arrow);
|
|
30293
|
+
}
|
|
30294
|
+
}
|
|
30295
|
+
for (const trail of trails) {
|
|
30296
|
+
if (trail.fade !== void 0) {
|
|
30297
|
+
physicsLog2.debug("trail.fade ignored in 3D mode (2D-only fade curve \u2014 3D draws an opaque tube)", { id: trail.id });
|
|
30298
|
+
}
|
|
30299
|
+
const points = trail.points.map((p) => [p.x, p.y, p.z ?? 0]);
|
|
30300
|
+
out.push(
|
|
30301
|
+
...polylineTube(points, trail.width ?? 0.05, trail.color ?? "#94a3b8", {
|
|
30302
|
+
...trail.opacity !== void 0 ? { opacity: trail.opacity } : {}
|
|
30303
|
+
})
|
|
30304
|
+
);
|
|
30305
|
+
}
|
|
30306
|
+
if (surface3d) {
|
|
30307
|
+
out.push(...heightFieldMesh(surface3d));
|
|
30308
|
+
}
|
|
30309
|
+
if (vectors3d.length > 0) {
|
|
30310
|
+
out.push(
|
|
30311
|
+
...arrowField(
|
|
30312
|
+
vectors3d.map((v) => ({
|
|
30313
|
+
id: v.id,
|
|
30314
|
+
from: [v.x, v.y, v.z ?? 0],
|
|
30315
|
+
delta: [v.dx, v.dy, v.dz ?? 0],
|
|
30316
|
+
color: v.color,
|
|
30317
|
+
label: v.label,
|
|
30318
|
+
width: v.width
|
|
30319
|
+
})),
|
|
30320
|
+
{ scale: vectorScale, labelColor }
|
|
30321
|
+
)
|
|
30322
|
+
);
|
|
30323
|
+
}
|
|
30324
|
+
return out;
|
|
30325
|
+
}, [
|
|
30326
|
+
mode,
|
|
30327
|
+
bodies,
|
|
30328
|
+
constraints,
|
|
30329
|
+
showVelocity,
|
|
30330
|
+
showForces,
|
|
30331
|
+
velocityScale,
|
|
30332
|
+
forceScale,
|
|
30333
|
+
shapes,
|
|
30334
|
+
sceneObjects,
|
|
30335
|
+
trails,
|
|
30336
|
+
vectors,
|
|
30337
|
+
surface3d,
|
|
30338
|
+
vectors3d,
|
|
30339
|
+
vectorScale,
|
|
30340
|
+
angles,
|
|
30341
|
+
field,
|
|
30342
|
+
meters,
|
|
30343
|
+
animate,
|
|
30344
|
+
backgroundColor
|
|
30345
|
+
]);
|
|
30346
|
+
const bodyIndexById = useMemo(() => {
|
|
30347
|
+
const m = /* @__PURE__ */ new Map();
|
|
30348
|
+
vectors3d.forEach((v, i) => {
|
|
30349
|
+
if (v.id) m.set(v.id, i);
|
|
30350
|
+
});
|
|
30351
|
+
bodies.forEach((b, i) => {
|
|
30352
|
+
if (b.id) m.set(b.id, i);
|
|
30353
|
+
});
|
|
30354
|
+
return m;
|
|
30355
|
+
}, [bodies, vectors3d]);
|
|
30356
|
+
if (mode === "3d") {
|
|
30357
|
+
return /* @__PURE__ */ jsx(
|
|
30358
|
+
LearningScene3D,
|
|
30359
|
+
{
|
|
30360
|
+
className,
|
|
30361
|
+
width,
|
|
30362
|
+
height,
|
|
30363
|
+
title,
|
|
30364
|
+
backgroundColor,
|
|
30365
|
+
drawables: drawables3D,
|
|
30366
|
+
camera,
|
|
30367
|
+
lighting,
|
|
30368
|
+
post,
|
|
30369
|
+
showGrid,
|
|
30370
|
+
shadows,
|
|
30371
|
+
interactive,
|
|
30372
|
+
isLoading,
|
|
30373
|
+
error,
|
|
30374
|
+
onItemClick: get3DClickPayload(onShapeClick, bodyIndexById)
|
|
30375
|
+
}
|
|
30376
|
+
);
|
|
30377
|
+
}
|
|
27772
30378
|
return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
|
|
27773
30379
|
title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
|
|
27774
30380
|
/* @__PURE__ */ jsx(
|
|
@@ -27778,7 +30384,9 @@ var init_PhysicsCanvas = __esm({
|
|
|
27778
30384
|
height,
|
|
27779
30385
|
backgroundColor,
|
|
27780
30386
|
shapes: derivedShapes,
|
|
27781
|
-
|
|
30387
|
+
readouts,
|
|
30388
|
+
traces,
|
|
30389
|
+
interactive: interactive ?? false,
|
|
27782
30390
|
animate,
|
|
27783
30391
|
onShapeClick,
|
|
27784
30392
|
isLoading,
|
|
@@ -27928,7 +30536,7 @@ function layoutFlow(nodeIds, adjacency, roots, width, height, margin) {
|
|
|
27928
30536
|
}
|
|
27929
30537
|
return nodeIds.map((id) => positions.get(id));
|
|
27930
30538
|
}
|
|
27931
|
-
function
|
|
30539
|
+
function layoutTree2(nodeIds, adjacency, roots, width, height, margin) {
|
|
27932
30540
|
const effectiveRoots = roots.length > 0 ? roots : [nodeIds[0]];
|
|
27933
30541
|
const layers = assignLayers(nodeIds, adjacency, effectiveRoots);
|
|
27934
30542
|
const maxLayer = Math.max(...Array.from(layers.values()));
|
|
@@ -27984,7 +30592,7 @@ function computeStaticLayout(mode, input) {
|
|
|
27984
30592
|
const adjacency = buildAdjacency(nodeIds, edges);
|
|
27985
30593
|
const roots = findRoots(nodeIds, adjacency);
|
|
27986
30594
|
if (mode === "flow") return layoutFlow(nodeIds, adjacency, roots, width, height, margin);
|
|
27987
|
-
if (mode === "tree") return
|
|
30595
|
+
if (mode === "tree") return layoutTree2(nodeIds, adjacency, roots, width, height, margin);
|
|
27988
30596
|
return layoutRadial(nodeIds, adjacency, roots, width, height, margin);
|
|
27989
30597
|
}
|
|
27990
30598
|
var init_graphViewLayouts = __esm({
|
|
@@ -28396,12 +31004,12 @@ var init_MapView = __esm({
|
|
|
28396
31004
|
shadowSize: [41, 41]
|
|
28397
31005
|
});
|
|
28398
31006
|
L.Marker.prototype.options.icon = defaultIcon;
|
|
28399
|
-
const { useEffect: useEffect60, useRef:
|
|
31007
|
+
const { useEffect: useEffect60, useRef: useRef63, useCallback: useCallback90, useState: useState92 } = React85__default;
|
|
28400
31008
|
const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
|
|
28401
31009
|
const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
|
|
28402
31010
|
function MapUpdater({ centerLat, centerLng, zoom }) {
|
|
28403
31011
|
const map = useMap();
|
|
28404
|
-
const prevRef =
|
|
31012
|
+
const prevRef = useRef63({ centerLat, centerLng, zoom });
|
|
28405
31013
|
useEffect60(() => {
|
|
28406
31014
|
const prev = prevRef.current;
|
|
28407
31015
|
if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
|
|
@@ -29265,8 +31873,8 @@ function TableView({
|
|
|
29265
31873
|
columns,
|
|
29266
31874
|
fields,
|
|
29267
31875
|
itemActions,
|
|
29268
|
-
maxInlineActions,
|
|
29269
|
-
itemClickEvent,
|
|
31876
|
+
maxInlineActions: _maxInlineActions,
|
|
31877
|
+
itemClickEvent = "",
|
|
29270
31878
|
selectable = false,
|
|
29271
31879
|
selectEvent,
|
|
29272
31880
|
selectedIds,
|
|
@@ -29316,7 +31924,6 @@ function TableView({
|
|
|
29316
31924
|
const hasMore = pageSize > 0 && visibleCount < ordered2.length;
|
|
29317
31925
|
const hasRenderProp = typeof children === "function";
|
|
29318
31926
|
const idField = dndItemIdField ?? "id";
|
|
29319
|
-
const isCoarsePointer = useMediaQuery("(pointer: coarse)");
|
|
29320
31927
|
React85__default.useEffect(() => {
|
|
29321
31928
|
tableViewLog.debug("render", {
|
|
29322
31929
|
rowCount: data.length,
|
|
@@ -29358,21 +31965,14 @@ function TableView({
|
|
|
29358
31965
|
const dir = sortColumn === (col.field ?? col.key) && sortDirection === "asc" ? "desc" : "asc";
|
|
29359
31966
|
eventBus.emit(`UI:${sortEvent}`, { column: col.field ?? col.key, direction: dir });
|
|
29360
31967
|
};
|
|
29361
|
-
const
|
|
29362
|
-
e.stopPropagation();
|
|
29363
|
-
const payload = {
|
|
29364
|
-
id: row.id,
|
|
29365
|
-
row
|
|
29366
|
-
};
|
|
29367
|
-
eventBus.emit(`UI:${action.event}`, payload);
|
|
29368
|
-
};
|
|
31968
|
+
const rowClickEvent = itemClickEvent || actionDefs.find((a) => a.variant !== "danger")?.event;
|
|
29369
31969
|
const handleRowClick = (row) => () => {
|
|
29370
|
-
if (!
|
|
31970
|
+
if (!rowClickEvent) return;
|
|
29371
31971
|
const payload = {
|
|
29372
31972
|
id: row.id,
|
|
29373
31973
|
row
|
|
29374
31974
|
};
|
|
29375
|
-
eventBus.emit(`UI:${
|
|
31975
|
+
eventBus.emit(`UI:${rowClickEvent}`, payload);
|
|
29376
31976
|
};
|
|
29377
31977
|
const colFloors = React85__default.useMemo(
|
|
29378
31978
|
() => colDefs.map((col) => {
|
|
@@ -29388,10 +31988,7 @@ function TableView({
|
|
|
29388
31988
|
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;
|
|
29389
31989
|
const lk = LOOKS[look];
|
|
29390
31990
|
const hasActions = actionDefs.length > 0;
|
|
29391
|
-
const
|
|
29392
|
-
const inlineActionCount = hasActions ? effectiveMaxInline != null ? Math.min(actionDefs.length, effectiveMaxInline) : actionDefs.length : 0;
|
|
29393
|
-
const hasOverflowActions = hasActions && effectiveMaxInline != null && actionDefs.length > effectiveMaxInline;
|
|
29394
|
-
const actionsTrack = hasActions ? `${inlineActionCount * 6 + (hasOverflowActions ? 3 : 0)}rem` : null;
|
|
31991
|
+
const actionsTrack = hasActions ? "3rem" : null;
|
|
29395
31992
|
const gridTemplateColumns = [
|
|
29396
31993
|
selectable ? "auto" : null,
|
|
29397
31994
|
...colDefs.map((c, i) => c.width ?? `minmax(${colFloors[i]}ch, 1fr)`),
|
|
@@ -29438,7 +32035,7 @@ function TableView({
|
|
|
29438
32035
|
col.key
|
|
29439
32036
|
);
|
|
29440
32037
|
}),
|
|
29441
|
-
hasActions && /* @__PURE__ */ jsx(Box, { "aria-hidden": true, className: "sticky right-0 bg-[var(--color-surface-subtle)]" })
|
|
32038
|
+
hasActions && /* @__PURE__ */ jsx(Box, { "aria-hidden": true, className: "sticky right-0 bg-[var(--color-surface-subtle)] border-l border-[var(--color-border)] h-full" })
|
|
29442
32039
|
]
|
|
29443
32040
|
}
|
|
29444
32041
|
);
|
|
@@ -29450,12 +32047,12 @@ function TableView({
|
|
|
29450
32047
|
role: "row",
|
|
29451
32048
|
"data-entity-row": true,
|
|
29452
32049
|
"data-entity-id": id,
|
|
29453
|
-
onClick:
|
|
32050
|
+
onClick: rowClickEvent ? handleRowClick(row) : void 0,
|
|
29454
32051
|
style: !hasRenderProp ? { gridTemplateColumns } : void 0,
|
|
29455
32052
|
className: cn(
|
|
29456
32053
|
"group items-center gap-3 transition-colors duration-fast",
|
|
29457
32054
|
hasRenderProp ? "flex" : "grid",
|
|
29458
|
-
|
|
32055
|
+
rowClickEvent && "cursor-pointer",
|
|
29459
32056
|
lk.rowPad,
|
|
29460
32057
|
lk.divider && "border-b border-[var(--color-border)]",
|
|
29461
32058
|
lk.striped && index % 2 === 1 && "bg-[var(--color-surface-subtle)]",
|
|
@@ -29463,7 +32060,7 @@ function TableView({
|
|
|
29463
32060
|
look === "bordered" && "[&>*]:border-r [&>*]:border-[var(--color-border)] [&>*:last-child]:border-r-0"
|
|
29464
32061
|
),
|
|
29465
32062
|
children: [
|
|
29466
|
-
selectable && /* @__PURE__ */ jsx(Box, { className: "flex items-center", onClick:
|
|
32063
|
+
selectable && /* @__PURE__ */ jsx(Box, { className: "flex items-center", onClick: rowClickEvent ? (e) => e.stopPropagation() : void 0, children: /* @__PURE__ */ jsx(
|
|
29467
32064
|
Checkbox,
|
|
29468
32065
|
{
|
|
29469
32066
|
checked: selected.has(id),
|
|
@@ -29484,53 +32081,37 @@ function TableView({
|
|
|
29484
32081
|
}
|
|
29485
32082
|
return /* @__PURE__ */ jsx(Box, { role: "cell", className: cellBase, children: /* @__PURE__ */ jsx("span", { className: "truncate text-foreground", children: formatCell(raw, col.format) }) }, col.key);
|
|
29486
32083
|
}),
|
|
29487
|
-
hasActions && /* @__PURE__ */
|
|
32084
|
+
hasActions && /* @__PURE__ */ jsx(
|
|
29488
32085
|
HStack,
|
|
29489
32086
|
{
|
|
29490
32087
|
gap: "xs",
|
|
29491
|
-
onClick:
|
|
32088
|
+
onClick: rowClickEvent ? (e) => e.stopPropagation() : void 0,
|
|
29492
32089
|
className: cn(
|
|
29493
32090
|
// Pinned: the fixed column tracks routinely overflow the caller's
|
|
29494
|
-
// scroll container, which
|
|
29495
|
-
// Opaque so
|
|
32091
|
+
// scroll container, which would leave the kebab off-screen.
|
|
32092
|
+
// Opaque + hairline edge so it reads as a pinned column, not a
|
|
32093
|
+
// floating control, while scrolled cells pass underneath.
|
|
29496
32094
|
"justify-end flex-shrink-0 sticky right-0 z-[1] transition-colors",
|
|
32095
|
+
"border-l border-[var(--color-border)]",
|
|
29497
32096
|
lk.striped && index % 2 === 1 ? "bg-[var(--color-surface-subtle)]" : "bg-[var(--color-card)] group-hover:bg-[var(--color-surface-subtle)]"
|
|
29498
32097
|
),
|
|
29499
|
-
children:
|
|
29500
|
-
|
|
29501
|
-
|
|
29502
|
-
|
|
29503
|
-
|
|
29504
|
-
|
|
29505
|
-
|
|
29506
|
-
|
|
29507
|
-
|
|
29508
|
-
|
|
29509
|
-
|
|
29510
|
-
|
|
29511
|
-
|
|
29512
|
-
|
|
29513
|
-
}
|
|
29514
|
-
|
|
29515
|
-
|
|
29516
|
-
effectiveMaxInline != null && actionDefs.length > effectiveMaxInline && /* @__PURE__ */ jsx(
|
|
29517
|
-
Menu,
|
|
29518
|
-
{
|
|
29519
|
-
position: "bottom-end",
|
|
29520
|
-
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" }) }),
|
|
29521
|
-
items: actionDefs.slice(effectiveMaxInline).map((action) => ({
|
|
29522
|
-
label: action.label,
|
|
29523
|
-
icon: action.icon,
|
|
29524
|
-
event: action.event,
|
|
29525
|
-
variant: action.variant === "danger" ? "danger" : "default",
|
|
29526
|
-
onClick: () => eventBus.emit(`UI:${action.event}`, {
|
|
29527
|
-
id: row.id,
|
|
29528
|
-
row
|
|
29529
|
-
})
|
|
29530
|
-
}))
|
|
29531
|
-
}
|
|
29532
|
-
)
|
|
29533
|
-
]
|
|
32098
|
+
children: /* @__PURE__ */ jsx(
|
|
32099
|
+
Menu,
|
|
32100
|
+
{
|
|
32101
|
+
position: "bottom-end",
|
|
32102
|
+
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" }) }),
|
|
32103
|
+
items: actionDefs.map((action) => ({
|
|
32104
|
+
label: action.label,
|
|
32105
|
+
icon: action.icon,
|
|
32106
|
+
event: action.event,
|
|
32107
|
+
variant: action.variant === "danger" ? "danger" : "default",
|
|
32108
|
+
onClick: () => eventBus.emit(`UI:${action.event}`, {
|
|
32109
|
+
id: row.id,
|
|
32110
|
+
row
|
|
32111
|
+
})
|
|
32112
|
+
}))
|
|
32113
|
+
}
|
|
32114
|
+
)
|
|
29534
32115
|
}
|
|
29535
32116
|
)
|
|
29536
32117
|
]
|
|
@@ -29573,7 +32154,6 @@ var init_TableView = __esm({
|
|
|
29573
32154
|
init_format();
|
|
29574
32155
|
init_getNestedValue();
|
|
29575
32156
|
init_useEventBus();
|
|
29576
|
-
init_useMediaQuery();
|
|
29577
32157
|
init_Box();
|
|
29578
32158
|
init_Stack();
|
|
29579
32159
|
init_Typography();
|
|
@@ -39066,7 +41646,7 @@ function getEnumOptions(field) {
|
|
|
39066
41646
|
}));
|
|
39067
41647
|
}
|
|
39068
41648
|
const validation = field.validation;
|
|
39069
|
-
if (validation?.enum &&
|
|
41649
|
+
if (validation?.enum && validation.enum.length > 0) {
|
|
39070
41650
|
return validation.enum.map((v) => ({
|
|
39071
41651
|
value: v,
|
|
39072
41652
|
label: v.charAt(0).toUpperCase() + v.slice(1).replace(/_/g, " ")
|
|
@@ -44513,6 +47093,7 @@ var init_component_registry_generated = __esm({
|
|
|
44513
47093
|
init_ActionTile();
|
|
44514
47094
|
init_ActivationBlock();
|
|
44515
47095
|
init_ComponentPatterns();
|
|
47096
|
+
init_AlgoGraphCanvas();
|
|
44516
47097
|
init_AlgorithmCanvas();
|
|
44517
47098
|
init_AnimatedCounter();
|
|
44518
47099
|
init_AnimatedGraphic();
|
|
@@ -44776,6 +47357,7 @@ var init_component_registry_generated = __esm({
|
|
|
44776
47357
|
"ActivationBlock": ActivationBlock,
|
|
44777
47358
|
"Alert": AlertPattern,
|
|
44778
47359
|
"AlertPattern": AlertPattern,
|
|
47360
|
+
"AlgoGraphCanvas": AlgoGraphCanvas,
|
|
44779
47361
|
"AlgorithmCanvas": AlgorithmCanvas,
|
|
44780
47362
|
"AnimatedCounter": AnimatedCounter,
|
|
44781
47363
|
"AnimatedGraphic": AnimatedGraphic,
|
|
@@ -46102,9 +48684,9 @@ var log3 = createLogger("almadar:ui:effects:client-handlers");
|
|
|
46102
48684
|
function createClientEffectHandlers(options) {
|
|
46103
48685
|
const { eventBus, slotSetter, navigate, notify, callService, liveEntity } = options;
|
|
46104
48686
|
return {
|
|
46105
|
-
emit: (event, payload) => {
|
|
48687
|
+
emit: (event, payload, source) => {
|
|
46106
48688
|
const prefixedEvent = event.startsWith("UI:") ? event : `UI:${event}`;
|
|
46107
|
-
eventBus.emit(prefixedEvent, payload);
|
|
48689
|
+
eventBus.emit(prefixedEvent, payload, source);
|
|
46108
48690
|
},
|
|
46109
48691
|
persist: async () => {
|
|
46110
48692
|
log3.warn("persist is server-side only, ignored on client");
|
|
@@ -46439,7 +49021,7 @@ function createSharedEntityWriter(binding, tick, traitStatesRef, emit) {
|
|
|
46439
49021
|
}
|
|
46440
49022
|
};
|
|
46441
49023
|
ctx.emit = (event, payload) => {
|
|
46442
|
-
emit(event, payload);
|
|
49024
|
+
emit(event, payload, { trait: traitName, tick: tick.name });
|
|
46443
49025
|
};
|
|
46444
49026
|
if (tick.guard !== void 0 && !evaluateGuard(tick.guard, ctx)) {
|
|
46445
49027
|
tickLog.debug("guard-blocked", { traitName, tick: tick.name, state: currentState });
|
|
@@ -46891,6 +49473,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
46891
49473
|
}
|
|
46892
49474
|
const effectContext = {
|
|
46893
49475
|
traitName,
|
|
49476
|
+
orbitalName: orbitalsByTrait?.[traitName],
|
|
46894
49477
|
state: previousState,
|
|
46895
49478
|
transition: `${previousState}->${newState}`,
|
|
46896
49479
|
linkedEntity,
|
|
@@ -46967,7 +49550,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
46967
49550
|
});
|
|
46968
49551
|
}
|
|
46969
49552
|
return emittedDuringExec;
|
|
46970
|
-
}, [eventBus, flushSlot, sharedEntityStore, publishBindingSnapshot]);
|
|
49553
|
+
}, [eventBus, flushSlot, sharedEntityStore, publishBindingSnapshot, orbitalsByTrait]);
|
|
46971
49554
|
const runTickEffects = useCallback((tick, binding) => {
|
|
46972
49555
|
const traitName = binding.trait.name;
|
|
46973
49556
|
const currentState = traitStatesRef.current.get(traitName)?.currentState ?? "";
|
|
@@ -47003,9 +49586,9 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
47003
49586
|
log: tickLog
|
|
47004
49587
|
});
|
|
47005
49588
|
}, [executeTransitionEffects, sharedEntityStore]);
|
|
47006
|
-
const emitFromSharedWriter = useCallback((event, payload) => {
|
|
49589
|
+
const emitFromSharedWriter = useCallback((event, payload, source) => {
|
|
47007
49590
|
const prefixedEvent = event.startsWith("UI:") ? event : `UI:${event}`;
|
|
47008
|
-
eventBus.emit(prefixedEvent, payload);
|
|
49591
|
+
eventBus.emit(prefixedEvent, payload, source);
|
|
47009
49592
|
}, [eventBus]);
|
|
47010
49593
|
useEffect(() => {
|
|
47011
49594
|
const scheduler = createTickScheduler();
|