@cfasim-ui/charts 0.7.8 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/BarChart/BarChart.d.ts +9 -26
- package/dist/ChartMenu/ChartMenu.d.ts +3 -2
- package/dist/ChartTooltip/ChartTooltip.d.ts +9 -12
- package/dist/ChoroplethMap/ChoroplethMap.d.ts +42 -190
- package/dist/ChoroplethMap/ChoroplethTooltip.d.ts +20 -27
- package/dist/ChoroplethMap/canvasLayer.d.ts +23 -6
- package/dist/ChoroplethMap/cityLayout.d.ts +3 -2
- package/dist/ChoroplethMap/mixedGeo.d.ts +74 -0
- package/dist/ChoroplethMap/mixedGeo.test.d.ts +1 -0
- package/dist/DataTable/DataTable.d.ts +3 -2
- package/dist/LineChart/LineChart.d.ts +9 -26
- package/dist/_shared/ChartAnnotations.d.ts +3 -2
- package/dist/_shared/ChartZoomControls.d.ts +3 -2
- package/dist/_shared/index.d.ts +2 -1
- package/dist/_shared/mapTheme.d.ts +159 -0
- package/dist/_shared/mapTheme.test.d.ts +1 -0
- package/dist/index.css +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1821 -1462
- package/docs/BarChart.md +776 -0
- package/docs/ChoroplethMap.md +1394 -0
- package/docs/DataTable.md +386 -0
- package/docs/LineChart.md +1267 -0
- package/docs/index.json +56 -0
- package/package.json +25 -21
- package/src/BarChart/BarChart.md +743 -0
- package/src/BarChart/BarChart.vue +1901 -0
- package/src/ChartMenu/ChartMenu.vue +220 -0
- package/src/ChartMenu/download.ts +120 -0
- package/src/ChartTooltip/ChartTooltip.vue +97 -0
- package/src/ChoroplethMap/ChoroplethMap.md +1354 -0
- package/src/ChoroplethMap/ChoroplethMap.vue +3778 -0
- package/src/ChoroplethMap/ChoroplethTooltip.vue +103 -0
- package/src/ChoroplethMap/canvasLayer.ts +373 -0
- package/src/ChoroplethMap/cityLayout.ts +262 -0
- package/src/ChoroplethMap/hsaMapping.ts +4116 -0
- package/src/ChoroplethMap/mixedGeo.ts +201 -0
- package/src/DataTable/DataTable.md +372 -0
- package/src/DataTable/DataTable.vue +406 -0
- package/src/LineChart/LineChart.md +1225 -0
- package/src/LineChart/LineChart.vue +1555 -0
- package/src/_shared/ChartAnnotations.vue +420 -0
- package/src/_shared/ChartZoomControls.vue +138 -0
- package/src/_shared/annotations.ts +106 -0
- package/src/_shared/axes.ts +69 -0
- package/src/_shared/chartProps.ts +201 -0
- package/src/_shared/computeTicks.ts +42 -0
- package/src/_shared/contrast.ts +100 -0
- package/src/_shared/dateAxis.ts +501 -0
- package/src/_shared/index.ts +78 -0
- package/src/_shared/mapTheme.ts +551 -0
- package/src/_shared/scale.ts +86 -0
- package/src/_shared/seriesCsv.ts +68 -0
- package/src/_shared/touch.ts +8 -0
- package/src/_shared/useChartFoundation.ts +175 -0
- package/src/_shared/useChartFullscreen.ts +254 -0
- package/src/_shared/useChartMenu.ts +111 -0
- package/src/_shared/useChartPadding.ts +235 -0
- package/src/_shared/useChartSize.ts +58 -0
- package/src/_shared/useChartTooltip.ts +205 -0
- package/src/env.d.ts +4 -0
- package/src/hsa-mapping.ts +1 -0
- package/src/index.ts +41 -0
- package/src/tooltip-position.ts +55 -0
- package/src/us-cities/data.ts +1371 -0
- package/src/us-cities/index.ts +122 -0
- package/src/us-cities.ts +7 -0
|
@@ -0,0 +1,1394 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed, ref } from "vue";
|
|
3
|
+
import countiesTopoForPerf from "us-atlas/counties-10m.json";
|
|
4
|
+
import { fipsToHsa } from "@cfasim-ui/charts/hsa-mapping";
|
|
5
|
+
import { nationalCityMarkers, stateCityMarkers } from "@cfasim-ui/charts/us-cities";
|
|
6
|
+
|
|
7
|
+
// Capital + 100 most-populous US cities for the national demo, and
|
|
8
|
+
// California's capital + top cities for the single-state demo.
|
|
9
|
+
const nationalCities = nationalCityMarkers();
|
|
10
|
+
const californiaCities = stateCityMarkers("06");
|
|
11
|
+
|
|
12
|
+
// Build one row per county (~3,143) with a deterministic-ish value so the
|
|
13
|
+
// perf example can render every region with a custom tooltip.
|
|
14
|
+
const denseCountyData = computed(() => {
|
|
15
|
+
const geoms = countiesTopoForPerf.objects.counties.geometries;
|
|
16
|
+
return geoms.map((g, i) => ({
|
|
17
|
+
id: String(g.id).padStart(5, "0"),
|
|
18
|
+
value: (i * 37) % 100,
|
|
19
|
+
}));
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// Focus demo state — bound directly to v-model:focus. The component
|
|
23
|
+
// handles click-to-toggle and emits null when the focused feature is
|
|
24
|
+
// re-clicked.
|
|
25
|
+
const focused = ref(null);
|
|
26
|
+
|
|
27
|
+
// "Outline a focused feature's parent" demo: focus is a county id;
|
|
28
|
+
// we derive the parent HSA and add it to the focus array as a dashed
|
|
29
|
+
// overlay so clicking a county also outlines its HSA.
|
|
30
|
+
// "Mixing levels" demo: county data everywhere except California and Texas,
|
|
31
|
+
// which report one whole-state estimate each (`geoType: "states"`).
|
|
32
|
+
const MERGED_STATES = ["06", "48"];
|
|
33
|
+
const mixedLevelData = computed(() => [
|
|
34
|
+
...denseCountyData.value.filter(
|
|
35
|
+
(d) => !MERGED_STATES.includes(d.id.slice(0, 2)),
|
|
36
|
+
),
|
|
37
|
+
{ id: "06", value: 85, geoType: "states" },
|
|
38
|
+
{ id: "48", value: 15, geoType: "states" },
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
// The other direction: a state map where New York alone is broken out into
|
|
42
|
+
// its counties.
|
|
43
|
+
const splitStateData = computed(() => [
|
|
44
|
+
{ id: "06", value: 70 },
|
|
45
|
+
{ id: "48", value: 30 },
|
|
46
|
+
{ id: "12", value: 55 },
|
|
47
|
+
...denseCountyData.value
|
|
48
|
+
.filter((d) => d.id.slice(0, 2) === "36")
|
|
49
|
+
.map((d) => ({ ...d, geoType: "counties" })),
|
|
50
|
+
]);
|
|
51
|
+
|
|
52
|
+
const focusedCounty = ref(null);
|
|
53
|
+
const parentFocus = computed(() => {
|
|
54
|
+
const fips = focusedCounty.value;
|
|
55
|
+
if (!fips) return null;
|
|
56
|
+
const hsa = fipsToHsa[fips];
|
|
57
|
+
return hsa
|
|
58
|
+
? [fips, { id: hsa, geoType: "hsas", style: "dashed", stroke: "#666" }]
|
|
59
|
+
: fips;
|
|
60
|
+
});
|
|
61
|
+
</script>
|
|
62
|
+
|
|
63
|
+
# ChoroplethMap
|
|
64
|
+
|
|
65
|
+
A US choropleth map using D3's Albers USA projection, which repositions Alaska and Hawaii to the bottom left. Supports state-level, county-level, and HSA-level (Health Service Areas) rendering via the `geoType` prop.
|
|
66
|
+
|
|
67
|
+
You must provide your own TopoJSON topology data via the `topology` prop. We recommend the [`us-atlas`](https://github.com/topojson/us-atlas) package:
|
|
68
|
+
|
|
69
|
+
```sh
|
|
70
|
+
npm install us-atlas
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
- **State-level maps**: use `us-atlas/states-10m.json`
|
|
74
|
+
- **County or HSA maps**: use `us-atlas/counties-10m.json` (includes both county and state boundaries)
|
|
75
|
+
|
|
76
|
+
```vue
|
|
77
|
+
<script setup>
|
|
78
|
+
import { ChoroplethMap } from "@cfasim-ui/charts";
|
|
79
|
+
import statesTopo from "us-atlas/states-10m.json";
|
|
80
|
+
import countiesTopo from "us-atlas/counties-10m.json";
|
|
81
|
+
</script>
|
|
82
|
+
|
|
83
|
+
<!-- State map -->
|
|
84
|
+
<ChoroplethMap :topology="statesTopo" :data="stateData" />
|
|
85
|
+
|
|
86
|
+
<!-- County map -->
|
|
87
|
+
<ChoroplethMap
|
|
88
|
+
:topology="countiesTopo"
|
|
89
|
+
geo-type="counties"
|
|
90
|
+
:data="countyData"
|
|
91
|
+
/>
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Examples
|
|
95
|
+
|
|
96
|
+
### Basic with state data
|
|
97
|
+
|
|
98
|
+
<ComponentDemo>
|
|
99
|
+
<ChoroplethMap
|
|
100
|
+
:topology="statesTopo"
|
|
101
|
+
:data="[
|
|
102
|
+
{ id: '06', value: 100 },
|
|
103
|
+
{ id: '36', value: 80 },
|
|
104
|
+
{ id: '48', value: 90 },
|
|
105
|
+
{ id: '12', value: 70 },
|
|
106
|
+
{ id: '17', value: 60 },
|
|
107
|
+
{ id: '37', value: 50 },
|
|
108
|
+
{ id: '42', value: 55 },
|
|
109
|
+
{ id: '39', value: 45 },
|
|
110
|
+
{ id: '13', value: 40 },
|
|
111
|
+
{ id: '26', value: 35 },
|
|
112
|
+
]"
|
|
113
|
+
title="Cases by State"
|
|
114
|
+
:legend-title="'Cases'"
|
|
115
|
+
:height="400"
|
|
116
|
+
/>
|
|
117
|
+
|
|
118
|
+
<template #code>
|
|
119
|
+
|
|
120
|
+
```vue
|
|
121
|
+
<script setup>
|
|
122
|
+
import statesTopo from "us-atlas/states-10m.json";
|
|
123
|
+
</script>
|
|
124
|
+
|
|
125
|
+
<ChoroplethMap
|
|
126
|
+
:topology="statesTopo"
|
|
127
|
+
:data="[
|
|
128
|
+
{ id: '06', value: 100 },
|
|
129
|
+
{ id: '36', value: 80 },
|
|
130
|
+
{ id: '48', value: 90 },
|
|
131
|
+
{ id: '12', value: 70 },
|
|
132
|
+
{ id: '17', value: 60 },
|
|
133
|
+
]"
|
|
134
|
+
title="Cases by State"
|
|
135
|
+
:legend-title="'Cases'"
|
|
136
|
+
:height="400"
|
|
137
|
+
/>
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
</template>
|
|
141
|
+
</ComponentDemo>
|
|
142
|
+
|
|
143
|
+
### Custom color scale
|
|
144
|
+
|
|
145
|
+
<ComponentDemo>
|
|
146
|
+
<ChoroplethMap
|
|
147
|
+
:topology="statesTopo"
|
|
148
|
+
:data="[
|
|
149
|
+
{ id: 'California', value: 100 },
|
|
150
|
+
{ id: 'Texas', value: 85 },
|
|
151
|
+
{ id: 'Florida', value: 70 },
|
|
152
|
+
{ id: 'New York', value: 90 },
|
|
153
|
+
{ id: 'Pennsylvania', value: 50 },
|
|
154
|
+
{ id: 'Illinois', value: 60 },
|
|
155
|
+
{ id: 'Ohio', value: 40 },
|
|
156
|
+
{ id: 'Georgia', value: 55 },
|
|
157
|
+
{ id: 'North Carolina', value: 45 },
|
|
158
|
+
{ id: 'Michigan', value: 35 },
|
|
159
|
+
]"
|
|
160
|
+
:color-scale="{ min: '#fff5f0', max: '#a50f15' }"
|
|
161
|
+
:legend-title="'Severity'"
|
|
162
|
+
:height="400"
|
|
163
|
+
/>
|
|
164
|
+
|
|
165
|
+
<template #code>
|
|
166
|
+
|
|
167
|
+
```vue
|
|
168
|
+
<ChoroplethMap
|
|
169
|
+
:topology="statesTopo"
|
|
170
|
+
:data="[
|
|
171
|
+
{ id: 'California', value: 100 },
|
|
172
|
+
{ id: 'Texas', value: 85 },
|
|
173
|
+
{ id: 'Florida', value: 70 },
|
|
174
|
+
{ id: 'New York', value: 90 },
|
|
175
|
+
]"
|
|
176
|
+
:color-scale="{ min: '#fff5f0', max: '#a50f15' }"
|
|
177
|
+
:legend-title="'Severity'"
|
|
178
|
+
:height="400"
|
|
179
|
+
/>
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
</template>
|
|
183
|
+
</ComponentDemo>
|
|
184
|
+
|
|
185
|
+
### Theming (`theme`)
|
|
186
|
+
|
|
187
|
+
All map paint styling lives in one `theme` prop: the base `fill` for features without data, the feature `stroke` (+ `strokeWidth`), the state-`borders` mesh over county/HSA maps (+ `bordersWidth`), an exterior `outline` (+ `outlineWidth`), a `background` wash, and the hover/focus `highlight`. Every color accepts any CSS color the page can express, including `var()`, `light-dark()`, and `color-mix()`, resolved against the map container's cascade. When the effective values change (a site light/dark toggle, an OS scheme flip, a custom-property redefinition), the map repaints on its own — in both the SVG and canvas renderers. `colorScale` colors resolve the same way, so theme tokens work there too.
|
|
188
|
+
|
|
189
|
+
The `outline` is the exterior boundary of the rendered geography — the national outline, or the selected state's boundary in single-state mode — drawn on top of interior borders with its own color and width. It's off by default and turns on when a visible color resolves.
|
|
190
|
+
|
|
191
|
+
<ComponentDemo>
|
|
192
|
+
<ChoroplethMap
|
|
193
|
+
:topology="statesTopo"
|
|
194
|
+
:data="[
|
|
195
|
+
{ id: 'California', value: 100 },
|
|
196
|
+
{ id: 'Texas', value: 85 },
|
|
197
|
+
{ id: 'Florida', value: 70 },
|
|
198
|
+
{ id: 'New York', value: 90 },
|
|
199
|
+
{ id: 'Illinois', value: 60 },
|
|
200
|
+
{ id: 'Ohio', value: 40 },
|
|
201
|
+
]"
|
|
202
|
+
:theme="{
|
|
203
|
+
fill: 'light-dark(#e2e8f0, #334155)',
|
|
204
|
+
stroke: 'light-dark(#f8fafc, #0f172a)',
|
|
205
|
+
outline: 'light-dark(#334155, #cbd5e1)',
|
|
206
|
+
outlineWidth: 1.5,
|
|
207
|
+
}"
|
|
208
|
+
title="Themed map with an exterior outline"
|
|
209
|
+
:legend-title="'Cases'"
|
|
210
|
+
:height="400"
|
|
211
|
+
/>
|
|
212
|
+
|
|
213
|
+
<template #code>
|
|
214
|
+
|
|
215
|
+
```vue
|
|
216
|
+
<ChoroplethMap
|
|
217
|
+
:topology="statesTopo"
|
|
218
|
+
:data="stateData"
|
|
219
|
+
:theme="{
|
|
220
|
+
fill: 'light-dark(#e2e8f0, #334155)',
|
|
221
|
+
stroke: 'light-dark(#f8fafc, #0f172a)',
|
|
222
|
+
outline: 'light-dark(#334155, #cbd5e1)',
|
|
223
|
+
outlineWidth: 1.5,
|
|
224
|
+
}"
|
|
225
|
+
title="Themed map with an exterior outline"
|
|
226
|
+
:legend-title="'Cases'"
|
|
227
|
+
:height="400"
|
|
228
|
+
/>
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
</template>
|
|
232
|
+
</ComponentDemo>
|
|
233
|
+
|
|
234
|
+
Every channel's default routes through a `--choropleth-*` custom property, so a stylesheet alone can theme every map on a page — JS `theme` values always win:
|
|
235
|
+
|
|
236
|
+
```css
|
|
237
|
+
:root {
|
|
238
|
+
color-scheme: light dark; /* required for light-dark() to engage */
|
|
239
|
+
--choropleth-fill: light-dark(#dbe4d7, #24332a);
|
|
240
|
+
--choropleth-stroke: light-dark(#f4f7f2, #131e17);
|
|
241
|
+
--choropleth-outline: light-dark(#3f5245, #93ac9b); /* enables the outline */
|
|
242
|
+
--choropleth-background: light-dark(
|
|
243
|
+
#f4f7f2,
|
|
244
|
+
#131e17
|
|
245
|
+
); /* enables a background */
|
|
246
|
+
--choropleth-highlight: light-dark(#000, #fff);
|
|
247
|
+
--choropleth-borders: transparent; /* falls back to the stroke color */
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
| Theme key | Default | Notes |
|
|
252
|
+
| ----------------- | ----------------------------------------------------- | ------------------------------------------------------------------------------------ |
|
|
253
|
+
| `fill` | `var(--choropleth-fill, light-dark(#ddd, #3f3f46))` | Fill for features without a data value |
|
|
254
|
+
| `stroke` | `var(--choropleth-stroke, light-dark(#fff, #18181b))` | Interior feature borders |
|
|
255
|
+
| `strokeWidth` | `0.5` (halved on county/HSA maps) | Explicit values apply as-is on every geoType; `0` disables |
|
|
256
|
+
| `borders` | `var(--choropleth-borders, transparent)` | State mesh over county/HSA maps; falls back to `stroke`; hide with `bordersWidth: 0` |
|
|
257
|
+
| `bordersWidth` | `1` | `0` disables |
|
|
258
|
+
| `outline` | `var(--choropleth-outline, transparent)` | Exterior boundary; off until a visible color resolves |
|
|
259
|
+
| `outlineWidth` | `1` | `0` disables |
|
|
260
|
+
| `background` | `var(--choropleth-background, transparent)` | Wash behind the map; off until a visible color resolves |
|
|
261
|
+
| `highlight` | `var(--choropleth-highlight, light-dark(#000, #fff))` | Hover/focus stroke; per-item `FocusItem.stroke` wins |
|
|
262
|
+
| `markerColor` | `--choropleth-city-marker` / `-label-color` vars | `cities` overlay dot + label color |
|
|
263
|
+
| `markerHalo` | `--choropleth-city-halo` var | Halo around marker dots and labels |
|
|
264
|
+
| `markerHaloWidth` | `0.9` | Dot halo width in CSS px; `0` disables |
|
|
265
|
+
| `markerOpacity` | `1` | Opacity of the whole marker layer |
|
|
266
|
+
|
|
267
|
+
::: warning Breaking change
|
|
268
|
+
`theme` replaces the former `noDataColor`, `strokeColor`, and `strokeWidth` props: use `theme.fill`, `theme.stroke`, and `theme.strokeWidth` instead.
|
|
269
|
+
:::
|
|
270
|
+
|
|
271
|
+
### Threshold color scale
|
|
272
|
+
|
|
273
|
+
Use an array of `ThresholdStop` objects instead of a linear scale. Each stop defines a `min` threshold — values at or above that threshold get the stop's color. The highest matching stop wins.
|
|
274
|
+
|
|
275
|
+
<ComponentDemo>
|
|
276
|
+
<ChoroplethMap
|
|
277
|
+
:topology="statesTopo"
|
|
278
|
+
:data="[
|
|
279
|
+
{ id: 'California', value: 80 },
|
|
280
|
+
{ id: 'Texas', value: 45 },
|
|
281
|
+
{ id: 'Florida', value: 60 },
|
|
282
|
+
{ id: 'New York', value: 25 },
|
|
283
|
+
{ id: 'Pennsylvania', value: 8 },
|
|
284
|
+
{ id: 'Illinois', value: 55 },
|
|
285
|
+
{ id: 'Ohio', value: 30 },
|
|
286
|
+
{ id: 'Georgia', value: 70 },
|
|
287
|
+
{ id: 'North Carolina', value: 15 },
|
|
288
|
+
{ id: 'Michigan', value: 3 },
|
|
289
|
+
]"
|
|
290
|
+
:color-scale="[
|
|
291
|
+
{ min: 0, color: '#fee5d9', label: 'Low' },
|
|
292
|
+
{ min: 10, color: '#fcae91', label: 'Some' },
|
|
293
|
+
{ min: 30, color: '#fb6a4a', label: 'Moderate' },
|
|
294
|
+
{ min: 60, color: '#cb181d', label: 'High' },
|
|
295
|
+
]"
|
|
296
|
+
title="Risk Level"
|
|
297
|
+
:legend-title="'Risk'"
|
|
298
|
+
:height="400"
|
|
299
|
+
/>
|
|
300
|
+
|
|
301
|
+
<template #code>
|
|
302
|
+
|
|
303
|
+
```vue
|
|
304
|
+
<ChoroplethMap
|
|
305
|
+
:topology="statesTopo"
|
|
306
|
+
:data="stateData"
|
|
307
|
+
:color-scale="[
|
|
308
|
+
{ min: 0, color: '#fee5d9', label: 'Low' },
|
|
309
|
+
{ min: 10, color: '#fcae91', label: 'Some' },
|
|
310
|
+
{ min: 30, color: '#fb6a4a', label: 'Moderate' },
|
|
311
|
+
{ min: 60, color: '#cb181d', label: 'High' },
|
|
312
|
+
]"
|
|
313
|
+
title="Risk Level"
|
|
314
|
+
:legend-title="'Risk'"
|
|
315
|
+
:height="400"
|
|
316
|
+
/>
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
</template>
|
|
320
|
+
</ComponentDemo>
|
|
321
|
+
|
|
322
|
+
### Categorical color scale
|
|
323
|
+
|
|
324
|
+
Use an array of `CategoricalStop` objects to map string values to colors. Each stop defines a `value` to match and a `color` to apply.
|
|
325
|
+
|
|
326
|
+
<ComponentDemo>
|
|
327
|
+
<ChoroplethMap
|
|
328
|
+
:topology="statesTopo"
|
|
329
|
+
:data="[
|
|
330
|
+
{ id: 'California', value: 'high' },
|
|
331
|
+
{ id: 'Texas', value: 'medium' },
|
|
332
|
+
{ id: 'Florida', value: 'high' },
|
|
333
|
+
{ id: 'New York', value: 'low' },
|
|
334
|
+
{ id: 'Pennsylvania', value: 'low' },
|
|
335
|
+
{ id: 'Illinois', value: 'medium' },
|
|
336
|
+
{ id: 'Ohio', value: 'low' },
|
|
337
|
+
{ id: 'Georgia', value: 'high' },
|
|
338
|
+
{ id: 'North Carolina', value: 'medium' },
|
|
339
|
+
{ id: 'Michigan', value: 'low' },
|
|
340
|
+
]"
|
|
341
|
+
:color-scale="[
|
|
342
|
+
{ value: 'low', color: '#fee5d9' },
|
|
343
|
+
{ value: 'medium', color: '#fb6a4a' },
|
|
344
|
+
{ value: 'high', color: '#cb181d' },
|
|
345
|
+
]"
|
|
346
|
+
title="Risk Category"
|
|
347
|
+
:legend-title="'Risk'"
|
|
348
|
+
:height="400"
|
|
349
|
+
/>
|
|
350
|
+
|
|
351
|
+
<template #code>
|
|
352
|
+
|
|
353
|
+
```vue
|
|
354
|
+
<ChoroplethMap
|
|
355
|
+
:topology="statesTopo"
|
|
356
|
+
:data="stateData"
|
|
357
|
+
:color-scale="[
|
|
358
|
+
{ value: 'low', color: '#fee5d9' },
|
|
359
|
+
{ value: 'medium', color: '#fb6a4a' },
|
|
360
|
+
{ value: 'high', color: '#cb181d' },
|
|
361
|
+
]"
|
|
362
|
+
title="Risk Category"
|
|
363
|
+
:legend-title="'Risk'"
|
|
364
|
+
:height="400"
|
|
365
|
+
/>
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
</template>
|
|
369
|
+
</ComponentDemo>
|
|
370
|
+
|
|
371
|
+
### Pan and zoom
|
|
372
|
+
|
|
373
|
+
Maps are **fully static by default** — tooltips, hover, click-select, and
|
|
374
|
+
programmatic `focus` zoom all work, but there are no zoom gestures and no
|
|
375
|
+
controls. That's the right mode when clicks mean navigation instead of
|
|
376
|
+
exploration (see the state-map pattern below); a parent driving `focus`
|
|
377
|
+
provides its own way back (e.g. a "Back" button that clears the focus).
|
|
378
|
+
|
|
379
|
+
Add the `zoom` prop to enable the interaction. The map is then **static
|
|
380
|
+
until the first zoom** — clicks and taps only ever select; it's the first
|
|
381
|
+
zoom gesture that switches panning on. The scroll wheel never zooms
|
|
382
|
+
inline, so the map can't hijack page scrolling. A grey hint overlaid on
|
|
383
|
+
the top of the map ("Double click to zoom" / "Double tap to zoom")
|
|
384
|
+
advertises the gesture until it's been used; pass `:zoom-hint="false"` to
|
|
385
|
+
hide it.
|
|
386
|
+
|
|
387
|
+
- **Desktop:** double-click zooms in place (shift+double-click zooms out),
|
|
388
|
+
or press **+** in the always-present **+ / − / reset** control stack in
|
|
389
|
+
the top-left corner. The first zoom — including a programmatic `focus`
|
|
390
|
+
zoom — starts the pan/zoom mode; from then on, drag pans the map. The
|
|
391
|
+
reset button (a counterclockwise arrow) animates back to the full extent
|
|
392
|
+
(clearing any `focus`); − and reset are disabled while the map is at its
|
|
393
|
+
full extent.
|
|
394
|
+
- **Touch:** a **double tap** expands the map to fill the window, zoomed
|
|
395
|
+
in on the tapped point; single taps select features inline. Inside the
|
|
396
|
+
expanded view, one finger pans, a pinch zooms, and a tap selects — its
|
|
397
|
+
tooltip slides up as a bottom sheet; the +/−/reset controls sit top-left
|
|
398
|
+
and a close (✕) button top-right returns to the inline map at full
|
|
399
|
+
extent. A single finger over the inline map still scrolls the page.
|
|
400
|
+
|
|
401
|
+
Filling the window is always an activated state: whether entered via the
|
|
402
|
+
menu's **Fullscreen** item or a double tap, pan/zoom works immediately and
|
|
403
|
+
the controls are present — no double-click needed first. The scroll wheel
|
|
404
|
+
(and trackpad pinch) zooms there too, since page scrolling is locked while
|
|
405
|
+
the map fills the window.
|
|
406
|
+
|
|
407
|
+
Prefer in-place zooming on touch? Set `:touch-expand="false"` — a double
|
|
408
|
+
tap (or a pinch) zooms the inline map on that point instead of expanding
|
|
409
|
+
it. From there one finger pans, a pinch zooms, and taps keep selecting;
|
|
410
|
+
the +/−/reset controls render inline, and reset restores the original
|
|
411
|
+
static, page-scrollable state. Fullscreen is unaffected: that view stays
|
|
412
|
+
continuously interactive and its reset only recenters.
|
|
413
|
+
|
|
414
|
+
#### Full-page mode (`zoom-mode="scroll"`)
|
|
415
|
+
|
|
416
|
+
When the map _is_ the page — a dedicated map route, a dashboard panel, a
|
|
417
|
+
kiosk — the activation step just gets in the way, and there's no page
|
|
418
|
+
scroll to protect. Pair `zoom` with `zoom-mode="scroll"` to make the map
|
|
419
|
+
immediately interactive: the wheel (and trackpad pinch) zooms, dragging
|
|
420
|
+
pans, and touch gestures work inline with no tap-to-expand step. The
|
|
421
|
+
+/−/reset controls are always shown and the hint is suppressed.
|
|
422
|
+
|
|
423
|
+
```vue
|
|
424
|
+
<ChoroplethMap
|
|
425
|
+
:topology="statesTopo"
|
|
426
|
+
:data="stateData"
|
|
427
|
+
zoom
|
|
428
|
+
zoom-mode="scroll"
|
|
429
|
+
/>
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
### County-level map
|
|
433
|
+
|
|
434
|
+
Set `geoType="counties"` to render county-level data using 5-digit FIPS codes. State borders are drawn on top for context. Double-click (or double-tap on touch) to explore — useful for dense county data.
|
|
435
|
+
|
|
436
|
+
This demo also wires up an interactive tooltip (`tooltip-trigger="hover"`):
|
|
437
|
+
hover a county on desktop, or tap one on touch, and it works the same
|
|
438
|
+
before and after zooming in.
|
|
439
|
+
|
|
440
|
+
<ComponentDemo>
|
|
441
|
+
<ChoroplethMap
|
|
442
|
+
:topology="countiesTopo"
|
|
443
|
+
geo-type="counties"
|
|
444
|
+
zoom
|
|
445
|
+
tooltip-trigger="hover"
|
|
446
|
+
:data="[
|
|
447
|
+
{ id: '06037', value: 100 },
|
|
448
|
+
{ id: '06073', value: 80 },
|
|
449
|
+
{ id: '06059', value: 70 },
|
|
450
|
+
{ id: '36061', value: 90 },
|
|
451
|
+
{ id: '36047', value: 75 },
|
|
452
|
+
{ id: '17031', value: 85 },
|
|
453
|
+
{ id: '48201', value: 65 },
|
|
454
|
+
{ id: '04013', value: 60 },
|
|
455
|
+
{ id: '12086', value: 55 },
|
|
456
|
+
{ id: '53033', value: 50 },
|
|
457
|
+
]"
|
|
458
|
+
title="Cases by County"
|
|
459
|
+
:legend-title="'Cases'"
|
|
460
|
+
:height="400"
|
|
461
|
+
/>
|
|
462
|
+
|
|
463
|
+
<template #code>
|
|
464
|
+
|
|
465
|
+
```vue
|
|
466
|
+
<ChoroplethMap
|
|
467
|
+
:topology="countiesTopo"
|
|
468
|
+
geo-type="counties"
|
|
469
|
+
zoom
|
|
470
|
+
tooltip-trigger="hover"
|
|
471
|
+
:data="[
|
|
472
|
+
{ id: '06037', value: 100 },
|
|
473
|
+
{ id: '36061', value: 90 },
|
|
474
|
+
{ id: '17031', value: 85 },
|
|
475
|
+
{ id: '48201', value: 65 },
|
|
476
|
+
{ id: '04013', value: 60 },
|
|
477
|
+
]"
|
|
478
|
+
title="Cases by County"
|
|
479
|
+
:legend-title="'Cases'"
|
|
480
|
+
:height="400"
|
|
481
|
+
/>
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
</template>
|
|
485
|
+
</ComponentDemo>
|
|
486
|
+
|
|
487
|
+
### Tighter national fit (`tight-fit`)
|
|
488
|
+
|
|
489
|
+
The Albers USA projection places Alaska and Hawaii in the bottom-left corner, and by default the whole composite is fit into view — so Alaska's western tail pushes the contiguous US in from the edges. Set `tight-fit` to crop that overhang and let the lower-48 fill more of the frame: Alaska's tail (and Hawaii) clip into the lower-left corner. Pass a number in `0`–`1` (e.g. `:tight-fit="0.5"`) to crop only partway.
|
|
490
|
+
|
|
491
|
+
Works on national state, county, and HSA maps — it's a no-op only in single-state mode. (County features are split by FIPS prefix; HSAs, whose ids are HSA codes rather than FIPS, are identified through the built-in FIPS-to-HSA table.)
|
|
492
|
+
|
|
493
|
+
<ComponentDemo>
|
|
494
|
+
<ChoroplethMap
|
|
495
|
+
:topology="statesTopo"
|
|
496
|
+
tight-fit
|
|
497
|
+
:data="[
|
|
498
|
+
{ id: '06', value: 100 },
|
|
499
|
+
{ id: '36', value: 80 },
|
|
500
|
+
{ id: '48', value: 90 },
|
|
501
|
+
{ id: '12', value: 70 },
|
|
502
|
+
{ id: '17', value: 60 },
|
|
503
|
+
]"
|
|
504
|
+
title="Cases by State (tight fit)"
|
|
505
|
+
:legend-title="'Cases'"
|
|
506
|
+
:height="400"
|
|
507
|
+
/>
|
|
508
|
+
|
|
509
|
+
<template #code>
|
|
510
|
+
|
|
511
|
+
```vue
|
|
512
|
+
<!-- Crop Alaska's overhang so the lower-48 fills the frame -->
|
|
513
|
+
<ChoroplethMap :topology="statesTopo" tight-fit :data="data" />
|
|
514
|
+
|
|
515
|
+
<!-- ...or crop only partway -->
|
|
516
|
+
<ChoroplethMap :topology="statesTopo" :tight-fit="0.5" :data="data" />
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
</template>
|
|
520
|
+
</ComponentDemo>
|
|
521
|
+
|
|
522
|
+
### City markers (`cities`)
|
|
523
|
+
|
|
524
|
+
Pass a `cities` array to overlay decorative point markers with name labels. Each entry is `{ name, coordinates: [lng, lat], capital?, minZoom? }`. Every city is a dot; `capital` cities are labeled first and never dropped, while any other label that can't be placed without overlapping is dropped (its dot stays), so labels never collide. The overlay is non-interactive — the choropleth's own hover/click is unaffected — and the markers pan/zoom with the map while staying a constant on-screen size. It works with both the SVG and canvas (`renderer`) backends.
|
|
525
|
+
|
|
526
|
+
**Level-of-detail:** on a zoomable map (`zoom`), each city has a `minZoom` and shows only once the map is zoomed to `scaleK >= minZoom`, so you can reveal the biggest cities first and progressively add more as the user zooms in. `nationalCityMarkers()` / `stateCityMarkers()` assign these tiers by population automatically (the capital always shows). A city without its own `minZoom` falls back to the `cities-min-zoom` prop (default `2`) — set that to `1` for a flat "always visible" layer, or pass `{ tiered: false }` to the selectors. **The overview below shows the biggest cities; double-click to zoom in and reveal more:**
|
|
527
|
+
|
|
528
|
+
<ComponentDemo>
|
|
529
|
+
<ChoroplethMap
|
|
530
|
+
:topology="statesTopo"
|
|
531
|
+
:cities="nationalCities"
|
|
532
|
+
zoom
|
|
533
|
+
title="100 most-populous US cities and the capital"
|
|
534
|
+
:legend="false"
|
|
535
|
+
:height="440"
|
|
536
|
+
/>
|
|
537
|
+
|
|
538
|
+
<template #code>
|
|
539
|
+
|
|
540
|
+
```vue
|
|
541
|
+
<script setup>
|
|
542
|
+
import { ChoroplethMap } from "@cfasim-ui/charts";
|
|
543
|
+
import { nationalCityMarkers } from "@cfasim-ui/charts/us-cities";
|
|
544
|
+
import statesTopo from "us-atlas/states-10m.json";
|
|
545
|
+
|
|
546
|
+
// Washington, DC (flagged as the capital) + the 100 most-populous US cities.
|
|
547
|
+
const cities = nationalCityMarkers();
|
|
548
|
+
</script>
|
|
549
|
+
|
|
550
|
+
<!-- zoom in one level to reveal the cities -->
|
|
551
|
+
<ChoroplethMap :topology="statesTopo" :cities="cities" zoom />
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
</template>
|
|
555
|
+
</ComponentDemo>
|
|
556
|
+
|
|
557
|
+
In a single-state map, pass that state's cities so its capital and top cities show:
|
|
558
|
+
|
|
559
|
+
<ComponentDemo>
|
|
560
|
+
<ChoroplethMap
|
|
561
|
+
:topology="countiesTopo"
|
|
562
|
+
state="California"
|
|
563
|
+
geo-type="counties"
|
|
564
|
+
:cities="californiaCities"
|
|
565
|
+
zoom
|
|
566
|
+
:legend="false"
|
|
567
|
+
:height="440"
|
|
568
|
+
/>
|
|
569
|
+
|
|
570
|
+
<template #code>
|
|
571
|
+
|
|
572
|
+
```vue
|
|
573
|
+
<!-- Sacramento (capital) + California's most-populous cities -->
|
|
574
|
+
<ChoroplethMap
|
|
575
|
+
:topology="countiesTopo"
|
|
576
|
+
state="California"
|
|
577
|
+
geo-type="counties"
|
|
578
|
+
:cities="stateCityMarkers('06')"
|
|
579
|
+
zoom
|
|
580
|
+
/>
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
</template>
|
|
584
|
+
</ComponentDemo>
|
|
585
|
+
|
|
586
|
+
#### Bundled US city data (`@cfasim-ui/charts/us-cities`)
|
|
587
|
+
|
|
588
|
+
`nationalCityMarkers()` and `stateCityMarkers()` come from the `@cfasim-ui/charts/us-cities` subpath — a ~5 KB gzipped dataset (Natural Earth, public domain) of the 100 most-populous US cities plus every state capital and DC. It's a separate entry point, so consumers who don't use it never pull it into their bundle.
|
|
589
|
+
|
|
590
|
+
```js
|
|
591
|
+
import {
|
|
592
|
+
usCities,
|
|
593
|
+
nationalCityMarkers,
|
|
594
|
+
stateCityMarkers,
|
|
595
|
+
} from "@cfasim-ui/charts/us-cities";
|
|
596
|
+
|
|
597
|
+
nationalCityMarkers(); //=> DC (capital) + top 100 cities, tiered by population
|
|
598
|
+
nationalCityMarkers({ limit: 25 }); //=> DC + top 25
|
|
599
|
+
nationalCityMarkers({ tiered: false }); //=> flat layer, no per-city minZoom
|
|
600
|
+
stateCityMarkers("48"); //=> Austin (capital) + top Texas cities
|
|
601
|
+
usCities; //=> the raw UsCity[] to build your own selection
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
Only the national capital is flagged on the national map; a state's own capital is flagged in its single-state view. A flagged capital's label is placed first and never dropped for collisions; it carries a `.choropleth-city-label-capital` class if you want to style it, but by default it looks like every other label — the marker is a plain dot too.
|
|
605
|
+
|
|
606
|
+
**Styling:** the default marker style is dark dots and labels with a thin white halo, which reads over any map fill in either color scheme. The `theme` prop's marker keys configure the layer — `markerColor` (dots + labels), `markerHalo` (the halo around both), `markerHaloWidth` (dot halo width in CSS px), and `markerOpacity` (the whole layer). Any CSS color works. A stylesheet can alternatively set the CSS custom properties on `.choropleth-cities` (`--choropleth-city-marker`, `--choropleth-city-label-color`, `--choropleth-city-halo`); theme keys win when both are set.
|
|
607
|
+
|
|
608
|
+
### HSA-level map
|
|
609
|
+
|
|
610
|
+
Set `geoType="hsas"` to render Health Service Area boundaries. HSAs are dissolved from county boundaries using a built-in FIPS-to-HSA mapping. Use 6-digit HSA codes as IDs. State borders are overlaid for context.
|
|
611
|
+
|
|
612
|
+
This demo pairs `tooltip-trigger="hover"` with a custom `#tooltip` slot —
|
|
613
|
+
hover an HSA on desktop, or tap one on touch, before or after zooming in.
|
|
614
|
+
|
|
615
|
+
<ComponentDemo>
|
|
616
|
+
<ChoroplethMap
|
|
617
|
+
:topology="countiesTopo"
|
|
618
|
+
geo-type="hsas"
|
|
619
|
+
zoom
|
|
620
|
+
tooltip-trigger="hover"
|
|
621
|
+
:data="[
|
|
622
|
+
{ id: '010259', value: 100 },
|
|
623
|
+
{ id: '060766', value: 90 },
|
|
624
|
+
{ id: '120159', value: 85 },
|
|
625
|
+
{ id: '090121', value: 70 },
|
|
626
|
+
{ id: '110061', value: 60 },
|
|
627
|
+
{ id: '040765', value: 55 },
|
|
628
|
+
{ id: '080731', value: 50 },
|
|
629
|
+
{ id: '050527', value: 45 },
|
|
630
|
+
{ id: '100075', value: 40 },
|
|
631
|
+
{ id: '020820', value: 35 },
|
|
632
|
+
]"
|
|
633
|
+
title="Cases by HSA"
|
|
634
|
+
:legend-title="'Cases'"
|
|
635
|
+
:height="400"
|
|
636
|
+
>
|
|
637
|
+
<template #tooltip="{ id, name, value }">
|
|
638
|
+
<div style="font-weight: 600">{{ name }}</div>
|
|
639
|
+
<div style="opacity: 0.7; font-size: 0.85em">HSA {{ id }}</div>
|
|
640
|
+
<div v-if="value != null">Cases: {{ value }}</div>
|
|
641
|
+
<div v-else style="opacity: 0.6">No data</div>
|
|
642
|
+
</template>
|
|
643
|
+
</ChoroplethMap>
|
|
644
|
+
|
|
645
|
+
<template #code>
|
|
646
|
+
|
|
647
|
+
```vue
|
|
648
|
+
<ChoroplethMap
|
|
649
|
+
:topology="countiesTopo"
|
|
650
|
+
geo-type="hsas"
|
|
651
|
+
zoom
|
|
652
|
+
tooltip-trigger="hover"
|
|
653
|
+
:data="[
|
|
654
|
+
{ id: '010259', value: 100 },
|
|
655
|
+
{ id: '060766', value: 90 },
|
|
656
|
+
{ id: '120159', value: 85 },
|
|
657
|
+
{ id: '090121', value: 70 },
|
|
658
|
+
{ id: '110061', value: 60 },
|
|
659
|
+
]"
|
|
660
|
+
title="Cases by HSA"
|
|
661
|
+
:legend-title="'Cases'"
|
|
662
|
+
:height="400"
|
|
663
|
+
>
|
|
664
|
+
<template #tooltip="{ id, name, value }">
|
|
665
|
+
<div style="font-weight: 600">{{ name }}</div>
|
|
666
|
+
<div style="opacity: 0.7; font-size: 0.85em">HSA {{ id }}</div>
|
|
667
|
+
<div v-if="value != null">Cases: {{ value }}</div>
|
|
668
|
+
<div v-else style="opacity: 0.6">No data</div>
|
|
669
|
+
</template>
|
|
670
|
+
</ChoroplethMap>
|
|
671
|
+
```
|
|
672
|
+
|
|
673
|
+
</template>
|
|
674
|
+
</ComponentDemo>
|
|
675
|
+
|
|
676
|
+
### Single-state map (`state`)
|
|
677
|
+
|
|
678
|
+
Set the `state` prop to render just one state's outline with its `counties` or
|
|
679
|
+
`hsas` inside it — no surrounding states — and the projection zooms to fit it.
|
|
680
|
+
Accepts a state **name** (`"California"`) or a 2-digit **FIPS code** (`"06"`).
|
|
681
|
+
This needs a counties topology (the same `us-atlas/counties-10m.json`). `data`
|
|
682
|
+
can stay national; only features inside the selected state are drawn and
|
|
683
|
+
colored.
|
|
684
|
+
|
|
685
|
+
This demo also opts for the quieter touch flow: `:touch-expand="false"` makes
|
|
686
|
+
a double tap (or pinch) zoom in place instead of expanding to fill the
|
|
687
|
+
window, and `:zoom-hint="false"` drops the grey gesture hint.
|
|
688
|
+
|
|
689
|
+
<ComponentDemo>
|
|
690
|
+
<ChoroplethMap
|
|
691
|
+
:topology="countiesTopo"
|
|
692
|
+
geo-type="counties"
|
|
693
|
+
state="California"
|
|
694
|
+
zoom
|
|
695
|
+
:touch-expand="false"
|
|
696
|
+
:zoom-hint="false"
|
|
697
|
+
:data="[
|
|
698
|
+
{ id: '06037', value: 100 },
|
|
699
|
+
{ id: '06073', value: 80 },
|
|
700
|
+
{ id: '06059', value: 65 },
|
|
701
|
+
{ id: '06065', value: 55 },
|
|
702
|
+
{ id: '06001', value: 45 },
|
|
703
|
+
{ id: '06085', value: 40 },
|
|
704
|
+
]"
|
|
705
|
+
title="California cases by county"
|
|
706
|
+
:legend-title="'Cases'"
|
|
707
|
+
:height="400"
|
|
708
|
+
/>
|
|
709
|
+
|
|
710
|
+
<template #code>
|
|
711
|
+
|
|
712
|
+
```vue
|
|
713
|
+
<ChoroplethMap
|
|
714
|
+
:topology="countiesTopo"
|
|
715
|
+
geo-type="counties"
|
|
716
|
+
state="California"
|
|
717
|
+
zoom
|
|
718
|
+
:touch-expand="false"
|
|
719
|
+
:zoom-hint="false"
|
|
720
|
+
:data="[
|
|
721
|
+
{ id: '06037', value: 100 }, // Los Angeles
|
|
722
|
+
{ id: '06073', value: 80 }, // San Diego
|
|
723
|
+
{ id: '06059', value: 65 }, // Orange
|
|
724
|
+
]"
|
|
725
|
+
title="California cases by county"
|
|
726
|
+
:legend-title="'Cases'"
|
|
727
|
+
:height="400"
|
|
728
|
+
/>
|
|
729
|
+
```
|
|
730
|
+
|
|
731
|
+
</template>
|
|
732
|
+
</ComponentDemo>
|
|
733
|
+
|
|
734
|
+
Switch `geo-type="hsas"` to fill the same state with Health Service Areas
|
|
735
|
+
instead. An unrecognized `state` value logs a warning and falls back to the
|
|
736
|
+
full national map.
|
|
737
|
+
|
|
738
|
+
### Click to focus (`v-model:focus`)
|
|
739
|
+
|
|
740
|
+
Bind the `focus` prop to pan and zoom to a specific feature. Pass a feature
|
|
741
|
+
id (FIPS code, HSA code, or name) — or an array of ids to focus on a region.
|
|
742
|
+
With `v-model:focus`, clicking an unfocused feature focuses it and clicking
|
|
743
|
+
the focused feature toggles back off. If a tooltip is configured, focusing
|
|
744
|
+
shows that feature's tooltip. Users can keep exploring around the focused
|
|
745
|
+
area; the built-in **reset** button clears focus and snaps back to the full
|
|
746
|
+
extent.
|
|
747
|
+
|
|
748
|
+
Each focus target's outline can be styled by passing a `FocusItem` object:
|
|
749
|
+
`style` picks the dash pattern (`"solid" | "dashed" | "dotted"`), `stroke`
|
|
750
|
+
the color, and `strokeWidth` the width in CSS px. In-place highlights
|
|
751
|
+
default to pure black/white following the theme (`light-dark(#000, #fff)`);
|
|
752
|
+
cross-geoType overlays default to white.
|
|
753
|
+
|
|
754
|
+
Set `:focus-zoom="false"` to highlight (and draw cross-geoType overlays)
|
|
755
|
+
**without** panning or zooming — useful for a click-to-select interaction
|
|
756
|
+
where the map should stay put while a side panel shows the details.
|
|
757
|
+
|
|
758
|
+
Selection works the same on touch: a single-finger **tap** on a feature
|
|
759
|
+
emits `stateClick` and toggles focus, inline or inside the expanded view.
|
|
760
|
+
(Inline with `zoom` on, the selection fires after the brief double-tap
|
|
761
|
+
window.) A tap also stands in for hover — it applies the hover highlight,
|
|
762
|
+
emits `stateHover`, and shows the feature's tooltip (anchored to the
|
|
763
|
+
feature inline; sliding up as a bottom sheet in the expanded view); only
|
|
764
|
+
continuous hover _tracking_ is off on touch, for performance.
|
|
765
|
+
|
|
766
|
+
Click-to-focus interactions usually pair best with `:touch-expand="false"`,
|
|
767
|
+
as in the demos below — a double tap zooms in place while taps keep
|
|
768
|
+
selecting, so the map stays in your layout instead of taking over the
|
|
769
|
+
window.
|
|
770
|
+
|
|
771
|
+
Counties are tiny without a zoom — focus is a natural fit for drill-down.
|
|
772
|
+
|
|
773
|
+
<ComponentDemo>
|
|
774
|
+
<ChoroplethMap
|
|
775
|
+
:topology="countiesTopo"
|
|
776
|
+
geo-type="counties"
|
|
777
|
+
v-model:focus="focused"
|
|
778
|
+
:focus-zoom-level="8"
|
|
779
|
+
zoom
|
|
780
|
+
:touch-expand="false"
|
|
781
|
+
:data="[
|
|
782
|
+
{ id: '06037', value: 100 },
|
|
783
|
+
{ id: '06073', value: 80 },
|
|
784
|
+
{ id: '36061', value: 90 },
|
|
785
|
+
{ id: '17031', value: 85 },
|
|
786
|
+
{ id: '48201', value: 65 },
|
|
787
|
+
{ id: '04013', value: 60 },
|
|
788
|
+
{ id: '12086', value: 55 },
|
|
789
|
+
{ id: '53033', value: 50 },
|
|
790
|
+
]"
|
|
791
|
+
title="Click a county to focus"
|
|
792
|
+
:legend-title="'Cases'"
|
|
793
|
+
:height="400"
|
|
794
|
+
>
|
|
795
|
+
<template #tooltip="{ name, value }">
|
|
796
|
+
<div style="font-weight: 600">{{ name }}</div>
|
|
797
|
+
<div v-if="value != null">Cases: {{ value }}</div>
|
|
798
|
+
<div v-else style="opacity: 0.6">No data</div>
|
|
799
|
+
</template>
|
|
800
|
+
</ChoroplethMap>
|
|
801
|
+
|
|
802
|
+
<template #code>
|
|
803
|
+
|
|
804
|
+
```vue
|
|
805
|
+
<script setup>
|
|
806
|
+
import { ref } from "vue";
|
|
807
|
+
const focused = ref(null);
|
|
808
|
+
</script>
|
|
809
|
+
|
|
810
|
+
<ChoroplethMap
|
|
811
|
+
:topology="countiesTopo"
|
|
812
|
+
geo-type="counties"
|
|
813
|
+
v-model:focus="focused"
|
|
814
|
+
:focus-zoom-level="8"
|
|
815
|
+
zoom
|
|
816
|
+
:touch-expand="false"
|
|
817
|
+
:data="data"
|
|
818
|
+
title="Click a county to focus"
|
|
819
|
+
>
|
|
820
|
+
<template #tooltip="{ name, value }">
|
|
821
|
+
<div style="font-weight: 600">{{ name }}</div>
|
|
822
|
+
<div v-if="value != null">Cases: {{ value }}</div>
|
|
823
|
+
<div v-else style="opacity: 0.6">No data</div>
|
|
824
|
+
</template>
|
|
825
|
+
</ChoroplethMap>
|
|
826
|
+
```
|
|
827
|
+
|
|
828
|
+
</template>
|
|
829
|
+
</ComponentDemo>
|
|
830
|
+
|
|
831
|
+
### Color by HSA, interact by county (`dataGeoType`)
|
|
832
|
+
|
|
833
|
+
Set `dataGeoType` when your data is keyed by a coarser geography than
|
|
834
|
+
the one you want to render. Each county fills with its parent HSA's
|
|
835
|
+
value (via the built-in FIPS → HSA mapping); hover, click, and `focus`
|
|
836
|
+
still operate on the county geometry, and you can layer an HSA outline
|
|
837
|
+
on top with a `FocusItem`.
|
|
838
|
+
|
|
839
|
+
<ComponentDemo>
|
|
840
|
+
<ChoroplethMap
|
|
841
|
+
:topology="countiesTopo"
|
|
842
|
+
geo-type="counties"
|
|
843
|
+
data-geo-type="hsas"
|
|
844
|
+
zoom
|
|
845
|
+
renderer="canvas"
|
|
846
|
+
:touch-expand="false"
|
|
847
|
+
:data="[
|
|
848
|
+
{ id: '060737', value: 80 },
|
|
849
|
+
{ id: '060723', value: 60 },
|
|
850
|
+
{ id: '060757', value: 45 },
|
|
851
|
+
{ id: '060807', value: 35 },
|
|
852
|
+
{ id: '060768', value: 25 },
|
|
853
|
+
{ id: '060774', value: 50 },
|
|
854
|
+
]"
|
|
855
|
+
:focus="[
|
|
856
|
+
{ id: '06043' },
|
|
857
|
+
{ id: '060737', geoType: 'hsas', style: 'dashed' },
|
|
858
|
+
]"
|
|
859
|
+
:focus-zoom-level="6"
|
|
860
|
+
title="HSA-keyed data on a county map"
|
|
861
|
+
:legend-title="'Cases'"
|
|
862
|
+
:height="400"
|
|
863
|
+
/>
|
|
864
|
+
|
|
865
|
+
<template #code>
|
|
866
|
+
|
|
867
|
+
```vue
|
|
868
|
+
<ChoroplethMap
|
|
869
|
+
:topology="countiesTopo"
|
|
870
|
+
geo-type="counties"
|
|
871
|
+
data-geo-type="hsas"
|
|
872
|
+
zoom
|
|
873
|
+
renderer="canvas"
|
|
874
|
+
:touch-expand="false"
|
|
875
|
+
:data="hsaData"
|
|
876
|
+
:focus="[{ id: '06043' }, { id: '060737', geoType: 'hsas', style: 'dashed' }]"
|
|
877
|
+
title="HSA-keyed data on a county map"
|
|
878
|
+
/>
|
|
879
|
+
```
|
|
880
|
+
|
|
881
|
+
</template>
|
|
882
|
+
</ComponentDemo>
|
|
883
|
+
|
|
884
|
+
### Mixing levels on one map (`data[].geoType`)
|
|
885
|
+
|
|
886
|
+
`dataGeoType` re-keys _all_ the data at once. When only some regions report at
|
|
887
|
+
a different level — most of the country has county estimates, but California
|
|
888
|
+
only published a statewide number — give those rows their own `geoType`
|
|
889
|
+
instead. The state is then drawn at that level: one merged California shape
|
|
890
|
+
carrying the state value, with every other state still split into counties.
|
|
891
|
+
|
|
892
|
+
```vue
|
|
893
|
+
<ChoroplethMap
|
|
894
|
+
:topology="countiesTopo"
|
|
895
|
+
geo-type="counties"
|
|
896
|
+
:data="[
|
|
897
|
+
{ id: '36061', value: 12 }, // county rows as usual
|
|
898
|
+
{ id: '06', value: 85, geoType: 'states' }, // …and California as a whole
|
|
899
|
+
]"
|
|
900
|
+
/>
|
|
901
|
+
```
|
|
902
|
+
|
|
903
|
+
The substituted region is a normal feature: it fills from its own row, hovers
|
|
904
|
+
and clicks as one unit, reports its own name (`California`) in the tooltip, and
|
|
905
|
+
can be focused by its id.
|
|
906
|
+
|
|
907
|
+
<ComponentDemo>
|
|
908
|
+
<ChoroplethMap
|
|
909
|
+
:topology="countiesTopo"
|
|
910
|
+
geo-type="counties"
|
|
911
|
+
renderer="canvas"
|
|
912
|
+
:data="mixedLevelData"
|
|
913
|
+
title="County estimates, with California and Texas reported statewide"
|
|
914
|
+
legend-title="Rate"
|
|
915
|
+
:height="420"
|
|
916
|
+
/>
|
|
917
|
+
|
|
918
|
+
<template #code>
|
|
919
|
+
|
|
920
|
+
```vue
|
|
921
|
+
<ChoroplethMap
|
|
922
|
+
:topology="countiesTopo"
|
|
923
|
+
geo-type="counties"
|
|
924
|
+
renderer="canvas"
|
|
925
|
+
:data="[
|
|
926
|
+
...countyRows,
|
|
927
|
+
{ id: '06', value: 85, geoType: 'states' },
|
|
928
|
+
{ id: '48', value: 15, geoType: 'states' },
|
|
929
|
+
]"
|
|
930
|
+
/>
|
|
931
|
+
```
|
|
932
|
+
|
|
933
|
+
</template>
|
|
934
|
+
</ComponentDemo>
|
|
935
|
+
|
|
936
|
+
It works in the other direction too — a state-level map where one state is
|
|
937
|
+
broken out into counties. The whole state is re-tiled, so its counties without
|
|
938
|
+
a row of their own render as no-data:
|
|
939
|
+
|
|
940
|
+
<ComponentDemo>
|
|
941
|
+
<ChoroplethMap
|
|
942
|
+
:topology="countiesTopo"
|
|
943
|
+
geo-type="states"
|
|
944
|
+
:data="splitStateData"
|
|
945
|
+
title="State estimates, with New York broken out by county"
|
|
946
|
+
legend-title="Rate"
|
|
947
|
+
:height="420"
|
|
948
|
+
/>
|
|
949
|
+
|
|
950
|
+
<template #code>
|
|
951
|
+
|
|
952
|
+
```vue
|
|
953
|
+
<ChoroplethMap
|
|
954
|
+
:topology="countiesTopo"
|
|
955
|
+
geo-type="states"
|
|
956
|
+
:data="[
|
|
957
|
+
{ id: '06', value: 70 },
|
|
958
|
+
{ id: '48', value: 30 },
|
|
959
|
+
...newYorkCountyRows.map((r) => ({ ...r, geoType: 'counties' })),
|
|
960
|
+
]"
|
|
961
|
+
/>
|
|
962
|
+
```
|
|
963
|
+
|
|
964
|
+
</template>
|
|
965
|
+
</ComponentDemo>
|
|
966
|
+
|
|
967
|
+
Rules to keep in mind:
|
|
968
|
+
|
|
969
|
+
- **A whole state is the unit.** One off-level row re-tiles its entire state, so
|
|
970
|
+
a single county row on a state map splits all of that state's counties out.
|
|
971
|
+
- **Any pair of levels works** (`states` / `counties` / `hsas`), in either
|
|
972
|
+
direction, as long as the topology can supply them — splitting a state into
|
|
973
|
+
counties or HSAs needs `counties-10m.json`, not `states-10m.json`. HSA
|
|
974
|
+
substitution waits for the lazily-loaded FIPS→HSA table and leaves the base
|
|
975
|
+
map untouched until it arrives.
|
|
976
|
+
- **Off-level rows ignore `dataGeoType`.** They're looked up by their own id, so
|
|
977
|
+
a map can use `dataGeoType` for its base features and still mix.
|
|
978
|
+
- **One level per state.** If two rows claim the same state at different levels,
|
|
979
|
+
the first wins and a warning is logged. A row whose id resolves to no state
|
|
980
|
+
is ignored (also with a warning) and the base map renders as usual.
|
|
981
|
+
- Ids can be codes or feature names (`"06"` or `"California"`), resolved in the
|
|
982
|
+
row's own `geoType`.
|
|
983
|
+
|
|
984
|
+
### Outline a focused feature's parent
|
|
985
|
+
|
|
986
|
+
Use `v-model:focus` together with a computed that derives a parent
|
|
987
|
+
feature (e.g. an HSA from a county via `fipsToHsa`). Pass both as a
|
|
988
|
+
`FocusItem` array — the focused county lights up as usual and the
|
|
989
|
+
parent HSA renders on top as a dashed overlay (`stroke: "#666"` here —
|
|
990
|
+
default is white).
|
|
991
|
+
|
|
992
|
+
`fipsToHsa` and `hsaNames` ship from the `@cfasim-ui/charts/hsa-mapping`
|
|
993
|
+
subpath so consumers that don't need HSA lookups don't pay for the
|
|
994
|
+
~25 KB of mapping data.
|
|
995
|
+
|
|
996
|
+
<ComponentDemo>
|
|
997
|
+
<ChoroplethMap
|
|
998
|
+
:topology="countiesTopo"
|
|
999
|
+
geo-type="counties"
|
|
1000
|
+
data-geo-type="hsas"
|
|
1001
|
+
zoom
|
|
1002
|
+
:touch-expand="false"
|
|
1003
|
+
:focus="parentFocus"
|
|
1004
|
+
@update:focus="focusedCounty = typeof $event === 'string' ? $event : null"
|
|
1005
|
+
:data="[
|
|
1006
|
+
{ id: '060737', value: 80 },
|
|
1007
|
+
{ id: '060723', value: 60 },
|
|
1008
|
+
{ id: '060757', value: 45 },
|
|
1009
|
+
{ id: '060807', value: 35 },
|
|
1010
|
+
{ id: '060768', value: 25 },
|
|
1011
|
+
{ id: '060774', value: 50 },
|
|
1012
|
+
]"
|
|
1013
|
+
:focus-zoom-level="6"
|
|
1014
|
+
title="Click a county to outline its HSA"
|
|
1015
|
+
:legend-title="'Cases'"
|
|
1016
|
+
:height="400"
|
|
1017
|
+
>
|
|
1018
|
+
<template #tooltip="{ name, value }">
|
|
1019
|
+
<div style="font-weight: 600">{{ name }}</div>
|
|
1020
|
+
<div v-if="value != null">Cases: {{ value }}</div>
|
|
1021
|
+
<div v-else style="opacity: 0.6">No data</div>
|
|
1022
|
+
</template>
|
|
1023
|
+
</ChoroplethMap>
|
|
1024
|
+
|
|
1025
|
+
<template #code>
|
|
1026
|
+
|
|
1027
|
+
```vue
|
|
1028
|
+
<script setup>
|
|
1029
|
+
import { ref, computed } from "vue";
|
|
1030
|
+
import { fipsToHsa } from "@cfasim-ui/charts/hsa-mapping";
|
|
1031
|
+
|
|
1032
|
+
const focusedCounty = ref(null);
|
|
1033
|
+
const focus = computed(() => {
|
|
1034
|
+
const fips = focusedCounty.value;
|
|
1035
|
+
if (!fips) return null;
|
|
1036
|
+
const hsa = fipsToHsa[fips];
|
|
1037
|
+
return hsa
|
|
1038
|
+
? [fips, { id: hsa, geoType: "hsas", style: "dashed", stroke: "#666" }]
|
|
1039
|
+
: fips;
|
|
1040
|
+
});
|
|
1041
|
+
</script>
|
|
1042
|
+
|
|
1043
|
+
<ChoroplethMap
|
|
1044
|
+
:topology="countiesTopo"
|
|
1045
|
+
geo-type="counties"
|
|
1046
|
+
data-geo-type="hsas"
|
|
1047
|
+
zoom
|
|
1048
|
+
:touch-expand="false"
|
|
1049
|
+
:data="hsaData"
|
|
1050
|
+
:focus="focus"
|
|
1051
|
+
@update:focus="focusedCounty = typeof $event === 'string' ? $event : null"
|
|
1052
|
+
title="Click a county to outline its HSA"
|
|
1053
|
+
/>
|
|
1054
|
+
```
|
|
1055
|
+
|
|
1056
|
+
</template>
|
|
1057
|
+
</ComponentDemo>
|
|
1058
|
+
|
|
1059
|
+
### Custom tooltip number format
|
|
1060
|
+
|
|
1061
|
+
Pass `tooltip-value-format` to format numeric values shown in the tooltip
|
|
1062
|
+
(both the native SVG `<title>` and the interactive HTML tooltip). Use the
|
|
1063
|
+
`#tooltip` slot if you want full control over the tooltip's content.
|
|
1064
|
+
|
|
1065
|
+
<ComponentDemo>
|
|
1066
|
+
<ChoroplethMap
|
|
1067
|
+
:topology="statesTopo"
|
|
1068
|
+
:data="[
|
|
1069
|
+
{ id: '06', value: 39538223 },
|
|
1070
|
+
{ id: '48', value: 29145505 },
|
|
1071
|
+
{ id: '12', value: 21538187 },
|
|
1072
|
+
{ id: '36', value: 20201249 },
|
|
1073
|
+
]"
|
|
1074
|
+
:tooltip-value-format="(v) => v.toLocaleString('en-US')"
|
|
1075
|
+
title="US population (2020)"
|
|
1076
|
+
:height="300"
|
|
1077
|
+
/>
|
|
1078
|
+
|
|
1079
|
+
<template #code>
|
|
1080
|
+
|
|
1081
|
+
```vue
|
|
1082
|
+
<ChoroplethMap
|
|
1083
|
+
:topology="statesTopo"
|
|
1084
|
+
:data="[
|
|
1085
|
+
{ id: '06', value: 39538223 },
|
|
1086
|
+
{ id: '48', value: 29145505 },
|
|
1087
|
+
{ id: '12', value: 21538187 },
|
|
1088
|
+
{ id: '36', value: 20201249 },
|
|
1089
|
+
]"
|
|
1090
|
+
:tooltip-value-format="(v) => v.toLocaleString('en-US')"
|
|
1091
|
+
title="US population (2020)"
|
|
1092
|
+
:height="300"
|
|
1093
|
+
/>
|
|
1094
|
+
```
|
|
1095
|
+
|
|
1096
|
+
</template>
|
|
1097
|
+
</ComponentDemo>
|
|
1098
|
+
|
|
1099
|
+
### Canvas rendering (`renderer="canvas"`)
|
|
1100
|
+
|
|
1101
|
+
For dense maps — every US county, HSAs, or anything that feels sluggish on
|
|
1102
|
+
mobile — set `renderer="canvas"`. Instead of one DOM path per feature, the
|
|
1103
|
+
whole map paints into a single `<canvas>`: zooming, panning, and hover/tap
|
|
1104
|
+
highlights redraw in a few milliseconds regardless of feature count, and
|
|
1105
|
+
DOM memory stays flat. Interactions are identical (hover, click/tap
|
|
1106
|
+
selection, `focus`, every zoom mode and touch flow); hit-testing runs
|
|
1107
|
+
through an offscreen picking bitmap, so it stays pixel-accurate.
|
|
1108
|
+
|
|
1109
|
+
Differences from the default SVG renderer:
|
|
1110
|
+
|
|
1111
|
+
- The menu offers **Fullscreen** and **Save as PNG** only (there is no SVG
|
|
1112
|
+
DOM to serialize; the PNG exports straight off the rendering canvas).
|
|
1113
|
+
- There is no per-feature `<title>` fallback or per-feature DOM for
|
|
1114
|
+
assistive tech — configure an interactive tooltip (`tooltip-trigger` or
|
|
1115
|
+
the `#tooltip` slot), or stay on SVG where that fallback matters.
|
|
1116
|
+
- `renderer` is fixed at mount.
|
|
1117
|
+
|
|
1118
|
+
The dense county demo below uses it.
|
|
1119
|
+
|
|
1120
|
+
### Dense county map
|
|
1121
|
+
|
|
1122
|
+
Renders every US county with a value and a custom tooltip slot, on the
|
|
1123
|
+
canvas backend.
|
|
1124
|
+
|
|
1125
|
+
<ComponentDemo>
|
|
1126
|
+
<ChoroplethMap
|
|
1127
|
+
:topology="countiesTopo"
|
|
1128
|
+
geo-type="counties"
|
|
1129
|
+
:data="denseCountyData"
|
|
1130
|
+
zoom
|
|
1131
|
+
renderer="canvas"
|
|
1132
|
+
:color-scale="{ min: '#f0f5ff', max: '#08306b' }"
|
|
1133
|
+
title="All US counties — tooltip perf demo"
|
|
1134
|
+
:height="500"
|
|
1135
|
+
>
|
|
1136
|
+
<template #tooltip="{ id, name, value }">
|
|
1137
|
+
<div style="font-weight: 600">{{ name }}</div>
|
|
1138
|
+
<div style="opacity: 0.7; font-size: 0.85em">FIPS {{ id }}</div>
|
|
1139
|
+
<div>Value: {{ value }}</div>
|
|
1140
|
+
</template>
|
|
1141
|
+
</ChoroplethMap>
|
|
1142
|
+
|
|
1143
|
+
<template #code>
|
|
1144
|
+
|
|
1145
|
+
```vue
|
|
1146
|
+
<script setup>
|
|
1147
|
+
import countiesTopo from "us-atlas/counties-10m.json";
|
|
1148
|
+
|
|
1149
|
+
// One row per county
|
|
1150
|
+
const data = countiesTopo.objects.counties.geometries.map((g, i) => ({
|
|
1151
|
+
id: String(g.id).padStart(5, "0"),
|
|
1152
|
+
value: (i * 37) % 100,
|
|
1153
|
+
}));
|
|
1154
|
+
</script>
|
|
1155
|
+
|
|
1156
|
+
<ChoroplethMap
|
|
1157
|
+
:topology="countiesTopo"
|
|
1158
|
+
geo-type="counties"
|
|
1159
|
+
:data="data"
|
|
1160
|
+
zoom
|
|
1161
|
+
renderer="canvas"
|
|
1162
|
+
>
|
|
1163
|
+
<template #tooltip="{ id, name, value }">
|
|
1164
|
+
<div style="font-weight: 600">{{ name }}</div>
|
|
1165
|
+
<div style="opacity: 0.7; font-size: 0.85em">FIPS {{ id }}</div>
|
|
1166
|
+
<div>Value: {{ value }}</div>
|
|
1167
|
+
</template>
|
|
1168
|
+
</ChoroplethMap>
|
|
1169
|
+
```
|
|
1170
|
+
|
|
1171
|
+
</template>
|
|
1172
|
+
</ComponentDemo>
|
|
1173
|
+
|
|
1174
|
+
### Custom tooltip content (`#tooltip` slot)
|
|
1175
|
+
|
|
1176
|
+
Use the `#tooltip` slot to render any Vue template — components, scoped
|
|
1177
|
+
styles, multi-line layouts — instead of the default `name: value`. The slot
|
|
1178
|
+
receives `{ id, name, value, feature }` for the hovered region. Providing the
|
|
1179
|
+
slot automatically enables interactive (HTML) tooltips, so you don't need to
|
|
1180
|
+
set `tooltip-trigger`.
|
|
1181
|
+
|
|
1182
|
+
<ComponentDemo>
|
|
1183
|
+
<ChoroplethMap
|
|
1184
|
+
:topology="statesTopo"
|
|
1185
|
+
:data="[
|
|
1186
|
+
{ id: '06', value: 39538223 },
|
|
1187
|
+
{ id: '48', value: 29145505 },
|
|
1188
|
+
{ id: '12', value: 21538187 },
|
|
1189
|
+
{ id: '36', value: 20201249 },
|
|
1190
|
+
{ id: '17', value: 12812508 },
|
|
1191
|
+
]"
|
|
1192
|
+
title="US population (2020)"
|
|
1193
|
+
:height="300"
|
|
1194
|
+
>
|
|
1195
|
+
<template #tooltip="{ name, value }">
|
|
1196
|
+
<div style="font-weight:600">{{ name }}</div>
|
|
1197
|
+
<div v-if="typeof value === 'number'">
|
|
1198
|
+
Pop: {{ value.toLocaleString('en-US') }}
|
|
1199
|
+
</div>
|
|
1200
|
+
<div v-else style="opacity:0.6">No data</div>
|
|
1201
|
+
</template>
|
|
1202
|
+
</ChoroplethMap>
|
|
1203
|
+
|
|
1204
|
+
<template #code>
|
|
1205
|
+
|
|
1206
|
+
```vue
|
|
1207
|
+
<ChoroplethMap :topology="statesTopo" :data="data" title="US population (2020)">
|
|
1208
|
+
<template #tooltip="{ name, value }">
|
|
1209
|
+
<div style="font-weight: 600">{{ name }}</div>
|
|
1210
|
+
<div v-if="typeof value === 'number'">
|
|
1211
|
+
Pop: {{ value.toLocaleString("en-US") }}
|
|
1212
|
+
</div>
|
|
1213
|
+
<div v-else style="opacity: 0.6">No data</div>
|
|
1214
|
+
</template>
|
|
1215
|
+
</ChoroplethMap>
|
|
1216
|
+
```
|
|
1217
|
+
|
|
1218
|
+
</template>
|
|
1219
|
+
</ComponentDemo>
|
|
1220
|
+
|
|
1221
|
+
## Accessibility
|
|
1222
|
+
|
|
1223
|
+
The map's individual regions aren't exposed to assistive tech, so the map
|
|
1224
|
+
announces itself with a single accessible name. When it has a `title`, the root
|
|
1225
|
+
element gets `role="figure"` and an `aria-label` set to the title, so screen
|
|
1226
|
+
readers announce it as a labeled figure while the menu and reset controls stay
|
|
1227
|
+
reachable.
|
|
1228
|
+
|
|
1229
|
+
Set `ariaLabel` to give screen readers a fuller summary than the visible title
|
|
1230
|
+
(it overrides `title` for the accessible name only):
|
|
1231
|
+
|
|
1232
|
+
```vue
|
|
1233
|
+
<ChoroplethMap
|
|
1234
|
+
:topology="usStates"
|
|
1235
|
+
:data="cases"
|
|
1236
|
+
title="Cases by state"
|
|
1237
|
+
aria-label="US map shaded by case count per state, highest in the Southeast"
|
|
1238
|
+
/>
|
|
1239
|
+
```
|
|
1240
|
+
|
|
1241
|
+
Pass `role` to override the default, e.g. `role="img"` to expose the map as a
|
|
1242
|
+
single image (note this hides the inner menu/reset controls from assistive
|
|
1243
|
+
tech).
|
|
1244
|
+
|
|
1245
|
+
## Props
|
|
1246
|
+
|
|
1247
|
+
| Prop | Type | Required | Default |
|
|
1248
|
+
|------|------|----------|---------|
|
|
1249
|
+
| `topology` | `Topology` | Yes | — |
|
|
1250
|
+
| `data` | `StateData[]` | No | — |
|
|
1251
|
+
| `geoType` | `GeoType` | No | `"states"` |
|
|
1252
|
+
| `dataGeoType` | `GeoType` | No | — |
|
|
1253
|
+
| `state` | `string` | No | — |
|
|
1254
|
+
| `width` | `number` | No | — |
|
|
1255
|
+
| `height` | `number` | No | — |
|
|
1256
|
+
| `tightFit` | `boolean \| number` | No | `false` |
|
|
1257
|
+
| `colorScale` | `ChoroplethColorScale \| ThresholdStop[] \| CategoricalStop[]` | No | — |
|
|
1258
|
+
| `title` | `string` | No | — |
|
|
1259
|
+
| `titleStyle` | `TitleStyle` | No | — |
|
|
1260
|
+
| `role` | `string` | No | — |
|
|
1261
|
+
| `ariaLabel` | `string` | No | — |
|
|
1262
|
+
| `legendStyle` | `LabelStyle` | No | — |
|
|
1263
|
+
| `theme` | `MapTheme` | No | — |
|
|
1264
|
+
| `menu` | `boolean \| string` | No | `true` |
|
|
1265
|
+
| `legend` | `boolean` | No | `true` |
|
|
1266
|
+
| `legendTitle` | `string` | No | — |
|
|
1267
|
+
| `zoom` | `boolean` | No | `false` |
|
|
1268
|
+
| `zoomMode` | `"activate" \| "scroll"` | No | `"activate"` |
|
|
1269
|
+
| `touchExpand` | `boolean` | No | `true` |
|
|
1270
|
+
| `zoomHint` | `boolean` | No | `true` |
|
|
1271
|
+
| `tooltipTrigger` | `"hover" \| "click"` | No | — |
|
|
1272
|
+
| `tooltipFormat` | `(data: {
|
|
1273
|
+
id: string` | No | — |
|
|
1274
|
+
| `name` | `string` | Yes | — |
|
|
1275
|
+
| `value` | `number \| string` | No | — |
|
|
1276
|
+
| `tooltipValueFormat` | `NumberFormat` | No | — |
|
|
1277
|
+
| `tooltipClamp` | `"none" \| "chart" \| "window"` | No | `"window"` |
|
|
1278
|
+
| `focus` | `FocusValue` | No | — |
|
|
1279
|
+
| `focusZoomLevel` | `number` | No | `4` |
|
|
1280
|
+
| `focusZoom` | `boolean` | No | `true` |
|
|
1281
|
+
| `renderer` | `"svg" \| "canvas"` | No | `"svg"` |
|
|
1282
|
+
| `fullscreenTarget` | `string \| HTMLElement` | No | — |
|
|
1283
|
+
| `cities` | `CityMarker[]` | No | — |
|
|
1284
|
+
| `citiesMinZoom` | `number` | No | `2` |
|
|
1285
|
+
|
|
1286
|
+
|
|
1287
|
+
### StateData
|
|
1288
|
+
|
|
1289
|
+
```ts
|
|
1290
|
+
interface StateData {
|
|
1291
|
+
/** FIPS code (e.g. "06" for California, "06037" for LA County) or name */
|
|
1292
|
+
id: string;
|
|
1293
|
+
value: number | string;
|
|
1294
|
+
/**
|
|
1295
|
+
* Level of *this row*, when it differs from the map's `geoType`. The row's
|
|
1296
|
+
* state is re-tiled at this level — see "Mixing levels on one map".
|
|
1297
|
+
*/
|
|
1298
|
+
geoType?: GeoType;
|
|
1299
|
+
}
|
|
1300
|
+
```
|
|
1301
|
+
|
|
1302
|
+
### ChoroplethColorScale
|
|
1303
|
+
|
|
1304
|
+
Any CSS color works, including `var()` and `light-dark()` — scale colors resolve against the map's container and re-resolve on page theme changes.
|
|
1305
|
+
|
|
1306
|
+
```ts
|
|
1307
|
+
interface ChoroplethColorScale {
|
|
1308
|
+
/** Minimum color (any CSS color). Default: "#e5f0fa" */
|
|
1309
|
+
min?: string;
|
|
1310
|
+
/** Maximum color (any CSS color). Default: "#08519c" */
|
|
1311
|
+
max?: string;
|
|
1312
|
+
}
|
|
1313
|
+
```
|
|
1314
|
+
|
|
1315
|
+
### ThresholdStop
|
|
1316
|
+
|
|
1317
|
+
Pass an array of `ThresholdStop` as `colorScale` for discrete color buckets instead of a linear gradient. The highest matching `min` wins.
|
|
1318
|
+
|
|
1319
|
+
```ts
|
|
1320
|
+
interface ThresholdStop {
|
|
1321
|
+
/** Lower bound (inclusive). Values at or above this get this color. */
|
|
1322
|
+
min: number;
|
|
1323
|
+
color: string;
|
|
1324
|
+
/** Optional label for the legend (defaults to the min value) */
|
|
1325
|
+
label?: string;
|
|
1326
|
+
}
|
|
1327
|
+
```
|
|
1328
|
+
|
|
1329
|
+
### CategoricalStop
|
|
1330
|
+
|
|
1331
|
+
Pass an array of `CategoricalStop` as `colorScale` to map string values to colors. States whose `value` matches a stop's `value` get that color; unmatched values get the theme's base `fill`.
|
|
1332
|
+
|
|
1333
|
+
```ts
|
|
1334
|
+
interface CategoricalStop {
|
|
1335
|
+
/** The categorical value to match */
|
|
1336
|
+
value: string;
|
|
1337
|
+
/** CSS color string */
|
|
1338
|
+
color: string;
|
|
1339
|
+
}
|
|
1340
|
+
```
|
|
1341
|
+
|
|
1342
|
+
### MapTheme
|
|
1343
|
+
|
|
1344
|
+
All keys are optional; unset keys fall back to their `--choropleth-*` custom property (see [Theming](#theming-theme)).
|
|
1345
|
+
|
|
1346
|
+
```ts
|
|
1347
|
+
interface MapTheme {
|
|
1348
|
+
/** Base fill for features without a data value (any CSS color). */
|
|
1349
|
+
fill?: string;
|
|
1350
|
+
/** Interior feature borders (any CSS color). */
|
|
1351
|
+
stroke?: string;
|
|
1352
|
+
/** Feature border width in CSS px, constant at any zoom. 0 disables. */
|
|
1353
|
+
strokeWidth?: number;
|
|
1354
|
+
/** State-boundary mesh over county/HSA maps. Falls back to `stroke`. */
|
|
1355
|
+
borders?: string;
|
|
1356
|
+
/** State-borders mesh width in CSS px. Default 1; 0 disables. */
|
|
1357
|
+
bordersWidth?: number;
|
|
1358
|
+
/** Exterior boundary, drawn on top of interior borders. Off by default. */
|
|
1359
|
+
outline?: string;
|
|
1360
|
+
/** Exterior outline width in CSS px. Default 1; 0 disables. */
|
|
1361
|
+
outlineWidth?: number;
|
|
1362
|
+
/** Background wash behind the map features. Off by default. */
|
|
1363
|
+
background?: string;
|
|
1364
|
+
/** Hover/focus highlight stroke. */
|
|
1365
|
+
highlight?: string;
|
|
1366
|
+
/** Marker overlay (`cities` prop): dot + label color. */
|
|
1367
|
+
markerColor?: string;
|
|
1368
|
+
/** Marker overlay: halo color around dots and labels. */
|
|
1369
|
+
markerHalo?: string;
|
|
1370
|
+
/** Dot halo width in CSS px. Default 0.9; 0 disables. */
|
|
1371
|
+
markerHaloWidth?: number;
|
|
1372
|
+
/** Opacity of the whole marker layer. Default 1. */
|
|
1373
|
+
markerOpacity?: number;
|
|
1374
|
+
}
|
|
1375
|
+
```
|
|
1376
|
+
|
|
1377
|
+
### FocusItem
|
|
1378
|
+
|
|
1379
|
+
The `focus` prop accepts a bare id, a `FocusItem`, or an array of either. Use objects when you want to pin features from a different `geoType` than the base map, or pick a non-default outline style.
|
|
1380
|
+
|
|
1381
|
+
```ts
|
|
1382
|
+
interface FocusItem {
|
|
1383
|
+
/** Feature id (FIPS code, HSA code) or name. */
|
|
1384
|
+
id: string;
|
|
1385
|
+
/** Defaults to the map's geoType. Cross-geoType items render as
|
|
1386
|
+
* non-interactive outlines on top of the base map. */
|
|
1387
|
+
geoType?: "states" | "counties" | "hsas";
|
|
1388
|
+
/** Outline style. "solid" (default) matches the hover highlight;
|
|
1389
|
+
* "dashed" uses long dashes; "dotted" uses small round dots. */
|
|
1390
|
+
style?: "solid" | "dashed" | "dotted";
|
|
1391
|
+
/** Stroke color for cross-geoType overlay paths. Default: "#fff". */
|
|
1392
|
+
stroke?: string;
|
|
1393
|
+
}
|
|
1394
|
+
```
|