@cfasim-ui/charts 0.8.0 → 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.
@@ -27,6 +27,28 @@ const focused = ref(null);
27
27
  // "Outline a focused feature's parent" demo: focus is a county id;
28
28
  // we derive the parent HSA and add it to the focus array as a dashed
29
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
+
30
52
  const focusedCounty = ref(null);
31
53
  const parentFocus = computed(() => {
32
54
  const fips = focusedCounty.value;
@@ -499,7 +521,7 @@ Works on national state, county, and HSA maps — it's a no-op only in single-st
499
521
 
500
522
  ### City markers (`cities`)
501
523
 
502
- 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 (with a lightly emphasized label) 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.
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.
503
525
 
504
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:**
505
527
 
@@ -521,7 +543,7 @@ import { ChoroplethMap } from "@cfasim-ui/charts";
521
543
  import { nationalCityMarkers } from "@cfasim-ui/charts/us-cities";
522
544
  import statesTopo from "us-atlas/states-10m.json";
523
545
 
524
- // Washington, DC (emphasized) + the 100 most-populous US cities.
546
+ // Washington, DC (flagged as the capital) + the 100 most-populous US cities.
525
547
  const cities = nationalCityMarkers();
526
548
  </script>
527
549
 
@@ -579,7 +601,7 @@ stateCityMarkers("48"); //=> Austin (capital) + top Texas cities
579
601
  usCities; //=> the raw UsCity[] to build your own selection
580
602
  ```
581
603
 
582
- 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 emphasized and never dropped for collisions — the marker itself is a plain dot like any other city.
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.
583
605
 
584
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.
585
607
 
@@ -859,6 +881,106 @@ on top with a `FocusItem`.
859
881
  </template>
860
882
  </ComponentDemo>
861
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
+
862
984
  ### Outline a focused feature's parent
863
985
 
864
986
  Use `v-model:focus` together with a computed that derives a parent
@@ -1169,6 +1291,11 @@ interface StateData {
1169
1291
  /** FIPS code (e.g. "06" for California, "06037" for LA County) or name */
1170
1292
  id: string;
1171
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;
1172
1299
  }
1173
1300
  ```
1174
1301
 
package/docs/index.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.8.0",
2
+ "version": "0.8.1",
3
3
  "package": "@cfasim-ui/charts",
4
4
  "content": {
5
5
  "charts": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cfasim-ui/charts",
3
- "version": "0.8.0",
3
+ "version": "0.8.1",
4
4
  "type": "module",
5
5
  "description": "Chart visualization components for cfasim-ui",
6
6
  "license": "Apache-2.0",
@@ -46,7 +46,7 @@
46
46
  "d3-zoom": "3.0.0",
47
47
  "reka-ui": "2.10.1",
48
48
  "topojson-client": "3.1.0",
49
- "@cfasim-ui/shared": "0.8.0"
49
+ "@cfasim-ui/shared": "0.8.1"
50
50
  },
51
51
  "peerDependencies": {
52
52
  "vue": "^3.5.0"
@@ -27,6 +27,28 @@ const focused = ref(null);
27
27
  // "Outline a focused feature's parent" demo: focus is a county id;
28
28
  // we derive the parent HSA and add it to the focus array as a dashed
29
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
+
30
52
  const focusedCounty = ref(null);
31
53
  const parentFocus = computed(() => {
32
54
  const fips = focusedCounty.value;
@@ -499,7 +521,7 @@ Works on national state, county, and HSA maps — it's a no-op only in single-st
499
521
 
500
522
  ### City markers (`cities`)
501
523
 
502
- 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 (with a lightly emphasized label) 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.
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.
503
525
 
504
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:**
505
527
 
@@ -521,7 +543,7 @@ import { ChoroplethMap } from "@cfasim-ui/charts";
521
543
  import { nationalCityMarkers } from "@cfasim-ui/charts/us-cities";
522
544
  import statesTopo from "us-atlas/states-10m.json";
523
545
 
524
- // Washington, DC (emphasized) + the 100 most-populous US cities.
546
+ // Washington, DC (flagged as the capital) + the 100 most-populous US cities.
525
547
  const cities = nationalCityMarkers();
526
548
  </script>
527
549
 
@@ -579,7 +601,7 @@ stateCityMarkers("48"); //=> Austin (capital) + top Texas cities
579
601
  usCities; //=> the raw UsCity[] to build your own selection
580
602
  ```
581
603
 
582
- 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 emphasized and never dropped for collisions — the marker itself is a plain dot like any other city.
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.
583
605
 
584
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.
585
607
 
@@ -859,6 +881,106 @@ on top with a `FocusItem`.
859
881
  </template>
860
882
  </ComponentDemo>
861
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
+
862
984
  ### Outline a focused feature's parent
863
985
 
864
986
  Use `v-model:focus` together with a computed that derives a parent
@@ -1129,6 +1251,11 @@ interface StateData {
1129
1251
  /** FIPS code (e.g. "06" for California, "06037" for LA County) or name */
1130
1252
  id: string;
1131
1253
  value: number | string;
1254
+ /**
1255
+ * Level of *this row*, when it differs from the map's `geoType`. The row's
1256
+ * state is re-tiled at this level — see "Mixing levels on one map".
1257
+ */
1258
+ geoType?: GeoType;
1132
1259
  }
1133
1260
  ```
1134
1261