@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/avl/index.cjs
CHANGED
|
@@ -3431,7 +3431,7 @@ function useEventBus() {
|
|
|
3431
3431
|
return {
|
|
3432
3432
|
...baseBus,
|
|
3433
3433
|
emit: (type, payload, source) => {
|
|
3434
|
-
if (typeof type === "string" && type.startsWith("UI:") && !type.slice(3).includes(".")) {
|
|
3434
|
+
if (typeof type === "string" && type.startsWith("UI:") && !type.slice(3).includes(".") && !source?.trait) {
|
|
3435
3435
|
scopeLog.warn("emit:bare-key-no-scope", { type });
|
|
3436
3436
|
}
|
|
3437
3437
|
baseBus.emit(type, payload, source);
|
|
@@ -15121,6 +15121,14 @@ function shapeBounds(shape) {
|
|
|
15121
15121
|
w: shape.radius * 2 + 8,
|
|
15122
15122
|
h: shape.radius * 2 + 8
|
|
15123
15123
|
};
|
|
15124
|
+
case "ellipse":
|
|
15125
|
+
if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) return null;
|
|
15126
|
+
return {
|
|
15127
|
+
x: shape.x - shape.width / 2 - 4,
|
|
15128
|
+
y: shape.y - shape.height / 2 - 4,
|
|
15129
|
+
w: shape.width + 8,
|
|
15130
|
+
h: shape.height + 8
|
|
15131
|
+
};
|
|
15124
15132
|
case "rect":
|
|
15125
15133
|
if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) return null;
|
|
15126
15134
|
return { x: shape.x - 4, y: shape.y - 4, w: shape.width + 8, h: shape.height + 8 };
|
|
@@ -15154,13 +15162,14 @@ function drawArrowHead(ctx, x1, y1, x2, y2, size) {
|
|
|
15154
15162
|
ctx.closePath();
|
|
15155
15163
|
ctx.fill();
|
|
15156
15164
|
}
|
|
15157
|
-
function drawShape(ctx, shape, width, height) {
|
|
15165
|
+
function drawShape(ctx, shape, width, height, allShapes) {
|
|
15158
15166
|
ctx.save();
|
|
15159
15167
|
const opacity = shape.opacity ?? 1;
|
|
15160
15168
|
ctx.globalAlpha = opacity;
|
|
15161
15169
|
const stroke = resolveColor2(shape.color, ctx, "#333333");
|
|
15162
15170
|
const fill = shape.fill ? resolveColor2(shape.fill, ctx, "#cccccc") : void 0;
|
|
15163
15171
|
ctx.lineWidth = shape.lineWidth ?? 2;
|
|
15172
|
+
if (shape.dash) ctx.setLineDash([...DASH_PATTERNS[shape.dash]]);
|
|
15164
15173
|
switch (shape.type) {
|
|
15165
15174
|
case "grid": {
|
|
15166
15175
|
const step = shape.step ?? 40;
|
|
@@ -15226,6 +15235,20 @@ function drawShape(ctx, shape, width, height) {
|
|
|
15226
15235
|
ctx.stroke();
|
|
15227
15236
|
break;
|
|
15228
15237
|
}
|
|
15238
|
+
case "ellipse": {
|
|
15239
|
+
if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) break;
|
|
15240
|
+
const startAngle = (shape.startAngle ?? 0) * Math.PI / 180;
|
|
15241
|
+
const endAngle = (shape.endAngle ?? 360) * Math.PI / 180;
|
|
15242
|
+
ctx.beginPath();
|
|
15243
|
+
ctx.ellipse(shape.x, shape.y, shape.width / 2, shape.height / 2, 0, startAngle, endAngle);
|
|
15244
|
+
if (fill) {
|
|
15245
|
+
ctx.fillStyle = fill;
|
|
15246
|
+
ctx.fill();
|
|
15247
|
+
}
|
|
15248
|
+
ctx.strokeStyle = stroke;
|
|
15249
|
+
ctx.stroke();
|
|
15250
|
+
break;
|
|
15251
|
+
}
|
|
15229
15252
|
case "rect": {
|
|
15230
15253
|
if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) break;
|
|
15231
15254
|
if (fill) {
|
|
@@ -15272,21 +15295,153 @@ function drawShape(ctx, shape, width, height) {
|
|
|
15272
15295
|
ctx.fillText(shape.text, shape.x, shape.y);
|
|
15273
15296
|
break;
|
|
15274
15297
|
}
|
|
15298
|
+
case "venn-region": {
|
|
15299
|
+
const resolveCircles = (ids) => (ids ?? []).flatMap((id) => {
|
|
15300
|
+
const c = allShapes.find((s) => s.type === "circle" && s.id === id);
|
|
15301
|
+
return c && c.x != null && c.y != null && c.radius != null ? [{ x: c.x, y: c.y, radius: c.radius }] : [];
|
|
15302
|
+
});
|
|
15303
|
+
const inside = resolveCircles(shape.inside);
|
|
15304
|
+
if (inside.length === 0) break;
|
|
15305
|
+
const outside = resolveCircles(shape.outside);
|
|
15306
|
+
const off = document.createElement("canvas");
|
|
15307
|
+
off.width = ctx.canvas.width;
|
|
15308
|
+
off.height = ctx.canvas.height;
|
|
15309
|
+
const octx = off.getContext("2d");
|
|
15310
|
+
if (!octx) break;
|
|
15311
|
+
octx.setTransform(ctx.getTransform());
|
|
15312
|
+
for (const c of inside) {
|
|
15313
|
+
const p = new Path2D();
|
|
15314
|
+
p.arc(c.x, c.y, c.radius, 0, Math.PI * 2);
|
|
15315
|
+
octx.clip(p);
|
|
15316
|
+
}
|
|
15317
|
+
octx.fillStyle = fill ?? stroke;
|
|
15318
|
+
octx.fillRect(0, 0, width, height);
|
|
15319
|
+
octx.globalCompositeOperation = "destination-out";
|
|
15320
|
+
for (const c of outside) {
|
|
15321
|
+
const p = new Path2D();
|
|
15322
|
+
p.arc(c.x, c.y, c.radius, 0, Math.PI * 2);
|
|
15323
|
+
octx.fill(p);
|
|
15324
|
+
}
|
|
15325
|
+
ctx.save();
|
|
15326
|
+
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
15327
|
+
ctx.drawImage(off, 0, 0);
|
|
15328
|
+
ctx.restore();
|
|
15329
|
+
break;
|
|
15330
|
+
}
|
|
15275
15331
|
}
|
|
15276
15332
|
ctx.restore();
|
|
15277
15333
|
}
|
|
15278
|
-
|
|
15334
|
+
function readoutShapes(readouts, width) {
|
|
15335
|
+
const out = [];
|
|
15336
|
+
const chipH = 18;
|
|
15337
|
+
const gap = 6;
|
|
15338
|
+
let rightEdge = width - 6;
|
|
15339
|
+
let rowY = 6;
|
|
15340
|
+
for (const readout of readouts) {
|
|
15341
|
+
const text = `${readout.label}: ${String(readout.value)}`;
|
|
15342
|
+
const chipW = Math.min(170, Math.max(34, text.length * 6 + 12));
|
|
15343
|
+
let chipX = rightEdge - chipW;
|
|
15344
|
+
if (chipX < 4) {
|
|
15345
|
+
rowY += chipH + 4;
|
|
15346
|
+
rightEdge = width - 6;
|
|
15347
|
+
chipX = rightEdge - chipW;
|
|
15348
|
+
}
|
|
15349
|
+
const color = readout.color ?? "#334155";
|
|
15350
|
+
out.push({ type: "rect", x: chipX, y: rowY, width: chipW, height: chipH, color, fill: color });
|
|
15351
|
+
out.push({
|
|
15352
|
+
type: "text",
|
|
15353
|
+
x: chipX + chipW / 2,
|
|
15354
|
+
y: rowY + chipH / 2,
|
|
15355
|
+
text,
|
|
15356
|
+
color: "#ffffff",
|
|
15357
|
+
fontSize: 10,
|
|
15358
|
+
align: "center"
|
|
15359
|
+
});
|
|
15360
|
+
rightEdge = chipX - gap;
|
|
15361
|
+
}
|
|
15362
|
+
return out;
|
|
15363
|
+
}
|
|
15364
|
+
function traceShapes(panel, k, width, height) {
|
|
15365
|
+
const w = panel.width ?? Math.round(width * 0.32);
|
|
15366
|
+
const h = panel.height ?? Math.round(height * 0.28);
|
|
15367
|
+
const x = panel.x ?? width - w - 8;
|
|
15368
|
+
const y = panel.y ?? height - h - 8 - k * (h + 8);
|
|
15369
|
+
const allSamples = panel.series.flatMap((series) => series.samples);
|
|
15370
|
+
let xLo = Math.min(...allSamples.map((p) => p.x));
|
|
15371
|
+
let xHi = Math.max(...allSamples.map((p) => p.x));
|
|
15372
|
+
let yLo = Math.min(...allSamples.map((p) => p.y));
|
|
15373
|
+
let yHi = Math.max(...allSamples.map((p) => p.y));
|
|
15374
|
+
if (xLo === xHi) {
|
|
15375
|
+
xLo -= 1;
|
|
15376
|
+
xHi += 1;
|
|
15377
|
+
}
|
|
15378
|
+
if (yLo === yHi) {
|
|
15379
|
+
yLo -= 1;
|
|
15380
|
+
yHi += 1;
|
|
15381
|
+
}
|
|
15382
|
+
const backgroundColor = panel.backgroundColor ?? "#ffffff";
|
|
15383
|
+
const frameColor = panel.frameColor ?? "#94a3b8";
|
|
15384
|
+
const out = [];
|
|
15385
|
+
out.push({
|
|
15386
|
+
type: "rect",
|
|
15387
|
+
x,
|
|
15388
|
+
y,
|
|
15389
|
+
width: w,
|
|
15390
|
+
height: h,
|
|
15391
|
+
color: backgroundColor,
|
|
15392
|
+
fill: backgroundColor,
|
|
15393
|
+
opacity: panel.backgroundOpacity ?? 0.85
|
|
15394
|
+
});
|
|
15395
|
+
out.push({ type: "rect", x, y, width: w, height: h, color: frameColor, lineWidth: 1 });
|
|
15396
|
+
panel.series.forEach((series, j) => {
|
|
15397
|
+
const color = series.color ?? TRACE_SERIES_COLORS[j % TRACE_SERIES_COLORS.length];
|
|
15398
|
+
const mapped = series.samples.map((p) => ({
|
|
15399
|
+
x: x + 4 + (p.x - xLo) / (xHi - xLo) * (w - 8),
|
|
15400
|
+
y: y + h - 4 - (p.y - yLo) / (yHi - yLo) * (h - 8)
|
|
15401
|
+
}));
|
|
15402
|
+
for (let i = 1; i < mapped.length; i++) {
|
|
15403
|
+
out.push({
|
|
15404
|
+
type: "line",
|
|
15405
|
+
x1: mapped[i - 1].x,
|
|
15406
|
+
y1: mapped[i - 1].y,
|
|
15407
|
+
x2: mapped[i].x,
|
|
15408
|
+
y2: mapped[i].y,
|
|
15409
|
+
color,
|
|
15410
|
+
lineWidth: 1.5
|
|
15411
|
+
});
|
|
15412
|
+
}
|
|
15413
|
+
if (mapped.length > 0) {
|
|
15414
|
+
const last = mapped[mapped.length - 1];
|
|
15415
|
+
out.push({ type: "circle", x: last.x, y: last.y, radius: 2, color, fill: color });
|
|
15416
|
+
}
|
|
15417
|
+
if (series.label) {
|
|
15418
|
+
out.push({ type: "text", x: x + 6, y: y + 10 + 11 * j, text: series.label, color, fontSize: 9 });
|
|
15419
|
+
}
|
|
15420
|
+
});
|
|
15421
|
+
if (panel.yLabel) {
|
|
15422
|
+
out.push({ type: "text", x: x + w - 6, y: y + 10, text: panel.yLabel, color: "#6b7280", fontSize: 9, align: "right" });
|
|
15423
|
+
}
|
|
15424
|
+
if (panel.xLabel) {
|
|
15425
|
+
out.push({ type: "text", x: x + w - 6, y: y + h - 6, text: panel.xLabel, color: "#6b7280", fontSize: 9, align: "right" });
|
|
15426
|
+
}
|
|
15427
|
+
return out;
|
|
15428
|
+
}
|
|
15429
|
+
var DASH_PATTERNS, TRACE_SERIES_COLORS, LearningCanvas;
|
|
15279
15430
|
var init_LearningCanvas = __esm({
|
|
15280
15431
|
"components/learning/atoms/LearningCanvas.tsx"() {
|
|
15281
15432
|
"use client";
|
|
15282
15433
|
init_cn();
|
|
15283
15434
|
init_useEventBus();
|
|
15435
|
+
DASH_PATTERNS = { dashed: [6, 4], dotted: [2, 3] };
|
|
15436
|
+
TRACE_SERIES_COLORS = ["#2563eb", "#dc2626", "#16a34a", "#f59e0b"];
|
|
15284
15437
|
LearningCanvas = ({
|
|
15285
15438
|
className,
|
|
15286
15439
|
width = 600,
|
|
15287
15440
|
height = 400,
|
|
15288
15441
|
backgroundColor,
|
|
15289
15442
|
shapes = [],
|
|
15443
|
+
readouts,
|
|
15444
|
+
traces,
|
|
15290
15445
|
interactive = false,
|
|
15291
15446
|
animate = false,
|
|
15292
15447
|
onShapeClick,
|
|
@@ -15312,6 +15467,12 @@ var init_LearningCanvas = __esm({
|
|
|
15312
15467
|
}
|
|
15313
15468
|
return -1;
|
|
15314
15469
|
}, [shapes]);
|
|
15470
|
+
const derivedShapes = React94.useMemo(() => {
|
|
15471
|
+
if (!traces?.length && !readouts?.length) return shapes;
|
|
15472
|
+
const traceOut = (traces ?? []).flatMap((panel, k) => traceShapes(panel, k, width, height));
|
|
15473
|
+
const readoutOut = readouts?.length ? readoutShapes(readouts, width) : [];
|
|
15474
|
+
return [...shapes, ...traceOut, ...readoutOut];
|
|
15475
|
+
}, [shapes, traces, readouts, width, height]);
|
|
15315
15476
|
const draw = React94.useCallback(() => {
|
|
15316
15477
|
const canvas = canvasRef.current;
|
|
15317
15478
|
if (!canvas) return;
|
|
@@ -15328,13 +15489,13 @@ var init_LearningCanvas = __esm({
|
|
|
15328
15489
|
ctx.fillStyle = backgroundColor;
|
|
15329
15490
|
ctx.fillRect(0, 0, width, height);
|
|
15330
15491
|
}
|
|
15331
|
-
for (const shape of
|
|
15332
|
-
if (shape.type !== "text") drawShape(ctx, shape, width, height);
|
|
15492
|
+
for (const shape of derivedShapes) {
|
|
15493
|
+
if (shape.type !== "text") drawShape(ctx, shape, width, height, derivedShapes);
|
|
15333
15494
|
}
|
|
15334
|
-
for (const shape of
|
|
15335
|
-
if (shape.type === "text") drawShape(ctx, shape, width, height);
|
|
15495
|
+
for (const shape of derivedShapes) {
|
|
15496
|
+
if (shape.type === "text") drawShape(ctx, shape, width, height, derivedShapes);
|
|
15336
15497
|
}
|
|
15337
|
-
}, [width, height, backgroundColor,
|
|
15498
|
+
}, [width, height, backgroundColor, derivedShapes]);
|
|
15338
15499
|
React94.useEffect(() => {
|
|
15339
15500
|
draw();
|
|
15340
15501
|
}, [draw]);
|
|
@@ -16884,7 +17045,363 @@ var init_ComponentPatterns = __esm({
|
|
|
16884
17045
|
AlertPattern.displayName = "AlertPattern";
|
|
16885
17046
|
}
|
|
16886
17047
|
});
|
|
16887
|
-
|
|
17048
|
+
function layoutCircle(nodes, width, height) {
|
|
17049
|
+
const cx = width / 2;
|
|
17050
|
+
const cy = height / 2;
|
|
17051
|
+
const radius = Math.max(10, Math.min(cx, cy) - 40);
|
|
17052
|
+
const positions = /* @__PURE__ */ new Map();
|
|
17053
|
+
const n = nodes.length;
|
|
17054
|
+
nodes.forEach((node, i) => {
|
|
17055
|
+
const angle = 2 * Math.PI * i / Math.max(n, 1) - Math.PI / 2;
|
|
17056
|
+
positions.set(node.id, { x: cx + radius * Math.cos(angle), y: cy + radius * Math.sin(angle) });
|
|
17057
|
+
});
|
|
17058
|
+
return positions;
|
|
17059
|
+
}
|
|
17060
|
+
function layoutTree2(nodes, edges, root, width, height) {
|
|
17061
|
+
const nodeIds = nodes.map((n) => n.id);
|
|
17062
|
+
const idSet = new Set(nodeIds);
|
|
17063
|
+
const childrenOf = /* @__PURE__ */ new Map();
|
|
17064
|
+
const hasIncoming = /* @__PURE__ */ new Set();
|
|
17065
|
+
for (const e of edges) {
|
|
17066
|
+
if (!idSet.has(e.from) || !idSet.has(e.to)) continue;
|
|
17067
|
+
const list = childrenOf.get(e.from) ?? [];
|
|
17068
|
+
list.push(e.to);
|
|
17069
|
+
childrenOf.set(e.from, list);
|
|
17070
|
+
hasIncoming.add(e.to);
|
|
17071
|
+
}
|
|
17072
|
+
const depth = /* @__PURE__ */ new Map();
|
|
17073
|
+
const treeChildren = /* @__PURE__ */ new Map();
|
|
17074
|
+
const visited = /* @__PURE__ */ new Set();
|
|
17075
|
+
const bfsFrom = (start) => {
|
|
17076
|
+
if (visited.has(start)) return;
|
|
17077
|
+
visited.add(start);
|
|
17078
|
+
depth.set(start, 0);
|
|
17079
|
+
const queue = [start];
|
|
17080
|
+
while (queue.length > 0) {
|
|
17081
|
+
const u = queue.shift();
|
|
17082
|
+
for (const v of childrenOf.get(u) ?? []) {
|
|
17083
|
+
if (visited.has(v)) continue;
|
|
17084
|
+
visited.add(v);
|
|
17085
|
+
depth.set(v, (depth.get(u) ?? 0) + 1);
|
|
17086
|
+
const list = treeChildren.get(u) ?? [];
|
|
17087
|
+
list.push(v);
|
|
17088
|
+
treeChildren.set(u, list);
|
|
17089
|
+
queue.push(v);
|
|
17090
|
+
}
|
|
17091
|
+
}
|
|
17092
|
+
};
|
|
17093
|
+
const primaryRoot = root && idSet.has(root) ? root : nodeIds.find((id) => !hasIncoming.has(id)) ?? nodeIds[0];
|
|
17094
|
+
const rootsOrder = [];
|
|
17095
|
+
if (primaryRoot !== void 0) {
|
|
17096
|
+
bfsFrom(primaryRoot);
|
|
17097
|
+
rootsOrder.push(primaryRoot);
|
|
17098
|
+
}
|
|
17099
|
+
for (const id of nodeIds) {
|
|
17100
|
+
if (!visited.has(id)) {
|
|
17101
|
+
bfsFrom(id);
|
|
17102
|
+
rootsOrder.push(id);
|
|
17103
|
+
}
|
|
17104
|
+
}
|
|
17105
|
+
let leafCounter = 0;
|
|
17106
|
+
const xSlot = /* @__PURE__ */ new Map();
|
|
17107
|
+
const assignXSlot = (u) => {
|
|
17108
|
+
const children = treeChildren.get(u) ?? [];
|
|
17109
|
+
if (children.length === 0) {
|
|
17110
|
+
const slot = leafCounter++;
|
|
17111
|
+
xSlot.set(u, slot);
|
|
17112
|
+
return slot;
|
|
17113
|
+
}
|
|
17114
|
+
const childSlots = children.map(assignXSlot);
|
|
17115
|
+
const avg = childSlots.reduce((a, b) => a + b, 0) / childSlots.length;
|
|
17116
|
+
xSlot.set(u, avg);
|
|
17117
|
+
return avg;
|
|
17118
|
+
};
|
|
17119
|
+
for (const r2 of rootsOrder) assignXSlot(r2);
|
|
17120
|
+
let maxDepth = 0;
|
|
17121
|
+
for (const d of depth.values()) maxDepth = Math.max(maxDepth, d);
|
|
17122
|
+
const colWidth = width / Math.max(1, leafCounter);
|
|
17123
|
+
const rowHeight = height / (maxDepth + 1);
|
|
17124
|
+
const positions = /* @__PURE__ */ new Map();
|
|
17125
|
+
for (const id of nodeIds) {
|
|
17126
|
+
const slot = xSlot.get(id) ?? 0;
|
|
17127
|
+
const d = depth.get(id) ?? 0;
|
|
17128
|
+
positions.set(id, { x: slot * colWidth + colWidth / 2, y: d * rowHeight + rowHeight / 2 });
|
|
17129
|
+
}
|
|
17130
|
+
return positions;
|
|
17131
|
+
}
|
|
17132
|
+
function layoutLayered(nodes, edges, width, height) {
|
|
17133
|
+
const nodeIds = nodes.map((n) => n.id);
|
|
17134
|
+
const idSet = new Set(nodeIds);
|
|
17135
|
+
const adj = /* @__PURE__ */ new Map();
|
|
17136
|
+
const remainingIndegree = /* @__PURE__ */ new Map();
|
|
17137
|
+
for (const id of nodeIds) remainingIndegree.set(id, 0);
|
|
17138
|
+
for (const e of edges) {
|
|
17139
|
+
if (!idSet.has(e.from) || !idSet.has(e.to)) continue;
|
|
17140
|
+
const list = adj.get(e.from) ?? [];
|
|
17141
|
+
list.push(e.to);
|
|
17142
|
+
adj.set(e.from, list);
|
|
17143
|
+
remainingIndegree.set(e.to, (remainingIndegree.get(e.to) ?? 0) + 1);
|
|
17144
|
+
}
|
|
17145
|
+
const layer = /* @__PURE__ */ new Map();
|
|
17146
|
+
const dequeued = /* @__PURE__ */ new Set();
|
|
17147
|
+
const queue = [];
|
|
17148
|
+
for (const id of nodeIds) {
|
|
17149
|
+
if ((remainingIndegree.get(id) ?? 0) === 0) {
|
|
17150
|
+
layer.set(id, 0);
|
|
17151
|
+
queue.push(id);
|
|
17152
|
+
}
|
|
17153
|
+
}
|
|
17154
|
+
while (queue.length > 0) {
|
|
17155
|
+
const u = queue.shift();
|
|
17156
|
+
dequeued.add(u);
|
|
17157
|
+
for (const v of adj.get(u) ?? []) {
|
|
17158
|
+
const candidate = (layer.get(u) ?? 0) + 1;
|
|
17159
|
+
layer.set(v, Math.max(layer.get(v) ?? 0, candidate));
|
|
17160
|
+
remainingIndegree.set(v, (remainingIndegree.get(v) ?? 0) - 1);
|
|
17161
|
+
if ((remainingIndegree.get(v) ?? 0) === 0 && !dequeued.has(v)) {
|
|
17162
|
+
queue.push(v);
|
|
17163
|
+
}
|
|
17164
|
+
}
|
|
17165
|
+
}
|
|
17166
|
+
let baseMaxLayer = 0;
|
|
17167
|
+
for (const id of nodeIds) {
|
|
17168
|
+
if (dequeued.has(id)) baseMaxLayer = Math.max(baseMaxLayer, layer.get(id) ?? 0);
|
|
17169
|
+
}
|
|
17170
|
+
const cycleLayer = baseMaxLayer + 1;
|
|
17171
|
+
let maxLayer = baseMaxLayer;
|
|
17172
|
+
for (const id of nodeIds) {
|
|
17173
|
+
if (!dequeued.has(id)) {
|
|
17174
|
+
layer.set(id, cycleLayer);
|
|
17175
|
+
maxLayer = cycleLayer;
|
|
17176
|
+
}
|
|
17177
|
+
}
|
|
17178
|
+
const colWidth = width / Math.max(1, maxLayer + 1);
|
|
17179
|
+
const byLayer = /* @__PURE__ */ new Map();
|
|
17180
|
+
for (const id of nodeIds) {
|
|
17181
|
+
const l = layer.get(id) ?? 0;
|
|
17182
|
+
const list = byLayer.get(l) ?? [];
|
|
17183
|
+
list.push(id);
|
|
17184
|
+
byLayer.set(l, list);
|
|
17185
|
+
}
|
|
17186
|
+
const positions = /* @__PURE__ */ new Map();
|
|
17187
|
+
for (const [l, ids] of byLayer) {
|
|
17188
|
+
const rowHeight = height / ids.length;
|
|
17189
|
+
ids.forEach((id, i) => {
|
|
17190
|
+
positions.set(id, { x: l * colWidth + colWidth / 2, y: i * rowHeight + rowHeight / 2 });
|
|
17191
|
+
});
|
|
17192
|
+
}
|
|
17193
|
+
return positions;
|
|
17194
|
+
}
|
|
17195
|
+
function computePositions(nodes, edges, layout, root, width, height) {
|
|
17196
|
+
switch (layout) {
|
|
17197
|
+
case "circle":
|
|
17198
|
+
return layoutCircle(nodes, width, height);
|
|
17199
|
+
case "tree":
|
|
17200
|
+
return layoutTree2(nodes, edges, root, width, height);
|
|
17201
|
+
case "layered":
|
|
17202
|
+
return layoutLayered(nodes, edges, width, height);
|
|
17203
|
+
case "manual":
|
|
17204
|
+
default: {
|
|
17205
|
+
const positions = /* @__PURE__ */ new Map();
|
|
17206
|
+
for (const n of nodes) positions.set(n.id, { x: n.x ?? 0, y: n.y ?? 0 });
|
|
17207
|
+
return positions;
|
|
17208
|
+
}
|
|
17209
|
+
}
|
|
17210
|
+
}
|
|
17211
|
+
var NODE_STATE_COLOR, EDGE_STATE_COLOR, DEFAULT_NODE_RADIUS, AlgoGraphCanvas;
|
|
17212
|
+
var init_AlgoGraphCanvas = __esm({
|
|
17213
|
+
"components/learning/molecules/AlgoGraphCanvas.tsx"() {
|
|
17214
|
+
"use client";
|
|
17215
|
+
init_atoms();
|
|
17216
|
+
init_Stack();
|
|
17217
|
+
init_LearningCanvas();
|
|
17218
|
+
NODE_STATE_COLOR = {
|
|
17219
|
+
unvisited: "#cbd5e1",
|
|
17220
|
+
frontier: "#f59e0b",
|
|
17221
|
+
current: "#ef4444",
|
|
17222
|
+
visited: "#22c55e",
|
|
17223
|
+
goal: "#8b5cf6",
|
|
17224
|
+
path: "#0ea5e9"
|
|
17225
|
+
};
|
|
17226
|
+
EDGE_STATE_COLOR = {
|
|
17227
|
+
default: "#9ca3af",
|
|
17228
|
+
tree: "#16a34a",
|
|
17229
|
+
relaxed: "#f59e0b",
|
|
17230
|
+
candidate: "#38bdf8",
|
|
17231
|
+
path: "#dc2626"
|
|
17232
|
+
};
|
|
17233
|
+
DEFAULT_NODE_RADIUS = 18;
|
|
17234
|
+
AlgoGraphCanvas = ({
|
|
17235
|
+
className,
|
|
17236
|
+
width = 600,
|
|
17237
|
+
height = 400,
|
|
17238
|
+
title,
|
|
17239
|
+
backgroundColor,
|
|
17240
|
+
nodes = [],
|
|
17241
|
+
edges = [],
|
|
17242
|
+
layout = "manual",
|
|
17243
|
+
root,
|
|
17244
|
+
shapes = [],
|
|
17245
|
+
interactive = false,
|
|
17246
|
+
animate = false,
|
|
17247
|
+
onShapeClick,
|
|
17248
|
+
onNodeClick,
|
|
17249
|
+
isLoading,
|
|
17250
|
+
error
|
|
17251
|
+
}) => {
|
|
17252
|
+
const nodeById = React94.useMemo(() => {
|
|
17253
|
+
const m = /* @__PURE__ */ new Map();
|
|
17254
|
+
for (const n of nodes) m.set(n.id, n);
|
|
17255
|
+
return m;
|
|
17256
|
+
}, [nodes]);
|
|
17257
|
+
const nodeIndexById = React94.useMemo(() => {
|
|
17258
|
+
const m = /* @__PURE__ */ new Map();
|
|
17259
|
+
nodes.forEach((n, i) => m.set(n.id, i));
|
|
17260
|
+
return m;
|
|
17261
|
+
}, [nodes]);
|
|
17262
|
+
const derivedShapes = React94.useMemo(() => {
|
|
17263
|
+
const out = [];
|
|
17264
|
+
const positions = computePositions(nodes, edges, layout, root, width, height);
|
|
17265
|
+
const edgeGeoms = [];
|
|
17266
|
+
for (const e of edges) {
|
|
17267
|
+
const a = nodeById.get(e.from);
|
|
17268
|
+
const b = nodeById.get(e.to);
|
|
17269
|
+
const posA = positions.get(e.from);
|
|
17270
|
+
const posB = positions.get(e.to);
|
|
17271
|
+
if (!a || !b || !posA || !posB) continue;
|
|
17272
|
+
const rA = a.radius ?? DEFAULT_NODE_RADIUS;
|
|
17273
|
+
const rB = b.radius ?? DEFAULT_NODE_RADIUS;
|
|
17274
|
+
const dx = posB.x - posA.x;
|
|
17275
|
+
const dy = posB.y - posA.y;
|
|
17276
|
+
const dist = Math.max(1e-6, Math.hypot(dx, dy));
|
|
17277
|
+
const ux = dx / dist;
|
|
17278
|
+
const uy = dy / dist;
|
|
17279
|
+
edgeGeoms.push({
|
|
17280
|
+
directed: e.directed ?? false,
|
|
17281
|
+
x1: posA.x + ux * rA,
|
|
17282
|
+
y1: posA.y + uy * rA,
|
|
17283
|
+
x2: posB.x - ux * rB,
|
|
17284
|
+
y2: posB.y - uy * rB,
|
|
17285
|
+
color: e.color ?? EDGE_STATE_COLOR[e.state ?? "default"],
|
|
17286
|
+
label: e.label ?? (e.weight != null ? String(e.weight) : void 0)
|
|
17287
|
+
});
|
|
17288
|
+
}
|
|
17289
|
+
for (const g of edgeGeoms) {
|
|
17290
|
+
out.push({
|
|
17291
|
+
type: g.directed ? "arrow" : "line",
|
|
17292
|
+
x1: g.x1,
|
|
17293
|
+
y1: g.y1,
|
|
17294
|
+
x2: g.x2,
|
|
17295
|
+
y2: g.y2,
|
|
17296
|
+
color: g.color,
|
|
17297
|
+
lineWidth: 2
|
|
17298
|
+
});
|
|
17299
|
+
}
|
|
17300
|
+
for (const g of edgeGeoms) {
|
|
17301
|
+
if (g.label === void 0) continue;
|
|
17302
|
+
const midX = (g.x1 + g.x2) / 2;
|
|
17303
|
+
const midY = (g.y1 + g.y2) / 2;
|
|
17304
|
+
const dx = g.x2 - g.x1;
|
|
17305
|
+
const dy = g.y2 - g.y1;
|
|
17306
|
+
const dist = Math.max(1e-6, Math.hypot(dx, dy));
|
|
17307
|
+
const perpX = -(dy / dist);
|
|
17308
|
+
const perpY = dx / dist;
|
|
17309
|
+
out.push({
|
|
17310
|
+
type: "text",
|
|
17311
|
+
x: midX + perpX * 10,
|
|
17312
|
+
y: midY + perpY * 10,
|
|
17313
|
+
text: g.label,
|
|
17314
|
+
fontSize: 11,
|
|
17315
|
+
align: "center",
|
|
17316
|
+
color: "#374151"
|
|
17317
|
+
});
|
|
17318
|
+
}
|
|
17319
|
+
const nodeGeoms = [];
|
|
17320
|
+
for (const n of nodes) {
|
|
17321
|
+
const pos = positions.get(n.id);
|
|
17322
|
+
if (!pos) continue;
|
|
17323
|
+
nodeGeoms.push({
|
|
17324
|
+
id: n.id,
|
|
17325
|
+
x: pos.x,
|
|
17326
|
+
y: pos.y,
|
|
17327
|
+
radius: n.radius ?? DEFAULT_NODE_RADIUS,
|
|
17328
|
+
color: n.color ?? NODE_STATE_COLOR[n.state ?? "unvisited"],
|
|
17329
|
+
label: n.label,
|
|
17330
|
+
badge: n.badge
|
|
17331
|
+
});
|
|
17332
|
+
}
|
|
17333
|
+
for (const g of nodeGeoms) {
|
|
17334
|
+
out.push({ type: "circle", id: g.id, x: g.x, y: g.y, radius: g.radius, color: g.color, fill: `${g.color}33` });
|
|
17335
|
+
}
|
|
17336
|
+
const badgeGeoms = [];
|
|
17337
|
+
for (const g of nodeGeoms) {
|
|
17338
|
+
if (!g.badge) continue;
|
|
17339
|
+
const w = Math.min(42, Math.max(18, g.badge.text.length * 6 + 10));
|
|
17340
|
+
badgeGeoms.push({
|
|
17341
|
+
cx: g.x + g.radius * 0.75,
|
|
17342
|
+
cy: g.y - g.radius * 0.75,
|
|
17343
|
+
w,
|
|
17344
|
+
h: 14,
|
|
17345
|
+
// Borderless pill: same color drives both stroke and fill.
|
|
17346
|
+
color: g.badge.color ?? "#1e293b",
|
|
17347
|
+
text: g.badge.text
|
|
17348
|
+
});
|
|
17349
|
+
}
|
|
17350
|
+
for (const b of badgeGeoms) {
|
|
17351
|
+
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 });
|
|
17352
|
+
}
|
|
17353
|
+
for (const b of badgeGeoms) {
|
|
17354
|
+
out.push({ type: "text", x: b.cx, y: b.cy, text: b.text, fontSize: 9, align: "center", color: "#ffffff" });
|
|
17355
|
+
}
|
|
17356
|
+
for (const g of nodeGeoms) {
|
|
17357
|
+
if (g.label === void 0) continue;
|
|
17358
|
+
out.push({
|
|
17359
|
+
type: "text",
|
|
17360
|
+
x: g.x,
|
|
17361
|
+
y: g.y + g.radius + 14,
|
|
17362
|
+
text: g.label,
|
|
17363
|
+
fontSize: 12,
|
|
17364
|
+
align: "center",
|
|
17365
|
+
color: "#111827"
|
|
17366
|
+
});
|
|
17367
|
+
}
|
|
17368
|
+
out.push(...shapes);
|
|
17369
|
+
return out;
|
|
17370
|
+
}, [nodes, edges, layout, root, width, height, nodeById, shapes]);
|
|
17371
|
+
const handleShapeClick = React94.useCallback(
|
|
17372
|
+
(payload) => {
|
|
17373
|
+
if (payload.type === "circle" && payload.id) {
|
|
17374
|
+
const node = nodeById.get(payload.id);
|
|
17375
|
+
const idx = nodeIndexById.get(payload.id);
|
|
17376
|
+
if (node && idx !== void 0) {
|
|
17377
|
+
onNodeClick?.({ id: node.id, label: node.label, index: idx });
|
|
17378
|
+
}
|
|
17379
|
+
}
|
|
17380
|
+
onShapeClick?.(payload);
|
|
17381
|
+
},
|
|
17382
|
+
[nodeById, nodeIndexById, onNodeClick, onShapeClick]
|
|
17383
|
+
);
|
|
17384
|
+
return /* @__PURE__ */ jsxRuntime.jsx(Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
|
|
17385
|
+
title ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", children: title }) : null,
|
|
17386
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
17387
|
+
LearningCanvas,
|
|
17388
|
+
{
|
|
17389
|
+
width,
|
|
17390
|
+
height,
|
|
17391
|
+
backgroundColor,
|
|
17392
|
+
shapes: derivedShapes,
|
|
17393
|
+
interactive,
|
|
17394
|
+
animate,
|
|
17395
|
+
onShapeClick: onShapeClick || onNodeClick ? handleShapeClick : void 0,
|
|
17396
|
+
isLoading,
|
|
17397
|
+
error
|
|
17398
|
+
}
|
|
17399
|
+
)
|
|
17400
|
+
] }) });
|
|
17401
|
+
};
|
|
17402
|
+
}
|
|
17403
|
+
});
|
|
17404
|
+
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;
|
|
16888
17405
|
var init_AlgorithmCanvas = __esm({
|
|
16889
17406
|
"components/learning/molecules/AlgorithmCanvas.tsx"() {
|
|
16890
17407
|
"use client";
|
|
@@ -16896,6 +17413,43 @@ var init_AlgorithmCanvas = __esm({
|
|
|
16896
17413
|
DEFAULT_POINTER_COLOR = "#dc2626";
|
|
16897
17414
|
POINTER_BAND = 34;
|
|
16898
17415
|
TOP_PAD = 26;
|
|
17416
|
+
PANEL_FAMILY_ORDER = ["bars", "slots", "cells", "buckets", "frames"];
|
|
17417
|
+
RANGE_COLOR_DEFAULT = "#3b82f6";
|
|
17418
|
+
RANGE_FILL_OPACITY = 0.15;
|
|
17419
|
+
BRACKET_TOP_OFFSET = 16;
|
|
17420
|
+
BRACKET_ROW_H = 14;
|
|
17421
|
+
BRACKET_TICK_H = 6;
|
|
17422
|
+
BRACKET_LABEL_OFFSET = 6;
|
|
17423
|
+
SLOT_EMPTY_FILL = "#f1f5f9";
|
|
17424
|
+
SLOT_EMPTY_STROKE = "#cbd5e1";
|
|
17425
|
+
SLOT_FILLED_STROKE = "#9ca3af";
|
|
17426
|
+
SLOT_HIGHLIGHT_DEFAULT = "#f59e0b";
|
|
17427
|
+
SLOT_VALUE_TEXT_COLOR = "#ffffff";
|
|
17428
|
+
FRAME_ACTIVE_COLOR = "#3b82f6";
|
|
17429
|
+
FRAME_RETURNING_COLOR = "#f59e0b";
|
|
17430
|
+
FRAME_DONE_COLOR = "#94a3b8";
|
|
17431
|
+
FRAME_LABEL_COLOR = "#ffffff";
|
|
17432
|
+
FRAME_DETAIL_COLOR = "#e2e8f0";
|
|
17433
|
+
FRAME_TWO_LINE_MIN_H = 22;
|
|
17434
|
+
BUCKET_INDEX_FILL = "#e2e8f0";
|
|
17435
|
+
BUCKET_INDEX_STROKE = "#9ca3af";
|
|
17436
|
+
BUCKET_INDEX_TEXT = "#374151";
|
|
17437
|
+
BUCKET_ENTRY_TEXT = "#ffffff";
|
|
17438
|
+
BUCKET_ENTRY_DEFAULT = "#3b82f6";
|
|
17439
|
+
BUCKET_ENTRY_HIGHLIGHT = "#f59e0b";
|
|
17440
|
+
BUCKET_ENTRY_PROBING = "#38bdf8";
|
|
17441
|
+
BUCKET_ENTRY_MIN_W = 24;
|
|
17442
|
+
BUCKET_ENTRY_MAX_W = 64;
|
|
17443
|
+
AXIS_LABEL_COLOR = "#6b7280";
|
|
17444
|
+
AXIS_LABEL_FONT_SIZE = 10;
|
|
17445
|
+
CORNER_TEXT_COLOR = "#111827";
|
|
17446
|
+
CORNER_FONT_SIZE = 7;
|
|
17447
|
+
CORNER_MIN_CELL = 28;
|
|
17448
|
+
CORNER_INSET_X = 3;
|
|
17449
|
+
CORNER_INSET_Y = 6;
|
|
17450
|
+
AUX_PRIMARY_RATIO = 0.6;
|
|
17451
|
+
AUX_LABEL_BAND = 18;
|
|
17452
|
+
AUX_BASELINE_PAD = 8;
|
|
16899
17453
|
AlgorithmCanvas = ({
|
|
16900
17454
|
className,
|
|
16901
17455
|
width = 600,
|
|
@@ -16905,6 +17459,14 @@ var init_AlgorithmCanvas = __esm({
|
|
|
16905
17459
|
bars = [],
|
|
16906
17460
|
cells = [],
|
|
16907
17461
|
pointers = [],
|
|
17462
|
+
ranges = [],
|
|
17463
|
+
slots = [],
|
|
17464
|
+
slotOrientation = "horizontal",
|
|
17465
|
+
frames = [],
|
|
17466
|
+
buckets = [],
|
|
17467
|
+
auxBars = [],
|
|
17468
|
+
rowLabels = [],
|
|
17469
|
+
colLabels = [],
|
|
16908
17470
|
shapes = [],
|
|
16909
17471
|
interactive = false,
|
|
16910
17472
|
animate = false,
|
|
@@ -16914,12 +17476,35 @@ var init_AlgorithmCanvas = __esm({
|
|
|
16914
17476
|
}) => {
|
|
16915
17477
|
const derivedShapes = React94.useMemo(() => {
|
|
16916
17478
|
const out = [];
|
|
17479
|
+
const presence = {
|
|
17480
|
+
bars: bars.length > 0,
|
|
17481
|
+
slots: slots.length > 0,
|
|
17482
|
+
cells: cells.length > 0,
|
|
17483
|
+
buckets: buckets.length > 0,
|
|
17484
|
+
frames: frames.length > 0
|
|
17485
|
+
};
|
|
17486
|
+
const panelCount = PANEL_FAMILY_ORDER.filter((f3) => presence[f3]).length;
|
|
17487
|
+
const panelHeight = height / Math.max(1, panelCount);
|
|
17488
|
+
const panelY = { bars: 0, slots: 0, cells: 0, buckets: 0, frames: 0 };
|
|
17489
|
+
let compactIndex = 0;
|
|
17490
|
+
PANEL_FAMILY_ORDER.forEach((f3) => {
|
|
17491
|
+
if (presence[f3]) {
|
|
17492
|
+
panelY[f3] = compactIndex * panelHeight;
|
|
17493
|
+
compactIndex += 1;
|
|
17494
|
+
}
|
|
17495
|
+
});
|
|
16917
17496
|
if (bars.length > 0) {
|
|
17497
|
+
const panelYBars = panelY.bars;
|
|
16918
17498
|
const slot = width / bars.length;
|
|
16919
17499
|
const barW = slot * 0.8;
|
|
16920
17500
|
const gap = slot * 0.1;
|
|
16921
|
-
const
|
|
16922
|
-
const
|
|
17501
|
+
const bracketRanges = ranges.filter((r2) => r2.kind === "bracket");
|
|
17502
|
+
const bracketCount = bracketRanges.length;
|
|
17503
|
+
const bracketHeadroom = bracketCount > 0 ? BRACKET_TOP_OFFSET + bracketCount * BRACKET_ROW_H : 0;
|
|
17504
|
+
const hasAux = auxBars.length > 0;
|
|
17505
|
+
const primaryH = hasAux ? panelHeight * AUX_PRIMARY_RATIO : panelHeight;
|
|
17506
|
+
const baseline = panelYBars + primaryH - POINTER_BAND;
|
|
17507
|
+
const usableH = baseline - (panelYBars + TOP_PAD + bracketHeadroom);
|
|
16923
17508
|
const maxV = Math.max(1, ...bars.map((b) => Number.isFinite(b.value) ? b.value : 0));
|
|
16924
17509
|
bars.forEach((bar, i) => {
|
|
16925
17510
|
const v = Number.isFinite(bar.value) ? bar.value : 0;
|
|
@@ -16949,6 +17534,89 @@ var init_AlgorithmCanvas = __esm({
|
|
|
16949
17534
|
});
|
|
16950
17535
|
}
|
|
16951
17536
|
});
|
|
17537
|
+
ranges.forEach((r2) => {
|
|
17538
|
+
const kind = r2.kind ?? "fill";
|
|
17539
|
+
if (kind !== "fill") return;
|
|
17540
|
+
const color = r2.color ?? RANGE_COLOR_DEFAULT;
|
|
17541
|
+
out.push({
|
|
17542
|
+
type: "rect",
|
|
17543
|
+
x: r2.from * slot,
|
|
17544
|
+
y: panelYBars,
|
|
17545
|
+
width: (r2.to - r2.from + 1) * slot,
|
|
17546
|
+
height: primaryH,
|
|
17547
|
+
color,
|
|
17548
|
+
fill: color,
|
|
17549
|
+
opacity: RANGE_FILL_OPACITY
|
|
17550
|
+
});
|
|
17551
|
+
if (r2.label) {
|
|
17552
|
+
out.push({
|
|
17553
|
+
type: "text",
|
|
17554
|
+
x: r2.from * slot + 4,
|
|
17555
|
+
// Sits below the bracket block (if any) so fill and bracket labels never collide.
|
|
17556
|
+
y: panelYBars + 10 + bracketHeadroom,
|
|
17557
|
+
text: r2.label,
|
|
17558
|
+
color,
|
|
17559
|
+
fontSize: 10,
|
|
17560
|
+
align: "left"
|
|
17561
|
+
});
|
|
17562
|
+
}
|
|
17563
|
+
});
|
|
17564
|
+
bracketRanges.forEach((r2, i) => {
|
|
17565
|
+
const bracketY = panelYBars + BRACKET_TOP_OFFSET + i * BRACKET_ROW_H;
|
|
17566
|
+
const x1 = r2.from * slot + slot * 0.1;
|
|
17567
|
+
const x2 = (r2.to + 1) * slot - slot * 0.1;
|
|
17568
|
+
const color = r2.color ?? RANGE_COLOR_DEFAULT;
|
|
17569
|
+
out.push({ type: "line", x1, y1: bracketY, x2, y2: bracketY, color, lineWidth: 2 });
|
|
17570
|
+
out.push({ type: "line", x1, y1: bracketY, x2: x1, y2: bracketY + BRACKET_TICK_H, color, lineWidth: 2 });
|
|
17571
|
+
out.push({ type: "line", x1: x2, y1: bracketY, x2, y2: bracketY + BRACKET_TICK_H, color, lineWidth: 2 });
|
|
17572
|
+
if (r2.label) {
|
|
17573
|
+
out.push({
|
|
17574
|
+
type: "text",
|
|
17575
|
+
x: (x1 + x2) / 2,
|
|
17576
|
+
y: bracketY - BRACKET_LABEL_OFFSET,
|
|
17577
|
+
text: r2.label,
|
|
17578
|
+
color,
|
|
17579
|
+
fontSize: 10,
|
|
17580
|
+
align: "center"
|
|
17581
|
+
});
|
|
17582
|
+
}
|
|
17583
|
+
});
|
|
17584
|
+
if (hasAux) {
|
|
17585
|
+
const auxH = panelHeight - primaryH;
|
|
17586
|
+
const slot2 = width / auxBars.length;
|
|
17587
|
+
const auxBaseline = panelYBars + primaryH + auxH - AUX_BASELINE_PAD;
|
|
17588
|
+
const auxUsableH = auxBaseline - (panelYBars + primaryH + AUX_LABEL_BAND);
|
|
17589
|
+
const maxAuxV = Math.max(1, ...auxBars.map((b) => Number.isFinite(b.value) ? b.value : 0));
|
|
17590
|
+
auxBars.forEach((bar, i) => {
|
|
17591
|
+
const v = Number.isFinite(bar.value) ? bar.value : 0;
|
|
17592
|
+
const bh = Math.max(0, v / maxAuxV * auxUsableH);
|
|
17593
|
+
const x = i * slot2 + slot2 * 0.1;
|
|
17594
|
+
const w = slot2 * 0.8;
|
|
17595
|
+
const color = bar.color ?? DEFAULT_BAR_COLOR;
|
|
17596
|
+
out.push({
|
|
17597
|
+
type: "rect",
|
|
17598
|
+
id: `auxbar-${i}`,
|
|
17599
|
+
x,
|
|
17600
|
+
y: auxBaseline - bh,
|
|
17601
|
+
width: w,
|
|
17602
|
+
height: bh,
|
|
17603
|
+
color,
|
|
17604
|
+
fill: color
|
|
17605
|
+
});
|
|
17606
|
+
const label = bar.label ?? (auxBars.length <= 24 ? String(v) : void 0);
|
|
17607
|
+
if (label) {
|
|
17608
|
+
out.push({
|
|
17609
|
+
type: "text",
|
|
17610
|
+
x: x + w / 2,
|
|
17611
|
+
y: auxBaseline - bh - 8,
|
|
17612
|
+
text: label,
|
|
17613
|
+
color: "#374151",
|
|
17614
|
+
fontSize: 11,
|
|
17615
|
+
align: "center"
|
|
17616
|
+
});
|
|
17617
|
+
}
|
|
17618
|
+
});
|
|
17619
|
+
}
|
|
16952
17620
|
pointers.forEach((p) => {
|
|
16953
17621
|
if (p.index < 0 || p.index >= bars.length) return;
|
|
16954
17622
|
const cx = p.index * slot + slot / 2;
|
|
@@ -16956,7 +17624,7 @@ var init_AlgorithmCanvas = __esm({
|
|
|
16956
17624
|
out.push({
|
|
16957
17625
|
type: "arrow",
|
|
16958
17626
|
x1: cx,
|
|
16959
|
-
y1:
|
|
17627
|
+
y1: panelYBars + primaryH - 18,
|
|
16960
17628
|
x2: cx,
|
|
16961
17629
|
y2: baseline + 4,
|
|
16962
17630
|
color,
|
|
@@ -16966,7 +17634,7 @@ var init_AlgorithmCanvas = __esm({
|
|
|
16966
17634
|
out.push({
|
|
16967
17635
|
type: "text",
|
|
16968
17636
|
x: cx,
|
|
16969
|
-
y:
|
|
17637
|
+
y: panelYBars + primaryH - 8,
|
|
16970
17638
|
text: p.label,
|
|
16971
17639
|
color,
|
|
16972
17640
|
fontSize: 11,
|
|
@@ -16975,14 +17643,111 @@ var init_AlgorithmCanvas = __esm({
|
|
|
16975
17643
|
}
|
|
16976
17644
|
});
|
|
16977
17645
|
}
|
|
17646
|
+
if (slots.length > 0) {
|
|
17647
|
+
const panelYSlots = panelY.slots;
|
|
17648
|
+
const n = slots.length;
|
|
17649
|
+
const vertical = slotOrientation === "vertical";
|
|
17650
|
+
const vBoxH = panelHeight / n;
|
|
17651
|
+
const vBoxW = Math.min(width * 0.5, 120);
|
|
17652
|
+
const vBoxX = (width - vBoxW) / 2;
|
|
17653
|
+
const hCellW = width / n;
|
|
17654
|
+
const hBoxW = hCellW * 0.82;
|
|
17655
|
+
const hBoxH = Math.min(panelHeight * 0.6, 48);
|
|
17656
|
+
const hBoxY = panelYSlots + (panelHeight - hBoxH) / 2;
|
|
17657
|
+
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 };
|
|
17658
|
+
slots.forEach((s, i) => {
|
|
17659
|
+
const box = slotBox(i);
|
|
17660
|
+
const state = s.state ?? "filled";
|
|
17661
|
+
const fill = state === "empty" ? SLOT_EMPTY_FILL : state === "highlight" ? s.color ?? SLOT_HIGHLIGHT_DEFAULT : s.color ?? DEFAULT_BAR_COLOR;
|
|
17662
|
+
const stroke = state === "empty" ? SLOT_EMPTY_STROKE : SLOT_FILLED_STROKE;
|
|
17663
|
+
out.push({
|
|
17664
|
+
type: "rect",
|
|
17665
|
+
id: `slot-${i}`,
|
|
17666
|
+
x: box.x,
|
|
17667
|
+
y: box.y,
|
|
17668
|
+
width: box.width,
|
|
17669
|
+
height: box.height,
|
|
17670
|
+
color: stroke,
|
|
17671
|
+
fill
|
|
17672
|
+
});
|
|
17673
|
+
if (s.value != null && state !== "empty") {
|
|
17674
|
+
out.push({
|
|
17675
|
+
type: "text",
|
|
17676
|
+
x: box.x + box.width / 2,
|
|
17677
|
+
y: box.y + box.height / 2,
|
|
17678
|
+
text: String(s.value),
|
|
17679
|
+
color: SLOT_VALUE_TEXT_COLOR,
|
|
17680
|
+
fontSize: 12,
|
|
17681
|
+
align: "center"
|
|
17682
|
+
});
|
|
17683
|
+
}
|
|
17684
|
+
});
|
|
17685
|
+
if (bars.length === 0) {
|
|
17686
|
+
pointers.forEach((p) => {
|
|
17687
|
+
if (p.index < 0 || p.index >= slots.length) return;
|
|
17688
|
+
const box = slotBox(p.index);
|
|
17689
|
+
const color = p.color ?? DEFAULT_POINTER_COLOR;
|
|
17690
|
+
if (vertical) {
|
|
17691
|
+
const cy = box.y + box.height / 2;
|
|
17692
|
+
out.push({
|
|
17693
|
+
type: "arrow",
|
|
17694
|
+
x1: box.x + box.width + 34,
|
|
17695
|
+
y1: cy,
|
|
17696
|
+
x2: box.x + box.width + 4,
|
|
17697
|
+
y2: cy,
|
|
17698
|
+
color,
|
|
17699
|
+
lineWidth: 2
|
|
17700
|
+
});
|
|
17701
|
+
if (p.label) {
|
|
17702
|
+
out.push({
|
|
17703
|
+
type: "text",
|
|
17704
|
+
x: box.x + box.width + 38,
|
|
17705
|
+
y: cy,
|
|
17706
|
+
text: p.label,
|
|
17707
|
+
color,
|
|
17708
|
+
fontSize: 11,
|
|
17709
|
+
align: "left"
|
|
17710
|
+
});
|
|
17711
|
+
}
|
|
17712
|
+
} else {
|
|
17713
|
+
const cx = box.x + box.width / 2;
|
|
17714
|
+
out.push({
|
|
17715
|
+
type: "arrow",
|
|
17716
|
+
x1: cx,
|
|
17717
|
+
y1: panelYSlots + panelHeight - 18,
|
|
17718
|
+
x2: cx,
|
|
17719
|
+
y2: box.y + box.height + 4,
|
|
17720
|
+
color,
|
|
17721
|
+
lineWidth: 2
|
|
17722
|
+
});
|
|
17723
|
+
if (p.label) {
|
|
17724
|
+
out.push({
|
|
17725
|
+
type: "text",
|
|
17726
|
+
x: cx,
|
|
17727
|
+
y: panelYSlots + panelHeight - 8,
|
|
17728
|
+
text: p.label,
|
|
17729
|
+
color,
|
|
17730
|
+
fontSize: 11,
|
|
17731
|
+
align: "center"
|
|
17732
|
+
});
|
|
17733
|
+
}
|
|
17734
|
+
}
|
|
17735
|
+
});
|
|
17736
|
+
}
|
|
17737
|
+
}
|
|
16978
17738
|
if (cells.length > 0) {
|
|
17739
|
+
const panelYCells = panelY.cells;
|
|
16979
17740
|
const maxCol = Math.max(0, ...cells.map((c) => c.col)) + 1;
|
|
16980
17741
|
const maxRow = Math.max(0, ...cells.map((c) => c.row)) + 1;
|
|
16981
|
-
const
|
|
16982
|
-
const
|
|
17742
|
+
const colLabelH = colLabels.length > 0 ? 16 : 0;
|
|
17743
|
+
const rowLabelW = rowLabels.length > 0 ? 20 : 0;
|
|
17744
|
+
const gridX0 = rowLabelW;
|
|
17745
|
+
const gridY0 = panelYCells + colLabelH;
|
|
17746
|
+
const cw = (width - rowLabelW) / maxCol;
|
|
17747
|
+
const ch = (panelHeight - colLabelH) / maxRow;
|
|
16983
17748
|
cells.forEach((c, i) => {
|
|
16984
|
-
const x = c.col * cw;
|
|
16985
|
-
const y = c.row * ch;
|
|
17749
|
+
const x = gridX0 + c.col * cw;
|
|
17750
|
+
const y = gridY0 + c.row * ch;
|
|
16986
17751
|
const color = c.color ?? DEFAULT_CELL_COLOR;
|
|
16987
17752
|
out.push({
|
|
16988
17753
|
type: "rect",
|
|
@@ -17006,11 +17771,207 @@ var init_AlgorithmCanvas = __esm({
|
|
|
17006
17771
|
align: "center"
|
|
17007
17772
|
});
|
|
17008
17773
|
}
|
|
17774
|
+
if (c.corner && cw >= CORNER_MIN_CELL && ch >= CORNER_MIN_CELL) {
|
|
17775
|
+
const { tl, tr, bl, br } = c.corner;
|
|
17776
|
+
if (tl) {
|
|
17777
|
+
out.push({
|
|
17778
|
+
type: "text",
|
|
17779
|
+
x: x + CORNER_INSET_X,
|
|
17780
|
+
y: y + CORNER_INSET_Y,
|
|
17781
|
+
text: tl,
|
|
17782
|
+
color: CORNER_TEXT_COLOR,
|
|
17783
|
+
fontSize: CORNER_FONT_SIZE,
|
|
17784
|
+
align: "left"
|
|
17785
|
+
});
|
|
17786
|
+
}
|
|
17787
|
+
if (tr) {
|
|
17788
|
+
out.push({
|
|
17789
|
+
type: "text",
|
|
17790
|
+
x: x + cw - CORNER_INSET_X,
|
|
17791
|
+
y: y + CORNER_INSET_Y,
|
|
17792
|
+
text: tr,
|
|
17793
|
+
color: CORNER_TEXT_COLOR,
|
|
17794
|
+
fontSize: CORNER_FONT_SIZE,
|
|
17795
|
+
align: "right"
|
|
17796
|
+
});
|
|
17797
|
+
}
|
|
17798
|
+
if (bl) {
|
|
17799
|
+
out.push({
|
|
17800
|
+
type: "text",
|
|
17801
|
+
x: x + CORNER_INSET_X,
|
|
17802
|
+
y: y + ch - CORNER_INSET_Y,
|
|
17803
|
+
text: bl,
|
|
17804
|
+
color: CORNER_TEXT_COLOR,
|
|
17805
|
+
fontSize: CORNER_FONT_SIZE,
|
|
17806
|
+
align: "left"
|
|
17807
|
+
});
|
|
17808
|
+
}
|
|
17809
|
+
if (br) {
|
|
17810
|
+
out.push({
|
|
17811
|
+
type: "text",
|
|
17812
|
+
x: x + cw - CORNER_INSET_X,
|
|
17813
|
+
y: y + ch - CORNER_INSET_Y,
|
|
17814
|
+
text: br,
|
|
17815
|
+
color: CORNER_TEXT_COLOR,
|
|
17816
|
+
fontSize: CORNER_FONT_SIZE,
|
|
17817
|
+
align: "right"
|
|
17818
|
+
});
|
|
17819
|
+
}
|
|
17820
|
+
}
|
|
17821
|
+
});
|
|
17822
|
+
colLabels.forEach((l) => {
|
|
17823
|
+
out.push({
|
|
17824
|
+
type: "text",
|
|
17825
|
+
x: gridX0 + l.index * cw + cw / 2,
|
|
17826
|
+
y: panelYCells + colLabelH / 2,
|
|
17827
|
+
text: l.text,
|
|
17828
|
+
color: l.color ?? AXIS_LABEL_COLOR,
|
|
17829
|
+
fontSize: AXIS_LABEL_FONT_SIZE,
|
|
17830
|
+
align: "center"
|
|
17831
|
+
});
|
|
17832
|
+
});
|
|
17833
|
+
rowLabels.forEach((l) => {
|
|
17834
|
+
out.push({
|
|
17835
|
+
type: "text",
|
|
17836
|
+
x: rowLabelW - 6,
|
|
17837
|
+
y: gridY0 + l.index * ch + ch / 2,
|
|
17838
|
+
text: l.text,
|
|
17839
|
+
color: l.color ?? AXIS_LABEL_COLOR,
|
|
17840
|
+
fontSize: AXIS_LABEL_FONT_SIZE,
|
|
17841
|
+
align: "right"
|
|
17842
|
+
});
|
|
17843
|
+
});
|
|
17844
|
+
}
|
|
17845
|
+
if (buckets.length > 0) {
|
|
17846
|
+
const panelYBuckets = panelY.buckets;
|
|
17847
|
+
const bucketCount = Math.max(0, ...buckets.map((b) => b.index)) + 1;
|
|
17848
|
+
const rowH = panelHeight / bucketCount;
|
|
17849
|
+
const indexColW = Math.min(width * 0.12, 40);
|
|
17850
|
+
const maxChainLen = Math.max(1, ...buckets.map((b) => b.entries.length));
|
|
17851
|
+
const entryW = Math.min(BUCKET_ENTRY_MAX_W, Math.max(BUCKET_ENTRY_MIN_W, (width - indexColW - 8) / maxChainLen));
|
|
17852
|
+
const maxVisible = Math.floor((width - indexColW - 4) / entryW);
|
|
17853
|
+
buckets.forEach((b) => {
|
|
17854
|
+
const rowY = panelYBuckets + b.index * rowH;
|
|
17855
|
+
out.push({
|
|
17856
|
+
type: "rect",
|
|
17857
|
+
id: `bucket-index-${b.index}`,
|
|
17858
|
+
x: 2,
|
|
17859
|
+
y: rowY + 2,
|
|
17860
|
+
width: indexColW - 4,
|
|
17861
|
+
height: rowH - 4,
|
|
17862
|
+
color: BUCKET_INDEX_STROKE,
|
|
17863
|
+
fill: BUCKET_INDEX_FILL
|
|
17864
|
+
});
|
|
17865
|
+
out.push({
|
|
17866
|
+
type: "text",
|
|
17867
|
+
x: 2 + (indexColW - 4) / 2,
|
|
17868
|
+
y: rowY + rowH / 2,
|
|
17869
|
+
text: String(b.index),
|
|
17870
|
+
color: BUCKET_INDEX_TEXT,
|
|
17871
|
+
fontSize: 10,
|
|
17872
|
+
align: "center"
|
|
17873
|
+
});
|
|
17874
|
+
const overflow = b.entries.length > maxVisible;
|
|
17875
|
+
const visibleCount = overflow ? Math.max(0, maxVisible - 1) : b.entries.length;
|
|
17876
|
+
for (let j = 0; j < visibleCount; j++) {
|
|
17877
|
+
const entry = b.entries[j];
|
|
17878
|
+
const ex = indexColW + 4 + j * entryW;
|
|
17879
|
+
const state = entry.state ?? "default";
|
|
17880
|
+
const fill = state === "highlight" ? entry.color ?? BUCKET_ENTRY_HIGHLIGHT : state === "probing" ? entry.color ?? BUCKET_ENTRY_PROBING : entry.color ?? BUCKET_ENTRY_DEFAULT;
|
|
17881
|
+
out.push({
|
|
17882
|
+
type: "rect",
|
|
17883
|
+
id: `bucket-${b.index}-${j}`,
|
|
17884
|
+
x: ex,
|
|
17885
|
+
y: rowY + 2,
|
|
17886
|
+
width: entryW - 2,
|
|
17887
|
+
height: rowH - 4,
|
|
17888
|
+
color: fill,
|
|
17889
|
+
fill
|
|
17890
|
+
});
|
|
17891
|
+
if (entryW >= 20 && rowH >= 16) {
|
|
17892
|
+
out.push({
|
|
17893
|
+
type: "text",
|
|
17894
|
+
x: ex + (entryW - 2) / 2,
|
|
17895
|
+
y: rowY + rowH / 2,
|
|
17896
|
+
text: entry.label,
|
|
17897
|
+
color: BUCKET_ENTRY_TEXT,
|
|
17898
|
+
fontSize: 10,
|
|
17899
|
+
align: "center"
|
|
17900
|
+
});
|
|
17901
|
+
}
|
|
17902
|
+
}
|
|
17903
|
+
if (overflow) {
|
|
17904
|
+
const ex = indexColW + 4 + visibleCount * entryW;
|
|
17905
|
+
out.push({
|
|
17906
|
+
type: "rect",
|
|
17907
|
+
id: `bucket-${b.index}-overflow`,
|
|
17908
|
+
x: ex,
|
|
17909
|
+
y: rowY + 2,
|
|
17910
|
+
width: entryW - 2,
|
|
17911
|
+
height: rowH - 4,
|
|
17912
|
+
color: BUCKET_ENTRY_DEFAULT,
|
|
17913
|
+
fill: BUCKET_ENTRY_DEFAULT
|
|
17914
|
+
});
|
|
17915
|
+
out.push({
|
|
17916
|
+
type: "text",
|
|
17917
|
+
x: ex + (entryW - 2) / 2,
|
|
17918
|
+
y: rowY + rowH / 2,
|
|
17919
|
+
text: `+${b.entries.length - visibleCount}`,
|
|
17920
|
+
color: BUCKET_ENTRY_TEXT,
|
|
17921
|
+
fontSize: 10,
|
|
17922
|
+
align: "center"
|
|
17923
|
+
});
|
|
17924
|
+
}
|
|
17925
|
+
});
|
|
17926
|
+
}
|
|
17927
|
+
if (frames.length > 0) {
|
|
17928
|
+
const panelYFrames = panelY.frames;
|
|
17929
|
+
const n = frames.length;
|
|
17930
|
+
const frameH = panelHeight / n;
|
|
17931
|
+
const x = 8;
|
|
17932
|
+
const w = width - 16;
|
|
17933
|
+
frames.forEach((f3, i) => {
|
|
17934
|
+
const y = panelYFrames + panelHeight - (i + 1) * frameH;
|
|
17935
|
+
const state = f3.state ?? "active";
|
|
17936
|
+
const fill = state === "returning" ? f3.color ?? FRAME_RETURNING_COLOR : state === "done" ? f3.color ?? FRAME_DONE_COLOR : f3.color ?? FRAME_ACTIVE_COLOR;
|
|
17937
|
+
out.push({ type: "rect", id: `frame-${i}`, x, y, width: w, height: frameH, color: fill, fill });
|
|
17938
|
+
if (frameH >= FRAME_TWO_LINE_MIN_H) {
|
|
17939
|
+
out.push({
|
|
17940
|
+
type: "text",
|
|
17941
|
+
x: 16,
|
|
17942
|
+
y: y + frameH * 0.35,
|
|
17943
|
+
text: f3.label,
|
|
17944
|
+
color: FRAME_LABEL_COLOR,
|
|
17945
|
+
fontSize: 10,
|
|
17946
|
+
align: "left"
|
|
17947
|
+
});
|
|
17948
|
+
if (f3.detail) {
|
|
17949
|
+
out.push({
|
|
17950
|
+
type: "text",
|
|
17951
|
+
x: 16,
|
|
17952
|
+
y: y + frameH * 0.7,
|
|
17953
|
+
text: f3.detail,
|
|
17954
|
+
color: FRAME_DETAIL_COLOR,
|
|
17955
|
+
fontSize: 10,
|
|
17956
|
+
align: "left"
|
|
17957
|
+
});
|
|
17958
|
+
}
|
|
17959
|
+
} else {
|
|
17960
|
+
out.push({
|
|
17961
|
+
type: "text",
|
|
17962
|
+
x: 16,
|
|
17963
|
+
y: y + frameH / 2,
|
|
17964
|
+
text: f3.label,
|
|
17965
|
+
color: FRAME_LABEL_COLOR,
|
|
17966
|
+
fontSize: 10,
|
|
17967
|
+
align: "left"
|
|
17968
|
+
});
|
|
17969
|
+
}
|
|
17009
17970
|
});
|
|
17010
17971
|
}
|
|
17011
17972
|
out.push(...shapes);
|
|
17012
17973
|
return out;
|
|
17013
|
-
}, [bars, cells, pointers, shapes, width, height]);
|
|
17974
|
+
}, [bars, cells, pointers, ranges, slots, slotOrientation, frames, buckets, auxBars, rowLabels, colLabels, shapes, width, height]);
|
|
17014
17975
|
return /* @__PURE__ */ jsxRuntime.jsx(Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
|
|
17015
17976
|
title ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", children: title }) : null,
|
|
17016
17977
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -17197,6 +18158,12 @@ function LearningScene3D({
|
|
|
17197
18158
|
const unitId = event.payload?.unitId;
|
|
17198
18159
|
if (typeof unitId === "string") onItemClickRef.current?.(unitId);
|
|
17199
18160
|
});
|
|
18161
|
+
if (typeof process !== "undefined" && process.env && process.env.NODE_ENV !== "production" && post?.bloom) {
|
|
18162
|
+
const unknownKeys = Object.keys(post.bloom).filter((k) => !KNOWN_BLOOM_KEYS.has(k));
|
|
18163
|
+
if (unknownKeys.length > 0) {
|
|
18164
|
+
sceneLog.debug("post.bloom has unrecognized keys \u2014 only intensity/threshold/smoothing are read", { unknownKeys });
|
|
18165
|
+
}
|
|
18166
|
+
}
|
|
17200
18167
|
const props3d = {
|
|
17201
18168
|
drawables,
|
|
17202
18169
|
isLoading,
|
|
@@ -17262,7 +18229,7 @@ function cylinderBetween(from, to, radius, color) {
|
|
|
17262
18229
|
material: { color }
|
|
17263
18230
|
};
|
|
17264
18231
|
}
|
|
17265
|
-
function arrowBetween(from, to, color, shaftRadius = 0.08) {
|
|
18232
|
+
function arrowBetween(from, to, color, shaftRadius = 0.08, id) {
|
|
17266
18233
|
const len = segmentLength(from, to);
|
|
17267
18234
|
if (len < 1e-6) return null;
|
|
17268
18235
|
const tipLen = Math.min(shaftRadius * 8, len * 0.35);
|
|
@@ -17290,6 +18257,7 @@ function arrowBetween(from, to, color, shaftRadius = 0.08) {
|
|
|
17290
18257
|
};
|
|
17291
18258
|
return {
|
|
17292
18259
|
type: "draw-group",
|
|
18260
|
+
...id !== void 0 ? { id } : {},
|
|
17293
18261
|
position: { x: from[0], y: from[1], z: from[2] },
|
|
17294
18262
|
items: tipLenActual < 1e-6 ? shaft ? [shaft] : [] : shaft ? [shaft, tip] : [tip]
|
|
17295
18263
|
};
|
|
@@ -17316,20 +18284,228 @@ function get3DClickPayload(onShapeClick, idToIndex) {
|
|
|
17316
18284
|
if (!onShapeClick) return void 0;
|
|
17317
18285
|
return (id) => onShapeClick({ id, index: idToIndex.get(id) ?? -1 });
|
|
17318
18286
|
}
|
|
17319
|
-
|
|
18287
|
+
function polylineTube(points, radius, color, opts) {
|
|
18288
|
+
const maxSegments = opts?.maxSegments ?? 128;
|
|
18289
|
+
let pts = points;
|
|
18290
|
+
if (pts.length - 1 > maxSegments) {
|
|
18291
|
+
const step = (pts.length - 1) / maxSegments;
|
|
18292
|
+
const kept = [pts[0]];
|
|
18293
|
+
for (let s = 1; s < maxSegments; s++) kept.push(pts[Math.round(s * step)]);
|
|
18294
|
+
kept.push(pts[pts.length - 1]);
|
|
18295
|
+
pts = kept;
|
|
18296
|
+
}
|
|
18297
|
+
const out = [];
|
|
18298
|
+
for (let i = 0; i < pts.length - 1; i++) {
|
|
18299
|
+
const seg = cylinderBetween(pts[i], pts[i + 1], radius, color);
|
|
18300
|
+
if (seg) out.push(opts?.opacity !== void 0 ? { ...seg, opacity: opts.opacity } : seg);
|
|
18301
|
+
}
|
|
18302
|
+
return out;
|
|
18303
|
+
}
|
|
18304
|
+
function heightFieldMesh(spec) {
|
|
18305
|
+
const { nx, ny, heights, spacing = 1, x = 0, y = 0 } = spec;
|
|
18306
|
+
const flatShading = spec.flatShading ?? true;
|
|
18307
|
+
const vertices = [];
|
|
18308
|
+
for (let iy = 0; iy < ny; iy++) {
|
|
18309
|
+
for (let ix = 0; ix < nx; ix++) {
|
|
18310
|
+
vertices.push([
|
|
18311
|
+
x + (ix - (nx - 1) / 2) * spacing,
|
|
18312
|
+
y + (iy - (ny - 1) / 2) * spacing,
|
|
18313
|
+
heights[iy * nx + ix] ?? 0
|
|
18314
|
+
]);
|
|
18315
|
+
}
|
|
18316
|
+
}
|
|
18317
|
+
const bands = [...spec.bands ?? []].sort((a, b) => (a.min ?? -Infinity) - (b.min ?? -Infinity));
|
|
18318
|
+
const facesByBand = /* @__PURE__ */ new Map();
|
|
18319
|
+
for (let iy = 0; iy < ny - 1; iy++) {
|
|
18320
|
+
for (let ix = 0; ix < nx - 1; ix++) {
|
|
18321
|
+
const v00 = iy * nx + ix;
|
|
18322
|
+
const v10 = iy * nx + ix + 1;
|
|
18323
|
+
const v01 = (iy + 1) * nx + ix;
|
|
18324
|
+
const v11 = (iy + 1) * nx + ix + 1;
|
|
18325
|
+
for (const face of [[v00, v10, v01], [v10, v11, v01]]) {
|
|
18326
|
+
const centroid = (vertices[face[0]][2] + vertices[face[1]][2] + vertices[face[2]][2]) / 3;
|
|
18327
|
+
let band = null;
|
|
18328
|
+
for (const b of bands) {
|
|
18329
|
+
if ((b.min ?? -Infinity) <= centroid) band = b;
|
|
18330
|
+
}
|
|
18331
|
+
const key = bands.length > 0 ? band : null;
|
|
18332
|
+
const list = facesByBand.get(key) ?? [];
|
|
18333
|
+
list.push(face);
|
|
18334
|
+
facesByBand.set(key, list);
|
|
18335
|
+
}
|
|
18336
|
+
}
|
|
18337
|
+
}
|
|
18338
|
+
const out = [];
|
|
18339
|
+
for (const [band, faces] of facesByBand) {
|
|
18340
|
+
if (faces.length === 0) continue;
|
|
18341
|
+
out.push({
|
|
18342
|
+
type: "draw-mesh",
|
|
18343
|
+
shape: "polyhedron",
|
|
18344
|
+
position: { x: 0, y: 0, z: 0 },
|
|
18345
|
+
vertices,
|
|
18346
|
+
faces,
|
|
18347
|
+
pivot: "center",
|
|
18348
|
+
material: { color: band?.color ?? spec.color ?? "#64748b", flatShading, side: "double" },
|
|
18349
|
+
...spec.opacity !== void 0 ? { opacity: spec.opacity } : {}
|
|
18350
|
+
});
|
|
18351
|
+
}
|
|
18352
|
+
return out;
|
|
18353
|
+
}
|
|
18354
|
+
function arrowField(vectors, opts) {
|
|
18355
|
+
const scale = opts?.scale ?? 1;
|
|
18356
|
+
const out = [];
|
|
18357
|
+
for (const v of vectors) {
|
|
18358
|
+
const to = [
|
|
18359
|
+
v.from[0] + v.delta[0] * scale,
|
|
18360
|
+
v.from[1] + v.delta[1] * scale,
|
|
18361
|
+
v.from[2] + v.delta[2] * scale
|
|
18362
|
+
];
|
|
18363
|
+
const arrow = arrowBetween(v.from, to, v.color ?? "#dc2626", v.width, v.id);
|
|
18364
|
+
if (arrow) out.push(arrow);
|
|
18365
|
+
if (v.label) out.push(billboardLabel(v.label, to[0], to[1], to[2], { color: opts?.labelColor }));
|
|
18366
|
+
}
|
|
18367
|
+
return out;
|
|
18368
|
+
}
|
|
18369
|
+
function helixDrawables(spec, opts) {
|
|
18370
|
+
const count = spec.count ?? spec.rungs?.length ?? 0;
|
|
18371
|
+
const rungs = Array.from({ length: count }, (_, i) => spec.rungs?.[i] ?? {});
|
|
18372
|
+
const radius = spec.radius ?? 1;
|
|
18373
|
+
const rise = spec.rise ?? 0.34;
|
|
18374
|
+
const twistRad = (spec.twistDeg ?? 36) * (Math.PI / 180);
|
|
18375
|
+
const strandAColor = spec.strandAColor ?? "#38bdf8";
|
|
18376
|
+
const strandBColor = spec.strandBColor ?? "#fb923c";
|
|
18377
|
+
const backboneRadius = spec.backboneRadius ?? 0.16;
|
|
18378
|
+
const rungRadius = spec.rungRadius ?? 0.12;
|
|
18379
|
+
const cx = spec.x ?? 0;
|
|
18380
|
+
const cy = spec.y ?? 0;
|
|
18381
|
+
const cz = spec.z ?? 0;
|
|
18382
|
+
const unwoundCount = spec.unwoundCount ?? 0;
|
|
18383
|
+
const unwindSpread = spec.unwindSpread ?? 1.8;
|
|
18384
|
+
const strandA = [];
|
|
18385
|
+
const strandB = [];
|
|
18386
|
+
for (let i = 0; i < count; i++) {
|
|
18387
|
+
const yi = cy + (i - (count - 1) / 2) * rise;
|
|
18388
|
+
const theta = i * twistRad;
|
|
18389
|
+
const s = i < unwoundCount ? unwindSpread : 1;
|
|
18390
|
+
strandA.push([cx + s * radius * Math.cos(theta), yi, cz + s * radius * Math.sin(theta)]);
|
|
18391
|
+
strandB.push([cx + s * radius * Math.cos(theta + Math.PI), yi, cz + s * radius * Math.sin(theta + Math.PI)]);
|
|
18392
|
+
}
|
|
18393
|
+
const out = [];
|
|
18394
|
+
for (let i = 0; i < count; i++) {
|
|
18395
|
+
out.push(meshSphere(`hx-a-${i}`, strandA[i][0], strandA[i][1], strandA[i][2], backboneRadius, strandAColor));
|
|
18396
|
+
out.push(meshSphere(`hx-b-${i}`, strandB[i][0], strandB[i][1], strandB[i][2], backboneRadius, strandBColor));
|
|
18397
|
+
if (i > 0) {
|
|
18398
|
+
const segA = cylinderBetween(strandA[i - 1], strandA[i], backboneRadius, strandAColor);
|
|
18399
|
+
if (segA) out.push(segA);
|
|
18400
|
+
const segB = cylinderBetween(strandB[i - 1], strandB[i], backboneRadius, strandBColor);
|
|
18401
|
+
if (segB) out.push(segB);
|
|
18402
|
+
}
|
|
18403
|
+
const rung = rungs[i];
|
|
18404
|
+
const rungColor = rung.color ?? "#94a3b8";
|
|
18405
|
+
const rod = cylinderBetween(strandA[i], strandB[i], rungRadius, rungColor);
|
|
18406
|
+
if (rod) out.push(rod);
|
|
18407
|
+
const mid = [
|
|
18408
|
+
(strandA[i][0] + strandB[i][0]) / 2,
|
|
18409
|
+
(strandA[i][1] + strandB[i][1]) / 2,
|
|
18410
|
+
(strandA[i][2] + strandB[i][2]) / 2
|
|
18411
|
+
];
|
|
18412
|
+
const markerRadius = rung.radius ?? rungRadius;
|
|
18413
|
+
out.push(meshSphere(rung.id, mid[0], mid[1], mid[2], markerRadius, rungColor));
|
|
18414
|
+
if (rung.label) out.push(billboardLabel(rung.label, mid[0], mid[1], mid[2] + markerRadius, { color: opts?.labelColor }));
|
|
18415
|
+
}
|
|
18416
|
+
return out;
|
|
18417
|
+
}
|
|
18418
|
+
function latticeDrawables(spec, opts) {
|
|
18419
|
+
const nx = spec.nx ?? 2;
|
|
18420
|
+
const ny = spec.ny ?? 2;
|
|
18421
|
+
const nz = spec.nz ?? 2;
|
|
18422
|
+
const latticeConstant = spec.latticeConstant ?? 2;
|
|
18423
|
+
const bondRadius = spec.bondRadius ?? 0.06;
|
|
18424
|
+
const highlightCell = spec.highlightCell ?? false;
|
|
18425
|
+
const dimColor = spec.dimColor ?? "#475569";
|
|
18426
|
+
const showLabels = spec.showLabels ?? false;
|
|
18427
|
+
const selectedColor = spec.selectedColor ?? "#f59e0b";
|
|
18428
|
+
const posByKey = /* @__PURE__ */ new Map();
|
|
18429
|
+
const inCellByKey = /* @__PURE__ */ new Map();
|
|
18430
|
+
const out = [];
|
|
18431
|
+
for (const site of spec.basis) {
|
|
18432
|
+
const snx = site.xEdge ? nx + 1 : nx;
|
|
18433
|
+
const sny = site.yEdge ? ny + 1 : ny;
|
|
18434
|
+
const snz = site.zEdge ? nz + 1 : nz;
|
|
18435
|
+
for (let i = 0; i < snx; i++) {
|
|
18436
|
+
for (let j = 0; j < sny; j++) {
|
|
18437
|
+
for (let k = 0; k < snz; k++) {
|
|
18438
|
+
const key = `${site.key}-${i}-${j}-${k}`;
|
|
18439
|
+
const inCell = i + site.dx <= 1 && j + site.dy <= 1 && k + site.dz <= 1;
|
|
18440
|
+
const pos = [
|
|
18441
|
+
(i + site.dx) * latticeConstant - nx * latticeConstant / 2,
|
|
18442
|
+
(j + site.dy) * latticeConstant - ny * latticeConstant / 2,
|
|
18443
|
+
(k + site.dz) * latticeConstant - nz * latticeConstant / 2
|
|
18444
|
+
];
|
|
18445
|
+
posByKey.set(key, pos);
|
|
18446
|
+
inCellByKey.set(key, inCell);
|
|
18447
|
+
const isSelected = spec.selectedId === `lat-${key}`;
|
|
18448
|
+
const color = isSelected ? selectedColor : highlightCell && !inCell ? dimColor : site.color ?? "#2563eb";
|
|
18449
|
+
const radius = (site.radius ?? 0.3) * (isSelected ? 1.4 : 1);
|
|
18450
|
+
out.push(meshSphere(`lat-${key}`, pos[0], pos[1], pos[2], radius, color));
|
|
18451
|
+
if (showLabels && site.element) {
|
|
18452
|
+
out.push(billboardLabel(site.element, pos[0], pos[1], pos[2] + radius, { color: opts?.labelColor }));
|
|
18453
|
+
}
|
|
18454
|
+
}
|
|
18455
|
+
}
|
|
18456
|
+
}
|
|
18457
|
+
}
|
|
18458
|
+
const basisByKey = new Map(spec.basis.map((s) => [s.key, s]));
|
|
18459
|
+
for (const bond of spec.bonds ?? []) {
|
|
18460
|
+
const fromSite = basisByKey.get(bond.from);
|
|
18461
|
+
const toSite = basisByKey.get(bond.to);
|
|
18462
|
+
if (!fromSite || !toSite) continue;
|
|
18463
|
+
const fnx = fromSite.xEdge ? nx + 1 : nx;
|
|
18464
|
+
const fny = fromSite.yEdge ? ny + 1 : ny;
|
|
18465
|
+
const fnz = fromSite.zEdge ? nz + 1 : nz;
|
|
18466
|
+
const tnx = toSite.xEdge ? nx + 1 : nx;
|
|
18467
|
+
const tny = toSite.yEdge ? ny + 1 : ny;
|
|
18468
|
+
const tnz = toSite.zEdge ? nz + 1 : nz;
|
|
18469
|
+
const bdx = bond.dx ?? 0;
|
|
18470
|
+
const bdy = bond.dy ?? 0;
|
|
18471
|
+
const bdz = bond.dz ?? 0;
|
|
18472
|
+
for (let i = 0; i < fnx; i++) {
|
|
18473
|
+
for (let j = 0; j < fny; j++) {
|
|
18474
|
+
for (let k = 0; k < fnz; k++) {
|
|
18475
|
+
const ti = i + bdx;
|
|
18476
|
+
const tj = j + bdy;
|
|
18477
|
+
const tk = k + bdz;
|
|
18478
|
+
if (ti < 0 || ti >= tnx || tj < 0 || tj >= tny || tk < 0 || tk >= tnz) continue;
|
|
18479
|
+
const fromKey = `${fromSite.key}-${i}-${j}-${k}`;
|
|
18480
|
+
const toKey = `${toSite.key}-${ti}-${tj}-${tk}`;
|
|
18481
|
+
const fromPos = posByKey.get(fromKey);
|
|
18482
|
+
const toPos = posByKey.get(toKey);
|
|
18483
|
+
if (!fromPos || !toPos) continue;
|
|
18484
|
+
const dimmed = highlightCell && !(inCellByKey.get(fromKey) && inCellByKey.get(toKey));
|
|
18485
|
+
const seg = cylinderBetween(fromPos, toPos, bondRadius, dimmed ? dimColor : bond.color ?? "#6b7280");
|
|
18486
|
+
if (seg) out.push(seg);
|
|
18487
|
+
}
|
|
18488
|
+
}
|
|
18489
|
+
}
|
|
18490
|
+
}
|
|
18491
|
+
return out;
|
|
18492
|
+
}
|
|
18493
|
+
var sceneLog, KNOWN_BLOOM_KEYS, Canvas3DHost2;
|
|
17320
18494
|
var init_learningScene3D = __esm({
|
|
17321
18495
|
"components/learning/molecules/learningScene3D.tsx"() {
|
|
17322
18496
|
"use client";
|
|
17323
18497
|
init_atoms();
|
|
17324
18498
|
init_Stack();
|
|
17325
18499
|
init_useEventBus();
|
|
18500
|
+
sceneLog = logger.createLogger("almadar:ui:learning-scene-3d");
|
|
18501
|
+
KNOWN_BLOOM_KEYS = /* @__PURE__ */ new Set(["intensity", "threshold", "smoothing"]);
|
|
17326
18502
|
Canvas3DHost2 = React94.lazy(
|
|
17327
18503
|
() => import('@almadar/ui/components/molecules/game/three').then((m) => ({ default: m.Canvas3DHost }))
|
|
17328
18504
|
);
|
|
17329
18505
|
LearningScene3D.displayName = "LearningScene3D";
|
|
17330
18506
|
}
|
|
17331
18507
|
});
|
|
17332
|
-
var biologyLog, BiologyCanvas;
|
|
18508
|
+
var biologyLog, BIO_BAND_COLORS, BIO_STAGE_FILL, BIO_STAGE_TEXT, BiologyCanvas;
|
|
17333
18509
|
var init_BiologyCanvas = __esm({
|
|
17334
18510
|
"components/learning/molecules/BiologyCanvas.tsx"() {
|
|
17335
18511
|
"use client";
|
|
@@ -17338,6 +18514,17 @@ var init_BiologyCanvas = __esm({
|
|
|
17338
18514
|
init_LearningCanvas();
|
|
17339
18515
|
init_learningScene3D();
|
|
17340
18516
|
biologyLog = logger.createLogger("almadar:ui:biology-canvas");
|
|
18517
|
+
BIO_BAND_COLORS = ["#dcfce7", "#fef9c3", "#fee2e2", "#e0e7ff"];
|
|
18518
|
+
BIO_STAGE_FILL = {
|
|
18519
|
+
pending: "#e2e8f0",
|
|
18520
|
+
active: "#3b82f6",
|
|
18521
|
+
done: "#94a3b8"
|
|
18522
|
+
};
|
|
18523
|
+
BIO_STAGE_TEXT = {
|
|
18524
|
+
pending: "#64748b",
|
|
18525
|
+
active: "#ffffff",
|
|
18526
|
+
done: "#ffffff"
|
|
18527
|
+
};
|
|
17341
18528
|
BiologyCanvas = ({
|
|
17342
18529
|
className,
|
|
17343
18530
|
width = 600,
|
|
@@ -17350,7 +18537,15 @@ var init_BiologyCanvas = __esm({
|
|
|
17350
18537
|
post,
|
|
17351
18538
|
nodes = [],
|
|
17352
18539
|
edges = [],
|
|
18540
|
+
compartments = [],
|
|
18541
|
+
bands = [],
|
|
18542
|
+
stages = [],
|
|
18543
|
+
stageStyle = "timeline",
|
|
18544
|
+
helix,
|
|
18545
|
+
helix3d,
|
|
17353
18546
|
shapes = [],
|
|
18547
|
+
readouts,
|
|
18548
|
+
traces,
|
|
17354
18549
|
showGrid,
|
|
17355
18550
|
shadows,
|
|
17356
18551
|
interactive,
|
|
@@ -17365,19 +18560,148 @@ var init_BiologyCanvas = __esm({
|
|
|
17365
18560
|
for (const n of nodes) {
|
|
17366
18561
|
if (n.id) nodeById.set(n.id, n);
|
|
17367
18562
|
}
|
|
18563
|
+
const bandCount = bands.length;
|
|
18564
|
+
for (let i = 0; i < bandCount; i++) {
|
|
18565
|
+
const band = bands[i];
|
|
18566
|
+
const bandColor = band.color ?? BIO_BAND_COLORS[i % BIO_BAND_COLORS.length];
|
|
18567
|
+
const bandY = i * height / bandCount;
|
|
18568
|
+
const bandH = height / bandCount;
|
|
18569
|
+
out.push({
|
|
18570
|
+
type: "rect",
|
|
18571
|
+
x: 0,
|
|
18572
|
+
y: bandY,
|
|
18573
|
+
width,
|
|
18574
|
+
height: bandH,
|
|
18575
|
+
color: bandColor,
|
|
18576
|
+
fill: bandColor,
|
|
18577
|
+
opacity: 0.45
|
|
18578
|
+
});
|
|
18579
|
+
if (band.label) {
|
|
18580
|
+
out.push({
|
|
18581
|
+
type: "text",
|
|
18582
|
+
x: 8,
|
|
18583
|
+
y: bandY + 14,
|
|
18584
|
+
text: band.label,
|
|
18585
|
+
color: "#6b7280",
|
|
18586
|
+
fontSize: 10
|
|
18587
|
+
});
|
|
18588
|
+
}
|
|
18589
|
+
}
|
|
18590
|
+
for (const c of compartments) {
|
|
18591
|
+
const color = c.color ?? "#16a34a";
|
|
18592
|
+
out.push({
|
|
18593
|
+
type: "ellipse",
|
|
18594
|
+
x: c.x,
|
|
18595
|
+
y: c.y,
|
|
18596
|
+
width: c.width,
|
|
18597
|
+
height: c.height,
|
|
18598
|
+
color,
|
|
18599
|
+
fill: c.fill ?? `${color}1A`,
|
|
18600
|
+
lineWidth: c.lineWidth ?? 2,
|
|
18601
|
+
...c.dash ? { dash: c.dash } : {}
|
|
18602
|
+
});
|
|
18603
|
+
if (c.label) {
|
|
18604
|
+
out.push({
|
|
18605
|
+
type: "text",
|
|
18606
|
+
x: c.x,
|
|
18607
|
+
y: c.y - c.height / 2 + 14,
|
|
18608
|
+
text: c.label,
|
|
18609
|
+
color: "#111827",
|
|
18610
|
+
fontSize: 11,
|
|
18611
|
+
align: "center"
|
|
18612
|
+
});
|
|
18613
|
+
}
|
|
18614
|
+
}
|
|
18615
|
+
if (helix) {
|
|
18616
|
+
const hx = helix.x ?? 24;
|
|
18617
|
+
const hy = helix.y ?? height * 0.25;
|
|
18618
|
+
const hw = helix.width ?? width - 48;
|
|
18619
|
+
const hh = helix.height ?? height * 0.5;
|
|
18620
|
+
const rungs = helix.rungs;
|
|
18621
|
+
const n = rungs.length;
|
|
18622
|
+
const cy = hy + hh / 2;
|
|
18623
|
+
const colorA = helix.colorA ?? "#2563eb";
|
|
18624
|
+
const colorB = helix.colorB ?? "#dc2626";
|
|
18625
|
+
const rungColor = helix.rungColor ?? "#94a3b8";
|
|
18626
|
+
const fork = helix.fork ?? 0;
|
|
18627
|
+
const maxSep = Math.min(hh - 8, 96);
|
|
18628
|
+
const strandA = [];
|
|
18629
|
+
const strandB = [];
|
|
18630
|
+
const rungGeoms = [];
|
|
18631
|
+
for (let i = 0; i < n; i++) {
|
|
18632
|
+
const rx = hx + (i + 0.5) * hw / n;
|
|
18633
|
+
const t = (i + 0.5) / n;
|
|
18634
|
+
const paired = t >= fork;
|
|
18635
|
+
const sep = paired ? 28 : 28 + (maxSep - 28) * ((fork - t) / fork);
|
|
18636
|
+
strandA.push({ x: rx, y: cy - sep / 2 });
|
|
18637
|
+
strandB.push({ x: rx, y: cy + sep / 2 });
|
|
18638
|
+
rungGeoms.push({ rx, sep, rung: rungs[i], paired });
|
|
18639
|
+
}
|
|
18640
|
+
for (let i = 1; i < n; i++) {
|
|
18641
|
+
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 });
|
|
18642
|
+
}
|
|
18643
|
+
for (let i = 1; i < n; i++) {
|
|
18644
|
+
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 });
|
|
18645
|
+
}
|
|
18646
|
+
for (const g of rungGeoms) {
|
|
18647
|
+
const rColor = g.rung.color ?? (g.rung.state === "new" ? "#16a34a" : rungColor);
|
|
18648
|
+
const topY = cy - g.sep / 2;
|
|
18649
|
+
const bottomY = cy + g.sep / 2;
|
|
18650
|
+
if (g.paired) {
|
|
18651
|
+
out.push({ type: "line", x1: g.rx, y1: topY, x2: g.rx, y2: bottomY, color: rColor });
|
|
18652
|
+
if (g.rung.a) {
|
|
18653
|
+
out.push({ type: "text", x: g.rx, y: cy - g.sep / 4, text: g.rung.a, fontSize: 9, align: "center", color: "#374151" });
|
|
18654
|
+
}
|
|
18655
|
+
if (g.rung.b) {
|
|
18656
|
+
out.push({ type: "text", x: g.rx, y: cy + g.sep / 4, text: g.rung.b, fontSize: 9, align: "center", color: "#374151" });
|
|
18657
|
+
}
|
|
18658
|
+
} else {
|
|
18659
|
+
const stubTopY = topY + 8;
|
|
18660
|
+
const stubBottomY = bottomY - 8;
|
|
18661
|
+
out.push({ type: "line", x1: g.rx, y1: topY, x2: g.rx, y2: stubTopY, color: rColor });
|
|
18662
|
+
out.push({ type: "line", x1: g.rx, y1: bottomY, x2: g.rx, y2: stubBottomY, color: rColor });
|
|
18663
|
+
if (g.rung.a) {
|
|
18664
|
+
out.push({ type: "text", x: g.rx, y: stubTopY + 6, text: g.rung.a, fontSize: 9, align: "center", color: "#374151" });
|
|
18665
|
+
}
|
|
18666
|
+
if (g.rung.b) {
|
|
18667
|
+
out.push({ type: "text", x: g.rx, y: stubBottomY - 6, text: g.rung.b, fontSize: 9, align: "center", color: "#374151" });
|
|
18668
|
+
}
|
|
18669
|
+
}
|
|
18670
|
+
}
|
|
18671
|
+
}
|
|
17368
18672
|
for (const e of edges) {
|
|
17369
18673
|
const a = nodeById.get(e.from);
|
|
17370
18674
|
const b = nodeById.get(e.to);
|
|
17371
18675
|
if (!a || !b) continue;
|
|
17372
|
-
|
|
17373
|
-
|
|
17374
|
-
|
|
17375
|
-
|
|
17376
|
-
|
|
17377
|
-
|
|
17378
|
-
|
|
17379
|
-
|
|
17380
|
-
|
|
18676
|
+
const color = e.color ?? "#9ca3af";
|
|
18677
|
+
if (e.directed) {
|
|
18678
|
+
const rA = a.radius ?? 16;
|
|
18679
|
+
const rB = b.radius ?? 16;
|
|
18680
|
+
const dx = b.x - a.x;
|
|
18681
|
+
const dy = b.y - a.y;
|
|
18682
|
+
const dist = Math.max(1e-6, Math.hypot(dx, dy));
|
|
18683
|
+
const ux = dx / dist;
|
|
18684
|
+
const uy = dy / dist;
|
|
18685
|
+
out.push({
|
|
18686
|
+
type: "arrow",
|
|
18687
|
+
x1: a.x + ux * rA,
|
|
18688
|
+
y1: a.y + uy * rA,
|
|
18689
|
+
x2: b.x - ux * rB,
|
|
18690
|
+
y2: b.y - uy * rB,
|
|
18691
|
+
color,
|
|
18692
|
+
lineWidth: 2
|
|
18693
|
+
});
|
|
18694
|
+
} else {
|
|
18695
|
+
out.push({
|
|
18696
|
+
type: "line",
|
|
18697
|
+
x1: a.x,
|
|
18698
|
+
y1: a.y,
|
|
18699
|
+
x2: b.x,
|
|
18700
|
+
y2: b.y,
|
|
18701
|
+
color,
|
|
18702
|
+
lineWidth: 2
|
|
18703
|
+
});
|
|
18704
|
+
}
|
|
17381
18705
|
if (e.label) {
|
|
17382
18706
|
out.push({
|
|
17383
18707
|
type: "text",
|
|
@@ -17390,6 +18714,8 @@ var init_BiologyCanvas = __esm({
|
|
|
17390
18714
|
}
|
|
17391
18715
|
}
|
|
17392
18716
|
for (const n of nodes) {
|
|
18717
|
+
const state = n.state ?? "default";
|
|
18718
|
+
const muted = state === "muted";
|
|
17393
18719
|
out.push({
|
|
17394
18720
|
type: "circle",
|
|
17395
18721
|
x: n.x,
|
|
@@ -17397,28 +18723,127 @@ var init_BiologyCanvas = __esm({
|
|
|
17397
18723
|
radius: n.radius ?? 16,
|
|
17398
18724
|
color: n.color ?? "#16a34a",
|
|
17399
18725
|
fill: `${n.color ?? "#16a34a"}33`,
|
|
17400
|
-
id: n.id
|
|
18726
|
+
id: n.id,
|
|
18727
|
+
...muted ? { opacity: 0.35 } : {}
|
|
17401
18728
|
});
|
|
18729
|
+
if (state === "highlight") {
|
|
18730
|
+
out.push({
|
|
18731
|
+
type: "circle",
|
|
18732
|
+
x: n.x,
|
|
18733
|
+
y: n.y,
|
|
18734
|
+
radius: (n.radius ?? 16) + 4,
|
|
18735
|
+
color: "#f59e0b",
|
|
18736
|
+
lineWidth: 2
|
|
18737
|
+
});
|
|
18738
|
+
}
|
|
17402
18739
|
if (n.label) {
|
|
17403
18740
|
out.push({
|
|
17404
18741
|
type: "text",
|
|
17405
18742
|
x: n.x,
|
|
17406
18743
|
y: n.y + (n.radius ?? 16) + 14,
|
|
17407
18744
|
text: n.label,
|
|
18745
|
+
...muted ? { opacity: 0.35 } : {},
|
|
17408
18746
|
color: "#111827",
|
|
17409
18747
|
fontSize: 12,
|
|
17410
18748
|
align: "center"
|
|
17411
18749
|
});
|
|
17412
18750
|
}
|
|
17413
18751
|
}
|
|
18752
|
+
const stageCount = stages.length;
|
|
18753
|
+
if (stageCount > 0) {
|
|
18754
|
+
if (stageStyle === "ring") {
|
|
18755
|
+
const cx = width / 2;
|
|
18756
|
+
const cy = height / 2;
|
|
18757
|
+
const R = Math.min(width, height) / 2 - 48;
|
|
18758
|
+
const ringPoints = [];
|
|
18759
|
+
for (let i = 0; i < stageCount; i++) {
|
|
18760
|
+
const angleRad = (-90 + 360 * i / stageCount) * Math.PI / 180;
|
|
18761
|
+
ringPoints.push({ x: cx + R * Math.cos(angleRad), y: cy + R * Math.sin(angleRad) });
|
|
18762
|
+
}
|
|
18763
|
+
if (stageCount >= 2) {
|
|
18764
|
+
for (let i = 0; i < stageCount - 1; i++) {
|
|
18765
|
+
const p1 = ringPoints[i];
|
|
18766
|
+
const p2 = ringPoints[i + 1];
|
|
18767
|
+
const dx = p2.x - p1.x;
|
|
18768
|
+
const dy = p2.y - p1.y;
|
|
18769
|
+
const dist = Math.max(1e-6, Math.hypot(dx, dy));
|
|
18770
|
+
const ux = dx / dist;
|
|
18771
|
+
const uy = dy / dist;
|
|
18772
|
+
out.push({
|
|
18773
|
+
type: "arrow",
|
|
18774
|
+
x1: p1.x + ux * 46,
|
|
18775
|
+
y1: p1.y + uy * 46,
|
|
18776
|
+
x2: p2.x - ux * 46,
|
|
18777
|
+
y2: p2.y - uy * 46,
|
|
18778
|
+
color: "#94a3b8"
|
|
18779
|
+
});
|
|
18780
|
+
}
|
|
18781
|
+
}
|
|
18782
|
+
for (let i = 0; i < stageCount; i++) {
|
|
18783
|
+
const stage = stages[i];
|
|
18784
|
+
const state = stage.state ?? "pending";
|
|
18785
|
+
const fill = stage.color ?? BIO_STAGE_FILL[state];
|
|
18786
|
+
const w = Math.max(26, Math.min(84, stage.label.length * 6 + 10));
|
|
18787
|
+
const h = 18;
|
|
18788
|
+
const p = ringPoints[i];
|
|
18789
|
+
out.push({ type: "rect", x: p.x - w / 2, y: p.y - h / 2, width: w, height: h, color: fill, fill });
|
|
18790
|
+
out.push({ type: "text", x: p.x, y: p.y, text: stage.label, color: BIO_STAGE_TEXT[state], fontSize: 10, align: "center" });
|
|
18791
|
+
}
|
|
18792
|
+
} else {
|
|
18793
|
+
const stripY = height - 32;
|
|
18794
|
+
const slotW = (width - 16) / stageCount;
|
|
18795
|
+
const chipGeoms = [];
|
|
18796
|
+
for (let i = 0; i < stageCount; i++) {
|
|
18797
|
+
chipGeoms.push({ x: 8 + i * slotW + 5, w: slotW - 10 });
|
|
18798
|
+
}
|
|
18799
|
+
for (let i = 0; i < stageCount - 1; i++) {
|
|
18800
|
+
const midY = stripY + 13;
|
|
18801
|
+
out.push({
|
|
18802
|
+
type: "arrow",
|
|
18803
|
+
x1: chipGeoms[i].x + chipGeoms[i].w,
|
|
18804
|
+
y1: midY,
|
|
18805
|
+
x2: chipGeoms[i + 1].x,
|
|
18806
|
+
y2: midY,
|
|
18807
|
+
color: "#94a3b8"
|
|
18808
|
+
});
|
|
18809
|
+
}
|
|
18810
|
+
for (let i = 0; i < stageCount; i++) {
|
|
18811
|
+
const stage = stages[i];
|
|
18812
|
+
const state = stage.state ?? "pending";
|
|
18813
|
+
const fill = stage.color ?? BIO_STAGE_FILL[state];
|
|
18814
|
+
const g = chipGeoms[i];
|
|
18815
|
+
out.push({ type: "rect", x: g.x, y: stripY, width: g.w, height: 26, color: fill, fill });
|
|
18816
|
+
out.push({
|
|
18817
|
+
type: "text",
|
|
18818
|
+
x: g.x + g.w / 2,
|
|
18819
|
+
y: stripY + 13,
|
|
18820
|
+
text: stage.label,
|
|
18821
|
+
color: BIO_STAGE_TEXT[state],
|
|
18822
|
+
fontSize: 10,
|
|
18823
|
+
align: "center"
|
|
18824
|
+
});
|
|
18825
|
+
}
|
|
18826
|
+
}
|
|
18827
|
+
}
|
|
17414
18828
|
out.push(...shapes);
|
|
17415
18829
|
return out;
|
|
17416
|
-
}, [nodes, edges, shapes]);
|
|
18830
|
+
}, [nodes, edges, compartments, bands, stages, stageStyle, helix, shapes, width, height]);
|
|
17417
18831
|
const drawables3D = React94.useMemo(() => {
|
|
17418
18832
|
if (mode !== "3d") return [];
|
|
17419
18833
|
if (shapes.length > 0) {
|
|
17420
18834
|
biologyLog.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
|
|
17421
18835
|
}
|
|
18836
|
+
if (compartments.length > 0 || bands.length > 0 || stages.length > 0 || helix) {
|
|
18837
|
+
biologyLog.debug("2D-only families ignored in 3D mode (pixel-authored 2D vocabulary)", {
|
|
18838
|
+
compartments: compartments.length,
|
|
18839
|
+
bands: bands.length,
|
|
18840
|
+
stages: stages.length,
|
|
18841
|
+
helix: helix != null
|
|
18842
|
+
});
|
|
18843
|
+
}
|
|
18844
|
+
if (animate) {
|
|
18845
|
+
biologyLog.debug("animate ignored in 3D mode (motion is entity-state driven)");
|
|
18846
|
+
}
|
|
17422
18847
|
const out = [];
|
|
17423
18848
|
const labelColor = labelColorForBackground(backgroundColor);
|
|
17424
18849
|
const nodeById = /* @__PURE__ */ new Map();
|
|
@@ -17452,15 +18877,21 @@ var init_BiologyCanvas = __esm({
|
|
|
17452
18877
|
out.push(billboardLabel(n.label, n.x, n.y, nz + radius, { color: labelColor }));
|
|
17453
18878
|
}
|
|
17454
18879
|
}
|
|
18880
|
+
if (helix3d) {
|
|
18881
|
+
out.push(...helixDrawables(helix3d, { labelColor }));
|
|
18882
|
+
}
|
|
17455
18883
|
return out;
|
|
17456
|
-
}, [mode, nodes, edges, shapes, backgroundColor]);
|
|
18884
|
+
}, [mode, nodes, edges, shapes, compartments, bands, stages, helix, helix3d, animate, backgroundColor]);
|
|
17457
18885
|
const nodeIndexById = React94.useMemo(() => {
|
|
17458
18886
|
const m = /* @__PURE__ */ new Map();
|
|
18887
|
+
(helix3d?.rungs ?? []).forEach((rung, i) => {
|
|
18888
|
+
if (rung.id) m.set(rung.id, i);
|
|
18889
|
+
});
|
|
17459
18890
|
nodes.forEach((n, i) => {
|
|
17460
18891
|
if (n.id) m.set(n.id, i);
|
|
17461
18892
|
});
|
|
17462
18893
|
return m;
|
|
17463
|
-
}, [nodes]);
|
|
18894
|
+
}, [nodes, helix3d]);
|
|
17464
18895
|
if (mode === "3d") {
|
|
17465
18896
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
17466
18897
|
LearningScene3D,
|
|
@@ -17492,6 +18923,8 @@ var init_BiologyCanvas = __esm({
|
|
|
17492
18923
|
height,
|
|
17493
18924
|
backgroundColor,
|
|
17494
18925
|
shapes: derivedShapes,
|
|
18926
|
+
readouts,
|
|
18927
|
+
traces,
|
|
17495
18928
|
interactive: interactive ?? false,
|
|
17496
18929
|
animate,
|
|
17497
18930
|
onShapeClick,
|
|
@@ -24341,7 +25774,7 @@ function bondPerpendicular(a, b) {
|
|
|
24341
25774
|
if (len < 1e-6) return [1, 0, 0];
|
|
24342
25775
|
return [px / len, py / len, 0];
|
|
24343
25776
|
}
|
|
24344
|
-
var chemistryLog, ChemistryCanvas;
|
|
25777
|
+
var chemistryLog, CHEM_BOND_STATE_COLOR, LONE_PAIR_ANGLES, ChemistryCanvas;
|
|
24345
25778
|
var init_ChemistryCanvas = __esm({
|
|
24346
25779
|
"components/learning/molecules/ChemistryCanvas.tsx"() {
|
|
24347
25780
|
"use client";
|
|
@@ -24350,6 +25783,13 @@ var init_ChemistryCanvas = __esm({
|
|
|
24350
25783
|
init_LearningCanvas();
|
|
24351
25784
|
init_learningScene3D();
|
|
24352
25785
|
chemistryLog = logger.createLogger("almadar:ui:chemistry-canvas");
|
|
25786
|
+
CHEM_BOND_STATE_COLOR = {
|
|
25787
|
+
default: "#6b7280",
|
|
25788
|
+
forming: "#16a34a",
|
|
25789
|
+
breaking: "#dc2626",
|
|
25790
|
+
highlight: "#f59e0b"
|
|
25791
|
+
};
|
|
25792
|
+
LONE_PAIR_ANGLES = [-90, 0, 90, 180];
|
|
24353
25793
|
ChemistryCanvas = ({
|
|
24354
25794
|
className,
|
|
24355
25795
|
width = 600,
|
|
@@ -24363,7 +25803,14 @@ var init_ChemistryCanvas = __esm({
|
|
|
24363
25803
|
atoms = [],
|
|
24364
25804
|
bonds = [],
|
|
24365
25805
|
arrows = [],
|
|
25806
|
+
bondStyle = "thick",
|
|
25807
|
+
containers = [],
|
|
25808
|
+
equation,
|
|
25809
|
+
equationColor,
|
|
25810
|
+
lattice3d,
|
|
24366
25811
|
shapes = [],
|
|
25812
|
+
readouts,
|
|
25813
|
+
traces,
|
|
24367
25814
|
showGrid,
|
|
24368
25815
|
shadows,
|
|
24369
25816
|
interactive,
|
|
@@ -24378,21 +25825,118 @@ var init_ChemistryCanvas = __esm({
|
|
|
24378
25825
|
for (const a of atoms) {
|
|
24379
25826
|
if (a.id) atomById.set(a.id, a);
|
|
24380
25827
|
}
|
|
25828
|
+
for (const c of containers) {
|
|
25829
|
+
const color = c.color ?? "#64748b";
|
|
25830
|
+
if (c.level != null) {
|
|
25831
|
+
const lv = c.level;
|
|
25832
|
+
out.push({
|
|
25833
|
+
type: "rect",
|
|
25834
|
+
x: c.x + 1,
|
|
25835
|
+
y: c.y + c.height * (1 - lv),
|
|
25836
|
+
width: c.width - 2,
|
|
25837
|
+
height: c.height * lv - 1,
|
|
25838
|
+
color: c.levelColor ?? "#60a5fa",
|
|
25839
|
+
fill: c.levelColor ?? "#60a5fa",
|
|
25840
|
+
opacity: 0.5
|
|
25841
|
+
});
|
|
25842
|
+
}
|
|
25843
|
+
out.push({
|
|
25844
|
+
type: "rect",
|
|
25845
|
+
x: c.x,
|
|
25846
|
+
y: c.y,
|
|
25847
|
+
width: c.width,
|
|
25848
|
+
height: c.height,
|
|
25849
|
+
color,
|
|
25850
|
+
fill: c.fill,
|
|
25851
|
+
lineWidth: c.lineWidth ?? 2
|
|
25852
|
+
});
|
|
25853
|
+
const divider = c.divider ?? "none";
|
|
25854
|
+
if (divider !== "none") {
|
|
25855
|
+
out.push({
|
|
25856
|
+
type: "line",
|
|
25857
|
+
x1: c.x + c.width / 2,
|
|
25858
|
+
y1: c.y,
|
|
25859
|
+
x2: c.x + c.width / 2,
|
|
25860
|
+
y2: c.y + c.height,
|
|
25861
|
+
color: c.dividerColor ?? color,
|
|
25862
|
+
...divider === "dashed" || divider === "dotted" ? { dash: divider } : {}
|
|
25863
|
+
});
|
|
25864
|
+
}
|
|
25865
|
+
if (c.leftLabel) {
|
|
25866
|
+
out.push({
|
|
25867
|
+
type: "text",
|
|
25868
|
+
x: c.x + c.width * 0.25,
|
|
25869
|
+
y: c.y + 12,
|
|
25870
|
+
text: c.leftLabel,
|
|
25871
|
+
color: "#374151",
|
|
25872
|
+
fontSize: 11,
|
|
25873
|
+
align: "center"
|
|
25874
|
+
});
|
|
25875
|
+
}
|
|
25876
|
+
if (c.rightLabel) {
|
|
25877
|
+
out.push({
|
|
25878
|
+
type: "text",
|
|
25879
|
+
x: c.x + c.width * 0.75,
|
|
25880
|
+
y: c.y + 12,
|
|
25881
|
+
text: c.rightLabel,
|
|
25882
|
+
color: "#374151",
|
|
25883
|
+
fontSize: 11,
|
|
25884
|
+
align: "center"
|
|
25885
|
+
});
|
|
25886
|
+
}
|
|
25887
|
+
if (c.label) {
|
|
25888
|
+
out.push({
|
|
25889
|
+
type: "text",
|
|
25890
|
+
x: c.x + c.width / 2,
|
|
25891
|
+
y: c.y + c.height + 12,
|
|
25892
|
+
text: c.label,
|
|
25893
|
+
color: "#111827",
|
|
25894
|
+
fontSize: 12,
|
|
25895
|
+
align: "center"
|
|
25896
|
+
});
|
|
25897
|
+
}
|
|
25898
|
+
}
|
|
24381
25899
|
for (const b of bonds) {
|
|
24382
25900
|
const a = atomById.get(b.from);
|
|
24383
25901
|
const c = atomById.get(b.to);
|
|
24384
25902
|
if (!a || !c) continue;
|
|
24385
|
-
const
|
|
24386
|
-
const
|
|
24387
|
-
|
|
24388
|
-
|
|
24389
|
-
|
|
24390
|
-
|
|
24391
|
-
|
|
24392
|
-
|
|
24393
|
-
|
|
24394
|
-
|
|
24395
|
-
|
|
25903
|
+
const state = b.state ?? "default";
|
|
25904
|
+
const color = b.color ?? CHEM_BOND_STATE_COLOR[state];
|
|
25905
|
+
const dash = state === "forming" || state === "breaking" ? "dashed" : void 0;
|
|
25906
|
+
if (bondStyle === "parallel") {
|
|
25907
|
+
const dx = c.x - a.x;
|
|
25908
|
+
const dy = c.y - a.y;
|
|
25909
|
+
const dist = Math.max(1e-6, Math.hypot(dx, dy));
|
|
25910
|
+
const ux = dx / dist;
|
|
25911
|
+
const uy = dy / dist;
|
|
25912
|
+
const px = -uy;
|
|
25913
|
+
const py = ux;
|
|
25914
|
+
const offsets = b.type === "double" ? [-3, 3] : b.type === "triple" ? [-4, 0, 4] : [0];
|
|
25915
|
+
for (const off of offsets) {
|
|
25916
|
+
out.push({
|
|
25917
|
+
type: "line",
|
|
25918
|
+
x1: a.x + px * off,
|
|
25919
|
+
y1: a.y + py * off,
|
|
25920
|
+
x2: c.x + px * off,
|
|
25921
|
+
y2: c.y + py * off,
|
|
25922
|
+
color,
|
|
25923
|
+
lineWidth: 2,
|
|
25924
|
+
...dash ? { dash } : {}
|
|
25925
|
+
});
|
|
25926
|
+
}
|
|
25927
|
+
} else {
|
|
25928
|
+
const strokeWidth = b.type === "double" ? 4 : b.type === "triple" ? 6 : 2;
|
|
25929
|
+
out.push({
|
|
25930
|
+
type: "line",
|
|
25931
|
+
x1: a.x,
|
|
25932
|
+
y1: a.y,
|
|
25933
|
+
x2: c.x,
|
|
25934
|
+
y2: c.y,
|
|
25935
|
+
color,
|
|
25936
|
+
lineWidth: strokeWidth,
|
|
25937
|
+
...dash ? { dash } : {}
|
|
25938
|
+
});
|
|
25939
|
+
}
|
|
24396
25940
|
}
|
|
24397
25941
|
for (const a of arrows) {
|
|
24398
25942
|
const angle = (a.angle ?? 0) * (Math.PI / 180);
|
|
@@ -24441,15 +25985,62 @@ var init_ChemistryCanvas = __esm({
|
|
|
24441
25985
|
align: "center"
|
|
24442
25986
|
});
|
|
24443
25987
|
}
|
|
25988
|
+
const r2 = a.radius ?? 14;
|
|
25989
|
+
if (a.charge) {
|
|
25990
|
+
out.push({
|
|
25991
|
+
type: "text",
|
|
25992
|
+
x: a.x + r2 * 0.85,
|
|
25993
|
+
y: a.y - r2 * 0.85,
|
|
25994
|
+
text: a.charge,
|
|
25995
|
+
color: "#111827",
|
|
25996
|
+
fontSize: 9,
|
|
25997
|
+
align: "left"
|
|
25998
|
+
});
|
|
25999
|
+
}
|
|
26000
|
+
const lonePairs = Math.max(0, Math.min(4, a.lonePairs ?? 0));
|
|
26001
|
+
for (let k = 0; k < lonePairs; k++) {
|
|
26002
|
+
const angleRad = LONE_PAIR_ANGLES[k] * Math.PI / 180;
|
|
26003
|
+
const cx = a.x + (r2 + 6) * Math.cos(angleRad);
|
|
26004
|
+
const cy = a.y + (r2 + 6) * Math.sin(angleRad);
|
|
26005
|
+
const perpX = -Math.sin(angleRad);
|
|
26006
|
+
const perpY = Math.cos(angleRad);
|
|
26007
|
+
for (const sign of [1, -1]) {
|
|
26008
|
+
out.push({
|
|
26009
|
+
type: "circle",
|
|
26010
|
+
x: cx + perpX * 2.5 * sign,
|
|
26011
|
+
y: cy + perpY * 2.5 * sign,
|
|
26012
|
+
radius: 1.5,
|
|
26013
|
+
color: "#374151",
|
|
26014
|
+
fill: "#374151"
|
|
26015
|
+
});
|
|
26016
|
+
}
|
|
26017
|
+
}
|
|
26018
|
+
}
|
|
26019
|
+
if (equation) {
|
|
26020
|
+
out.push({
|
|
26021
|
+
type: "text",
|
|
26022
|
+
x: width / 2,
|
|
26023
|
+
y: 14,
|
|
26024
|
+
text: equation,
|
|
26025
|
+
color: equationColor ?? "#111827",
|
|
26026
|
+
fontSize: 13,
|
|
26027
|
+
align: "center"
|
|
26028
|
+
});
|
|
24444
26029
|
}
|
|
24445
26030
|
out.push(...shapes);
|
|
24446
26031
|
return out;
|
|
24447
|
-
}, [atoms, bonds, arrows, shapes]);
|
|
26032
|
+
}, [atoms, bonds, arrows, bondStyle, containers, equation, equationColor, shapes, width]);
|
|
24448
26033
|
const drawables3D = React94.useMemo(() => {
|
|
24449
26034
|
if (mode !== "3d") return [];
|
|
24450
26035
|
if (shapes.length > 0) {
|
|
24451
26036
|
chemistryLog.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
|
|
24452
26037
|
}
|
|
26038
|
+
if (containers.length > 0) {
|
|
26039
|
+
chemistryLog.debug("containers ignored in 3D mode (pixel-authored 2D vocabulary)", { count: containers.length });
|
|
26040
|
+
}
|
|
26041
|
+
if (animate) {
|
|
26042
|
+
chemistryLog.debug("animate ignored in 3D mode (motion is entity-state driven)");
|
|
26043
|
+
}
|
|
24453
26044
|
const out = [];
|
|
24454
26045
|
const labelColor = labelColorForBackground(backgroundColor);
|
|
24455
26046
|
const atomById = /* @__PURE__ */ new Map();
|
|
@@ -24496,8 +26087,11 @@ var init_ChemistryCanvas = __esm({
|
|
|
24496
26087
|
out.push(billboardLabel(a.element, a.x, a.y, az + radius, { color: labelColor }));
|
|
24497
26088
|
}
|
|
24498
26089
|
}
|
|
26090
|
+
if (lattice3d) {
|
|
26091
|
+
out.push(...latticeDrawables(lattice3d, { labelColor }));
|
|
26092
|
+
}
|
|
24499
26093
|
return out;
|
|
24500
|
-
}, [mode, atoms, bonds, arrows, shapes, backgroundColor]);
|
|
26094
|
+
}, [mode, atoms, bonds, arrows, shapes, containers, lattice3d, animate, backgroundColor]);
|
|
24501
26095
|
const atomIndexById = React94.useMemo(() => {
|
|
24502
26096
|
const m = /* @__PURE__ */ new Map();
|
|
24503
26097
|
atoms.forEach((a, i) => {
|
|
@@ -24536,6 +26130,8 @@ var init_ChemistryCanvas = __esm({
|
|
|
24536
26130
|
height,
|
|
24537
26131
|
backgroundColor,
|
|
24538
26132
|
shapes: derivedShapes,
|
|
26133
|
+
readouts,
|
|
26134
|
+
traces,
|
|
24539
26135
|
interactive: interactive ?? false,
|
|
24540
26136
|
animate,
|
|
24541
26137
|
onShapeClick,
|
|
@@ -30725,6 +32321,10 @@ var init_ProgressDots = __esm({
|
|
|
30725
32321
|
ProgressDots.displayName = "ProgressDots";
|
|
30726
32322
|
}
|
|
30727
32323
|
});
|
|
32324
|
+
function formatTick(v) {
|
|
32325
|
+
if (Number.isInteger(v)) return String(v);
|
|
32326
|
+
return v.toFixed(2).replace(/0+$/, "").replace(/\.$/, "");
|
|
32327
|
+
}
|
|
30728
32328
|
var MathCanvas;
|
|
30729
32329
|
var init_MathCanvas = __esm({
|
|
30730
32330
|
"components/learning/molecules/MathCanvas.tsx"() {
|
|
@@ -30744,10 +32344,19 @@ var init_MathCanvas = __esm({
|
|
|
30744
32344
|
showAxes = true,
|
|
30745
32345
|
showGrid = true,
|
|
30746
32346
|
gridStep = 1,
|
|
32347
|
+
showTickLabels = false,
|
|
32348
|
+
showCurveLabels = false,
|
|
30747
32349
|
curves = [],
|
|
30748
32350
|
points = [],
|
|
30749
32351
|
vectors = [],
|
|
32352
|
+
regions = [],
|
|
32353
|
+
bars = [],
|
|
32354
|
+
guides = [],
|
|
32355
|
+
angles = [],
|
|
32356
|
+
hops = [],
|
|
30750
32357
|
shapes = [],
|
|
32358
|
+
readouts,
|
|
32359
|
+
traces,
|
|
30751
32360
|
interactive = false,
|
|
30752
32361
|
animate = false,
|
|
30753
32362
|
onShapeClick,
|
|
@@ -30761,6 +32370,8 @@ var init_MathCanvas = __esm({
|
|
|
30761
32370
|
const plotH = height - margin * 2;
|
|
30762
32371
|
const mapX = (x) => margin + (x - xMin) / (xMax - xMin) * plotW;
|
|
30763
32372
|
const mapY = (y) => height - (margin + (y - yMin) / (yMax - yMin) * plotH);
|
|
32373
|
+
const xAxisY = Math.max(margin, Math.min(height - margin, mapY(0)));
|
|
32374
|
+
const yAxisX = Math.max(margin, Math.min(width - margin, mapX(0)));
|
|
30764
32375
|
if (showGrid) {
|
|
30765
32376
|
for (let x = Math.ceil(xMin / gridStep) * gridStep; x <= xMax; x += gridStep) {
|
|
30766
32377
|
const px = mapX(x);
|
|
@@ -30771,14 +32382,99 @@ var init_MathCanvas = __esm({
|
|
|
30771
32382
|
out.push({ type: "line", x1: margin, y1: py, x2: width - margin, y2: py, color: "#e5e7eb", lineWidth: 1 });
|
|
30772
32383
|
}
|
|
30773
32384
|
}
|
|
32385
|
+
if (showTickLabels) {
|
|
32386
|
+
const labelEveryX = Math.max(1, Math.ceil((xMax - xMin) / gridStep / Math.floor(plotW / 40)));
|
|
32387
|
+
let kx = 0;
|
|
32388
|
+
for (let x = Math.ceil(xMin / gridStep) * gridStep; x <= xMax; x += gridStep, kx++) {
|
|
32389
|
+
if (kx % labelEveryX === 0 && x !== 0) {
|
|
32390
|
+
out.push({ type: "text", x: mapX(x), y: xAxisY + 12, text: formatTick(x), color: "#6b7280", fontSize: 10, align: "center" });
|
|
32391
|
+
}
|
|
32392
|
+
}
|
|
32393
|
+
const labelEveryY = Math.max(1, Math.ceil((yMax - yMin) / gridStep / Math.floor(plotH / 28)));
|
|
32394
|
+
let ky = 0;
|
|
32395
|
+
for (let y = Math.ceil(yMin / gridStep) * gridStep; y <= yMax; y += gridStep, ky++) {
|
|
32396
|
+
if (ky % labelEveryY === 0 && y !== 0) {
|
|
32397
|
+
out.push({ type: "text", x: yAxisX - 6, y: mapY(y), text: formatTick(y), color: "#6b7280", fontSize: 10, align: "right" });
|
|
32398
|
+
}
|
|
32399
|
+
}
|
|
32400
|
+
if (xMin <= 0 && xMax >= 0 && yMin <= 0 && yMax >= 0) {
|
|
32401
|
+
out.push({ type: "text", x: yAxisX - 6, y: xAxisY + 12, text: "0", color: "#6b7280", fontSize: 10, align: "right" });
|
|
32402
|
+
}
|
|
32403
|
+
}
|
|
32404
|
+
for (const region of regions) {
|
|
32405
|
+
if (!region.samples || region.samples.length === 0) continue;
|
|
32406
|
+
const baseline = region.baseline ?? 0;
|
|
32407
|
+
const clampedPoint = (p) => ({
|
|
32408
|
+
x: mapX(Math.min(xMax, Math.max(xMin, p.x))),
|
|
32409
|
+
y: mapY(Math.min(yMax, Math.max(yMin, p.y)))
|
|
32410
|
+
});
|
|
32411
|
+
const upper = region.samples.map(clampedPoint);
|
|
32412
|
+
const first = region.samples[0];
|
|
32413
|
+
const last = region.samples[region.samples.length - 1];
|
|
32414
|
+
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 })];
|
|
32415
|
+
const color = region.color ?? "#2563eb";
|
|
32416
|
+
out.push({
|
|
32417
|
+
type: "polygon",
|
|
32418
|
+
points: [...upper, ...closing],
|
|
32419
|
+
fill: color,
|
|
32420
|
+
color,
|
|
32421
|
+
opacity: region.opacity ?? 0.2,
|
|
32422
|
+
lineWidth: 1
|
|
32423
|
+
});
|
|
32424
|
+
if (region.label) {
|
|
32425
|
+
const mid = Math.floor(region.samples.length / 2);
|
|
32426
|
+
out.push({
|
|
32427
|
+
type: "text",
|
|
32428
|
+
x: mapX((first.x + last.x) / 2),
|
|
32429
|
+
y: (mapY(region.samples[mid].y) + mapY(baseline)) / 2,
|
|
32430
|
+
text: region.label,
|
|
32431
|
+
color: "#111827",
|
|
32432
|
+
fontSize: 11
|
|
32433
|
+
});
|
|
32434
|
+
}
|
|
32435
|
+
}
|
|
32436
|
+
for (const bar of bars) {
|
|
32437
|
+
if (bar.x + bar.width < xMin || bar.x > xMax) continue;
|
|
32438
|
+
const y0 = bar.y0 ?? 0;
|
|
32439
|
+
const color = bar.color ?? "#93c5fd";
|
|
32440
|
+
out.push({
|
|
32441
|
+
type: "rect",
|
|
32442
|
+
x: mapX(bar.x),
|
|
32443
|
+
y: mapY(Math.max(y0, bar.y1)),
|
|
32444
|
+
width: mapX(bar.x + bar.width) - mapX(bar.x),
|
|
32445
|
+
height: Math.abs(mapY(bar.y1) - mapY(y0)),
|
|
32446
|
+
color,
|
|
32447
|
+
fill: color,
|
|
32448
|
+
opacity: bar.opacity ?? 0.5,
|
|
32449
|
+
lineWidth: 1
|
|
32450
|
+
});
|
|
32451
|
+
}
|
|
30774
32452
|
if (showAxes) {
|
|
30775
|
-
const xAxisY = Math.max(margin, Math.min(height - margin, mapY(0)));
|
|
30776
|
-
const yAxisX = Math.max(margin, Math.min(width - margin, mapX(0)));
|
|
30777
32453
|
out.push({ type: "line", x1: margin, y1: xAxisY, x2: width - margin, y2: xAxisY, color: "#374151", lineWidth: 2 });
|
|
30778
32454
|
out.push({ type: "line", x1: yAxisX, y1: margin, x2: yAxisX, y2: height - margin, color: "#374151", lineWidth: 2 });
|
|
30779
32455
|
}
|
|
32456
|
+
for (const guide of guides) {
|
|
32457
|
+
const color = guide.color ?? "#9ca3af";
|
|
32458
|
+
const dash = guide.dash ?? "dashed";
|
|
32459
|
+
if (guide.kind === "vline") {
|
|
32460
|
+
if (guide.at < xMin || guide.at > xMax) continue;
|
|
32461
|
+
const px = mapX(guide.at);
|
|
32462
|
+
out.push({ type: "line", x1: px, y1: margin, x2: px, y2: height - margin, color, dash });
|
|
32463
|
+
if (guide.label) {
|
|
32464
|
+
out.push({ type: "text", x: px + 4, y: margin + 10, text: guide.label, color: "#111827", fontSize: 11 });
|
|
32465
|
+
}
|
|
32466
|
+
} else {
|
|
32467
|
+
if (guide.at < yMin || guide.at > yMax) continue;
|
|
32468
|
+
const py = mapY(guide.at);
|
|
32469
|
+
out.push({ type: "line", x1: margin, y1: py, x2: width - margin, y2: py, color, dash });
|
|
32470
|
+
if (guide.label) {
|
|
32471
|
+
out.push({ type: "text", x: width - margin - 4, y: py - 8, text: guide.label, color: "#111827", fontSize: 11, align: "right" });
|
|
32472
|
+
}
|
|
32473
|
+
}
|
|
32474
|
+
}
|
|
30780
32475
|
for (const curve of curves) {
|
|
30781
32476
|
if (!curve.samples || curve.samples.length < 2) continue;
|
|
32477
|
+
let lastInRange;
|
|
30782
32478
|
for (let i = 1; i < curve.samples.length; i++) {
|
|
30783
32479
|
const a = curve.samples[i - 1];
|
|
30784
32480
|
const b = curve.samples[i];
|
|
@@ -30790,19 +32486,97 @@ var init_MathCanvas = __esm({
|
|
|
30790
32486
|
x2: mapX(b.x),
|
|
30791
32487
|
y2: mapY(b.y),
|
|
30792
32488
|
color: curve.color ?? "#2563eb",
|
|
30793
|
-
lineWidth: 2
|
|
32489
|
+
lineWidth: 2,
|
|
32490
|
+
dash: curve.dash
|
|
32491
|
+
});
|
|
32492
|
+
lastInRange = b;
|
|
32493
|
+
}
|
|
32494
|
+
if (showCurveLabels && curve.label && lastInRange) {
|
|
32495
|
+
out.push({
|
|
32496
|
+
type: "text",
|
|
32497
|
+
x: mapX(lastInRange.x) + 6,
|
|
32498
|
+
y: mapY(lastInRange.y) - 6,
|
|
32499
|
+
text: curve.label,
|
|
32500
|
+
color: curve.color ?? "#2563eb",
|
|
32501
|
+
fontSize: 11
|
|
32502
|
+
});
|
|
32503
|
+
}
|
|
32504
|
+
}
|
|
32505
|
+
for (const hop of hops) {
|
|
32506
|
+
const x1 = mapX(hop.from);
|
|
32507
|
+
const x2 = mapX(hop.to);
|
|
32508
|
+
const peak = Math.min(36, plotH * 0.3);
|
|
32509
|
+
const color = hop.color ?? "#7c3aed";
|
|
32510
|
+
out.push({
|
|
32511
|
+
type: "ellipse",
|
|
32512
|
+
x: (x1 + x2) / 2,
|
|
32513
|
+
y: xAxisY,
|
|
32514
|
+
width: Math.abs(x2 - x1),
|
|
32515
|
+
height: 2 * peak,
|
|
32516
|
+
startAngle: 180,
|
|
32517
|
+
endAngle: 360,
|
|
32518
|
+
color
|
|
32519
|
+
});
|
|
32520
|
+
const s = Math.sign(hop.to - hop.from);
|
|
32521
|
+
out.push({
|
|
32522
|
+
type: "polygon",
|
|
32523
|
+
points: [
|
|
32524
|
+
{ x: x2, y: xAxisY },
|
|
32525
|
+
{ x: x2 - 4 * s, y: xAxisY - 7 },
|
|
32526
|
+
{ x: x2 + 2 * s, y: xAxisY - 7 }
|
|
32527
|
+
],
|
|
32528
|
+
fill: color,
|
|
32529
|
+
color
|
|
32530
|
+
});
|
|
32531
|
+
if (hop.label) {
|
|
32532
|
+
out.push({
|
|
32533
|
+
type: "text",
|
|
32534
|
+
x: (x1 + x2) / 2,
|
|
32535
|
+
y: xAxisY - peak - 8,
|
|
32536
|
+
text: hop.label,
|
|
32537
|
+
color: "#111827",
|
|
32538
|
+
fontSize: 10,
|
|
32539
|
+
align: "center"
|
|
32540
|
+
});
|
|
32541
|
+
}
|
|
32542
|
+
}
|
|
32543
|
+
for (const angle of angles) {
|
|
32544
|
+
const radius = angle.radius ?? 0.8;
|
|
32545
|
+
const color = angle.color ?? "#0ea5e9";
|
|
32546
|
+
out.push({
|
|
32547
|
+
type: "ellipse",
|
|
32548
|
+
x: mapX(angle.x),
|
|
32549
|
+
y: mapY(angle.y),
|
|
32550
|
+
width: 2 * radius * plotW / (xMax - xMin),
|
|
32551
|
+
height: 2 * radius * plotH / (yMax - yMin),
|
|
32552
|
+
startAngle: -angle.to,
|
|
32553
|
+
endAngle: -angle.from,
|
|
32554
|
+
color
|
|
32555
|
+
});
|
|
32556
|
+
if (angle.label) {
|
|
32557
|
+
const mid = (angle.from + angle.to) / 2;
|
|
32558
|
+
const rad = mid * Math.PI / 180;
|
|
32559
|
+
out.push({
|
|
32560
|
+
type: "text",
|
|
32561
|
+
x: mapX(angle.x + 1.35 * radius * Math.cos(rad)),
|
|
32562
|
+
y: mapY(angle.y + 1.35 * radius * Math.sin(rad)),
|
|
32563
|
+
text: angle.label,
|
|
32564
|
+
color: "#111827",
|
|
32565
|
+
fontSize: 11,
|
|
32566
|
+
align: "center"
|
|
30794
32567
|
});
|
|
30795
32568
|
}
|
|
30796
32569
|
}
|
|
30797
32570
|
for (const p of points) {
|
|
30798
32571
|
if (p.x < xMin || p.x > xMax || p.y < yMin || p.y > yMax) continue;
|
|
32572
|
+
const isOpen = p.style === "open";
|
|
30799
32573
|
out.push({
|
|
30800
32574
|
type: "circle",
|
|
30801
32575
|
x: mapX(p.x),
|
|
30802
32576
|
y: mapY(p.y),
|
|
30803
32577
|
radius: p.radius ?? 4,
|
|
30804
32578
|
color: p.color ?? "#dc2626",
|
|
30805
|
-
fill: p.color ?? "#dc2626"
|
|
32579
|
+
fill: isOpen ? "#ffffff" : p.color ?? "#dc2626"
|
|
30806
32580
|
});
|
|
30807
32581
|
if (p.label) {
|
|
30808
32582
|
out.push({ type: "text", x: mapX(p.x) + 8, y: mapY(p.y) - 8, text: p.label, color: "#111827", fontSize: 12 });
|
|
@@ -30821,7 +32595,28 @@ var init_MathCanvas = __esm({
|
|
|
30821
32595
|
}
|
|
30822
32596
|
out.push(...shapes);
|
|
30823
32597
|
return out;
|
|
30824
|
-
}, [
|
|
32598
|
+
}, [
|
|
32599
|
+
width,
|
|
32600
|
+
height,
|
|
32601
|
+
xMin,
|
|
32602
|
+
xMax,
|
|
32603
|
+
yMin,
|
|
32604
|
+
yMax,
|
|
32605
|
+
showAxes,
|
|
32606
|
+
showGrid,
|
|
32607
|
+
gridStep,
|
|
32608
|
+
showTickLabels,
|
|
32609
|
+
showCurveLabels,
|
|
32610
|
+
curves,
|
|
32611
|
+
points,
|
|
32612
|
+
vectors,
|
|
32613
|
+
regions,
|
|
32614
|
+
bars,
|
|
32615
|
+
guides,
|
|
32616
|
+
angles,
|
|
32617
|
+
hops,
|
|
32618
|
+
shapes
|
|
32619
|
+
]);
|
|
30825
32620
|
return /* @__PURE__ */ jsxRuntime.jsx(Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
|
|
30826
32621
|
title ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", children: title }) : null,
|
|
30827
32622
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -30830,6 +32625,8 @@ var init_MathCanvas = __esm({
|
|
|
30830
32625
|
width,
|
|
30831
32626
|
height,
|
|
30832
32627
|
shapes: derivedShapes,
|
|
32628
|
+
readouts,
|
|
32629
|
+
traces,
|
|
30833
32630
|
interactive,
|
|
30834
32631
|
animate,
|
|
30835
32632
|
onShapeClick,
|
|
@@ -30841,7 +32638,315 @@ var init_MathCanvas = __esm({
|
|
|
30841
32638
|
};
|
|
30842
32639
|
}
|
|
30843
32640
|
});
|
|
30844
|
-
|
|
32641
|
+
function formatMeterValue(v) {
|
|
32642
|
+
return Number.isInteger(v) ? String(v) : String(Number(v.toFixed(2)));
|
|
32643
|
+
}
|
|
32644
|
+
function sceneObjectShapes(obj, canvasWidth, canvasHeight) {
|
|
32645
|
+
const out = [];
|
|
32646
|
+
const color = obj.color ?? "#334155";
|
|
32647
|
+
switch (obj.kind) {
|
|
32648
|
+
case "ground": {
|
|
32649
|
+
const xStart = obj.x1 ?? 0;
|
|
32650
|
+
const xEnd = obj.x2 ?? canvasWidth;
|
|
32651
|
+
const y = obj.y ?? 0;
|
|
32652
|
+
out.push({ type: "line", x1: xStart, y1: y, x2: xEnd, y2: y, color, lineWidth: 2 });
|
|
32653
|
+
for (let hx = xStart + 7; hx <= xEnd; hx += 14) {
|
|
32654
|
+
out.push({ type: "line", x1: hx, y1: y, x2: hx - 7, y2: y + 7, color, lineWidth: 1 });
|
|
32655
|
+
}
|
|
32656
|
+
if (obj.label) {
|
|
32657
|
+
out.push({
|
|
32658
|
+
type: "text",
|
|
32659
|
+
x: (xStart + xEnd) / 2,
|
|
32660
|
+
y: y - 10,
|
|
32661
|
+
text: obj.label,
|
|
32662
|
+
color: PHYSICS_LABEL_COLOR,
|
|
32663
|
+
fontSize: 11,
|
|
32664
|
+
align: "center"
|
|
32665
|
+
});
|
|
32666
|
+
}
|
|
32667
|
+
break;
|
|
32668
|
+
}
|
|
32669
|
+
case "wall": {
|
|
32670
|
+
const yStart = obj.y1 ?? 0;
|
|
32671
|
+
const yEnd = obj.y2 ?? canvasHeight;
|
|
32672
|
+
const x = obj.x ?? 0;
|
|
32673
|
+
out.push({ type: "line", x1: x, y1: yStart, x2: x, y2: yEnd, color, lineWidth: 2 });
|
|
32674
|
+
for (let hy = yStart + 7; hy <= yEnd; hy += 14) {
|
|
32675
|
+
out.push({ type: "line", x1: x, y1: hy, x2: x - 7, y2: hy + 7, color, lineWidth: 1 });
|
|
32676
|
+
}
|
|
32677
|
+
if (obj.label) {
|
|
32678
|
+
out.push({
|
|
32679
|
+
type: "text",
|
|
32680
|
+
x: x + 12,
|
|
32681
|
+
y: (yStart + yEnd) / 2,
|
|
32682
|
+
text: obj.label,
|
|
32683
|
+
color: PHYSICS_LABEL_COLOR,
|
|
32684
|
+
fontSize: 11,
|
|
32685
|
+
align: "left"
|
|
32686
|
+
});
|
|
32687
|
+
}
|
|
32688
|
+
break;
|
|
32689
|
+
}
|
|
32690
|
+
case "ramp": {
|
|
32691
|
+
const x1 = obj.x1 ?? 0;
|
|
32692
|
+
const y1 = obj.y1 ?? 0;
|
|
32693
|
+
const x2 = obj.x2 ?? canvasWidth;
|
|
32694
|
+
const y2 = obj.y2 ?? canvasHeight;
|
|
32695
|
+
out.push({
|
|
32696
|
+
type: "polygon",
|
|
32697
|
+
points: [
|
|
32698
|
+
{ x: x1, y: y1 },
|
|
32699
|
+
{ x: x2, y: y2 },
|
|
32700
|
+
{ x: x1, y: y2 }
|
|
32701
|
+
],
|
|
32702
|
+
color,
|
|
32703
|
+
fill: obj.fill ?? "#e2e8f0",
|
|
32704
|
+
lineWidth: 2
|
|
32705
|
+
});
|
|
32706
|
+
if (obj.label) {
|
|
32707
|
+
out.push({
|
|
32708
|
+
type: "text",
|
|
32709
|
+
x: (2 * x1 + x2) / 3,
|
|
32710
|
+
y: (y1 + 2 * y2) / 3,
|
|
32711
|
+
text: obj.label,
|
|
32712
|
+
color: PHYSICS_LABEL_COLOR,
|
|
32713
|
+
fontSize: 11,
|
|
32714
|
+
align: "center"
|
|
32715
|
+
});
|
|
32716
|
+
}
|
|
32717
|
+
break;
|
|
32718
|
+
}
|
|
32719
|
+
case "box": {
|
|
32720
|
+
const x = obj.x ?? 0;
|
|
32721
|
+
const y = obj.y ?? 0;
|
|
32722
|
+
const w = obj.width ?? 40;
|
|
32723
|
+
const h = obj.height ?? 40;
|
|
32724
|
+
out.push({ type: "rect", x, y, width: w, height: h, color, fill: obj.fill, lineWidth: 2 });
|
|
32725
|
+
if (obj.label) {
|
|
32726
|
+
out.push({
|
|
32727
|
+
type: "text",
|
|
32728
|
+
x: x + w / 2,
|
|
32729
|
+
y: y + h / 2,
|
|
32730
|
+
text: obj.label,
|
|
32731
|
+
color: PHYSICS_LABEL_COLOR,
|
|
32732
|
+
fontSize: 11,
|
|
32733
|
+
align: "center"
|
|
32734
|
+
});
|
|
32735
|
+
}
|
|
32736
|
+
break;
|
|
32737
|
+
}
|
|
32738
|
+
case "pivot": {
|
|
32739
|
+
const x = obj.x ?? 0;
|
|
32740
|
+
const y = obj.y ?? 0;
|
|
32741
|
+
out.push({ type: "circle", x, y, radius: 5, color, fill: color });
|
|
32742
|
+
out.push({ type: "line", x1: x - 14, y1: y - 8, x2: x + 14, y2: y - 8, color, lineWidth: 1 });
|
|
32743
|
+
for (let k = 0; k < 5; k++) {
|
|
32744
|
+
const hx = x - 14 + 7 * k;
|
|
32745
|
+
out.push({ type: "line", x1: hx, y1: y - 8, x2: hx - 6, y2: y - 14, color, lineWidth: 1 });
|
|
32746
|
+
}
|
|
32747
|
+
if (obj.label) {
|
|
32748
|
+
out.push({
|
|
32749
|
+
type: "text",
|
|
32750
|
+
x,
|
|
32751
|
+
y: y - 20,
|
|
32752
|
+
text: obj.label,
|
|
32753
|
+
color: PHYSICS_LABEL_COLOR,
|
|
32754
|
+
fontSize: 11,
|
|
32755
|
+
align: "center"
|
|
32756
|
+
});
|
|
32757
|
+
}
|
|
32758
|
+
break;
|
|
32759
|
+
}
|
|
32760
|
+
}
|
|
32761
|
+
return out;
|
|
32762
|
+
}
|
|
32763
|
+
function trailShapes(trail) {
|
|
32764
|
+
const n = trail.points.length;
|
|
32765
|
+
if (n < 2) return [];
|
|
32766
|
+
const color = trail.color ?? "#94a3b8";
|
|
32767
|
+
const lineWidth = trail.width ?? 2;
|
|
32768
|
+
const fade = trail.fade ?? true;
|
|
32769
|
+
const globalOpacity = trail.opacity ?? 1;
|
|
32770
|
+
const out = [];
|
|
32771
|
+
for (let i = 0; i < n - 1; i++) {
|
|
32772
|
+
const a = trail.points[i];
|
|
32773
|
+
const b = trail.points[i + 1];
|
|
32774
|
+
const segmentOpacity = fade ? 0.12 + 0.68 * i / (n - 1) : 0.6;
|
|
32775
|
+
out.push({
|
|
32776
|
+
type: "line",
|
|
32777
|
+
x1: a.x,
|
|
32778
|
+
y1: a.y,
|
|
32779
|
+
x2: b.x,
|
|
32780
|
+
y2: b.y,
|
|
32781
|
+
color,
|
|
32782
|
+
lineWidth,
|
|
32783
|
+
opacity: segmentOpacity * globalOpacity
|
|
32784
|
+
});
|
|
32785
|
+
}
|
|
32786
|
+
return out;
|
|
32787
|
+
}
|
|
32788
|
+
function constraintShapes(c, a, b) {
|
|
32789
|
+
const color = c.color ?? "#9ca3af";
|
|
32790
|
+
const kind = c.kind ?? "rod";
|
|
32791
|
+
if (kind === "rod") {
|
|
32792
|
+
return [{ type: "line", x1: a.x, y1: a.y, x2: b.x, y2: b.y, color, lineWidth: 2 }];
|
|
32793
|
+
}
|
|
32794
|
+
if (kind === "string") {
|
|
32795
|
+
return [{ type: "line", x1: a.x, y1: a.y, x2: b.x, y2: b.y, color, lineWidth: 2, dash: "dashed" }];
|
|
32796
|
+
}
|
|
32797
|
+
const COILS = 8;
|
|
32798
|
+
const AMP = 7;
|
|
32799
|
+
const LEAD = 10;
|
|
32800
|
+
const dx = b.x - a.x;
|
|
32801
|
+
const dy = b.y - a.y;
|
|
32802
|
+
const dist = Math.max(1e-6, Math.hypot(dx, dy));
|
|
32803
|
+
const ux = dx / dist;
|
|
32804
|
+
const uy = dy / dist;
|
|
32805
|
+
const perpX = -uy;
|
|
32806
|
+
const perpY = ux;
|
|
32807
|
+
const aPrime = { x: a.x + LEAD * ux, y: a.y + LEAD * uy };
|
|
32808
|
+
const bPrime = { x: b.x - LEAD * ux, y: b.y - LEAD * uy };
|
|
32809
|
+
const m = 2 * COILS;
|
|
32810
|
+
const polyline = [{ x: a.x, y: a.y }, aPrime];
|
|
32811
|
+
for (let j = 1; j <= m; j++) {
|
|
32812
|
+
const t = j / (m + 1);
|
|
32813
|
+
const baseX = aPrime.x + t * (bPrime.x - aPrime.x);
|
|
32814
|
+
const baseY = aPrime.y + t * (bPrime.y - aPrime.y);
|
|
32815
|
+
const sign = j % 2 === 0 ? 1 : -1;
|
|
32816
|
+
polyline.push({ x: baseX + sign * AMP * perpX, y: baseY + sign * AMP * perpY });
|
|
32817
|
+
}
|
|
32818
|
+
polyline.push(bPrime, { x: b.x, y: b.y });
|
|
32819
|
+
const out = [];
|
|
32820
|
+
for (let i = 1; i < polyline.length; i++) {
|
|
32821
|
+
out.push({
|
|
32822
|
+
type: "line",
|
|
32823
|
+
x1: polyline[i - 1].x,
|
|
32824
|
+
y1: polyline[i - 1].y,
|
|
32825
|
+
x2: polyline[i].x,
|
|
32826
|
+
y2: polyline[i].y,
|
|
32827
|
+
color,
|
|
32828
|
+
lineWidth: 2
|
|
32829
|
+
});
|
|
32830
|
+
}
|
|
32831
|
+
return out;
|
|
32832
|
+
}
|
|
32833
|
+
function vectorShapes(v, bodyById) {
|
|
32834
|
+
let ax;
|
|
32835
|
+
let ay;
|
|
32836
|
+
if (v.body) {
|
|
32837
|
+
const anchor = bodyById.get(v.body);
|
|
32838
|
+
if (!anchor) return [];
|
|
32839
|
+
ax = anchor.x;
|
|
32840
|
+
ay = anchor.y;
|
|
32841
|
+
} else {
|
|
32842
|
+
ax = v.x ?? 0;
|
|
32843
|
+
ay = v.y ?? 0;
|
|
32844
|
+
}
|
|
32845
|
+
const scale = v.scale ?? 1;
|
|
32846
|
+
const color = v.color ?? "#dc2626";
|
|
32847
|
+
const tx = ax + v.dx * scale;
|
|
32848
|
+
const ty = ay + v.dy * scale;
|
|
32849
|
+
const out = [{ type: "arrow", x1: ax, y1: ay, x2: tx, y2: ty, color, lineWidth: 2, dash: v.dash }];
|
|
32850
|
+
if (v.label) {
|
|
32851
|
+
const dist = Math.max(1e-6, Math.hypot(tx - ax, ty - ay));
|
|
32852
|
+
const ux = (tx - ax) / dist;
|
|
32853
|
+
const uy = (ty - ay) / dist;
|
|
32854
|
+
out.push({
|
|
32855
|
+
type: "text",
|
|
32856
|
+
x: tx + 8 * ux,
|
|
32857
|
+
y: ty + 8 * uy,
|
|
32858
|
+
text: v.label,
|
|
32859
|
+
color,
|
|
32860
|
+
fontSize: 11,
|
|
32861
|
+
align: "center"
|
|
32862
|
+
});
|
|
32863
|
+
}
|
|
32864
|
+
return out;
|
|
32865
|
+
}
|
|
32866
|
+
function angleMarkerShapes(a) {
|
|
32867
|
+
const radius = a.radius ?? 26;
|
|
32868
|
+
const color = a.color ?? "#0ea5e9";
|
|
32869
|
+
const out = [
|
|
32870
|
+
{
|
|
32871
|
+
type: "ellipse",
|
|
32872
|
+
x: a.x,
|
|
32873
|
+
y: a.y,
|
|
32874
|
+
width: radius * 2,
|
|
32875
|
+
height: radius * 2,
|
|
32876
|
+
startAngle: a.from,
|
|
32877
|
+
endAngle: a.to,
|
|
32878
|
+
color,
|
|
32879
|
+
lineWidth: 2
|
|
32880
|
+
}
|
|
32881
|
+
];
|
|
32882
|
+
if (a.label) {
|
|
32883
|
+
const mid = (a.from + a.to) / 2 * (Math.PI / 180);
|
|
32884
|
+
out.push({
|
|
32885
|
+
type: "text",
|
|
32886
|
+
x: a.x + (radius + 13) * Math.cos(mid),
|
|
32887
|
+
y: a.y + (radius + 13) * Math.sin(mid),
|
|
32888
|
+
text: a.label,
|
|
32889
|
+
color,
|
|
32890
|
+
fontSize: 11,
|
|
32891
|
+
align: "center"
|
|
32892
|
+
});
|
|
32893
|
+
}
|
|
32894
|
+
return out;
|
|
32895
|
+
}
|
|
32896
|
+
function fieldShapes(field, canvasWidth, canvasHeight) {
|
|
32897
|
+
const spacing = field.spacing ?? 48;
|
|
32898
|
+
const size = field.size ?? 14;
|
|
32899
|
+
const color = field.color ?? "#94a3b8";
|
|
32900
|
+
const regionX = field.x ?? 0;
|
|
32901
|
+
const regionY = field.y ?? 0;
|
|
32902
|
+
const regionW = field.width ?? canvasWidth;
|
|
32903
|
+
const regionH = field.height ?? canvasHeight;
|
|
32904
|
+
const out = [];
|
|
32905
|
+
for (let gx = regionX + spacing / 2; gx < regionX + regionW; gx += spacing) {
|
|
32906
|
+
for (let gy = regionY + spacing / 2; gy < regionY + regionH; gy += spacing) {
|
|
32907
|
+
if (field.kind === "arrows") {
|
|
32908
|
+
const rad = (field.angle ?? 0) * Math.PI / 180;
|
|
32909
|
+
const hx = Math.cos(rad) * size / 2;
|
|
32910
|
+
const hy = Math.sin(rad) * size / 2;
|
|
32911
|
+
out.push({ type: "arrow", x1: gx - hx, y1: gy - hy, x2: gx + hx, y2: gy + hy, color, lineWidth: 2 });
|
|
32912
|
+
} else if (field.kind === "into") {
|
|
32913
|
+
const r2 = size / 3;
|
|
32914
|
+
const d = 0.6 * r2 * Math.SQRT1_2;
|
|
32915
|
+
out.push({ type: "circle", x: gx, y: gy, radius: r2, color });
|
|
32916
|
+
out.push({ type: "line", x1: gx - d, y1: gy - d, x2: gx + d, y2: gy + d, color, lineWidth: 1 });
|
|
32917
|
+
out.push({ type: "line", x1: gx - d, y1: gy + d, x2: gx + d, y2: gy - d, color, lineWidth: 1 });
|
|
32918
|
+
} else {
|
|
32919
|
+
const r2 = size / 3;
|
|
32920
|
+
out.push({ type: "circle", x: gx, y: gy, radius: r2, color });
|
|
32921
|
+
out.push({ type: "circle", x: gx, y: gy, radius: 1.5, color, fill: color });
|
|
32922
|
+
}
|
|
32923
|
+
}
|
|
32924
|
+
}
|
|
32925
|
+
return out;
|
|
32926
|
+
}
|
|
32927
|
+
function meterShapes(meters, canvasHeight) {
|
|
32928
|
+
const n = meters.length;
|
|
32929
|
+
const out = [];
|
|
32930
|
+
const sharedMax = Math.max(1e-6, ...meters.map((m) => m.value));
|
|
32931
|
+
meters.forEach((meter, i) => {
|
|
32932
|
+
const rowY = canvasHeight - 10 - 16 * (n - i);
|
|
32933
|
+
const color = meter.color ?? "#3b82f6";
|
|
32934
|
+
const M = meter.max ?? sharedMax;
|
|
32935
|
+
const w = Math.round(Math.min(1, Math.max(0, meter.value / M)) * 110);
|
|
32936
|
+
out.push({ type: "text", x: 8, y: rowY + 8, text: meter.label, color: PHYSICS_LABEL_COLOR, fontSize: 10 });
|
|
32937
|
+
out.push({ type: "rect", x: 52, y: rowY, width: w, height: 10, color, fill: color });
|
|
32938
|
+
out.push({
|
|
32939
|
+
type: "text",
|
|
32940
|
+
x: 166,
|
|
32941
|
+
y: rowY + 8,
|
|
32942
|
+
text: formatMeterValue(meter.value),
|
|
32943
|
+
color: "#6b7280",
|
|
32944
|
+
fontSize: 9
|
|
32945
|
+
});
|
|
32946
|
+
});
|
|
32947
|
+
return out;
|
|
32948
|
+
}
|
|
32949
|
+
var physicsLog2, PHYSICS_LABEL_COLOR, PhysicsCanvas;
|
|
30845
32950
|
var init_PhysicsCanvas = __esm({
|
|
30846
32951
|
"components/learning/molecules/PhysicsCanvas.tsx"() {
|
|
30847
32952
|
"use client";
|
|
@@ -30850,6 +32955,7 @@ var init_PhysicsCanvas = __esm({
|
|
|
30850
32955
|
init_LearningCanvas();
|
|
30851
32956
|
init_learningScene3D();
|
|
30852
32957
|
physicsLog2 = logger.createLogger("almadar:ui:physics-canvas");
|
|
32958
|
+
PHYSICS_LABEL_COLOR = "#374151";
|
|
30853
32959
|
PhysicsCanvas = ({
|
|
30854
32960
|
className,
|
|
30855
32961
|
width = 600,
|
|
@@ -30866,7 +32972,18 @@ var init_PhysicsCanvas = __esm({
|
|
|
30866
32972
|
showForces = false,
|
|
30867
32973
|
velocityScale = 20,
|
|
30868
32974
|
forceScale = 20,
|
|
32975
|
+
sceneObjects = [],
|
|
32976
|
+
trails = [],
|
|
32977
|
+
vectors = [],
|
|
32978
|
+
surface3d,
|
|
32979
|
+
vectors3d = [],
|
|
32980
|
+
vectorScale = 1,
|
|
32981
|
+
angles = [],
|
|
32982
|
+
field,
|
|
32983
|
+
meters = [],
|
|
30869
32984
|
shapes = [],
|
|
32985
|
+
readouts,
|
|
32986
|
+
traces,
|
|
30870
32987
|
showGrid,
|
|
30871
32988
|
shadows,
|
|
30872
32989
|
interactive,
|
|
@@ -30881,19 +32998,14 @@ var init_PhysicsCanvas = __esm({
|
|
|
30881
32998
|
for (const b of bodies) {
|
|
30882
32999
|
if (b.id) bodyById.set(b.id, b);
|
|
30883
33000
|
}
|
|
33001
|
+
if (field) out.push(...fieldShapes(field, width, height));
|
|
33002
|
+
for (const obj of sceneObjects) out.push(...sceneObjectShapes(obj, width, height));
|
|
33003
|
+
for (const trail of trails) out.push(...trailShapes(trail));
|
|
30884
33004
|
for (const c of constraints) {
|
|
30885
33005
|
const a = bodyById.get(c.from);
|
|
30886
33006
|
const b = bodyById.get(c.to);
|
|
30887
33007
|
if (!a || !b) continue;
|
|
30888
|
-
out.push(
|
|
30889
|
-
type: "line",
|
|
30890
|
-
x1: a.x,
|
|
30891
|
-
y1: a.y,
|
|
30892
|
-
x2: b.x,
|
|
30893
|
-
y2: b.y,
|
|
30894
|
-
color: c.color ?? "#9ca3af",
|
|
30895
|
-
lineWidth: 2
|
|
30896
|
-
});
|
|
33008
|
+
out.push(...constraintShapes(c, a, b));
|
|
30897
33009
|
}
|
|
30898
33010
|
for (const b of bodies) {
|
|
30899
33011
|
out.push({
|
|
@@ -30938,14 +33050,51 @@ var init_PhysicsCanvas = __esm({
|
|
|
30938
33050
|
});
|
|
30939
33051
|
}
|
|
30940
33052
|
}
|
|
33053
|
+
for (const v of vectors) out.push(...vectorShapes(v, bodyById));
|
|
33054
|
+
for (const a of angles) out.push(...angleMarkerShapes(a));
|
|
33055
|
+
if (meters.length > 0) out.push(...meterShapes(meters, height));
|
|
30941
33056
|
out.push(...shapes);
|
|
30942
33057
|
return out;
|
|
30943
|
-
}, [
|
|
33058
|
+
}, [
|
|
33059
|
+
bodies,
|
|
33060
|
+
constraints,
|
|
33061
|
+
showVelocity,
|
|
33062
|
+
showForces,
|
|
33063
|
+
velocityScale,
|
|
33064
|
+
forceScale,
|
|
33065
|
+
sceneObjects,
|
|
33066
|
+
trails,
|
|
33067
|
+
vectors,
|
|
33068
|
+
angles,
|
|
33069
|
+
field,
|
|
33070
|
+
meters,
|
|
33071
|
+
shapes,
|
|
33072
|
+
width,
|
|
33073
|
+
height
|
|
33074
|
+
]);
|
|
30944
33075
|
const drawables3D = React94.useMemo(() => {
|
|
30945
33076
|
if (mode !== "3d") return [];
|
|
30946
33077
|
if (shapes.length > 0) {
|
|
30947
33078
|
physicsLog2.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
|
|
30948
33079
|
}
|
|
33080
|
+
if (sceneObjects.length > 0) {
|
|
33081
|
+
physicsLog2.debug("sceneObjects ignored in 3D mode (pixel-authored 2D vocabulary)", { count: sceneObjects.length });
|
|
33082
|
+
}
|
|
33083
|
+
if (vectors.length > 0) {
|
|
33084
|
+
physicsLog2.debug("vectors ignored in 3D mode (pixel-authored 2D vocabulary)", { count: vectors.length });
|
|
33085
|
+
}
|
|
33086
|
+
if (angles.length > 0) {
|
|
33087
|
+
physicsLog2.debug("angles ignored in 3D mode (pixel-authored 2D vocabulary)", { count: angles.length });
|
|
33088
|
+
}
|
|
33089
|
+
if (field) {
|
|
33090
|
+
physicsLog2.debug("field ignored in 3D mode (pixel-authored 2D vocabulary)");
|
|
33091
|
+
}
|
|
33092
|
+
if (meters.length > 0) {
|
|
33093
|
+
physicsLog2.debug("meters ignored in 3D mode (pixel-authored 2D vocabulary)", { count: meters.length });
|
|
33094
|
+
}
|
|
33095
|
+
if (animate) {
|
|
33096
|
+
physicsLog2.debug("animate ignored in 3D mode (motion is entity-state driven)");
|
|
33097
|
+
}
|
|
30949
33098
|
const out = [];
|
|
30950
33099
|
const labelColor = labelColorForBackground(backgroundColor);
|
|
30951
33100
|
const bodyById = /* @__PURE__ */ new Map();
|
|
@@ -30993,15 +33142,67 @@ var init_PhysicsCanvas = __esm({
|
|
|
30993
33142
|
if (arrow) out.push(arrow);
|
|
30994
33143
|
}
|
|
30995
33144
|
}
|
|
33145
|
+
for (const trail of trails) {
|
|
33146
|
+
if (trail.fade !== void 0) {
|
|
33147
|
+
physicsLog2.debug("trail.fade ignored in 3D mode (2D-only fade curve \u2014 3D draws an opaque tube)", { id: trail.id });
|
|
33148
|
+
}
|
|
33149
|
+
const points = trail.points.map((p) => [p.x, p.y, p.z ?? 0]);
|
|
33150
|
+
out.push(
|
|
33151
|
+
...polylineTube(points, trail.width ?? 0.05, trail.color ?? "#94a3b8", {
|
|
33152
|
+
...trail.opacity !== void 0 ? { opacity: trail.opacity } : {}
|
|
33153
|
+
})
|
|
33154
|
+
);
|
|
33155
|
+
}
|
|
33156
|
+
if (surface3d) {
|
|
33157
|
+
out.push(...heightFieldMesh(surface3d));
|
|
33158
|
+
}
|
|
33159
|
+
if (vectors3d.length > 0) {
|
|
33160
|
+
out.push(
|
|
33161
|
+
...arrowField(
|
|
33162
|
+
vectors3d.map((v) => ({
|
|
33163
|
+
id: v.id,
|
|
33164
|
+
from: [v.x, v.y, v.z ?? 0],
|
|
33165
|
+
delta: [v.dx, v.dy, v.dz ?? 0],
|
|
33166
|
+
color: v.color,
|
|
33167
|
+
label: v.label,
|
|
33168
|
+
width: v.width
|
|
33169
|
+
})),
|
|
33170
|
+
{ scale: vectorScale, labelColor }
|
|
33171
|
+
)
|
|
33172
|
+
);
|
|
33173
|
+
}
|
|
30996
33174
|
return out;
|
|
30997
|
-
}, [
|
|
33175
|
+
}, [
|
|
33176
|
+
mode,
|
|
33177
|
+
bodies,
|
|
33178
|
+
constraints,
|
|
33179
|
+
showVelocity,
|
|
33180
|
+
showForces,
|
|
33181
|
+
velocityScale,
|
|
33182
|
+
forceScale,
|
|
33183
|
+
shapes,
|
|
33184
|
+
sceneObjects,
|
|
33185
|
+
trails,
|
|
33186
|
+
vectors,
|
|
33187
|
+
surface3d,
|
|
33188
|
+
vectors3d,
|
|
33189
|
+
vectorScale,
|
|
33190
|
+
angles,
|
|
33191
|
+
field,
|
|
33192
|
+
meters,
|
|
33193
|
+
animate,
|
|
33194
|
+
backgroundColor
|
|
33195
|
+
]);
|
|
30998
33196
|
const bodyIndexById = React94.useMemo(() => {
|
|
30999
33197
|
const m = /* @__PURE__ */ new Map();
|
|
33198
|
+
vectors3d.forEach((v, i) => {
|
|
33199
|
+
if (v.id) m.set(v.id, i);
|
|
33200
|
+
});
|
|
31000
33201
|
bodies.forEach((b, i) => {
|
|
31001
33202
|
if (b.id) m.set(b.id, i);
|
|
31002
33203
|
});
|
|
31003
33204
|
return m;
|
|
31004
|
-
}, [bodies]);
|
|
33205
|
+
}, [bodies, vectors3d]);
|
|
31005
33206
|
if (mode === "3d") {
|
|
31006
33207
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
31007
33208
|
LearningScene3D,
|
|
@@ -31033,6 +33234,8 @@ var init_PhysicsCanvas = __esm({
|
|
|
31033
33234
|
height,
|
|
31034
33235
|
backgroundColor,
|
|
31035
33236
|
shapes: derivedShapes,
|
|
33237
|
+
readouts,
|
|
33238
|
+
traces,
|
|
31036
33239
|
interactive: interactive ?? false,
|
|
31037
33240
|
animate,
|
|
31038
33241
|
onShapeClick,
|
|
@@ -31183,7 +33386,7 @@ function layoutFlow(nodeIds, adjacency, roots, width, height, margin) {
|
|
|
31183
33386
|
}
|
|
31184
33387
|
return nodeIds.map((id) => positions.get(id));
|
|
31185
33388
|
}
|
|
31186
|
-
function
|
|
33389
|
+
function layoutTree3(nodeIds, adjacency, roots, width, height, margin) {
|
|
31187
33390
|
const effectiveRoots = roots.length > 0 ? roots : [nodeIds[0]];
|
|
31188
33391
|
const layers = assignLayers(nodeIds, adjacency, effectiveRoots);
|
|
31189
33392
|
const maxLayer = Math.max(...Array.from(layers.values()));
|
|
@@ -31239,7 +33442,7 @@ function computeStaticLayout(mode, input) {
|
|
|
31239
33442
|
const adjacency = buildAdjacency(nodeIds, edges);
|
|
31240
33443
|
const roots = findRoots(nodeIds, adjacency);
|
|
31241
33444
|
if (mode === "flow") return layoutFlow(nodeIds, adjacency, roots, width, height, margin);
|
|
31242
|
-
if (mode === "tree") return
|
|
33445
|
+
if (mode === "tree") return layoutTree3(nodeIds, adjacency, roots, width, height, margin);
|
|
31243
33446
|
return layoutRadial(nodeIds, adjacency, roots, width, height, margin);
|
|
31244
33447
|
}
|
|
31245
33448
|
var init_graphViewLayouts = __esm({
|
|
@@ -32482,26 +34685,6 @@ var init_Lightbox = __esm({
|
|
|
32482
34685
|
Lightbox.displayName = "Lightbox";
|
|
32483
34686
|
}
|
|
32484
34687
|
});
|
|
32485
|
-
function useMediaQuery(query) {
|
|
32486
|
-
const subscribe = React94.useCallback(
|
|
32487
|
-
(onChange) => {
|
|
32488
|
-
const mql = window.matchMedia(query);
|
|
32489
|
-
mql.addEventListener("change", onChange);
|
|
32490
|
-
return () => mql.removeEventListener("change", onChange);
|
|
32491
|
-
},
|
|
32492
|
-
[query]
|
|
32493
|
-
);
|
|
32494
|
-
return React94.useSyncExternalStore(
|
|
32495
|
-
subscribe,
|
|
32496
|
-
() => window.matchMedia(query).matches,
|
|
32497
|
-
() => false
|
|
32498
|
-
);
|
|
32499
|
-
}
|
|
32500
|
-
var init_useMediaQuery = __esm({
|
|
32501
|
-
"hooks/useMediaQuery.ts"() {
|
|
32502
|
-
"use client";
|
|
32503
|
-
}
|
|
32504
|
-
});
|
|
32505
34688
|
function renderIconInput3(icon, props) {
|
|
32506
34689
|
return typeof icon === "string" ? /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: icon, ...props }) : /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon, ...props });
|
|
32507
34690
|
}
|
|
@@ -32540,8 +34723,8 @@ function TableView({
|
|
|
32540
34723
|
columns,
|
|
32541
34724
|
fields,
|
|
32542
34725
|
itemActions,
|
|
32543
|
-
maxInlineActions,
|
|
32544
|
-
itemClickEvent,
|
|
34726
|
+
maxInlineActions: _maxInlineActions,
|
|
34727
|
+
itemClickEvent = "",
|
|
32545
34728
|
selectable = false,
|
|
32546
34729
|
selectEvent,
|
|
32547
34730
|
selectedIds,
|
|
@@ -32591,7 +34774,6 @@ function TableView({
|
|
|
32591
34774
|
const hasMore = pageSize > 0 && visibleCount < ordered2.length;
|
|
32592
34775
|
const hasRenderProp = typeof children === "function";
|
|
32593
34776
|
const idField = dndItemIdField ?? "id";
|
|
32594
|
-
const isCoarsePointer = useMediaQuery("(pointer: coarse)");
|
|
32595
34777
|
React94__namespace.default.useEffect(() => {
|
|
32596
34778
|
tableViewLog.debug("render", {
|
|
32597
34779
|
rowCount: data.length,
|
|
@@ -32633,21 +34815,14 @@ function TableView({
|
|
|
32633
34815
|
const dir = sortColumn === (col.field ?? col.key) && sortDirection === "asc" ? "desc" : "asc";
|
|
32634
34816
|
eventBus.emit(`UI:${sortEvent}`, { column: col.field ?? col.key, direction: dir });
|
|
32635
34817
|
};
|
|
32636
|
-
const
|
|
32637
|
-
e.stopPropagation();
|
|
32638
|
-
const payload = {
|
|
32639
|
-
id: row.id,
|
|
32640
|
-
row
|
|
32641
|
-
};
|
|
32642
|
-
eventBus.emit(`UI:${action.event}`, payload);
|
|
32643
|
-
};
|
|
34818
|
+
const rowClickEvent = itemClickEvent || actionDefs.find((a) => a.variant !== "danger")?.event;
|
|
32644
34819
|
const handleRowClick = (row) => () => {
|
|
32645
|
-
if (!
|
|
34820
|
+
if (!rowClickEvent) return;
|
|
32646
34821
|
const payload = {
|
|
32647
34822
|
id: row.id,
|
|
32648
34823
|
row
|
|
32649
34824
|
};
|
|
32650
|
-
eventBus.emit(`UI:${
|
|
34825
|
+
eventBus.emit(`UI:${rowClickEvent}`, payload);
|
|
32651
34826
|
};
|
|
32652
34827
|
const colFloors = React94__namespace.default.useMemo(
|
|
32653
34828
|
() => colDefs.map((col) => {
|
|
@@ -32663,10 +34838,7 @@ function TableView({
|
|
|
32663
34838
|
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;
|
|
32664
34839
|
const lk = LOOKS[look];
|
|
32665
34840
|
const hasActions = actionDefs.length > 0;
|
|
32666
|
-
const
|
|
32667
|
-
const inlineActionCount = hasActions ? effectiveMaxInline != null ? Math.min(actionDefs.length, effectiveMaxInline) : actionDefs.length : 0;
|
|
32668
|
-
const hasOverflowActions = hasActions && effectiveMaxInline != null && actionDefs.length > effectiveMaxInline;
|
|
32669
|
-
const actionsTrack = hasActions ? `${inlineActionCount * 6 + (hasOverflowActions ? 3 : 0)}rem` : null;
|
|
34841
|
+
const actionsTrack = hasActions ? "3rem" : null;
|
|
32670
34842
|
const gridTemplateColumns = [
|
|
32671
34843
|
selectable ? "auto" : null,
|
|
32672
34844
|
...colDefs.map((c, i) => c.width ?? `minmax(${colFloors[i]}ch, 1fr)`),
|
|
@@ -32713,7 +34885,7 @@ function TableView({
|
|
|
32713
34885
|
col.key
|
|
32714
34886
|
);
|
|
32715
34887
|
}),
|
|
32716
|
-
hasActions && /* @__PURE__ */ jsxRuntime.jsx(Box, { "aria-hidden": true, className: "sticky right-0 bg-[var(--color-surface-subtle)]" })
|
|
34888
|
+
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" })
|
|
32717
34889
|
]
|
|
32718
34890
|
}
|
|
32719
34891
|
);
|
|
@@ -32725,12 +34897,12 @@ function TableView({
|
|
|
32725
34897
|
role: "row",
|
|
32726
34898
|
"data-entity-row": true,
|
|
32727
34899
|
"data-entity-id": id,
|
|
32728
|
-
onClick:
|
|
34900
|
+
onClick: rowClickEvent ? handleRowClick(row) : void 0,
|
|
32729
34901
|
style: !hasRenderProp ? { gridTemplateColumns } : void 0,
|
|
32730
34902
|
className: cn(
|
|
32731
34903
|
"group items-center gap-3 transition-colors duration-fast",
|
|
32732
34904
|
hasRenderProp ? "flex" : "grid",
|
|
32733
|
-
|
|
34905
|
+
rowClickEvent && "cursor-pointer",
|
|
32734
34906
|
lk.rowPad,
|
|
32735
34907
|
lk.divider && "border-b border-[var(--color-border)]",
|
|
32736
34908
|
lk.striped && index % 2 === 1 && "bg-[var(--color-surface-subtle)]",
|
|
@@ -32738,7 +34910,7 @@ function TableView({
|
|
|
32738
34910
|
look === "bordered" && "[&>*]:border-r [&>*]:border-[var(--color-border)] [&>*:last-child]:border-r-0"
|
|
32739
34911
|
),
|
|
32740
34912
|
children: [
|
|
32741
|
-
selectable && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "flex items-center", onClick:
|
|
34913
|
+
selectable && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "flex items-center", onClick: rowClickEvent ? (e) => e.stopPropagation() : void 0, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
32742
34914
|
Checkbox,
|
|
32743
34915
|
{
|
|
32744
34916
|
checked: selected.has(id),
|
|
@@ -32759,53 +34931,37 @@ function TableView({
|
|
|
32759
34931
|
}
|
|
32760
34932
|
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);
|
|
32761
34933
|
}),
|
|
32762
|
-
hasActions && /* @__PURE__ */ jsxRuntime.
|
|
34934
|
+
hasActions && /* @__PURE__ */ jsxRuntime.jsx(
|
|
32763
34935
|
HStack,
|
|
32764
34936
|
{
|
|
32765
34937
|
gap: "xs",
|
|
32766
|
-
onClick:
|
|
34938
|
+
onClick: rowClickEvent ? (e) => e.stopPropagation() : void 0,
|
|
32767
34939
|
className: cn(
|
|
32768
34940
|
// Pinned: the fixed column tracks routinely overflow the caller's
|
|
32769
|
-
// scroll container, which
|
|
32770
|
-
// Opaque so
|
|
34941
|
+
// scroll container, which would leave the kebab off-screen.
|
|
34942
|
+
// Opaque + hairline edge so it reads as a pinned column, not a
|
|
34943
|
+
// floating control, while scrolled cells pass underneath.
|
|
32771
34944
|
"justify-end flex-shrink-0 sticky right-0 z-[1] transition-colors",
|
|
34945
|
+
"border-l border-[var(--color-border)]",
|
|
32772
34946
|
lk.striped && index % 2 === 1 ? "bg-[var(--color-surface-subtle)]" : "bg-[var(--color-card)] group-hover:bg-[var(--color-surface-subtle)]"
|
|
32773
34947
|
),
|
|
32774
|
-
children:
|
|
32775
|
-
|
|
32776
|
-
|
|
32777
|
-
|
|
32778
|
-
|
|
32779
|
-
|
|
32780
|
-
|
|
32781
|
-
|
|
32782
|
-
|
|
32783
|
-
|
|
32784
|
-
|
|
32785
|
-
|
|
32786
|
-
|
|
32787
|
-
|
|
32788
|
-
}
|
|
32789
|
-
|
|
32790
|
-
|
|
32791
|
-
effectiveMaxInline != null && actionDefs.length > effectiveMaxInline && /* @__PURE__ */ jsxRuntime.jsx(
|
|
32792
|
-
Menu,
|
|
32793
|
-
{
|
|
32794
|
-
position: "bottom-end",
|
|
32795
|
-
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" }) }),
|
|
32796
|
-
items: actionDefs.slice(effectiveMaxInline).map((action) => ({
|
|
32797
|
-
label: action.label,
|
|
32798
|
-
icon: action.icon,
|
|
32799
|
-
event: action.event,
|
|
32800
|
-
variant: action.variant === "danger" ? "danger" : "default",
|
|
32801
|
-
onClick: () => eventBus.emit(`UI:${action.event}`, {
|
|
32802
|
-
id: row.id,
|
|
32803
|
-
row
|
|
32804
|
-
})
|
|
32805
|
-
}))
|
|
32806
|
-
}
|
|
32807
|
-
)
|
|
32808
|
-
]
|
|
34948
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
34949
|
+
Menu,
|
|
34950
|
+
{
|
|
34951
|
+
position: "bottom-end",
|
|
34952
|
+
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" }) }),
|
|
34953
|
+
items: actionDefs.map((action) => ({
|
|
34954
|
+
label: action.label,
|
|
34955
|
+
icon: action.icon,
|
|
34956
|
+
event: action.event,
|
|
34957
|
+
variant: action.variant === "danger" ? "danger" : "default",
|
|
34958
|
+
onClick: () => eventBus.emit(`UI:${action.event}`, {
|
|
34959
|
+
id: row.id,
|
|
34960
|
+
row
|
|
34961
|
+
})
|
|
34962
|
+
}))
|
|
34963
|
+
}
|
|
34964
|
+
)
|
|
32809
34965
|
}
|
|
32810
34966
|
)
|
|
32811
34967
|
]
|
|
@@ -32848,7 +35004,6 @@ var init_TableView = __esm({
|
|
|
32848
35004
|
init_format();
|
|
32849
35005
|
init_getNestedValue();
|
|
32850
35006
|
init_useEventBus();
|
|
32851
|
-
init_useMediaQuery();
|
|
32852
35007
|
init_Box();
|
|
32853
35008
|
init_Stack();
|
|
32854
35009
|
init_Typography();
|
|
@@ -47517,6 +49672,7 @@ var init_component_registry_generated = __esm({
|
|
|
47517
49672
|
init_ActionTile();
|
|
47518
49673
|
init_ActivationBlock();
|
|
47519
49674
|
init_ComponentPatterns();
|
|
49675
|
+
init_AlgoGraphCanvas();
|
|
47520
49676
|
init_AlgorithmCanvas();
|
|
47521
49677
|
init_AnimatedCounter();
|
|
47522
49678
|
init_AnimatedGraphic();
|
|
@@ -47780,6 +49936,7 @@ var init_component_registry_generated = __esm({
|
|
|
47780
49936
|
"ActivationBlock": ActivationBlock,
|
|
47781
49937
|
"Alert": AlertPattern,
|
|
47782
49938
|
"AlertPattern": AlertPattern,
|
|
49939
|
+
"AlgoGraphCanvas": AlgoGraphCanvas,
|
|
47783
49940
|
"AlgorithmCanvas": AlgorithmCanvas,
|
|
47784
49941
|
"AnimatedCounter": AnimatedCounter,
|
|
47785
49942
|
"AnimatedGraphic": AnimatedGraphic,
|
|
@@ -52652,9 +54809,9 @@ var log8 = logger.createLogger("almadar:ui:effects:client-handlers");
|
|
|
52652
54809
|
function createClientEffectHandlers(options) {
|
|
52653
54810
|
const { eventBus, slotSetter, navigate, notify, callService, liveEntity } = options;
|
|
52654
54811
|
return {
|
|
52655
|
-
emit: (event, payload) => {
|
|
54812
|
+
emit: (event, payload, source) => {
|
|
52656
54813
|
const prefixedEvent = event.startsWith("UI:") ? event : `UI:${event}`;
|
|
52657
|
-
eventBus.emit(prefixedEvent, payload);
|
|
54814
|
+
eventBus.emit(prefixedEvent, payload, source);
|
|
52658
54815
|
},
|
|
52659
54816
|
persist: async () => {
|
|
52660
54817
|
log8.warn("persist is server-side only, ignored on client");
|
|
@@ -52819,7 +54976,7 @@ function createSharedEntityWriter(binding, tick, traitStatesRef, emit) {
|
|
|
52819
54976
|
}
|
|
52820
54977
|
};
|
|
52821
54978
|
ctx.emit = (event, payload) => {
|
|
52822
|
-
emit(event, payload);
|
|
54979
|
+
emit(event, payload, { trait: traitName, tick: tick.name });
|
|
52823
54980
|
};
|
|
52824
54981
|
if (tick.guard !== void 0 && !evaluator.evaluateGuard(tick.guard, ctx)) {
|
|
52825
54982
|
tickLog.debug("guard-blocked", { traitName, tick: tick.name, state: currentState });
|
|
@@ -53271,6 +55428,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
53271
55428
|
}
|
|
53272
55429
|
const effectContext = {
|
|
53273
55430
|
traitName,
|
|
55431
|
+
orbitalName: orbitalsByTrait?.[traitName],
|
|
53274
55432
|
state: previousState,
|
|
53275
55433
|
transition: `${previousState}->${newState}`,
|
|
53276
55434
|
linkedEntity,
|
|
@@ -53347,7 +55505,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
53347
55505
|
});
|
|
53348
55506
|
}
|
|
53349
55507
|
return emittedDuringExec;
|
|
53350
|
-
}, [eventBus, flushSlot, sharedEntityStore, publishBindingSnapshot]);
|
|
55508
|
+
}, [eventBus, flushSlot, sharedEntityStore, publishBindingSnapshot, orbitalsByTrait]);
|
|
53351
55509
|
const runTickEffects = React94.useCallback((tick, binding) => {
|
|
53352
55510
|
const traitName = binding.trait.name;
|
|
53353
55511
|
const currentState = traitStatesRef.current.get(traitName)?.currentState ?? "";
|
|
@@ -53383,9 +55541,9 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
53383
55541
|
log: tickLog
|
|
53384
55542
|
});
|
|
53385
55543
|
}, [executeTransitionEffects, sharedEntityStore]);
|
|
53386
|
-
const emitFromSharedWriter = React94.useCallback((event, payload) => {
|
|
55544
|
+
const emitFromSharedWriter = React94.useCallback((event, payload, source) => {
|
|
53387
55545
|
const prefixedEvent = event.startsWith("UI:") ? event : `UI:${event}`;
|
|
53388
|
-
eventBus.emit(prefixedEvent, payload);
|
|
55546
|
+
eventBus.emit(prefixedEvent, payload, source);
|
|
53389
55547
|
}, [eventBus]);
|
|
53390
55548
|
React94.useEffect(() => {
|
|
53391
55549
|
const scheduler = runtime.createTickScheduler();
|
|
@@ -54136,11 +56294,11 @@ function SchemaRunner({ schema, serverUrl, transport, mockData, pageName, routeP
|
|
|
54136
56294
|
for (const orb of schema.orbitals) {
|
|
54137
56295
|
for (const pageRef of orb.pages ?? []) {
|
|
54138
56296
|
const name = typeof pageRef === "object" && pageRef !== null ? pageRef.name : void 0;
|
|
54139
|
-
if (name === pageName) return orb.theme;
|
|
56297
|
+
if (name === pageName) return orb.theme ?? schema.theme;
|
|
54140
56298
|
}
|
|
54141
56299
|
}
|
|
54142
56300
|
}
|
|
54143
|
-
return schema.orbitals[0]?.theme;
|
|
56301
|
+
return schema.orbitals[0]?.theme ?? schema.theme;
|
|
54144
56302
|
}, [schema, pageName]);
|
|
54145
56303
|
const inner = /* @__PURE__ */ jsxRuntime.jsx(providers.VerificationProvider, { enabled: true, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
54146
56304
|
providers.EntitySchemaProvider,
|