@diagrammo/dgmo 0.47.0 → 0.48.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (319) hide show
  1. package/dist/auto.js +1 -1
  2. package/dist/auto.mjs +1 -1
  3. package/dist/element.js +1 -1
  4. package/dist/element.mjs +1 -1
  5. package/package.json +19 -70
  6. package/dist/advanced.cjs +0 -71648
  7. package/dist/advanced.d.cts +0 -5719
  8. package/dist/auto.cjs +0 -70648
  9. package/dist/auto.d.cts +0 -54
  10. package/dist/block.cjs +0 -70208
  11. package/dist/block.d.cts +0 -115
  12. package/dist/editor.cjs +0 -526
  13. package/dist/editor.d.cts +0 -26
  14. package/dist/element.cjs +0 -70530
  15. package/dist/element.d.cts +0 -62
  16. package/dist/highlight.cjs +0 -852
  17. package/dist/highlight.d.cts +0 -49
  18. package/dist/index.cjs +0 -70491
  19. package/dist/index.d.cts +0 -160
  20. package/dist/pert.cjs +0 -325
  21. package/dist/pert.d.cts +0 -559
  22. package/dist/themes-1-CuKpeH.d.cts +0 -908
  23. package/src/advanced.ts +0 -873
  24. package/src/arc/parser.ts +0 -15
  25. package/src/arc/renderer.ts +0 -633
  26. package/src/auto/index.ts +0 -621
  27. package/src/auto/shared.ts +0 -370
  28. package/src/auto/styles.ts +0 -73
  29. package/src/block/layout.ts +0 -198
  30. package/src/block/parser.ts +0 -427
  31. package/src/block/renderer.ts +0 -484
  32. package/src/block/types.ts +0 -67
  33. package/src/boxes-and-lines/collapse.ts +0 -96
  34. package/src/boxes-and-lines/focus.ts +0 -257
  35. package/src/boxes-and-lines/label-placement.ts +0 -339
  36. package/src/boxes-and-lines/layout-grouped.ts +0 -912
  37. package/src/boxes-and-lines/layout-layered.ts +0 -722
  38. package/src/boxes-and-lines/layout-search.ts +0 -1678
  39. package/src/boxes-and-lines/layout.ts +0 -398
  40. package/src/boxes-and-lines/parser.ts +0 -1181
  41. package/src/boxes-and-lines/renderer.ts +0 -1465
  42. package/src/boxes-and-lines/types.ts +0 -66
  43. package/src/c4/layout.ts +0 -2441
  44. package/src/c4/parser.ts +0 -1158
  45. package/src/c4/renderer.ts +0 -2008
  46. package/src/c4/types.ts +0 -89
  47. package/src/chart-type-registry.ts +0 -529
  48. package/src/chart-types.ts +0 -225
  49. package/src/chart.ts +0 -685
  50. package/src/charts-d3/bar.ts +0 -338
  51. package/src/charts-d3/chord.ts +0 -140
  52. package/src/charts-d3/function.ts +0 -147
  53. package/src/charts-d3/funnel.ts +0 -104
  54. package/src/charts-d3/heatmap.ts +0 -145
  55. package/src/charts-d3/index.ts +0 -337
  56. package/src/charts-d3/interactions.ts +0 -477
  57. package/src/charts-d3/line.ts +0 -344
  58. package/src/charts-d3/mount.ts +0 -151
  59. package/src/charts-d3/pie.ts +0 -117
  60. package/src/charts-d3/polar.ts +0 -89
  61. package/src/charts-d3/radar.ts +0 -0
  62. package/src/charts-d3/sankey.ts +0 -0
  63. package/src/charts-d3/scatter.ts +0 -185
  64. package/src/charts-d3/shared.ts +0 -208
  65. package/src/class/layout.ts +0 -326
  66. package/src/class/parser.ts +0 -635
  67. package/src/class/renderer.ts +0 -793
  68. package/src/class/types.ts +0 -58
  69. package/src/cli-banner.ts +0 -106
  70. package/src/cli.ts +0 -1080
  71. package/src/colors-diagnostics.ts +0 -35
  72. package/src/colors.ts +0 -379
  73. package/src/completion-types.ts +0 -27
  74. package/src/completion.ts +0 -2668
  75. package/src/cycle/layout.ts +0 -1026
  76. package/src/cycle/parser.ts +0 -387
  77. package/src/cycle/renderer.ts +0 -653
  78. package/src/cycle/types.ts +0 -101
  79. package/src/d3.ts +0 -1434
  80. package/src/data-chart-parser.ts +0 -1007
  81. package/src/dgmo-router.ts +0 -296
  82. package/src/diagnostics-registry.ts +0 -108
  83. package/src/diagnostics.ts +0 -0
  84. package/src/dimensions.ts +0 -20
  85. package/src/directives-registry.ts +0 -396
  86. package/src/editor/dgmo.grammar +0 -76
  87. package/src/editor/dgmo.grammar.d.ts +0 -2
  88. package/src/editor/dgmo.grammar.js +0 -18
  89. package/src/editor/dgmo.grammar.terms.d.ts +0 -5
  90. package/src/editor/dgmo.grammar.terms.js +0 -35
  91. package/src/editor/highlight-api.ts +0 -596
  92. package/src/editor/highlight.ts +0 -36
  93. package/src/editor/index.ts +0 -27
  94. package/src/editor/keywords.ts +0 -128
  95. package/src/editor/tokens.ts +0 -30
  96. package/src/element/index.ts +0 -432
  97. package/src/embed/css.ts +0 -312
  98. package/src/embed/escape.ts +0 -15
  99. package/src/embed/index.ts +0 -280
  100. package/src/er/classify.ts +0 -228
  101. package/src/er/layout.ts +0 -530
  102. package/src/er/parser.ts +0 -750
  103. package/src/er/renderer.ts +0 -725
  104. package/src/er/types.ts +0 -52
  105. package/src/error-card.ts +0 -269
  106. package/src/event-line/diagnostics.ts +0 -117
  107. package/src/event-line/parser.ts +0 -643
  108. package/src/event-line/renderer.ts +0 -1742
  109. package/src/event-line/types.ts +0 -74
  110. package/src/fonts.ts +0 -3
  111. package/src/gantt/calculator.ts +0 -979
  112. package/src/gantt/parser.ts +0 -1641
  113. package/src/gantt/renderer.ts +0 -3787
  114. package/src/gantt/resolver.ts +0 -172
  115. package/src/gantt/types.ts +0 -206
  116. package/src/graph/diagnostics.ts +0 -64
  117. package/src/graph/edge-spline.ts +0 -29
  118. package/src/graph/flowchart-parser.ts +0 -759
  119. package/src/graph/flowchart-renderer.ts +0 -817
  120. package/src/graph/layout.ts +0 -472
  121. package/src/graph/notes.ts +0 -21
  122. package/src/graph/state-collapse.ts +0 -78
  123. package/src/graph/state-parser.ts +0 -574
  124. package/src/graph/state-renderer.ts +0 -653
  125. package/src/graph/types.ts +0 -65
  126. package/src/index.ts +0 -261
  127. package/src/infra/compute.ts +0 -1385
  128. package/src/infra/layout.ts +0 -888
  129. package/src/infra/parser.ts +0 -1234
  130. package/src/infra/renderer.ts +0 -2534
  131. package/src/infra/roles.ts +0 -75
  132. package/src/infra/types.ts +0 -216
  133. package/src/infra/validation.ts +0 -201
  134. package/src/journey-map/layout.ts +0 -490
  135. package/src/journey-map/parser.ts +0 -557
  136. package/src/journey-map/renderer.ts +0 -1580
  137. package/src/journey-map/types.ts +0 -47
  138. package/src/kanban/mutations.ts +0 -175
  139. package/src/kanban/parser.ts +0 -509
  140. package/src/kanban/renderer.ts +0 -1449
  141. package/src/kanban/types.ts +0 -39
  142. package/src/label-layout.ts +0 -368
  143. package/src/map/colorize.ts +0 -54
  144. package/src/map/completion.ts +0 -316
  145. package/src/map/context-labels.ts +0 -609
  146. package/src/map/data/PROVENANCE.json +0 -1
  147. package/src/map/data/README.md +0 -40
  148. package/src/map/data/airport-collisions.json +0 -1
  149. package/src/map/data/airports.json +0 -1
  150. package/src/map/data/gazetteer.json +0 -1
  151. package/src/map/data/lakes.json +0 -1
  152. package/src/map/data/mountain-ranges.json +0 -1
  153. package/src/map/data/na-lakes.json +0 -1
  154. package/src/map/data/na-land.json +0 -1
  155. package/src/map/data/region-names.json +0 -1
  156. package/src/map/data/rivers.json +0 -1
  157. package/src/map/data/types.ts +0 -121
  158. package/src/map/data/us-states.json +0 -1
  159. package/src/map/data/water-bodies.json +0 -1
  160. package/src/map/data/world-coarse.json +0 -1
  161. package/src/map/data/world-detail.json +0 -1
  162. package/src/map/diagnostics.ts +0 -134
  163. package/src/map/dimensions.ts +0 -150
  164. package/src/map/geo-query.ts +0 -295
  165. package/src/map/geo.ts +0 -438
  166. package/src/map/invert.ts +0 -111
  167. package/src/map/layout.ts +0 -5069
  168. package/src/map/legend-band.ts +0 -127
  169. package/src/map/load-data.ts +0 -186
  170. package/src/map/parser.ts +0 -783
  171. package/src/map/renderer.ts +0 -1445
  172. package/src/map/resolved-types.ts +0 -160
  173. package/src/map/resolver.ts +0 -1146
  174. package/src/map/types.ts +0 -194
  175. package/src/mindmap/collapse.ts +0 -67
  176. package/src/mindmap/layout.ts +0 -649
  177. package/src/mindmap/parser.ts +0 -430
  178. package/src/mindmap/renderer.ts +0 -637
  179. package/src/mindmap/text-wrap.ts +0 -218
  180. package/src/mindmap/types.ts +0 -56
  181. package/src/org/collapse.ts +0 -151
  182. package/src/org/layout.ts +0 -1262
  183. package/src/org/parser.ts +0 -519
  184. package/src/org/renderer.ts +0 -866
  185. package/src/org/resolver.ts +0 -636
  186. package/src/palettes/atlas.ts +0 -77
  187. package/src/palettes/blueprint.ts +0 -73
  188. package/src/palettes/catppuccin.ts +0 -84
  189. package/src/palettes/color-utils.ts +0 -443
  190. package/src/palettes/index.ts +0 -67
  191. package/src/palettes/nord.ts +0 -67
  192. package/src/palettes/registry.ts +0 -114
  193. package/src/palettes/slate.ts +0 -73
  194. package/src/palettes/tidewater.ts +0 -73
  195. package/src/palettes/tokyo-night.ts +0 -86
  196. package/src/palettes/types.ts +0 -88
  197. package/src/pert/analyzer.ts +0 -1183
  198. package/src/pert/diagnostics.ts +0 -110
  199. package/src/pert/index.ts +0 -43
  200. package/src/pert/internal.ts +0 -252
  201. package/src/pert/layout.ts +0 -971
  202. package/src/pert/monte-carlo.ts +0 -446
  203. package/src/pert/parser.ts +0 -1606
  204. package/src/pert/renderer.ts +0 -4009
  205. package/src/pert/share-normalize.ts +0 -62
  206. package/src/pert/types.ts +0 -604
  207. package/src/pyramid/parser.ts +0 -185
  208. package/src/pyramid/renderer.ts +0 -768
  209. package/src/pyramid/types.ts +0 -28
  210. package/src/quadrant/parser.ts +0 -18
  211. package/src/quadrant/renderer.ts +0 -638
  212. package/src/raci/diagnostics.ts +0 -213
  213. package/src/raci/index.ts +0 -23
  214. package/src/raci/mutations.ts +0 -321
  215. package/src/raci/parser.ts +0 -699
  216. package/src/raci/renderer.ts +0 -1627
  217. package/src/raci/types.ts +0 -82
  218. package/src/raci/variants.ts +0 -320
  219. package/src/render.ts +0 -268
  220. package/src/ring/parser.ts +0 -212
  221. package/src/ring/renderer.ts +0 -490
  222. package/src/ring/types.ts +0 -27
  223. package/src/sequence/collapse.ts +0 -174
  224. package/src/sequence/parser.ts +0 -1596
  225. package/src/sequence/participant-inference.ts +0 -152
  226. package/src/sequence/renderer.ts +0 -3168
  227. package/src/sequence/tag-resolution.ts +0 -164
  228. package/src/sharing.ts +0 -211
  229. package/src/sitemap/collapse.ts +0 -100
  230. package/src/sitemap/layout.ts +0 -925
  231. package/src/sitemap/parser.ts +0 -755
  232. package/src/sitemap/renderer.ts +0 -828
  233. package/src/sitemap/types.ts +0 -42
  234. package/src/slope/parser.ts +0 -18
  235. package/src/slope/renderer.ts +0 -378
  236. package/src/swimlane/diagnostics.ts +0 -94
  237. package/src/swimlane/layout.ts +0 -597
  238. package/src/swimlane/parser.ts +0 -613
  239. package/src/swimlane/renderer.ts +0 -681
  240. package/src/swimlane/types.ts +0 -151
  241. package/src/tech-radar/interactive.ts +0 -1129
  242. package/src/tech-radar/layout.ts +0 -197
  243. package/src/tech-radar/parser.ts +0 -436
  244. package/src/tech-radar/renderer.ts +0 -1239
  245. package/src/tech-radar/shared.ts +0 -151
  246. package/src/tech-radar/types.ts +0 -86
  247. package/src/themes.ts +0 -22
  248. package/src/timeline/parser.ts +0 -472
  249. package/src/timeline/renderer.ts +0 -3024
  250. package/src/timeline/types.ts +0 -33
  251. package/src/timeline/viz-parser.ts +0 -21
  252. package/src/treemap/diagnostics.ts +0 -62
  253. package/src/treemap/layout-radial.ts +0 -233
  254. package/src/treemap/layout.ts +0 -189
  255. package/src/treemap/parser.ts +0 -463
  256. package/src/treemap/renderer-radial.ts +0 -438
  257. package/src/treemap/renderer.ts +0 -572
  258. package/src/treemap/treemap-shared.ts +0 -309
  259. package/src/treemap/types.ts +0 -60
  260. package/src/utils/arrow-markers.ts +0 -51
  261. package/src/utils/arrows-diagnostics.ts +0 -62
  262. package/src/utils/arrows.ts +0 -227
  263. package/src/utils/brand.ts +0 -61
  264. package/src/utils/card.ts +0 -183
  265. package/src/utils/collapse-engine/tree-edges.ts +0 -115
  266. package/src/utils/collapse-engine/tree.ts +0 -117
  267. package/src/utils/d3-helpers.ts +0 -219
  268. package/src/utils/d3-types.ts +0 -9
  269. package/src/utils/description-helpers.ts +0 -38
  270. package/src/utils/duration.ts +0 -312
  271. package/src/utils/export-container.ts +0 -41
  272. package/src/utils/expr-eval.ts +0 -27
  273. package/src/utils/fit-canvas.ts +0 -64
  274. package/src/utils/hover-styles.ts +0 -673
  275. package/src/utils/inline-markdown.ts +0 -96
  276. package/src/utils/legend-constants.ts +0 -48
  277. package/src/utils/legend-d3.ts +0 -618
  278. package/src/utils/legend-integration.ts +0 -107
  279. package/src/utils/legend-layout.ts +0 -776
  280. package/src/utils/legend-svg.ts +0 -214
  281. package/src/utils/legend-types.ts +0 -272
  282. package/src/utils/name-normalize.ts +0 -163
  283. package/src/utils/note-box/constants.ts +0 -25
  284. package/src/utils/note-box/index.ts +0 -11
  285. package/src/utils/note-box/metrics.ts +0 -90
  286. package/src/utils/note-box/svg.ts +0 -331
  287. package/src/utils/notes/bounds.ts +0 -30
  288. package/src/utils/notes/build.ts +0 -131
  289. package/src/utils/notes/index.ts +0 -18
  290. package/src/utils/notes/model.ts +0 -19
  291. package/src/utils/notes/parse.ts +0 -131
  292. package/src/utils/notes/place.ts +0 -177
  293. package/src/utils/notes/resolve.ts +0 -88
  294. package/src/utils/number-format.ts +0 -36
  295. package/src/utils/parsing.ts +0 -953
  296. package/src/utils/reserved-key-registry.ts +0 -239
  297. package/src/utils/safe-href.ts +0 -38
  298. package/src/utils/scaling.ts +0 -97
  299. package/src/utils/svg-embed.ts +0 -251
  300. package/src/utils/tag-groups.ts +0 -607
  301. package/src/utils/text-measure.ts +0 -122
  302. package/src/utils/time-ticks.ts +0 -253
  303. package/src/utils/title-constants.ts +0 -24
  304. package/src/utils/visual-conventions.ts +0 -61
  305. package/src/utils/wrapped-desc.ts +0 -87
  306. package/src/venn/parser.ts +0 -18
  307. package/src/venn/renderer.ts +0 -1047
  308. package/src/version-control/diagnostics.ts +0 -67
  309. package/src/version-control/parser.ts +0 -544
  310. package/src/version-control/renderer.ts +0 -724
  311. package/src/version-control/types.ts +0 -100
  312. package/src/visualizations/parse.ts +0 -1560
  313. package/src/visualizations/types.ts +0 -262
  314. package/src/wireframe/layout.ts +0 -479
  315. package/src/wireframe/parser.ts +0 -1114
  316. package/src/wireframe/renderer.ts +0 -1318
  317. package/src/wireframe/types.ts +0 -110
  318. package/src/wordcloud/parser.ts +0 -18
  319. package/src/wordcloud/renderer.ts +0 -357
@@ -1,681 +0,0 @@
1
- // ============================================================
2
- // Swimlane Diagram — D3 SVG Renderer
3
- // ============================================================
4
- //
5
- // Draws lane band backgrounds + headers, phase dividers + headers, nodes
6
- // (shape per SwimShape), edges (in-arrow labels, dashed back-edges), terminals
7
- // (typed color + glyph), gateways (diamond; `+` for parallel), subprocess
8
- // (double border). Fill cascade: active tag value → event/symbol type → lane
9
- // shade. resvg has no `color-mix()` — all blends go through `mix()`.
10
-
11
- import * as d3Selection from 'd3-selection';
12
- import { FONT_FAMILY } from '../fonts';
13
- import { appendArrowheadMarkers } from '../utils/arrow-markers';
14
- import {
15
- TITLE_FONT_SIZE,
16
- TITLE_FONT_WEIGHT,
17
- TITLE_Y,
18
- } from '../utils/title-constants';
19
- import { mix, contrastText, themeBaseBg } from '../palettes/color-utils';
20
- import { resolveColor, CATEGORICAL_COLOR_ORDER } from '../colors';
21
- import { resolveTagColor, tagAttrKey } from '../utils/tag-groups';
22
- import type { PaletteColors } from '../palettes';
23
- import type {
24
- ParsedSwimlane,
25
- SwimlaneLayoutResult,
26
- SwimLayoutNode,
27
- } from './types';
28
-
29
- const NODE_FONT_SIZE = 12;
30
- const LANE_LABEL_FONT = 12;
31
- const PHASE_LABEL_FONT = 12;
32
- const EDGE_LABEL_FONT = 11;
33
- const NODE_RX = 8;
34
- const NODE_STROKE = 1.5;
35
- const EDGE_STROKE = 1.6;
36
- const EDGE_CORNER_R = 11; // fillet radius for orthogonal edge corners
37
- const ARROW_W = 9;
38
- const ARROW_H = 6.4;
39
- const LANE_RADIUS = 10;
40
- const LANE_INSET = 2; // visual gutter between adjacent lane cards
41
-
42
- /** Rect path with independent corner radii (clamped to half-extent). */
43
- /** Orthogonal polyline with rounded (filleted) corners for softer routes. */
44
- function roundedPolyline(
45
- pts: readonly { readonly x: number; readonly y: number }[],
46
- radius: number
47
- ): string {
48
- if (pts.length < 3) {
49
- return pts.map((p, i) => `${i ? 'L' : 'M'}${p.x} ${p.y}`).join(' ');
50
- }
51
- let d = `M${pts[0]!.x} ${pts[0]!.y}`;
52
- for (let i = 1; i < pts.length - 1; i++) {
53
- const p0 = pts[i - 1]!;
54
- const p1 = pts[i]!;
55
- const p2 = pts[i + 1]!;
56
- const l1 = Math.hypot(p1.x - p0.x, p1.y - p0.y);
57
- const l2 = Math.hypot(p2.x - p1.x, p2.y - p1.y);
58
- if (l1 < 0.01 || l2 < 0.01) {
59
- d += ` L${p1.x} ${p1.y}`;
60
- continue;
61
- }
62
- const r = Math.min(radius, l1 / 2, l2 / 2);
63
- const ax = p1.x - ((p1.x - p0.x) / l1) * r;
64
- const ay = p1.y - ((p1.y - p0.y) / l1) * r;
65
- const bx = p1.x + ((p2.x - p1.x) / l2) * r;
66
- const by = p1.y + ((p2.y - p1.y) / l2) * r;
67
- d += ` L${ax} ${ay} Q${p1.x} ${p1.y} ${bx} ${by}`;
68
- }
69
- const last = pts[pts.length - 1]!;
70
- d += ` L${last.x} ${last.y}`;
71
- return d;
72
- }
73
-
74
- /** Shortest distance from a point to a line segment. */
75
- function distToSeg(
76
- px: number,
77
- py: number,
78
- x1: number,
79
- y1: number,
80
- x2: number,
81
- y2: number
82
- ): number {
83
- const dx = x2 - x1;
84
- const dy = y2 - y1;
85
- const l2 = dx * dx + dy * dy;
86
- let t = l2 > 0 ? ((px - x1) * dx + (py - y1) * dy) / l2 : 0;
87
- t = Math.max(0, Math.min(1, t));
88
- return Math.hypot(px - (x1 + t * dx), py - (y1 + t * dy));
89
- }
90
-
91
- function roundedRectPath(
92
- x: number,
93
- y: number,
94
- w: number,
95
- h: number,
96
- r: { tl?: number; tr?: number; br?: number; bl?: number } = {}
97
- ): string {
98
- const m = Math.min(w, h) / 2;
99
- const tl = Math.min(r.tl ?? 0, m);
100
- const tr = Math.min(r.tr ?? 0, m);
101
- const br = Math.min(r.br ?? 0, m);
102
- const bl = Math.min(r.bl ?? 0, m);
103
- return [
104
- `M${x + tl},${y}`,
105
- `H${x + w - tr}`,
106
- tr ? `A${tr},${tr} 0 0 1 ${x + w},${y + tr}` : '',
107
- `V${y + h - br}`,
108
- br ? `A${br},${br} 0 0 1 ${x + w - br},${y + h}` : '',
109
- `H${x + bl}`,
110
- bl ? `A${bl},${bl} 0 0 1 ${x},${y + h - bl}` : '',
111
- `V${y + tl}`,
112
- tl ? `A${tl},${tl} 0 0 1 ${x + tl},${y}` : '',
113
- 'Z',
114
- ].join(' ');
115
- }
116
-
117
- type D3Svg = d3Selection.Selection<SVGSVGElement, unknown, null, undefined>;
118
- type D3G = d3Selection.Selection<SVGGElement, unknown, null, undefined>;
119
-
120
- export interface SwimlaneRenderOptions {
121
- exportDims?: { width: number; height: number };
122
- activeTagGroup?: string | null;
123
- exportMode?: boolean;
124
- }
125
-
126
- interface EventColors {
127
- error: string;
128
- success: string;
129
- terminate: string;
130
- neutral: string;
131
- }
132
-
133
- export function renderSwimlaneForExport(
134
- container: HTMLElement,
135
- parsed: ParsedSwimlane,
136
- layout: SwimlaneLayoutResult,
137
- palette: PaletteColors,
138
- isDark: boolean,
139
- opts: SwimlaneRenderOptions = {}
140
- ): void {
141
- // Clear any prior render so re-renders in a live container (app preview)
142
- // don't stack SVGs. Export uses a throwaway container, so this is a no-op there.
143
- d3Selection.select(container).selectAll(':not([data-d3-tooltip])').remove();
144
-
145
- const titleOffset = parsed.title ? 40 : 0;
146
- const width = opts.exportDims?.width ?? layout.width;
147
- const height = (opts.exportDims?.height ?? layout.height) + 0;
148
-
149
- const svg = d3Selection
150
- .select(container)
151
- .append('svg')
152
- .attr('xmlns', 'http://www.w3.org/2000/svg')
153
- .attr('width', width)
154
- .attr('height', height)
155
- .attr('viewBox', `0 0 ${width} ${height}`)
156
- .attr('preserveAspectRatio', 'xMidYMin meet')
157
- .style('font-family', FONT_FAMILY) as unknown as D3Svg;
158
-
159
- const defs = svg.append('defs');
160
- const baseBg = themeBaseBg(palette, isDark);
161
- const ev: EventColors = {
162
- error: palette.colors.red,
163
- success: palette.colors.green,
164
- terminate: palette.text,
165
- neutral: palette.border,
166
- };
167
- appendArrowheadMarkers(defs, {
168
- idPrefix: 'sw',
169
- width: ARROW_W,
170
- height: ARROW_H,
171
- baseFill: palette.textMuted,
172
- colors: [ev.error, ev.success],
173
- });
174
-
175
- const root = svg
176
- .append('g')
177
- .attr('transform', `translate(0, ${titleOffset})`) as unknown as D3G;
178
-
179
- // ── Title ───────────────────────────────────────────────────
180
- if (parsed.title) {
181
- svg
182
- .append('text')
183
- .attr('x', width / 2)
184
- .attr('y', TITLE_Y)
185
- .attr('text-anchor', 'middle')
186
- .attr('font-size', TITLE_FONT_SIZE)
187
- .attr('font-weight', TITLE_FONT_WEIGHT)
188
- .attr('fill', palette.text)
189
- .text(parsed.title);
190
- }
191
-
192
- // ── Lane colors (explicit or auto-categorical) ──────────────
193
- const laneColorById = new Map<string, string>();
194
- parsed.lanes.forEach((lane, i) => {
195
- const hex =
196
- lane.color ??
197
- resolveColor(
198
- CATEGORICAL_COLOR_ORDER[i % CATEGORICAL_COLOR_ORDER.length]!,
199
- palette
200
- ) ??
201
- palette.border;
202
- laneColorById.set(lane.id, hex);
203
- });
204
-
205
- const isLR = parsed.direction === 'LR';
206
- const solid = parsed.options['solid-fill'] === 'on';
207
-
208
- // Halo colour for label legibility: match the lane background the text sits
209
- // over (not a flat light fill) so the halo blends in and just fades out the
210
- // edges/outlines crossing behind the text. Falls back to the base bg.
211
- const laneFill = (hex: string): string => mix(hex, baseBg, 9);
212
- const haloAt = (cross: number): string => {
213
- for (const band of layout.lanes) {
214
- const lo = isLR ? band.y : band.x;
215
- const hi = lo + (isLR ? band.height : band.width);
216
- if (cross >= lo && cross <= hi)
217
- return laneFill(laneColorById.get(band.id) ?? palette.border);
218
- }
219
- return baseBg;
220
- };
221
-
222
- // ── Lane band backgrounds + headers (rounded cards) ─────────
223
- const laneG = root.append('g').attr('class', 'dgmo-swimlane-lanes');
224
- for (const band of layout.lanes) {
225
- const hex = laneColorById.get(band.id) ?? palette.border;
226
- // Inset on the cross axis (vertical in LR, horizontal in TB) so adjacent
227
- // rounded lane cards read as separate rows/columns instead of one block.
228
- const bx = isLR ? band.x : band.x + LANE_INSET;
229
- const by = isLR ? band.y + LANE_INSET : band.y;
230
- const bw = isLR ? band.width : band.width - LANE_INSET * 2;
231
- const bh = isLR ? band.height - LANE_INSET * 2 : band.height;
232
- laneG
233
- .append('path')
234
- .attr(
235
- 'd',
236
- roundedRectPath(bx, by, bw, bh, {
237
- tl: LANE_RADIUS,
238
- tr: LANE_RADIUS,
239
- br: LANE_RADIUS,
240
- bl: LANE_RADIUS,
241
- })
242
- )
243
- .attr('fill', mix(hex, baseBg, 9))
244
- .attr('stroke', mix(palette.border, baseBg, 60))
245
- .attr('stroke-width', 1)
246
- .attr('data-line-number', String(band.lineNumber));
247
- // Header gutter (left in LR / top in TB) — slightly stronger tint, with
248
- // only its leading-edge corners rounded to hug the card.
249
- const gw = isLR ? band.headerSize : bw;
250
- const gh = isLR ? bh : band.headerSize;
251
- laneG
252
- .append('path')
253
- .attr(
254
- 'd',
255
- roundedRectPath(
256
- bx,
257
- by,
258
- gw,
259
- gh,
260
- isLR
261
- ? { tl: LANE_RADIUS, bl: LANE_RADIUS }
262
- : { tl: LANE_RADIUS, tr: LANE_RADIUS }
263
- )
264
- )
265
- .attr('fill', mix(hex, baseBg, 16))
266
- .attr('opacity', 0.6);
267
- laneG
268
- .append('text')
269
- .attr('x', isLR ? bx + 12 : bx + bw / 2)
270
- .attr('y', isLR ? by + bh / 2 : by + band.headerSize / 2)
271
- .attr('text-anchor', isLR ? 'start' : 'middle')
272
- .attr('dominant-baseline', 'middle')
273
- .attr('font-size', LANE_LABEL_FONT)
274
- .attr('font-weight', '700')
275
- .attr('fill', mix(hex, palette.text, 55))
276
- .text(band.label);
277
- }
278
-
279
- // ── Phase bands (zebra striping + centered headers) ─────────
280
- // Phases are shown as faint alternating column washes instead of divider
281
- // lines: under cross-phase compaction a column can host two phases, so a hard
282
- // divider would land mid-column. Each band already has a clean, gap-free,
283
- // non-overlapping column range, so a subtle wash on every other band reads as
284
- // distinct vertical sections without any line stepping on the headers.
285
- const phaseG = root.append('g').attr('class', 'dgmo-swimlane-phases');
286
- layout.phases.forEach((band, idx) => {
287
- if (idx % 2 === 1) {
288
- phaseG
289
- .append('rect')
290
- .attr('x', band.x)
291
- .attr('y', band.y)
292
- .attr('width', band.width)
293
- .attr('height', band.height)
294
- .attr('rx', LANE_RADIUS)
295
- .attr('fill', palette.text)
296
- .attr('opacity', 0.05)
297
- .attr('pointer-events', 'none');
298
- }
299
- phaseG
300
- .append('text')
301
- .attr('x', isLR ? band.x + band.width / 2 : band.headerSize / 2)
302
- .attr('y', isLR ? band.headerSize / 2 : band.y + band.height / 2)
303
- .attr('text-anchor', 'middle')
304
- .attr('dominant-baseline', 'middle')
305
- .attr('font-size', PHASE_LABEL_FONT)
306
- .attr('font-weight', '700')
307
- .attr('letter-spacing', '0.06em')
308
- .attr('fill', palette.textMuted)
309
- .attr('data-line-number', String(band.lineNumber))
310
- .text(band.label);
311
- });
312
-
313
- // ── Node fill cascade ───────────────────────────────────────
314
- const activeTag = opts.activeTagGroup ?? null;
315
- const nodeFill = (n: SwimLayoutNode): { fill: string; stroke: string } => {
316
- // 1. active tag value.
317
- if (activeTag) {
318
- const key = tagAttrKey(activeTag);
319
- if (n.tags[key]) {
320
- const c = resolveTagColor(n.tags, [...parsed.tagGroups], activeTag);
321
- if (c && c !== '#999999')
322
- return { fill: solid ? c : mix(c, baseBg, 22), stroke: c };
323
- }
324
- }
325
- // 2. event / symbol type.
326
- if (n.shape === 'terminal') {
327
- if (n.event === 'error')
328
- return {
329
- fill: solid ? ev.error : mix(ev.error, baseBg, 22),
330
- stroke: ev.error,
331
- };
332
- if (n.event === 'success')
333
- return {
334
- fill: solid ? ev.success : mix(ev.success, baseBg, 22),
335
- stroke: ev.success,
336
- };
337
- if (n.event === 'terminate')
338
- return { fill: mix(palette.text, baseBg, 30), stroke: palette.text };
339
- return { fill: palette.bg, stroke: palette.textMuted };
340
- }
341
- if (n.shape === 'exclusive' || n.shape === 'parallel') {
342
- // Gateways stay neutral.
343
- return {
344
- fill: mix(palette.surface, baseBg, 80),
345
- stroke: palette.textMuted,
346
- };
347
- }
348
- // 3. lane shade.
349
- const hex = laneColorById.get(n.lane) ?? palette.border;
350
- return {
351
- fill: solid ? hex : mix(hex, baseBg, 20),
352
- stroke: mix(hex, palette.text, 40),
353
- };
354
- };
355
-
356
- // ── Edges ───────────────────────────────────────────────────
357
- const nodeById = new Map(layout.nodes.map((n) => [n.id, n]));
358
- const edgeG = root.append('g').attr('class', 'dgmo-swimlane-edges');
359
- for (const e of layout.edges) {
360
- const tgt = nodeById.get(e.target);
361
- let stroke = palette.textMuted;
362
- let marker = 'sw-arrow';
363
- if (tgt?.shape === 'terminal' && tgt.event === 'error') {
364
- stroke = ev.error;
365
- marker = `sw-arrow-${ev.error.replace('#', '')}`;
366
- } else if (tgt?.shape === 'terminal' && tgt.event === 'success') {
367
- stroke = ev.success;
368
- marker = `sw-arrow-${ev.success.replace('#', '')}`;
369
- }
370
- const d = roundedPolyline(e.points, EDGE_CORNER_R);
371
- edgeG
372
- .append('path')
373
- .attr('d', d)
374
- .attr('fill', 'none')
375
- .attr('stroke', stroke)
376
- .attr('stroke-width', EDGE_STROKE)
377
- .attr('stroke-dasharray', e.back ? '5 4' : null)
378
- .attr('marker-end', `url(#${marker})`)
379
- .attr('data-line-number', String(e.lineNumber));
380
- if (e.label) {
381
- // Place the label at the path's ARC-LENGTH midpoint. Two branches out of
382
- // one gateway share their first segment (the exit port), so a
383
- // longest-segment or first-segment anchor stacks both labels there; the
384
- // arc midpoint sits past the divergence, separating sibling labels.
385
- const segLen: number[] = [];
386
- let total = 0;
387
- for (let k = 0; k < e.points.length - 1; k++) {
388
- const p0 = e.points[k]!;
389
- const p1 = e.points[k + 1]!;
390
- const len = Math.abs(p1.x - p0.x) + Math.abs(p1.y - p0.y);
391
- segLen.push(len);
392
- total += len;
393
- }
394
- let remain = total / 2;
395
- let best = 0;
396
- for (let k = 0; k < segLen.length; k++) {
397
- if (remain <= segLen[k]! || k === segLen.length - 1) {
398
- best = k;
399
- break;
400
- }
401
- remain -= segLen[k]!;
402
- }
403
- const a = e.points[best]!;
404
- const b = e.points[best + 1] ?? a;
405
- const t = segLen[best]! > 0 ? remain / segLen[best]! : 0.5;
406
- const mid = { x: a.x + (b.x - a.x) * t, y: a.y + (b.y - a.y) * t };
407
- // Offset the label PERPENDICULAR to its segment, choosing the side with
408
- // more clearance from OTHER edges. A gateway's two branches share their
409
- // exit, so one branch's riser often runs right beside the other branch's
410
- // straight-line label — picking the clearer side moves the label off it.
411
- const horizontal = Math.abs(b.x - a.x) >= Math.abs(b.y - a.y);
412
- const LABEL_GAP = 7;
413
- const pointClearance = (px: number, py: number): number => {
414
- let m = Infinity;
415
- for (const oe of layout.edges) {
416
- if (oe === e) continue;
417
- for (let k = 0; k < oe.points.length - 1; k++) {
418
- const d = distToSeg(
419
- px,
420
- py,
421
- oe.points[k]!.x,
422
- oe.points[k]!.y,
423
- oe.points[k + 1]!.x,
424
- oe.points[k + 1]!.y
425
- );
426
- if (d < m) m = d;
427
- }
428
- }
429
- return m;
430
- };
431
- // Sample the whole text run, not just the anchor: a label extends ~half
432
- // its width past a centered anchor (or its full width past a start/end
433
- // anchor), and an edge crossing that tail strikes the text even when the
434
- // anchor itself looks clear.
435
- const estW = e.label.length * EDGE_LABEL_FONT * 0.6;
436
- const clearance = (px: number, py: number, spanDir: number): number => {
437
- const xs =
438
- spanDir === 0
439
- ? [px - estW / 2, px, px + estW / 2]
440
- : [px, px + (spanDir * estW) / 2, px + spanDir * estW];
441
- return Math.min(...xs.map((x) => pointClearance(x, py)));
442
- };
443
- const CROWD = 10; // px: only flip off the default side when it's crowded
444
- let tx: number;
445
- let ty: number;
446
- let anchor: string;
447
- let baseline: string;
448
- if (horizontal) {
449
- // Centered anchor: text spans ±estW/2 (spanDir 0).
450
- const up = clearance(mid.x, mid.y - LABEL_GAP, 0);
451
- const down = clearance(mid.x, mid.y + LABEL_GAP, 0);
452
- const below = up < CROWD && down > up;
453
- tx = mid.x;
454
- ty = mid.y + (below ? LABEL_GAP : -LABEL_GAP);
455
- anchor = 'middle';
456
- baseline = below ? 'hanging' : 'auto';
457
- } else {
458
- // Start/end anchor: text spans one full width away from the riser.
459
- const right = clearance(mid.x + LABEL_GAP, mid.y, 1);
460
- const left = clearance(mid.x - LABEL_GAP, mid.y, -1);
461
- const toLeft = right < CROWD && left > right;
462
- tx = mid.x + (toLeft ? -LABEL_GAP : LABEL_GAP);
463
- ty = mid.y;
464
- anchor = toLeft ? 'end' : 'start';
465
- baseline = 'middle';
466
- }
467
- edgeG
468
- .append('text')
469
- .attr('x', tx)
470
- .attr('y', ty)
471
- .attr('text-anchor', anchor)
472
- .attr('dominant-baseline', baseline)
473
- .attr('font-size', EDGE_LABEL_FONT)
474
- .attr('fill', stroke === palette.textMuted ? palette.textMuted : stroke)
475
- .attr('stroke', haloAt(ty))
476
- .attr('stroke-width', 3.5)
477
- .attr('stroke-linejoin', 'round')
478
- .attr('paint-order', 'stroke')
479
- .text(e.label);
480
- }
481
- }
482
-
483
- // ── Nodes ───────────────────────────────────────────────────
484
- const nodesG = root.append('g').attr('class', 'dgmo-swimlane-nodes');
485
- for (const n of layout.nodes) {
486
- const { fill, stroke } = nodeFill(n);
487
- const g = nodesG
488
- .append('g')
489
- .attr('data-line-number', String(n.lineNumber))
490
- // Lane key for baked-CSS cross-highlight (hover a node → dim other lanes).
491
- .attr('data-lane', n.lane);
492
- const cx = n.x;
493
- const cy = n.y;
494
- if (n.shape === 'exclusive' || n.shape === 'parallel') {
495
- const r = n.width / 2;
496
- g.append('polygon')
497
- .attr(
498
- 'points',
499
- `${cx},${cy - r} ${cx + r},${cy} ${cx},${cy + r} ${cx - r},${cy}`
500
- )
501
- .attr('fill', fill)
502
- .attr('stroke', stroke)
503
- .attr('stroke-width', NODE_STROKE);
504
- if (n.shape === 'parallel') {
505
- // `+` glyph.
506
- const g2 = palette.textMuted;
507
- g.append('line')
508
- .attr('x1', cx)
509
- .attr('y1', cy - r / 2.4)
510
- .attr('x2', cx)
511
- .attr('y2', cy + r / 2.4)
512
- .attr('stroke', g2)
513
- .attr('stroke-width', 2.4);
514
- g.append('line')
515
- .attr('x1', cx - r / 2.4)
516
- .attr('y1', cy)
517
- .attr('x2', cx + r / 2.4)
518
- .attr('y2', cy)
519
- .attr('stroke', g2)
520
- .attr('stroke-width', 2.4);
521
- // Label below the diamond.
522
- g.append('text')
523
- .attr('x', cx)
524
- .attr('y', cy + r + 12)
525
- .attr('text-anchor', 'middle')
526
- .attr('font-size', NODE_FONT_SIZE - 1)
527
- .attr('fill', palette.textMuted)
528
- .attr('stroke', haloAt(cy + r + 12))
529
- .attr('stroke-width', 3.5)
530
- .attr('stroke-linejoin', 'round')
531
- .attr('paint-order', 'stroke')
532
- .text(n.label);
533
- } else {
534
- // Halo with the lane background so the diamond outline fades behind a
535
- // label that overflows the narrow gateway.
536
- drawCenteredLabel(
537
- g,
538
- n.label,
539
- cx,
540
- cy,
541
- palette.text,
542
- NODE_FONT_SIZE - 1,
543
- true,
544
- haloAt(cy)
545
- );
546
- }
547
- } else if (n.shape === 'terminal') {
548
- const r = n.width / 2;
549
- g.append('circle')
550
- .attr('cx', cx)
551
- .attr('cy', cy)
552
- .attr('r', r)
553
- .attr('fill', fill)
554
- .attr('stroke', stroke)
555
- .attr('stroke-width', n.event === 'none' ? NODE_STROKE : 2.4);
556
- // Success = double ring; terminate = thick inner ring.
557
- if (n.event === 'success') {
558
- g.append('circle')
559
- .attr('cx', cx)
560
- .attr('cy', cy)
561
- .attr('r', r - 4)
562
- .attr('fill', 'none')
563
- .attr('stroke', stroke)
564
- .attr('stroke-width', 1);
565
- }
566
- // Terminal label goes below the circle by default, but if an edge
567
- // connects on the BOTTOM of the circle (it approaches from the lane
568
- // below) the label would sit on that line — flip it above instead.
569
- let connectsBelow = false;
570
- for (const le of layout.edges) {
571
- const pt =
572
- le.target === n.id
573
- ? le.points[le.points.length - 1]
574
- : le.source === n.id
575
- ? le.points[0]
576
- : undefined;
577
- if (pt && pt.y > cy + r * 0.5) connectsBelow = true;
578
- }
579
- const labelY = connectsBelow ? cy - r - 6 : cy + r + 12;
580
- g.append('text')
581
- .attr('x', cx)
582
- .attr('y', labelY)
583
- .attr('text-anchor', 'middle')
584
- .attr('font-size', NODE_FONT_SIZE - 1)
585
- .attr('fill', palette.text)
586
- .attr('stroke', haloAt(labelY))
587
- .attr('stroke-width', 3.5)
588
- .attr('stroke-linejoin', 'round')
589
- .attr('paint-order', 'stroke')
590
- .text(n.label);
591
- } else {
592
- // task / subprocess rectangle.
593
- g.append('rect')
594
- .attr('x', cx - n.width / 2)
595
- .attr('y', cy - n.height / 2)
596
- .attr('width', n.width)
597
- .attr('height', n.height)
598
- .attr('rx', NODE_RX)
599
- .attr('fill', fill)
600
- .attr('stroke', stroke)
601
- .attr('stroke-width', NODE_STROKE);
602
- if (n.shape === 'subprocess') {
603
- g.append('rect')
604
- .attr('x', cx - n.width / 2 + 3)
605
- .attr('y', cy - n.height / 2 + 3)
606
- .attr('width', n.width - 6)
607
- .attr('height', n.height - 6)
608
- .attr('rx', NODE_RX - 2)
609
- .attr('fill', 'none')
610
- .attr('stroke', stroke)
611
- .attr('stroke-width', 1);
612
- }
613
- const textColor = contrastText(
614
- fill,
615
- palette.textOnFillLight,
616
- palette.textOnFillDark
617
- );
618
- drawCenteredLabel(g, n.label, cx, cy, textColor, NODE_FONT_SIZE);
619
- }
620
- }
621
- }
622
-
623
- /** Draw a one/two-line centered label inside a node. */
624
- /** Greedy word-wrap to a target line length; never splits a single word. */
625
- function wrapWords(words: string[], target: number): string[] {
626
- const lines: string[] = [];
627
- let cur = '';
628
- for (const w of words) {
629
- if (cur === '') cur = w;
630
- else if (cur.length + 1 + w.length <= target) cur += ' ' + w;
631
- else {
632
- lines.push(cur);
633
- cur = w;
634
- }
635
- }
636
- if (cur) lines.push(cur);
637
- return lines;
638
- }
639
-
640
- function drawCenteredLabel(
641
- g: D3G,
642
- label: string,
643
- cx: number,
644
- cy: number,
645
- fill: string,
646
- fontSize: number,
647
- tight = false,
648
- halo?: string
649
- ): void {
650
- const words = label.split(/\s+/);
651
- let lines: string[];
652
- if (tight) {
653
- // Gateways are narrow diamonds — any multi-word label is broken onto
654
- // balanced short lines so it stays inside the shape.
655
- lines = words.length === 1 ? [label] : wrapWords(words, 7);
656
- } else if (label.length <= 14 || words.length === 1) {
657
- lines = [label];
658
- } else {
659
- const mid = Math.ceil(words.length / 2);
660
- lines = [words.slice(0, mid).join(' '), words.slice(mid).join(' ')];
661
- }
662
- const lineH = fontSize * 1.2;
663
- const startY = cy - ((lines.length - 1) * lineH) / 2;
664
- lines.forEach((ln, i) => {
665
- const txt = g
666
- .append('text')
667
- .attr('x', cx)
668
- .attr('y', startY + i * lineH)
669
- .attr('text-anchor', 'middle')
670
- .attr('dominant-baseline', 'middle')
671
- .attr('font-size', fontSize)
672
- .attr('fill', fill);
673
- if (halo)
674
- txt
675
- .attr('stroke', halo)
676
- .attr('stroke-width', 3.5)
677
- .attr('stroke-linejoin', 'round')
678
- .attr('paint-order', 'stroke');
679
- txt.text(ln);
680
- });
681
- }