@diagrammo/dgmo 0.26.0 → 0.27.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 (136) hide show
  1. package/README.md +3 -3
  2. package/dist/advanced.cjs +4182 -2704
  3. package/dist/advanced.d.cts +266 -58
  4. package/dist/advanced.d.ts +266 -58
  5. package/dist/advanced.js +4182 -2698
  6. package/dist/auto.cjs +4042 -2581
  7. package/dist/auto.js +124 -122
  8. package/dist/auto.mjs +4042 -2581
  9. package/dist/cli.cjs +172 -170
  10. package/dist/editor.cjs +4 -0
  11. package/dist/editor.js +4 -0
  12. package/dist/highlight.cjs +4 -0
  13. package/dist/highlight.js +4 -0
  14. package/dist/index.cjs +4067 -2583
  15. package/dist/index.d.cts +33 -8
  16. package/dist/index.d.ts +33 -8
  17. package/dist/index.js +4067 -2583
  18. package/dist/internal.cjs +4182 -2704
  19. package/dist/internal.d.cts +266 -58
  20. package/dist/internal.d.ts +266 -58
  21. package/dist/internal.js +4182 -2698
  22. package/dist/map-data/PROVENANCE.json +1 -1
  23. package/dist/map-data/airport-collisions.json +1 -0
  24. package/dist/map-data/airports.json +1 -0
  25. package/docs/language-reference.md +68 -18
  26. package/gallery/fixtures/boxes-and-lines-diverging.dgmo +15 -0
  27. package/gallery/fixtures/map-choropleth-diverging.dgmo +9 -0
  28. package/gallery/fixtures/map-region-values.dgmo +13 -0
  29. package/gallery/fixtures/map-subnational-zoom.dgmo +12 -0
  30. package/gallery/fixtures/map-tagged-legs.dgmo +16 -0
  31. package/gallery/fixtures/map-undirected-edges.dgmo +12 -0
  32. package/package.json +1 -1
  33. package/src/advanced.ts +1 -6
  34. package/src/auto/index.ts +1 -1
  35. package/src/boxes-and-lines/layout.ts +146 -26
  36. package/src/boxes-and-lines/parser.ts +43 -8
  37. package/src/boxes-and-lines/renderer.ts +223 -96
  38. package/src/boxes-and-lines/types.ts +9 -2
  39. package/src/c4/layout.ts +14 -32
  40. package/src/c4/parser.ts +9 -5
  41. package/src/c4/renderer.ts +34 -39
  42. package/src/class/layout.ts +118 -18
  43. package/src/class/parser.ts +35 -0
  44. package/src/class/renderer.ts +58 -2
  45. package/src/class/types.ts +3 -0
  46. package/src/cli.ts +4 -4
  47. package/src/completion.ts +26 -12
  48. package/src/cycle/layout.ts +55 -72
  49. package/src/cycle/renderer.ts +11 -6
  50. package/src/d3.ts +78 -117
  51. package/src/diagnostics.ts +16 -0
  52. package/src/echarts.ts +46 -33
  53. package/src/editor/keywords.ts +4 -0
  54. package/src/er/layout.ts +114 -22
  55. package/src/er/parser.ts +28 -0
  56. package/src/er/renderer.ts +55 -2
  57. package/src/er/types.ts +3 -0
  58. package/src/gantt/renderer.ts +46 -38
  59. package/src/gantt/resolver.ts +9 -2
  60. package/src/graph/edge-spline.ts +29 -0
  61. package/src/graph/flowchart-parser.ts +34 -1
  62. package/src/graph/flowchart-renderer.ts +78 -64
  63. package/src/graph/layout.ts +206 -23
  64. package/src/graph/notes.ts +21 -0
  65. package/src/graph/state-parser.ts +26 -1
  66. package/src/graph/state-renderer.ts +78 -64
  67. package/src/graph/types.ts +13 -0
  68. package/src/index.ts +1 -1
  69. package/src/infra/layout.ts +46 -26
  70. package/src/infra/renderer.ts +16 -7
  71. package/src/journey-map/layout.ts +38 -49
  72. package/src/journey-map/renderer.ts +22 -45
  73. package/src/kanban/renderer.ts +15 -6
  74. package/src/label-layout.ts +3 -3
  75. package/src/map/completion.ts +77 -22
  76. package/src/map/context-labels.ts +57 -12
  77. package/src/map/data/PROVENANCE.json +1 -1
  78. package/src/map/data/airport-collisions.json +1 -0
  79. package/src/map/data/airports.json +1 -0
  80. package/src/map/data/types.ts +19 -0
  81. package/src/map/layout.ts +1196 -90
  82. package/src/map/legend-band.ts +2 -2
  83. package/src/map/load-data.ts +10 -1
  84. package/src/map/parser.ts +61 -32
  85. package/src/map/renderer.ts +284 -12
  86. package/src/map/resolved-types.ts +15 -1
  87. package/src/map/resolver.ts +132 -12
  88. package/src/map/types.ts +28 -8
  89. package/src/migrate/embedded.ts +9 -7
  90. package/src/mindmap/text-wrap.ts +13 -14
  91. package/src/org/layout.ts +19 -17
  92. package/src/org/renderer.ts +11 -4
  93. package/src/palettes/color-utils.ts +82 -21
  94. package/src/palettes/index.ts +0 -19
  95. package/src/palettes/registry.ts +1 -1
  96. package/src/palettes/types.ts +2 -2
  97. package/src/pert/layout.ts +48 -40
  98. package/src/pert/renderer.ts +30 -43
  99. package/src/pyramid/renderer.ts +4 -5
  100. package/src/raci/renderer.ts +34 -68
  101. package/src/render.ts +1 -1
  102. package/src/ring/renderer.ts +1 -2
  103. package/src/sequence/parser.ts +100 -22
  104. package/src/sequence/renderer.ts +75 -50
  105. package/src/sitemap/layout.ts +27 -19
  106. package/src/sitemap/renderer.ts +12 -5
  107. package/src/tech-radar/renderer.ts +11 -35
  108. package/src/utils/arrow-markers.ts +51 -0
  109. package/src/utils/fit-canvas.ts +64 -0
  110. package/src/utils/legend-constants.ts +8 -54
  111. package/src/utils/legend-d3.ts +10 -7
  112. package/src/utils/legend-layout.ts +7 -4
  113. package/src/utils/legend-types.ts +10 -4
  114. package/src/utils/note-box/constants.ts +25 -0
  115. package/src/utils/note-box/index.ts +11 -0
  116. package/src/utils/note-box/metrics.ts +90 -0
  117. package/src/utils/note-box/svg.ts +331 -0
  118. package/src/utils/notes/bounds.ts +30 -0
  119. package/src/utils/notes/build.ts +131 -0
  120. package/src/utils/notes/index.ts +18 -0
  121. package/src/utils/notes/model.ts +19 -0
  122. package/src/utils/notes/parse.ts +131 -0
  123. package/src/utils/notes/place.ts +177 -0
  124. package/src/utils/notes/resolve.ts +88 -0
  125. package/src/utils/number-format.ts +36 -0
  126. package/src/utils/parsing.ts +41 -0
  127. package/src/utils/reserved-key-registry.ts +4 -0
  128. package/src/utils/text-measure.ts +122 -0
  129. package/src/wireframe/layout.ts +4 -2
  130. package/src/wireframe/renderer.ts +8 -6
  131. package/src/palettes/dracula.ts +0 -68
  132. package/src/palettes/gruvbox.ts +0 -85
  133. package/src/palettes/monokai.ts +0 -68
  134. package/src/palettes/one-dark.ts +0 -70
  135. package/src/palettes/rose-pine.ts +0 -84
  136. package/src/palettes/solarized.ts +0 -77
@@ -38,8 +38,8 @@ export function mapLegendGroups(legend: MapLayoutLegend): LegendGroupData[] {
38
38
  gradient: {
39
39
  min: ramp.min,
40
40
  max: ramp.max,
41
- hue: ramp.hue,
42
- base: ramp.base,
41
+ low: ramp.low,
42
+ high: ramp.high,
43
43
  },
44
44
  }
45
45
  : null;
@@ -18,7 +18,12 @@
18
18
  // `await import('jsdom')` seam in render.ts. The web build injects `MapData` via
19
19
  // DI and never calls `loadMapData`, so the dynamic import only runs in Node.
20
20
  import type { MapData } from './resolved-types';
21
- import type { BoundaryTopology, Gazetteer, WaterBodies } from './data/types';
21
+ import type {
22
+ BoundaryTopology,
23
+ Gazetteer,
24
+ WaterBodies,
25
+ AirportData,
26
+ } from './data/types';
22
27
 
23
28
  type NodeBuiltins = {
24
29
  readFile: typeof import('node:fs/promises').readFile;
@@ -47,6 +52,7 @@ const FILES = {
47
52
  naLand: 'na-land.json',
48
53
  naLakes: 'na-lakes.json',
49
54
  waterBodies: 'water-bodies.json',
55
+ airports: 'airports.json',
50
56
  gazetteer: 'gazetteer.json',
51
57
  } as const;
52
58
 
@@ -137,6 +143,7 @@ export function loadMapData(): Promise<MapData> {
137
143
  naLand,
138
144
  naLakes,
139
145
  waterBodies,
146
+ airports,
140
147
  gazetteer,
141
148
  ] = await Promise.all([
142
149
  // worldCoarse (110m) is LOAD-BEARING but NOT a render source: the world
@@ -155,6 +162,7 @@ export function loadMapData(): Promise<MapData> {
155
162
  readJson<BoundaryTopology>(nb, dir, FILES.naLand).catch(() => undefined),
156
163
  readJson<BoundaryTopology>(nb, dir, FILES.naLakes).catch(() => undefined),
157
164
  readJson<WaterBodies>(nb, dir, FILES.waterBodies).catch(() => undefined),
165
+ readJson<AirportData>(nb, dir, FILES.airports).catch(() => undefined),
158
166
  readJson<Gazetteer>(nb, dir, FILES.gazetteer),
159
167
  ]);
160
168
  return validate({
@@ -168,6 +176,7 @@ export function loadMapData(): Promise<MapData> {
168
176
  ...(naLand && { naLand }),
169
177
  ...(naLakes && { naLakes }),
170
178
  ...(waterBodies && { waterBodies }),
179
+ ...(airports && { airports }),
171
180
  });
172
181
  })().catch((e: unknown) => {
173
182
  cache = undefined; // don't poison future calls with a rejected promise
package/src/map/parser.ts CHANGED
@@ -5,11 +5,12 @@
5
5
  import { makeDgmoError, formatDgmoError } from '../diagnostics';
6
6
  import type { DgmoError } from '../diagnostics';
7
7
  import type { Writable } from '../utils/brand';
8
+ import type { PaletteColors } from '../palettes/types';
8
9
  import {
9
10
  measureIndent,
10
11
  splitNameAndMeta,
11
12
  extractColor,
12
- peelTrailingColorName,
13
+ peelRampColors,
13
14
  } from '../utils/parsing';
14
15
  import {
15
16
  MAP_REGISTRY,
@@ -40,10 +41,14 @@ const NUMERIC_LEAD_RE = /^[+-]?\d/; // a name region that starts like a number
40
41
  const SCOPE_RE = /^[A-Z]{2}(?:-[A-Z0-9]{1,3})?$/;
41
42
  // Arrow tokens (longest-match first); only recognized with surrounding
42
43
  // whitespace, so hyphens inside names (`office-east`) and `foo-bar` are safe.
43
- const ARROW_SPLIT = /\s+(-[^>]*?->|->|~[^>]*?~>|~>|--)\s+/;
44
- const HUB_RE = /^(->|~>)\s+(.+)$/;
44
+ // Drop the trailing `>` to drop the arrowhead: `--`/`-label-` are undirected
45
+ // straight, `~~`/`~label~` undirected arc. `[^>]` excludes `>` so an undirected
46
+ // branch can never swallow a directed token; directed branches are listed first.
47
+ const ARROW_TOKENS = '-[^>]*?->|->|~[^>]*?~>|~>|-[^>]*?-|~[^>]*?~';
48
+ const ARROW_SPLIT = new RegExp(`\\s+(${ARROW_TOKENS})\\s+`);
49
+ const HUB_RE = new RegExp(`^(${ARROW_TOKENS})\\s+(.+)$`);
45
50
  // A route leg line: an optional leading arrow (with in-arrow label) + a destination.
46
- const LEG_ARROW_RE = /^(-[^>]*?->|->|~[^>]*?~>|~>|--)\s+(.+)$/;
51
+ const LEG_ARROW_RE = new RegExp(`^(${ARROW_TOKENS})\\s+(.+)$`);
47
52
  const AT_RE = /(^|[\s,])at\s*:/i; // the removed `at:` coord form (§24B.9)
48
53
 
49
54
  // The 14 map-specific directives (§24B.2): 6 irreducible-intent + 8 `no-*`
@@ -63,8 +68,10 @@ const DIRECTIVE_SET: ReadonlySet<string> = new Set([
63
68
  'no-relief',
64
69
  'no-context-labels',
65
70
  'no-region-labels',
71
+ 'no-region-value',
66
72
  'no-poi-labels',
67
73
  'no-colorize',
74
+ 'no-cities',
68
75
  'no-cluster-pois',
69
76
  ]);
70
77
 
@@ -86,7 +93,7 @@ function stripInlineComment(line: string): string {
86
93
  .replace(/\s+$/, '');
87
94
  }
88
95
 
89
- export function parseMap(content: string): ParsedMap {
96
+ export function parseMap(content: string, palette?: PaletteColors): ParsedMap {
90
97
  const result: Writable<ParsedMap> = {
91
98
  title: null,
92
99
  titleLineNumber: null,
@@ -197,12 +204,17 @@ export function parseMap(content: string): ParsedMap {
197
204
  if (hub) {
198
205
  const src = open.poi.poi.alias ?? poiName(open.poi.poi.pos);
199
206
  if (src) {
207
+ const arr = classifyArrow(hub[1]!, lineNumber);
208
+ const hubSplit = splitNameAndMeta(hub[2]!, registry(), aliasMap);
209
+ const { tags, meta } = partitionMeta(hubSplit.meta, tagGroupNames());
200
210
  edges.push({
201
211
  from: src,
202
- to: hub[2]!.trim(),
203
- directed: true,
204
- style: hub[1] === '~>' ? 'arc' : 'straight',
205
- meta: {},
212
+ to: hubSplit.name.trim(),
213
+ ...(arr.label !== undefined && { label: arr.label }),
214
+ directed: arr.directed,
215
+ style: arr.style,
216
+ meta,
217
+ tags,
206
218
  lineNumber,
207
219
  });
208
220
  }
@@ -287,12 +299,18 @@ export function parseMap(content: string): ParsedMap {
287
299
  switch (key) {
288
300
  case 'region-metric': {
289
301
  dup(d.regionMetric);
290
- // A trailing color names the choropleth ramp hue (§24B.3): the
291
- // label keeps the rest. `region-metric Sales ($M) blue` blue ramp.
292
- const { label: rmLabel, colorName: rmColor } =
293
- peelTrailingColorName(value);
302
+ // Up to two trailing colors name the choropleth ramp endpoints
303
+ // (§24B.3): one high hue over a neutral low; two ⇒ `low high`. The
304
+ // label keeps the rest. `region-metric Sales ($M) green red` →
305
+ // green→red ramp.
306
+ const {
307
+ label: rmLabel,
308
+ low: rmLow,
309
+ high: rmHigh,
310
+ } = peelRampColors(value);
294
311
  d.regionMetric = rmLabel;
295
- if (rmColor) d.regionMetricColor = rmColor;
312
+ if (rmHigh) d.regionMetricColor = rmHigh;
313
+ if (rmLow) d.regionMetricLowColor = rmLow;
296
314
  break;
297
315
  }
298
316
  case 'poi-metric':
@@ -335,12 +353,18 @@ export function parseMap(content: string): ParsedMap {
335
353
  case 'no-region-labels':
336
354
  d.noRegionLabels = true;
337
355
  break;
356
+ case 'no-region-value':
357
+ d.noRegionValue = true;
358
+ break;
338
359
  case 'no-poi-labels':
339
360
  d.noPoiLabels = true;
340
361
  break;
341
362
  case 'no-colorize':
342
363
  d.noColorize = true;
343
364
  break;
365
+ case 'no-cities':
366
+ d.noCities = true;
367
+ break;
344
368
  case 'no-cluster-pois':
345
369
  d.noClusterPois = true;
346
370
  break;
@@ -376,7 +400,7 @@ export function parseMap(content: string): ParsedMap {
376
400
  line: number
377
401
  ): void {
378
402
  const { text: clean, isDefault } = stripDefaultModifier(text);
379
- const { label, color } = extractColor(clean, undefined, diagnostics, line);
403
+ const { label, color } = extractColor(clean, palette, diagnostics, line);
380
404
  if (!color) {
381
405
  pushError(line, `Expected 'Value color' in tag group '${group.name}'`);
382
406
  return;
@@ -507,7 +531,8 @@ export function parseMap(content: string): ParsedMap {
507
531
 
508
532
  /** Parse one route body line into a leg: `[-label->] <destination> [keys]`.
509
533
  * The arrow (if any) gives the leg label + shape; `value:` is leg thickness;
510
- * a tag / `label:` decorate the destination stop. Bare `<dest>` = plain leg. */
534
+ * a tag colours the LINE (§24B.6); `label:`/`as` name the destination stop.
535
+ * Bare `<dest>` = plain leg. */
511
536
  function parseLeg(
512
537
  trimmed: string,
513
538
  line: number,
@@ -549,7 +574,7 @@ export function parseMap(content: string): ParsedMap {
549
574
  dest: pos,
550
575
  ...(split.alias !== undefined && { destAlias: split.alias }),
551
576
  ...(destLabel !== undefined && { destLabel }),
552
- destTags: tags,
577
+ tags, // colour the LINE (§24B.6); stop tags go on the poi line
553
578
  lineNumber: line,
554
579
  };
555
580
  }
@@ -572,13 +597,18 @@ export function parseMap(content: string): ParsedMap {
572
597
  else links.push(classifyArrow(parts[k]!, line));
573
598
  }
574
599
  // Trailing metadata rides on the final endpoint only (R5), and attaches to
575
- // the FINAL leg only — not broadcast to every leg of a chain (#7).
600
+ // the FINAL leg only — not broadcast to every leg of a chain (#7). A tag on
601
+ // the line colours the LINE (§24B.6), so peel tags out of the meta here.
576
602
  const lastSplit = splitNameAndMeta(
577
603
  endpoints[endpoints.length - 1]!,
578
604
  registry(),
579
605
  aliasMap
580
606
  );
581
607
  endpoints[endpoints.length - 1] = lastSplit.name;
608
+ const { tags: lastTags, meta: lastMeta } = partitionMeta(
609
+ lastSplit.meta,
610
+ tagGroupNames()
611
+ );
582
612
  for (let k = 0; k < links.length; k++) {
583
613
  const from = endpoints[k]!;
584
614
  const to = endpoints[k + 1]!;
@@ -587,7 +617,8 @@ export function parseMap(content: string): ParsedMap {
587
617
  continue;
588
618
  }
589
619
  const isLast = k === links.length - 1;
590
- const meta = isLast ? lastSplit.meta : {};
620
+ const meta = isLast ? lastMeta : {};
621
+ const tags = isLast ? lastTags : {};
591
622
  // Edge shape comes only from the arrow token (surface parsing removed).
592
623
  const style: 'straight' | 'arc' =
593
624
  links[k]!.style === 'arc' ? 'arc' : 'straight';
@@ -598,6 +629,7 @@ export function parseMap(content: string): ParsedMap {
598
629
  directed: links[k]!.directed,
599
630
  style,
600
631
  meta,
632
+ tags,
601
633
  lineNumber: line,
602
634
  });
603
635
  }
@@ -608,24 +640,21 @@ export function parseMap(content: string): ParsedMap {
608
640
  line: number
609
641
  ): { label?: string; directed: boolean; style: 'straight' | 'arc' } {
610
642
  if (tok === '--') return { directed: false, style: 'straight' };
643
+ if (tok === '~~') return { directed: false, style: 'arc' };
611
644
  if (tok === '->') return { directed: true, style: 'straight' };
612
645
  if (tok === '~>') return { directed: true, style: 'arc' };
613
- if (tok.startsWith('~')) {
614
- const lbl = parseInArrowLabel(tok.slice(1, -2), line);
615
- lbl.diagnostics.forEach((d) => diagnostics.push(d));
616
- return {
617
- ...(lbl.label !== undefined && { label: lbl.label }),
618
- directed: true,
619
- style: 'arc',
620
- };
621
- }
622
- // directed labeled `-…->`
623
- const lbl = parseInArrowLabel(tok.slice(1, -2), line);
646
+ // Labeled form: arrowhead iff it ends in `>`, arc iff it starts with `~`.
647
+ // The label sits between the delimiters — drop 2 trailing chars for the
648
+ // directed `…->`/`…~>`, 1 for the undirected `…-`/`…~`.
649
+ const directed = tok.endsWith('>');
650
+ const style: 'straight' | 'arc' = tok.startsWith('~') ? 'arc' : 'straight';
651
+ const inner = directed ? tok.slice(1, -2) : tok.slice(1, -1);
652
+ const lbl = parseInArrowLabel(inner, line);
624
653
  lbl.diagnostics.forEach((d) => diagnostics.push(d));
625
654
  return {
626
655
  ...(lbl.label !== undefined && { label: lbl.label }),
627
- directed: true,
628
- style: 'straight',
656
+ directed,
657
+ style,
629
658
  };
630
659
  }
631
660