@diagrammo/dgmo 0.21.1 → 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 +2003 -466
- package/dist/advanced.d.cts +5714 -0
- package/dist/advanced.d.ts +5714 -0
- package/dist/advanced.js +1999 -466
- package/dist/auto.cjs +2048 -449
- package/dist/auto.d.cts +39 -0
- package/dist/auto.d.ts +39 -0
- package/dist/auto.js +121 -121
- package/dist/auto.mjs +2050 -450
- package/dist/cli.cjs +170 -170
- package/dist/editor.cjs +13 -16
- package/dist/editor.js +13 -16
- package/dist/highlight.cjs +15 -13
- package/dist/highlight.js +15 -13
- package/dist/index.cjs +2032 -435
- package/dist/index.d.cts +339 -0
- package/dist/index.d.ts +339 -0
- package/dist/index.js +2034 -436
- package/dist/internal.cjs +2003 -466
- package/dist/internal.d.cts +5714 -0
- package/dist/internal.d.ts +5714 -0
- package/dist/internal.js +1999 -466
- package/dist/map-data/water-bodies.json +1 -0
- package/docs/language-reference.md +20 -9
- 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 +0 -1
- 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 +12 -1
- package/src/boxes-and-lines/renderer.ts +39 -12
- package/src/cli.ts +1 -1
- package/src/completion.ts +32 -25
- package/src/cycle/renderer.ts +14 -1
- package/src/d3.ts +8 -2
- package/src/editor/highlight-api.ts +4 -0
- package/src/editor/keywords.ts +13 -16
- 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/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 +21 -3
- package/src/map/geo.ts +47 -1
- package/src/map/layout.ts +1300 -251
- package/src/map/load-data.ts +10 -2
- package/src/map/parser.ts +42 -116
- package/src/map/renderer.ts +512 -13
- package/src/map/resolved-types.ts +16 -2
- package/src/map/resolver.ts +208 -59
- package/src/map/types.ts +30 -32
- 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;
|
|
@@ -46,6 +46,7 @@ const FILES = {
|
|
|
46
46
|
mountainRanges: 'mountain-ranges.json',
|
|
47
47
|
naLand: 'na-land.json',
|
|
48
48
|
naLakes: 'na-lakes.json',
|
|
49
|
+
waterBodies: 'water-bodies.json',
|
|
49
50
|
gazetteer: 'gazetteer.json',
|
|
50
51
|
} as const;
|
|
51
52
|
|
|
@@ -135,12 +136,17 @@ export function loadMapData(): Promise<MapData> {
|
|
|
135
136
|
mountainRanges,
|
|
136
137
|
naLand,
|
|
137
138
|
naLakes,
|
|
139
|
+
waterBodies,
|
|
138
140
|
gazetteer,
|
|
139
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.
|
|
140
146
|
readJson<BoundaryTopology>(nb, dir, FILES.worldCoarse),
|
|
141
147
|
readJson<BoundaryTopology>(nb, dir, FILES.worldDetail),
|
|
142
148
|
readJson<BoundaryTopology>(nb, dir, FILES.usStates),
|
|
143
|
-
// Lakes/rivers/mountain/NA assets are optional — older bundles may predate them.
|
|
149
|
+
// Lakes/rivers/mountain/NA/water assets are optional — older bundles may predate them.
|
|
144
150
|
readJson<BoundaryTopology>(nb, dir, FILES.lakes).catch(() => undefined),
|
|
145
151
|
readJson<BoundaryTopology>(nb, dir, FILES.rivers).catch(() => undefined),
|
|
146
152
|
readJson<BoundaryTopology>(nb, dir, FILES.mountainRanges).catch(
|
|
@@ -148,6 +154,7 @@ export function loadMapData(): Promise<MapData> {
|
|
|
148
154
|
),
|
|
149
155
|
readJson<BoundaryTopology>(nb, dir, FILES.naLand).catch(() => undefined),
|
|
150
156
|
readJson<BoundaryTopology>(nb, dir, FILES.naLakes).catch(() => undefined),
|
|
157
|
+
readJson<WaterBodies>(nb, dir, FILES.waterBodies).catch(() => undefined),
|
|
151
158
|
readJson<Gazetteer>(nb, dir, FILES.gazetteer),
|
|
152
159
|
]);
|
|
153
160
|
return validate({
|
|
@@ -160,6 +167,7 @@ export function loadMapData(): Promise<MapData> {
|
|
|
160
167
|
...(mountainRanges && { mountainRanges }),
|
|
161
168
|
...(naLand && { naLand }),
|
|
162
169
|
...(naLakes && { naLakes }),
|
|
170
|
+
...(waterBodies && { waterBodies }),
|
|
163
171
|
});
|
|
164
172
|
})().catch((e: unknown) => {
|
|
165
173
|
cache = undefined; // don't poison future calls with a rejected promise
|
package/src/map/parser.ts
CHANGED
|
@@ -32,7 +32,6 @@ import type {
|
|
|
32
32
|
MapRouteLeg,
|
|
33
33
|
MapEdge,
|
|
34
34
|
PoiPos,
|
|
35
|
-
MapScale,
|
|
36
35
|
} from './types';
|
|
37
36
|
import type { TagGroup, TagEntry } from '../utils/tag-groups';
|
|
38
37
|
|
|
@@ -47,23 +46,22 @@ const HUB_RE = /^(->|~>)\s+(.+)$/;
|
|
|
47
46
|
const LEG_ARROW_RE = /^(-[^>]*?->|->|~[^>]*?~>|~>|--)\s+(.+)$/;
|
|
48
47
|
const AT_RE = /(^|[\s,])at\s*:/i; // the removed `at:` coord form (§24B.9)
|
|
49
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.
|
|
50
51
|
const DIRECTIVE_SET: ReadonlySet<string> = new Set([
|
|
51
|
-
'region',
|
|
52
|
-
'projection',
|
|
53
52
|
'region-metric',
|
|
54
53
|
'poi-metric',
|
|
55
54
|
'flow-metric',
|
|
56
|
-
'
|
|
57
|
-
'region-labels',
|
|
58
|
-
'poi-labels',
|
|
59
|
-
'default-country',
|
|
60
|
-
'default-state',
|
|
55
|
+
'locale',
|
|
61
56
|
'active-tag',
|
|
62
|
-
'no-legend',
|
|
63
|
-
'no-insets',
|
|
64
|
-
'relief',
|
|
65
|
-
'subtitle',
|
|
66
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',
|
|
67
65
|
]);
|
|
68
66
|
|
|
69
67
|
/** True when the first non-blank/non-comment line declares `map`. */
|
|
@@ -224,15 +222,6 @@ export function parseMap(content: string): ParsedMap {
|
|
|
224
222
|
handleTag(trimmed, lineNumber);
|
|
225
223
|
continue;
|
|
226
224
|
}
|
|
227
|
-
// Bare-flag directives (no value) — only when the line is exactly the flag,
|
|
228
|
-
// so a region named e.g. "Natural Bridge" still parses as a region.
|
|
229
|
-
if (
|
|
230
|
-
(firstWord === 'muted' || firstWord === 'natural') &&
|
|
231
|
-
trimmed === firstWord
|
|
232
|
-
) {
|
|
233
|
-
handleDirective(firstWord, '', lineNumber);
|
|
234
|
-
continue;
|
|
235
|
-
}
|
|
236
225
|
if (
|
|
237
226
|
DIRECTIVE_SET.has(firstWord) &&
|
|
238
227
|
!trimmed.slice(firstWord.length).trimStart().startsWith(':')
|
|
@@ -292,27 +281,6 @@ export function parseMap(content: string): ParsedMap {
|
|
|
292
281
|
pushWarning(line, `Duplicate directive "${key}" — last value wins.`);
|
|
293
282
|
};
|
|
294
283
|
switch (key) {
|
|
295
|
-
case 'region':
|
|
296
|
-
dup(d.region);
|
|
297
|
-
d.region = value;
|
|
298
|
-
break;
|
|
299
|
-
case 'projection':
|
|
300
|
-
dup(d.projection);
|
|
301
|
-
if (
|
|
302
|
-
value &&
|
|
303
|
-
![
|
|
304
|
-
'equirectangular',
|
|
305
|
-
'natural-earth',
|
|
306
|
-
'albers-usa',
|
|
307
|
-
'mercator',
|
|
308
|
-
].includes(value)
|
|
309
|
-
)
|
|
310
|
-
pushWarning(
|
|
311
|
-
line,
|
|
312
|
-
`Unknown projection "${value}" (expected equirectangular | natural-earth | albers-usa | mercator).`
|
|
313
|
-
);
|
|
314
|
-
d.projection = value;
|
|
315
|
-
break;
|
|
316
284
|
case 'region-metric': {
|
|
317
285
|
dup(d.regionMetric);
|
|
318
286
|
// A trailing color names the choropleth ramp hue (§24B.3): the
|
|
@@ -331,94 +299,44 @@ export function parseMap(content: string): ParsedMap {
|
|
|
331
299
|
dup(d.flowMetric);
|
|
332
300
|
d.flowMetric = value;
|
|
333
301
|
break;
|
|
334
|
-
case '
|
|
335
|
-
dup(d.
|
|
336
|
-
|
|
337
|
-
const s = parseScale(value, line);
|
|
338
|
-
if (s) d.scale = s;
|
|
339
|
-
}
|
|
340
|
-
break;
|
|
341
|
-
case 'region-labels':
|
|
342
|
-
dup(d.regionLabels);
|
|
343
|
-
if (value && !['full', 'abbrev', 'off'].includes(value))
|
|
344
|
-
pushWarning(
|
|
345
|
-
line,
|
|
346
|
-
`Unknown region-labels "${value}" (expected full | abbrev | off).`
|
|
347
|
-
);
|
|
348
|
-
d.regionLabels = value;
|
|
349
|
-
break;
|
|
350
|
-
case 'poi-labels':
|
|
351
|
-
dup(d.poiLabels);
|
|
352
|
-
if (value && !['off', 'auto', 'all'].includes(value))
|
|
353
|
-
pushWarning(
|
|
354
|
-
line,
|
|
355
|
-
`Unknown poi-labels "${value}" (expected off | auto | all).`
|
|
356
|
-
);
|
|
357
|
-
d.poiLabels = value;
|
|
358
|
-
break;
|
|
359
|
-
case 'default-country':
|
|
360
|
-
dup(d.defaultCountry);
|
|
361
|
-
d.defaultCountry = value;
|
|
362
|
-
break;
|
|
363
|
-
case 'default-state':
|
|
364
|
-
dup(d.defaultState);
|
|
365
|
-
d.defaultState = value;
|
|
302
|
+
case 'locale':
|
|
303
|
+
dup(d.locale);
|
|
304
|
+
d.locale = value;
|
|
366
305
|
break;
|
|
367
306
|
case 'active-tag':
|
|
368
307
|
dup(d.activeTag);
|
|
369
308
|
d.activeTag = value;
|
|
370
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. ──
|
|
371
316
|
case 'no-legend':
|
|
372
317
|
d.noLegend = true;
|
|
373
318
|
break;
|
|
374
|
-
case 'no-
|
|
375
|
-
d.
|
|
319
|
+
case 'no-coastline':
|
|
320
|
+
d.noCoastline = true;
|
|
376
321
|
break;
|
|
377
|
-
case 'relief':
|
|
378
|
-
|
|
379
|
-
d.relief = true;
|
|
322
|
+
case 'no-relief':
|
|
323
|
+
d.noRelief = true;
|
|
380
324
|
break;
|
|
381
|
-
case '
|
|
382
|
-
|
|
383
|
-
if (d.basemapStyle !== undefined && d.basemapStyle !== key)
|
|
384
|
-
pushWarning(
|
|
385
|
-
line,
|
|
386
|
-
`Conflicting basemap dress — "${d.basemapStyle}" then "${key}"; last wins.`
|
|
387
|
-
);
|
|
388
|
-
d.basemapStyle = key;
|
|
325
|
+
case 'no-context-labels':
|
|
326
|
+
d.noContextLabels = true;
|
|
389
327
|
break;
|
|
390
|
-
case '
|
|
391
|
-
|
|
392
|
-
d.subtitle = value;
|
|
328
|
+
case 'no-region-labels':
|
|
329
|
+
d.noRegionLabels = true;
|
|
393
330
|
break;
|
|
394
|
-
case '
|
|
395
|
-
|
|
396
|
-
|
|
331
|
+
case 'no-poi-labels':
|
|
332
|
+
d.noPoiLabels = true;
|
|
333
|
+
break;
|
|
334
|
+
case 'no-colorize':
|
|
335
|
+
d.noColorize = true;
|
|
397
336
|
break;
|
|
398
337
|
}
|
|
399
338
|
}
|
|
400
339
|
|
|
401
|
-
function parseScale(value: string, line: number): MapScale | null {
|
|
402
|
-
const toks = value.split(/\s+/).filter(Boolean);
|
|
403
|
-
const min = Number(toks[0]);
|
|
404
|
-
const max = Number(toks[1]);
|
|
405
|
-
if (!Number.isFinite(min) || !Number.isFinite(max)) {
|
|
406
|
-
pushError(line, `scale requires numeric <min> <max> (got "${value}").`);
|
|
407
|
-
return null;
|
|
408
|
-
}
|
|
409
|
-
const scale: Writable<MapScale> = { min, max };
|
|
410
|
-
if (toks[2] === 'center') {
|
|
411
|
-
const c = Number(toks[3]);
|
|
412
|
-
if (Number.isFinite(c)) scale.center = c;
|
|
413
|
-
else
|
|
414
|
-
pushError(
|
|
415
|
-
line,
|
|
416
|
-
`scale center requires a number (got "${toks[3] ?? ''}").`
|
|
417
|
-
);
|
|
418
|
-
}
|
|
419
|
-
return scale;
|
|
420
|
-
}
|
|
421
|
-
|
|
422
340
|
function handleTag(trimmed: string, line: number): void {
|
|
423
341
|
const m = matchTagBlockHeading(trimmed);
|
|
424
342
|
if (!m) {
|
|
@@ -559,6 +477,8 @@ export function parseMap(content: string): ParsedMap {
|
|
|
559
477
|
const { tags, meta } = partitionMeta(split.meta, tagGroupNames());
|
|
560
478
|
const originLabel = meta['label'];
|
|
561
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).
|
|
562
482
|
const style: 'straight' | 'arc' =
|
|
563
483
|
meta['style'] === 'arc' ? 'arc' : 'straight';
|
|
564
484
|
const route: Writable<MapRoute> = {
|
|
@@ -608,6 +528,8 @@ export function parseMap(content: string): ParsedMap {
|
|
|
608
528
|
const { tags, meta } = partitionMeta(split.meta, tagGroupNames());
|
|
609
529
|
const value = meta['value'];
|
|
610
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).
|
|
611
533
|
const style: 'straight' | 'arc' =
|
|
612
534
|
arrowStyle === 'arc' || headerStyle === 'arc' ? 'arc' : 'straight';
|
|
613
535
|
return {
|
|
@@ -654,13 +576,17 @@ export function parseMap(content: string): ParsedMap {
|
|
|
654
576
|
pushError(line, `Edge has an empty endpoint: "${trimmed}".`);
|
|
655
577
|
continue;
|
|
656
578
|
}
|
|
657
|
-
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';
|
|
658
584
|
edges.push({
|
|
659
585
|
from,
|
|
660
586
|
to,
|
|
661
587
|
...(links[k]!.label !== undefined && { label: links[k]!.label }),
|
|
662
588
|
directed: links[k]!.directed,
|
|
663
|
-
style
|
|
589
|
+
style,
|
|
664
590
|
meta,
|
|
665
591
|
lineNumber: line,
|
|
666
592
|
});
|