@diagrammo/dgmo 0.19.0 → 0.20.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.cjs +948 -321
- package/dist/advanced.d.cts +148 -54
- package/dist/advanced.d.ts +148 -54
- package/dist/advanced.js +949 -321
- package/dist/auto.cjs +930 -317
- package/dist/auto.js +117 -117
- package/dist/auto.mjs +934 -318
- package/dist/cli.cjs +160 -160
- package/dist/index.cjs +929 -316
- package/dist/index.js +933 -317
- package/dist/internal.cjs +948 -321
- package/dist/internal.d.cts +148 -54
- package/dist/internal.d.ts +148 -54
- package/dist/internal.js +949 -321
- package/dist/map-data/PROVENANCE.json +1 -1
- package/dist/map-data/lakes.json +1 -0
- package/dist/map-data/na-lakes.json +1 -0
- package/dist/map-data/na-land.json +1 -0
- package/dist/map-data/rivers.json +1 -0
- package/docs/language-reference.md +12 -7
- package/gallery/fixtures/map-region-scope.dgmo +15 -0
- package/package.json +4 -4
- package/src/advanced.ts +7 -6
- package/src/c4/parser.ts +6 -6
- package/src/completion.ts +6 -2
- package/src/echarts.ts +1 -1
- package/src/infra/parser.ts +10 -10
- package/src/journey-map/parser.ts +1 -1
- package/src/label-layout.ts +36 -0
- package/src/map/data/PROVENANCE.json +1 -1
- package/src/map/data/README.md +2 -0
- package/src/map/data/lakes.json +1 -0
- package/src/map/data/na-lakes.json +1 -0
- package/src/map/data/na-land.json +1 -0
- package/src/map/data/rivers.json +1 -0
- package/src/map/layout.ts +1022 -205
- package/src/map/load-data.ts +73 -17
- package/src/map/parser.ts +22 -13
- package/src/map/renderer.ts +200 -219
- package/src/map/resolved-types.ts +18 -1
- package/src/map/resolver.ts +79 -7
- package/src/map/types.ts +4 -0
- package/src/mindmap/parser.ts +1 -1
- package/src/sitemap/parser.ts +1 -1
- package/src/utils/legend-d3.ts +42 -0
- package/src/utils/legend-layout.ts +83 -3
- package/src/utils/legend-svg.ts +1 -8
- package/src/utils/legend-types.ts +44 -1
package/src/map/load-data.ts
CHANGED
|
@@ -10,16 +10,41 @@
|
|
|
10
10
|
// build must inject `MapData` (or supply a fetch/bundle loader) — `resolveMap`
|
|
11
11
|
// takes `MapData` by DI precisely so the browser path can differ. Do NOT assume
|
|
12
12
|
// a green Node smoke test proves the browser load.
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
//
|
|
14
|
+
// Node builtins (`fs/promises`, `url`, `path`) are imported LAZILY inside
|
|
15
|
+
// `loadMapData` — never at module top level — so this file can be pulled into a
|
|
16
|
+
// browser bundle (Obsidian's esbuild `platform: browser`, the app's Vite/Rollup
|
|
17
|
+
// web build) without the bundler trying to resolve `node:*`. Mirrors the
|
|
18
|
+
// `await import('jsdom')` seam in render.ts. The web build injects `MapData` via
|
|
19
|
+
// DI and never calls `loadMapData`, so the dynamic import only runs in Node.
|
|
16
20
|
import type { MapData } from './resolved-types';
|
|
17
21
|
import type { BoundaryTopology, Gazetteer } from './data/types';
|
|
18
22
|
|
|
23
|
+
type NodeBuiltins = {
|
|
24
|
+
readFile: typeof import('node:fs/promises').readFile;
|
|
25
|
+
fileURLToPath: typeof import('node:url').fileURLToPath;
|
|
26
|
+
dirname: typeof import('node:path').dirname;
|
|
27
|
+
resolve: typeof import('node:path').resolve;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
async function loadNodeBuiltins(): Promise<NodeBuiltins> {
|
|
31
|
+
const [{ readFile }, { fileURLToPath }, { dirname, resolve }] =
|
|
32
|
+
await Promise.all([
|
|
33
|
+
import('node:fs/promises'),
|
|
34
|
+
import('node:url'),
|
|
35
|
+
import('node:path'),
|
|
36
|
+
]);
|
|
37
|
+
return { readFile, fileURLToPath, dirname, resolve };
|
|
38
|
+
}
|
|
39
|
+
|
|
19
40
|
const FILES = {
|
|
20
41
|
worldCoarse: 'world-coarse.json',
|
|
21
42
|
worldDetail: 'world-detail.json',
|
|
22
43
|
usStates: 'us-states.json',
|
|
44
|
+
lakes: 'lakes.json',
|
|
45
|
+
rivers: 'rivers.json',
|
|
46
|
+
naLand: 'na-land.json',
|
|
47
|
+
naLakes: 'na-lakes.json',
|
|
23
48
|
gazetteer: 'gazetteer.json',
|
|
24
49
|
} as const;
|
|
25
50
|
|
|
@@ -33,15 +58,22 @@ const CANDIDATE_DIRS = [
|
|
|
33
58
|
|
|
34
59
|
let cache: Promise<MapData> | undefined;
|
|
35
60
|
|
|
36
|
-
async function readJson<T>(
|
|
37
|
-
|
|
61
|
+
async function readJson<T>(
|
|
62
|
+
nb: NodeBuiltins,
|
|
63
|
+
dir: string,
|
|
64
|
+
name: string
|
|
65
|
+
): Promise<T> {
|
|
66
|
+
return JSON.parse(await nb.readFile(nb.resolve(dir, name), 'utf8')) as T;
|
|
38
67
|
}
|
|
39
68
|
|
|
40
|
-
async function firstExistingDir(
|
|
69
|
+
async function firstExistingDir(
|
|
70
|
+
nb: NodeBuiltins,
|
|
71
|
+
baseDir: string
|
|
72
|
+
): Promise<string> {
|
|
41
73
|
for (const rel of CANDIDATE_DIRS) {
|
|
42
|
-
const dir = resolve(baseDir, rel);
|
|
74
|
+
const dir = nb.resolve(baseDir, rel);
|
|
43
75
|
try {
|
|
44
|
-
await readFile(resolve(dir, FILES.gazetteer), 'utf8');
|
|
76
|
+
await nb.readFile(nb.resolve(dir, FILES.gazetteer), 'utf8');
|
|
45
77
|
return dir;
|
|
46
78
|
} catch {
|
|
47
79
|
/* try next candidate */
|
|
@@ -74,10 +106,10 @@ function validate(data: MapData): MapData {
|
|
|
74
106
|
* (dist/cli.cjs, where `import.meta.url` is `undefined` → `fileURLToPath`
|
|
75
107
|
* throws). The `typeof __dirname` guard is safe in ESM (evaluates to
|
|
76
108
|
* 'undefined' without a ReferenceError). */
|
|
77
|
-
function moduleBaseDir(): string {
|
|
109
|
+
function moduleBaseDir(nb: NodeBuiltins): string {
|
|
78
110
|
try {
|
|
79
111
|
const url = import.meta.url;
|
|
80
|
-
if (url) return dirname(fileURLToPath(url));
|
|
112
|
+
if (url) return nb.dirname(nb.fileURLToPath(url));
|
|
81
113
|
} catch {
|
|
82
114
|
/* CJS: import.meta unavailable — fall through */
|
|
83
115
|
}
|
|
@@ -91,14 +123,38 @@ function moduleBaseDir(): string {
|
|
|
91
123
|
* call can retry rather than inheriting a poisoned promise. */
|
|
92
124
|
export function loadMapData(): Promise<MapData> {
|
|
93
125
|
cache ??= (async (): Promise<MapData> => {
|
|
94
|
-
const
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
126
|
+
const nb = await loadNodeBuiltins();
|
|
127
|
+
const dir = await firstExistingDir(nb, moduleBaseDir(nb));
|
|
128
|
+
const [
|
|
129
|
+
worldCoarse,
|
|
130
|
+
worldDetail,
|
|
131
|
+
usStates,
|
|
132
|
+
lakes,
|
|
133
|
+
rivers,
|
|
134
|
+
naLand,
|
|
135
|
+
naLakes,
|
|
136
|
+
gazetteer,
|
|
137
|
+
] = await Promise.all([
|
|
138
|
+
readJson<BoundaryTopology>(nb, dir, FILES.worldCoarse),
|
|
139
|
+
readJson<BoundaryTopology>(nb, dir, FILES.worldDetail),
|
|
140
|
+
readJson<BoundaryTopology>(nb, dir, FILES.usStates),
|
|
141
|
+
// Lakes/rivers/NA assets are optional — older bundles may predate them.
|
|
142
|
+
readJson<BoundaryTopology>(nb, dir, FILES.lakes).catch(() => undefined),
|
|
143
|
+
readJson<BoundaryTopology>(nb, dir, FILES.rivers).catch(() => undefined),
|
|
144
|
+
readJson<BoundaryTopology>(nb, dir, FILES.naLand).catch(() => undefined),
|
|
145
|
+
readJson<BoundaryTopology>(nb, dir, FILES.naLakes).catch(() => undefined),
|
|
146
|
+
readJson<Gazetteer>(nb, dir, FILES.gazetteer),
|
|
100
147
|
]);
|
|
101
|
-
return validate({
|
|
148
|
+
return validate({
|
|
149
|
+
worldCoarse,
|
|
150
|
+
worldDetail,
|
|
151
|
+
usStates,
|
|
152
|
+
gazetteer,
|
|
153
|
+
...(lakes && { lakes }),
|
|
154
|
+
...(rivers && { rivers }),
|
|
155
|
+
...(naLand && { naLand }),
|
|
156
|
+
...(naLakes && { naLakes }),
|
|
157
|
+
});
|
|
102
158
|
})().catch((e: unknown) => {
|
|
103
159
|
cache = undefined; // don't poison future calls with a rejected promise
|
|
104
160
|
throw e;
|
package/src/map/parser.ts
CHANGED
|
@@ -105,11 +105,6 @@ export function parseMap(content: string): ParsedMap {
|
|
|
105
105
|
const pushWarning = (line: number, message: string): void => {
|
|
106
106
|
diagnostics.push(makeDgmoError(line, message, 'warning'));
|
|
107
107
|
};
|
|
108
|
-
// §24B calls the score+tag-coexistence note "info", but DgmoSeverity is only
|
|
109
|
-
// error|warning — emit at warning (lowest available informational severity).
|
|
110
|
-
const pushInfo = (line: number, message: string): void => {
|
|
111
|
-
diagnostics.push(makeDgmoError(line, message, 'warning'));
|
|
112
|
-
};
|
|
113
108
|
|
|
114
109
|
const lines = content.split('\n');
|
|
115
110
|
|
|
@@ -289,11 +284,16 @@ export function parseMap(content: string): ParsedMap {
|
|
|
289
284
|
dup(d.projection);
|
|
290
285
|
if (
|
|
291
286
|
value &&
|
|
292
|
-
![
|
|
287
|
+
![
|
|
288
|
+
'equirectangular',
|
|
289
|
+
'natural-earth',
|
|
290
|
+
'albers-usa',
|
|
291
|
+
'mercator',
|
|
292
|
+
].includes(value)
|
|
293
293
|
)
|
|
294
294
|
pushWarning(
|
|
295
295
|
line,
|
|
296
|
-
`Unknown projection "${value}" (expected natural-earth | albers-usa | mercator).`
|
|
296
|
+
`Unknown projection "${value}" (expected equirectangular | natural-earth | albers-usa | mercator).`
|
|
297
297
|
);
|
|
298
298
|
d.projection = value;
|
|
299
299
|
break;
|
|
@@ -441,17 +441,26 @@ export function parseMap(content: string): ParsedMap {
|
|
|
441
441
|
scoreNum = undefined;
|
|
442
442
|
}
|
|
443
443
|
}
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
444
|
+
// A region may carry BOTH a `score:` and a tag value — they are two
|
|
445
|
+
// selectable colouring dimensions (the legend flips between the score ramp
|
|
446
|
+
// and the tag group), so this is no longer warned (bivariate is handled).
|
|
447
|
+
// Peel a trailing ISO scope token (§24B.8) — same qualifier POIs accept,
|
|
448
|
+
// so `Georgia US-GA` / `Georgia US` can force the country-vs-state pick.
|
|
449
|
+
let regionName = split.name;
|
|
450
|
+
let regionScope: string | undefined;
|
|
451
|
+
const rToks = regionName.split(/\s+/);
|
|
452
|
+
const rLast = rToks[rToks.length - 1]!;
|
|
453
|
+
if (rToks.length > 1 && SCOPE_RE.test(rLast)) {
|
|
454
|
+
regionName = rToks.slice(0, -1).join(' ');
|
|
455
|
+
regionScope = rLast;
|
|
456
|
+
}
|
|
449
457
|
const region: Writable<MapRegion> = {
|
|
450
|
-
name:
|
|
458
|
+
name: regionName,
|
|
451
459
|
tags,
|
|
452
460
|
meta,
|
|
453
461
|
lineNumber: line,
|
|
454
462
|
};
|
|
463
|
+
if (regionScope !== undefined) region.scope = regionScope;
|
|
455
464
|
if (scoreNum !== undefined) region.score = scoreNum;
|
|
456
465
|
regions.push(region);
|
|
457
466
|
}
|