@cdc/map 4.25.8 → 4.25.11

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.
Files changed (137) hide show
  1. package/.claude/agents/typescript-organizer.md +118 -0
  2. package/.claude/settings.local.json +30 -0
  3. package/dist/{cdcmap-fce76882.es.js → cdcmap-BnB1QM5d.es.js} +6 -13
  4. package/dist/{cdcmap-c55ac1ea.es.js → cdcmap-D6CG2-Hb.es.js} +5 -12
  5. package/dist/{cdcmap-31a33da1.es.js → cdcmap-MXgURbdZ.es.js} +6 -13
  6. package/dist/{cdcmap-1a1724a1.es.js → cdcmap-dgT_1dIT.es.js} +136 -151
  7. package/dist/cdcmap.js +56991 -53706
  8. package/examples/example-city-state.json +9 -1
  9. package/examples/multi-country-centering.json +45 -0
  10. package/examples/private/c.json +290 -0
  11. package/examples/private/canvas-city-hover.json +787 -0
  12. package/examples/private/colors-2.json +221 -0
  13. package/examples/private/colors.json +221 -0
  14. package/examples/private/d.json +345 -0
  15. package/examples/private/g.json +1 -0
  16. package/examples/private/h.json +105911 -0
  17. package/examples/private/measles-data.json +378 -0
  18. package/examples/private/measles.json +211 -0
  19. package/examples/private/north-dakota.json +1132 -0
  20. package/examples/private/state-with-pattern.json +883 -0
  21. package/index.html +36 -34
  22. package/package.json +26 -5
  23. package/src/CdcMap.tsx +23 -8
  24. package/src/CdcMapComponent.tsx +238 -308
  25. package/src/_stories/CdcMap.ColumnWrap.stories.tsx +31 -0
  26. package/src/_stories/CdcMap.DistrictOfColumbia.stories.tsx +320 -0
  27. package/src/_stories/CdcMap.Editor.stories.tsx +3371 -0
  28. package/src/_stories/CdcMap.Filters.stories.tsx +2 -2
  29. package/src/_stories/CdcMap.Legend.Gradient.stories.tsx +3 -3
  30. package/src/_stories/CdcMap.Legend.stories.tsx +7 -4
  31. package/src/_stories/CdcMap.Patterns.stories.tsx +2 -2
  32. package/src/_stories/CdcMap.SmallMultiples.stories.tsx +35 -0
  33. package/src/_stories/CdcMap.Table.stories.tsx +2 -2
  34. package/src/_stories/CdcMap.stories.tsx +37 -9
  35. package/src/_stories/GoogleMap.stories.tsx +2 -2
  36. package/src/_stories/UsaMap.NoData.stories.tsx +2 -2
  37. package/src/_stories/_mock/column-wrap-test.json +265 -0
  38. package/src/_stories/_mock/equal-number.json +1109 -0
  39. package/src/_stories/_mock/multi-country-hide.json +78 -0
  40. package/src/_stories/_mock/multi-country.json +95 -0
  41. package/src/_stories/_mock/multi-state.json +887 -20403
  42. package/src/_stories/_mock/small_multiples/multi-state-small-multiples.json +8399 -0
  43. package/src/_stories/_mock/small_multiples/region-small-multiples.json +657 -0
  44. package/src/_stories/_mock/small_multiples/wastewater-map-small-multiples.json +221 -0
  45. package/src/_stories/_mock/us-bubble-cities.json +306 -0
  46. package/src/_stories/_mock/usa-state-gradient.json +2 -4
  47. package/src/components/BubbleList.tsx +17 -13
  48. package/src/components/CityList.tsx +85 -107
  49. package/src/components/EditorPanel/components/EditorPanel.tsx +787 -709
  50. package/src/components/EditorPanel/components/HexShapeSettings.tsx +58 -95
  51. package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +34 -42
  52. package/src/components/EditorPanel/components/Panels/Panel.SmallMultiples.tsx +354 -0
  53. package/src/components/EditorPanel/components/Panels/index.tsx +3 -1
  54. package/src/components/Geo.tsx +22 -3
  55. package/src/components/Legend/components/Legend.tsx +76 -40
  56. package/src/components/Legend/components/LegendGroup/Legend.Group.tsx +10 -7
  57. package/src/components/Legend/components/index.scss +1 -1
  58. package/src/components/MapContainer.tsx +52 -0
  59. package/src/components/MapControls.tsx +44 -0
  60. package/src/components/NavigationMenu.tsx +27 -15
  61. package/src/components/SmallMultiples/SmallMultipleTile.tsx +163 -0
  62. package/src/components/SmallMultiples/SmallMultiples.css +32 -0
  63. package/src/components/SmallMultiples/SmallMultiples.tsx +150 -0
  64. package/src/components/SmallMultiples/SynchronizedTooltip.tsx +105 -0
  65. package/src/components/SmallMultiples/index.tsx +3 -0
  66. package/src/components/UsaMap/components/SingleState/SingleState.CountyOutput.tsx +36 -4
  67. package/src/components/UsaMap/components/TerritoriesSection.tsx +26 -12
  68. package/src/components/UsaMap/components/Territory/Territory.Hexagon.tsx +30 -4
  69. package/src/components/UsaMap/components/Territory/Territory.Rectangle.tsx +23 -4
  70. package/src/components/UsaMap/components/Territory/TerritoryShape.ts +6 -0
  71. package/src/components/UsaMap/components/UsaMap.County.tsx +123 -37
  72. package/src/components/UsaMap/components/UsaMap.Region.tsx +36 -5
  73. package/src/components/UsaMap/components/UsaMap.SingleState.tsx +30 -10
  74. package/src/components/UsaMap/components/UsaMap.State.tsx +53 -12
  75. package/src/components/UsaMap/helpers/map.ts +4 -4
  76. package/src/components/UsaMap/helpers/shapes.ts +9 -6
  77. package/src/components/WorldMap/WorldMap.tsx +193 -35
  78. package/src/components/ZoomControls.tsx +6 -9
  79. package/src/context/LegendMemoContext.tsx +30 -0
  80. package/src/context.ts +1 -40
  81. package/src/data/initial-state.js +153 -130
  82. package/src/data/supported-geos.js +25 -78
  83. package/src/helpers/addUIDs.ts +13 -2
  84. package/src/helpers/applyColorToLegend.ts +140 -20
  85. package/src/helpers/applyLegendToRow.ts +10 -6
  86. package/src/helpers/componentHelpers.ts +8 -0
  87. package/src/helpers/constants.ts +12 -14
  88. package/src/helpers/dataTableHelpers.ts +6 -0
  89. package/src/helpers/displayGeoName.ts +18 -3
  90. package/src/helpers/generateRuntimeLegend.ts +44 -10
  91. package/src/helpers/generateRuntimeLegendHash.ts +4 -2
  92. package/src/helpers/getColumnNames.ts +1 -1
  93. package/src/helpers/getCountriesPicked.ts +103 -0
  94. package/src/helpers/getMapContainerClasses.ts +7 -0
  95. package/src/helpers/getPatternForRow.ts +33 -0
  96. package/src/helpers/getStatesPicked.ts +8 -5
  97. package/src/helpers/index.ts +3 -3
  98. package/src/helpers/isLegendItemDisabled.ts +16 -0
  99. package/src/helpers/mapObserverHelpers.ts +40 -0
  100. package/src/helpers/resetLegendToggles.ts +3 -2
  101. package/src/helpers/smallMultiplesHelpers.ts +359 -0
  102. package/src/helpers/tests/titleCase.test.ts +76 -0
  103. package/src/helpers/titleCase.ts +13 -13
  104. package/src/helpers/toggleLegendActive.ts +6 -11
  105. package/src/helpers/urlDataHelpers.ts +70 -0
  106. package/src/hooks/useCountryZoom.tsx +241 -0
  107. package/src/hooks/useGeoClickHandler.ts +36 -2
  108. package/src/hooks/useLegendMemo.ts +17 -0
  109. package/src/hooks/useMapLayers.tsx +5 -4
  110. package/src/hooks/useProgrammaticMapTooltip.ts +110 -0
  111. package/src/hooks/useResizeObserver.ts +5 -2
  112. package/src/hooks/useStateZoom.tsx +30 -8
  113. package/src/hooks/useSynchronizedGeographies.ts +56 -0
  114. package/src/hooks/useTooltip.ts +1 -2
  115. package/src/index.jsx +1 -2
  116. package/src/scss/editor-panel.scss +4 -440
  117. package/src/scss/main.scss +1 -1
  118. package/src/scss/map.scss +12 -15
  119. package/src/store/map.actions.ts +7 -7
  120. package/src/store/map.reducer.ts +17 -6
  121. package/src/test/CdcMap.test.jsx +11 -0
  122. package/src/types/MapConfig.ts +46 -18
  123. package/src/types/MapContext.ts +6 -7
  124. package/src/types/runtimeLegend.ts +17 -1
  125. package/vite.config.js +2 -7
  126. package/vitest.config.ts +16 -0
  127. package/src/components/DataTable.tsx +0 -385
  128. package/src/components/EditorPanel/components/Inputs.tsx +0 -59
  129. package/src/coreStyles_map.scss +0 -3
  130. package/src/helpers/colorDistributions.ts +0 -12
  131. package/src/helpers/generateColorsArray.ts +0 -14
  132. package/src/helpers/tests/generateColorsArray.test.ts +0 -18
  133. package/src/helpers/tests/generateRuntimeLegendHash.test.ts +0 -11
  134. package/src/hooks/useActiveElement.ts +0 -19
  135. package/src/scss/mixins.scss +0 -47
  136. package/src/types/Annotations.ts +0 -24
  137. /package/dist/{cdcmap-548642e6.es.js → cdcmap-Ct2SB0vL.es.js} +0 -0
@@ -7,8 +7,12 @@ import { Mercator } from '@visx/geo'
7
7
 
8
8
  // Cdc Components
9
9
  import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
10
+ import { publishAnalyticsEvent } from '@cdc/core/helpers/metrics/helpers'
11
+ import { getVizTitle, getVizSubType } from '@cdc/core/helpers/metrics/utils'
10
12
  import ConfigContext from '../../../context'
13
+ import { useLegendMemoContext } from '../../../context/LegendMemoContext'
11
14
  import Annotation from '../../Annotation'
15
+ import SmallMultiples from '../../SmallMultiples/SmallMultiples'
12
16
 
13
17
  // Data
14
18
  import { supportedTerritories } from '../../../data/supported-geos'
@@ -20,6 +24,7 @@ import useGeoClickHandler from '../../../hooks/useGeoClickHandler'
20
24
  import useApplyTooltipsToGeo from '../../../hooks/useApplyTooltipsToGeo'
21
25
  import './UsaMap.Region.styles.css'
22
26
  import { applyLegendToRow } from '../../../helpers/applyLegendToRow'
27
+ import { useSynchronizedGeographies } from '../../../hooks/useSynchronizedGeographies'
23
28
 
24
29
  type TerritoryRectProps = {
25
30
  posX?: number
@@ -51,10 +56,12 @@ const Rect: React.FC<RectProps> = ({ label, text, stroke, strokeWidth, ...props
51
56
  }
52
57
 
53
58
  const UsaRegionMap = () => {
54
- const { data, config, tooltipId, legendMemo, legendSpecialClassLastMemo, runtimeLegend } = useContext(ConfigContext)
59
+ const { runtimeData, config, tooltipId, runtimeLegend, interactionLabel } = useContext(ConfigContext)
60
+ const { legendMemo, legendSpecialClassLastMemo } = useLegendMemoContext()
55
61
  const [focusedStates, setFocusedStates] = useState(null)
56
62
  const { geoClickHandler } = useGeoClickHandler()
57
63
  const { applyTooltipsToGeo } = useApplyTooltipsToGeo()
64
+ const { getSyncProps, syncHandlers } = useSynchronizedGeographies()
58
65
  const { general } = config
59
66
  const { displayStateLabels, territoriesLabel, displayAsHex, type } = general
60
67
  const tooltipInteractionType = config.tooltips.appearanceType
@@ -75,22 +82,27 @@ const UsaRegionMap = () => {
75
82
 
76
83
  useEffect(() => {
77
84
  // Territories need to show up if they're in the data at all, not just if they're "active". That's why this is different from Cities
78
- const territoriesList = territoriesKeys.filter(key => data[key])
85
+ const territoriesList = territoriesKeys.filter(key => runtimeData[key])
79
86
 
80
87
  setTerritoriesData(territoriesList)
81
- }, [data])
88
+ }, [runtimeData])
82
89
 
83
90
  if (!focusedStates) {
84
91
  return <></>
85
92
  }
86
93
 
94
+ // Early return for small multiples rendering
95
+ if (config.smallMultiples?.mode) {
96
+ return <SmallMultiples />
97
+ }
98
+
87
99
  const geoStrokeColor = getGeoStrokeColor(config)
88
100
  const geoFillColor = getGeoFillColor(config)
89
101
 
90
102
  const territories = territoriesData.map(territory => {
91
103
  const Shape = Rect
92
104
 
93
- const territoryData = data[territory]
105
+ const territoryData = runtimeData[territory]
94
106
 
95
107
  let toolTip: string
96
108
 
@@ -162,7 +174,7 @@ const UsaRegionMap = () => {
162
174
 
163
175
  if (!geoKey) return
164
176
 
165
- const geoData = data[geoKey]
177
+ const geoData = runtimeData[geoKey]
166
178
 
167
179
  let legendColors
168
180
  // Once we receive data for this geographic item, setup variables.
@@ -208,6 +220,7 @@ const UsaRegionMap = () => {
208
220
 
209
221
  return (
210
222
  <g
223
+ {...getSyncProps(geoKey)}
211
224
  key={key}
212
225
  className='geo-group'
213
226
  style={styles}
@@ -215,6 +228,24 @@ const UsaRegionMap = () => {
215
228
  data-tooltip-id={`tooltip__${tooltipId}`}
216
229
  data-tooltip-html={toolTip}
217
230
  tabIndex={-1}
231
+ onMouseEnter={e => {
232
+ // Track hover analytics event if this is a new location
233
+ const locationName = geoDisplayName.replace(/[^a-zA-Z0-9]/g, '_')
234
+ publishAnalyticsEvent({
235
+ vizType: config.type,
236
+ vizSubType: getVizSubType(config),
237
+ eventType: `map_hover`,
238
+ eventAction: 'hover',
239
+ eventLabel: interactionLabel,
240
+ vizTitle: getVizTitle(config),
241
+ location: geoDisplayName,
242
+ specifics: `location: ${locationName?.toLowerCase()}`
243
+ })
244
+ syncHandlers.onMouseEnter(geoKey, e.clientY)
245
+ }}
246
+ onMouseLeave={() => {
247
+ syncHandlers.onMouseLeave()
248
+ }}
218
249
  >
219
250
  <path tabIndex={-1} className='single-geo' stroke={geoStrokeColor} strokeWidth={1} d={path} />
220
251
  <g id={`region-${index + 1}-label`}>
@@ -13,6 +13,7 @@ import ZoomControls from '../../ZoomControls'
13
13
  import { MapContext } from '../../../types/MapContext'
14
14
  import useStateZoom from '../../../hooks/useStateZoom'
15
15
  import { Text } from '@visx/text'
16
+ import SmallMultiples from '../../SmallMultiples/SmallMultiples'
16
17
 
17
18
  import './UsaMap.SingleState.styles.css'
18
19
 
@@ -37,11 +38,12 @@ const SingleStateMap: React.FC = () => {
37
38
  position,
38
39
  topoData,
39
40
  scale,
40
- translate
41
+ translate,
42
+ useDynamicViewbox
41
43
  } = useContext<MapContext>(ConfigContext)
42
44
 
43
45
  const dispatch = useContext(MapDispatchContext)
44
- const { handleMoveEnd, handleZoomIn, handleZoomOut, handleReset, projection } = useStateZoom(topoData)
46
+ const { handleMoveEnd, handleZoomIn, handleZoomOut, handleZoomReset, projection, bounds } = useStateZoom(topoData)
45
47
 
46
48
  // Memoize statesPicked to prevent creating new arrays on every render
47
49
  const statesPicked = useMemo(() => {
@@ -62,6 +64,19 @@ const SingleStateMap: React.FC = () => {
62
64
  const geoStrokeColor = getGeoStrokeColor(config)
63
65
  const path = geoPath().projection(projection)
64
66
 
67
+ const dynamicViewBox = useMemo(() => {
68
+ if (!useDynamicViewbox || !bounds) {
69
+ return SVG_VIEWBOX
70
+ }
71
+
72
+ const x = Math.floor(bounds[0][0] - SVG_PADDING)
73
+ const y = Math.floor(bounds[0][1] - SVG_PADDING)
74
+ const width = Math.ceil(bounds[1][0] - bounds[0][0] + SVG_PADDING * 2)
75
+ const height = Math.ceil(bounds[1][1] - bounds[0][1] + SVG_PADDING * 2)
76
+
77
+ return `${x} ${y} ${width} ${height}`
78
+ }, [useDynamicViewbox, bounds])
79
+
65
80
  useEffect(() => {
66
81
  let currentYear = getCurrentTopoYear(config, runtimeFilters)
67
82
 
@@ -70,7 +85,7 @@ const SingleStateMap: React.FC = () => {
70
85
  dispatch({ type: 'SET_TOPO_DATA', payload: response })
71
86
  })
72
87
  }
73
- }, [runtimeFilters?.length, topoData?.year])
88
+ }, [config.general.countyCensusYear, config.general.filterControlsCountyYear, JSON.stringify(runtimeFilters)])
74
89
 
75
90
  if (!isTopoReady(topoData, config, runtimeFilters)) {
76
91
  return (
@@ -80,6 +95,11 @@ const SingleStateMap: React.FC = () => {
80
95
  )
81
96
  }
82
97
 
98
+ // Early return for small multiples rendering
99
+ if (config.smallMultiples?.mode) {
100
+ return <SmallMultiples />
101
+ }
102
+
83
103
  const checkForNoData = () => {
84
104
  // If no statesPicked, return true
85
105
  if (statesPicked?.every(sp => !sp.fipsCode)) return true
@@ -129,9 +149,9 @@ const SingleStateMap: React.FC = () => {
129
149
  }
130
150
  return (
131
151
  <ErrorBoundary component='SingleStateMap'>
132
- {statesPicked.length && config.general.allowMapZoom && statesPicked.some(sp => sp.fipsCode) && (
152
+ {!!statesPicked.length && config.general.allowMapZoom && statesPicked.some(sp => sp.fipsCode) && (
133
153
  <svg
134
- viewBox={SVG_VIEWBOX}
154
+ viewBox={dynamicViewBox}
135
155
  preserveAspectRatio='xMinYMin'
136
156
  className='svg-container'
137
157
  role='img'
@@ -192,9 +212,9 @@ const SingleStateMap: React.FC = () => {
192
212
  </ZoomableGroup>
193
213
  </svg>
194
214
  )}
195
- {statesPicked && !config.general.allowMapZoom && statesPicked.some(sp => sp.fipsCode) && (
215
+ {!!statesPicked && !config.general.allowMapZoom && statesPicked.some(sp => sp.fipsCode) && (
196
216
  <svg
197
- viewBox={SVG_VIEWBOX}
217
+ viewBox={dynamicViewBox}
198
218
  preserveAspectRatio='xMinYMin'
199
219
  className='svg-container'
200
220
  role='img'
@@ -247,7 +267,7 @@ const SingleStateMap: React.FC = () => {
247
267
 
248
268
  {checkForNoData() && (
249
269
  <svg
250
- viewBox={SVG_VIEWBOX}
270
+ viewBox={dynamicViewBox}
251
271
  preserveAspectRatio='xMinYMin'
252
272
  className='svg-container'
253
273
  role='img'
@@ -262,7 +282,7 @@ const SingleStateMap: React.FC = () => {
262
282
  fontSize={18}
263
283
  style={{ fontSize: '28px', height: '18px' }}
264
284
  >
265
- {config.general.noStateFoundMessage}
285
+ {config.general.noDataMessage}
266
286
  </Text>
267
287
  </svg>
268
288
  )}
@@ -270,7 +290,7 @@ const SingleStateMap: React.FC = () => {
270
290
  // prettier-ignore
271
291
  handleZoomIn={handleZoomIn}
272
292
  handleZoomOut={handleZoomOut}
273
- handleReset={handleReset}
293
+ handleZoomReset={handleZoomReset}
274
294
  />
275
295
  </ErrorBoundary>
276
296
  )
@@ -1,6 +1,8 @@
1
1
  import React, { useState, useEffect, useContext, useRef } from 'react'
2
2
 
3
3
  import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
4
+ import { publishAnalyticsEvent } from '@cdc/core/helpers/metrics/helpers'
5
+ import { getVizTitle, getVizSubType } from '@cdc/core/helpers/metrics/utils'
4
6
 
5
7
  // United States Topojson resources
6
8
  import hexTopoJSON from '../data/us-hex-topo.json'
@@ -20,9 +22,12 @@ import Annotation from '../../Annotation'
20
22
  import Territory from './Territory'
21
23
 
22
24
  import ConfigContext, { MapDispatchContext } from '../../../context'
25
+ import { useLegendMemoContext } from '../../../context/LegendMemoContext'
23
26
  import { MapContext } from '../../../types/MapContext'
24
27
  import { checkColorContrast, getContrastColor, outlinedTextColor } from '@cdc/core/helpers/cove/accessibility'
25
28
  import TerritoriesSection from './TerritoriesSection'
29
+ import SmallMultiples from '../../SmallMultiples'
30
+ import { useSynchronizedGeographies } from '../../../hooks/useSynchronizedGeographies'
26
31
 
27
32
  import { isMobileStateLabelViewport } from '@cdc/core/helpers/viewports'
28
33
  import { APP_FONT_COLOR } from '@cdc/core/helpers/constants'
@@ -69,20 +74,25 @@ const nudges = {
69
74
 
70
75
  const UsaMap = () => {
71
76
  const {
72
- data,
77
+ runtimeData,
73
78
  setSharedFilterValue,
74
79
  config,
75
80
  setConfig,
76
81
  tooltipId,
77
82
  mapId,
78
83
  logo,
79
- legendMemo,
80
- legendSpecialClassLastMemo,
81
84
  currentViewport,
85
+ vizViewport,
86
+ dimensions,
82
87
  translate,
83
- runtimeLegend
88
+ runtimeLegend,
89
+ interactionLabel
84
90
  } = useContext<MapContext>(ConfigContext)
85
91
 
92
+ const { legendMemo, legendSpecialClassLastMemo } = useLegendMemoContext()
93
+
94
+ const { getSyncProps, syncHandlers } = useSynchronizedGeographies()
95
+
86
96
  let isFilterValueSupported = false
87
97
  const { general, columns, tooltips, hexMap, map, annotations } = config
88
98
  const { displayAsHex } = general
@@ -120,14 +130,14 @@ const UsaMap = () => {
120
130
 
121
131
  const legendMemoUpdated = focusedStates?.every(geo => {
122
132
  const geoKey = geo.properties.iso
123
- const geoData = data[geoKey]
133
+ const geoData = runtimeData[geoKey]
124
134
  const hash = hashObj(geoData)
125
135
  return legendMemo.current.has(hash)
126
136
  })
127
137
 
128
138
  // we use dataRef so that we can use the old data when legendMemo has not been updated yet
129
139
  // prevents flickering of the map when filter is changed
130
- if (legendMemoUpdated) dataRef.current = data
140
+ if (legendMemoUpdated) dataRef.current = runtimeData
131
141
 
132
142
  useEffect(() => {
133
143
  const fetchData = async () => {
@@ -153,18 +163,22 @@ const UsaMap = () => {
153
163
  setTerritoriesData(territoriesKeys)
154
164
  } else {
155
165
  // Territories need to show up if they're in the data at all, not just if they're "active". That's why this is different from Cities
156
- const territoriesList = territoriesKeys.filter(key => data?.[key])
166
+ const territoriesList = territoriesKeys.filter(key => runtimeData?.[key])
157
167
  setTerritoriesData(territoriesList)
158
168
  }
159
- }, [data, dataRef.current, general.territoriesAlwaysShow])
169
+ }, [runtimeData, dataRef.current, general.territoriesAlwaysShow])
160
170
 
161
171
  const geoStrokeColor = getGeoStrokeColor(config)
162
172
  const geoFillColor = getGeoFillColor(config)
163
173
 
174
+ // Chrome needs wider stroke for small maps or it doesn't render the pattern
175
+ const mapWidth = dimensions?.[0] || 880
176
+ const patternLinesStrokeWidth = mapWidth < 200 ? 1.75 : mapWidth < 375 ? 1.25 : 0.75
177
+
164
178
  const territories = territoriesData.map((territory, territoryIndex) => {
165
179
  const Shape = displayAsHex ? Territory.Hexagon : Territory.Rectangle
166
180
 
167
- const territoryData = data?.[territory]
181
+ const territoryData = runtimeData?.[territory]
168
182
 
169
183
  let toolTip
170
184
 
@@ -186,6 +200,8 @@ const UsaMap = () => {
186
200
  strokeColor='#fff'
187
201
  territoryData={territoryData}
188
202
  backgroundColor={styles.fill}
203
+ getSyncProps={getSyncProps}
204
+ syncHandlers={syncHandlers}
189
205
  />
190
206
  )
191
207
 
@@ -238,6 +254,8 @@ const UsaMap = () => {
238
254
  territoryData={territoryData}
239
255
  tabIndex={-1}
240
256
  backgroundColor={styles.fill}
257
+ getSyncProps={getSyncProps}
258
+ syncHandlers={syncHandlers}
241
259
  />
242
260
  )
243
261
  }
@@ -252,6 +270,10 @@ const UsaMap = () => {
252
270
  return <></>
253
271
  }
254
272
 
273
+ if (config.smallMultiples?.mode) {
274
+ return <SmallMultiples />
275
+ }
276
+
255
277
  // Constructs and displays markup for all geos on the map (except territories right now)
256
278
  const constructGeoJsx = (geographies, projection) => {
257
279
  let showLabel = general.displayStateLabels
@@ -290,7 +312,7 @@ const UsaMap = () => {
290
312
 
291
313
  if (!geoKey) return
292
314
 
293
- const geoData = data?.[geoKey]
315
+ const geoData = runtimeData?.[geoKey]
294
316
 
295
317
  let legendColors
296
318
 
@@ -433,6 +455,7 @@ const UsaMap = () => {
433
455
  return (
434
456
  <g data-name={geoName} key={key} tabIndex={-1}>
435
457
  <g
458
+ {...getSyncProps(geoKey)}
436
459
  className='geo-group'
437
460
  style={styles}
438
461
  onClick={() => geoClickHandler(geoDisplayName, geoData)}
@@ -440,6 +463,24 @@ const UsaMap = () => {
440
463
  data-tooltip-id={`tooltip__${tooltipId}`}
441
464
  data-tooltip-html={tooltip}
442
465
  tabIndex={-1}
466
+ onMouseEnter={e => {
467
+ // Track hover analytics event if this is a new location
468
+ const locationName = geoDisplayName.replace(/[^a-zA-Z0-9]/g, '_')
469
+ publishAnalyticsEvent({
470
+ vizType: config.type,
471
+ vizSubType: getVizSubType(config),
472
+ eventType: `map_hover`,
473
+ eventAction: 'hover',
474
+ eventLabel: interactionLabel,
475
+ vizTitle: getVizTitle(config),
476
+ location: geoDisplayName,
477
+ specifics: `location: ${locationName?.toLowerCase()}`
478
+ })
479
+ syncHandlers.onMouseEnter(geoKey, e.clientY)
480
+ }}
481
+ onMouseLeave={() => {
482
+ syncHandlers.onMouseLeave()
483
+ }}
443
484
  >
444
485
  {/* state path */}
445
486
  <path tabIndex={-1} className='single-geo' strokeWidth={1} d={path} />
@@ -481,7 +522,7 @@ const UsaMap = () => {
481
522
  height={patternSizes[size] ?? 6}
482
523
  width={patternSizes[size] ?? 6}
483
524
  stroke={patternColor}
484
- strokeWidth={0.75}
525
+ strokeWidth={patternLinesStrokeWidth}
485
526
  orientation={['diagonalRightToLeft']}
486
527
  />
487
528
  )}
@@ -572,7 +613,7 @@ const UsaMap = () => {
572
613
  <text
573
614
  x={x}
574
615
  y={y}
575
- fontSize={isMobileStateLabelViewport(currentViewport) ? 16 : 13}
616
+ fontSize={isMobileStateLabelViewport(vizViewport) ? 16 : 13}
576
617
  fontWeight={900}
577
618
  strokeWidth='1'
578
619
  paintOrder='stroke'
@@ -1,7 +1,7 @@
1
1
  import { feature } from 'topojson-client'
2
2
  import usExtendedGeography from './../data/us-extended-geography.json'
3
3
 
4
- export const getCountyTopoURL = year => {
4
+ const getCountyTopoURL = year => {
5
5
  return `https://www.cdc.gov/TemplatePackage/contrib/data/county-topography/cb_${year}_us_county_20m.json`
6
6
  }
7
7
 
@@ -85,7 +85,7 @@ export const isTopoReady = (topoData, state, runtimeFilters) => {
85
85
  return topoData?.year && (!currentYear || currentYear === topoData?.year)
86
86
  }
87
87
 
88
- export const hasMoreThanFromHash = (data: { [key: string]: any }): boolean => {
88
+ const hasMoreThanFromHash = (data: { [key: string]: any }): boolean => {
89
89
  // Get all keys of the data object
90
90
  const keys = Object.keys(data)
91
91
 
@@ -96,7 +96,7 @@ export const hasMoreThanFromHash = (data: { [key: string]: any }): boolean => {
96
96
  return otherKeys.length > 0
97
97
  }
98
98
 
99
- export const getFilterControllingStatesPicked = (state, runtimeData) => {
99
+ export const getFilterControllingStatesPicked = (state, runtimeData): string[] => {
100
100
  if (!state.general.filterControlsStatesPicked || !runtimeData) {
101
101
  return state?.general?.statesPicked?.map(sp => sp.stateName) || []
102
102
  } else {
@@ -111,7 +111,7 @@ export const getFilterControllingStatesPicked = (state, runtimeData) => {
111
111
  } else if (statesPickedFromFilter) {
112
112
  return [statesPickedFromFilter]
113
113
  } else {
114
- return state?.general?.statesPicked?.map(sp => sp.stateName) || ['Alabama']
114
+ return state?.general?.statesPicked?.map(sp => sp.stateName) || []
115
115
  }
116
116
  }
117
117
  return state?.general?.statesPicked?.map(sp => sp.stateName) || []
@@ -1,4 +1,4 @@
1
- export const drawCircle = (circle, context, state) => {
1
+ const drawCircle = (circle, context, state) => {
2
2
  const percentOfOriginalSize = 0.75
3
3
  const scaleVal = 1
4
4
  const adjustedGeoRadius =
@@ -12,7 +12,8 @@ export const drawCircle = (circle, context, state) => {
12
12
  context.fill()
13
13
  context.stroke()
14
14
  }
15
- export const drawSquare = (square, context, state) => {
15
+
16
+ const drawSquare = (square, context, state) => {
16
17
  const percentOfOriginalSize = 0.75
17
18
  const scaleVal = 1.75
18
19
  const sideLength = square.size * scaleVal
@@ -32,7 +33,7 @@ export const drawSquare = (square, context, state) => {
32
33
  context.stroke()
33
34
  }
34
35
 
35
- export const drawDiamond = (diamond, context, state) => {
36
+ const drawDiamond = (diamond, context, state) => {
36
37
  const percentOfOriginalSize = 0.75
37
38
  const scaleVal = 2.2
38
39
  const fullSize = diamond.size * scaleVal
@@ -69,7 +70,8 @@ export const drawDiamond = (diamond, context, state) => {
69
70
  context.fill()
70
71
  context.stroke()
71
72
  }
72
- export const drawTriangle = (triangle, context, state) => {
73
+
74
+ const drawTriangle = (triangle, context, state) => {
73
75
  const percentOfOriginalSize = 0.75
74
76
  const scaleVal = 2.2
75
77
  const baseLength = triangle.size * scaleVal
@@ -102,7 +104,8 @@ export const drawTriangle = (triangle, context, state) => {
102
104
  context.fill()
103
105
  context.stroke()
104
106
  }
105
- export const drawStar = (star, context, state) => {
107
+
108
+ const drawStar = (star, context, state) => {
106
109
  const percentOfOriginalSize = 0.75
107
110
  const scaleVal = 2.2
108
111
  const spikes = star.spikes
@@ -155,7 +158,7 @@ export const drawStar = (star, context, state) => {
155
158
  context.stroke()
156
159
  }
157
160
 
158
- export const drawPin = (pin, ctx, state) => {
161
+ const drawPin = (pin, ctx, state) => {
159
162
  const scaleVal = 10
160
163
  const percentOfOriginalSize = 0.75
161
164
  const baseSize = pin.size * scaleVal