@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.cjs
CHANGED
|
@@ -209,7 +209,7 @@ function useEventBus() {
|
|
|
209
209
|
return {
|
|
210
210
|
...baseBus,
|
|
211
211
|
emit: (type, payload, source) => {
|
|
212
|
-
if (typeof type === "string" && type.startsWith("UI:") && !type.slice(3).includes(".")) {
|
|
212
|
+
if (typeof type === "string" && type.startsWith("UI:") && !type.slice(3).includes(".") && !source?.trait) {
|
|
213
213
|
scopeLog.warn("emit:bare-key-no-scope", { type });
|
|
214
214
|
}
|
|
215
215
|
baseBus.emit(type, payload, source);
|
|
@@ -613,26 +613,6 @@ var init_useDragReorder = __esm({
|
|
|
613
613
|
"use client";
|
|
614
614
|
}
|
|
615
615
|
});
|
|
616
|
-
function useMediaQuery(query) {
|
|
617
|
-
const subscribe = React85.useCallback(
|
|
618
|
-
(onChange) => {
|
|
619
|
-
const mql = window.matchMedia(query);
|
|
620
|
-
mql.addEventListener("change", onChange);
|
|
621
|
-
return () => mql.removeEventListener("change", onChange);
|
|
622
|
-
},
|
|
623
|
-
[query]
|
|
624
|
-
);
|
|
625
|
-
return React85.useSyncExternalStore(
|
|
626
|
-
subscribe,
|
|
627
|
-
() => window.matchMedia(query).matches,
|
|
628
|
-
() => false
|
|
629
|
-
);
|
|
630
|
-
}
|
|
631
|
-
var init_useMediaQuery = __esm({
|
|
632
|
-
"hooks/useMediaQuery.ts"() {
|
|
633
|
-
"use client";
|
|
634
|
-
}
|
|
635
|
-
});
|
|
636
616
|
function useInfiniteScroll(onLoadMore, options = {}) {
|
|
637
617
|
const { rootMargin = "200px", hasMore = true, isLoading = false } = options;
|
|
638
618
|
const observerRef = React85.useRef(null);
|
|
@@ -10729,9 +10709,10 @@ function Canvas({
|
|
|
10729
10709
|
drawables: [...drawables ?? [], ...childDrawables],
|
|
10730
10710
|
isLoading,
|
|
10731
10711
|
cameraMode: to3DCameraMode(camera?.mode),
|
|
10732
|
-
...zoom !== void 0 ? {
|
|
10712
|
+
...zoom !== void 0 ? { zoom } : {},
|
|
10733
10713
|
...camera?.fov !== void 0 ? { fov: camera.fov } : {},
|
|
10734
10714
|
...camera?.azimuth !== void 0 ? { azimuth: camera.azimuth } : {},
|
|
10715
|
+
...camera?.elevation !== void 0 ? { elevation: camera.elevation } : {},
|
|
10735
10716
|
...camera?.target !== void 0 ? { followTarget: camera.target } : {},
|
|
10736
10717
|
unitScale,
|
|
10737
10718
|
backgroundColor,
|
|
@@ -11732,6 +11713,14 @@ function shapeBounds(shape) {
|
|
|
11732
11713
|
w: shape.radius * 2 + 8,
|
|
11733
11714
|
h: shape.radius * 2 + 8
|
|
11734
11715
|
};
|
|
11716
|
+
case "ellipse":
|
|
11717
|
+
if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) return null;
|
|
11718
|
+
return {
|
|
11719
|
+
x: shape.x - shape.width / 2 - 4,
|
|
11720
|
+
y: shape.y - shape.height / 2 - 4,
|
|
11721
|
+
w: shape.width + 8,
|
|
11722
|
+
h: shape.height + 8
|
|
11723
|
+
};
|
|
11735
11724
|
case "rect":
|
|
11736
11725
|
if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) return null;
|
|
11737
11726
|
return { x: shape.x - 4, y: shape.y - 4, w: shape.width + 8, h: shape.height + 8 };
|
|
@@ -11765,13 +11754,14 @@ function drawArrowHead(ctx, x1, y1, x2, y2, size) {
|
|
|
11765
11754
|
ctx.closePath();
|
|
11766
11755
|
ctx.fill();
|
|
11767
11756
|
}
|
|
11768
|
-
function drawShape(ctx, shape, width, height) {
|
|
11757
|
+
function drawShape(ctx, shape, width, height, allShapes) {
|
|
11769
11758
|
ctx.save();
|
|
11770
11759
|
const opacity = shape.opacity ?? 1;
|
|
11771
11760
|
ctx.globalAlpha = opacity;
|
|
11772
11761
|
const stroke = resolveColor2(shape.color, ctx, "#333333");
|
|
11773
11762
|
const fill = shape.fill ? resolveColor2(shape.fill, ctx, "#cccccc") : void 0;
|
|
11774
11763
|
ctx.lineWidth = shape.lineWidth ?? 2;
|
|
11764
|
+
if (shape.dash) ctx.setLineDash([...DASH_PATTERNS[shape.dash]]);
|
|
11775
11765
|
switch (shape.type) {
|
|
11776
11766
|
case "grid": {
|
|
11777
11767
|
const step = shape.step ?? 40;
|
|
@@ -11837,6 +11827,20 @@ function drawShape(ctx, shape, width, height) {
|
|
|
11837
11827
|
ctx.stroke();
|
|
11838
11828
|
break;
|
|
11839
11829
|
}
|
|
11830
|
+
case "ellipse": {
|
|
11831
|
+
if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) break;
|
|
11832
|
+
const startAngle = (shape.startAngle ?? 0) * Math.PI / 180;
|
|
11833
|
+
const endAngle = (shape.endAngle ?? 360) * Math.PI / 180;
|
|
11834
|
+
ctx.beginPath();
|
|
11835
|
+
ctx.ellipse(shape.x, shape.y, shape.width / 2, shape.height / 2, 0, startAngle, endAngle);
|
|
11836
|
+
if (fill) {
|
|
11837
|
+
ctx.fillStyle = fill;
|
|
11838
|
+
ctx.fill();
|
|
11839
|
+
}
|
|
11840
|
+
ctx.strokeStyle = stroke;
|
|
11841
|
+
ctx.stroke();
|
|
11842
|
+
break;
|
|
11843
|
+
}
|
|
11840
11844
|
case "rect": {
|
|
11841
11845
|
if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) break;
|
|
11842
11846
|
if (fill) {
|
|
@@ -11883,21 +11887,153 @@ function drawShape(ctx, shape, width, height) {
|
|
|
11883
11887
|
ctx.fillText(shape.text, shape.x, shape.y);
|
|
11884
11888
|
break;
|
|
11885
11889
|
}
|
|
11890
|
+
case "venn-region": {
|
|
11891
|
+
const resolveCircles = (ids) => (ids ?? []).flatMap((id) => {
|
|
11892
|
+
const c = allShapes.find((s) => s.type === "circle" && s.id === id);
|
|
11893
|
+
return c && c.x != null && c.y != null && c.radius != null ? [{ x: c.x, y: c.y, radius: c.radius }] : [];
|
|
11894
|
+
});
|
|
11895
|
+
const inside = resolveCircles(shape.inside);
|
|
11896
|
+
if (inside.length === 0) break;
|
|
11897
|
+
const outside = resolveCircles(shape.outside);
|
|
11898
|
+
const off = document.createElement("canvas");
|
|
11899
|
+
off.width = ctx.canvas.width;
|
|
11900
|
+
off.height = ctx.canvas.height;
|
|
11901
|
+
const octx = off.getContext("2d");
|
|
11902
|
+
if (!octx) break;
|
|
11903
|
+
octx.setTransform(ctx.getTransform());
|
|
11904
|
+
for (const c of inside) {
|
|
11905
|
+
const p = new Path2D();
|
|
11906
|
+
p.arc(c.x, c.y, c.radius, 0, Math.PI * 2);
|
|
11907
|
+
octx.clip(p);
|
|
11908
|
+
}
|
|
11909
|
+
octx.fillStyle = fill ?? stroke;
|
|
11910
|
+
octx.fillRect(0, 0, width, height);
|
|
11911
|
+
octx.globalCompositeOperation = "destination-out";
|
|
11912
|
+
for (const c of outside) {
|
|
11913
|
+
const p = new Path2D();
|
|
11914
|
+
p.arc(c.x, c.y, c.radius, 0, Math.PI * 2);
|
|
11915
|
+
octx.fill(p);
|
|
11916
|
+
}
|
|
11917
|
+
ctx.save();
|
|
11918
|
+
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
11919
|
+
ctx.drawImage(off, 0, 0);
|
|
11920
|
+
ctx.restore();
|
|
11921
|
+
break;
|
|
11922
|
+
}
|
|
11886
11923
|
}
|
|
11887
11924
|
ctx.restore();
|
|
11888
11925
|
}
|
|
11889
|
-
|
|
11926
|
+
function readoutShapes(readouts, width) {
|
|
11927
|
+
const out = [];
|
|
11928
|
+
const chipH = 18;
|
|
11929
|
+
const gap = 6;
|
|
11930
|
+
let rightEdge = width - 6;
|
|
11931
|
+
let rowY = 6;
|
|
11932
|
+
for (const readout of readouts) {
|
|
11933
|
+
const text = `${readout.label}: ${String(readout.value)}`;
|
|
11934
|
+
const chipW = Math.min(170, Math.max(34, text.length * 6 + 12));
|
|
11935
|
+
let chipX = rightEdge - chipW;
|
|
11936
|
+
if (chipX < 4) {
|
|
11937
|
+
rowY += chipH + 4;
|
|
11938
|
+
rightEdge = width - 6;
|
|
11939
|
+
chipX = rightEdge - chipW;
|
|
11940
|
+
}
|
|
11941
|
+
const color = readout.color ?? "#334155";
|
|
11942
|
+
out.push({ type: "rect", x: chipX, y: rowY, width: chipW, height: chipH, color, fill: color });
|
|
11943
|
+
out.push({
|
|
11944
|
+
type: "text",
|
|
11945
|
+
x: chipX + chipW / 2,
|
|
11946
|
+
y: rowY + chipH / 2,
|
|
11947
|
+
text,
|
|
11948
|
+
color: "#ffffff",
|
|
11949
|
+
fontSize: 10,
|
|
11950
|
+
align: "center"
|
|
11951
|
+
});
|
|
11952
|
+
rightEdge = chipX - gap;
|
|
11953
|
+
}
|
|
11954
|
+
return out;
|
|
11955
|
+
}
|
|
11956
|
+
function traceShapes(panel, k, width, height) {
|
|
11957
|
+
const w = panel.width ?? Math.round(width * 0.32);
|
|
11958
|
+
const h = panel.height ?? Math.round(height * 0.28);
|
|
11959
|
+
const x = panel.x ?? width - w - 8;
|
|
11960
|
+
const y = panel.y ?? height - h - 8 - k * (h + 8);
|
|
11961
|
+
const allSamples = panel.series.flatMap((series) => series.samples);
|
|
11962
|
+
let xLo = Math.min(...allSamples.map((p) => p.x));
|
|
11963
|
+
let xHi = Math.max(...allSamples.map((p) => p.x));
|
|
11964
|
+
let yLo = Math.min(...allSamples.map((p) => p.y));
|
|
11965
|
+
let yHi = Math.max(...allSamples.map((p) => p.y));
|
|
11966
|
+
if (xLo === xHi) {
|
|
11967
|
+
xLo -= 1;
|
|
11968
|
+
xHi += 1;
|
|
11969
|
+
}
|
|
11970
|
+
if (yLo === yHi) {
|
|
11971
|
+
yLo -= 1;
|
|
11972
|
+
yHi += 1;
|
|
11973
|
+
}
|
|
11974
|
+
const backgroundColor = panel.backgroundColor ?? "#ffffff";
|
|
11975
|
+
const frameColor = panel.frameColor ?? "#94a3b8";
|
|
11976
|
+
const out = [];
|
|
11977
|
+
out.push({
|
|
11978
|
+
type: "rect",
|
|
11979
|
+
x,
|
|
11980
|
+
y,
|
|
11981
|
+
width: w,
|
|
11982
|
+
height: h,
|
|
11983
|
+
color: backgroundColor,
|
|
11984
|
+
fill: backgroundColor,
|
|
11985
|
+
opacity: panel.backgroundOpacity ?? 0.85
|
|
11986
|
+
});
|
|
11987
|
+
out.push({ type: "rect", x, y, width: w, height: h, color: frameColor, lineWidth: 1 });
|
|
11988
|
+
panel.series.forEach((series, j) => {
|
|
11989
|
+
const color = series.color ?? TRACE_SERIES_COLORS[j % TRACE_SERIES_COLORS.length];
|
|
11990
|
+
const mapped = series.samples.map((p) => ({
|
|
11991
|
+
x: x + 4 + (p.x - xLo) / (xHi - xLo) * (w - 8),
|
|
11992
|
+
y: y + h - 4 - (p.y - yLo) / (yHi - yLo) * (h - 8)
|
|
11993
|
+
}));
|
|
11994
|
+
for (let i = 1; i < mapped.length; i++) {
|
|
11995
|
+
out.push({
|
|
11996
|
+
type: "line",
|
|
11997
|
+
x1: mapped[i - 1].x,
|
|
11998
|
+
y1: mapped[i - 1].y,
|
|
11999
|
+
x2: mapped[i].x,
|
|
12000
|
+
y2: mapped[i].y,
|
|
12001
|
+
color,
|
|
12002
|
+
lineWidth: 1.5
|
|
12003
|
+
});
|
|
12004
|
+
}
|
|
12005
|
+
if (mapped.length > 0) {
|
|
12006
|
+
const last = mapped[mapped.length - 1];
|
|
12007
|
+
out.push({ type: "circle", x: last.x, y: last.y, radius: 2, color, fill: color });
|
|
12008
|
+
}
|
|
12009
|
+
if (series.label) {
|
|
12010
|
+
out.push({ type: "text", x: x + 6, y: y + 10 + 11 * j, text: series.label, color, fontSize: 9 });
|
|
12011
|
+
}
|
|
12012
|
+
});
|
|
12013
|
+
if (panel.yLabel) {
|
|
12014
|
+
out.push({ type: "text", x: x + w - 6, y: y + 10, text: panel.yLabel, color: "#6b7280", fontSize: 9, align: "right" });
|
|
12015
|
+
}
|
|
12016
|
+
if (panel.xLabel) {
|
|
12017
|
+
out.push({ type: "text", x: x + w - 6, y: y + h - 6, text: panel.xLabel, color: "#6b7280", fontSize: 9, align: "right" });
|
|
12018
|
+
}
|
|
12019
|
+
return out;
|
|
12020
|
+
}
|
|
12021
|
+
var DASH_PATTERNS, TRACE_SERIES_COLORS, LearningCanvas;
|
|
11890
12022
|
var init_LearningCanvas = __esm({
|
|
11891
12023
|
"components/learning/atoms/LearningCanvas.tsx"() {
|
|
11892
12024
|
"use client";
|
|
11893
12025
|
init_cn();
|
|
11894
12026
|
init_useEventBus();
|
|
12027
|
+
DASH_PATTERNS = { dashed: [6, 4], dotted: [2, 3] };
|
|
12028
|
+
TRACE_SERIES_COLORS = ["#2563eb", "#dc2626", "#16a34a", "#f59e0b"];
|
|
11895
12029
|
LearningCanvas = ({
|
|
11896
12030
|
className,
|
|
11897
12031
|
width = 600,
|
|
11898
12032
|
height = 400,
|
|
11899
12033
|
backgroundColor,
|
|
11900
12034
|
shapes = [],
|
|
12035
|
+
readouts,
|
|
12036
|
+
traces,
|
|
11901
12037
|
interactive = false,
|
|
11902
12038
|
animate = false,
|
|
11903
12039
|
onShapeClick,
|
|
@@ -11923,6 +12059,12 @@ var init_LearningCanvas = __esm({
|
|
|
11923
12059
|
}
|
|
11924
12060
|
return -1;
|
|
11925
12061
|
}, [shapes]);
|
|
12062
|
+
const derivedShapes = React85.useMemo(() => {
|
|
12063
|
+
if (!traces?.length && !readouts?.length) return shapes;
|
|
12064
|
+
const traceOut = (traces ?? []).flatMap((panel, k) => traceShapes(panel, k, width, height));
|
|
12065
|
+
const readoutOut = readouts?.length ? readoutShapes(readouts, width) : [];
|
|
12066
|
+
return [...shapes, ...traceOut, ...readoutOut];
|
|
12067
|
+
}, [shapes, traces, readouts, width, height]);
|
|
11926
12068
|
const draw = React85.useCallback(() => {
|
|
11927
12069
|
const canvas = canvasRef.current;
|
|
11928
12070
|
if (!canvas) return;
|
|
@@ -11939,13 +12081,13 @@ var init_LearningCanvas = __esm({
|
|
|
11939
12081
|
ctx.fillStyle = backgroundColor;
|
|
11940
12082
|
ctx.fillRect(0, 0, width, height);
|
|
11941
12083
|
}
|
|
11942
|
-
for (const shape of
|
|
11943
|
-
if (shape.type !== "text") drawShape(ctx, shape, width, height);
|
|
12084
|
+
for (const shape of derivedShapes) {
|
|
12085
|
+
if (shape.type !== "text") drawShape(ctx, shape, width, height, derivedShapes);
|
|
11944
12086
|
}
|
|
11945
|
-
for (const shape of
|
|
11946
|
-
if (shape.type === "text") drawShape(ctx, shape, width, height);
|
|
12087
|
+
for (const shape of derivedShapes) {
|
|
12088
|
+
if (shape.type === "text") drawShape(ctx, shape, width, height, derivedShapes);
|
|
11947
12089
|
}
|
|
11948
|
-
}, [width, height, backgroundColor,
|
|
12090
|
+
}, [width, height, backgroundColor, derivedShapes]);
|
|
11949
12091
|
React85.useEffect(() => {
|
|
11950
12092
|
draw();
|
|
11951
12093
|
}, [draw]);
|
|
@@ -13495,7 +13637,363 @@ var init_ComponentPatterns = __esm({
|
|
|
13495
13637
|
AlertPattern.displayName = "AlertPattern";
|
|
13496
13638
|
}
|
|
13497
13639
|
});
|
|
13498
|
-
|
|
13640
|
+
function layoutCircle(nodes, width, height) {
|
|
13641
|
+
const cx = width / 2;
|
|
13642
|
+
const cy = height / 2;
|
|
13643
|
+
const radius = Math.max(10, Math.min(cx, cy) - 40);
|
|
13644
|
+
const positions = /* @__PURE__ */ new Map();
|
|
13645
|
+
const n = nodes.length;
|
|
13646
|
+
nodes.forEach((node, i) => {
|
|
13647
|
+
const angle = 2 * Math.PI * i / Math.max(n, 1) - Math.PI / 2;
|
|
13648
|
+
positions.set(node.id, { x: cx + radius * Math.cos(angle), y: cy + radius * Math.sin(angle) });
|
|
13649
|
+
});
|
|
13650
|
+
return positions;
|
|
13651
|
+
}
|
|
13652
|
+
function layoutTree(nodes, edges, root, width, height) {
|
|
13653
|
+
const nodeIds = nodes.map((n) => n.id);
|
|
13654
|
+
const idSet = new Set(nodeIds);
|
|
13655
|
+
const childrenOf = /* @__PURE__ */ new Map();
|
|
13656
|
+
const hasIncoming = /* @__PURE__ */ new Set();
|
|
13657
|
+
for (const e of edges) {
|
|
13658
|
+
if (!idSet.has(e.from) || !idSet.has(e.to)) continue;
|
|
13659
|
+
const list = childrenOf.get(e.from) ?? [];
|
|
13660
|
+
list.push(e.to);
|
|
13661
|
+
childrenOf.set(e.from, list);
|
|
13662
|
+
hasIncoming.add(e.to);
|
|
13663
|
+
}
|
|
13664
|
+
const depth = /* @__PURE__ */ new Map();
|
|
13665
|
+
const treeChildren = /* @__PURE__ */ new Map();
|
|
13666
|
+
const visited = /* @__PURE__ */ new Set();
|
|
13667
|
+
const bfsFrom = (start) => {
|
|
13668
|
+
if (visited.has(start)) return;
|
|
13669
|
+
visited.add(start);
|
|
13670
|
+
depth.set(start, 0);
|
|
13671
|
+
const queue = [start];
|
|
13672
|
+
while (queue.length > 0) {
|
|
13673
|
+
const u = queue.shift();
|
|
13674
|
+
for (const v of childrenOf.get(u) ?? []) {
|
|
13675
|
+
if (visited.has(v)) continue;
|
|
13676
|
+
visited.add(v);
|
|
13677
|
+
depth.set(v, (depth.get(u) ?? 0) + 1);
|
|
13678
|
+
const list = treeChildren.get(u) ?? [];
|
|
13679
|
+
list.push(v);
|
|
13680
|
+
treeChildren.set(u, list);
|
|
13681
|
+
queue.push(v);
|
|
13682
|
+
}
|
|
13683
|
+
}
|
|
13684
|
+
};
|
|
13685
|
+
const primaryRoot = root && idSet.has(root) ? root : nodeIds.find((id) => !hasIncoming.has(id)) ?? nodeIds[0];
|
|
13686
|
+
const rootsOrder = [];
|
|
13687
|
+
if (primaryRoot !== void 0) {
|
|
13688
|
+
bfsFrom(primaryRoot);
|
|
13689
|
+
rootsOrder.push(primaryRoot);
|
|
13690
|
+
}
|
|
13691
|
+
for (const id of nodeIds) {
|
|
13692
|
+
if (!visited.has(id)) {
|
|
13693
|
+
bfsFrom(id);
|
|
13694
|
+
rootsOrder.push(id);
|
|
13695
|
+
}
|
|
13696
|
+
}
|
|
13697
|
+
let leafCounter = 0;
|
|
13698
|
+
const xSlot = /* @__PURE__ */ new Map();
|
|
13699
|
+
const assignXSlot = (u) => {
|
|
13700
|
+
const children = treeChildren.get(u) ?? [];
|
|
13701
|
+
if (children.length === 0) {
|
|
13702
|
+
const slot = leafCounter++;
|
|
13703
|
+
xSlot.set(u, slot);
|
|
13704
|
+
return slot;
|
|
13705
|
+
}
|
|
13706
|
+
const childSlots = children.map(assignXSlot);
|
|
13707
|
+
const avg = childSlots.reduce((a, b) => a + b, 0) / childSlots.length;
|
|
13708
|
+
xSlot.set(u, avg);
|
|
13709
|
+
return avg;
|
|
13710
|
+
};
|
|
13711
|
+
for (const r of rootsOrder) assignXSlot(r);
|
|
13712
|
+
let maxDepth = 0;
|
|
13713
|
+
for (const d of depth.values()) maxDepth = Math.max(maxDepth, d);
|
|
13714
|
+
const colWidth = width / Math.max(1, leafCounter);
|
|
13715
|
+
const rowHeight = height / (maxDepth + 1);
|
|
13716
|
+
const positions = /* @__PURE__ */ new Map();
|
|
13717
|
+
for (const id of nodeIds) {
|
|
13718
|
+
const slot = xSlot.get(id) ?? 0;
|
|
13719
|
+
const d = depth.get(id) ?? 0;
|
|
13720
|
+
positions.set(id, { x: slot * colWidth + colWidth / 2, y: d * rowHeight + rowHeight / 2 });
|
|
13721
|
+
}
|
|
13722
|
+
return positions;
|
|
13723
|
+
}
|
|
13724
|
+
function layoutLayered(nodes, edges, width, height) {
|
|
13725
|
+
const nodeIds = nodes.map((n) => n.id);
|
|
13726
|
+
const idSet = new Set(nodeIds);
|
|
13727
|
+
const adj = /* @__PURE__ */ new Map();
|
|
13728
|
+
const remainingIndegree = /* @__PURE__ */ new Map();
|
|
13729
|
+
for (const id of nodeIds) remainingIndegree.set(id, 0);
|
|
13730
|
+
for (const e of edges) {
|
|
13731
|
+
if (!idSet.has(e.from) || !idSet.has(e.to)) continue;
|
|
13732
|
+
const list = adj.get(e.from) ?? [];
|
|
13733
|
+
list.push(e.to);
|
|
13734
|
+
adj.set(e.from, list);
|
|
13735
|
+
remainingIndegree.set(e.to, (remainingIndegree.get(e.to) ?? 0) + 1);
|
|
13736
|
+
}
|
|
13737
|
+
const layer = /* @__PURE__ */ new Map();
|
|
13738
|
+
const dequeued = /* @__PURE__ */ new Set();
|
|
13739
|
+
const queue = [];
|
|
13740
|
+
for (const id of nodeIds) {
|
|
13741
|
+
if ((remainingIndegree.get(id) ?? 0) === 0) {
|
|
13742
|
+
layer.set(id, 0);
|
|
13743
|
+
queue.push(id);
|
|
13744
|
+
}
|
|
13745
|
+
}
|
|
13746
|
+
while (queue.length > 0) {
|
|
13747
|
+
const u = queue.shift();
|
|
13748
|
+
dequeued.add(u);
|
|
13749
|
+
for (const v of adj.get(u) ?? []) {
|
|
13750
|
+
const candidate = (layer.get(u) ?? 0) + 1;
|
|
13751
|
+
layer.set(v, Math.max(layer.get(v) ?? 0, candidate));
|
|
13752
|
+
remainingIndegree.set(v, (remainingIndegree.get(v) ?? 0) - 1);
|
|
13753
|
+
if ((remainingIndegree.get(v) ?? 0) === 0 && !dequeued.has(v)) {
|
|
13754
|
+
queue.push(v);
|
|
13755
|
+
}
|
|
13756
|
+
}
|
|
13757
|
+
}
|
|
13758
|
+
let baseMaxLayer = 0;
|
|
13759
|
+
for (const id of nodeIds) {
|
|
13760
|
+
if (dequeued.has(id)) baseMaxLayer = Math.max(baseMaxLayer, layer.get(id) ?? 0);
|
|
13761
|
+
}
|
|
13762
|
+
const cycleLayer = baseMaxLayer + 1;
|
|
13763
|
+
let maxLayer = baseMaxLayer;
|
|
13764
|
+
for (const id of nodeIds) {
|
|
13765
|
+
if (!dequeued.has(id)) {
|
|
13766
|
+
layer.set(id, cycleLayer);
|
|
13767
|
+
maxLayer = cycleLayer;
|
|
13768
|
+
}
|
|
13769
|
+
}
|
|
13770
|
+
const colWidth = width / Math.max(1, maxLayer + 1);
|
|
13771
|
+
const byLayer = /* @__PURE__ */ new Map();
|
|
13772
|
+
for (const id of nodeIds) {
|
|
13773
|
+
const l = layer.get(id) ?? 0;
|
|
13774
|
+
const list = byLayer.get(l) ?? [];
|
|
13775
|
+
list.push(id);
|
|
13776
|
+
byLayer.set(l, list);
|
|
13777
|
+
}
|
|
13778
|
+
const positions = /* @__PURE__ */ new Map();
|
|
13779
|
+
for (const [l, ids] of byLayer) {
|
|
13780
|
+
const rowHeight = height / ids.length;
|
|
13781
|
+
ids.forEach((id, i) => {
|
|
13782
|
+
positions.set(id, { x: l * colWidth + colWidth / 2, y: i * rowHeight + rowHeight / 2 });
|
|
13783
|
+
});
|
|
13784
|
+
}
|
|
13785
|
+
return positions;
|
|
13786
|
+
}
|
|
13787
|
+
function computePositions(nodes, edges, layout, root, width, height) {
|
|
13788
|
+
switch (layout) {
|
|
13789
|
+
case "circle":
|
|
13790
|
+
return layoutCircle(nodes, width, height);
|
|
13791
|
+
case "tree":
|
|
13792
|
+
return layoutTree(nodes, edges, root, width, height);
|
|
13793
|
+
case "layered":
|
|
13794
|
+
return layoutLayered(nodes, edges, width, height);
|
|
13795
|
+
case "manual":
|
|
13796
|
+
default: {
|
|
13797
|
+
const positions = /* @__PURE__ */ new Map();
|
|
13798
|
+
for (const n of nodes) positions.set(n.id, { x: n.x ?? 0, y: n.y ?? 0 });
|
|
13799
|
+
return positions;
|
|
13800
|
+
}
|
|
13801
|
+
}
|
|
13802
|
+
}
|
|
13803
|
+
var NODE_STATE_COLOR, EDGE_STATE_COLOR, DEFAULT_NODE_RADIUS, AlgoGraphCanvas;
|
|
13804
|
+
var init_AlgoGraphCanvas = __esm({
|
|
13805
|
+
"components/learning/molecules/AlgoGraphCanvas.tsx"() {
|
|
13806
|
+
"use client";
|
|
13807
|
+
init_atoms();
|
|
13808
|
+
init_Stack();
|
|
13809
|
+
init_LearningCanvas();
|
|
13810
|
+
NODE_STATE_COLOR = {
|
|
13811
|
+
unvisited: "#cbd5e1",
|
|
13812
|
+
frontier: "#f59e0b",
|
|
13813
|
+
current: "#ef4444",
|
|
13814
|
+
visited: "#22c55e",
|
|
13815
|
+
goal: "#8b5cf6",
|
|
13816
|
+
path: "#0ea5e9"
|
|
13817
|
+
};
|
|
13818
|
+
EDGE_STATE_COLOR = {
|
|
13819
|
+
default: "#9ca3af",
|
|
13820
|
+
tree: "#16a34a",
|
|
13821
|
+
relaxed: "#f59e0b",
|
|
13822
|
+
candidate: "#38bdf8",
|
|
13823
|
+
path: "#dc2626"
|
|
13824
|
+
};
|
|
13825
|
+
DEFAULT_NODE_RADIUS = 18;
|
|
13826
|
+
AlgoGraphCanvas = ({
|
|
13827
|
+
className,
|
|
13828
|
+
width = 600,
|
|
13829
|
+
height = 400,
|
|
13830
|
+
title,
|
|
13831
|
+
backgroundColor,
|
|
13832
|
+
nodes = [],
|
|
13833
|
+
edges = [],
|
|
13834
|
+
layout = "manual",
|
|
13835
|
+
root,
|
|
13836
|
+
shapes = [],
|
|
13837
|
+
interactive = false,
|
|
13838
|
+
animate = false,
|
|
13839
|
+
onShapeClick,
|
|
13840
|
+
onNodeClick,
|
|
13841
|
+
isLoading,
|
|
13842
|
+
error
|
|
13843
|
+
}) => {
|
|
13844
|
+
const nodeById = React85.useMemo(() => {
|
|
13845
|
+
const m = /* @__PURE__ */ new Map();
|
|
13846
|
+
for (const n of nodes) m.set(n.id, n);
|
|
13847
|
+
return m;
|
|
13848
|
+
}, [nodes]);
|
|
13849
|
+
const nodeIndexById = React85.useMemo(() => {
|
|
13850
|
+
const m = /* @__PURE__ */ new Map();
|
|
13851
|
+
nodes.forEach((n, i) => m.set(n.id, i));
|
|
13852
|
+
return m;
|
|
13853
|
+
}, [nodes]);
|
|
13854
|
+
const derivedShapes = React85.useMemo(() => {
|
|
13855
|
+
const out = [];
|
|
13856
|
+
const positions = computePositions(nodes, edges, layout, root, width, height);
|
|
13857
|
+
const edgeGeoms = [];
|
|
13858
|
+
for (const e of edges) {
|
|
13859
|
+
const a = nodeById.get(e.from);
|
|
13860
|
+
const b = nodeById.get(e.to);
|
|
13861
|
+
const posA = positions.get(e.from);
|
|
13862
|
+
const posB = positions.get(e.to);
|
|
13863
|
+
if (!a || !b || !posA || !posB) continue;
|
|
13864
|
+
const rA = a.radius ?? DEFAULT_NODE_RADIUS;
|
|
13865
|
+
const rB = b.radius ?? DEFAULT_NODE_RADIUS;
|
|
13866
|
+
const dx = posB.x - posA.x;
|
|
13867
|
+
const dy = posB.y - posA.y;
|
|
13868
|
+
const dist = Math.max(1e-6, Math.hypot(dx, dy));
|
|
13869
|
+
const ux = dx / dist;
|
|
13870
|
+
const uy = dy / dist;
|
|
13871
|
+
edgeGeoms.push({
|
|
13872
|
+
directed: e.directed ?? false,
|
|
13873
|
+
x1: posA.x + ux * rA,
|
|
13874
|
+
y1: posA.y + uy * rA,
|
|
13875
|
+
x2: posB.x - ux * rB,
|
|
13876
|
+
y2: posB.y - uy * rB,
|
|
13877
|
+
color: e.color ?? EDGE_STATE_COLOR[e.state ?? "default"],
|
|
13878
|
+
label: e.label ?? (e.weight != null ? String(e.weight) : void 0)
|
|
13879
|
+
});
|
|
13880
|
+
}
|
|
13881
|
+
for (const g of edgeGeoms) {
|
|
13882
|
+
out.push({
|
|
13883
|
+
type: g.directed ? "arrow" : "line",
|
|
13884
|
+
x1: g.x1,
|
|
13885
|
+
y1: g.y1,
|
|
13886
|
+
x2: g.x2,
|
|
13887
|
+
y2: g.y2,
|
|
13888
|
+
color: g.color,
|
|
13889
|
+
lineWidth: 2
|
|
13890
|
+
});
|
|
13891
|
+
}
|
|
13892
|
+
for (const g of edgeGeoms) {
|
|
13893
|
+
if (g.label === void 0) continue;
|
|
13894
|
+
const midX = (g.x1 + g.x2) / 2;
|
|
13895
|
+
const midY = (g.y1 + g.y2) / 2;
|
|
13896
|
+
const dx = g.x2 - g.x1;
|
|
13897
|
+
const dy = g.y2 - g.y1;
|
|
13898
|
+
const dist = Math.max(1e-6, Math.hypot(dx, dy));
|
|
13899
|
+
const perpX = -(dy / dist);
|
|
13900
|
+
const perpY = dx / dist;
|
|
13901
|
+
out.push({
|
|
13902
|
+
type: "text",
|
|
13903
|
+
x: midX + perpX * 10,
|
|
13904
|
+
y: midY + perpY * 10,
|
|
13905
|
+
text: g.label,
|
|
13906
|
+
fontSize: 11,
|
|
13907
|
+
align: "center",
|
|
13908
|
+
color: "#374151"
|
|
13909
|
+
});
|
|
13910
|
+
}
|
|
13911
|
+
const nodeGeoms = [];
|
|
13912
|
+
for (const n of nodes) {
|
|
13913
|
+
const pos = positions.get(n.id);
|
|
13914
|
+
if (!pos) continue;
|
|
13915
|
+
nodeGeoms.push({
|
|
13916
|
+
id: n.id,
|
|
13917
|
+
x: pos.x,
|
|
13918
|
+
y: pos.y,
|
|
13919
|
+
radius: n.radius ?? DEFAULT_NODE_RADIUS,
|
|
13920
|
+
color: n.color ?? NODE_STATE_COLOR[n.state ?? "unvisited"],
|
|
13921
|
+
label: n.label,
|
|
13922
|
+
badge: n.badge
|
|
13923
|
+
});
|
|
13924
|
+
}
|
|
13925
|
+
for (const g of nodeGeoms) {
|
|
13926
|
+
out.push({ type: "circle", id: g.id, x: g.x, y: g.y, radius: g.radius, color: g.color, fill: `${g.color}33` });
|
|
13927
|
+
}
|
|
13928
|
+
const badgeGeoms = [];
|
|
13929
|
+
for (const g of nodeGeoms) {
|
|
13930
|
+
if (!g.badge) continue;
|
|
13931
|
+
const w = Math.min(42, Math.max(18, g.badge.text.length * 6 + 10));
|
|
13932
|
+
badgeGeoms.push({
|
|
13933
|
+
cx: g.x + g.radius * 0.75,
|
|
13934
|
+
cy: g.y - g.radius * 0.75,
|
|
13935
|
+
w,
|
|
13936
|
+
h: 14,
|
|
13937
|
+
// Borderless pill: same color drives both stroke and fill.
|
|
13938
|
+
color: g.badge.color ?? "#1e293b",
|
|
13939
|
+
text: g.badge.text
|
|
13940
|
+
});
|
|
13941
|
+
}
|
|
13942
|
+
for (const b of badgeGeoms) {
|
|
13943
|
+
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 });
|
|
13944
|
+
}
|
|
13945
|
+
for (const b of badgeGeoms) {
|
|
13946
|
+
out.push({ type: "text", x: b.cx, y: b.cy, text: b.text, fontSize: 9, align: "center", color: "#ffffff" });
|
|
13947
|
+
}
|
|
13948
|
+
for (const g of nodeGeoms) {
|
|
13949
|
+
if (g.label === void 0) continue;
|
|
13950
|
+
out.push({
|
|
13951
|
+
type: "text",
|
|
13952
|
+
x: g.x,
|
|
13953
|
+
y: g.y + g.radius + 14,
|
|
13954
|
+
text: g.label,
|
|
13955
|
+
fontSize: 12,
|
|
13956
|
+
align: "center",
|
|
13957
|
+
color: "#111827"
|
|
13958
|
+
});
|
|
13959
|
+
}
|
|
13960
|
+
out.push(...shapes);
|
|
13961
|
+
return out;
|
|
13962
|
+
}, [nodes, edges, layout, root, width, height, nodeById, shapes]);
|
|
13963
|
+
const handleShapeClick = React85.useCallback(
|
|
13964
|
+
(payload) => {
|
|
13965
|
+
if (payload.type === "circle" && payload.id) {
|
|
13966
|
+
const node = nodeById.get(payload.id);
|
|
13967
|
+
const idx = nodeIndexById.get(payload.id);
|
|
13968
|
+
if (node && idx !== void 0) {
|
|
13969
|
+
onNodeClick?.({ id: node.id, label: node.label, index: idx });
|
|
13970
|
+
}
|
|
13971
|
+
}
|
|
13972
|
+
onShapeClick?.(payload);
|
|
13973
|
+
},
|
|
13974
|
+
[nodeById, nodeIndexById, onNodeClick, onShapeClick]
|
|
13975
|
+
);
|
|
13976
|
+
return /* @__PURE__ */ jsxRuntime.jsx(Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
|
|
13977
|
+
title ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", children: title }) : null,
|
|
13978
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
13979
|
+
LearningCanvas,
|
|
13980
|
+
{
|
|
13981
|
+
width,
|
|
13982
|
+
height,
|
|
13983
|
+
backgroundColor,
|
|
13984
|
+
shapes: derivedShapes,
|
|
13985
|
+
interactive,
|
|
13986
|
+
animate,
|
|
13987
|
+
onShapeClick: onShapeClick || onNodeClick ? handleShapeClick : void 0,
|
|
13988
|
+
isLoading,
|
|
13989
|
+
error
|
|
13990
|
+
}
|
|
13991
|
+
)
|
|
13992
|
+
] }) });
|
|
13993
|
+
};
|
|
13994
|
+
}
|
|
13995
|
+
});
|
|
13996
|
+
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;
|
|
13499
13997
|
var init_AlgorithmCanvas = __esm({
|
|
13500
13998
|
"components/learning/molecules/AlgorithmCanvas.tsx"() {
|
|
13501
13999
|
"use client";
|
|
@@ -13507,6 +14005,43 @@ var init_AlgorithmCanvas = __esm({
|
|
|
13507
14005
|
DEFAULT_POINTER_COLOR = "#dc2626";
|
|
13508
14006
|
POINTER_BAND = 34;
|
|
13509
14007
|
TOP_PAD = 26;
|
|
14008
|
+
PANEL_FAMILY_ORDER = ["bars", "slots", "cells", "buckets", "frames"];
|
|
14009
|
+
RANGE_COLOR_DEFAULT = "#3b82f6";
|
|
14010
|
+
RANGE_FILL_OPACITY = 0.15;
|
|
14011
|
+
BRACKET_TOP_OFFSET = 16;
|
|
14012
|
+
BRACKET_ROW_H = 14;
|
|
14013
|
+
BRACKET_TICK_H = 6;
|
|
14014
|
+
BRACKET_LABEL_OFFSET = 6;
|
|
14015
|
+
SLOT_EMPTY_FILL = "#f1f5f9";
|
|
14016
|
+
SLOT_EMPTY_STROKE = "#cbd5e1";
|
|
14017
|
+
SLOT_FILLED_STROKE = "#9ca3af";
|
|
14018
|
+
SLOT_HIGHLIGHT_DEFAULT = "#f59e0b";
|
|
14019
|
+
SLOT_VALUE_TEXT_COLOR = "#ffffff";
|
|
14020
|
+
FRAME_ACTIVE_COLOR = "#3b82f6";
|
|
14021
|
+
FRAME_RETURNING_COLOR = "#f59e0b";
|
|
14022
|
+
FRAME_DONE_COLOR = "#94a3b8";
|
|
14023
|
+
FRAME_LABEL_COLOR = "#ffffff";
|
|
14024
|
+
FRAME_DETAIL_COLOR = "#e2e8f0";
|
|
14025
|
+
FRAME_TWO_LINE_MIN_H = 22;
|
|
14026
|
+
BUCKET_INDEX_FILL = "#e2e8f0";
|
|
14027
|
+
BUCKET_INDEX_STROKE = "#9ca3af";
|
|
14028
|
+
BUCKET_INDEX_TEXT = "#374151";
|
|
14029
|
+
BUCKET_ENTRY_TEXT = "#ffffff";
|
|
14030
|
+
BUCKET_ENTRY_DEFAULT = "#3b82f6";
|
|
14031
|
+
BUCKET_ENTRY_HIGHLIGHT = "#f59e0b";
|
|
14032
|
+
BUCKET_ENTRY_PROBING = "#38bdf8";
|
|
14033
|
+
BUCKET_ENTRY_MIN_W = 24;
|
|
14034
|
+
BUCKET_ENTRY_MAX_W = 64;
|
|
14035
|
+
AXIS_LABEL_COLOR = "#6b7280";
|
|
14036
|
+
AXIS_LABEL_FONT_SIZE = 10;
|
|
14037
|
+
CORNER_TEXT_COLOR = "#111827";
|
|
14038
|
+
CORNER_FONT_SIZE = 7;
|
|
14039
|
+
CORNER_MIN_CELL = 28;
|
|
14040
|
+
CORNER_INSET_X = 3;
|
|
14041
|
+
CORNER_INSET_Y = 6;
|
|
14042
|
+
AUX_PRIMARY_RATIO = 0.6;
|
|
14043
|
+
AUX_LABEL_BAND = 18;
|
|
14044
|
+
AUX_BASELINE_PAD = 8;
|
|
13510
14045
|
AlgorithmCanvas = ({
|
|
13511
14046
|
className,
|
|
13512
14047
|
width = 600,
|
|
@@ -13516,6 +14051,14 @@ var init_AlgorithmCanvas = __esm({
|
|
|
13516
14051
|
bars = [],
|
|
13517
14052
|
cells = [],
|
|
13518
14053
|
pointers = [],
|
|
14054
|
+
ranges = [],
|
|
14055
|
+
slots = [],
|
|
14056
|
+
slotOrientation = "horizontal",
|
|
14057
|
+
frames = [],
|
|
14058
|
+
buckets = [],
|
|
14059
|
+
auxBars = [],
|
|
14060
|
+
rowLabels = [],
|
|
14061
|
+
colLabels = [],
|
|
13519
14062
|
shapes = [],
|
|
13520
14063
|
interactive = false,
|
|
13521
14064
|
animate = false,
|
|
@@ -13525,12 +14068,35 @@ var init_AlgorithmCanvas = __esm({
|
|
|
13525
14068
|
}) => {
|
|
13526
14069
|
const derivedShapes = React85.useMemo(() => {
|
|
13527
14070
|
const out = [];
|
|
14071
|
+
const presence = {
|
|
14072
|
+
bars: bars.length > 0,
|
|
14073
|
+
slots: slots.length > 0,
|
|
14074
|
+
cells: cells.length > 0,
|
|
14075
|
+
buckets: buckets.length > 0,
|
|
14076
|
+
frames: frames.length > 0
|
|
14077
|
+
};
|
|
14078
|
+
const panelCount = PANEL_FAMILY_ORDER.filter((f3) => presence[f3]).length;
|
|
14079
|
+
const panelHeight = height / Math.max(1, panelCount);
|
|
14080
|
+
const panelY = { bars: 0, slots: 0, cells: 0, buckets: 0, frames: 0 };
|
|
14081
|
+
let compactIndex = 0;
|
|
14082
|
+
PANEL_FAMILY_ORDER.forEach((f3) => {
|
|
14083
|
+
if (presence[f3]) {
|
|
14084
|
+
panelY[f3] = compactIndex * panelHeight;
|
|
14085
|
+
compactIndex += 1;
|
|
14086
|
+
}
|
|
14087
|
+
});
|
|
13528
14088
|
if (bars.length > 0) {
|
|
14089
|
+
const panelYBars = panelY.bars;
|
|
13529
14090
|
const slot = width / bars.length;
|
|
13530
14091
|
const barW = slot * 0.8;
|
|
13531
14092
|
const gap = slot * 0.1;
|
|
13532
|
-
const
|
|
13533
|
-
const
|
|
14093
|
+
const bracketRanges = ranges.filter((r) => r.kind === "bracket");
|
|
14094
|
+
const bracketCount = bracketRanges.length;
|
|
14095
|
+
const bracketHeadroom = bracketCount > 0 ? BRACKET_TOP_OFFSET + bracketCount * BRACKET_ROW_H : 0;
|
|
14096
|
+
const hasAux = auxBars.length > 0;
|
|
14097
|
+
const primaryH = hasAux ? panelHeight * AUX_PRIMARY_RATIO : panelHeight;
|
|
14098
|
+
const baseline = panelYBars + primaryH - POINTER_BAND;
|
|
14099
|
+
const usableH = baseline - (panelYBars + TOP_PAD + bracketHeadroom);
|
|
13534
14100
|
const maxV = Math.max(1, ...bars.map((b) => Number.isFinite(b.value) ? b.value : 0));
|
|
13535
14101
|
bars.forEach((bar, i) => {
|
|
13536
14102
|
const v = Number.isFinite(bar.value) ? bar.value : 0;
|
|
@@ -13560,6 +14126,89 @@ var init_AlgorithmCanvas = __esm({
|
|
|
13560
14126
|
});
|
|
13561
14127
|
}
|
|
13562
14128
|
});
|
|
14129
|
+
ranges.forEach((r) => {
|
|
14130
|
+
const kind = r.kind ?? "fill";
|
|
14131
|
+
if (kind !== "fill") return;
|
|
14132
|
+
const color = r.color ?? RANGE_COLOR_DEFAULT;
|
|
14133
|
+
out.push({
|
|
14134
|
+
type: "rect",
|
|
14135
|
+
x: r.from * slot,
|
|
14136
|
+
y: panelYBars,
|
|
14137
|
+
width: (r.to - r.from + 1) * slot,
|
|
14138
|
+
height: primaryH,
|
|
14139
|
+
color,
|
|
14140
|
+
fill: color,
|
|
14141
|
+
opacity: RANGE_FILL_OPACITY
|
|
14142
|
+
});
|
|
14143
|
+
if (r.label) {
|
|
14144
|
+
out.push({
|
|
14145
|
+
type: "text",
|
|
14146
|
+
x: r.from * slot + 4,
|
|
14147
|
+
// Sits below the bracket block (if any) so fill and bracket labels never collide.
|
|
14148
|
+
y: panelYBars + 10 + bracketHeadroom,
|
|
14149
|
+
text: r.label,
|
|
14150
|
+
color,
|
|
14151
|
+
fontSize: 10,
|
|
14152
|
+
align: "left"
|
|
14153
|
+
});
|
|
14154
|
+
}
|
|
14155
|
+
});
|
|
14156
|
+
bracketRanges.forEach((r, i) => {
|
|
14157
|
+
const bracketY = panelYBars + BRACKET_TOP_OFFSET + i * BRACKET_ROW_H;
|
|
14158
|
+
const x1 = r.from * slot + slot * 0.1;
|
|
14159
|
+
const x2 = (r.to + 1) * slot - slot * 0.1;
|
|
14160
|
+
const color = r.color ?? RANGE_COLOR_DEFAULT;
|
|
14161
|
+
out.push({ type: "line", x1, y1: bracketY, x2, y2: bracketY, color, lineWidth: 2 });
|
|
14162
|
+
out.push({ type: "line", x1, y1: bracketY, x2: x1, y2: bracketY + BRACKET_TICK_H, color, lineWidth: 2 });
|
|
14163
|
+
out.push({ type: "line", x1: x2, y1: bracketY, x2, y2: bracketY + BRACKET_TICK_H, color, lineWidth: 2 });
|
|
14164
|
+
if (r.label) {
|
|
14165
|
+
out.push({
|
|
14166
|
+
type: "text",
|
|
14167
|
+
x: (x1 + x2) / 2,
|
|
14168
|
+
y: bracketY - BRACKET_LABEL_OFFSET,
|
|
14169
|
+
text: r.label,
|
|
14170
|
+
color,
|
|
14171
|
+
fontSize: 10,
|
|
14172
|
+
align: "center"
|
|
14173
|
+
});
|
|
14174
|
+
}
|
|
14175
|
+
});
|
|
14176
|
+
if (hasAux) {
|
|
14177
|
+
const auxH = panelHeight - primaryH;
|
|
14178
|
+
const slot2 = width / auxBars.length;
|
|
14179
|
+
const auxBaseline = panelYBars + primaryH + auxH - AUX_BASELINE_PAD;
|
|
14180
|
+
const auxUsableH = auxBaseline - (panelYBars + primaryH + AUX_LABEL_BAND);
|
|
14181
|
+
const maxAuxV = Math.max(1, ...auxBars.map((b) => Number.isFinite(b.value) ? b.value : 0));
|
|
14182
|
+
auxBars.forEach((bar, i) => {
|
|
14183
|
+
const v = Number.isFinite(bar.value) ? bar.value : 0;
|
|
14184
|
+
const bh = Math.max(0, v / maxAuxV * auxUsableH);
|
|
14185
|
+
const x = i * slot2 + slot2 * 0.1;
|
|
14186
|
+
const w = slot2 * 0.8;
|
|
14187
|
+
const color = bar.color ?? DEFAULT_BAR_COLOR;
|
|
14188
|
+
out.push({
|
|
14189
|
+
type: "rect",
|
|
14190
|
+
id: `auxbar-${i}`,
|
|
14191
|
+
x,
|
|
14192
|
+
y: auxBaseline - bh,
|
|
14193
|
+
width: w,
|
|
14194
|
+
height: bh,
|
|
14195
|
+
color,
|
|
14196
|
+
fill: color
|
|
14197
|
+
});
|
|
14198
|
+
const label = bar.label ?? (auxBars.length <= 24 ? String(v) : void 0);
|
|
14199
|
+
if (label) {
|
|
14200
|
+
out.push({
|
|
14201
|
+
type: "text",
|
|
14202
|
+
x: x + w / 2,
|
|
14203
|
+
y: auxBaseline - bh - 8,
|
|
14204
|
+
text: label,
|
|
14205
|
+
color: "#374151",
|
|
14206
|
+
fontSize: 11,
|
|
14207
|
+
align: "center"
|
|
14208
|
+
});
|
|
14209
|
+
}
|
|
14210
|
+
});
|
|
14211
|
+
}
|
|
13563
14212
|
pointers.forEach((p) => {
|
|
13564
14213
|
if (p.index < 0 || p.index >= bars.length) return;
|
|
13565
14214
|
const cx = p.index * slot + slot / 2;
|
|
@@ -13567,7 +14216,7 @@ var init_AlgorithmCanvas = __esm({
|
|
|
13567
14216
|
out.push({
|
|
13568
14217
|
type: "arrow",
|
|
13569
14218
|
x1: cx,
|
|
13570
|
-
y1:
|
|
14219
|
+
y1: panelYBars + primaryH - 18,
|
|
13571
14220
|
x2: cx,
|
|
13572
14221
|
y2: baseline + 4,
|
|
13573
14222
|
color,
|
|
@@ -13577,7 +14226,7 @@ var init_AlgorithmCanvas = __esm({
|
|
|
13577
14226
|
out.push({
|
|
13578
14227
|
type: "text",
|
|
13579
14228
|
x: cx,
|
|
13580
|
-
y:
|
|
14229
|
+
y: panelYBars + primaryH - 8,
|
|
13581
14230
|
text: p.label,
|
|
13582
14231
|
color,
|
|
13583
14232
|
fontSize: 11,
|
|
@@ -13586,14 +14235,111 @@ var init_AlgorithmCanvas = __esm({
|
|
|
13586
14235
|
}
|
|
13587
14236
|
});
|
|
13588
14237
|
}
|
|
14238
|
+
if (slots.length > 0) {
|
|
14239
|
+
const panelYSlots = panelY.slots;
|
|
14240
|
+
const n = slots.length;
|
|
14241
|
+
const vertical = slotOrientation === "vertical";
|
|
14242
|
+
const vBoxH = panelHeight / n;
|
|
14243
|
+
const vBoxW = Math.min(width * 0.5, 120);
|
|
14244
|
+
const vBoxX = (width - vBoxW) / 2;
|
|
14245
|
+
const hCellW = width / n;
|
|
14246
|
+
const hBoxW = hCellW * 0.82;
|
|
14247
|
+
const hBoxH = Math.min(panelHeight * 0.6, 48);
|
|
14248
|
+
const hBoxY = panelYSlots + (panelHeight - hBoxH) / 2;
|
|
14249
|
+
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 };
|
|
14250
|
+
slots.forEach((s, i) => {
|
|
14251
|
+
const box = slotBox(i);
|
|
14252
|
+
const state = s.state ?? "filled";
|
|
14253
|
+
const fill = state === "empty" ? SLOT_EMPTY_FILL : state === "highlight" ? s.color ?? SLOT_HIGHLIGHT_DEFAULT : s.color ?? DEFAULT_BAR_COLOR;
|
|
14254
|
+
const stroke = state === "empty" ? SLOT_EMPTY_STROKE : SLOT_FILLED_STROKE;
|
|
14255
|
+
out.push({
|
|
14256
|
+
type: "rect",
|
|
14257
|
+
id: `slot-${i}`,
|
|
14258
|
+
x: box.x,
|
|
14259
|
+
y: box.y,
|
|
14260
|
+
width: box.width,
|
|
14261
|
+
height: box.height,
|
|
14262
|
+
color: stroke,
|
|
14263
|
+
fill
|
|
14264
|
+
});
|
|
14265
|
+
if (s.value != null && state !== "empty") {
|
|
14266
|
+
out.push({
|
|
14267
|
+
type: "text",
|
|
14268
|
+
x: box.x + box.width / 2,
|
|
14269
|
+
y: box.y + box.height / 2,
|
|
14270
|
+
text: String(s.value),
|
|
14271
|
+
color: SLOT_VALUE_TEXT_COLOR,
|
|
14272
|
+
fontSize: 12,
|
|
14273
|
+
align: "center"
|
|
14274
|
+
});
|
|
14275
|
+
}
|
|
14276
|
+
});
|
|
14277
|
+
if (bars.length === 0) {
|
|
14278
|
+
pointers.forEach((p) => {
|
|
14279
|
+
if (p.index < 0 || p.index >= slots.length) return;
|
|
14280
|
+
const box = slotBox(p.index);
|
|
14281
|
+
const color = p.color ?? DEFAULT_POINTER_COLOR;
|
|
14282
|
+
if (vertical) {
|
|
14283
|
+
const cy = box.y + box.height / 2;
|
|
14284
|
+
out.push({
|
|
14285
|
+
type: "arrow",
|
|
14286
|
+
x1: box.x + box.width + 34,
|
|
14287
|
+
y1: cy,
|
|
14288
|
+
x2: box.x + box.width + 4,
|
|
14289
|
+
y2: cy,
|
|
14290
|
+
color,
|
|
14291
|
+
lineWidth: 2
|
|
14292
|
+
});
|
|
14293
|
+
if (p.label) {
|
|
14294
|
+
out.push({
|
|
14295
|
+
type: "text",
|
|
14296
|
+
x: box.x + box.width + 38,
|
|
14297
|
+
y: cy,
|
|
14298
|
+
text: p.label,
|
|
14299
|
+
color,
|
|
14300
|
+
fontSize: 11,
|
|
14301
|
+
align: "left"
|
|
14302
|
+
});
|
|
14303
|
+
}
|
|
14304
|
+
} else {
|
|
14305
|
+
const cx = box.x + box.width / 2;
|
|
14306
|
+
out.push({
|
|
14307
|
+
type: "arrow",
|
|
14308
|
+
x1: cx,
|
|
14309
|
+
y1: panelYSlots + panelHeight - 18,
|
|
14310
|
+
x2: cx,
|
|
14311
|
+
y2: box.y + box.height + 4,
|
|
14312
|
+
color,
|
|
14313
|
+
lineWidth: 2
|
|
14314
|
+
});
|
|
14315
|
+
if (p.label) {
|
|
14316
|
+
out.push({
|
|
14317
|
+
type: "text",
|
|
14318
|
+
x: cx,
|
|
14319
|
+
y: panelYSlots + panelHeight - 8,
|
|
14320
|
+
text: p.label,
|
|
14321
|
+
color,
|
|
14322
|
+
fontSize: 11,
|
|
14323
|
+
align: "center"
|
|
14324
|
+
});
|
|
14325
|
+
}
|
|
14326
|
+
}
|
|
14327
|
+
});
|
|
14328
|
+
}
|
|
14329
|
+
}
|
|
13589
14330
|
if (cells.length > 0) {
|
|
14331
|
+
const panelYCells = panelY.cells;
|
|
13590
14332
|
const maxCol = Math.max(0, ...cells.map((c) => c.col)) + 1;
|
|
13591
14333
|
const maxRow = Math.max(0, ...cells.map((c) => c.row)) + 1;
|
|
13592
|
-
const
|
|
13593
|
-
const
|
|
14334
|
+
const colLabelH = colLabels.length > 0 ? 16 : 0;
|
|
14335
|
+
const rowLabelW = rowLabels.length > 0 ? 20 : 0;
|
|
14336
|
+
const gridX0 = rowLabelW;
|
|
14337
|
+
const gridY0 = panelYCells + colLabelH;
|
|
14338
|
+
const cw = (width - rowLabelW) / maxCol;
|
|
14339
|
+
const ch = (panelHeight - colLabelH) / maxRow;
|
|
13594
14340
|
cells.forEach((c, i) => {
|
|
13595
|
-
const x = c.col * cw;
|
|
13596
|
-
const y = c.row * ch;
|
|
14341
|
+
const x = gridX0 + c.col * cw;
|
|
14342
|
+
const y = gridY0 + c.row * ch;
|
|
13597
14343
|
const color = c.color ?? DEFAULT_CELL_COLOR;
|
|
13598
14344
|
out.push({
|
|
13599
14345
|
type: "rect",
|
|
@@ -13617,11 +14363,207 @@ var init_AlgorithmCanvas = __esm({
|
|
|
13617
14363
|
align: "center"
|
|
13618
14364
|
});
|
|
13619
14365
|
}
|
|
14366
|
+
if (c.corner && cw >= CORNER_MIN_CELL && ch >= CORNER_MIN_CELL) {
|
|
14367
|
+
const { tl, tr, bl, br } = c.corner;
|
|
14368
|
+
if (tl) {
|
|
14369
|
+
out.push({
|
|
14370
|
+
type: "text",
|
|
14371
|
+
x: x + CORNER_INSET_X,
|
|
14372
|
+
y: y + CORNER_INSET_Y,
|
|
14373
|
+
text: tl,
|
|
14374
|
+
color: CORNER_TEXT_COLOR,
|
|
14375
|
+
fontSize: CORNER_FONT_SIZE,
|
|
14376
|
+
align: "left"
|
|
14377
|
+
});
|
|
14378
|
+
}
|
|
14379
|
+
if (tr) {
|
|
14380
|
+
out.push({
|
|
14381
|
+
type: "text",
|
|
14382
|
+
x: x + cw - CORNER_INSET_X,
|
|
14383
|
+
y: y + CORNER_INSET_Y,
|
|
14384
|
+
text: tr,
|
|
14385
|
+
color: CORNER_TEXT_COLOR,
|
|
14386
|
+
fontSize: CORNER_FONT_SIZE,
|
|
14387
|
+
align: "right"
|
|
14388
|
+
});
|
|
14389
|
+
}
|
|
14390
|
+
if (bl) {
|
|
14391
|
+
out.push({
|
|
14392
|
+
type: "text",
|
|
14393
|
+
x: x + CORNER_INSET_X,
|
|
14394
|
+
y: y + ch - CORNER_INSET_Y,
|
|
14395
|
+
text: bl,
|
|
14396
|
+
color: CORNER_TEXT_COLOR,
|
|
14397
|
+
fontSize: CORNER_FONT_SIZE,
|
|
14398
|
+
align: "left"
|
|
14399
|
+
});
|
|
14400
|
+
}
|
|
14401
|
+
if (br) {
|
|
14402
|
+
out.push({
|
|
14403
|
+
type: "text",
|
|
14404
|
+
x: x + cw - CORNER_INSET_X,
|
|
14405
|
+
y: y + ch - CORNER_INSET_Y,
|
|
14406
|
+
text: br,
|
|
14407
|
+
color: CORNER_TEXT_COLOR,
|
|
14408
|
+
fontSize: CORNER_FONT_SIZE,
|
|
14409
|
+
align: "right"
|
|
14410
|
+
});
|
|
14411
|
+
}
|
|
14412
|
+
}
|
|
14413
|
+
});
|
|
14414
|
+
colLabels.forEach((l) => {
|
|
14415
|
+
out.push({
|
|
14416
|
+
type: "text",
|
|
14417
|
+
x: gridX0 + l.index * cw + cw / 2,
|
|
14418
|
+
y: panelYCells + colLabelH / 2,
|
|
14419
|
+
text: l.text,
|
|
14420
|
+
color: l.color ?? AXIS_LABEL_COLOR,
|
|
14421
|
+
fontSize: AXIS_LABEL_FONT_SIZE,
|
|
14422
|
+
align: "center"
|
|
14423
|
+
});
|
|
14424
|
+
});
|
|
14425
|
+
rowLabels.forEach((l) => {
|
|
14426
|
+
out.push({
|
|
14427
|
+
type: "text",
|
|
14428
|
+
x: rowLabelW - 6,
|
|
14429
|
+
y: gridY0 + l.index * ch + ch / 2,
|
|
14430
|
+
text: l.text,
|
|
14431
|
+
color: l.color ?? AXIS_LABEL_COLOR,
|
|
14432
|
+
fontSize: AXIS_LABEL_FONT_SIZE,
|
|
14433
|
+
align: "right"
|
|
14434
|
+
});
|
|
14435
|
+
});
|
|
14436
|
+
}
|
|
14437
|
+
if (buckets.length > 0) {
|
|
14438
|
+
const panelYBuckets = panelY.buckets;
|
|
14439
|
+
const bucketCount = Math.max(0, ...buckets.map((b) => b.index)) + 1;
|
|
14440
|
+
const rowH = panelHeight / bucketCount;
|
|
14441
|
+
const indexColW = Math.min(width * 0.12, 40);
|
|
14442
|
+
const maxChainLen = Math.max(1, ...buckets.map((b) => b.entries.length));
|
|
14443
|
+
const entryW = Math.min(BUCKET_ENTRY_MAX_W, Math.max(BUCKET_ENTRY_MIN_W, (width - indexColW - 8) / maxChainLen));
|
|
14444
|
+
const maxVisible = Math.floor((width - indexColW - 4) / entryW);
|
|
14445
|
+
buckets.forEach((b) => {
|
|
14446
|
+
const rowY = panelYBuckets + b.index * rowH;
|
|
14447
|
+
out.push({
|
|
14448
|
+
type: "rect",
|
|
14449
|
+
id: `bucket-index-${b.index}`,
|
|
14450
|
+
x: 2,
|
|
14451
|
+
y: rowY + 2,
|
|
14452
|
+
width: indexColW - 4,
|
|
14453
|
+
height: rowH - 4,
|
|
14454
|
+
color: BUCKET_INDEX_STROKE,
|
|
14455
|
+
fill: BUCKET_INDEX_FILL
|
|
14456
|
+
});
|
|
14457
|
+
out.push({
|
|
14458
|
+
type: "text",
|
|
14459
|
+
x: 2 + (indexColW - 4) / 2,
|
|
14460
|
+
y: rowY + rowH / 2,
|
|
14461
|
+
text: String(b.index),
|
|
14462
|
+
color: BUCKET_INDEX_TEXT,
|
|
14463
|
+
fontSize: 10,
|
|
14464
|
+
align: "center"
|
|
14465
|
+
});
|
|
14466
|
+
const overflow = b.entries.length > maxVisible;
|
|
14467
|
+
const visibleCount = overflow ? Math.max(0, maxVisible - 1) : b.entries.length;
|
|
14468
|
+
for (let j = 0; j < visibleCount; j++) {
|
|
14469
|
+
const entry = b.entries[j];
|
|
14470
|
+
const ex = indexColW + 4 + j * entryW;
|
|
14471
|
+
const state = entry.state ?? "default";
|
|
14472
|
+
const fill = state === "highlight" ? entry.color ?? BUCKET_ENTRY_HIGHLIGHT : state === "probing" ? entry.color ?? BUCKET_ENTRY_PROBING : entry.color ?? BUCKET_ENTRY_DEFAULT;
|
|
14473
|
+
out.push({
|
|
14474
|
+
type: "rect",
|
|
14475
|
+
id: `bucket-${b.index}-${j}`,
|
|
14476
|
+
x: ex,
|
|
14477
|
+
y: rowY + 2,
|
|
14478
|
+
width: entryW - 2,
|
|
14479
|
+
height: rowH - 4,
|
|
14480
|
+
color: fill,
|
|
14481
|
+
fill
|
|
14482
|
+
});
|
|
14483
|
+
if (entryW >= 20 && rowH >= 16) {
|
|
14484
|
+
out.push({
|
|
14485
|
+
type: "text",
|
|
14486
|
+
x: ex + (entryW - 2) / 2,
|
|
14487
|
+
y: rowY + rowH / 2,
|
|
14488
|
+
text: entry.label,
|
|
14489
|
+
color: BUCKET_ENTRY_TEXT,
|
|
14490
|
+
fontSize: 10,
|
|
14491
|
+
align: "center"
|
|
14492
|
+
});
|
|
14493
|
+
}
|
|
14494
|
+
}
|
|
14495
|
+
if (overflow) {
|
|
14496
|
+
const ex = indexColW + 4 + visibleCount * entryW;
|
|
14497
|
+
out.push({
|
|
14498
|
+
type: "rect",
|
|
14499
|
+
id: `bucket-${b.index}-overflow`,
|
|
14500
|
+
x: ex,
|
|
14501
|
+
y: rowY + 2,
|
|
14502
|
+
width: entryW - 2,
|
|
14503
|
+
height: rowH - 4,
|
|
14504
|
+
color: BUCKET_ENTRY_DEFAULT,
|
|
14505
|
+
fill: BUCKET_ENTRY_DEFAULT
|
|
14506
|
+
});
|
|
14507
|
+
out.push({
|
|
14508
|
+
type: "text",
|
|
14509
|
+
x: ex + (entryW - 2) / 2,
|
|
14510
|
+
y: rowY + rowH / 2,
|
|
14511
|
+
text: `+${b.entries.length - visibleCount}`,
|
|
14512
|
+
color: BUCKET_ENTRY_TEXT,
|
|
14513
|
+
fontSize: 10,
|
|
14514
|
+
align: "center"
|
|
14515
|
+
});
|
|
14516
|
+
}
|
|
14517
|
+
});
|
|
14518
|
+
}
|
|
14519
|
+
if (frames.length > 0) {
|
|
14520
|
+
const panelYFrames = panelY.frames;
|
|
14521
|
+
const n = frames.length;
|
|
14522
|
+
const frameH = panelHeight / n;
|
|
14523
|
+
const x = 8;
|
|
14524
|
+
const w = width - 16;
|
|
14525
|
+
frames.forEach((f3, i) => {
|
|
14526
|
+
const y = panelYFrames + panelHeight - (i + 1) * frameH;
|
|
14527
|
+
const state = f3.state ?? "active";
|
|
14528
|
+
const fill = state === "returning" ? f3.color ?? FRAME_RETURNING_COLOR : state === "done" ? f3.color ?? FRAME_DONE_COLOR : f3.color ?? FRAME_ACTIVE_COLOR;
|
|
14529
|
+
out.push({ type: "rect", id: `frame-${i}`, x, y, width: w, height: frameH, color: fill, fill });
|
|
14530
|
+
if (frameH >= FRAME_TWO_LINE_MIN_H) {
|
|
14531
|
+
out.push({
|
|
14532
|
+
type: "text",
|
|
14533
|
+
x: 16,
|
|
14534
|
+
y: y + frameH * 0.35,
|
|
14535
|
+
text: f3.label,
|
|
14536
|
+
color: FRAME_LABEL_COLOR,
|
|
14537
|
+
fontSize: 10,
|
|
14538
|
+
align: "left"
|
|
14539
|
+
});
|
|
14540
|
+
if (f3.detail) {
|
|
14541
|
+
out.push({
|
|
14542
|
+
type: "text",
|
|
14543
|
+
x: 16,
|
|
14544
|
+
y: y + frameH * 0.7,
|
|
14545
|
+
text: f3.detail,
|
|
14546
|
+
color: FRAME_DETAIL_COLOR,
|
|
14547
|
+
fontSize: 10,
|
|
14548
|
+
align: "left"
|
|
14549
|
+
});
|
|
14550
|
+
}
|
|
14551
|
+
} else {
|
|
14552
|
+
out.push({
|
|
14553
|
+
type: "text",
|
|
14554
|
+
x: 16,
|
|
14555
|
+
y: y + frameH / 2,
|
|
14556
|
+
text: f3.label,
|
|
14557
|
+
color: FRAME_LABEL_COLOR,
|
|
14558
|
+
fontSize: 10,
|
|
14559
|
+
align: "left"
|
|
14560
|
+
});
|
|
14561
|
+
}
|
|
13620
14562
|
});
|
|
13621
14563
|
}
|
|
13622
14564
|
out.push(...shapes);
|
|
13623
14565
|
return out;
|
|
13624
|
-
}, [bars, cells, pointers, shapes, width, height]);
|
|
14566
|
+
}, [bars, cells, pointers, ranges, slots, slotOrientation, frames, buckets, auxBars, rowLabels, colLabels, shapes, width, height]);
|
|
13625
14567
|
return /* @__PURE__ */ jsxRuntime.jsx(Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
|
|
13626
14568
|
title ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", children: title }) : null,
|
|
13627
14569
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -14610,23 +15552,422 @@ var init_BehaviorView = __esm({
|
|
|
14610
15552
|
BehaviorView.displayName = "BehaviorView";
|
|
14611
15553
|
}
|
|
14612
15554
|
});
|
|
14613
|
-
|
|
15555
|
+
function LearningScene3D({
|
|
15556
|
+
className,
|
|
15557
|
+
width = 600,
|
|
15558
|
+
height = 400,
|
|
15559
|
+
title,
|
|
15560
|
+
backgroundColor,
|
|
15561
|
+
drawables,
|
|
15562
|
+
camera,
|
|
15563
|
+
lighting,
|
|
15564
|
+
post,
|
|
15565
|
+
showGrid = false,
|
|
15566
|
+
shadows,
|
|
15567
|
+
interactive,
|
|
15568
|
+
isLoading,
|
|
15569
|
+
error,
|
|
15570
|
+
onItemClick
|
|
15571
|
+
}) {
|
|
15572
|
+
const instanceId = React85.useId().replace(/[^a-zA-Z0-9]/g, "");
|
|
15573
|
+
const clickEvent = onItemClick ? `LEARNING_SCENE_3D.ITEM_CLICK.${instanceId}` : void 0;
|
|
15574
|
+
const onItemClickRef = React85.useRef(onItemClick);
|
|
15575
|
+
onItemClickRef.current = onItemClick;
|
|
15576
|
+
useEventListener(`UI:${clickEvent ?? "LEARNING_SCENE_3D.ITEM_CLICK.__disabled"}`, (event) => {
|
|
15577
|
+
const unitId = event.payload?.unitId;
|
|
15578
|
+
if (typeof unitId === "string") onItemClickRef.current?.(unitId);
|
|
15579
|
+
});
|
|
15580
|
+
if (typeof process !== "undefined" && process.env && process.env.NODE_ENV !== "production" && post?.bloom) {
|
|
15581
|
+
const unknownKeys = Object.keys(post.bloom).filter((k) => !KNOWN_BLOOM_KEYS.has(k));
|
|
15582
|
+
if (unknownKeys.length > 0) {
|
|
15583
|
+
sceneLog.debug("post.bloom has unrecognized keys \u2014 only intensity/threshold/smoothing are read", { unknownKeys });
|
|
15584
|
+
}
|
|
15585
|
+
}
|
|
15586
|
+
const props3d = {
|
|
15587
|
+
drawables,
|
|
15588
|
+
isLoading,
|
|
15589
|
+
error: error ? error.message : null,
|
|
15590
|
+
cameraMode: camera?.mode ?? "perspective",
|
|
15591
|
+
...camera?.zoom !== void 0 ? { zoom: camera.zoom } : {},
|
|
15592
|
+
...camera?.fov !== void 0 ? { fov: camera.fov } : {},
|
|
15593
|
+
...camera?.azimuth !== void 0 ? { azimuth: camera.azimuth } : {},
|
|
15594
|
+
...camera?.elevation !== void 0 ? { elevation: camera.elevation } : {},
|
|
15595
|
+
...camera?.target !== void 0 ? { followTarget: camera.target } : {},
|
|
15596
|
+
backgroundColor,
|
|
15597
|
+
showGrid,
|
|
15598
|
+
...shadows !== void 0 ? { shadows } : {},
|
|
15599
|
+
...interactive !== void 0 ? { controlsEnabled: interactive } : {},
|
|
15600
|
+
lighting,
|
|
15601
|
+
post,
|
|
15602
|
+
...clickEvent !== void 0 ? { unitClickEvent: clickEvent } : {}
|
|
15603
|
+
};
|
|
15604
|
+
return /* @__PURE__ */ jsxRuntime.jsx(Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
|
|
15605
|
+
title ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", children: title }) : null,
|
|
15606
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { width, height, display: "flex" }, children: /* @__PURE__ */ jsxRuntime.jsx(React85.Suspense, { fallback: null, children: /* @__PURE__ */ jsxRuntime.jsx(Canvas3DHost2, { ...props3d }) }) })
|
|
15607
|
+
] }) });
|
|
15608
|
+
}
|
|
15609
|
+
function meshSphere(id, x, y, z, radius, color, opts) {
|
|
15610
|
+
return {
|
|
15611
|
+
type: "draw-mesh",
|
|
15612
|
+
...id !== void 0 ? { id } : {},
|
|
15613
|
+
shape: opts?.shape ?? "sphere",
|
|
15614
|
+
position: { x, y, z },
|
|
15615
|
+
radius,
|
|
15616
|
+
pivot: "center",
|
|
15617
|
+
segments: opts?.segments ?? 24,
|
|
15618
|
+
material: { color, ...opts?.material },
|
|
15619
|
+
...opts?.opacity !== void 0 ? { opacity: opts.opacity } : {}
|
|
15620
|
+
};
|
|
15621
|
+
}
|
|
15622
|
+
function axisRotation(from, to) {
|
|
15623
|
+
const wx = to[0] - from[0];
|
|
15624
|
+
const wy = to[2] - from[2];
|
|
15625
|
+
const wz = to[1] - from[1];
|
|
15626
|
+
const len = Math.sqrt(wx * wx + wy * wy + wz * wz);
|
|
15627
|
+
const tilt = Math.acos(Math.min(1, Math.max(-1, len > 0 ? wy / len : 1)));
|
|
15628
|
+
return [0, Math.atan2(wz, -wx), tilt];
|
|
15629
|
+
}
|
|
15630
|
+
function segmentLength(from, to) {
|
|
15631
|
+
const dx = to[0] - from[0];
|
|
15632
|
+
const dy = to[1] - from[1];
|
|
15633
|
+
const dz = to[2] - from[2];
|
|
15634
|
+
return Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
15635
|
+
}
|
|
15636
|
+
function cylinderBetween(from, to, radius, color) {
|
|
15637
|
+
const len = segmentLength(from, to);
|
|
15638
|
+
if (len < 1e-6) return null;
|
|
15639
|
+
return {
|
|
15640
|
+
type: "draw-mesh",
|
|
15641
|
+
shape: "cylinder",
|
|
15642
|
+
position: { x: (from[0] + to[0]) / 2, y: (from[1] + to[1]) / 2, z: (from[2] + to[2]) / 2 },
|
|
15643
|
+
radius,
|
|
15644
|
+
height: len,
|
|
15645
|
+
rotation: axisRotation(from, to),
|
|
15646
|
+
pivot: "center",
|
|
15647
|
+
segments: 12,
|
|
15648
|
+
material: { color }
|
|
15649
|
+
};
|
|
15650
|
+
}
|
|
15651
|
+
function arrowBetween(from, to, color, shaftRadius = 0.08, id) {
|
|
15652
|
+
const len = segmentLength(from, to);
|
|
15653
|
+
if (len < 1e-6) return null;
|
|
15654
|
+
const tipLen = Math.min(shaftRadius * 8, len * 0.35);
|
|
15655
|
+
const k = (len - tipLen) / len;
|
|
15656
|
+
const delta = [to[0] - from[0], to[1] - from[1], to[2] - from[2]];
|
|
15657
|
+
const shaftEnd = [delta[0] * k, delta[1] * k, delta[2] * k];
|
|
15658
|
+
const tipStart = shaftEnd;
|
|
15659
|
+
const tipEnd = delta;
|
|
15660
|
+
const shaft = cylinderBetween([0, 0, 0], shaftEnd, shaftRadius, color);
|
|
15661
|
+
const tipLenActual = segmentLength(tipStart, tipEnd);
|
|
15662
|
+
const tip = {
|
|
15663
|
+
type: "draw-mesh",
|
|
15664
|
+
shape: "cone",
|
|
15665
|
+
position: {
|
|
15666
|
+
x: (tipStart[0] + tipEnd[0]) / 2,
|
|
15667
|
+
y: (tipStart[1] + tipEnd[1]) / 2,
|
|
15668
|
+
z: (tipStart[2] + tipEnd[2]) / 2
|
|
15669
|
+
},
|
|
15670
|
+
radius: shaftRadius * 3,
|
|
15671
|
+
height: tipLenActual,
|
|
15672
|
+
rotation: axisRotation(tipStart, tipEnd),
|
|
15673
|
+
pivot: "center",
|
|
15674
|
+
segments: 12,
|
|
15675
|
+
material: { color }
|
|
15676
|
+
};
|
|
15677
|
+
return {
|
|
15678
|
+
type: "draw-group",
|
|
15679
|
+
...id !== void 0 ? { id } : {},
|
|
15680
|
+
position: { x: from[0], y: from[1], z: from[2] },
|
|
15681
|
+
items: tipLenActual < 1e-6 ? shaft ? [shaft] : [] : shaft ? [shaft, tip] : [tip]
|
|
15682
|
+
};
|
|
15683
|
+
}
|
|
15684
|
+
function billboardLabel(text, x, y, z, opts) {
|
|
15685
|
+
return {
|
|
15686
|
+
type: "draw-text",
|
|
15687
|
+
text,
|
|
15688
|
+
position: { x, y, z },
|
|
15689
|
+
color: opts?.color ?? "#111827"
|
|
15690
|
+
};
|
|
15691
|
+
}
|
|
15692
|
+
function labelColorForBackground(backgroundColor) {
|
|
15693
|
+
const m = /^#(?:([0-9a-f]{3})|([0-9a-f]{6}))$/i.exec(backgroundColor ?? "");
|
|
15694
|
+
if (!m) return "#111827";
|
|
15695
|
+
const hex = m[1] !== void 0 ? m[1].split("").map((c) => c + c).join("") : m[2];
|
|
15696
|
+
const r = parseInt(hex.slice(0, 2), 16) / 255;
|
|
15697
|
+
const g = parseInt(hex.slice(2, 4), 16) / 255;
|
|
15698
|
+
const b = parseInt(hex.slice(4, 6), 16) / 255;
|
|
15699
|
+
const luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
15700
|
+
return luminance < 0.5 ? "#e5e7eb" : "#111827";
|
|
15701
|
+
}
|
|
15702
|
+
function get3DClickPayload(onShapeClick, idToIndex) {
|
|
15703
|
+
if (!onShapeClick) return void 0;
|
|
15704
|
+
return (id) => onShapeClick({ id, index: idToIndex.get(id) ?? -1 });
|
|
15705
|
+
}
|
|
15706
|
+
function polylineTube(points, radius, color, opts) {
|
|
15707
|
+
const maxSegments = opts?.maxSegments ?? 128;
|
|
15708
|
+
let pts = points;
|
|
15709
|
+
if (pts.length - 1 > maxSegments) {
|
|
15710
|
+
const step = (pts.length - 1) / maxSegments;
|
|
15711
|
+
const kept = [pts[0]];
|
|
15712
|
+
for (let s = 1; s < maxSegments; s++) kept.push(pts[Math.round(s * step)]);
|
|
15713
|
+
kept.push(pts[pts.length - 1]);
|
|
15714
|
+
pts = kept;
|
|
15715
|
+
}
|
|
15716
|
+
const out = [];
|
|
15717
|
+
for (let i = 0; i < pts.length - 1; i++) {
|
|
15718
|
+
const seg = cylinderBetween(pts[i], pts[i + 1], radius, color);
|
|
15719
|
+
if (seg) out.push(opts?.opacity !== void 0 ? { ...seg, opacity: opts.opacity } : seg);
|
|
15720
|
+
}
|
|
15721
|
+
return out;
|
|
15722
|
+
}
|
|
15723
|
+
function heightFieldMesh(spec) {
|
|
15724
|
+
const { nx, ny, heights, spacing = 1, x = 0, y = 0 } = spec;
|
|
15725
|
+
const flatShading = spec.flatShading ?? true;
|
|
15726
|
+
const vertices = [];
|
|
15727
|
+
for (let iy = 0; iy < ny; iy++) {
|
|
15728
|
+
for (let ix = 0; ix < nx; ix++) {
|
|
15729
|
+
vertices.push([
|
|
15730
|
+
x + (ix - (nx - 1) / 2) * spacing,
|
|
15731
|
+
y + (iy - (ny - 1) / 2) * spacing,
|
|
15732
|
+
heights[iy * nx + ix] ?? 0
|
|
15733
|
+
]);
|
|
15734
|
+
}
|
|
15735
|
+
}
|
|
15736
|
+
const bands = [...spec.bands ?? []].sort((a, b) => (a.min ?? -Infinity) - (b.min ?? -Infinity));
|
|
15737
|
+
const facesByBand = /* @__PURE__ */ new Map();
|
|
15738
|
+
for (let iy = 0; iy < ny - 1; iy++) {
|
|
15739
|
+
for (let ix = 0; ix < nx - 1; ix++) {
|
|
15740
|
+
const v00 = iy * nx + ix;
|
|
15741
|
+
const v10 = iy * nx + ix + 1;
|
|
15742
|
+
const v01 = (iy + 1) * nx + ix;
|
|
15743
|
+
const v11 = (iy + 1) * nx + ix + 1;
|
|
15744
|
+
for (const face of [[v00, v10, v01], [v10, v11, v01]]) {
|
|
15745
|
+
const centroid = (vertices[face[0]][2] + vertices[face[1]][2] + vertices[face[2]][2]) / 3;
|
|
15746
|
+
let band = null;
|
|
15747
|
+
for (const b of bands) {
|
|
15748
|
+
if ((b.min ?? -Infinity) <= centroid) band = b;
|
|
15749
|
+
}
|
|
15750
|
+
const key = bands.length > 0 ? band : null;
|
|
15751
|
+
const list = facesByBand.get(key) ?? [];
|
|
15752
|
+
list.push(face);
|
|
15753
|
+
facesByBand.set(key, list);
|
|
15754
|
+
}
|
|
15755
|
+
}
|
|
15756
|
+
}
|
|
15757
|
+
const out = [];
|
|
15758
|
+
for (const [band, faces] of facesByBand) {
|
|
15759
|
+
if (faces.length === 0) continue;
|
|
15760
|
+
out.push({
|
|
15761
|
+
type: "draw-mesh",
|
|
15762
|
+
shape: "polyhedron",
|
|
15763
|
+
position: { x: 0, y: 0, z: 0 },
|
|
15764
|
+
vertices,
|
|
15765
|
+
faces,
|
|
15766
|
+
pivot: "center",
|
|
15767
|
+
material: { color: band?.color ?? spec.color ?? "#64748b", flatShading, side: "double" },
|
|
15768
|
+
...spec.opacity !== void 0 ? { opacity: spec.opacity } : {}
|
|
15769
|
+
});
|
|
15770
|
+
}
|
|
15771
|
+
return out;
|
|
15772
|
+
}
|
|
15773
|
+
function arrowField(vectors, opts) {
|
|
15774
|
+
const scale = opts?.scale ?? 1;
|
|
15775
|
+
const out = [];
|
|
15776
|
+
for (const v of vectors) {
|
|
15777
|
+
const to = [
|
|
15778
|
+
v.from[0] + v.delta[0] * scale,
|
|
15779
|
+
v.from[1] + v.delta[1] * scale,
|
|
15780
|
+
v.from[2] + v.delta[2] * scale
|
|
15781
|
+
];
|
|
15782
|
+
const arrow = arrowBetween(v.from, to, v.color ?? "#dc2626", v.width, v.id);
|
|
15783
|
+
if (arrow) out.push(arrow);
|
|
15784
|
+
if (v.label) out.push(billboardLabel(v.label, to[0], to[1], to[2], { color: opts?.labelColor }));
|
|
15785
|
+
}
|
|
15786
|
+
return out;
|
|
15787
|
+
}
|
|
15788
|
+
function helixDrawables(spec, opts) {
|
|
15789
|
+
const count = spec.count ?? spec.rungs?.length ?? 0;
|
|
15790
|
+
const rungs = Array.from({ length: count }, (_, i) => spec.rungs?.[i] ?? {});
|
|
15791
|
+
const radius = spec.radius ?? 1;
|
|
15792
|
+
const rise = spec.rise ?? 0.34;
|
|
15793
|
+
const twistRad = (spec.twistDeg ?? 36) * (Math.PI / 180);
|
|
15794
|
+
const strandAColor = spec.strandAColor ?? "#38bdf8";
|
|
15795
|
+
const strandBColor = spec.strandBColor ?? "#fb923c";
|
|
15796
|
+
const backboneRadius = spec.backboneRadius ?? 0.16;
|
|
15797
|
+
const rungRadius = spec.rungRadius ?? 0.12;
|
|
15798
|
+
const cx = spec.x ?? 0;
|
|
15799
|
+
const cy = spec.y ?? 0;
|
|
15800
|
+
const cz = spec.z ?? 0;
|
|
15801
|
+
const unwoundCount = spec.unwoundCount ?? 0;
|
|
15802
|
+
const unwindSpread = spec.unwindSpread ?? 1.8;
|
|
15803
|
+
const strandA = [];
|
|
15804
|
+
const strandB = [];
|
|
15805
|
+
for (let i = 0; i < count; i++) {
|
|
15806
|
+
const yi = cy + (i - (count - 1) / 2) * rise;
|
|
15807
|
+
const theta = i * twistRad;
|
|
15808
|
+
const s = i < unwoundCount ? unwindSpread : 1;
|
|
15809
|
+
strandA.push([cx + s * radius * Math.cos(theta), yi, cz + s * radius * Math.sin(theta)]);
|
|
15810
|
+
strandB.push([cx + s * radius * Math.cos(theta + Math.PI), yi, cz + s * radius * Math.sin(theta + Math.PI)]);
|
|
15811
|
+
}
|
|
15812
|
+
const out = [];
|
|
15813
|
+
for (let i = 0; i < count; i++) {
|
|
15814
|
+
out.push(meshSphere(`hx-a-${i}`, strandA[i][0], strandA[i][1], strandA[i][2], backboneRadius, strandAColor));
|
|
15815
|
+
out.push(meshSphere(`hx-b-${i}`, strandB[i][0], strandB[i][1], strandB[i][2], backboneRadius, strandBColor));
|
|
15816
|
+
if (i > 0) {
|
|
15817
|
+
const segA = cylinderBetween(strandA[i - 1], strandA[i], backboneRadius, strandAColor);
|
|
15818
|
+
if (segA) out.push(segA);
|
|
15819
|
+
const segB = cylinderBetween(strandB[i - 1], strandB[i], backboneRadius, strandBColor);
|
|
15820
|
+
if (segB) out.push(segB);
|
|
15821
|
+
}
|
|
15822
|
+
const rung = rungs[i];
|
|
15823
|
+
const rungColor = rung.color ?? "#94a3b8";
|
|
15824
|
+
const rod = cylinderBetween(strandA[i], strandB[i], rungRadius, rungColor);
|
|
15825
|
+
if (rod) out.push(rod);
|
|
15826
|
+
const mid = [
|
|
15827
|
+
(strandA[i][0] + strandB[i][0]) / 2,
|
|
15828
|
+
(strandA[i][1] + strandB[i][1]) / 2,
|
|
15829
|
+
(strandA[i][2] + strandB[i][2]) / 2
|
|
15830
|
+
];
|
|
15831
|
+
const markerRadius = rung.radius ?? rungRadius;
|
|
15832
|
+
out.push(meshSphere(rung.id, mid[0], mid[1], mid[2], markerRadius, rungColor));
|
|
15833
|
+
if (rung.label) out.push(billboardLabel(rung.label, mid[0], mid[1], mid[2] + markerRadius, { color: opts?.labelColor }));
|
|
15834
|
+
}
|
|
15835
|
+
return out;
|
|
15836
|
+
}
|
|
15837
|
+
function latticeDrawables(spec, opts) {
|
|
15838
|
+
const nx = spec.nx ?? 2;
|
|
15839
|
+
const ny = spec.ny ?? 2;
|
|
15840
|
+
const nz = spec.nz ?? 2;
|
|
15841
|
+
const latticeConstant = spec.latticeConstant ?? 2;
|
|
15842
|
+
const bondRadius = spec.bondRadius ?? 0.06;
|
|
15843
|
+
const highlightCell = spec.highlightCell ?? false;
|
|
15844
|
+
const dimColor = spec.dimColor ?? "#475569";
|
|
15845
|
+
const showLabels = spec.showLabels ?? false;
|
|
15846
|
+
const selectedColor = spec.selectedColor ?? "#f59e0b";
|
|
15847
|
+
const posByKey = /* @__PURE__ */ new Map();
|
|
15848
|
+
const inCellByKey = /* @__PURE__ */ new Map();
|
|
15849
|
+
const out = [];
|
|
15850
|
+
for (const site of spec.basis) {
|
|
15851
|
+
const snx = site.xEdge ? nx + 1 : nx;
|
|
15852
|
+
const sny = site.yEdge ? ny + 1 : ny;
|
|
15853
|
+
const snz = site.zEdge ? nz + 1 : nz;
|
|
15854
|
+
for (let i = 0; i < snx; i++) {
|
|
15855
|
+
for (let j = 0; j < sny; j++) {
|
|
15856
|
+
for (let k = 0; k < snz; k++) {
|
|
15857
|
+
const key = `${site.key}-${i}-${j}-${k}`;
|
|
15858
|
+
const inCell = i + site.dx <= 1 && j + site.dy <= 1 && k + site.dz <= 1;
|
|
15859
|
+
const pos = [
|
|
15860
|
+
(i + site.dx) * latticeConstant - nx * latticeConstant / 2,
|
|
15861
|
+
(j + site.dy) * latticeConstant - ny * latticeConstant / 2,
|
|
15862
|
+
(k + site.dz) * latticeConstant - nz * latticeConstant / 2
|
|
15863
|
+
];
|
|
15864
|
+
posByKey.set(key, pos);
|
|
15865
|
+
inCellByKey.set(key, inCell);
|
|
15866
|
+
const isSelected = spec.selectedId === `lat-${key}`;
|
|
15867
|
+
const color = isSelected ? selectedColor : highlightCell && !inCell ? dimColor : site.color ?? "#2563eb";
|
|
15868
|
+
const radius = (site.radius ?? 0.3) * (isSelected ? 1.4 : 1);
|
|
15869
|
+
out.push(meshSphere(`lat-${key}`, pos[0], pos[1], pos[2], radius, color));
|
|
15870
|
+
if (showLabels && site.element) {
|
|
15871
|
+
out.push(billboardLabel(site.element, pos[0], pos[1], pos[2] + radius, { color: opts?.labelColor }));
|
|
15872
|
+
}
|
|
15873
|
+
}
|
|
15874
|
+
}
|
|
15875
|
+
}
|
|
15876
|
+
}
|
|
15877
|
+
const basisByKey = new Map(spec.basis.map((s) => [s.key, s]));
|
|
15878
|
+
for (const bond of spec.bonds ?? []) {
|
|
15879
|
+
const fromSite = basisByKey.get(bond.from);
|
|
15880
|
+
const toSite = basisByKey.get(bond.to);
|
|
15881
|
+
if (!fromSite || !toSite) continue;
|
|
15882
|
+
const fnx = fromSite.xEdge ? nx + 1 : nx;
|
|
15883
|
+
const fny = fromSite.yEdge ? ny + 1 : ny;
|
|
15884
|
+
const fnz = fromSite.zEdge ? nz + 1 : nz;
|
|
15885
|
+
const tnx = toSite.xEdge ? nx + 1 : nx;
|
|
15886
|
+
const tny = toSite.yEdge ? ny + 1 : ny;
|
|
15887
|
+
const tnz = toSite.zEdge ? nz + 1 : nz;
|
|
15888
|
+
const bdx = bond.dx ?? 0;
|
|
15889
|
+
const bdy = bond.dy ?? 0;
|
|
15890
|
+
const bdz = bond.dz ?? 0;
|
|
15891
|
+
for (let i = 0; i < fnx; i++) {
|
|
15892
|
+
for (let j = 0; j < fny; j++) {
|
|
15893
|
+
for (let k = 0; k < fnz; k++) {
|
|
15894
|
+
const ti = i + bdx;
|
|
15895
|
+
const tj = j + bdy;
|
|
15896
|
+
const tk = k + bdz;
|
|
15897
|
+
if (ti < 0 || ti >= tnx || tj < 0 || tj >= tny || tk < 0 || tk >= tnz) continue;
|
|
15898
|
+
const fromKey = `${fromSite.key}-${i}-${j}-${k}`;
|
|
15899
|
+
const toKey = `${toSite.key}-${ti}-${tj}-${tk}`;
|
|
15900
|
+
const fromPos = posByKey.get(fromKey);
|
|
15901
|
+
const toPos = posByKey.get(toKey);
|
|
15902
|
+
if (!fromPos || !toPos) continue;
|
|
15903
|
+
const dimmed = highlightCell && !(inCellByKey.get(fromKey) && inCellByKey.get(toKey));
|
|
15904
|
+
const seg = cylinderBetween(fromPos, toPos, bondRadius, dimmed ? dimColor : bond.color ?? "#6b7280");
|
|
15905
|
+
if (seg) out.push(seg);
|
|
15906
|
+
}
|
|
15907
|
+
}
|
|
15908
|
+
}
|
|
15909
|
+
}
|
|
15910
|
+
return out;
|
|
15911
|
+
}
|
|
15912
|
+
var sceneLog, KNOWN_BLOOM_KEYS, Canvas3DHost2;
|
|
15913
|
+
var init_learningScene3D = __esm({
|
|
15914
|
+
"components/learning/molecules/learningScene3D.tsx"() {
|
|
15915
|
+
"use client";
|
|
15916
|
+
init_atoms();
|
|
15917
|
+
init_Stack();
|
|
15918
|
+
init_useEventBus();
|
|
15919
|
+
sceneLog = logger.createLogger("almadar:ui:learning-scene-3d");
|
|
15920
|
+
KNOWN_BLOOM_KEYS = /* @__PURE__ */ new Set(["intensity", "threshold", "smoothing"]);
|
|
15921
|
+
Canvas3DHost2 = React85.lazy(
|
|
15922
|
+
() => import('@almadar/ui/components/molecules/game/three').then((m) => ({ default: m.Canvas3DHost }))
|
|
15923
|
+
);
|
|
15924
|
+
LearningScene3D.displayName = "LearningScene3D";
|
|
15925
|
+
}
|
|
15926
|
+
});
|
|
15927
|
+
var biologyLog, BIO_BAND_COLORS, BIO_STAGE_FILL, BIO_STAGE_TEXT, BiologyCanvas;
|
|
14614
15928
|
var init_BiologyCanvas = __esm({
|
|
14615
15929
|
"components/learning/molecules/BiologyCanvas.tsx"() {
|
|
14616
15930
|
"use client";
|
|
14617
15931
|
init_atoms();
|
|
14618
15932
|
init_Stack();
|
|
14619
15933
|
init_LearningCanvas();
|
|
15934
|
+
init_learningScene3D();
|
|
15935
|
+
biologyLog = logger.createLogger("almadar:ui:biology-canvas");
|
|
15936
|
+
BIO_BAND_COLORS = ["#dcfce7", "#fef9c3", "#fee2e2", "#e0e7ff"];
|
|
15937
|
+
BIO_STAGE_FILL = {
|
|
15938
|
+
pending: "#e2e8f0",
|
|
15939
|
+
active: "#3b82f6",
|
|
15940
|
+
done: "#94a3b8"
|
|
15941
|
+
};
|
|
15942
|
+
BIO_STAGE_TEXT = {
|
|
15943
|
+
pending: "#64748b",
|
|
15944
|
+
active: "#ffffff",
|
|
15945
|
+
done: "#ffffff"
|
|
15946
|
+
};
|
|
14620
15947
|
BiologyCanvas = ({
|
|
14621
15948
|
className,
|
|
14622
15949
|
width = 600,
|
|
14623
15950
|
height = 400,
|
|
14624
15951
|
title,
|
|
14625
15952
|
backgroundColor,
|
|
15953
|
+
mode = "2d",
|
|
15954
|
+
camera,
|
|
15955
|
+
lighting,
|
|
15956
|
+
post,
|
|
14626
15957
|
nodes = [],
|
|
14627
15958
|
edges = [],
|
|
15959
|
+
compartments = [],
|
|
15960
|
+
bands = [],
|
|
15961
|
+
stages = [],
|
|
15962
|
+
stageStyle = "timeline",
|
|
15963
|
+
helix,
|
|
15964
|
+
helix3d,
|
|
14628
15965
|
shapes = [],
|
|
14629
|
-
|
|
15966
|
+
readouts,
|
|
15967
|
+
traces,
|
|
15968
|
+
showGrid,
|
|
15969
|
+
shadows,
|
|
15970
|
+
interactive,
|
|
14630
15971
|
animate = false,
|
|
14631
15972
|
onShapeClick,
|
|
14632
15973
|
isLoading,
|
|
@@ -14638,19 +15979,148 @@ var init_BiologyCanvas = __esm({
|
|
|
14638
15979
|
for (const n of nodes) {
|
|
14639
15980
|
if (n.id) nodeById.set(n.id, n);
|
|
14640
15981
|
}
|
|
15982
|
+
const bandCount = bands.length;
|
|
15983
|
+
for (let i = 0; i < bandCount; i++) {
|
|
15984
|
+
const band = bands[i];
|
|
15985
|
+
const bandColor = band.color ?? BIO_BAND_COLORS[i % BIO_BAND_COLORS.length];
|
|
15986
|
+
const bandY = i * height / bandCount;
|
|
15987
|
+
const bandH = height / bandCount;
|
|
15988
|
+
out.push({
|
|
15989
|
+
type: "rect",
|
|
15990
|
+
x: 0,
|
|
15991
|
+
y: bandY,
|
|
15992
|
+
width,
|
|
15993
|
+
height: bandH,
|
|
15994
|
+
color: bandColor,
|
|
15995
|
+
fill: bandColor,
|
|
15996
|
+
opacity: 0.45
|
|
15997
|
+
});
|
|
15998
|
+
if (band.label) {
|
|
15999
|
+
out.push({
|
|
16000
|
+
type: "text",
|
|
16001
|
+
x: 8,
|
|
16002
|
+
y: bandY + 14,
|
|
16003
|
+
text: band.label,
|
|
16004
|
+
color: "#6b7280",
|
|
16005
|
+
fontSize: 10
|
|
16006
|
+
});
|
|
16007
|
+
}
|
|
16008
|
+
}
|
|
16009
|
+
for (const c of compartments) {
|
|
16010
|
+
const color = c.color ?? "#16a34a";
|
|
16011
|
+
out.push({
|
|
16012
|
+
type: "ellipse",
|
|
16013
|
+
x: c.x,
|
|
16014
|
+
y: c.y,
|
|
16015
|
+
width: c.width,
|
|
16016
|
+
height: c.height,
|
|
16017
|
+
color,
|
|
16018
|
+
fill: c.fill ?? `${color}1A`,
|
|
16019
|
+
lineWidth: c.lineWidth ?? 2,
|
|
16020
|
+
...c.dash ? { dash: c.dash } : {}
|
|
16021
|
+
});
|
|
16022
|
+
if (c.label) {
|
|
16023
|
+
out.push({
|
|
16024
|
+
type: "text",
|
|
16025
|
+
x: c.x,
|
|
16026
|
+
y: c.y - c.height / 2 + 14,
|
|
16027
|
+
text: c.label,
|
|
16028
|
+
color: "#111827",
|
|
16029
|
+
fontSize: 11,
|
|
16030
|
+
align: "center"
|
|
16031
|
+
});
|
|
16032
|
+
}
|
|
16033
|
+
}
|
|
16034
|
+
if (helix) {
|
|
16035
|
+
const hx = helix.x ?? 24;
|
|
16036
|
+
const hy = helix.y ?? height * 0.25;
|
|
16037
|
+
const hw = helix.width ?? width - 48;
|
|
16038
|
+
const hh = helix.height ?? height * 0.5;
|
|
16039
|
+
const rungs = helix.rungs;
|
|
16040
|
+
const n = rungs.length;
|
|
16041
|
+
const cy = hy + hh / 2;
|
|
16042
|
+
const colorA = helix.colorA ?? "#2563eb";
|
|
16043
|
+
const colorB = helix.colorB ?? "#dc2626";
|
|
16044
|
+
const rungColor = helix.rungColor ?? "#94a3b8";
|
|
16045
|
+
const fork = helix.fork ?? 0;
|
|
16046
|
+
const maxSep = Math.min(hh - 8, 96);
|
|
16047
|
+
const strandA = [];
|
|
16048
|
+
const strandB = [];
|
|
16049
|
+
const rungGeoms = [];
|
|
16050
|
+
for (let i = 0; i < n; i++) {
|
|
16051
|
+
const rx = hx + (i + 0.5) * hw / n;
|
|
16052
|
+
const t = (i + 0.5) / n;
|
|
16053
|
+
const paired = t >= fork;
|
|
16054
|
+
const sep = paired ? 28 : 28 + (maxSep - 28) * ((fork - t) / fork);
|
|
16055
|
+
strandA.push({ x: rx, y: cy - sep / 2 });
|
|
16056
|
+
strandB.push({ x: rx, y: cy + sep / 2 });
|
|
16057
|
+
rungGeoms.push({ rx, sep, rung: rungs[i], paired });
|
|
16058
|
+
}
|
|
16059
|
+
for (let i = 1; i < n; i++) {
|
|
16060
|
+
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 });
|
|
16061
|
+
}
|
|
16062
|
+
for (let i = 1; i < n; i++) {
|
|
16063
|
+
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 });
|
|
16064
|
+
}
|
|
16065
|
+
for (const g of rungGeoms) {
|
|
16066
|
+
const rColor = g.rung.color ?? (g.rung.state === "new" ? "#16a34a" : rungColor);
|
|
16067
|
+
const topY = cy - g.sep / 2;
|
|
16068
|
+
const bottomY = cy + g.sep / 2;
|
|
16069
|
+
if (g.paired) {
|
|
16070
|
+
out.push({ type: "line", x1: g.rx, y1: topY, x2: g.rx, y2: bottomY, color: rColor });
|
|
16071
|
+
if (g.rung.a) {
|
|
16072
|
+
out.push({ type: "text", x: g.rx, y: cy - g.sep / 4, text: g.rung.a, fontSize: 9, align: "center", color: "#374151" });
|
|
16073
|
+
}
|
|
16074
|
+
if (g.rung.b) {
|
|
16075
|
+
out.push({ type: "text", x: g.rx, y: cy + g.sep / 4, text: g.rung.b, fontSize: 9, align: "center", color: "#374151" });
|
|
16076
|
+
}
|
|
16077
|
+
} else {
|
|
16078
|
+
const stubTopY = topY + 8;
|
|
16079
|
+
const stubBottomY = bottomY - 8;
|
|
16080
|
+
out.push({ type: "line", x1: g.rx, y1: topY, x2: g.rx, y2: stubTopY, color: rColor });
|
|
16081
|
+
out.push({ type: "line", x1: g.rx, y1: bottomY, x2: g.rx, y2: stubBottomY, color: rColor });
|
|
16082
|
+
if (g.rung.a) {
|
|
16083
|
+
out.push({ type: "text", x: g.rx, y: stubTopY + 6, text: g.rung.a, fontSize: 9, align: "center", color: "#374151" });
|
|
16084
|
+
}
|
|
16085
|
+
if (g.rung.b) {
|
|
16086
|
+
out.push({ type: "text", x: g.rx, y: stubBottomY - 6, text: g.rung.b, fontSize: 9, align: "center", color: "#374151" });
|
|
16087
|
+
}
|
|
16088
|
+
}
|
|
16089
|
+
}
|
|
16090
|
+
}
|
|
14641
16091
|
for (const e of edges) {
|
|
14642
16092
|
const a = nodeById.get(e.from);
|
|
14643
16093
|
const b = nodeById.get(e.to);
|
|
14644
16094
|
if (!a || !b) continue;
|
|
14645
|
-
|
|
14646
|
-
|
|
14647
|
-
|
|
14648
|
-
|
|
14649
|
-
|
|
14650
|
-
|
|
14651
|
-
|
|
14652
|
-
|
|
14653
|
-
|
|
16095
|
+
const color = e.color ?? "#9ca3af";
|
|
16096
|
+
if (e.directed) {
|
|
16097
|
+
const rA = a.radius ?? 16;
|
|
16098
|
+
const rB = b.radius ?? 16;
|
|
16099
|
+
const dx = b.x - a.x;
|
|
16100
|
+
const dy = b.y - a.y;
|
|
16101
|
+
const dist = Math.max(1e-6, Math.hypot(dx, dy));
|
|
16102
|
+
const ux = dx / dist;
|
|
16103
|
+
const uy = dy / dist;
|
|
16104
|
+
out.push({
|
|
16105
|
+
type: "arrow",
|
|
16106
|
+
x1: a.x + ux * rA,
|
|
16107
|
+
y1: a.y + uy * rA,
|
|
16108
|
+
x2: b.x - ux * rB,
|
|
16109
|
+
y2: b.y - uy * rB,
|
|
16110
|
+
color,
|
|
16111
|
+
lineWidth: 2
|
|
16112
|
+
});
|
|
16113
|
+
} else {
|
|
16114
|
+
out.push({
|
|
16115
|
+
type: "line",
|
|
16116
|
+
x1: a.x,
|
|
16117
|
+
y1: a.y,
|
|
16118
|
+
x2: b.x,
|
|
16119
|
+
y2: b.y,
|
|
16120
|
+
color,
|
|
16121
|
+
lineWidth: 2
|
|
16122
|
+
});
|
|
16123
|
+
}
|
|
14654
16124
|
if (e.label) {
|
|
14655
16125
|
out.push({
|
|
14656
16126
|
type: "text",
|
|
@@ -14663,6 +16133,8 @@ var init_BiologyCanvas = __esm({
|
|
|
14663
16133
|
}
|
|
14664
16134
|
}
|
|
14665
16135
|
for (const n of nodes) {
|
|
16136
|
+
const state = n.state ?? "default";
|
|
16137
|
+
const muted = state === "muted";
|
|
14666
16138
|
out.push({
|
|
14667
16139
|
type: "circle",
|
|
14668
16140
|
x: n.x,
|
|
@@ -14670,23 +16142,197 @@ var init_BiologyCanvas = __esm({
|
|
|
14670
16142
|
radius: n.radius ?? 16,
|
|
14671
16143
|
color: n.color ?? "#16a34a",
|
|
14672
16144
|
fill: `${n.color ?? "#16a34a"}33`,
|
|
14673
|
-
id: n.id
|
|
16145
|
+
id: n.id,
|
|
16146
|
+
...muted ? { opacity: 0.35 } : {}
|
|
14674
16147
|
});
|
|
16148
|
+
if (state === "highlight") {
|
|
16149
|
+
out.push({
|
|
16150
|
+
type: "circle",
|
|
16151
|
+
x: n.x,
|
|
16152
|
+
y: n.y,
|
|
16153
|
+
radius: (n.radius ?? 16) + 4,
|
|
16154
|
+
color: "#f59e0b",
|
|
16155
|
+
lineWidth: 2
|
|
16156
|
+
});
|
|
16157
|
+
}
|
|
14675
16158
|
if (n.label) {
|
|
14676
16159
|
out.push({
|
|
14677
16160
|
type: "text",
|
|
14678
16161
|
x: n.x,
|
|
14679
16162
|
y: n.y + (n.radius ?? 16) + 14,
|
|
14680
16163
|
text: n.label,
|
|
16164
|
+
...muted ? { opacity: 0.35 } : {},
|
|
14681
16165
|
color: "#111827",
|
|
14682
16166
|
fontSize: 12,
|
|
14683
16167
|
align: "center"
|
|
14684
16168
|
});
|
|
14685
16169
|
}
|
|
14686
16170
|
}
|
|
16171
|
+
const stageCount = stages.length;
|
|
16172
|
+
if (stageCount > 0) {
|
|
16173
|
+
if (stageStyle === "ring") {
|
|
16174
|
+
const cx = width / 2;
|
|
16175
|
+
const cy = height / 2;
|
|
16176
|
+
const R = Math.min(width, height) / 2 - 48;
|
|
16177
|
+
const ringPoints = [];
|
|
16178
|
+
for (let i = 0; i < stageCount; i++) {
|
|
16179
|
+
const angleRad = (-90 + 360 * i / stageCount) * Math.PI / 180;
|
|
16180
|
+
ringPoints.push({ x: cx + R * Math.cos(angleRad), y: cy + R * Math.sin(angleRad) });
|
|
16181
|
+
}
|
|
16182
|
+
if (stageCount >= 2) {
|
|
16183
|
+
for (let i = 0; i < stageCount - 1; i++) {
|
|
16184
|
+
const p1 = ringPoints[i];
|
|
16185
|
+
const p2 = ringPoints[i + 1];
|
|
16186
|
+
const dx = p2.x - p1.x;
|
|
16187
|
+
const dy = p2.y - p1.y;
|
|
16188
|
+
const dist = Math.max(1e-6, Math.hypot(dx, dy));
|
|
16189
|
+
const ux = dx / dist;
|
|
16190
|
+
const uy = dy / dist;
|
|
16191
|
+
out.push({
|
|
16192
|
+
type: "arrow",
|
|
16193
|
+
x1: p1.x + ux * 46,
|
|
16194
|
+
y1: p1.y + uy * 46,
|
|
16195
|
+
x2: p2.x - ux * 46,
|
|
16196
|
+
y2: p2.y - uy * 46,
|
|
16197
|
+
color: "#94a3b8"
|
|
16198
|
+
});
|
|
16199
|
+
}
|
|
16200
|
+
}
|
|
16201
|
+
for (let i = 0; i < stageCount; i++) {
|
|
16202
|
+
const stage = stages[i];
|
|
16203
|
+
const state = stage.state ?? "pending";
|
|
16204
|
+
const fill = stage.color ?? BIO_STAGE_FILL[state];
|
|
16205
|
+
const w = Math.max(26, Math.min(84, stage.label.length * 6 + 10));
|
|
16206
|
+
const h = 18;
|
|
16207
|
+
const p = ringPoints[i];
|
|
16208
|
+
out.push({ type: "rect", x: p.x - w / 2, y: p.y - h / 2, width: w, height: h, color: fill, fill });
|
|
16209
|
+
out.push({ type: "text", x: p.x, y: p.y, text: stage.label, color: BIO_STAGE_TEXT[state], fontSize: 10, align: "center" });
|
|
16210
|
+
}
|
|
16211
|
+
} else {
|
|
16212
|
+
const stripY = height - 32;
|
|
16213
|
+
const slotW = (width - 16) / stageCount;
|
|
16214
|
+
const chipGeoms = [];
|
|
16215
|
+
for (let i = 0; i < stageCount; i++) {
|
|
16216
|
+
chipGeoms.push({ x: 8 + i * slotW + 5, w: slotW - 10 });
|
|
16217
|
+
}
|
|
16218
|
+
for (let i = 0; i < stageCount - 1; i++) {
|
|
16219
|
+
const midY = stripY + 13;
|
|
16220
|
+
out.push({
|
|
16221
|
+
type: "arrow",
|
|
16222
|
+
x1: chipGeoms[i].x + chipGeoms[i].w,
|
|
16223
|
+
y1: midY,
|
|
16224
|
+
x2: chipGeoms[i + 1].x,
|
|
16225
|
+
y2: midY,
|
|
16226
|
+
color: "#94a3b8"
|
|
16227
|
+
});
|
|
16228
|
+
}
|
|
16229
|
+
for (let i = 0; i < stageCount; i++) {
|
|
16230
|
+
const stage = stages[i];
|
|
16231
|
+
const state = stage.state ?? "pending";
|
|
16232
|
+
const fill = stage.color ?? BIO_STAGE_FILL[state];
|
|
16233
|
+
const g = chipGeoms[i];
|
|
16234
|
+
out.push({ type: "rect", x: g.x, y: stripY, width: g.w, height: 26, color: fill, fill });
|
|
16235
|
+
out.push({
|
|
16236
|
+
type: "text",
|
|
16237
|
+
x: g.x + g.w / 2,
|
|
16238
|
+
y: stripY + 13,
|
|
16239
|
+
text: stage.label,
|
|
16240
|
+
color: BIO_STAGE_TEXT[state],
|
|
16241
|
+
fontSize: 10,
|
|
16242
|
+
align: "center"
|
|
16243
|
+
});
|
|
16244
|
+
}
|
|
16245
|
+
}
|
|
16246
|
+
}
|
|
14687
16247
|
out.push(...shapes);
|
|
14688
16248
|
return out;
|
|
14689
|
-
}, [nodes, edges, shapes]);
|
|
16249
|
+
}, [nodes, edges, compartments, bands, stages, stageStyle, helix, shapes, width, height]);
|
|
16250
|
+
const drawables3D = React85.useMemo(() => {
|
|
16251
|
+
if (mode !== "3d") return [];
|
|
16252
|
+
if (shapes.length > 0) {
|
|
16253
|
+
biologyLog.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
|
|
16254
|
+
}
|
|
16255
|
+
if (compartments.length > 0 || bands.length > 0 || stages.length > 0 || helix) {
|
|
16256
|
+
biologyLog.debug("2D-only families ignored in 3D mode (pixel-authored 2D vocabulary)", {
|
|
16257
|
+
compartments: compartments.length,
|
|
16258
|
+
bands: bands.length,
|
|
16259
|
+
stages: stages.length,
|
|
16260
|
+
helix: helix != null
|
|
16261
|
+
});
|
|
16262
|
+
}
|
|
16263
|
+
if (animate) {
|
|
16264
|
+
biologyLog.debug("animate ignored in 3D mode (motion is entity-state driven)");
|
|
16265
|
+
}
|
|
16266
|
+
const out = [];
|
|
16267
|
+
const labelColor = labelColorForBackground(backgroundColor);
|
|
16268
|
+
const nodeById = /* @__PURE__ */ new Map();
|
|
16269
|
+
for (const n of nodes) {
|
|
16270
|
+
if (n.id) nodeById.set(n.id, n);
|
|
16271
|
+
}
|
|
16272
|
+
for (const e of edges) {
|
|
16273
|
+
const a = nodeById.get(e.from);
|
|
16274
|
+
const b = nodeById.get(e.to);
|
|
16275
|
+
if (!a || !b) continue;
|
|
16276
|
+
const edgeRadius = Math.max(0.04, Math.min(a.radius ?? 0.5, b.radius ?? 0.5) * 0.12);
|
|
16277
|
+
const edge = cylinderBetween([a.x, a.y, a.z ?? 0], [b.x, b.y, b.z ?? 0], edgeRadius, e.color ?? "#9ca3af");
|
|
16278
|
+
if (edge) out.push(edge);
|
|
16279
|
+
if (e.label) {
|
|
16280
|
+
out.push(
|
|
16281
|
+
billboardLabel(
|
|
16282
|
+
e.label,
|
|
16283
|
+
(a.x + b.x) / 2,
|
|
16284
|
+
(a.y + b.y) / 2,
|
|
16285
|
+
((a.z ?? 0) + (b.z ?? 0)) / 2,
|
|
16286
|
+
{ color: labelColor }
|
|
16287
|
+
)
|
|
16288
|
+
);
|
|
16289
|
+
}
|
|
16290
|
+
}
|
|
16291
|
+
for (const n of nodes) {
|
|
16292
|
+
const radius = n.radius ?? 0.5;
|
|
16293
|
+
const nz = n.z ?? 0;
|
|
16294
|
+
out.push(meshSphere(n.id, n.x, n.y, nz, radius, n.color ?? "#16a34a", { shape: n.shape, ...n.opacity !== void 0 ? { opacity: n.opacity } : {} }));
|
|
16295
|
+
if (n.label) {
|
|
16296
|
+
out.push(billboardLabel(n.label, n.x, n.y, nz + radius, { color: labelColor }));
|
|
16297
|
+
}
|
|
16298
|
+
}
|
|
16299
|
+
if (helix3d) {
|
|
16300
|
+
out.push(...helixDrawables(helix3d, { labelColor }));
|
|
16301
|
+
}
|
|
16302
|
+
return out;
|
|
16303
|
+
}, [mode, nodes, edges, shapes, compartments, bands, stages, helix, helix3d, animate, backgroundColor]);
|
|
16304
|
+
const nodeIndexById = React85.useMemo(() => {
|
|
16305
|
+
const m = /* @__PURE__ */ new Map();
|
|
16306
|
+
(helix3d?.rungs ?? []).forEach((rung, i) => {
|
|
16307
|
+
if (rung.id) m.set(rung.id, i);
|
|
16308
|
+
});
|
|
16309
|
+
nodes.forEach((n, i) => {
|
|
16310
|
+
if (n.id) m.set(n.id, i);
|
|
16311
|
+
});
|
|
16312
|
+
return m;
|
|
16313
|
+
}, [nodes, helix3d]);
|
|
16314
|
+
if (mode === "3d") {
|
|
16315
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
16316
|
+
LearningScene3D,
|
|
16317
|
+
{
|
|
16318
|
+
className,
|
|
16319
|
+
width,
|
|
16320
|
+
height,
|
|
16321
|
+
title,
|
|
16322
|
+
backgroundColor,
|
|
16323
|
+
drawables: drawables3D,
|
|
16324
|
+
camera,
|
|
16325
|
+
lighting,
|
|
16326
|
+
post,
|
|
16327
|
+
showGrid,
|
|
16328
|
+
shadows,
|
|
16329
|
+
interactive,
|
|
16330
|
+
isLoading,
|
|
16331
|
+
error,
|
|
16332
|
+
onItemClick: get3DClickPayload(onShapeClick, nodeIndexById)
|
|
16333
|
+
}
|
|
16334
|
+
);
|
|
16335
|
+
}
|
|
14690
16336
|
return /* @__PURE__ */ jsxRuntime.jsx(Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
|
|
14691
16337
|
title ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", children: title }) : null,
|
|
14692
16338
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -14696,7 +16342,9 @@ var init_BiologyCanvas = __esm({
|
|
|
14696
16342
|
height,
|
|
14697
16343
|
backgroundColor,
|
|
14698
16344
|
shapes: derivedShapes,
|
|
14699
|
-
|
|
16345
|
+
readouts,
|
|
16346
|
+
traces,
|
|
16347
|
+
interactive: interactive ?? false,
|
|
14700
16348
|
animate,
|
|
14701
16349
|
onShapeClick,
|
|
14702
16350
|
isLoading,
|
|
@@ -21419,24 +23067,55 @@ var init_ChatBar = __esm({
|
|
|
21419
23067
|
ChatBar.displayName = "ChatBar";
|
|
21420
23068
|
}
|
|
21421
23069
|
});
|
|
21422
|
-
|
|
23070
|
+
function bondPerpendicular(a, b) {
|
|
23071
|
+
const dx = b[0] - a[0];
|
|
23072
|
+
const dy = b[1] - a[1];
|
|
23073
|
+
const px = dy;
|
|
23074
|
+
const py = -dx;
|
|
23075
|
+
const len = Math.sqrt(px * px + py * py);
|
|
23076
|
+
if (len < 1e-6) return [1, 0, 0];
|
|
23077
|
+
return [px / len, py / len, 0];
|
|
23078
|
+
}
|
|
23079
|
+
var chemistryLog, CHEM_BOND_STATE_COLOR, LONE_PAIR_ANGLES, ChemistryCanvas;
|
|
21423
23080
|
var init_ChemistryCanvas = __esm({
|
|
21424
23081
|
"components/learning/molecules/ChemistryCanvas.tsx"() {
|
|
21425
23082
|
"use client";
|
|
21426
23083
|
init_atoms();
|
|
21427
23084
|
init_Stack();
|
|
21428
23085
|
init_LearningCanvas();
|
|
23086
|
+
init_learningScene3D();
|
|
23087
|
+
chemistryLog = logger.createLogger("almadar:ui:chemistry-canvas");
|
|
23088
|
+
CHEM_BOND_STATE_COLOR = {
|
|
23089
|
+
default: "#6b7280",
|
|
23090
|
+
forming: "#16a34a",
|
|
23091
|
+
breaking: "#dc2626",
|
|
23092
|
+
highlight: "#f59e0b"
|
|
23093
|
+
};
|
|
23094
|
+
LONE_PAIR_ANGLES = [-90, 0, 90, 180];
|
|
21429
23095
|
ChemistryCanvas = ({
|
|
21430
23096
|
className,
|
|
21431
23097
|
width = 600,
|
|
21432
23098
|
height = 400,
|
|
21433
23099
|
title,
|
|
21434
23100
|
backgroundColor,
|
|
23101
|
+
mode = "2d",
|
|
23102
|
+
camera,
|
|
23103
|
+
lighting,
|
|
23104
|
+
post,
|
|
21435
23105
|
atoms = [],
|
|
21436
23106
|
bonds = [],
|
|
21437
23107
|
arrows = [],
|
|
23108
|
+
bondStyle = "thick",
|
|
23109
|
+
containers = [],
|
|
23110
|
+
equation,
|
|
23111
|
+
equationColor,
|
|
23112
|
+
lattice3d,
|
|
21438
23113
|
shapes = [],
|
|
21439
|
-
|
|
23114
|
+
readouts,
|
|
23115
|
+
traces,
|
|
23116
|
+
showGrid,
|
|
23117
|
+
shadows,
|
|
23118
|
+
interactive,
|
|
21440
23119
|
animate = false,
|
|
21441
23120
|
onShapeClick,
|
|
21442
23121
|
isLoading,
|
|
@@ -21448,21 +23127,118 @@ var init_ChemistryCanvas = __esm({
|
|
|
21448
23127
|
for (const a of atoms) {
|
|
21449
23128
|
if (a.id) atomById.set(a.id, a);
|
|
21450
23129
|
}
|
|
23130
|
+
for (const c of containers) {
|
|
23131
|
+
const color = c.color ?? "#64748b";
|
|
23132
|
+
if (c.level != null) {
|
|
23133
|
+
const lv = c.level;
|
|
23134
|
+
out.push({
|
|
23135
|
+
type: "rect",
|
|
23136
|
+
x: c.x + 1,
|
|
23137
|
+
y: c.y + c.height * (1 - lv),
|
|
23138
|
+
width: c.width - 2,
|
|
23139
|
+
height: c.height * lv - 1,
|
|
23140
|
+
color: c.levelColor ?? "#60a5fa",
|
|
23141
|
+
fill: c.levelColor ?? "#60a5fa",
|
|
23142
|
+
opacity: 0.5
|
|
23143
|
+
});
|
|
23144
|
+
}
|
|
23145
|
+
out.push({
|
|
23146
|
+
type: "rect",
|
|
23147
|
+
x: c.x,
|
|
23148
|
+
y: c.y,
|
|
23149
|
+
width: c.width,
|
|
23150
|
+
height: c.height,
|
|
23151
|
+
color,
|
|
23152
|
+
fill: c.fill,
|
|
23153
|
+
lineWidth: c.lineWidth ?? 2
|
|
23154
|
+
});
|
|
23155
|
+
const divider = c.divider ?? "none";
|
|
23156
|
+
if (divider !== "none") {
|
|
23157
|
+
out.push({
|
|
23158
|
+
type: "line",
|
|
23159
|
+
x1: c.x + c.width / 2,
|
|
23160
|
+
y1: c.y,
|
|
23161
|
+
x2: c.x + c.width / 2,
|
|
23162
|
+
y2: c.y + c.height,
|
|
23163
|
+
color: c.dividerColor ?? color,
|
|
23164
|
+
...divider === "dashed" || divider === "dotted" ? { dash: divider } : {}
|
|
23165
|
+
});
|
|
23166
|
+
}
|
|
23167
|
+
if (c.leftLabel) {
|
|
23168
|
+
out.push({
|
|
23169
|
+
type: "text",
|
|
23170
|
+
x: c.x + c.width * 0.25,
|
|
23171
|
+
y: c.y + 12,
|
|
23172
|
+
text: c.leftLabel,
|
|
23173
|
+
color: "#374151",
|
|
23174
|
+
fontSize: 11,
|
|
23175
|
+
align: "center"
|
|
23176
|
+
});
|
|
23177
|
+
}
|
|
23178
|
+
if (c.rightLabel) {
|
|
23179
|
+
out.push({
|
|
23180
|
+
type: "text",
|
|
23181
|
+
x: c.x + c.width * 0.75,
|
|
23182
|
+
y: c.y + 12,
|
|
23183
|
+
text: c.rightLabel,
|
|
23184
|
+
color: "#374151",
|
|
23185
|
+
fontSize: 11,
|
|
23186
|
+
align: "center"
|
|
23187
|
+
});
|
|
23188
|
+
}
|
|
23189
|
+
if (c.label) {
|
|
23190
|
+
out.push({
|
|
23191
|
+
type: "text",
|
|
23192
|
+
x: c.x + c.width / 2,
|
|
23193
|
+
y: c.y + c.height + 12,
|
|
23194
|
+
text: c.label,
|
|
23195
|
+
color: "#111827",
|
|
23196
|
+
fontSize: 12,
|
|
23197
|
+
align: "center"
|
|
23198
|
+
});
|
|
23199
|
+
}
|
|
23200
|
+
}
|
|
21451
23201
|
for (const b of bonds) {
|
|
21452
23202
|
const a = atomById.get(b.from);
|
|
21453
23203
|
const c = atomById.get(b.to);
|
|
21454
23204
|
if (!a || !c) continue;
|
|
21455
|
-
const
|
|
21456
|
-
const
|
|
21457
|
-
|
|
21458
|
-
|
|
21459
|
-
|
|
21460
|
-
|
|
21461
|
-
|
|
21462
|
-
|
|
21463
|
-
|
|
21464
|
-
|
|
21465
|
-
|
|
23205
|
+
const state = b.state ?? "default";
|
|
23206
|
+
const color = b.color ?? CHEM_BOND_STATE_COLOR[state];
|
|
23207
|
+
const dash = state === "forming" || state === "breaking" ? "dashed" : void 0;
|
|
23208
|
+
if (bondStyle === "parallel") {
|
|
23209
|
+
const dx = c.x - a.x;
|
|
23210
|
+
const dy = c.y - a.y;
|
|
23211
|
+
const dist = Math.max(1e-6, Math.hypot(dx, dy));
|
|
23212
|
+
const ux = dx / dist;
|
|
23213
|
+
const uy = dy / dist;
|
|
23214
|
+
const px = -uy;
|
|
23215
|
+
const py = ux;
|
|
23216
|
+
const offsets = b.type === "double" ? [-3, 3] : b.type === "triple" ? [-4, 0, 4] : [0];
|
|
23217
|
+
for (const off of offsets) {
|
|
23218
|
+
out.push({
|
|
23219
|
+
type: "line",
|
|
23220
|
+
x1: a.x + px * off,
|
|
23221
|
+
y1: a.y + py * off,
|
|
23222
|
+
x2: c.x + px * off,
|
|
23223
|
+
y2: c.y + py * off,
|
|
23224
|
+
color,
|
|
23225
|
+
lineWidth: 2,
|
|
23226
|
+
...dash ? { dash } : {}
|
|
23227
|
+
});
|
|
23228
|
+
}
|
|
23229
|
+
} else {
|
|
23230
|
+
const strokeWidth = b.type === "double" ? 4 : b.type === "triple" ? 6 : 2;
|
|
23231
|
+
out.push({
|
|
23232
|
+
type: "line",
|
|
23233
|
+
x1: a.x,
|
|
23234
|
+
y1: a.y,
|
|
23235
|
+
x2: c.x,
|
|
23236
|
+
y2: c.y,
|
|
23237
|
+
color,
|
|
23238
|
+
lineWidth: strokeWidth,
|
|
23239
|
+
...dash ? { dash } : {}
|
|
23240
|
+
});
|
|
23241
|
+
}
|
|
21466
23242
|
}
|
|
21467
23243
|
for (const a of arrows) {
|
|
21468
23244
|
const angle = (a.angle ?? 0) * (Math.PI / 180);
|
|
@@ -21511,10 +23287,142 @@ var init_ChemistryCanvas = __esm({
|
|
|
21511
23287
|
align: "center"
|
|
21512
23288
|
});
|
|
21513
23289
|
}
|
|
23290
|
+
const r = a.radius ?? 14;
|
|
23291
|
+
if (a.charge) {
|
|
23292
|
+
out.push({
|
|
23293
|
+
type: "text",
|
|
23294
|
+
x: a.x + r * 0.85,
|
|
23295
|
+
y: a.y - r * 0.85,
|
|
23296
|
+
text: a.charge,
|
|
23297
|
+
color: "#111827",
|
|
23298
|
+
fontSize: 9,
|
|
23299
|
+
align: "left"
|
|
23300
|
+
});
|
|
23301
|
+
}
|
|
23302
|
+
const lonePairs = Math.max(0, Math.min(4, a.lonePairs ?? 0));
|
|
23303
|
+
for (let k = 0; k < lonePairs; k++) {
|
|
23304
|
+
const angleRad = LONE_PAIR_ANGLES[k] * Math.PI / 180;
|
|
23305
|
+
const cx = a.x + (r + 6) * Math.cos(angleRad);
|
|
23306
|
+
const cy = a.y + (r + 6) * Math.sin(angleRad);
|
|
23307
|
+
const perpX = -Math.sin(angleRad);
|
|
23308
|
+
const perpY = Math.cos(angleRad);
|
|
23309
|
+
for (const sign of [1, -1]) {
|
|
23310
|
+
out.push({
|
|
23311
|
+
type: "circle",
|
|
23312
|
+
x: cx + perpX * 2.5 * sign,
|
|
23313
|
+
y: cy + perpY * 2.5 * sign,
|
|
23314
|
+
radius: 1.5,
|
|
23315
|
+
color: "#374151",
|
|
23316
|
+
fill: "#374151"
|
|
23317
|
+
});
|
|
23318
|
+
}
|
|
23319
|
+
}
|
|
23320
|
+
}
|
|
23321
|
+
if (equation) {
|
|
23322
|
+
out.push({
|
|
23323
|
+
type: "text",
|
|
23324
|
+
x: width / 2,
|
|
23325
|
+
y: 14,
|
|
23326
|
+
text: equation,
|
|
23327
|
+
color: equationColor ?? "#111827",
|
|
23328
|
+
fontSize: 13,
|
|
23329
|
+
align: "center"
|
|
23330
|
+
});
|
|
21514
23331
|
}
|
|
21515
23332
|
out.push(...shapes);
|
|
21516
23333
|
return out;
|
|
21517
|
-
}, [atoms, bonds, arrows, shapes]);
|
|
23334
|
+
}, [atoms, bonds, arrows, bondStyle, containers, equation, equationColor, shapes, width]);
|
|
23335
|
+
const drawables3D = React85.useMemo(() => {
|
|
23336
|
+
if (mode !== "3d") return [];
|
|
23337
|
+
if (shapes.length > 0) {
|
|
23338
|
+
chemistryLog.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
|
|
23339
|
+
}
|
|
23340
|
+
if (containers.length > 0) {
|
|
23341
|
+
chemistryLog.debug("containers ignored in 3D mode (pixel-authored 2D vocabulary)", { count: containers.length });
|
|
23342
|
+
}
|
|
23343
|
+
if (animate) {
|
|
23344
|
+
chemistryLog.debug("animate ignored in 3D mode (motion is entity-state driven)");
|
|
23345
|
+
}
|
|
23346
|
+
const out = [];
|
|
23347
|
+
const labelColor = labelColorForBackground(backgroundColor);
|
|
23348
|
+
const atomById = /* @__PURE__ */ new Map();
|
|
23349
|
+
for (const a of atoms) {
|
|
23350
|
+
if (a.id) atomById.set(a.id, a);
|
|
23351
|
+
}
|
|
23352
|
+
for (const b of bonds) {
|
|
23353
|
+
const a = atomById.get(b.from);
|
|
23354
|
+
const c = atomById.get(b.to);
|
|
23355
|
+
if (!a || !c) continue;
|
|
23356
|
+
const color = b.color ?? "#6b7280";
|
|
23357
|
+
const from = [a.x, a.y, a.z ?? 0];
|
|
23358
|
+
const to = [c.x, c.y, c.z ?? 0];
|
|
23359
|
+
const perp = bondPerpendicular(from, to);
|
|
23360
|
+
const bondRadius = Math.max(0.06, Math.min(a.radius ?? 0.45, c.radius ?? 0.45) * 0.22);
|
|
23361
|
+
const step = bondRadius * 2.2;
|
|
23362
|
+
const offsets = b.type === "double" ? [-step, step] : b.type === "triple" ? [-step, 0, step] : [0];
|
|
23363
|
+
for (const off of offsets) {
|
|
23364
|
+
const bond = cylinderBetween(
|
|
23365
|
+
[from[0] + perp[0] * off, from[1] + perp[1] * off, from[2] + perp[2] * off],
|
|
23366
|
+
[to[0] + perp[0] * off, to[1] + perp[1] * off, to[2] + perp[2] * off],
|
|
23367
|
+
bondRadius,
|
|
23368
|
+
color
|
|
23369
|
+
);
|
|
23370
|
+
if (bond) out.push(bond);
|
|
23371
|
+
}
|
|
23372
|
+
}
|
|
23373
|
+
for (const a of arrows) {
|
|
23374
|
+
const angle = (a.angle ?? 0) * (Math.PI / 180);
|
|
23375
|
+
const len = a.length ?? 60;
|
|
23376
|
+
const x2 = a.x + Math.cos(angle) * len;
|
|
23377
|
+
const y2 = a.y + Math.sin(angle) * len;
|
|
23378
|
+
const arrow = arrowBetween([a.x, a.y, 0], [x2, y2, 0], a.color ?? "#dc2626");
|
|
23379
|
+
if (arrow) out.push(arrow);
|
|
23380
|
+
if (a.label) {
|
|
23381
|
+
out.push(billboardLabel(a.label, (a.x + x2) / 2, (a.y + y2) / 2, 0, { color: labelColor }));
|
|
23382
|
+
}
|
|
23383
|
+
}
|
|
23384
|
+
for (const a of atoms) {
|
|
23385
|
+
const radius = a.radius ?? 0.45;
|
|
23386
|
+
const az = a.z ?? 0;
|
|
23387
|
+
out.push(meshSphere(a.id, a.x, a.y, az, radius, a.color ?? "#2563eb"));
|
|
23388
|
+
if (a.element) {
|
|
23389
|
+
out.push(billboardLabel(a.element, a.x, a.y, az + radius, { color: labelColor }));
|
|
23390
|
+
}
|
|
23391
|
+
}
|
|
23392
|
+
if (lattice3d) {
|
|
23393
|
+
out.push(...latticeDrawables(lattice3d, { labelColor }));
|
|
23394
|
+
}
|
|
23395
|
+
return out;
|
|
23396
|
+
}, [mode, atoms, bonds, arrows, shapes, containers, lattice3d, animate, backgroundColor]);
|
|
23397
|
+
const atomIndexById = React85.useMemo(() => {
|
|
23398
|
+
const m = /* @__PURE__ */ new Map();
|
|
23399
|
+
atoms.forEach((a, i) => {
|
|
23400
|
+
if (a.id) m.set(a.id, i);
|
|
23401
|
+
});
|
|
23402
|
+
return m;
|
|
23403
|
+
}, [atoms]);
|
|
23404
|
+
if (mode === "3d") {
|
|
23405
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
23406
|
+
LearningScene3D,
|
|
23407
|
+
{
|
|
23408
|
+
className,
|
|
23409
|
+
width,
|
|
23410
|
+
height,
|
|
23411
|
+
title,
|
|
23412
|
+
backgroundColor,
|
|
23413
|
+
drawables: drawables3D,
|
|
23414
|
+
camera,
|
|
23415
|
+
lighting,
|
|
23416
|
+
post,
|
|
23417
|
+
showGrid,
|
|
23418
|
+
shadows,
|
|
23419
|
+
interactive,
|
|
23420
|
+
isLoading,
|
|
23421
|
+
error,
|
|
23422
|
+
onItemClick: get3DClickPayload(onShapeClick, atomIndexById)
|
|
23423
|
+
}
|
|
23424
|
+
);
|
|
23425
|
+
}
|
|
21518
23426
|
return /* @__PURE__ */ jsxRuntime.jsx(Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
|
|
21519
23427
|
title ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", children: title }) : null,
|
|
21520
23428
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -21524,7 +23432,9 @@ var init_ChemistryCanvas = __esm({
|
|
|
21524
23432
|
height,
|
|
21525
23433
|
backgroundColor,
|
|
21526
23434
|
shapes: derivedShapes,
|
|
21527
|
-
|
|
23435
|
+
readouts,
|
|
23436
|
+
traces,
|
|
23437
|
+
interactive: interactive ?? false,
|
|
21528
23438
|
animate,
|
|
21529
23439
|
onShapeClick,
|
|
21530
23440
|
isLoading,
|
|
@@ -27635,6 +29545,10 @@ var init_ProgressDots = __esm({
|
|
|
27635
29545
|
ProgressDots.displayName = "ProgressDots";
|
|
27636
29546
|
}
|
|
27637
29547
|
});
|
|
29548
|
+
function formatTick(v) {
|
|
29549
|
+
if (Number.isInteger(v)) return String(v);
|
|
29550
|
+
return v.toFixed(2).replace(/0+$/, "").replace(/\.$/, "");
|
|
29551
|
+
}
|
|
27638
29552
|
var MathCanvas;
|
|
27639
29553
|
var init_MathCanvas = __esm({
|
|
27640
29554
|
"components/learning/molecules/MathCanvas.tsx"() {
|
|
@@ -27654,10 +29568,19 @@ var init_MathCanvas = __esm({
|
|
|
27654
29568
|
showAxes = true,
|
|
27655
29569
|
showGrid = true,
|
|
27656
29570
|
gridStep = 1,
|
|
29571
|
+
showTickLabels = false,
|
|
29572
|
+
showCurveLabels = false,
|
|
27657
29573
|
curves = [],
|
|
27658
29574
|
points = [],
|
|
27659
29575
|
vectors = [],
|
|
29576
|
+
regions = [],
|
|
29577
|
+
bars = [],
|
|
29578
|
+
guides = [],
|
|
29579
|
+
angles = [],
|
|
29580
|
+
hops = [],
|
|
27660
29581
|
shapes = [],
|
|
29582
|
+
readouts,
|
|
29583
|
+
traces,
|
|
27661
29584
|
interactive = false,
|
|
27662
29585
|
animate = false,
|
|
27663
29586
|
onShapeClick,
|
|
@@ -27671,6 +29594,8 @@ var init_MathCanvas = __esm({
|
|
|
27671
29594
|
const plotH = height - margin * 2;
|
|
27672
29595
|
const mapX = (x) => margin + (x - xMin) / (xMax - xMin) * plotW;
|
|
27673
29596
|
const mapY = (y) => height - (margin + (y - yMin) / (yMax - yMin) * plotH);
|
|
29597
|
+
const xAxisY = Math.max(margin, Math.min(height - margin, mapY(0)));
|
|
29598
|
+
const yAxisX = Math.max(margin, Math.min(width - margin, mapX(0)));
|
|
27674
29599
|
if (showGrid) {
|
|
27675
29600
|
for (let x = Math.ceil(xMin / gridStep) * gridStep; x <= xMax; x += gridStep) {
|
|
27676
29601
|
const px = mapX(x);
|
|
@@ -27681,14 +29606,99 @@ var init_MathCanvas = __esm({
|
|
|
27681
29606
|
out.push({ type: "line", x1: margin, y1: py, x2: width - margin, y2: py, color: "#e5e7eb", lineWidth: 1 });
|
|
27682
29607
|
}
|
|
27683
29608
|
}
|
|
29609
|
+
if (showTickLabels) {
|
|
29610
|
+
const labelEveryX = Math.max(1, Math.ceil((xMax - xMin) / gridStep / Math.floor(plotW / 40)));
|
|
29611
|
+
let kx = 0;
|
|
29612
|
+
for (let x = Math.ceil(xMin / gridStep) * gridStep; x <= xMax; x += gridStep, kx++) {
|
|
29613
|
+
if (kx % labelEveryX === 0 && x !== 0) {
|
|
29614
|
+
out.push({ type: "text", x: mapX(x), y: xAxisY + 12, text: formatTick(x), color: "#6b7280", fontSize: 10, align: "center" });
|
|
29615
|
+
}
|
|
29616
|
+
}
|
|
29617
|
+
const labelEveryY = Math.max(1, Math.ceil((yMax - yMin) / gridStep / Math.floor(plotH / 28)));
|
|
29618
|
+
let ky = 0;
|
|
29619
|
+
for (let y = Math.ceil(yMin / gridStep) * gridStep; y <= yMax; y += gridStep, ky++) {
|
|
29620
|
+
if (ky % labelEveryY === 0 && y !== 0) {
|
|
29621
|
+
out.push({ type: "text", x: yAxisX - 6, y: mapY(y), text: formatTick(y), color: "#6b7280", fontSize: 10, align: "right" });
|
|
29622
|
+
}
|
|
29623
|
+
}
|
|
29624
|
+
if (xMin <= 0 && xMax >= 0 && yMin <= 0 && yMax >= 0) {
|
|
29625
|
+
out.push({ type: "text", x: yAxisX - 6, y: xAxisY + 12, text: "0", color: "#6b7280", fontSize: 10, align: "right" });
|
|
29626
|
+
}
|
|
29627
|
+
}
|
|
29628
|
+
for (const region of regions) {
|
|
29629
|
+
if (!region.samples || region.samples.length === 0) continue;
|
|
29630
|
+
const baseline = region.baseline ?? 0;
|
|
29631
|
+
const clampedPoint = (p) => ({
|
|
29632
|
+
x: mapX(Math.min(xMax, Math.max(xMin, p.x))),
|
|
29633
|
+
y: mapY(Math.min(yMax, Math.max(yMin, p.y)))
|
|
29634
|
+
});
|
|
29635
|
+
const upper = region.samples.map(clampedPoint);
|
|
29636
|
+
const first = region.samples[0];
|
|
29637
|
+
const last = region.samples[region.samples.length - 1];
|
|
29638
|
+
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 })];
|
|
29639
|
+
const color = region.color ?? "#2563eb";
|
|
29640
|
+
out.push({
|
|
29641
|
+
type: "polygon",
|
|
29642
|
+
points: [...upper, ...closing],
|
|
29643
|
+
fill: color,
|
|
29644
|
+
color,
|
|
29645
|
+
opacity: region.opacity ?? 0.2,
|
|
29646
|
+
lineWidth: 1
|
|
29647
|
+
});
|
|
29648
|
+
if (region.label) {
|
|
29649
|
+
const mid = Math.floor(region.samples.length / 2);
|
|
29650
|
+
out.push({
|
|
29651
|
+
type: "text",
|
|
29652
|
+
x: mapX((first.x + last.x) / 2),
|
|
29653
|
+
y: (mapY(region.samples[mid].y) + mapY(baseline)) / 2,
|
|
29654
|
+
text: region.label,
|
|
29655
|
+
color: "#111827",
|
|
29656
|
+
fontSize: 11
|
|
29657
|
+
});
|
|
29658
|
+
}
|
|
29659
|
+
}
|
|
29660
|
+
for (const bar of bars) {
|
|
29661
|
+
if (bar.x + bar.width < xMin || bar.x > xMax) continue;
|
|
29662
|
+
const y0 = bar.y0 ?? 0;
|
|
29663
|
+
const color = bar.color ?? "#93c5fd";
|
|
29664
|
+
out.push({
|
|
29665
|
+
type: "rect",
|
|
29666
|
+
x: mapX(bar.x),
|
|
29667
|
+
y: mapY(Math.max(y0, bar.y1)),
|
|
29668
|
+
width: mapX(bar.x + bar.width) - mapX(bar.x),
|
|
29669
|
+
height: Math.abs(mapY(bar.y1) - mapY(y0)),
|
|
29670
|
+
color,
|
|
29671
|
+
fill: color,
|
|
29672
|
+
opacity: bar.opacity ?? 0.5,
|
|
29673
|
+
lineWidth: 1
|
|
29674
|
+
});
|
|
29675
|
+
}
|
|
27684
29676
|
if (showAxes) {
|
|
27685
|
-
const xAxisY = Math.max(margin, Math.min(height - margin, mapY(0)));
|
|
27686
|
-
const yAxisX = Math.max(margin, Math.min(width - margin, mapX(0)));
|
|
27687
29677
|
out.push({ type: "line", x1: margin, y1: xAxisY, x2: width - margin, y2: xAxisY, color: "#374151", lineWidth: 2 });
|
|
27688
29678
|
out.push({ type: "line", x1: yAxisX, y1: margin, x2: yAxisX, y2: height - margin, color: "#374151", lineWidth: 2 });
|
|
27689
29679
|
}
|
|
29680
|
+
for (const guide of guides) {
|
|
29681
|
+
const color = guide.color ?? "#9ca3af";
|
|
29682
|
+
const dash = guide.dash ?? "dashed";
|
|
29683
|
+
if (guide.kind === "vline") {
|
|
29684
|
+
if (guide.at < xMin || guide.at > xMax) continue;
|
|
29685
|
+
const px = mapX(guide.at);
|
|
29686
|
+
out.push({ type: "line", x1: px, y1: margin, x2: px, y2: height - margin, color, dash });
|
|
29687
|
+
if (guide.label) {
|
|
29688
|
+
out.push({ type: "text", x: px + 4, y: margin + 10, text: guide.label, color: "#111827", fontSize: 11 });
|
|
29689
|
+
}
|
|
29690
|
+
} else {
|
|
29691
|
+
if (guide.at < yMin || guide.at > yMax) continue;
|
|
29692
|
+
const py = mapY(guide.at);
|
|
29693
|
+
out.push({ type: "line", x1: margin, y1: py, x2: width - margin, y2: py, color, dash });
|
|
29694
|
+
if (guide.label) {
|
|
29695
|
+
out.push({ type: "text", x: width - margin - 4, y: py - 8, text: guide.label, color: "#111827", fontSize: 11, align: "right" });
|
|
29696
|
+
}
|
|
29697
|
+
}
|
|
29698
|
+
}
|
|
27690
29699
|
for (const curve of curves) {
|
|
27691
29700
|
if (!curve.samples || curve.samples.length < 2) continue;
|
|
29701
|
+
let lastInRange;
|
|
27692
29702
|
for (let i = 1; i < curve.samples.length; i++) {
|
|
27693
29703
|
const a = curve.samples[i - 1];
|
|
27694
29704
|
const b = curve.samples[i];
|
|
@@ -27700,19 +29710,97 @@ var init_MathCanvas = __esm({
|
|
|
27700
29710
|
x2: mapX(b.x),
|
|
27701
29711
|
y2: mapY(b.y),
|
|
27702
29712
|
color: curve.color ?? "#2563eb",
|
|
27703
|
-
lineWidth: 2
|
|
29713
|
+
lineWidth: 2,
|
|
29714
|
+
dash: curve.dash
|
|
29715
|
+
});
|
|
29716
|
+
lastInRange = b;
|
|
29717
|
+
}
|
|
29718
|
+
if (showCurveLabels && curve.label && lastInRange) {
|
|
29719
|
+
out.push({
|
|
29720
|
+
type: "text",
|
|
29721
|
+
x: mapX(lastInRange.x) + 6,
|
|
29722
|
+
y: mapY(lastInRange.y) - 6,
|
|
29723
|
+
text: curve.label,
|
|
29724
|
+
color: curve.color ?? "#2563eb",
|
|
29725
|
+
fontSize: 11
|
|
29726
|
+
});
|
|
29727
|
+
}
|
|
29728
|
+
}
|
|
29729
|
+
for (const hop of hops) {
|
|
29730
|
+
const x1 = mapX(hop.from);
|
|
29731
|
+
const x2 = mapX(hop.to);
|
|
29732
|
+
const peak = Math.min(36, plotH * 0.3);
|
|
29733
|
+
const color = hop.color ?? "#7c3aed";
|
|
29734
|
+
out.push({
|
|
29735
|
+
type: "ellipse",
|
|
29736
|
+
x: (x1 + x2) / 2,
|
|
29737
|
+
y: xAxisY,
|
|
29738
|
+
width: Math.abs(x2 - x1),
|
|
29739
|
+
height: 2 * peak,
|
|
29740
|
+
startAngle: 180,
|
|
29741
|
+
endAngle: 360,
|
|
29742
|
+
color
|
|
29743
|
+
});
|
|
29744
|
+
const s = Math.sign(hop.to - hop.from);
|
|
29745
|
+
out.push({
|
|
29746
|
+
type: "polygon",
|
|
29747
|
+
points: [
|
|
29748
|
+
{ x: x2, y: xAxisY },
|
|
29749
|
+
{ x: x2 - 4 * s, y: xAxisY - 7 },
|
|
29750
|
+
{ x: x2 + 2 * s, y: xAxisY - 7 }
|
|
29751
|
+
],
|
|
29752
|
+
fill: color,
|
|
29753
|
+
color
|
|
29754
|
+
});
|
|
29755
|
+
if (hop.label) {
|
|
29756
|
+
out.push({
|
|
29757
|
+
type: "text",
|
|
29758
|
+
x: (x1 + x2) / 2,
|
|
29759
|
+
y: xAxisY - peak - 8,
|
|
29760
|
+
text: hop.label,
|
|
29761
|
+
color: "#111827",
|
|
29762
|
+
fontSize: 10,
|
|
29763
|
+
align: "center"
|
|
29764
|
+
});
|
|
29765
|
+
}
|
|
29766
|
+
}
|
|
29767
|
+
for (const angle of angles) {
|
|
29768
|
+
const radius = angle.radius ?? 0.8;
|
|
29769
|
+
const color = angle.color ?? "#0ea5e9";
|
|
29770
|
+
out.push({
|
|
29771
|
+
type: "ellipse",
|
|
29772
|
+
x: mapX(angle.x),
|
|
29773
|
+
y: mapY(angle.y),
|
|
29774
|
+
width: 2 * radius * plotW / (xMax - xMin),
|
|
29775
|
+
height: 2 * radius * plotH / (yMax - yMin),
|
|
29776
|
+
startAngle: -angle.to,
|
|
29777
|
+
endAngle: -angle.from,
|
|
29778
|
+
color
|
|
29779
|
+
});
|
|
29780
|
+
if (angle.label) {
|
|
29781
|
+
const mid = (angle.from + angle.to) / 2;
|
|
29782
|
+
const rad = mid * Math.PI / 180;
|
|
29783
|
+
out.push({
|
|
29784
|
+
type: "text",
|
|
29785
|
+
x: mapX(angle.x + 1.35 * radius * Math.cos(rad)),
|
|
29786
|
+
y: mapY(angle.y + 1.35 * radius * Math.sin(rad)),
|
|
29787
|
+
text: angle.label,
|
|
29788
|
+
color: "#111827",
|
|
29789
|
+
fontSize: 11,
|
|
29790
|
+
align: "center"
|
|
27704
29791
|
});
|
|
27705
29792
|
}
|
|
27706
29793
|
}
|
|
27707
29794
|
for (const p of points) {
|
|
27708
29795
|
if (p.x < xMin || p.x > xMax || p.y < yMin || p.y > yMax) continue;
|
|
29796
|
+
const isOpen = p.style === "open";
|
|
27709
29797
|
out.push({
|
|
27710
29798
|
type: "circle",
|
|
27711
29799
|
x: mapX(p.x),
|
|
27712
29800
|
y: mapY(p.y),
|
|
27713
29801
|
radius: p.radius ?? 4,
|
|
27714
29802
|
color: p.color ?? "#dc2626",
|
|
27715
|
-
fill: p.color ?? "#dc2626"
|
|
29803
|
+
fill: isOpen ? "#ffffff" : p.color ?? "#dc2626"
|
|
27716
29804
|
});
|
|
27717
29805
|
if (p.label) {
|
|
27718
29806
|
out.push({ type: "text", x: mapX(p.x) + 8, y: mapY(p.y) - 8, text: p.label, color: "#111827", fontSize: 12 });
|
|
@@ -27731,7 +29819,28 @@ var init_MathCanvas = __esm({
|
|
|
27731
29819
|
}
|
|
27732
29820
|
out.push(...shapes);
|
|
27733
29821
|
return out;
|
|
27734
|
-
}, [
|
|
29822
|
+
}, [
|
|
29823
|
+
width,
|
|
29824
|
+
height,
|
|
29825
|
+
xMin,
|
|
29826
|
+
xMax,
|
|
29827
|
+
yMin,
|
|
29828
|
+
yMax,
|
|
29829
|
+
showAxes,
|
|
29830
|
+
showGrid,
|
|
29831
|
+
gridStep,
|
|
29832
|
+
showTickLabels,
|
|
29833
|
+
showCurveLabels,
|
|
29834
|
+
curves,
|
|
29835
|
+
points,
|
|
29836
|
+
vectors,
|
|
29837
|
+
regions,
|
|
29838
|
+
bars,
|
|
29839
|
+
guides,
|
|
29840
|
+
angles,
|
|
29841
|
+
hops,
|
|
29842
|
+
shapes
|
|
29843
|
+
]);
|
|
27735
29844
|
return /* @__PURE__ */ jsxRuntime.jsx(Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
|
|
27736
29845
|
title ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", children: title }) : null,
|
|
27737
29846
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -27740,6 +29849,8 @@ var init_MathCanvas = __esm({
|
|
|
27740
29849
|
width,
|
|
27741
29850
|
height,
|
|
27742
29851
|
shapes: derivedShapes,
|
|
29852
|
+
readouts,
|
|
29853
|
+
traces,
|
|
27743
29854
|
interactive,
|
|
27744
29855
|
animate,
|
|
27745
29856
|
onShapeClick,
|
|
@@ -27751,27 +29862,355 @@ var init_MathCanvas = __esm({
|
|
|
27751
29862
|
};
|
|
27752
29863
|
}
|
|
27753
29864
|
});
|
|
27754
|
-
|
|
29865
|
+
function formatMeterValue(v) {
|
|
29866
|
+
return Number.isInteger(v) ? String(v) : String(Number(v.toFixed(2)));
|
|
29867
|
+
}
|
|
29868
|
+
function sceneObjectShapes(obj, canvasWidth, canvasHeight) {
|
|
29869
|
+
const out = [];
|
|
29870
|
+
const color = obj.color ?? "#334155";
|
|
29871
|
+
switch (obj.kind) {
|
|
29872
|
+
case "ground": {
|
|
29873
|
+
const xStart = obj.x1 ?? 0;
|
|
29874
|
+
const xEnd = obj.x2 ?? canvasWidth;
|
|
29875
|
+
const y = obj.y ?? 0;
|
|
29876
|
+
out.push({ type: "line", x1: xStart, y1: y, x2: xEnd, y2: y, color, lineWidth: 2 });
|
|
29877
|
+
for (let hx = xStart + 7; hx <= xEnd; hx += 14) {
|
|
29878
|
+
out.push({ type: "line", x1: hx, y1: y, x2: hx - 7, y2: y + 7, color, lineWidth: 1 });
|
|
29879
|
+
}
|
|
29880
|
+
if (obj.label) {
|
|
29881
|
+
out.push({
|
|
29882
|
+
type: "text",
|
|
29883
|
+
x: (xStart + xEnd) / 2,
|
|
29884
|
+
y: y - 10,
|
|
29885
|
+
text: obj.label,
|
|
29886
|
+
color: PHYSICS_LABEL_COLOR,
|
|
29887
|
+
fontSize: 11,
|
|
29888
|
+
align: "center"
|
|
29889
|
+
});
|
|
29890
|
+
}
|
|
29891
|
+
break;
|
|
29892
|
+
}
|
|
29893
|
+
case "wall": {
|
|
29894
|
+
const yStart = obj.y1 ?? 0;
|
|
29895
|
+
const yEnd = obj.y2 ?? canvasHeight;
|
|
29896
|
+
const x = obj.x ?? 0;
|
|
29897
|
+
out.push({ type: "line", x1: x, y1: yStart, x2: x, y2: yEnd, color, lineWidth: 2 });
|
|
29898
|
+
for (let hy = yStart + 7; hy <= yEnd; hy += 14) {
|
|
29899
|
+
out.push({ type: "line", x1: x, y1: hy, x2: x - 7, y2: hy + 7, color, lineWidth: 1 });
|
|
29900
|
+
}
|
|
29901
|
+
if (obj.label) {
|
|
29902
|
+
out.push({
|
|
29903
|
+
type: "text",
|
|
29904
|
+
x: x + 12,
|
|
29905
|
+
y: (yStart + yEnd) / 2,
|
|
29906
|
+
text: obj.label,
|
|
29907
|
+
color: PHYSICS_LABEL_COLOR,
|
|
29908
|
+
fontSize: 11,
|
|
29909
|
+
align: "left"
|
|
29910
|
+
});
|
|
29911
|
+
}
|
|
29912
|
+
break;
|
|
29913
|
+
}
|
|
29914
|
+
case "ramp": {
|
|
29915
|
+
const x1 = obj.x1 ?? 0;
|
|
29916
|
+
const y1 = obj.y1 ?? 0;
|
|
29917
|
+
const x2 = obj.x2 ?? canvasWidth;
|
|
29918
|
+
const y2 = obj.y2 ?? canvasHeight;
|
|
29919
|
+
out.push({
|
|
29920
|
+
type: "polygon",
|
|
29921
|
+
points: [
|
|
29922
|
+
{ x: x1, y: y1 },
|
|
29923
|
+
{ x: x2, y: y2 },
|
|
29924
|
+
{ x: x1, y: y2 }
|
|
29925
|
+
],
|
|
29926
|
+
color,
|
|
29927
|
+
fill: obj.fill ?? "#e2e8f0",
|
|
29928
|
+
lineWidth: 2
|
|
29929
|
+
});
|
|
29930
|
+
if (obj.label) {
|
|
29931
|
+
out.push({
|
|
29932
|
+
type: "text",
|
|
29933
|
+
x: (2 * x1 + x2) / 3,
|
|
29934
|
+
y: (y1 + 2 * y2) / 3,
|
|
29935
|
+
text: obj.label,
|
|
29936
|
+
color: PHYSICS_LABEL_COLOR,
|
|
29937
|
+
fontSize: 11,
|
|
29938
|
+
align: "center"
|
|
29939
|
+
});
|
|
29940
|
+
}
|
|
29941
|
+
break;
|
|
29942
|
+
}
|
|
29943
|
+
case "box": {
|
|
29944
|
+
const x = obj.x ?? 0;
|
|
29945
|
+
const y = obj.y ?? 0;
|
|
29946
|
+
const w = obj.width ?? 40;
|
|
29947
|
+
const h = obj.height ?? 40;
|
|
29948
|
+
out.push({ type: "rect", x, y, width: w, height: h, color, fill: obj.fill, lineWidth: 2 });
|
|
29949
|
+
if (obj.label) {
|
|
29950
|
+
out.push({
|
|
29951
|
+
type: "text",
|
|
29952
|
+
x: x + w / 2,
|
|
29953
|
+
y: y + h / 2,
|
|
29954
|
+
text: obj.label,
|
|
29955
|
+
color: PHYSICS_LABEL_COLOR,
|
|
29956
|
+
fontSize: 11,
|
|
29957
|
+
align: "center"
|
|
29958
|
+
});
|
|
29959
|
+
}
|
|
29960
|
+
break;
|
|
29961
|
+
}
|
|
29962
|
+
case "pivot": {
|
|
29963
|
+
const x = obj.x ?? 0;
|
|
29964
|
+
const y = obj.y ?? 0;
|
|
29965
|
+
out.push({ type: "circle", x, y, radius: 5, color, fill: color });
|
|
29966
|
+
out.push({ type: "line", x1: x - 14, y1: y - 8, x2: x + 14, y2: y - 8, color, lineWidth: 1 });
|
|
29967
|
+
for (let k = 0; k < 5; k++) {
|
|
29968
|
+
const hx = x - 14 + 7 * k;
|
|
29969
|
+
out.push({ type: "line", x1: hx, y1: y - 8, x2: hx - 6, y2: y - 14, color, lineWidth: 1 });
|
|
29970
|
+
}
|
|
29971
|
+
if (obj.label) {
|
|
29972
|
+
out.push({
|
|
29973
|
+
type: "text",
|
|
29974
|
+
x,
|
|
29975
|
+
y: y - 20,
|
|
29976
|
+
text: obj.label,
|
|
29977
|
+
color: PHYSICS_LABEL_COLOR,
|
|
29978
|
+
fontSize: 11,
|
|
29979
|
+
align: "center"
|
|
29980
|
+
});
|
|
29981
|
+
}
|
|
29982
|
+
break;
|
|
29983
|
+
}
|
|
29984
|
+
}
|
|
29985
|
+
return out;
|
|
29986
|
+
}
|
|
29987
|
+
function trailShapes(trail) {
|
|
29988
|
+
const n = trail.points.length;
|
|
29989
|
+
if (n < 2) return [];
|
|
29990
|
+
const color = trail.color ?? "#94a3b8";
|
|
29991
|
+
const lineWidth = trail.width ?? 2;
|
|
29992
|
+
const fade = trail.fade ?? true;
|
|
29993
|
+
const globalOpacity = trail.opacity ?? 1;
|
|
29994
|
+
const out = [];
|
|
29995
|
+
for (let i = 0; i < n - 1; i++) {
|
|
29996
|
+
const a = trail.points[i];
|
|
29997
|
+
const b = trail.points[i + 1];
|
|
29998
|
+
const segmentOpacity = fade ? 0.12 + 0.68 * i / (n - 1) : 0.6;
|
|
29999
|
+
out.push({
|
|
30000
|
+
type: "line",
|
|
30001
|
+
x1: a.x,
|
|
30002
|
+
y1: a.y,
|
|
30003
|
+
x2: b.x,
|
|
30004
|
+
y2: b.y,
|
|
30005
|
+
color,
|
|
30006
|
+
lineWidth,
|
|
30007
|
+
opacity: segmentOpacity * globalOpacity
|
|
30008
|
+
});
|
|
30009
|
+
}
|
|
30010
|
+
return out;
|
|
30011
|
+
}
|
|
30012
|
+
function constraintShapes(c, a, b) {
|
|
30013
|
+
const color = c.color ?? "#9ca3af";
|
|
30014
|
+
const kind = c.kind ?? "rod";
|
|
30015
|
+
if (kind === "rod") {
|
|
30016
|
+
return [{ type: "line", x1: a.x, y1: a.y, x2: b.x, y2: b.y, color, lineWidth: 2 }];
|
|
30017
|
+
}
|
|
30018
|
+
if (kind === "string") {
|
|
30019
|
+
return [{ type: "line", x1: a.x, y1: a.y, x2: b.x, y2: b.y, color, lineWidth: 2, dash: "dashed" }];
|
|
30020
|
+
}
|
|
30021
|
+
const COILS = 8;
|
|
30022
|
+
const AMP = 7;
|
|
30023
|
+
const LEAD = 10;
|
|
30024
|
+
const dx = b.x - a.x;
|
|
30025
|
+
const dy = b.y - a.y;
|
|
30026
|
+
const dist = Math.max(1e-6, Math.hypot(dx, dy));
|
|
30027
|
+
const ux = dx / dist;
|
|
30028
|
+
const uy = dy / dist;
|
|
30029
|
+
const perpX = -uy;
|
|
30030
|
+
const perpY = ux;
|
|
30031
|
+
const aPrime = { x: a.x + LEAD * ux, y: a.y + LEAD * uy };
|
|
30032
|
+
const bPrime = { x: b.x - LEAD * ux, y: b.y - LEAD * uy };
|
|
30033
|
+
const m = 2 * COILS;
|
|
30034
|
+
const polyline = [{ x: a.x, y: a.y }, aPrime];
|
|
30035
|
+
for (let j = 1; j <= m; j++) {
|
|
30036
|
+
const t = j / (m + 1);
|
|
30037
|
+
const baseX = aPrime.x + t * (bPrime.x - aPrime.x);
|
|
30038
|
+
const baseY = aPrime.y + t * (bPrime.y - aPrime.y);
|
|
30039
|
+
const sign = j % 2 === 0 ? 1 : -1;
|
|
30040
|
+
polyline.push({ x: baseX + sign * AMP * perpX, y: baseY + sign * AMP * perpY });
|
|
30041
|
+
}
|
|
30042
|
+
polyline.push(bPrime, { x: b.x, y: b.y });
|
|
30043
|
+
const out = [];
|
|
30044
|
+
for (let i = 1; i < polyline.length; i++) {
|
|
30045
|
+
out.push({
|
|
30046
|
+
type: "line",
|
|
30047
|
+
x1: polyline[i - 1].x,
|
|
30048
|
+
y1: polyline[i - 1].y,
|
|
30049
|
+
x2: polyline[i].x,
|
|
30050
|
+
y2: polyline[i].y,
|
|
30051
|
+
color,
|
|
30052
|
+
lineWidth: 2
|
|
30053
|
+
});
|
|
30054
|
+
}
|
|
30055
|
+
return out;
|
|
30056
|
+
}
|
|
30057
|
+
function vectorShapes(v, bodyById) {
|
|
30058
|
+
let ax;
|
|
30059
|
+
let ay;
|
|
30060
|
+
if (v.body) {
|
|
30061
|
+
const anchor = bodyById.get(v.body);
|
|
30062
|
+
if (!anchor) return [];
|
|
30063
|
+
ax = anchor.x;
|
|
30064
|
+
ay = anchor.y;
|
|
30065
|
+
} else {
|
|
30066
|
+
ax = v.x ?? 0;
|
|
30067
|
+
ay = v.y ?? 0;
|
|
30068
|
+
}
|
|
30069
|
+
const scale = v.scale ?? 1;
|
|
30070
|
+
const color = v.color ?? "#dc2626";
|
|
30071
|
+
const tx = ax + v.dx * scale;
|
|
30072
|
+
const ty = ay + v.dy * scale;
|
|
30073
|
+
const out = [{ type: "arrow", x1: ax, y1: ay, x2: tx, y2: ty, color, lineWidth: 2, dash: v.dash }];
|
|
30074
|
+
if (v.label) {
|
|
30075
|
+
const dist = Math.max(1e-6, Math.hypot(tx - ax, ty - ay));
|
|
30076
|
+
const ux = (tx - ax) / dist;
|
|
30077
|
+
const uy = (ty - ay) / dist;
|
|
30078
|
+
out.push({
|
|
30079
|
+
type: "text",
|
|
30080
|
+
x: tx + 8 * ux,
|
|
30081
|
+
y: ty + 8 * uy,
|
|
30082
|
+
text: v.label,
|
|
30083
|
+
color,
|
|
30084
|
+
fontSize: 11,
|
|
30085
|
+
align: "center"
|
|
30086
|
+
});
|
|
30087
|
+
}
|
|
30088
|
+
return out;
|
|
30089
|
+
}
|
|
30090
|
+
function angleMarkerShapes(a) {
|
|
30091
|
+
const radius = a.radius ?? 26;
|
|
30092
|
+
const color = a.color ?? "#0ea5e9";
|
|
30093
|
+
const out = [
|
|
30094
|
+
{
|
|
30095
|
+
type: "ellipse",
|
|
30096
|
+
x: a.x,
|
|
30097
|
+
y: a.y,
|
|
30098
|
+
width: radius * 2,
|
|
30099
|
+
height: radius * 2,
|
|
30100
|
+
startAngle: a.from,
|
|
30101
|
+
endAngle: a.to,
|
|
30102
|
+
color,
|
|
30103
|
+
lineWidth: 2
|
|
30104
|
+
}
|
|
30105
|
+
];
|
|
30106
|
+
if (a.label) {
|
|
30107
|
+
const mid = (a.from + a.to) / 2 * (Math.PI / 180);
|
|
30108
|
+
out.push({
|
|
30109
|
+
type: "text",
|
|
30110
|
+
x: a.x + (radius + 13) * Math.cos(mid),
|
|
30111
|
+
y: a.y + (radius + 13) * Math.sin(mid),
|
|
30112
|
+
text: a.label,
|
|
30113
|
+
color,
|
|
30114
|
+
fontSize: 11,
|
|
30115
|
+
align: "center"
|
|
30116
|
+
});
|
|
30117
|
+
}
|
|
30118
|
+
return out;
|
|
30119
|
+
}
|
|
30120
|
+
function fieldShapes(field, canvasWidth, canvasHeight) {
|
|
30121
|
+
const spacing = field.spacing ?? 48;
|
|
30122
|
+
const size = field.size ?? 14;
|
|
30123
|
+
const color = field.color ?? "#94a3b8";
|
|
30124
|
+
const regionX = field.x ?? 0;
|
|
30125
|
+
const regionY = field.y ?? 0;
|
|
30126
|
+
const regionW = field.width ?? canvasWidth;
|
|
30127
|
+
const regionH = field.height ?? canvasHeight;
|
|
30128
|
+
const out = [];
|
|
30129
|
+
for (let gx = regionX + spacing / 2; gx < regionX + regionW; gx += spacing) {
|
|
30130
|
+
for (let gy = regionY + spacing / 2; gy < regionY + regionH; gy += spacing) {
|
|
30131
|
+
if (field.kind === "arrows") {
|
|
30132
|
+
const rad = (field.angle ?? 0) * Math.PI / 180;
|
|
30133
|
+
const hx = Math.cos(rad) * size / 2;
|
|
30134
|
+
const hy = Math.sin(rad) * size / 2;
|
|
30135
|
+
out.push({ type: "arrow", x1: gx - hx, y1: gy - hy, x2: gx + hx, y2: gy + hy, color, lineWidth: 2 });
|
|
30136
|
+
} else if (field.kind === "into") {
|
|
30137
|
+
const r = size / 3;
|
|
30138
|
+
const d = 0.6 * r * Math.SQRT1_2;
|
|
30139
|
+
out.push({ type: "circle", x: gx, y: gy, radius: r, color });
|
|
30140
|
+
out.push({ type: "line", x1: gx - d, y1: gy - d, x2: gx + d, y2: gy + d, color, lineWidth: 1 });
|
|
30141
|
+
out.push({ type: "line", x1: gx - d, y1: gy + d, x2: gx + d, y2: gy - d, color, lineWidth: 1 });
|
|
30142
|
+
} else {
|
|
30143
|
+
const r = size / 3;
|
|
30144
|
+
out.push({ type: "circle", x: gx, y: gy, radius: r, color });
|
|
30145
|
+
out.push({ type: "circle", x: gx, y: gy, radius: 1.5, color, fill: color });
|
|
30146
|
+
}
|
|
30147
|
+
}
|
|
30148
|
+
}
|
|
30149
|
+
return out;
|
|
30150
|
+
}
|
|
30151
|
+
function meterShapes(meters, canvasHeight) {
|
|
30152
|
+
const n = meters.length;
|
|
30153
|
+
const out = [];
|
|
30154
|
+
const sharedMax = Math.max(1e-6, ...meters.map((m) => m.value));
|
|
30155
|
+
meters.forEach((meter, i) => {
|
|
30156
|
+
const rowY = canvasHeight - 10 - 16 * (n - i);
|
|
30157
|
+
const color = meter.color ?? "#3b82f6";
|
|
30158
|
+
const M = meter.max ?? sharedMax;
|
|
30159
|
+
const w = Math.round(Math.min(1, Math.max(0, meter.value / M)) * 110);
|
|
30160
|
+
out.push({ type: "text", x: 8, y: rowY + 8, text: meter.label, color: PHYSICS_LABEL_COLOR, fontSize: 10 });
|
|
30161
|
+
out.push({ type: "rect", x: 52, y: rowY, width: w, height: 10, color, fill: color });
|
|
30162
|
+
out.push({
|
|
30163
|
+
type: "text",
|
|
30164
|
+
x: 166,
|
|
30165
|
+
y: rowY + 8,
|
|
30166
|
+
text: formatMeterValue(meter.value),
|
|
30167
|
+
color: "#6b7280",
|
|
30168
|
+
fontSize: 9
|
|
30169
|
+
});
|
|
30170
|
+
});
|
|
30171
|
+
return out;
|
|
30172
|
+
}
|
|
30173
|
+
var physicsLog2, PHYSICS_LABEL_COLOR, PhysicsCanvas;
|
|
27755
30174
|
var init_PhysicsCanvas = __esm({
|
|
27756
30175
|
"components/learning/molecules/PhysicsCanvas.tsx"() {
|
|
27757
30176
|
"use client";
|
|
27758
30177
|
init_atoms();
|
|
27759
30178
|
init_Stack();
|
|
27760
30179
|
init_LearningCanvas();
|
|
30180
|
+
init_learningScene3D();
|
|
30181
|
+
physicsLog2 = logger.createLogger("almadar:ui:physics-canvas");
|
|
30182
|
+
PHYSICS_LABEL_COLOR = "#374151";
|
|
27761
30183
|
PhysicsCanvas = ({
|
|
27762
30184
|
className,
|
|
27763
30185
|
width = 600,
|
|
27764
30186
|
height = 400,
|
|
27765
30187
|
title,
|
|
27766
30188
|
backgroundColor,
|
|
30189
|
+
mode = "2d",
|
|
30190
|
+
camera,
|
|
30191
|
+
lighting,
|
|
30192
|
+
post,
|
|
27767
30193
|
bodies = [],
|
|
27768
30194
|
constraints = [],
|
|
27769
30195
|
showVelocity = true,
|
|
27770
30196
|
showForces = false,
|
|
27771
30197
|
velocityScale = 20,
|
|
27772
30198
|
forceScale = 20,
|
|
30199
|
+
sceneObjects = [],
|
|
30200
|
+
trails = [],
|
|
30201
|
+
vectors = [],
|
|
30202
|
+
surface3d,
|
|
30203
|
+
vectors3d = [],
|
|
30204
|
+
vectorScale = 1,
|
|
30205
|
+
angles = [],
|
|
30206
|
+
field,
|
|
30207
|
+
meters = [],
|
|
27773
30208
|
shapes = [],
|
|
27774
|
-
|
|
30209
|
+
readouts,
|
|
30210
|
+
traces,
|
|
30211
|
+
showGrid,
|
|
30212
|
+
shadows,
|
|
30213
|
+
interactive,
|
|
27775
30214
|
animate = false,
|
|
27776
30215
|
onShapeClick,
|
|
27777
30216
|
isLoading,
|
|
@@ -27783,19 +30222,14 @@ var init_PhysicsCanvas = __esm({
|
|
|
27783
30222
|
for (const b of bodies) {
|
|
27784
30223
|
if (b.id) bodyById.set(b.id, b);
|
|
27785
30224
|
}
|
|
30225
|
+
if (field) out.push(...fieldShapes(field, width, height));
|
|
30226
|
+
for (const obj of sceneObjects) out.push(...sceneObjectShapes(obj, width, height));
|
|
30227
|
+
for (const trail of trails) out.push(...trailShapes(trail));
|
|
27786
30228
|
for (const c of constraints) {
|
|
27787
30229
|
const a = bodyById.get(c.from);
|
|
27788
30230
|
const b = bodyById.get(c.to);
|
|
27789
30231
|
if (!a || !b) continue;
|
|
27790
|
-
out.push(
|
|
27791
|
-
type: "line",
|
|
27792
|
-
x1: a.x,
|
|
27793
|
-
y1: a.y,
|
|
27794
|
-
x2: b.x,
|
|
27795
|
-
y2: b.y,
|
|
27796
|
-
color: c.color ?? "#9ca3af",
|
|
27797
|
-
lineWidth: 2
|
|
27798
|
-
});
|
|
30232
|
+
out.push(...constraintShapes(c, a, b));
|
|
27799
30233
|
}
|
|
27800
30234
|
for (const b of bodies) {
|
|
27801
30235
|
out.push({
|
|
@@ -27840,9 +30274,181 @@ var init_PhysicsCanvas = __esm({
|
|
|
27840
30274
|
});
|
|
27841
30275
|
}
|
|
27842
30276
|
}
|
|
30277
|
+
for (const v of vectors) out.push(...vectorShapes(v, bodyById));
|
|
30278
|
+
for (const a of angles) out.push(...angleMarkerShapes(a));
|
|
30279
|
+
if (meters.length > 0) out.push(...meterShapes(meters, height));
|
|
27843
30280
|
out.push(...shapes);
|
|
27844
30281
|
return out;
|
|
27845
|
-
}, [
|
|
30282
|
+
}, [
|
|
30283
|
+
bodies,
|
|
30284
|
+
constraints,
|
|
30285
|
+
showVelocity,
|
|
30286
|
+
showForces,
|
|
30287
|
+
velocityScale,
|
|
30288
|
+
forceScale,
|
|
30289
|
+
sceneObjects,
|
|
30290
|
+
trails,
|
|
30291
|
+
vectors,
|
|
30292
|
+
angles,
|
|
30293
|
+
field,
|
|
30294
|
+
meters,
|
|
30295
|
+
shapes,
|
|
30296
|
+
width,
|
|
30297
|
+
height
|
|
30298
|
+
]);
|
|
30299
|
+
const drawables3D = React85.useMemo(() => {
|
|
30300
|
+
if (mode !== "3d") return [];
|
|
30301
|
+
if (shapes.length > 0) {
|
|
30302
|
+
physicsLog2.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
|
|
30303
|
+
}
|
|
30304
|
+
if (sceneObjects.length > 0) {
|
|
30305
|
+
physicsLog2.debug("sceneObjects ignored in 3D mode (pixel-authored 2D vocabulary)", { count: sceneObjects.length });
|
|
30306
|
+
}
|
|
30307
|
+
if (vectors.length > 0) {
|
|
30308
|
+
physicsLog2.debug("vectors ignored in 3D mode (pixel-authored 2D vocabulary)", { count: vectors.length });
|
|
30309
|
+
}
|
|
30310
|
+
if (angles.length > 0) {
|
|
30311
|
+
physicsLog2.debug("angles ignored in 3D mode (pixel-authored 2D vocabulary)", { count: angles.length });
|
|
30312
|
+
}
|
|
30313
|
+
if (field) {
|
|
30314
|
+
physicsLog2.debug("field ignored in 3D mode (pixel-authored 2D vocabulary)");
|
|
30315
|
+
}
|
|
30316
|
+
if (meters.length > 0) {
|
|
30317
|
+
physicsLog2.debug("meters ignored in 3D mode (pixel-authored 2D vocabulary)", { count: meters.length });
|
|
30318
|
+
}
|
|
30319
|
+
if (animate) {
|
|
30320
|
+
physicsLog2.debug("animate ignored in 3D mode (motion is entity-state driven)");
|
|
30321
|
+
}
|
|
30322
|
+
const out = [];
|
|
30323
|
+
const labelColor = labelColorForBackground(backgroundColor);
|
|
30324
|
+
const bodyById = /* @__PURE__ */ new Map();
|
|
30325
|
+
for (const b of bodies) {
|
|
30326
|
+
if (b.id) bodyById.set(b.id, b);
|
|
30327
|
+
}
|
|
30328
|
+
for (const c of constraints) {
|
|
30329
|
+
const a = bodyById.get(c.from);
|
|
30330
|
+
const b = bodyById.get(c.to);
|
|
30331
|
+
if (!a || !b) continue;
|
|
30332
|
+
const rodRadius = Math.max(0.05, Math.min(a.radius ?? 0.5, b.radius ?? 0.5) * 0.15);
|
|
30333
|
+
const rod = cylinderBetween([a.x, a.y, a.z ?? 0], [b.x, b.y, b.z ?? 0], rodRadius, c.color ?? "#9ca3af");
|
|
30334
|
+
if (rod) out.push(rod);
|
|
30335
|
+
}
|
|
30336
|
+
for (const b of bodies) {
|
|
30337
|
+
const radius = b.radius ?? 0.5;
|
|
30338
|
+
const bz = b.z ?? 0;
|
|
30339
|
+
out.push(meshSphere(b.id, b.x, b.y, bz, radius, b.color ?? "#2563eb"));
|
|
30340
|
+
if (b.label) {
|
|
30341
|
+
out.push(billboardLabel(b.label, b.x, b.y, bz + radius, { color: labelColor }));
|
|
30342
|
+
}
|
|
30343
|
+
const vx = b.vx ?? 0;
|
|
30344
|
+
const vy = b.vy ?? 0;
|
|
30345
|
+
const vz = b.vz ?? 0;
|
|
30346
|
+
const arrowRadius = Math.max(0.05, radius * 0.15);
|
|
30347
|
+
if (showVelocity && (vx !== 0 || vy !== 0 || vz !== 0)) {
|
|
30348
|
+
const arrow = arrowBetween(
|
|
30349
|
+
[b.x, b.y, bz],
|
|
30350
|
+
[b.x + vx * velocityScale, b.y + vy * velocityScale, bz + vz * velocityScale],
|
|
30351
|
+
"#16a34a",
|
|
30352
|
+
arrowRadius
|
|
30353
|
+
);
|
|
30354
|
+
if (arrow) out.push(arrow);
|
|
30355
|
+
}
|
|
30356
|
+
const fx = b.fx ?? 0;
|
|
30357
|
+
const fy = b.fy ?? 0;
|
|
30358
|
+
const fz = b.fz ?? 0;
|
|
30359
|
+
if (showForces && (fx !== 0 || fy !== 0 || fz !== 0)) {
|
|
30360
|
+
const arrow = arrowBetween(
|
|
30361
|
+
[b.x, b.y, bz],
|
|
30362
|
+
[b.x + fx * forceScale, b.y + fy * forceScale, bz + fz * forceScale],
|
|
30363
|
+
"#dc2626",
|
|
30364
|
+
arrowRadius
|
|
30365
|
+
);
|
|
30366
|
+
if (arrow) out.push(arrow);
|
|
30367
|
+
}
|
|
30368
|
+
}
|
|
30369
|
+
for (const trail of trails) {
|
|
30370
|
+
if (trail.fade !== void 0) {
|
|
30371
|
+
physicsLog2.debug("trail.fade ignored in 3D mode (2D-only fade curve \u2014 3D draws an opaque tube)", { id: trail.id });
|
|
30372
|
+
}
|
|
30373
|
+
const points = trail.points.map((p) => [p.x, p.y, p.z ?? 0]);
|
|
30374
|
+
out.push(
|
|
30375
|
+
...polylineTube(points, trail.width ?? 0.05, trail.color ?? "#94a3b8", {
|
|
30376
|
+
...trail.opacity !== void 0 ? { opacity: trail.opacity } : {}
|
|
30377
|
+
})
|
|
30378
|
+
);
|
|
30379
|
+
}
|
|
30380
|
+
if (surface3d) {
|
|
30381
|
+
out.push(...heightFieldMesh(surface3d));
|
|
30382
|
+
}
|
|
30383
|
+
if (vectors3d.length > 0) {
|
|
30384
|
+
out.push(
|
|
30385
|
+
...arrowField(
|
|
30386
|
+
vectors3d.map((v) => ({
|
|
30387
|
+
id: v.id,
|
|
30388
|
+
from: [v.x, v.y, v.z ?? 0],
|
|
30389
|
+
delta: [v.dx, v.dy, v.dz ?? 0],
|
|
30390
|
+
color: v.color,
|
|
30391
|
+
label: v.label,
|
|
30392
|
+
width: v.width
|
|
30393
|
+
})),
|
|
30394
|
+
{ scale: vectorScale, labelColor }
|
|
30395
|
+
)
|
|
30396
|
+
);
|
|
30397
|
+
}
|
|
30398
|
+
return out;
|
|
30399
|
+
}, [
|
|
30400
|
+
mode,
|
|
30401
|
+
bodies,
|
|
30402
|
+
constraints,
|
|
30403
|
+
showVelocity,
|
|
30404
|
+
showForces,
|
|
30405
|
+
velocityScale,
|
|
30406
|
+
forceScale,
|
|
30407
|
+
shapes,
|
|
30408
|
+
sceneObjects,
|
|
30409
|
+
trails,
|
|
30410
|
+
vectors,
|
|
30411
|
+
surface3d,
|
|
30412
|
+
vectors3d,
|
|
30413
|
+
vectorScale,
|
|
30414
|
+
angles,
|
|
30415
|
+
field,
|
|
30416
|
+
meters,
|
|
30417
|
+
animate,
|
|
30418
|
+
backgroundColor
|
|
30419
|
+
]);
|
|
30420
|
+
const bodyIndexById = React85.useMemo(() => {
|
|
30421
|
+
const m = /* @__PURE__ */ new Map();
|
|
30422
|
+
vectors3d.forEach((v, i) => {
|
|
30423
|
+
if (v.id) m.set(v.id, i);
|
|
30424
|
+
});
|
|
30425
|
+
bodies.forEach((b, i) => {
|
|
30426
|
+
if (b.id) m.set(b.id, i);
|
|
30427
|
+
});
|
|
30428
|
+
return m;
|
|
30429
|
+
}, [bodies, vectors3d]);
|
|
30430
|
+
if (mode === "3d") {
|
|
30431
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
30432
|
+
LearningScene3D,
|
|
30433
|
+
{
|
|
30434
|
+
className,
|
|
30435
|
+
width,
|
|
30436
|
+
height,
|
|
30437
|
+
title,
|
|
30438
|
+
backgroundColor,
|
|
30439
|
+
drawables: drawables3D,
|
|
30440
|
+
camera,
|
|
30441
|
+
lighting,
|
|
30442
|
+
post,
|
|
30443
|
+
showGrid,
|
|
30444
|
+
shadows,
|
|
30445
|
+
interactive,
|
|
30446
|
+
isLoading,
|
|
30447
|
+
error,
|
|
30448
|
+
onItemClick: get3DClickPayload(onShapeClick, bodyIndexById)
|
|
30449
|
+
}
|
|
30450
|
+
);
|
|
30451
|
+
}
|
|
27846
30452
|
return /* @__PURE__ */ jsxRuntime.jsx(Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
|
|
27847
30453
|
title ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", children: title }) : null,
|
|
27848
30454
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -27852,7 +30458,9 @@ var init_PhysicsCanvas = __esm({
|
|
|
27852
30458
|
height,
|
|
27853
30459
|
backgroundColor,
|
|
27854
30460
|
shapes: derivedShapes,
|
|
27855
|
-
|
|
30461
|
+
readouts,
|
|
30462
|
+
traces,
|
|
30463
|
+
interactive: interactive ?? false,
|
|
27856
30464
|
animate,
|
|
27857
30465
|
onShapeClick,
|
|
27858
30466
|
isLoading,
|
|
@@ -28002,7 +30610,7 @@ function layoutFlow(nodeIds, adjacency, roots, width, height, margin) {
|
|
|
28002
30610
|
}
|
|
28003
30611
|
return nodeIds.map((id) => positions.get(id));
|
|
28004
30612
|
}
|
|
28005
|
-
function
|
|
30613
|
+
function layoutTree2(nodeIds, adjacency, roots, width, height, margin) {
|
|
28006
30614
|
const effectiveRoots = roots.length > 0 ? roots : [nodeIds[0]];
|
|
28007
30615
|
const layers = assignLayers(nodeIds, adjacency, effectiveRoots);
|
|
28008
30616
|
const maxLayer = Math.max(...Array.from(layers.values()));
|
|
@@ -28058,7 +30666,7 @@ function computeStaticLayout(mode, input) {
|
|
|
28058
30666
|
const adjacency = buildAdjacency(nodeIds, edges);
|
|
28059
30667
|
const roots = findRoots(nodeIds, adjacency);
|
|
28060
30668
|
if (mode === "flow") return layoutFlow(nodeIds, adjacency, roots, width, height, margin);
|
|
28061
|
-
if (mode === "tree") return
|
|
30669
|
+
if (mode === "tree") return layoutTree2(nodeIds, adjacency, roots, width, height, margin);
|
|
28062
30670
|
return layoutRadial(nodeIds, adjacency, roots, width, height, margin);
|
|
28063
30671
|
}
|
|
28064
30672
|
var init_graphViewLayouts = __esm({
|
|
@@ -28470,12 +31078,12 @@ var init_MapView = __esm({
|
|
|
28470
31078
|
shadowSize: [41, 41]
|
|
28471
31079
|
});
|
|
28472
31080
|
L.Marker.prototype.options.icon = defaultIcon;
|
|
28473
|
-
const { useEffect: useEffect60, useRef:
|
|
31081
|
+
const { useEffect: useEffect60, useRef: useRef63, useCallback: useCallback90, useState: useState92 } = React85__namespace.default;
|
|
28474
31082
|
const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
|
|
28475
31083
|
const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
|
|
28476
31084
|
function MapUpdater({ centerLat, centerLng, zoom }) {
|
|
28477
31085
|
const map = useMap();
|
|
28478
|
-
const prevRef =
|
|
31086
|
+
const prevRef = useRef63({ centerLat, centerLng, zoom });
|
|
28479
31087
|
useEffect60(() => {
|
|
28480
31088
|
const prev = prevRef.current;
|
|
28481
31089
|
if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
|
|
@@ -29339,8 +31947,8 @@ function TableView({
|
|
|
29339
31947
|
columns,
|
|
29340
31948
|
fields,
|
|
29341
31949
|
itemActions,
|
|
29342
|
-
maxInlineActions,
|
|
29343
|
-
itemClickEvent,
|
|
31950
|
+
maxInlineActions: _maxInlineActions,
|
|
31951
|
+
itemClickEvent = "",
|
|
29344
31952
|
selectable = false,
|
|
29345
31953
|
selectEvent,
|
|
29346
31954
|
selectedIds,
|
|
@@ -29390,7 +31998,6 @@ function TableView({
|
|
|
29390
31998
|
const hasMore = pageSize > 0 && visibleCount < ordered2.length;
|
|
29391
31999
|
const hasRenderProp = typeof children === "function";
|
|
29392
32000
|
const idField = dndItemIdField ?? "id";
|
|
29393
|
-
const isCoarsePointer = useMediaQuery("(pointer: coarse)");
|
|
29394
32001
|
React85__namespace.default.useEffect(() => {
|
|
29395
32002
|
tableViewLog.debug("render", {
|
|
29396
32003
|
rowCount: data.length,
|
|
@@ -29432,21 +32039,14 @@ function TableView({
|
|
|
29432
32039
|
const dir = sortColumn === (col.field ?? col.key) && sortDirection === "asc" ? "desc" : "asc";
|
|
29433
32040
|
eventBus.emit(`UI:${sortEvent}`, { column: col.field ?? col.key, direction: dir });
|
|
29434
32041
|
};
|
|
29435
|
-
const
|
|
29436
|
-
e.stopPropagation();
|
|
29437
|
-
const payload = {
|
|
29438
|
-
id: row.id,
|
|
29439
|
-
row
|
|
29440
|
-
};
|
|
29441
|
-
eventBus.emit(`UI:${action.event}`, payload);
|
|
29442
|
-
};
|
|
32042
|
+
const rowClickEvent = itemClickEvent || actionDefs.find((a) => a.variant !== "danger")?.event;
|
|
29443
32043
|
const handleRowClick = (row) => () => {
|
|
29444
|
-
if (!
|
|
32044
|
+
if (!rowClickEvent) return;
|
|
29445
32045
|
const payload = {
|
|
29446
32046
|
id: row.id,
|
|
29447
32047
|
row
|
|
29448
32048
|
};
|
|
29449
|
-
eventBus.emit(`UI:${
|
|
32049
|
+
eventBus.emit(`UI:${rowClickEvent}`, payload);
|
|
29450
32050
|
};
|
|
29451
32051
|
const colFloors = React85__namespace.default.useMemo(
|
|
29452
32052
|
() => colDefs.map((col) => {
|
|
@@ -29462,10 +32062,7 @@ function TableView({
|
|
|
29462
32062
|
const statusNode = isLoading ? /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "text-center py-8", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", color: "secondary", children: t("loading.items") }) }) : error ? /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "text-center py-8", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", color: "error", children: error.message }) }) : data.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "text-center py-12", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", color: "secondary", children: emptyMessage || t("empty.noItems") }) }) : null;
|
|
29463
32063
|
const lk = LOOKS[look];
|
|
29464
32064
|
const hasActions = actionDefs.length > 0;
|
|
29465
|
-
const
|
|
29466
|
-
const inlineActionCount = hasActions ? effectiveMaxInline != null ? Math.min(actionDefs.length, effectiveMaxInline) : actionDefs.length : 0;
|
|
29467
|
-
const hasOverflowActions = hasActions && effectiveMaxInline != null && actionDefs.length > effectiveMaxInline;
|
|
29468
|
-
const actionsTrack = hasActions ? `${inlineActionCount * 6 + (hasOverflowActions ? 3 : 0)}rem` : null;
|
|
32065
|
+
const actionsTrack = hasActions ? "3rem" : null;
|
|
29469
32066
|
const gridTemplateColumns = [
|
|
29470
32067
|
selectable ? "auto" : null,
|
|
29471
32068
|
...colDefs.map((c, i) => c.width ?? `minmax(${colFloors[i]}ch, 1fr)`),
|
|
@@ -29512,7 +32109,7 @@ function TableView({
|
|
|
29512
32109
|
col.key
|
|
29513
32110
|
);
|
|
29514
32111
|
}),
|
|
29515
|
-
hasActions && /* @__PURE__ */ jsxRuntime.jsx(Box, { "aria-hidden": true, className: "sticky right-0 bg-[var(--color-surface-subtle)]" })
|
|
32112
|
+
hasActions && /* @__PURE__ */ jsxRuntime.jsx(Box, { "aria-hidden": true, className: "sticky right-0 bg-[var(--color-surface-subtle)] border-l border-[var(--color-border)] h-full" })
|
|
29516
32113
|
]
|
|
29517
32114
|
}
|
|
29518
32115
|
);
|
|
@@ -29524,12 +32121,12 @@ function TableView({
|
|
|
29524
32121
|
role: "row",
|
|
29525
32122
|
"data-entity-row": true,
|
|
29526
32123
|
"data-entity-id": id,
|
|
29527
|
-
onClick:
|
|
32124
|
+
onClick: rowClickEvent ? handleRowClick(row) : void 0,
|
|
29528
32125
|
style: !hasRenderProp ? { gridTemplateColumns } : void 0,
|
|
29529
32126
|
className: cn(
|
|
29530
32127
|
"group items-center gap-3 transition-colors duration-fast",
|
|
29531
32128
|
hasRenderProp ? "flex" : "grid",
|
|
29532
|
-
|
|
32129
|
+
rowClickEvent && "cursor-pointer",
|
|
29533
32130
|
lk.rowPad,
|
|
29534
32131
|
lk.divider && "border-b border-[var(--color-border)]",
|
|
29535
32132
|
lk.striped && index % 2 === 1 && "bg-[var(--color-surface-subtle)]",
|
|
@@ -29537,7 +32134,7 @@ function TableView({
|
|
|
29537
32134
|
look === "bordered" && "[&>*]:border-r [&>*]:border-[var(--color-border)] [&>*:last-child]:border-r-0"
|
|
29538
32135
|
),
|
|
29539
32136
|
children: [
|
|
29540
|
-
selectable && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "flex items-center", onClick:
|
|
32137
|
+
selectable && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "flex items-center", onClick: rowClickEvent ? (e) => e.stopPropagation() : void 0, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
29541
32138
|
Checkbox,
|
|
29542
32139
|
{
|
|
29543
32140
|
checked: selected.has(id),
|
|
@@ -29558,53 +32155,37 @@ function TableView({
|
|
|
29558
32155
|
}
|
|
29559
32156
|
return /* @__PURE__ */ jsxRuntime.jsx(Box, { role: "cell", className: cellBase, children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-foreground", children: formatCell(raw, col.format) }) }, col.key);
|
|
29560
32157
|
}),
|
|
29561
|
-
hasActions && /* @__PURE__ */ jsxRuntime.
|
|
32158
|
+
hasActions && /* @__PURE__ */ jsxRuntime.jsx(
|
|
29562
32159
|
HStack,
|
|
29563
32160
|
{
|
|
29564
32161
|
gap: "xs",
|
|
29565
|
-
onClick:
|
|
32162
|
+
onClick: rowClickEvent ? (e) => e.stopPropagation() : void 0,
|
|
29566
32163
|
className: cn(
|
|
29567
32164
|
// Pinned: the fixed column tracks routinely overflow the caller's
|
|
29568
|
-
// scroll container, which
|
|
29569
|
-
// Opaque so
|
|
32165
|
+
// scroll container, which would leave the kebab off-screen.
|
|
32166
|
+
// Opaque + hairline edge so it reads as a pinned column, not a
|
|
32167
|
+
// floating control, while scrolled cells pass underneath.
|
|
29570
32168
|
"justify-end flex-shrink-0 sticky right-0 z-[1] transition-colors",
|
|
32169
|
+
"border-l border-[var(--color-border)]",
|
|
29571
32170
|
lk.striped && index % 2 === 1 ? "bg-[var(--color-surface-subtle)]" : "bg-[var(--color-card)] group-hover:bg-[var(--color-surface-subtle)]"
|
|
29572
32171
|
),
|
|
29573
|
-
children:
|
|
29574
|
-
|
|
29575
|
-
|
|
29576
|
-
|
|
29577
|
-
|
|
29578
|
-
|
|
29579
|
-
|
|
29580
|
-
|
|
29581
|
-
|
|
29582
|
-
|
|
29583
|
-
|
|
29584
|
-
|
|
29585
|
-
|
|
29586
|
-
|
|
29587
|
-
}
|
|
29588
|
-
|
|
29589
|
-
|
|
29590
|
-
effectiveMaxInline != null && actionDefs.length > effectiveMaxInline && /* @__PURE__ */ jsxRuntime.jsx(
|
|
29591
|
-
Menu,
|
|
29592
|
-
{
|
|
29593
|
-
position: "bottom-end",
|
|
29594
|
-
trigger: /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "more-horizontal", size: "xs" }) }),
|
|
29595
|
-
items: actionDefs.slice(effectiveMaxInline).map((action) => ({
|
|
29596
|
-
label: action.label,
|
|
29597
|
-
icon: action.icon,
|
|
29598
|
-
event: action.event,
|
|
29599
|
-
variant: action.variant === "danger" ? "danger" : "default",
|
|
29600
|
-
onClick: () => eventBus.emit(`UI:${action.event}`, {
|
|
29601
|
-
id: row.id,
|
|
29602
|
-
row
|
|
29603
|
-
})
|
|
29604
|
-
}))
|
|
29605
|
-
}
|
|
29606
|
-
)
|
|
29607
|
-
]
|
|
32172
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
32173
|
+
Menu,
|
|
32174
|
+
{
|
|
32175
|
+
position: "bottom-end",
|
|
32176
|
+
trigger: /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", "data-row-id": String(row.id), children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "more-horizontal", size: "xs" }) }),
|
|
32177
|
+
items: actionDefs.map((action) => ({
|
|
32178
|
+
label: action.label,
|
|
32179
|
+
icon: action.icon,
|
|
32180
|
+
event: action.event,
|
|
32181
|
+
variant: action.variant === "danger" ? "danger" : "default",
|
|
32182
|
+
onClick: () => eventBus.emit(`UI:${action.event}`, {
|
|
32183
|
+
id: row.id,
|
|
32184
|
+
row
|
|
32185
|
+
})
|
|
32186
|
+
}))
|
|
32187
|
+
}
|
|
32188
|
+
)
|
|
29608
32189
|
}
|
|
29609
32190
|
)
|
|
29610
32191
|
]
|
|
@@ -29647,7 +32228,6 @@ var init_TableView = __esm({
|
|
|
29647
32228
|
init_format();
|
|
29648
32229
|
init_getNestedValue();
|
|
29649
32230
|
init_useEventBus();
|
|
29650
|
-
init_useMediaQuery();
|
|
29651
32231
|
init_Box();
|
|
29652
32232
|
init_Stack();
|
|
29653
32233
|
init_Typography();
|
|
@@ -39140,7 +41720,7 @@ function getEnumOptions(field) {
|
|
|
39140
41720
|
}));
|
|
39141
41721
|
}
|
|
39142
41722
|
const validation = field.validation;
|
|
39143
|
-
if (validation?.enum &&
|
|
41723
|
+
if (validation?.enum && validation.enum.length > 0) {
|
|
39144
41724
|
return validation.enum.map((v) => ({
|
|
39145
41725
|
value: v,
|
|
39146
41726
|
label: v.charAt(0).toUpperCase() + v.slice(1).replace(/_/g, " ")
|
|
@@ -44587,6 +47167,7 @@ var init_component_registry_generated = __esm({
|
|
|
44587
47167
|
init_ActionTile();
|
|
44588
47168
|
init_ActivationBlock();
|
|
44589
47169
|
init_ComponentPatterns();
|
|
47170
|
+
init_AlgoGraphCanvas();
|
|
44590
47171
|
init_AlgorithmCanvas();
|
|
44591
47172
|
init_AnimatedCounter();
|
|
44592
47173
|
init_AnimatedGraphic();
|
|
@@ -44850,6 +47431,7 @@ var init_component_registry_generated = __esm({
|
|
|
44850
47431
|
"ActivationBlock": ActivationBlock,
|
|
44851
47432
|
"Alert": AlertPattern,
|
|
44852
47433
|
"AlertPattern": AlertPattern,
|
|
47434
|
+
"AlgoGraphCanvas": AlgoGraphCanvas,
|
|
44853
47435
|
"AlgorithmCanvas": AlgorithmCanvas,
|
|
44854
47436
|
"AnimatedCounter": AnimatedCounter,
|
|
44855
47437
|
"AnimatedGraphic": AnimatedGraphic,
|
|
@@ -46176,9 +48758,9 @@ var log3 = logger.createLogger("almadar:ui:effects:client-handlers");
|
|
|
46176
48758
|
function createClientEffectHandlers(options) {
|
|
46177
48759
|
const { eventBus, slotSetter, navigate, notify, callService, liveEntity } = options;
|
|
46178
48760
|
return {
|
|
46179
|
-
emit: (event, payload) => {
|
|
48761
|
+
emit: (event, payload, source) => {
|
|
46180
48762
|
const prefixedEvent = event.startsWith("UI:") ? event : `UI:${event}`;
|
|
46181
|
-
eventBus.emit(prefixedEvent, payload);
|
|
48763
|
+
eventBus.emit(prefixedEvent, payload, source);
|
|
46182
48764
|
},
|
|
46183
48765
|
persist: async () => {
|
|
46184
48766
|
log3.warn("persist is server-side only, ignored on client");
|
|
@@ -46513,7 +49095,7 @@ function createSharedEntityWriter(binding, tick, traitStatesRef, emit) {
|
|
|
46513
49095
|
}
|
|
46514
49096
|
};
|
|
46515
49097
|
ctx.emit = (event, payload) => {
|
|
46516
|
-
emit(event, payload);
|
|
49098
|
+
emit(event, payload, { trait: traitName, tick: tick.name });
|
|
46517
49099
|
};
|
|
46518
49100
|
if (tick.guard !== void 0 && !evaluator.evaluateGuard(tick.guard, ctx)) {
|
|
46519
49101
|
tickLog.debug("guard-blocked", { traitName, tick: tick.name, state: currentState });
|
|
@@ -46965,6 +49547,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
46965
49547
|
}
|
|
46966
49548
|
const effectContext = {
|
|
46967
49549
|
traitName,
|
|
49550
|
+
orbitalName: orbitalsByTrait?.[traitName],
|
|
46968
49551
|
state: previousState,
|
|
46969
49552
|
transition: `${previousState}->${newState}`,
|
|
46970
49553
|
linkedEntity,
|
|
@@ -47041,7 +49624,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
47041
49624
|
});
|
|
47042
49625
|
}
|
|
47043
49626
|
return emittedDuringExec;
|
|
47044
|
-
}, [eventBus, flushSlot, sharedEntityStore, publishBindingSnapshot]);
|
|
49627
|
+
}, [eventBus, flushSlot, sharedEntityStore, publishBindingSnapshot, orbitalsByTrait]);
|
|
47045
49628
|
const runTickEffects = React85.useCallback((tick, binding) => {
|
|
47046
49629
|
const traitName = binding.trait.name;
|
|
47047
49630
|
const currentState = traitStatesRef.current.get(traitName)?.currentState ?? "";
|
|
@@ -47077,9 +49660,9 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
47077
49660
|
log: tickLog
|
|
47078
49661
|
});
|
|
47079
49662
|
}, [executeTransitionEffects, sharedEntityStore]);
|
|
47080
|
-
const emitFromSharedWriter = React85.useCallback((event, payload) => {
|
|
49663
|
+
const emitFromSharedWriter = React85.useCallback((event, payload, source) => {
|
|
47081
49664
|
const prefixedEvent = event.startsWith("UI:") ? event : `UI:${event}`;
|
|
47082
|
-
eventBus.emit(prefixedEvent, payload);
|
|
49665
|
+
eventBus.emit(prefixedEvent, payload, source);
|
|
47083
49666
|
}, [eventBus]);
|
|
47084
49667
|
React85.useEffect(() => {
|
|
47085
49668
|
const scheduler = runtime.createTickScheduler();
|