@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
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"entries":[[85.078,21.558,"Arctic Ocean",0,"ocean"],[-26.746,83.424,"Indian Ocean",0,"ocean"],[39.898,-30.68,"North Atlantic Ocean",0,"ocean",[[40,-55],[30,-45],[50,-25],[15,-45]]],[24.49,-136.445,"North Pacific Ocean",0,"ocean",[[36,-126],[47,-131],[55,-143],[35,160],[15,165]]],[-34.311,-18.311,"South Atlantic Ocean",0,"ocean",[[-25,-35],[-40,-15],[-10,-20]]],[-30.137,-126.822,"South Pacific Ocean",0,"ocean",[[-20,-110],[-40,-95],[-15,170],[-35,170]]],[-65.892,-18.447,"Southern Ocean",0,"ocean"],[13.692,63.675,"Arabian Sea",1,"sea"],[72.951,-66.643,"Baffin Bay",1,"bay"],[13.118,86.757,"Bay of Bengal",1,"bay"],[71.997,-136.388,"Beaufort Sea",1,"sea"],[43.547,31.292,"Black Sea",1,"sea"],[13.754,-78.235,"Caribbean Sea",1,"sea"],[41.954,50.61,"Caspian Sea",1,"sea"],[-18.886,157.126,"Coral Sea",1,"sea"],[58.108,-149.198,"Gulf of Alaska",1,"gulf"],[25.319,-90.053,"Gulf of America",1,"gulf"],[59.246,-85.292,"Hudson Bay",1,"bay"],[55.979,-52.602,"Labrador Sea",1,"sea"],[34.341,17.988,"Mediterranean Sea",1,"sea"],[25.683,52.866,"Persian Gulf",1,"gulf"],[17.263,133.481,"Philippine Sea",1,"sea"],[19.579,38.751,"Red Sea",1,"sea"],[-77.493,-169.96,"Ross Sea",1,"sea"],[40.613,136.423,"Sea of Japan",1,"sea"],[52.851,149.205,"Sea of Okhotsk",1,"sea"],[13.781,114.703,"South China Sea",1,"sea"],[-40.358,160.682,"Tasman Sea",1,"sea"],[-75.66,-53.473,"Weddell Sea",1,"sea"],[-73.979,-106.379,"Amundsen Sea",2,"sea"],[10.878,95.492,"Andaman Sea",2,"sea"],[-9.347,135.278,"Arafura Sea",2,"sea"],[19.67,-93.559,"Bahía de Campeche",2,"bay"],[56.024,19.234,"Baltic Sea",2,"sea"],[-5.658,126.23,"Banda Sea",2,"sea"],[74.604,43.063,"Barents Sea",2,"sea"],[45.627,-4.33,"Bay of Biscay",2,"bay"],[-72.084,-82.745,"Bellingshausen Sea",2,"sea"],[56.349,-171.512,"Bering Sea",2,"sea"],[3.571,122.574,"Celebes Sea",2,"sea"],[68.633,-169.418,"Chukchi Sea",2,"sea"],[64.549,-57.848,"Davis Strait",2,"strait"],[-66.686,-68.017,"Drake Passage",2,"channel"],[28.854,125.37,"East China Sea",2,"sea"],[76.558,-8.516,"Greenland Sea",2,"sea"],[3.294,2.903,"Gulf of Guinea",2,"gulf"],[9.545,101.832,"Gulf of Thailand",2,"gulf"],[53.543,-5.35,"Irish Sea",2,"sea"],[-5.2,112.569,"Java Sea",2,"sea"],[76.92,73.103,"Kara Sea",2,"sea"],[5.871,75.234,"Laccadive Sea",2,"sea"],[75.961,120.379,"Laptev Sea",2,"sea"],[22.073,120.87,"Luzon Strait",2,"strait"],[-20.03,40.414,"Mozambique Channel",2,"channel"],[56.502,2.655,"North Sea",2,"sea"],[66.883,1.329,"Norwegian Sea",2,"sea"],[27.646,-59.716,"Sargasso Sea",2,"sea"],[-61.149,-54.893,"Scotia Sea",2,"sea"],[8.392,120.208,"Sulu Sea",2,"sea"],[-10.978,127.712,"Timor Sea",2,"sea"],[35.164,123.956,"Yellow Sea",2,"sea"],[42.81,15.327,"Adriatic Sea",3,"sea"],[49.707,-2.894,"English Channel",3,"channel"],[26.35,-110.53,"Golfo de California",3,"gulf"],[-35.41,131.682,"Great Australian Bight",3,"gulf"],[12.621,48.305,"Gulf of Aden",3,"gulf"],[61.904,19.763,"Gulf of Bothnia",3,"gulf"],[-14.387,139.215,"Gulf of Carpentaria",3,"gulf"],[16.3,-88.001,"Gulf of Honduras",3,"gulf"],[24.54,58.766,"Gulf of Oman",3,"gulf"],[63.3,-73.224,"Hudson Strait",3,"strait"],[53.746,-80.255,"James Bay",3,"bay"],[39.863,12.144,"Tyrrhenian Sea",3,"sea"],[65.418,38.854,"White Sea",3,"sea"],[38.899,24.994,"Aegean Sea",4,"sea"],[70.531,-120.687,"Amundsen Gulf",4,"gulf"],[40.132,1.619,"Balearic Sea",4,"sea"],[44.909,-66.051,"Bay of Fundy",4,"bay"],[-37.489,176.808,"Bay of Plenty",4,"bay"],[-3.776,147.903,"Bismarck Sea",4,"sea"],[38.756,120.053,"Bo Hai",4,"sea"],[57.651,-160.179,"Bristol Bay",4,"bay"],[51.086,-5.339,"Bristol Channel",4,"channel"],[-2.412,131.169,"Ceram Sea",4,"sea"],[37.829,-76.031,"Chesapeake Bay",4,"bay"],[59.657,-152.312,"Cook Inlet",4,"bay"],[42.981,3.976,"Golfe du Lion",4,"gulf"],[-46.117,-66.587,"Golfo San Jorge",4,"gulf"],[8.18,-79.211,"Golfo de Panamá",4,"gulf"],[59.969,26.987,"Gulf of Finland",4,"gulf"],[22.57,69.333,"Gulf of Kutch",4,"gulf"],[43.368,-68.504,"Gulf of Maine",4,"gulf"],[8.43,78.962,"Gulf of Mannar",4,"gulf"],[48.615,-60.525,"Gulf of Saint Lawrence",4,"gulf"],[19.791,107.611,"Gulf of Tonkin",4,"gulf"],[33.956,132.471,"Inner Sea",4,"sea"],[56.267,-7.03,"Inner Seas",4,"sea"],[38.178,18.626,"Ionian Sea",4,"sea"],[33.799,128.392,"Korea Strait",4,"strait"],[-1.86,118.021,"Makassar Strait",4,"strait"],[75.523,-61.147,"Melville Bay",4,"bay"],[0.591,126.128,"Molucca Sea",4,"sea"],[-35.469,-56.554,"Río de la Plata",4,"gulf"],[59.897,157.821,"Shelikhova Gulf",4,"gulf"],[-9.247,155.069,"Solomon Sea",4,"sea"],[35.783,-5.919,"Strait of Gibraltar",4,"strait"],[5.586,99.038,"Strait of Malacca",4,"strait"],[1.193,103.68,"Strait of Singapore",4,"strait"],[25.571,-79.276,"Straits of Florida",4,"strait"],[24.166,119.387,"Taiwan Strait",4,"strait"],[70.288,-99.272,"The North Western Passages",4,"channel"],[59.341,-67.41,"Ungava Bay",4,"bay"],[73.851,-109.102,"Viscount Melville Sound",4,"sound"]]}
|
|
@@ -2644,7 +2644,9 @@ Markers in cells are always **rendered in canonical alphabet order** (`R A C I`,
|
|
|
2644
2644
|
|
|
2645
2645
|
Geographic concept maps: highlight/shade political subdivisions, drop points of interest (POIs), and connect them with routes or edges. For "share a concept" business maps, not cartography. Renders at a fixed, auto-fit position — no pan/zoom. Basemap and viewport are **inferred from the content you reference** — most maps need no directives. v1 boundaries: world countries + US states.
|
|
2646
2646
|
|
|
2647
|
-
**
|
|
2647
|
+
**The zero-config map is the good-looking map.** Type `map`, name some places, and you're done — coastlines, mountain relief (on reference maps), region/POI labels, and orientation labels all render by default. There is no projection, scale, or label directive; the only knobs are the bare `no-*` opt-outs.
|
|
2648
|
+
|
|
2649
|
+
**How the map type is decided (inference):** the resolver takes the bounding box of everything referenced (valued/tagged regions + POIs + edge endpoints), pads it, and measures its span. Projection is **always inferred — never configured**: `albers-usa` (US conic + AK/HI insets) when the map is US-oriented; at world/multi-continent scale a **data** map (any region/POI carries `value:` or a tag) gets **Equal Earth** (equal-area — honest for thematic comparison) while a **dataless reference** map gets **natural-earth** (the prettier curved compromise); `mercator` for a tight regional or single-continent cluster. The US-state mesh is added whenever you name a US state (or the map is US-oriented).
|
|
2648
2650
|
|
|
2649
2651
|
### Declaration
|
|
2650
2652
|
|
|
@@ -2660,7 +2662,6 @@ A subdivision name on its own line with a `value:` fills with a single-hue tint
|
|
|
2660
2662
|
|
|
2661
2663
|
```
|
|
2662
2664
|
map US Sales
|
|
2663
|
-
region us-states
|
|
2664
2665
|
region-metric Sales ($M)
|
|
2665
2666
|
|
|
2666
2667
|
California value: 92
|
|
@@ -2668,7 +2669,8 @@ Texas value: 78
|
|
|
2668
2669
|
Florida value: 51
|
|
2669
2670
|
```
|
|
2670
2671
|
|
|
2671
|
-
- `region-metric <label>` labels the ramp in the legend; a trailing color on it sets the ramp hue (`region-metric Sales ($M) blue` → blue ramp, default red).
|
|
2672
|
+
- `region-metric <label>` labels the ramp in the legend; a trailing color on it sets the ramp hue (`region-metric Sales ($M) blue` → blue ramp, default red).
|
|
2673
|
+
- The ramp **auto-fits**: all-non-negative data anchors the low end at **0** (shared baseline); mixed-sign data fits data-min→data-max. There is no `scale` directive.
|
|
2672
2674
|
- A subdivision with no `value:`/tag renders as the neutral base.
|
|
2673
2675
|
|
|
2674
2676
|
### Region fill — categorical (tags)
|
|
@@ -2677,7 +2679,6 @@ Uses the universal tag model (§1.3): declare a `tag` group and apply its alias
|
|
|
2677
2679
|
|
|
2678
2680
|
```
|
|
2679
2681
|
map Global Presence
|
|
2680
|
-
region world
|
|
2681
2682
|
|
|
2682
2683
|
tag Market as m
|
|
2683
2684
|
HQ blue
|
|
@@ -2734,18 +2735,28 @@ dcw # hub/star — indented edges share the source
|
|
|
2734
2735
|
-> office-west
|
|
2735
2736
|
```
|
|
2736
2737
|
|
|
2737
|
-
`~>` curves a single edge.
|
|
2738
|
+
`~>` curves a single edge. There is no geographic path-finding and no `surface:` — legs are plain straight or arced geometry (`style: arc` to bow one) and may cross land.
|
|
2739
|
+
|
|
2740
|
+
```
|
|
2741
|
+
map Caribbean Cruise
|
|
2742
|
+
|
|
2743
|
+
route Miami style: arc
|
|
2744
|
+
-weigh anchor-> Havana
|
|
2745
|
+
-> Kingston
|
|
2746
|
+
-> Cartagena
|
|
2747
|
+
```
|
|
2738
2748
|
|
|
2739
2749
|
### Labels, legend & chrome
|
|
2740
2750
|
|
|
2741
|
-
- Title is the declaration line; `
|
|
2751
|
+
- Title is the declaration line; `caption` (data-source attribution, travels with the exported PNG) is the only chrome directive. There is no `subtitle`.
|
|
2742
2752
|
- Legend auto-composes below the title: the value ramp + `region-metric` and each tag group are **selectable colouring groups** (collapse/activate to flip the fill); POI size (`poi-metric`) and edge thickness (`flow-metric`) are self-evident from scale and carry no legend key in v1. `no-legend` suppresses all of it.
|
|
2743
|
-
-
|
|
2753
|
+
- **Region and POI labels are on by default.** Region labels auto-fit **full → abbrev → hide** (a US-state 2-letter abbreviation is tried when the full name doesn't fit; other regions degrade full → hide); POI labels are collision-managed. Labels render **on the map** (export-safe), escalating inline → leader line → numbered pin in dense clusters; markers never move. A wide map in a narrow column (< ~480px) prefers abbreviations and drops reference relief, as if zoomed out.
|
|
2754
|
+
- **Cosmetic features are on by default**; the only switches are bare `no-*` opt-outs (no positive opt-in flag): `no-coastline`, `no-relief`, `no-context-labels`, `no-region-labels`, `no-poi-labels`, `no-legend`. A plain look = the four basemap flags together.
|
|
2744
2755
|
|
|
2745
2756
|
### Name resolution
|
|
2746
2757
|
|
|
2747
2758
|
- Admin units use **ISO 3166** (geometry keyed by code, so "United States" / "USA" / "US" resolve alike); cities use **GeoNames** (alias/accent matching, population ranking, did-you-mean).
|
|
2748
|
-
- `
|
|
2759
|
+
- `locale <ISO>` scopes bare city resolution to a country (`locale US`) or subdivision (`locale US-GA`) — inferred from content if unset.
|
|
2749
2760
|
- A bare ambiguous, undeclared name → most-populous in scope (info note).
|
|
2750
2761
|
- **Disambiguate once:** trailing ISO code at first declaration — `San Jose CR` (country) or `Portland US-OR` (subdivision). Thereafter reference the bare name. Two same-named cities → `as <alias>` each.
|
|
2751
2762
|
- **Region fills disambiguate the country-vs-state collision** (`Georgia` = country `GE` or US state `US-GA`) by ISO code or name + scope — pick whichever reads best:
|
|
@@ -2756,7 +2767,7 @@ dcw # hub/star — indented edges share the source
|
|
|
2756
2767
|
|
|
2757
2768
|
### Directives & reserved keys
|
|
2758
2769
|
|
|
2759
|
-
|
|
2770
|
+
The directive set is **12, all colon-free**: six naming intent the renderer can't infer — `region-metric`, `poi-metric`, `flow-metric`, `locale`, `active-tag`, `caption` — and six `no-*` cosmetic opt-outs — `no-legend`, `no-coastline`, `no-relief`, `no-context-labels`, `no-region-labels`, `no-poi-labels`. There is **no** `projection`, `scale`, `subtitle`, `surface`, `region`, or label-enum directive, and cosmetics have no positive opt-in form. Reserved metadata keys (need colons): `value`, `label`, `style` (`value` = the one numeric channel: region shade / POI size / edge thickness); `surface:` is no longer recognized. A bare US state postal code resolves to that state (`poi Portland OR` → Oregon; `CA` = California). Coordinates are positional (no `at:` key). Projection is inferred from extent + whether the map carries data (US → albers-usa; world data → Equal Earth; world reference → natural-earth; regional → mercator) and cannot be overridden.
|
|
2760
2771
|
|
|
2761
2772
|
---
|
|
2762
2773
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
map Global Market Presence
|
|
2
|
+
|
|
3
|
+
tag Market as m
|
|
4
|
+
HQ blue
|
|
5
|
+
Region teal
|
|
6
|
+
Prospect orange
|
|
7
|
+
active-tag Market
|
|
8
|
+
|
|
9
|
+
United States m: HQ
|
|
10
|
+
Germany m: Region
|
|
11
|
+
Japan m: Region
|
|
12
|
+
Brazil m: Prospect
|
|
13
|
+
India m: Region
|
|
14
|
+
Australia m: Prospect
|
|
15
|
+
South Africa m: Prospect
|
|
16
|
+
Indonesia m: Prospect
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
map Privateer's Atlas
|
|
2
|
+
|
|
3
|
+
// Regions named, no value: or tags → colorize is the inferred dress: every
|
|
4
|
+
// state/country gets a distinct pastel and no two neighbours share a hue
|
|
5
|
+
// (4-colour political look). The named states aren't special — the whole drawn
|
|
6
|
+
// mesh colours, so it reads as one chart. `no-colorize` would force green-land.
|
|
7
|
+
California
|
|
8
|
+
Texas
|
|
9
|
+
Florida
|
|
10
|
+
New York
|
|
11
|
+
Washington
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
map World Reference
|
|
2
|
+
|
|
3
|
+
// A dataless reference map (regions named, no value: or tags) — so the
|
|
4
|
+
// zero-config basemap shows its full dress: coastline water-lines, mountain
|
|
5
|
+
// relief, and orientation labels, on a natural-earth projection.
|
|
6
|
+
United States
|
|
7
|
+
Brazil
|
|
8
|
+
Egypt
|
|
9
|
+
India
|
|
10
|
+
Japan
|
|
11
|
+
Australia
|
package/package.json
CHANGED
package/src/advanced.ts
CHANGED
|
@@ -540,6 +540,14 @@ export type {
|
|
|
540
540
|
MapLayoutLegend,
|
|
541
541
|
} from './map/layout';
|
|
542
542
|
export { renderMap, renderMapForExport } from './map/renderer';
|
|
543
|
+
// Content-aware export dimensions — derive the canvas height from a map's intrinsic
|
|
544
|
+
// projected aspect so exports/embeds match the content's natural shape (no vertical
|
|
545
|
+
// stretch). Used by the CLI/MCP/SSG export path and by Obsidian's DI render.
|
|
546
|
+
export {
|
|
547
|
+
mapContentAspect,
|
|
548
|
+
mapExportDimensions,
|
|
549
|
+
type MapExportDimensions,
|
|
550
|
+
} from './map/dimensions';
|
|
543
551
|
// Map geo-query (step-5 coordinate/location inspector) — a SEPARATE entry from
|
|
544
552
|
// the renderer; takes `MapData` by DI so it's browser-safe (never calls the
|
|
545
553
|
// Node-only `loadMapData`).
|
|
@@ -734,7 +742,10 @@ export {
|
|
|
734
742
|
gruvboxPalette,
|
|
735
743
|
tokyoNightPalette,
|
|
736
744
|
oneDarkPalette,
|
|
737
|
-
|
|
745
|
+
atlasPalette,
|
|
746
|
+
blueprintPalette,
|
|
747
|
+
slatePalette,
|
|
748
|
+
tidewaterPalette,
|
|
738
749
|
draculaPalette,
|
|
739
750
|
monokaiPalette,
|
|
740
751
|
} from './palettes';
|
|
@@ -324,6 +324,9 @@ interface BLRenderOptions {
|
|
|
324
324
|
onToggleDescriptions?: (active: boolean) => void;
|
|
325
325
|
onToggleControlsExpand?: () => void;
|
|
326
326
|
exportMode?: boolean;
|
|
327
|
+
/** When 'app', the description toggle is hosted by the app overlay strip
|
|
328
|
+
* (inline gear suppressed, controls row + anchor reserved). */
|
|
329
|
+
controlsHost?: 'app' | 'inline';
|
|
327
330
|
}
|
|
328
331
|
|
|
329
332
|
export function renderBoxesAndLines(
|
|
@@ -344,6 +347,7 @@ export function renderBoxesAndLines(
|
|
|
344
347
|
onToggleDescriptions,
|
|
345
348
|
onToggleControlsExpand,
|
|
346
349
|
exportMode = false,
|
|
350
|
+
controlsHost,
|
|
347
351
|
} = options ?? {};
|
|
348
352
|
d3Selection.select(container).selectAll(':not([data-d3-tooltip])').remove();
|
|
349
353
|
|
|
@@ -364,16 +368,27 @@ export function renderBoxesAndLines(
|
|
|
364
368
|
const sGroupLabelZone = sctx.structural(GROUP_LABEL_ZONE);
|
|
365
369
|
const sTitleFontSize = sctx.text(TITLE_FONT_SIZE);
|
|
366
370
|
const sTitleY = sctx.structural(TITLE_Y);
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
mode: exportMode ? 'export' : 'preview',
|
|
373
|
-
},
|
|
374
|
-
width
|
|
375
|
-
)
|
|
371
|
+
// Reserve legend height only when a legend will actually render. App-hosted
|
|
372
|
+
// controls move the Descriptions toggle to the app overlay, so a
|
|
373
|
+
// descriptions-only chart (no tag groups) reserves nothing.
|
|
374
|
+
const reserveHasDescriptions = parsed.nodes.some(
|
|
375
|
+
(n) => n.description && n.description.length > 0
|
|
376
376
|
);
|
|
377
|
+
const willRenderLegend =
|
|
378
|
+
parsed.tagGroups.length > 0 ||
|
|
379
|
+
(reserveHasDescriptions && controlsHost !== 'app');
|
|
380
|
+
const sLegendHeight = willRenderLegend
|
|
381
|
+
? sctx.structural(
|
|
382
|
+
getMaxLegendReservedHeight(
|
|
383
|
+
{
|
|
384
|
+
groups: parsed.tagGroups,
|
|
385
|
+
position: { placement: 'top-center', titleRelation: 'below-title' },
|
|
386
|
+
mode: exportMode ? 'export' : 'preview',
|
|
387
|
+
},
|
|
388
|
+
width
|
|
389
|
+
)
|
|
390
|
+
)
|
|
391
|
+
: 0;
|
|
377
392
|
|
|
378
393
|
const activeGroup = resolveActiveTagGroup(
|
|
379
394
|
parsed.tagGroups,
|
|
@@ -995,12 +1010,17 @@ export function renderBoxesAndLines(
|
|
|
995
1010
|
const hasDescriptions = parsed.nodes.some(
|
|
996
1011
|
(n) => n.description && n.description.length > 0
|
|
997
1012
|
);
|
|
998
|
-
|
|
1013
|
+
// App-hosted: the Descriptions control moves to the app overlay, so a
|
|
1014
|
+
// descriptions-only legend (no tag groups) has nothing left to render.
|
|
1015
|
+
const hasLegend =
|
|
1016
|
+
parsed.tagGroups.length > 0 || (hasDescriptions && controlsHost !== 'app');
|
|
999
1017
|
|
|
1000
1018
|
if (hasLegend) {
|
|
1001
|
-
// Build controls group for description toggle
|
|
1019
|
+
// Build controls group for description toggle. App-hosted controls own the
|
|
1020
|
+
// toggling, so the group is built (to gate + size the row) even without the
|
|
1021
|
+
// inline-gear callback.
|
|
1002
1022
|
let controlsGroup: { toggles: ControlsGroupToggle[] } | undefined;
|
|
1003
|
-
if (hasDescriptions && onToggleDescriptions) {
|
|
1023
|
+
if (hasDescriptions && (onToggleDescriptions || controlsHost === 'app')) {
|
|
1004
1024
|
controlsGroup = {
|
|
1005
1025
|
toggles: [
|
|
1006
1026
|
{
|
|
@@ -1018,7 +1038,14 @@ export function renderBoxesAndLines(
|
|
|
1018
1038
|
groups: parsed.tagGroups,
|
|
1019
1039
|
position: { placement: 'top-center', titleRelation: 'below-title' },
|
|
1020
1040
|
mode: exportMode ? 'export' : 'preview',
|
|
1041
|
+
// Keep inactive sibling tag groups visible as collapsed pills so the user
|
|
1042
|
+
// can click one to flip the active colouring dimension (preview only —
|
|
1043
|
+
// export shows just the active group). Without this, declaring a second
|
|
1044
|
+
// tag group (e.g. Team) leaves it invisible whenever another group is
|
|
1045
|
+
// active. The app's BoxesAndLinesPreview already wires pill clicks.
|
|
1046
|
+
showInactivePills: true,
|
|
1021
1047
|
...(controlsGroup !== undefined && { controlsGroup }),
|
|
1048
|
+
...(controlsHost !== undefined && { controlsHost }),
|
|
1022
1049
|
};
|
|
1023
1050
|
const legendState: LegendState = {
|
|
1024
1051
|
activeGroup,
|
package/src/cli.ts
CHANGED
|
@@ -136,7 +136,7 @@ Key options:
|
|
|
136
136
|
- \`-o <file>\` — output file; format inferred from extension (\`.svg\` → SVG, else PNG)
|
|
137
137
|
- \`-o url\` — output a shareable diagrammo.app URL
|
|
138
138
|
- \`--theme <theme>\` — \`light\` (default), \`dark\`, \`transparent\`
|
|
139
|
-
- \`--palette <name>\` — \`nord\` (default), \`solarized\`, \`catppuccin\`, \`rose-pine\`, \`gruvbox\`, \`tokyo-night\`, \`one-dark\`, \`
|
|
139
|
+
- \`--palette <name>\` — \`nord\` (default), \`atlas\`, \`blueprint\`, \`slate\`, \`tidewater\`, \`solarized\`, \`catppuccin\`, \`rose-pine\`, \`gruvbox\`, \`tokyo-night\`, \`one-dark\`, \`dracula\`, \`monokai\`
|
|
140
140
|
- \`--copy\` — copy the URL to clipboard (use with \`-o url\`)
|
|
141
141
|
- \`--chart-types\` — list all supported chart types
|
|
142
142
|
|
package/src/completion.ts
CHANGED
|
@@ -98,9 +98,12 @@ const GLOBAL_DIRECTIVES: Record<string, DirectiveValueSpec> = {
|
|
|
98
98
|
'gruvbox',
|
|
99
99
|
'tokyo-night',
|
|
100
100
|
'one-dark',
|
|
101
|
-
'bold',
|
|
102
101
|
'dracula',
|
|
103
102
|
'monokai',
|
|
103
|
+
'atlas',
|
|
104
|
+
'blueprint',
|
|
105
|
+
'slate',
|
|
106
|
+
'tidewater',
|
|
104
107
|
],
|
|
105
108
|
},
|
|
106
109
|
theme: {
|
|
@@ -508,19 +511,12 @@ export const COMPLETION_REGISTRY = new Map<string, DirectiveSpec>([
|
|
|
508
511
|
],
|
|
509
512
|
[
|
|
510
513
|
'map',
|
|
511
|
-
// Geographic map directives (§24B.2/.7).
|
|
512
|
-
//
|
|
513
|
-
//
|
|
514
|
+
// Geographic map directives (§24B.2/.7). Cosmetics are ON by default — the
|
|
515
|
+
// only switches are bare `no-*` opt-outs, surfaced proactively so a
|
|
516
|
+
// zero-config map still hints at what can be turned off. `poi`/`route` are
|
|
517
|
+
// content keywords, not directives; metadata keys (value/label/style) live
|
|
518
|
+
// in the reserved-key registry.
|
|
514
519
|
withGlobals({
|
|
515
|
-
region: {
|
|
516
|
-
description:
|
|
517
|
-
'Basemap: us-states (force US state mesh + scoping) | world (inert — already the default)',
|
|
518
|
-
values: ['us-states', 'world'],
|
|
519
|
-
},
|
|
520
|
-
projection: {
|
|
521
|
-
description: 'Override the auto projection',
|
|
522
|
-
values: ['equirectangular', 'natural-earth', 'albers-usa', 'mercator'],
|
|
523
|
-
},
|
|
524
520
|
'region-metric': { description: 'Label for the region value ramp' },
|
|
525
521
|
'poi-metric': {
|
|
526
522
|
description: 'Label for the POI value (marker size) channel',
|
|
@@ -528,21 +524,32 @@ export const COMPLETION_REGISTRY = new Map<string, DirectiveSpec>([
|
|
|
528
524
|
'flow-metric': {
|
|
529
525
|
description: 'Label for the edge/leg value (thickness) channel',
|
|
530
526
|
},
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
values: ['full', 'abbrev', 'off'],
|
|
527
|
+
locale: {
|
|
528
|
+
description:
|
|
529
|
+
'Default country/state for bare place names, e.g. locale US-GA',
|
|
535
530
|
},
|
|
536
|
-
'
|
|
537
|
-
description: '
|
|
538
|
-
values: ['off', 'auto', 'all'],
|
|
531
|
+
'active-tag': {
|
|
532
|
+
description: 'Which tag group leads when several are present',
|
|
539
533
|
},
|
|
540
|
-
|
|
541
|
-
'default-state': { description: 'ISO subdivision scope' },
|
|
534
|
+
caption: { description: 'Caption line (data-source attribution)' },
|
|
542
535
|
'no-legend': { description: 'Suppress the legend' },
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
536
|
+
'no-coastline': {
|
|
537
|
+
description: 'Turn off coastal water-lines (on by default)',
|
|
538
|
+
},
|
|
539
|
+
'no-relief': {
|
|
540
|
+
description: 'Turn off mountain-range relief shading (on by default)',
|
|
541
|
+
},
|
|
542
|
+
'no-context-labels': {
|
|
543
|
+
description: 'Turn off orientation labels for water + nearby countries',
|
|
544
|
+
},
|
|
545
|
+
'no-region-labels': {
|
|
546
|
+
description: 'Turn off subdivision name labels (on by default)',
|
|
547
|
+
},
|
|
548
|
+
'no-poi-labels': { description: 'Turn off POI labels (on by default)' },
|
|
549
|
+
'no-colorize': {
|
|
550
|
+
description:
|
|
551
|
+
'Force plain green-land reference dress (regions are auto-coloured by default)',
|
|
552
|
+
},
|
|
546
553
|
}),
|
|
547
554
|
],
|
|
548
555
|
]);
|
package/src/cycle/renderer.ts
CHANGED
|
@@ -48,6 +48,10 @@ export interface CycleRenderOptions {
|
|
|
48
48
|
onToggleDescriptions?: (active: boolean) => void;
|
|
49
49
|
onToggleControlsExpand?: () => void;
|
|
50
50
|
exportMode?: boolean;
|
|
51
|
+
/** When 'app', the description toggle is hosted by the app overlay strip:
|
|
52
|
+
* the inline gear is suppressed and a controls row + anchor are reserved.
|
|
53
|
+
* Default (inline) renders the gear as before. */
|
|
54
|
+
controlsHost?: 'app' | 'inline';
|
|
51
55
|
}
|
|
52
56
|
|
|
53
57
|
/**
|
|
@@ -92,7 +96,13 @@ export function renderCycle(
|
|
|
92
96
|
const hasDescriptions =
|
|
93
97
|
parsed.nodes.some((n) => n.description.length > 0) ||
|
|
94
98
|
parsed.edges.some((e) => e.description.length > 0);
|
|
95
|
-
|
|
99
|
+
// App-hosted: controls live in the app overlay strip. Cycle has no tag groups,
|
|
100
|
+
// so there's no in-SVG legend left to render — don't reserve a legend band.
|
|
101
|
+
const appHostedControls = renderOptions?.controlsHost === 'app';
|
|
102
|
+
const hasLegend =
|
|
103
|
+
!appHostedControls &&
|
|
104
|
+
hasDescriptions &&
|
|
105
|
+
!!renderOptions?.onToggleDescriptions;
|
|
96
106
|
|
|
97
107
|
const showTitle = !!parsed.title && parsed.options['no-title'] !== 'on';
|
|
98
108
|
const legendOffset = hasLegend ? sLegendHeight : 0;
|
|
@@ -160,6 +170,9 @@ export function renderCycle(
|
|
|
160
170
|
position: { placement: 'top-center', titleRelation: 'below-title' },
|
|
161
171
|
mode: renderOptions?.exportMode ? 'export' : 'preview',
|
|
162
172
|
controlsGroup,
|
|
173
|
+
...(renderOptions?.controlsHost !== undefined && {
|
|
174
|
+
controlsHost: renderOptions.controlsHost,
|
|
175
|
+
}),
|
|
163
176
|
};
|
|
164
177
|
const legendState: LegendState = {
|
|
165
178
|
activeGroup: null,
|
package/src/d3.ts
CHANGED
|
@@ -8459,6 +8459,7 @@ export async function renderForExport(
|
|
|
8459
8459
|
const { parseMap } = await import('./map/parser');
|
|
8460
8460
|
const { resolveMap } = await import('./map/resolver');
|
|
8461
8461
|
const { renderMapForExport } = await import('./map/renderer');
|
|
8462
|
+
const { mapExportDimensions } = await import('./map/dimensions');
|
|
8462
8463
|
|
|
8463
8464
|
const effectivePalette = await resolveExportPalette(theme, palette);
|
|
8464
8465
|
const mapParsed = parseMap(content);
|
|
@@ -8478,14 +8479,19 @@ export async function renderForExport(
|
|
|
8478
8479
|
}
|
|
8479
8480
|
const mapResolved = resolveMap(mapParsed, mapData);
|
|
8480
8481
|
|
|
8481
|
-
|
|
8482
|
+
// Content-aware canvas: derive the height from the map's intrinsic projected
|
|
8483
|
+
// aspect (world ~2.3:1, a region taller, etc.) instead of the fixed 800, so the
|
|
8484
|
+
// export matches the content's natural shape — no vertical stretch, no
|
|
8485
|
+
// letterbox bands. `preferContain` rides along to the renderer.
|
|
8486
|
+
const dims = mapExportDimensions(mapResolved, mapData, EXPORT_WIDTH);
|
|
8487
|
+
const container = createExportContainer(dims.width, dims.height);
|
|
8482
8488
|
renderMapForExport(
|
|
8483
8489
|
container,
|
|
8484
8490
|
mapResolved,
|
|
8485
8491
|
mapData,
|
|
8486
8492
|
effectivePalette,
|
|
8487
8493
|
theme === 'dark',
|
|
8488
|
-
|
|
8494
|
+
dims
|
|
8489
8495
|
);
|
|
8490
8496
|
return finalizeSvgExport(container, theme, effectivePalette);
|
|
8491
8497
|
}
|
package/src/editor/keywords.ts
CHANGED
|
@@ -80,11 +80,10 @@ export const METADATA_KEYS = new Set([
|
|
|
80
80
|
'quadrant',
|
|
81
81
|
'ring',
|
|
82
82
|
'trend',
|
|
83
|
-
// Map (§24B) metadata keys
|
|
84
|
-
'
|
|
83
|
+
// Map (§24B) reserved metadata keys
|
|
84
|
+
'value',
|
|
85
85
|
'label',
|
|
86
|
-
'
|
|
87
|
-
'weight',
|
|
86
|
+
'style',
|
|
88
87
|
]);
|
|
89
88
|
|
|
90
89
|
/** Tag declaration keyword. */
|
|
@@ -150,22 +149,20 @@ export const DIRECTIVE_KEYWORDS = new Set([
|
|
|
150
149
|
// Sequence
|
|
151
150
|
'activations',
|
|
152
151
|
'no-activations',
|
|
153
|
-
// Map (§24B) directives
|
|
154
|
-
'region',
|
|
155
|
-
'projection',
|
|
152
|
+
// Map (§24B) directives — cosmetics on by default, bare `no-*` opt-outs
|
|
156
153
|
'region-metric',
|
|
157
154
|
'poi-metric',
|
|
158
155
|
'flow-metric',
|
|
159
|
-
'
|
|
160
|
-
'
|
|
161
|
-
'default-country',
|
|
162
|
-
'default-state',
|
|
163
|
-
'no-legend',
|
|
164
|
-
'no-insets',
|
|
165
|
-
'muted',
|
|
166
|
-
'natural',
|
|
167
|
-
'subtitle',
|
|
156
|
+
'locale',
|
|
157
|
+
'active-tag',
|
|
168
158
|
'caption',
|
|
159
|
+
'no-legend',
|
|
160
|
+
'no-coastline',
|
|
161
|
+
'no-relief',
|
|
162
|
+
'no-context-labels',
|
|
163
|
+
'no-region-labels',
|
|
164
|
+
'no-poi-labels',
|
|
165
|
+
'no-colorize',
|
|
169
166
|
'poi',
|
|
170
167
|
'route',
|
|
171
168
|
// Data charts
|
package/src/infra/renderer.ts
CHANGED
|
@@ -2080,9 +2080,13 @@ function renderLegend(
|
|
|
2080
2080
|
isDark: boolean,
|
|
2081
2081
|
activeGroup: string | null,
|
|
2082
2082
|
playback?: InfraPlaybackState,
|
|
2083
|
-
exportMode = false
|
|
2083
|
+
exportMode = false,
|
|
2084
|
+
controlsHost?: 'app' | 'inline'
|
|
2084
2085
|
) {
|
|
2085
2086
|
if (legendGroups.length === 0 && !playback) return;
|
|
2087
|
+
// App-hosted playback: the play/pause + speed UI lives in the app overlay
|
|
2088
|
+
// strip, so suppress the in-SVG Playback pill and emit the controls anchor.
|
|
2089
|
+
const appHostedPlayback = controlsHost === 'app' && !!playback;
|
|
2086
2090
|
|
|
2087
2091
|
const legendG = rootSvg
|
|
2088
2092
|
.append('g')
|
|
@@ -2097,8 +2101,9 @@ function renderLegend(
|
|
|
2097
2101
|
name: g.name,
|
|
2098
2102
|
entries: g.entries.map((e) => ({ value: e.value, color: e.color })),
|
|
2099
2103
|
}));
|
|
2100
|
-
// Add Playback as a group with empty entries (collapsed pill) or dummy entries
|
|
2101
|
-
|
|
2104
|
+
// Add Playback as a group with empty entries (collapsed pill) or dummy entries
|
|
2105
|
+
// (expanded) — unless the app hosts it, in which case it's suppressed.
|
|
2106
|
+
if (playback && !appHostedPlayback) {
|
|
2102
2107
|
allGroups.push({ name: 'Playback', entries: [] });
|
|
2103
2108
|
}
|
|
2104
2109
|
|
|
@@ -2107,6 +2112,20 @@ function renderLegend(
|
|
|
2107
2112
|
position: { placement: 'top-center', titleRelation: 'below-title' },
|
|
2108
2113
|
mode: exportMode ? 'export' : 'preview',
|
|
2109
2114
|
showEmptyGroups: true,
|
|
2115
|
+
...(appHostedPlayback && {
|
|
2116
|
+
controlsHost: 'app' as const,
|
|
2117
|
+
controlsGroup: {
|
|
2118
|
+
toggles: [
|
|
2119
|
+
{
|
|
2120
|
+
id: 'playback',
|
|
2121
|
+
type: 'toggle' as const,
|
|
2122
|
+
label: 'Playback',
|
|
2123
|
+
active: true,
|
|
2124
|
+
onToggle: () => {},
|
|
2125
|
+
},
|
|
2126
|
+
],
|
|
2127
|
+
},
|
|
2128
|
+
}),
|
|
2110
2129
|
};
|
|
2111
2130
|
const legendState: LegendState = { activeGroup };
|
|
2112
2131
|
renderLegendD3(
|
|
@@ -2233,9 +2252,13 @@ export function renderInfra(
|
|
|
2233
2252
|
playback?: InfraPlaybackState | null,
|
|
2234
2253
|
expandedNodeIds?: Set<string> | null,
|
|
2235
2254
|
exportMode?: boolean,
|
|
2236
|
-
collapsedNodes?: Set<string> | null
|
|
2255
|
+
collapsedNodes?: Set<string> | null,
|
|
2256
|
+
/** When 'app', the playback pill is suppressed and a controls row + anchor are
|
|
2257
|
+
* reserved for the app overlay strip (play/pause + speed live there). */
|
|
2258
|
+
controlsHost?: 'app' | 'inline'
|
|
2237
2259
|
) {
|
|
2238
2260
|
d3Selection.select(container).selectAll(':not([data-d3-tooltip])').remove();
|
|
2261
|
+
const appHostedPlayback = controlsHost === 'app' && !!playback;
|
|
2239
2262
|
|
|
2240
2263
|
const ctx = ScaleContext.identity();
|
|
2241
2264
|
const sc = buildScaledConstants(ctx);
|
|
@@ -2246,7 +2269,10 @@ export function renderInfra(
|
|
|
2246
2269
|
palette,
|
|
2247
2270
|
layout.edges
|
|
2248
2271
|
);
|
|
2249
|
-
|
|
2272
|
+
// App-hosted: the playback pill moves to the app overlay, so a playback-only
|
|
2273
|
+
// legend (no tag groups) has nothing left to render.
|
|
2274
|
+
const hasLegend =
|
|
2275
|
+
legendGroups.length > 0 || (!!playback && !appHostedPlayback);
|
|
2250
2276
|
const fixedLegend = !exportMode && hasLegend;
|
|
2251
2277
|
const legendDynamicH = hasLegend
|
|
2252
2278
|
? getMaxLegendReservedHeight(
|
|
@@ -2461,7 +2487,8 @@ export function renderInfra(
|
|
|
2461
2487
|
isDark,
|
|
2462
2488
|
activeGroup ?? null,
|
|
2463
2489
|
playback ?? undefined,
|
|
2464
|
-
exportMode
|
|
2490
|
+
exportMode,
|
|
2491
|
+
controlsHost
|
|
2465
2492
|
);
|
|
2466
2493
|
// Re-enable pointer events on interactive legend elements
|
|
2467
2494
|
legendSvg
|
|
@@ -2478,7 +2505,8 @@ export function renderInfra(
|
|
|
2478
2505
|
isDark,
|
|
2479
2506
|
activeGroup ?? null,
|
|
2480
2507
|
playback ?? undefined,
|
|
2481
|
-
exportMode
|
|
2508
|
+
exportMode,
|
|
2509
|
+
controlsHost
|
|
2482
2510
|
);
|
|
2483
2511
|
}
|
|
2484
2512
|
}
|