sketchmark 0.2.7 → 0.2.8
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/README.md +1041 -1066
- package/dist/animation/index.d.ts +6 -1
- package/dist/animation/index.d.ts.map +1 -1
- package/dist/ast/types.d.ts +6 -17
- package/dist/ast/types.d.ts.map +1 -1
- package/dist/config.d.ts +150 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/export/index.d.ts.map +1 -1
- package/dist/index.cjs +1566 -1352
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1566 -1352
- package/dist/index.js.map +1 -1
- package/dist/layout/entity-rect.d.ts +9 -0
- package/dist/layout/entity-rect.d.ts.map +1 -0
- package/dist/layout/index.d.ts.map +1 -1
- package/dist/markdown/parser.d.ts.map +1 -1
- package/dist/parser/index.d.ts.map +1 -1
- package/dist/parser/tokenizer.d.ts.map +1 -1
- package/dist/renderer/canvas/index.d.ts.map +1 -1
- package/dist/renderer/roughChart.d.ts.map +1 -1
- package/dist/renderer/shapes/box.d.ts +3 -0
- package/dist/renderer/shapes/box.d.ts.map +1 -0
- package/dist/renderer/shapes/circle.d.ts +3 -0
- package/dist/renderer/shapes/circle.d.ts.map +1 -0
- package/dist/renderer/shapes/cylinder.d.ts +3 -0
- package/dist/renderer/shapes/cylinder.d.ts.map +1 -0
- package/dist/renderer/shapes/diamond.d.ts +3 -0
- package/dist/renderer/shapes/diamond.d.ts.map +1 -0
- package/dist/renderer/shapes/hexagon.d.ts +3 -0
- package/dist/renderer/shapes/hexagon.d.ts.map +1 -0
- package/dist/renderer/shapes/icon.d.ts +3 -0
- package/dist/renderer/shapes/icon.d.ts.map +1 -0
- package/dist/renderer/shapes/image.d.ts +3 -0
- package/dist/renderer/shapes/image.d.ts.map +1 -0
- package/dist/renderer/shapes/index.d.ts +4 -0
- package/dist/renderer/shapes/index.d.ts.map +1 -0
- package/dist/renderer/shapes/line.d.ts +3 -0
- package/dist/renderer/shapes/line.d.ts.map +1 -0
- package/dist/renderer/shapes/note.d.ts +3 -0
- package/dist/renderer/shapes/note.d.ts.map +1 -0
- package/dist/renderer/shapes/parallelogram.d.ts +3 -0
- package/dist/renderer/shapes/parallelogram.d.ts.map +1 -0
- package/dist/renderer/shapes/path.d.ts +3 -0
- package/dist/renderer/shapes/path.d.ts.map +1 -0
- package/dist/renderer/shapes/registry.d.ts +5 -0
- package/dist/renderer/shapes/registry.d.ts.map +1 -0
- package/dist/renderer/shapes/text-shape.d.ts +3 -0
- package/dist/renderer/shapes/text-shape.d.ts.map +1 -0
- package/dist/renderer/shapes/triangle.d.ts +3 -0
- package/dist/renderer/shapes/triangle.d.ts.map +1 -0
- package/dist/renderer/shapes/types.d.ts +50 -0
- package/dist/renderer/shapes/types.d.ts.map +1 -0
- package/dist/renderer/shared.d.ts +26 -0
- package/dist/renderer/shared.d.ts.map +1 -0
- package/dist/renderer/svg/index.d.ts.map +1 -1
- package/dist/renderer/svg/roughChartSVG.d.ts.map +1 -1
- package/dist/renderer/typography.d.ts +27 -0
- package/dist/renderer/typography.d.ts.map +1 -0
- package/dist/scene/index.d.ts +5 -13
- package/dist/scene/index.d.ts.map +1 -1
- package/dist/sketchmark.iife.js +1567 -1353
- package/package.json +1 -1
package/dist/sketchmark.iife.js
CHANGED
|
@@ -21,6 +21,8 @@ var AIDiagram = (function (exports) {
|
|
|
21
21
|
"text",
|
|
22
22
|
"image",
|
|
23
23
|
"icon",
|
|
24
|
+
"line",
|
|
25
|
+
"path",
|
|
24
26
|
"group",
|
|
25
27
|
"style",
|
|
26
28
|
"step",
|
|
@@ -224,7 +226,7 @@ var AIDiagram = (function (exports) {
|
|
|
224
226
|
function resetUid() {
|
|
225
227
|
_uid = 0;
|
|
226
228
|
}
|
|
227
|
-
const SHAPES = [
|
|
229
|
+
const SHAPES$1 = [
|
|
228
230
|
"box",
|
|
229
231
|
"circle",
|
|
230
232
|
"diamond",
|
|
@@ -235,6 +237,8 @@ var AIDiagram = (function (exports) {
|
|
|
235
237
|
"text",
|
|
236
238
|
"image",
|
|
237
239
|
"icon",
|
|
240
|
+
"line",
|
|
241
|
+
"path",
|
|
238
242
|
];
|
|
239
243
|
const CHART_TYPES = [
|
|
240
244
|
"bar-chart",
|
|
@@ -315,7 +319,6 @@ var AIDiagram = (function (exports) {
|
|
|
315
319
|
edges: [],
|
|
316
320
|
groups: [],
|
|
317
321
|
steps: [],
|
|
318
|
-
notes: [],
|
|
319
322
|
charts: [],
|
|
320
323
|
tables: [],
|
|
321
324
|
markdowns: [],
|
|
@@ -326,7 +329,6 @@ var AIDiagram = (function (exports) {
|
|
|
326
329
|
};
|
|
327
330
|
const nodeIds = new Set();
|
|
328
331
|
const tableIds = new Set();
|
|
329
|
-
const noteIds = new Set();
|
|
330
332
|
const chartIds = new Set();
|
|
331
333
|
const groupIds = new Set();
|
|
332
334
|
const markdownIds = new Set();
|
|
@@ -425,10 +427,14 @@ var AIDiagram = (function (exports) {
|
|
|
425
427
|
kind: "node",
|
|
426
428
|
id,
|
|
427
429
|
shape,
|
|
428
|
-
label: props.label ||
|
|
430
|
+
label: props.label || "",
|
|
429
431
|
...(groupId ? { groupId } : {}),
|
|
430
432
|
...(props.width ? { width: parseFloat(props.width) } : {}),
|
|
431
433
|
...(props.height ? { height: parseFloat(props.height) } : {}),
|
|
434
|
+
...(props.deg ? { deg: parseFloat(props.deg) } : {}),
|
|
435
|
+
...(props.dx ? { dx: parseFloat(props.dx) } : {}),
|
|
436
|
+
...(props.dy ? { dy: parseFloat(props.dy) } : {}),
|
|
437
|
+
...(props.factor ? { factor: parseFloat(props.factor) } : {}),
|
|
432
438
|
...(props.theme ? { theme: props.theme } : {}),
|
|
433
439
|
style: propsToStyle(props),
|
|
434
440
|
};
|
|
@@ -436,6 +442,8 @@ var AIDiagram = (function (exports) {
|
|
|
436
442
|
node.imageUrl = props.url;
|
|
437
443
|
if (props.name)
|
|
438
444
|
node.iconName = props.name;
|
|
445
|
+
if (props.value)
|
|
446
|
+
node.pathData = props.value;
|
|
439
447
|
return node;
|
|
440
448
|
}
|
|
441
449
|
function parseEdge(fromId, connector, rest) {
|
|
@@ -476,7 +484,7 @@ var AIDiagram = (function (exports) {
|
|
|
476
484
|
style: propsToStyle(props),
|
|
477
485
|
};
|
|
478
486
|
}
|
|
479
|
-
// ── parseNote
|
|
487
|
+
// ── parseNote → returns ASTNode with shape='note' ────────
|
|
480
488
|
function parseNote(groupId) {
|
|
481
489
|
skip(); // 'note'
|
|
482
490
|
const toks = lineTokens();
|
|
@@ -506,9 +514,11 @@ var AIDiagram = (function (exports) {
|
|
|
506
514
|
// Support multiline via literal \n in label string
|
|
507
515
|
const rawLabel = props.label ?? "";
|
|
508
516
|
return {
|
|
509
|
-
kind: "
|
|
517
|
+
kind: "node",
|
|
510
518
|
id,
|
|
519
|
+
shape: "note",
|
|
511
520
|
label: rawLabel.replace(/\\n/g, "\n"),
|
|
521
|
+
groupId,
|
|
512
522
|
theme: props.theme,
|
|
513
523
|
style: propsToStyle(props),
|
|
514
524
|
...(props.width ? { width: parseFloat(props.width) } : {}),
|
|
@@ -596,12 +606,12 @@ var AIDiagram = (function (exports) {
|
|
|
596
606
|
group.children.push({ kind: "table", id: tbl.id });
|
|
597
607
|
continue;
|
|
598
608
|
}
|
|
599
|
-
// ── Note
|
|
609
|
+
// ── Note (parsed as node with shape='note') ──────
|
|
600
610
|
if (v === "note") {
|
|
601
611
|
const note = parseNote(id);
|
|
602
|
-
ast.
|
|
603
|
-
|
|
604
|
-
group.children.push({ kind: "
|
|
612
|
+
ast.nodes.push(note);
|
|
613
|
+
nodeIds.add(note.id);
|
|
614
|
+
group.children.push({ kind: "node", id: note.id });
|
|
605
615
|
continue;
|
|
606
616
|
}
|
|
607
617
|
// ── Markdown ───────────────────────────────────────
|
|
@@ -628,7 +638,7 @@ var AIDiagram = (function (exports) {
|
|
|
628
638
|
continue;
|
|
629
639
|
}
|
|
630
640
|
// ── Node shape ────────────────────────────────────
|
|
631
|
-
if (SHAPES.includes(v)) {
|
|
641
|
+
if (SHAPES$1.includes(v)) {
|
|
632
642
|
const node = parseNode(v, id);
|
|
633
643
|
if (!nodeIds.has(node.id)) {
|
|
634
644
|
nodeIds.add(node.id);
|
|
@@ -830,7 +840,7 @@ var AIDiagram = (function (exports) {
|
|
|
830
840
|
props[k] = cur().value;
|
|
831
841
|
skip();
|
|
832
842
|
}
|
|
833
|
-
else if (SHAPES.includes(v) ||
|
|
843
|
+
else if (SHAPES$1.includes(v) ||
|
|
834
844
|
v === "step" ||
|
|
835
845
|
v === "group" ||
|
|
836
846
|
v === "note" || // ← ADD
|
|
@@ -890,7 +900,7 @@ var AIDiagram = (function (exports) {
|
|
|
890
900
|
const table = {
|
|
891
901
|
kind: "table",
|
|
892
902
|
id,
|
|
893
|
-
label: props.label ??
|
|
903
|
+
label: props.label ?? "",
|
|
894
904
|
rows: [],
|
|
895
905
|
theme: props.theme,
|
|
896
906
|
style: propsToStyle(props),
|
|
@@ -1101,12 +1111,12 @@ var AIDiagram = (function (exports) {
|
|
|
1101
1111
|
ast.rootOrder.push({ kind: "table", id: tbl.id });
|
|
1102
1112
|
continue;
|
|
1103
1113
|
}
|
|
1104
|
-
// note
|
|
1114
|
+
// note (parsed as node with shape='note')
|
|
1105
1115
|
if (v === "note") {
|
|
1106
1116
|
const note = parseNote();
|
|
1107
|
-
ast.
|
|
1108
|
-
|
|
1109
|
-
ast.rootOrder.push({ kind: "
|
|
1117
|
+
ast.nodes.push(note);
|
|
1118
|
+
nodeIds.add(note.id);
|
|
1119
|
+
ast.rootOrder.push({ kind: "node", id: note.id });
|
|
1110
1120
|
continue;
|
|
1111
1121
|
}
|
|
1112
1122
|
// step
|
|
@@ -1143,7 +1153,6 @@ var AIDiagram = (function (exports) {
|
|
|
1143
1153
|
for (const nid of [fromId, edge.to]) {
|
|
1144
1154
|
if (!nodeIds.has(nid) &&
|
|
1145
1155
|
!tableIds.has(nid) &&
|
|
1146
|
-
!noteIds.has(nid) &&
|
|
1147
1156
|
!chartIds.has(nid) &&
|
|
1148
1157
|
!groupIds.has(nid)) {
|
|
1149
1158
|
nodeIds.add(nid);
|
|
@@ -1161,7 +1170,7 @@ var AIDiagram = (function (exports) {
|
|
|
1161
1170
|
}
|
|
1162
1171
|
}
|
|
1163
1172
|
// node shapes — only reached if NOT followed by an arrow
|
|
1164
|
-
if (SHAPES.includes(v)) {
|
|
1173
|
+
if (SHAPES$1.includes(v)) {
|
|
1165
1174
|
const node = parseNode(v);
|
|
1166
1175
|
if (!nodeIds.has(node.id)) {
|
|
1167
1176
|
nodeIds.add(node.id);
|
|
@@ -1182,32 +1191,146 @@ var AIDiagram = (function (exports) {
|
|
|
1182
1191
|
}
|
|
1183
1192
|
|
|
1184
1193
|
// ============================================================
|
|
1185
|
-
// sketchmark —
|
|
1186
|
-
//
|
|
1194
|
+
// sketchmark — Design Tokens (single source of truth)
|
|
1195
|
+
//
|
|
1196
|
+
// All layout, sizing, typography, and rendering constants live
|
|
1197
|
+
// here. Import from this file instead of scattering magic
|
|
1198
|
+
// numbers across modules.
|
|
1187
1199
|
// ============================================================
|
|
1188
|
-
// ──
|
|
1189
|
-
const
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1200
|
+
// ── Layout ─────────────────────────────────────────────────
|
|
1201
|
+
const LAYOUT = {
|
|
1202
|
+
margin: 60, // default canvas margin (px)
|
|
1203
|
+
gap: 80, // default gap between root-level items (px)
|
|
1204
|
+
groupLabelH: 22, // height reserved for group label strip (px)
|
|
1205
|
+
groupPad: 26, // default group inner padding (px)
|
|
1206
|
+
groupGap: 10, // default gap between items inside a group (px)
|
|
1207
|
+
};
|
|
1208
|
+
// ── Node sizing ────────────────────────────────────────────
|
|
1209
|
+
const NODE = {
|
|
1210
|
+
minW: 90, // minimum auto-sized node width (px)
|
|
1211
|
+
maxW: 180, // maximum auto-sized node width (px)
|
|
1212
|
+
fontPxPerChar: 8.6, // approximate px per character for label width
|
|
1213
|
+
basePad: 26, // base padding added to label width (px)
|
|
1214
|
+
};
|
|
1215
|
+
// ── Shape-specific sizing ──────────────────────────────────
|
|
1216
|
+
const SHAPES = {
|
|
1217
|
+
cylinder: { defaultH: 66, ellipseH: 18 },
|
|
1218
|
+
diamond: { minW: 130, minH: 62, aspect: 0.46, labelPad: 30 },
|
|
1219
|
+
hexagon: { minW: 126, minH: 54, aspect: 0.44, labelPad: 20, inset: 0.56 },
|
|
1220
|
+
triangle: { minW: 108, minH: 64, aspect: 0.6, labelPad: 10 },
|
|
1221
|
+
parallelogram: { defaultH: 50, labelPad: 28, skew: 18 },
|
|
1222
|
+
};
|
|
1223
|
+
// ── Table sizing ───────────────────────────────────────────
|
|
1224
|
+
const TABLE = {
|
|
1225
|
+
cellPad: 20, // total horizontal padding per cell (px)
|
|
1226
|
+
minColW: 50, // minimum column width (px)
|
|
1227
|
+
fontPxPerChar: 7.5, // approx px per char at 12px sans-serif
|
|
1228
|
+
rowH: 30, // data row height (px)
|
|
1229
|
+
headerH: 34, // header row height (px)
|
|
1230
|
+
labelH: 22, // label strip height (px)
|
|
1231
|
+
};
|
|
1232
|
+
// ── Note shape ─────────────────────────────────────────────
|
|
1233
|
+
const NOTE = {
|
|
1234
|
+
lineH: 20, // line height for note text (px)
|
|
1235
|
+
padX: 16, // horizontal padding (px)
|
|
1236
|
+
padY: 12, // vertical padding (px)
|
|
1237
|
+
fontPxPerChar: 7.5, // approx px per char for note text
|
|
1238
|
+
fold: 14, // fold corner size (px)
|
|
1239
|
+
minW: 120, // minimum note width (px)
|
|
1240
|
+
};
|
|
1241
|
+
// ── Typography defaults ────────────────────────────────────
|
|
1242
|
+
const TYPOGRAPHY = {
|
|
1243
|
+
defaultFontSize: 14,
|
|
1244
|
+
defaultFontWeight: 500,
|
|
1245
|
+
defaultLineHeight: 1.3, // multiplier (× fontSize = px)
|
|
1246
|
+
defaultPadding: 8,
|
|
1247
|
+
defaultAlign: "center",
|
|
1248
|
+
defaultVAlign: "middle",
|
|
1249
|
+
};
|
|
1250
|
+
// ── Title ──────────────────────────────────────────────────
|
|
1251
|
+
const TITLE = {
|
|
1252
|
+
y: 26, // baseline Y position (px)
|
|
1253
|
+
fontSize: 18, // default title font size
|
|
1254
|
+
fontWeight: 600, // default title font weight
|
|
1255
|
+
};
|
|
1256
|
+
// ── Group label typography ─────────────────────────────────
|
|
1257
|
+
const GROUP_LABEL = {
|
|
1258
|
+
fontSize: 12,
|
|
1259
|
+
fontWeight: 500,
|
|
1260
|
+
padding: 14,
|
|
1195
1261
|
};
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1262
|
+
// ── Edge / arrow ───────────────────────────────────────────
|
|
1263
|
+
const EDGE = {
|
|
1264
|
+
arrowSize: 12, // arrowhead polygon size (px)
|
|
1265
|
+
headInset: 13, // line shortening for arrowhead overlap (px)
|
|
1266
|
+
labelOffset: 14, // perpendicular offset of label from edge line (px)
|
|
1267
|
+
labelFontSize: 11, // default edge label font size
|
|
1268
|
+
labelFontWeight: 400, // default edge label font weight
|
|
1269
|
+
dashPattern: [6, 5], // stroke-dasharray for dashed edges
|
|
1202
1270
|
};
|
|
1203
|
-
//
|
|
1204
|
-
const
|
|
1205
|
-
h1:
|
|
1206
|
-
h2:
|
|
1207
|
-
h3: 28,
|
|
1208
|
-
|
|
1209
|
-
blank: 10,
|
|
1271
|
+
// ── Markdown typography ────────────────────────────────────
|
|
1272
|
+
const MARKDOWN = {
|
|
1273
|
+
fontSize: { h1: 40, h2: 28, h3: 20, p: 15, blank: 0 },
|
|
1274
|
+
fontWeight: { h1: 700, h2: 600, h3: 600, p: 400, blank: 400 },
|
|
1275
|
+
spacing: { h1: 52, h2: 38, h3: 28, p: 22, blank: 10 },
|
|
1276
|
+
defaultPad: 16,
|
|
1210
1277
|
};
|
|
1278
|
+
// ── Rough.js rendering ─────────────────────────────────────
|
|
1279
|
+
const ROUGH = {
|
|
1280
|
+
roughness: 1.3, // default roughness for nodes/edges
|
|
1281
|
+
chartRoughness: 1.2, // slightly smoother for chart elements
|
|
1282
|
+
bowing: 0.7,
|
|
1283
|
+
};
|
|
1284
|
+
// ── Chart layout ───────────────────────────────────────────
|
|
1285
|
+
const CHART = {
|
|
1286
|
+
titleH: 24, // title strip height when label present (px)
|
|
1287
|
+
titleHEmpty: 8, // title strip height when no label (px)
|
|
1288
|
+
padL: 44, // left padding for plot area (px)
|
|
1289
|
+
padR: 12, // right padding (px)
|
|
1290
|
+
padT: 6, // top padding (px)
|
|
1291
|
+
padB: 28, // bottom padding (px)
|
|
1292
|
+
defaultW: 320, // default chart width (px)
|
|
1293
|
+
defaultH: 240, // default chart height (px)
|
|
1294
|
+
};
|
|
1295
|
+
// ── Animation timing ───────────────────────────────────────
|
|
1296
|
+
const ANIMATION = {
|
|
1297
|
+
// Edge drawing
|
|
1298
|
+
strokeDur: 360, // edge stroke-draw duration (ms)
|
|
1299
|
+
arrowReveal: 120, // arrow fade-in delay after stroke (ms)
|
|
1300
|
+
dashClear: 160, // delay before clearing dash overrides (ms)
|
|
1301
|
+
// Shape drawing (per entity type)
|
|
1302
|
+
nodeStrokeDur: 420, // node stroke-draw duration (ms)
|
|
1303
|
+
nodeStagger: 55, // stagger between node paths (ms)
|
|
1304
|
+
groupStrokeDur: 550, // group stroke-draw duration (ms)
|
|
1305
|
+
groupStagger: 40, // stagger between group paths (ms)
|
|
1306
|
+
tableStrokeDur: 500, // table stroke-draw duration (ms)
|
|
1307
|
+
tableStagger: 40, // stagger between table paths (ms)
|
|
1308
|
+
// Text / misc
|
|
1309
|
+
textFade: 200, // text opacity fade-in duration (ms)
|
|
1310
|
+
fillFadeOffset: -60, // fill-opacity start relative to stroke end (ms)
|
|
1311
|
+
textDelay: 80, // extra buffer before text reveals (ms)
|
|
1312
|
+
chartFade: 500, // chart/markdown opacity transition (ms)
|
|
1313
|
+
};
|
|
1314
|
+
// ── Export defaults ────────────────────────────────────────
|
|
1315
|
+
const EXPORT = {
|
|
1316
|
+
pngScale: 2, // default PNG pixel density multiplier
|
|
1317
|
+
fallbackW: 400, // fallback SVG width when not set (px)
|
|
1318
|
+
fallbackH: 300, // fallback SVG height when not set (px)
|
|
1319
|
+
fallbackBg: "#f8f4ea", // default PNG/HTML background color
|
|
1320
|
+
revokeDelay: 5000, // blob URL revocation delay (ms)
|
|
1321
|
+
defaultFps: 30, // default video FPS
|
|
1322
|
+
};
|
|
1323
|
+
// ── SVG namespace ──────────────────────────────────────────
|
|
1324
|
+
const SVG_NS$1 = "http://www.w3.org/2000/svg";
|
|
1325
|
+
|
|
1326
|
+
// ============================================================
|
|
1327
|
+
// sketchmark — Markdown inline parser
|
|
1328
|
+
// Supports: # h1 ## h2 ### h3 **bold** *italic* blank lines
|
|
1329
|
+
// ============================================================
|
|
1330
|
+
// ── Font sizes per line kind (re-exported from config) ───
|
|
1331
|
+
const LINE_FONT_SIZE = { ...MARKDOWN.fontSize };
|
|
1332
|
+
const LINE_FONT_WEIGHT = { ...MARKDOWN.fontWeight };
|
|
1333
|
+
const LINE_SPACING = { ...MARKDOWN.spacing };
|
|
1211
1334
|
// ── Parse a full markdown string into lines ───────────────
|
|
1212
1335
|
function parseMarkdownContent(content) {
|
|
1213
1336
|
const raw = content.split('\n');
|
|
@@ -1259,7 +1382,7 @@ var AIDiagram = (function (exports) {
|
|
|
1259
1382
|
return runs;
|
|
1260
1383
|
}
|
|
1261
1384
|
// ── Calculate natural height of a parsed block ────────────
|
|
1262
|
-
function calcMarkdownHeight(lines, pad =
|
|
1385
|
+
function calcMarkdownHeight(lines, pad = MARKDOWN.defaultPad) {
|
|
1263
1386
|
let h = pad * 2; // top + bottom
|
|
1264
1387
|
for (const line of lines)
|
|
1265
1388
|
h += LINE_SPACING[line.kind];
|
|
@@ -1281,9 +1404,14 @@ var AIDiagram = (function (exports) {
|
|
|
1281
1404
|
groupId: n.groupId,
|
|
1282
1405
|
width: n.width,
|
|
1283
1406
|
height: n.height,
|
|
1407
|
+
deg: n.deg,
|
|
1408
|
+
dx: n.dx,
|
|
1409
|
+
dy: n.dy,
|
|
1410
|
+
factor: n.factor,
|
|
1284
1411
|
meta: n.meta,
|
|
1285
1412
|
imageUrl: n.imageUrl,
|
|
1286
1413
|
iconName: n.iconName,
|
|
1414
|
+
pathData: n.pathData,
|
|
1287
1415
|
x: 0,
|
|
1288
1416
|
y: 0,
|
|
1289
1417
|
w: 0,
|
|
@@ -1299,8 +1427,8 @@ var AIDiagram = (function (exports) {
|
|
|
1299
1427
|
children: g.children,
|
|
1300
1428
|
layout: (g.layout ?? "column"),
|
|
1301
1429
|
columns: g.columns ?? 1,
|
|
1302
|
-
padding: g.padding ??
|
|
1303
|
-
gap: g.gap ??
|
|
1430
|
+
padding: g.padding ?? LAYOUT.groupPad,
|
|
1431
|
+
gap: g.gap ?? LAYOUT.groupGap,
|
|
1304
1432
|
align: (g.align ?? "start"),
|
|
1305
1433
|
justify: (g.justify ?? "start"),
|
|
1306
1434
|
style: { ...ast.styles[g.id], ...themeStyle, ...g.style },
|
|
@@ -1319,9 +1447,9 @@ var AIDiagram = (function (exports) {
|
|
|
1319
1447
|
label: t.label,
|
|
1320
1448
|
rows: t.rows,
|
|
1321
1449
|
colWidths: [],
|
|
1322
|
-
rowH:
|
|
1323
|
-
headerH:
|
|
1324
|
-
labelH:
|
|
1450
|
+
rowH: TABLE.rowH,
|
|
1451
|
+
headerH: TABLE.headerH,
|
|
1452
|
+
labelH: TABLE.labelH,
|
|
1325
1453
|
style: { ...ast.styles[t.id], ...themeStyle, ...t.style },
|
|
1326
1454
|
x: 0,
|
|
1327
1455
|
y: 0,
|
|
@@ -1329,20 +1457,6 @@ var AIDiagram = (function (exports) {
|
|
|
1329
1457
|
h: 0,
|
|
1330
1458
|
};
|
|
1331
1459
|
});
|
|
1332
|
-
const notes = ast.notes.map((n) => {
|
|
1333
|
-
const themeStyle = n.theme ? (ast.themes[n.theme] ?? {}) : {};
|
|
1334
|
-
return {
|
|
1335
|
-
id: n.id,
|
|
1336
|
-
lines: n.label.split("\n"),
|
|
1337
|
-
style: { ...ast.styles[n.id], ...themeStyle, ...n.style },
|
|
1338
|
-
x: 0,
|
|
1339
|
-
y: 0,
|
|
1340
|
-
w: 0,
|
|
1341
|
-
h: 0,
|
|
1342
|
-
width: n.width,
|
|
1343
|
-
height: n.height,
|
|
1344
|
-
};
|
|
1345
|
-
});
|
|
1346
1460
|
const charts = ast.charts.map((c) => {
|
|
1347
1461
|
const themeStyle = c.theme ? (ast.themes[c.theme] ?? {}) : {};
|
|
1348
1462
|
return {
|
|
@@ -1353,8 +1467,8 @@ var AIDiagram = (function (exports) {
|
|
|
1353
1467
|
style: { ...ast.styles[c.id], ...themeStyle, ...c.style },
|
|
1354
1468
|
x: 0,
|
|
1355
1469
|
y: 0,
|
|
1356
|
-
w: c.width ??
|
|
1357
|
-
h: c.height ??
|
|
1470
|
+
w: c.width ?? CHART.defaultW,
|
|
1471
|
+
h: c.height ?? CHART.defaultH,
|
|
1358
1472
|
};
|
|
1359
1473
|
});
|
|
1360
1474
|
const markdowns = (ast.markdowns ?? []).map(m => {
|
|
@@ -1397,7 +1511,6 @@ var AIDiagram = (function (exports) {
|
|
|
1397
1511
|
edges,
|
|
1398
1512
|
groups,
|
|
1399
1513
|
tables,
|
|
1400
|
-
notes,
|
|
1401
1514
|
charts,
|
|
1402
1515
|
markdowns,
|
|
1403
1516
|
animation: { steps: ast.steps, currentStep: -1 },
|
|
@@ -1418,9 +1531,6 @@ var AIDiagram = (function (exports) {
|
|
|
1418
1531
|
function tableMap(sg) {
|
|
1419
1532
|
return new Map(sg.tables.map((t) => [t.id, t]));
|
|
1420
1533
|
}
|
|
1421
|
-
function noteMap(sg) {
|
|
1422
|
-
return new Map(sg.notes.map((n) => [n.id, n]));
|
|
1423
|
-
}
|
|
1424
1534
|
function chartMap(sg) {
|
|
1425
1535
|
return new Map(sg.charts.map((c) => [c.id, c]));
|
|
1426
1536
|
}
|
|
@@ -1428,6 +1538,688 @@ var AIDiagram = (function (exports) {
|
|
|
1428
1538
|
return new Map((sg.markdowns ?? []).map(m => [m.id, m]));
|
|
1429
1539
|
}
|
|
1430
1540
|
|
|
1541
|
+
// ============================================================
|
|
1542
|
+
// Entity Rect Map — unified lookup for all positionable entities
|
|
1543
|
+
//
|
|
1544
|
+
// Every scene entity (node, group, table, chart, markdown)
|
|
1545
|
+
// has { x, y, w, h }. This map lets layout code look up any
|
|
1546
|
+
// entity by ID without kind dispatch.
|
|
1547
|
+
// ============================================================
|
|
1548
|
+
function buildEntityMap(sg) {
|
|
1549
|
+
const m = new Map();
|
|
1550
|
+
for (const n of sg.nodes)
|
|
1551
|
+
m.set(n.id, n);
|
|
1552
|
+
for (const g of sg.groups)
|
|
1553
|
+
m.set(g.id, g);
|
|
1554
|
+
for (const t of sg.tables)
|
|
1555
|
+
m.set(t.id, t);
|
|
1556
|
+
for (const c of sg.charts)
|
|
1557
|
+
m.set(c.id, c);
|
|
1558
|
+
for (const md of sg.markdowns)
|
|
1559
|
+
m.set(md.id, md);
|
|
1560
|
+
return m;
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
// ============================================================
|
|
1564
|
+
// Shape Strategy Interfaces
|
|
1565
|
+
// ============================================================
|
|
1566
|
+
// Re-export from centralized config for backward compatibility
|
|
1567
|
+
const MIN_W = NODE.minW;
|
|
1568
|
+
const MAX_W = NODE.maxW;
|
|
1569
|
+
const SVG_NS = SVG_NS$1;
|
|
1570
|
+
|
|
1571
|
+
// ============================================================
|
|
1572
|
+
// Shape Registry — Strategy pattern for extensible shapes
|
|
1573
|
+
// ============================================================
|
|
1574
|
+
const shapes = new Map();
|
|
1575
|
+
function registerShape(name, def) {
|
|
1576
|
+
shapes.set(name, def);
|
|
1577
|
+
}
|
|
1578
|
+
function getShape(name) {
|
|
1579
|
+
return shapes.get(name);
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
const boxShape = {
|
|
1583
|
+
size(n, labelW) {
|
|
1584
|
+
n.w = n.w || Math.max(MIN_W, Math.min(MAX_W, labelW));
|
|
1585
|
+
n.h = n.h || 52;
|
|
1586
|
+
},
|
|
1587
|
+
renderSVG(rc, n, _palette, opts) {
|
|
1588
|
+
return [rc.rectangle(n.x + 1, n.y + 1, n.w - 2, n.h - 2, opts)];
|
|
1589
|
+
},
|
|
1590
|
+
renderCanvas(rc, _ctx, n, _palette, opts) {
|
|
1591
|
+
rc.rectangle(n.x + 1, n.y + 1, n.w - 2, n.h - 2, opts);
|
|
1592
|
+
},
|
|
1593
|
+
};
|
|
1594
|
+
|
|
1595
|
+
const circleShape = {
|
|
1596
|
+
size(n, labelW) {
|
|
1597
|
+
n.w = n.w || Math.max(84, Math.min(MAX_W, labelW));
|
|
1598
|
+
n.h = n.h || n.w;
|
|
1599
|
+
},
|
|
1600
|
+
renderSVG(rc, n, _palette, opts) {
|
|
1601
|
+
const cx = n.x + n.w / 2, cy = n.y + n.h / 2;
|
|
1602
|
+
return [rc.ellipse(cx, cy, n.w * 0.88, n.h * 0.88, opts)];
|
|
1603
|
+
},
|
|
1604
|
+
renderCanvas(rc, _ctx, n, _palette, opts) {
|
|
1605
|
+
const cx = n.x + n.w / 2, cy = n.y + n.h / 2;
|
|
1606
|
+
rc.ellipse(cx, cy, n.w * 0.88, n.h * 0.88, opts);
|
|
1607
|
+
},
|
|
1608
|
+
};
|
|
1609
|
+
|
|
1610
|
+
const diamondShape = {
|
|
1611
|
+
size(n, labelW) {
|
|
1612
|
+
n.w = n.w || Math.max(SHAPES.diamond.minW, Math.min(MAX_W, labelW + SHAPES.diamond.labelPad));
|
|
1613
|
+
n.h = n.h || Math.max(SHAPES.diamond.minH, n.w * SHAPES.diamond.aspect);
|
|
1614
|
+
},
|
|
1615
|
+
renderSVG(rc, n, _palette, opts) {
|
|
1616
|
+
const cx = n.x + n.w / 2, cy = n.y + n.h / 2;
|
|
1617
|
+
const hw = n.w / 2 - 2;
|
|
1618
|
+
return [rc.polygon([[cx, n.y + 2], [cx + hw, cy], [cx, n.y + n.h - 2], [cx - hw, cy]], opts)];
|
|
1619
|
+
},
|
|
1620
|
+
renderCanvas(rc, _ctx, n, _palette, opts) {
|
|
1621
|
+
const cx = n.x + n.w / 2, cy = n.y + n.h / 2;
|
|
1622
|
+
const hw = n.w / 2 - 2;
|
|
1623
|
+
rc.polygon([[cx, n.y + 2], [cx + hw, cy], [cx, n.y + n.h - 2], [cx - hw, cy]], opts);
|
|
1624
|
+
},
|
|
1625
|
+
};
|
|
1626
|
+
|
|
1627
|
+
const hexagonShape = {
|
|
1628
|
+
size(n, labelW) {
|
|
1629
|
+
n.w = n.w || Math.max(SHAPES.hexagon.minW, Math.min(MAX_W, labelW + SHAPES.hexagon.labelPad));
|
|
1630
|
+
n.h = n.h || Math.max(SHAPES.hexagon.minH, n.w * SHAPES.hexagon.aspect);
|
|
1631
|
+
},
|
|
1632
|
+
renderSVG(rc, n, _palette, opts) {
|
|
1633
|
+
const cx = n.x + n.w / 2, cy = n.y + n.h / 2;
|
|
1634
|
+
const hw = n.w / 2 - 2;
|
|
1635
|
+
const hw2 = hw * SHAPES.hexagon.inset;
|
|
1636
|
+
return [rc.polygon([
|
|
1637
|
+
[cx - hw2, n.y + 3], [cx + hw2, n.y + 3], [cx + hw, cy],
|
|
1638
|
+
[cx + hw2, n.y + n.h - 3], [cx - hw2, n.y + n.h - 3], [cx - hw, cy],
|
|
1639
|
+
], opts)];
|
|
1640
|
+
},
|
|
1641
|
+
renderCanvas(rc, _ctx, n, _palette, opts) {
|
|
1642
|
+
const cx = n.x + n.w / 2, cy = n.y + n.h / 2;
|
|
1643
|
+
const hw = n.w / 2 - 2;
|
|
1644
|
+
const hw2 = hw * SHAPES.hexagon.inset;
|
|
1645
|
+
rc.polygon([
|
|
1646
|
+
[cx - hw2, n.y + 3], [cx + hw2, n.y + 3], [cx + hw, cy],
|
|
1647
|
+
[cx + hw2, n.y + n.h - 3], [cx - hw2, n.y + n.h - 3], [cx - hw, cy],
|
|
1648
|
+
], opts);
|
|
1649
|
+
},
|
|
1650
|
+
};
|
|
1651
|
+
|
|
1652
|
+
const triangleShape = {
|
|
1653
|
+
size(n, labelW) {
|
|
1654
|
+
n.w = n.w || Math.max(SHAPES.triangle.minW, Math.min(MAX_W, labelW + SHAPES.triangle.labelPad));
|
|
1655
|
+
n.h = n.h || Math.max(SHAPES.triangle.minH, n.w * SHAPES.triangle.aspect);
|
|
1656
|
+
},
|
|
1657
|
+
renderSVG(rc, n, _palette, opts) {
|
|
1658
|
+
const cx = n.x + n.w / 2;
|
|
1659
|
+
return [rc.polygon([
|
|
1660
|
+
[cx, n.y + 3],
|
|
1661
|
+
[n.x + n.w - 3, n.y + n.h - 3],
|
|
1662
|
+
[n.x + 3, n.y + n.h - 3],
|
|
1663
|
+
], opts)];
|
|
1664
|
+
},
|
|
1665
|
+
renderCanvas(rc, _ctx, n, _palette, opts) {
|
|
1666
|
+
const cx = n.x + n.w / 2;
|
|
1667
|
+
rc.polygon([
|
|
1668
|
+
[cx, n.y + 3],
|
|
1669
|
+
[n.x + n.w - 3, n.y + n.h - 3],
|
|
1670
|
+
[n.x + 3, n.y + n.h - 3],
|
|
1671
|
+
], opts);
|
|
1672
|
+
},
|
|
1673
|
+
};
|
|
1674
|
+
|
|
1675
|
+
const cylinderShape = {
|
|
1676
|
+
size(n, labelW) {
|
|
1677
|
+
n.w = n.w || Math.max(MIN_W, Math.min(MAX_W, labelW));
|
|
1678
|
+
n.h = n.h || SHAPES.cylinder.defaultH;
|
|
1679
|
+
},
|
|
1680
|
+
renderSVG(rc, n, _palette, opts) {
|
|
1681
|
+
const cx = n.x + n.w / 2;
|
|
1682
|
+
const eH = SHAPES.cylinder.ellipseH;
|
|
1683
|
+
return [
|
|
1684
|
+
rc.rectangle(n.x + 3, n.y + eH / 2, n.w - 6, n.h - eH, opts),
|
|
1685
|
+
rc.ellipse(cx, n.y + eH / 2, n.w - 8, eH, { ...opts, roughness: 0.6 }),
|
|
1686
|
+
rc.ellipse(cx, n.y + n.h - eH / 2, n.w - 8, eH, { ...opts, roughness: 0.6, fill: "none" }),
|
|
1687
|
+
];
|
|
1688
|
+
},
|
|
1689
|
+
renderCanvas(rc, _ctx, n, _palette, opts) {
|
|
1690
|
+
const cx = n.x + n.w / 2;
|
|
1691
|
+
const eH = SHAPES.cylinder.ellipseH;
|
|
1692
|
+
rc.rectangle(n.x + 3, n.y + eH / 2, n.w - 6, n.h - eH, opts);
|
|
1693
|
+
rc.ellipse(cx, n.y + eH / 2, n.w - 8, eH, { ...opts, roughness: 0.6 });
|
|
1694
|
+
rc.ellipse(cx, n.y + n.h - eH / 2, n.w - 8, eH, { ...opts, roughness: 0.6, fill: "none" });
|
|
1695
|
+
},
|
|
1696
|
+
};
|
|
1697
|
+
|
|
1698
|
+
const parallelogramShape = {
|
|
1699
|
+
size(n, labelW) {
|
|
1700
|
+
n.w = n.w || Math.max(MIN_W, Math.min(MAX_W, labelW + SHAPES.parallelogram.labelPad));
|
|
1701
|
+
n.h = n.h || SHAPES.parallelogram.defaultH;
|
|
1702
|
+
},
|
|
1703
|
+
renderSVG(rc, n, _palette, opts) {
|
|
1704
|
+
return [rc.polygon([
|
|
1705
|
+
[n.x + SHAPES.parallelogram.skew, n.y + 1], [n.x + n.w - 1, n.y + 1],
|
|
1706
|
+
[n.x + n.w - SHAPES.parallelogram.skew, n.y + n.h - 1], [n.x + 1, n.y + n.h - 1],
|
|
1707
|
+
], opts)];
|
|
1708
|
+
},
|
|
1709
|
+
renderCanvas(rc, _ctx, n, _palette, opts) {
|
|
1710
|
+
rc.polygon([
|
|
1711
|
+
[n.x + SHAPES.parallelogram.skew, n.y + 1], [n.x + n.w - 1, n.y + 1],
|
|
1712
|
+
[n.x + n.w - SHAPES.parallelogram.skew, n.y + n.h - 1], [n.x + 1, n.y + n.h - 1],
|
|
1713
|
+
], opts);
|
|
1714
|
+
},
|
|
1715
|
+
};
|
|
1716
|
+
|
|
1717
|
+
const textShape = {
|
|
1718
|
+
size(n, _labelW) {
|
|
1719
|
+
const fontSize = Number(n.style?.fontSize ?? 13);
|
|
1720
|
+
const charWidth = fontSize * 0.55;
|
|
1721
|
+
const pad = Number(n.style?.padding ?? 8) * 2;
|
|
1722
|
+
if (n.width) {
|
|
1723
|
+
const approxLines = Math.ceil((n.label.length * charWidth) / (n.width - pad));
|
|
1724
|
+
n.w = n.width;
|
|
1725
|
+
n.h = n.height ?? Math.max(24, approxLines * fontSize * 1.5 + pad);
|
|
1726
|
+
}
|
|
1727
|
+
else {
|
|
1728
|
+
const lines = n.label.split("\\n");
|
|
1729
|
+
const longest = lines.reduce((a, b) => (a.length > b.length ? a : b), "");
|
|
1730
|
+
n.w = Math.max(MIN_W, Math.round(longest.length * charWidth + pad));
|
|
1731
|
+
n.h = n.height ?? Math.max(24, lines.length * fontSize * 1.5 + pad);
|
|
1732
|
+
}
|
|
1733
|
+
},
|
|
1734
|
+
renderSVG(_rc, _n, _palette, _opts) {
|
|
1735
|
+
return []; // no shape drawn — text only
|
|
1736
|
+
},
|
|
1737
|
+
renderCanvas(_rc, _ctx, _n, _palette, _opts) {
|
|
1738
|
+
// no shape drawn — text only
|
|
1739
|
+
},
|
|
1740
|
+
};
|
|
1741
|
+
|
|
1742
|
+
const iconShape = {
|
|
1743
|
+
size(n, labelW) {
|
|
1744
|
+
const iconBase = 48;
|
|
1745
|
+
const labelH = n.label ? 20 : 0;
|
|
1746
|
+
n.w = n.w || Math.max(iconBase, n.label ? labelW : 0);
|
|
1747
|
+
n.h = n.h || (iconBase + labelH);
|
|
1748
|
+
},
|
|
1749
|
+
renderSVG(rc, n, palette, opts) {
|
|
1750
|
+
const s = n.style ?? {};
|
|
1751
|
+
if (n.iconName) {
|
|
1752
|
+
const [prefix, name] = n.iconName.includes(":")
|
|
1753
|
+
? n.iconName.split(":", 2)
|
|
1754
|
+
: ["mdi", n.iconName];
|
|
1755
|
+
const iconColor = s.color
|
|
1756
|
+
? encodeURIComponent(String(s.color))
|
|
1757
|
+
: encodeURIComponent(String(palette.nodeStroke));
|
|
1758
|
+
const labelSpace = n.label ? 20 : 0;
|
|
1759
|
+
const iconAreaH = n.h - labelSpace;
|
|
1760
|
+
const iconSize = Math.min(n.w, iconAreaH) - 4;
|
|
1761
|
+
const iconUrl = `https://api.iconify.design/${prefix}/${name}.svg?color=${iconColor}&width=${iconSize}&height=${iconSize}`;
|
|
1762
|
+
const img = document.createElementNS(SVG_NS, "image");
|
|
1763
|
+
img.setAttribute("href", iconUrl);
|
|
1764
|
+
const iconX = n.x + (n.w - iconSize) / 2;
|
|
1765
|
+
const iconY = n.y + (iconAreaH - iconSize) / 2;
|
|
1766
|
+
img.setAttribute("x", String(iconX));
|
|
1767
|
+
img.setAttribute("y", String(iconY));
|
|
1768
|
+
img.setAttribute("width", String(iconSize));
|
|
1769
|
+
img.setAttribute("height", String(iconSize));
|
|
1770
|
+
img.setAttribute("preserveAspectRatio", "xMidYMid meet");
|
|
1771
|
+
if (s.opacity != null)
|
|
1772
|
+
img.setAttribute("opacity", String(s.opacity));
|
|
1773
|
+
const clipId = `clip-${n.id}`;
|
|
1774
|
+
const defs = document.createElementNS(SVG_NS, "defs");
|
|
1775
|
+
const clip = document.createElementNS(SVG_NS, "clipPath");
|
|
1776
|
+
clip.setAttribute("id", clipId);
|
|
1777
|
+
const rect = document.createElementNS(SVG_NS, "rect");
|
|
1778
|
+
rect.setAttribute("x", String(iconX));
|
|
1779
|
+
rect.setAttribute("y", String(iconY));
|
|
1780
|
+
rect.setAttribute("width", String(iconSize));
|
|
1781
|
+
rect.setAttribute("height", String(iconSize));
|
|
1782
|
+
rect.setAttribute("rx", "6");
|
|
1783
|
+
clip.appendChild(rect);
|
|
1784
|
+
defs.appendChild(clip);
|
|
1785
|
+
img.setAttribute("clip-path", `url(#${clipId})`);
|
|
1786
|
+
const els = [defs, img];
|
|
1787
|
+
if (s.stroke) {
|
|
1788
|
+
els.push(rc.rectangle(n.x + 1, n.y + 1, n.w - 2, n.h - 2, { ...opts, fill: "none" }));
|
|
1789
|
+
}
|
|
1790
|
+
return els;
|
|
1791
|
+
}
|
|
1792
|
+
return [rc.rectangle(n.x + 1, n.y + 1, n.w - 2, n.h - 2, { ...opts, fill: "#e0e0e0", stroke: "#999999" })];
|
|
1793
|
+
},
|
|
1794
|
+
renderCanvas(rc, ctx, n, palette, opts) {
|
|
1795
|
+
const s = n.style ?? {};
|
|
1796
|
+
if (n.iconName) {
|
|
1797
|
+
const [prefix, name] = n.iconName.includes(":")
|
|
1798
|
+
? n.iconName.split(":", 2)
|
|
1799
|
+
: ["mdi", n.iconName];
|
|
1800
|
+
const iconColor = s.color
|
|
1801
|
+
? encodeURIComponent(String(s.color))
|
|
1802
|
+
: encodeURIComponent(String(palette.nodeStroke));
|
|
1803
|
+
const iconLabelSpace = n.label ? 20 : 0;
|
|
1804
|
+
const iconAreaH = n.h - iconLabelSpace;
|
|
1805
|
+
const iconSize = Math.min(n.w, iconAreaH) - 4;
|
|
1806
|
+
const iconUrl = `https://api.iconify.design/${prefix}/${name}.svg?color=${iconColor}&width=${iconSize}&height=${iconSize}`;
|
|
1807
|
+
const img = new Image();
|
|
1808
|
+
img.crossOrigin = "anonymous";
|
|
1809
|
+
img.onload = () => {
|
|
1810
|
+
ctx.save();
|
|
1811
|
+
if (s.opacity != null)
|
|
1812
|
+
ctx.globalAlpha = Number(s.opacity);
|
|
1813
|
+
const iconX = n.x + (n.w - iconSize) / 2;
|
|
1814
|
+
const iconY = n.y + (iconAreaH - iconSize) / 2;
|
|
1815
|
+
ctx.beginPath();
|
|
1816
|
+
const r = 6;
|
|
1817
|
+
ctx.moveTo(iconX + r, iconY);
|
|
1818
|
+
ctx.lineTo(iconX + iconSize - r, iconY);
|
|
1819
|
+
ctx.quadraticCurveTo(iconX + iconSize, iconY, iconX + iconSize, iconY + r);
|
|
1820
|
+
ctx.lineTo(iconX + iconSize, iconY + iconSize - r);
|
|
1821
|
+
ctx.quadraticCurveTo(iconX + iconSize, iconY + iconSize, iconX + iconSize - r, iconY + iconSize);
|
|
1822
|
+
ctx.lineTo(iconX + r, iconY + iconSize);
|
|
1823
|
+
ctx.quadraticCurveTo(iconX, iconY + iconSize, iconX, iconY + iconSize - r);
|
|
1824
|
+
ctx.lineTo(iconX, iconY + r);
|
|
1825
|
+
ctx.quadraticCurveTo(iconX, iconY, iconX + r, iconY);
|
|
1826
|
+
ctx.closePath();
|
|
1827
|
+
ctx.clip();
|
|
1828
|
+
ctx.drawImage(img, iconX, iconY, iconSize, iconSize);
|
|
1829
|
+
ctx.restore();
|
|
1830
|
+
if (s.stroke) {
|
|
1831
|
+
rc.rectangle(n.x + 1, n.y + 1, n.w - 2, n.h - 2, { ...opts, fill: "none" });
|
|
1832
|
+
}
|
|
1833
|
+
};
|
|
1834
|
+
img.src = iconUrl;
|
|
1835
|
+
}
|
|
1836
|
+
else {
|
|
1837
|
+
rc.rectangle(n.x + 1, n.y + 1, n.w - 2, n.h - 2, { ...opts, fill: "#e0e0e0", stroke: "#999999" });
|
|
1838
|
+
}
|
|
1839
|
+
},
|
|
1840
|
+
};
|
|
1841
|
+
|
|
1842
|
+
const imageShape = {
|
|
1843
|
+
size(n, labelW) {
|
|
1844
|
+
n.w = n.w || Math.max(MIN_W, Math.min(MAX_W, labelW));
|
|
1845
|
+
n.h = n.h || 52;
|
|
1846
|
+
},
|
|
1847
|
+
renderSVG(rc, n, _palette, opts) {
|
|
1848
|
+
const s = n.style ?? {};
|
|
1849
|
+
if (n.imageUrl) {
|
|
1850
|
+
const imgLabelSpace = n.label ? 20 : 0;
|
|
1851
|
+
const imgAreaH = n.h - imgLabelSpace;
|
|
1852
|
+
const img = document.createElementNS(SVG_NS, "image");
|
|
1853
|
+
img.setAttribute("href", n.imageUrl);
|
|
1854
|
+
img.setAttribute("x", String(n.x + 1));
|
|
1855
|
+
img.setAttribute("y", String(n.y + 1));
|
|
1856
|
+
img.setAttribute("width", String(n.w - 2));
|
|
1857
|
+
img.setAttribute("height", String(imgAreaH - 2));
|
|
1858
|
+
img.setAttribute("preserveAspectRatio", "xMidYMid meet");
|
|
1859
|
+
const clipId = `clip-${n.id}`;
|
|
1860
|
+
const defs = document.createElementNS(SVG_NS, "defs");
|
|
1861
|
+
const clip = document.createElementNS(SVG_NS, "clipPath");
|
|
1862
|
+
clip.setAttribute("id", clipId);
|
|
1863
|
+
const rect = document.createElementNS(SVG_NS, "rect");
|
|
1864
|
+
rect.setAttribute("x", String(n.x + 1));
|
|
1865
|
+
rect.setAttribute("y", String(n.y + 1));
|
|
1866
|
+
rect.setAttribute("width", String(n.w - 2));
|
|
1867
|
+
rect.setAttribute("height", String(imgAreaH - 2));
|
|
1868
|
+
rect.setAttribute("rx", "6");
|
|
1869
|
+
clip.appendChild(rect);
|
|
1870
|
+
defs.appendChild(clip);
|
|
1871
|
+
img.setAttribute("clip-path", `url(#${clipId})`);
|
|
1872
|
+
const els = [defs, img];
|
|
1873
|
+
if (s.stroke) {
|
|
1874
|
+
els.push(rc.rectangle(n.x + 1, n.y + 1, n.w - 2, n.h - 2, { ...opts, fill: "none" }));
|
|
1875
|
+
}
|
|
1876
|
+
return els;
|
|
1877
|
+
}
|
|
1878
|
+
return [rc.rectangle(n.x + 1, n.y + 1, n.w - 2, n.h - 2, { ...opts, fill: "#e0e0e0", stroke: "#999999" })];
|
|
1879
|
+
},
|
|
1880
|
+
renderCanvas(rc, ctx, n, _palette, opts) {
|
|
1881
|
+
const s = n.style ?? {};
|
|
1882
|
+
if (n.imageUrl) {
|
|
1883
|
+
const imgLblSpace = n.label ? 20 : 0;
|
|
1884
|
+
const imgAreaH = n.h - imgLblSpace;
|
|
1885
|
+
const img = new Image();
|
|
1886
|
+
img.crossOrigin = "anonymous";
|
|
1887
|
+
img.onload = () => {
|
|
1888
|
+
ctx.save();
|
|
1889
|
+
ctx.beginPath();
|
|
1890
|
+
const r = 6;
|
|
1891
|
+
ctx.moveTo(n.x + r, n.y);
|
|
1892
|
+
ctx.lineTo(n.x + n.w - r, n.y);
|
|
1893
|
+
ctx.quadraticCurveTo(n.x + n.w, n.y, n.x + n.w, n.y + r);
|
|
1894
|
+
ctx.lineTo(n.x + n.w, n.y + imgAreaH - r);
|
|
1895
|
+
ctx.quadraticCurveTo(n.x + n.w, n.y + imgAreaH, n.x + n.w - r, n.y + imgAreaH);
|
|
1896
|
+
ctx.lineTo(n.x + r, n.y + imgAreaH);
|
|
1897
|
+
ctx.quadraticCurveTo(n.x, n.y + imgAreaH, n.x, n.y + imgAreaH - r);
|
|
1898
|
+
ctx.lineTo(n.x, n.y + r);
|
|
1899
|
+
ctx.quadraticCurveTo(n.x, n.y, n.x + r, n.y);
|
|
1900
|
+
ctx.closePath();
|
|
1901
|
+
ctx.clip();
|
|
1902
|
+
ctx.drawImage(img, n.x + 1, n.y + 1, n.w - 2, imgAreaH - 2);
|
|
1903
|
+
ctx.restore();
|
|
1904
|
+
if (s.stroke) {
|
|
1905
|
+
rc.rectangle(n.x + 1, n.y + 1, n.w - 2, n.h - 2, { ...opts, fill: "none" });
|
|
1906
|
+
}
|
|
1907
|
+
};
|
|
1908
|
+
img.src = n.imageUrl;
|
|
1909
|
+
}
|
|
1910
|
+
else {
|
|
1911
|
+
rc.rectangle(n.x + 1, n.y + 1, n.w - 2, n.h - 2, { ...opts, fill: "#e0e0e0", stroke: "#999999" });
|
|
1912
|
+
}
|
|
1913
|
+
},
|
|
1914
|
+
};
|
|
1915
|
+
|
|
1916
|
+
// ============================================================
|
|
1917
|
+
// sketchmark — Font Registry
|
|
1918
|
+
// ============================================================
|
|
1919
|
+
// built-in named fonts — user can reference these by short name
|
|
1920
|
+
const BUILTIN_FONTS = {
|
|
1921
|
+
// hand-drawn
|
|
1922
|
+
caveat: {
|
|
1923
|
+
family: "'Caveat', cursive",
|
|
1924
|
+
url: 'https://fonts.googleapis.com/css2?family=Caveat:wght@400;500;600&display=swap',
|
|
1925
|
+
},
|
|
1926
|
+
handlee: {
|
|
1927
|
+
family: "'Handlee', cursive",
|
|
1928
|
+
url: 'https://fonts.googleapis.com/css2?family=Handlee&display=swap',
|
|
1929
|
+
},
|
|
1930
|
+
'indie-flower': {
|
|
1931
|
+
family: "'Indie Flower', cursive",
|
|
1932
|
+
url: 'https://fonts.googleapis.com/css2?family=Indie+Flower&display=swap',
|
|
1933
|
+
},
|
|
1934
|
+
'patrick-hand': {
|
|
1935
|
+
family: "'Patrick Hand', cursive",
|
|
1936
|
+
url: 'https://fonts.googleapis.com/css2?family=Patrick+Hand&display=swap',
|
|
1937
|
+
},
|
|
1938
|
+
// clean / readable
|
|
1939
|
+
'dm-mono': {
|
|
1940
|
+
family: "'DM Mono', monospace",
|
|
1941
|
+
url: 'https://fonts.googleapis.com/css2?family=DM+Mono:wght@300;400;500&display=swap',
|
|
1942
|
+
},
|
|
1943
|
+
'jetbrains': {
|
|
1944
|
+
family: "'JetBrains Mono', monospace",
|
|
1945
|
+
url: 'https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;500&display=swap',
|
|
1946
|
+
},
|
|
1947
|
+
'instrument': {
|
|
1948
|
+
family: "'Instrument Serif', serif",
|
|
1949
|
+
url: 'https://fonts.googleapis.com/css2?family=Instrument+Serif&display=swap',
|
|
1950
|
+
},
|
|
1951
|
+
'playfair': {
|
|
1952
|
+
family: "'Playfair Display', serif",
|
|
1953
|
+
url: 'https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;500&display=swap',
|
|
1954
|
+
},
|
|
1955
|
+
// system fallbacks (no URL needed)
|
|
1956
|
+
system: { family: 'system-ui, sans-serif' },
|
|
1957
|
+
mono: { family: "'Courier New', monospace" },
|
|
1958
|
+
serif: { family: 'Georgia, serif' },
|
|
1959
|
+
};
|
|
1960
|
+
// default — what renders when no font is specified
|
|
1961
|
+
const DEFAULT_FONT = 'system-ui, sans-serif';
|
|
1962
|
+
// resolve a short name or pass-through a quoted CSS family
|
|
1963
|
+
function resolveFont(nameOrFamily) {
|
|
1964
|
+
const key = nameOrFamily.toLowerCase().trim();
|
|
1965
|
+
if (BUILTIN_FONTS[key])
|
|
1966
|
+
return BUILTIN_FONTS[key].family;
|
|
1967
|
+
return nameOrFamily; // treat as raw CSS font-family
|
|
1968
|
+
}
|
|
1969
|
+
// inject a <link> into <head> for a built-in font (browser only)
|
|
1970
|
+
function loadFont(name) {
|
|
1971
|
+
if (typeof document === 'undefined')
|
|
1972
|
+
return;
|
|
1973
|
+
const key = name.toLowerCase().trim();
|
|
1974
|
+
const def = BUILTIN_FONTS[key];
|
|
1975
|
+
if (!def?.url || def.loaded)
|
|
1976
|
+
return;
|
|
1977
|
+
if (document.querySelector(`link[data-sketchmark-font="${key}"]`))
|
|
1978
|
+
return;
|
|
1979
|
+
const link = document.createElement('link');
|
|
1980
|
+
link.rel = 'stylesheet';
|
|
1981
|
+
link.href = def.url;
|
|
1982
|
+
link.setAttribute('data-sketchmark-font', key);
|
|
1983
|
+
document.head.appendChild(link);
|
|
1984
|
+
def.loaded = true;
|
|
1985
|
+
}
|
|
1986
|
+
// user registers their own font (already loaded via CSS/link)
|
|
1987
|
+
function registerFont(name, family, url) {
|
|
1988
|
+
BUILTIN_FONTS[name.toLowerCase()] = { family, url };
|
|
1989
|
+
if (url)
|
|
1990
|
+
loadFont(name);
|
|
1991
|
+
}
|
|
1992
|
+
|
|
1993
|
+
// ============================================================
|
|
1994
|
+
// sketchmark — Shared Renderer Utilities
|
|
1995
|
+
//
|
|
1996
|
+
// Functions used by both SVG and Canvas renderers, extracted
|
|
1997
|
+
// to eliminate duplication (Phase 1 of SOLID refactoring).
|
|
1998
|
+
// ============================================================
|
|
1999
|
+
// ── Hash string to seed ───────────────────────────────────────────────────
|
|
2000
|
+
function hashStr$3(s) {
|
|
2001
|
+
let h = 5381;
|
|
2002
|
+
for (let i = 0; i < s.length; i++)
|
|
2003
|
+
h = ((h * 33) ^ s.charCodeAt(i)) & 0xffff;
|
|
2004
|
+
return h;
|
|
2005
|
+
}
|
|
2006
|
+
// ── Darken a CSS hex colour by `amount` (0–1) ────────────────────────────
|
|
2007
|
+
function darkenHex(hex, amount = 0.12) {
|
|
2008
|
+
const m = /^#?([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i.exec(hex);
|
|
2009
|
+
if (!m)
|
|
2010
|
+
return hex;
|
|
2011
|
+
const d = (v) => Math.max(0, Math.round(parseInt(v, 16) * (1 - amount)));
|
|
2012
|
+
return `#${d(m[1]).toString(16).padStart(2, "0")}${d(m[2]).toString(16).padStart(2, "0")}${d(m[3]).toString(16).padStart(2, "0")}`;
|
|
2013
|
+
}
|
|
2014
|
+
// ── Load + resolve font from style or fall back ──────────────────────────
|
|
2015
|
+
function resolveStyleFont(style, fallback) {
|
|
2016
|
+
const raw = String(style["font"] ?? "");
|
|
2017
|
+
if (!raw)
|
|
2018
|
+
return fallback;
|
|
2019
|
+
loadFont(raw);
|
|
2020
|
+
return resolveFont(raw);
|
|
2021
|
+
}
|
|
2022
|
+
// ── Soft word-wrap ───────────────────────────────────────────────────────
|
|
2023
|
+
function wrapText(text, maxWidth, fontSize) {
|
|
2024
|
+
const charWidth = fontSize * 0.55;
|
|
2025
|
+
const maxChars = Math.floor(maxWidth / charWidth);
|
|
2026
|
+
const words = text.split(' ');
|
|
2027
|
+
const lines = [];
|
|
2028
|
+
let current = '';
|
|
2029
|
+
for (const word of words) {
|
|
2030
|
+
const test = current ? `${current} ${word}` : word;
|
|
2031
|
+
if (test.length > maxChars && current) {
|
|
2032
|
+
lines.push(current);
|
|
2033
|
+
current = word;
|
|
2034
|
+
}
|
|
2035
|
+
else {
|
|
2036
|
+
current = test;
|
|
2037
|
+
}
|
|
2038
|
+
}
|
|
2039
|
+
if (current)
|
|
2040
|
+
lines.push(current);
|
|
2041
|
+
return lines.length ? lines : [text];
|
|
2042
|
+
}
|
|
2043
|
+
// ── Arrow direction from connector ───────────────────────────────────────
|
|
2044
|
+
function connMeta(connector) {
|
|
2045
|
+
if (connector === "--")
|
|
2046
|
+
return { arrowAt: "none", dashed: false };
|
|
2047
|
+
if (connector === "---")
|
|
2048
|
+
return { arrowAt: "none", dashed: true };
|
|
2049
|
+
const bidir = connector.includes("<") && connector.includes(">");
|
|
2050
|
+
if (bidir)
|
|
2051
|
+
return { arrowAt: "both", dashed: connector.includes("--") };
|
|
2052
|
+
const back = connector.startsWith("<");
|
|
2053
|
+
const dashed = connector.includes("--");
|
|
2054
|
+
if (back)
|
|
2055
|
+
return { arrowAt: "start", dashed };
|
|
2056
|
+
return { arrowAt: "end", dashed };
|
|
2057
|
+
}
|
|
2058
|
+
// ── Generic rect connection point ────────────────────────────────────────
|
|
2059
|
+
function rectConnPoint$1(rx, ry, rw, rh, ox, oy) {
|
|
2060
|
+
const cx = rx + rw / 2, cy = ry + rh / 2;
|
|
2061
|
+
const dx = ox - cx, dy = oy - cy;
|
|
2062
|
+
if (Math.abs(dx) < 0.01 && Math.abs(dy) < 0.01)
|
|
2063
|
+
return [cx, cy];
|
|
2064
|
+
const hw = rw / 2 - 2, hh = rh / 2 - 2;
|
|
2065
|
+
const tx = Math.abs(dx) > 0.01 ? hw / Math.abs(dx) : 1e9;
|
|
2066
|
+
const ty = Math.abs(dy) > 0.01 ? hh / Math.abs(dy) : 1e9;
|
|
2067
|
+
const t = Math.min(tx, ty);
|
|
2068
|
+
return [cx + t * dx, cy + t * dy];
|
|
2069
|
+
}
|
|
2070
|
+
// ── Resolve an endpoint entity by ID across all maps ─────────────────────
|
|
2071
|
+
function resolveEndpoint(id, nm, tm, gm, cm) {
|
|
2072
|
+
return nm.get(id) ?? tm.get(id) ?? gm.get(id) ?? cm.get(id) ?? null;
|
|
2073
|
+
}
|
|
2074
|
+
// ── Get connection point for any entity ──────────────────────────────────
|
|
2075
|
+
function getConnPoint(src, dstCX, dstCY) {
|
|
2076
|
+
if ("shape" in src && src.shape) {
|
|
2077
|
+
return connPoint(src, { x: dstCX - 1, y: dstCY - 1, w: 2, h: 2});
|
|
2078
|
+
}
|
|
2079
|
+
return rectConnPoint$1(src.x, src.y, src.w, src.h, dstCX, dstCY);
|
|
2080
|
+
}
|
|
2081
|
+
// ── Group depth (for paint order) ────────────────────────────────────────
|
|
2082
|
+
function groupDepth(g, gm) {
|
|
2083
|
+
let d = 0;
|
|
2084
|
+
let cur = g;
|
|
2085
|
+
while (cur?.parentId) {
|
|
2086
|
+
d++;
|
|
2087
|
+
cur = gm.get(cur.parentId);
|
|
2088
|
+
}
|
|
2089
|
+
return d;
|
|
2090
|
+
}
|
|
2091
|
+
|
|
2092
|
+
const noteShape = {
|
|
2093
|
+
idPrefix: "note",
|
|
2094
|
+
cssClass: "ntg",
|
|
2095
|
+
size(n, _labelW) {
|
|
2096
|
+
const lines = n.label.split("\n");
|
|
2097
|
+
const maxChars = Math.max(...lines.map((l) => l.length));
|
|
2098
|
+
n.w = n.w || Math.max(NOTE.minW, Math.ceil(maxChars * NOTE.fontPxPerChar) + NOTE.padX * 2);
|
|
2099
|
+
n.h = n.h || lines.length * NOTE.lineH + NOTE.padY * 2;
|
|
2100
|
+
if (n.width && n.w < n.width)
|
|
2101
|
+
n.w = n.width;
|
|
2102
|
+
if (n.height && n.h < n.height)
|
|
2103
|
+
n.h = n.height;
|
|
2104
|
+
},
|
|
2105
|
+
renderSVG(rc, n, palette, opts) {
|
|
2106
|
+
const s = n.style ?? {};
|
|
2107
|
+
const { x, y, w, h } = n;
|
|
2108
|
+
const fold = NOTE.fold;
|
|
2109
|
+
const strk = String(s.stroke ?? palette.noteStroke);
|
|
2110
|
+
const nStrokeWidth = Number(s.strokeWidth ?? 1.2);
|
|
2111
|
+
const body = rc.polygon([[x, y], [x + w - fold, y], [x + w, y + fold], [x + w, y + h], [x, y + h]], {
|
|
2112
|
+
...opts,
|
|
2113
|
+
stroke: strk,
|
|
2114
|
+
strokeWidth: nStrokeWidth,
|
|
2115
|
+
...(s.strokeDash ? { strokeLineDash: s.strokeDash } : {}),
|
|
2116
|
+
});
|
|
2117
|
+
const foldEl = rc.polygon([[x + w - fold, y], [x + w, y + fold], [x + w - fold, y + fold]], {
|
|
2118
|
+
roughness: 0.4,
|
|
2119
|
+
seed: hashStr$3(n.id + "f"),
|
|
2120
|
+
fill: palette.noteFold,
|
|
2121
|
+
fillStyle: "solid",
|
|
2122
|
+
stroke: strk,
|
|
2123
|
+
strokeWidth: Math.min(nStrokeWidth, 0.8),
|
|
2124
|
+
});
|
|
2125
|
+
return [body, foldEl];
|
|
2126
|
+
},
|
|
2127
|
+
renderCanvas(rc, _ctx, n, palette, opts) {
|
|
2128
|
+
const s = n.style ?? {};
|
|
2129
|
+
const { x, y, w, h } = n;
|
|
2130
|
+
const fold = NOTE.fold;
|
|
2131
|
+
const strk = String(s.stroke ?? palette.noteStroke);
|
|
2132
|
+
const nStrokeWidth = Number(s.strokeWidth ?? 1.2);
|
|
2133
|
+
rc.polygon([[x, y], [x + w - fold, y], [x + w, y + fold], [x + w, y + h], [x, y + h]], {
|
|
2134
|
+
...opts,
|
|
2135
|
+
stroke: strk,
|
|
2136
|
+
strokeWidth: nStrokeWidth,
|
|
2137
|
+
...(s.strokeDash ? { strokeLineDash: s.strokeDash } : {}),
|
|
2138
|
+
});
|
|
2139
|
+
rc.polygon([[x + w - fold, y], [x + w, y + fold], [x + w - fold, y + fold]], {
|
|
2140
|
+
roughness: 0.4,
|
|
2141
|
+
seed: hashStr$3(n.id + "f"),
|
|
2142
|
+
fill: palette.noteFold,
|
|
2143
|
+
fillStyle: "solid",
|
|
2144
|
+
stroke: strk,
|
|
2145
|
+
strokeWidth: Math.min(nStrokeWidth, 0.8),
|
|
2146
|
+
});
|
|
2147
|
+
},
|
|
2148
|
+
};
|
|
2149
|
+
|
|
2150
|
+
const lineShape = {
|
|
2151
|
+
size(n, labelW) {
|
|
2152
|
+
const labelH = n.label ? 20 : 0;
|
|
2153
|
+
n.w = n.width ?? Math.max(MIN_W, labelW + 20);
|
|
2154
|
+
n.h = n.height ?? (6 + labelH);
|
|
2155
|
+
},
|
|
2156
|
+
renderSVG(rc, n, _palette, opts) {
|
|
2157
|
+
const labelH = n.label ? 20 : 0;
|
|
2158
|
+
const lineY = n.y + (n.h - labelH) / 2;
|
|
2159
|
+
return [rc.line(n.x, lineY, n.x + n.w, lineY, opts)];
|
|
2160
|
+
},
|
|
2161
|
+
renderCanvas(rc, _ctx, n, _palette, opts) {
|
|
2162
|
+
const labelH = n.label ? 20 : 0;
|
|
2163
|
+
const lineY = n.y + (n.h - labelH) / 2;
|
|
2164
|
+
rc.line(n.x, lineY, n.x + n.w, lineY, opts);
|
|
2165
|
+
},
|
|
2166
|
+
};
|
|
2167
|
+
|
|
2168
|
+
const pathShape = {
|
|
2169
|
+
size(n, labelW) {
|
|
2170
|
+
// User should provide width/height; defaults to 100x100
|
|
2171
|
+
n.w = n.width ?? Math.max(100, labelW + 20);
|
|
2172
|
+
n.h = n.height ?? 100;
|
|
2173
|
+
},
|
|
2174
|
+
renderSVG(rc, n, _palette, opts) {
|
|
2175
|
+
const d = n.pathData;
|
|
2176
|
+
if (!d) {
|
|
2177
|
+
// No path data — render placeholder box
|
|
2178
|
+
return [rc.rectangle(n.x + 1, n.y + 1, n.w - 2, n.h - 2, opts)];
|
|
2179
|
+
}
|
|
2180
|
+
const el = rc.path(d, opts);
|
|
2181
|
+
// Wrap in a group to translate the user's path to the node position
|
|
2182
|
+
const g = document.createElementNS(SVG_NS, "g");
|
|
2183
|
+
g.setAttribute("transform", `translate(${n.x},${n.y})`);
|
|
2184
|
+
g.appendChild(el);
|
|
2185
|
+
return [g];
|
|
2186
|
+
},
|
|
2187
|
+
renderCanvas(rc, ctx, n, _palette, opts) {
|
|
2188
|
+
const d = n.pathData;
|
|
2189
|
+
if (!d) {
|
|
2190
|
+
rc.rectangle(n.x + 1, n.y + 1, n.w - 2, n.h - 2, opts);
|
|
2191
|
+
return;
|
|
2192
|
+
}
|
|
2193
|
+
ctx.save();
|
|
2194
|
+
ctx.translate(n.x, n.y);
|
|
2195
|
+
rc.path(d, opts);
|
|
2196
|
+
ctx.restore();
|
|
2197
|
+
},
|
|
2198
|
+
};
|
|
2199
|
+
|
|
2200
|
+
// ============================================================
|
|
2201
|
+
// Shape Registry — registers all built-in shapes
|
|
2202
|
+
//
|
|
2203
|
+
// To add a new shape:
|
|
2204
|
+
// 1. Create src/renderer/shapes/my-shape.ts implementing ShapeDefinition
|
|
2205
|
+
// 2. Import and register it here
|
|
2206
|
+
// 3. Add the shape name to NodeShape union in ast/types.ts
|
|
2207
|
+
// 4. Add to SHAPES array in parser/index.ts and KEYWORDS in tokenizer.ts
|
|
2208
|
+
// ============================================================
|
|
2209
|
+
registerShape("box", boxShape);
|
|
2210
|
+
registerShape("circle", circleShape);
|
|
2211
|
+
registerShape("diamond", diamondShape);
|
|
2212
|
+
registerShape("hexagon", hexagonShape);
|
|
2213
|
+
registerShape("triangle", triangleShape);
|
|
2214
|
+
registerShape("cylinder", cylinderShape);
|
|
2215
|
+
registerShape("parallelogram", parallelogramShape);
|
|
2216
|
+
registerShape("text", textShape);
|
|
2217
|
+
registerShape("icon", iconShape);
|
|
2218
|
+
registerShape("image", imageShape);
|
|
2219
|
+
registerShape("note", noteShape);
|
|
2220
|
+
registerShape("line", lineShape);
|
|
2221
|
+
registerShape("path", pathShape);
|
|
2222
|
+
|
|
1431
2223
|
// ============================================================
|
|
1432
2224
|
// sketchmark — Layout Engine (Flexbox-style, recursive)
|
|
1433
2225
|
//
|
|
@@ -1442,22 +2234,6 @@ var AIDiagram = (function (exports) {
|
|
|
1442
2234
|
// align=… → align-items
|
|
1443
2235
|
// justify=… → justify-content
|
|
1444
2236
|
// ============================================================
|
|
1445
|
-
// ── Constants ─────────────────────────────────────────────
|
|
1446
|
-
const FONT_PX_PER_CHAR = 8.6;
|
|
1447
|
-
const MIN_W = 90;
|
|
1448
|
-
const MAX_W = 180;
|
|
1449
|
-
const BASE_PAD = 26;
|
|
1450
|
-
const GROUP_LABEL_H = 22;
|
|
1451
|
-
const DEFAULT_MARGIN = 60;
|
|
1452
|
-
const DEFAULT_GAP_MAIN = 80;
|
|
1453
|
-
// Table sizing
|
|
1454
|
-
const CELL_PAD = 20; // total horizontal padding per cell (left + right)
|
|
1455
|
-
const MIN_COL_W = 50; // minimum column width
|
|
1456
|
-
const TBL_FONT = 7.5; // px per char at 12px sans-serif
|
|
1457
|
-
const NOTE_LINE_H = 20;
|
|
1458
|
-
const NOTE_PAD_X = 16;
|
|
1459
|
-
const NOTE_PAD_Y = 12;
|
|
1460
|
-
const NOTE_FONT = 7.5;
|
|
1461
2237
|
// ── Node auto-sizing ──────────────────────────────────────
|
|
1462
2238
|
function sizeNode(n) {
|
|
1463
2239
|
// User-specified dimensions win
|
|
@@ -1465,72 +2241,16 @@ var AIDiagram = (function (exports) {
|
|
|
1465
2241
|
n.w = n.width;
|
|
1466
2242
|
if (n.height && n.height > 0)
|
|
1467
2243
|
n.h = n.height;
|
|
1468
|
-
const labelW = Math.round(n.label.length *
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
break;
|
|
1478
|
-
case "hexagon":
|
|
1479
|
-
n.w = n.w || Math.max(126, Math.min(MAX_W, labelW + 20));
|
|
1480
|
-
n.h = n.h || Math.max(54, n.w * 0.44);
|
|
1481
|
-
break;
|
|
1482
|
-
case "triangle":
|
|
1483
|
-
n.w = n.w || Math.max(108, Math.min(MAX_W, labelW + 10));
|
|
1484
|
-
n.h = n.h || Math.max(64, n.w * 0.6);
|
|
1485
|
-
break;
|
|
1486
|
-
case "cylinder":
|
|
1487
|
-
n.w = n.w || Math.max(MIN_W, Math.min(MAX_W, labelW));
|
|
1488
|
-
n.h = n.h || 66;
|
|
1489
|
-
break;
|
|
1490
|
-
case "parallelogram":
|
|
1491
|
-
n.w = n.w || Math.max(MIN_W, Math.min(MAX_W, labelW + 28));
|
|
1492
|
-
n.h = n.h || 50;
|
|
1493
|
-
break;
|
|
1494
|
-
case "text": {
|
|
1495
|
-
const fontSize = Number(n.style?.fontSize ?? 13);
|
|
1496
|
-
const charWidth = fontSize * 0.55;
|
|
1497
|
-
const pad = Number(n.style?.padding ?? 8) * 2;
|
|
1498
|
-
if (n.width) {
|
|
1499
|
-
// User set width → word-wrap within it
|
|
1500
|
-
const approxLines = Math.ceil((n.label.length * charWidth) / (n.width - pad));
|
|
1501
|
-
n.w = n.width;
|
|
1502
|
-
n.h = n.height ?? Math.max(24, approxLines * fontSize * 1.5 + pad);
|
|
1503
|
-
}
|
|
1504
|
-
else {
|
|
1505
|
-
// Auto-size to content
|
|
1506
|
-
const lines = n.label.split("\\n");
|
|
1507
|
-
const longest = lines.reduce((a, b) => (a.length > b.length ? a : b), "");
|
|
1508
|
-
n.w = Math.max(MIN_W, Math.round(longest.length * charWidth + pad));
|
|
1509
|
-
n.h = n.height ?? Math.max(24, lines.length * fontSize * 1.5 + pad);
|
|
1510
|
-
}
|
|
1511
|
-
break;
|
|
1512
|
-
}
|
|
1513
|
-
case "icon": {
|
|
1514
|
-
const iconBase = 48;
|
|
1515
|
-
const labelH = n.label ? 20 : 0;
|
|
1516
|
-
n.w = n.w || Math.max(iconBase, n.label ? labelW : 0);
|
|
1517
|
-
n.h = n.h || (iconBase + labelH);
|
|
1518
|
-
break;
|
|
1519
|
-
}
|
|
1520
|
-
default:
|
|
1521
|
-
n.w = n.w || Math.max(MIN_W, Math.min(MAX_W, labelW));
|
|
1522
|
-
n.h = n.h || 52;
|
|
1523
|
-
break;
|
|
2244
|
+
const labelW = Math.round(n.label.length * NODE.fontPxPerChar + NODE.basePad);
|
|
2245
|
+
const shape = getShape(n.shape);
|
|
2246
|
+
if (shape) {
|
|
2247
|
+
shape.size(n, labelW);
|
|
2248
|
+
}
|
|
2249
|
+
else {
|
|
2250
|
+
// fallback for unknown shapes — box-like default
|
|
2251
|
+
n.w = n.w || Math.max(90, Math.min(180, labelW));
|
|
2252
|
+
n.h = n.h || 52;
|
|
1524
2253
|
}
|
|
1525
|
-
}
|
|
1526
|
-
function sizeNote(n) {
|
|
1527
|
-
const maxChars = Math.max(...n.lines.map((l) => l.length));
|
|
1528
|
-
n.w = Math.max(120, Math.ceil(maxChars * NOTE_FONT) + NOTE_PAD_X * 2);
|
|
1529
|
-
n.h = n.lines.length * NOTE_LINE_H + NOTE_PAD_Y * 2;
|
|
1530
|
-
if (n.width && n.w < n.width)
|
|
1531
|
-
n.w = n.width; // ← add
|
|
1532
|
-
if (n.height && n.h < n.height)
|
|
1533
|
-
n.h = n.height; // ← add
|
|
1534
2254
|
}
|
|
1535
2255
|
// ── Table auto-sizing ─────────────────────────────────────
|
|
1536
2256
|
function sizeTable(t) {
|
|
@@ -1541,10 +2261,10 @@ var AIDiagram = (function (exports) {
|
|
|
1541
2261
|
return;
|
|
1542
2262
|
}
|
|
1543
2263
|
const numCols = Math.max(...rows.map((r) => r.cells.length));
|
|
1544
|
-
const colW = Array(numCols).fill(
|
|
2264
|
+
const colW = Array(numCols).fill(TABLE.minColW);
|
|
1545
2265
|
for (const row of rows) {
|
|
1546
2266
|
row.cells.forEach((cell, i) => {
|
|
1547
|
-
colW[i] = Math.max(colW[i], Math.ceil(cell.length *
|
|
2267
|
+
colW[i] = Math.max(colW[i], Math.ceil(cell.length * TABLE.fontPxPerChar) + TABLE.cellPad);
|
|
1548
2268
|
});
|
|
1549
2269
|
}
|
|
1550
2270
|
t.colWidths = colW;
|
|
@@ -1561,79 +2281,27 @@ var AIDiagram = (function (exports) {
|
|
|
1561
2281
|
m.w = m.width ?? 400;
|
|
1562
2282
|
m.h = m.height ?? calcMarkdownHeight(m.lines, pad);
|
|
1563
2283
|
}
|
|
1564
|
-
// ── Item size helpers
|
|
1565
|
-
function iW(r,
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
return mdm.get(r.id).w;
|
|
1576
|
-
return gm.get(r.id).w;
|
|
1577
|
-
}
|
|
1578
|
-
function iH(r, nm, gm, tm, ntm, cm, mdm) {
|
|
1579
|
-
if (r.kind === "node")
|
|
1580
|
-
return nm.get(r.id).h;
|
|
1581
|
-
if (r.kind === "table")
|
|
1582
|
-
return tm.get(r.id).h;
|
|
1583
|
-
if (r.kind === "note")
|
|
1584
|
-
return ntm.get(r.id).h;
|
|
1585
|
-
if (r.kind === "chart")
|
|
1586
|
-
return cm.get(r.id).h;
|
|
1587
|
-
if (r.kind === "markdown")
|
|
1588
|
-
return mdm.get(r.id).h;
|
|
1589
|
-
return gm.get(r.id).h;
|
|
1590
|
-
}
|
|
1591
|
-
function setPos(r, x, y, nm, gm, tm, ntm, cm, mdm) {
|
|
1592
|
-
if (r.kind === "node") {
|
|
1593
|
-
const n = nm.get(r.id);
|
|
1594
|
-
n.x = Math.round(x);
|
|
1595
|
-
n.y = Math.round(y);
|
|
1596
|
-
return;
|
|
1597
|
-
}
|
|
1598
|
-
if (r.kind === "table") {
|
|
1599
|
-
const t = tm.get(r.id);
|
|
1600
|
-
t.x = Math.round(x);
|
|
1601
|
-
t.y = Math.round(y);
|
|
1602
|
-
return;
|
|
1603
|
-
}
|
|
1604
|
-
if (r.kind === "note") {
|
|
1605
|
-
const nt = ntm.get(r.id);
|
|
1606
|
-
nt.x = Math.round(x);
|
|
1607
|
-
nt.y = Math.round(y);
|
|
1608
|
-
return;
|
|
1609
|
-
}
|
|
1610
|
-
if (r.kind === "chart") {
|
|
1611
|
-
const c = cm.get(r.id);
|
|
1612
|
-
c.x = Math.round(x);
|
|
1613
|
-
c.y = Math.round(y);
|
|
1614
|
-
return;
|
|
1615
|
-
}
|
|
1616
|
-
if (r.kind === "markdown") {
|
|
1617
|
-
const md = mdm.get(r.id);
|
|
1618
|
-
md.x = Math.round(x);
|
|
1619
|
-
md.y = Math.round(y);
|
|
1620
|
-
return;
|
|
1621
|
-
}
|
|
1622
|
-
const g = gm.get(r.id);
|
|
1623
|
-
g.x = Math.round(x);
|
|
1624
|
-
g.y = Math.round(y);
|
|
2284
|
+
// ── Item size helpers (entity-map based) ─────────────────
|
|
2285
|
+
function iW(r, em) {
|
|
2286
|
+
return em.get(r.id).w;
|
|
2287
|
+
}
|
|
2288
|
+
function iH(r, em) {
|
|
2289
|
+
return em.get(r.id).h;
|
|
2290
|
+
}
|
|
2291
|
+
function setPos(r, x, y, em) {
|
|
2292
|
+
const e = em.get(r.id);
|
|
2293
|
+
e.x = Math.round(x);
|
|
2294
|
+
e.y = Math.round(y);
|
|
1625
2295
|
}
|
|
1626
2296
|
// ── Pass 1: Measure (bottom-up) ───────────────────────────
|
|
1627
2297
|
// Recursively computes w, h for a group from its children's sizes.
|
|
1628
|
-
function measure(g,
|
|
2298
|
+
function measure(g, gm, tm, cm, mdm, em) {
|
|
1629
2299
|
// Recurse into nested groups first; size tables before reading their dims
|
|
1630
2300
|
for (const r of g.children) {
|
|
1631
2301
|
if (r.kind === "group")
|
|
1632
|
-
measure(gm.get(r.id),
|
|
2302
|
+
measure(gm.get(r.id), gm, tm, cm, mdm, em);
|
|
1633
2303
|
if (r.kind === "table")
|
|
1634
2304
|
sizeTable(tm.get(r.id));
|
|
1635
|
-
if (r.kind === "note")
|
|
1636
|
-
sizeNote(ntm.get(r.id));
|
|
1637
2305
|
if (r.kind === "chart")
|
|
1638
2306
|
sizeChart(cm.get(r.id));
|
|
1639
2307
|
if (r.kind === "markdown")
|
|
@@ -1641,7 +2309,7 @@ var AIDiagram = (function (exports) {
|
|
|
1641
2309
|
}
|
|
1642
2310
|
const { padding: pad, gap, columns, layout } = g;
|
|
1643
2311
|
const kids = g.children;
|
|
1644
|
-
const labelH = g.label ?
|
|
2312
|
+
const labelH = g.label ? LAYOUT.groupLabelH : 0;
|
|
1645
2313
|
if (!kids.length) {
|
|
1646
2314
|
g.w = pad * 2;
|
|
1647
2315
|
g.h = pad * 2 + labelH;
|
|
@@ -1651,8 +2319,8 @@ var AIDiagram = (function (exports) {
|
|
|
1651
2319
|
g.h = g.height;
|
|
1652
2320
|
return;
|
|
1653
2321
|
}
|
|
1654
|
-
const ws = kids.map((r) => iW(r,
|
|
1655
|
-
const hs = kids.map((r) => iH(r,
|
|
2322
|
+
const ws = kids.map((r) => iW(r, em));
|
|
2323
|
+
const hs = kids.map((r) => iH(r, em));
|
|
1656
2324
|
const n = kids.length;
|
|
1657
2325
|
if (layout === "row") {
|
|
1658
2326
|
g.w = ws.reduce((s, w) => s + w, 0) + gap * (n - 1) + pad * 2;
|
|
@@ -1717,9 +2385,9 @@ var AIDiagram = (function (exports) {
|
|
|
1717
2385
|
}
|
|
1718
2386
|
// ── Pass 2: Place (top-down) ──────────────────────────────
|
|
1719
2387
|
// Assigns x, y to each child. Assumes g.x / g.y already set by parent.
|
|
1720
|
-
function place(g,
|
|
2388
|
+
function place(g, gm, em) {
|
|
1721
2389
|
const { padding: pad, gap, columns, layout, align, justify } = g;
|
|
1722
|
-
const labelH = g.label ?
|
|
2390
|
+
const labelH = g.label ? LAYOUT.groupLabelH : 0;
|
|
1723
2391
|
const contentX = g.x + pad;
|
|
1724
2392
|
const contentY = g.y + labelH + pad;
|
|
1725
2393
|
const contentW = g.w - pad * 2;
|
|
@@ -1728,8 +2396,8 @@ var AIDiagram = (function (exports) {
|
|
|
1728
2396
|
if (!kids.length)
|
|
1729
2397
|
return;
|
|
1730
2398
|
if (layout === "row") {
|
|
1731
|
-
const ws = kids.map((r) => iW(r,
|
|
1732
|
-
const hs = kids.map((r) => iH(r,
|
|
2399
|
+
const ws = kids.map((r) => iW(r, em));
|
|
2400
|
+
const hs = kids.map((r) => iH(r, em));
|
|
1733
2401
|
const { start, gaps } = distribute(ws, contentW, gap, justify);
|
|
1734
2402
|
let x = contentX + start;
|
|
1735
2403
|
for (let i = 0; i < kids.length; i++) {
|
|
@@ -1744,22 +2412,22 @@ var AIDiagram = (function (exports) {
|
|
|
1744
2412
|
default:
|
|
1745
2413
|
y = contentY;
|
|
1746
2414
|
}
|
|
1747
|
-
setPos(kids[i], x, y,
|
|
2415
|
+
setPos(kids[i], x, y, em);
|
|
1748
2416
|
x += ws[i] + (i < gaps.length ? gaps[i] : 0);
|
|
1749
2417
|
}
|
|
1750
2418
|
}
|
|
1751
2419
|
else if (layout === "grid") {
|
|
1752
2420
|
const cols = Math.max(1, columns);
|
|
1753
|
-
const cellW = Math.max(...kids.map((r) => iW(r,
|
|
1754
|
-
const cellH = Math.max(...kids.map((r) => iH(r,
|
|
2421
|
+
const cellW = Math.max(...kids.map((r) => iW(r, em)));
|
|
2422
|
+
const cellH = Math.max(...kids.map((r) => iH(r, em)));
|
|
1755
2423
|
kids.forEach((ref, i) => {
|
|
1756
|
-
setPos(ref, contentX + (i % cols) * (cellW + gap), contentY + Math.floor(i / cols) * (cellH + gap),
|
|
2424
|
+
setPos(ref, contentX + (i % cols) * (cellW + gap), contentY + Math.floor(i / cols) * (cellH + gap), em);
|
|
1757
2425
|
});
|
|
1758
2426
|
}
|
|
1759
2427
|
else {
|
|
1760
2428
|
// column (default)
|
|
1761
|
-
const ws = kids.map((r) => iW(r,
|
|
1762
|
-
const hs = kids.map((r) => iH(r,
|
|
2429
|
+
const ws = kids.map((r) => iW(r, em));
|
|
2430
|
+
const hs = kids.map((r) => iH(r, em));
|
|
1763
2431
|
const { start, gaps } = distribute(hs, contentH, gap, justify);
|
|
1764
2432
|
let y = contentY + start;
|
|
1765
2433
|
for (let i = 0; i < kids.length; i++) {
|
|
@@ -1774,14 +2442,14 @@ var AIDiagram = (function (exports) {
|
|
|
1774
2442
|
default:
|
|
1775
2443
|
x = contentX;
|
|
1776
2444
|
}
|
|
1777
|
-
setPos(kids[i], x, y,
|
|
2445
|
+
setPos(kids[i], x, y, em);
|
|
1778
2446
|
y += hs[i] + (i < gaps.length ? gaps[i] : 0);
|
|
1779
2447
|
}
|
|
1780
2448
|
}
|
|
1781
2449
|
// Recurse into nested groups
|
|
1782
2450
|
for (const r of kids) {
|
|
1783
2451
|
if (r.kind === "group")
|
|
1784
|
-
place(gm.get(r.id),
|
|
2452
|
+
place(gm.get(r.id), gm, em);
|
|
1785
2453
|
}
|
|
1786
2454
|
}
|
|
1787
2455
|
// ── Edge routing ──────────────────────────────────────────
|
|
@@ -1801,7 +2469,7 @@ var AIDiagram = (function (exports) {
|
|
|
1801
2469
|
const t = Math.min(tx, ty);
|
|
1802
2470
|
return [cx + t * dx, cy + t * dy];
|
|
1803
2471
|
}
|
|
1804
|
-
function rectConnPoint
|
|
2472
|
+
function rectConnPoint(rx, ry, rw, rh, ox, oy) {
|
|
1805
2473
|
const cx = rx + rw / 2, cy = ry + rh / 2;
|
|
1806
2474
|
const dx = ox - cx, dy = oy - cy;
|
|
1807
2475
|
if (Math.abs(dx) < 0.01 && Math.abs(dy) < 0.01)
|
|
@@ -1817,7 +2485,6 @@ var AIDiagram = (function (exports) {
|
|
|
1817
2485
|
const tm = tableMap(sg);
|
|
1818
2486
|
const gm = groupMap(sg);
|
|
1819
2487
|
const cm = chartMap(sg);
|
|
1820
|
-
const ntm = noteMap(sg);
|
|
1821
2488
|
function resolve(id) {
|
|
1822
2489
|
const n = nm.get(id);
|
|
1823
2490
|
if (n)
|
|
@@ -1831,9 +2498,6 @@ var AIDiagram = (function (exports) {
|
|
|
1831
2498
|
const c = cm.get(id);
|
|
1832
2499
|
if (c)
|
|
1833
2500
|
return c;
|
|
1834
|
-
const nt = ntm.get(id);
|
|
1835
|
-
if (nt)
|
|
1836
|
-
return nt;
|
|
1837
2501
|
return null;
|
|
1838
2502
|
}
|
|
1839
2503
|
function connPt(src, dstCX, dstCY) {
|
|
@@ -1845,7 +2509,7 @@ var AIDiagram = (function (exports) {
|
|
|
1845
2509
|
w: 2,
|
|
1846
2510
|
h: 2});
|
|
1847
2511
|
}
|
|
1848
|
-
return rectConnPoint
|
|
2512
|
+
return rectConnPoint(src.x, src.y, src.w, src.h, dstCX, dstCY);
|
|
1849
2513
|
}
|
|
1850
2514
|
for (const e of sg.edges) {
|
|
1851
2515
|
const src = resolve(e.from);
|
|
@@ -1864,7 +2528,6 @@ var AIDiagram = (function (exports) {
|
|
|
1864
2528
|
...sg.nodes.map((n) => n.x + n.w),
|
|
1865
2529
|
...sg.groups.filter((g) => g.w).map((g) => g.x + g.w),
|
|
1866
2530
|
...sg.tables.map((t) => t.x + t.w),
|
|
1867
|
-
...sg.notes.map((n) => n.x + n.w),
|
|
1868
2531
|
...sg.charts.map((c) => c.x + c.w),
|
|
1869
2532
|
...sg.markdowns.map((m) => m.x + m.w),
|
|
1870
2533
|
];
|
|
@@ -1872,7 +2535,6 @@ var AIDiagram = (function (exports) {
|
|
|
1872
2535
|
...sg.nodes.map((n) => n.y + n.h),
|
|
1873
2536
|
...sg.groups.filter((g) => g.h).map((g) => g.y + g.h),
|
|
1874
2537
|
...sg.tables.map((t) => t.y + t.h),
|
|
1875
|
-
...sg.notes.map((n) => n.y + n.h),
|
|
1876
2538
|
...sg.charts.map((c) => c.y + c.h),
|
|
1877
2539
|
...sg.markdowns.map((m) => m.y + m.h),
|
|
1878
2540
|
];
|
|
@@ -1881,37 +2543,33 @@ var AIDiagram = (function (exports) {
|
|
|
1881
2543
|
}
|
|
1882
2544
|
// ── Public entry point ────────────────────────────────────
|
|
1883
2545
|
function layout(sg) {
|
|
1884
|
-
const GAP_MAIN = Number(sg.config["gap"] ??
|
|
1885
|
-
const MARGIN = Number(sg.config["margin"] ??
|
|
1886
|
-
const nm = nodeMap(sg);
|
|
2546
|
+
const GAP_MAIN = Number(sg.config["gap"] ?? LAYOUT.gap);
|
|
2547
|
+
const MARGIN = Number(sg.config["margin"] ?? LAYOUT.margin);
|
|
1887
2548
|
const gm = groupMap(sg);
|
|
1888
2549
|
const tm = tableMap(sg);
|
|
1889
|
-
const ntm = noteMap(sg);
|
|
1890
2550
|
const cm = chartMap(sg);
|
|
1891
2551
|
const mdm = markdownMap(sg);
|
|
1892
2552
|
// 1. Size all nodes and tables
|
|
1893
2553
|
sg.nodes.forEach(sizeNode);
|
|
1894
2554
|
sg.tables.forEach(sizeTable);
|
|
1895
|
-
sg.notes.forEach(sizeNote);
|
|
1896
2555
|
sg.charts.forEach(sizeChart);
|
|
1897
2556
|
sg.markdowns.forEach(sizeMarkdown);
|
|
1898
|
-
//
|
|
2557
|
+
// Build unified entity map (all entities have x,y,w,h — map holds direct refs)
|
|
2558
|
+
const em = buildEntityMap(sg);
|
|
1899
2559
|
// 2. Identify root vs nested items
|
|
1900
2560
|
const nestedGroupIds = new Set(sg.groups.flatMap((g) => g.children.filter((c) => c.kind === "group").map((c) => c.id)));
|
|
1901
2561
|
const groupedNodeIds = new Set(sg.groups.flatMap((g) => g.children.filter((c) => c.kind === "node").map((c) => c.id)));
|
|
1902
2562
|
const groupedTableIds = new Set(sg.groups.flatMap((g) => g.children.filter((c) => c.kind === "table").map((c) => c.id)));
|
|
1903
|
-
const groupedNoteIds = new Set(sg.groups.flatMap((g) => g.children.filter((c) => c.kind === "note").map((c) => c.id)));
|
|
1904
2563
|
const groupedChartIds = new Set(sg.groups.flatMap((g) => g.children.filter((c) => c.kind === "chart").map((c) => c.id)));
|
|
1905
2564
|
const groupedMarkdownIds = new Set(sg.groups.flatMap((g) => g.children.filter((c) => c.kind === "markdown").map((c) => c.id)));
|
|
1906
2565
|
const rootGroups = sg.groups.filter((g) => !nestedGroupIds.has(g.id));
|
|
1907
2566
|
const rootNodes = sg.nodes.filter((n) => !groupedNodeIds.has(n.id));
|
|
1908
2567
|
const rootTables = sg.tables.filter((t) => !groupedTableIds.has(t.id));
|
|
1909
|
-
const rootNotes = sg.notes.filter((n) => !groupedNoteIds.has(n.id));
|
|
1910
2568
|
const rootCharts = sg.charts.filter((c) => !groupedChartIds.has(c.id));
|
|
1911
2569
|
const rootMarkdowns = sg.markdowns.filter((m) => !groupedMarkdownIds.has(m.id));
|
|
1912
2570
|
// 3. Measure root groups bottom-up
|
|
1913
2571
|
for (const g of rootGroups)
|
|
1914
|
-
measure(g,
|
|
2572
|
+
measure(g, gm, tm, cm, mdm, em);
|
|
1915
2573
|
// 4. Build root order
|
|
1916
2574
|
// sg.rootOrder preserves DSL declaration order.
|
|
1917
2575
|
// Fall back: groups, then nodes, then tables.
|
|
@@ -1921,7 +2579,6 @@ var AIDiagram = (function (exports) {
|
|
|
1921
2579
|
...rootGroups.map((g) => ({ kind: "group", id: g.id })),
|
|
1922
2580
|
...rootNodes.map((n) => ({ kind: "node", id: n.id })),
|
|
1923
2581
|
...rootTables.map((t) => ({ kind: "table", id: t.id })),
|
|
1924
|
-
...rootNotes.map((n) => ({ kind: "note", id: n.id })),
|
|
1925
2582
|
...rootCharts.map((c) => ({ kind: "chart", id: c.id })),
|
|
1926
2583
|
...rootMarkdowns.map((m) => ({ kind: "markdown", id: m.id })),
|
|
1927
2584
|
];
|
|
@@ -1938,38 +2595,14 @@ var AIDiagram = (function (exports) {
|
|
|
1938
2595
|
// ── Grid: per-row heights, per-column widths (no wasted space) ──
|
|
1939
2596
|
const cols = rootCols;
|
|
1940
2597
|
const rows = Math.ceil(rootOrder.length / cols);
|
|
1941
|
-
const colWidths = Array(cols).fill(0);
|
|
1942
|
-
const rowHeights = Array(rows).fill(0);
|
|
1943
|
-
rootOrder.forEach((ref, idx) => {
|
|
1944
|
-
const col = idx % cols;
|
|
1945
|
-
const row = Math.floor(idx / cols);
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
h = gm.get(ref.id).h;
|
|
1950
|
-
}
|
|
1951
|
-
else if (ref.kind === "table") {
|
|
1952
|
-
w = tm.get(ref.id).w;
|
|
1953
|
-
h = tm.get(ref.id).h;
|
|
1954
|
-
}
|
|
1955
|
-
else if (ref.kind === "note") {
|
|
1956
|
-
w = ntm.get(ref.id).w;
|
|
1957
|
-
h = ntm.get(ref.id).h;
|
|
1958
|
-
}
|
|
1959
|
-
else if (ref.kind === "chart") {
|
|
1960
|
-
w = cm.get(ref.id).w;
|
|
1961
|
-
h = cm.get(ref.id).h;
|
|
1962
|
-
}
|
|
1963
|
-
else if (ref.kind === "markdown") {
|
|
1964
|
-
w = mdm.get(ref.id).w;
|
|
1965
|
-
h = mdm.get(ref.id).h;
|
|
1966
|
-
}
|
|
1967
|
-
else {
|
|
1968
|
-
w = nm.get(ref.id).w;
|
|
1969
|
-
h = nm.get(ref.id).h;
|
|
1970
|
-
}
|
|
1971
|
-
colWidths[col] = Math.max(colWidths[col], w);
|
|
1972
|
-
rowHeights[row] = Math.max(rowHeights[row], h);
|
|
2598
|
+
const colWidths = Array(cols).fill(0);
|
|
2599
|
+
const rowHeights = Array(rows).fill(0);
|
|
2600
|
+
rootOrder.forEach((ref, idx) => {
|
|
2601
|
+
const col = idx % cols;
|
|
2602
|
+
const row = Math.floor(idx / cols);
|
|
2603
|
+
const e = em.get(ref.id);
|
|
2604
|
+
colWidths[col] = Math.max(colWidths[col], e.w);
|
|
2605
|
+
rowHeights[row] = Math.max(rowHeights[row], e.h);
|
|
1973
2606
|
});
|
|
1974
2607
|
const colX = [];
|
|
1975
2608
|
let cx = MARGIN;
|
|
@@ -1984,95 +2617,24 @@ var AIDiagram = (function (exports) {
|
|
|
1984
2617
|
ry += rowHeights[r] + GAP_MAIN;
|
|
1985
2618
|
}
|
|
1986
2619
|
rootOrder.forEach((ref, idx) => {
|
|
1987
|
-
const
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
gm.get(ref.id).x = x;
|
|
1991
|
-
gm.get(ref.id).y = y;
|
|
1992
|
-
}
|
|
1993
|
-
else if (ref.kind === "table") {
|
|
1994
|
-
tm.get(ref.id).x = x;
|
|
1995
|
-
tm.get(ref.id).y = y;
|
|
1996
|
-
}
|
|
1997
|
-
else if (ref.kind === "note") {
|
|
1998
|
-
ntm.get(ref.id).x = x;
|
|
1999
|
-
ntm.get(ref.id).y = y;
|
|
2000
|
-
}
|
|
2001
|
-
else if (ref.kind === "chart") {
|
|
2002
|
-
cm.get(ref.id).x = x;
|
|
2003
|
-
cm.get(ref.id).y = y;
|
|
2004
|
-
}
|
|
2005
|
-
else if (ref.kind === "markdown") {
|
|
2006
|
-
mdm.get(ref.id).x = x;
|
|
2007
|
-
mdm.get(ref.id).y = y;
|
|
2008
|
-
}
|
|
2009
|
-
else {
|
|
2010
|
-
nm.get(ref.id).x = x;
|
|
2011
|
-
nm.get(ref.id).y = y;
|
|
2012
|
-
}
|
|
2620
|
+
const e = em.get(ref.id);
|
|
2621
|
+
e.x = colX[idx % cols];
|
|
2622
|
+
e.y = rowY[Math.floor(idx / cols)];
|
|
2013
2623
|
});
|
|
2014
2624
|
}
|
|
2015
2625
|
else {
|
|
2016
2626
|
// ── Row or Column linear flow ──────────────────────────
|
|
2017
2627
|
let pos = MARGIN;
|
|
2018
2628
|
for (const ref of rootOrder) {
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
}
|
|
2024
|
-
else if (ref.kind === "table") {
|
|
2025
|
-
w = tm.get(ref.id).w;
|
|
2026
|
-
h = tm.get(ref.id).h;
|
|
2027
|
-
}
|
|
2028
|
-
else if (ref.kind === "note") {
|
|
2029
|
-
w = ntm.get(ref.id).w;
|
|
2030
|
-
h = ntm.get(ref.id).h;
|
|
2031
|
-
}
|
|
2032
|
-
else if (ref.kind === "chart") {
|
|
2033
|
-
w = cm.get(ref.id).w;
|
|
2034
|
-
h = cm.get(ref.id).h;
|
|
2035
|
-
}
|
|
2036
|
-
else if (ref.kind === "markdown") {
|
|
2037
|
-
w = mdm.get(ref.id).w;
|
|
2038
|
-
h = mdm.get(ref.id).h;
|
|
2039
|
-
}
|
|
2040
|
-
else {
|
|
2041
|
-
w = nm.get(ref.id).w;
|
|
2042
|
-
h = nm.get(ref.id).h;
|
|
2043
|
-
}
|
|
2044
|
-
const x = useColumn ? MARGIN : pos;
|
|
2045
|
-
const y = useColumn ? pos : MARGIN;
|
|
2046
|
-
if (ref.kind === "group") {
|
|
2047
|
-
gm.get(ref.id).x = x;
|
|
2048
|
-
gm.get(ref.id).y = y;
|
|
2049
|
-
}
|
|
2050
|
-
else if (ref.kind === "table") {
|
|
2051
|
-
tm.get(ref.id).x = x;
|
|
2052
|
-
tm.get(ref.id).y = y;
|
|
2053
|
-
}
|
|
2054
|
-
else if (ref.kind === "note") {
|
|
2055
|
-
ntm.get(ref.id).x = x;
|
|
2056
|
-
ntm.get(ref.id).y = y;
|
|
2057
|
-
}
|
|
2058
|
-
else if (ref.kind === "chart") {
|
|
2059
|
-
cm.get(ref.id).x = x;
|
|
2060
|
-
cm.get(ref.id).y = y;
|
|
2061
|
-
}
|
|
2062
|
-
else if (ref.kind === "markdown") {
|
|
2063
|
-
mdm.get(ref.id).x = x;
|
|
2064
|
-
mdm.get(ref.id).y = y;
|
|
2065
|
-
}
|
|
2066
|
-
else {
|
|
2067
|
-
nm.get(ref.id).x = x;
|
|
2068
|
-
nm.get(ref.id).y = y;
|
|
2069
|
-
}
|
|
2070
|
-
pos += (useColumn ? h : w) + GAP_MAIN;
|
|
2629
|
+
const e = em.get(ref.id);
|
|
2630
|
+
e.x = useColumn ? MARGIN : pos;
|
|
2631
|
+
e.y = useColumn ? pos : MARGIN;
|
|
2632
|
+
pos += (useColumn ? e.h : e.w) + GAP_MAIN;
|
|
2071
2633
|
}
|
|
2072
2634
|
}
|
|
2073
2635
|
// 6. Place children within each root group (top-down, recursive)
|
|
2074
2636
|
for (const g of rootGroups)
|
|
2075
|
-
place(g,
|
|
2637
|
+
place(g, gm, em);
|
|
2076
2638
|
// 7. Route edges and compute canvas size
|
|
2077
2639
|
routeEdges(sg);
|
|
2078
2640
|
computeBounds(sg, MARGIN);
|
|
@@ -2089,8 +2651,8 @@ var AIDiagram = (function (exports) {
|
|
|
2089
2651
|
'#7F77DD', '#D4537E', '#639922', '#E24B4A',
|
|
2090
2652
|
];
|
|
2091
2653
|
function chartLayout(c) {
|
|
2092
|
-
const titleH = c.label ?
|
|
2093
|
-
const padL =
|
|
2654
|
+
const titleH = c.label ? CHART.titleH : CHART.titleHEmpty;
|
|
2655
|
+
const padL = CHART.padL, padR = CHART.padR, padB = CHART.padB, padT = CHART.padT;
|
|
2094
2656
|
const pw = c.w - padL - padR;
|
|
2095
2657
|
const ph = c.h - titleH - padT - padB;
|
|
2096
2658
|
return {
|
|
@@ -2184,7 +2746,7 @@ var AIDiagram = (function (exports) {
|
|
|
2184
2746
|
// Also remove the Chart.js `declare const Chart: any;` at the top of svg/index.ts
|
|
2185
2747
|
// and the CHART_COLORS array (they live in roughChart.ts now).
|
|
2186
2748
|
// ============================================================
|
|
2187
|
-
const NS$1 =
|
|
2749
|
+
const NS$1 = SVG_NS$1;
|
|
2188
2750
|
const se$1 = (tag) => document.createElementNS(NS$1, tag);
|
|
2189
2751
|
function mkG(id, cls) {
|
|
2190
2752
|
const g = se$1('g');
|
|
@@ -2207,23 +2769,23 @@ var AIDiagram = (function (exports) {
|
|
|
2207
2769
|
t.textContent = txt;
|
|
2208
2770
|
return t;
|
|
2209
2771
|
}
|
|
2210
|
-
function hashStr$
|
|
2772
|
+
function hashStr$2(s) {
|
|
2211
2773
|
let h = 5381;
|
|
2212
2774
|
for (let i = 0; i < s.length; i++)
|
|
2213
2775
|
h = ((h * 33) ^ s.charCodeAt(i)) & 0xffff;
|
|
2214
2776
|
return h;
|
|
2215
2777
|
}
|
|
2216
|
-
const BASE = { roughness:
|
|
2778
|
+
const BASE = { roughness: ROUGH.chartRoughness, bowing: ROUGH.bowing };
|
|
2217
2779
|
// ── Axes ───────────────────────────────────────────────────
|
|
2218
2780
|
function drawAxes$1(rc, g, c, px, py, pw, ph, allY, labelCol, font = 'system-ui, sans-serif') {
|
|
2219
2781
|
// Y axis
|
|
2220
2782
|
g.appendChild(rc.line(px, py, px, py + ph, {
|
|
2221
|
-
roughness: 0.4, seed: hashStr$
|
|
2783
|
+
roughness: 0.4, seed: hashStr$2(c.id + 'ya'), stroke: labelCol, strokeWidth: 1,
|
|
2222
2784
|
}));
|
|
2223
2785
|
// X axis (baseline)
|
|
2224
2786
|
const baseline = makeValueToY(allY, py, ph)(0);
|
|
2225
2787
|
g.appendChild(rc.line(px, baseline, px + pw, baseline, {
|
|
2226
|
-
roughness: 0.4, seed: hashStr$
|
|
2788
|
+
roughness: 0.4, seed: hashStr$2(c.id + 'xa'), stroke: labelCol, strokeWidth: 1,
|
|
2227
2789
|
}));
|
|
2228
2790
|
// Y ticks + labels
|
|
2229
2791
|
const toY = makeValueToY(allY, py, ph);
|
|
@@ -2232,7 +2794,7 @@ var AIDiagram = (function (exports) {
|
|
|
2232
2794
|
if (ty < py - 2 || ty > py + ph + 2)
|
|
2233
2795
|
continue;
|
|
2234
2796
|
g.appendChild(rc.line(px - 3, ty, px, ty, {
|
|
2235
|
-
roughness: 0.2, seed: hashStr$
|
|
2797
|
+
roughness: 0.2, seed: hashStr$2(c.id + 'yt' + tick), stroke: labelCol, strokeWidth: 0.7,
|
|
2236
2798
|
}));
|
|
2237
2799
|
g.appendChild(mkT(fmtNum$1(tick), px - 5, ty, 9, 400, labelCol, 'end', font));
|
|
2238
2800
|
}
|
|
@@ -2271,7 +2833,7 @@ var AIDiagram = (function (exports) {
|
|
|
2271
2833
|
cg.setAttribute('opacity', String(s.opacity));
|
|
2272
2834
|
// Background box
|
|
2273
2835
|
cg.appendChild(rc.rectangle(c.x, c.y, c.w, c.h, {
|
|
2274
|
-
...BASE, seed: hashStr$
|
|
2836
|
+
...BASE, seed: hashStr$2(c.id),
|
|
2275
2837
|
fill: bgFill, fillStyle: 'solid',
|
|
2276
2838
|
stroke: bgStroke, strokeWidth: Number(s.strokeWidth ?? 1.2),
|
|
2277
2839
|
...(s.strokeDash ? { strokeLineDash: s.strokeDash } : {}),
|
|
@@ -2295,7 +2857,7 @@ var AIDiagram = (function (exports) {
|
|
|
2295
2857
|
? donutArcPath(cx, cy, r, ir, angle, angle + sweep)
|
|
2296
2858
|
: pieArcPath(cx, cy, r, angle, angle + sweep);
|
|
2297
2859
|
cg.appendChild(rc.path(d, {
|
|
2298
|
-
roughness: 1.0, bowing: 0.5, seed: hashStr$
|
|
2860
|
+
roughness: 1.0, bowing: 0.5, seed: hashStr$2(c.id + seg.label),
|
|
2299
2861
|
fill: seg.color + 'bb',
|
|
2300
2862
|
fillStyle: 'solid',
|
|
2301
2863
|
stroke: seg.color,
|
|
@@ -2314,11 +2876,11 @@ var AIDiagram = (function (exports) {
|
|
|
2314
2876
|
const toX = makeValueToX(xs, px, pw);
|
|
2315
2877
|
const toY = makeValueToY(ys, py, ph);
|
|
2316
2878
|
// Simple axes (no named ticks — raw data ranges)
|
|
2317
|
-
cg.appendChild(rc.line(px, py, px, py + ph, { roughness: 0.4, seed: hashStr$
|
|
2318
|
-
cg.appendChild(rc.line(px, py + ph, px + pw, py + ph, { roughness: 0.4, seed: hashStr$
|
|
2879
|
+
cg.appendChild(rc.line(px, py, px, py + ph, { roughness: 0.4, seed: hashStr$2(c.id + 'ya'), stroke: lc, strokeWidth: 1 }));
|
|
2880
|
+
cg.appendChild(rc.line(px, py + ph, px + pw, py + ph, { roughness: 0.4, seed: hashStr$2(c.id + 'xa'), stroke: lc, strokeWidth: 1 }));
|
|
2319
2881
|
pts.forEach((pt, i) => {
|
|
2320
2882
|
cg.appendChild(rc.ellipse(toX(pt.x), toY(pt.y), 10, 10, {
|
|
2321
|
-
roughness: 0.8, seed: hashStr$
|
|
2883
|
+
roughness: 0.8, seed: hashStr$2(c.id + pt.label),
|
|
2322
2884
|
fill: CHART_COLORS[i % CHART_COLORS.length] + '99',
|
|
2323
2885
|
fillStyle: 'solid',
|
|
2324
2886
|
stroke: CHART_COLORS[i % CHART_COLORS.length],
|
|
@@ -2351,7 +2913,7 @@ var AIDiagram = (function (exports) {
|
|
|
2351
2913
|
const bh = Math.abs(baseline - toY(val)) || 2;
|
|
2352
2914
|
cg.appendChild(rc.rectangle(bx, by, barW, bh, {
|
|
2353
2915
|
roughness: 1.1, bowing: 0.5,
|
|
2354
|
-
seed: hashStr$
|
|
2916
|
+
seed: hashStr$2(c.id + si + i),
|
|
2355
2917
|
fill: ser.color + 'bb',
|
|
2356
2918
|
fillStyle: 'hachure',
|
|
2357
2919
|
hachureAngle: -41,
|
|
@@ -2379,7 +2941,7 @@ var AIDiagram = (function (exports) {
|
|
|
2379
2941
|
[pts[pts.length - 1][0], baseline],
|
|
2380
2942
|
];
|
|
2381
2943
|
cg.appendChild(rc.polygon(poly, {
|
|
2382
|
-
roughness: 0.5, seed: hashStr$
|
|
2944
|
+
roughness: 0.5, seed: hashStr$2(c.id + 'af' + si),
|
|
2383
2945
|
fill: ser.color + '44',
|
|
2384
2946
|
fillStyle: 'solid',
|
|
2385
2947
|
stroke: 'none',
|
|
@@ -2389,7 +2951,7 @@ var AIDiagram = (function (exports) {
|
|
|
2389
2951
|
for (let i = 0; i < pts.length - 1; i++) {
|
|
2390
2952
|
cg.appendChild(rc.line(pts[i][0], pts[i][1], pts[i + 1][0], pts[i + 1][1], {
|
|
2391
2953
|
roughness: 0.9, bowing: 0.6,
|
|
2392
|
-
seed: hashStr$
|
|
2954
|
+
seed: hashStr$2(c.id + si + i),
|
|
2393
2955
|
stroke: ser.color,
|
|
2394
2956
|
strokeWidth: 1.8,
|
|
2395
2957
|
}));
|
|
@@ -2397,7 +2959,7 @@ var AIDiagram = (function (exports) {
|
|
|
2397
2959
|
// Point dots
|
|
2398
2960
|
pts.forEach(([px2, py2], i) => {
|
|
2399
2961
|
cg.appendChild(rc.ellipse(px2, py2, 7, 7, {
|
|
2400
|
-
roughness: 0.3, seed: hashStr$
|
|
2962
|
+
roughness: 0.3, seed: hashStr$2(c.id + 'dot' + si + i),
|
|
2401
2963
|
fill: ser.color,
|
|
2402
2964
|
fillStyle: 'solid',
|
|
2403
2965
|
stroke: ser.color,
|
|
@@ -2711,83 +3273,6 @@ var AIDiagram = (function (exports) {
|
|
|
2711
3273
|
}
|
|
2712
3274
|
const THEME_NAMES = Object.keys(PALETTES);
|
|
2713
3275
|
|
|
2714
|
-
// ============================================================
|
|
2715
|
-
// sketchmark — Font Registry
|
|
2716
|
-
// ============================================================
|
|
2717
|
-
// built-in named fonts — user can reference these by short name
|
|
2718
|
-
const BUILTIN_FONTS = {
|
|
2719
|
-
// hand-drawn
|
|
2720
|
-
caveat: {
|
|
2721
|
-
family: "'Caveat', cursive",
|
|
2722
|
-
url: 'https://fonts.googleapis.com/css2?family=Caveat:wght@400;500;600&display=swap',
|
|
2723
|
-
},
|
|
2724
|
-
handlee: {
|
|
2725
|
-
family: "'Handlee', cursive",
|
|
2726
|
-
url: 'https://fonts.googleapis.com/css2?family=Handlee&display=swap',
|
|
2727
|
-
},
|
|
2728
|
-
'indie-flower': {
|
|
2729
|
-
family: "'Indie Flower', cursive",
|
|
2730
|
-
url: 'https://fonts.googleapis.com/css2?family=Indie+Flower&display=swap',
|
|
2731
|
-
},
|
|
2732
|
-
'patrick-hand': {
|
|
2733
|
-
family: "'Patrick Hand', cursive",
|
|
2734
|
-
url: 'https://fonts.googleapis.com/css2?family=Patrick+Hand&display=swap',
|
|
2735
|
-
},
|
|
2736
|
-
// clean / readable
|
|
2737
|
-
'dm-mono': {
|
|
2738
|
-
family: "'DM Mono', monospace",
|
|
2739
|
-
url: 'https://fonts.googleapis.com/css2?family=DM+Mono:wght@300;400;500&display=swap',
|
|
2740
|
-
},
|
|
2741
|
-
'jetbrains': {
|
|
2742
|
-
family: "'JetBrains Mono', monospace",
|
|
2743
|
-
url: 'https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;500&display=swap',
|
|
2744
|
-
},
|
|
2745
|
-
'instrument': {
|
|
2746
|
-
family: "'Instrument Serif', serif",
|
|
2747
|
-
url: 'https://fonts.googleapis.com/css2?family=Instrument+Serif&display=swap',
|
|
2748
|
-
},
|
|
2749
|
-
'playfair': {
|
|
2750
|
-
family: "'Playfair Display', serif",
|
|
2751
|
-
url: 'https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;500&display=swap',
|
|
2752
|
-
},
|
|
2753
|
-
// system fallbacks (no URL needed)
|
|
2754
|
-
system: { family: 'system-ui, sans-serif' },
|
|
2755
|
-
mono: { family: "'Courier New', monospace" },
|
|
2756
|
-
serif: { family: 'Georgia, serif' },
|
|
2757
|
-
};
|
|
2758
|
-
// default — what renders when no font is specified
|
|
2759
|
-
const DEFAULT_FONT = 'system-ui, sans-serif';
|
|
2760
|
-
// resolve a short name or pass-through a quoted CSS family
|
|
2761
|
-
function resolveFont(nameOrFamily) {
|
|
2762
|
-
const key = nameOrFamily.toLowerCase().trim();
|
|
2763
|
-
if (BUILTIN_FONTS[key])
|
|
2764
|
-
return BUILTIN_FONTS[key].family;
|
|
2765
|
-
return nameOrFamily; // treat as raw CSS font-family
|
|
2766
|
-
}
|
|
2767
|
-
// inject a <link> into <head> for a built-in font (browser only)
|
|
2768
|
-
function loadFont(name) {
|
|
2769
|
-
if (typeof document === 'undefined')
|
|
2770
|
-
return;
|
|
2771
|
-
const key = name.toLowerCase().trim();
|
|
2772
|
-
const def = BUILTIN_FONTS[key];
|
|
2773
|
-
if (!def?.url || def.loaded)
|
|
2774
|
-
return;
|
|
2775
|
-
if (document.querySelector(`link[data-sketchmark-font="${key}"]`))
|
|
2776
|
-
return;
|
|
2777
|
-
const link = document.createElement('link');
|
|
2778
|
-
link.rel = 'stylesheet';
|
|
2779
|
-
link.href = def.url;
|
|
2780
|
-
link.setAttribute('data-sketchmark-font', key);
|
|
2781
|
-
document.head.appendChild(link);
|
|
2782
|
-
def.loaded = true;
|
|
2783
|
-
}
|
|
2784
|
-
// user registers their own font (already loaded via CSS/link)
|
|
2785
|
-
function registerFont(name, family, url) {
|
|
2786
|
-
BUILTIN_FONTS[name.toLowerCase()] = { family, url };
|
|
2787
|
-
if (url)
|
|
2788
|
-
loadFont(name);
|
|
2789
|
-
}
|
|
2790
|
-
|
|
2791
3276
|
function rotatePoints(points, center, degrees) {
|
|
2792
3277
|
if (points && points.length) {
|
|
2793
3278
|
const [cx, cy] = center;
|
|
@@ -4863,53 +5348,62 @@ var AIDiagram = (function (exports) {
|
|
|
4863
5348
|
};
|
|
4864
5349
|
|
|
4865
5350
|
// ============================================================
|
|
4866
|
-
//
|
|
5351
|
+
// Shared Typography Resolution
|
|
5352
|
+
//
|
|
5353
|
+
// Extracts the repeated pattern of resolving fontSize, fontWeight,
|
|
5354
|
+
// textColor, font, textAlign, letterSpacing, lineHeight, padding,
|
|
5355
|
+
// verticalAlign from a style object with entity-specific defaults.
|
|
4867
5356
|
// ============================================================
|
|
4868
|
-
const
|
|
4869
|
-
|
|
4870
|
-
|
|
4871
|
-
|
|
4872
|
-
|
|
4873
|
-
|
|
4874
|
-
|
|
4875
|
-
|
|
4876
|
-
|
|
4877
|
-
|
|
4878
|
-
|
|
4879
|
-
const
|
|
4880
|
-
|
|
4881
|
-
|
|
4882
|
-
const
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
|
|
4891
|
-
return resolveFont(raw);
|
|
5357
|
+
const ANCHOR_MAP = {
|
|
5358
|
+
left: "start",
|
|
5359
|
+
center: "middle",
|
|
5360
|
+
right: "end",
|
|
5361
|
+
};
|
|
5362
|
+
function resolveTypography(style, defaults, diagramFont, fallbackTextColor) {
|
|
5363
|
+
const s = (style ?? {});
|
|
5364
|
+
const fontSize = Number(s.fontSize ?? defaults.fontSize ?? TYPOGRAPHY.defaultFontSize);
|
|
5365
|
+
const fontWeight = (s.fontWeight ?? defaults.fontWeight ?? TYPOGRAPHY.defaultFontWeight);
|
|
5366
|
+
const textColor = String(s.color ?? defaults.textColor ?? fallbackTextColor);
|
|
5367
|
+
const font = resolveStyleFont(s, diagramFont);
|
|
5368
|
+
const textAlign = String(s.textAlign ?? defaults.textAlign ?? TYPOGRAPHY.defaultAlign);
|
|
5369
|
+
const textAnchor = ANCHOR_MAP[textAlign] ?? "middle";
|
|
5370
|
+
const letterSpacing = s.letterSpacing;
|
|
5371
|
+
const lhMult = Number(s.lineHeight ?? defaults.lineHeight ?? TYPOGRAPHY.defaultLineHeight);
|
|
5372
|
+
const lineHeight = lhMult * fontSize;
|
|
5373
|
+
const verticalAlign = String(s.verticalAlign ?? defaults.verticalAlign ?? TYPOGRAPHY.defaultVAlign);
|
|
5374
|
+
const padding = Number(s.padding ?? defaults.padding ?? TYPOGRAPHY.defaultPadding);
|
|
5375
|
+
return {
|
|
5376
|
+
fontSize, fontWeight, textColor, font,
|
|
5377
|
+
textAlign, textAnchor, letterSpacing,
|
|
5378
|
+
lineHeight, verticalAlign, padding,
|
|
5379
|
+
};
|
|
4892
5380
|
}
|
|
4893
|
-
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
4905
|
-
|
|
4906
|
-
|
|
4907
|
-
|
|
4908
|
-
|
|
4909
|
-
|
|
4910
|
-
|
|
4911
|
-
return lines;
|
|
5381
|
+
/** Compute the x coordinate for text based on alignment within a box. */
|
|
5382
|
+
function computeTextX(typo, x, w) {
|
|
5383
|
+
return typo.textAlign === "left" ? x + typo.padding
|
|
5384
|
+
: typo.textAlign === "right" ? x + w - typo.padding
|
|
5385
|
+
: x + w / 2;
|
|
5386
|
+
}
|
|
5387
|
+
/** Compute the vertical center for a block of text lines within a box. */
|
|
5388
|
+
function computeTextCY(typo, y, h, lineCount, topOffset) {
|
|
5389
|
+
const pad = typo.padding;
|
|
5390
|
+
const top = y + (topOffset ?? pad);
|
|
5391
|
+
const bottom = y + h - pad;
|
|
5392
|
+
const mid = (top + bottom) / 2;
|
|
5393
|
+
const blockH = (lineCount - 1) * typo.lineHeight;
|
|
5394
|
+
if (typo.verticalAlign === "top")
|
|
5395
|
+
return top + blockH / 2;
|
|
5396
|
+
if (typo.verticalAlign === "bottom")
|
|
5397
|
+
return bottom - blockH / 2;
|
|
5398
|
+
return mid;
|
|
4912
5399
|
}
|
|
5400
|
+
|
|
5401
|
+
// ============================================================
|
|
5402
|
+
// sketchmark — SVG Renderer (rough.js hand-drawn)
|
|
5403
|
+
// ============================================================
|
|
5404
|
+
const NS = SVG_NS$1;
|
|
5405
|
+
const se = (tag) => document.createElementNS(NS, tag);
|
|
5406
|
+
const BASE_ROUGH = { roughness: ROUGH.roughness, bowing: ROUGH.bowing };
|
|
4913
5407
|
// ── SVG text helpers ──────────────────────────────────────────────────────
|
|
4914
5408
|
/**
|
|
4915
5409
|
* Single-line SVG text element.
|
|
@@ -4990,56 +5484,6 @@ var AIDiagram = (function (exports) {
|
|
|
4990
5484
|
g.setAttribute("class", cls);
|
|
4991
5485
|
return g;
|
|
4992
5486
|
}
|
|
4993
|
-
// ── Arrow direction from connector ────────────────────────────────────────
|
|
4994
|
-
function connMeta$1(connector) {
|
|
4995
|
-
if (connector === "--")
|
|
4996
|
-
return { arrowAt: "none", dashed: false };
|
|
4997
|
-
if (connector === "---")
|
|
4998
|
-
return { arrowAt: "none", dashed: true };
|
|
4999
|
-
const bidir = connector.includes("<") && connector.includes(">");
|
|
5000
|
-
if (bidir)
|
|
5001
|
-
return { arrowAt: "both", dashed: connector.includes("--") };
|
|
5002
|
-
const back = connector.startsWith("<");
|
|
5003
|
-
const dashed = connector.includes("--");
|
|
5004
|
-
if (back)
|
|
5005
|
-
return { arrowAt: "start", dashed };
|
|
5006
|
-
return { arrowAt: "end", dashed };
|
|
5007
|
-
}
|
|
5008
|
-
// ── Generic rect connection point ─────────────────────────────────────────
|
|
5009
|
-
function rectConnPoint$1(rx, ry, rw, rh, ox, oy) {
|
|
5010
|
-
const cx = rx + rw / 2, cy = ry + rh / 2;
|
|
5011
|
-
const dx = ox - cx, dy = oy - cy;
|
|
5012
|
-
if (Math.abs(dx) < 0.01 && Math.abs(dy) < 0.01)
|
|
5013
|
-
return [cx, cy];
|
|
5014
|
-
const hw = rw / 2 - 2, hh = rh / 2 - 2;
|
|
5015
|
-
const tx = Math.abs(dx) > 0.01 ? hw / Math.abs(dx) : 1e9;
|
|
5016
|
-
const ty = Math.abs(dy) > 0.01 ? hh / Math.abs(dy) : 1e9;
|
|
5017
|
-
const t = Math.min(tx, ty);
|
|
5018
|
-
return [cx + t * dx, cy + t * dy];
|
|
5019
|
-
}
|
|
5020
|
-
function resolveEndpoint$1(id, nm, tm, gm, cm, ntm) {
|
|
5021
|
-
return (nm.get(id) ?? tm.get(id) ?? gm.get(id) ?? cm.get(id) ?? ntm.get(id) ?? null);
|
|
5022
|
-
}
|
|
5023
|
-
function getConnPoint$1(src, dstCX, dstCY) {
|
|
5024
|
-
if ("shape" in src && src.shape) {
|
|
5025
|
-
return connPoint(src, {
|
|
5026
|
-
x: dstCX - 1,
|
|
5027
|
-
y: dstCY - 1,
|
|
5028
|
-
w: 2,
|
|
5029
|
-
h: 2});
|
|
5030
|
-
}
|
|
5031
|
-
return rectConnPoint$1(src.x, src.y, src.w, src.h, dstCX, dstCY);
|
|
5032
|
-
}
|
|
5033
|
-
// ── Group depth (for paint order) ─────────────────────────────────────────
|
|
5034
|
-
function groupDepth$1(g, gm) {
|
|
5035
|
-
let d = 0;
|
|
5036
|
-
let cur = g;
|
|
5037
|
-
while (cur?.parentId) {
|
|
5038
|
-
d++;
|
|
5039
|
-
cur = gm.get(cur.parentId);
|
|
5040
|
-
}
|
|
5041
|
-
return d;
|
|
5042
|
-
}
|
|
5043
5487
|
// ── Node shapes ───────────────────────────────────────────────────────────
|
|
5044
5488
|
function renderShape$1(rc, n, palette) {
|
|
5045
5489
|
const s = n.style ?? {};
|
|
@@ -5054,171 +5498,15 @@ var AIDiagram = (function (exports) {
|
|
|
5054
5498
|
strokeWidth: Number(s.strokeWidth ?? 1.9),
|
|
5055
5499
|
...(s.strokeDash ? { strokeLineDash: s.strokeDash } : {}),
|
|
5056
5500
|
};
|
|
5057
|
-
const
|
|
5058
|
-
|
|
5059
|
-
|
|
5060
|
-
|
|
5061
|
-
|
|
5062
|
-
case "diamond":
|
|
5063
|
-
return [
|
|
5064
|
-
rc.polygon([
|
|
5065
|
-
[cx, n.y + 2],
|
|
5066
|
-
[cx + hw, cy],
|
|
5067
|
-
[cx, n.y + n.h - 2],
|
|
5068
|
-
[cx - hw, cy],
|
|
5069
|
-
], opts),
|
|
5070
|
-
];
|
|
5071
|
-
case "hexagon": {
|
|
5072
|
-
const hw2 = hw * 0.56;
|
|
5073
|
-
return [
|
|
5074
|
-
rc.polygon([
|
|
5075
|
-
[cx - hw2, n.y + 3],
|
|
5076
|
-
[cx + hw2, n.y + 3],
|
|
5077
|
-
[cx + hw, cy],
|
|
5078
|
-
[cx + hw2, n.y + n.h - 3],
|
|
5079
|
-
[cx - hw2, n.y + n.h - 3],
|
|
5080
|
-
[cx - hw, cy],
|
|
5081
|
-
], opts),
|
|
5082
|
-
];
|
|
5083
|
-
}
|
|
5084
|
-
case "triangle":
|
|
5085
|
-
return [
|
|
5086
|
-
rc.polygon([
|
|
5087
|
-
[cx, n.y + 3],
|
|
5088
|
-
[n.x + n.w - 3, n.y + n.h - 3],
|
|
5089
|
-
[n.x + 3, n.y + n.h - 3],
|
|
5090
|
-
], opts),
|
|
5091
|
-
];
|
|
5092
|
-
case "parallelogram":
|
|
5093
|
-
return [
|
|
5094
|
-
rc.polygon([
|
|
5095
|
-
[n.x + 18, n.y + 1],
|
|
5096
|
-
[n.x + n.w - 1, n.y + 1],
|
|
5097
|
-
[n.x + n.w - 18, n.y + n.h - 1],
|
|
5098
|
-
[n.x + 1, n.y + n.h - 1],
|
|
5099
|
-
], opts),
|
|
5100
|
-
];
|
|
5101
|
-
case "cylinder": {
|
|
5102
|
-
const eH = 18;
|
|
5103
|
-
return [
|
|
5104
|
-
rc.rectangle(n.x + 3, n.y + eH / 2, n.w - 6, n.h - eH, opts),
|
|
5105
|
-
rc.ellipse(cx, n.y + eH / 2, n.w - 8, eH, { ...opts, roughness: 0.6 }),
|
|
5106
|
-
rc.ellipse(cx, n.y + n.h - eH / 2, n.w - 8, eH, {
|
|
5107
|
-
...opts,
|
|
5108
|
-
roughness: 0.6,
|
|
5109
|
-
fill: "none",
|
|
5110
|
-
}),
|
|
5111
|
-
];
|
|
5112
|
-
}
|
|
5113
|
-
case "text":
|
|
5114
|
-
return [];
|
|
5115
|
-
case "icon": {
|
|
5116
|
-
if (n.iconName) {
|
|
5117
|
-
const [prefix, name] = n.iconName.includes(":")
|
|
5118
|
-
? n.iconName.split(":", 2)
|
|
5119
|
-
: ["mdi", n.iconName];
|
|
5120
|
-
const iconColor = s.color
|
|
5121
|
-
? encodeURIComponent(String(s.color))
|
|
5122
|
-
: encodeURIComponent(String(palette.nodeStroke));
|
|
5123
|
-
// reserve bottom 20px for label when present
|
|
5124
|
-
const labelSpace = n.label ? 20 : 0;
|
|
5125
|
-
const iconAreaH = n.h - labelSpace;
|
|
5126
|
-
const iconSize = Math.min(n.w, iconAreaH) - 4;
|
|
5127
|
-
const iconUrl = `https://api.iconify.design/${prefix}/${name}.svg?color=${iconColor}&width=${iconSize}&height=${iconSize}`;
|
|
5128
|
-
const img = document.createElementNS(NS, "image");
|
|
5129
|
-
img.setAttribute("href", iconUrl);
|
|
5130
|
-
const iconX = n.x + (n.w - iconSize) / 2;
|
|
5131
|
-
const iconY = n.y + (iconAreaH - iconSize) / 2;
|
|
5132
|
-
img.setAttribute("x", String(iconX));
|
|
5133
|
-
img.setAttribute("y", String(iconY));
|
|
5134
|
-
img.setAttribute("width", String(iconSize));
|
|
5135
|
-
img.setAttribute("height", String(iconSize));
|
|
5136
|
-
img.setAttribute("preserveAspectRatio", "xMidYMid meet");
|
|
5137
|
-
if (s.opacity != null)
|
|
5138
|
-
img.setAttribute("opacity", String(s.opacity));
|
|
5139
|
-
// clip-path for rounded corners
|
|
5140
|
-
const clipId = `clip-${n.id}`;
|
|
5141
|
-
const defs = document.createElementNS(NS, "defs");
|
|
5142
|
-
const clip = document.createElementNS(NS, "clipPath");
|
|
5143
|
-
clip.setAttribute("id", clipId);
|
|
5144
|
-
const rect = document.createElementNS(NS, "rect");
|
|
5145
|
-
rect.setAttribute("x", String(iconX));
|
|
5146
|
-
rect.setAttribute("y", String(iconY));
|
|
5147
|
-
rect.setAttribute("width", String(iconSize));
|
|
5148
|
-
rect.setAttribute("height", String(iconSize));
|
|
5149
|
-
rect.setAttribute("rx", "6");
|
|
5150
|
-
clip.appendChild(rect);
|
|
5151
|
-
defs.appendChild(clip);
|
|
5152
|
-
img.setAttribute("clip-path", `url(#${clipId})`);
|
|
5153
|
-
// only draw border when stroke is explicitly set
|
|
5154
|
-
const els = [defs, img];
|
|
5155
|
-
if (s.stroke) {
|
|
5156
|
-
els.push(rc.rectangle(n.x + 1, n.y + 1, n.w - 2, n.h - 2, {
|
|
5157
|
-
...opts,
|
|
5158
|
-
fill: "none",
|
|
5159
|
-
}));
|
|
5160
|
-
}
|
|
5161
|
-
return els;
|
|
5162
|
-
}
|
|
5163
|
-
// fallback: placeholder square
|
|
5164
|
-
return [
|
|
5165
|
-
rc.rectangle(n.x + 1, n.y + 1, n.w - 2, n.h - 2, {
|
|
5166
|
-
...opts,
|
|
5167
|
-
fill: "#e0e0e0",
|
|
5168
|
-
stroke: "#999999",
|
|
5169
|
-
}),
|
|
5170
|
-
];
|
|
5171
|
-
}
|
|
5172
|
-
case "image": {
|
|
5173
|
-
if (n.imageUrl) {
|
|
5174
|
-
// reserve bottom 20px for label when present
|
|
5175
|
-
const imgLabelSpace = n.label ? 20 : 0;
|
|
5176
|
-
const imgAreaH = n.h - imgLabelSpace;
|
|
5177
|
-
const img = document.createElementNS(NS, "image");
|
|
5178
|
-
img.setAttribute("href", n.imageUrl);
|
|
5179
|
-
img.setAttribute("x", String(n.x + 1));
|
|
5180
|
-
img.setAttribute("y", String(n.y + 1));
|
|
5181
|
-
img.setAttribute("width", String(n.w - 2));
|
|
5182
|
-
img.setAttribute("height", String(imgAreaH - 2));
|
|
5183
|
-
img.setAttribute("preserveAspectRatio", "xMidYMid meet");
|
|
5184
|
-
const clipId = `clip-${n.id}`;
|
|
5185
|
-
const defs = document.createElementNS(NS, "defs");
|
|
5186
|
-
const clip = document.createElementNS(NS, "clipPath");
|
|
5187
|
-
clip.setAttribute("id", clipId);
|
|
5188
|
-
const rect = document.createElementNS(NS, "rect");
|
|
5189
|
-
rect.setAttribute("x", String(n.x + 1));
|
|
5190
|
-
rect.setAttribute("y", String(n.y + 1));
|
|
5191
|
-
rect.setAttribute("width", String(n.w - 2));
|
|
5192
|
-
rect.setAttribute("height", String(imgAreaH - 2));
|
|
5193
|
-
rect.setAttribute("rx", "6");
|
|
5194
|
-
clip.appendChild(rect);
|
|
5195
|
-
defs.appendChild(clip);
|
|
5196
|
-
img.setAttribute("clip-path", `url(#${clipId})`);
|
|
5197
|
-
// only draw border when stroke is explicitly set
|
|
5198
|
-
const els = [defs, img];
|
|
5199
|
-
if (s.stroke) {
|
|
5200
|
-
els.push(rc.rectangle(n.x + 1, n.y + 1, n.w - 2, n.h - 2, {
|
|
5201
|
-
...opts,
|
|
5202
|
-
fill: "none",
|
|
5203
|
-
}));
|
|
5204
|
-
}
|
|
5205
|
-
return els;
|
|
5206
|
-
}
|
|
5207
|
-
return [
|
|
5208
|
-
rc.rectangle(n.x + 1, n.y + 1, n.w - 2, n.h - 2, {
|
|
5209
|
-
...opts,
|
|
5210
|
-
fill: "#e0e0e0",
|
|
5211
|
-
stroke: "#999999",
|
|
5212
|
-
}),
|
|
5213
|
-
];
|
|
5214
|
-
}
|
|
5215
|
-
default:
|
|
5216
|
-
return [rc.rectangle(n.x + 1, n.y + 1, n.w - 2, n.h - 2, opts)];
|
|
5217
|
-
}
|
|
5501
|
+
const shape = getShape(n.shape);
|
|
5502
|
+
if (shape)
|
|
5503
|
+
return shape.renderSVG(rc, n, palette, opts);
|
|
5504
|
+
// fallback: box
|
|
5505
|
+
return [rc.rectangle(n.x + 1, n.y + 1, n.w - 2, n.h - 2, opts)];
|
|
5218
5506
|
}
|
|
5219
5507
|
// ── Arrowhead ─────────────────────────────────────────────────────────────
|
|
5220
5508
|
function arrowHead(rc, x, y, angle, col, seed) {
|
|
5221
|
-
const as =
|
|
5509
|
+
const as = EDGE.arrowSize;
|
|
5222
5510
|
return rc.polygon([
|
|
5223
5511
|
[x, y],
|
|
5224
5512
|
[
|
|
@@ -5286,13 +5574,13 @@ var AIDiagram = (function (exports) {
|
|
|
5286
5574
|
// ── Title ────────────────────────────────────────────────
|
|
5287
5575
|
if (options.showTitle && sg.title) {
|
|
5288
5576
|
const titleColor = String(sg.config["title-color"] ?? palette.titleText);
|
|
5289
|
-
const titleSize = Number(sg.config["title-size"] ??
|
|
5290
|
-
const titleWeight = Number(sg.config["title-weight"] ??
|
|
5291
|
-
svg.appendChild(mkText(sg.title, sg.width / 2,
|
|
5577
|
+
const titleSize = Number(sg.config["title-size"] ?? TITLE.fontSize);
|
|
5578
|
+
const titleWeight = Number(sg.config["title-weight"] ?? TITLE.fontWeight);
|
|
5579
|
+
svg.appendChild(mkText(sg.title, sg.width / 2, TITLE.y, titleSize, titleWeight, titleColor, "middle", diagramFont));
|
|
5292
5580
|
}
|
|
5293
5581
|
// ── Groups ───────────────────────────────────────────────
|
|
5294
5582
|
const gmMap = new Map(sg.groups.map((g) => [g.id, g]));
|
|
5295
|
-
const sortedGroups = [...sg.groups].sort((a, b) => groupDepth
|
|
5583
|
+
const sortedGroups = [...sg.groups].sort((a, b) => groupDepth(a, gmMap) - groupDepth(b, gmMap));
|
|
5296
5584
|
const GL = mkGroup("grp-layer");
|
|
5297
5585
|
for (const g of sortedGroups) {
|
|
5298
5586
|
if (!g.w)
|
|
@@ -5313,26 +5601,10 @@ var AIDiagram = (function (exports) {
|
|
|
5313
5601
|
strokeLineDash: gs.strokeDash ?? palette.groupDash,
|
|
5314
5602
|
}));
|
|
5315
5603
|
// ── Group label typography ──────────────────────────
|
|
5316
|
-
const
|
|
5317
|
-
const
|
|
5318
|
-
const gFontWeight = gs.fontWeight ?? 500;
|
|
5319
|
-
const gFont = resolveStyleFont$1(gs, diagramFont);
|
|
5320
|
-
const gLetterSpacing = gs.letterSpacing;
|
|
5321
|
-
const gPad = Number(gs.padding ?? 14);
|
|
5322
|
-
const gTextAlign = String(gs.textAlign ?? "left");
|
|
5323
|
-
const gAnchorMap = {
|
|
5324
|
-
left: "start",
|
|
5325
|
-
center: "middle",
|
|
5326
|
-
right: "end",
|
|
5327
|
-
};
|
|
5328
|
-
const gAnchor = gAnchorMap[gTextAlign] ?? "start";
|
|
5329
|
-
const gTextX = gTextAlign === "right"
|
|
5330
|
-
? g.x + g.w - gPad
|
|
5331
|
-
: gTextAlign === "center"
|
|
5332
|
-
? g.x + g.w / 2
|
|
5333
|
-
: g.x + gPad;
|
|
5604
|
+
const gTypo = resolveTypography(gs, { fontSize: GROUP_LABEL.fontSize, fontWeight: GROUP_LABEL.fontWeight, textAlign: "left", padding: GROUP_LABEL.padding }, diagramFont, palette.groupLabel);
|
|
5605
|
+
const gTextX = computeTextX(gTypo, g.x, g.w);
|
|
5334
5606
|
if (g.label) {
|
|
5335
|
-
gg.appendChild(mkText(g.label, gTextX, g.y +
|
|
5607
|
+
gg.appendChild(mkText(g.label, gTextX, g.y + gTypo.padding, gTypo.fontSize, gTypo.fontWeight, gTypo.textColor, gTypo.textAnchor, gTypo.font, gTypo.letterSpacing));
|
|
5336
5608
|
}
|
|
5337
5609
|
GL.appendChild(gg);
|
|
5338
5610
|
}
|
|
@@ -5341,44 +5613,51 @@ var AIDiagram = (function (exports) {
|
|
|
5341
5613
|
const nm = nodeMap(sg);
|
|
5342
5614
|
const tm = tableMap(sg);
|
|
5343
5615
|
const cm = chartMap(sg);
|
|
5344
|
-
const ntm = noteMap(sg);
|
|
5345
5616
|
const EL = mkGroup("edge-layer");
|
|
5346
5617
|
for (const e of sg.edges) {
|
|
5347
|
-
const src = resolveEndpoint
|
|
5348
|
-
const dst = resolveEndpoint
|
|
5618
|
+
const src = resolveEndpoint(e.from, nm, tm, gmMap, cm);
|
|
5619
|
+
const dst = resolveEndpoint(e.to, nm, tm, gmMap, cm);
|
|
5349
5620
|
if (!src || !dst)
|
|
5350
5621
|
continue;
|
|
5351
5622
|
const dstCX = dst.x + dst.w / 2, dstCY = dst.y + dst.h / 2;
|
|
5352
5623
|
const srcCX = src.x + src.w / 2, srcCY = src.y + src.h / 2;
|
|
5353
|
-
const [x1, y1] = getConnPoint
|
|
5354
|
-
const [x2, y2] = getConnPoint
|
|
5624
|
+
const [x1, y1] = getConnPoint(src, dstCX, dstCY);
|
|
5625
|
+
const [x2, y2] = getConnPoint(dst, srcCX, srcCY);
|
|
5355
5626
|
const eg = mkGroup(`edge-${e.from}-${e.to}`, "eg");
|
|
5356
5627
|
if (e.style?.opacity != null)
|
|
5357
5628
|
eg.setAttribute("opacity", String(e.style.opacity));
|
|
5358
5629
|
const len = Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2) || 1;
|
|
5359
5630
|
const nx = (x2 - x1) / len, ny = (y2 - y1) / len;
|
|
5360
5631
|
const ecol = String(e.style?.stroke ?? palette.edgeStroke);
|
|
5361
|
-
const { arrowAt, dashed } = connMeta
|
|
5362
|
-
const HEAD =
|
|
5632
|
+
const { arrowAt, dashed } = connMeta(e.connector);
|
|
5633
|
+
const HEAD = EDGE.headInset;
|
|
5363
5634
|
const sx1 = arrowAt === "start" || arrowAt === "both" ? x1 + nx * HEAD : x1;
|
|
5364
5635
|
const sy1 = arrowAt === "start" || arrowAt === "both" ? y1 + ny * HEAD : y1;
|
|
5365
5636
|
const sx2 = arrowAt === "end" || arrowAt === "both" ? x2 - nx * HEAD : x2;
|
|
5366
5637
|
const sy2 = arrowAt === "end" || arrowAt === "both" ? y2 - ny * HEAD : y2;
|
|
5367
|
-
|
|
5638
|
+
const shaft = rc.line(sx1, sy1, sx2, sy2, {
|
|
5368
5639
|
...BASE_ROUGH,
|
|
5369
5640
|
roughness: 0.9,
|
|
5370
5641
|
seed: hashStr$3(e.from + e.to),
|
|
5371
5642
|
stroke: ecol,
|
|
5372
5643
|
strokeWidth: Number(e.style?.strokeWidth ?? 1.6),
|
|
5373
|
-
...(dashed ? { strokeLineDash:
|
|
5374
|
-
})
|
|
5375
|
-
|
|
5376
|
-
|
|
5377
|
-
if (arrowAt === "
|
|
5378
|
-
|
|
5644
|
+
...(dashed ? { strokeLineDash: EDGE.dashPattern } : {}),
|
|
5645
|
+
});
|
|
5646
|
+
shaft.setAttribute("data-edge-role", "shaft");
|
|
5647
|
+
eg.appendChild(shaft);
|
|
5648
|
+
if (arrowAt === "end" || arrowAt === "both") {
|
|
5649
|
+
const endHead = arrowHead(rc, x2, y2, Math.atan2(y2 - y1, x2 - x1), ecol, hashStr$3(e.to));
|
|
5650
|
+
endHead.setAttribute("data-edge-role", "head");
|
|
5651
|
+
eg.appendChild(endHead);
|
|
5652
|
+
}
|
|
5653
|
+
if (arrowAt === "start" || arrowAt === "both") {
|
|
5654
|
+
const startHead = arrowHead(rc, x1, y1, Math.atan2(y1 - y2, x1 - x2), ecol, hashStr$3(e.from + "back"));
|
|
5655
|
+
startHead.setAttribute("data-edge-role", "head");
|
|
5656
|
+
eg.appendChild(startHead);
|
|
5657
|
+
}
|
|
5379
5658
|
if (e.label) {
|
|
5380
|
-
const mx = (x1 + x2) / 2 - ny *
|
|
5381
|
-
const my = (y1 + y2) / 2 + nx *
|
|
5659
|
+
const mx = (x1 + x2) / 2 - ny * EDGE.labelOffset;
|
|
5660
|
+
const my = (y1 + y2) / 2 + nx * EDGE.labelOffset;
|
|
5382
5661
|
const tw = Math.max(e.label.length * 7 + 12, 36);
|
|
5383
5662
|
const bg = se("rect");
|
|
5384
5663
|
bg.setAttribute("x", String(mx - tw / 2));
|
|
@@ -5388,16 +5667,19 @@ var AIDiagram = (function (exports) {
|
|
|
5388
5667
|
bg.setAttribute("fill", palette.edgeLabelBg);
|
|
5389
5668
|
bg.setAttribute("rx", "3");
|
|
5390
5669
|
bg.setAttribute("opacity", "0.9");
|
|
5670
|
+
bg.setAttribute("data-edge-role", "label-bg");
|
|
5391
5671
|
eg.appendChild(bg);
|
|
5392
5672
|
// ── Edge label typography ───────────────────────
|
|
5393
5673
|
// supports: font, font-size, letter-spacing
|
|
5394
5674
|
// always center-anchored (single line floating on edge)
|
|
5395
|
-
const eFontSize = Number(e.style?.fontSize ??
|
|
5396
|
-
const eFont = resolveStyleFont
|
|
5675
|
+
const eFontSize = Number(e.style?.fontSize ?? EDGE.labelFontSize);
|
|
5676
|
+
const eFont = resolveStyleFont(e.style ?? {}, diagramFont);
|
|
5397
5677
|
const eLetterSpacing = e.style?.letterSpacing;
|
|
5398
|
-
const eFontWeight = e.style?.fontWeight ??
|
|
5678
|
+
const eFontWeight = e.style?.fontWeight ?? EDGE.labelFontWeight;
|
|
5399
5679
|
const eLabelColor = String(e.style?.color ?? palette.edgeLabelText);
|
|
5400
|
-
|
|
5680
|
+
const label = mkText(e.label, mx, my, eFontSize, eFontWeight, eLabelColor, "middle", eFont, eLetterSpacing);
|
|
5681
|
+
label.setAttribute("data-edge-role", "label");
|
|
5682
|
+
eg.appendChild(label);
|
|
5401
5683
|
}
|
|
5402
5684
|
EL.appendChild(eg);
|
|
5403
5685
|
}
|
|
@@ -5405,54 +5687,70 @@ var AIDiagram = (function (exports) {
|
|
|
5405
5687
|
// ── Nodes ─────────────────────────────────────────────────
|
|
5406
5688
|
const NL = mkGroup("node-layer");
|
|
5407
5689
|
for (const n of sg.nodes) {
|
|
5408
|
-
const
|
|
5690
|
+
const shapeDef = getShape(n.shape);
|
|
5691
|
+
const idPrefix = shapeDef?.idPrefix ?? "node";
|
|
5692
|
+
const cssClass = shapeDef?.cssClass ?? "ng";
|
|
5693
|
+
const ng = mkGroup(`${idPrefix}-${n.id}`, cssClass);
|
|
5694
|
+
ng.dataset.nodeShape = n.shape;
|
|
5695
|
+
ng.dataset.x = String(n.x);
|
|
5696
|
+
ng.dataset.y = String(n.y);
|
|
5697
|
+
ng.dataset.w = String(n.w);
|
|
5698
|
+
ng.dataset.h = String(n.h);
|
|
5699
|
+
if (n.pathData)
|
|
5700
|
+
ng.dataset.pathData = n.pathData;
|
|
5409
5701
|
if (n.style?.opacity != null)
|
|
5410
5702
|
ng.setAttribute("opacity", String(n.style.opacity));
|
|
5703
|
+
// ── Static transform (deg, dx, dy, factor) ──────────
|
|
5704
|
+
// Uses CSS style.transform so that transform-box:fill-box +
|
|
5705
|
+
// transform-origin:center on .ng gives correct center-anchored transforms.
|
|
5706
|
+
// The base transform is stored in data-base-transform so the animation
|
|
5707
|
+
// controller can restore it after _clearAll() instead of wiping to "".
|
|
5708
|
+
const hasTx = n.dx || n.dy || n.deg || (n.factor && n.factor !== 1);
|
|
5709
|
+
if (hasTx) {
|
|
5710
|
+
const parts = [];
|
|
5711
|
+
if (n.dx || n.dy)
|
|
5712
|
+
parts.push(`translate(${n.dx ?? 0}px,${n.dy ?? 0}px)`);
|
|
5713
|
+
if (n.deg)
|
|
5714
|
+
parts.push(`rotate(${n.deg}deg)`);
|
|
5715
|
+
if (n.factor && n.factor !== 1)
|
|
5716
|
+
parts.push(`scale(${n.factor})`);
|
|
5717
|
+
const tx = parts.join(" ");
|
|
5718
|
+
ng.style.transform = tx;
|
|
5719
|
+
ng.dataset.baseTransform = tx;
|
|
5720
|
+
}
|
|
5411
5721
|
renderShape$1(rc, n, palette).forEach((s) => ng.appendChild(s));
|
|
5412
5722
|
// ── Node / text typography ─────────────────────────
|
|
5413
|
-
|
|
5414
|
-
const
|
|
5415
|
-
const
|
|
5416
|
-
const
|
|
5417
|
-
|
|
5418
|
-
|
|
5419
|
-
|
|
5420
|
-
|
|
5421
|
-
|
|
5422
|
-
|
|
5423
|
-
|
|
5424
|
-
};
|
|
5425
|
-
|
|
5426
|
-
|
|
5427
|
-
const
|
|
5428
|
-
|
|
5429
|
-
|
|
5430
|
-
|
|
5431
|
-
|
|
5432
|
-
? n.x + pad
|
|
5433
|
-
: textAlign === "right"
|
|
5434
|
-
? n.x + n.w - pad
|
|
5435
|
-
: n.x + n.w / 2;
|
|
5723
|
+
const isText = n.shape === "text";
|
|
5724
|
+
const isNote = n.shape === "note";
|
|
5725
|
+
const isMediaShape = n.shape === "icon" || n.shape === "image" || n.shape === "line";
|
|
5726
|
+
const typo = resolveTypography(n.style, {
|
|
5727
|
+
fontSize: isText ? 13 : isNote ? 12 : 14,
|
|
5728
|
+
fontWeight: isText || isNote ? 400 : 500,
|
|
5729
|
+
textColor: isText ? palette.edgeLabelText : isNote ? palette.noteText : palette.nodeText,
|
|
5730
|
+
textAlign: isNote ? "left" : undefined,
|
|
5731
|
+
lineHeight: isNote ? 1.4 : undefined,
|
|
5732
|
+
padding: isNote ? 12 : undefined,
|
|
5733
|
+
verticalAlign: isNote ? "top" : undefined,
|
|
5734
|
+
}, diagramFont, palette.nodeText);
|
|
5735
|
+
// Note textX accounts for fold corner
|
|
5736
|
+
const FOLD = NOTE.fold;
|
|
5737
|
+
const textX = isNote
|
|
5738
|
+
? (typo.textAlign === "right" ? n.x + n.w - FOLD - typo.padding
|
|
5739
|
+
: typo.textAlign === "center" ? n.x + (n.w - FOLD) / 2
|
|
5740
|
+
: n.x + typo.padding)
|
|
5741
|
+
: computeTextX(typo, n.x, n.w);
|
|
5436
5742
|
const lines = n.shape === 'text' && !n.label.includes('\n')
|
|
5437
|
-
? wrapText
|
|
5743
|
+
? wrapText(n.label, n.w - typo.padding * 2, typo.fontSize)
|
|
5438
5744
|
: n.label.split('\n');
|
|
5439
|
-
const verticalAlign = String(n.style?.verticalAlign ?? "middle");
|
|
5440
|
-
const nodeBodyTop = n.y + pad;
|
|
5441
|
-
const nodeBodyBottom = n.y + n.h - pad;
|
|
5442
|
-
const nodeBodyMid = n.y + n.h / 2;
|
|
5443
|
-
const blockH = (lines.length - 1) * lineHeight;
|
|
5444
|
-
const isMediaShape = n.shape === "icon" || n.shape === "image";
|
|
5445
5745
|
const textCY = isMediaShape
|
|
5446
|
-
? n.y + n.h - 10
|
|
5447
|
-
:
|
|
5448
|
-
?
|
|
5449
|
-
:
|
|
5450
|
-
? nodeBodyBottom - blockH / 2
|
|
5451
|
-
: nodeBodyMid;
|
|
5746
|
+
? n.y + n.h - 10
|
|
5747
|
+
: isNote
|
|
5748
|
+
? computeTextCY(typo, n.y, n.h, lines.length, FOLD + typo.padding)
|
|
5749
|
+
: computeTextCY(typo, n.y, n.h, lines.length);
|
|
5452
5750
|
if (n.label) {
|
|
5453
5751
|
ng.appendChild(lines.length > 1
|
|
5454
|
-
? mkMultilineText(lines, textX, textCY, fontSize, fontWeight, textColor, textAnchor, lineHeight,
|
|
5455
|
-
: mkText(n.label, textX, textCY, fontSize, fontWeight, textColor, textAnchor,
|
|
5752
|
+
? mkMultilineText(lines, textX, textCY, typo.fontSize, typo.fontWeight, typo.textColor, typo.textAnchor, typo.lineHeight, typo.font, typo.letterSpacing)
|
|
5753
|
+
: mkText(n.label, textX, textCY, typo.fontSize, typo.fontWeight, typo.textColor, typo.textAnchor, typo.font, typo.letterSpacing));
|
|
5456
5754
|
}
|
|
5457
5755
|
if (options.interactive) {
|
|
5458
5756
|
ng.style.cursor = "pointer";
|
|
@@ -5475,7 +5773,7 @@ var AIDiagram = (function (exports) {
|
|
|
5475
5773
|
const fill = String(gs.fill ?? palette.tableFill);
|
|
5476
5774
|
const strk = String(gs.stroke ?? palette.tableStroke);
|
|
5477
5775
|
const textCol = String(gs.color ?? palette.tableText);
|
|
5478
|
-
const hdrFill = gs.fill ? darkenHex
|
|
5776
|
+
const hdrFill = gs.fill ? darkenHex(fill, 0.08) : palette.tableHeaderFill;
|
|
5479
5777
|
const hdrText = String(gs.color ?? palette.tableHeaderText);
|
|
5480
5778
|
const divCol = palette.tableDivider;
|
|
5481
5779
|
const pad = t.labelH;
|
|
@@ -5484,7 +5782,7 @@ var AIDiagram = (function (exports) {
|
|
|
5484
5782
|
// ── Table-level font (applies to label + all cells) ─
|
|
5485
5783
|
// supports: font, font-size, letter-spacing
|
|
5486
5784
|
const tFontSize = Number(gs.fontSize ?? 12);
|
|
5487
|
-
const tFont = resolveStyleFont
|
|
5785
|
+
const tFont = resolveStyleFont(gs, diagramFont);
|
|
5488
5786
|
const tLetterSpacing = gs.letterSpacing;
|
|
5489
5787
|
if (gs.opacity != null)
|
|
5490
5788
|
tg.setAttribute("opacity", String(gs.opacity));
|
|
@@ -5557,99 +5855,22 @@ var AIDiagram = (function (exports) {
|
|
|
5557
5855
|
}));
|
|
5558
5856
|
}
|
|
5559
5857
|
cx += cw;
|
|
5560
|
-
});
|
|
5561
|
-
rowY += rh;
|
|
5562
|
-
}
|
|
5563
|
-
if (options.interactive) {
|
|
5564
|
-
tg.style.cursor = "pointer";
|
|
5565
|
-
tg.addEventListener("click", () => options.onNodeClick?.(t.id));
|
|
5566
|
-
}
|
|
5567
|
-
TL.appendChild(tg);
|
|
5568
|
-
}
|
|
5569
|
-
svg.appendChild(TL);
|
|
5570
|
-
// ── Notes ─────────────────────────────────────────────────
|
|
5571
|
-
const NoteL = mkGroup("note-layer");
|
|
5572
|
-
for (const n of sg.notes) {
|
|
5573
|
-
const ng = mkGroup(`note-${n.id}`, "ntg");
|
|
5574
|
-
const gs = n.style ?? {};
|
|
5575
|
-
const fill = String(gs.fill ?? palette.noteFill);
|
|
5576
|
-
const strk = String(gs.stroke ?? palette.noteStroke);
|
|
5577
|
-
const nStrokeWidth = Number(gs.strokeWidth ?? 1.2);
|
|
5578
|
-
const fold = 14;
|
|
5579
|
-
const { x, y, w, h } = n;
|
|
5580
|
-
if (gs.opacity != null)
|
|
5581
|
-
ng.setAttribute("opacity", String(gs.opacity));
|
|
5582
|
-
// ── Note typography ─────────────────────────────────
|
|
5583
|
-
const nFontSize = Number(gs.fontSize ?? 12);
|
|
5584
|
-
const nFontWeight = gs.fontWeight ?? 400;
|
|
5585
|
-
const nFont = resolveStyleFont$1(gs, diagramFont);
|
|
5586
|
-
const nLetterSpacing = gs.letterSpacing;
|
|
5587
|
-
const nLineHeight = Number(gs.lineHeight ?? 1.4) * nFontSize;
|
|
5588
|
-
const nTextAlign = String(gs.textAlign ?? "left");
|
|
5589
|
-
const nPad = Number(gs.padding ?? 12);
|
|
5590
|
-
const nAnchorMap = {
|
|
5591
|
-
left: "start",
|
|
5592
|
-
center: "middle",
|
|
5593
|
-
right: "end",
|
|
5594
|
-
};
|
|
5595
|
-
const nAnchor = nAnchorMap[nTextAlign] ?? "start";
|
|
5596
|
-
const nTextX = nTextAlign === "right"
|
|
5597
|
-
? x + w - fold - nPad
|
|
5598
|
-
: nTextAlign === "center"
|
|
5599
|
-
? x + (w - fold) / 2
|
|
5600
|
-
: x + nPad;
|
|
5601
|
-
const nFoldPad = fold + nPad; // text starts below fold + user padding
|
|
5602
|
-
ng.appendChild(rc.polygon([
|
|
5603
|
-
[x, y],
|
|
5604
|
-
[x + w - fold, y],
|
|
5605
|
-
[x + w, y + fold],
|
|
5606
|
-
[x + w, y + h],
|
|
5607
|
-
[x, y + h],
|
|
5608
|
-
], {
|
|
5609
|
-
...BASE_ROUGH,
|
|
5610
|
-
seed: hashStr$3(n.id),
|
|
5611
|
-
fill,
|
|
5612
|
-
fillStyle: "solid",
|
|
5613
|
-
stroke: strk,
|
|
5614
|
-
strokeWidth: nStrokeWidth,
|
|
5615
|
-
...(gs.strokeDash ? { strokeLineDash: gs.strokeDash } : {}),
|
|
5616
|
-
}));
|
|
5617
|
-
ng.appendChild(rc.polygon([
|
|
5618
|
-
[x + w - fold, y],
|
|
5619
|
-
[x + w, y + fold],
|
|
5620
|
-
[x + w - fold, y + fold],
|
|
5621
|
-
], {
|
|
5622
|
-
roughness: 0.4,
|
|
5623
|
-
seed: hashStr$3(n.id + "f"),
|
|
5624
|
-
fill: palette.noteFold,
|
|
5625
|
-
fillStyle: "solid",
|
|
5626
|
-
stroke: strk,
|
|
5627
|
-
strokeWidth: Math.min(nStrokeWidth, 0.8),
|
|
5628
|
-
}));
|
|
5629
|
-
const nVerticalAlign = String(gs.verticalAlign ?? "top");
|
|
5630
|
-
const bodyTop = y + nFoldPad;
|
|
5631
|
-
const bodyBottom = y + h - nPad;
|
|
5632
|
-
const bodyMid = (bodyTop + bodyBottom) / 2;
|
|
5633
|
-
const blockH = (n.lines.length - 1) * nLineHeight;
|
|
5634
|
-
const blockCY = nVerticalAlign === "bottom"
|
|
5635
|
-
? bodyBottom - blockH / 2
|
|
5636
|
-
: nVerticalAlign === "middle"
|
|
5637
|
-
? bodyMid
|
|
5638
|
-
: bodyTop + blockH / 2;
|
|
5639
|
-
if (n.lines.length > 1) {
|
|
5640
|
-
ng.appendChild(mkMultilineText(n.lines, nTextX, blockCY, nFontSize, nFontWeight, String(gs.color ?? palette.noteText), nAnchor, nLineHeight, nFont, nLetterSpacing));
|
|
5858
|
+
});
|
|
5859
|
+
rowY += rh;
|
|
5641
5860
|
}
|
|
5642
|
-
|
|
5643
|
-
|
|
5861
|
+
if (options.interactive) {
|
|
5862
|
+
tg.style.cursor = "pointer";
|
|
5863
|
+
tg.addEventListener("click", () => options.onNodeClick?.(t.id));
|
|
5644
5864
|
}
|
|
5645
|
-
|
|
5865
|
+
TL.appendChild(tg);
|
|
5646
5866
|
}
|
|
5647
|
-
svg.appendChild(
|
|
5867
|
+
svg.appendChild(TL);
|
|
5868
|
+
// ── Notes are now rendered as nodes via the shape registry ──
|
|
5648
5869
|
const MDL = mkGroup('markdown-layer');
|
|
5649
5870
|
for (const m of sg.markdowns) {
|
|
5650
5871
|
const mg = mkGroup(`markdown-${m.id}`, 'mdg');
|
|
5651
5872
|
const gs = m.style ?? {};
|
|
5652
|
-
const mFont = resolveStyleFont
|
|
5873
|
+
const mFont = resolveStyleFont(gs, diagramFont);
|
|
5653
5874
|
const baseColor = String(gs.color ?? palette.nodeText);
|
|
5654
5875
|
const textAlign = String(gs.textAlign ?? 'left');
|
|
5655
5876
|
const anchor = textAlign === 'right' ? 'end'
|
|
@@ -5737,7 +5958,7 @@ var AIDiagram = (function (exports) {
|
|
|
5737
5958
|
// with:
|
|
5738
5959
|
// for (const c of sg.charts) drawRoughChartCanvas(rc, ctx, c, pal, R);
|
|
5739
5960
|
// ============================================================
|
|
5740
|
-
function hashStr$
|
|
5961
|
+
function hashStr$1(s) {
|
|
5741
5962
|
let h = 5381;
|
|
5742
5963
|
for (let i = 0; i < s.length; i++)
|
|
5743
5964
|
h = ((h * 33) ^ s.charCodeAt(i)) & 0xffff;
|
|
@@ -5786,15 +6007,15 @@ var AIDiagram = (function (exports) {
|
|
|
5786
6007
|
const toY = makeValueToY(allY, py, ph);
|
|
5787
6008
|
const baseline = toY(0);
|
|
5788
6009
|
// Y axis
|
|
5789
|
-
rc.line(px, py, px, py + ph, { ...R, roughness: 0.4, seed: hashStr$
|
|
6010
|
+
rc.line(px, py, px, py + ph, { ...R, roughness: 0.4, seed: hashStr$1(c.id + 'ya'), stroke: labelCol, strokeWidth: 1 });
|
|
5790
6011
|
// X axis (baseline)
|
|
5791
|
-
rc.line(px, baseline, px + pw, baseline, { ...R, roughness: 0.4, seed: hashStr$
|
|
6012
|
+
rc.line(px, baseline, px + pw, baseline, { ...R, roughness: 0.4, seed: hashStr$1(c.id + 'xa'), stroke: labelCol, strokeWidth: 1 });
|
|
5792
6013
|
// Y ticks + labels
|
|
5793
6014
|
for (const tick of yTicks(allY)) {
|
|
5794
6015
|
const ty = toY(tick);
|
|
5795
6016
|
if (ty < py - 2 || ty > py + ph + 2)
|
|
5796
6017
|
continue;
|
|
5797
|
-
rc.line(px - 3, ty, px, ty, { roughness: 0.2, seed: hashStr$
|
|
6018
|
+
rc.line(px - 3, ty, px, ty, { roughness: 0.2, seed: hashStr$1(c.id + 'yt' + tick), stroke: labelCol, strokeWidth: 0.7 });
|
|
5798
6019
|
ctx.save();
|
|
5799
6020
|
ctx.font = `400 9px ${font}`;
|
|
5800
6021
|
ctx.fillStyle = labelCol;
|
|
@@ -5832,7 +6053,7 @@ var AIDiagram = (function (exports) {
|
|
|
5832
6053
|
ctx.globalAlpha = Number(s.opacity);
|
|
5833
6054
|
// Background
|
|
5834
6055
|
rc.rectangle(c.x, c.y, c.w, c.h, {
|
|
5835
|
-
...R, seed: hashStr$
|
|
6056
|
+
...R, seed: hashStr$1(c.id),
|
|
5836
6057
|
fill: bgFill,
|
|
5837
6058
|
fillStyle: 'solid',
|
|
5838
6059
|
stroke: bgStroke,
|
|
@@ -5860,7 +6081,7 @@ var AIDiagram = (function (exports) {
|
|
|
5860
6081
|
let angle = -Math.PI / 2;
|
|
5861
6082
|
segments.forEach((seg, i) => {
|
|
5862
6083
|
const sweep = (seg.value / total) * Math.PI * 2;
|
|
5863
|
-
drawPieArc(rc, ctx, cx, cy, r, ir, angle, angle + sweep, seg.color, hashStr$
|
|
6084
|
+
drawPieArc(rc, ctx, cx, cy, r, ir, angle, angle + sweep, seg.color, hashStr$1(c.id + seg.label + i));
|
|
5864
6085
|
angle += sweep;
|
|
5865
6086
|
});
|
|
5866
6087
|
drawLegend(ctx, segments.map(s => `${s.label} ${Math.round(s.value / total * 100)}%`), segments.map(s => s.color), legendX, legendY, lc, cFont);
|
|
@@ -5873,11 +6094,11 @@ var AIDiagram = (function (exports) {
|
|
|
5873
6094
|
const xs = pts.map(p => p.x), ys = pts.map(p => p.y);
|
|
5874
6095
|
const toX = makeValueToX(xs, px, pw);
|
|
5875
6096
|
const toY = makeValueToY(ys, py, ph);
|
|
5876
|
-
rc.line(px, py, px, py + ph, { ...R, roughness: 0.4, seed: hashStr$
|
|
5877
|
-
rc.line(px, py + ph, px + pw, py + ph, { ...R, roughness: 0.4, seed: hashStr$
|
|
6097
|
+
rc.line(px, py, px, py + ph, { ...R, roughness: 0.4, seed: hashStr$1(c.id + 'ya'), stroke: lc, strokeWidth: 1 });
|
|
6098
|
+
rc.line(px, py + ph, px + pw, py + ph, { ...R, roughness: 0.4, seed: hashStr$1(c.id + 'xa'), stroke: lc, strokeWidth: 1 });
|
|
5878
6099
|
pts.forEach((pt, i) => {
|
|
5879
6100
|
rc.ellipse(toX(pt.x), toY(pt.y), 10, 10, {
|
|
5880
|
-
roughness: 0.8, seed: hashStr$
|
|
6101
|
+
roughness: 0.8, seed: hashStr$1(c.id + pt.label),
|
|
5881
6102
|
fill: CHART_COLORS[i % CHART_COLORS.length] + '99',
|
|
5882
6103
|
fillStyle: 'solid',
|
|
5883
6104
|
stroke: CHART_COLORS[i % CHART_COLORS.length],
|
|
@@ -5917,7 +6138,7 @@ var AIDiagram = (function (exports) {
|
|
|
5917
6138
|
const bh = Math.abs(baseline - toY(val)) || 2;
|
|
5918
6139
|
rc.rectangle(bx, by, barW, bh, {
|
|
5919
6140
|
roughness: 1.1, bowing: 0.5,
|
|
5920
|
-
seed: hashStr$
|
|
6141
|
+
seed: hashStr$1(c.id + si + i),
|
|
5921
6142
|
fill: ser.color + 'bb',
|
|
5922
6143
|
fillStyle: 'hachure',
|
|
5923
6144
|
hachureAngle: -41,
|
|
@@ -5945,7 +6166,7 @@ var AIDiagram = (function (exports) {
|
|
|
5945
6166
|
[pts[pts.length - 1][0], baseline],
|
|
5946
6167
|
];
|
|
5947
6168
|
rc.polygon(poly, {
|
|
5948
|
-
roughness: 0.5, seed: hashStr$
|
|
6169
|
+
roughness: 0.5, seed: hashStr$1(c.id + 'af' + si),
|
|
5949
6170
|
fill: ser.color + '44',
|
|
5950
6171
|
fillStyle: 'solid',
|
|
5951
6172
|
stroke: 'none',
|
|
@@ -5955,7 +6176,7 @@ var AIDiagram = (function (exports) {
|
|
|
5955
6176
|
for (let i = 0; i < pts.length - 1; i++) {
|
|
5956
6177
|
rc.line(pts[i][0], pts[i][1], pts[i + 1][0], pts[i + 1][1], {
|
|
5957
6178
|
roughness: 0.9, bowing: 0.6,
|
|
5958
|
-
seed: hashStr$
|
|
6179
|
+
seed: hashStr$1(c.id + si + i),
|
|
5959
6180
|
stroke: ser.color,
|
|
5960
6181
|
strokeWidth: 1.8,
|
|
5961
6182
|
});
|
|
@@ -5963,7 +6184,7 @@ var AIDiagram = (function (exports) {
|
|
|
5963
6184
|
// Dots
|
|
5964
6185
|
pts.forEach(([px2, py2], i) => {
|
|
5965
6186
|
rc.ellipse(px2, py2, 7, 7, {
|
|
5966
|
-
roughness: 0.3, seed: hashStr$
|
|
6187
|
+
roughness: 0.3, seed: hashStr$1(c.id + 'dot' + si + i),
|
|
5967
6188
|
fill: ser.color,
|
|
5968
6189
|
fillStyle: 'solid',
|
|
5969
6190
|
stroke: ser.color,
|
|
@@ -5983,28 +6204,6 @@ var AIDiagram = (function (exports) {
|
|
|
5983
6204
|
// sketchmark — Canvas Renderer
|
|
5984
6205
|
// Uses rough.js canvas API for hand-drawn rendering
|
|
5985
6206
|
// ============================================================
|
|
5986
|
-
function hashStr$1(s) {
|
|
5987
|
-
let h = 5381;
|
|
5988
|
-
for (let i = 0; i < s.length; i++)
|
|
5989
|
-
h = ((h * 33) ^ s.charCodeAt(i)) & 0xffff;
|
|
5990
|
-
return h;
|
|
5991
|
-
}
|
|
5992
|
-
/** Darken a CSS hex colour by `amount` (0–1). Falls back to input for non-hex. */
|
|
5993
|
-
function darkenHex(hex, amount = 0.12) {
|
|
5994
|
-
const m = /^#?([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i.exec(hex);
|
|
5995
|
-
if (!m)
|
|
5996
|
-
return hex;
|
|
5997
|
-
const d = (v) => Math.max(0, Math.round(parseInt(v, 16) * (1 - amount)));
|
|
5998
|
-
return `#${d(m[1]).toString(16).padStart(2, "0")}${d(m[2]).toString(16).padStart(2, "0")}${d(m[3]).toString(16).padStart(2, "0")}`;
|
|
5999
|
-
}
|
|
6000
|
-
// ── Small helper: load + resolve font from a style map ────────────────────
|
|
6001
|
-
function resolveStyleFont(style, fallback) {
|
|
6002
|
-
const raw = String(style['font'] ?? '');
|
|
6003
|
-
if (!raw)
|
|
6004
|
-
return fallback;
|
|
6005
|
-
loadFont(raw);
|
|
6006
|
-
return resolveFont(raw);
|
|
6007
|
-
}
|
|
6008
6207
|
// ── Canvas text helpers ────────────────────────────────────────────────────
|
|
6009
6208
|
function drawText(ctx, txt, x, y, sz = 14, wt = 500, col = '#1a1208', align = 'center', font = 'system-ui, sans-serif', letterSpacing) {
|
|
6010
6209
|
ctx.save();
|
|
@@ -6037,211 +6236,28 @@ var AIDiagram = (function (exports) {
|
|
|
6037
6236
|
drawText(ctx, line, x, startY + i * lineH, sz, wt, col, align, font, letterSpacing);
|
|
6038
6237
|
});
|
|
6039
6238
|
}
|
|
6040
|
-
// Soft word-wrap for `text` shape nodes
|
|
6041
|
-
function wrapText(text, maxWidth, fontSize) {
|
|
6042
|
-
const charWidth = fontSize * 0.55;
|
|
6043
|
-
const maxChars = Math.floor(maxWidth / charWidth);
|
|
6044
|
-
const words = text.split(' ');
|
|
6045
|
-
const lines = [];
|
|
6046
|
-
let current = '';
|
|
6047
|
-
for (const word of words) {
|
|
6048
|
-
const test = current ? `${current} ${word}` : word;
|
|
6049
|
-
if (test.length > maxChars && current) {
|
|
6050
|
-
lines.push(current);
|
|
6051
|
-
current = word;
|
|
6052
|
-
}
|
|
6053
|
-
else {
|
|
6054
|
-
current = test;
|
|
6055
|
-
}
|
|
6056
|
-
}
|
|
6057
|
-
if (current)
|
|
6058
|
-
lines.push(current);
|
|
6059
|
-
return lines.length ? lines : [text];
|
|
6060
|
-
}
|
|
6061
|
-
// ── Arrow direction ────────────────────────────────────────────────────────
|
|
6062
|
-
function connMeta(connector) {
|
|
6063
|
-
if (connector === '--')
|
|
6064
|
-
return { arrowAt: 'none', dashed: false };
|
|
6065
|
-
if (connector === '---')
|
|
6066
|
-
return { arrowAt: 'none', dashed: true };
|
|
6067
|
-
const bidir = connector.includes('<') && connector.includes('>');
|
|
6068
|
-
if (bidir)
|
|
6069
|
-
return { arrowAt: 'both', dashed: connector.includes('--') };
|
|
6070
|
-
const back = connector.startsWith('<');
|
|
6071
|
-
const dashed = connector.includes('--');
|
|
6072
|
-
if (back)
|
|
6073
|
-
return { arrowAt: 'start', dashed };
|
|
6074
|
-
return { arrowAt: 'end', dashed };
|
|
6075
|
-
}
|
|
6076
|
-
// ── Rect connection point ──────────────────────────────────────────────────
|
|
6077
|
-
function rectConnPoint(rx, ry, rw, rh, ox, oy) {
|
|
6078
|
-
const cx = rx + rw / 2, cy = ry + rh / 2;
|
|
6079
|
-
const dx = ox - cx, dy = oy - cy;
|
|
6080
|
-
if (Math.abs(dx) < 0.01 && Math.abs(dy) < 0.01)
|
|
6081
|
-
return [cx, cy];
|
|
6082
|
-
const hw = rw / 2 - 2, hh = rh / 2 - 2;
|
|
6083
|
-
const tx = Math.abs(dx) > 0.01 ? hw / Math.abs(dx) : 1e9;
|
|
6084
|
-
const ty = Math.abs(dy) > 0.01 ? hh / Math.abs(dy) : 1e9;
|
|
6085
|
-
const t = Math.min(tx, ty);
|
|
6086
|
-
return [cx + t * dx, cy + t * dy];
|
|
6087
|
-
}
|
|
6088
|
-
function resolveEndpoint(id, nm, tm, gm, cm, ntm) {
|
|
6089
|
-
return nm.get(id) ?? tm.get(id) ?? gm.get(id) ?? cm.get(id) ?? ntm.get(id) ?? null;
|
|
6090
|
-
}
|
|
6091
|
-
function getConnPoint(src, dstCX, dstCY) {
|
|
6092
|
-
if ('shape' in src && src.shape) {
|
|
6093
|
-
return connPoint(src, {
|
|
6094
|
-
x: dstCX - 1, y: dstCY - 1, w: 2, h: 2});
|
|
6095
|
-
}
|
|
6096
|
-
return rectConnPoint(src.x, src.y, src.w, src.h, dstCX, dstCY);
|
|
6097
|
-
}
|
|
6098
|
-
// ── Group depth ────────────────────────────────────────────────────────────
|
|
6099
|
-
function groupDepth(g, gm) {
|
|
6100
|
-
let d = 0;
|
|
6101
|
-
let cur = g;
|
|
6102
|
-
while (cur?.parentId) {
|
|
6103
|
-
d++;
|
|
6104
|
-
cur = gm.get(cur.parentId);
|
|
6105
|
-
}
|
|
6106
|
-
return d;
|
|
6107
|
-
}
|
|
6108
6239
|
// ── Node shapes ────────────────────────────────────────────────────────────
|
|
6109
6240
|
function renderShape(rc, ctx, n, palette, R) {
|
|
6110
6241
|
const s = n.style ?? {};
|
|
6111
6242
|
const fill = String(s.fill ?? palette.nodeFill);
|
|
6112
6243
|
const stroke = String(s.stroke ?? palette.nodeStroke);
|
|
6113
6244
|
const opts = {
|
|
6114
|
-
...R, seed: hashStr$
|
|
6245
|
+
...R, seed: hashStr$3(n.id),
|
|
6115
6246
|
fill, fillStyle: 'solid',
|
|
6116
6247
|
stroke, strokeWidth: Number(s.strokeWidth ?? 1.9),
|
|
6117
6248
|
...(s.strokeDash ? { strokeLineDash: s.strokeDash } : {}),
|
|
6118
6249
|
};
|
|
6119
|
-
const
|
|
6120
|
-
|
|
6121
|
-
|
|
6122
|
-
|
|
6123
|
-
rc.ellipse(cx, cy, n.w * 0.88, n.h * 0.88, opts);
|
|
6124
|
-
break;
|
|
6125
|
-
case 'diamond':
|
|
6126
|
-
rc.polygon([[cx, n.y + 2], [cx + hw, cy], [cx, n.y + n.h - 2], [cx - hw, cy]], opts);
|
|
6127
|
-
break;
|
|
6128
|
-
case 'hexagon': {
|
|
6129
|
-
const hw2 = hw * 0.56;
|
|
6130
|
-
rc.polygon([
|
|
6131
|
-
[cx - hw2, n.y + 3], [cx + hw2, n.y + 3], [cx + hw, cy],
|
|
6132
|
-
[cx + hw2, n.y + n.h - 3], [cx - hw2, n.y + n.h - 3], [cx - hw, cy],
|
|
6133
|
-
], opts);
|
|
6134
|
-
break;
|
|
6135
|
-
}
|
|
6136
|
-
case 'triangle':
|
|
6137
|
-
rc.polygon([[cx, n.y + 3], [n.x + n.w - 3, n.y + n.h - 3], [n.x + 3, n.y + n.h - 3]], opts);
|
|
6138
|
-
break;
|
|
6139
|
-
case 'cylinder': {
|
|
6140
|
-
const eH = 18;
|
|
6141
|
-
rc.rectangle(n.x + 3, n.y + eH / 2, n.w - 6, n.h - eH, opts);
|
|
6142
|
-
rc.ellipse(cx, n.y + eH / 2, n.w - 8, eH, { ...opts, roughness: 0.6 });
|
|
6143
|
-
rc.ellipse(cx, n.y + n.h - eH / 2, n.w - 8, eH, { ...opts, roughness: 0.6, fill: 'none' });
|
|
6144
|
-
break;
|
|
6145
|
-
}
|
|
6146
|
-
case 'parallelogram':
|
|
6147
|
-
rc.polygon([
|
|
6148
|
-
[n.x + 18, n.y + 1], [n.x + n.w - 1, n.y + 1],
|
|
6149
|
-
[n.x + n.w - 18, n.y + n.h - 1], [n.x + 1, n.y + n.h - 1],
|
|
6150
|
-
], opts);
|
|
6151
|
-
break;
|
|
6152
|
-
case 'text':
|
|
6153
|
-
break; // no shape drawn
|
|
6154
|
-
case 'icon': {
|
|
6155
|
-
if (n.iconName) {
|
|
6156
|
-
const [prefix, name] = n.iconName.includes(':')
|
|
6157
|
-
? n.iconName.split(':', 2)
|
|
6158
|
-
: ['mdi', n.iconName];
|
|
6159
|
-
const iconColor = s.color
|
|
6160
|
-
? encodeURIComponent(String(s.color))
|
|
6161
|
-
: encodeURIComponent(String(palette.nodeStroke));
|
|
6162
|
-
// reserve bottom for label
|
|
6163
|
-
const iconLabelSpace = n.label ? 20 : 0;
|
|
6164
|
-
const iconAreaH = n.h - iconLabelSpace;
|
|
6165
|
-
const iconSize = Math.min(n.w, iconAreaH) - 4;
|
|
6166
|
-
const iconUrl = `https://api.iconify.design/${prefix}/${name}.svg?color=${iconColor}&width=${iconSize}&height=${iconSize}`;
|
|
6167
|
-
const img = new Image();
|
|
6168
|
-
img.crossOrigin = 'anonymous';
|
|
6169
|
-
img.onload = () => {
|
|
6170
|
-
ctx.save();
|
|
6171
|
-
if (s.opacity != null)
|
|
6172
|
-
ctx.globalAlpha = Number(s.opacity);
|
|
6173
|
-
const iconX = n.x + (n.w - iconSize) / 2;
|
|
6174
|
-
const iconY = n.y + (iconAreaH - iconSize) / 2;
|
|
6175
|
-
ctx.beginPath();
|
|
6176
|
-
const r = 6;
|
|
6177
|
-
ctx.moveTo(iconX + r, iconY);
|
|
6178
|
-
ctx.lineTo(iconX + iconSize - r, iconY);
|
|
6179
|
-
ctx.quadraticCurveTo(iconX + iconSize, iconY, iconX + iconSize, iconY + r);
|
|
6180
|
-
ctx.lineTo(iconX + iconSize, iconY + iconSize - r);
|
|
6181
|
-
ctx.quadraticCurveTo(iconX + iconSize, iconY + iconSize, iconX + iconSize - r, iconY + iconSize);
|
|
6182
|
-
ctx.lineTo(iconX + r, iconY + iconSize);
|
|
6183
|
-
ctx.quadraticCurveTo(iconX, iconY + iconSize, iconX, iconY + iconSize - r);
|
|
6184
|
-
ctx.lineTo(iconX, iconY + r);
|
|
6185
|
-
ctx.quadraticCurveTo(iconX, iconY, iconX + r, iconY);
|
|
6186
|
-
ctx.closePath();
|
|
6187
|
-
ctx.clip();
|
|
6188
|
-
ctx.drawImage(img, iconX, iconY, iconSize, iconSize);
|
|
6189
|
-
ctx.restore();
|
|
6190
|
-
if (s.stroke) {
|
|
6191
|
-
rc.rectangle(n.x + 1, n.y + 1, n.w - 2, n.h - 2, { ...opts, fill: 'none' });
|
|
6192
|
-
}
|
|
6193
|
-
};
|
|
6194
|
-
img.src = iconUrl;
|
|
6195
|
-
}
|
|
6196
|
-
else {
|
|
6197
|
-
rc.rectangle(n.x + 1, n.y + 1, n.w - 2, n.h - 2, { ...opts, fill: '#e0e0e0', stroke: '#999999' });
|
|
6198
|
-
}
|
|
6199
|
-
return;
|
|
6200
|
-
}
|
|
6201
|
-
case 'image': {
|
|
6202
|
-
if (n.imageUrl) {
|
|
6203
|
-
// reserve bottom for label
|
|
6204
|
-
const imgLblSpace = n.label ? 20 : 0;
|
|
6205
|
-
const imgAreaH = n.h - imgLblSpace;
|
|
6206
|
-
const img = new Image();
|
|
6207
|
-
img.crossOrigin = 'anonymous';
|
|
6208
|
-
img.onload = () => {
|
|
6209
|
-
ctx.save();
|
|
6210
|
-
ctx.beginPath();
|
|
6211
|
-
const r = 6;
|
|
6212
|
-
ctx.moveTo(n.x + r, n.y);
|
|
6213
|
-
ctx.lineTo(n.x + n.w - r, n.y);
|
|
6214
|
-
ctx.quadraticCurveTo(n.x + n.w, n.y, n.x + n.w, n.y + r);
|
|
6215
|
-
ctx.lineTo(n.x + n.w, n.y + imgAreaH - r);
|
|
6216
|
-
ctx.quadraticCurveTo(n.x + n.w, n.y + imgAreaH, n.x + n.w - r, n.y + imgAreaH);
|
|
6217
|
-
ctx.lineTo(n.x + r, n.y + imgAreaH);
|
|
6218
|
-
ctx.quadraticCurveTo(n.x, n.y + imgAreaH, n.x, n.y + imgAreaH - r);
|
|
6219
|
-
ctx.lineTo(n.x, n.y + r);
|
|
6220
|
-
ctx.quadraticCurveTo(n.x, n.y, n.x + r, n.y);
|
|
6221
|
-
ctx.closePath();
|
|
6222
|
-
ctx.clip();
|
|
6223
|
-
ctx.drawImage(img, n.x + 1, n.y + 1, n.w - 2, imgAreaH - 2);
|
|
6224
|
-
ctx.restore();
|
|
6225
|
-
// only draw border when stroke is explicitly set
|
|
6226
|
-
if (s.stroke) {
|
|
6227
|
-
rc.rectangle(n.x + 1, n.y + 1, n.w - 2, n.h - 2, { ...opts, fill: 'none' });
|
|
6228
|
-
}
|
|
6229
|
-
};
|
|
6230
|
-
img.src = n.imageUrl;
|
|
6231
|
-
}
|
|
6232
|
-
else {
|
|
6233
|
-
rc.rectangle(n.x + 1, n.y + 1, n.w - 2, n.h - 2, { ...opts, fill: '#e0e0e0', stroke: '#999999' });
|
|
6234
|
-
}
|
|
6235
|
-
return;
|
|
6236
|
-
}
|
|
6237
|
-
default:
|
|
6238
|
-
rc.rectangle(n.x + 1, n.y + 1, n.w - 2, n.h - 2, opts);
|
|
6239
|
-
break;
|
|
6250
|
+
const shape = getShape(n.shape);
|
|
6251
|
+
if (shape) {
|
|
6252
|
+
shape.renderCanvas(rc, ctx, n, palette, opts);
|
|
6253
|
+
return;
|
|
6240
6254
|
}
|
|
6255
|
+
// fallback: box
|
|
6256
|
+
rc.rectangle(n.x + 1, n.y + 1, n.w - 2, n.h - 2, opts);
|
|
6241
6257
|
}
|
|
6242
6258
|
// ── Arrowhead ─────────────────────────────────────────────────────────────
|
|
6243
6259
|
function drawArrowHead(rc, x, y, angle, col, seed, R) {
|
|
6244
|
-
const as =
|
|
6260
|
+
const as = EDGE.arrowSize;
|
|
6245
6261
|
rc.polygon([
|
|
6246
6262
|
[x, y],
|
|
6247
6263
|
[x - as * Math.cos(angle - Math.PI / 6.5), y - as * Math.sin(angle - Math.PI / 6.5)],
|
|
@@ -6284,18 +6300,17 @@ var AIDiagram = (function (exports) {
|
|
|
6284
6300
|
ctx.clearRect(0, 0, sg.width, sg.height);
|
|
6285
6301
|
}
|
|
6286
6302
|
const rc = rough.canvas(canvas);
|
|
6287
|
-
const R = { roughness: options.roughness ??
|
|
6303
|
+
const R = { roughness: options.roughness ?? ROUGH.roughness, bowing: options.bowing ?? ROUGH.bowing };
|
|
6288
6304
|
const nm = nodeMap(sg);
|
|
6289
6305
|
const tm = tableMap(sg);
|
|
6290
6306
|
const gm = groupMap(sg);
|
|
6291
6307
|
const cm = chartMap(sg);
|
|
6292
|
-
const ntm = noteMap(sg);
|
|
6293
6308
|
// ── Title ────────────────────────────────────────────────
|
|
6294
6309
|
if (sg.title) {
|
|
6295
|
-
const titleSize = Number(sg.config['title-size'] ??
|
|
6296
|
-
const titleWeight = Number(sg.config['title-weight'] ??
|
|
6310
|
+
const titleSize = Number(sg.config['title-size'] ?? TITLE.fontSize);
|
|
6311
|
+
const titleWeight = Number(sg.config['title-weight'] ?? TITLE.fontWeight);
|
|
6297
6312
|
const titleColor = String(sg.config['title-color'] ?? palette.titleText);
|
|
6298
|
-
drawText(ctx, sg.title, sg.width / 2,
|
|
6313
|
+
drawText(ctx, sg.title, sg.width / 2, TITLE.y + 2, titleSize, titleWeight, titleColor, 'center', diagramFont);
|
|
6299
6314
|
}
|
|
6300
6315
|
// ── Groups (outermost first) ─────────────────────────────
|
|
6301
6316
|
const sortedGroups = [...sg.groups].sort((a, b) => groupDepth(a, gm) - groupDepth(b, gm));
|
|
@@ -6306,7 +6321,7 @@ var AIDiagram = (function (exports) {
|
|
|
6306
6321
|
if (gs.opacity != null)
|
|
6307
6322
|
ctx.globalAlpha = Number(gs.opacity);
|
|
6308
6323
|
rc.rectangle(g.x, g.y, g.w, g.h, {
|
|
6309
|
-
...R, roughness: 1.7, bowing: 0.4, seed: hashStr$
|
|
6324
|
+
...R, roughness: 1.7, bowing: 0.4, seed: hashStr$3(g.id),
|
|
6310
6325
|
fill: String(gs.fill ?? palette.groupFill),
|
|
6311
6326
|
fillStyle: 'solid',
|
|
6312
6327
|
stroke: String(gs.stroke ?? palette.groupStroke),
|
|
@@ -6314,25 +6329,17 @@ var AIDiagram = (function (exports) {
|
|
|
6314
6329
|
strokeLineDash: gs.strokeDash ?? palette.groupDash,
|
|
6315
6330
|
});
|
|
6316
6331
|
if (g.label) {
|
|
6317
|
-
const
|
|
6318
|
-
const
|
|
6319
|
-
|
|
6320
|
-
const gLetterSpacing = gs.letterSpacing;
|
|
6321
|
-
const gLabelColor = gs.color ? String(gs.color) : palette.groupLabel;
|
|
6322
|
-
const gPad = Number(gs.padding ?? 14);
|
|
6323
|
-
const gTextAlign = String(gs.textAlign ?? 'left');
|
|
6324
|
-
const gTextX = gTextAlign === 'right' ? g.x + g.w - gPad
|
|
6325
|
-
: gTextAlign === 'center' ? g.x + g.w / 2
|
|
6326
|
-
: g.x + gPad;
|
|
6327
|
-
drawText(ctx, g.label, gTextX, g.y + gPad + 2, gFontSize, gFontWeight, gLabelColor, gTextAlign, gFont, gLetterSpacing);
|
|
6332
|
+
const gTypo = resolveTypography(gs, { fontSize: GROUP_LABEL.fontSize, fontWeight: GROUP_LABEL.fontWeight, textAlign: "left", padding: GROUP_LABEL.padding }, diagramFont, palette.groupLabel);
|
|
6333
|
+
const gTextX = computeTextX(gTypo, g.x, g.w);
|
|
6334
|
+
drawText(ctx, g.label, gTextX, g.y + gTypo.padding + 2, gTypo.fontSize, gTypo.fontWeight, gTypo.textColor, gTypo.textAlign, gTypo.font, gTypo.letterSpacing);
|
|
6328
6335
|
}
|
|
6329
6336
|
if (gs.opacity != null)
|
|
6330
6337
|
ctx.globalAlpha = 1;
|
|
6331
6338
|
}
|
|
6332
6339
|
// ── Edges ─────────────────────────────────────────────────
|
|
6333
6340
|
for (const e of sg.edges) {
|
|
6334
|
-
const src = resolveEndpoint(e.from, nm, tm, gm, cm
|
|
6335
|
-
const dst = resolveEndpoint(e.to, nm, tm, gm, cm
|
|
6341
|
+
const src = resolveEndpoint(e.from, nm, tm, gm, cm);
|
|
6342
|
+
const dst = resolveEndpoint(e.to, nm, tm, gm, cm);
|
|
6336
6343
|
if (!src || !dst)
|
|
6337
6344
|
continue;
|
|
6338
6345
|
const dstCX = dst.x + dst.w / 2, dstCY = dst.y + dst.h / 2;
|
|
@@ -6345,31 +6352,31 @@ var AIDiagram = (function (exports) {
|
|
|
6345
6352
|
const { arrowAt, dashed } = connMeta(e.connector);
|
|
6346
6353
|
const len = Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2) || 1;
|
|
6347
6354
|
const nx = (x2 - x1) / len, ny = (y2 - y1) / len;
|
|
6348
|
-
const HEAD =
|
|
6355
|
+
const HEAD = EDGE.headInset;
|
|
6349
6356
|
const sx1 = arrowAt === 'start' || arrowAt === 'both' ? x1 + nx * HEAD : x1;
|
|
6350
6357
|
const sy1 = arrowAt === 'start' || arrowAt === 'both' ? y1 + ny * HEAD : y1;
|
|
6351
6358
|
const sx2 = arrowAt === 'end' || arrowAt === 'both' ? x2 - nx * HEAD : x2;
|
|
6352
6359
|
const sy2 = arrowAt === 'end' || arrowAt === 'both' ? y2 - ny * HEAD : y2;
|
|
6353
6360
|
rc.line(sx1, sy1, sx2, sy2, {
|
|
6354
|
-
...R, roughness: 0.9, seed: hashStr$
|
|
6361
|
+
...R, roughness: 0.9, seed: hashStr$3(e.from + e.to),
|
|
6355
6362
|
stroke: ecol,
|
|
6356
6363
|
strokeWidth: Number(e.style?.strokeWidth ?? 1.6),
|
|
6357
|
-
...(dashed ? { strokeLineDash:
|
|
6364
|
+
...(dashed ? { strokeLineDash: EDGE.dashPattern } : {}),
|
|
6358
6365
|
});
|
|
6359
6366
|
const ang = Math.atan2(y2 - y1, x2 - x1);
|
|
6360
6367
|
if (arrowAt === 'end' || arrowAt === 'both')
|
|
6361
|
-
drawArrowHead(rc, x2, y2, ang, ecol, hashStr$
|
|
6368
|
+
drawArrowHead(rc, x2, y2, ang, ecol, hashStr$3(e.to));
|
|
6362
6369
|
if (arrowAt === 'start' || arrowAt === 'both')
|
|
6363
|
-
drawArrowHead(rc, x1, y1, Math.atan2(y1 - y2, x1 - x2), ecol, hashStr$
|
|
6370
|
+
drawArrowHead(rc, x1, y1, Math.atan2(y1 - y2, x1 - x2), ecol, hashStr$3(e.from + 'back'));
|
|
6364
6371
|
if (e.label) {
|
|
6365
|
-
const mx = (x1 + x2) / 2 - ny *
|
|
6366
|
-
const my = (y1 + y2) / 2 + nx *
|
|
6372
|
+
const mx = (x1 + x2) / 2 - ny * EDGE.labelOffset;
|
|
6373
|
+
const my = (y1 + y2) / 2 + nx * EDGE.labelOffset;
|
|
6367
6374
|
// ── Edge label: font, font-size, letter-spacing ──
|
|
6368
6375
|
// always center-anchored (single line)
|
|
6369
|
-
const eFontSize = Number(e.style?.fontSize ??
|
|
6376
|
+
const eFontSize = Number(e.style?.fontSize ?? EDGE.labelFontSize);
|
|
6370
6377
|
const eFont = resolveStyleFont(e.style ?? {}, diagramFont);
|
|
6371
6378
|
const eLetterSpacing = e.style?.letterSpacing;
|
|
6372
|
-
const eFontWeight = e.style?.fontWeight ??
|
|
6379
|
+
const eFontWeight = e.style?.fontWeight ?? EDGE.labelFontWeight;
|
|
6373
6380
|
const eLabelColor = String(e.style?.color ?? palette.edgeLabelText);
|
|
6374
6381
|
ctx.save();
|
|
6375
6382
|
ctx.font = `${eFontWeight} ${eFontSize}px ${eFont}`;
|
|
@@ -6385,47 +6392,60 @@ var AIDiagram = (function (exports) {
|
|
|
6385
6392
|
for (const n of sg.nodes) {
|
|
6386
6393
|
if (n.style?.opacity != null)
|
|
6387
6394
|
ctx.globalAlpha = Number(n.style.opacity);
|
|
6395
|
+
// ── Static transform (deg, dx, dy, factor) ──────────
|
|
6396
|
+
// All transforms anchor around the node's visual center.
|
|
6397
|
+
const hasTx = n.dx || n.dy || n.deg || (n.factor && n.factor !== 1);
|
|
6398
|
+
if (hasTx) {
|
|
6399
|
+
ctx.save();
|
|
6400
|
+
const cx = n.x + n.w / 2, cy = n.y + n.h / 2;
|
|
6401
|
+
// Move to center, apply rotate + scale there, move back
|
|
6402
|
+
ctx.translate(cx + (n.dx ?? 0), cy + (n.dy ?? 0));
|
|
6403
|
+
if (n.deg)
|
|
6404
|
+
ctx.rotate((n.deg * Math.PI) / 180);
|
|
6405
|
+
if (n.factor && n.factor !== 1)
|
|
6406
|
+
ctx.scale(n.factor, n.factor);
|
|
6407
|
+
ctx.translate(-cx, -cy);
|
|
6408
|
+
}
|
|
6388
6409
|
renderShape(rc, ctx, n, palette, R);
|
|
6389
6410
|
// ── Node / text typography ─────────────────────────
|
|
6390
|
-
|
|
6391
|
-
|
|
6392
|
-
const
|
|
6393
|
-
const
|
|
6394
|
-
|
|
6395
|
-
|
|
6396
|
-
|
|
6397
|
-
|
|
6398
|
-
|
|
6399
|
-
|
|
6400
|
-
|
|
6401
|
-
|
|
6402
|
-
//
|
|
6403
|
-
const
|
|
6404
|
-
|
|
6405
|
-
|
|
6406
|
-
|
|
6411
|
+
const isText = n.shape === 'text';
|
|
6412
|
+
const isNote = n.shape === 'note';
|
|
6413
|
+
const isMediaShape = n.shape === 'icon' || n.shape === 'image' || n.shape === 'line';
|
|
6414
|
+
const typo = resolveTypography(n.style, {
|
|
6415
|
+
fontSize: isText ? 13 : isNote ? 12 : 14,
|
|
6416
|
+
fontWeight: isText || isNote ? 400 : 500,
|
|
6417
|
+
textColor: isText ? palette.edgeLabelText : isNote ? palette.noteText : palette.nodeText,
|
|
6418
|
+
textAlign: isNote ? "left" : undefined,
|
|
6419
|
+
lineHeight: isNote ? 1.4 : undefined,
|
|
6420
|
+
padding: isNote ? 12 : undefined,
|
|
6421
|
+
verticalAlign: isNote ? "top" : undefined,
|
|
6422
|
+
}, diagramFont, palette.nodeText);
|
|
6423
|
+
// Note textX accounts for fold corner
|
|
6424
|
+
const FOLD = NOTE.fold;
|
|
6425
|
+
const textX = isNote
|
|
6426
|
+
? (typo.textAlign === 'right' ? n.x + n.w - FOLD - typo.padding
|
|
6427
|
+
: typo.textAlign === 'center' ? n.x + (n.w - FOLD) / 2
|
|
6428
|
+
: n.x + typo.padding)
|
|
6429
|
+
: computeTextX(typo, n.x, n.w);
|
|
6407
6430
|
const rawLines = n.label.split('\n');
|
|
6408
6431
|
const lines = n.shape === 'text' && rawLines.length === 1
|
|
6409
|
-
? wrapText(n.label, n.w -
|
|
6432
|
+
? wrapText(n.label, n.w - typo.padding * 2, typo.fontSize)
|
|
6410
6433
|
: rawLines;
|
|
6411
|
-
// vertical-align: compute textCY from top/middle/bottom
|
|
6412
|
-
const nodeBodyTop = n.y + pad;
|
|
6413
|
-
const nodeBodyBottom = n.y + n.h - pad;
|
|
6414
|
-
const blockH = (lines.length - 1) * lineHeight;
|
|
6415
|
-
const isMediaShape = n.shape === 'icon' || n.shape === 'image';
|
|
6416
6434
|
const textCY = isMediaShape
|
|
6417
|
-
? n.y + n.h - 10
|
|
6418
|
-
:
|
|
6419
|
-
|
|
6420
|
-
|
|
6435
|
+
? n.y + n.h - 10
|
|
6436
|
+
: isNote
|
|
6437
|
+
? computeTextCY(typo, n.y, n.h, lines.length, FOLD + typo.padding)
|
|
6438
|
+
: computeTextCY(typo, n.y, n.h, lines.length);
|
|
6421
6439
|
if (n.label) {
|
|
6422
6440
|
if (lines.length > 1) {
|
|
6423
|
-
drawMultilineText(ctx, lines, textX, textCY, fontSize, fontWeight, textColor, textAlign, lineHeight,
|
|
6441
|
+
drawMultilineText(ctx, lines, textX, textCY, typo.fontSize, typo.fontWeight, typo.textColor, typo.textAlign, typo.lineHeight, typo.font, typo.letterSpacing);
|
|
6424
6442
|
}
|
|
6425
6443
|
else {
|
|
6426
|
-
drawText(ctx, lines[0] ?? '', textX, textCY, fontSize, fontWeight, textColor, textAlign,
|
|
6444
|
+
drawText(ctx, lines[0] ?? '', textX, textCY, typo.fontSize, typo.fontWeight, typo.textColor, typo.textAlign, typo.font, typo.letterSpacing);
|
|
6427
6445
|
}
|
|
6428
6446
|
}
|
|
6447
|
+
if (hasTx)
|
|
6448
|
+
ctx.restore();
|
|
6429
6449
|
if (n.style?.opacity != null)
|
|
6430
6450
|
ctx.globalAlpha = 1;
|
|
6431
6451
|
}
|
|
@@ -6445,12 +6465,12 @@ var AIDiagram = (function (exports) {
|
|
|
6445
6465
|
if (gs.opacity != null)
|
|
6446
6466
|
ctx.globalAlpha = Number(gs.opacity);
|
|
6447
6467
|
rc.rectangle(t.x, t.y, t.w, t.h, {
|
|
6448
|
-
...R, seed: hashStr$
|
|
6468
|
+
...R, seed: hashStr$3(t.id),
|
|
6449
6469
|
fill, fillStyle: 'solid', stroke: strk, strokeWidth: tStrokeWidth,
|
|
6450
6470
|
...(gs.strokeDash ? { strokeLineDash: gs.strokeDash } : {}),
|
|
6451
6471
|
});
|
|
6452
6472
|
rc.line(t.x, t.y + pad, t.x + t.w, t.y + pad, {
|
|
6453
|
-
roughness: 0.6, seed: hashStr$
|
|
6473
|
+
roughness: 0.6, seed: hashStr$3(t.id + 'l'), stroke: strk, strokeWidth: 1,
|
|
6454
6474
|
});
|
|
6455
6475
|
// ── Table label: always left-anchored ───────────────
|
|
6456
6476
|
drawText(ctx, t.label, t.x + 10, t.y + pad / 2, tFontSize, tFontWeight, textCol, 'left', tFont, tLetterSpacing);
|
|
@@ -6462,7 +6482,7 @@ var AIDiagram = (function (exports) {
|
|
|
6462
6482
|
ctx.fillRect(t.x + 1, rowY + 1, t.w - 2, rh - 1);
|
|
6463
6483
|
}
|
|
6464
6484
|
rc.line(t.x, rowY + rh, t.x + t.w, rowY + rh, {
|
|
6465
|
-
roughness: 0.4, seed: hashStr$
|
|
6485
|
+
roughness: 0.4, seed: hashStr$3(t.id + rowY),
|
|
6466
6486
|
stroke: row.kind === 'header' ? strk : palette.tableDivider,
|
|
6467
6487
|
strokeWidth: row.kind === 'header' ? 1.2 : 0.6,
|
|
6468
6488
|
});
|
|
@@ -6484,7 +6504,7 @@ var AIDiagram = (function (exports) {
|
|
|
6484
6504
|
drawText(ctx, cell, cellX, rowY + rh / 2, tFontSize, cellFw, cellColor, cellAlignProp, tFont, tLetterSpacing);
|
|
6485
6505
|
if (i < row.cells.length - 1) {
|
|
6486
6506
|
rc.line(cx + cw, t.y + pad, cx + cw, t.y + t.h, {
|
|
6487
|
-
roughness: 0.3, seed: hashStr$
|
|
6507
|
+
roughness: 0.3, seed: hashStr$3(t.id + 'c' + i),
|
|
6488
6508
|
stroke: palette.tableDivider, strokeWidth: 0.5,
|
|
6489
6509
|
});
|
|
6490
6510
|
}
|
|
@@ -6494,62 +6514,7 @@ var AIDiagram = (function (exports) {
|
|
|
6494
6514
|
}
|
|
6495
6515
|
ctx.globalAlpha = 1;
|
|
6496
6516
|
}
|
|
6497
|
-
// ── Notes
|
|
6498
|
-
for (const n of sg.notes) {
|
|
6499
|
-
const gs = n.style ?? {};
|
|
6500
|
-
const fill = String(gs.fill ?? palette.noteFill);
|
|
6501
|
-
const strk = String(gs.stroke ?? palette.noteStroke);
|
|
6502
|
-
const nStrokeWidth = Number(gs.strokeWidth ?? 1.2);
|
|
6503
|
-
const fold = 14;
|
|
6504
|
-
const { x, y, w, h } = n;
|
|
6505
|
-
if (gs.opacity != null)
|
|
6506
|
-
ctx.globalAlpha = Number(gs.opacity);
|
|
6507
|
-
rc.polygon([
|
|
6508
|
-
[x, y],
|
|
6509
|
-
[x + w - fold, y],
|
|
6510
|
-
[x + w, y + fold],
|
|
6511
|
-
[x + w, y + h],
|
|
6512
|
-
[x, y + h],
|
|
6513
|
-
], { ...R, seed: hashStr$1(n.id), fill, fillStyle: 'solid', stroke: strk,
|
|
6514
|
-
strokeWidth: nStrokeWidth,
|
|
6515
|
-
...(gs.strokeDash ? { strokeLineDash: gs.strokeDash } : {}),
|
|
6516
|
-
});
|
|
6517
|
-
rc.polygon([
|
|
6518
|
-
[x + w - fold, y],
|
|
6519
|
-
[x + w, y + fold],
|
|
6520
|
-
[x + w - fold, y + fold],
|
|
6521
|
-
], { roughness: 0.4, seed: hashStr$1(n.id + 'f'),
|
|
6522
|
-
fill: palette.noteFold, fillStyle: 'solid', stroke: strk,
|
|
6523
|
-
strokeWidth: Math.min(nStrokeWidth, 0.8),
|
|
6524
|
-
});
|
|
6525
|
-
const nFontSize = Number(gs.fontSize ?? 12);
|
|
6526
|
-
const nFontWeight = gs.fontWeight ?? 400;
|
|
6527
|
-
const nFont = resolveStyleFont(gs, diagramFont);
|
|
6528
|
-
const nLetterSpacing = gs.letterSpacing;
|
|
6529
|
-
const nLineHeight = Number(gs.lineHeight ?? 1.4) * nFontSize;
|
|
6530
|
-
const nTextAlign = String(gs.textAlign ?? 'left');
|
|
6531
|
-
const nVertAlign = String(gs.verticalAlign ?? 'top');
|
|
6532
|
-
const nColor = String(gs.color ?? palette.noteText);
|
|
6533
|
-
const nPad = Number(gs.padding ?? 12);
|
|
6534
|
-
const nTextX = nTextAlign === 'right' ? x + w - fold - nPad
|
|
6535
|
-
: nTextAlign === 'center' ? x + (w - fold) / 2
|
|
6536
|
-
: x + nPad;
|
|
6537
|
-
const nFoldPad = fold + nPad;
|
|
6538
|
-
const bodyTop = y + nFoldPad;
|
|
6539
|
-
const bodyBottom = y + h - nPad;
|
|
6540
|
-
const blockH = (n.lines.length - 1) * nLineHeight;
|
|
6541
|
-
const blockCY = nVertAlign === 'bottom' ? bodyBottom - blockH / 2
|
|
6542
|
-
: nVertAlign === 'middle' ? (bodyTop + bodyBottom) / 2
|
|
6543
|
-
: bodyTop + blockH / 2;
|
|
6544
|
-
if (n.lines.length > 1) {
|
|
6545
|
-
drawMultilineText(ctx, n.lines, nTextX, blockCY, nFontSize, nFontWeight, nColor, nTextAlign, nLineHeight, nFont, nLetterSpacing);
|
|
6546
|
-
}
|
|
6547
|
-
else {
|
|
6548
|
-
drawText(ctx, n.lines[0] ?? '', nTextX, blockCY, nFontSize, nFontWeight, nColor, nTextAlign, nFont, nLetterSpacing);
|
|
6549
|
-
}
|
|
6550
|
-
if (gs.opacity != null)
|
|
6551
|
-
ctx.globalAlpha = 1;
|
|
6552
|
-
}
|
|
6517
|
+
// ── Notes are now rendered as nodes via the shape registry ──
|
|
6553
6518
|
// ── Markdown blocks ────────────────────────────────────────
|
|
6554
6519
|
// Renders prose with Markdown headings and bold/italic inline spans.
|
|
6555
6520
|
// Canvas has no native bold-within-a-run, so each run is drawn
|
|
@@ -6566,7 +6531,7 @@ var AIDiagram = (function (exports) {
|
|
|
6566
6531
|
// Background + border
|
|
6567
6532
|
if (gs.fill || gs.stroke) {
|
|
6568
6533
|
rc.rectangle(m.x, m.y, m.w, m.h, {
|
|
6569
|
-
...R, seed: hashStr$
|
|
6534
|
+
...R, seed: hashStr$3(m.id),
|
|
6570
6535
|
fill: String(gs.fill ?? 'none'), fillStyle: 'solid',
|
|
6571
6536
|
stroke: String(gs.stroke ?? 'none'),
|
|
6572
6537
|
strokeWidth: Number(gs.strokeWidth ?? 1.2),
|
|
@@ -6708,6 +6673,209 @@ var AIDiagram = (function (exports) {
|
|
|
6708
6673
|
});
|
|
6709
6674
|
}, delayMs);
|
|
6710
6675
|
}
|
|
6676
|
+
const NODE_DRAW_GUIDE_ATTR = "data-node-draw-guide";
|
|
6677
|
+
const GUIDED_NODE_SHAPES = new Set([
|
|
6678
|
+
"box",
|
|
6679
|
+
"circle",
|
|
6680
|
+
"diamond",
|
|
6681
|
+
"hexagon",
|
|
6682
|
+
"triangle",
|
|
6683
|
+
"parallelogram",
|
|
6684
|
+
"line",
|
|
6685
|
+
"path",
|
|
6686
|
+
]);
|
|
6687
|
+
function polygonPath(points) {
|
|
6688
|
+
return points.map(([x, y], i) => `${i === 0 ? "M" : "L"} ${x} ${y}`).join(" ") + " Z";
|
|
6689
|
+
}
|
|
6690
|
+
function rectPath(x, y, w, h) {
|
|
6691
|
+
return polygonPath([
|
|
6692
|
+
[x, y],
|
|
6693
|
+
[x + w, y],
|
|
6694
|
+
[x + w, y + h],
|
|
6695
|
+
[x, y + h],
|
|
6696
|
+
]);
|
|
6697
|
+
}
|
|
6698
|
+
function ellipsePath(cx, cy, rx, ry) {
|
|
6699
|
+
return [
|
|
6700
|
+
`M ${cx - rx} ${cy}`,
|
|
6701
|
+
`A ${rx} ${ry} 0 1 0 ${cx + rx} ${cy}`,
|
|
6702
|
+
`A ${rx} ${ry} 0 1 0 ${cx - rx} ${cy}`,
|
|
6703
|
+
].join(" ");
|
|
6704
|
+
}
|
|
6705
|
+
function nodeMetric(el, key) {
|
|
6706
|
+
const raw = el.dataset[key];
|
|
6707
|
+
const n = raw == null ? Number.NaN : Number(raw);
|
|
6708
|
+
return Number.isFinite(n) ? n : null;
|
|
6709
|
+
}
|
|
6710
|
+
function buildNodeGuidePath(el) {
|
|
6711
|
+
const shape = el.dataset.nodeShape;
|
|
6712
|
+
if (!shape || !GUIDED_NODE_SHAPES.has(shape))
|
|
6713
|
+
return null;
|
|
6714
|
+
const x = nodeMetric(el, "x");
|
|
6715
|
+
const y = nodeMetric(el, "y");
|
|
6716
|
+
const w = nodeMetric(el, "w");
|
|
6717
|
+
const h = nodeMetric(el, "h");
|
|
6718
|
+
if (x == null || y == null || w == null || h == null)
|
|
6719
|
+
return null;
|
|
6720
|
+
switch (shape) {
|
|
6721
|
+
case "box":
|
|
6722
|
+
return rectPath(x + 1, y + 1, w - 2, h - 2);
|
|
6723
|
+
case "circle":
|
|
6724
|
+
return ellipsePath(x + w / 2, y + h / 2, (w * 0.88) / 2, (h * 0.88) / 2);
|
|
6725
|
+
case "diamond": {
|
|
6726
|
+
const cx = x + w / 2;
|
|
6727
|
+
const cy = y + h / 2;
|
|
6728
|
+
const hw = w / 2 - 2;
|
|
6729
|
+
return polygonPath([
|
|
6730
|
+
[cx, y + 2],
|
|
6731
|
+
[cx + hw, cy],
|
|
6732
|
+
[cx, y + h - 2],
|
|
6733
|
+
[cx - hw, cy],
|
|
6734
|
+
]);
|
|
6735
|
+
}
|
|
6736
|
+
case "hexagon": {
|
|
6737
|
+
const cx = x + w / 2;
|
|
6738
|
+
const cy = y + h / 2;
|
|
6739
|
+
const hw = w / 2 - 2;
|
|
6740
|
+
const hw2 = hw * SHAPES.hexagon.inset;
|
|
6741
|
+
return polygonPath([
|
|
6742
|
+
[cx - hw2, y + 3],
|
|
6743
|
+
[cx + hw2, y + 3],
|
|
6744
|
+
[cx + hw, cy],
|
|
6745
|
+
[cx + hw2, y + h - 3],
|
|
6746
|
+
[cx - hw2, y + h - 3],
|
|
6747
|
+
[cx - hw, cy],
|
|
6748
|
+
]);
|
|
6749
|
+
}
|
|
6750
|
+
case "triangle": {
|
|
6751
|
+
const cx = x + w / 2;
|
|
6752
|
+
return polygonPath([
|
|
6753
|
+
[cx, y + 3],
|
|
6754
|
+
[x + w - 3, y + h - 3],
|
|
6755
|
+
[x + 3, y + h - 3],
|
|
6756
|
+
]);
|
|
6757
|
+
}
|
|
6758
|
+
case "parallelogram":
|
|
6759
|
+
return polygonPath([
|
|
6760
|
+
[x + SHAPES.parallelogram.skew, y + 1],
|
|
6761
|
+
[x + w - 1, y + 1],
|
|
6762
|
+
[x + w - SHAPES.parallelogram.skew, y + h - 1],
|
|
6763
|
+
[x + 1, y + h - 1],
|
|
6764
|
+
]);
|
|
6765
|
+
case "line": {
|
|
6766
|
+
const labelH = el.querySelector("text") ? 20 : 0;
|
|
6767
|
+
const lineY = y + (h - labelH) / 2;
|
|
6768
|
+
return `M ${x} ${lineY} L ${x + w} ${lineY}`;
|
|
6769
|
+
}
|
|
6770
|
+
case "path":
|
|
6771
|
+
return el.dataset.pathData ?? null;
|
|
6772
|
+
default:
|
|
6773
|
+
return null;
|
|
6774
|
+
}
|
|
6775
|
+
}
|
|
6776
|
+
function nodeGuidePathEl(el) {
|
|
6777
|
+
return el.querySelector(`path[${NODE_DRAW_GUIDE_ATTR}="true"]`);
|
|
6778
|
+
}
|
|
6779
|
+
function removeNodeGuide(el) {
|
|
6780
|
+
nodeGuidePathEl(el)?.remove();
|
|
6781
|
+
}
|
|
6782
|
+
function nodePaths(el) {
|
|
6783
|
+
return Array.from(el.querySelectorAll("path")).filter((p) => p.getAttribute(NODE_DRAW_GUIDE_ATTR) !== "true");
|
|
6784
|
+
}
|
|
6785
|
+
function nodeText(el) {
|
|
6786
|
+
return el.querySelector("text");
|
|
6787
|
+
}
|
|
6788
|
+
function nodeStrokeTemplate(el) {
|
|
6789
|
+
return (nodePaths(el).find((p) => (p.getAttribute("stroke") ?? "") !== "none") ??
|
|
6790
|
+
nodePaths(el)[0] ??
|
|
6791
|
+
null);
|
|
6792
|
+
}
|
|
6793
|
+
function clearNodeDrawStyles(el) {
|
|
6794
|
+
removeNodeGuide(el);
|
|
6795
|
+
nodePaths(el).forEach((p) => {
|
|
6796
|
+
p.style.strokeDasharray =
|
|
6797
|
+
p.style.strokeDashoffset =
|
|
6798
|
+
p.style.fillOpacity =
|
|
6799
|
+
p.style.transition =
|
|
6800
|
+
p.style.opacity =
|
|
6801
|
+
"";
|
|
6802
|
+
});
|
|
6803
|
+
const text = nodeText(el);
|
|
6804
|
+
if (text) {
|
|
6805
|
+
text.style.opacity = text.style.transition = "";
|
|
6806
|
+
}
|
|
6807
|
+
}
|
|
6808
|
+
function prepareNodeForDraw(el) {
|
|
6809
|
+
clearNodeDrawStyles(el);
|
|
6810
|
+
const d = buildNodeGuidePath(el);
|
|
6811
|
+
const source = nodeStrokeTemplate(el);
|
|
6812
|
+
if (!d || !source) {
|
|
6813
|
+
prepareForDraw(el);
|
|
6814
|
+
return;
|
|
6815
|
+
}
|
|
6816
|
+
const guide = document.createElementNS(SVG_NS$1, "path");
|
|
6817
|
+
guide.setAttribute("d", d);
|
|
6818
|
+
guide.setAttribute("fill", "none");
|
|
6819
|
+
guide.setAttribute("stroke", source.getAttribute("stroke") ?? "#000");
|
|
6820
|
+
guide.setAttribute("stroke-width", source.getAttribute("stroke-width") ?? "1.8");
|
|
6821
|
+
guide.setAttribute("stroke-linecap", "round");
|
|
6822
|
+
guide.setAttribute("stroke-linejoin", "round");
|
|
6823
|
+
guide.setAttribute(NODE_DRAW_GUIDE_ATTR, "true");
|
|
6824
|
+
if (el.dataset.nodeShape === "path") {
|
|
6825
|
+
const pathX = nodeMetric(el, "x") ?? 0;
|
|
6826
|
+
const pathY = nodeMetric(el, "y") ?? 0;
|
|
6827
|
+
guide.setAttribute("transform", `translate(${pathX},${pathY})`);
|
|
6828
|
+
}
|
|
6829
|
+
guide.style.pointerEvents = "none";
|
|
6830
|
+
const len = pathLength(guide);
|
|
6831
|
+
guide.style.strokeDasharray = `${len}`;
|
|
6832
|
+
guide.style.strokeDashoffset = `${len}`;
|
|
6833
|
+
guide.style.transition = "none";
|
|
6834
|
+
nodePaths(el).forEach((p) => {
|
|
6835
|
+
p.style.opacity = "0";
|
|
6836
|
+
p.style.transition = "none";
|
|
6837
|
+
});
|
|
6838
|
+
const text = nodeText(el);
|
|
6839
|
+
if (text) {
|
|
6840
|
+
text.style.opacity = "0";
|
|
6841
|
+
text.style.transition = "none";
|
|
6842
|
+
}
|
|
6843
|
+
el.appendChild(guide);
|
|
6844
|
+
}
|
|
6845
|
+
function revealNodeInstant(el) {
|
|
6846
|
+
clearNodeDrawStyles(el);
|
|
6847
|
+
}
|
|
6848
|
+
function animateNodeDraw(el, strokeDur = ANIMATION.nodeStrokeDur) {
|
|
6849
|
+
const guide = nodeGuidePathEl(el);
|
|
6850
|
+
if (!guide) {
|
|
6851
|
+
const firstPath = el.querySelector("path");
|
|
6852
|
+
if (!firstPath?.style.strokeDasharray)
|
|
6853
|
+
prepareForDraw(el);
|
|
6854
|
+
animateShapeDraw(el, strokeDur, ANIMATION.nodeStagger);
|
|
6855
|
+
const nodePathCount = el.querySelectorAll("path").length;
|
|
6856
|
+
clearDashOverridesAfter(el, nodePathCount * ANIMATION.nodeStagger + strokeDur + 120);
|
|
6857
|
+
return;
|
|
6858
|
+
}
|
|
6859
|
+
const roughPaths = nodePaths(el);
|
|
6860
|
+
const text = nodeText(el);
|
|
6861
|
+
const revealDelay = strokeDur + 30;
|
|
6862
|
+
const textDelay = revealDelay + ANIMATION.textDelay;
|
|
6863
|
+
requestAnimationFrame(() => requestAnimationFrame(() => {
|
|
6864
|
+
guide.style.transition = `stroke-dashoffset ${strokeDur}ms cubic-bezier(.4,0,.2,1)`;
|
|
6865
|
+
guide.style.strokeDashoffset = "0";
|
|
6866
|
+
roughPaths.forEach((p) => {
|
|
6867
|
+
p.style.transition = `opacity 140ms ease ${revealDelay}ms`;
|
|
6868
|
+
p.style.opacity = "1";
|
|
6869
|
+
});
|
|
6870
|
+
if (text) {
|
|
6871
|
+
text.style.transition = `opacity ${ANIMATION.textFade}ms ease ${textDelay}ms`;
|
|
6872
|
+
text.style.opacity = "1";
|
|
6873
|
+
}
|
|
6874
|
+
setTimeout(() => {
|
|
6875
|
+
clearNodeDrawStyles(el);
|
|
6876
|
+
}, textDelay + ANIMATION.textFade + 40);
|
|
6877
|
+
}));
|
|
6878
|
+
}
|
|
6711
6879
|
// ── Arrow connector parser ────────────────────────────────
|
|
6712
6880
|
const ARROW_CONNECTORS = ["<-->", "<->", "-->", "<--", "->", "<-", "---", "--"];
|
|
6713
6881
|
function parseEdgeTarget(target) {
|
|
@@ -6758,19 +6926,6 @@ var AIDiagram = (function (exports) {
|
|
|
6758
6926
|
text.style.transition = "none";
|
|
6759
6927
|
}
|
|
6760
6928
|
}
|
|
6761
|
-
function revealInstant(el) {
|
|
6762
|
-
el.querySelectorAll("path").forEach((p) => {
|
|
6763
|
-
p.style.transition = "none";
|
|
6764
|
-
p.style.strokeDashoffset = "0";
|
|
6765
|
-
p.style.fillOpacity = "";
|
|
6766
|
-
p.style.strokeDasharray = "";
|
|
6767
|
-
});
|
|
6768
|
-
const text = el.querySelector("text");
|
|
6769
|
-
if (text) {
|
|
6770
|
-
text.style.transition = "none";
|
|
6771
|
-
text.style.opacity = "";
|
|
6772
|
-
}
|
|
6773
|
-
}
|
|
6774
6929
|
function clearDrawStyles(el) {
|
|
6775
6930
|
el.querySelectorAll("path").forEach((p) => {
|
|
6776
6931
|
p.style.strokeDasharray =
|
|
@@ -6784,12 +6939,12 @@ var AIDiagram = (function (exports) {
|
|
|
6784
6939
|
text.style.opacity = text.style.transition = "";
|
|
6785
6940
|
}
|
|
6786
6941
|
}
|
|
6787
|
-
function animateShapeDraw(el, strokeDur =
|
|
6942
|
+
function animateShapeDraw(el, strokeDur = ANIMATION.nodeStrokeDur, stag = ANIMATION.nodeStagger) {
|
|
6788
6943
|
const paths = Array.from(el.querySelectorAll("path"));
|
|
6789
6944
|
const text = el.querySelector("text");
|
|
6790
6945
|
requestAnimationFrame(() => requestAnimationFrame(() => {
|
|
6791
6946
|
paths.forEach((p, i) => {
|
|
6792
|
-
const sd = i * stag, fd = sd + strokeDur
|
|
6947
|
+
const sd = i * stag, fd = sd + strokeDur + ANIMATION.fillFadeOffset;
|
|
6793
6948
|
p.style.transition = [
|
|
6794
6949
|
`stroke-dashoffset ${strokeDur}ms cubic-bezier(.4,0,.2,1) ${sd}ms`,
|
|
6795
6950
|
`fill-opacity 180ms ease ${Math.max(0, fd)}ms`,
|
|
@@ -6798,57 +6953,87 @@ var AIDiagram = (function (exports) {
|
|
|
6798
6953
|
p.style.fillOpacity = "1";
|
|
6799
6954
|
});
|
|
6800
6955
|
if (text) {
|
|
6801
|
-
const td = paths.length * stag + strokeDur +
|
|
6802
|
-
text.style.transition = `opacity
|
|
6956
|
+
const td = paths.length * stag + strokeDur + ANIMATION.textDelay;
|
|
6957
|
+
text.style.transition = `opacity ${ANIMATION.textFade}ms ease ${td}ms`;
|
|
6803
6958
|
text.style.opacity = "1";
|
|
6804
6959
|
}
|
|
6805
6960
|
}));
|
|
6806
6961
|
}
|
|
6807
6962
|
// ── Edge draw helpers ─────────────────────────────────────
|
|
6963
|
+
const EDGE_SHAFT_SELECTOR = '[data-edge-role="shaft"] path';
|
|
6964
|
+
const EDGE_DECOR_SELECTOR = '[data-edge-role="head"], [data-edge-role="label"], [data-edge-role="label-bg"]';
|
|
6965
|
+
function edgeShaftPaths(el) {
|
|
6966
|
+
return Array.from(el.querySelectorAll(EDGE_SHAFT_SELECTOR));
|
|
6967
|
+
}
|
|
6968
|
+
function edgeDecorEls(el) {
|
|
6969
|
+
return Array.from(el.querySelectorAll(EDGE_DECOR_SELECTOR));
|
|
6970
|
+
}
|
|
6971
|
+
function prepareEdgeForDraw(el) {
|
|
6972
|
+
edgeShaftPaths(el).forEach((p) => {
|
|
6973
|
+
const len = pathLength(p);
|
|
6974
|
+
p.style.strokeDasharray = `${len}`;
|
|
6975
|
+
p.style.strokeDashoffset = `${len}`;
|
|
6976
|
+
p.style.transition = "none";
|
|
6977
|
+
});
|
|
6978
|
+
edgeDecorEls(el).forEach((part) => {
|
|
6979
|
+
part.style.opacity = "0";
|
|
6980
|
+
part.style.transition = "none";
|
|
6981
|
+
});
|
|
6982
|
+
}
|
|
6983
|
+
function revealEdgeInstant(el) {
|
|
6984
|
+
edgeShaftPaths(el).forEach((p) => {
|
|
6985
|
+
p.style.transition = "none";
|
|
6986
|
+
p.style.strokeDashoffset = "0";
|
|
6987
|
+
p.style.strokeDasharray = "";
|
|
6988
|
+
});
|
|
6989
|
+
edgeDecorEls(el).forEach((part) => {
|
|
6990
|
+
part.style.transition = "none";
|
|
6991
|
+
part.style.opacity = "1";
|
|
6992
|
+
});
|
|
6993
|
+
}
|
|
6808
6994
|
function clearEdgeDrawStyles(el) {
|
|
6809
|
-
el
|
|
6995
|
+
edgeShaftPaths(el).forEach((p) => {
|
|
6810
6996
|
p.style.strokeDasharray =
|
|
6811
6997
|
p.style.strokeDashoffset =
|
|
6812
|
-
p.style.
|
|
6813
|
-
|
|
6814
|
-
|
|
6998
|
+
p.style.transition =
|
|
6999
|
+
"";
|
|
7000
|
+
});
|
|
7001
|
+
edgeDecorEls(el).forEach((part) => {
|
|
7002
|
+
part.style.opacity = part.style.transition = "";
|
|
6815
7003
|
});
|
|
6816
7004
|
}
|
|
6817
|
-
function animateEdgeDraw(el, conn) {
|
|
6818
|
-
const
|
|
6819
|
-
|
|
7005
|
+
function animateEdgeDraw(el, conn, strokeDur = ANIMATION.strokeDur) {
|
|
7006
|
+
const shaftPaths = edgeShaftPaths(el);
|
|
7007
|
+
const decorEls = edgeDecorEls(el);
|
|
7008
|
+
if (!shaftPaths.length)
|
|
6820
7009
|
return;
|
|
6821
|
-
const linePath = paths[0];
|
|
6822
|
-
const headPaths = paths.slice(1);
|
|
6823
|
-
const STROKE_DUR = 360;
|
|
6824
|
-
const len = pathLength(linePath);
|
|
6825
7010
|
const reversed = conn.startsWith('<') && !conn.includes('>');
|
|
6826
|
-
|
|
6827
|
-
|
|
6828
|
-
|
|
6829
|
-
|
|
6830
|
-
p.style.
|
|
6831
|
-
|
|
7011
|
+
shaftPaths.forEach((p) => {
|
|
7012
|
+
const len = pathLength(p);
|
|
7013
|
+
p.style.strokeDasharray = `${len}`;
|
|
7014
|
+
p.style.strokeDashoffset = reversed ? `${-len}` : `${len}`;
|
|
7015
|
+
p.style.transition = "none";
|
|
7016
|
+
});
|
|
7017
|
+
decorEls.forEach((part) => {
|
|
7018
|
+
part.style.opacity = "0";
|
|
7019
|
+
part.style.transition = "none";
|
|
6832
7020
|
});
|
|
6833
|
-
el.classList.remove('draw-hidden');
|
|
6834
|
-
el.classList.add('draw-reveal');
|
|
6835
|
-
el.style.opacity = '1';
|
|
6836
7021
|
requestAnimationFrame(() => requestAnimationFrame(() => {
|
|
6837
|
-
|
|
6838
|
-
|
|
7022
|
+
shaftPaths.forEach((p) => {
|
|
7023
|
+
p.style.transition = `stroke-dashoffset ${strokeDur}ms cubic-bezier(.4,0,.2,1)`;
|
|
7024
|
+
p.style.strokeDashoffset = "0";
|
|
7025
|
+
});
|
|
6839
7026
|
setTimeout(() => {
|
|
6840
|
-
|
|
6841
|
-
|
|
6842
|
-
|
|
7027
|
+
decorEls.forEach((part) => {
|
|
7028
|
+
part.style.transition = `opacity ${ANIMATION.arrowReveal}ms ease`;
|
|
7029
|
+
part.style.opacity = "1";
|
|
6843
7030
|
});
|
|
6844
7031
|
// ── ADD: clear inline dash overrides so SVG attribute
|
|
6845
7032
|
// (stroke-dasharray="6,5" for dashed arrows) takes over again
|
|
6846
7033
|
setTimeout(() => {
|
|
6847
|
-
|
|
6848
|
-
|
|
6849
|
-
|
|
6850
|
-
}, 160);
|
|
6851
|
-
}, STROKE_DUR - 40);
|
|
7034
|
+
clearEdgeDrawStyles(el);
|
|
7035
|
+
}, ANIMATION.dashClear);
|
|
7036
|
+
}, Math.max(0, strokeDur - 40));
|
|
6852
7037
|
}));
|
|
6853
7038
|
}
|
|
6854
7039
|
// ── AnimationController ───────────────────────────────────
|
|
@@ -6860,6 +7045,7 @@ var AIDiagram = (function (exports) {
|
|
|
6860
7045
|
this.svg = svg;
|
|
6861
7046
|
this.steps = steps;
|
|
6862
7047
|
this._step = -1;
|
|
7048
|
+
this._pendingStepTimers = new Set();
|
|
6863
7049
|
this._transforms = new Map();
|
|
6864
7050
|
this._listeners = [];
|
|
6865
7051
|
this.drawTargetEdges = getDrawTargetEdgeIds(steps);
|
|
@@ -6958,8 +7144,9 @@ var AIDiagram = (function (exports) {
|
|
|
6958
7144
|
async play(msPerStep = 900) {
|
|
6959
7145
|
this.emit("animation-start");
|
|
6960
7146
|
while (this.canNext) {
|
|
7147
|
+
const nextStep = this.steps[this._step + 1];
|
|
6961
7148
|
this.next();
|
|
6962
|
-
await new Promise((r) => setTimeout(r, msPerStep));
|
|
7149
|
+
await new Promise((r) => setTimeout(r, this._playbackWaitMs(nextStep, msPerStep)));
|
|
6963
7150
|
}
|
|
6964
7151
|
}
|
|
6965
7152
|
goTo(index) {
|
|
@@ -6976,20 +7163,43 @@ var AIDiagram = (function (exports) {
|
|
|
6976
7163
|
}
|
|
6977
7164
|
this.emit("step-change");
|
|
6978
7165
|
}
|
|
7166
|
+
_clearPendingStepTimers() {
|
|
7167
|
+
this._pendingStepTimers.forEach((id) => window.clearTimeout(id));
|
|
7168
|
+
this._pendingStepTimers.clear();
|
|
7169
|
+
}
|
|
7170
|
+
_scheduleStep(fn, delayMs) {
|
|
7171
|
+
if (delayMs <= 0) {
|
|
7172
|
+
fn();
|
|
7173
|
+
return;
|
|
7174
|
+
}
|
|
7175
|
+
const id = window.setTimeout(() => {
|
|
7176
|
+
this._pendingStepTimers.delete(id);
|
|
7177
|
+
fn();
|
|
7178
|
+
}, delayMs);
|
|
7179
|
+
this._pendingStepTimers.add(id);
|
|
7180
|
+
}
|
|
7181
|
+
_playbackWaitMs(step, fallbackMs) {
|
|
7182
|
+
if (!step)
|
|
7183
|
+
return fallbackMs;
|
|
7184
|
+
const delay = Math.max(0, step.delay ?? 0);
|
|
7185
|
+
const duration = Math.max(0, step.duration ?? 0);
|
|
7186
|
+
return delay + Math.max(fallbackMs, duration);
|
|
7187
|
+
}
|
|
6979
7188
|
_clearAll() {
|
|
7189
|
+
this._clearPendingStepTimers();
|
|
6980
7190
|
this._transforms.clear();
|
|
6981
7191
|
// Nodes
|
|
6982
7192
|
this.svg.querySelectorAll(".ng").forEach((el) => {
|
|
6983
|
-
el.style.transform = "";
|
|
7193
|
+
el.style.transform = el.dataset.baseTransform ?? "";
|
|
6984
7194
|
el.style.transition = "";
|
|
6985
7195
|
el.classList.remove("hl", "faded", "hidden");
|
|
6986
7196
|
el.style.opacity = el.style.filter = "";
|
|
6987
7197
|
if (this.drawTargetNodes.has(el.id)) {
|
|
6988
|
-
|
|
6989
|
-
|
|
7198
|
+
prepareNodeForDraw(el);
|
|
7199
|
+
}
|
|
7200
|
+
else {
|
|
7201
|
+
clearNodeDrawStyles(el);
|
|
6990
7202
|
}
|
|
6991
|
-
else
|
|
6992
|
-
clearDrawStyles(el);
|
|
6993
7203
|
});
|
|
6994
7204
|
// Groups — hide draw-target groups, show the rest
|
|
6995
7205
|
this.svg.querySelectorAll(".gg").forEach((el) => {
|
|
@@ -7009,16 +7219,13 @@ var AIDiagram = (function (exports) {
|
|
|
7009
7219
|
});
|
|
7010
7220
|
// Edges
|
|
7011
7221
|
this.svg.querySelectorAll(".eg").forEach((el) => {
|
|
7012
|
-
el.classList.remove("draw-reveal");
|
|
7013
7222
|
clearEdgeDrawStyles(el);
|
|
7014
7223
|
el.style.transition = "none";
|
|
7224
|
+
el.style.opacity = "";
|
|
7015
7225
|
if (this.drawTargetEdges.has(el.id)) {
|
|
7016
|
-
el
|
|
7017
|
-
el.classList.add("draw-hidden");
|
|
7226
|
+
prepareEdgeForDraw(el);
|
|
7018
7227
|
}
|
|
7019
7228
|
else {
|
|
7020
|
-
el.style.opacity = "";
|
|
7021
|
-
el.classList.remove("draw-hidden");
|
|
7022
7229
|
requestAnimationFrame(() => {
|
|
7023
7230
|
el.style.transition = "";
|
|
7024
7231
|
});
|
|
@@ -7095,6 +7302,14 @@ var AIDiagram = (function (exports) {
|
|
|
7095
7302
|
const s = this.steps[i];
|
|
7096
7303
|
if (!s)
|
|
7097
7304
|
return;
|
|
7305
|
+
const run = () => this._runStep(s, silent);
|
|
7306
|
+
if (silent) {
|
|
7307
|
+
run();
|
|
7308
|
+
return;
|
|
7309
|
+
}
|
|
7310
|
+
this._scheduleStep(run, Math.max(0, s.delay ?? 0));
|
|
7311
|
+
}
|
|
7312
|
+
_runStep(s, silent) {
|
|
7098
7313
|
switch (s.action) {
|
|
7099
7314
|
case "highlight":
|
|
7100
7315
|
this._doHighlight(s.target);
|
|
@@ -7106,20 +7321,20 @@ var AIDiagram = (function (exports) {
|
|
|
7106
7321
|
this._doFade(s.target, false);
|
|
7107
7322
|
break;
|
|
7108
7323
|
case "draw":
|
|
7109
|
-
this._doDraw(s
|
|
7324
|
+
this._doDraw(s, silent);
|
|
7110
7325
|
break;
|
|
7111
7326
|
case "erase":
|
|
7112
|
-
this._doErase(s.target);
|
|
7327
|
+
this._doErase(s.target, s.duration);
|
|
7113
7328
|
break;
|
|
7114
7329
|
case "show":
|
|
7115
|
-
this._doShowHide(s.target, true, silent);
|
|
7330
|
+
this._doShowHide(s.target, true, silent, s.duration);
|
|
7116
7331
|
break;
|
|
7117
7332
|
case "hide":
|
|
7118
|
-
this._doShowHide(s.target, false, silent);
|
|
7333
|
+
this._doShowHide(s.target, false, silent, s.duration);
|
|
7119
7334
|
break;
|
|
7120
7335
|
case "pulse":
|
|
7121
7336
|
if (!silent)
|
|
7122
|
-
this._doPulse(s.target);
|
|
7337
|
+
this._doPulse(s.target, s.duration);
|
|
7123
7338
|
break;
|
|
7124
7339
|
case "color":
|
|
7125
7340
|
this._doColor(s.target, s.value);
|
|
@@ -7163,7 +7378,9 @@ var AIDiagram = (function (exports) {
|
|
|
7163
7378
|
el.style.transition = silent
|
|
7164
7379
|
? "none"
|
|
7165
7380
|
: `transform ${duration}ms cubic-bezier(.4,0,.2,1)`;
|
|
7166
|
-
|
|
7381
|
+
const base = el.dataset.baseTransform ?? "";
|
|
7382
|
+
const anim = parts.join(" ");
|
|
7383
|
+
el.style.transform = anim ? `${anim} ${base}`.trim() : base;
|
|
7167
7384
|
if (silent) {
|
|
7168
7385
|
requestAnimationFrame(() => requestAnimationFrame(() => {
|
|
7169
7386
|
el.style.transition = "";
|
|
@@ -7219,7 +7436,8 @@ var AIDiagram = (function (exports) {
|
|
|
7219
7436
|
});
|
|
7220
7437
|
this._writeTransform(el, target, silent, step.duration ?? 400);
|
|
7221
7438
|
}
|
|
7222
|
-
_doDraw(
|
|
7439
|
+
_doDraw(step, silent) {
|
|
7440
|
+
const { target } = step;
|
|
7223
7441
|
const edge = parseEdgeTarget(target);
|
|
7224
7442
|
if (edge) {
|
|
7225
7443
|
// ── Edge draw ──────────────────────────────────────
|
|
@@ -7227,17 +7445,13 @@ var AIDiagram = (function (exports) {
|
|
|
7227
7445
|
if (!el)
|
|
7228
7446
|
return;
|
|
7229
7447
|
if (silent) {
|
|
7230
|
-
|
|
7231
|
-
el.style.transition = "none";
|
|
7232
|
-
el.classList.remove("draw-hidden");
|
|
7233
|
-
el.classList.add("draw-reveal");
|
|
7234
|
-
el.style.opacity = "1";
|
|
7448
|
+
revealEdgeInstant(el);
|
|
7235
7449
|
requestAnimationFrame(() => requestAnimationFrame(() => {
|
|
7236
|
-
el
|
|
7450
|
+
clearEdgeDrawStyles(el);
|
|
7237
7451
|
}));
|
|
7238
7452
|
}
|
|
7239
7453
|
else {
|
|
7240
|
-
animateEdgeDraw(el, edge.conn);
|
|
7454
|
+
animateEdgeDraw(el, edge.conn, step.duration ?? ANIMATION.strokeDur);
|
|
7241
7455
|
}
|
|
7242
7456
|
return;
|
|
7243
7457
|
}
|
|
@@ -7261,9 +7475,10 @@ var AIDiagram = (function (exports) {
|
|
|
7261
7475
|
const firstPath = groupEl.querySelector("path");
|
|
7262
7476
|
if (!firstPath?.style.strokeDasharray)
|
|
7263
7477
|
prepareForDraw(groupEl);
|
|
7264
|
-
|
|
7478
|
+
const groupStrokeDur = step.duration ?? ANIMATION.groupStrokeDur;
|
|
7479
|
+
animateShapeDraw(groupEl, groupStrokeDur, ANIMATION.groupStagger);
|
|
7265
7480
|
const pathCount = groupEl.querySelectorAll('path').length;
|
|
7266
|
-
const totalMs = pathCount *
|
|
7481
|
+
const totalMs = pathCount * ANIMATION.groupStagger + groupStrokeDur + 120;
|
|
7267
7482
|
clearDashOverridesAfter(groupEl, totalMs);
|
|
7268
7483
|
}
|
|
7269
7484
|
return;
|
|
@@ -7284,9 +7499,10 @@ var AIDiagram = (function (exports) {
|
|
|
7284
7499
|
else {
|
|
7285
7500
|
tableEl.classList.remove("gg-hidden");
|
|
7286
7501
|
prepareForDraw(tableEl);
|
|
7287
|
-
|
|
7502
|
+
const tableStrokeDur = step.duration ?? ANIMATION.tableStrokeDur;
|
|
7503
|
+
animateShapeDraw(tableEl, tableStrokeDur, ANIMATION.tableStagger);
|
|
7288
7504
|
const tablePathCount = tableEl.querySelectorAll('path').length;
|
|
7289
|
-
clearDashOverridesAfter(tableEl, tablePathCount *
|
|
7505
|
+
clearDashOverridesAfter(tableEl, tablePathCount * ANIMATION.tableStagger + tableStrokeDur + 120);
|
|
7290
7506
|
}
|
|
7291
7507
|
return;
|
|
7292
7508
|
}
|
|
@@ -7306,9 +7522,10 @@ var AIDiagram = (function (exports) {
|
|
|
7306
7522
|
else {
|
|
7307
7523
|
noteEl.classList.remove("gg-hidden");
|
|
7308
7524
|
prepareForDraw(noteEl);
|
|
7309
|
-
|
|
7525
|
+
const noteStrokeDur = step.duration ?? ANIMATION.nodeStrokeDur;
|
|
7526
|
+
animateShapeDraw(noteEl, noteStrokeDur, ANIMATION.nodeStagger);
|
|
7310
7527
|
const notePathCount = noteEl.querySelectorAll('path').length;
|
|
7311
|
-
clearDashOverridesAfter(noteEl, notePathCount *
|
|
7528
|
+
clearDashOverridesAfter(noteEl, notePathCount * ANIMATION.nodeStagger + noteStrokeDur + 120);
|
|
7312
7529
|
}
|
|
7313
7530
|
return;
|
|
7314
7531
|
}
|
|
@@ -7329,8 +7546,9 @@ var AIDiagram = (function (exports) {
|
|
|
7329
7546
|
else {
|
|
7330
7547
|
chartEl.style.opacity = "0"; // start from 0 explicitly
|
|
7331
7548
|
chartEl.classList.remove("gg-hidden");
|
|
7549
|
+
const chartFade = step.duration ?? ANIMATION.chartFade;
|
|
7332
7550
|
requestAnimationFrame(() => requestAnimationFrame(() => {
|
|
7333
|
-
chartEl.style.transition =
|
|
7551
|
+
chartEl.style.transition = `opacity ${chartFade}ms ease`;
|
|
7334
7552
|
chartEl.style.opacity = "1";
|
|
7335
7553
|
}));
|
|
7336
7554
|
}
|
|
@@ -7351,8 +7569,9 @@ var AIDiagram = (function (exports) {
|
|
|
7351
7569
|
else {
|
|
7352
7570
|
markdownEl.style.opacity = "0";
|
|
7353
7571
|
markdownEl.classList.remove("gg-hidden");
|
|
7572
|
+
const markdownFade = step.duration ?? ANIMATION.chartFade;
|
|
7354
7573
|
requestAnimationFrame(() => requestAnimationFrame(() => {
|
|
7355
|
-
markdownEl.style.transition =
|
|
7574
|
+
markdownEl.style.transition = `opacity ${markdownFade}ms ease`;
|
|
7356
7575
|
markdownEl.style.opacity = "1";
|
|
7357
7576
|
}));
|
|
7358
7577
|
}
|
|
@@ -7363,41 +7582,38 @@ var AIDiagram = (function (exports) {
|
|
|
7363
7582
|
if (!nodeEl)
|
|
7364
7583
|
return;
|
|
7365
7584
|
if (silent) {
|
|
7366
|
-
|
|
7367
|
-
requestAnimationFrame(() => requestAnimationFrame(() => clearDrawStyles(nodeEl)));
|
|
7585
|
+
revealNodeInstant(nodeEl);
|
|
7368
7586
|
}
|
|
7369
7587
|
else {
|
|
7370
|
-
|
|
7371
|
-
|
|
7372
|
-
|
|
7373
|
-
|
|
7374
|
-
const nodePathCount = nodeEl.querySelectorAll('path').length;
|
|
7375
|
-
clearDashOverridesAfter(nodeEl, nodePathCount * 55 + 420 + 120);
|
|
7588
|
+
if (!nodeGuidePathEl(nodeEl) && !nodeEl.querySelector("path")?.style.strokeDasharray) {
|
|
7589
|
+
prepareNodeForDraw(nodeEl);
|
|
7590
|
+
}
|
|
7591
|
+
animateNodeDraw(nodeEl, step.duration ?? ANIMATION.nodeStrokeDur);
|
|
7376
7592
|
}
|
|
7377
7593
|
}
|
|
7378
7594
|
// ── erase ─────────────────────────────────────────────────
|
|
7379
|
-
_doErase(target) {
|
|
7595
|
+
_doErase(target, duration = 400) {
|
|
7380
7596
|
const el = resolveEl(this.svg, target); // handles edges too now
|
|
7381
7597
|
if (el) {
|
|
7382
|
-
el.style.transition =
|
|
7598
|
+
el.style.transition = `opacity ${duration}ms`;
|
|
7383
7599
|
el.style.opacity = "0";
|
|
7384
7600
|
}
|
|
7385
7601
|
}
|
|
7386
7602
|
// ── show / hide ───────────────────────────────────────────
|
|
7387
|
-
_doShowHide(target, show, silent) {
|
|
7603
|
+
_doShowHide(target, show, silent, duration = 400) {
|
|
7388
7604
|
const el = resolveEl(this.svg, target);
|
|
7389
7605
|
if (!el)
|
|
7390
7606
|
return;
|
|
7391
|
-
el.style.transition = silent ? "none" :
|
|
7607
|
+
el.style.transition = silent ? "none" : `opacity ${duration}ms`;
|
|
7392
7608
|
el.style.opacity = show ? "1" : "0";
|
|
7393
7609
|
}
|
|
7394
7610
|
// ── pulse ─────────────────────────────────────────────────
|
|
7395
|
-
_doPulse(target) {
|
|
7611
|
+
_doPulse(target, duration = 500) {
|
|
7396
7612
|
resolveEl(this.svg, target)?.animate([
|
|
7397
7613
|
{ filter: "brightness(1)" },
|
|
7398
7614
|
{ filter: "brightness(1.6)" },
|
|
7399
7615
|
{ filter: "brightness(1)" },
|
|
7400
|
-
], { duration
|
|
7616
|
+
], { duration, iterations: 3 });
|
|
7401
7617
|
}
|
|
7402
7618
|
// ── color ─────────────────────────────────────────────────
|
|
7403
7619
|
_doColor(target, color) {
|
|
@@ -7435,41 +7651,39 @@ var AIDiagram = (function (exports) {
|
|
|
7435
7651
|
}
|
|
7436
7652
|
}
|
|
7437
7653
|
}
|
|
7438
|
-
const ANIMATION_CSS = `
|
|
7439
|
-
.ng, .gg, .tg, .ntg, .cg, .eg, .mdg {
|
|
7440
|
-
transform-box: fill-box;
|
|
7441
|
-
transform-origin: center;
|
|
7442
|
-
transition: filter 0.3s, opacity 0.35s;
|
|
7443
|
-
}
|
|
7444
|
-
|
|
7445
|
-
/* highlight */
|
|
7446
|
-
.ng.hl path, .ng.hl rect, .ng.hl ellipse, .ng.hl polygon,
|
|
7447
|
-
.tg.hl path, .tg.hl rect,
|
|
7448
|
-
.ntg.hl path, .ntg.hl polygon,
|
|
7449
|
-
.cg.hl path, .cg.hl rect,
|
|
7450
|
-
.mdg.hl text,
|
|
7451
|
-
.eg.hl path, .eg.hl line, .eg.hl polygon { stroke-width: 2.8 !important; }
|
|
7452
|
-
|
|
7453
|
-
.ng.hl, .tg.hl, .ntg.hl, .cg.hl, .mdg.hl, .eg.hl {
|
|
7454
|
-
animation: ng-pulse 1.4s ease-in-out infinite;
|
|
7455
|
-
}
|
|
7456
|
-
@keyframes ng-pulse {
|
|
7457
|
-
0%, 100% { filter: drop-shadow(0 0 7px rgba(200,84,40,.6)); }
|
|
7458
|
-
50% { filter: drop-shadow(0 0 14px rgba(200,84,40,.9)); }
|
|
7459
|
-
}
|
|
7460
|
-
|
|
7461
|
-
/* fade */
|
|
7462
|
-
.ng.faded, .gg.faded, .tg.faded, .ntg.faded,
|
|
7654
|
+
const ANIMATION_CSS = `
|
|
7655
|
+
.ng, .gg, .tg, .ntg, .cg, .eg, .mdg {
|
|
7656
|
+
transform-box: fill-box;
|
|
7657
|
+
transform-origin: center;
|
|
7658
|
+
transition: filter 0.3s, opacity 0.35s;
|
|
7659
|
+
}
|
|
7660
|
+
|
|
7661
|
+
/* highlight */
|
|
7662
|
+
.ng.hl path, .ng.hl rect, .ng.hl ellipse, .ng.hl polygon,
|
|
7663
|
+
.tg.hl path, .tg.hl rect,
|
|
7664
|
+
.ntg.hl path, .ntg.hl polygon,
|
|
7665
|
+
.cg.hl path, .cg.hl rect,
|
|
7666
|
+
.mdg.hl text,
|
|
7667
|
+
.eg.hl path, .eg.hl line, .eg.hl polygon { stroke-width: 2.8 !important; }
|
|
7668
|
+
|
|
7669
|
+
.ng.hl, .tg.hl, .ntg.hl, .cg.hl, .mdg.hl, .eg.hl {
|
|
7670
|
+
animation: ng-pulse 1.4s ease-in-out infinite;
|
|
7671
|
+
}
|
|
7672
|
+
@keyframes ng-pulse {
|
|
7673
|
+
0%, 100% { filter: drop-shadow(0 0 7px rgba(200,84,40,.6)); }
|
|
7674
|
+
50% { filter: drop-shadow(0 0 14px rgba(200,84,40,.9)); }
|
|
7675
|
+
}
|
|
7676
|
+
|
|
7677
|
+
/* fade */
|
|
7678
|
+
.ng.faded, .gg.faded, .tg.faded, .ntg.faded,
|
|
7463
7679
|
.cg.faded, .eg.faded, .mdg.faded { opacity: 0.22; }
|
|
7464
7680
|
|
|
7465
7681
|
.ng.hidden { opacity: 0; pointer-events: none; }
|
|
7466
|
-
.eg.draw-hidden { opacity: 0; }
|
|
7467
|
-
.eg.draw-reveal { opacity: 1; }
|
|
7468
7682
|
.gg.gg-hidden { opacity: 0; }
|
|
7469
7683
|
.tg.gg-hidden { opacity: 0; }
|
|
7470
7684
|
.ntg.gg-hidden { opacity: 0; }
|
|
7471
|
-
.cg.gg-hidden { opacity: 0; }
|
|
7472
|
-
.mdg.gg-hidden { opacity: 0; }
|
|
7685
|
+
.cg.gg-hidden { opacity: 0; }
|
|
7686
|
+
.mdg.gg-hidden { opacity: 0; }
|
|
7473
7687
|
`;
|
|
7474
7688
|
|
|
7475
7689
|
// ============================================================
|
|
@@ -7485,7 +7699,7 @@ var AIDiagram = (function (exports) {
|
|
|
7485
7699
|
document.body.appendChild(a);
|
|
7486
7700
|
a.click();
|
|
7487
7701
|
document.body.removeChild(a);
|
|
7488
|
-
setTimeout(() => URL.revokeObjectURL(url),
|
|
7702
|
+
setTimeout(() => URL.revokeObjectURL(url), EXPORT.revokeDelay);
|
|
7489
7703
|
}
|
|
7490
7704
|
// ── SVG export ────────────────────────────────────────────
|
|
7491
7705
|
function exportSVG(svg, opts = {}) {
|
|
@@ -7507,9 +7721,9 @@ var AIDiagram = (function (exports) {
|
|
|
7507
7721
|
download(blob, opts.filename ?? 'diagram.png');
|
|
7508
7722
|
}
|
|
7509
7723
|
async function svgToPNGDataURL(svg, opts = {}) {
|
|
7510
|
-
const scale = opts.scale ??
|
|
7511
|
-
const w = parseFloat(svg.getAttribute('width') ??
|
|
7512
|
-
const h = parseFloat(svg.getAttribute('height') ??
|
|
7724
|
+
const scale = opts.scale ?? EXPORT.pngScale;
|
|
7725
|
+
const w = parseFloat(svg.getAttribute('width') ?? String(EXPORT.fallbackW));
|
|
7726
|
+
const h = parseFloat(svg.getAttribute('height') ?? String(EXPORT.fallbackH));
|
|
7513
7727
|
const canvas = document.createElement('canvas');
|
|
7514
7728
|
canvas.width = w * scale;
|
|
7515
7729
|
canvas.height = h * scale;
|
|
@@ -7520,7 +7734,7 @@ var AIDiagram = (function (exports) {
|
|
|
7520
7734
|
ctx.fillRect(0, 0, w, h);
|
|
7521
7735
|
}
|
|
7522
7736
|
else {
|
|
7523
|
-
ctx.fillStyle =
|
|
7737
|
+
ctx.fillStyle = EXPORT.fallbackBg;
|
|
7524
7738
|
ctx.fillRect(0, 0, w, h);
|
|
7525
7739
|
}
|
|
7526
7740
|
const svgStr = svgToString(svg);
|
|
@@ -7549,7 +7763,7 @@ var AIDiagram = (function (exports) {
|
|
|
7549
7763
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
7550
7764
|
<title>sketchmark export</title>
|
|
7551
7765
|
<style>
|
|
7552
|
-
body { margin: 0; background:
|
|
7766
|
+
body { margin: 0; background: ${EXPORT.fallbackBg}; display: flex; flex-direction: column; align-items: center; padding: 2rem; font-family: system-ui, sans-serif; }
|
|
7553
7767
|
.diagram { max-width: 100%; }
|
|
7554
7768
|
.dsl { margin-top: 2rem; background: #131008; color: #e0c898; padding: 1rem; border-radius: 8px; font-family: monospace; font-size: 13px; line-height: 1.7; white-space: pre; max-width: 800px; width: 100%; overflow: auto; }
|
|
7555
7769
|
</style>
|
|
@@ -7572,7 +7786,7 @@ var AIDiagram = (function (exports) {
|
|
|
7572
7786
|
}
|
|
7573
7787
|
// ── MP4 stub (requires ffmpeg.wasm or MediaRecorder) ──────
|
|
7574
7788
|
async function exportMP4(canvas, durationMs, opts = {}) {
|
|
7575
|
-
const fps = opts.fps ??
|
|
7789
|
+
const fps = opts.fps ?? EXPORT.defaultFps;
|
|
7576
7790
|
const stream = canvas.captureStream?.(fps);
|
|
7577
7791
|
if (!stream)
|
|
7578
7792
|
throw new Error('captureStream not supported in this browser');
|