@diagrammo/dgmo 0.21.0 → 0.22.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.
- package/README.md +16 -6
- package/dist/advanced.cjs +2521 -623
- package/dist/advanced.d.cts +917 -534
- package/dist/advanced.d.ts +917 -534
- package/dist/advanced.js +2516 -623
- package/dist/auto.cjs +2333 -608
- package/dist/auto.js +119 -119
- package/dist/auto.mjs +2335 -609
- package/dist/cli.cjs +168 -168
- package/dist/editor.cjs +13 -15
- package/dist/editor.js +13 -15
- package/dist/highlight.cjs +15 -12
- package/dist/highlight.js +15 -12
- package/dist/index.cjs +2317 -595
- package/dist/index.d.cts +4 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +2319 -596
- package/dist/internal.cjs +2521 -623
- package/dist/internal.d.cts +917 -534
- package/dist/internal.d.ts +917 -534
- package/dist/internal.js +2516 -623
- package/dist/map-data/PROVENANCE.json +1 -1
- package/dist/map-data/mountain-ranges.json +1 -0
- package/dist/map-data/water-bodies.json +1 -0
- package/docs/language-reference.md +44 -31
- package/gallery/fixtures/map-categorical-world.dgmo +16 -0
- package/gallery/fixtures/map-categorical.dgmo +0 -1
- package/gallery/fixtures/map-choropleth.dgmo +0 -1
- package/gallery/fixtures/map-coastline.dgmo +7 -0
- package/gallery/fixtures/map-colorize.dgmo +11 -0
- package/gallery/fixtures/map-direct-color.dgmo +9 -0
- package/gallery/fixtures/map-reference-world.dgmo +11 -0
- package/gallery/fixtures/map-region-scope.dgmo +0 -3
- package/gallery/fixtures/map-route.dgmo +0 -1
- package/package.json +1 -1
- package/src/advanced.ts +26 -1
- package/src/boxes-and-lines/renderer.ts +39 -12
- package/src/cli.ts +1 -1
- package/src/completion.ts +32 -24
- package/src/cycle/renderer.ts +14 -1
- package/src/d3.ts +23 -11
- package/src/editor/highlight-api.ts +4 -0
- package/src/editor/keywords.ts +13 -15
- package/src/infra/renderer.ts +35 -7
- package/src/map/colorize.ts +54 -0
- package/src/map/context-labels.ts +429 -0
- package/src/map/data/PROVENANCE.json +1 -1
- package/src/map/data/mountain-ranges.json +1 -0
- package/src/map/data/types.ts +34 -0
- package/src/map/data/water-bodies.json +1 -0
- package/src/map/dimensions.ts +117 -0
- package/src/map/geo-query.ts +295 -0
- package/src/map/geo.ts +305 -2
- package/src/map/invert.ts +111 -0
- package/src/map/layout.ts +1504 -335
- package/src/map/load-data.ts +16 -2
- package/src/map/parser.ts +57 -111
- package/src/map/renderer.ts +556 -13
- package/src/map/resolved-types.ts +24 -2
- package/src/map/resolver.ts +237 -67
- package/src/map/types.ts +39 -23
- package/src/mindmap/renderer.ts +10 -1
- package/src/palettes/atlas.ts +77 -0
- package/src/palettes/blueprint.ts +73 -0
- package/src/palettes/color-utils.ts +58 -1
- package/src/palettes/index.ts +12 -3
- package/src/palettes/slate.ts +73 -0
- package/src/palettes/tidewater.ts +73 -0
- package/src/render.ts +8 -1
- package/src/tech-radar/renderer.ts +3 -0
- package/src/tech-radar/types.ts +3 -0
- package/src/utils/d3-types.ts +5 -0
- package/src/utils/legend-layout.ts +21 -4
- package/src/utils/legend-types.ts +7 -0
- package/src/utils/reserved-key-registry.ts +3 -0
- package/src/palettes/bold.ts +0 -67
package/src/map/load-data.ts
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
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 } from './data/types';
|
|
21
|
+
import type { BoundaryTopology, Gazetteer, WaterBodies } from './data/types';
|
|
22
22
|
|
|
23
23
|
type NodeBuiltins = {
|
|
24
24
|
readFile: typeof import('node:fs/promises').readFile;
|
|
@@ -43,8 +43,10 @@ const FILES = {
|
|
|
43
43
|
usStates: 'us-states.json',
|
|
44
44
|
lakes: 'lakes.json',
|
|
45
45
|
rivers: 'rivers.json',
|
|
46
|
+
mountainRanges: 'mountain-ranges.json',
|
|
46
47
|
naLand: 'na-land.json',
|
|
47
48
|
naLakes: 'na-lakes.json',
|
|
49
|
+
waterBodies: 'water-bodies.json',
|
|
48
50
|
gazetteer: 'gazetteer.json',
|
|
49
51
|
} as const;
|
|
50
52
|
|
|
@@ -131,18 +133,28 @@ export function loadMapData(): Promise<MapData> {
|
|
|
131
133
|
usStates,
|
|
132
134
|
lakes,
|
|
133
135
|
rivers,
|
|
136
|
+
mountainRanges,
|
|
134
137
|
naLand,
|
|
135
138
|
naLakes,
|
|
139
|
+
waterBodies,
|
|
136
140
|
gazetteer,
|
|
137
141
|
] = await Promise.all([
|
|
142
|
+
// worldCoarse (110m) is LOAD-BEARING but NOT a render source: the world
|
|
143
|
+
// basemap renders from worldDetail (50m) at all scales (resolver pins
|
|
144
|
+
// basemaps.world = 'detail'). Coarse stays as the authoritative region
|
|
145
|
+
// name index + dominant-landmass bbox source in resolver.ts. Do not drop it.
|
|
138
146
|
readJson<BoundaryTopology>(nb, dir, FILES.worldCoarse),
|
|
139
147
|
readJson<BoundaryTopology>(nb, dir, FILES.worldDetail),
|
|
140
148
|
readJson<BoundaryTopology>(nb, dir, FILES.usStates),
|
|
141
|
-
// Lakes/rivers/NA assets are optional — older bundles may predate them.
|
|
149
|
+
// Lakes/rivers/mountain/NA/water assets are optional — older bundles may predate them.
|
|
142
150
|
readJson<BoundaryTopology>(nb, dir, FILES.lakes).catch(() => undefined),
|
|
143
151
|
readJson<BoundaryTopology>(nb, dir, FILES.rivers).catch(() => undefined),
|
|
152
|
+
readJson<BoundaryTopology>(nb, dir, FILES.mountainRanges).catch(
|
|
153
|
+
() => undefined
|
|
154
|
+
),
|
|
144
155
|
readJson<BoundaryTopology>(nb, dir, FILES.naLand).catch(() => undefined),
|
|
145
156
|
readJson<BoundaryTopology>(nb, dir, FILES.naLakes).catch(() => undefined),
|
|
157
|
+
readJson<WaterBodies>(nb, dir, FILES.waterBodies).catch(() => undefined),
|
|
146
158
|
readJson<Gazetteer>(nb, dir, FILES.gazetteer),
|
|
147
159
|
]);
|
|
148
160
|
return validate({
|
|
@@ -152,8 +164,10 @@ export function loadMapData(): Promise<MapData> {
|
|
|
152
164
|
gazetteer,
|
|
153
165
|
...(lakes && { lakes }),
|
|
154
166
|
...(rivers && { rivers }),
|
|
167
|
+
...(mountainRanges && { mountainRanges }),
|
|
155
168
|
...(naLand && { naLand }),
|
|
156
169
|
...(naLakes && { naLakes }),
|
|
170
|
+
...(waterBodies && { waterBodies }),
|
|
157
171
|
});
|
|
158
172
|
})().catch((e: unknown) => {
|
|
159
173
|
cache = undefined; // don't poison future calls with a rejected promise
|
package/src/map/parser.ts
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
measureIndent,
|
|
10
10
|
splitNameAndMeta,
|
|
11
11
|
extractColor,
|
|
12
|
+
peelTrailingColorName,
|
|
12
13
|
} from '../utils/parsing';
|
|
13
14
|
import {
|
|
14
15
|
MAP_REGISTRY,
|
|
@@ -31,7 +32,6 @@ import type {
|
|
|
31
32
|
MapRouteLeg,
|
|
32
33
|
MapEdge,
|
|
33
34
|
PoiPos,
|
|
34
|
-
MapScale,
|
|
35
35
|
} from './types';
|
|
36
36
|
import type { TagGroup, TagEntry } from '../utils/tag-groups';
|
|
37
37
|
|
|
@@ -46,21 +46,22 @@ const HUB_RE = /^(->|~>)\s+(.+)$/;
|
|
|
46
46
|
const LEG_ARROW_RE = /^(-[^>]*?->|->|~[^>]*?~>|~>|--)\s+(.+)$/;
|
|
47
47
|
const AT_RE = /(^|[\s,])at\s*:/i; // the removed `at:` coord form (§24B.9)
|
|
48
48
|
|
|
49
|
+
// Final 13 (§24B.2): 6 irreducible-intent directives + 7 `no-*` cosmetic
|
|
50
|
+
// opt-outs. Every cosmetic is on by default; its `no-*` flag is the only switch.
|
|
49
51
|
const DIRECTIVE_SET: ReadonlySet<string> = new Set([
|
|
50
|
-
'region',
|
|
51
|
-
'projection',
|
|
52
52
|
'region-metric',
|
|
53
53
|
'poi-metric',
|
|
54
54
|
'flow-metric',
|
|
55
|
-
'
|
|
56
|
-
'region-labels',
|
|
57
|
-
'poi-labels',
|
|
58
|
-
'default-country',
|
|
59
|
-
'default-state',
|
|
55
|
+
'locale',
|
|
60
56
|
'active-tag',
|
|
61
|
-
'no-legend',
|
|
62
|
-
'subtitle',
|
|
63
57
|
'caption',
|
|
58
|
+
'no-legend',
|
|
59
|
+
'no-coastline',
|
|
60
|
+
'no-relief',
|
|
61
|
+
'no-context-labels',
|
|
62
|
+
'no-region-labels',
|
|
63
|
+
'no-poi-labels',
|
|
64
|
+
'no-colorize',
|
|
64
65
|
]);
|
|
65
66
|
|
|
66
67
|
/** True when the first non-blank/non-comment line declares `map`. */
|
|
@@ -221,15 +222,6 @@ export function parseMap(content: string): ParsedMap {
|
|
|
221
222
|
handleTag(trimmed, lineNumber);
|
|
222
223
|
continue;
|
|
223
224
|
}
|
|
224
|
-
// Bare-flag directives (no value) — only when the line is exactly the flag,
|
|
225
|
-
// so a region named e.g. "Natural Bridge" still parses as a region.
|
|
226
|
-
if (
|
|
227
|
-
(firstWord === 'muted' || firstWord === 'natural') &&
|
|
228
|
-
trimmed === firstWord
|
|
229
|
-
) {
|
|
230
|
-
handleDirective(firstWord, '', lineNumber);
|
|
231
|
-
continue;
|
|
232
|
-
}
|
|
233
225
|
if (
|
|
234
226
|
DIRECTIVE_SET.has(firstWord) &&
|
|
235
227
|
!trimmed.slice(firstWord.length).trimStart().startsWith(':')
|
|
@@ -289,31 +281,16 @@ export function parseMap(content: string): ParsedMap {
|
|
|
289
281
|
pushWarning(line, `Duplicate directive "${key}" — last value wins.`);
|
|
290
282
|
};
|
|
291
283
|
switch (key) {
|
|
292
|
-
case 'region':
|
|
293
|
-
dup(d.region);
|
|
294
|
-
d.region = value;
|
|
295
|
-
break;
|
|
296
|
-
case 'projection':
|
|
297
|
-
dup(d.projection);
|
|
298
|
-
if (
|
|
299
|
-
value &&
|
|
300
|
-
![
|
|
301
|
-
'equirectangular',
|
|
302
|
-
'natural-earth',
|
|
303
|
-
'albers-usa',
|
|
304
|
-
'mercator',
|
|
305
|
-
].includes(value)
|
|
306
|
-
)
|
|
307
|
-
pushWarning(
|
|
308
|
-
line,
|
|
309
|
-
`Unknown projection "${value}" (expected equirectangular | natural-earth | albers-usa | mercator).`
|
|
310
|
-
);
|
|
311
|
-
d.projection = value;
|
|
312
|
-
break;
|
|
313
|
-
case 'region-metric':
|
|
284
|
+
case 'region-metric': {
|
|
314
285
|
dup(d.regionMetric);
|
|
315
|
-
|
|
286
|
+
// A trailing color names the choropleth ramp hue (§24B.3): the
|
|
287
|
+
// label keeps the rest. `region-metric Sales ($M) blue` → blue ramp.
|
|
288
|
+
const { label: rmLabel, colorName: rmColor } =
|
|
289
|
+
peelTrailingColorName(value);
|
|
290
|
+
d.regionMetric = rmLabel;
|
|
291
|
+
if (rmColor) d.regionMetricColor = rmColor;
|
|
316
292
|
break;
|
|
293
|
+
}
|
|
317
294
|
case 'poi-metric':
|
|
318
295
|
dup(d.poiMetric);
|
|
319
296
|
d.poiMetric = value;
|
|
@@ -322,87 +299,44 @@ export function parseMap(content: string): ParsedMap {
|
|
|
322
299
|
dup(d.flowMetric);
|
|
323
300
|
d.flowMetric = value;
|
|
324
301
|
break;
|
|
325
|
-
case '
|
|
326
|
-
dup(d.
|
|
327
|
-
|
|
328
|
-
const s = parseScale(value, line);
|
|
329
|
-
if (s) d.scale = s;
|
|
330
|
-
}
|
|
331
|
-
break;
|
|
332
|
-
case 'region-labels':
|
|
333
|
-
dup(d.regionLabels);
|
|
334
|
-
if (value && !['full', 'abbrev', 'off'].includes(value))
|
|
335
|
-
pushWarning(
|
|
336
|
-
line,
|
|
337
|
-
`Unknown region-labels "${value}" (expected full | abbrev | off).`
|
|
338
|
-
);
|
|
339
|
-
d.regionLabels = value;
|
|
340
|
-
break;
|
|
341
|
-
case 'poi-labels':
|
|
342
|
-
dup(d.poiLabels);
|
|
343
|
-
if (value && !['off', 'auto', 'all'].includes(value))
|
|
344
|
-
pushWarning(
|
|
345
|
-
line,
|
|
346
|
-
`Unknown poi-labels "${value}" (expected off | auto | all).`
|
|
347
|
-
);
|
|
348
|
-
d.poiLabels = value;
|
|
349
|
-
break;
|
|
350
|
-
case 'default-country':
|
|
351
|
-
dup(d.defaultCountry);
|
|
352
|
-
d.defaultCountry = value;
|
|
353
|
-
break;
|
|
354
|
-
case 'default-state':
|
|
355
|
-
dup(d.defaultState);
|
|
356
|
-
d.defaultState = value;
|
|
302
|
+
case 'locale':
|
|
303
|
+
dup(d.locale);
|
|
304
|
+
d.locale = value;
|
|
357
305
|
break;
|
|
358
306
|
case 'active-tag':
|
|
359
307
|
dup(d.activeTag);
|
|
360
308
|
d.activeTag = value;
|
|
361
309
|
break;
|
|
310
|
+
case 'caption':
|
|
311
|
+
dup(d.caption);
|
|
312
|
+
d.caption = value;
|
|
313
|
+
break;
|
|
314
|
+
// ── Cosmetic `no-*` opt-outs: bare flags, idempotent (mirror `no-legend`,
|
|
315
|
+
// no dup warning); each defaults the feature ON when absent. ──
|
|
362
316
|
case 'no-legend':
|
|
363
317
|
d.noLegend = true;
|
|
364
318
|
break;
|
|
365
|
-
case '
|
|
366
|
-
|
|
367
|
-
if (d.basemapStyle !== undefined && d.basemapStyle !== key)
|
|
368
|
-
pushWarning(
|
|
369
|
-
line,
|
|
370
|
-
`Conflicting basemap dress — "${d.basemapStyle}" then "${key}"; last wins.`
|
|
371
|
-
);
|
|
372
|
-
d.basemapStyle = key;
|
|
319
|
+
case 'no-coastline':
|
|
320
|
+
d.noCoastline = true;
|
|
373
321
|
break;
|
|
374
|
-
case '
|
|
375
|
-
|
|
376
|
-
d.subtitle = value;
|
|
322
|
+
case 'no-relief':
|
|
323
|
+
d.noRelief = true;
|
|
377
324
|
break;
|
|
378
|
-
case '
|
|
379
|
-
|
|
380
|
-
|
|
325
|
+
case 'no-context-labels':
|
|
326
|
+
d.noContextLabels = true;
|
|
327
|
+
break;
|
|
328
|
+
case 'no-region-labels':
|
|
329
|
+
d.noRegionLabels = true;
|
|
330
|
+
break;
|
|
331
|
+
case 'no-poi-labels':
|
|
332
|
+
d.noPoiLabels = true;
|
|
333
|
+
break;
|
|
334
|
+
case 'no-colorize':
|
|
335
|
+
d.noColorize = true;
|
|
381
336
|
break;
|
|
382
337
|
}
|
|
383
338
|
}
|
|
384
339
|
|
|
385
|
-
function parseScale(value: string, line: number): MapScale | null {
|
|
386
|
-
const toks = value.split(/\s+/).filter(Boolean);
|
|
387
|
-
const min = Number(toks[0]);
|
|
388
|
-
const max = Number(toks[1]);
|
|
389
|
-
if (!Number.isFinite(min) || !Number.isFinite(max)) {
|
|
390
|
-
pushError(line, `scale requires numeric <min> <max> (got "${value}").`);
|
|
391
|
-
return null;
|
|
392
|
-
}
|
|
393
|
-
const scale: Writable<MapScale> = { min, max };
|
|
394
|
-
if (toks[2] === 'center') {
|
|
395
|
-
const c = Number(toks[3]);
|
|
396
|
-
if (Number.isFinite(c)) scale.center = c;
|
|
397
|
-
else
|
|
398
|
-
pushError(
|
|
399
|
-
line,
|
|
400
|
-
`scale center requires a number (got "${toks[3] ?? ''}").`
|
|
401
|
-
);
|
|
402
|
-
}
|
|
403
|
-
return scale;
|
|
404
|
-
}
|
|
405
|
-
|
|
406
340
|
function handleTag(trimmed: string, line: number): void {
|
|
407
341
|
const m = matchTagBlockHeading(trimmed);
|
|
408
342
|
if (!m) {
|
|
@@ -488,6 +422,8 @@ export function parseMap(content: string): ParsedMap {
|
|
|
488
422
|
};
|
|
489
423
|
if (regionScope !== undefined) region.scope = regionScope;
|
|
490
424
|
if (valueNum !== undefined) region.value = valueNum;
|
|
425
|
+
// §1.5 trailing color → flat categorical override fill (§24B.4).
|
|
426
|
+
if (split.color) region.color = split.color;
|
|
491
427
|
regions.push(region);
|
|
492
428
|
}
|
|
493
429
|
|
|
@@ -513,6 +449,8 @@ export function parseMap(content: string): ParsedMap {
|
|
|
513
449
|
const poi: Writable<MapPoi> = { pos, tags, meta, lineNumber: line };
|
|
514
450
|
if (split.alias) poi.alias = split.alias;
|
|
515
451
|
if (label !== undefined) poi.label = label;
|
|
452
|
+
// §1.5 trailing color → flat marker fill (§24B.5); wins over a tag color.
|
|
453
|
+
if (split.color) poi.color = split.color;
|
|
516
454
|
pois.push(poi);
|
|
517
455
|
open.poi = { poi, indent };
|
|
518
456
|
}
|
|
@@ -539,6 +477,8 @@ export function parseMap(content: string): ParsedMap {
|
|
|
539
477
|
const { tags, meta } = partitionMeta(split.meta, tagGroupNames());
|
|
540
478
|
const originLabel = meta['label'];
|
|
541
479
|
const originValue = meta['value'];
|
|
480
|
+
// Leg shape comes only from `style: arc` / arrow style (surface parsing
|
|
481
|
+
// removed — a leg no longer bows just because it crosses water).
|
|
542
482
|
const style: 'straight' | 'arc' =
|
|
543
483
|
meta['style'] === 'arc' ? 'arc' : 'straight';
|
|
544
484
|
const route: Writable<MapRoute> = {
|
|
@@ -588,6 +528,8 @@ export function parseMap(content: string): ParsedMap {
|
|
|
588
528
|
const { tags, meta } = partitionMeta(split.meta, tagGroupNames());
|
|
589
529
|
const value = meta['value'];
|
|
590
530
|
const destLabel = meta['label'];
|
|
531
|
+
// Leg shape comes only from the arrow style or the route header `style: arc`
|
|
532
|
+
// (surface parsing removed — no implied bow).
|
|
591
533
|
const style: 'straight' | 'arc' =
|
|
592
534
|
arrowStyle === 'arc' || headerStyle === 'arc' ? 'arc' : 'straight';
|
|
593
535
|
return {
|
|
@@ -634,13 +576,17 @@ export function parseMap(content: string): ParsedMap {
|
|
|
634
576
|
pushError(line, `Edge has an empty endpoint: "${trimmed}".`);
|
|
635
577
|
continue;
|
|
636
578
|
}
|
|
637
|
-
const
|
|
579
|
+
const isLast = k === links.length - 1;
|
|
580
|
+
const meta = isLast ? lastSplit.meta : {};
|
|
581
|
+
// Edge shape comes only from the arrow token (surface parsing removed).
|
|
582
|
+
const style: 'straight' | 'arc' =
|
|
583
|
+
links[k]!.style === 'arc' ? 'arc' : 'straight';
|
|
638
584
|
edges.push({
|
|
639
585
|
from,
|
|
640
586
|
to,
|
|
641
587
|
...(links[k]!.label !== undefined && { label: links[k]!.label }),
|
|
642
588
|
directed: links[k]!.directed,
|
|
643
|
-
style
|
|
589
|
+
style,
|
|
644
590
|
meta,
|
|
645
591
|
lineNumber: line,
|
|
646
592
|
});
|