@diagrammo/dgmo 0.27.0 → 0.28.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/dist/advanced.cjs +1482 -502
- package/dist/advanced.d.cts +6 -0
- package/dist/advanced.d.ts +6 -0
- package/dist/advanced.js +1479 -499
- package/dist/auto.cjs +1483 -503
- package/dist/auto.js +116 -139
- package/dist/auto.mjs +1480 -500
- package/dist/cli.cjs +168 -191
- package/dist/index.cjs +1482 -502
- package/dist/index.js +1479 -499
- package/dist/internal.cjs +1482 -502
- package/dist/internal.d.cts +6 -0
- package/dist/internal.d.ts +6 -0
- package/dist/internal.js +1479 -499
- package/package.json +7 -3
- package/src/boxes-and-lines/layout-layered.ts +722 -0
- package/src/boxes-and-lines/layout-search.ts +1200 -0
- package/src/boxes-and-lines/layout.ts +67 -556
- package/src/map/context-labels.ts +45 -14
- package/src/map/layout.ts +16 -6
|
@@ -29,6 +29,11 @@ export interface CountryCandidate {
|
|
|
29
29
|
/** Projected screen anchor `[x, y]` (mainland anchor or `path.centroid`), or
|
|
30
30
|
* null when the feature doesn't project to a finite point. */
|
|
31
31
|
readonly anchor: readonly [number, number] | null;
|
|
32
|
+
/** True when `anchor` came from a curated WORLD_LABEL_ANCHORS entry (a trusted
|
|
33
|
+
* mainland point) rather than the area-weighted centroid. Such a country
|
|
34
|
+
* bypasses the both-axes smear gate (its full-canvas bbox is an antimeridian
|
|
35
|
+
* artifact, not a real footprint, so the unreliable-centroid concern is moot). */
|
|
36
|
+
readonly curatedAnchor?: boolean;
|
|
32
37
|
}
|
|
33
38
|
|
|
34
39
|
export interface ContextLabelArgs {
|
|
@@ -102,10 +107,10 @@ export function labelBudget(
|
|
|
102
107
|
band: TierBand
|
|
103
108
|
): number {
|
|
104
109
|
const bandCap: Record<TierBand, number> = {
|
|
105
|
-
world:
|
|
106
|
-
continental:
|
|
107
|
-
regional:
|
|
108
|
-
local:
|
|
110
|
+
world: 7,
|
|
111
|
+
continental: 6,
|
|
112
|
+
regional: 5,
|
|
113
|
+
local: 4,
|
|
109
114
|
};
|
|
110
115
|
const area = Math.floor(Math.sqrt(Math.max(0, width * height)) / 150);
|
|
111
116
|
return Math.max(0, Math.min(area, bandCap[band]));
|
|
@@ -347,8 +352,11 @@ export function placeContextLabels(args: ContextLabelArgs): PlacedLabel[] {
|
|
|
347
352
|
letterSpacing: WATER_LETTER_SPACING,
|
|
348
353
|
color: waterColor,
|
|
349
354
|
fontSize: FONT, // water names keep the base font (no footprint to scale on)
|
|
350
|
-
//
|
|
351
|
-
|
|
355
|
+
// Orientation-value bands (lower = earlier): MAJOR water (oceans + major
|
|
356
|
+
// seas, tier ≤ 1) leads at `tier*10+kind` (0..~16); MINOR water (tier ≥ 2:
|
|
357
|
+
// smaller seas, bays, gulfs, straits) drops to band 2 (2000+) — BELOW the
|
|
358
|
+
// country band (1000+), so a big country outranks a minor basin.
|
|
359
|
+
sort: (tier <= 1 ? 0 : 2000) + tier * 10 + KIND_ORDER[kind],
|
|
352
360
|
});
|
|
353
361
|
}
|
|
354
362
|
|
|
@@ -369,15 +377,24 @@ export function placeContextLabels(args: ContextLabelArgs): PlacedLabel[] {
|
|
|
369
377
|
let ci = 0;
|
|
370
378
|
for (const r of ranked) {
|
|
371
379
|
const { c, w, h, area } = r;
|
|
372
|
-
// F2: an antimeridian-crossing / global-smear country
|
|
373
|
-
//
|
|
374
|
-
// is then unreliable (mid-map, wrong basin).
|
|
375
|
-
//
|
|
376
|
-
|
|
380
|
+
// F2: an antimeridian-crossing / global-smear country fills the canvas in
|
|
381
|
+
// BOTH dimensions while its real landmass is split — the `path.centroid`
|
|
382
|
+
// anchor is then unreliable (mid-map, wrong basin). Reject only that
|
|
383
|
+
// both-axes canvas-smear: a country that is merely wide-but-short (Canada
|
|
384
|
+
// hugging the top of a US frame) or tall-but-narrow (Chile) is a legitimate
|
|
385
|
+
// landmass whose clipExtent-clipped centroid still lands over its own ground,
|
|
386
|
+
// so it must NOT be dropped for footprint shape alone. A `curatedAnchor`
|
|
387
|
+
// (WORLD_LABEL_ANCHORS, e.g. Russia → European Russia) is trustworthy by
|
|
388
|
+
// construction — the smear concern is moot, so it bypasses this gate (the
|
|
389
|
+
// `insideViewport` check below still drops an anchor that projects off-frame).
|
|
390
|
+
if (!c.curatedAnchor && w > width * 0.66 && h > height * 0.66) continue;
|
|
377
391
|
if (!insideViewport(c.anchor, width, height)) continue;
|
|
378
392
|
// Footprint-driven scale (Decision: big landmass = large, faded backdrop
|
|
379
393
|
// name). t∈[0,1] over the [MIN,MAX] linear-fraction band; font ramps up and
|
|
380
|
-
//
|
|
394
|
+
// ink rises in lockstep, so a bigger name reads as a large, softly-inked
|
|
395
|
+
// backdrop. A curated-anchor giant (Russia) keeps the standard big-country
|
|
396
|
+
// styling — its full-canvas bbox lands at the top of the ramp (font/ink like
|
|
397
|
+
// any large in-frame country), which is the intended subdued-backdrop look.
|
|
381
398
|
const sizeFrac = Math.sqrt(area) / canvasLinear;
|
|
382
399
|
const t = Math.min(
|
|
383
400
|
1,
|
|
@@ -407,8 +424,12 @@ export function placeContextLabels(args: ContextLabelArgs): PlacedLabel[] {
|
|
|
407
424
|
letterSpacing: 0,
|
|
408
425
|
color,
|
|
409
426
|
fontSize,
|
|
410
|
-
//
|
|
411
|
-
|
|
427
|
+
// Band 1 (orientation-value ranking): above MINOR water (band 2, 2000+) but
|
|
428
|
+
// below MAJOR water — oceans + major seas (band 0, ≤~16). So a big country
|
|
429
|
+
// (US, Canada, Russia) outranks a minor sea/bay (Sargasso, Bahía de
|
|
430
|
+
// Campeche) yet never displaces an ocean. Larger area = earlier within the
|
|
431
|
+
// band (`ci` is the area-desc rank index).
|
|
432
|
+
sort: 1000 + ci++,
|
|
412
433
|
});
|
|
413
434
|
}
|
|
414
435
|
|
|
@@ -416,8 +437,17 @@ export function placeContextLabels(args: ContextLabelArgs): PlacedLabel[] {
|
|
|
416
437
|
candidates.sort((a, b) => a.sort - b.sort);
|
|
417
438
|
const placed: PlacedLabel[] = [];
|
|
418
439
|
const placedRects: LabelRect[] = [];
|
|
440
|
+
// Guarantee country/state room: water can otherwise monopolise a small budget
|
|
441
|
+
// (a coastal view borders many oceans/seas), so reserve up to 2 slots for
|
|
442
|
+
// countries whenever any country candidate exists. No effect on pure-water
|
|
443
|
+
// views (`countryCount` 0 ⇒ cap = budget). Major water still leads by sort, so
|
|
444
|
+
// this only trims the LAST water bodies that would have crowded out a country.
|
|
445
|
+
const countryCount = candidates.reduce((n, c) => n + (c.italic ? 0 : 1), 0);
|
|
446
|
+
const waterCap = budget - Math.min(2, countryCount);
|
|
447
|
+
let waterPlaced = 0;
|
|
419
448
|
for (const cand of candidates) {
|
|
420
449
|
if (placed.length >= budget) break;
|
|
450
|
+
if (cand.italic && waterPlaced >= waterCap) continue;
|
|
421
451
|
const rect = rectAround(
|
|
422
452
|
cand.cx,
|
|
423
453
|
cand.cy,
|
|
@@ -452,6 +482,7 @@ export function placeContextLabels(args: ContextLabelArgs): PlacedLabel[] {
|
|
|
452
482
|
if (collides(rect)) continue;
|
|
453
483
|
if (placedRects.some((r) => overlapsPadded(rect, r, CONTEXT_PAD))) continue;
|
|
454
484
|
placedRects.push(rect);
|
|
485
|
+
if (cand.italic) waterPlaced++;
|
|
455
486
|
placed.push({
|
|
456
487
|
x: cand.cx,
|
|
457
488
|
y: cand.cy,
|
package/src/map/layout.ts
CHANGED
|
@@ -159,6 +159,13 @@ const FONT = 11; // on-map label font px
|
|
|
159
159
|
// previously used for hover) mistook the wrapped sliver for half the shape.
|
|
160
160
|
const WORLD_LABEL_ANCHORS: Record<string, [number, number]> = {
|
|
161
161
|
US: [-98.5, 39.5], // CONUS geographic centre (near Lebanon, Kansas)
|
|
162
|
+
// Russia crosses the antimeridian (Chukotka at ~170°W), so on a non-global
|
|
163
|
+
// (e.g. Europe) projection its geometry smears across the whole frame and the
|
|
164
|
+
// area-weighted centroid lands mid-map (over Europe) — useless as a label
|
|
165
|
+
// anchor. Pin it to European Russia (~Volga) so a Europe view labels visible
|
|
166
|
+
// western Russia on its eastern margin; on a world view this still sits over
|
|
167
|
+
// Russian land. (See the curated-anchor smear-gate bypass in context-labels.)
|
|
168
|
+
RU: [45, 58],
|
|
162
169
|
};
|
|
163
170
|
// POI-cluster hover-only gate (Decision #1). A ≥2-member cluster's callout
|
|
164
171
|
// column falls back to hover-only labels when it would sprawl or overflow:
|
|
@@ -3925,22 +3932,25 @@ export function layoutMap(
|
|
|
3925
3932
|
name: (f.properties as { name?: string } | undefined)?.name ?? iso,
|
|
3926
3933
|
bbox: [x0, y0, x1, y1],
|
|
3927
3934
|
anchor: a && Number.isFinite(a[0]) ? [a[0], a[1]] : null,
|
|
3935
|
+
curatedAnchor: !!anchorLngLat,
|
|
3928
3936
|
});
|
|
3929
3937
|
}
|
|
3930
|
-
//
|
|
3931
|
-
//
|
|
3932
|
-
// (Nevada, Oregon, Arizona…) in the muted context
|
|
3933
|
-
//
|
|
3938
|
+
// Framed US states (POI-only region framing): when the frame is snapped to a
|
|
3939
|
+
// US-state container (e.g. California), label the focus state AND the
|
|
3940
|
+
// surrounding in-frame states (Nevada, Oregon, Arizona…) in the muted context
|
|
3941
|
+
// style for orientation. None are data (the region-label pass skipped them).
|
|
3934
3942
|
// Anchor each to the centroid of its VISIBLE (culled) geometry so a state only
|
|
3935
3943
|
// partly in frame (a sliver of Oregon at the top) still anchors on-screen
|
|
3936
3944
|
// rather than at an off-frame centroid that `insideViewport` would reject.
|
|
3945
|
+
// The focus container IS included (gives the map its headline name) — only a
|
|
3946
|
+
// data-referenced state is skipped, to avoid double-labelling what
|
|
3947
|
+
// region-labels already named.
|
|
3937
3948
|
const framedStateContainers = (resolved.poiFrameContainers ?? []).some(
|
|
3938
3949
|
(id) => id.startsWith('US-')
|
|
3939
3950
|
);
|
|
3940
3951
|
if (usLayer && framedStateContainers) {
|
|
3941
|
-
const containerSet = new Set(resolved.poiFrameContainers);
|
|
3942
3952
|
for (const [iso, f] of usLayer) {
|
|
3943
|
-
if (
|
|
3953
|
+
if (regionById.has(iso)) continue;
|
|
3944
3954
|
const viewF = cullFeatureToView(f);
|
|
3945
3955
|
if (!viewF) continue; // not in frame
|
|
3946
3956
|
const b = path.bounds(viewF as never) as [
|