@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.
- package/.claude/agents/typescript-organizer.md +118 -0
- package/.claude/settings.local.json +30 -0
- package/dist/{cdcmap-fce76882.es.js → cdcmap-BnB1QM5d.es.js} +6 -13
- package/dist/{cdcmap-c55ac1ea.es.js → cdcmap-D6CG2-Hb.es.js} +5 -12
- package/dist/{cdcmap-31a33da1.es.js → cdcmap-MXgURbdZ.es.js} +6 -13
- package/dist/{cdcmap-1a1724a1.es.js → cdcmap-dgT_1dIT.es.js} +136 -151
- package/dist/cdcmap.js +56991 -53706
- package/examples/example-city-state.json +9 -1
- package/examples/multi-country-centering.json +45 -0
- package/examples/private/c.json +290 -0
- package/examples/private/canvas-city-hover.json +787 -0
- package/examples/private/colors-2.json +221 -0
- package/examples/private/colors.json +221 -0
- package/examples/private/d.json +345 -0
- package/examples/private/g.json +1 -0
- package/examples/private/h.json +105911 -0
- package/examples/private/measles-data.json +378 -0
- package/examples/private/measles.json +211 -0
- package/examples/private/north-dakota.json +1132 -0
- package/examples/private/state-with-pattern.json +883 -0
- package/index.html +36 -34
- package/package.json +26 -5
- package/src/CdcMap.tsx +23 -8
- package/src/CdcMapComponent.tsx +238 -308
- package/src/_stories/CdcMap.ColumnWrap.stories.tsx +31 -0
- package/src/_stories/CdcMap.DistrictOfColumbia.stories.tsx +320 -0
- package/src/_stories/CdcMap.Editor.stories.tsx +3371 -0
- package/src/_stories/CdcMap.Filters.stories.tsx +2 -2
- package/src/_stories/CdcMap.Legend.Gradient.stories.tsx +3 -3
- package/src/_stories/CdcMap.Legend.stories.tsx +7 -4
- package/src/_stories/CdcMap.Patterns.stories.tsx +2 -2
- package/src/_stories/CdcMap.SmallMultiples.stories.tsx +35 -0
- package/src/_stories/CdcMap.Table.stories.tsx +2 -2
- package/src/_stories/CdcMap.stories.tsx +37 -9
- package/src/_stories/GoogleMap.stories.tsx +2 -2
- package/src/_stories/UsaMap.NoData.stories.tsx +2 -2
- package/src/_stories/_mock/column-wrap-test.json +265 -0
- package/src/_stories/_mock/equal-number.json +1109 -0
- package/src/_stories/_mock/multi-country-hide.json +78 -0
- package/src/_stories/_mock/multi-country.json +95 -0
- package/src/_stories/_mock/multi-state.json +887 -20403
- package/src/_stories/_mock/small_multiples/multi-state-small-multiples.json +8399 -0
- package/src/_stories/_mock/small_multiples/region-small-multiples.json +657 -0
- package/src/_stories/_mock/small_multiples/wastewater-map-small-multiples.json +221 -0
- package/src/_stories/_mock/us-bubble-cities.json +306 -0
- package/src/_stories/_mock/usa-state-gradient.json +2 -4
- package/src/components/BubbleList.tsx +17 -13
- package/src/components/CityList.tsx +85 -107
- package/src/components/EditorPanel/components/EditorPanel.tsx +787 -709
- package/src/components/EditorPanel/components/HexShapeSettings.tsx +58 -95
- package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +34 -42
- package/src/components/EditorPanel/components/Panels/Panel.SmallMultiples.tsx +354 -0
- package/src/components/EditorPanel/components/Panels/index.tsx +3 -1
- package/src/components/Geo.tsx +22 -3
- package/src/components/Legend/components/Legend.tsx +76 -40
- package/src/components/Legend/components/LegendGroup/Legend.Group.tsx +10 -7
- package/src/components/Legend/components/index.scss +1 -1
- package/src/components/MapContainer.tsx +52 -0
- package/src/components/MapControls.tsx +44 -0
- package/src/components/NavigationMenu.tsx +27 -15
- package/src/components/SmallMultiples/SmallMultipleTile.tsx +163 -0
- package/src/components/SmallMultiples/SmallMultiples.css +32 -0
- package/src/components/SmallMultiples/SmallMultiples.tsx +150 -0
- package/src/components/SmallMultiples/SynchronizedTooltip.tsx +105 -0
- package/src/components/SmallMultiples/index.tsx +3 -0
- package/src/components/UsaMap/components/SingleState/SingleState.CountyOutput.tsx +36 -4
- package/src/components/UsaMap/components/TerritoriesSection.tsx +26 -12
- package/src/components/UsaMap/components/Territory/Territory.Hexagon.tsx +30 -4
- package/src/components/UsaMap/components/Territory/Territory.Rectangle.tsx +23 -4
- package/src/components/UsaMap/components/Territory/TerritoryShape.ts +6 -0
- package/src/components/UsaMap/components/UsaMap.County.tsx +123 -37
- package/src/components/UsaMap/components/UsaMap.Region.tsx +36 -5
- package/src/components/UsaMap/components/UsaMap.SingleState.tsx +30 -10
- package/src/components/UsaMap/components/UsaMap.State.tsx +53 -12
- package/src/components/UsaMap/helpers/map.ts +4 -4
- package/src/components/UsaMap/helpers/shapes.ts +9 -6
- package/src/components/WorldMap/WorldMap.tsx +193 -35
- package/src/components/ZoomControls.tsx +6 -9
- package/src/context/LegendMemoContext.tsx +30 -0
- package/src/context.ts +1 -40
- package/src/data/initial-state.js +153 -130
- package/src/data/supported-geos.js +25 -78
- package/src/helpers/addUIDs.ts +13 -2
- package/src/helpers/applyColorToLegend.ts +140 -20
- package/src/helpers/applyLegendToRow.ts +10 -6
- package/src/helpers/componentHelpers.ts +8 -0
- package/src/helpers/constants.ts +12 -14
- package/src/helpers/dataTableHelpers.ts +6 -0
- package/src/helpers/displayGeoName.ts +18 -3
- package/src/helpers/generateRuntimeLegend.ts +44 -10
- package/src/helpers/generateRuntimeLegendHash.ts +4 -2
- package/src/helpers/getColumnNames.ts +1 -1
- package/src/helpers/getCountriesPicked.ts +103 -0
- package/src/helpers/getMapContainerClasses.ts +7 -0
- package/src/helpers/getPatternForRow.ts +33 -0
- package/src/helpers/getStatesPicked.ts +8 -5
- package/src/helpers/index.ts +3 -3
- package/src/helpers/isLegendItemDisabled.ts +16 -0
- package/src/helpers/mapObserverHelpers.ts +40 -0
- package/src/helpers/resetLegendToggles.ts +3 -2
- package/src/helpers/smallMultiplesHelpers.ts +359 -0
- package/src/helpers/tests/titleCase.test.ts +76 -0
- package/src/helpers/titleCase.ts +13 -13
- package/src/helpers/toggleLegendActive.ts +6 -11
- package/src/helpers/urlDataHelpers.ts +70 -0
- package/src/hooks/useCountryZoom.tsx +241 -0
- package/src/hooks/useGeoClickHandler.ts +36 -2
- package/src/hooks/useLegendMemo.ts +17 -0
- package/src/hooks/useMapLayers.tsx +5 -4
- package/src/hooks/useProgrammaticMapTooltip.ts +110 -0
- package/src/hooks/useResizeObserver.ts +5 -2
- package/src/hooks/useStateZoom.tsx +30 -8
- package/src/hooks/useSynchronizedGeographies.ts +56 -0
- package/src/hooks/useTooltip.ts +1 -2
- package/src/index.jsx +1 -2
- package/src/scss/editor-panel.scss +4 -440
- package/src/scss/main.scss +1 -1
- package/src/scss/map.scss +12 -15
- package/src/store/map.actions.ts +7 -7
- package/src/store/map.reducer.ts +17 -6
- package/src/test/CdcMap.test.jsx +11 -0
- package/src/types/MapConfig.ts +46 -18
- package/src/types/MapContext.ts +6 -7
- package/src/types/runtimeLegend.ts +17 -1
- package/vite.config.js +2 -7
- package/vitest.config.ts +16 -0
- package/src/components/DataTable.tsx +0 -385
- package/src/components/EditorPanel/components/Inputs.tsx +0 -59
- package/src/coreStyles_map.scss +0 -3
- package/src/helpers/colorDistributions.ts +0 -12
- package/src/helpers/generateColorsArray.ts +0 -14
- package/src/helpers/tests/generateColorsArray.test.ts +0 -18
- package/src/helpers/tests/generateRuntimeLegendHash.test.ts +0 -11
- package/src/hooks/useActiveElement.ts +0 -19
- package/src/scss/mixins.scss +0 -47
- package/src/types/Annotations.ts +0 -24
- /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 {
|
|
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 =>
|
|
85
|
+
const territoriesList = territoriesKeys.filter(key => runtimeData[key])
|
|
79
86
|
|
|
80
87
|
setTerritoriesData(territoriesList)
|
|
81
|
-
}, [
|
|
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 =
|
|
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 =
|
|
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,
|
|
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
|
-
}, [
|
|
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={
|
|
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={
|
|
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={
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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 =>
|
|
166
|
+
const territoriesList = territoriesKeys.filter(key => runtimeData?.[key])
|
|
157
167
|
setTerritoriesData(territoriesList)
|
|
158
168
|
}
|
|
159
|
-
}, [
|
|
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 =
|
|
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 =
|
|
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={
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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) || [
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
161
|
+
const drawPin = (pin, ctx, state) => {
|
|
159
162
|
const scaleVal = 10
|
|
160
163
|
const percentOfOriginalSize = 0.75
|
|
161
164
|
const baseSize = pin.size * scaleVal
|