@almadar/ui 5.148.0 → 5.150.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{GameAudioProvider-CPGwD49P.d.cts → GameAudioProvider-B48iXwz3.d.ts} +2 -50
- package/dist/{GameAudioProvider-CPGwD49P.d.ts → GameAudioProvider-CQAdPreB.d.cts} +2 -50
- package/dist/avl/index.cjs +2741 -158
- package/dist/avl/index.js +2741 -158
- package/dist/{avl-schema-parser-DVmrgdwc.d.cts → avl-schema-parser-CVzkNzg7.d.cts} +18 -9
- package/dist/{avl-schema-parser-BRJ77Yze.d.ts → avl-schema-parser-Cx_4SVg9.d.ts} +18 -9
- package/dist/{cn-BAn68sNO.d.cts → cn-BnAJZcNb.d.cts} +2 -8
- package/dist/{cn-CWxxLkri.d.ts → cn-DJTUjk1M.d.ts} +2 -8
- package/dist/components/index.cjs +2764 -158
- package/dist/components/index.d.cts +1077 -107
- package/dist/components/index.d.ts +1077 -107
- package/dist/components/index.js +2760 -160
- package/dist/hooks/index.cjs +1 -1
- package/dist/hooks/index.js +1 -1
- package/dist/lib/drawable/three/index.cjs +57 -30
- package/dist/lib/drawable/three/index.d.cts +4 -3
- package/dist/lib/drawable/three/index.d.ts +4 -3
- package/dist/lib/drawable/three/index.js +57 -30
- package/dist/lib/index.d.cts +3 -2
- package/dist/lib/index.d.ts +3 -2
- package/dist/marketing/index.cjs +1 -1
- package/dist/marketing/index.js +1 -1
- package/dist/{paintDispatch-Cb_hQj4Y.d.ts → paintDispatch-BjZjUbcb.d.cts} +63 -69
- package/dist/{paintDispatch-Cb_hQj4Y.d.cts → paintDispatch-CxdLjHQq.d.ts} +63 -69
- package/dist/providers/index.cjs +2734 -152
- package/dist/providers/index.d.cts +2 -1
- package/dist/providers/index.d.ts +2 -1
- package/dist/providers/index.js +2734 -152
- package/dist/runtime/index.cjs +2741 -158
- package/dist/runtime/index.d.cts +2 -2
- package/dist/runtime/index.d.ts +2 -2
- package/dist/runtime/index.js +2741 -158
- package/dist/types-B3nHgnMG.d.cts +51 -0
- package/dist/types-B3nHgnMG.d.ts +51 -0
- package/package.json +3 -3
package/dist/providers/index.cjs
CHANGED
|
@@ -207,7 +207,7 @@ function useEventBus() {
|
|
|
207
207
|
return {
|
|
208
208
|
...baseBus,
|
|
209
209
|
emit: (type, payload, source) => {
|
|
210
|
-
if (typeof type === "string" && type.startsWith("UI:") && !type.slice(3).includes(".")) {
|
|
210
|
+
if (typeof type === "string" && type.startsWith("UI:") && !type.slice(3).includes(".") && !source?.trait) {
|
|
211
211
|
scopeLog.warn("emit:bare-key-no-scope", { type });
|
|
212
212
|
}
|
|
213
213
|
baseBus.emit(type, payload, source);
|
|
@@ -10375,9 +10375,10 @@ function Canvas({
|
|
|
10375
10375
|
drawables: [...drawables ?? [], ...childDrawables],
|
|
10376
10376
|
isLoading,
|
|
10377
10377
|
cameraMode: to3DCameraMode(camera?.mode),
|
|
10378
|
-
...zoom !== void 0 ? {
|
|
10378
|
+
...zoom !== void 0 ? { zoom } : {},
|
|
10379
10379
|
...camera?.fov !== void 0 ? { fov: camera.fov } : {},
|
|
10380
10380
|
...camera?.azimuth !== void 0 ? { azimuth: camera.azimuth } : {},
|
|
10381
|
+
...camera?.elevation !== void 0 ? { elevation: camera.elevation } : {},
|
|
10381
10382
|
...camera?.target !== void 0 ? { followTarget: camera.target } : {},
|
|
10382
10383
|
unitScale,
|
|
10383
10384
|
backgroundColor,
|
|
@@ -11645,6 +11646,14 @@ function shapeBounds(shape) {
|
|
|
11645
11646
|
w: shape.radius * 2 + 8,
|
|
11646
11647
|
h: shape.radius * 2 + 8
|
|
11647
11648
|
};
|
|
11649
|
+
case "ellipse":
|
|
11650
|
+
if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) return null;
|
|
11651
|
+
return {
|
|
11652
|
+
x: shape.x - shape.width / 2 - 4,
|
|
11653
|
+
y: shape.y - shape.height / 2 - 4,
|
|
11654
|
+
w: shape.width + 8,
|
|
11655
|
+
h: shape.height + 8
|
|
11656
|
+
};
|
|
11648
11657
|
case "rect":
|
|
11649
11658
|
if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) return null;
|
|
11650
11659
|
return { x: shape.x - 4, y: shape.y - 4, w: shape.width + 8, h: shape.height + 8 };
|
|
@@ -11678,13 +11687,14 @@ function drawArrowHead(ctx, x1, y1, x2, y2, size) {
|
|
|
11678
11687
|
ctx.closePath();
|
|
11679
11688
|
ctx.fill();
|
|
11680
11689
|
}
|
|
11681
|
-
function drawShape(ctx, shape, width, height) {
|
|
11690
|
+
function drawShape(ctx, shape, width, height, allShapes) {
|
|
11682
11691
|
ctx.save();
|
|
11683
11692
|
const opacity = shape.opacity ?? 1;
|
|
11684
11693
|
ctx.globalAlpha = opacity;
|
|
11685
11694
|
const stroke = resolveColor2(shape.color, ctx, "#333333");
|
|
11686
11695
|
const fill = shape.fill ? resolveColor2(shape.fill, ctx, "#cccccc") : void 0;
|
|
11687
11696
|
ctx.lineWidth = shape.lineWidth ?? 2;
|
|
11697
|
+
if (shape.dash) ctx.setLineDash([...DASH_PATTERNS[shape.dash]]);
|
|
11688
11698
|
switch (shape.type) {
|
|
11689
11699
|
case "grid": {
|
|
11690
11700
|
const step = shape.step ?? 40;
|
|
@@ -11750,6 +11760,20 @@ function drawShape(ctx, shape, width, height) {
|
|
|
11750
11760
|
ctx.stroke();
|
|
11751
11761
|
break;
|
|
11752
11762
|
}
|
|
11763
|
+
case "ellipse": {
|
|
11764
|
+
if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) break;
|
|
11765
|
+
const startAngle = (shape.startAngle ?? 0) * Math.PI / 180;
|
|
11766
|
+
const endAngle = (shape.endAngle ?? 360) * Math.PI / 180;
|
|
11767
|
+
ctx.beginPath();
|
|
11768
|
+
ctx.ellipse(shape.x, shape.y, shape.width / 2, shape.height / 2, 0, startAngle, endAngle);
|
|
11769
|
+
if (fill) {
|
|
11770
|
+
ctx.fillStyle = fill;
|
|
11771
|
+
ctx.fill();
|
|
11772
|
+
}
|
|
11773
|
+
ctx.strokeStyle = stroke;
|
|
11774
|
+
ctx.stroke();
|
|
11775
|
+
break;
|
|
11776
|
+
}
|
|
11753
11777
|
case "rect": {
|
|
11754
11778
|
if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) break;
|
|
11755
11779
|
if (fill) {
|
|
@@ -11796,21 +11820,153 @@ function drawShape(ctx, shape, width, height) {
|
|
|
11796
11820
|
ctx.fillText(shape.text, shape.x, shape.y);
|
|
11797
11821
|
break;
|
|
11798
11822
|
}
|
|
11823
|
+
case "venn-region": {
|
|
11824
|
+
const resolveCircles = (ids) => (ids ?? []).flatMap((id) => {
|
|
11825
|
+
const c = allShapes.find((s) => s.type === "circle" && s.id === id);
|
|
11826
|
+
return c && c.x != null && c.y != null && c.radius != null ? [{ x: c.x, y: c.y, radius: c.radius }] : [];
|
|
11827
|
+
});
|
|
11828
|
+
const inside = resolveCircles(shape.inside);
|
|
11829
|
+
if (inside.length === 0) break;
|
|
11830
|
+
const outside = resolveCircles(shape.outside);
|
|
11831
|
+
const off = document.createElement("canvas");
|
|
11832
|
+
off.width = ctx.canvas.width;
|
|
11833
|
+
off.height = ctx.canvas.height;
|
|
11834
|
+
const octx = off.getContext("2d");
|
|
11835
|
+
if (!octx) break;
|
|
11836
|
+
octx.setTransform(ctx.getTransform());
|
|
11837
|
+
for (const c of inside) {
|
|
11838
|
+
const p = new Path2D();
|
|
11839
|
+
p.arc(c.x, c.y, c.radius, 0, Math.PI * 2);
|
|
11840
|
+
octx.clip(p);
|
|
11841
|
+
}
|
|
11842
|
+
octx.fillStyle = fill ?? stroke;
|
|
11843
|
+
octx.fillRect(0, 0, width, height);
|
|
11844
|
+
octx.globalCompositeOperation = "destination-out";
|
|
11845
|
+
for (const c of outside) {
|
|
11846
|
+
const p = new Path2D();
|
|
11847
|
+
p.arc(c.x, c.y, c.radius, 0, Math.PI * 2);
|
|
11848
|
+
octx.fill(p);
|
|
11849
|
+
}
|
|
11850
|
+
ctx.save();
|
|
11851
|
+
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
11852
|
+
ctx.drawImage(off, 0, 0);
|
|
11853
|
+
ctx.restore();
|
|
11854
|
+
break;
|
|
11855
|
+
}
|
|
11799
11856
|
}
|
|
11800
11857
|
ctx.restore();
|
|
11801
11858
|
}
|
|
11802
|
-
|
|
11859
|
+
function readoutShapes(readouts, width) {
|
|
11860
|
+
const out = [];
|
|
11861
|
+
const chipH = 18;
|
|
11862
|
+
const gap = 6;
|
|
11863
|
+
let rightEdge = width - 6;
|
|
11864
|
+
let rowY = 6;
|
|
11865
|
+
for (const readout of readouts) {
|
|
11866
|
+
const text = `${readout.label}: ${String(readout.value)}`;
|
|
11867
|
+
const chipW = Math.min(170, Math.max(34, text.length * 6 + 12));
|
|
11868
|
+
let chipX = rightEdge - chipW;
|
|
11869
|
+
if (chipX < 4) {
|
|
11870
|
+
rowY += chipH + 4;
|
|
11871
|
+
rightEdge = width - 6;
|
|
11872
|
+
chipX = rightEdge - chipW;
|
|
11873
|
+
}
|
|
11874
|
+
const color = readout.color ?? "#334155";
|
|
11875
|
+
out.push({ type: "rect", x: chipX, y: rowY, width: chipW, height: chipH, color, fill: color });
|
|
11876
|
+
out.push({
|
|
11877
|
+
type: "text",
|
|
11878
|
+
x: chipX + chipW / 2,
|
|
11879
|
+
y: rowY + chipH / 2,
|
|
11880
|
+
text,
|
|
11881
|
+
color: "#ffffff",
|
|
11882
|
+
fontSize: 10,
|
|
11883
|
+
align: "center"
|
|
11884
|
+
});
|
|
11885
|
+
rightEdge = chipX - gap;
|
|
11886
|
+
}
|
|
11887
|
+
return out;
|
|
11888
|
+
}
|
|
11889
|
+
function traceShapes(panel, k, width, height) {
|
|
11890
|
+
const w = panel.width ?? Math.round(width * 0.32);
|
|
11891
|
+
const h = panel.height ?? Math.round(height * 0.28);
|
|
11892
|
+
const x = panel.x ?? width - w - 8;
|
|
11893
|
+
const y = panel.y ?? height - h - 8 - k * (h + 8);
|
|
11894
|
+
const allSamples = panel.series.flatMap((series) => series.samples);
|
|
11895
|
+
let xLo = Math.min(...allSamples.map((p) => p.x));
|
|
11896
|
+
let xHi = Math.max(...allSamples.map((p) => p.x));
|
|
11897
|
+
let yLo = Math.min(...allSamples.map((p) => p.y));
|
|
11898
|
+
let yHi = Math.max(...allSamples.map((p) => p.y));
|
|
11899
|
+
if (xLo === xHi) {
|
|
11900
|
+
xLo -= 1;
|
|
11901
|
+
xHi += 1;
|
|
11902
|
+
}
|
|
11903
|
+
if (yLo === yHi) {
|
|
11904
|
+
yLo -= 1;
|
|
11905
|
+
yHi += 1;
|
|
11906
|
+
}
|
|
11907
|
+
const backgroundColor = panel.backgroundColor ?? "#ffffff";
|
|
11908
|
+
const frameColor = panel.frameColor ?? "#94a3b8";
|
|
11909
|
+
const out = [];
|
|
11910
|
+
out.push({
|
|
11911
|
+
type: "rect",
|
|
11912
|
+
x,
|
|
11913
|
+
y,
|
|
11914
|
+
width: w,
|
|
11915
|
+
height: h,
|
|
11916
|
+
color: backgroundColor,
|
|
11917
|
+
fill: backgroundColor,
|
|
11918
|
+
opacity: panel.backgroundOpacity ?? 0.85
|
|
11919
|
+
});
|
|
11920
|
+
out.push({ type: "rect", x, y, width: w, height: h, color: frameColor, lineWidth: 1 });
|
|
11921
|
+
panel.series.forEach((series, j) => {
|
|
11922
|
+
const color = series.color ?? TRACE_SERIES_COLORS[j % TRACE_SERIES_COLORS.length];
|
|
11923
|
+
const mapped = series.samples.map((p) => ({
|
|
11924
|
+
x: x + 4 + (p.x - xLo) / (xHi - xLo) * (w - 8),
|
|
11925
|
+
y: y + h - 4 - (p.y - yLo) / (yHi - yLo) * (h - 8)
|
|
11926
|
+
}));
|
|
11927
|
+
for (let i = 1; i < mapped.length; i++) {
|
|
11928
|
+
out.push({
|
|
11929
|
+
type: "line",
|
|
11930
|
+
x1: mapped[i - 1].x,
|
|
11931
|
+
y1: mapped[i - 1].y,
|
|
11932
|
+
x2: mapped[i].x,
|
|
11933
|
+
y2: mapped[i].y,
|
|
11934
|
+
color,
|
|
11935
|
+
lineWidth: 1.5
|
|
11936
|
+
});
|
|
11937
|
+
}
|
|
11938
|
+
if (mapped.length > 0) {
|
|
11939
|
+
const last = mapped[mapped.length - 1];
|
|
11940
|
+
out.push({ type: "circle", x: last.x, y: last.y, radius: 2, color, fill: color });
|
|
11941
|
+
}
|
|
11942
|
+
if (series.label) {
|
|
11943
|
+
out.push({ type: "text", x: x + 6, y: y + 10 + 11 * j, text: series.label, color, fontSize: 9 });
|
|
11944
|
+
}
|
|
11945
|
+
});
|
|
11946
|
+
if (panel.yLabel) {
|
|
11947
|
+
out.push({ type: "text", x: x + w - 6, y: y + 10, text: panel.yLabel, color: "#6b7280", fontSize: 9, align: "right" });
|
|
11948
|
+
}
|
|
11949
|
+
if (panel.xLabel) {
|
|
11950
|
+
out.push({ type: "text", x: x + w - 6, y: y + h - 6, text: panel.xLabel, color: "#6b7280", fontSize: 9, align: "right" });
|
|
11951
|
+
}
|
|
11952
|
+
return out;
|
|
11953
|
+
}
|
|
11954
|
+
var DASH_PATTERNS, TRACE_SERIES_COLORS, LearningCanvas;
|
|
11803
11955
|
var init_LearningCanvas = __esm({
|
|
11804
11956
|
"components/learning/atoms/LearningCanvas.tsx"() {
|
|
11805
11957
|
"use client";
|
|
11806
11958
|
init_cn();
|
|
11807
11959
|
init_useEventBus();
|
|
11960
|
+
DASH_PATTERNS = { dashed: [6, 4], dotted: [2, 3] };
|
|
11961
|
+
TRACE_SERIES_COLORS = ["#2563eb", "#dc2626", "#16a34a", "#f59e0b"];
|
|
11808
11962
|
LearningCanvas = ({
|
|
11809
11963
|
className,
|
|
11810
11964
|
width = 600,
|
|
11811
11965
|
height = 400,
|
|
11812
11966
|
backgroundColor,
|
|
11813
11967
|
shapes = [],
|
|
11968
|
+
readouts,
|
|
11969
|
+
traces,
|
|
11814
11970
|
interactive = false,
|
|
11815
11971
|
animate = false,
|
|
11816
11972
|
onShapeClick,
|
|
@@ -11836,6 +11992,12 @@ var init_LearningCanvas = __esm({
|
|
|
11836
11992
|
}
|
|
11837
11993
|
return -1;
|
|
11838
11994
|
}, [shapes]);
|
|
11995
|
+
const derivedShapes = React87.useMemo(() => {
|
|
11996
|
+
if (!traces?.length && !readouts?.length) return shapes;
|
|
11997
|
+
const traceOut = (traces ?? []).flatMap((panel, k) => traceShapes(panel, k, width, height));
|
|
11998
|
+
const readoutOut = readouts?.length ? readoutShapes(readouts, width) : [];
|
|
11999
|
+
return [...shapes, ...traceOut, ...readoutOut];
|
|
12000
|
+
}, [shapes, traces, readouts, width, height]);
|
|
11839
12001
|
const draw = React87.useCallback(() => {
|
|
11840
12002
|
const canvas = canvasRef.current;
|
|
11841
12003
|
if (!canvas) return;
|
|
@@ -11852,13 +12014,13 @@ var init_LearningCanvas = __esm({
|
|
|
11852
12014
|
ctx.fillStyle = backgroundColor;
|
|
11853
12015
|
ctx.fillRect(0, 0, width, height);
|
|
11854
12016
|
}
|
|
11855
|
-
for (const shape of
|
|
11856
|
-
if (shape.type !== "text") drawShape(ctx, shape, width, height);
|
|
12017
|
+
for (const shape of derivedShapes) {
|
|
12018
|
+
if (shape.type !== "text") drawShape(ctx, shape, width, height, derivedShapes);
|
|
11857
12019
|
}
|
|
11858
|
-
for (const shape of
|
|
11859
|
-
if (shape.type === "text") drawShape(ctx, shape, width, height);
|
|
12020
|
+
for (const shape of derivedShapes) {
|
|
12021
|
+
if (shape.type === "text") drawShape(ctx, shape, width, height, derivedShapes);
|
|
11860
12022
|
}
|
|
11861
|
-
}, [width, height, backgroundColor,
|
|
12023
|
+
}, [width, height, backgroundColor, derivedShapes]);
|
|
11862
12024
|
React87.useEffect(() => {
|
|
11863
12025
|
draw();
|
|
11864
12026
|
}, [draw]);
|
|
@@ -13791,7 +13953,363 @@ var init_ComponentPatterns = __esm({
|
|
|
13791
13953
|
AlertPattern.displayName = "AlertPattern";
|
|
13792
13954
|
}
|
|
13793
13955
|
});
|
|
13794
|
-
|
|
13956
|
+
function layoutCircle(nodes, width, height) {
|
|
13957
|
+
const cx = width / 2;
|
|
13958
|
+
const cy = height / 2;
|
|
13959
|
+
const radius = Math.max(10, Math.min(cx, cy) - 40);
|
|
13960
|
+
const positions = /* @__PURE__ */ new Map();
|
|
13961
|
+
const n = nodes.length;
|
|
13962
|
+
nodes.forEach((node, i) => {
|
|
13963
|
+
const angle = 2 * Math.PI * i / Math.max(n, 1) - Math.PI / 2;
|
|
13964
|
+
positions.set(node.id, { x: cx + radius * Math.cos(angle), y: cy + radius * Math.sin(angle) });
|
|
13965
|
+
});
|
|
13966
|
+
return positions;
|
|
13967
|
+
}
|
|
13968
|
+
function layoutTree(nodes, edges, root, width, height) {
|
|
13969
|
+
const nodeIds = nodes.map((n) => n.id);
|
|
13970
|
+
const idSet = new Set(nodeIds);
|
|
13971
|
+
const childrenOf = /* @__PURE__ */ new Map();
|
|
13972
|
+
const hasIncoming = /* @__PURE__ */ new Set();
|
|
13973
|
+
for (const e of edges) {
|
|
13974
|
+
if (!idSet.has(e.from) || !idSet.has(e.to)) continue;
|
|
13975
|
+
const list = childrenOf.get(e.from) ?? [];
|
|
13976
|
+
list.push(e.to);
|
|
13977
|
+
childrenOf.set(e.from, list);
|
|
13978
|
+
hasIncoming.add(e.to);
|
|
13979
|
+
}
|
|
13980
|
+
const depth = /* @__PURE__ */ new Map();
|
|
13981
|
+
const treeChildren = /* @__PURE__ */ new Map();
|
|
13982
|
+
const visited = /* @__PURE__ */ new Set();
|
|
13983
|
+
const bfsFrom = (start) => {
|
|
13984
|
+
if (visited.has(start)) return;
|
|
13985
|
+
visited.add(start);
|
|
13986
|
+
depth.set(start, 0);
|
|
13987
|
+
const queue = [start];
|
|
13988
|
+
while (queue.length > 0) {
|
|
13989
|
+
const u = queue.shift();
|
|
13990
|
+
for (const v of childrenOf.get(u) ?? []) {
|
|
13991
|
+
if (visited.has(v)) continue;
|
|
13992
|
+
visited.add(v);
|
|
13993
|
+
depth.set(v, (depth.get(u) ?? 0) + 1);
|
|
13994
|
+
const list = treeChildren.get(u) ?? [];
|
|
13995
|
+
list.push(v);
|
|
13996
|
+
treeChildren.set(u, list);
|
|
13997
|
+
queue.push(v);
|
|
13998
|
+
}
|
|
13999
|
+
}
|
|
14000
|
+
};
|
|
14001
|
+
const primaryRoot = root && idSet.has(root) ? root : nodeIds.find((id) => !hasIncoming.has(id)) ?? nodeIds[0];
|
|
14002
|
+
const rootsOrder = [];
|
|
14003
|
+
if (primaryRoot !== void 0) {
|
|
14004
|
+
bfsFrom(primaryRoot);
|
|
14005
|
+
rootsOrder.push(primaryRoot);
|
|
14006
|
+
}
|
|
14007
|
+
for (const id of nodeIds) {
|
|
14008
|
+
if (!visited.has(id)) {
|
|
14009
|
+
bfsFrom(id);
|
|
14010
|
+
rootsOrder.push(id);
|
|
14011
|
+
}
|
|
14012
|
+
}
|
|
14013
|
+
let leafCounter = 0;
|
|
14014
|
+
const xSlot = /* @__PURE__ */ new Map();
|
|
14015
|
+
const assignXSlot = (u) => {
|
|
14016
|
+
const children = treeChildren.get(u) ?? [];
|
|
14017
|
+
if (children.length === 0) {
|
|
14018
|
+
const slot = leafCounter++;
|
|
14019
|
+
xSlot.set(u, slot);
|
|
14020
|
+
return slot;
|
|
14021
|
+
}
|
|
14022
|
+
const childSlots = children.map(assignXSlot);
|
|
14023
|
+
const avg = childSlots.reduce((a, b) => a + b, 0) / childSlots.length;
|
|
14024
|
+
xSlot.set(u, avg);
|
|
14025
|
+
return avg;
|
|
14026
|
+
};
|
|
14027
|
+
for (const r of rootsOrder) assignXSlot(r);
|
|
14028
|
+
let maxDepth = 0;
|
|
14029
|
+
for (const d of depth.values()) maxDepth = Math.max(maxDepth, d);
|
|
14030
|
+
const colWidth = width / Math.max(1, leafCounter);
|
|
14031
|
+
const rowHeight = height / (maxDepth + 1);
|
|
14032
|
+
const positions = /* @__PURE__ */ new Map();
|
|
14033
|
+
for (const id of nodeIds) {
|
|
14034
|
+
const slot = xSlot.get(id) ?? 0;
|
|
14035
|
+
const d = depth.get(id) ?? 0;
|
|
14036
|
+
positions.set(id, { x: slot * colWidth + colWidth / 2, y: d * rowHeight + rowHeight / 2 });
|
|
14037
|
+
}
|
|
14038
|
+
return positions;
|
|
14039
|
+
}
|
|
14040
|
+
function layoutLayered(nodes, edges, width, height) {
|
|
14041
|
+
const nodeIds = nodes.map((n) => n.id);
|
|
14042
|
+
const idSet = new Set(nodeIds);
|
|
14043
|
+
const adj = /* @__PURE__ */ new Map();
|
|
14044
|
+
const remainingIndegree = /* @__PURE__ */ new Map();
|
|
14045
|
+
for (const id of nodeIds) remainingIndegree.set(id, 0);
|
|
14046
|
+
for (const e of edges) {
|
|
14047
|
+
if (!idSet.has(e.from) || !idSet.has(e.to)) continue;
|
|
14048
|
+
const list = adj.get(e.from) ?? [];
|
|
14049
|
+
list.push(e.to);
|
|
14050
|
+
adj.set(e.from, list);
|
|
14051
|
+
remainingIndegree.set(e.to, (remainingIndegree.get(e.to) ?? 0) + 1);
|
|
14052
|
+
}
|
|
14053
|
+
const layer = /* @__PURE__ */ new Map();
|
|
14054
|
+
const dequeued = /* @__PURE__ */ new Set();
|
|
14055
|
+
const queue = [];
|
|
14056
|
+
for (const id of nodeIds) {
|
|
14057
|
+
if ((remainingIndegree.get(id) ?? 0) === 0) {
|
|
14058
|
+
layer.set(id, 0);
|
|
14059
|
+
queue.push(id);
|
|
14060
|
+
}
|
|
14061
|
+
}
|
|
14062
|
+
while (queue.length > 0) {
|
|
14063
|
+
const u = queue.shift();
|
|
14064
|
+
dequeued.add(u);
|
|
14065
|
+
for (const v of adj.get(u) ?? []) {
|
|
14066
|
+
const candidate = (layer.get(u) ?? 0) + 1;
|
|
14067
|
+
layer.set(v, Math.max(layer.get(v) ?? 0, candidate));
|
|
14068
|
+
remainingIndegree.set(v, (remainingIndegree.get(v) ?? 0) - 1);
|
|
14069
|
+
if ((remainingIndegree.get(v) ?? 0) === 0 && !dequeued.has(v)) {
|
|
14070
|
+
queue.push(v);
|
|
14071
|
+
}
|
|
14072
|
+
}
|
|
14073
|
+
}
|
|
14074
|
+
let baseMaxLayer = 0;
|
|
14075
|
+
for (const id of nodeIds) {
|
|
14076
|
+
if (dequeued.has(id)) baseMaxLayer = Math.max(baseMaxLayer, layer.get(id) ?? 0);
|
|
14077
|
+
}
|
|
14078
|
+
const cycleLayer = baseMaxLayer + 1;
|
|
14079
|
+
let maxLayer = baseMaxLayer;
|
|
14080
|
+
for (const id of nodeIds) {
|
|
14081
|
+
if (!dequeued.has(id)) {
|
|
14082
|
+
layer.set(id, cycleLayer);
|
|
14083
|
+
maxLayer = cycleLayer;
|
|
14084
|
+
}
|
|
14085
|
+
}
|
|
14086
|
+
const colWidth = width / Math.max(1, maxLayer + 1);
|
|
14087
|
+
const byLayer = /* @__PURE__ */ new Map();
|
|
14088
|
+
for (const id of nodeIds) {
|
|
14089
|
+
const l = layer.get(id) ?? 0;
|
|
14090
|
+
const list = byLayer.get(l) ?? [];
|
|
14091
|
+
list.push(id);
|
|
14092
|
+
byLayer.set(l, list);
|
|
14093
|
+
}
|
|
14094
|
+
const positions = /* @__PURE__ */ new Map();
|
|
14095
|
+
for (const [l, ids] of byLayer) {
|
|
14096
|
+
const rowHeight = height / ids.length;
|
|
14097
|
+
ids.forEach((id, i) => {
|
|
14098
|
+
positions.set(id, { x: l * colWidth + colWidth / 2, y: i * rowHeight + rowHeight / 2 });
|
|
14099
|
+
});
|
|
14100
|
+
}
|
|
14101
|
+
return positions;
|
|
14102
|
+
}
|
|
14103
|
+
function computePositions(nodes, edges, layout, root, width, height) {
|
|
14104
|
+
switch (layout) {
|
|
14105
|
+
case "circle":
|
|
14106
|
+
return layoutCircle(nodes, width, height);
|
|
14107
|
+
case "tree":
|
|
14108
|
+
return layoutTree(nodes, edges, root, width, height);
|
|
14109
|
+
case "layered":
|
|
14110
|
+
return layoutLayered(nodes, edges, width, height);
|
|
14111
|
+
case "manual":
|
|
14112
|
+
default: {
|
|
14113
|
+
const positions = /* @__PURE__ */ new Map();
|
|
14114
|
+
for (const n of nodes) positions.set(n.id, { x: n.x ?? 0, y: n.y ?? 0 });
|
|
14115
|
+
return positions;
|
|
14116
|
+
}
|
|
14117
|
+
}
|
|
14118
|
+
}
|
|
14119
|
+
var NODE_STATE_COLOR, EDGE_STATE_COLOR, DEFAULT_NODE_RADIUS, AlgoGraphCanvas;
|
|
14120
|
+
var init_AlgoGraphCanvas = __esm({
|
|
14121
|
+
"components/learning/molecules/AlgoGraphCanvas.tsx"() {
|
|
14122
|
+
"use client";
|
|
14123
|
+
init_atoms();
|
|
14124
|
+
init_Stack();
|
|
14125
|
+
init_LearningCanvas();
|
|
14126
|
+
NODE_STATE_COLOR = {
|
|
14127
|
+
unvisited: "#cbd5e1",
|
|
14128
|
+
frontier: "#f59e0b",
|
|
14129
|
+
current: "#ef4444",
|
|
14130
|
+
visited: "#22c55e",
|
|
14131
|
+
goal: "#8b5cf6",
|
|
14132
|
+
path: "#0ea5e9"
|
|
14133
|
+
};
|
|
14134
|
+
EDGE_STATE_COLOR = {
|
|
14135
|
+
default: "#9ca3af",
|
|
14136
|
+
tree: "#16a34a",
|
|
14137
|
+
relaxed: "#f59e0b",
|
|
14138
|
+
candidate: "#38bdf8",
|
|
14139
|
+
path: "#dc2626"
|
|
14140
|
+
};
|
|
14141
|
+
DEFAULT_NODE_RADIUS = 18;
|
|
14142
|
+
AlgoGraphCanvas = ({
|
|
14143
|
+
className,
|
|
14144
|
+
width = 600,
|
|
14145
|
+
height = 400,
|
|
14146
|
+
title,
|
|
14147
|
+
backgroundColor,
|
|
14148
|
+
nodes = [],
|
|
14149
|
+
edges = [],
|
|
14150
|
+
layout = "manual",
|
|
14151
|
+
root,
|
|
14152
|
+
shapes = [],
|
|
14153
|
+
interactive = false,
|
|
14154
|
+
animate = false,
|
|
14155
|
+
onShapeClick,
|
|
14156
|
+
onNodeClick,
|
|
14157
|
+
isLoading,
|
|
14158
|
+
error
|
|
14159
|
+
}) => {
|
|
14160
|
+
const nodeById = React87.useMemo(() => {
|
|
14161
|
+
const m = /* @__PURE__ */ new Map();
|
|
14162
|
+
for (const n of nodes) m.set(n.id, n);
|
|
14163
|
+
return m;
|
|
14164
|
+
}, [nodes]);
|
|
14165
|
+
const nodeIndexById = React87.useMemo(() => {
|
|
14166
|
+
const m = /* @__PURE__ */ new Map();
|
|
14167
|
+
nodes.forEach((n, i) => m.set(n.id, i));
|
|
14168
|
+
return m;
|
|
14169
|
+
}, [nodes]);
|
|
14170
|
+
const derivedShapes = React87.useMemo(() => {
|
|
14171
|
+
const out = [];
|
|
14172
|
+
const positions = computePositions(nodes, edges, layout, root, width, height);
|
|
14173
|
+
const edgeGeoms = [];
|
|
14174
|
+
for (const e of edges) {
|
|
14175
|
+
const a = nodeById.get(e.from);
|
|
14176
|
+
const b = nodeById.get(e.to);
|
|
14177
|
+
const posA = positions.get(e.from);
|
|
14178
|
+
const posB = positions.get(e.to);
|
|
14179
|
+
if (!a || !b || !posA || !posB) continue;
|
|
14180
|
+
const rA = a.radius ?? DEFAULT_NODE_RADIUS;
|
|
14181
|
+
const rB = b.radius ?? DEFAULT_NODE_RADIUS;
|
|
14182
|
+
const dx = posB.x - posA.x;
|
|
14183
|
+
const dy = posB.y - posA.y;
|
|
14184
|
+
const dist = Math.max(1e-6, Math.hypot(dx, dy));
|
|
14185
|
+
const ux = dx / dist;
|
|
14186
|
+
const uy = dy / dist;
|
|
14187
|
+
edgeGeoms.push({
|
|
14188
|
+
directed: e.directed ?? false,
|
|
14189
|
+
x1: posA.x + ux * rA,
|
|
14190
|
+
y1: posA.y + uy * rA,
|
|
14191
|
+
x2: posB.x - ux * rB,
|
|
14192
|
+
y2: posB.y - uy * rB,
|
|
14193
|
+
color: e.color ?? EDGE_STATE_COLOR[e.state ?? "default"],
|
|
14194
|
+
label: e.label ?? (e.weight != null ? String(e.weight) : void 0)
|
|
14195
|
+
});
|
|
14196
|
+
}
|
|
14197
|
+
for (const g of edgeGeoms) {
|
|
14198
|
+
out.push({
|
|
14199
|
+
type: g.directed ? "arrow" : "line",
|
|
14200
|
+
x1: g.x1,
|
|
14201
|
+
y1: g.y1,
|
|
14202
|
+
x2: g.x2,
|
|
14203
|
+
y2: g.y2,
|
|
14204
|
+
color: g.color,
|
|
14205
|
+
lineWidth: 2
|
|
14206
|
+
});
|
|
14207
|
+
}
|
|
14208
|
+
for (const g of edgeGeoms) {
|
|
14209
|
+
if (g.label === void 0) continue;
|
|
14210
|
+
const midX = (g.x1 + g.x2) / 2;
|
|
14211
|
+
const midY = (g.y1 + g.y2) / 2;
|
|
14212
|
+
const dx = g.x2 - g.x1;
|
|
14213
|
+
const dy = g.y2 - g.y1;
|
|
14214
|
+
const dist = Math.max(1e-6, Math.hypot(dx, dy));
|
|
14215
|
+
const perpX = -(dy / dist);
|
|
14216
|
+
const perpY = dx / dist;
|
|
14217
|
+
out.push({
|
|
14218
|
+
type: "text",
|
|
14219
|
+
x: midX + perpX * 10,
|
|
14220
|
+
y: midY + perpY * 10,
|
|
14221
|
+
text: g.label,
|
|
14222
|
+
fontSize: 11,
|
|
14223
|
+
align: "center",
|
|
14224
|
+
color: "#374151"
|
|
14225
|
+
});
|
|
14226
|
+
}
|
|
14227
|
+
const nodeGeoms = [];
|
|
14228
|
+
for (const n of nodes) {
|
|
14229
|
+
const pos = positions.get(n.id);
|
|
14230
|
+
if (!pos) continue;
|
|
14231
|
+
nodeGeoms.push({
|
|
14232
|
+
id: n.id,
|
|
14233
|
+
x: pos.x,
|
|
14234
|
+
y: pos.y,
|
|
14235
|
+
radius: n.radius ?? DEFAULT_NODE_RADIUS,
|
|
14236
|
+
color: n.color ?? NODE_STATE_COLOR[n.state ?? "unvisited"],
|
|
14237
|
+
label: n.label,
|
|
14238
|
+
badge: n.badge
|
|
14239
|
+
});
|
|
14240
|
+
}
|
|
14241
|
+
for (const g of nodeGeoms) {
|
|
14242
|
+
out.push({ type: "circle", id: g.id, x: g.x, y: g.y, radius: g.radius, color: g.color, fill: `${g.color}33` });
|
|
14243
|
+
}
|
|
14244
|
+
const badgeGeoms = [];
|
|
14245
|
+
for (const g of nodeGeoms) {
|
|
14246
|
+
if (!g.badge) continue;
|
|
14247
|
+
const w = Math.min(42, Math.max(18, g.badge.text.length * 6 + 10));
|
|
14248
|
+
badgeGeoms.push({
|
|
14249
|
+
cx: g.x + g.radius * 0.75,
|
|
14250
|
+
cy: g.y - g.radius * 0.75,
|
|
14251
|
+
w,
|
|
14252
|
+
h: 14,
|
|
14253
|
+
// Borderless pill: same color drives both stroke and fill.
|
|
14254
|
+
color: g.badge.color ?? "#1e293b",
|
|
14255
|
+
text: g.badge.text
|
|
14256
|
+
});
|
|
14257
|
+
}
|
|
14258
|
+
for (const b of badgeGeoms) {
|
|
14259
|
+
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 });
|
|
14260
|
+
}
|
|
14261
|
+
for (const b of badgeGeoms) {
|
|
14262
|
+
out.push({ type: "text", x: b.cx, y: b.cy, text: b.text, fontSize: 9, align: "center", color: "#ffffff" });
|
|
14263
|
+
}
|
|
14264
|
+
for (const g of nodeGeoms) {
|
|
14265
|
+
if (g.label === void 0) continue;
|
|
14266
|
+
out.push({
|
|
14267
|
+
type: "text",
|
|
14268
|
+
x: g.x,
|
|
14269
|
+
y: g.y + g.radius + 14,
|
|
14270
|
+
text: g.label,
|
|
14271
|
+
fontSize: 12,
|
|
14272
|
+
align: "center",
|
|
14273
|
+
color: "#111827"
|
|
14274
|
+
});
|
|
14275
|
+
}
|
|
14276
|
+
out.push(...shapes);
|
|
14277
|
+
return out;
|
|
14278
|
+
}, [nodes, edges, layout, root, width, height, nodeById, shapes]);
|
|
14279
|
+
const handleShapeClick = React87.useCallback(
|
|
14280
|
+
(payload) => {
|
|
14281
|
+
if (payload.type === "circle" && payload.id) {
|
|
14282
|
+
const node = nodeById.get(payload.id);
|
|
14283
|
+
const idx = nodeIndexById.get(payload.id);
|
|
14284
|
+
if (node && idx !== void 0) {
|
|
14285
|
+
onNodeClick?.({ id: node.id, label: node.label, index: idx });
|
|
14286
|
+
}
|
|
14287
|
+
}
|
|
14288
|
+
onShapeClick?.(payload);
|
|
14289
|
+
},
|
|
14290
|
+
[nodeById, nodeIndexById, onNodeClick, onShapeClick]
|
|
14291
|
+
);
|
|
14292
|
+
return /* @__PURE__ */ jsxRuntime.jsx(Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
|
|
14293
|
+
title ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", children: title }) : null,
|
|
14294
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
14295
|
+
LearningCanvas,
|
|
14296
|
+
{
|
|
14297
|
+
width,
|
|
14298
|
+
height,
|
|
14299
|
+
backgroundColor,
|
|
14300
|
+
shapes: derivedShapes,
|
|
14301
|
+
interactive,
|
|
14302
|
+
animate,
|
|
14303
|
+
onShapeClick: onShapeClick || onNodeClick ? handleShapeClick : void 0,
|
|
14304
|
+
isLoading,
|
|
14305
|
+
error
|
|
14306
|
+
}
|
|
14307
|
+
)
|
|
14308
|
+
] }) });
|
|
14309
|
+
};
|
|
14310
|
+
}
|
|
14311
|
+
});
|
|
14312
|
+
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;
|
|
13795
14313
|
var init_AlgorithmCanvas = __esm({
|
|
13796
14314
|
"components/learning/molecules/AlgorithmCanvas.tsx"() {
|
|
13797
14315
|
"use client";
|
|
@@ -13803,6 +14321,43 @@ var init_AlgorithmCanvas = __esm({
|
|
|
13803
14321
|
DEFAULT_POINTER_COLOR = "#dc2626";
|
|
13804
14322
|
POINTER_BAND = 34;
|
|
13805
14323
|
TOP_PAD = 26;
|
|
14324
|
+
PANEL_FAMILY_ORDER = ["bars", "slots", "cells", "buckets", "frames"];
|
|
14325
|
+
RANGE_COLOR_DEFAULT = "#3b82f6";
|
|
14326
|
+
RANGE_FILL_OPACITY = 0.15;
|
|
14327
|
+
BRACKET_TOP_OFFSET = 16;
|
|
14328
|
+
BRACKET_ROW_H = 14;
|
|
14329
|
+
BRACKET_TICK_H = 6;
|
|
14330
|
+
BRACKET_LABEL_OFFSET = 6;
|
|
14331
|
+
SLOT_EMPTY_FILL = "#f1f5f9";
|
|
14332
|
+
SLOT_EMPTY_STROKE = "#cbd5e1";
|
|
14333
|
+
SLOT_FILLED_STROKE = "#9ca3af";
|
|
14334
|
+
SLOT_HIGHLIGHT_DEFAULT = "#f59e0b";
|
|
14335
|
+
SLOT_VALUE_TEXT_COLOR = "#ffffff";
|
|
14336
|
+
FRAME_ACTIVE_COLOR = "#3b82f6";
|
|
14337
|
+
FRAME_RETURNING_COLOR = "#f59e0b";
|
|
14338
|
+
FRAME_DONE_COLOR = "#94a3b8";
|
|
14339
|
+
FRAME_LABEL_COLOR = "#ffffff";
|
|
14340
|
+
FRAME_DETAIL_COLOR = "#e2e8f0";
|
|
14341
|
+
FRAME_TWO_LINE_MIN_H = 22;
|
|
14342
|
+
BUCKET_INDEX_FILL = "#e2e8f0";
|
|
14343
|
+
BUCKET_INDEX_STROKE = "#9ca3af";
|
|
14344
|
+
BUCKET_INDEX_TEXT = "#374151";
|
|
14345
|
+
BUCKET_ENTRY_TEXT = "#ffffff";
|
|
14346
|
+
BUCKET_ENTRY_DEFAULT = "#3b82f6";
|
|
14347
|
+
BUCKET_ENTRY_HIGHLIGHT = "#f59e0b";
|
|
14348
|
+
BUCKET_ENTRY_PROBING = "#38bdf8";
|
|
14349
|
+
BUCKET_ENTRY_MIN_W = 24;
|
|
14350
|
+
BUCKET_ENTRY_MAX_W = 64;
|
|
14351
|
+
AXIS_LABEL_COLOR = "#6b7280";
|
|
14352
|
+
AXIS_LABEL_FONT_SIZE = 10;
|
|
14353
|
+
CORNER_TEXT_COLOR = "#111827";
|
|
14354
|
+
CORNER_FONT_SIZE = 7;
|
|
14355
|
+
CORNER_MIN_CELL = 28;
|
|
14356
|
+
CORNER_INSET_X = 3;
|
|
14357
|
+
CORNER_INSET_Y = 6;
|
|
14358
|
+
AUX_PRIMARY_RATIO = 0.6;
|
|
14359
|
+
AUX_LABEL_BAND = 18;
|
|
14360
|
+
AUX_BASELINE_PAD = 8;
|
|
13806
14361
|
AlgorithmCanvas = ({
|
|
13807
14362
|
className,
|
|
13808
14363
|
width = 600,
|
|
@@ -13812,6 +14367,14 @@ var init_AlgorithmCanvas = __esm({
|
|
|
13812
14367
|
bars = [],
|
|
13813
14368
|
cells = [],
|
|
13814
14369
|
pointers = [],
|
|
14370
|
+
ranges = [],
|
|
14371
|
+
slots = [],
|
|
14372
|
+
slotOrientation = "horizontal",
|
|
14373
|
+
frames = [],
|
|
14374
|
+
buckets = [],
|
|
14375
|
+
auxBars = [],
|
|
14376
|
+
rowLabels = [],
|
|
14377
|
+
colLabels = [],
|
|
13815
14378
|
shapes = [],
|
|
13816
14379
|
interactive = false,
|
|
13817
14380
|
animate = false,
|
|
@@ -13821,12 +14384,35 @@ var init_AlgorithmCanvas = __esm({
|
|
|
13821
14384
|
}) => {
|
|
13822
14385
|
const derivedShapes = React87.useMemo(() => {
|
|
13823
14386
|
const out = [];
|
|
14387
|
+
const presence = {
|
|
14388
|
+
bars: bars.length > 0,
|
|
14389
|
+
slots: slots.length > 0,
|
|
14390
|
+
cells: cells.length > 0,
|
|
14391
|
+
buckets: buckets.length > 0,
|
|
14392
|
+
frames: frames.length > 0
|
|
14393
|
+
};
|
|
14394
|
+
const panelCount = PANEL_FAMILY_ORDER.filter((f3) => presence[f3]).length;
|
|
14395
|
+
const panelHeight = height / Math.max(1, panelCount);
|
|
14396
|
+
const panelY = { bars: 0, slots: 0, cells: 0, buckets: 0, frames: 0 };
|
|
14397
|
+
let compactIndex = 0;
|
|
14398
|
+
PANEL_FAMILY_ORDER.forEach((f3) => {
|
|
14399
|
+
if (presence[f3]) {
|
|
14400
|
+
panelY[f3] = compactIndex * panelHeight;
|
|
14401
|
+
compactIndex += 1;
|
|
14402
|
+
}
|
|
14403
|
+
});
|
|
13824
14404
|
if (bars.length > 0) {
|
|
14405
|
+
const panelYBars = panelY.bars;
|
|
13825
14406
|
const slot = width / bars.length;
|
|
13826
14407
|
const barW = slot * 0.8;
|
|
13827
14408
|
const gap = slot * 0.1;
|
|
13828
|
-
const
|
|
13829
|
-
const
|
|
14409
|
+
const bracketRanges = ranges.filter((r) => r.kind === "bracket");
|
|
14410
|
+
const bracketCount = bracketRanges.length;
|
|
14411
|
+
const bracketHeadroom = bracketCount > 0 ? BRACKET_TOP_OFFSET + bracketCount * BRACKET_ROW_H : 0;
|
|
14412
|
+
const hasAux = auxBars.length > 0;
|
|
14413
|
+
const primaryH = hasAux ? panelHeight * AUX_PRIMARY_RATIO : panelHeight;
|
|
14414
|
+
const baseline = panelYBars + primaryH - POINTER_BAND;
|
|
14415
|
+
const usableH = baseline - (panelYBars + TOP_PAD + bracketHeadroom);
|
|
13830
14416
|
const maxV = Math.max(1, ...bars.map((b) => Number.isFinite(b.value) ? b.value : 0));
|
|
13831
14417
|
bars.forEach((bar, i) => {
|
|
13832
14418
|
const v = Number.isFinite(bar.value) ? bar.value : 0;
|
|
@@ -13856,6 +14442,89 @@ var init_AlgorithmCanvas = __esm({
|
|
|
13856
14442
|
});
|
|
13857
14443
|
}
|
|
13858
14444
|
});
|
|
14445
|
+
ranges.forEach((r) => {
|
|
14446
|
+
const kind = r.kind ?? "fill";
|
|
14447
|
+
if (kind !== "fill") return;
|
|
14448
|
+
const color = r.color ?? RANGE_COLOR_DEFAULT;
|
|
14449
|
+
out.push({
|
|
14450
|
+
type: "rect",
|
|
14451
|
+
x: r.from * slot,
|
|
14452
|
+
y: panelYBars,
|
|
14453
|
+
width: (r.to - r.from + 1) * slot,
|
|
14454
|
+
height: primaryH,
|
|
14455
|
+
color,
|
|
14456
|
+
fill: color,
|
|
14457
|
+
opacity: RANGE_FILL_OPACITY
|
|
14458
|
+
});
|
|
14459
|
+
if (r.label) {
|
|
14460
|
+
out.push({
|
|
14461
|
+
type: "text",
|
|
14462
|
+
x: r.from * slot + 4,
|
|
14463
|
+
// Sits below the bracket block (if any) so fill and bracket labels never collide.
|
|
14464
|
+
y: panelYBars + 10 + bracketHeadroom,
|
|
14465
|
+
text: r.label,
|
|
14466
|
+
color,
|
|
14467
|
+
fontSize: 10,
|
|
14468
|
+
align: "left"
|
|
14469
|
+
});
|
|
14470
|
+
}
|
|
14471
|
+
});
|
|
14472
|
+
bracketRanges.forEach((r, i) => {
|
|
14473
|
+
const bracketY = panelYBars + BRACKET_TOP_OFFSET + i * BRACKET_ROW_H;
|
|
14474
|
+
const x1 = r.from * slot + slot * 0.1;
|
|
14475
|
+
const x2 = (r.to + 1) * slot - slot * 0.1;
|
|
14476
|
+
const color = r.color ?? RANGE_COLOR_DEFAULT;
|
|
14477
|
+
out.push({ type: "line", x1, y1: bracketY, x2, y2: bracketY, color, lineWidth: 2 });
|
|
14478
|
+
out.push({ type: "line", x1, y1: bracketY, x2: x1, y2: bracketY + BRACKET_TICK_H, color, lineWidth: 2 });
|
|
14479
|
+
out.push({ type: "line", x1: x2, y1: bracketY, x2, y2: bracketY + BRACKET_TICK_H, color, lineWidth: 2 });
|
|
14480
|
+
if (r.label) {
|
|
14481
|
+
out.push({
|
|
14482
|
+
type: "text",
|
|
14483
|
+
x: (x1 + x2) / 2,
|
|
14484
|
+
y: bracketY - BRACKET_LABEL_OFFSET,
|
|
14485
|
+
text: r.label,
|
|
14486
|
+
color,
|
|
14487
|
+
fontSize: 10,
|
|
14488
|
+
align: "center"
|
|
14489
|
+
});
|
|
14490
|
+
}
|
|
14491
|
+
});
|
|
14492
|
+
if (hasAux) {
|
|
14493
|
+
const auxH = panelHeight - primaryH;
|
|
14494
|
+
const slot2 = width / auxBars.length;
|
|
14495
|
+
const auxBaseline = panelYBars + primaryH + auxH - AUX_BASELINE_PAD;
|
|
14496
|
+
const auxUsableH = auxBaseline - (panelYBars + primaryH + AUX_LABEL_BAND);
|
|
14497
|
+
const maxAuxV = Math.max(1, ...auxBars.map((b) => Number.isFinite(b.value) ? b.value : 0));
|
|
14498
|
+
auxBars.forEach((bar, i) => {
|
|
14499
|
+
const v = Number.isFinite(bar.value) ? bar.value : 0;
|
|
14500
|
+
const bh = Math.max(0, v / maxAuxV * auxUsableH);
|
|
14501
|
+
const x = i * slot2 + slot2 * 0.1;
|
|
14502
|
+
const w = slot2 * 0.8;
|
|
14503
|
+
const color = bar.color ?? DEFAULT_BAR_COLOR;
|
|
14504
|
+
out.push({
|
|
14505
|
+
type: "rect",
|
|
14506
|
+
id: `auxbar-${i}`,
|
|
14507
|
+
x,
|
|
14508
|
+
y: auxBaseline - bh,
|
|
14509
|
+
width: w,
|
|
14510
|
+
height: bh,
|
|
14511
|
+
color,
|
|
14512
|
+
fill: color
|
|
14513
|
+
});
|
|
14514
|
+
const label = bar.label ?? (auxBars.length <= 24 ? String(v) : void 0);
|
|
14515
|
+
if (label) {
|
|
14516
|
+
out.push({
|
|
14517
|
+
type: "text",
|
|
14518
|
+
x: x + w / 2,
|
|
14519
|
+
y: auxBaseline - bh - 8,
|
|
14520
|
+
text: label,
|
|
14521
|
+
color: "#374151",
|
|
14522
|
+
fontSize: 11,
|
|
14523
|
+
align: "center"
|
|
14524
|
+
});
|
|
14525
|
+
}
|
|
14526
|
+
});
|
|
14527
|
+
}
|
|
13859
14528
|
pointers.forEach((p) => {
|
|
13860
14529
|
if (p.index < 0 || p.index >= bars.length) return;
|
|
13861
14530
|
const cx = p.index * slot + slot / 2;
|
|
@@ -13863,7 +14532,7 @@ var init_AlgorithmCanvas = __esm({
|
|
|
13863
14532
|
out.push({
|
|
13864
14533
|
type: "arrow",
|
|
13865
14534
|
x1: cx,
|
|
13866
|
-
y1:
|
|
14535
|
+
y1: panelYBars + primaryH - 18,
|
|
13867
14536
|
x2: cx,
|
|
13868
14537
|
y2: baseline + 4,
|
|
13869
14538
|
color,
|
|
@@ -13873,7 +14542,7 @@ var init_AlgorithmCanvas = __esm({
|
|
|
13873
14542
|
out.push({
|
|
13874
14543
|
type: "text",
|
|
13875
14544
|
x: cx,
|
|
13876
|
-
y:
|
|
14545
|
+
y: panelYBars + primaryH - 8,
|
|
13877
14546
|
text: p.label,
|
|
13878
14547
|
color,
|
|
13879
14548
|
fontSize: 11,
|
|
@@ -13882,14 +14551,111 @@ var init_AlgorithmCanvas = __esm({
|
|
|
13882
14551
|
}
|
|
13883
14552
|
});
|
|
13884
14553
|
}
|
|
14554
|
+
if (slots.length > 0) {
|
|
14555
|
+
const panelYSlots = panelY.slots;
|
|
14556
|
+
const n = slots.length;
|
|
14557
|
+
const vertical = slotOrientation === "vertical";
|
|
14558
|
+
const vBoxH = panelHeight / n;
|
|
14559
|
+
const vBoxW = Math.min(width * 0.5, 120);
|
|
14560
|
+
const vBoxX = (width - vBoxW) / 2;
|
|
14561
|
+
const hCellW = width / n;
|
|
14562
|
+
const hBoxW = hCellW * 0.82;
|
|
14563
|
+
const hBoxH = Math.min(panelHeight * 0.6, 48);
|
|
14564
|
+
const hBoxY = panelYSlots + (panelHeight - hBoxH) / 2;
|
|
14565
|
+
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 };
|
|
14566
|
+
slots.forEach((s, i) => {
|
|
14567
|
+
const box = slotBox(i);
|
|
14568
|
+
const state = s.state ?? "filled";
|
|
14569
|
+
const fill = state === "empty" ? SLOT_EMPTY_FILL : state === "highlight" ? s.color ?? SLOT_HIGHLIGHT_DEFAULT : s.color ?? DEFAULT_BAR_COLOR;
|
|
14570
|
+
const stroke = state === "empty" ? SLOT_EMPTY_STROKE : SLOT_FILLED_STROKE;
|
|
14571
|
+
out.push({
|
|
14572
|
+
type: "rect",
|
|
14573
|
+
id: `slot-${i}`,
|
|
14574
|
+
x: box.x,
|
|
14575
|
+
y: box.y,
|
|
14576
|
+
width: box.width,
|
|
14577
|
+
height: box.height,
|
|
14578
|
+
color: stroke,
|
|
14579
|
+
fill
|
|
14580
|
+
});
|
|
14581
|
+
if (s.value != null && state !== "empty") {
|
|
14582
|
+
out.push({
|
|
14583
|
+
type: "text",
|
|
14584
|
+
x: box.x + box.width / 2,
|
|
14585
|
+
y: box.y + box.height / 2,
|
|
14586
|
+
text: String(s.value),
|
|
14587
|
+
color: SLOT_VALUE_TEXT_COLOR,
|
|
14588
|
+
fontSize: 12,
|
|
14589
|
+
align: "center"
|
|
14590
|
+
});
|
|
14591
|
+
}
|
|
14592
|
+
});
|
|
14593
|
+
if (bars.length === 0) {
|
|
14594
|
+
pointers.forEach((p) => {
|
|
14595
|
+
if (p.index < 0 || p.index >= slots.length) return;
|
|
14596
|
+
const box = slotBox(p.index);
|
|
14597
|
+
const color = p.color ?? DEFAULT_POINTER_COLOR;
|
|
14598
|
+
if (vertical) {
|
|
14599
|
+
const cy = box.y + box.height / 2;
|
|
14600
|
+
out.push({
|
|
14601
|
+
type: "arrow",
|
|
14602
|
+
x1: box.x + box.width + 34,
|
|
14603
|
+
y1: cy,
|
|
14604
|
+
x2: box.x + box.width + 4,
|
|
14605
|
+
y2: cy,
|
|
14606
|
+
color,
|
|
14607
|
+
lineWidth: 2
|
|
14608
|
+
});
|
|
14609
|
+
if (p.label) {
|
|
14610
|
+
out.push({
|
|
14611
|
+
type: "text",
|
|
14612
|
+
x: box.x + box.width + 38,
|
|
14613
|
+
y: cy,
|
|
14614
|
+
text: p.label,
|
|
14615
|
+
color,
|
|
14616
|
+
fontSize: 11,
|
|
14617
|
+
align: "left"
|
|
14618
|
+
});
|
|
14619
|
+
}
|
|
14620
|
+
} else {
|
|
14621
|
+
const cx = box.x + box.width / 2;
|
|
14622
|
+
out.push({
|
|
14623
|
+
type: "arrow",
|
|
14624
|
+
x1: cx,
|
|
14625
|
+
y1: panelYSlots + panelHeight - 18,
|
|
14626
|
+
x2: cx,
|
|
14627
|
+
y2: box.y + box.height + 4,
|
|
14628
|
+
color,
|
|
14629
|
+
lineWidth: 2
|
|
14630
|
+
});
|
|
14631
|
+
if (p.label) {
|
|
14632
|
+
out.push({
|
|
14633
|
+
type: "text",
|
|
14634
|
+
x: cx,
|
|
14635
|
+
y: panelYSlots + panelHeight - 8,
|
|
14636
|
+
text: p.label,
|
|
14637
|
+
color,
|
|
14638
|
+
fontSize: 11,
|
|
14639
|
+
align: "center"
|
|
14640
|
+
});
|
|
14641
|
+
}
|
|
14642
|
+
}
|
|
14643
|
+
});
|
|
14644
|
+
}
|
|
14645
|
+
}
|
|
13885
14646
|
if (cells.length > 0) {
|
|
14647
|
+
const panelYCells = panelY.cells;
|
|
13886
14648
|
const maxCol = Math.max(0, ...cells.map((c) => c.col)) + 1;
|
|
13887
14649
|
const maxRow = Math.max(0, ...cells.map((c) => c.row)) + 1;
|
|
13888
|
-
const
|
|
13889
|
-
const
|
|
14650
|
+
const colLabelH = colLabels.length > 0 ? 16 : 0;
|
|
14651
|
+
const rowLabelW = rowLabels.length > 0 ? 20 : 0;
|
|
14652
|
+
const gridX0 = rowLabelW;
|
|
14653
|
+
const gridY0 = panelYCells + colLabelH;
|
|
14654
|
+
const cw = (width - rowLabelW) / maxCol;
|
|
14655
|
+
const ch = (panelHeight - colLabelH) / maxRow;
|
|
13890
14656
|
cells.forEach((c, i) => {
|
|
13891
|
-
const x = c.col * cw;
|
|
13892
|
-
const y = c.row * ch;
|
|
14657
|
+
const x = gridX0 + c.col * cw;
|
|
14658
|
+
const y = gridY0 + c.row * ch;
|
|
13893
14659
|
const color = c.color ?? DEFAULT_CELL_COLOR;
|
|
13894
14660
|
out.push({
|
|
13895
14661
|
type: "rect",
|
|
@@ -13913,11 +14679,207 @@ var init_AlgorithmCanvas = __esm({
|
|
|
13913
14679
|
align: "center"
|
|
13914
14680
|
});
|
|
13915
14681
|
}
|
|
14682
|
+
if (c.corner && cw >= CORNER_MIN_CELL && ch >= CORNER_MIN_CELL) {
|
|
14683
|
+
const { tl, tr, bl, br } = c.corner;
|
|
14684
|
+
if (tl) {
|
|
14685
|
+
out.push({
|
|
14686
|
+
type: "text",
|
|
14687
|
+
x: x + CORNER_INSET_X,
|
|
14688
|
+
y: y + CORNER_INSET_Y,
|
|
14689
|
+
text: tl,
|
|
14690
|
+
color: CORNER_TEXT_COLOR,
|
|
14691
|
+
fontSize: CORNER_FONT_SIZE,
|
|
14692
|
+
align: "left"
|
|
14693
|
+
});
|
|
14694
|
+
}
|
|
14695
|
+
if (tr) {
|
|
14696
|
+
out.push({
|
|
14697
|
+
type: "text",
|
|
14698
|
+
x: x + cw - CORNER_INSET_X,
|
|
14699
|
+
y: y + CORNER_INSET_Y,
|
|
14700
|
+
text: tr,
|
|
14701
|
+
color: CORNER_TEXT_COLOR,
|
|
14702
|
+
fontSize: CORNER_FONT_SIZE,
|
|
14703
|
+
align: "right"
|
|
14704
|
+
});
|
|
14705
|
+
}
|
|
14706
|
+
if (bl) {
|
|
14707
|
+
out.push({
|
|
14708
|
+
type: "text",
|
|
14709
|
+
x: x + CORNER_INSET_X,
|
|
14710
|
+
y: y + ch - CORNER_INSET_Y,
|
|
14711
|
+
text: bl,
|
|
14712
|
+
color: CORNER_TEXT_COLOR,
|
|
14713
|
+
fontSize: CORNER_FONT_SIZE,
|
|
14714
|
+
align: "left"
|
|
14715
|
+
});
|
|
14716
|
+
}
|
|
14717
|
+
if (br) {
|
|
14718
|
+
out.push({
|
|
14719
|
+
type: "text",
|
|
14720
|
+
x: x + cw - CORNER_INSET_X,
|
|
14721
|
+
y: y + ch - CORNER_INSET_Y,
|
|
14722
|
+
text: br,
|
|
14723
|
+
color: CORNER_TEXT_COLOR,
|
|
14724
|
+
fontSize: CORNER_FONT_SIZE,
|
|
14725
|
+
align: "right"
|
|
14726
|
+
});
|
|
14727
|
+
}
|
|
14728
|
+
}
|
|
14729
|
+
});
|
|
14730
|
+
colLabels.forEach((l) => {
|
|
14731
|
+
out.push({
|
|
14732
|
+
type: "text",
|
|
14733
|
+
x: gridX0 + l.index * cw + cw / 2,
|
|
14734
|
+
y: panelYCells + colLabelH / 2,
|
|
14735
|
+
text: l.text,
|
|
14736
|
+
color: l.color ?? AXIS_LABEL_COLOR,
|
|
14737
|
+
fontSize: AXIS_LABEL_FONT_SIZE,
|
|
14738
|
+
align: "center"
|
|
14739
|
+
});
|
|
14740
|
+
});
|
|
14741
|
+
rowLabels.forEach((l) => {
|
|
14742
|
+
out.push({
|
|
14743
|
+
type: "text",
|
|
14744
|
+
x: rowLabelW - 6,
|
|
14745
|
+
y: gridY0 + l.index * ch + ch / 2,
|
|
14746
|
+
text: l.text,
|
|
14747
|
+
color: l.color ?? AXIS_LABEL_COLOR,
|
|
14748
|
+
fontSize: AXIS_LABEL_FONT_SIZE,
|
|
14749
|
+
align: "right"
|
|
14750
|
+
});
|
|
14751
|
+
});
|
|
14752
|
+
}
|
|
14753
|
+
if (buckets.length > 0) {
|
|
14754
|
+
const panelYBuckets = panelY.buckets;
|
|
14755
|
+
const bucketCount = Math.max(0, ...buckets.map((b) => b.index)) + 1;
|
|
14756
|
+
const rowH = panelHeight / bucketCount;
|
|
14757
|
+
const indexColW = Math.min(width * 0.12, 40);
|
|
14758
|
+
const maxChainLen = Math.max(1, ...buckets.map((b) => b.entries.length));
|
|
14759
|
+
const entryW = Math.min(BUCKET_ENTRY_MAX_W, Math.max(BUCKET_ENTRY_MIN_W, (width - indexColW - 8) / maxChainLen));
|
|
14760
|
+
const maxVisible = Math.floor((width - indexColW - 4) / entryW);
|
|
14761
|
+
buckets.forEach((b) => {
|
|
14762
|
+
const rowY = panelYBuckets + b.index * rowH;
|
|
14763
|
+
out.push({
|
|
14764
|
+
type: "rect",
|
|
14765
|
+
id: `bucket-index-${b.index}`,
|
|
14766
|
+
x: 2,
|
|
14767
|
+
y: rowY + 2,
|
|
14768
|
+
width: indexColW - 4,
|
|
14769
|
+
height: rowH - 4,
|
|
14770
|
+
color: BUCKET_INDEX_STROKE,
|
|
14771
|
+
fill: BUCKET_INDEX_FILL
|
|
14772
|
+
});
|
|
14773
|
+
out.push({
|
|
14774
|
+
type: "text",
|
|
14775
|
+
x: 2 + (indexColW - 4) / 2,
|
|
14776
|
+
y: rowY + rowH / 2,
|
|
14777
|
+
text: String(b.index),
|
|
14778
|
+
color: BUCKET_INDEX_TEXT,
|
|
14779
|
+
fontSize: 10,
|
|
14780
|
+
align: "center"
|
|
14781
|
+
});
|
|
14782
|
+
const overflow = b.entries.length > maxVisible;
|
|
14783
|
+
const visibleCount = overflow ? Math.max(0, maxVisible - 1) : b.entries.length;
|
|
14784
|
+
for (let j = 0; j < visibleCount; j++) {
|
|
14785
|
+
const entry = b.entries[j];
|
|
14786
|
+
const ex = indexColW + 4 + j * entryW;
|
|
14787
|
+
const state = entry.state ?? "default";
|
|
14788
|
+
const fill = state === "highlight" ? entry.color ?? BUCKET_ENTRY_HIGHLIGHT : state === "probing" ? entry.color ?? BUCKET_ENTRY_PROBING : entry.color ?? BUCKET_ENTRY_DEFAULT;
|
|
14789
|
+
out.push({
|
|
14790
|
+
type: "rect",
|
|
14791
|
+
id: `bucket-${b.index}-${j}`,
|
|
14792
|
+
x: ex,
|
|
14793
|
+
y: rowY + 2,
|
|
14794
|
+
width: entryW - 2,
|
|
14795
|
+
height: rowH - 4,
|
|
14796
|
+
color: fill,
|
|
14797
|
+
fill
|
|
14798
|
+
});
|
|
14799
|
+
if (entryW >= 20 && rowH >= 16) {
|
|
14800
|
+
out.push({
|
|
14801
|
+
type: "text",
|
|
14802
|
+
x: ex + (entryW - 2) / 2,
|
|
14803
|
+
y: rowY + rowH / 2,
|
|
14804
|
+
text: entry.label,
|
|
14805
|
+
color: BUCKET_ENTRY_TEXT,
|
|
14806
|
+
fontSize: 10,
|
|
14807
|
+
align: "center"
|
|
14808
|
+
});
|
|
14809
|
+
}
|
|
14810
|
+
}
|
|
14811
|
+
if (overflow) {
|
|
14812
|
+
const ex = indexColW + 4 + visibleCount * entryW;
|
|
14813
|
+
out.push({
|
|
14814
|
+
type: "rect",
|
|
14815
|
+
id: `bucket-${b.index}-overflow`,
|
|
14816
|
+
x: ex,
|
|
14817
|
+
y: rowY + 2,
|
|
14818
|
+
width: entryW - 2,
|
|
14819
|
+
height: rowH - 4,
|
|
14820
|
+
color: BUCKET_ENTRY_DEFAULT,
|
|
14821
|
+
fill: BUCKET_ENTRY_DEFAULT
|
|
14822
|
+
});
|
|
14823
|
+
out.push({
|
|
14824
|
+
type: "text",
|
|
14825
|
+
x: ex + (entryW - 2) / 2,
|
|
14826
|
+
y: rowY + rowH / 2,
|
|
14827
|
+
text: `+${b.entries.length - visibleCount}`,
|
|
14828
|
+
color: BUCKET_ENTRY_TEXT,
|
|
14829
|
+
fontSize: 10,
|
|
14830
|
+
align: "center"
|
|
14831
|
+
});
|
|
14832
|
+
}
|
|
14833
|
+
});
|
|
14834
|
+
}
|
|
14835
|
+
if (frames.length > 0) {
|
|
14836
|
+
const panelYFrames = panelY.frames;
|
|
14837
|
+
const n = frames.length;
|
|
14838
|
+
const frameH = panelHeight / n;
|
|
14839
|
+
const x = 8;
|
|
14840
|
+
const w = width - 16;
|
|
14841
|
+
frames.forEach((f3, i) => {
|
|
14842
|
+
const y = panelYFrames + panelHeight - (i + 1) * frameH;
|
|
14843
|
+
const state = f3.state ?? "active";
|
|
14844
|
+
const fill = state === "returning" ? f3.color ?? FRAME_RETURNING_COLOR : state === "done" ? f3.color ?? FRAME_DONE_COLOR : f3.color ?? FRAME_ACTIVE_COLOR;
|
|
14845
|
+
out.push({ type: "rect", id: `frame-${i}`, x, y, width: w, height: frameH, color: fill, fill });
|
|
14846
|
+
if (frameH >= FRAME_TWO_LINE_MIN_H) {
|
|
14847
|
+
out.push({
|
|
14848
|
+
type: "text",
|
|
14849
|
+
x: 16,
|
|
14850
|
+
y: y + frameH * 0.35,
|
|
14851
|
+
text: f3.label,
|
|
14852
|
+
color: FRAME_LABEL_COLOR,
|
|
14853
|
+
fontSize: 10,
|
|
14854
|
+
align: "left"
|
|
14855
|
+
});
|
|
14856
|
+
if (f3.detail) {
|
|
14857
|
+
out.push({
|
|
14858
|
+
type: "text",
|
|
14859
|
+
x: 16,
|
|
14860
|
+
y: y + frameH * 0.7,
|
|
14861
|
+
text: f3.detail,
|
|
14862
|
+
color: FRAME_DETAIL_COLOR,
|
|
14863
|
+
fontSize: 10,
|
|
14864
|
+
align: "left"
|
|
14865
|
+
});
|
|
14866
|
+
}
|
|
14867
|
+
} else {
|
|
14868
|
+
out.push({
|
|
14869
|
+
type: "text",
|
|
14870
|
+
x: 16,
|
|
14871
|
+
y: y + frameH / 2,
|
|
14872
|
+
text: f3.label,
|
|
14873
|
+
color: FRAME_LABEL_COLOR,
|
|
14874
|
+
fontSize: 10,
|
|
14875
|
+
align: "left"
|
|
14876
|
+
});
|
|
14877
|
+
}
|
|
13916
14878
|
});
|
|
13917
14879
|
}
|
|
13918
14880
|
out.push(...shapes);
|
|
13919
14881
|
return out;
|
|
13920
|
-
}, [bars, cells, pointers, shapes, width, height]);
|
|
14882
|
+
}, [bars, cells, pointers, ranges, slots, slotOrientation, frames, buckets, auxBars, rowLabels, colLabels, shapes, width, height]);
|
|
13921
14883
|
return /* @__PURE__ */ jsxRuntime.jsx(Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
|
|
13922
14884
|
title ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", children: title }) : null,
|
|
13923
14885
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -14906,23 +15868,422 @@ var init_BehaviorView = __esm({
|
|
|
14906
15868
|
BehaviorView.displayName = "BehaviorView";
|
|
14907
15869
|
}
|
|
14908
15870
|
});
|
|
14909
|
-
|
|
15871
|
+
function LearningScene3D({
|
|
15872
|
+
className,
|
|
15873
|
+
width = 600,
|
|
15874
|
+
height = 400,
|
|
15875
|
+
title,
|
|
15876
|
+
backgroundColor,
|
|
15877
|
+
drawables,
|
|
15878
|
+
camera,
|
|
15879
|
+
lighting,
|
|
15880
|
+
post,
|
|
15881
|
+
showGrid = false,
|
|
15882
|
+
shadows,
|
|
15883
|
+
interactive,
|
|
15884
|
+
isLoading,
|
|
15885
|
+
error,
|
|
15886
|
+
onItemClick
|
|
15887
|
+
}) {
|
|
15888
|
+
const instanceId = React87.useId().replace(/[^a-zA-Z0-9]/g, "");
|
|
15889
|
+
const clickEvent = onItemClick ? `LEARNING_SCENE_3D.ITEM_CLICK.${instanceId}` : void 0;
|
|
15890
|
+
const onItemClickRef = React87.useRef(onItemClick);
|
|
15891
|
+
onItemClickRef.current = onItemClick;
|
|
15892
|
+
useEventListener(`UI:${clickEvent ?? "LEARNING_SCENE_3D.ITEM_CLICK.__disabled"}`, (event) => {
|
|
15893
|
+
const unitId = event.payload?.unitId;
|
|
15894
|
+
if (typeof unitId === "string") onItemClickRef.current?.(unitId);
|
|
15895
|
+
});
|
|
15896
|
+
if (typeof process !== "undefined" && process.env && process.env.NODE_ENV !== "production" && post?.bloom) {
|
|
15897
|
+
const unknownKeys = Object.keys(post.bloom).filter((k) => !KNOWN_BLOOM_KEYS.has(k));
|
|
15898
|
+
if (unknownKeys.length > 0) {
|
|
15899
|
+
sceneLog.debug("post.bloom has unrecognized keys \u2014 only intensity/threshold/smoothing are read", { unknownKeys });
|
|
15900
|
+
}
|
|
15901
|
+
}
|
|
15902
|
+
const props3d = {
|
|
15903
|
+
drawables,
|
|
15904
|
+
isLoading,
|
|
15905
|
+
error: error ? error.message : null,
|
|
15906
|
+
cameraMode: camera?.mode ?? "perspective",
|
|
15907
|
+
...camera?.zoom !== void 0 ? { zoom: camera.zoom } : {},
|
|
15908
|
+
...camera?.fov !== void 0 ? { fov: camera.fov } : {},
|
|
15909
|
+
...camera?.azimuth !== void 0 ? { azimuth: camera.azimuth } : {},
|
|
15910
|
+
...camera?.elevation !== void 0 ? { elevation: camera.elevation } : {},
|
|
15911
|
+
...camera?.target !== void 0 ? { followTarget: camera.target } : {},
|
|
15912
|
+
backgroundColor,
|
|
15913
|
+
showGrid,
|
|
15914
|
+
...shadows !== void 0 ? { shadows } : {},
|
|
15915
|
+
...interactive !== void 0 ? { controlsEnabled: interactive } : {},
|
|
15916
|
+
lighting,
|
|
15917
|
+
post,
|
|
15918
|
+
...clickEvent !== void 0 ? { unitClickEvent: clickEvent } : {}
|
|
15919
|
+
};
|
|
15920
|
+
return /* @__PURE__ */ jsxRuntime.jsx(Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
|
|
15921
|
+
title ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", children: title }) : null,
|
|
15922
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { width, height, display: "flex" }, children: /* @__PURE__ */ jsxRuntime.jsx(React87.Suspense, { fallback: null, children: /* @__PURE__ */ jsxRuntime.jsx(Canvas3DHost2, { ...props3d }) }) })
|
|
15923
|
+
] }) });
|
|
15924
|
+
}
|
|
15925
|
+
function meshSphere(id, x, y, z, radius, color, opts) {
|
|
15926
|
+
return {
|
|
15927
|
+
type: "draw-mesh",
|
|
15928
|
+
...id !== void 0 ? { id } : {},
|
|
15929
|
+
shape: opts?.shape ?? "sphere",
|
|
15930
|
+
position: { x, y, z },
|
|
15931
|
+
radius,
|
|
15932
|
+
pivot: "center",
|
|
15933
|
+
segments: opts?.segments ?? 24,
|
|
15934
|
+
material: { color, ...opts?.material },
|
|
15935
|
+
...opts?.opacity !== void 0 ? { opacity: opts.opacity } : {}
|
|
15936
|
+
};
|
|
15937
|
+
}
|
|
15938
|
+
function axisRotation(from, to) {
|
|
15939
|
+
const wx = to[0] - from[0];
|
|
15940
|
+
const wy = to[2] - from[2];
|
|
15941
|
+
const wz = to[1] - from[1];
|
|
15942
|
+
const len = Math.sqrt(wx * wx + wy * wy + wz * wz);
|
|
15943
|
+
const tilt = Math.acos(Math.min(1, Math.max(-1, len > 0 ? wy / len : 1)));
|
|
15944
|
+
return [0, Math.atan2(wz, -wx), tilt];
|
|
15945
|
+
}
|
|
15946
|
+
function segmentLength(from, to) {
|
|
15947
|
+
const dx = to[0] - from[0];
|
|
15948
|
+
const dy = to[1] - from[1];
|
|
15949
|
+
const dz = to[2] - from[2];
|
|
15950
|
+
return Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
15951
|
+
}
|
|
15952
|
+
function cylinderBetween(from, to, radius, color) {
|
|
15953
|
+
const len = segmentLength(from, to);
|
|
15954
|
+
if (len < 1e-6) return null;
|
|
15955
|
+
return {
|
|
15956
|
+
type: "draw-mesh",
|
|
15957
|
+
shape: "cylinder",
|
|
15958
|
+
position: { x: (from[0] + to[0]) / 2, y: (from[1] + to[1]) / 2, z: (from[2] + to[2]) / 2 },
|
|
15959
|
+
radius,
|
|
15960
|
+
height: len,
|
|
15961
|
+
rotation: axisRotation(from, to),
|
|
15962
|
+
pivot: "center",
|
|
15963
|
+
segments: 12,
|
|
15964
|
+
material: { color }
|
|
15965
|
+
};
|
|
15966
|
+
}
|
|
15967
|
+
function arrowBetween(from, to, color, shaftRadius = 0.08, id) {
|
|
15968
|
+
const len = segmentLength(from, to);
|
|
15969
|
+
if (len < 1e-6) return null;
|
|
15970
|
+
const tipLen = Math.min(shaftRadius * 8, len * 0.35);
|
|
15971
|
+
const k = (len - tipLen) / len;
|
|
15972
|
+
const delta = [to[0] - from[0], to[1] - from[1], to[2] - from[2]];
|
|
15973
|
+
const shaftEnd = [delta[0] * k, delta[1] * k, delta[2] * k];
|
|
15974
|
+
const tipStart = shaftEnd;
|
|
15975
|
+
const tipEnd = delta;
|
|
15976
|
+
const shaft = cylinderBetween([0, 0, 0], shaftEnd, shaftRadius, color);
|
|
15977
|
+
const tipLenActual = segmentLength(tipStart, tipEnd);
|
|
15978
|
+
const tip = {
|
|
15979
|
+
type: "draw-mesh",
|
|
15980
|
+
shape: "cone",
|
|
15981
|
+
position: {
|
|
15982
|
+
x: (tipStart[0] + tipEnd[0]) / 2,
|
|
15983
|
+
y: (tipStart[1] + tipEnd[1]) / 2,
|
|
15984
|
+
z: (tipStart[2] + tipEnd[2]) / 2
|
|
15985
|
+
},
|
|
15986
|
+
radius: shaftRadius * 3,
|
|
15987
|
+
height: tipLenActual,
|
|
15988
|
+
rotation: axisRotation(tipStart, tipEnd),
|
|
15989
|
+
pivot: "center",
|
|
15990
|
+
segments: 12,
|
|
15991
|
+
material: { color }
|
|
15992
|
+
};
|
|
15993
|
+
return {
|
|
15994
|
+
type: "draw-group",
|
|
15995
|
+
...id !== void 0 ? { id } : {},
|
|
15996
|
+
position: { x: from[0], y: from[1], z: from[2] },
|
|
15997
|
+
items: tipLenActual < 1e-6 ? shaft ? [shaft] : [] : shaft ? [shaft, tip] : [tip]
|
|
15998
|
+
};
|
|
15999
|
+
}
|
|
16000
|
+
function billboardLabel(text, x, y, z, opts) {
|
|
16001
|
+
return {
|
|
16002
|
+
type: "draw-text",
|
|
16003
|
+
text,
|
|
16004
|
+
position: { x, y, z },
|
|
16005
|
+
color: opts?.color ?? "#111827"
|
|
16006
|
+
};
|
|
16007
|
+
}
|
|
16008
|
+
function labelColorForBackground(backgroundColor) {
|
|
16009
|
+
const m = /^#(?:([0-9a-f]{3})|([0-9a-f]{6}))$/i.exec(backgroundColor ?? "");
|
|
16010
|
+
if (!m) return "#111827";
|
|
16011
|
+
const hex = m[1] !== void 0 ? m[1].split("").map((c) => c + c).join("") : m[2];
|
|
16012
|
+
const r = parseInt(hex.slice(0, 2), 16) / 255;
|
|
16013
|
+
const g = parseInt(hex.slice(2, 4), 16) / 255;
|
|
16014
|
+
const b = parseInt(hex.slice(4, 6), 16) / 255;
|
|
16015
|
+
const luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
16016
|
+
return luminance < 0.5 ? "#e5e7eb" : "#111827";
|
|
16017
|
+
}
|
|
16018
|
+
function get3DClickPayload(onShapeClick, idToIndex) {
|
|
16019
|
+
if (!onShapeClick) return void 0;
|
|
16020
|
+
return (id) => onShapeClick({ id, index: idToIndex.get(id) ?? -1 });
|
|
16021
|
+
}
|
|
16022
|
+
function polylineTube(points, radius, color, opts) {
|
|
16023
|
+
const maxSegments = opts?.maxSegments ?? 128;
|
|
16024
|
+
let pts = points;
|
|
16025
|
+
if (pts.length - 1 > maxSegments) {
|
|
16026
|
+
const step = (pts.length - 1) / maxSegments;
|
|
16027
|
+
const kept = [pts[0]];
|
|
16028
|
+
for (let s = 1; s < maxSegments; s++) kept.push(pts[Math.round(s * step)]);
|
|
16029
|
+
kept.push(pts[pts.length - 1]);
|
|
16030
|
+
pts = kept;
|
|
16031
|
+
}
|
|
16032
|
+
const out = [];
|
|
16033
|
+
for (let i = 0; i < pts.length - 1; i++) {
|
|
16034
|
+
const seg = cylinderBetween(pts[i], pts[i + 1], radius, color);
|
|
16035
|
+
if (seg) out.push(opts?.opacity !== void 0 ? { ...seg, opacity: opts.opacity } : seg);
|
|
16036
|
+
}
|
|
16037
|
+
return out;
|
|
16038
|
+
}
|
|
16039
|
+
function heightFieldMesh(spec) {
|
|
16040
|
+
const { nx, ny, heights, spacing = 1, x = 0, y = 0 } = spec;
|
|
16041
|
+
const flatShading = spec.flatShading ?? true;
|
|
16042
|
+
const vertices = [];
|
|
16043
|
+
for (let iy = 0; iy < ny; iy++) {
|
|
16044
|
+
for (let ix = 0; ix < nx; ix++) {
|
|
16045
|
+
vertices.push([
|
|
16046
|
+
x + (ix - (nx - 1) / 2) * spacing,
|
|
16047
|
+
y + (iy - (ny - 1) / 2) * spacing,
|
|
16048
|
+
heights[iy * nx + ix] ?? 0
|
|
16049
|
+
]);
|
|
16050
|
+
}
|
|
16051
|
+
}
|
|
16052
|
+
const bands = [...spec.bands ?? []].sort((a, b) => (a.min ?? -Infinity) - (b.min ?? -Infinity));
|
|
16053
|
+
const facesByBand = /* @__PURE__ */ new Map();
|
|
16054
|
+
for (let iy = 0; iy < ny - 1; iy++) {
|
|
16055
|
+
for (let ix = 0; ix < nx - 1; ix++) {
|
|
16056
|
+
const v00 = iy * nx + ix;
|
|
16057
|
+
const v10 = iy * nx + ix + 1;
|
|
16058
|
+
const v01 = (iy + 1) * nx + ix;
|
|
16059
|
+
const v11 = (iy + 1) * nx + ix + 1;
|
|
16060
|
+
for (const face of [[v00, v10, v01], [v10, v11, v01]]) {
|
|
16061
|
+
const centroid = (vertices[face[0]][2] + vertices[face[1]][2] + vertices[face[2]][2]) / 3;
|
|
16062
|
+
let band = null;
|
|
16063
|
+
for (const b of bands) {
|
|
16064
|
+
if ((b.min ?? -Infinity) <= centroid) band = b;
|
|
16065
|
+
}
|
|
16066
|
+
const key = bands.length > 0 ? band : null;
|
|
16067
|
+
const list = facesByBand.get(key) ?? [];
|
|
16068
|
+
list.push(face);
|
|
16069
|
+
facesByBand.set(key, list);
|
|
16070
|
+
}
|
|
16071
|
+
}
|
|
16072
|
+
}
|
|
16073
|
+
const out = [];
|
|
16074
|
+
for (const [band, faces] of facesByBand) {
|
|
16075
|
+
if (faces.length === 0) continue;
|
|
16076
|
+
out.push({
|
|
16077
|
+
type: "draw-mesh",
|
|
16078
|
+
shape: "polyhedron",
|
|
16079
|
+
position: { x: 0, y: 0, z: 0 },
|
|
16080
|
+
vertices,
|
|
16081
|
+
faces,
|
|
16082
|
+
pivot: "center",
|
|
16083
|
+
material: { color: band?.color ?? spec.color ?? "#64748b", flatShading, side: "double" },
|
|
16084
|
+
...spec.opacity !== void 0 ? { opacity: spec.opacity } : {}
|
|
16085
|
+
});
|
|
16086
|
+
}
|
|
16087
|
+
return out;
|
|
16088
|
+
}
|
|
16089
|
+
function arrowField(vectors, opts) {
|
|
16090
|
+
const scale = opts?.scale ?? 1;
|
|
16091
|
+
const out = [];
|
|
16092
|
+
for (const v of vectors) {
|
|
16093
|
+
const to = [
|
|
16094
|
+
v.from[0] + v.delta[0] * scale,
|
|
16095
|
+
v.from[1] + v.delta[1] * scale,
|
|
16096
|
+
v.from[2] + v.delta[2] * scale
|
|
16097
|
+
];
|
|
16098
|
+
const arrow = arrowBetween(v.from, to, v.color ?? "#dc2626", v.width, v.id);
|
|
16099
|
+
if (arrow) out.push(arrow);
|
|
16100
|
+
if (v.label) out.push(billboardLabel(v.label, to[0], to[1], to[2], { color: opts?.labelColor }));
|
|
16101
|
+
}
|
|
16102
|
+
return out;
|
|
16103
|
+
}
|
|
16104
|
+
function helixDrawables(spec, opts) {
|
|
16105
|
+
const count = spec.count ?? spec.rungs?.length ?? 0;
|
|
16106
|
+
const rungs = Array.from({ length: count }, (_, i) => spec.rungs?.[i] ?? {});
|
|
16107
|
+
const radius = spec.radius ?? 1;
|
|
16108
|
+
const rise = spec.rise ?? 0.34;
|
|
16109
|
+
const twistRad = (spec.twistDeg ?? 36) * (Math.PI / 180);
|
|
16110
|
+
const strandAColor = spec.strandAColor ?? "#38bdf8";
|
|
16111
|
+
const strandBColor = spec.strandBColor ?? "#fb923c";
|
|
16112
|
+
const backboneRadius = spec.backboneRadius ?? 0.16;
|
|
16113
|
+
const rungRadius = spec.rungRadius ?? 0.12;
|
|
16114
|
+
const cx = spec.x ?? 0;
|
|
16115
|
+
const cy = spec.y ?? 0;
|
|
16116
|
+
const cz = spec.z ?? 0;
|
|
16117
|
+
const unwoundCount = spec.unwoundCount ?? 0;
|
|
16118
|
+
const unwindSpread = spec.unwindSpread ?? 1.8;
|
|
16119
|
+
const strandA = [];
|
|
16120
|
+
const strandB = [];
|
|
16121
|
+
for (let i = 0; i < count; i++) {
|
|
16122
|
+
const yi = cy + (i - (count - 1) / 2) * rise;
|
|
16123
|
+
const theta = i * twistRad;
|
|
16124
|
+
const s = i < unwoundCount ? unwindSpread : 1;
|
|
16125
|
+
strandA.push([cx + s * radius * Math.cos(theta), yi, cz + s * radius * Math.sin(theta)]);
|
|
16126
|
+
strandB.push([cx + s * radius * Math.cos(theta + Math.PI), yi, cz + s * radius * Math.sin(theta + Math.PI)]);
|
|
16127
|
+
}
|
|
16128
|
+
const out = [];
|
|
16129
|
+
for (let i = 0; i < count; i++) {
|
|
16130
|
+
out.push(meshSphere(`hx-a-${i}`, strandA[i][0], strandA[i][1], strandA[i][2], backboneRadius, strandAColor));
|
|
16131
|
+
out.push(meshSphere(`hx-b-${i}`, strandB[i][0], strandB[i][1], strandB[i][2], backboneRadius, strandBColor));
|
|
16132
|
+
if (i > 0) {
|
|
16133
|
+
const segA = cylinderBetween(strandA[i - 1], strandA[i], backboneRadius, strandAColor);
|
|
16134
|
+
if (segA) out.push(segA);
|
|
16135
|
+
const segB = cylinderBetween(strandB[i - 1], strandB[i], backboneRadius, strandBColor);
|
|
16136
|
+
if (segB) out.push(segB);
|
|
16137
|
+
}
|
|
16138
|
+
const rung = rungs[i];
|
|
16139
|
+
const rungColor = rung.color ?? "#94a3b8";
|
|
16140
|
+
const rod = cylinderBetween(strandA[i], strandB[i], rungRadius, rungColor);
|
|
16141
|
+
if (rod) out.push(rod);
|
|
16142
|
+
const mid = [
|
|
16143
|
+
(strandA[i][0] + strandB[i][0]) / 2,
|
|
16144
|
+
(strandA[i][1] + strandB[i][1]) / 2,
|
|
16145
|
+
(strandA[i][2] + strandB[i][2]) / 2
|
|
16146
|
+
];
|
|
16147
|
+
const markerRadius = rung.radius ?? rungRadius;
|
|
16148
|
+
out.push(meshSphere(rung.id, mid[0], mid[1], mid[2], markerRadius, rungColor));
|
|
16149
|
+
if (rung.label) out.push(billboardLabel(rung.label, mid[0], mid[1], mid[2] + markerRadius, { color: opts?.labelColor }));
|
|
16150
|
+
}
|
|
16151
|
+
return out;
|
|
16152
|
+
}
|
|
16153
|
+
function latticeDrawables(spec, opts) {
|
|
16154
|
+
const nx = spec.nx ?? 2;
|
|
16155
|
+
const ny = spec.ny ?? 2;
|
|
16156
|
+
const nz = spec.nz ?? 2;
|
|
16157
|
+
const latticeConstant = spec.latticeConstant ?? 2;
|
|
16158
|
+
const bondRadius = spec.bondRadius ?? 0.06;
|
|
16159
|
+
const highlightCell = spec.highlightCell ?? false;
|
|
16160
|
+
const dimColor = spec.dimColor ?? "#475569";
|
|
16161
|
+
const showLabels = spec.showLabels ?? false;
|
|
16162
|
+
const selectedColor = spec.selectedColor ?? "#f59e0b";
|
|
16163
|
+
const posByKey = /* @__PURE__ */ new Map();
|
|
16164
|
+
const inCellByKey = /* @__PURE__ */ new Map();
|
|
16165
|
+
const out = [];
|
|
16166
|
+
for (const site of spec.basis) {
|
|
16167
|
+
const snx = site.xEdge ? nx + 1 : nx;
|
|
16168
|
+
const sny = site.yEdge ? ny + 1 : ny;
|
|
16169
|
+
const snz = site.zEdge ? nz + 1 : nz;
|
|
16170
|
+
for (let i = 0; i < snx; i++) {
|
|
16171
|
+
for (let j = 0; j < sny; j++) {
|
|
16172
|
+
for (let k = 0; k < snz; k++) {
|
|
16173
|
+
const key = `${site.key}-${i}-${j}-${k}`;
|
|
16174
|
+
const inCell = i + site.dx <= 1 && j + site.dy <= 1 && k + site.dz <= 1;
|
|
16175
|
+
const pos = [
|
|
16176
|
+
(i + site.dx) * latticeConstant - nx * latticeConstant / 2,
|
|
16177
|
+
(j + site.dy) * latticeConstant - ny * latticeConstant / 2,
|
|
16178
|
+
(k + site.dz) * latticeConstant - nz * latticeConstant / 2
|
|
16179
|
+
];
|
|
16180
|
+
posByKey.set(key, pos);
|
|
16181
|
+
inCellByKey.set(key, inCell);
|
|
16182
|
+
const isSelected = spec.selectedId === `lat-${key}`;
|
|
16183
|
+
const color = isSelected ? selectedColor : highlightCell && !inCell ? dimColor : site.color ?? "#2563eb";
|
|
16184
|
+
const radius = (site.radius ?? 0.3) * (isSelected ? 1.4 : 1);
|
|
16185
|
+
out.push(meshSphere(`lat-${key}`, pos[0], pos[1], pos[2], radius, color));
|
|
16186
|
+
if (showLabels && site.element) {
|
|
16187
|
+
out.push(billboardLabel(site.element, pos[0], pos[1], pos[2] + radius, { color: opts?.labelColor }));
|
|
16188
|
+
}
|
|
16189
|
+
}
|
|
16190
|
+
}
|
|
16191
|
+
}
|
|
16192
|
+
}
|
|
16193
|
+
const basisByKey = new Map(spec.basis.map((s) => [s.key, s]));
|
|
16194
|
+
for (const bond of spec.bonds ?? []) {
|
|
16195
|
+
const fromSite = basisByKey.get(bond.from);
|
|
16196
|
+
const toSite = basisByKey.get(bond.to);
|
|
16197
|
+
if (!fromSite || !toSite) continue;
|
|
16198
|
+
const fnx = fromSite.xEdge ? nx + 1 : nx;
|
|
16199
|
+
const fny = fromSite.yEdge ? ny + 1 : ny;
|
|
16200
|
+
const fnz = fromSite.zEdge ? nz + 1 : nz;
|
|
16201
|
+
const tnx = toSite.xEdge ? nx + 1 : nx;
|
|
16202
|
+
const tny = toSite.yEdge ? ny + 1 : ny;
|
|
16203
|
+
const tnz = toSite.zEdge ? nz + 1 : nz;
|
|
16204
|
+
const bdx = bond.dx ?? 0;
|
|
16205
|
+
const bdy = bond.dy ?? 0;
|
|
16206
|
+
const bdz = bond.dz ?? 0;
|
|
16207
|
+
for (let i = 0; i < fnx; i++) {
|
|
16208
|
+
for (let j = 0; j < fny; j++) {
|
|
16209
|
+
for (let k = 0; k < fnz; k++) {
|
|
16210
|
+
const ti = i + bdx;
|
|
16211
|
+
const tj = j + bdy;
|
|
16212
|
+
const tk = k + bdz;
|
|
16213
|
+
if (ti < 0 || ti >= tnx || tj < 0 || tj >= tny || tk < 0 || tk >= tnz) continue;
|
|
16214
|
+
const fromKey = `${fromSite.key}-${i}-${j}-${k}`;
|
|
16215
|
+
const toKey = `${toSite.key}-${ti}-${tj}-${tk}`;
|
|
16216
|
+
const fromPos = posByKey.get(fromKey);
|
|
16217
|
+
const toPos = posByKey.get(toKey);
|
|
16218
|
+
if (!fromPos || !toPos) continue;
|
|
16219
|
+
const dimmed = highlightCell && !(inCellByKey.get(fromKey) && inCellByKey.get(toKey));
|
|
16220
|
+
const seg = cylinderBetween(fromPos, toPos, bondRadius, dimmed ? dimColor : bond.color ?? "#6b7280");
|
|
16221
|
+
if (seg) out.push(seg);
|
|
16222
|
+
}
|
|
16223
|
+
}
|
|
16224
|
+
}
|
|
16225
|
+
}
|
|
16226
|
+
return out;
|
|
16227
|
+
}
|
|
16228
|
+
var sceneLog, KNOWN_BLOOM_KEYS, Canvas3DHost2;
|
|
16229
|
+
var init_learningScene3D = __esm({
|
|
16230
|
+
"components/learning/molecules/learningScene3D.tsx"() {
|
|
16231
|
+
"use client";
|
|
16232
|
+
init_atoms();
|
|
16233
|
+
init_Stack();
|
|
16234
|
+
init_useEventBus();
|
|
16235
|
+
sceneLog = logger.createLogger("almadar:ui:learning-scene-3d");
|
|
16236
|
+
KNOWN_BLOOM_KEYS = /* @__PURE__ */ new Set(["intensity", "threshold", "smoothing"]);
|
|
16237
|
+
Canvas3DHost2 = React87.lazy(
|
|
16238
|
+
() => import('@almadar/ui/components/molecules/game/three').then((m) => ({ default: m.Canvas3DHost }))
|
|
16239
|
+
);
|
|
16240
|
+
LearningScene3D.displayName = "LearningScene3D";
|
|
16241
|
+
}
|
|
16242
|
+
});
|
|
16243
|
+
var biologyLog, BIO_BAND_COLORS, BIO_STAGE_FILL, BIO_STAGE_TEXT, BiologyCanvas;
|
|
14910
16244
|
var init_BiologyCanvas = __esm({
|
|
14911
16245
|
"components/learning/molecules/BiologyCanvas.tsx"() {
|
|
14912
16246
|
"use client";
|
|
14913
16247
|
init_atoms();
|
|
14914
16248
|
init_Stack();
|
|
14915
16249
|
init_LearningCanvas();
|
|
16250
|
+
init_learningScene3D();
|
|
16251
|
+
biologyLog = logger.createLogger("almadar:ui:biology-canvas");
|
|
16252
|
+
BIO_BAND_COLORS = ["#dcfce7", "#fef9c3", "#fee2e2", "#e0e7ff"];
|
|
16253
|
+
BIO_STAGE_FILL = {
|
|
16254
|
+
pending: "#e2e8f0",
|
|
16255
|
+
active: "#3b82f6",
|
|
16256
|
+
done: "#94a3b8"
|
|
16257
|
+
};
|
|
16258
|
+
BIO_STAGE_TEXT = {
|
|
16259
|
+
pending: "#64748b",
|
|
16260
|
+
active: "#ffffff",
|
|
16261
|
+
done: "#ffffff"
|
|
16262
|
+
};
|
|
14916
16263
|
BiologyCanvas = ({
|
|
14917
16264
|
className,
|
|
14918
16265
|
width = 600,
|
|
14919
16266
|
height = 400,
|
|
14920
16267
|
title,
|
|
14921
16268
|
backgroundColor,
|
|
16269
|
+
mode = "2d",
|
|
16270
|
+
camera,
|
|
16271
|
+
lighting,
|
|
16272
|
+
post,
|
|
14922
16273
|
nodes = [],
|
|
14923
16274
|
edges = [],
|
|
16275
|
+
compartments = [],
|
|
16276
|
+
bands = [],
|
|
16277
|
+
stages = [],
|
|
16278
|
+
stageStyle = "timeline",
|
|
16279
|
+
helix,
|
|
16280
|
+
helix3d,
|
|
14924
16281
|
shapes = [],
|
|
14925
|
-
|
|
16282
|
+
readouts,
|
|
16283
|
+
traces,
|
|
16284
|
+
showGrid,
|
|
16285
|
+
shadows,
|
|
16286
|
+
interactive,
|
|
14926
16287
|
animate = false,
|
|
14927
16288
|
onShapeClick,
|
|
14928
16289
|
isLoading,
|
|
@@ -14934,19 +16295,148 @@ var init_BiologyCanvas = __esm({
|
|
|
14934
16295
|
for (const n of nodes) {
|
|
14935
16296
|
if (n.id) nodeById.set(n.id, n);
|
|
14936
16297
|
}
|
|
16298
|
+
const bandCount = bands.length;
|
|
16299
|
+
for (let i = 0; i < bandCount; i++) {
|
|
16300
|
+
const band = bands[i];
|
|
16301
|
+
const bandColor = band.color ?? BIO_BAND_COLORS[i % BIO_BAND_COLORS.length];
|
|
16302
|
+
const bandY = i * height / bandCount;
|
|
16303
|
+
const bandH = height / bandCount;
|
|
16304
|
+
out.push({
|
|
16305
|
+
type: "rect",
|
|
16306
|
+
x: 0,
|
|
16307
|
+
y: bandY,
|
|
16308
|
+
width,
|
|
16309
|
+
height: bandH,
|
|
16310
|
+
color: bandColor,
|
|
16311
|
+
fill: bandColor,
|
|
16312
|
+
opacity: 0.45
|
|
16313
|
+
});
|
|
16314
|
+
if (band.label) {
|
|
16315
|
+
out.push({
|
|
16316
|
+
type: "text",
|
|
16317
|
+
x: 8,
|
|
16318
|
+
y: bandY + 14,
|
|
16319
|
+
text: band.label,
|
|
16320
|
+
color: "#6b7280",
|
|
16321
|
+
fontSize: 10
|
|
16322
|
+
});
|
|
16323
|
+
}
|
|
16324
|
+
}
|
|
16325
|
+
for (const c of compartments) {
|
|
16326
|
+
const color = c.color ?? "#16a34a";
|
|
16327
|
+
out.push({
|
|
16328
|
+
type: "ellipse",
|
|
16329
|
+
x: c.x,
|
|
16330
|
+
y: c.y,
|
|
16331
|
+
width: c.width,
|
|
16332
|
+
height: c.height,
|
|
16333
|
+
color,
|
|
16334
|
+
fill: c.fill ?? `${color}1A`,
|
|
16335
|
+
lineWidth: c.lineWidth ?? 2,
|
|
16336
|
+
...c.dash ? { dash: c.dash } : {}
|
|
16337
|
+
});
|
|
16338
|
+
if (c.label) {
|
|
16339
|
+
out.push({
|
|
16340
|
+
type: "text",
|
|
16341
|
+
x: c.x,
|
|
16342
|
+
y: c.y - c.height / 2 + 14,
|
|
16343
|
+
text: c.label,
|
|
16344
|
+
color: "#111827",
|
|
16345
|
+
fontSize: 11,
|
|
16346
|
+
align: "center"
|
|
16347
|
+
});
|
|
16348
|
+
}
|
|
16349
|
+
}
|
|
16350
|
+
if (helix) {
|
|
16351
|
+
const hx = helix.x ?? 24;
|
|
16352
|
+
const hy = helix.y ?? height * 0.25;
|
|
16353
|
+
const hw = helix.width ?? width - 48;
|
|
16354
|
+
const hh = helix.height ?? height * 0.5;
|
|
16355
|
+
const rungs = helix.rungs;
|
|
16356
|
+
const n = rungs.length;
|
|
16357
|
+
const cy = hy + hh / 2;
|
|
16358
|
+
const colorA = helix.colorA ?? "#2563eb";
|
|
16359
|
+
const colorB = helix.colorB ?? "#dc2626";
|
|
16360
|
+
const rungColor = helix.rungColor ?? "#94a3b8";
|
|
16361
|
+
const fork = helix.fork ?? 0;
|
|
16362
|
+
const maxSep = Math.min(hh - 8, 96);
|
|
16363
|
+
const strandA = [];
|
|
16364
|
+
const strandB = [];
|
|
16365
|
+
const rungGeoms = [];
|
|
16366
|
+
for (let i = 0; i < n; i++) {
|
|
16367
|
+
const rx = hx + (i + 0.5) * hw / n;
|
|
16368
|
+
const t = (i + 0.5) / n;
|
|
16369
|
+
const paired = t >= fork;
|
|
16370
|
+
const sep = paired ? 28 : 28 + (maxSep - 28) * ((fork - t) / fork);
|
|
16371
|
+
strandA.push({ x: rx, y: cy - sep / 2 });
|
|
16372
|
+
strandB.push({ x: rx, y: cy + sep / 2 });
|
|
16373
|
+
rungGeoms.push({ rx, sep, rung: rungs[i], paired });
|
|
16374
|
+
}
|
|
16375
|
+
for (let i = 1; i < n; i++) {
|
|
16376
|
+
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 });
|
|
16377
|
+
}
|
|
16378
|
+
for (let i = 1; i < n; i++) {
|
|
16379
|
+
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 });
|
|
16380
|
+
}
|
|
16381
|
+
for (const g of rungGeoms) {
|
|
16382
|
+
const rColor = g.rung.color ?? (g.rung.state === "new" ? "#16a34a" : rungColor);
|
|
16383
|
+
const topY = cy - g.sep / 2;
|
|
16384
|
+
const bottomY = cy + g.sep / 2;
|
|
16385
|
+
if (g.paired) {
|
|
16386
|
+
out.push({ type: "line", x1: g.rx, y1: topY, x2: g.rx, y2: bottomY, color: rColor });
|
|
16387
|
+
if (g.rung.a) {
|
|
16388
|
+
out.push({ type: "text", x: g.rx, y: cy - g.sep / 4, text: g.rung.a, fontSize: 9, align: "center", color: "#374151" });
|
|
16389
|
+
}
|
|
16390
|
+
if (g.rung.b) {
|
|
16391
|
+
out.push({ type: "text", x: g.rx, y: cy + g.sep / 4, text: g.rung.b, fontSize: 9, align: "center", color: "#374151" });
|
|
16392
|
+
}
|
|
16393
|
+
} else {
|
|
16394
|
+
const stubTopY = topY + 8;
|
|
16395
|
+
const stubBottomY = bottomY - 8;
|
|
16396
|
+
out.push({ type: "line", x1: g.rx, y1: topY, x2: g.rx, y2: stubTopY, color: rColor });
|
|
16397
|
+
out.push({ type: "line", x1: g.rx, y1: bottomY, x2: g.rx, y2: stubBottomY, color: rColor });
|
|
16398
|
+
if (g.rung.a) {
|
|
16399
|
+
out.push({ type: "text", x: g.rx, y: stubTopY + 6, text: g.rung.a, fontSize: 9, align: "center", color: "#374151" });
|
|
16400
|
+
}
|
|
16401
|
+
if (g.rung.b) {
|
|
16402
|
+
out.push({ type: "text", x: g.rx, y: stubBottomY - 6, text: g.rung.b, fontSize: 9, align: "center", color: "#374151" });
|
|
16403
|
+
}
|
|
16404
|
+
}
|
|
16405
|
+
}
|
|
16406
|
+
}
|
|
14937
16407
|
for (const e of edges) {
|
|
14938
16408
|
const a = nodeById.get(e.from);
|
|
14939
16409
|
const b = nodeById.get(e.to);
|
|
14940
16410
|
if (!a || !b) continue;
|
|
14941
|
-
|
|
14942
|
-
|
|
14943
|
-
|
|
14944
|
-
|
|
14945
|
-
|
|
14946
|
-
|
|
14947
|
-
|
|
14948
|
-
|
|
14949
|
-
|
|
16411
|
+
const color = e.color ?? "#9ca3af";
|
|
16412
|
+
if (e.directed) {
|
|
16413
|
+
const rA = a.radius ?? 16;
|
|
16414
|
+
const rB = b.radius ?? 16;
|
|
16415
|
+
const dx = b.x - a.x;
|
|
16416
|
+
const dy = b.y - a.y;
|
|
16417
|
+
const dist = Math.max(1e-6, Math.hypot(dx, dy));
|
|
16418
|
+
const ux = dx / dist;
|
|
16419
|
+
const uy = dy / dist;
|
|
16420
|
+
out.push({
|
|
16421
|
+
type: "arrow",
|
|
16422
|
+
x1: a.x + ux * rA,
|
|
16423
|
+
y1: a.y + uy * rA,
|
|
16424
|
+
x2: b.x - ux * rB,
|
|
16425
|
+
y2: b.y - uy * rB,
|
|
16426
|
+
color,
|
|
16427
|
+
lineWidth: 2
|
|
16428
|
+
});
|
|
16429
|
+
} else {
|
|
16430
|
+
out.push({
|
|
16431
|
+
type: "line",
|
|
16432
|
+
x1: a.x,
|
|
16433
|
+
y1: a.y,
|
|
16434
|
+
x2: b.x,
|
|
16435
|
+
y2: b.y,
|
|
16436
|
+
color,
|
|
16437
|
+
lineWidth: 2
|
|
16438
|
+
});
|
|
16439
|
+
}
|
|
14950
16440
|
if (e.label) {
|
|
14951
16441
|
out.push({
|
|
14952
16442
|
type: "text",
|
|
@@ -14959,6 +16449,8 @@ var init_BiologyCanvas = __esm({
|
|
|
14959
16449
|
}
|
|
14960
16450
|
}
|
|
14961
16451
|
for (const n of nodes) {
|
|
16452
|
+
const state = n.state ?? "default";
|
|
16453
|
+
const muted = state === "muted";
|
|
14962
16454
|
out.push({
|
|
14963
16455
|
type: "circle",
|
|
14964
16456
|
x: n.x,
|
|
@@ -14966,23 +16458,197 @@ var init_BiologyCanvas = __esm({
|
|
|
14966
16458
|
radius: n.radius ?? 16,
|
|
14967
16459
|
color: n.color ?? "#16a34a",
|
|
14968
16460
|
fill: `${n.color ?? "#16a34a"}33`,
|
|
14969
|
-
id: n.id
|
|
16461
|
+
id: n.id,
|
|
16462
|
+
...muted ? { opacity: 0.35 } : {}
|
|
14970
16463
|
});
|
|
16464
|
+
if (state === "highlight") {
|
|
16465
|
+
out.push({
|
|
16466
|
+
type: "circle",
|
|
16467
|
+
x: n.x,
|
|
16468
|
+
y: n.y,
|
|
16469
|
+
radius: (n.radius ?? 16) + 4,
|
|
16470
|
+
color: "#f59e0b",
|
|
16471
|
+
lineWidth: 2
|
|
16472
|
+
});
|
|
16473
|
+
}
|
|
14971
16474
|
if (n.label) {
|
|
14972
16475
|
out.push({
|
|
14973
16476
|
type: "text",
|
|
14974
16477
|
x: n.x,
|
|
14975
16478
|
y: n.y + (n.radius ?? 16) + 14,
|
|
14976
16479
|
text: n.label,
|
|
16480
|
+
...muted ? { opacity: 0.35 } : {},
|
|
14977
16481
|
color: "#111827",
|
|
14978
16482
|
fontSize: 12,
|
|
14979
16483
|
align: "center"
|
|
14980
16484
|
});
|
|
14981
16485
|
}
|
|
14982
16486
|
}
|
|
16487
|
+
const stageCount = stages.length;
|
|
16488
|
+
if (stageCount > 0) {
|
|
16489
|
+
if (stageStyle === "ring") {
|
|
16490
|
+
const cx = width / 2;
|
|
16491
|
+
const cy = height / 2;
|
|
16492
|
+
const R = Math.min(width, height) / 2 - 48;
|
|
16493
|
+
const ringPoints = [];
|
|
16494
|
+
for (let i = 0; i < stageCount; i++) {
|
|
16495
|
+
const angleRad = (-90 + 360 * i / stageCount) * Math.PI / 180;
|
|
16496
|
+
ringPoints.push({ x: cx + R * Math.cos(angleRad), y: cy + R * Math.sin(angleRad) });
|
|
16497
|
+
}
|
|
16498
|
+
if (stageCount >= 2) {
|
|
16499
|
+
for (let i = 0; i < stageCount - 1; i++) {
|
|
16500
|
+
const p1 = ringPoints[i];
|
|
16501
|
+
const p2 = ringPoints[i + 1];
|
|
16502
|
+
const dx = p2.x - p1.x;
|
|
16503
|
+
const dy = p2.y - p1.y;
|
|
16504
|
+
const dist = Math.max(1e-6, Math.hypot(dx, dy));
|
|
16505
|
+
const ux = dx / dist;
|
|
16506
|
+
const uy = dy / dist;
|
|
16507
|
+
out.push({
|
|
16508
|
+
type: "arrow",
|
|
16509
|
+
x1: p1.x + ux * 46,
|
|
16510
|
+
y1: p1.y + uy * 46,
|
|
16511
|
+
x2: p2.x - ux * 46,
|
|
16512
|
+
y2: p2.y - uy * 46,
|
|
16513
|
+
color: "#94a3b8"
|
|
16514
|
+
});
|
|
16515
|
+
}
|
|
16516
|
+
}
|
|
16517
|
+
for (let i = 0; i < stageCount; i++) {
|
|
16518
|
+
const stage = stages[i];
|
|
16519
|
+
const state = stage.state ?? "pending";
|
|
16520
|
+
const fill = stage.color ?? BIO_STAGE_FILL[state];
|
|
16521
|
+
const w = Math.max(26, Math.min(84, stage.label.length * 6 + 10));
|
|
16522
|
+
const h = 18;
|
|
16523
|
+
const p = ringPoints[i];
|
|
16524
|
+
out.push({ type: "rect", x: p.x - w / 2, y: p.y - h / 2, width: w, height: h, color: fill, fill });
|
|
16525
|
+
out.push({ type: "text", x: p.x, y: p.y, text: stage.label, color: BIO_STAGE_TEXT[state], fontSize: 10, align: "center" });
|
|
16526
|
+
}
|
|
16527
|
+
} else {
|
|
16528
|
+
const stripY = height - 32;
|
|
16529
|
+
const slotW = (width - 16) / stageCount;
|
|
16530
|
+
const chipGeoms = [];
|
|
16531
|
+
for (let i = 0; i < stageCount; i++) {
|
|
16532
|
+
chipGeoms.push({ x: 8 + i * slotW + 5, w: slotW - 10 });
|
|
16533
|
+
}
|
|
16534
|
+
for (let i = 0; i < stageCount - 1; i++) {
|
|
16535
|
+
const midY = stripY + 13;
|
|
16536
|
+
out.push({
|
|
16537
|
+
type: "arrow",
|
|
16538
|
+
x1: chipGeoms[i].x + chipGeoms[i].w,
|
|
16539
|
+
y1: midY,
|
|
16540
|
+
x2: chipGeoms[i + 1].x,
|
|
16541
|
+
y2: midY,
|
|
16542
|
+
color: "#94a3b8"
|
|
16543
|
+
});
|
|
16544
|
+
}
|
|
16545
|
+
for (let i = 0; i < stageCount; i++) {
|
|
16546
|
+
const stage = stages[i];
|
|
16547
|
+
const state = stage.state ?? "pending";
|
|
16548
|
+
const fill = stage.color ?? BIO_STAGE_FILL[state];
|
|
16549
|
+
const g = chipGeoms[i];
|
|
16550
|
+
out.push({ type: "rect", x: g.x, y: stripY, width: g.w, height: 26, color: fill, fill });
|
|
16551
|
+
out.push({
|
|
16552
|
+
type: "text",
|
|
16553
|
+
x: g.x + g.w / 2,
|
|
16554
|
+
y: stripY + 13,
|
|
16555
|
+
text: stage.label,
|
|
16556
|
+
color: BIO_STAGE_TEXT[state],
|
|
16557
|
+
fontSize: 10,
|
|
16558
|
+
align: "center"
|
|
16559
|
+
});
|
|
16560
|
+
}
|
|
16561
|
+
}
|
|
16562
|
+
}
|
|
14983
16563
|
out.push(...shapes);
|
|
14984
16564
|
return out;
|
|
14985
|
-
}, [nodes, edges, shapes]);
|
|
16565
|
+
}, [nodes, edges, compartments, bands, stages, stageStyle, helix, shapes, width, height]);
|
|
16566
|
+
const drawables3D = React87.useMemo(() => {
|
|
16567
|
+
if (mode !== "3d") return [];
|
|
16568
|
+
if (shapes.length > 0) {
|
|
16569
|
+
biologyLog.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
|
|
16570
|
+
}
|
|
16571
|
+
if (compartments.length > 0 || bands.length > 0 || stages.length > 0 || helix) {
|
|
16572
|
+
biologyLog.debug("2D-only families ignored in 3D mode (pixel-authored 2D vocabulary)", {
|
|
16573
|
+
compartments: compartments.length,
|
|
16574
|
+
bands: bands.length,
|
|
16575
|
+
stages: stages.length,
|
|
16576
|
+
helix: helix != null
|
|
16577
|
+
});
|
|
16578
|
+
}
|
|
16579
|
+
if (animate) {
|
|
16580
|
+
biologyLog.debug("animate ignored in 3D mode (motion is entity-state driven)");
|
|
16581
|
+
}
|
|
16582
|
+
const out = [];
|
|
16583
|
+
const labelColor = labelColorForBackground(backgroundColor);
|
|
16584
|
+
const nodeById = /* @__PURE__ */ new Map();
|
|
16585
|
+
for (const n of nodes) {
|
|
16586
|
+
if (n.id) nodeById.set(n.id, n);
|
|
16587
|
+
}
|
|
16588
|
+
for (const e of edges) {
|
|
16589
|
+
const a = nodeById.get(e.from);
|
|
16590
|
+
const b = nodeById.get(e.to);
|
|
16591
|
+
if (!a || !b) continue;
|
|
16592
|
+
const edgeRadius = Math.max(0.04, Math.min(a.radius ?? 0.5, b.radius ?? 0.5) * 0.12);
|
|
16593
|
+
const edge = cylinderBetween([a.x, a.y, a.z ?? 0], [b.x, b.y, b.z ?? 0], edgeRadius, e.color ?? "#9ca3af");
|
|
16594
|
+
if (edge) out.push(edge);
|
|
16595
|
+
if (e.label) {
|
|
16596
|
+
out.push(
|
|
16597
|
+
billboardLabel(
|
|
16598
|
+
e.label,
|
|
16599
|
+
(a.x + b.x) / 2,
|
|
16600
|
+
(a.y + b.y) / 2,
|
|
16601
|
+
((a.z ?? 0) + (b.z ?? 0)) / 2,
|
|
16602
|
+
{ color: labelColor }
|
|
16603
|
+
)
|
|
16604
|
+
);
|
|
16605
|
+
}
|
|
16606
|
+
}
|
|
16607
|
+
for (const n of nodes) {
|
|
16608
|
+
const radius = n.radius ?? 0.5;
|
|
16609
|
+
const nz = n.z ?? 0;
|
|
16610
|
+
out.push(meshSphere(n.id, n.x, n.y, nz, radius, n.color ?? "#16a34a", { shape: n.shape, ...n.opacity !== void 0 ? { opacity: n.opacity } : {} }));
|
|
16611
|
+
if (n.label) {
|
|
16612
|
+
out.push(billboardLabel(n.label, n.x, n.y, nz + radius, { color: labelColor }));
|
|
16613
|
+
}
|
|
16614
|
+
}
|
|
16615
|
+
if (helix3d) {
|
|
16616
|
+
out.push(...helixDrawables(helix3d, { labelColor }));
|
|
16617
|
+
}
|
|
16618
|
+
return out;
|
|
16619
|
+
}, [mode, nodes, edges, shapes, compartments, bands, stages, helix, helix3d, animate, backgroundColor]);
|
|
16620
|
+
const nodeIndexById = React87.useMemo(() => {
|
|
16621
|
+
const m = /* @__PURE__ */ new Map();
|
|
16622
|
+
(helix3d?.rungs ?? []).forEach((rung, i) => {
|
|
16623
|
+
if (rung.id) m.set(rung.id, i);
|
|
16624
|
+
});
|
|
16625
|
+
nodes.forEach((n, i) => {
|
|
16626
|
+
if (n.id) m.set(n.id, i);
|
|
16627
|
+
});
|
|
16628
|
+
return m;
|
|
16629
|
+
}, [nodes, helix3d]);
|
|
16630
|
+
if (mode === "3d") {
|
|
16631
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
16632
|
+
LearningScene3D,
|
|
16633
|
+
{
|
|
16634
|
+
className,
|
|
16635
|
+
width,
|
|
16636
|
+
height,
|
|
16637
|
+
title,
|
|
16638
|
+
backgroundColor,
|
|
16639
|
+
drawables: drawables3D,
|
|
16640
|
+
camera,
|
|
16641
|
+
lighting,
|
|
16642
|
+
post,
|
|
16643
|
+
showGrid,
|
|
16644
|
+
shadows,
|
|
16645
|
+
interactive,
|
|
16646
|
+
isLoading,
|
|
16647
|
+
error,
|
|
16648
|
+
onItemClick: get3DClickPayload(onShapeClick, nodeIndexById)
|
|
16649
|
+
}
|
|
16650
|
+
);
|
|
16651
|
+
}
|
|
14986
16652
|
return /* @__PURE__ */ jsxRuntime.jsx(Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
|
|
14987
16653
|
title ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", children: title }) : null,
|
|
14988
16654
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -14992,7 +16658,9 @@ var init_BiologyCanvas = __esm({
|
|
|
14992
16658
|
height,
|
|
14993
16659
|
backgroundColor,
|
|
14994
16660
|
shapes: derivedShapes,
|
|
14995
|
-
|
|
16661
|
+
readouts,
|
|
16662
|
+
traces,
|
|
16663
|
+
interactive: interactive ?? false,
|
|
14996
16664
|
animate,
|
|
14997
16665
|
onShapeClick,
|
|
14998
16666
|
isLoading,
|
|
@@ -21832,24 +23500,55 @@ var init_ChatBar = __esm({
|
|
|
21832
23500
|
ChatBar.displayName = "ChatBar";
|
|
21833
23501
|
}
|
|
21834
23502
|
});
|
|
21835
|
-
|
|
23503
|
+
function bondPerpendicular(a, b) {
|
|
23504
|
+
const dx = b[0] - a[0];
|
|
23505
|
+
const dy = b[1] - a[1];
|
|
23506
|
+
const px = dy;
|
|
23507
|
+
const py = -dx;
|
|
23508
|
+
const len = Math.sqrt(px * px + py * py);
|
|
23509
|
+
if (len < 1e-6) return [1, 0, 0];
|
|
23510
|
+
return [px / len, py / len, 0];
|
|
23511
|
+
}
|
|
23512
|
+
var chemistryLog, CHEM_BOND_STATE_COLOR, LONE_PAIR_ANGLES, ChemistryCanvas;
|
|
21836
23513
|
var init_ChemistryCanvas = __esm({
|
|
21837
23514
|
"components/learning/molecules/ChemistryCanvas.tsx"() {
|
|
21838
23515
|
"use client";
|
|
21839
23516
|
init_atoms();
|
|
21840
23517
|
init_Stack();
|
|
21841
23518
|
init_LearningCanvas();
|
|
23519
|
+
init_learningScene3D();
|
|
23520
|
+
chemistryLog = logger.createLogger("almadar:ui:chemistry-canvas");
|
|
23521
|
+
CHEM_BOND_STATE_COLOR = {
|
|
23522
|
+
default: "#6b7280",
|
|
23523
|
+
forming: "#16a34a",
|
|
23524
|
+
breaking: "#dc2626",
|
|
23525
|
+
highlight: "#f59e0b"
|
|
23526
|
+
};
|
|
23527
|
+
LONE_PAIR_ANGLES = [-90, 0, 90, 180];
|
|
21842
23528
|
ChemistryCanvas = ({
|
|
21843
23529
|
className,
|
|
21844
23530
|
width = 600,
|
|
21845
23531
|
height = 400,
|
|
21846
23532
|
title,
|
|
21847
23533
|
backgroundColor,
|
|
23534
|
+
mode = "2d",
|
|
23535
|
+
camera,
|
|
23536
|
+
lighting,
|
|
23537
|
+
post,
|
|
21848
23538
|
atoms = [],
|
|
21849
23539
|
bonds = [],
|
|
21850
23540
|
arrows = [],
|
|
23541
|
+
bondStyle = "thick",
|
|
23542
|
+
containers = [],
|
|
23543
|
+
equation,
|
|
23544
|
+
equationColor,
|
|
23545
|
+
lattice3d,
|
|
21851
23546
|
shapes = [],
|
|
21852
|
-
|
|
23547
|
+
readouts,
|
|
23548
|
+
traces,
|
|
23549
|
+
showGrid,
|
|
23550
|
+
shadows,
|
|
23551
|
+
interactive,
|
|
21853
23552
|
animate = false,
|
|
21854
23553
|
onShapeClick,
|
|
21855
23554
|
isLoading,
|
|
@@ -21861,21 +23560,118 @@ var init_ChemistryCanvas = __esm({
|
|
|
21861
23560
|
for (const a of atoms) {
|
|
21862
23561
|
if (a.id) atomById.set(a.id, a);
|
|
21863
23562
|
}
|
|
23563
|
+
for (const c of containers) {
|
|
23564
|
+
const color = c.color ?? "#64748b";
|
|
23565
|
+
if (c.level != null) {
|
|
23566
|
+
const lv = c.level;
|
|
23567
|
+
out.push({
|
|
23568
|
+
type: "rect",
|
|
23569
|
+
x: c.x + 1,
|
|
23570
|
+
y: c.y + c.height * (1 - lv),
|
|
23571
|
+
width: c.width - 2,
|
|
23572
|
+
height: c.height * lv - 1,
|
|
23573
|
+
color: c.levelColor ?? "#60a5fa",
|
|
23574
|
+
fill: c.levelColor ?? "#60a5fa",
|
|
23575
|
+
opacity: 0.5
|
|
23576
|
+
});
|
|
23577
|
+
}
|
|
23578
|
+
out.push({
|
|
23579
|
+
type: "rect",
|
|
23580
|
+
x: c.x,
|
|
23581
|
+
y: c.y,
|
|
23582
|
+
width: c.width,
|
|
23583
|
+
height: c.height,
|
|
23584
|
+
color,
|
|
23585
|
+
fill: c.fill,
|
|
23586
|
+
lineWidth: c.lineWidth ?? 2
|
|
23587
|
+
});
|
|
23588
|
+
const divider = c.divider ?? "none";
|
|
23589
|
+
if (divider !== "none") {
|
|
23590
|
+
out.push({
|
|
23591
|
+
type: "line",
|
|
23592
|
+
x1: c.x + c.width / 2,
|
|
23593
|
+
y1: c.y,
|
|
23594
|
+
x2: c.x + c.width / 2,
|
|
23595
|
+
y2: c.y + c.height,
|
|
23596
|
+
color: c.dividerColor ?? color,
|
|
23597
|
+
...divider === "dashed" || divider === "dotted" ? { dash: divider } : {}
|
|
23598
|
+
});
|
|
23599
|
+
}
|
|
23600
|
+
if (c.leftLabel) {
|
|
23601
|
+
out.push({
|
|
23602
|
+
type: "text",
|
|
23603
|
+
x: c.x + c.width * 0.25,
|
|
23604
|
+
y: c.y + 12,
|
|
23605
|
+
text: c.leftLabel,
|
|
23606
|
+
color: "#374151",
|
|
23607
|
+
fontSize: 11,
|
|
23608
|
+
align: "center"
|
|
23609
|
+
});
|
|
23610
|
+
}
|
|
23611
|
+
if (c.rightLabel) {
|
|
23612
|
+
out.push({
|
|
23613
|
+
type: "text",
|
|
23614
|
+
x: c.x + c.width * 0.75,
|
|
23615
|
+
y: c.y + 12,
|
|
23616
|
+
text: c.rightLabel,
|
|
23617
|
+
color: "#374151",
|
|
23618
|
+
fontSize: 11,
|
|
23619
|
+
align: "center"
|
|
23620
|
+
});
|
|
23621
|
+
}
|
|
23622
|
+
if (c.label) {
|
|
23623
|
+
out.push({
|
|
23624
|
+
type: "text",
|
|
23625
|
+
x: c.x + c.width / 2,
|
|
23626
|
+
y: c.y + c.height + 12,
|
|
23627
|
+
text: c.label,
|
|
23628
|
+
color: "#111827",
|
|
23629
|
+
fontSize: 12,
|
|
23630
|
+
align: "center"
|
|
23631
|
+
});
|
|
23632
|
+
}
|
|
23633
|
+
}
|
|
21864
23634
|
for (const b of bonds) {
|
|
21865
23635
|
const a = atomById.get(b.from);
|
|
21866
23636
|
const c = atomById.get(b.to);
|
|
21867
23637
|
if (!a || !c) continue;
|
|
21868
|
-
const
|
|
21869
|
-
const
|
|
21870
|
-
|
|
21871
|
-
|
|
21872
|
-
|
|
21873
|
-
|
|
21874
|
-
|
|
21875
|
-
|
|
21876
|
-
|
|
21877
|
-
|
|
21878
|
-
|
|
23638
|
+
const state = b.state ?? "default";
|
|
23639
|
+
const color = b.color ?? CHEM_BOND_STATE_COLOR[state];
|
|
23640
|
+
const dash = state === "forming" || state === "breaking" ? "dashed" : void 0;
|
|
23641
|
+
if (bondStyle === "parallel") {
|
|
23642
|
+
const dx = c.x - a.x;
|
|
23643
|
+
const dy = c.y - a.y;
|
|
23644
|
+
const dist = Math.max(1e-6, Math.hypot(dx, dy));
|
|
23645
|
+
const ux = dx / dist;
|
|
23646
|
+
const uy = dy / dist;
|
|
23647
|
+
const px = -uy;
|
|
23648
|
+
const py = ux;
|
|
23649
|
+
const offsets = b.type === "double" ? [-3, 3] : b.type === "triple" ? [-4, 0, 4] : [0];
|
|
23650
|
+
for (const off of offsets) {
|
|
23651
|
+
out.push({
|
|
23652
|
+
type: "line",
|
|
23653
|
+
x1: a.x + px * off,
|
|
23654
|
+
y1: a.y + py * off,
|
|
23655
|
+
x2: c.x + px * off,
|
|
23656
|
+
y2: c.y + py * off,
|
|
23657
|
+
color,
|
|
23658
|
+
lineWidth: 2,
|
|
23659
|
+
...dash ? { dash } : {}
|
|
23660
|
+
});
|
|
23661
|
+
}
|
|
23662
|
+
} else {
|
|
23663
|
+
const strokeWidth = b.type === "double" ? 4 : b.type === "triple" ? 6 : 2;
|
|
23664
|
+
out.push({
|
|
23665
|
+
type: "line",
|
|
23666
|
+
x1: a.x,
|
|
23667
|
+
y1: a.y,
|
|
23668
|
+
x2: c.x,
|
|
23669
|
+
y2: c.y,
|
|
23670
|
+
color,
|
|
23671
|
+
lineWidth: strokeWidth,
|
|
23672
|
+
...dash ? { dash } : {}
|
|
23673
|
+
});
|
|
23674
|
+
}
|
|
21879
23675
|
}
|
|
21880
23676
|
for (const a of arrows) {
|
|
21881
23677
|
const angle = (a.angle ?? 0) * (Math.PI / 180);
|
|
@@ -21924,10 +23720,142 @@ var init_ChemistryCanvas = __esm({
|
|
|
21924
23720
|
align: "center"
|
|
21925
23721
|
});
|
|
21926
23722
|
}
|
|
23723
|
+
const r = a.radius ?? 14;
|
|
23724
|
+
if (a.charge) {
|
|
23725
|
+
out.push({
|
|
23726
|
+
type: "text",
|
|
23727
|
+
x: a.x + r * 0.85,
|
|
23728
|
+
y: a.y - r * 0.85,
|
|
23729
|
+
text: a.charge,
|
|
23730
|
+
color: "#111827",
|
|
23731
|
+
fontSize: 9,
|
|
23732
|
+
align: "left"
|
|
23733
|
+
});
|
|
23734
|
+
}
|
|
23735
|
+
const lonePairs = Math.max(0, Math.min(4, a.lonePairs ?? 0));
|
|
23736
|
+
for (let k = 0; k < lonePairs; k++) {
|
|
23737
|
+
const angleRad = LONE_PAIR_ANGLES[k] * Math.PI / 180;
|
|
23738
|
+
const cx = a.x + (r + 6) * Math.cos(angleRad);
|
|
23739
|
+
const cy = a.y + (r + 6) * Math.sin(angleRad);
|
|
23740
|
+
const perpX = -Math.sin(angleRad);
|
|
23741
|
+
const perpY = Math.cos(angleRad);
|
|
23742
|
+
for (const sign of [1, -1]) {
|
|
23743
|
+
out.push({
|
|
23744
|
+
type: "circle",
|
|
23745
|
+
x: cx + perpX * 2.5 * sign,
|
|
23746
|
+
y: cy + perpY * 2.5 * sign,
|
|
23747
|
+
radius: 1.5,
|
|
23748
|
+
color: "#374151",
|
|
23749
|
+
fill: "#374151"
|
|
23750
|
+
});
|
|
23751
|
+
}
|
|
23752
|
+
}
|
|
23753
|
+
}
|
|
23754
|
+
if (equation) {
|
|
23755
|
+
out.push({
|
|
23756
|
+
type: "text",
|
|
23757
|
+
x: width / 2,
|
|
23758
|
+
y: 14,
|
|
23759
|
+
text: equation,
|
|
23760
|
+
color: equationColor ?? "#111827",
|
|
23761
|
+
fontSize: 13,
|
|
23762
|
+
align: "center"
|
|
23763
|
+
});
|
|
21927
23764
|
}
|
|
21928
23765
|
out.push(...shapes);
|
|
21929
23766
|
return out;
|
|
21930
|
-
}, [atoms, bonds, arrows, shapes]);
|
|
23767
|
+
}, [atoms, bonds, arrows, bondStyle, containers, equation, equationColor, shapes, width]);
|
|
23768
|
+
const drawables3D = React87.useMemo(() => {
|
|
23769
|
+
if (mode !== "3d") return [];
|
|
23770
|
+
if (shapes.length > 0) {
|
|
23771
|
+
chemistryLog.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
|
|
23772
|
+
}
|
|
23773
|
+
if (containers.length > 0) {
|
|
23774
|
+
chemistryLog.debug("containers ignored in 3D mode (pixel-authored 2D vocabulary)", { count: containers.length });
|
|
23775
|
+
}
|
|
23776
|
+
if (animate) {
|
|
23777
|
+
chemistryLog.debug("animate ignored in 3D mode (motion is entity-state driven)");
|
|
23778
|
+
}
|
|
23779
|
+
const out = [];
|
|
23780
|
+
const labelColor = labelColorForBackground(backgroundColor);
|
|
23781
|
+
const atomById = /* @__PURE__ */ new Map();
|
|
23782
|
+
for (const a of atoms) {
|
|
23783
|
+
if (a.id) atomById.set(a.id, a);
|
|
23784
|
+
}
|
|
23785
|
+
for (const b of bonds) {
|
|
23786
|
+
const a = atomById.get(b.from);
|
|
23787
|
+
const c = atomById.get(b.to);
|
|
23788
|
+
if (!a || !c) continue;
|
|
23789
|
+
const color = b.color ?? "#6b7280";
|
|
23790
|
+
const from = [a.x, a.y, a.z ?? 0];
|
|
23791
|
+
const to = [c.x, c.y, c.z ?? 0];
|
|
23792
|
+
const perp = bondPerpendicular(from, to);
|
|
23793
|
+
const bondRadius = Math.max(0.06, Math.min(a.radius ?? 0.45, c.radius ?? 0.45) * 0.22);
|
|
23794
|
+
const step = bondRadius * 2.2;
|
|
23795
|
+
const offsets = b.type === "double" ? [-step, step] : b.type === "triple" ? [-step, 0, step] : [0];
|
|
23796
|
+
for (const off of offsets) {
|
|
23797
|
+
const bond = cylinderBetween(
|
|
23798
|
+
[from[0] + perp[0] * off, from[1] + perp[1] * off, from[2] + perp[2] * off],
|
|
23799
|
+
[to[0] + perp[0] * off, to[1] + perp[1] * off, to[2] + perp[2] * off],
|
|
23800
|
+
bondRadius,
|
|
23801
|
+
color
|
|
23802
|
+
);
|
|
23803
|
+
if (bond) out.push(bond);
|
|
23804
|
+
}
|
|
23805
|
+
}
|
|
23806
|
+
for (const a of arrows) {
|
|
23807
|
+
const angle = (a.angle ?? 0) * (Math.PI / 180);
|
|
23808
|
+
const len = a.length ?? 60;
|
|
23809
|
+
const x2 = a.x + Math.cos(angle) * len;
|
|
23810
|
+
const y2 = a.y + Math.sin(angle) * len;
|
|
23811
|
+
const arrow = arrowBetween([a.x, a.y, 0], [x2, y2, 0], a.color ?? "#dc2626");
|
|
23812
|
+
if (arrow) out.push(arrow);
|
|
23813
|
+
if (a.label) {
|
|
23814
|
+
out.push(billboardLabel(a.label, (a.x + x2) / 2, (a.y + y2) / 2, 0, { color: labelColor }));
|
|
23815
|
+
}
|
|
23816
|
+
}
|
|
23817
|
+
for (const a of atoms) {
|
|
23818
|
+
const radius = a.radius ?? 0.45;
|
|
23819
|
+
const az = a.z ?? 0;
|
|
23820
|
+
out.push(meshSphere(a.id, a.x, a.y, az, radius, a.color ?? "#2563eb"));
|
|
23821
|
+
if (a.element) {
|
|
23822
|
+
out.push(billboardLabel(a.element, a.x, a.y, az + radius, { color: labelColor }));
|
|
23823
|
+
}
|
|
23824
|
+
}
|
|
23825
|
+
if (lattice3d) {
|
|
23826
|
+
out.push(...latticeDrawables(lattice3d, { labelColor }));
|
|
23827
|
+
}
|
|
23828
|
+
return out;
|
|
23829
|
+
}, [mode, atoms, bonds, arrows, shapes, containers, lattice3d, animate, backgroundColor]);
|
|
23830
|
+
const atomIndexById = React87.useMemo(() => {
|
|
23831
|
+
const m = /* @__PURE__ */ new Map();
|
|
23832
|
+
atoms.forEach((a, i) => {
|
|
23833
|
+
if (a.id) m.set(a.id, i);
|
|
23834
|
+
});
|
|
23835
|
+
return m;
|
|
23836
|
+
}, [atoms]);
|
|
23837
|
+
if (mode === "3d") {
|
|
23838
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
23839
|
+
LearningScene3D,
|
|
23840
|
+
{
|
|
23841
|
+
className,
|
|
23842
|
+
width,
|
|
23843
|
+
height,
|
|
23844
|
+
title,
|
|
23845
|
+
backgroundColor,
|
|
23846
|
+
drawables: drawables3D,
|
|
23847
|
+
camera,
|
|
23848
|
+
lighting,
|
|
23849
|
+
post,
|
|
23850
|
+
showGrid,
|
|
23851
|
+
shadows,
|
|
23852
|
+
interactive,
|
|
23853
|
+
isLoading,
|
|
23854
|
+
error,
|
|
23855
|
+
onItemClick: get3DClickPayload(onShapeClick, atomIndexById)
|
|
23856
|
+
}
|
|
23857
|
+
);
|
|
23858
|
+
}
|
|
21931
23859
|
return /* @__PURE__ */ jsxRuntime.jsx(Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
|
|
21932
23860
|
title ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", children: title }) : null,
|
|
21933
23861
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -21937,7 +23865,9 @@ var init_ChemistryCanvas = __esm({
|
|
|
21937
23865
|
height,
|
|
21938
23866
|
backgroundColor,
|
|
21939
23867
|
shapes: derivedShapes,
|
|
21940
|
-
|
|
23868
|
+
readouts,
|
|
23869
|
+
traces,
|
|
23870
|
+
interactive: interactive ?? false,
|
|
21941
23871
|
animate,
|
|
21942
23872
|
onShapeClick,
|
|
21943
23873
|
isLoading,
|
|
@@ -28126,6 +30056,10 @@ var init_ProgressDots = __esm({
|
|
|
28126
30056
|
ProgressDots.displayName = "ProgressDots";
|
|
28127
30057
|
}
|
|
28128
30058
|
});
|
|
30059
|
+
function formatTick(v) {
|
|
30060
|
+
if (Number.isInteger(v)) return String(v);
|
|
30061
|
+
return v.toFixed(2).replace(/0+$/, "").replace(/\.$/, "");
|
|
30062
|
+
}
|
|
28129
30063
|
var MathCanvas;
|
|
28130
30064
|
var init_MathCanvas = __esm({
|
|
28131
30065
|
"components/learning/molecules/MathCanvas.tsx"() {
|
|
@@ -28145,10 +30079,19 @@ var init_MathCanvas = __esm({
|
|
|
28145
30079
|
showAxes = true,
|
|
28146
30080
|
showGrid = true,
|
|
28147
30081
|
gridStep = 1,
|
|
30082
|
+
showTickLabels = false,
|
|
30083
|
+
showCurveLabels = false,
|
|
28148
30084
|
curves = [],
|
|
28149
30085
|
points = [],
|
|
28150
30086
|
vectors = [],
|
|
30087
|
+
regions = [],
|
|
30088
|
+
bars = [],
|
|
30089
|
+
guides = [],
|
|
30090
|
+
angles = [],
|
|
30091
|
+
hops = [],
|
|
28151
30092
|
shapes = [],
|
|
30093
|
+
readouts,
|
|
30094
|
+
traces,
|
|
28152
30095
|
interactive = false,
|
|
28153
30096
|
animate = false,
|
|
28154
30097
|
onShapeClick,
|
|
@@ -28162,6 +30105,8 @@ var init_MathCanvas = __esm({
|
|
|
28162
30105
|
const plotH = height - margin * 2;
|
|
28163
30106
|
const mapX = (x) => margin + (x - xMin) / (xMax - xMin) * plotW;
|
|
28164
30107
|
const mapY = (y) => height - (margin + (y - yMin) / (yMax - yMin) * plotH);
|
|
30108
|
+
const xAxisY = Math.max(margin, Math.min(height - margin, mapY(0)));
|
|
30109
|
+
const yAxisX = Math.max(margin, Math.min(width - margin, mapX(0)));
|
|
28165
30110
|
if (showGrid) {
|
|
28166
30111
|
for (let x = Math.ceil(xMin / gridStep) * gridStep; x <= xMax; x += gridStep) {
|
|
28167
30112
|
const px = mapX(x);
|
|
@@ -28172,14 +30117,99 @@ var init_MathCanvas = __esm({
|
|
|
28172
30117
|
out.push({ type: "line", x1: margin, y1: py, x2: width - margin, y2: py, color: "#e5e7eb", lineWidth: 1 });
|
|
28173
30118
|
}
|
|
28174
30119
|
}
|
|
30120
|
+
if (showTickLabels) {
|
|
30121
|
+
const labelEveryX = Math.max(1, Math.ceil((xMax - xMin) / gridStep / Math.floor(plotW / 40)));
|
|
30122
|
+
let kx = 0;
|
|
30123
|
+
for (let x = Math.ceil(xMin / gridStep) * gridStep; x <= xMax; x += gridStep, kx++) {
|
|
30124
|
+
if (kx % labelEveryX === 0 && x !== 0) {
|
|
30125
|
+
out.push({ type: "text", x: mapX(x), y: xAxisY + 12, text: formatTick(x), color: "#6b7280", fontSize: 10, align: "center" });
|
|
30126
|
+
}
|
|
30127
|
+
}
|
|
30128
|
+
const labelEveryY = Math.max(1, Math.ceil((yMax - yMin) / gridStep / Math.floor(plotH / 28)));
|
|
30129
|
+
let ky = 0;
|
|
30130
|
+
for (let y = Math.ceil(yMin / gridStep) * gridStep; y <= yMax; y += gridStep, ky++) {
|
|
30131
|
+
if (ky % labelEveryY === 0 && y !== 0) {
|
|
30132
|
+
out.push({ type: "text", x: yAxisX - 6, y: mapY(y), text: formatTick(y), color: "#6b7280", fontSize: 10, align: "right" });
|
|
30133
|
+
}
|
|
30134
|
+
}
|
|
30135
|
+
if (xMin <= 0 && xMax >= 0 && yMin <= 0 && yMax >= 0) {
|
|
30136
|
+
out.push({ type: "text", x: yAxisX - 6, y: xAxisY + 12, text: "0", color: "#6b7280", fontSize: 10, align: "right" });
|
|
30137
|
+
}
|
|
30138
|
+
}
|
|
30139
|
+
for (const region of regions) {
|
|
30140
|
+
if (!region.samples || region.samples.length === 0) continue;
|
|
30141
|
+
const baseline = region.baseline ?? 0;
|
|
30142
|
+
const clampedPoint = (p) => ({
|
|
30143
|
+
x: mapX(Math.min(xMax, Math.max(xMin, p.x))),
|
|
30144
|
+
y: mapY(Math.min(yMax, Math.max(yMin, p.y)))
|
|
30145
|
+
});
|
|
30146
|
+
const upper = region.samples.map(clampedPoint);
|
|
30147
|
+
const first = region.samples[0];
|
|
30148
|
+
const last = region.samples[region.samples.length - 1];
|
|
30149
|
+
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 })];
|
|
30150
|
+
const color = region.color ?? "#2563eb";
|
|
30151
|
+
out.push({
|
|
30152
|
+
type: "polygon",
|
|
30153
|
+
points: [...upper, ...closing],
|
|
30154
|
+
fill: color,
|
|
30155
|
+
color,
|
|
30156
|
+
opacity: region.opacity ?? 0.2,
|
|
30157
|
+
lineWidth: 1
|
|
30158
|
+
});
|
|
30159
|
+
if (region.label) {
|
|
30160
|
+
const mid = Math.floor(region.samples.length / 2);
|
|
30161
|
+
out.push({
|
|
30162
|
+
type: "text",
|
|
30163
|
+
x: mapX((first.x + last.x) / 2),
|
|
30164
|
+
y: (mapY(region.samples[mid].y) + mapY(baseline)) / 2,
|
|
30165
|
+
text: region.label,
|
|
30166
|
+
color: "#111827",
|
|
30167
|
+
fontSize: 11
|
|
30168
|
+
});
|
|
30169
|
+
}
|
|
30170
|
+
}
|
|
30171
|
+
for (const bar of bars) {
|
|
30172
|
+
if (bar.x + bar.width < xMin || bar.x > xMax) continue;
|
|
30173
|
+
const y0 = bar.y0 ?? 0;
|
|
30174
|
+
const color = bar.color ?? "#93c5fd";
|
|
30175
|
+
out.push({
|
|
30176
|
+
type: "rect",
|
|
30177
|
+
x: mapX(bar.x),
|
|
30178
|
+
y: mapY(Math.max(y0, bar.y1)),
|
|
30179
|
+
width: mapX(bar.x + bar.width) - mapX(bar.x),
|
|
30180
|
+
height: Math.abs(mapY(bar.y1) - mapY(y0)),
|
|
30181
|
+
color,
|
|
30182
|
+
fill: color,
|
|
30183
|
+
opacity: bar.opacity ?? 0.5,
|
|
30184
|
+
lineWidth: 1
|
|
30185
|
+
});
|
|
30186
|
+
}
|
|
28175
30187
|
if (showAxes) {
|
|
28176
|
-
const xAxisY = Math.max(margin, Math.min(height - margin, mapY(0)));
|
|
28177
|
-
const yAxisX = Math.max(margin, Math.min(width - margin, mapX(0)));
|
|
28178
30188
|
out.push({ type: "line", x1: margin, y1: xAxisY, x2: width - margin, y2: xAxisY, color: "#374151", lineWidth: 2 });
|
|
28179
30189
|
out.push({ type: "line", x1: yAxisX, y1: margin, x2: yAxisX, y2: height - margin, color: "#374151", lineWidth: 2 });
|
|
28180
30190
|
}
|
|
30191
|
+
for (const guide of guides) {
|
|
30192
|
+
const color = guide.color ?? "#9ca3af";
|
|
30193
|
+
const dash = guide.dash ?? "dashed";
|
|
30194
|
+
if (guide.kind === "vline") {
|
|
30195
|
+
if (guide.at < xMin || guide.at > xMax) continue;
|
|
30196
|
+
const px = mapX(guide.at);
|
|
30197
|
+
out.push({ type: "line", x1: px, y1: margin, x2: px, y2: height - margin, color, dash });
|
|
30198
|
+
if (guide.label) {
|
|
30199
|
+
out.push({ type: "text", x: px + 4, y: margin + 10, text: guide.label, color: "#111827", fontSize: 11 });
|
|
30200
|
+
}
|
|
30201
|
+
} else {
|
|
30202
|
+
if (guide.at < yMin || guide.at > yMax) continue;
|
|
30203
|
+
const py = mapY(guide.at);
|
|
30204
|
+
out.push({ type: "line", x1: margin, y1: py, x2: width - margin, y2: py, color, dash });
|
|
30205
|
+
if (guide.label) {
|
|
30206
|
+
out.push({ type: "text", x: width - margin - 4, y: py - 8, text: guide.label, color: "#111827", fontSize: 11, align: "right" });
|
|
30207
|
+
}
|
|
30208
|
+
}
|
|
30209
|
+
}
|
|
28181
30210
|
for (const curve of curves) {
|
|
28182
30211
|
if (!curve.samples || curve.samples.length < 2) continue;
|
|
30212
|
+
let lastInRange;
|
|
28183
30213
|
for (let i = 1; i < curve.samples.length; i++) {
|
|
28184
30214
|
const a = curve.samples[i - 1];
|
|
28185
30215
|
const b = curve.samples[i];
|
|
@@ -28191,19 +30221,97 @@ var init_MathCanvas = __esm({
|
|
|
28191
30221
|
x2: mapX(b.x),
|
|
28192
30222
|
y2: mapY(b.y),
|
|
28193
30223
|
color: curve.color ?? "#2563eb",
|
|
28194
|
-
lineWidth: 2
|
|
30224
|
+
lineWidth: 2,
|
|
30225
|
+
dash: curve.dash
|
|
30226
|
+
});
|
|
30227
|
+
lastInRange = b;
|
|
30228
|
+
}
|
|
30229
|
+
if (showCurveLabels && curve.label && lastInRange) {
|
|
30230
|
+
out.push({
|
|
30231
|
+
type: "text",
|
|
30232
|
+
x: mapX(lastInRange.x) + 6,
|
|
30233
|
+
y: mapY(lastInRange.y) - 6,
|
|
30234
|
+
text: curve.label,
|
|
30235
|
+
color: curve.color ?? "#2563eb",
|
|
30236
|
+
fontSize: 11
|
|
30237
|
+
});
|
|
30238
|
+
}
|
|
30239
|
+
}
|
|
30240
|
+
for (const hop of hops) {
|
|
30241
|
+
const x1 = mapX(hop.from);
|
|
30242
|
+
const x2 = mapX(hop.to);
|
|
30243
|
+
const peak = Math.min(36, plotH * 0.3);
|
|
30244
|
+
const color = hop.color ?? "#7c3aed";
|
|
30245
|
+
out.push({
|
|
30246
|
+
type: "ellipse",
|
|
30247
|
+
x: (x1 + x2) / 2,
|
|
30248
|
+
y: xAxisY,
|
|
30249
|
+
width: Math.abs(x2 - x1),
|
|
30250
|
+
height: 2 * peak,
|
|
30251
|
+
startAngle: 180,
|
|
30252
|
+
endAngle: 360,
|
|
30253
|
+
color
|
|
30254
|
+
});
|
|
30255
|
+
const s = Math.sign(hop.to - hop.from);
|
|
30256
|
+
out.push({
|
|
30257
|
+
type: "polygon",
|
|
30258
|
+
points: [
|
|
30259
|
+
{ x: x2, y: xAxisY },
|
|
30260
|
+
{ x: x2 - 4 * s, y: xAxisY - 7 },
|
|
30261
|
+
{ x: x2 + 2 * s, y: xAxisY - 7 }
|
|
30262
|
+
],
|
|
30263
|
+
fill: color,
|
|
30264
|
+
color
|
|
30265
|
+
});
|
|
30266
|
+
if (hop.label) {
|
|
30267
|
+
out.push({
|
|
30268
|
+
type: "text",
|
|
30269
|
+
x: (x1 + x2) / 2,
|
|
30270
|
+
y: xAxisY - peak - 8,
|
|
30271
|
+
text: hop.label,
|
|
30272
|
+
color: "#111827",
|
|
30273
|
+
fontSize: 10,
|
|
30274
|
+
align: "center"
|
|
30275
|
+
});
|
|
30276
|
+
}
|
|
30277
|
+
}
|
|
30278
|
+
for (const angle of angles) {
|
|
30279
|
+
const radius = angle.radius ?? 0.8;
|
|
30280
|
+
const color = angle.color ?? "#0ea5e9";
|
|
30281
|
+
out.push({
|
|
30282
|
+
type: "ellipse",
|
|
30283
|
+
x: mapX(angle.x),
|
|
30284
|
+
y: mapY(angle.y),
|
|
30285
|
+
width: 2 * radius * plotW / (xMax - xMin),
|
|
30286
|
+
height: 2 * radius * plotH / (yMax - yMin),
|
|
30287
|
+
startAngle: -angle.to,
|
|
30288
|
+
endAngle: -angle.from,
|
|
30289
|
+
color
|
|
30290
|
+
});
|
|
30291
|
+
if (angle.label) {
|
|
30292
|
+
const mid = (angle.from + angle.to) / 2;
|
|
30293
|
+
const rad = mid * Math.PI / 180;
|
|
30294
|
+
out.push({
|
|
30295
|
+
type: "text",
|
|
30296
|
+
x: mapX(angle.x + 1.35 * radius * Math.cos(rad)),
|
|
30297
|
+
y: mapY(angle.y + 1.35 * radius * Math.sin(rad)),
|
|
30298
|
+
text: angle.label,
|
|
30299
|
+
color: "#111827",
|
|
30300
|
+
fontSize: 11,
|
|
30301
|
+
align: "center"
|
|
28195
30302
|
});
|
|
28196
30303
|
}
|
|
28197
30304
|
}
|
|
28198
30305
|
for (const p of points) {
|
|
28199
30306
|
if (p.x < xMin || p.x > xMax || p.y < yMin || p.y > yMax) continue;
|
|
30307
|
+
const isOpen = p.style === "open";
|
|
28200
30308
|
out.push({
|
|
28201
30309
|
type: "circle",
|
|
28202
30310
|
x: mapX(p.x),
|
|
28203
30311
|
y: mapY(p.y),
|
|
28204
30312
|
radius: p.radius ?? 4,
|
|
28205
30313
|
color: p.color ?? "#dc2626",
|
|
28206
|
-
fill: p.color ?? "#dc2626"
|
|
30314
|
+
fill: isOpen ? "#ffffff" : p.color ?? "#dc2626"
|
|
28207
30315
|
});
|
|
28208
30316
|
if (p.label) {
|
|
28209
30317
|
out.push({ type: "text", x: mapX(p.x) + 8, y: mapY(p.y) - 8, text: p.label, color: "#111827", fontSize: 12 });
|
|
@@ -28222,7 +30330,28 @@ var init_MathCanvas = __esm({
|
|
|
28222
30330
|
}
|
|
28223
30331
|
out.push(...shapes);
|
|
28224
30332
|
return out;
|
|
28225
|
-
}, [
|
|
30333
|
+
}, [
|
|
30334
|
+
width,
|
|
30335
|
+
height,
|
|
30336
|
+
xMin,
|
|
30337
|
+
xMax,
|
|
30338
|
+
yMin,
|
|
30339
|
+
yMax,
|
|
30340
|
+
showAxes,
|
|
30341
|
+
showGrid,
|
|
30342
|
+
gridStep,
|
|
30343
|
+
showTickLabels,
|
|
30344
|
+
showCurveLabels,
|
|
30345
|
+
curves,
|
|
30346
|
+
points,
|
|
30347
|
+
vectors,
|
|
30348
|
+
regions,
|
|
30349
|
+
bars,
|
|
30350
|
+
guides,
|
|
30351
|
+
angles,
|
|
30352
|
+
hops,
|
|
30353
|
+
shapes
|
|
30354
|
+
]);
|
|
28226
30355
|
return /* @__PURE__ */ jsxRuntime.jsx(Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
|
|
28227
30356
|
title ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", children: title }) : null,
|
|
28228
30357
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -28231,6 +30360,8 @@ var init_MathCanvas = __esm({
|
|
|
28231
30360
|
width,
|
|
28232
30361
|
height,
|
|
28233
30362
|
shapes: derivedShapes,
|
|
30363
|
+
readouts,
|
|
30364
|
+
traces,
|
|
28234
30365
|
interactive,
|
|
28235
30366
|
animate,
|
|
28236
30367
|
onShapeClick,
|
|
@@ -28242,27 +30373,355 @@ var init_MathCanvas = __esm({
|
|
|
28242
30373
|
};
|
|
28243
30374
|
}
|
|
28244
30375
|
});
|
|
28245
|
-
|
|
30376
|
+
function formatMeterValue(v) {
|
|
30377
|
+
return Number.isInteger(v) ? String(v) : String(Number(v.toFixed(2)));
|
|
30378
|
+
}
|
|
30379
|
+
function sceneObjectShapes(obj, canvasWidth, canvasHeight) {
|
|
30380
|
+
const out = [];
|
|
30381
|
+
const color = obj.color ?? "#334155";
|
|
30382
|
+
switch (obj.kind) {
|
|
30383
|
+
case "ground": {
|
|
30384
|
+
const xStart = obj.x1 ?? 0;
|
|
30385
|
+
const xEnd = obj.x2 ?? canvasWidth;
|
|
30386
|
+
const y = obj.y ?? 0;
|
|
30387
|
+
out.push({ type: "line", x1: xStart, y1: y, x2: xEnd, y2: y, color, lineWidth: 2 });
|
|
30388
|
+
for (let hx = xStart + 7; hx <= xEnd; hx += 14) {
|
|
30389
|
+
out.push({ type: "line", x1: hx, y1: y, x2: hx - 7, y2: y + 7, color, lineWidth: 1 });
|
|
30390
|
+
}
|
|
30391
|
+
if (obj.label) {
|
|
30392
|
+
out.push({
|
|
30393
|
+
type: "text",
|
|
30394
|
+
x: (xStart + xEnd) / 2,
|
|
30395
|
+
y: y - 10,
|
|
30396
|
+
text: obj.label,
|
|
30397
|
+
color: PHYSICS_LABEL_COLOR,
|
|
30398
|
+
fontSize: 11,
|
|
30399
|
+
align: "center"
|
|
30400
|
+
});
|
|
30401
|
+
}
|
|
30402
|
+
break;
|
|
30403
|
+
}
|
|
30404
|
+
case "wall": {
|
|
30405
|
+
const yStart = obj.y1 ?? 0;
|
|
30406
|
+
const yEnd = obj.y2 ?? canvasHeight;
|
|
30407
|
+
const x = obj.x ?? 0;
|
|
30408
|
+
out.push({ type: "line", x1: x, y1: yStart, x2: x, y2: yEnd, color, lineWidth: 2 });
|
|
30409
|
+
for (let hy = yStart + 7; hy <= yEnd; hy += 14) {
|
|
30410
|
+
out.push({ type: "line", x1: x, y1: hy, x2: x - 7, y2: hy + 7, color, lineWidth: 1 });
|
|
30411
|
+
}
|
|
30412
|
+
if (obj.label) {
|
|
30413
|
+
out.push({
|
|
30414
|
+
type: "text",
|
|
30415
|
+
x: x + 12,
|
|
30416
|
+
y: (yStart + yEnd) / 2,
|
|
30417
|
+
text: obj.label,
|
|
30418
|
+
color: PHYSICS_LABEL_COLOR,
|
|
30419
|
+
fontSize: 11,
|
|
30420
|
+
align: "left"
|
|
30421
|
+
});
|
|
30422
|
+
}
|
|
30423
|
+
break;
|
|
30424
|
+
}
|
|
30425
|
+
case "ramp": {
|
|
30426
|
+
const x1 = obj.x1 ?? 0;
|
|
30427
|
+
const y1 = obj.y1 ?? 0;
|
|
30428
|
+
const x2 = obj.x2 ?? canvasWidth;
|
|
30429
|
+
const y2 = obj.y2 ?? canvasHeight;
|
|
30430
|
+
out.push({
|
|
30431
|
+
type: "polygon",
|
|
30432
|
+
points: [
|
|
30433
|
+
{ x: x1, y: y1 },
|
|
30434
|
+
{ x: x2, y: y2 },
|
|
30435
|
+
{ x: x1, y: y2 }
|
|
30436
|
+
],
|
|
30437
|
+
color,
|
|
30438
|
+
fill: obj.fill ?? "#e2e8f0",
|
|
30439
|
+
lineWidth: 2
|
|
30440
|
+
});
|
|
30441
|
+
if (obj.label) {
|
|
30442
|
+
out.push({
|
|
30443
|
+
type: "text",
|
|
30444
|
+
x: (2 * x1 + x2) / 3,
|
|
30445
|
+
y: (y1 + 2 * y2) / 3,
|
|
30446
|
+
text: obj.label,
|
|
30447
|
+
color: PHYSICS_LABEL_COLOR,
|
|
30448
|
+
fontSize: 11,
|
|
30449
|
+
align: "center"
|
|
30450
|
+
});
|
|
30451
|
+
}
|
|
30452
|
+
break;
|
|
30453
|
+
}
|
|
30454
|
+
case "box": {
|
|
30455
|
+
const x = obj.x ?? 0;
|
|
30456
|
+
const y = obj.y ?? 0;
|
|
30457
|
+
const w = obj.width ?? 40;
|
|
30458
|
+
const h = obj.height ?? 40;
|
|
30459
|
+
out.push({ type: "rect", x, y, width: w, height: h, color, fill: obj.fill, lineWidth: 2 });
|
|
30460
|
+
if (obj.label) {
|
|
30461
|
+
out.push({
|
|
30462
|
+
type: "text",
|
|
30463
|
+
x: x + w / 2,
|
|
30464
|
+
y: y + h / 2,
|
|
30465
|
+
text: obj.label,
|
|
30466
|
+
color: PHYSICS_LABEL_COLOR,
|
|
30467
|
+
fontSize: 11,
|
|
30468
|
+
align: "center"
|
|
30469
|
+
});
|
|
30470
|
+
}
|
|
30471
|
+
break;
|
|
30472
|
+
}
|
|
30473
|
+
case "pivot": {
|
|
30474
|
+
const x = obj.x ?? 0;
|
|
30475
|
+
const y = obj.y ?? 0;
|
|
30476
|
+
out.push({ type: "circle", x, y, radius: 5, color, fill: color });
|
|
30477
|
+
out.push({ type: "line", x1: x - 14, y1: y - 8, x2: x + 14, y2: y - 8, color, lineWidth: 1 });
|
|
30478
|
+
for (let k = 0; k < 5; k++) {
|
|
30479
|
+
const hx = x - 14 + 7 * k;
|
|
30480
|
+
out.push({ type: "line", x1: hx, y1: y - 8, x2: hx - 6, y2: y - 14, color, lineWidth: 1 });
|
|
30481
|
+
}
|
|
30482
|
+
if (obj.label) {
|
|
30483
|
+
out.push({
|
|
30484
|
+
type: "text",
|
|
30485
|
+
x,
|
|
30486
|
+
y: y - 20,
|
|
30487
|
+
text: obj.label,
|
|
30488
|
+
color: PHYSICS_LABEL_COLOR,
|
|
30489
|
+
fontSize: 11,
|
|
30490
|
+
align: "center"
|
|
30491
|
+
});
|
|
30492
|
+
}
|
|
30493
|
+
break;
|
|
30494
|
+
}
|
|
30495
|
+
}
|
|
30496
|
+
return out;
|
|
30497
|
+
}
|
|
30498
|
+
function trailShapes(trail) {
|
|
30499
|
+
const n = trail.points.length;
|
|
30500
|
+
if (n < 2) return [];
|
|
30501
|
+
const color = trail.color ?? "#94a3b8";
|
|
30502
|
+
const lineWidth = trail.width ?? 2;
|
|
30503
|
+
const fade = trail.fade ?? true;
|
|
30504
|
+
const globalOpacity = trail.opacity ?? 1;
|
|
30505
|
+
const out = [];
|
|
30506
|
+
for (let i = 0; i < n - 1; i++) {
|
|
30507
|
+
const a = trail.points[i];
|
|
30508
|
+
const b = trail.points[i + 1];
|
|
30509
|
+
const segmentOpacity = fade ? 0.12 + 0.68 * i / (n - 1) : 0.6;
|
|
30510
|
+
out.push({
|
|
30511
|
+
type: "line",
|
|
30512
|
+
x1: a.x,
|
|
30513
|
+
y1: a.y,
|
|
30514
|
+
x2: b.x,
|
|
30515
|
+
y2: b.y,
|
|
30516
|
+
color,
|
|
30517
|
+
lineWidth,
|
|
30518
|
+
opacity: segmentOpacity * globalOpacity
|
|
30519
|
+
});
|
|
30520
|
+
}
|
|
30521
|
+
return out;
|
|
30522
|
+
}
|
|
30523
|
+
function constraintShapes(c, a, b) {
|
|
30524
|
+
const color = c.color ?? "#9ca3af";
|
|
30525
|
+
const kind = c.kind ?? "rod";
|
|
30526
|
+
if (kind === "rod") {
|
|
30527
|
+
return [{ type: "line", x1: a.x, y1: a.y, x2: b.x, y2: b.y, color, lineWidth: 2 }];
|
|
30528
|
+
}
|
|
30529
|
+
if (kind === "string") {
|
|
30530
|
+
return [{ type: "line", x1: a.x, y1: a.y, x2: b.x, y2: b.y, color, lineWidth: 2, dash: "dashed" }];
|
|
30531
|
+
}
|
|
30532
|
+
const COILS = 8;
|
|
30533
|
+
const AMP = 7;
|
|
30534
|
+
const LEAD = 10;
|
|
30535
|
+
const dx = b.x - a.x;
|
|
30536
|
+
const dy = b.y - a.y;
|
|
30537
|
+
const dist = Math.max(1e-6, Math.hypot(dx, dy));
|
|
30538
|
+
const ux = dx / dist;
|
|
30539
|
+
const uy = dy / dist;
|
|
30540
|
+
const perpX = -uy;
|
|
30541
|
+
const perpY = ux;
|
|
30542
|
+
const aPrime = { x: a.x + LEAD * ux, y: a.y + LEAD * uy };
|
|
30543
|
+
const bPrime = { x: b.x - LEAD * ux, y: b.y - LEAD * uy };
|
|
30544
|
+
const m = 2 * COILS;
|
|
30545
|
+
const polyline = [{ x: a.x, y: a.y }, aPrime];
|
|
30546
|
+
for (let j = 1; j <= m; j++) {
|
|
30547
|
+
const t = j / (m + 1);
|
|
30548
|
+
const baseX = aPrime.x + t * (bPrime.x - aPrime.x);
|
|
30549
|
+
const baseY = aPrime.y + t * (bPrime.y - aPrime.y);
|
|
30550
|
+
const sign = j % 2 === 0 ? 1 : -1;
|
|
30551
|
+
polyline.push({ x: baseX + sign * AMP * perpX, y: baseY + sign * AMP * perpY });
|
|
30552
|
+
}
|
|
30553
|
+
polyline.push(bPrime, { x: b.x, y: b.y });
|
|
30554
|
+
const out = [];
|
|
30555
|
+
for (let i = 1; i < polyline.length; i++) {
|
|
30556
|
+
out.push({
|
|
30557
|
+
type: "line",
|
|
30558
|
+
x1: polyline[i - 1].x,
|
|
30559
|
+
y1: polyline[i - 1].y,
|
|
30560
|
+
x2: polyline[i].x,
|
|
30561
|
+
y2: polyline[i].y,
|
|
30562
|
+
color,
|
|
30563
|
+
lineWidth: 2
|
|
30564
|
+
});
|
|
30565
|
+
}
|
|
30566
|
+
return out;
|
|
30567
|
+
}
|
|
30568
|
+
function vectorShapes(v, bodyById) {
|
|
30569
|
+
let ax;
|
|
30570
|
+
let ay;
|
|
30571
|
+
if (v.body) {
|
|
30572
|
+
const anchor = bodyById.get(v.body);
|
|
30573
|
+
if (!anchor) return [];
|
|
30574
|
+
ax = anchor.x;
|
|
30575
|
+
ay = anchor.y;
|
|
30576
|
+
} else {
|
|
30577
|
+
ax = v.x ?? 0;
|
|
30578
|
+
ay = v.y ?? 0;
|
|
30579
|
+
}
|
|
30580
|
+
const scale = v.scale ?? 1;
|
|
30581
|
+
const color = v.color ?? "#dc2626";
|
|
30582
|
+
const tx = ax + v.dx * scale;
|
|
30583
|
+
const ty = ay + v.dy * scale;
|
|
30584
|
+
const out = [{ type: "arrow", x1: ax, y1: ay, x2: tx, y2: ty, color, lineWidth: 2, dash: v.dash }];
|
|
30585
|
+
if (v.label) {
|
|
30586
|
+
const dist = Math.max(1e-6, Math.hypot(tx - ax, ty - ay));
|
|
30587
|
+
const ux = (tx - ax) / dist;
|
|
30588
|
+
const uy = (ty - ay) / dist;
|
|
30589
|
+
out.push({
|
|
30590
|
+
type: "text",
|
|
30591
|
+
x: tx + 8 * ux,
|
|
30592
|
+
y: ty + 8 * uy,
|
|
30593
|
+
text: v.label,
|
|
30594
|
+
color,
|
|
30595
|
+
fontSize: 11,
|
|
30596
|
+
align: "center"
|
|
30597
|
+
});
|
|
30598
|
+
}
|
|
30599
|
+
return out;
|
|
30600
|
+
}
|
|
30601
|
+
function angleMarkerShapes(a) {
|
|
30602
|
+
const radius = a.radius ?? 26;
|
|
30603
|
+
const color = a.color ?? "#0ea5e9";
|
|
30604
|
+
const out = [
|
|
30605
|
+
{
|
|
30606
|
+
type: "ellipse",
|
|
30607
|
+
x: a.x,
|
|
30608
|
+
y: a.y,
|
|
30609
|
+
width: radius * 2,
|
|
30610
|
+
height: radius * 2,
|
|
30611
|
+
startAngle: a.from,
|
|
30612
|
+
endAngle: a.to,
|
|
30613
|
+
color,
|
|
30614
|
+
lineWidth: 2
|
|
30615
|
+
}
|
|
30616
|
+
];
|
|
30617
|
+
if (a.label) {
|
|
30618
|
+
const mid = (a.from + a.to) / 2 * (Math.PI / 180);
|
|
30619
|
+
out.push({
|
|
30620
|
+
type: "text",
|
|
30621
|
+
x: a.x + (radius + 13) * Math.cos(mid),
|
|
30622
|
+
y: a.y + (radius + 13) * Math.sin(mid),
|
|
30623
|
+
text: a.label,
|
|
30624
|
+
color,
|
|
30625
|
+
fontSize: 11,
|
|
30626
|
+
align: "center"
|
|
30627
|
+
});
|
|
30628
|
+
}
|
|
30629
|
+
return out;
|
|
30630
|
+
}
|
|
30631
|
+
function fieldShapes(field, canvasWidth, canvasHeight) {
|
|
30632
|
+
const spacing = field.spacing ?? 48;
|
|
30633
|
+
const size = field.size ?? 14;
|
|
30634
|
+
const color = field.color ?? "#94a3b8";
|
|
30635
|
+
const regionX = field.x ?? 0;
|
|
30636
|
+
const regionY = field.y ?? 0;
|
|
30637
|
+
const regionW = field.width ?? canvasWidth;
|
|
30638
|
+
const regionH = field.height ?? canvasHeight;
|
|
30639
|
+
const out = [];
|
|
30640
|
+
for (let gx = regionX + spacing / 2; gx < regionX + regionW; gx += spacing) {
|
|
30641
|
+
for (let gy = regionY + spacing / 2; gy < regionY + regionH; gy += spacing) {
|
|
30642
|
+
if (field.kind === "arrows") {
|
|
30643
|
+
const rad = (field.angle ?? 0) * Math.PI / 180;
|
|
30644
|
+
const hx = Math.cos(rad) * size / 2;
|
|
30645
|
+
const hy = Math.sin(rad) * size / 2;
|
|
30646
|
+
out.push({ type: "arrow", x1: gx - hx, y1: gy - hy, x2: gx + hx, y2: gy + hy, color, lineWidth: 2 });
|
|
30647
|
+
} else if (field.kind === "into") {
|
|
30648
|
+
const r = size / 3;
|
|
30649
|
+
const d = 0.6 * r * Math.SQRT1_2;
|
|
30650
|
+
out.push({ type: "circle", x: gx, y: gy, radius: r, color });
|
|
30651
|
+
out.push({ type: "line", x1: gx - d, y1: gy - d, x2: gx + d, y2: gy + d, color, lineWidth: 1 });
|
|
30652
|
+
out.push({ type: "line", x1: gx - d, y1: gy + d, x2: gx + d, y2: gy - d, color, lineWidth: 1 });
|
|
30653
|
+
} else {
|
|
30654
|
+
const r = size / 3;
|
|
30655
|
+
out.push({ type: "circle", x: gx, y: gy, radius: r, color });
|
|
30656
|
+
out.push({ type: "circle", x: gx, y: gy, radius: 1.5, color, fill: color });
|
|
30657
|
+
}
|
|
30658
|
+
}
|
|
30659
|
+
}
|
|
30660
|
+
return out;
|
|
30661
|
+
}
|
|
30662
|
+
function meterShapes(meters, canvasHeight) {
|
|
30663
|
+
const n = meters.length;
|
|
30664
|
+
const out = [];
|
|
30665
|
+
const sharedMax = Math.max(1e-6, ...meters.map((m) => m.value));
|
|
30666
|
+
meters.forEach((meter, i) => {
|
|
30667
|
+
const rowY = canvasHeight - 10 - 16 * (n - i);
|
|
30668
|
+
const color = meter.color ?? "#3b82f6";
|
|
30669
|
+
const M = meter.max ?? sharedMax;
|
|
30670
|
+
const w = Math.round(Math.min(1, Math.max(0, meter.value / M)) * 110);
|
|
30671
|
+
out.push({ type: "text", x: 8, y: rowY + 8, text: meter.label, color: PHYSICS_LABEL_COLOR, fontSize: 10 });
|
|
30672
|
+
out.push({ type: "rect", x: 52, y: rowY, width: w, height: 10, color, fill: color });
|
|
30673
|
+
out.push({
|
|
30674
|
+
type: "text",
|
|
30675
|
+
x: 166,
|
|
30676
|
+
y: rowY + 8,
|
|
30677
|
+
text: formatMeterValue(meter.value),
|
|
30678
|
+
color: "#6b7280",
|
|
30679
|
+
fontSize: 9
|
|
30680
|
+
});
|
|
30681
|
+
});
|
|
30682
|
+
return out;
|
|
30683
|
+
}
|
|
30684
|
+
var physicsLog2, PHYSICS_LABEL_COLOR, PhysicsCanvas;
|
|
28246
30685
|
var init_PhysicsCanvas = __esm({
|
|
28247
30686
|
"components/learning/molecules/PhysicsCanvas.tsx"() {
|
|
28248
30687
|
"use client";
|
|
28249
30688
|
init_atoms();
|
|
28250
30689
|
init_Stack();
|
|
28251
30690
|
init_LearningCanvas();
|
|
30691
|
+
init_learningScene3D();
|
|
30692
|
+
physicsLog2 = logger.createLogger("almadar:ui:physics-canvas");
|
|
30693
|
+
PHYSICS_LABEL_COLOR = "#374151";
|
|
28252
30694
|
PhysicsCanvas = ({
|
|
28253
30695
|
className,
|
|
28254
30696
|
width = 600,
|
|
28255
30697
|
height = 400,
|
|
28256
30698
|
title,
|
|
28257
30699
|
backgroundColor,
|
|
30700
|
+
mode = "2d",
|
|
30701
|
+
camera,
|
|
30702
|
+
lighting,
|
|
30703
|
+
post,
|
|
28258
30704
|
bodies = [],
|
|
28259
30705
|
constraints = [],
|
|
28260
30706
|
showVelocity = true,
|
|
28261
30707
|
showForces = false,
|
|
28262
30708
|
velocityScale = 20,
|
|
28263
30709
|
forceScale = 20,
|
|
30710
|
+
sceneObjects = [],
|
|
30711
|
+
trails = [],
|
|
30712
|
+
vectors = [],
|
|
30713
|
+
surface3d,
|
|
30714
|
+
vectors3d = [],
|
|
30715
|
+
vectorScale = 1,
|
|
30716
|
+
angles = [],
|
|
30717
|
+
field,
|
|
30718
|
+
meters = [],
|
|
28264
30719
|
shapes = [],
|
|
28265
|
-
|
|
30720
|
+
readouts,
|
|
30721
|
+
traces,
|
|
30722
|
+
showGrid,
|
|
30723
|
+
shadows,
|
|
30724
|
+
interactive,
|
|
28266
30725
|
animate = false,
|
|
28267
30726
|
onShapeClick,
|
|
28268
30727
|
isLoading,
|
|
@@ -28274,19 +30733,14 @@ var init_PhysicsCanvas = __esm({
|
|
|
28274
30733
|
for (const b of bodies) {
|
|
28275
30734
|
if (b.id) bodyById.set(b.id, b);
|
|
28276
30735
|
}
|
|
30736
|
+
if (field) out.push(...fieldShapes(field, width, height));
|
|
30737
|
+
for (const obj of sceneObjects) out.push(...sceneObjectShapes(obj, width, height));
|
|
30738
|
+
for (const trail of trails) out.push(...trailShapes(trail));
|
|
28277
30739
|
for (const c of constraints) {
|
|
28278
30740
|
const a = bodyById.get(c.from);
|
|
28279
30741
|
const b = bodyById.get(c.to);
|
|
28280
30742
|
if (!a || !b) continue;
|
|
28281
|
-
out.push(
|
|
28282
|
-
type: "line",
|
|
28283
|
-
x1: a.x,
|
|
28284
|
-
y1: a.y,
|
|
28285
|
-
x2: b.x,
|
|
28286
|
-
y2: b.y,
|
|
28287
|
-
color: c.color ?? "#9ca3af",
|
|
28288
|
-
lineWidth: 2
|
|
28289
|
-
});
|
|
30743
|
+
out.push(...constraintShapes(c, a, b));
|
|
28290
30744
|
}
|
|
28291
30745
|
for (const b of bodies) {
|
|
28292
30746
|
out.push({
|
|
@@ -28331,9 +30785,181 @@ var init_PhysicsCanvas = __esm({
|
|
|
28331
30785
|
});
|
|
28332
30786
|
}
|
|
28333
30787
|
}
|
|
30788
|
+
for (const v of vectors) out.push(...vectorShapes(v, bodyById));
|
|
30789
|
+
for (const a of angles) out.push(...angleMarkerShapes(a));
|
|
30790
|
+
if (meters.length > 0) out.push(...meterShapes(meters, height));
|
|
28334
30791
|
out.push(...shapes);
|
|
28335
30792
|
return out;
|
|
28336
|
-
}, [
|
|
30793
|
+
}, [
|
|
30794
|
+
bodies,
|
|
30795
|
+
constraints,
|
|
30796
|
+
showVelocity,
|
|
30797
|
+
showForces,
|
|
30798
|
+
velocityScale,
|
|
30799
|
+
forceScale,
|
|
30800
|
+
sceneObjects,
|
|
30801
|
+
trails,
|
|
30802
|
+
vectors,
|
|
30803
|
+
angles,
|
|
30804
|
+
field,
|
|
30805
|
+
meters,
|
|
30806
|
+
shapes,
|
|
30807
|
+
width,
|
|
30808
|
+
height
|
|
30809
|
+
]);
|
|
30810
|
+
const drawables3D = React87.useMemo(() => {
|
|
30811
|
+
if (mode !== "3d") return [];
|
|
30812
|
+
if (shapes.length > 0) {
|
|
30813
|
+
physicsLog2.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
|
|
30814
|
+
}
|
|
30815
|
+
if (sceneObjects.length > 0) {
|
|
30816
|
+
physicsLog2.debug("sceneObjects ignored in 3D mode (pixel-authored 2D vocabulary)", { count: sceneObjects.length });
|
|
30817
|
+
}
|
|
30818
|
+
if (vectors.length > 0) {
|
|
30819
|
+
physicsLog2.debug("vectors ignored in 3D mode (pixel-authored 2D vocabulary)", { count: vectors.length });
|
|
30820
|
+
}
|
|
30821
|
+
if (angles.length > 0) {
|
|
30822
|
+
physicsLog2.debug("angles ignored in 3D mode (pixel-authored 2D vocabulary)", { count: angles.length });
|
|
30823
|
+
}
|
|
30824
|
+
if (field) {
|
|
30825
|
+
physicsLog2.debug("field ignored in 3D mode (pixel-authored 2D vocabulary)");
|
|
30826
|
+
}
|
|
30827
|
+
if (meters.length > 0) {
|
|
30828
|
+
physicsLog2.debug("meters ignored in 3D mode (pixel-authored 2D vocabulary)", { count: meters.length });
|
|
30829
|
+
}
|
|
30830
|
+
if (animate) {
|
|
30831
|
+
physicsLog2.debug("animate ignored in 3D mode (motion is entity-state driven)");
|
|
30832
|
+
}
|
|
30833
|
+
const out = [];
|
|
30834
|
+
const labelColor = labelColorForBackground(backgroundColor);
|
|
30835
|
+
const bodyById = /* @__PURE__ */ new Map();
|
|
30836
|
+
for (const b of bodies) {
|
|
30837
|
+
if (b.id) bodyById.set(b.id, b);
|
|
30838
|
+
}
|
|
30839
|
+
for (const c of constraints) {
|
|
30840
|
+
const a = bodyById.get(c.from);
|
|
30841
|
+
const b = bodyById.get(c.to);
|
|
30842
|
+
if (!a || !b) continue;
|
|
30843
|
+
const rodRadius = Math.max(0.05, Math.min(a.radius ?? 0.5, b.radius ?? 0.5) * 0.15);
|
|
30844
|
+
const rod = cylinderBetween([a.x, a.y, a.z ?? 0], [b.x, b.y, b.z ?? 0], rodRadius, c.color ?? "#9ca3af");
|
|
30845
|
+
if (rod) out.push(rod);
|
|
30846
|
+
}
|
|
30847
|
+
for (const b of bodies) {
|
|
30848
|
+
const radius = b.radius ?? 0.5;
|
|
30849
|
+
const bz = b.z ?? 0;
|
|
30850
|
+
out.push(meshSphere(b.id, b.x, b.y, bz, radius, b.color ?? "#2563eb"));
|
|
30851
|
+
if (b.label) {
|
|
30852
|
+
out.push(billboardLabel(b.label, b.x, b.y, bz + radius, { color: labelColor }));
|
|
30853
|
+
}
|
|
30854
|
+
const vx = b.vx ?? 0;
|
|
30855
|
+
const vy = b.vy ?? 0;
|
|
30856
|
+
const vz = b.vz ?? 0;
|
|
30857
|
+
const arrowRadius = Math.max(0.05, radius * 0.15);
|
|
30858
|
+
if (showVelocity && (vx !== 0 || vy !== 0 || vz !== 0)) {
|
|
30859
|
+
const arrow = arrowBetween(
|
|
30860
|
+
[b.x, b.y, bz],
|
|
30861
|
+
[b.x + vx * velocityScale, b.y + vy * velocityScale, bz + vz * velocityScale],
|
|
30862
|
+
"#16a34a",
|
|
30863
|
+
arrowRadius
|
|
30864
|
+
);
|
|
30865
|
+
if (arrow) out.push(arrow);
|
|
30866
|
+
}
|
|
30867
|
+
const fx = b.fx ?? 0;
|
|
30868
|
+
const fy = b.fy ?? 0;
|
|
30869
|
+
const fz = b.fz ?? 0;
|
|
30870
|
+
if (showForces && (fx !== 0 || fy !== 0 || fz !== 0)) {
|
|
30871
|
+
const arrow = arrowBetween(
|
|
30872
|
+
[b.x, b.y, bz],
|
|
30873
|
+
[b.x + fx * forceScale, b.y + fy * forceScale, bz + fz * forceScale],
|
|
30874
|
+
"#dc2626",
|
|
30875
|
+
arrowRadius
|
|
30876
|
+
);
|
|
30877
|
+
if (arrow) out.push(arrow);
|
|
30878
|
+
}
|
|
30879
|
+
}
|
|
30880
|
+
for (const trail of trails) {
|
|
30881
|
+
if (trail.fade !== void 0) {
|
|
30882
|
+
physicsLog2.debug("trail.fade ignored in 3D mode (2D-only fade curve \u2014 3D draws an opaque tube)", { id: trail.id });
|
|
30883
|
+
}
|
|
30884
|
+
const points = trail.points.map((p) => [p.x, p.y, p.z ?? 0]);
|
|
30885
|
+
out.push(
|
|
30886
|
+
...polylineTube(points, trail.width ?? 0.05, trail.color ?? "#94a3b8", {
|
|
30887
|
+
...trail.opacity !== void 0 ? { opacity: trail.opacity } : {}
|
|
30888
|
+
})
|
|
30889
|
+
);
|
|
30890
|
+
}
|
|
30891
|
+
if (surface3d) {
|
|
30892
|
+
out.push(...heightFieldMesh(surface3d));
|
|
30893
|
+
}
|
|
30894
|
+
if (vectors3d.length > 0) {
|
|
30895
|
+
out.push(
|
|
30896
|
+
...arrowField(
|
|
30897
|
+
vectors3d.map((v) => ({
|
|
30898
|
+
id: v.id,
|
|
30899
|
+
from: [v.x, v.y, v.z ?? 0],
|
|
30900
|
+
delta: [v.dx, v.dy, v.dz ?? 0],
|
|
30901
|
+
color: v.color,
|
|
30902
|
+
label: v.label,
|
|
30903
|
+
width: v.width
|
|
30904
|
+
})),
|
|
30905
|
+
{ scale: vectorScale, labelColor }
|
|
30906
|
+
)
|
|
30907
|
+
);
|
|
30908
|
+
}
|
|
30909
|
+
return out;
|
|
30910
|
+
}, [
|
|
30911
|
+
mode,
|
|
30912
|
+
bodies,
|
|
30913
|
+
constraints,
|
|
30914
|
+
showVelocity,
|
|
30915
|
+
showForces,
|
|
30916
|
+
velocityScale,
|
|
30917
|
+
forceScale,
|
|
30918
|
+
shapes,
|
|
30919
|
+
sceneObjects,
|
|
30920
|
+
trails,
|
|
30921
|
+
vectors,
|
|
30922
|
+
surface3d,
|
|
30923
|
+
vectors3d,
|
|
30924
|
+
vectorScale,
|
|
30925
|
+
angles,
|
|
30926
|
+
field,
|
|
30927
|
+
meters,
|
|
30928
|
+
animate,
|
|
30929
|
+
backgroundColor
|
|
30930
|
+
]);
|
|
30931
|
+
const bodyIndexById = React87.useMemo(() => {
|
|
30932
|
+
const m = /* @__PURE__ */ new Map();
|
|
30933
|
+
vectors3d.forEach((v, i) => {
|
|
30934
|
+
if (v.id) m.set(v.id, i);
|
|
30935
|
+
});
|
|
30936
|
+
bodies.forEach((b, i) => {
|
|
30937
|
+
if (b.id) m.set(b.id, i);
|
|
30938
|
+
});
|
|
30939
|
+
return m;
|
|
30940
|
+
}, [bodies, vectors3d]);
|
|
30941
|
+
if (mode === "3d") {
|
|
30942
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
30943
|
+
LearningScene3D,
|
|
30944
|
+
{
|
|
30945
|
+
className,
|
|
30946
|
+
width,
|
|
30947
|
+
height,
|
|
30948
|
+
title,
|
|
30949
|
+
backgroundColor,
|
|
30950
|
+
drawables: drawables3D,
|
|
30951
|
+
camera,
|
|
30952
|
+
lighting,
|
|
30953
|
+
post,
|
|
30954
|
+
showGrid,
|
|
30955
|
+
shadows,
|
|
30956
|
+
interactive,
|
|
30957
|
+
isLoading,
|
|
30958
|
+
error,
|
|
30959
|
+
onItemClick: get3DClickPayload(onShapeClick, bodyIndexById)
|
|
30960
|
+
}
|
|
30961
|
+
);
|
|
30962
|
+
}
|
|
28337
30963
|
return /* @__PURE__ */ jsxRuntime.jsx(Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
|
|
28338
30964
|
title ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", children: title }) : null,
|
|
28339
30965
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -28343,7 +30969,9 @@ var init_PhysicsCanvas = __esm({
|
|
|
28343
30969
|
height,
|
|
28344
30970
|
backgroundColor,
|
|
28345
30971
|
shapes: derivedShapes,
|
|
28346
|
-
|
|
30972
|
+
readouts,
|
|
30973
|
+
traces,
|
|
30974
|
+
interactive: interactive ?? false,
|
|
28347
30975
|
animate,
|
|
28348
30976
|
onShapeClick,
|
|
28349
30977
|
isLoading,
|
|
@@ -28493,7 +31121,7 @@ function layoutFlow(nodeIds, adjacency, roots, width, height, margin) {
|
|
|
28493
31121
|
}
|
|
28494
31122
|
return nodeIds.map((id) => positions.get(id));
|
|
28495
31123
|
}
|
|
28496
|
-
function
|
|
31124
|
+
function layoutTree2(nodeIds, adjacency, roots, width, height, margin) {
|
|
28497
31125
|
const effectiveRoots = roots.length > 0 ? roots : [nodeIds[0]];
|
|
28498
31126
|
const layers = assignLayers(nodeIds, adjacency, effectiveRoots);
|
|
28499
31127
|
const maxLayer = Math.max(...Array.from(layers.values()));
|
|
@@ -28549,7 +31177,7 @@ function computeStaticLayout(mode, input) {
|
|
|
28549
31177
|
const adjacency = buildAdjacency(nodeIds, edges);
|
|
28550
31178
|
const roots = findRoots(nodeIds, adjacency);
|
|
28551
31179
|
if (mode === "flow") return layoutFlow(nodeIds, adjacency, roots, width, height, margin);
|
|
28552
|
-
if (mode === "tree") return
|
|
31180
|
+
if (mode === "tree") return layoutTree2(nodeIds, adjacency, roots, width, height, margin);
|
|
28553
31181
|
return layoutRadial(nodeIds, adjacency, roots, width, height, margin);
|
|
28554
31182
|
}
|
|
28555
31183
|
var init_graphViewLayouts = __esm({
|
|
@@ -28961,12 +31589,12 @@ var init_MapView = __esm({
|
|
|
28961
31589
|
shadowSize: [41, 41]
|
|
28962
31590
|
});
|
|
28963
31591
|
L.Marker.prototype.options.icon = defaultIcon;
|
|
28964
|
-
const { useEffect: useEffect63, useRef:
|
|
31592
|
+
const { useEffect: useEffect63, useRef: useRef63, useCallback: useCallback96, useState: useState94 } = React87__namespace.default;
|
|
28965
31593
|
const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
|
|
28966
31594
|
const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
|
|
28967
31595
|
function MapUpdater({ centerLat, centerLng, zoom }) {
|
|
28968
31596
|
const map = useMap();
|
|
28969
|
-
const prevRef =
|
|
31597
|
+
const prevRef = useRef63({ centerLat, centerLng, zoom });
|
|
28970
31598
|
useEffect63(() => {
|
|
28971
31599
|
const prev = prevRef.current;
|
|
28972
31600
|
if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
|
|
@@ -29792,26 +32420,6 @@ var init_Lightbox = __esm({
|
|
|
29792
32420
|
Lightbox.displayName = "Lightbox";
|
|
29793
32421
|
}
|
|
29794
32422
|
});
|
|
29795
|
-
function useMediaQuery(query) {
|
|
29796
|
-
const subscribe = React87.useCallback(
|
|
29797
|
-
(onChange) => {
|
|
29798
|
-
const mql = window.matchMedia(query);
|
|
29799
|
-
mql.addEventListener("change", onChange);
|
|
29800
|
-
return () => mql.removeEventListener("change", onChange);
|
|
29801
|
-
},
|
|
29802
|
-
[query]
|
|
29803
|
-
);
|
|
29804
|
-
return React87.useSyncExternalStore(
|
|
29805
|
-
subscribe,
|
|
29806
|
-
() => window.matchMedia(query).matches,
|
|
29807
|
-
() => false
|
|
29808
|
-
);
|
|
29809
|
-
}
|
|
29810
|
-
var init_useMediaQuery = __esm({
|
|
29811
|
-
"hooks/useMediaQuery.ts"() {
|
|
29812
|
-
"use client";
|
|
29813
|
-
}
|
|
29814
|
-
});
|
|
29815
32423
|
function renderIconInput3(icon, props) {
|
|
29816
32424
|
return typeof icon === "string" ? /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: icon, ...props }) : /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon, ...props });
|
|
29817
32425
|
}
|
|
@@ -29850,8 +32458,8 @@ function TableView({
|
|
|
29850
32458
|
columns,
|
|
29851
32459
|
fields,
|
|
29852
32460
|
itemActions,
|
|
29853
|
-
maxInlineActions,
|
|
29854
|
-
itemClickEvent,
|
|
32461
|
+
maxInlineActions: _maxInlineActions,
|
|
32462
|
+
itemClickEvent = "",
|
|
29855
32463
|
selectable = false,
|
|
29856
32464
|
selectEvent,
|
|
29857
32465
|
selectedIds,
|
|
@@ -29901,7 +32509,6 @@ function TableView({
|
|
|
29901
32509
|
const hasMore = pageSize > 0 && visibleCount < ordered2.length;
|
|
29902
32510
|
const hasRenderProp = typeof children === "function";
|
|
29903
32511
|
const idField = dndItemIdField ?? "id";
|
|
29904
|
-
const isCoarsePointer = useMediaQuery("(pointer: coarse)");
|
|
29905
32512
|
React87__namespace.default.useEffect(() => {
|
|
29906
32513
|
tableViewLog.debug("render", {
|
|
29907
32514
|
rowCount: data.length,
|
|
@@ -29943,21 +32550,14 @@ function TableView({
|
|
|
29943
32550
|
const dir = sortColumn === (col.field ?? col.key) && sortDirection === "asc" ? "desc" : "asc";
|
|
29944
32551
|
eventBus.emit(`UI:${sortEvent}`, { column: col.field ?? col.key, direction: dir });
|
|
29945
32552
|
};
|
|
29946
|
-
const
|
|
29947
|
-
e.stopPropagation();
|
|
29948
|
-
const payload = {
|
|
29949
|
-
id: row.id,
|
|
29950
|
-
row
|
|
29951
|
-
};
|
|
29952
|
-
eventBus.emit(`UI:${action.event}`, payload);
|
|
29953
|
-
};
|
|
32553
|
+
const rowClickEvent = itemClickEvent || actionDefs.find((a) => a.variant !== "danger")?.event;
|
|
29954
32554
|
const handleRowClick = (row) => () => {
|
|
29955
|
-
if (!
|
|
32555
|
+
if (!rowClickEvent) return;
|
|
29956
32556
|
const payload = {
|
|
29957
32557
|
id: row.id,
|
|
29958
32558
|
row
|
|
29959
32559
|
};
|
|
29960
|
-
eventBus.emit(`UI:${
|
|
32560
|
+
eventBus.emit(`UI:${rowClickEvent}`, payload);
|
|
29961
32561
|
};
|
|
29962
32562
|
const colFloors = React87__namespace.default.useMemo(
|
|
29963
32563
|
() => colDefs.map((col) => {
|
|
@@ -29973,10 +32573,7 @@ function TableView({
|
|
|
29973
32573
|
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;
|
|
29974
32574
|
const lk = LOOKS[look];
|
|
29975
32575
|
const hasActions = actionDefs.length > 0;
|
|
29976
|
-
const
|
|
29977
|
-
const inlineActionCount = hasActions ? effectiveMaxInline != null ? Math.min(actionDefs.length, effectiveMaxInline) : actionDefs.length : 0;
|
|
29978
|
-
const hasOverflowActions = hasActions && effectiveMaxInline != null && actionDefs.length > effectiveMaxInline;
|
|
29979
|
-
const actionsTrack = hasActions ? `${inlineActionCount * 6 + (hasOverflowActions ? 3 : 0)}rem` : null;
|
|
32576
|
+
const actionsTrack = hasActions ? "3rem" : null;
|
|
29980
32577
|
const gridTemplateColumns = [
|
|
29981
32578
|
selectable ? "auto" : null,
|
|
29982
32579
|
...colDefs.map((c, i) => c.width ?? `minmax(${colFloors[i]}ch, 1fr)`),
|
|
@@ -30023,7 +32620,7 @@ function TableView({
|
|
|
30023
32620
|
col.key
|
|
30024
32621
|
);
|
|
30025
32622
|
}),
|
|
30026
|
-
hasActions && /* @__PURE__ */ jsxRuntime.jsx(Box, { "aria-hidden": true, className: "sticky right-0 bg-[var(--color-surface-subtle)]" })
|
|
32623
|
+
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" })
|
|
30027
32624
|
]
|
|
30028
32625
|
}
|
|
30029
32626
|
);
|
|
@@ -30035,12 +32632,12 @@ function TableView({
|
|
|
30035
32632
|
role: "row",
|
|
30036
32633
|
"data-entity-row": true,
|
|
30037
32634
|
"data-entity-id": id,
|
|
30038
|
-
onClick:
|
|
32635
|
+
onClick: rowClickEvent ? handleRowClick(row) : void 0,
|
|
30039
32636
|
style: !hasRenderProp ? { gridTemplateColumns } : void 0,
|
|
30040
32637
|
className: cn(
|
|
30041
32638
|
"group items-center gap-3 transition-colors duration-fast",
|
|
30042
32639
|
hasRenderProp ? "flex" : "grid",
|
|
30043
|
-
|
|
32640
|
+
rowClickEvent && "cursor-pointer",
|
|
30044
32641
|
lk.rowPad,
|
|
30045
32642
|
lk.divider && "border-b border-[var(--color-border)]",
|
|
30046
32643
|
lk.striped && index % 2 === 1 && "bg-[var(--color-surface-subtle)]",
|
|
@@ -30048,7 +32645,7 @@ function TableView({
|
|
|
30048
32645
|
look === "bordered" && "[&>*]:border-r [&>*]:border-[var(--color-border)] [&>*:last-child]:border-r-0"
|
|
30049
32646
|
),
|
|
30050
32647
|
children: [
|
|
30051
|
-
selectable && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "flex items-center", onClick:
|
|
32648
|
+
selectable && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "flex items-center", onClick: rowClickEvent ? (e) => e.stopPropagation() : void 0, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
30052
32649
|
Checkbox,
|
|
30053
32650
|
{
|
|
30054
32651
|
checked: selected.has(id),
|
|
@@ -30069,53 +32666,37 @@ function TableView({
|
|
|
30069
32666
|
}
|
|
30070
32667
|
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);
|
|
30071
32668
|
}),
|
|
30072
|
-
hasActions && /* @__PURE__ */ jsxRuntime.
|
|
32669
|
+
hasActions && /* @__PURE__ */ jsxRuntime.jsx(
|
|
30073
32670
|
HStack,
|
|
30074
32671
|
{
|
|
30075
32672
|
gap: "xs",
|
|
30076
|
-
onClick:
|
|
32673
|
+
onClick: rowClickEvent ? (e) => e.stopPropagation() : void 0,
|
|
30077
32674
|
className: cn(
|
|
30078
32675
|
// Pinned: the fixed column tracks routinely overflow the caller's
|
|
30079
|
-
// scroll container, which
|
|
30080
|
-
// Opaque so
|
|
32676
|
+
// scroll container, which would leave the kebab off-screen.
|
|
32677
|
+
// Opaque + hairline edge so it reads as a pinned column, not a
|
|
32678
|
+
// floating control, while scrolled cells pass underneath.
|
|
30081
32679
|
"justify-end flex-shrink-0 sticky right-0 z-[1] transition-colors",
|
|
32680
|
+
"border-l border-[var(--color-border)]",
|
|
30082
32681
|
lk.striped && index % 2 === 1 ? "bg-[var(--color-surface-subtle)]" : "bg-[var(--color-card)] group-hover:bg-[var(--color-surface-subtle)]"
|
|
30083
32682
|
),
|
|
30084
|
-
children:
|
|
30085
|
-
|
|
30086
|
-
|
|
30087
|
-
|
|
30088
|
-
|
|
30089
|
-
|
|
30090
|
-
|
|
30091
|
-
|
|
30092
|
-
|
|
30093
|
-
|
|
30094
|
-
|
|
30095
|
-
|
|
30096
|
-
|
|
30097
|
-
|
|
30098
|
-
}
|
|
30099
|
-
|
|
30100
|
-
|
|
30101
|
-
effectiveMaxInline != null && actionDefs.length > effectiveMaxInline && /* @__PURE__ */ jsxRuntime.jsx(
|
|
30102
|
-
Menu,
|
|
30103
|
-
{
|
|
30104
|
-
position: "bottom-end",
|
|
30105
|
-
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" }) }),
|
|
30106
|
-
items: actionDefs.slice(effectiveMaxInline).map((action) => ({
|
|
30107
|
-
label: action.label,
|
|
30108
|
-
icon: action.icon,
|
|
30109
|
-
event: action.event,
|
|
30110
|
-
variant: action.variant === "danger" ? "danger" : "default",
|
|
30111
|
-
onClick: () => eventBus.emit(`UI:${action.event}`, {
|
|
30112
|
-
id: row.id,
|
|
30113
|
-
row
|
|
30114
|
-
})
|
|
30115
|
-
}))
|
|
30116
|
-
}
|
|
30117
|
-
)
|
|
30118
|
-
]
|
|
32683
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
32684
|
+
Menu,
|
|
32685
|
+
{
|
|
32686
|
+
position: "bottom-end",
|
|
32687
|
+
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" }) }),
|
|
32688
|
+
items: actionDefs.map((action) => ({
|
|
32689
|
+
label: action.label,
|
|
32690
|
+
icon: action.icon,
|
|
32691
|
+
event: action.event,
|
|
32692
|
+
variant: action.variant === "danger" ? "danger" : "default",
|
|
32693
|
+
onClick: () => eventBus.emit(`UI:${action.event}`, {
|
|
32694
|
+
id: row.id,
|
|
32695
|
+
row
|
|
32696
|
+
})
|
|
32697
|
+
}))
|
|
32698
|
+
}
|
|
32699
|
+
)
|
|
30119
32700
|
}
|
|
30120
32701
|
)
|
|
30121
32702
|
]
|
|
@@ -30158,7 +32739,6 @@ var init_TableView = __esm({
|
|
|
30158
32739
|
init_format();
|
|
30159
32740
|
init_getNestedValue();
|
|
30160
32741
|
init_useEventBus();
|
|
30161
|
-
init_useMediaQuery();
|
|
30162
32742
|
init_Box();
|
|
30163
32743
|
init_Stack();
|
|
30164
32744
|
init_Typography();
|
|
@@ -39789,7 +42369,7 @@ function getEnumOptions(field) {
|
|
|
39789
42369
|
}));
|
|
39790
42370
|
}
|
|
39791
42371
|
const validation = field.validation;
|
|
39792
|
-
if (validation?.enum &&
|
|
42372
|
+
if (validation?.enum && validation.enum.length > 0) {
|
|
39793
42373
|
return validation.enum.map((v) => ({
|
|
39794
42374
|
value: v,
|
|
39795
42375
|
label: v.charAt(0).toUpperCase() + v.slice(1).replace(/_/g, " ")
|
|
@@ -45217,6 +47797,7 @@ var init_component_registry_generated = __esm({
|
|
|
45217
47797
|
init_ActionTile();
|
|
45218
47798
|
init_ActivationBlock();
|
|
45219
47799
|
init_ComponentPatterns();
|
|
47800
|
+
init_AlgoGraphCanvas();
|
|
45220
47801
|
init_AlgorithmCanvas();
|
|
45221
47802
|
init_AnimatedCounter();
|
|
45222
47803
|
init_AnimatedGraphic();
|
|
@@ -45480,6 +48061,7 @@ var init_component_registry_generated = __esm({
|
|
|
45480
48061
|
"ActivationBlock": ActivationBlock,
|
|
45481
48062
|
"Alert": AlertPattern,
|
|
45482
48063
|
"AlertPattern": AlertPattern,
|
|
48064
|
+
"AlgoGraphCanvas": AlgoGraphCanvas,
|
|
45483
48065
|
"AlgorithmCanvas": AlgorithmCanvas,
|
|
45484
48066
|
"AnimatedCounter": AnimatedCounter,
|
|
45485
48067
|
"AnimatedGraphic": AnimatedGraphic,
|