@diagrammo/dgmo 0.30.0 → 0.31.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 (41) hide show
  1. package/README.md +21 -3
  2. package/dist/advanced.cjs +560 -269
  3. package/dist/advanced.d.cts +27 -2
  4. package/dist/advanced.d.ts +27 -2
  5. package/dist/advanced.js +559 -269
  6. package/dist/auto.cjs +558 -270
  7. package/dist/auto.js +93 -93
  8. package/dist/auto.mjs +558 -270
  9. package/dist/cli.cjs +144 -143
  10. package/dist/index.cjs +557 -269
  11. package/dist/index.js +557 -269
  12. package/package.json +1 -1
  13. package/src/advanced.ts +3 -0
  14. package/src/boxes-and-lines/layout-search.ts +214 -0
  15. package/src/boxes-and-lines/layout.ts +4 -0
  16. package/src/boxes-and-lines/parser.ts +78 -0
  17. package/src/boxes-and-lines/renderer.ts +57 -5
  18. package/src/boxes-and-lines/types.ts +9 -0
  19. package/src/c4/renderer.ts +7 -5
  20. package/src/chart-types.ts +2 -2
  21. package/src/class/renderer.ts +4 -2
  22. package/src/cli-banner.ts +107 -0
  23. package/src/cli.ts +13 -0
  24. package/src/colors.ts +22 -0
  25. package/src/er/renderer.ts +4 -2
  26. package/src/graph/flowchart-renderer.ts +4 -2
  27. package/src/graph/state-renderer.ts +4 -2
  28. package/src/infra/renderer.ts +8 -4
  29. package/src/journey-map/parser.ts +15 -1
  30. package/src/journey-map/renderer.ts +1 -1
  31. package/src/kanban/renderer.ts +1 -1
  32. package/src/map/renderer.ts +27 -14
  33. package/src/mindmap/renderer.ts +5 -3
  34. package/src/org/renderer.ts +67 -120
  35. package/src/palettes/color-utils.ts +7 -2
  36. package/src/pert/renderer.ts +13 -8
  37. package/src/raci/renderer.ts +1 -1
  38. package/src/sitemap/renderer.ts +35 -37
  39. package/src/utils/card.ts +183 -0
  40. package/src/utils/tag-groups.ts +10 -10
  41. package/src/utils/visual-conventions.ts +61 -0
@@ -23,6 +23,7 @@ import {
23
23
  import { renderIntegratedLegend } from '../utils/legend-integration';
24
24
  import { getMaxLegendReservedHeight } from '../utils/legend-layout';
25
25
  import { ScaleContext } from '../utils/scaling';
26
+ import { renderNodeCard } from '../utils/card';
26
27
 
27
28
  // ============================================================
28
29
  // Constants
@@ -32,26 +33,28 @@ const DIAGRAM_PADDING = 20;
32
33
  const MAX_SCALE = 3;
33
34
  import { TITLE_FONT_SIZE, TITLE_FONT_WEIGHT } from '../utils/title-constants';
34
35
  const TITLE_HEIGHT = 30;
35
- const LABEL_FONT_SIZE = 13;
36
- const META_FONT_SIZE = 11;
37
- const META_LINE_HEIGHT = 16;
38
- const HEADER_HEIGHT = 28;
39
- const SEPARATOR_GAP = 6;
40
- const EDGE_STROKE_WIDTH = 1.5;
41
- const NODE_STROKE_WIDTH = 1.5;
42
- const CARD_RADIUS = 6;
43
- const CONTAINER_RADIUS = 8;
44
- const CONTAINER_LABEL_FONT_SIZE = 13;
45
- const CONTAINER_META_FONT_SIZE = 11;
46
- const CONTAINER_META_LINE_HEIGHT = 16;
47
- const CONTAINER_HEADER_HEIGHT = 28;
36
+ // Shared card / group / collapse constants (Story 111.1). sitemap matches every
37
+ // convention default, so it imports the full set.
38
+ import {
39
+ LABEL_FONT_SIZE,
40
+ META_FONT_SIZE,
41
+ META_LINE_HEIGHT,
42
+ HEADER_HEIGHT,
43
+ SEPARATOR_GAP,
44
+ EDGE_STROKE_WIDTH,
45
+ NODE_STROKE_WIDTH,
46
+ CARD_RADIUS,
47
+ CONTAINER_RADIUS,
48
+ CONTAINER_LABEL_FONT_SIZE,
49
+ CONTAINER_META_FONT_SIZE,
50
+ CONTAINER_META_LINE_HEIGHT,
51
+ CONTAINER_HEADER_HEIGHT,
52
+ COLLAPSE_BAR_HEIGHT,
53
+ } from '../utils/visual-conventions';
48
54
  const ARROWHEAD_W = 10;
49
55
  const ARROWHEAD_H = 7;
50
56
  const EDGE_LABEL_FONT_SIZE = 11;
51
57
 
52
- // Collapsed-node accent bar
53
- const COLLAPSE_BAR_HEIGHT = 6;
54
-
55
58
  const LEGEND_FIXED_GAP = 8; // gap between fixed legend and scaled diagram — local, not shared
56
59
 
57
60
  // ============================================================
@@ -492,32 +495,27 @@ export function renderSitemap(
492
495
  const fill = nodeFill(palette, isDark, node.color, solid);
493
496
  const stroke = nodeStroke(palette, node.color);
494
497
 
495
- // Card background
496
- nodeG
497
- .append('rect')
498
- .attr('x', 0)
499
- .attr('y', 0)
500
- .attr('width', node.width)
501
- .attr('height', node.height)
502
- .attr('rx', CARD_RADIUS)
503
- .attr('fill', fill)
504
- .attr('stroke', stroke)
505
- .attr('stroke-width', sNodeStrokeWidth);
506
-
498
+ // Card background + label via the shared card door (Story 111.1). sitemap's
499
+ // metadata, description lines, and collapse bar diverge from the convention
500
+ // (different meta baseline; semi-transparent collapse bar + "+N" badge), so
501
+ // they stay inline below — see conventions §1/§3 deviations log.
507
502
  const labelColor = contrastText(
508
503
  fill,
509
504
  palette.textOnFillLight,
510
505
  palette.textOnFillDark
511
506
  );
512
- nodeG
513
- .append('text')
514
- .attr('x', node.width / 2)
515
- .attr('y', sHeaderHeight / 2 + sLabelFontSize / 2 - 2)
516
- .attr('text-anchor', 'middle')
517
- .attr('fill', labelColor)
518
- .attr('font-size', sLabelFontSize)
519
- .attr('font-weight', 'bold')
520
- .text(node.label);
507
+ renderNodeCard(nodeG, {
508
+ width: node.width,
509
+ height: node.height,
510
+ rx: CARD_RADIUS,
511
+ fill,
512
+ stroke,
513
+ strokeWidth: sNodeStrokeWidth,
514
+ label: node.label,
515
+ labelColor,
516
+ labelFontSize: sLabelFontSize,
517
+ headerHeight: sHeaderHeight,
518
+ });
521
519
 
522
520
  const metaEntries = Object.entries(node.metadata);
523
521
  if (metaEntries.length > 0) {
@@ -0,0 +1,183 @@
1
+ // ============================================================
2
+ // Node-card door (Story 111.1)
3
+ // ============================================================
4
+ //
5
+ // The node card described in docs/architecture/diagram-visual-conventions.md §1
6
+ // (rect → label → header separator → metadata rows) and the collapse accent bar
7
+ // (§3) were hand-rolled inline in every structured-diagram renderer. This module
8
+ // owns that drawing behind one interface, mirroring the `renderIntegratedLegend`
9
+ // door (utils/legend-integration.ts): the renderer creates its wrapper `<g>` and
10
+ // resolves geometry + colors (scaling and palette stay the renderer's job — see
11
+ // ScaleContext / 111.3), then makes one call.
12
+ //
13
+ // Output is byte-identical to the prior inline code: same element order, same
14
+ // attributes, same values. Scaled numbers are passed IN (the door never calls
15
+ // ScaleContext), so rounding order is unchanged.
16
+
17
+ import type { D3Sel } from './legend-types';
18
+ import { measureText } from './text-measure';
19
+
20
+ /** One resolved metadata row: a display key (already mapped) and its value. */
21
+ export type CardMetaRow = readonly [displayKey: string, value: string];
22
+
23
+ export interface CardMetaOptions {
24
+ /** Rows in display order. */
25
+ rows: readonly CardMetaRow[];
26
+ /** Scaled metadata font size. */
27
+ fontSize: number;
28
+ /** Scaled metadata line height. */
29
+ lineHeight: number;
30
+ /** Scaled gap between the header separator and the first row baseline. */
31
+ separatorGap: number;
32
+ /** Header separator stroke color (callers pass `solid ? labelColor : stroke`). */
33
+ separatorColor: string;
34
+ /** Fill for both key and value text (node cards use the label color for both). */
35
+ textColor: string;
36
+ /** Left inset for the key column (defaults to 10, the convention value). */
37
+ keyX?: number;
38
+ }
39
+
40
+ export interface NodeCardOptions {
41
+ /** Card width (already laid out). */
42
+ width: number;
43
+ /** Card height (already laid out). */
44
+ height: number;
45
+ /** Scaled corner radius. */
46
+ rx: number;
47
+ /** Resolved card fill. */
48
+ fill: string;
49
+ /** Resolved card stroke. */
50
+ stroke: string;
51
+ /** Scaled stroke width. */
52
+ strokeWidth: number;
53
+ /** Dashed border (e.g. org `isContainer`); uses the `'6 3'` convention pattern. */
54
+ dashed?: boolean;
55
+ /** Label text. */
56
+ label: string;
57
+ /** Resolved label color (also used for metadata text). */
58
+ labelColor: string;
59
+ /** Scaled label font size. */
60
+ labelFontSize: number;
61
+ /** Scaled header band height (positions the label and the separator). */
62
+ headerHeight: number;
63
+ /** Metadata block; omit (or pass empty rows) for a card with no separator/meta. */
64
+ meta?: CardMetaOptions;
65
+ }
66
+
67
+ /**
68
+ * Draw a node card (rect → label → optional separator + metadata rows) into
69
+ * `container` (the renderer's wrapper `<g>`). Reproduces conventions §1 exactly.
70
+ */
71
+ export function renderNodeCard(container: D3Sel, opts: NodeCardOptions): void {
72
+ const rect = container
73
+ .append('rect')
74
+ .attr('x', 0)
75
+ .attr('y', 0)
76
+ .attr('width', opts.width)
77
+ .attr('height', opts.height)
78
+ .attr('rx', opts.rx)
79
+ .attr('fill', opts.fill)
80
+ .attr('stroke', opts.stroke)
81
+ .attr('stroke-width', opts.strokeWidth);
82
+
83
+ if (opts.dashed) {
84
+ rect.attr('stroke-dasharray', '6 3');
85
+ }
86
+
87
+ container
88
+ .append('text')
89
+ .attr('x', opts.width / 2)
90
+ .attr('y', opts.headerHeight / 2 + opts.labelFontSize / 2 - 2)
91
+ .attr('text-anchor', 'middle')
92
+ .attr('fill', opts.labelColor)
93
+ .attr('font-size', opts.labelFontSize)
94
+ .attr('font-weight', 'bold')
95
+ .text(opts.label);
96
+
97
+ const meta = opts.meta;
98
+ if (!meta || meta.rows.length === 0) return;
99
+
100
+ container
101
+ .append('line')
102
+ .attr('x1', 0)
103
+ .attr('y1', opts.headerHeight)
104
+ .attr('x2', opts.width)
105
+ .attr('y2', opts.headerHeight)
106
+ .attr('stroke', meta.separatorColor)
107
+ .attr('stroke-opacity', 0.3)
108
+ .attr('stroke-width', 1);
109
+
110
+ const keyX = meta.keyX ?? 10;
111
+ const maxKeyWidth = Math.max(
112
+ ...meta.rows.map(([key]) => measureText(`${key}: `, meta.fontSize))
113
+ );
114
+ const valueX = keyX + maxKeyWidth;
115
+ const metaStartY = opts.headerHeight + meta.separatorGap + meta.fontSize;
116
+
117
+ for (let i = 0; i < meta.rows.length; i++) {
118
+ const [displayKey, value] = meta.rows[i]!;
119
+ const rowY = metaStartY + i * meta.lineHeight;
120
+
121
+ container
122
+ .append('text')
123
+ .attr('x', keyX)
124
+ .attr('y', rowY)
125
+ .attr('fill', meta.textColor)
126
+ .attr('font-size', meta.fontSize)
127
+ .text(`${displayKey}: `);
128
+
129
+ container
130
+ .append('text')
131
+ .attr('x', valueX)
132
+ .attr('y', rowY)
133
+ .attr('fill', meta.textColor)
134
+ .attr('font-size', meta.fontSize)
135
+ .text(value);
136
+ }
137
+ }
138
+
139
+ export interface CollapseBarOptions {
140
+ /** Card width. */
141
+ width: number;
142
+ /** Card height (the bar sits at the bottom). */
143
+ height: number;
144
+ /** Scaled bar height. */
145
+ barHeight: number;
146
+ /** Scaled horizontal inset from each edge (usually 0). */
147
+ inset: number;
148
+ /** Scaled corner radius for the clip rect (matches the card). */
149
+ rx: number;
150
+ /** Bar fill (callers pass `solid ? labelColor : stroke`). */
151
+ fill: string;
152
+ /** Unique clip-path id (kept renderer-local to avoid collisions). */
153
+ clipId: string;
154
+ /** Wrapper class for the bar rect (e.g. `org-collapse-bar`). */
155
+ className: string;
156
+ }
157
+
158
+ /**
159
+ * Draw the collapse accent bar (conventions §3) at the bottom of a card,
160
+ * clipped to the card's rounded corners. Reproduces the prior inline code.
161
+ */
162
+ export function renderCollapseBar(
163
+ container: D3Sel,
164
+ opts: CollapseBarOptions
165
+ ): void {
166
+ container
167
+ .append('clipPath')
168
+ .attr('id', opts.clipId)
169
+ .append('rect')
170
+ .attr('width', opts.width)
171
+ .attr('height', opts.height)
172
+ .attr('rx', opts.rx);
173
+
174
+ container
175
+ .append('rect')
176
+ .attr('x', opts.inset)
177
+ .attr('y', opts.height - opts.barHeight)
178
+ .attr('width', opts.width - opts.inset * 2)
179
+ .attr('height', opts.barHeight)
180
+ .attr('fill', opts.fill)
181
+ .attr('clip-path', `url(#${opts.clipId})`)
182
+ .attr('class', opts.className);
183
+ }
@@ -9,7 +9,11 @@ import {
9
9
  tagShorthandRemovedMessage,
10
10
  type DgmoError,
11
11
  } from '../diagnostics';
12
- import { RECOGNIZED_COLOR_NAMES, resolveColor } from '../colors';
12
+ import {
13
+ CATEGORICAL_COLOR_ORDER,
14
+ RECOGNIZED_COLOR_NAMES,
15
+ resolveColor,
16
+ } from '../colors';
13
17
  import type { PaletteColors } from '../palettes/types';
14
18
  import type { Writable } from './brand';
15
19
 
@@ -91,16 +95,12 @@ export const AUTO_TAG_COLOR_SENTINEL = '';
91
95
 
92
96
  /**
93
97
  * The categorical name cycle used to auto-assign colors to bare tag values,
94
- * in deterministic order. Drawn from `RECOGNIZED_COLOR_NAMES` but excludes
95
- * the non-categorical neutrals (`gray`/`black`/`white`) so auto-picked
96
- * colors are visually distinct legend swatches. If a group has more
97
- * colorless entries than free categorical names, the cycle wraps.
98
+ * in deterministic order. Aliased to the shared {@link CATEGORICAL_COLOR_ORDER}
99
+ * (RGB-seeded, max-contrast, neutrals excluded) so tag swatches and data-chart
100
+ * series colors share one canonical rotation. If a group has more colorless
101
+ * entries than free categorical names, the cycle wraps.
98
102
  */
99
- export const autoTagColorCycle: readonly string[] = Object.freeze(
100
- RECOGNIZED_COLOR_NAMES.filter(
101
- (n) => n !== 'gray' && n !== 'black' && n !== 'white'
102
- )
103
- );
103
+ export const autoTagColorCycle: readonly string[] = CATEGORICAL_COLOR_ORDER;
104
104
 
105
105
  /**
106
106
  * Finalize a tag group's auto-color assignment.
@@ -0,0 +1,61 @@
1
+ // ============================================================
2
+ // Visual conventions — shared card/group/collapse constants (Story 111.1)
3
+ // ============================================================
4
+ //
5
+ // Single source of truth for the structured-diagram visual constants described
6
+ // in docs/architecture/diagram-visual-conventions.md. Before this module these
7
+ // values were re-declared in every renderer (NODE_STROKE_WIDTH in 13 files,
8
+ // HEADER_HEIGHT in 12, ...), so a convention change meant editing N files and
9
+ // hoping the snapshots agreed.
10
+ //
11
+ // Two tiers, because the renderers do NOT all agree:
12
+ //
13
+ // 1. UNIVERSAL — identical at every call site. Import these everywhere; never
14
+ // re-declare. Changing one of these is a deliberate cross-chart change.
15
+ //
16
+ // 2. CONVENTION DEFAULTS — the org/sitemap baseline from the convention. Most
17
+ // card-shaped renderers match these and should import them. A few have a
18
+ // DOCUMENTED intentional deviation and keep a local override (with a
19
+ // comment saying why):
20
+ // - infra: META_FONT_SIZE 10 / META_LINE_HEIGHT 14 (denser meta rows)
21
+ // - boxes-and-lines: COLLAPSE_BAR_HEIGHT 4 / SEPARATOR_GAP 4
22
+ // - cycle, raci: HEADER_HEIGHT 36 (not label+meta cards)
23
+ // Those overrides are the exception that proves the rule — they stay local
24
+ // and visible, not hidden inside a re-declared full constant set.
25
+
26
+ // ── Universal (same value at every site) ──
27
+
28
+ /** Node card + group border stroke width. */
29
+ export const NODE_STROKE_WIDTH = 1.5;
30
+ /** Edge / connector stroke width. */
31
+ export const EDGE_STROKE_WIDTH = 1.5;
32
+ /** Node card corner radius. */
33
+ export const CARD_RADIUS = 6;
34
+ /** Group / container corner radius. */
35
+ export const CONTAINER_RADIUS = 8;
36
+ /** Collapse accent bar horizontal inset from the card edges. */
37
+ export const COLLAPSE_BAR_INSET = 0;
38
+
39
+ // ── Convention defaults (org/sitemap baseline; see deviations above) ──
40
+
41
+ /** Node card header band height. */
42
+ export const HEADER_HEIGHT = 28;
43
+ /** Node card label font size. */
44
+ export const LABEL_FONT_SIZE = 13;
45
+ /** Node card metadata-row font size. */
46
+ export const META_FONT_SIZE = 11;
47
+ /** Node card metadata-row line height. */
48
+ export const META_LINE_HEIGHT = 16;
49
+ /** Gap between the header separator and the first metadata row. */
50
+ export const SEPARATOR_GAP = 6;
51
+ /** Collapse accent bar height. */
52
+ export const COLLAPSE_BAR_HEIGHT = 6;
53
+
54
+ /** Group / container reserved header band height. */
55
+ export const CONTAINER_HEADER_HEIGHT = 28;
56
+ /** Group / container label font size. */
57
+ export const CONTAINER_LABEL_FONT_SIZE = 13;
58
+ /** Group / container metadata-row font size. */
59
+ export const CONTAINER_META_FONT_SIZE = 11;
60
+ /** Group / container metadata-row line height. */
61
+ export const CONTAINER_META_LINE_HEIGHT = 16;