@almadar/ui 5.149.0 → 5.151.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/avl/index.cjs +2315 -157
- package/dist/avl/index.js +2315 -157
- package/dist/components/index.cjs +2327 -153
- package/dist/components/index.d.cts +811 -15
- package/dist/components/index.d.ts +811 -15
- package/dist/components/index.js +2329 -155
- package/dist/hooks/index.cjs +1 -1
- package/dist/hooks/index.js +1 -1
- package/dist/lib/drawable/three/index.cjs +1 -1
- package/dist/lib/drawable/three/index.js +1 -1
- package/dist/marketing/index.cjs +1 -1
- package/dist/marketing/index.js +1 -1
- package/dist/providers/index.cjs +2306 -149
- package/dist/providers/index.js +2306 -149
- package/dist/runtime/index.cjs +2315 -157
- package/dist/runtime/index.d.cts +2 -2
- package/dist/runtime/index.d.ts +2 -2
- package/dist/runtime/index.js +2315 -157
- package/package.json +4 -4
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);
|
|
@@ -11646,6 +11646,14 @@ function shapeBounds(shape) {
|
|
|
11646
11646
|
w: shape.radius * 2 + 8,
|
|
11647
11647
|
h: shape.radius * 2 + 8
|
|
11648
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
|
+
};
|
|
11649
11657
|
case "rect":
|
|
11650
11658
|
if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) return null;
|
|
11651
11659
|
return { x: shape.x - 4, y: shape.y - 4, w: shape.width + 8, h: shape.height + 8 };
|
|
@@ -11679,13 +11687,14 @@ function drawArrowHead(ctx, x1, y1, x2, y2, size) {
|
|
|
11679
11687
|
ctx.closePath();
|
|
11680
11688
|
ctx.fill();
|
|
11681
11689
|
}
|
|
11682
|
-
function drawShape(ctx, shape, width, height) {
|
|
11690
|
+
function drawShape(ctx, shape, width, height, allShapes) {
|
|
11683
11691
|
ctx.save();
|
|
11684
11692
|
const opacity = shape.opacity ?? 1;
|
|
11685
11693
|
ctx.globalAlpha = opacity;
|
|
11686
11694
|
const stroke = resolveColor2(shape.color, ctx, "#333333");
|
|
11687
11695
|
const fill = shape.fill ? resolveColor2(shape.fill, ctx, "#cccccc") : void 0;
|
|
11688
11696
|
ctx.lineWidth = shape.lineWidth ?? 2;
|
|
11697
|
+
if (shape.dash) ctx.setLineDash([...DASH_PATTERNS[shape.dash]]);
|
|
11689
11698
|
switch (shape.type) {
|
|
11690
11699
|
case "grid": {
|
|
11691
11700
|
const step = shape.step ?? 40;
|
|
@@ -11751,6 +11760,20 @@ function drawShape(ctx, shape, width, height) {
|
|
|
11751
11760
|
ctx.stroke();
|
|
11752
11761
|
break;
|
|
11753
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
|
+
}
|
|
11754
11777
|
case "rect": {
|
|
11755
11778
|
if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) break;
|
|
11756
11779
|
if (fill) {
|
|
@@ -11797,21 +11820,153 @@ function drawShape(ctx, shape, width, height) {
|
|
|
11797
11820
|
ctx.fillText(shape.text, shape.x, shape.y);
|
|
11798
11821
|
break;
|
|
11799
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
|
+
}
|
|
11800
11856
|
}
|
|
11801
11857
|
ctx.restore();
|
|
11802
11858
|
}
|
|
11803
|
-
|
|
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;
|
|
11804
11955
|
var init_LearningCanvas = __esm({
|
|
11805
11956
|
"components/learning/atoms/LearningCanvas.tsx"() {
|
|
11806
11957
|
"use client";
|
|
11807
11958
|
init_cn();
|
|
11808
11959
|
init_useEventBus();
|
|
11960
|
+
DASH_PATTERNS = { dashed: [6, 4], dotted: [2, 3] };
|
|
11961
|
+
TRACE_SERIES_COLORS = ["#2563eb", "#dc2626", "#16a34a", "#f59e0b"];
|
|
11809
11962
|
LearningCanvas = ({
|
|
11810
11963
|
className,
|
|
11811
11964
|
width = 600,
|
|
11812
11965
|
height = 400,
|
|
11813
11966
|
backgroundColor,
|
|
11814
11967
|
shapes = [],
|
|
11968
|
+
readouts,
|
|
11969
|
+
traces,
|
|
11815
11970
|
interactive = false,
|
|
11816
11971
|
animate = false,
|
|
11817
11972
|
onShapeClick,
|
|
@@ -11837,6 +11992,12 @@ var init_LearningCanvas = __esm({
|
|
|
11837
11992
|
}
|
|
11838
11993
|
return -1;
|
|
11839
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]);
|
|
11840
12001
|
const draw = React87.useCallback(() => {
|
|
11841
12002
|
const canvas = canvasRef.current;
|
|
11842
12003
|
if (!canvas) return;
|
|
@@ -11853,13 +12014,13 @@ var init_LearningCanvas = __esm({
|
|
|
11853
12014
|
ctx.fillStyle = backgroundColor;
|
|
11854
12015
|
ctx.fillRect(0, 0, width, height);
|
|
11855
12016
|
}
|
|
11856
|
-
for (const shape of
|
|
11857
|
-
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);
|
|
11858
12019
|
}
|
|
11859
|
-
for (const shape of
|
|
11860
|
-
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);
|
|
11861
12022
|
}
|
|
11862
|
-
}, [width, height, backgroundColor,
|
|
12023
|
+
}, [width, height, backgroundColor, derivedShapes]);
|
|
11863
12024
|
React87.useEffect(() => {
|
|
11864
12025
|
draw();
|
|
11865
12026
|
}, [draw]);
|
|
@@ -13792,7 +13953,363 @@ var init_ComponentPatterns = __esm({
|
|
|
13792
13953
|
AlertPattern.displayName = "AlertPattern";
|
|
13793
13954
|
}
|
|
13794
13955
|
});
|
|
13795
|
-
|
|
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;
|
|
13796
14313
|
var init_AlgorithmCanvas = __esm({
|
|
13797
14314
|
"components/learning/molecules/AlgorithmCanvas.tsx"() {
|
|
13798
14315
|
"use client";
|
|
@@ -13804,6 +14321,43 @@ var init_AlgorithmCanvas = __esm({
|
|
|
13804
14321
|
DEFAULT_POINTER_COLOR = "#dc2626";
|
|
13805
14322
|
POINTER_BAND = 34;
|
|
13806
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;
|
|
13807
14361
|
AlgorithmCanvas = ({
|
|
13808
14362
|
className,
|
|
13809
14363
|
width = 600,
|
|
@@ -13813,6 +14367,14 @@ var init_AlgorithmCanvas = __esm({
|
|
|
13813
14367
|
bars = [],
|
|
13814
14368
|
cells = [],
|
|
13815
14369
|
pointers = [],
|
|
14370
|
+
ranges = [],
|
|
14371
|
+
slots = [],
|
|
14372
|
+
slotOrientation = "horizontal",
|
|
14373
|
+
frames = [],
|
|
14374
|
+
buckets = [],
|
|
14375
|
+
auxBars = [],
|
|
14376
|
+
rowLabels = [],
|
|
14377
|
+
colLabels = [],
|
|
13816
14378
|
shapes = [],
|
|
13817
14379
|
interactive = false,
|
|
13818
14380
|
animate = false,
|
|
@@ -13822,12 +14384,35 @@ var init_AlgorithmCanvas = __esm({
|
|
|
13822
14384
|
}) => {
|
|
13823
14385
|
const derivedShapes = React87.useMemo(() => {
|
|
13824
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
|
+
});
|
|
13825
14404
|
if (bars.length > 0) {
|
|
14405
|
+
const panelYBars = panelY.bars;
|
|
13826
14406
|
const slot = width / bars.length;
|
|
13827
14407
|
const barW = slot * 0.8;
|
|
13828
14408
|
const gap = slot * 0.1;
|
|
13829
|
-
const
|
|
13830
|
-
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);
|
|
13831
14416
|
const maxV = Math.max(1, ...bars.map((b) => Number.isFinite(b.value) ? b.value : 0));
|
|
13832
14417
|
bars.forEach((bar, i) => {
|
|
13833
14418
|
const v = Number.isFinite(bar.value) ? bar.value : 0;
|
|
@@ -13857,6 +14442,89 @@ var init_AlgorithmCanvas = __esm({
|
|
|
13857
14442
|
});
|
|
13858
14443
|
}
|
|
13859
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
|
+
}
|
|
13860
14528
|
pointers.forEach((p) => {
|
|
13861
14529
|
if (p.index < 0 || p.index >= bars.length) return;
|
|
13862
14530
|
const cx = p.index * slot + slot / 2;
|
|
@@ -13864,7 +14532,7 @@ var init_AlgorithmCanvas = __esm({
|
|
|
13864
14532
|
out.push({
|
|
13865
14533
|
type: "arrow",
|
|
13866
14534
|
x1: cx,
|
|
13867
|
-
y1:
|
|
14535
|
+
y1: panelYBars + primaryH - 18,
|
|
13868
14536
|
x2: cx,
|
|
13869
14537
|
y2: baseline + 4,
|
|
13870
14538
|
color,
|
|
@@ -13874,7 +14542,7 @@ var init_AlgorithmCanvas = __esm({
|
|
|
13874
14542
|
out.push({
|
|
13875
14543
|
type: "text",
|
|
13876
14544
|
x: cx,
|
|
13877
|
-
y:
|
|
14545
|
+
y: panelYBars + primaryH - 8,
|
|
13878
14546
|
text: p.label,
|
|
13879
14547
|
color,
|
|
13880
14548
|
fontSize: 11,
|
|
@@ -13883,14 +14551,111 @@ var init_AlgorithmCanvas = __esm({
|
|
|
13883
14551
|
}
|
|
13884
14552
|
});
|
|
13885
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
|
+
}
|
|
13886
14646
|
if (cells.length > 0) {
|
|
14647
|
+
const panelYCells = panelY.cells;
|
|
13887
14648
|
const maxCol = Math.max(0, ...cells.map((c) => c.col)) + 1;
|
|
13888
14649
|
const maxRow = Math.max(0, ...cells.map((c) => c.row)) + 1;
|
|
13889
|
-
const
|
|
13890
|
-
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;
|
|
13891
14656
|
cells.forEach((c, i) => {
|
|
13892
|
-
const x = c.col * cw;
|
|
13893
|
-
const y = c.row * ch;
|
|
14657
|
+
const x = gridX0 + c.col * cw;
|
|
14658
|
+
const y = gridY0 + c.row * ch;
|
|
13894
14659
|
const color = c.color ?? DEFAULT_CELL_COLOR;
|
|
13895
14660
|
out.push({
|
|
13896
14661
|
type: "rect",
|
|
@@ -13914,11 +14679,207 @@ var init_AlgorithmCanvas = __esm({
|
|
|
13914
14679
|
align: "center"
|
|
13915
14680
|
});
|
|
13916
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
|
+
}
|
|
13917
14878
|
});
|
|
13918
14879
|
}
|
|
13919
14880
|
out.push(...shapes);
|
|
13920
14881
|
return out;
|
|
13921
|
-
}, [bars, cells, pointers, shapes, width, height]);
|
|
14882
|
+
}, [bars, cells, pointers, ranges, slots, slotOrientation, frames, buckets, auxBars, rowLabels, colLabels, shapes, width, height]);
|
|
13922
14883
|
return /* @__PURE__ */ jsxRuntime.jsx(Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
|
|
13923
14884
|
title ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", children: title }) : null,
|
|
13924
14885
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -14932,6 +15893,12 @@ function LearningScene3D({
|
|
|
14932
15893
|
const unitId = event.payload?.unitId;
|
|
14933
15894
|
if (typeof unitId === "string") onItemClickRef.current?.(unitId);
|
|
14934
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
|
+
}
|
|
14935
15902
|
const props3d = {
|
|
14936
15903
|
drawables,
|
|
14937
15904
|
isLoading,
|
|
@@ -14997,7 +15964,7 @@ function cylinderBetween(from, to, radius, color) {
|
|
|
14997
15964
|
material: { color }
|
|
14998
15965
|
};
|
|
14999
15966
|
}
|
|
15000
|
-
function arrowBetween(from, to, color, shaftRadius = 0.08) {
|
|
15967
|
+
function arrowBetween(from, to, color, shaftRadius = 0.08, id) {
|
|
15001
15968
|
const len = segmentLength(from, to);
|
|
15002
15969
|
if (len < 1e-6) return null;
|
|
15003
15970
|
const tipLen = Math.min(shaftRadius * 8, len * 0.35);
|
|
@@ -15025,6 +15992,7 @@ function arrowBetween(from, to, color, shaftRadius = 0.08) {
|
|
|
15025
15992
|
};
|
|
15026
15993
|
return {
|
|
15027
15994
|
type: "draw-group",
|
|
15995
|
+
...id !== void 0 ? { id } : {},
|
|
15028
15996
|
position: { x: from[0], y: from[1], z: from[2] },
|
|
15029
15997
|
items: tipLenActual < 1e-6 ? shaft ? [shaft] : [] : shaft ? [shaft, tip] : [tip]
|
|
15030
15998
|
};
|
|
@@ -15051,20 +16019,228 @@ function get3DClickPayload(onShapeClick, idToIndex) {
|
|
|
15051
16019
|
if (!onShapeClick) return void 0;
|
|
15052
16020
|
return (id) => onShapeClick({ id, index: idToIndex.get(id) ?? -1 });
|
|
15053
16021
|
}
|
|
15054
|
-
|
|
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;
|
|
15055
16229
|
var init_learningScene3D = __esm({
|
|
15056
16230
|
"components/learning/molecules/learningScene3D.tsx"() {
|
|
15057
16231
|
"use client";
|
|
15058
16232
|
init_atoms();
|
|
15059
16233
|
init_Stack();
|
|
15060
16234
|
init_useEventBus();
|
|
16235
|
+
sceneLog = logger.createLogger("almadar:ui:learning-scene-3d");
|
|
16236
|
+
KNOWN_BLOOM_KEYS = /* @__PURE__ */ new Set(["intensity", "threshold", "smoothing"]);
|
|
15061
16237
|
Canvas3DHost2 = React87.lazy(
|
|
15062
16238
|
() => import('@almadar/ui/components/molecules/game/three').then((m) => ({ default: m.Canvas3DHost }))
|
|
15063
16239
|
);
|
|
15064
16240
|
LearningScene3D.displayName = "LearningScene3D";
|
|
15065
16241
|
}
|
|
15066
16242
|
});
|
|
15067
|
-
var biologyLog, BiologyCanvas;
|
|
16243
|
+
var biologyLog, BIO_BAND_COLORS, BIO_STAGE_FILL, BIO_STAGE_TEXT, BiologyCanvas;
|
|
15068
16244
|
var init_BiologyCanvas = __esm({
|
|
15069
16245
|
"components/learning/molecules/BiologyCanvas.tsx"() {
|
|
15070
16246
|
"use client";
|
|
@@ -15073,6 +16249,17 @@ var init_BiologyCanvas = __esm({
|
|
|
15073
16249
|
init_LearningCanvas();
|
|
15074
16250
|
init_learningScene3D();
|
|
15075
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
|
+
};
|
|
15076
16263
|
BiologyCanvas = ({
|
|
15077
16264
|
className,
|
|
15078
16265
|
width = 600,
|
|
@@ -15085,7 +16272,15 @@ var init_BiologyCanvas = __esm({
|
|
|
15085
16272
|
post,
|
|
15086
16273
|
nodes = [],
|
|
15087
16274
|
edges = [],
|
|
16275
|
+
compartments = [],
|
|
16276
|
+
bands = [],
|
|
16277
|
+
stages = [],
|
|
16278
|
+
stageStyle = "timeline",
|
|
16279
|
+
helix,
|
|
16280
|
+
helix3d,
|
|
15088
16281
|
shapes = [],
|
|
16282
|
+
readouts,
|
|
16283
|
+
traces,
|
|
15089
16284
|
showGrid,
|
|
15090
16285
|
shadows,
|
|
15091
16286
|
interactive,
|
|
@@ -15100,19 +16295,148 @@ var init_BiologyCanvas = __esm({
|
|
|
15100
16295
|
for (const n of nodes) {
|
|
15101
16296
|
if (n.id) nodeById.set(n.id, n);
|
|
15102
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
|
+
}
|
|
15103
16407
|
for (const e of edges) {
|
|
15104
16408
|
const a = nodeById.get(e.from);
|
|
15105
16409
|
const b = nodeById.get(e.to);
|
|
15106
16410
|
if (!a || !b) continue;
|
|
15107
|
-
|
|
15108
|
-
|
|
15109
|
-
|
|
15110
|
-
|
|
15111
|
-
|
|
15112
|
-
|
|
15113
|
-
|
|
15114
|
-
|
|
15115
|
-
|
|
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
|
+
}
|
|
15116
16440
|
if (e.label) {
|
|
15117
16441
|
out.push({
|
|
15118
16442
|
type: "text",
|
|
@@ -15125,6 +16449,8 @@ var init_BiologyCanvas = __esm({
|
|
|
15125
16449
|
}
|
|
15126
16450
|
}
|
|
15127
16451
|
for (const n of nodes) {
|
|
16452
|
+
const state = n.state ?? "default";
|
|
16453
|
+
const muted = state === "muted";
|
|
15128
16454
|
out.push({
|
|
15129
16455
|
type: "circle",
|
|
15130
16456
|
x: n.x,
|
|
@@ -15132,28 +16458,127 @@ var init_BiologyCanvas = __esm({
|
|
|
15132
16458
|
radius: n.radius ?? 16,
|
|
15133
16459
|
color: n.color ?? "#16a34a",
|
|
15134
16460
|
fill: `${n.color ?? "#16a34a"}33`,
|
|
15135
|
-
id: n.id
|
|
16461
|
+
id: n.id,
|
|
16462
|
+
...muted ? { opacity: 0.35 } : {}
|
|
15136
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
|
+
}
|
|
15137
16474
|
if (n.label) {
|
|
15138
16475
|
out.push({
|
|
15139
16476
|
type: "text",
|
|
15140
16477
|
x: n.x,
|
|
15141
16478
|
y: n.y + (n.radius ?? 16) + 14,
|
|
15142
16479
|
text: n.label,
|
|
16480
|
+
...muted ? { opacity: 0.35 } : {},
|
|
15143
16481
|
color: "#111827",
|
|
15144
16482
|
fontSize: 12,
|
|
15145
16483
|
align: "center"
|
|
15146
16484
|
});
|
|
15147
16485
|
}
|
|
15148
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
|
+
}
|
|
15149
16563
|
out.push(...shapes);
|
|
15150
16564
|
return out;
|
|
15151
|
-
}, [nodes, edges, shapes]);
|
|
16565
|
+
}, [nodes, edges, compartments, bands, stages, stageStyle, helix, shapes, width, height]);
|
|
15152
16566
|
const drawables3D = React87.useMemo(() => {
|
|
15153
16567
|
if (mode !== "3d") return [];
|
|
15154
16568
|
if (shapes.length > 0) {
|
|
15155
16569
|
biologyLog.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
|
|
15156
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
|
+
}
|
|
15157
16582
|
const out = [];
|
|
15158
16583
|
const labelColor = labelColorForBackground(backgroundColor);
|
|
15159
16584
|
const nodeById = /* @__PURE__ */ new Map();
|
|
@@ -15187,15 +16612,21 @@ var init_BiologyCanvas = __esm({
|
|
|
15187
16612
|
out.push(billboardLabel(n.label, n.x, n.y, nz + radius, { color: labelColor }));
|
|
15188
16613
|
}
|
|
15189
16614
|
}
|
|
16615
|
+
if (helix3d) {
|
|
16616
|
+
out.push(...helixDrawables(helix3d, { labelColor }));
|
|
16617
|
+
}
|
|
15190
16618
|
return out;
|
|
15191
|
-
}, [mode, nodes, edges, shapes, backgroundColor]);
|
|
16619
|
+
}, [mode, nodes, edges, shapes, compartments, bands, stages, helix, helix3d, animate, backgroundColor]);
|
|
15192
16620
|
const nodeIndexById = React87.useMemo(() => {
|
|
15193
16621
|
const m = /* @__PURE__ */ new Map();
|
|
16622
|
+
(helix3d?.rungs ?? []).forEach((rung, i) => {
|
|
16623
|
+
if (rung.id) m.set(rung.id, i);
|
|
16624
|
+
});
|
|
15194
16625
|
nodes.forEach((n, i) => {
|
|
15195
16626
|
if (n.id) m.set(n.id, i);
|
|
15196
16627
|
});
|
|
15197
16628
|
return m;
|
|
15198
|
-
}, [nodes]);
|
|
16629
|
+
}, [nodes, helix3d]);
|
|
15199
16630
|
if (mode === "3d") {
|
|
15200
16631
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
15201
16632
|
LearningScene3D,
|
|
@@ -15227,6 +16658,8 @@ var init_BiologyCanvas = __esm({
|
|
|
15227
16658
|
height,
|
|
15228
16659
|
backgroundColor,
|
|
15229
16660
|
shapes: derivedShapes,
|
|
16661
|
+
readouts,
|
|
16662
|
+
traces,
|
|
15230
16663
|
interactive: interactive ?? false,
|
|
15231
16664
|
animate,
|
|
15232
16665
|
onShapeClick,
|
|
@@ -22076,7 +23509,7 @@ function bondPerpendicular(a, b) {
|
|
|
22076
23509
|
if (len < 1e-6) return [1, 0, 0];
|
|
22077
23510
|
return [px / len, py / len, 0];
|
|
22078
23511
|
}
|
|
22079
|
-
var chemistryLog, ChemistryCanvas;
|
|
23512
|
+
var chemistryLog, CHEM_BOND_STATE_COLOR, LONE_PAIR_ANGLES, ChemistryCanvas;
|
|
22080
23513
|
var init_ChemistryCanvas = __esm({
|
|
22081
23514
|
"components/learning/molecules/ChemistryCanvas.tsx"() {
|
|
22082
23515
|
"use client";
|
|
@@ -22085,6 +23518,13 @@ var init_ChemistryCanvas = __esm({
|
|
|
22085
23518
|
init_LearningCanvas();
|
|
22086
23519
|
init_learningScene3D();
|
|
22087
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];
|
|
22088
23528
|
ChemistryCanvas = ({
|
|
22089
23529
|
className,
|
|
22090
23530
|
width = 600,
|
|
@@ -22098,7 +23538,14 @@ var init_ChemistryCanvas = __esm({
|
|
|
22098
23538
|
atoms = [],
|
|
22099
23539
|
bonds = [],
|
|
22100
23540
|
arrows = [],
|
|
23541
|
+
bondStyle = "thick",
|
|
23542
|
+
containers = [],
|
|
23543
|
+
equation,
|
|
23544
|
+
equationColor,
|
|
23545
|
+
lattice3d,
|
|
22101
23546
|
shapes = [],
|
|
23547
|
+
readouts,
|
|
23548
|
+
traces,
|
|
22102
23549
|
showGrid,
|
|
22103
23550
|
shadows,
|
|
22104
23551
|
interactive,
|
|
@@ -22113,21 +23560,118 @@ var init_ChemistryCanvas = __esm({
|
|
|
22113
23560
|
for (const a of atoms) {
|
|
22114
23561
|
if (a.id) atomById.set(a.id, a);
|
|
22115
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
|
+
}
|
|
22116
23634
|
for (const b of bonds) {
|
|
22117
23635
|
const a = atomById.get(b.from);
|
|
22118
23636
|
const c = atomById.get(b.to);
|
|
22119
23637
|
if (!a || !c) continue;
|
|
22120
|
-
const
|
|
22121
|
-
const
|
|
22122
|
-
|
|
22123
|
-
|
|
22124
|
-
|
|
22125
|
-
|
|
22126
|
-
|
|
22127
|
-
|
|
22128
|
-
|
|
22129
|
-
|
|
22130
|
-
|
|
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
|
+
}
|
|
22131
23675
|
}
|
|
22132
23676
|
for (const a of arrows) {
|
|
22133
23677
|
const angle = (a.angle ?? 0) * (Math.PI / 180);
|
|
@@ -22176,15 +23720,62 @@ var init_ChemistryCanvas = __esm({
|
|
|
22176
23720
|
align: "center"
|
|
22177
23721
|
});
|
|
22178
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
|
+
});
|
|
22179
23764
|
}
|
|
22180
23765
|
out.push(...shapes);
|
|
22181
23766
|
return out;
|
|
22182
|
-
}, [atoms, bonds, arrows, shapes]);
|
|
23767
|
+
}, [atoms, bonds, arrows, bondStyle, containers, equation, equationColor, shapes, width]);
|
|
22183
23768
|
const drawables3D = React87.useMemo(() => {
|
|
22184
23769
|
if (mode !== "3d") return [];
|
|
22185
23770
|
if (shapes.length > 0) {
|
|
22186
23771
|
chemistryLog.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
|
|
22187
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
|
+
}
|
|
22188
23779
|
const out = [];
|
|
22189
23780
|
const labelColor = labelColorForBackground(backgroundColor);
|
|
22190
23781
|
const atomById = /* @__PURE__ */ new Map();
|
|
@@ -22231,8 +23822,11 @@ var init_ChemistryCanvas = __esm({
|
|
|
22231
23822
|
out.push(billboardLabel(a.element, a.x, a.y, az + radius, { color: labelColor }));
|
|
22232
23823
|
}
|
|
22233
23824
|
}
|
|
23825
|
+
if (lattice3d) {
|
|
23826
|
+
out.push(...latticeDrawables(lattice3d, { labelColor }));
|
|
23827
|
+
}
|
|
22234
23828
|
return out;
|
|
22235
|
-
}, [mode, atoms, bonds, arrows, shapes, backgroundColor]);
|
|
23829
|
+
}, [mode, atoms, bonds, arrows, shapes, containers, lattice3d, animate, backgroundColor]);
|
|
22236
23830
|
const atomIndexById = React87.useMemo(() => {
|
|
22237
23831
|
const m = /* @__PURE__ */ new Map();
|
|
22238
23832
|
atoms.forEach((a, i) => {
|
|
@@ -22271,6 +23865,8 @@ var init_ChemistryCanvas = __esm({
|
|
|
22271
23865
|
height,
|
|
22272
23866
|
backgroundColor,
|
|
22273
23867
|
shapes: derivedShapes,
|
|
23868
|
+
readouts,
|
|
23869
|
+
traces,
|
|
22274
23870
|
interactive: interactive ?? false,
|
|
22275
23871
|
animate,
|
|
22276
23872
|
onShapeClick,
|
|
@@ -28460,6 +30056,10 @@ var init_ProgressDots = __esm({
|
|
|
28460
30056
|
ProgressDots.displayName = "ProgressDots";
|
|
28461
30057
|
}
|
|
28462
30058
|
});
|
|
30059
|
+
function formatTick(v) {
|
|
30060
|
+
if (Number.isInteger(v)) return String(v);
|
|
30061
|
+
return v.toFixed(2).replace(/0+$/, "").replace(/\.$/, "");
|
|
30062
|
+
}
|
|
28463
30063
|
var MathCanvas;
|
|
28464
30064
|
var init_MathCanvas = __esm({
|
|
28465
30065
|
"components/learning/molecules/MathCanvas.tsx"() {
|
|
@@ -28479,10 +30079,19 @@ var init_MathCanvas = __esm({
|
|
|
28479
30079
|
showAxes = true,
|
|
28480
30080
|
showGrid = true,
|
|
28481
30081
|
gridStep = 1,
|
|
30082
|
+
showTickLabels = false,
|
|
30083
|
+
showCurveLabels = false,
|
|
28482
30084
|
curves = [],
|
|
28483
30085
|
points = [],
|
|
28484
30086
|
vectors = [],
|
|
30087
|
+
regions = [],
|
|
30088
|
+
bars = [],
|
|
30089
|
+
guides = [],
|
|
30090
|
+
angles = [],
|
|
30091
|
+
hops = [],
|
|
28485
30092
|
shapes = [],
|
|
30093
|
+
readouts,
|
|
30094
|
+
traces,
|
|
28486
30095
|
interactive = false,
|
|
28487
30096
|
animate = false,
|
|
28488
30097
|
onShapeClick,
|
|
@@ -28496,6 +30105,8 @@ var init_MathCanvas = __esm({
|
|
|
28496
30105
|
const plotH = height - margin * 2;
|
|
28497
30106
|
const mapX = (x) => margin + (x - xMin) / (xMax - xMin) * plotW;
|
|
28498
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)));
|
|
28499
30110
|
if (showGrid) {
|
|
28500
30111
|
for (let x = Math.ceil(xMin / gridStep) * gridStep; x <= xMax; x += gridStep) {
|
|
28501
30112
|
const px = mapX(x);
|
|
@@ -28506,14 +30117,99 @@ var init_MathCanvas = __esm({
|
|
|
28506
30117
|
out.push({ type: "line", x1: margin, y1: py, x2: width - margin, y2: py, color: "#e5e7eb", lineWidth: 1 });
|
|
28507
30118
|
}
|
|
28508
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
|
+
}
|
|
28509
30187
|
if (showAxes) {
|
|
28510
|
-
const xAxisY = Math.max(margin, Math.min(height - margin, mapY(0)));
|
|
28511
|
-
const yAxisX = Math.max(margin, Math.min(width - margin, mapX(0)));
|
|
28512
30188
|
out.push({ type: "line", x1: margin, y1: xAxisY, x2: width - margin, y2: xAxisY, color: "#374151", lineWidth: 2 });
|
|
28513
30189
|
out.push({ type: "line", x1: yAxisX, y1: margin, x2: yAxisX, y2: height - margin, color: "#374151", lineWidth: 2 });
|
|
28514
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
|
+
}
|
|
28515
30210
|
for (const curve of curves) {
|
|
28516
30211
|
if (!curve.samples || curve.samples.length < 2) continue;
|
|
30212
|
+
let lastInRange;
|
|
28517
30213
|
for (let i = 1; i < curve.samples.length; i++) {
|
|
28518
30214
|
const a = curve.samples[i - 1];
|
|
28519
30215
|
const b = curve.samples[i];
|
|
@@ -28525,19 +30221,97 @@ var init_MathCanvas = __esm({
|
|
|
28525
30221
|
x2: mapX(b.x),
|
|
28526
30222
|
y2: mapY(b.y),
|
|
28527
30223
|
color: curve.color ?? "#2563eb",
|
|
28528
|
-
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"
|
|
28529
30302
|
});
|
|
28530
30303
|
}
|
|
28531
30304
|
}
|
|
28532
30305
|
for (const p of points) {
|
|
28533
30306
|
if (p.x < xMin || p.x > xMax || p.y < yMin || p.y > yMax) continue;
|
|
30307
|
+
const isOpen = p.style === "open";
|
|
28534
30308
|
out.push({
|
|
28535
30309
|
type: "circle",
|
|
28536
30310
|
x: mapX(p.x),
|
|
28537
30311
|
y: mapY(p.y),
|
|
28538
30312
|
radius: p.radius ?? 4,
|
|
28539
30313
|
color: p.color ?? "#dc2626",
|
|
28540
|
-
fill: p.color ?? "#dc2626"
|
|
30314
|
+
fill: isOpen ? "#ffffff" : p.color ?? "#dc2626"
|
|
28541
30315
|
});
|
|
28542
30316
|
if (p.label) {
|
|
28543
30317
|
out.push({ type: "text", x: mapX(p.x) + 8, y: mapY(p.y) - 8, text: p.label, color: "#111827", fontSize: 12 });
|
|
@@ -28556,7 +30330,28 @@ var init_MathCanvas = __esm({
|
|
|
28556
30330
|
}
|
|
28557
30331
|
out.push(...shapes);
|
|
28558
30332
|
return out;
|
|
28559
|
-
}, [
|
|
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
|
+
]);
|
|
28560
30355
|
return /* @__PURE__ */ jsxRuntime.jsx(Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
|
|
28561
30356
|
title ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", children: title }) : null,
|
|
28562
30357
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -28565,6 +30360,8 @@ var init_MathCanvas = __esm({
|
|
|
28565
30360
|
width,
|
|
28566
30361
|
height,
|
|
28567
30362
|
shapes: derivedShapes,
|
|
30363
|
+
readouts,
|
|
30364
|
+
traces,
|
|
28568
30365
|
interactive,
|
|
28569
30366
|
animate,
|
|
28570
30367
|
onShapeClick,
|
|
@@ -28576,7 +30373,315 @@ var init_MathCanvas = __esm({
|
|
|
28576
30373
|
};
|
|
28577
30374
|
}
|
|
28578
30375
|
});
|
|
28579
|
-
|
|
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;
|
|
28580
30685
|
var init_PhysicsCanvas = __esm({
|
|
28581
30686
|
"components/learning/molecules/PhysicsCanvas.tsx"() {
|
|
28582
30687
|
"use client";
|
|
@@ -28585,6 +30690,7 @@ var init_PhysicsCanvas = __esm({
|
|
|
28585
30690
|
init_LearningCanvas();
|
|
28586
30691
|
init_learningScene3D();
|
|
28587
30692
|
physicsLog2 = logger.createLogger("almadar:ui:physics-canvas");
|
|
30693
|
+
PHYSICS_LABEL_COLOR = "#374151";
|
|
28588
30694
|
PhysicsCanvas = ({
|
|
28589
30695
|
className,
|
|
28590
30696
|
width = 600,
|
|
@@ -28601,7 +30707,18 @@ var init_PhysicsCanvas = __esm({
|
|
|
28601
30707
|
showForces = false,
|
|
28602
30708
|
velocityScale = 20,
|
|
28603
30709
|
forceScale = 20,
|
|
30710
|
+
sceneObjects = [],
|
|
30711
|
+
trails = [],
|
|
30712
|
+
vectors = [],
|
|
30713
|
+
surface3d,
|
|
30714
|
+
vectors3d = [],
|
|
30715
|
+
vectorScale = 1,
|
|
30716
|
+
angles = [],
|
|
30717
|
+
field,
|
|
30718
|
+
meters = [],
|
|
28604
30719
|
shapes = [],
|
|
30720
|
+
readouts,
|
|
30721
|
+
traces,
|
|
28605
30722
|
showGrid,
|
|
28606
30723
|
shadows,
|
|
28607
30724
|
interactive,
|
|
@@ -28616,19 +30733,14 @@ var init_PhysicsCanvas = __esm({
|
|
|
28616
30733
|
for (const b of bodies) {
|
|
28617
30734
|
if (b.id) bodyById.set(b.id, b);
|
|
28618
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));
|
|
28619
30739
|
for (const c of constraints) {
|
|
28620
30740
|
const a = bodyById.get(c.from);
|
|
28621
30741
|
const b = bodyById.get(c.to);
|
|
28622
30742
|
if (!a || !b) continue;
|
|
28623
|
-
out.push(
|
|
28624
|
-
type: "line",
|
|
28625
|
-
x1: a.x,
|
|
28626
|
-
y1: a.y,
|
|
28627
|
-
x2: b.x,
|
|
28628
|
-
y2: b.y,
|
|
28629
|
-
color: c.color ?? "#9ca3af",
|
|
28630
|
-
lineWidth: 2
|
|
28631
|
-
});
|
|
30743
|
+
out.push(...constraintShapes(c, a, b));
|
|
28632
30744
|
}
|
|
28633
30745
|
for (const b of bodies) {
|
|
28634
30746
|
out.push({
|
|
@@ -28673,14 +30785,51 @@ var init_PhysicsCanvas = __esm({
|
|
|
28673
30785
|
});
|
|
28674
30786
|
}
|
|
28675
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));
|
|
28676
30791
|
out.push(...shapes);
|
|
28677
30792
|
return out;
|
|
28678
|
-
}, [
|
|
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
|
+
]);
|
|
28679
30810
|
const drawables3D = React87.useMemo(() => {
|
|
28680
30811
|
if (mode !== "3d") return [];
|
|
28681
30812
|
if (shapes.length > 0) {
|
|
28682
30813
|
physicsLog2.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
|
|
28683
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
|
+
}
|
|
28684
30833
|
const out = [];
|
|
28685
30834
|
const labelColor = labelColorForBackground(backgroundColor);
|
|
28686
30835
|
const bodyById = /* @__PURE__ */ new Map();
|
|
@@ -28728,15 +30877,67 @@ var init_PhysicsCanvas = __esm({
|
|
|
28728
30877
|
if (arrow) out.push(arrow);
|
|
28729
30878
|
}
|
|
28730
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
|
+
}
|
|
28731
30909
|
return out;
|
|
28732
|
-
}, [
|
|
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
|
+
]);
|
|
28733
30931
|
const bodyIndexById = React87.useMemo(() => {
|
|
28734
30932
|
const m = /* @__PURE__ */ new Map();
|
|
30933
|
+
vectors3d.forEach((v, i) => {
|
|
30934
|
+
if (v.id) m.set(v.id, i);
|
|
30935
|
+
});
|
|
28735
30936
|
bodies.forEach((b, i) => {
|
|
28736
30937
|
if (b.id) m.set(b.id, i);
|
|
28737
30938
|
});
|
|
28738
30939
|
return m;
|
|
28739
|
-
}, [bodies]);
|
|
30940
|
+
}, [bodies, vectors3d]);
|
|
28740
30941
|
if (mode === "3d") {
|
|
28741
30942
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
28742
30943
|
LearningScene3D,
|
|
@@ -28768,6 +30969,8 @@ var init_PhysicsCanvas = __esm({
|
|
|
28768
30969
|
height,
|
|
28769
30970
|
backgroundColor,
|
|
28770
30971
|
shapes: derivedShapes,
|
|
30972
|
+
readouts,
|
|
30973
|
+
traces,
|
|
28771
30974
|
interactive: interactive ?? false,
|
|
28772
30975
|
animate,
|
|
28773
30976
|
onShapeClick,
|
|
@@ -28918,7 +31121,7 @@ function layoutFlow(nodeIds, adjacency, roots, width, height, margin) {
|
|
|
28918
31121
|
}
|
|
28919
31122
|
return nodeIds.map((id) => positions.get(id));
|
|
28920
31123
|
}
|
|
28921
|
-
function
|
|
31124
|
+
function layoutTree2(nodeIds, adjacency, roots, width, height, margin) {
|
|
28922
31125
|
const effectiveRoots = roots.length > 0 ? roots : [nodeIds[0]];
|
|
28923
31126
|
const layers = assignLayers(nodeIds, adjacency, effectiveRoots);
|
|
28924
31127
|
const maxLayer = Math.max(...Array.from(layers.values()));
|
|
@@ -28974,7 +31177,7 @@ function computeStaticLayout(mode, input) {
|
|
|
28974
31177
|
const adjacency = buildAdjacency(nodeIds, edges);
|
|
28975
31178
|
const roots = findRoots(nodeIds, adjacency);
|
|
28976
31179
|
if (mode === "flow") return layoutFlow(nodeIds, adjacency, roots, width, height, margin);
|
|
28977
|
-
if (mode === "tree") return
|
|
31180
|
+
if (mode === "tree") return layoutTree2(nodeIds, adjacency, roots, width, height, margin);
|
|
28978
31181
|
return layoutRadial(nodeIds, adjacency, roots, width, height, margin);
|
|
28979
31182
|
}
|
|
28980
31183
|
var init_graphViewLayouts = __esm({
|
|
@@ -30217,26 +32420,6 @@ var init_Lightbox = __esm({
|
|
|
30217
32420
|
Lightbox.displayName = "Lightbox";
|
|
30218
32421
|
}
|
|
30219
32422
|
});
|
|
30220
|
-
function useMediaQuery(query) {
|
|
30221
|
-
const subscribe = React87.useCallback(
|
|
30222
|
-
(onChange) => {
|
|
30223
|
-
const mql = window.matchMedia(query);
|
|
30224
|
-
mql.addEventListener("change", onChange);
|
|
30225
|
-
return () => mql.removeEventListener("change", onChange);
|
|
30226
|
-
},
|
|
30227
|
-
[query]
|
|
30228
|
-
);
|
|
30229
|
-
return React87.useSyncExternalStore(
|
|
30230
|
-
subscribe,
|
|
30231
|
-
() => window.matchMedia(query).matches,
|
|
30232
|
-
() => false
|
|
30233
|
-
);
|
|
30234
|
-
}
|
|
30235
|
-
var init_useMediaQuery = __esm({
|
|
30236
|
-
"hooks/useMediaQuery.ts"() {
|
|
30237
|
-
"use client";
|
|
30238
|
-
}
|
|
30239
|
-
});
|
|
30240
32423
|
function renderIconInput3(icon, props) {
|
|
30241
32424
|
return typeof icon === "string" ? /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: icon, ...props }) : /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon, ...props });
|
|
30242
32425
|
}
|
|
@@ -30275,8 +32458,8 @@ function TableView({
|
|
|
30275
32458
|
columns,
|
|
30276
32459
|
fields,
|
|
30277
32460
|
itemActions,
|
|
30278
|
-
maxInlineActions,
|
|
30279
|
-
itemClickEvent,
|
|
32461
|
+
maxInlineActions: _maxInlineActions,
|
|
32462
|
+
itemClickEvent = "",
|
|
30280
32463
|
selectable = false,
|
|
30281
32464
|
selectEvent,
|
|
30282
32465
|
selectedIds,
|
|
@@ -30326,7 +32509,6 @@ function TableView({
|
|
|
30326
32509
|
const hasMore = pageSize > 0 && visibleCount < ordered2.length;
|
|
30327
32510
|
const hasRenderProp = typeof children === "function";
|
|
30328
32511
|
const idField = dndItemIdField ?? "id";
|
|
30329
|
-
const isCoarsePointer = useMediaQuery("(pointer: coarse)");
|
|
30330
32512
|
React87__namespace.default.useEffect(() => {
|
|
30331
32513
|
tableViewLog.debug("render", {
|
|
30332
32514
|
rowCount: data.length,
|
|
@@ -30368,21 +32550,14 @@ function TableView({
|
|
|
30368
32550
|
const dir = sortColumn === (col.field ?? col.key) && sortDirection === "asc" ? "desc" : "asc";
|
|
30369
32551
|
eventBus.emit(`UI:${sortEvent}`, { column: col.field ?? col.key, direction: dir });
|
|
30370
32552
|
};
|
|
30371
|
-
const
|
|
30372
|
-
e.stopPropagation();
|
|
30373
|
-
const payload = {
|
|
30374
|
-
id: row.id,
|
|
30375
|
-
row
|
|
30376
|
-
};
|
|
30377
|
-
eventBus.emit(`UI:${action.event}`, payload);
|
|
30378
|
-
};
|
|
32553
|
+
const rowClickEvent = itemClickEvent || actionDefs.find((a) => a.variant !== "danger")?.event;
|
|
30379
32554
|
const handleRowClick = (row) => () => {
|
|
30380
|
-
if (!
|
|
32555
|
+
if (!rowClickEvent) return;
|
|
30381
32556
|
const payload = {
|
|
30382
32557
|
id: row.id,
|
|
30383
32558
|
row
|
|
30384
32559
|
};
|
|
30385
|
-
eventBus.emit(`UI:${
|
|
32560
|
+
eventBus.emit(`UI:${rowClickEvent}`, payload);
|
|
30386
32561
|
};
|
|
30387
32562
|
const colFloors = React87__namespace.default.useMemo(
|
|
30388
32563
|
() => colDefs.map((col) => {
|
|
@@ -30398,10 +32573,7 @@ function TableView({
|
|
|
30398
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;
|
|
30399
32574
|
const lk = LOOKS[look];
|
|
30400
32575
|
const hasActions = actionDefs.length > 0;
|
|
30401
|
-
const
|
|
30402
|
-
const inlineActionCount = hasActions ? effectiveMaxInline != null ? Math.min(actionDefs.length, effectiveMaxInline) : actionDefs.length : 0;
|
|
30403
|
-
const hasOverflowActions = hasActions && effectiveMaxInline != null && actionDefs.length > effectiveMaxInline;
|
|
30404
|
-
const actionsTrack = hasActions ? `${inlineActionCount * 6 + (hasOverflowActions ? 3 : 0)}rem` : null;
|
|
32576
|
+
const actionsTrack = hasActions ? "3rem" : null;
|
|
30405
32577
|
const gridTemplateColumns = [
|
|
30406
32578
|
selectable ? "auto" : null,
|
|
30407
32579
|
...colDefs.map((c, i) => c.width ?? `minmax(${colFloors[i]}ch, 1fr)`),
|
|
@@ -30448,7 +32620,7 @@ function TableView({
|
|
|
30448
32620
|
col.key
|
|
30449
32621
|
);
|
|
30450
32622
|
}),
|
|
30451
|
-
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" })
|
|
30452
32624
|
]
|
|
30453
32625
|
}
|
|
30454
32626
|
);
|
|
@@ -30460,12 +32632,12 @@ function TableView({
|
|
|
30460
32632
|
role: "row",
|
|
30461
32633
|
"data-entity-row": true,
|
|
30462
32634
|
"data-entity-id": id,
|
|
30463
|
-
onClick:
|
|
32635
|
+
onClick: rowClickEvent ? handleRowClick(row) : void 0,
|
|
30464
32636
|
style: !hasRenderProp ? { gridTemplateColumns } : void 0,
|
|
30465
32637
|
className: cn(
|
|
30466
32638
|
"group items-center gap-3 transition-colors duration-fast",
|
|
30467
32639
|
hasRenderProp ? "flex" : "grid",
|
|
30468
|
-
|
|
32640
|
+
rowClickEvent && "cursor-pointer",
|
|
30469
32641
|
lk.rowPad,
|
|
30470
32642
|
lk.divider && "border-b border-[var(--color-border)]",
|
|
30471
32643
|
lk.striped && index % 2 === 1 && "bg-[var(--color-surface-subtle)]",
|
|
@@ -30473,7 +32645,7 @@ function TableView({
|
|
|
30473
32645
|
look === "bordered" && "[&>*]:border-r [&>*]:border-[var(--color-border)] [&>*:last-child]:border-r-0"
|
|
30474
32646
|
),
|
|
30475
32647
|
children: [
|
|
30476
|
-
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(
|
|
30477
32649
|
Checkbox,
|
|
30478
32650
|
{
|
|
30479
32651
|
checked: selected.has(id),
|
|
@@ -30494,53 +32666,37 @@ function TableView({
|
|
|
30494
32666
|
}
|
|
30495
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);
|
|
30496
32668
|
}),
|
|
30497
|
-
hasActions && /* @__PURE__ */ jsxRuntime.
|
|
32669
|
+
hasActions && /* @__PURE__ */ jsxRuntime.jsx(
|
|
30498
32670
|
HStack,
|
|
30499
32671
|
{
|
|
30500
32672
|
gap: "xs",
|
|
30501
|
-
onClick:
|
|
32673
|
+
onClick: rowClickEvent ? (e) => e.stopPropagation() : void 0,
|
|
30502
32674
|
className: cn(
|
|
30503
32675
|
// Pinned: the fixed column tracks routinely overflow the caller's
|
|
30504
|
-
// scroll container, which
|
|
30505
|
-
// 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.
|
|
30506
32679
|
"justify-end flex-shrink-0 sticky right-0 z-[1] transition-colors",
|
|
32680
|
+
"border-l border-[var(--color-border)]",
|
|
30507
32681
|
lk.striped && index % 2 === 1 ? "bg-[var(--color-surface-subtle)]" : "bg-[var(--color-card)] group-hover:bg-[var(--color-surface-subtle)]"
|
|
30508
32682
|
),
|
|
30509
|
-
children:
|
|
30510
|
-
|
|
30511
|
-
|
|
30512
|
-
|
|
30513
|
-
|
|
30514
|
-
|
|
30515
|
-
|
|
30516
|
-
|
|
30517
|
-
|
|
30518
|
-
|
|
30519
|
-
|
|
30520
|
-
|
|
30521
|
-
|
|
30522
|
-
|
|
30523
|
-
}
|
|
30524
|
-
|
|
30525
|
-
|
|
30526
|
-
effectiveMaxInline != null && actionDefs.length > effectiveMaxInline && /* @__PURE__ */ jsxRuntime.jsx(
|
|
30527
|
-
Menu,
|
|
30528
|
-
{
|
|
30529
|
-
position: "bottom-end",
|
|
30530
|
-
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" }) }),
|
|
30531
|
-
items: actionDefs.slice(effectiveMaxInline).map((action) => ({
|
|
30532
|
-
label: action.label,
|
|
30533
|
-
icon: action.icon,
|
|
30534
|
-
event: action.event,
|
|
30535
|
-
variant: action.variant === "danger" ? "danger" : "default",
|
|
30536
|
-
onClick: () => eventBus.emit(`UI:${action.event}`, {
|
|
30537
|
-
id: row.id,
|
|
30538
|
-
row
|
|
30539
|
-
})
|
|
30540
|
-
}))
|
|
30541
|
-
}
|
|
30542
|
-
)
|
|
30543
|
-
]
|
|
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
|
+
)
|
|
30544
32700
|
}
|
|
30545
32701
|
)
|
|
30546
32702
|
]
|
|
@@ -30583,7 +32739,6 @@ var init_TableView = __esm({
|
|
|
30583
32739
|
init_format();
|
|
30584
32740
|
init_getNestedValue();
|
|
30585
32741
|
init_useEventBus();
|
|
30586
|
-
init_useMediaQuery();
|
|
30587
32742
|
init_Box();
|
|
30588
32743
|
init_Stack();
|
|
30589
32744
|
init_Typography();
|
|
@@ -45642,6 +47797,7 @@ var init_component_registry_generated = __esm({
|
|
|
45642
47797
|
init_ActionTile();
|
|
45643
47798
|
init_ActivationBlock();
|
|
45644
47799
|
init_ComponentPatterns();
|
|
47800
|
+
init_AlgoGraphCanvas();
|
|
45645
47801
|
init_AlgorithmCanvas();
|
|
45646
47802
|
init_AnimatedCounter();
|
|
45647
47803
|
init_AnimatedGraphic();
|
|
@@ -45905,6 +48061,7 @@ var init_component_registry_generated = __esm({
|
|
|
45905
48061
|
"ActivationBlock": ActivationBlock,
|
|
45906
48062
|
"Alert": AlertPattern,
|
|
45907
48063
|
"AlertPattern": AlertPattern,
|
|
48064
|
+
"AlgoGraphCanvas": AlgoGraphCanvas,
|
|
45908
48065
|
"AlgorithmCanvas": AlgorithmCanvas,
|
|
45909
48066
|
"AnimatedCounter": AnimatedCounter,
|
|
45910
48067
|
"AnimatedGraphic": AnimatedGraphic,
|