choropleth-kit 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +215 -2
- package/dist/chunk-R3XS6XPP.js +1816 -0
- package/dist/core.d.ts +123 -0
- package/dist/core.js +197 -0
- package/dist/drill-chain-DCiuiZKK.d.ts +1247 -0
- package/dist/geo.d.ts +35 -0
- package/dist/geo.js +361 -0
- package/dist/index.d.ts +1186 -0
- package/dist/index.js +2926 -0
- package/dist/styles.css +417 -0
- package/dist/styles.d.ts +2 -0
- package/package.json +138 -3
|
@@ -0,0 +1,1247 @@
|
|
|
1
|
+
import { FeatureCollection, Feature, Geometry } from 'geojson';
|
|
2
|
+
import { Topology } from 'topojson-specification';
|
|
3
|
+
import { GeoPath, geoEqualEarth, geoNaturalEarth1, geoMercator, geoAlbers, geoOrthographic, GeoProjection, GeoPermissibleObjects } from 'd3-geo';
|
|
4
|
+
import { interpolateZoom } from 'd3-interpolate';
|
|
5
|
+
import { interpolateBlues, interpolateGreens, interpolateGreys, interpolateOranges, interpolatePurples, interpolateReds, interpolateViridis, interpolateRdBu, interpolateBrBG, interpolatePiYG, interpolatePRGn, interpolatePuOr, interpolateRdYlBu, interpolateRdYlGn, interpolateSpectral } from 'd3-scale-chromatic';
|
|
6
|
+
|
|
7
|
+
/** Already-loaded geometry. */
|
|
8
|
+
type GeographyData = Topology | FeatureCollection;
|
|
9
|
+
/**
|
|
10
|
+
* What the `geography` prop accepts: the data, a promise already in flight, or a
|
|
11
|
+
* function that loads it on demand (`() => import("…/world-110m")`; the whole module with `default` works).
|
|
12
|
+
*/
|
|
13
|
+
type GeographyInput = GeographyData | Promise<GeographyData | {
|
|
14
|
+
default: GeographyData;
|
|
15
|
+
}> | (() => Promise<GeographyData | {
|
|
16
|
+
default: GeographyData;
|
|
17
|
+
}>);
|
|
18
|
+
/** An ES module (`import()`) brings the geometry in `default`. */
|
|
19
|
+
declare const unwrapGeography: (value: GeographyData | {
|
|
20
|
+
default: GeographyData;
|
|
21
|
+
}) => GeographyData;
|
|
22
|
+
type ToFeatureCollectionOptions = {
|
|
23
|
+
geography: Topology | FeatureCollection;
|
|
24
|
+
/**
|
|
25
|
+
* Which topology object to use (e.g. "countries" or "land" in world-atlas).
|
|
26
|
+
* Ignored if `geography` is already GeoJSON.
|
|
27
|
+
*/
|
|
28
|
+
objectName?: string;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Single entry point for geodata: accepts TopoJSON or GeoJSON and ALWAYS returns a
|
|
32
|
+
* `FeatureCollection`. Everything that follows (joining with the data, projection, SVG paths) can
|
|
33
|
+
* assume that shape and never ask again "what format was I given?".
|
|
34
|
+
*
|
|
35
|
+
* It is a pure, synchronous function: it does not fetch. The deferred load (`() => import(...)`) of
|
|
36
|
+
* `GeographyInput` is resolved in the React layer; here the data arrives already resolved.
|
|
37
|
+
*/
|
|
38
|
+
declare const toFeatureCollection: ({ geography, objectName, }: ToFeatureCollectionOptions) => FeatureCollection;
|
|
39
|
+
|
|
40
|
+
declare const DEFAULT_MAX_RADIUS = 24;
|
|
41
|
+
type RadiusScale = {
|
|
42
|
+
/** Value → radius in pixels. Area proportional to the value. */
|
|
43
|
+
getRadius: (value: number) => number;
|
|
44
|
+
/** Maximum value (radius = `maxRadius`). 0 if there are no positive values. */
|
|
45
|
+
max: number;
|
|
46
|
+
maxRadius: number;
|
|
47
|
+
};
|
|
48
|
+
type SizeLegendItem = {
|
|
49
|
+
value: number;
|
|
50
|
+
radius: number;
|
|
51
|
+
label: string;
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* SQUARE-ROOT radius scale: the eye compares areas, not radii. If a store sells twice
|
|
55
|
+
* as much, its circle must have twice the AREA (radius ×1.41), not twice the radius, which would be
|
|
56
|
+
* four times the area.
|
|
57
|
+
*
|
|
58
|
+
* The domain starts at 0 (not at the minimum): a circle of radius 0 represents 0. With a [min, max]
|
|
59
|
+
* domain the minimum value would vanish and the ratios between areas would be false.
|
|
60
|
+
*/
|
|
61
|
+
declare const createRadiusScale: ({ values, maxRadius, }: {
|
|
62
|
+
values: readonly (number | null | undefined)[];
|
|
63
|
+
maxRadius?: number;
|
|
64
|
+
}) => RadiusScale;
|
|
65
|
+
/** Three reference circles (¼, ½ and the maximum) to read the size. Empty without positive values. */
|
|
66
|
+
declare const buildSizeLegend: ({ scale, formatValue, }: {
|
|
67
|
+
scale: RadiusScale;
|
|
68
|
+
formatValue?: (value: number) => string;
|
|
69
|
+
}) => SizeLegendItem[];
|
|
70
|
+
|
|
71
|
+
type Classification = {
|
|
72
|
+
method: "linear";
|
|
73
|
+
} | {
|
|
74
|
+
method: "quantize";
|
|
75
|
+
classes?: number;
|
|
76
|
+
} | {
|
|
77
|
+
method: "quantile";
|
|
78
|
+
classes?: number;
|
|
79
|
+
} | {
|
|
80
|
+
method: "threshold";
|
|
81
|
+
thresholds: number[];
|
|
82
|
+
};
|
|
83
|
+
declare const DEFAULT_CLASSES = 5;
|
|
84
|
+
/**
|
|
85
|
+
* Discards whatever is not a usable number: `null`, `undefined` and `NaN`/`Infinity`.
|
|
86
|
+
* It goes before any statistical computation: a single `NaN` in the array contaminates a minimum, a
|
|
87
|
+
* maximum or a whole quantile (they would all become `NaN`) and the map would come out in one color.
|
|
88
|
+
* A `0` IS kept: it is a datum, not an absence ("no data" ≠ 0).
|
|
89
|
+
*/
|
|
90
|
+
declare const finiteValues: (values: readonly (number | null | undefined)[]) => number[];
|
|
91
|
+
/**
|
|
92
|
+
* Computes the **breaks** between classes: N classes ⇒ N-1 ascending numbers. A value `v` falls in
|
|
93
|
+
* class `i`, where `i` is the number of breaks `v` reaches or exceeds, so a value exactly on a break
|
|
94
|
+
* belongs to the class ABOVE (the behavior of `d3.scaleThreshold`).
|
|
95
|
+
*
|
|
96
|
+
* It returns numbers, not colors, on purpose: separating "which classes exist" from "what color
|
|
97
|
+
* each one is" lets the statistics be tested without looking at colors, and lets the legend (a later
|
|
98
|
+
* step) use the same breaks as the map without recomputing them.
|
|
99
|
+
*
|
|
100
|
+
* `values` must already be filtered with `finiteValues`. `domain` is the range `quantize` is split
|
|
101
|
+
* over; the caller decides it (in a diverging map it is symmetric around the midpoint, see scales.ts).
|
|
102
|
+
*/
|
|
103
|
+
declare const computeThresholds: ({ values, classification, domain, }: {
|
|
104
|
+
values: readonly number[];
|
|
105
|
+
classification: Classification;
|
|
106
|
+
domain: readonly [number, number];
|
|
107
|
+
}) => number[];
|
|
108
|
+
|
|
109
|
+
declare const SEQUENTIAL_PALETTES: {
|
|
110
|
+
readonly blues: typeof interpolateBlues;
|
|
111
|
+
readonly greens: typeof interpolateGreens;
|
|
112
|
+
readonly greys: typeof interpolateGreys;
|
|
113
|
+
readonly oranges: typeof interpolateOranges;
|
|
114
|
+
readonly purples: typeof interpolatePurples;
|
|
115
|
+
readonly reds: typeof interpolateReds;
|
|
116
|
+
readonly viridis: typeof interpolateViridis;
|
|
117
|
+
};
|
|
118
|
+
declare const DIVERGING_PALETTES: {
|
|
119
|
+
readonly rdbu: typeof interpolateRdBu;
|
|
120
|
+
readonly brbg: typeof interpolateBrBG;
|
|
121
|
+
readonly piyg: typeof interpolatePiYG;
|
|
122
|
+
readonly prgn: typeof interpolatePRGn;
|
|
123
|
+
readonly puor: typeof interpolatePuOr;
|
|
124
|
+
readonly rdylbu: typeof interpolateRdYlBu;
|
|
125
|
+
readonly rdylgn: typeof interpolateRdYlGn;
|
|
126
|
+
readonly spectral: typeof interpolateSpectral;
|
|
127
|
+
};
|
|
128
|
+
type SequentialPaletteName = keyof typeof SEQUENTIAL_PALETTES;
|
|
129
|
+
type DivergingPaletteName = keyof typeof DIVERGING_PALETTES;
|
|
130
|
+
/** Categorical palettes: loose colors with no order (unlike the ramps above). */
|
|
131
|
+
declare const CATEGORICAL_PALETTES: {
|
|
132
|
+
readonly tableau10: readonly string[];
|
|
133
|
+
readonly set2: readonly string[];
|
|
134
|
+
readonly set3: readonly string[];
|
|
135
|
+
readonly accent: readonly string[];
|
|
136
|
+
readonly dark2: readonly string[];
|
|
137
|
+
readonly paired: readonly string[];
|
|
138
|
+
readonly pastel1: readonly string[];
|
|
139
|
+
};
|
|
140
|
+
type CategoricalPaletteName = keyof typeof CATEGORICAL_PALETTES;
|
|
141
|
+
|
|
142
|
+
declare const DEFAULT_EMPTY_FILL = "#d4d4d4";
|
|
143
|
+
type CommonOptions = {
|
|
144
|
+
/** One value per region (or per datum). `null`/`undefined`/`NaN` are treated as "no data". */
|
|
145
|
+
values: readonly (number | null | undefined)[];
|
|
146
|
+
classification?: Classification;
|
|
147
|
+
emptyFill?: string;
|
|
148
|
+
};
|
|
149
|
+
type ColorScaleOptions = CommonOptions & ({
|
|
150
|
+
variant: "sequential";
|
|
151
|
+
palette?: SequentialPaletteName;
|
|
152
|
+
} | {
|
|
153
|
+
variant: "diverging";
|
|
154
|
+
palette?: DivergingPaletteName;
|
|
155
|
+
midpoint?: number;
|
|
156
|
+
});
|
|
157
|
+
type ColorScale = {
|
|
158
|
+
/** Value → CSS color. `null`/`undefined`/`NaN` → `emptyFill`. `0` is a datum and gets colored. */
|
|
159
|
+
getColor: (value: number | null | undefined) => string;
|
|
160
|
+
/** "classed": colors by bands. "continuous": a gradient with no classes (`linear`). */
|
|
161
|
+
kind: "classed" | "continuous";
|
|
162
|
+
/** Data range covered by the scale. In diverging it is symmetric around the midpoint. */
|
|
163
|
+
domain: readonly [number, number];
|
|
164
|
+
/** Breaks between classes (N-1). Empty in continuous. The legend will use these same ones. */
|
|
165
|
+
thresholds: readonly number[];
|
|
166
|
+
/** One color per class (N). Empty in continuous. */
|
|
167
|
+
colors: readonly string[];
|
|
168
|
+
emptyFill: string;
|
|
169
|
+
};
|
|
170
|
+
/**
|
|
171
|
+
* Creates the function that decides each region's color.
|
|
172
|
+
*
|
|
173
|
+
* The order of the steps matters because each one feeds the next:
|
|
174
|
+
* clean data → domain → breaks → colors → scale.
|
|
175
|
+
*/
|
|
176
|
+
declare const createColorScale: (options: ColorScaleOptions) => ColorScale;
|
|
177
|
+
|
|
178
|
+
type LegendItem = {
|
|
179
|
+
color: string;
|
|
180
|
+
label: string;
|
|
181
|
+
/** Lower bound of the class (inclusive). Absent on the "no data" item. */
|
|
182
|
+
from?: number;
|
|
183
|
+
/** Upper bound of the class (exclusive, except on the last class). */
|
|
184
|
+
to?: number;
|
|
185
|
+
/** Number of regions in that class. */
|
|
186
|
+
count: number;
|
|
187
|
+
/** It is the "no data" item (not a class: it has no class index). */
|
|
188
|
+
empty?: true;
|
|
189
|
+
};
|
|
190
|
+
type BuildLegendOptions = {
|
|
191
|
+
scale: ColorScale;
|
|
192
|
+
/** One value per region. The same ones used to create the scale. `null`/`NaN` = no data. */
|
|
193
|
+
values: readonly (number | null | undefined)[];
|
|
194
|
+
formatValue?: (value: number) => string;
|
|
195
|
+
/** If given, a "no data" item with this text and the scale's `emptyFill` is appended at the end. */
|
|
196
|
+
emptyLabel?: string;
|
|
197
|
+
};
|
|
198
|
+
declare const defaultFormatValue: (value: number) => string;
|
|
199
|
+
declare const classIndex: (thresholds: readonly number[], value: number) => number;
|
|
200
|
+
/**
|
|
201
|
+
* Turns the scale into legend items reusing ITS breaks and colors (nothing is recomputed), and
|
|
202
|
+
* counts how many regions fall in each class.
|
|
203
|
+
*
|
|
204
|
+
* Continuous scale (`linear`): there are no classes, it returns [] (drawn as a gradient with `scale.domain`).
|
|
205
|
+
*/
|
|
206
|
+
declare const buildLegend: (options: BuildLegendOptions) => LegendItem[];
|
|
207
|
+
|
|
208
|
+
type CategorySpec = {
|
|
209
|
+
id: string;
|
|
210
|
+
label?: string;
|
|
211
|
+
/** Fixed color. If missing, it is taken from the palette according to the category's position. */
|
|
212
|
+
color?: string;
|
|
213
|
+
};
|
|
214
|
+
type ResolvedCategory = {
|
|
215
|
+
id: string;
|
|
216
|
+
label: string;
|
|
217
|
+
color: string;
|
|
218
|
+
};
|
|
219
|
+
type CategoricalScale = {
|
|
220
|
+
/** Category → color. `null`/`undefined` and unknown categories → `emptyFill`. */
|
|
221
|
+
getColor: (category: string | null | undefined) => string;
|
|
222
|
+
getLabel: (category: string | null | undefined) => string;
|
|
223
|
+
categories: readonly ResolvedCategory[];
|
|
224
|
+
emptyFill: string;
|
|
225
|
+
};
|
|
226
|
+
type CreateCategoricalScaleOptions = {
|
|
227
|
+
values: readonly (string | null | undefined)[];
|
|
228
|
+
/** Fixed order and colors. Without this, categories come from the data, sorted alphabetically. */
|
|
229
|
+
categories?: readonly CategorySpec[];
|
|
230
|
+
palette?: CategoricalPaletteName;
|
|
231
|
+
emptyFill: string;
|
|
232
|
+
};
|
|
233
|
+
/**
|
|
234
|
+
* Assigns a color to each category.
|
|
235
|
+
*
|
|
236
|
+
* Without `categories`, the order is ALPHABETICAL and not order of appearance: if it depended on
|
|
237
|
+
* the data order, a reload with the rows in another order would change each category's color. For
|
|
238
|
+
* stable colors across screens (e.g. "Europe" always blue) pass `categories`.
|
|
239
|
+
*
|
|
240
|
+
* With more categories than colors in the palette, colors repeat (d3 does the same): two categories
|
|
241
|
+
* with the same color are indistinguishable, so better pass `categories` with `color`.
|
|
242
|
+
*/
|
|
243
|
+
declare const createCategoricalScale: ({ values, categories, palette, emptyFill, }: CreateCategoricalScaleOptions) => CategoricalScale;
|
|
244
|
+
/** One item per category (in the scale's order) with its number of regions; optional "no data". */
|
|
245
|
+
declare const buildCategoricalLegend: ({ scale, values, emptyLabel, }: {
|
|
246
|
+
scale: CategoricalScale;
|
|
247
|
+
values: readonly (string | null | undefined)[];
|
|
248
|
+
emptyLabel?: string;
|
|
249
|
+
}) => LegendItem[];
|
|
250
|
+
|
|
251
|
+
/** Point [x, y] in pixels or [lon, lat] in degrees, depending on context. */
|
|
252
|
+
type Point = readonly [number, number];
|
|
253
|
+
/**
|
|
254
|
+
* Configuration per `variant`. A discriminated union: each variant requires its
|
|
255
|
+
* own things (`categorical` asks for `getCategory`, `bubble` accepts `getCoordinates`…) and TypeScript
|
|
256
|
+
* rejects invalid combinations, such as a diverging palette on a sequential map.
|
|
257
|
+
*/
|
|
258
|
+
type VariantConfig<TDatum> = {
|
|
259
|
+
variant: "sequential";
|
|
260
|
+
getValue: (datum: TDatum) => number | null;
|
|
261
|
+
classification?: Classification;
|
|
262
|
+
palette?: SequentialPaletteName;
|
|
263
|
+
} | {
|
|
264
|
+
variant: "diverging";
|
|
265
|
+
getValue: (datum: TDatum) => number | null;
|
|
266
|
+
classification?: Classification;
|
|
267
|
+
palette?: DivergingPaletteName;
|
|
268
|
+
midpoint?: number;
|
|
269
|
+
} | {
|
|
270
|
+
variant: "categorical";
|
|
271
|
+
getCategory: (datum: TDatum) => string | null;
|
|
272
|
+
/** Fixed order and colors. */
|
|
273
|
+
categories?: readonly CategorySpec[];
|
|
274
|
+
palette?: CategoricalPaletteName;
|
|
275
|
+
} | {
|
|
276
|
+
variant: "bubble";
|
|
277
|
+
/** ABSOLUTE value that gives the circle's AREA. `null`s and values ≤ 0 draw no circle. */
|
|
278
|
+
getValue: (datum: TDatum) => number | null;
|
|
279
|
+
/** Second metric → the bubble's color (bivariate, diverging scale). */
|
|
280
|
+
getColorValue?: (datum: TDatum) => number | null;
|
|
281
|
+
/** The datum's [lon, lat]. Without this, the bubble goes at its region's centroid. */
|
|
282
|
+
getCoordinates?: (datum: TDatum) => readonly [number, number] | null;
|
|
283
|
+
maxRadius?: number;
|
|
284
|
+
/** Single color when there is no `getColorValue`. */
|
|
285
|
+
color?: string;
|
|
286
|
+
classification?: Classification;
|
|
287
|
+
palette?: DivergingPaletteName;
|
|
288
|
+
midpoint?: number;
|
|
289
|
+
};
|
|
290
|
+
type VariantName = VariantConfig<unknown>["variant"];
|
|
291
|
+
type VariantEntry<TDatum> = {
|
|
292
|
+
/** The region's id, or `undefined` if it has none. */
|
|
293
|
+
id: string | undefined;
|
|
294
|
+
/** Datum joined with the region, or `undefined` if there is none. */
|
|
295
|
+
datum: TDatum | undefined;
|
|
296
|
+
};
|
|
297
|
+
type Bubble<TDatum> = {
|
|
298
|
+
id: string;
|
|
299
|
+
x: number;
|
|
300
|
+
y: number;
|
|
301
|
+
r: number;
|
|
302
|
+
fill: string;
|
|
303
|
+
/** Value that gives the area. */
|
|
304
|
+
value: number;
|
|
305
|
+
colorValue: number | null;
|
|
306
|
+
datum: TDatum;
|
|
307
|
+
/**
|
|
308
|
+
* The datum's own [lon, lat] (`getCoordinates`), or `null` if the bubble is at its region's centroid.
|
|
309
|
+
* Tells a point (a store) apart from a region's bubble, for interaction purposes.
|
|
310
|
+
*/
|
|
311
|
+
coordinates: Point | null;
|
|
312
|
+
};
|
|
313
|
+
type VariantResult<TDatum> = {
|
|
314
|
+
/** Color of each region, in the order of `entries`. */
|
|
315
|
+
fills: string[];
|
|
316
|
+
/** Value of each region: number, category (`categorical`) or `null`. */
|
|
317
|
+
values: (number | string | null)[];
|
|
318
|
+
/** `values` already formatted for display ("1.5 %", "Europe"); empty string if there is no value. */
|
|
319
|
+
labels: string[];
|
|
320
|
+
/** Numeric color scale. `null` in `categorical` and in `bubble` without `getColorValue`. */
|
|
321
|
+
scale: ColorScale | null;
|
|
322
|
+
legend: LegendItem[];
|
|
323
|
+
/**
|
|
324
|
+
* Index of the legend CLASS each region belongs to (same order as `fills`), or `null` if it belongs to none
|
|
325
|
+
* (no data, continuous scale, `bubble`). It is what makes it possible to highlight a class without re-rendering.
|
|
326
|
+
*/
|
|
327
|
+
classes: (number | null)[];
|
|
328
|
+
bubbles: Bubble<TDatum>[];
|
|
329
|
+
sizeLegend: SizeLegendItem[];
|
|
330
|
+
};
|
|
331
|
+
type ResolveVariantOptions<TDatum> = {
|
|
332
|
+
config: VariantConfig<TDatum>;
|
|
333
|
+
entries: readonly VariantEntry<TDatum>[];
|
|
334
|
+
/** All data rows (bubbles with `getCoordinates` do not go through the regions). */
|
|
335
|
+
data: readonly TDatum[];
|
|
336
|
+
getId: (datum: TDatum) => string;
|
|
337
|
+
/** Projected centroid of each region, in the order of `entries` (only `bubble` uses it). */
|
|
338
|
+
centroids: readonly (Point | null)[];
|
|
339
|
+
/** [lon, lat] → [x, y] in pixels (only `bubble` uses it). */
|
|
340
|
+
project: (coordinates: Point) => Point | null;
|
|
341
|
+
emptyFill: string;
|
|
342
|
+
formatValue: (value: number) => string;
|
|
343
|
+
/** Text of the legend's "no data" item; without this, the legend does not include it. */
|
|
344
|
+
emptyLabel?: string;
|
|
345
|
+
};
|
|
346
|
+
declare const DEFAULT_BUBBLE_FILL = "#2563eb";
|
|
347
|
+
/**
|
|
348
|
+
* Each variant's "preset": a PURE function that receives the data already joined with the
|
|
349
|
+
* regions and returns how it is painted (colors, legend, bubbles). The hook only orchestrates; adding
|
|
350
|
+
* a new variant means adding a branch here and a case in the type union.
|
|
351
|
+
*/
|
|
352
|
+
declare const resolveVariant: <TDatum>(options: ResolveVariantOptions<TDatum>) => VariantResult<TDatum>;
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* The camera: an affine `transform` `{ k, x, y }` over the already-projected canvas.
|
|
356
|
+
* A canvas point `[px, py]` appears on screen at `[px·k + x, py·k + y]`. `k = 1` is the level's fit
|
|
357
|
+
* (the map fits the container exactly).
|
|
358
|
+
*
|
|
359
|
+
* This whole file is pure math, with no DOM or d3-zoom: it is tested without a browser. What writes
|
|
360
|
+
* the transform to the DOM (`use-zoom.ts`) comes in the next step.
|
|
361
|
+
*/
|
|
362
|
+
type CameraTransform = {
|
|
363
|
+
k: number;
|
|
364
|
+
x: number;
|
|
365
|
+
y: number;
|
|
366
|
+
};
|
|
367
|
+
/** The camera at the level's fit. */
|
|
368
|
+
declare const FIT_TRANSFORM: CameraTransform;
|
|
369
|
+
/** Is the camera (almost) at the fit? Used to disable "reset view". */
|
|
370
|
+
declare const isFitTransform: (t: CameraTransform, epsilon?: number) => boolean;
|
|
371
|
+
type Size = {
|
|
372
|
+
width: number;
|
|
373
|
+
height: number;
|
|
374
|
+
};
|
|
375
|
+
/** Rectangle [[x0, y0], [x1, y1]]. */
|
|
376
|
+
type Extent = readonly [Point, Point];
|
|
377
|
+
/** Canvas → screen. */
|
|
378
|
+
declare const applyTransform: ({ k, x, y }: CameraTransform, [px, py]: Point) => Point;
|
|
379
|
+
/** Screen → canvas. */
|
|
380
|
+
declare const invertTransform: ({ k, x, y }: CameraTransform, [sx, sy]: Point) => Point;
|
|
381
|
+
type ConstrainAxisParams = {
|
|
382
|
+
/** Current translation on this axis. */
|
|
383
|
+
t: number;
|
|
384
|
+
k: number;
|
|
385
|
+
/** Extremes of the content on the canvas (before scaling). */
|
|
386
|
+
c0: number;
|
|
387
|
+
c1: number;
|
|
388
|
+
/** Extremes of the view on screen. */
|
|
389
|
+
v0: number;
|
|
390
|
+
v1: number;
|
|
391
|
+
/** Elastic slack as a fraction of the view size. Defaults to 0 (hard limit). */
|
|
392
|
+
overscroll?: number;
|
|
393
|
+
};
|
|
394
|
+
/**
|
|
395
|
+
* Allowed translation on ONE axis. d3's default `constrain` leaves a map smaller
|
|
396
|
+
* than the view stuck in a corner; this one **centers** it if it fits and, if it does not fit,
|
|
397
|
+
* prevents its edges from entering the view (plus an optional elastic slack).
|
|
398
|
+
*/
|
|
399
|
+
declare const constrainAxis: ({ t, k, c0, c1, v0, v1, overscroll, }: ConstrainAxisParams) => number;
|
|
400
|
+
type ConstrainTransformParams = {
|
|
401
|
+
transform: CameraTransform;
|
|
402
|
+
/** The canvas: normally [[0, 0], [width, height]]. */
|
|
403
|
+
content: Extent;
|
|
404
|
+
/** The view: normally [[0, 0], [width, height]]. */
|
|
405
|
+
view: Extent;
|
|
406
|
+
overscroll?: number;
|
|
407
|
+
};
|
|
408
|
+
/** `constrainAxis` on both axes. The scale `k` is not touched. */
|
|
409
|
+
declare const constrainTransform: ({ transform, content, view, overscroll, }: ConstrainTransformParams) => CameraTransform;
|
|
410
|
+
type PreserveViewParams = {
|
|
411
|
+
transform: CameraTransform;
|
|
412
|
+
oldSize: Size;
|
|
413
|
+
newSize: Size;
|
|
414
|
+
/** OLD canvas pixels → [lon, lat] (`oldProjection.invert`). */
|
|
415
|
+
invertOld: (point: Point) => Point | null;
|
|
416
|
+
/** [lon, lat] → NEW canvas pixels (`newProjection`). */
|
|
417
|
+
projectNew: (coordinates: Point) => Point | null;
|
|
418
|
+
};
|
|
419
|
+
/**
|
|
420
|
+
* Equivalent camera after a resize. When the size changes, the projection
|
|
421
|
+
* is refitted and the pixels change: without this, the view would "go back to the fit". The
|
|
422
|
+
* **geographic center** and `k` are preserved: it looks at which [lon, lat] was at the center of the
|
|
423
|
+
* view and places it at the center of the new view.
|
|
424
|
+
*
|
|
425
|
+
* If the center falls outside the projection (ocean beyond the globe) there is no geographic center
|
|
426
|
+
* to preserve: the fit is returned.
|
|
427
|
+
*/
|
|
428
|
+
declare const preserveView: ({ transform, oldSize, newSize, invertOld, projectNew, }: PreserveViewParams) => CameraTransform;
|
|
429
|
+
/** Minimum and maximum duration of a flight, in ms. */
|
|
430
|
+
declare const MIN_FLIGHT_MS = 350;
|
|
431
|
+
declare const MAX_FLIGHT_MS = 1200;
|
|
432
|
+
/** Default curvature (ρ) for dashboards (1.2–1.3): less "jump" than √2. */
|
|
433
|
+
declare const DEFAULT_FLIGHT_CURVATURE = 1.25;
|
|
434
|
+
/**
|
|
435
|
+
* The camera as a `interpolateZoom` "view" (van Wijk and Nuij): [center x, center y, visible width],
|
|
436
|
+
* all three in canvas coordinates.
|
|
437
|
+
*/
|
|
438
|
+
declare const transformToZoomView: ({ k, x, y }: CameraTransform, { width, height }: Size) => [number, number, number];
|
|
439
|
+
declare const createZoomInterpolator: (rho: number) => typeof interpolateZoom;
|
|
440
|
+
type FlightDurationParams = {
|
|
441
|
+
from: CameraTransform;
|
|
442
|
+
to: CameraTransform;
|
|
443
|
+
size: Size;
|
|
444
|
+
/** Speed multiplier (> 1 = slower). Defaults to 1. */
|
|
445
|
+
speed?: number;
|
|
446
|
+
curvature?: number;
|
|
447
|
+
};
|
|
448
|
+
/**
|
|
449
|
+
* Duration of a flight between two cameras: the "natural" one from `interpolateZoom` (depends on the
|
|
450
|
+
* distance and the scale change) × `speed`, clamped to [350, 1200] ms. Short flights are fast;
|
|
451
|
+
* long ones, readable.
|
|
452
|
+
*/
|
|
453
|
+
declare const flightDuration: ({ from, to, size, speed, curvature, }: FlightDurationParams) => number;
|
|
454
|
+
/** Inertia time constant, in ms: iOS's. */
|
|
455
|
+
declare const INERTIA_TAU_MS = 325;
|
|
456
|
+
type InertiaOffsetParams = {
|
|
457
|
+
/** Release velocity on this axis, px/ms. */
|
|
458
|
+
velocity: number;
|
|
459
|
+
/** ms since release. */
|
|
460
|
+
elapsed: number;
|
|
461
|
+
tau?: number;
|
|
462
|
+
};
|
|
463
|
+
/**
|
|
464
|
+
* Accumulated displacement (px) `elapsed` ms after release, with exponential decay:
|
|
465
|
+
* `v·τ·(1 − e^(−t/τ))`. It tends to `v·τ` and never exceeds it; with negative or `NaN` `elapsed` it does not move.
|
|
466
|
+
*/
|
|
467
|
+
declare const inertiaOffset: ({ velocity, elapsed, tau, }: InertiaOffsetParams) => number;
|
|
468
|
+
/** Minimum speed (px/ms) for releasing a drag to start inertia. */
|
|
469
|
+
declare const INERTIA_MIN_VELOCITY = 0.3;
|
|
470
|
+
/** Window of samples used to measure the release velocity, in ms. */
|
|
471
|
+
declare const INERTIA_WINDOW_MS = 100;
|
|
472
|
+
/** A camera position at an instant (ms, from `performance.now()`). */
|
|
473
|
+
type CameraSample = {
|
|
474
|
+
time: number;
|
|
475
|
+
k: number;
|
|
476
|
+
x: number;
|
|
477
|
+
y: number;
|
|
478
|
+
};
|
|
479
|
+
type ReleaseVelocityParams = {
|
|
480
|
+
samples: readonly CameraSample[];
|
|
481
|
+
/** Instant of release. */
|
|
482
|
+
now: number;
|
|
483
|
+
windowMs?: number;
|
|
484
|
+
minVelocity?: number;
|
|
485
|
+
};
|
|
486
|
+
/**
|
|
487
|
+
* Velocity `[vx, vy]` (px/ms) at which a drag was released, or `null` if there must be no inertia:
|
|
488
|
+
* - there was a zoom (`k` changed within the window): inertia is **pan** only;
|
|
489
|
+
* - the hand stopped before releasing (the last sample is older than the window);
|
|
490
|
+
* - there are no two samples in the window, or the velocity is below the threshold (0.3 px/ms).
|
|
491
|
+
*/
|
|
492
|
+
declare const releaseVelocity: ({ samples, now, windowMs, minVelocity, }: ReleaseVelocityParams) => Point | null;
|
|
493
|
+
/**
|
|
494
|
+
* What bubbles do when zooming in:
|
|
495
|
+
* - `"sqrt"` (default): they grow with √k (a sense of depth without covering the country);
|
|
496
|
+
* - `"screen"`: constant size on screen;
|
|
497
|
+
* - `"geo"`: they grow with the map (full scale).
|
|
498
|
+
*/
|
|
499
|
+
type BubbleScaling = "sqrt" | "screen" | "geo";
|
|
500
|
+
/**
|
|
501
|
+
* The value of `--cm-bubble-scale` for a given `k`: the bubble is drawn inside the viewport (which
|
|
502
|
+
* already multiplies by `k`), so it is compensated with `1/√k`, `1/k` or `1`.
|
|
503
|
+
*/
|
|
504
|
+
declare const bubbleScale: (k: number, scaling: BubbleScaling) => number;
|
|
505
|
+
/** Drill-down level. Same as `DrillLevelInfo` in `react/map-item.ts`. */
|
|
506
|
+
type ViewLevel = {
|
|
507
|
+
id: string;
|
|
508
|
+
depth: number;
|
|
509
|
+
};
|
|
510
|
+
/** What `onViewChange` receives. */
|
|
511
|
+
type ViewState = {
|
|
512
|
+
/** Camera transform, in level pixels. */
|
|
513
|
+
k: number;
|
|
514
|
+
x: number;
|
|
515
|
+
y: number;
|
|
516
|
+
/** = `k` (1 = the level's fit): a readable alias. */
|
|
517
|
+
zoom: number;
|
|
518
|
+
/** [lon, lat] of the view's center. */
|
|
519
|
+
center: Point;
|
|
520
|
+
/** Visible [[lonW, latS], [lonE, latN]]. Approximate: does not account for the antimeridian. */
|
|
521
|
+
bounds: readonly [Point, Point];
|
|
522
|
+
level: ViewLevel;
|
|
523
|
+
};
|
|
524
|
+
type ToViewStateParams = {
|
|
525
|
+
transform: CameraTransform;
|
|
526
|
+
size: Size;
|
|
527
|
+
/** Canvas pixels → [lon, lat] (`projection.invert`); `null` if it falls outside the globe. */
|
|
528
|
+
invert: (point: Point) => Point | null;
|
|
529
|
+
level?: ViewLevel;
|
|
530
|
+
};
|
|
531
|
+
/**
|
|
532
|
+
* Turns the camera into a `ViewState` with geographic coordinates. The view's corners often fall
|
|
533
|
+
* outside the globe (a world map in Equal Earth is not a rectangle), so the bounds are estimated by
|
|
534
|
+
* sampling a grid over the view and keeping the points that do project. If none does, it returns the
|
|
535
|
+
* whole world and the center `[0, 0]`.
|
|
536
|
+
*/
|
|
537
|
+
declare const toViewState: ({ transform, size, invert, level, }: ToViewStateParams) => ViewState;
|
|
538
|
+
|
|
539
|
+
type GetFocusGeometryParams = {
|
|
540
|
+
feature: Feature;
|
|
541
|
+
/** An already-projected `geoPath`: areas and distances are measured in pixels, not degrees. */
|
|
542
|
+
path: GeoPath;
|
|
543
|
+
/**
|
|
544
|
+
* A polygon counts as part of the "main body" if the center of its bounds is closer than
|
|
545
|
+
* `near × diagonal(main)` to the center of the main polygon. Defaults to 0.5.
|
|
546
|
+
*/
|
|
547
|
+
near?: number;
|
|
548
|
+
};
|
|
549
|
+
/**
|
|
550
|
+
* The "main" sub-geometry of a feature: the polygon with the largest projected area plus the ones
|
|
551
|
+
* close to it (Mallorca, Corsica or Sicily yes; Guyana, Hawaii or the Canary Islands no).
|
|
552
|
+
*
|
|
553
|
+
* Why it exists: `geoPath.bounds` / `centroid` of the whole feature include the
|
|
554
|
+
* distant territories. Zooming to "France" would show half the Atlantic, and its bubble would land
|
|
555
|
+
* in the Bay of Biscay. It is used to frame the view and to anchor bubbles and labels.
|
|
556
|
+
*
|
|
557
|
+
* Returns `null` if the feature has no geometry. Non-polygon geometries and lone polygons are
|
|
558
|
+
* returned as they are.
|
|
559
|
+
*/
|
|
560
|
+
declare const getFocusGeometry: ({ feature, path, near, }: GetFocusGeometryParams) => Geometry | null;
|
|
561
|
+
/**
|
|
562
|
+
* Anchor point for bubbles and labels: the "pole of inaccessibility" (`polylabel`) of the largest
|
|
563
|
+
* projected polygon of `geometry`, i.e. the interior point farthest from the edge. Unlike the
|
|
564
|
+
* centroid, it always falls INSIDE the polygon (Chile, Croatia, France).
|
|
565
|
+
*
|
|
566
|
+
* `project` turns [lon, lat] into pixel [x, y] and returns `null` if it cannot. `precision` is in
|
|
567
|
+
* pixels. Returns `null` if there is no projectable polygon.
|
|
568
|
+
*/
|
|
569
|
+
declare const getAnchorPoint: ({ geometry, project, precision, }: {
|
|
570
|
+
geometry: Geometry | null;
|
|
571
|
+
project: (coordinates: Point) => Point | null;
|
|
572
|
+
precision?: number;
|
|
573
|
+
}) => Point | null;
|
|
574
|
+
type Padding = {
|
|
575
|
+
top: number;
|
|
576
|
+
right: number;
|
|
577
|
+
bottom: number;
|
|
578
|
+
left: number;
|
|
579
|
+
};
|
|
580
|
+
/** Default padding when fitting and when flying to a region, in px. */
|
|
581
|
+
declare const DEFAULT_PADDING = 24;
|
|
582
|
+
/** `number` = the same padding on all four sides. */
|
|
583
|
+
declare const normalizePadding: (padding?: number | Padding) => Padding;
|
|
584
|
+
type BoundsToTransformParams = {
|
|
585
|
+
/** What to fit, in canvas pixels: [[x0, y0], [x1, y1]]. */
|
|
586
|
+
bounds: Extent;
|
|
587
|
+
/** The view (the container). */
|
|
588
|
+
size: Size;
|
|
589
|
+
padding?: number | Padding;
|
|
590
|
+
/** [minimum, maximum] of `k`. Unbounded by default. */
|
|
591
|
+
scaleExtent?: readonly [number, number];
|
|
592
|
+
};
|
|
593
|
+
/**
|
|
594
|
+
* Camera that fits some bounds into the view, with padding in pixels. It is the
|
|
595
|
+
* target of "zoom to a country" and of the initial framing. It honors `scaleExtent`: if the country
|
|
596
|
+
* is so small that more zoom than allowed would be needed, it stays at the maximum (and the country
|
|
597
|
+
* is centered).
|
|
598
|
+
*
|
|
599
|
+
* Degenerate bounds (a single point): `k` rises up to the maximum instead of producing `Infinity`. If
|
|
600
|
+
* the padding eats the whole view, `k` drops to the minimum.
|
|
601
|
+
*/
|
|
602
|
+
declare const boundsToTransform: ({ bounds, size, padding, scaleExtent, }: BoundsToTransformParams) => CameraTransform;
|
|
603
|
+
/** Geographic bounds [[lonW, latS], [lonE, latN]]. If `lonE < lonW`, they cross the antimeridian. */
|
|
604
|
+
type LonLatBounds = readonly [Point, Point];
|
|
605
|
+
/**
|
|
606
|
+
* Geographic rectangle → pixel rectangle that contains it. A lon/lat rectangle is NOT a rectangle on
|
|
607
|
+
* screen (parallels curve in Equal Earth, the globe narrows at the poles), so points are projected
|
|
608
|
+
* along its four sides and the minimum box containing them is taken. Points the projection cannot
|
|
609
|
+
* project are discarded; if none remain, it returns `null`.
|
|
610
|
+
*
|
|
611
|
+
* If `lonE < lonW` the rectangle crosses the antimeridian (Fiji, the Pacific). On a flat map that
|
|
612
|
+
* region appears split across both edges and **no rectangle contains both halves** (it would give a
|
|
613
|
+
* box as wide as the whole world): the wider half is framed, just as `getFocusGeometry` keeps the
|
|
614
|
+
* main body.
|
|
615
|
+
*/
|
|
616
|
+
declare const projectBounds: (bounds: LonLatBounds, project: (coordinates: Point) => Point | null) => Extent | null;
|
|
617
|
+
type GetFocusExtentParams = GetFocusGeometryParams & {
|
|
618
|
+
/** [lon, lat] → canvas pixels; `null` if it does not project. */
|
|
619
|
+
project: (coordinates: Point) => Point | null;
|
|
620
|
+
};
|
|
621
|
+
/**
|
|
622
|
+
* Pixel bounds of a region's main body: what gets framed when flying to it. It is
|
|
623
|
+
* `getFocusGeometry` + its projected bounds, with one special case:
|
|
624
|
+
*
|
|
625
|
+
* **Antimeridian.** The main polygon of Russia (and Fiji's or Alaska's) crosses the ±180° line
|
|
626
|
+
* without being split, and `path.bounds` measures both halves: the framing would span the map from
|
|
627
|
+
* edge to edge, with Chukotka poking out on the opposite side. It is detected with `geoBounds` (which
|
|
628
|
+
* returns `west > east` when crossing) and the wider half is framed (`projectBounds`).
|
|
629
|
+
*
|
|
630
|
+
* Returns `null` if the feature has no geometry or its bounds are not finite.
|
|
631
|
+
*/
|
|
632
|
+
declare const getFocusExtent: ({ feature, path, project, near, }: GetFocusExtentParams) => Extent | null;
|
|
633
|
+
|
|
634
|
+
/**
|
|
635
|
+
* Theme: a typed object that is translated into `--cm-*` CSS variables. **The
|
|
636
|
+
* variables remain the single source of truth**: the object is sugar so there is autocompletion and
|
|
637
|
+
* reusable themes.
|
|
638
|
+
*
|
|
639
|
+
* It is pure (no React or DOM) and lives in `core` even though it is consumed only by `react`:
|
|
640
|
+
* that way it is tested without rendering and a consumer without React can use `themeToCssVars`.
|
|
641
|
+
*
|
|
642
|
+
* It only has fields for what EXISTS today. `sphere`, `graticule`, `hover.effect`, `selected.glow`,
|
|
643
|
+
* `motion` and `empty.pattern` are planned and arrive with the layers and effects that consume
|
|
644
|
+
* them (adding a field breaks nobody; having one that does nothing does mislead).
|
|
645
|
+
*/
|
|
646
|
+
type ChoroplethTheme = {
|
|
647
|
+
/** Map background (the `.cm-root`). Transparent by default. */
|
|
648
|
+
background?: string;
|
|
649
|
+
/** Regions without data. `pattern`: a pattern over regions without data (not just color: they can be told apart without seeing it either). None by default. */
|
|
650
|
+
empty?: {
|
|
651
|
+
fill?: string;
|
|
652
|
+
pattern?: "none" | "hatch" | "dots";
|
|
653
|
+
};
|
|
654
|
+
/** The levels above in a drill: gray, with no data color. */
|
|
655
|
+
context?: {
|
|
656
|
+
fill?: string;
|
|
657
|
+
border?: string;
|
|
658
|
+
};
|
|
659
|
+
/** Borders between regions. */
|
|
660
|
+
border?: {
|
|
661
|
+
color?: string;
|
|
662
|
+
width?: number;
|
|
663
|
+
};
|
|
664
|
+
hover?: {
|
|
665
|
+
stroke?: string;
|
|
666
|
+
width?: number;
|
|
667
|
+
};
|
|
668
|
+
selected?: {
|
|
669
|
+
stroke?: string;
|
|
670
|
+
width?: number;
|
|
671
|
+
};
|
|
672
|
+
/** Keyboard focus ring: color and thickness (px). */
|
|
673
|
+
focus?: {
|
|
674
|
+
ring?: string;
|
|
675
|
+
width?: number;
|
|
676
|
+
};
|
|
677
|
+
/** Opacity of the rest of the map with `dimOthers`. */
|
|
678
|
+
dim?: {
|
|
679
|
+
opacity?: number;
|
|
680
|
+
};
|
|
681
|
+
bubble?: {
|
|
682
|
+
fill?: string;
|
|
683
|
+
stroke?: string;
|
|
684
|
+
opacity?: number;
|
|
685
|
+
};
|
|
686
|
+
font?: {
|
|
687
|
+
family?: string;
|
|
688
|
+
size?: number;
|
|
689
|
+
color?: string;
|
|
690
|
+
};
|
|
691
|
+
tooltip?: {
|
|
692
|
+
background?: string;
|
|
693
|
+
color?: string;
|
|
694
|
+
border?: string;
|
|
695
|
+
/** px */
|
|
696
|
+
radius?: number;
|
|
697
|
+
/** A `box-shadow` value. */
|
|
698
|
+
shadow?: string;
|
|
699
|
+
};
|
|
700
|
+
/** Zoom buttons and drill breadcrumbs. */
|
|
701
|
+
controls?: {
|
|
702
|
+
background?: string;
|
|
703
|
+
color?: string;
|
|
704
|
+
border?: string;
|
|
705
|
+
radius?: number;
|
|
706
|
+
};
|
|
707
|
+
};
|
|
708
|
+
type ThemeName = "light" | "dark" | "minimal" | "contrast";
|
|
709
|
+
/** Identity: it exists to infer the type with autocompletion and to reuse a theme across maps. */
|
|
710
|
+
declare const defineTheme: (theme: ChoroplethTheme) => ChoroplethTheme;
|
|
711
|
+
declare const THEMES: Readonly<Record<ThemeName, ChoroplethTheme>>;
|
|
712
|
+
declare const isThemeName: (value: unknown) => value is ThemeName;
|
|
713
|
+
/** A theme's object by name, or the same object if it already is one. `undefined` = no theme. */
|
|
714
|
+
declare const resolveTheme: (theme: ThemeName | ChoroplethTheme | undefined) => ChoroplethTheme | undefined;
|
|
715
|
+
/**
|
|
716
|
+
* Starts from a theme (by name or object) and overlays adjustments, group by group: whatever you put in
|
|
717
|
+
* `border` replaces only those `border` fields, not the rest of the theme.
|
|
718
|
+
*/
|
|
719
|
+
declare const extendTheme: (base: ThemeName | ChoroplethTheme, overrides: ChoroplethTheme) => ChoroplethTheme;
|
|
720
|
+
/**
|
|
721
|
+
* The `theme` object → CSS variables (`{ "--cm-border-color": "#fff", … }`), ready for a `style`. Pure:
|
|
722
|
+
* no DOM. Undefined fields produce no variable (whatever was there stays: the base CSS or yours).
|
|
723
|
+
*/
|
|
724
|
+
declare const themeToCssVars: (theme: ChoroplethTheme | undefined) => Record<string, string>;
|
|
725
|
+
|
|
726
|
+
/**
|
|
727
|
+
* The library's UI texts (i18n). English by default; `es` for Spanish. Each phase adds its own
|
|
728
|
+
* (`drillHint`, `breadcrumbRoot`…). Console messages do NOT go through here: they are for the
|
|
729
|
+
* developer and are always in English.
|
|
730
|
+
*/
|
|
731
|
+
type ChoroplethLabels = {
|
|
732
|
+
/** Region without data: tooltip and legend. */
|
|
733
|
+
noData: string;
|
|
734
|
+
/** The asynchronous geometry could not be loaded. */
|
|
735
|
+
geographyError: string;
|
|
736
|
+
/** Zoom buttons (`aria-label`). */
|
|
737
|
+
zoomIn: string;
|
|
738
|
+
zoomOut: string;
|
|
739
|
+
resetView: string;
|
|
740
|
+
/** Hint for `wheel: "cooperative"`. The `Mac` variant is used on macOS and iOS (⌘ instead of Ctrl). */
|
|
741
|
+
gestureHintWheel: string;
|
|
742
|
+
gestureHintWheelMac: string;
|
|
743
|
+
gestureHintTouch: string;
|
|
744
|
+
/** Drill-down: `aria-label` of the breadcrumbs' `<nav>`, its root (if the first level has no `label`), the "←" button and the tooltip hint. */
|
|
745
|
+
breadcrumb: string;
|
|
746
|
+
breadcrumbRoot: string;
|
|
747
|
+
drillUp: string;
|
|
748
|
+
drillHint: string;
|
|
749
|
+
/** Touch: the tooltip hint after the first tap (the second one drills down). */
|
|
750
|
+
tapAgainHint: string;
|
|
751
|
+
};
|
|
752
|
+
declare const en: ChoroplethLabels;
|
|
753
|
+
declare const es: ChoroplethLabels;
|
|
754
|
+
/** Fills in any missing texts with the English ones. */
|
|
755
|
+
declare const resolveLabels: (labels?: Partial<ChoroplethLabels>) => ChoroplethLabels;
|
|
756
|
+
|
|
757
|
+
type JoinDataOptions<TDatum> = {
|
|
758
|
+
features: readonly Feature[];
|
|
759
|
+
data: readonly TDatum[];
|
|
760
|
+
/** Id of each user datum (e.g. `(d) => d.countryCode`). */
|
|
761
|
+
getId: (datum: TDatum) => string;
|
|
762
|
+
/**
|
|
763
|
+
* Id of each region if it is not `feature.id` (e.g. `(f) => f.properties?.iso_a2`).
|
|
764
|
+
* Returning `undefined` means "this region has no id": it cannot be joined and is ignored.
|
|
765
|
+
*/
|
|
766
|
+
getFeatureId?: (feature: Feature) => string | undefined;
|
|
767
|
+
};
|
|
768
|
+
type JoinResult<TDatum> = {
|
|
769
|
+
/** region id → datum. Only contains joins that DID happen. */
|
|
770
|
+
joined: Map<string, TDatum>;
|
|
771
|
+
/** ids of data that do not match any region (e.g. "ES" when the geometry uses "724"). */
|
|
772
|
+
unmatchedData: string[];
|
|
773
|
+
/** ids of regions left without data (they will be painted with `emptyFill`). */
|
|
774
|
+
featuresWithoutData: string[];
|
|
775
|
+
/** repeated ids in `data`: only the first of each is kept. */
|
|
776
|
+
duplicateDataIds: string[];
|
|
777
|
+
};
|
|
778
|
+
/**
|
|
779
|
+
* Joins the user's data with the map's regions by a common identifier.
|
|
780
|
+
*
|
|
781
|
+
* This is the step where the #1 cause of "my map is gray" is born: the datum says "ES" and the
|
|
782
|
+
* geometry says "724". This function does NOT try to guess or fix ids (that would hide the error):
|
|
783
|
+
* it joins them as they are and **reports** what did not join. Whoever shows the warning to the
|
|
784
|
+
* developer (console.warn, `onJoinReport`) will do it with this result, in another layer.
|
|
785
|
+
*
|
|
786
|
+
* It is pure: it does not write to the console or read `process.env`. Complexity O(regions + data)
|
|
787
|
+
* thanks to Map/Set; with `data.find(...)` per region it would be O(regions × data).
|
|
788
|
+
*/
|
|
789
|
+
declare const joinData: <TDatum>({ features, data, getId, getFeatureId, }: JoinDataOptions<TDatum>) => JoinResult<TDatum>;
|
|
790
|
+
|
|
791
|
+
declare const PROJECTIONS: {
|
|
792
|
+
readonly equalEarth: typeof geoEqualEarth;
|
|
793
|
+
readonly naturalEarth: typeof geoNaturalEarth1;
|
|
794
|
+
readonly mercator: typeof geoMercator;
|
|
795
|
+
readonly albers: typeof geoAlbers;
|
|
796
|
+
readonly orthographic: typeof geoOrthographic;
|
|
797
|
+
};
|
|
798
|
+
type ProjectionName = keyof typeof PROJECTIONS;
|
|
799
|
+
/** The factory of a projection by name (each call, a new instance). */
|
|
800
|
+
declare const projectionFactory: (name?: ProjectionName) => (() => GeoProjection);
|
|
801
|
+
declare const DEFAULT_PROJECTION: ProjectionName;
|
|
802
|
+
type CreateProjectionOptions = {
|
|
803
|
+
/** Projection by name. Defaults to `equalEarth`. */
|
|
804
|
+
name?: ProjectionName;
|
|
805
|
+
/** Container size in pixels. */
|
|
806
|
+
width: number;
|
|
807
|
+
height: number;
|
|
808
|
+
/** Geometry that must fit entirely in `width × height`. */
|
|
809
|
+
featureCollection: FeatureCollection;
|
|
810
|
+
};
|
|
811
|
+
/**
|
|
812
|
+
* Creates an already-fitted d3 projection (`fitSize`) so the geometry fits the container snugly.
|
|
813
|
+
*
|
|
814
|
+
* Without the fit, a d3 projection uses a fixed scale designed for 960×500: in a different
|
|
815
|
+
* container the map would come out cropped or tiny. `fitSize` computes scale and translation for us
|
|
816
|
+
* (no zoom or center math by hand).
|
|
817
|
+
*/
|
|
818
|
+
declare const createProjection: ({ name, width, height, featureCollection, }: CreateProjectionOptions) => GeoProjection;
|
|
819
|
+
/** Aspect ratio used if the geometry cannot be measured (the world in Equal Earth). */
|
|
820
|
+
declare const DEFAULT_ASPECT_RATIO: number;
|
|
821
|
+
/**
|
|
822
|
+
* width / height the geometry takes up with that projection (`aspectRatio: "auto"`).
|
|
823
|
+
*
|
|
824
|
+
* Chile is very tall and Norway very wide: with a fixed ratio there would be too much or too little
|
|
825
|
+
* space. The projection is fitted to a reference width and the resulting rectangle is measured; the
|
|
826
|
+
* ratio does not depend on the chosen width. It is clamped to [0.2 – 5] so an odd datum (a single
|
|
827
|
+
* island) does not leave a container 1 px tall.
|
|
828
|
+
*/
|
|
829
|
+
declare const computeAspectRatio: ({ name, featureCollection, }: {
|
|
830
|
+
name?: ProjectionName;
|
|
831
|
+
featureCollection: FeatureCollection;
|
|
832
|
+
}) => number;
|
|
833
|
+
/**
|
|
834
|
+
* The affine map `R` such that `to(p) === R(from(p))` for every `p`, where `R(q) = k·q + (x, y)`.
|
|
835
|
+
*
|
|
836
|
+
* It only holds if `from` and `to` share EVERYTHING except `scale` and `translate` (same projection,
|
|
837
|
+
* rotation, center…). Then switching from one to the other is an affine transformation of the
|
|
838
|
+
* pixels, and so it can be expressed as a camera (`CameraTransform`): `R` is at once "how far the
|
|
839
|
+
* parent's camera must move" and "what separates the parent's pixels from the child's".
|
|
840
|
+
*/
|
|
841
|
+
declare const relativeTransform: (from: GeoProjection, to: GeoProjection) => CameraTransform;
|
|
842
|
+
type RebaseParams = {
|
|
843
|
+
/** The parent level's projection (it is not modified). */
|
|
844
|
+
parent: GeoProjection;
|
|
845
|
+
/** Creates a new projection of the SAME type as `parent` (d3 projections are mutable). */
|
|
846
|
+
factory: () => GeoProjection;
|
|
847
|
+
/** The geometry that must fill the view: the region's main body (`getFocusGeometry`). */
|
|
848
|
+
focus: GeoPermissibleObjects;
|
|
849
|
+
width: number;
|
|
850
|
+
height: number;
|
|
851
|
+
padding: Padding;
|
|
852
|
+
};
|
|
853
|
+
type RebaseResult = {
|
|
854
|
+
/** The child's projection, fitted to `focus`. At `k = 1` the precision is full. */
|
|
855
|
+
projection: GeoProjection;
|
|
856
|
+
/** `projection(p) === relative(parent(p))`: the exact destination of the flight and of the context layer. */
|
|
857
|
+
relative: CameraTransform;
|
|
858
|
+
};
|
|
859
|
+
/**
|
|
860
|
+
* Child-level projection with perfect continuity.
|
|
861
|
+
*
|
|
862
|
+
* Two naive solutions fail: a single projection with a huge `k` (at `k ≈ 300` the 3 decimals of
|
|
863
|
+
* `geoPath` give jagged edges) and a new `fitSize` per level (the child appears at another scale and
|
|
864
|
+
* position: a visible jump). Here the child is the parent's projection with **the same rotation,
|
|
865
|
+
* center and other parameters** and only `scale`/`translate` readjusted (`fitExtent`). That is an
|
|
866
|
+
* affine map of the output, so if the flight ends exactly at `relative`, switching to the child
|
|
867
|
+
* projection with an identity camera paints the **same pixels**.
|
|
868
|
+
*/
|
|
869
|
+
declare const rebaseProjection: ({ parent, factory, focus, width, height, padding, }: RebaseParams) => RebaseResult;
|
|
870
|
+
|
|
871
|
+
type Corner = "top-left" | "top-right" | "bottom-left" | "bottom-right";
|
|
872
|
+
type ZoomControlsOptions = {
|
|
873
|
+
/** Corner of the map. Defaults to `"bottom-right"` (the legend goes bottom-left by default). */
|
|
874
|
+
position?: Corner;
|
|
875
|
+
/** Show the "reset view" button. Defaults to `true`. */
|
|
876
|
+
reset?: boolean;
|
|
877
|
+
};
|
|
878
|
+
/**
|
|
879
|
+
* Where to move the camera: a region, some bounds or a center with zoom.
|
|
880
|
+
* - `string`: id of a region on the map. Its **main body** is framed (France without Guiana).
|
|
881
|
+
* - `{ bounds }`: [[lonW, latS], [lonE, latN]].
|
|
882
|
+
* - `{ center, zoom }`: `zoom` relative to the fit (1 = fit).
|
|
883
|
+
*/
|
|
884
|
+
type ZoomTarget = string | {
|
|
885
|
+
bounds: LonLatBounds;
|
|
886
|
+
} | {
|
|
887
|
+
center: readonly [number, number];
|
|
888
|
+
zoom: number;
|
|
889
|
+
};
|
|
890
|
+
type CameraOptions = {
|
|
891
|
+
/** ms. Without this, the flight's natural duration (350–1200 ms). `0` = no animation. */
|
|
892
|
+
duration?: number;
|
|
893
|
+
/** Margin when framing (px). Defaults to that of `zoom.padding` (24). */
|
|
894
|
+
padding?: number | Padding;
|
|
895
|
+
};
|
|
896
|
+
/**
|
|
897
|
+
* Explicit framings per region, in lon/lat. **Spain is framed with the Canary Islands by default**,
|
|
898
|
+
* even though the geometry's main body does not include them. It is merged with the consumer's
|
|
899
|
+
* `focusBounds`; with `{ ES: null }` it returns to the geometric framing (peninsula only).
|
|
900
|
+
*/
|
|
901
|
+
declare const DEFAULT_FOCUS_BOUNDS: Readonly<Record<string, LonLatBounds>>;
|
|
902
|
+
/** Result of an animated camera move: `completed` is `false` if something interrupted it. */
|
|
903
|
+
type TransitionResult = {
|
|
904
|
+
completed: boolean;
|
|
905
|
+
};
|
|
906
|
+
/**
|
|
907
|
+
* Zoom and pan options. Only the ones that already do something are here; `inertia`,
|
|
908
|
+
* `padding` and `initialView` are added in steps 26-27 with their behavior (declaring them earlier
|
|
909
|
+
* would promise what does not exist).
|
|
910
|
+
*/
|
|
911
|
+
type ZoomOptions = {
|
|
912
|
+
/** Minimum scale relative to the level's fit (1 = cannot zoom out further than the fit). Default 1. */
|
|
913
|
+
min?: number;
|
|
914
|
+
/** Maximum scale. Default 12. */
|
|
915
|
+
max?: number;
|
|
916
|
+
/**
|
|
917
|
+
* What the wheel does over the map. Default `"always"`: with the mouse over it, the wheel zooms.
|
|
918
|
+
* - `"cooperative"`: only with Ctrl/⌘ + wheel (and touch with two fingers); the page keeps
|
|
919
|
+
* scrolling. Recommended for maps inside a long page or a dashboard.
|
|
920
|
+
* - `"never"`: the wheel does not zoom (dragging and pinching still work).
|
|
921
|
+
*
|
|
922
|
+
* ⚠️ With `"always"`, a map on a scrolling page "eats" the wheel when passed over
|
|
923
|
+
* (_scroll-jacking_). `"cooperative"` is the alternative; the default is `"always"`.
|
|
924
|
+
*/
|
|
925
|
+
wheel?: "cooperative" | "always" | "never";
|
|
926
|
+
/** Two-finger pinch. Default `true`. (A trackpad pinch arrives as Ctrl + wheel.) */
|
|
927
|
+
pinch?: boolean;
|
|
928
|
+
/** Drag to pan. Default `true`. */
|
|
929
|
+
pan?: boolean;
|
|
930
|
+
/**
|
|
931
|
+
* Double click / double tap = zoom ×2. `"auto"` (default): active unless the click has another
|
|
932
|
+
* meaning (`onItemClick` or selection), so as not to do both at once.
|
|
933
|
+
*/
|
|
934
|
+
doubleClick?: boolean | "auto";
|
|
935
|
+
/** Margin when framing a region or some bounds, in px. Default 24. */
|
|
936
|
+
padding?: number | Padding;
|
|
937
|
+
/**
|
|
938
|
+
* Initial view: the camera is placed there **without animating** on first render. `focusId` takes
|
|
939
|
+
* precedence over this. Useful for shareable URLs: `initialView: { center: [-3.7, 40.4], zoom: 4 }`.
|
|
940
|
+
*/
|
|
941
|
+
initialView?: ZoomTarget;
|
|
942
|
+
/**
|
|
943
|
+
* Bubbles when zooming: `"sqrt"` (default, they grow with √k), `"screen"` (constant
|
|
944
|
+
* size on screen) or `"geo"` (they grow with the map).
|
|
945
|
+
*/
|
|
946
|
+
bubbleScaling?: BubbleScaling;
|
|
947
|
+
/** When a fast drag is released, the map keeps sliding and slows down. Default `true`. */
|
|
948
|
+
inertia?: boolean;
|
|
949
|
+
/** `"contain"` (default): the content is not lost and is centered if it fits. `"free"`: no limits. */
|
|
950
|
+
constrain?: "contain" | "free";
|
|
951
|
+
/** Keyboard shortcuts with the map focused: `+`, `−` and `0`. Default `true`. */
|
|
952
|
+
keyboard?: boolean;
|
|
953
|
+
/**
|
|
954
|
+
* `+` / `−` / `⟲` buttons. **Opt-in** (default `false`): `true` or `{ position, reset }`. The preset
|
|
955
|
+
* draws them; with the compound, put `<Choropleth.ZoomControls>` wherever you like.
|
|
956
|
+
*/
|
|
957
|
+
controls?: boolean | ZoomControlsOptions;
|
|
958
|
+
/** At the END of each gesture (never per frame). With programmatic flights, at the end of the flight. */
|
|
959
|
+
onViewChange?: (view: ViewState) => void;
|
|
960
|
+
};
|
|
961
|
+
type ResolvedZoom = {
|
|
962
|
+
min: number;
|
|
963
|
+
max: number;
|
|
964
|
+
wheel: "cooperative" | "always" | "never";
|
|
965
|
+
pinch: boolean;
|
|
966
|
+
pan: boolean;
|
|
967
|
+
/** Already resolved: `"auto"` becomes `true`/`false` depending on whether the click has another meaning. */
|
|
968
|
+
doubleClick: boolean;
|
|
969
|
+
constrain: "contain" | "free";
|
|
970
|
+
bubbleScaling: BubbleScaling;
|
|
971
|
+
inertia: boolean;
|
|
972
|
+
keyboard: boolean;
|
|
973
|
+
padding: number | Padding;
|
|
974
|
+
initialView: ZoomTarget | undefined;
|
|
975
|
+
};
|
|
976
|
+
declare const DEFAULT_ZOOM_MIN = 1;
|
|
977
|
+
declare const DEFAULT_ZOOM_MAX = 12;
|
|
978
|
+
/**
|
|
979
|
+
* `zoom` (the prop) → full options, or `null` if zoom is disabled (`zoom={false}`).
|
|
980
|
+
* `clickIsBusy`: the click already does something (`onItemClick`, selection), to resolve `doubleClick: "auto"`.
|
|
981
|
+
*/
|
|
982
|
+
declare const resolveZoomOptions: (zoom: boolean | ZoomOptions | undefined, { clickIsBusy }: {
|
|
983
|
+
clickIsBusy: boolean;
|
|
984
|
+
}) => ResolvedZoom | null;
|
|
985
|
+
/** `zoom.controls` → what to draw, or `null` if there are no controls (the default). */
|
|
986
|
+
declare const resolveControls: (zoom: boolean | ZoomOptions | undefined) => Required<ZoomControlsOptions> | null;
|
|
987
|
+
type ZoomKeyAction = "in" | "out" | "reset";
|
|
988
|
+
/**
|
|
989
|
+
* What a key does on the focused map: `+` zooms in, `−` zooms out, `0` returns to the fit.
|
|
990
|
+
*
|
|
991
|
+
* With Ctrl, ⌘ or Alt it is NOT interpreted: Ctrl/⌘ + `+`, `−`, `0` is the browser's page zoom, and
|
|
992
|
+
* taking it from the user would be an accessibility failure.
|
|
993
|
+
*/
|
|
994
|
+
declare const zoomKeyAction: (event: {
|
|
995
|
+
key: string;
|
|
996
|
+
ctrlKey?: boolean;
|
|
997
|
+
metaKey?: boolean;
|
|
998
|
+
altKey?: boolean;
|
|
999
|
+
}) => ZoomKeyAction | null;
|
|
1000
|
+
/** The minimum the filter needs from a DOM event (so it can be tested without a DOM). */
|
|
1001
|
+
type ZoomEventLike = {
|
|
1002
|
+
type: string;
|
|
1003
|
+
ctrlKey?: boolean;
|
|
1004
|
+
metaKey?: boolean;
|
|
1005
|
+
button?: number;
|
|
1006
|
+
touches?: {
|
|
1007
|
+
length: number;
|
|
1008
|
+
};
|
|
1009
|
+
};
|
|
1010
|
+
type ZoomFilterParams = Pick<ResolvedZoom, "wheel" | "pinch" | "pan" | "doubleClick"> & {
|
|
1011
|
+
/** Called when a gesture does NOT zoom but could be taught how (the Ctrl/⌘ + wheel hint). */
|
|
1012
|
+
onGestureHint?: (kind: "wheel" | "touch") => void;
|
|
1013
|
+
};
|
|
1014
|
+
/**
|
|
1015
|
+
* Which events trigger zoom. Replaces d3-zoom's default filter, which is
|
|
1016
|
+
* `(!ctrlKey || type === "wheel") && !button`.
|
|
1017
|
+
*
|
|
1018
|
+
* Returning `false` does NOT call `preventDefault`: in `cooperative` the wheel without a modifier and
|
|
1019
|
+
* one-finger touch keep scrolling the page.
|
|
1020
|
+
*/
|
|
1021
|
+
declare const createZoomFilter: ({ wheel, pinch, pan, doubleClick, onGestureHint }: ZoomFilterParams) => (event: ZoomEventLike) => boolean;
|
|
1022
|
+
type ResolveZoomTargetParams = {
|
|
1023
|
+
target: ZoomTarget;
|
|
1024
|
+
size: Size;
|
|
1025
|
+
padding?: number | Padding;
|
|
1026
|
+
/** [minimum, maximum] of `k`. */
|
|
1027
|
+
scaleExtent: readonly [number, number];
|
|
1028
|
+
/** [lon, lat] → canvas pixels; `null` if it does not project. */
|
|
1029
|
+
project: (coordinates: Point) => Point | null;
|
|
1030
|
+
/** Pixel bounds of a region's main body, or `null` if it does not exist. */
|
|
1031
|
+
getRegionExtent: (id: string) => Extent | null;
|
|
1032
|
+
/** Framings per region (already merged with `DEFAULT_FOCUS_BOUNDS`). `null` = use the geometry. */
|
|
1033
|
+
focusBounds?: Readonly<Record<string, LonLatBounds | null | undefined>>;
|
|
1034
|
+
};
|
|
1035
|
+
/**
|
|
1036
|
+
* `ZoomTarget` → camera. Pure: the projection and geometry arrive as
|
|
1037
|
+
* functions. Returns `null` if it cannot be resolved (nonexistent region, point outside the globe).
|
|
1038
|
+
*
|
|
1039
|
+
* It does not apply `constrain`: whoever moves the camera takes care of that, because d3-zoom only
|
|
1040
|
+
* applies it to gestures, not to programmatic transitions.
|
|
1041
|
+
*/
|
|
1042
|
+
declare const resolveZoomTarget: ({ target, size, padding, scaleExtent, project, getRegionExtent, focusBounds, }: ResolveZoomTargetParams) => CameraTransform | null;
|
|
1043
|
+
|
|
1044
|
+
/**
|
|
1045
|
+
* Drill-down state machine. It is a pure _reducer_: no DOM, no timers, no promises.
|
|
1046
|
+
* Whoever uses it (`react/`) translates the real world into events (click, end of flight, geometry
|
|
1047
|
+
* ready…) and renders the resulting state. That way every edge case (click during a flight, error,
|
|
1048
|
+
* going up before finishing going down…) is tested without jsdom.
|
|
1049
|
+
*
|
|
1050
|
+
* Vocabulary:
|
|
1051
|
+
* - **path**: ids from the root (`[]` = world, `["ES"]` = Spain). It is the "optimistic" target: it
|
|
1052
|
+
* changes the instant of the click.
|
|
1053
|
+
* - **settledPath**: the path of what is actually drawn as the active layer. Until the rebase,
|
|
1054
|
+
* `path` and `settledPath` differ.
|
|
1055
|
+
* - **layer**: the regions of one level. The layer at depth `d` shows the children of
|
|
1056
|
+
* `path[0..d)`. At `["ES"]` there are two: the world (`context`) and Spain's regions (`active`).
|
|
1057
|
+
*/
|
|
1058
|
+
type DrillPhase = "idle" | "flying" | "loading" | "revealing";
|
|
1059
|
+
/** `down`: one level deeper; `up`: to an ancestor; `lateral`: a sibling; `jump`: any other jump. */
|
|
1060
|
+
type DrillDirection = "down" | "up" | "lateral" | "jump";
|
|
1061
|
+
type DrillLayerRole = "active" | "context" | "entering" | "exiting";
|
|
1062
|
+
type DrillLayer = {
|
|
1063
|
+
/** Layer identity: see `layerKey`. */
|
|
1064
|
+
key: string;
|
|
1065
|
+
depth: number;
|
|
1066
|
+
role: DrillLayerRole;
|
|
1067
|
+
};
|
|
1068
|
+
type DrillMachineState = {
|
|
1069
|
+
phase: DrillPhase;
|
|
1070
|
+
/** Target (optimistic). */
|
|
1071
|
+
path: readonly string[];
|
|
1072
|
+
/** What is currently drawn as active. */
|
|
1073
|
+
settledPath: readonly string[];
|
|
1074
|
+
/** Direction of the transition in progress; `null` at rest. */
|
|
1075
|
+
direction: DrillDirection | null;
|
|
1076
|
+
layers: readonly DrillLayer[];
|
|
1077
|
+
/** Is a camera flight pending? */
|
|
1078
|
+
flight: "pending" | "done";
|
|
1079
|
+
/** Identifies the current flight: a `FLIGHT_END` from another flight (already redirected) is ignored. */
|
|
1080
|
+
flightId: number;
|
|
1081
|
+
/** State of the new layer's geometry. `null` if there is nothing to load. */
|
|
1082
|
+
geometry: "pending" | "ready" | "error" | null;
|
|
1083
|
+
/** `key` of the layer whose geometry is awaited; any other geometry notices are stale. */
|
|
1084
|
+
geometryKey: string | null;
|
|
1085
|
+
/** Camera of each level when going down from it, by `layerKey`. Going up flies back to the saved one. */
|
|
1086
|
+
savedCameras: Readonly<Record<string, CameraTransform>>;
|
|
1087
|
+
/** Camera at the instant the transition started: where to return if it fails or is cancelled. */
|
|
1088
|
+
returnCamera: CameraTransform | null;
|
|
1089
|
+
/** `key` of the layer whose load failed, until the next `DRILL`. Feeds `onDrillError`. */
|
|
1090
|
+
error: string | null;
|
|
1091
|
+
};
|
|
1092
|
+
type DrillMachineEvent = {
|
|
1093
|
+
type: "DRILL";
|
|
1094
|
+
to: readonly string[];
|
|
1095
|
+
/** Only `"jump"` is honored (no animation); the rest is deduced from `settledPath` → `to`. */
|
|
1096
|
+
direction?: DrillDirection;
|
|
1097
|
+
/** The current camera: saved when going down, so that going up returns to it. */
|
|
1098
|
+
camera?: CameraTransform;
|
|
1099
|
+
/** The new layer's geometry is already cached: there is nothing to wait for. */
|
|
1100
|
+
cached?: boolean;
|
|
1101
|
+
} | {
|
|
1102
|
+
type: "FLIGHT_END";
|
|
1103
|
+
id: number;
|
|
1104
|
+
completed: boolean;
|
|
1105
|
+
} | {
|
|
1106
|
+
type: "GEOMETRY_READY";
|
|
1107
|
+
key: string;
|
|
1108
|
+
} | {
|
|
1109
|
+
type: "GEOMETRY_ERROR";
|
|
1110
|
+
key: string;
|
|
1111
|
+
} | {
|
|
1112
|
+
type: "REVEAL_END";
|
|
1113
|
+
};
|
|
1114
|
+
/**
|
|
1115
|
+
* Key of the layer at depth `depth` under `path`: the JSON of `path.slice(0, depth)`. With JSON there
|
|
1116
|
+
* are no collisions even if an id contains `/` or `.`.
|
|
1117
|
+
*/
|
|
1118
|
+
declare const layerKey: (path: readonly string[], depth?: number) => string;
|
|
1119
|
+
/**
|
|
1120
|
+
* How to get from `from` to `to`: `"equal"` if they are the same; `"down"` if `to` is `from` + one id;
|
|
1121
|
+
* `"up"` if `to` is an ancestor (any number of steps); `"lateral"` if only the last id changes;
|
|
1122
|
+
* `"jump"` in any other case (several levels down, another branch…).
|
|
1123
|
+
*/
|
|
1124
|
+
declare const classifyDrill: (from: readonly string[], to: readonly string[]) => DrillDirection | "equal";
|
|
1125
|
+
/** Resting state at `path` (the world by default). Mounting with a route from the URL does not animate. */
|
|
1126
|
+
declare const createDrillState: (path?: readonly string[]) => DrillMachineState;
|
|
1127
|
+
/** The camera to return to when going up to `path` (the one saved when going down from there), if any. */
|
|
1128
|
+
declare const savedCameraFor: (state: DrillMachineState, path: readonly string[]) => CameraTransform | null;
|
|
1129
|
+
declare const drillReducer: (state: DrillMachineState, event: DrillMachineEvent) => DrillMachineState;
|
|
1130
|
+
|
|
1131
|
+
/**
|
|
1132
|
+
* Pure drill-down pieces that depend on neither React nor the DOM: a route's
|
|
1133
|
+
* context, the breadcrumbs, the geometry cache key, the LRU cache and level validation.
|
|
1134
|
+
*/
|
|
1135
|
+
type DrillContext = {
|
|
1136
|
+
/** Ids from the root: [] = world, ["ES"] = Spain, ["ES", "ES-MD"] = Madrid. */
|
|
1137
|
+
path: readonly string[];
|
|
1138
|
+
/** `path.at(-1) ?? null`. */
|
|
1139
|
+
parentId: string | null;
|
|
1140
|
+
/** `path.length`. */
|
|
1141
|
+
depth: number;
|
|
1142
|
+
};
|
|
1143
|
+
declare const contextOf: (path: readonly string[]) => DrillContext;
|
|
1144
|
+
/** The minimum needed from a level for these functions (so they can be tested without React). */
|
|
1145
|
+
type LevelShape = {
|
|
1146
|
+
id: string;
|
|
1147
|
+
label?: string;
|
|
1148
|
+
/** Only whether it is `"inherit"` or a function matters; the rest is the geometry itself. */
|
|
1149
|
+
geography: unknown;
|
|
1150
|
+
geographyKey?: (ctx: DrillContext) => string;
|
|
1151
|
+
};
|
|
1152
|
+
/**
|
|
1153
|
+
* Validates the list of levels when it is defined. Errors explain what to change (DX principle from
|
|
1154
|
+
* the design): an empty list, repeated ids (the level id is used as a key) or a first level that
|
|
1155
|
+
* "inherits" geometry from a parent that does not exist.
|
|
1156
|
+
*/
|
|
1157
|
+
declare const validateLevels: (levels: readonly LevelShape[]) => void;
|
|
1158
|
+
/**
|
|
1159
|
+
* Trims a route to the depth the levels allow: with N levels, the longest path has N − 1 ids (the
|
|
1160
|
+
* last level cannot be drilled into any further). `clamped` signals that trimming happened.
|
|
1161
|
+
*/
|
|
1162
|
+
declare const clampPath: (path: readonly string[], levelCount: number) => {
|
|
1163
|
+
path: readonly string[];
|
|
1164
|
+
clamped: boolean;
|
|
1165
|
+
};
|
|
1166
|
+
type DrillCrumb = {
|
|
1167
|
+
depth: number;
|
|
1168
|
+
/** `null` at the root. */
|
|
1169
|
+
id: string | null;
|
|
1170
|
+
label: string;
|
|
1171
|
+
};
|
|
1172
|
+
/**
|
|
1173
|
+
* Breadcrumbs of the route: the root (with the first level's label or `rootLabel`) and one step per id.
|
|
1174
|
+
* `getLabel` names each step (the id by default).
|
|
1175
|
+
*/
|
|
1176
|
+
declare const buildBreadcrumbs: ({ path, rootLabel, getLabel, }: {
|
|
1177
|
+
path: readonly string[];
|
|
1178
|
+
rootLabel: string;
|
|
1179
|
+
getLabel?: (args: {
|
|
1180
|
+
id: string;
|
|
1181
|
+
depth: number;
|
|
1182
|
+
}) => string;
|
|
1183
|
+
}) => DrillCrumb[];
|
|
1184
|
+
/**
|
|
1185
|
+
* Cache key of the geometry of the level at `path.length`: the consumer's
|
|
1186
|
+
* (`geographyKey`), or `${id}:${parentId}` if the geometry depends on the parent (it is a function),
|
|
1187
|
+
* or just the id. With `"inherit"` the geometry is the parent's, so the key is too (stores over a
|
|
1188
|
+
* country share the country's geometry).
|
|
1189
|
+
*/
|
|
1190
|
+
declare const geographyKeyFor: (levels: readonly LevelShape[], path: readonly string[]) => string;
|
|
1191
|
+
/**
|
|
1192
|
+
* `data` as a list or as a selector for the current level. `undefined` means "loading" and is
|
|
1193
|
+
* respected: the map keeps the previous layer, with no warnings.
|
|
1194
|
+
*/
|
|
1195
|
+
declare const resolveDrillData: <TDatum>(data: readonly TDatum[] | undefined | ((ctx: DrillContext) => readonly TDatum[] | undefined), ctx: DrillContext) => readonly TDatum[] | undefined;
|
|
1196
|
+
/** Which drill event is a route change; `null` if it does not change. */
|
|
1197
|
+
declare const drillDirectionBetween: (from: readonly string[], to: readonly string[]) => DrillDirection | null;
|
|
1198
|
+
type LruCache<V> = {
|
|
1199
|
+
/** Returns the value and marks it as the most recent. */
|
|
1200
|
+
get: (key: string) => V | undefined;
|
|
1201
|
+
/** Does not mark it as recent (to check without altering the order). */
|
|
1202
|
+
has: (key: string) => boolean;
|
|
1203
|
+
/** Inserts; if capacity is exceeded, evicts the least recent. */
|
|
1204
|
+
set: (key: string, value: V) => void;
|
|
1205
|
+
delete: (key: string) => void;
|
|
1206
|
+
clear: () => void;
|
|
1207
|
+
readonly size: number;
|
|
1208
|
+
};
|
|
1209
|
+
/** Cache of the levels already computed (8 entries). Going up recomputes nothing. */
|
|
1210
|
+
declare const DEFAULT_LEVEL_CACHE_SIZE = 8;
|
|
1211
|
+
declare const createLruCache: <V>(capacity?: number) => LruCache<V>;
|
|
1212
|
+
|
|
1213
|
+
/** What is drawn at each depth: the (already filtered) features and how they are identified. */
|
|
1214
|
+
type ChainLevel = {
|
|
1215
|
+
features: readonly Feature[];
|
|
1216
|
+
idOf: (feature: Feature) => string | undefined;
|
|
1217
|
+
};
|
|
1218
|
+
type BuildProjectionChainParams = {
|
|
1219
|
+
/** `levels[d]` = what is shown at depth `d`. It has `path.length + 1` elements. */
|
|
1220
|
+
levels: readonly ChainLevel[];
|
|
1221
|
+
/** Ids from the root: `path[d]` is the region of `levels[d]` that is entered. */
|
|
1222
|
+
path: readonly string[];
|
|
1223
|
+
name?: ProjectionName;
|
|
1224
|
+
width: number;
|
|
1225
|
+
height: number;
|
|
1226
|
+
padding?: number | Padding;
|
|
1227
|
+
/** Explicit framings by id in lon/lat (Spain with the Canary Islands). Overrides the main body. */
|
|
1228
|
+
focusBounds?: Readonly<Record<string, LonLatBounds | null | undefined>>;
|
|
1229
|
+
};
|
|
1230
|
+
type ProjectionChain = {
|
|
1231
|
+
/** `projections[d]` frames region `path[d − 1]` within `projections[d − 1]` (0 being the world). */
|
|
1232
|
+
projections: GeoProjection[];
|
|
1233
|
+
/**
|
|
1234
|
+
* Id of the region that could not be framed (it is not in the parent level, or has no extent).
|
|
1235
|
+
* From there on, the projections repeat the last valid one, so the map does not break.
|
|
1236
|
+
*/
|
|
1237
|
+
missing: string | null;
|
|
1238
|
+
};
|
|
1239
|
+
/**
|
|
1240
|
+
* The projection chain for the whole route: the root is fitted to the world and each
|
|
1241
|
+
* level is its parent's with `scale`/`translate` readjusted to the chosen region's main body
|
|
1242
|
+
* (`rebaseProjection`). A pure function of (levels, route, size, padding). On resize it is
|
|
1243
|
+
* recomputed entirely: one `fitExtent` per level.
|
|
1244
|
+
*/
|
|
1245
|
+
declare const buildProjectionChain: ({ levels, path, name, width, height, padding, focusBounds, }: BuildProjectionChainParams) => ProjectionChain;
|
|
1246
|
+
|
|
1247
|
+
export { INERTIA_TAU_MS as $, DEFAULT_LEVEL_CACHE_SIZE as A, type BoundsToTransformParams as B, CATEGORICAL_PALETTES as C, DEFAULT_ASPECT_RATIO as D, DEFAULT_MAX_RADIUS as E, DEFAULT_PADDING as F, DEFAULT_PROJECTION as G, DEFAULT_ZOOM_MAX as H, DEFAULT_ZOOM_MIN as I, DIVERGING_PALETTES as J, type DivergingPaletteName as K, type DrillContext as L, type DrillCrumb as M, type DrillDirection as N, type DrillLayer as O, type DrillLayerRole as P, type DrillMachineEvent as Q, type DrillMachineState as R, type DrillPhase as S, type Extent as T, FIT_TRANSFORM as U, type FlightDurationParams as V, type GeographyData as W, type GeographyInput as X, type GetFocusExtentParams as Y, type GetFocusGeometryParams as Z, INERTIA_MIN_VELOCITY as _, type Bubble as a, createLruCache as a$, INERTIA_WINDOW_MS as a0, type InertiaOffsetParams as a1, type JoinDataOptions as a2, type JoinResult as a3, type LegendItem as a4, type LevelShape as a5, type LonLatBounds as a6, type LruCache as a7, MAX_FLIGHT_MS as a8, MIN_FLIGHT_MS as a9, type ViewLevel as aA, type ViewState as aB, type ZoomControlsOptions as aC, type ZoomEventLike as aD, type ZoomFilterParams as aE, type ZoomKeyAction as aF, type ZoomOptions as aG, type ZoomTarget as aH, applyTransform as aI, boundsToTransform as aJ, bubbleScale as aK, buildBreadcrumbs as aL, buildCategoricalLegend as aM, buildLegend as aN, buildProjectionChain as aO, buildSizeLegend as aP, clampPath as aQ, classIndex as aR, classifyDrill as aS, computeAspectRatio as aT, computeThresholds as aU, constrainAxis as aV, constrainTransform as aW, contextOf as aX, createCategoricalScale as aY, createColorScale as aZ, createDrillState as a_, type Padding as aa, type Point as ab, type PreserveViewParams as ac, type ProjectionChain as ad, type ProjectionName as ae, type RadiusScale as af, type RebaseParams as ag, type RebaseResult as ah, type ReleaseVelocityParams as ai, type ResolveVariantOptions as aj, type ResolveZoomTargetParams as ak, type ResolvedCategory as al, type ResolvedZoom as am, SEQUENTIAL_PALETTES as an, type SequentialPaletteName as ao, type Size as ap, type SizeLegendItem as aq, THEMES as ar, type ThemeName as as, type ToFeatureCollectionOptions as at, type ToViewStateParams as au, type TransitionResult as av, type VariantConfig as aw, type VariantEntry as ax, type VariantName as ay, type VariantResult as az, type BubbleScaling as b, createProjection as b0, createRadiusScale as b1, createZoomFilter as b2, createZoomInterpolator as b3, defaultFormatValue as b4, defineTheme as b5, drillDirectionBetween as b6, drillReducer as b7, en as b8, es as b9, resolveZoomTarget as bA, savedCameraFor as bB, themeToCssVars as bC, toFeatureCollection as bD, toViewState as bE, transformToZoomView as bF, unwrapGeography as bG, validateLevels as bH, zoomKeyAction as bI, extendTheme as ba, finiteValues as bb, flightDuration as bc, geographyKeyFor as bd, getAnchorPoint as be, getFocusExtent as bf, getFocusGeometry as bg, inertiaOffset as bh, invertTransform as bi, isFitTransform as bj, isThemeName as bk, joinData as bl, layerKey as bm, normalizePadding as bn, preserveView as bo, projectBounds as bp, projectionFactory as bq, rebaseProjection as br, relativeTransform as bs, releaseVelocity as bt, resolveControls as bu, resolveDrillData as bv, resolveLabels as bw, resolveTheme as bx, resolveVariant as by, resolveZoomOptions as bz, type BuildLegendOptions as c, type BuildProjectionChainParams as d, type CameraOptions as e, type CameraSample as f, type CameraTransform as g, type CategoricalPaletteName as h, type CategoricalScale as i, type CategorySpec as j, type ChainLevel as k, type ChoroplethLabels as l, type ChoroplethTheme as m, type Classification as n, type ColorScale as o, type ColorScaleOptions as p, type ConstrainAxisParams as q, type ConstrainTransformParams as r, type Corner as s, type CreateCategoricalScaleOptions as t, type CreateProjectionOptions as u, DEFAULT_BUBBLE_FILL as v, DEFAULT_CLASSES as w, DEFAULT_EMPTY_FILL as x, DEFAULT_FLIGHT_CURVATURE as y, DEFAULT_FOCUS_BOUNDS as z };
|