@diagrammo/dgmo 0.47.0 → 0.48.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/advanced.js +11 -11
- package/dist/auto.js +16 -16
- package/dist/auto.mjs +41 -12
- package/dist/block.js +4 -4
- package/dist/{chunk-FNB3PNGF.js → chunk-7DTWEFUE.js} +3 -3
- package/dist/{chunk-2DXMZRWJ.js → chunk-B65G5DNL.js} +8 -1
- package/dist/{chunk-RZCF2VVM.js → chunk-B7JUCQUA.js} +3 -1
- package/dist/{chunk-DU7W62N5.js → chunk-FANX6UJR.js} +3 -3
- package/dist/{chunk-6FZYCKXB.js → chunk-HSH73DP6.js} +2 -2
- package/dist/{chunk-P4RH4OSP.js → chunk-ILKORZ3H.js} +2 -2
- package/dist/{chunk-Y5QGVTVC.js → chunk-JTSMEYIZ.js} +7 -3
- package/dist/{chunk-RCERWOJR.js → chunk-ODZVHU3R.js} +1 -2
- package/dist/{chunk-DZAQBW35.js → chunk-SGZYXELT.js} +3 -7
- package/dist/{chunk-CUVHQHZ7.js → chunk-THXCFRTU.js} +3 -3
- package/dist/{chunk-IH6ZMVQ5.js → chunk-UC7TFHUM.js} +29 -11
- package/dist/{chunk-EMS2SK26.js → chunk-ZBWMSD5N.js} +3 -1
- package/dist/cli.cjs +15 -15
- package/dist/{dgmo-router-DZCIDUO5.js → dgmo-router-X35VLZLM.js} +3 -3
- package/dist/element.js +16 -16
- package/dist/element.mjs +41 -12
- package/dist/{flowchart-renderer-ZAEOL4DX.js → flowchart-renderer-SNDSII4D.js} +2 -2
- package/dist/index.d.ts +4 -2
- package/dist/index.js +4 -4
- package/dist/{parser-FUSKFGZN.js → parser-2EWECDSA.js} +2 -2
- package/dist/{renderer-UWA5XS74.js → renderer-4JHRUBE6.js} +3 -3
- package/dist/{renderer-WZRREQ2P.js → renderer-5MYBMBYO.js} +2 -2
- package/dist/{renderer-TOCOVLHS.js → renderer-B2OMQIQG.js} +3 -3
- package/dist/{renderer-2XQQQ6QR.js → renderer-N43WR5GV.js} +3 -3
- package/dist/{renderer-T7YKC5VP.js → renderer-ZRNG54CW.js} +3 -3
- package/dist/{state-renderer-RFYHSP4K.js → state-renderer-B4DQBM6M.js} +2 -2
- package/package.json +23 -71
- package/dist/advanced.cjs +0 -71648
- package/dist/advanced.d.cts +0 -5719
- package/dist/auto.cjs +0 -70648
- package/dist/auto.d.cts +0 -54
- package/dist/block.cjs +0 -70208
- package/dist/block.d.cts +0 -115
- package/dist/editor.cjs +0 -526
- package/dist/editor.d.cts +0 -26
- package/dist/element.cjs +0 -70530
- package/dist/element.d.cts +0 -62
- package/dist/highlight.cjs +0 -852
- package/dist/highlight.d.cts +0 -49
- package/dist/index.cjs +0 -70491
- package/dist/index.d.cts +0 -160
- package/dist/pert.cjs +0 -325
- package/dist/pert.d.cts +0 -559
- package/dist/themes-1-CuKpeH.d.cts +0 -908
- package/src/advanced.ts +0 -873
- package/src/arc/parser.ts +0 -15
- package/src/arc/renderer.ts +0 -633
- package/src/auto/index.ts +0 -621
- package/src/auto/shared.ts +0 -370
- package/src/auto/styles.ts +0 -73
- package/src/block/layout.ts +0 -198
- package/src/block/parser.ts +0 -427
- package/src/block/renderer.ts +0 -484
- package/src/block/types.ts +0 -67
- package/src/boxes-and-lines/collapse.ts +0 -96
- package/src/boxes-and-lines/focus.ts +0 -257
- package/src/boxes-and-lines/label-placement.ts +0 -339
- package/src/boxes-and-lines/layout-grouped.ts +0 -912
- package/src/boxes-and-lines/layout-layered.ts +0 -722
- package/src/boxes-and-lines/layout-search.ts +0 -1678
- package/src/boxes-and-lines/layout.ts +0 -398
- package/src/boxes-and-lines/parser.ts +0 -1181
- package/src/boxes-and-lines/renderer.ts +0 -1465
- package/src/boxes-and-lines/types.ts +0 -66
- package/src/c4/layout.ts +0 -2441
- package/src/c4/parser.ts +0 -1158
- package/src/c4/renderer.ts +0 -2008
- package/src/c4/types.ts +0 -89
- package/src/chart-type-registry.ts +0 -529
- package/src/chart-types.ts +0 -225
- package/src/chart.ts +0 -685
- package/src/charts-d3/bar.ts +0 -338
- package/src/charts-d3/chord.ts +0 -140
- package/src/charts-d3/function.ts +0 -147
- package/src/charts-d3/funnel.ts +0 -104
- package/src/charts-d3/heatmap.ts +0 -145
- package/src/charts-d3/index.ts +0 -337
- package/src/charts-d3/interactions.ts +0 -477
- package/src/charts-d3/line.ts +0 -344
- package/src/charts-d3/mount.ts +0 -151
- package/src/charts-d3/pie.ts +0 -117
- package/src/charts-d3/polar.ts +0 -89
- package/src/charts-d3/radar.ts +0 -0
- package/src/charts-d3/sankey.ts +0 -0
- package/src/charts-d3/scatter.ts +0 -185
- package/src/charts-d3/shared.ts +0 -208
- package/src/class/layout.ts +0 -326
- package/src/class/parser.ts +0 -635
- package/src/class/renderer.ts +0 -793
- package/src/class/types.ts +0 -58
- package/src/cli-banner.ts +0 -106
- package/src/cli.ts +0 -1080
- package/src/colors-diagnostics.ts +0 -35
- package/src/colors.ts +0 -379
- package/src/completion-types.ts +0 -27
- package/src/completion.ts +0 -2668
- package/src/cycle/layout.ts +0 -1026
- package/src/cycle/parser.ts +0 -387
- package/src/cycle/renderer.ts +0 -653
- package/src/cycle/types.ts +0 -101
- package/src/d3.ts +0 -1434
- package/src/data-chart-parser.ts +0 -1007
- package/src/dgmo-router.ts +0 -296
- package/src/diagnostics-registry.ts +0 -108
- package/src/diagnostics.ts +0 -0
- package/src/dimensions.ts +0 -20
- package/src/directives-registry.ts +0 -396
- package/src/editor/dgmo.grammar +0 -76
- package/src/editor/dgmo.grammar.d.ts +0 -2
- package/src/editor/dgmo.grammar.js +0 -18
- package/src/editor/dgmo.grammar.terms.d.ts +0 -5
- package/src/editor/dgmo.grammar.terms.js +0 -35
- package/src/editor/highlight-api.ts +0 -596
- package/src/editor/highlight.ts +0 -36
- package/src/editor/index.ts +0 -27
- package/src/editor/keywords.ts +0 -128
- package/src/editor/tokens.ts +0 -30
- package/src/element/index.ts +0 -432
- package/src/embed/css.ts +0 -312
- package/src/embed/escape.ts +0 -15
- package/src/embed/index.ts +0 -280
- package/src/er/classify.ts +0 -228
- package/src/er/layout.ts +0 -530
- package/src/er/parser.ts +0 -750
- package/src/er/renderer.ts +0 -725
- package/src/er/types.ts +0 -52
- package/src/error-card.ts +0 -269
- package/src/event-line/diagnostics.ts +0 -117
- package/src/event-line/parser.ts +0 -643
- package/src/event-line/renderer.ts +0 -1742
- package/src/event-line/types.ts +0 -74
- package/src/fonts.ts +0 -3
- package/src/gantt/calculator.ts +0 -979
- package/src/gantt/parser.ts +0 -1641
- package/src/gantt/renderer.ts +0 -3787
- package/src/gantt/resolver.ts +0 -172
- package/src/gantt/types.ts +0 -206
- package/src/graph/diagnostics.ts +0 -64
- package/src/graph/edge-spline.ts +0 -29
- package/src/graph/flowchart-parser.ts +0 -759
- package/src/graph/flowchart-renderer.ts +0 -817
- package/src/graph/layout.ts +0 -472
- package/src/graph/notes.ts +0 -21
- package/src/graph/state-collapse.ts +0 -78
- package/src/graph/state-parser.ts +0 -574
- package/src/graph/state-renderer.ts +0 -653
- package/src/graph/types.ts +0 -65
- package/src/index.ts +0 -261
- package/src/infra/compute.ts +0 -1385
- package/src/infra/layout.ts +0 -888
- package/src/infra/parser.ts +0 -1234
- package/src/infra/renderer.ts +0 -2534
- package/src/infra/roles.ts +0 -75
- package/src/infra/types.ts +0 -216
- package/src/infra/validation.ts +0 -201
- package/src/journey-map/layout.ts +0 -490
- package/src/journey-map/parser.ts +0 -557
- package/src/journey-map/renderer.ts +0 -1580
- package/src/journey-map/types.ts +0 -47
- package/src/kanban/mutations.ts +0 -175
- package/src/kanban/parser.ts +0 -509
- package/src/kanban/renderer.ts +0 -1449
- package/src/kanban/types.ts +0 -39
- package/src/label-layout.ts +0 -368
- package/src/map/colorize.ts +0 -54
- package/src/map/completion.ts +0 -316
- package/src/map/context-labels.ts +0 -609
- package/src/map/data/PROVENANCE.json +0 -1
- package/src/map/data/README.md +0 -40
- package/src/map/data/airport-collisions.json +0 -1
- package/src/map/data/airports.json +0 -1
- package/src/map/data/gazetteer.json +0 -1
- package/src/map/data/lakes.json +0 -1
- package/src/map/data/mountain-ranges.json +0 -1
- package/src/map/data/na-lakes.json +0 -1
- package/src/map/data/na-land.json +0 -1
- package/src/map/data/region-names.json +0 -1
- package/src/map/data/rivers.json +0 -1
- package/src/map/data/types.ts +0 -121
- package/src/map/data/us-states.json +0 -1
- package/src/map/data/water-bodies.json +0 -1
- package/src/map/data/world-coarse.json +0 -1
- package/src/map/data/world-detail.json +0 -1
- package/src/map/diagnostics.ts +0 -134
- package/src/map/dimensions.ts +0 -150
- package/src/map/geo-query.ts +0 -295
- package/src/map/geo.ts +0 -438
- package/src/map/invert.ts +0 -111
- package/src/map/layout.ts +0 -5069
- package/src/map/legend-band.ts +0 -127
- package/src/map/load-data.ts +0 -186
- package/src/map/parser.ts +0 -783
- package/src/map/renderer.ts +0 -1445
- package/src/map/resolved-types.ts +0 -160
- package/src/map/resolver.ts +0 -1146
- package/src/map/types.ts +0 -194
- package/src/mindmap/collapse.ts +0 -67
- package/src/mindmap/layout.ts +0 -649
- package/src/mindmap/parser.ts +0 -430
- package/src/mindmap/renderer.ts +0 -637
- package/src/mindmap/text-wrap.ts +0 -218
- package/src/mindmap/types.ts +0 -56
- package/src/org/collapse.ts +0 -151
- package/src/org/layout.ts +0 -1262
- package/src/org/parser.ts +0 -519
- package/src/org/renderer.ts +0 -866
- package/src/org/resolver.ts +0 -636
- package/src/palettes/atlas.ts +0 -77
- package/src/palettes/blueprint.ts +0 -73
- package/src/palettes/catppuccin.ts +0 -84
- package/src/palettes/color-utils.ts +0 -443
- package/src/palettes/index.ts +0 -67
- package/src/palettes/nord.ts +0 -67
- package/src/palettes/registry.ts +0 -114
- package/src/palettes/slate.ts +0 -73
- package/src/palettes/tidewater.ts +0 -73
- package/src/palettes/tokyo-night.ts +0 -86
- package/src/palettes/types.ts +0 -88
- package/src/pert/analyzer.ts +0 -1183
- package/src/pert/diagnostics.ts +0 -110
- package/src/pert/index.ts +0 -43
- package/src/pert/internal.ts +0 -252
- package/src/pert/layout.ts +0 -971
- package/src/pert/monte-carlo.ts +0 -446
- package/src/pert/parser.ts +0 -1606
- package/src/pert/renderer.ts +0 -4009
- package/src/pert/share-normalize.ts +0 -62
- package/src/pert/types.ts +0 -604
- package/src/pyramid/parser.ts +0 -185
- package/src/pyramid/renderer.ts +0 -768
- package/src/pyramid/types.ts +0 -28
- package/src/quadrant/parser.ts +0 -18
- package/src/quadrant/renderer.ts +0 -638
- package/src/raci/diagnostics.ts +0 -213
- package/src/raci/index.ts +0 -23
- package/src/raci/mutations.ts +0 -321
- package/src/raci/parser.ts +0 -699
- package/src/raci/renderer.ts +0 -1627
- package/src/raci/types.ts +0 -82
- package/src/raci/variants.ts +0 -320
- package/src/render.ts +0 -268
- package/src/ring/parser.ts +0 -212
- package/src/ring/renderer.ts +0 -490
- package/src/ring/types.ts +0 -27
- package/src/sequence/collapse.ts +0 -174
- package/src/sequence/parser.ts +0 -1596
- package/src/sequence/participant-inference.ts +0 -152
- package/src/sequence/renderer.ts +0 -3168
- package/src/sequence/tag-resolution.ts +0 -164
- package/src/sharing.ts +0 -211
- package/src/sitemap/collapse.ts +0 -100
- package/src/sitemap/layout.ts +0 -925
- package/src/sitemap/parser.ts +0 -755
- package/src/sitemap/renderer.ts +0 -828
- package/src/sitemap/types.ts +0 -42
- package/src/slope/parser.ts +0 -18
- package/src/slope/renderer.ts +0 -378
- package/src/swimlane/diagnostics.ts +0 -94
- package/src/swimlane/layout.ts +0 -597
- package/src/swimlane/parser.ts +0 -613
- package/src/swimlane/renderer.ts +0 -681
- package/src/swimlane/types.ts +0 -151
- package/src/tech-radar/interactive.ts +0 -1129
- package/src/tech-radar/layout.ts +0 -197
- package/src/tech-radar/parser.ts +0 -436
- package/src/tech-radar/renderer.ts +0 -1239
- package/src/tech-radar/shared.ts +0 -151
- package/src/tech-radar/types.ts +0 -86
- package/src/themes.ts +0 -22
- package/src/timeline/parser.ts +0 -472
- package/src/timeline/renderer.ts +0 -3024
- package/src/timeline/types.ts +0 -33
- package/src/timeline/viz-parser.ts +0 -21
- package/src/treemap/diagnostics.ts +0 -62
- package/src/treemap/layout-radial.ts +0 -233
- package/src/treemap/layout.ts +0 -189
- package/src/treemap/parser.ts +0 -463
- package/src/treemap/renderer-radial.ts +0 -438
- package/src/treemap/renderer.ts +0 -572
- package/src/treemap/treemap-shared.ts +0 -309
- package/src/treemap/types.ts +0 -60
- package/src/utils/arrow-markers.ts +0 -51
- package/src/utils/arrows-diagnostics.ts +0 -62
- package/src/utils/arrows.ts +0 -227
- package/src/utils/brand.ts +0 -61
- package/src/utils/card.ts +0 -183
- package/src/utils/collapse-engine/tree-edges.ts +0 -115
- package/src/utils/collapse-engine/tree.ts +0 -117
- package/src/utils/d3-helpers.ts +0 -219
- package/src/utils/d3-types.ts +0 -9
- package/src/utils/description-helpers.ts +0 -38
- package/src/utils/duration.ts +0 -312
- package/src/utils/export-container.ts +0 -41
- package/src/utils/expr-eval.ts +0 -27
- package/src/utils/fit-canvas.ts +0 -64
- package/src/utils/hover-styles.ts +0 -673
- package/src/utils/inline-markdown.ts +0 -96
- package/src/utils/legend-constants.ts +0 -48
- package/src/utils/legend-d3.ts +0 -618
- package/src/utils/legend-integration.ts +0 -107
- package/src/utils/legend-layout.ts +0 -776
- package/src/utils/legend-svg.ts +0 -214
- package/src/utils/legend-types.ts +0 -272
- package/src/utils/name-normalize.ts +0 -163
- package/src/utils/note-box/constants.ts +0 -25
- package/src/utils/note-box/index.ts +0 -11
- package/src/utils/note-box/metrics.ts +0 -90
- package/src/utils/note-box/svg.ts +0 -331
- package/src/utils/notes/bounds.ts +0 -30
- package/src/utils/notes/build.ts +0 -131
- package/src/utils/notes/index.ts +0 -18
- package/src/utils/notes/model.ts +0 -19
- package/src/utils/notes/parse.ts +0 -131
- package/src/utils/notes/place.ts +0 -177
- package/src/utils/notes/resolve.ts +0 -88
- package/src/utils/number-format.ts +0 -36
- package/src/utils/parsing.ts +0 -953
- package/src/utils/reserved-key-registry.ts +0 -239
- package/src/utils/safe-href.ts +0 -38
- package/src/utils/scaling.ts +0 -97
- package/src/utils/svg-embed.ts +0 -251
- package/src/utils/tag-groups.ts +0 -607
- package/src/utils/text-measure.ts +0 -122
- package/src/utils/time-ticks.ts +0 -253
- package/src/utils/title-constants.ts +0 -24
- package/src/utils/visual-conventions.ts +0 -61
- package/src/utils/wrapped-desc.ts +0 -87
- package/src/venn/parser.ts +0 -18
- package/src/venn/renderer.ts +0 -1047
- package/src/version-control/diagnostics.ts +0 -67
- package/src/version-control/parser.ts +0 -544
- package/src/version-control/renderer.ts +0 -724
- package/src/version-control/types.ts +0 -100
- package/src/visualizations/parse.ts +0 -1560
- package/src/visualizations/types.ts +0 -262
- package/src/wireframe/layout.ts +0 -479
- package/src/wireframe/parser.ts +0 -1114
- package/src/wireframe/renderer.ts +0 -1318
- package/src/wireframe/types.ts +0 -110
- package/src/wordcloud/parser.ts +0 -18
- package/src/wordcloud/renderer.ts +0 -357
package/src/swimlane/renderer.ts
DELETED
|
@@ -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
|
-
}
|