@cdc/map 4.25.7 → 4.25.10
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/settings.local.json +30 -0
- package/CLAUDE.local.md +0 -0
- package/dist/cdcmap.js +54785 -53159
- package/examples/private/c.json +290 -0
- package/examples/private/canvas-city-hover.json +787 -0
- package/examples/private/d.json +345 -0
- package/examples/private/filter-map.json +909 -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/rsv-data.json +532 -0
- package/examples/private/state-with-pattern.json +883 -0
- package/examples/private/test.json +222 -640
- package/index.html +1 -1
- package/package.json +26 -5
- package/src/CdcMap.tsx +28 -8
- package/src/CdcMapComponent.tsx +230 -306
- 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.Table.stories.tsx +2 -2
- package/src/_stories/CdcMap.stories.tsx +18 -11
- package/src/_stories/GoogleMap.stories.tsx +2 -2
- package/src/_stories/UsaMap.NoData.stories.tsx +2 -2
- package/src/_stories/_mock/equal-number.json +1109 -0
- package/src/_stories/_mock/multi-state.json +21389 -0
- package/src/_stories/_mock/us-bubble-cities.json +306 -0
- package/src/components/BubbleList.tsx +16 -12
- package/src/components/CityList.tsx +88 -110
- package/src/components/DataTable.tsx +44 -12
- package/src/components/EditorPanel/components/EditorPanel.tsx +201 -203
- package/src/components/EditorPanel/components/HexShapeSettings.tsx +3 -2
- package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +7 -5
- package/src/components/Geo.tsx +2 -0
- package/src/components/Legend/components/Legend.tsx +117 -93
- package/src/components/Legend/components/LegendGroup/Legend.Group.tsx +10 -7
- package/src/components/MapContainer.tsx +52 -0
- package/src/components/MapControls.tsx +44 -0
- package/src/components/Modal.tsx +2 -8
- package/src/components/NavigationMenu.tsx +13 -1
- package/src/components/UsaMap/components/SingleState/SingleState.CountyOutput.tsx +24 -7
- package/src/components/UsaMap/components/SingleState/SingleState.StateOutput.tsx +21 -15
- package/src/components/UsaMap/components/TerritoriesSection.tsx +2 -2
- package/src/components/UsaMap/components/UsaMap.County.tsx +112 -33
- package/src/components/UsaMap/components/UsaMap.Region.tsx +23 -5
- package/src/components/UsaMap/components/UsaMap.SingleState.tsx +38 -26
- package/src/components/UsaMap/components/UsaMap.State.tsx +28 -10
- package/src/components/UsaMap/helpers/map.ts +16 -8
- package/src/components/WorldMap/WorldMap.tsx +116 -11
- package/src/components/ZoomControls.tsx +6 -9
- package/src/context/LegendMemoContext.tsx +30 -0
- package/src/context.ts +1 -39
- package/src/data/initial-state.js +143 -128
- package/src/data/supported-geos.js +202 -4
- package/src/helpers/addUIDs.ts +8 -8
- package/src/helpers/applyColorToLegend.ts +122 -45
- package/src/helpers/applyLegendToRow.ts +15 -13
- package/src/helpers/componentHelpers.ts +8 -0
- package/src/helpers/constants.ts +12 -0
- package/src/helpers/dataTableHelpers.ts +6 -0
- package/src/helpers/displayGeoName.ts +12 -7
- package/src/helpers/formatLegendLocation.ts +1 -3
- package/src/helpers/generateRuntimeLegend.ts +192 -340
- package/src/helpers/generateRuntimeLegendHash.ts +4 -2
- package/src/helpers/getColumnNames.ts +1 -1
- package/src/helpers/getPatternForRow.ts +36 -0
- package/src/helpers/getStatesPicked.ts +14 -0
- package/src/helpers/handleMapAriaLabels.ts +2 -2
- package/src/helpers/index.ts +11 -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/toggleLegendActive.ts +6 -11
- package/src/helpers/urlDataHelpers.ts +70 -0
- package/src/hooks/useGeoClickHandler.ts +35 -1
- package/src/hooks/useLegendMemo.ts +17 -0
- package/src/hooks/useMapLayers.tsx +5 -4
- package/src/hooks/useStateZoom.tsx +137 -88
- package/src/hooks/useTooltip.ts +1 -2
- package/src/index.jsx +6 -3
- package/src/scss/main.scss +23 -12
- package/src/store/map.actions.ts +2 -2
- package/src/store/map.reducer.ts +21 -10
- package/src/test/CdcMap.test.jsx +11 -0
- package/src/types/MapConfig.ts +25 -17
- package/src/types/MapContext.ts +2 -8
- package/src/types/runtimeLegend.ts +12 -10
- package/vite.config.js +2 -7
- package/vitest.config.ts +16 -0
- package/src/_stories/_mock/floating-point.json +0 -427
- package/src/coreStyles_map.scss +0 -3
- package/src/helpers/colorDistributions.ts +0 -12
- package/src/helpers/generateColorsArray.ts +0 -14
- package/src/helpers/getStatePicked.ts +0 -8
- package/src/helpers/tests/generateColorsArray.test.ts +0 -18
- package/src/helpers/tests/generateRuntimeLegendHash.test.ts +0 -11
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import React, { useContext } from 'react'
|
|
1
|
+
import React, { useContext, useState } from 'react'
|
|
2
2
|
import ConfigContext from '../../../../context'
|
|
3
|
+
import { useLegendMemoContext } from '../../../../context/LegendMemoContext'
|
|
3
4
|
import { MapContext } from '../../../../types/MapContext'
|
|
4
5
|
import { getGeoFillColor, displayGeoName } from '../../../../helpers'
|
|
5
6
|
import useApplyTooltipsToGeo from '../../../../hooks/useApplyTooltipsToGeo'
|
|
6
7
|
import { applyLegendToRow } from '../../../../helpers/applyLegendToRow'
|
|
7
8
|
import useGeoClickHandler, { geoClickHandler } from '././../../../../hooks/useGeoClickHandler'
|
|
9
|
+
import { publishAnalyticsEvent } from '@cdc/core/helpers/metrics/helpers'
|
|
10
|
+
import { getVizTitle, getVizSubType } from '@cdc/core/helpers/metrics/utils'
|
|
8
11
|
|
|
9
12
|
interface CountyOutputProps {
|
|
10
13
|
counties: any[]
|
|
@@ -15,14 +18,15 @@ interface CountyOutputProps {
|
|
|
15
18
|
}
|
|
16
19
|
|
|
17
20
|
const CountyOutput: React.FC<CountyOutputProps> = ({ path, counties, scale, geoStrokeColor, tooltipId }) => {
|
|
18
|
-
const { config,
|
|
21
|
+
const { config, runtimeData, runtimeLegend, interactionLabel } = useContext<MapContext>(ConfigContext)
|
|
22
|
+
const { legendMemo, legendSpecialClassLastMemo } = useLegendMemoContext()
|
|
19
23
|
const { applyTooltipsToGeo } = useApplyTooltipsToGeo()
|
|
20
24
|
const geoFillColor = getGeoFillColor(config)
|
|
21
25
|
const { geoClickHandler } = useGeoClickHandler()
|
|
22
26
|
|
|
23
27
|
return (
|
|
24
28
|
<>
|
|
25
|
-
{counties.map(county => {
|
|
29
|
+
{counties.map((county, countyIndex) => {
|
|
26
30
|
// Map the name from the geo data with the appropriate key for the processed data
|
|
27
31
|
const geoKey = county.id
|
|
28
32
|
|
|
@@ -30,7 +34,7 @@ const CountyOutput: React.FC<CountyOutputProps> = ({ path, counties, scale, geoS
|
|
|
30
34
|
|
|
31
35
|
const countyPath = path(county)
|
|
32
36
|
|
|
33
|
-
const geoData =
|
|
37
|
+
const geoData = runtimeData[county.id]
|
|
34
38
|
let legendColors
|
|
35
39
|
|
|
36
40
|
// Once we receive data for this geographic item, setup variables.
|
|
@@ -68,13 +72,26 @@ const CountyOutput: React.FC<CountyOutputProps> = ({ path, counties, scale, geoS
|
|
|
68
72
|
return (
|
|
69
73
|
<g
|
|
70
74
|
key={`key--${county.id}`}
|
|
71
|
-
className={`county county--${geoDisplayName.split(' ').join('')} county--${
|
|
72
|
-
|
|
73
|
-
}`}
|
|
75
|
+
className={`county county--${geoDisplayName.split(' ').join('')} county--${geoData[config.columns.geo.name]
|
|
76
|
+
}`}
|
|
74
77
|
style={styles}
|
|
75
78
|
onClick={() => geoClickHandler(geoDisplayName, geoData)}
|
|
76
79
|
data-tooltip-id={`tooltip__${tooltipId}`}
|
|
77
80
|
data-tooltip-html={toolTip}
|
|
81
|
+
onMouseEnter={() => {
|
|
82
|
+
// Track hover analytics event if this is a new location
|
|
83
|
+
const locationName = geoDisplayName.replace(/[^a-zA-Z0-9]/g, '_')
|
|
84
|
+
publishAnalyticsEvent({
|
|
85
|
+
vizType: config.type,
|
|
86
|
+
vizSubType: getVizSubType(config),
|
|
87
|
+
eventType: `map_hover`,
|
|
88
|
+
eventAction: 'hover',
|
|
89
|
+
eventLabel: interactionLabel,
|
|
90
|
+
vizTitle: getVizTitle(config),
|
|
91
|
+
location: geoDisplayName,
|
|
92
|
+
specifics: `location: ${locationName?.toLowerCase()}`
|
|
93
|
+
})
|
|
94
|
+
}}
|
|
78
95
|
>
|
|
79
96
|
<path
|
|
80
97
|
tabIndex={-1}
|
|
@@ -1,37 +1,43 @@
|
|
|
1
1
|
import { useContext } from 'react'
|
|
2
|
-
import {
|
|
2
|
+
import { Topology } from 'topojson-client'
|
|
3
3
|
import ConfigContext from '../../../../context'
|
|
4
|
-
import {
|
|
4
|
+
import { getGeoStrokeColor } from '../../../../helpers/colors'
|
|
5
|
+
import { getStatesPicked } from '../../../../helpers/getStatesPicked'
|
|
5
6
|
|
|
6
7
|
type StateOutputProps = {
|
|
7
8
|
topoData: Topology
|
|
8
9
|
path: any
|
|
9
10
|
scale: any
|
|
11
|
+
runtimeData?: any
|
|
10
12
|
}
|
|
11
13
|
|
|
12
|
-
const StateOutput: React.FC<StateOutputProps> = ({ topoData, path, scale,
|
|
14
|
+
const StateOutput: React.FC<StateOutputProps> = ({ topoData, path, scale, runtimeData }: StateOutputProps) => {
|
|
13
15
|
const { config } = useContext(ConfigContext)
|
|
14
|
-
if (!topoData?.
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
if (!topoData?.states) return null
|
|
17
|
+
|
|
18
|
+
// Use filter-aware state selection instead of direct config access
|
|
19
|
+
const statesPickedData = getStatesPicked(config, runtimeData)
|
|
20
|
+
const stateNames = statesPickedData.map(sp => sp.stateName)
|
|
21
|
+
|
|
22
|
+
const statesPicked = topoData.states.filter(s => {
|
|
23
|
+
return stateNames.includes(s.properties.name)
|
|
17
24
|
})
|
|
18
25
|
|
|
19
26
|
const geoStrokeColor = getGeoStrokeColor(config)
|
|
20
|
-
const geoFillColor = getGeoFillColor(config)
|
|
21
27
|
|
|
22
|
-
|
|
28
|
+
const stateLines = statesPicked.map(s => path(s.geometry))
|
|
23
29
|
|
|
24
|
-
return (
|
|
30
|
+
return stateLines.map((line, index) => (
|
|
25
31
|
<g
|
|
26
|
-
key={
|
|
27
|
-
className='single-state'
|
|
28
|
-
style={{ fill:
|
|
32
|
+
key={`single-state-${index}`}
|
|
33
|
+
className='single-state pe-none'
|
|
34
|
+
style={{ fill: 'transparent' }}
|
|
29
35
|
stroke={geoStrokeColor}
|
|
30
|
-
strokeWidth={
|
|
36
|
+
strokeWidth={2 / scale}
|
|
31
37
|
>
|
|
32
|
-
<path tabIndex={-1} className='state-path' d={
|
|
38
|
+
<path tabIndex={-1} className='state-path' d={line} />
|
|
33
39
|
</g>
|
|
34
|
-
)
|
|
40
|
+
))
|
|
35
41
|
}
|
|
36
42
|
|
|
37
43
|
export default StateOutput
|
|
@@ -55,7 +55,7 @@ const TerritoriesSection: React.FC<TerritoriesSectionProps> = ({ territories, lo
|
|
|
55
55
|
<div className='d-flex flex-wrap' style={{ columnGap: '1.5rem' }}>
|
|
56
56
|
{(usTerritories.length > 0 || config.general.territoriesAlwaysShow) && (
|
|
57
57
|
<div>
|
|
58
|
-
<
|
|
58
|
+
<span className='territories-label'>U.S. territories</span>
|
|
59
59
|
<span
|
|
60
60
|
className={`mt-2 ${isMobileViewport ? 'mb-3' : 'mb-4'} d-flex territories`}
|
|
61
61
|
style={{ minWidth: `${usTerritories.length * SVG_WIDTH + (usTerritories.length - 1) * SVG_GAP}px` }}
|
|
@@ -66,7 +66,7 @@ const TerritoriesSection: React.FC<TerritoriesSectionProps> = ({ territories, lo
|
|
|
66
66
|
)}
|
|
67
67
|
{(freelyAssociatedStates.length > 0 || config.general.territoriesAlwaysShow) && (
|
|
68
68
|
<div>
|
|
69
|
-
<
|
|
69
|
+
<span className='territories-label'>Freely associated states</span>
|
|
70
70
|
<span
|
|
71
71
|
className={`mt-2 ${isMobileViewport ? 'mb-3' : 'mb-4'} d-flex territories`}
|
|
72
72
|
style={{
|
|
@@ -7,13 +7,17 @@ import Loading from '@cdc/core/components/Loading'
|
|
|
7
7
|
import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
8
8
|
import useMapLayers from '../../../hooks/useMapLayers'
|
|
9
9
|
import ConfigContext from '../../../context'
|
|
10
|
+
import { useLegendMemoContext } from '../../../context/LegendMemoContext'
|
|
10
11
|
import { drawShape, createShapeProperties } from '../helpers/shapes'
|
|
11
|
-
import { getGeoStrokeColor, handleMapAriaLabels, displayGeoName } from '../../../helpers'
|
|
12
|
+
import { getGeoStrokeColor, handleMapAriaLabels, displayGeoName, isLegendItemDisabled } from '../../../helpers'
|
|
13
|
+
import { supportedStatesFipsCodes } from '../../../data/supported-geos'
|
|
12
14
|
import useGeoClickHandler from '../../../hooks/useGeoClickHandler'
|
|
13
15
|
import { applyLegendToRow } from '../../../helpers/applyLegendToRow'
|
|
14
16
|
import useApplyTooltipsToGeo from '../../../hooks/useApplyTooltipsToGeo'
|
|
15
17
|
import { MapConfig } from '../../../types/MapConfig'
|
|
16
18
|
import { DEFAULT_MAP_BACKGROUND } from '../../../helpers/constants'
|
|
19
|
+
import { publishAnalyticsEvent } from '@cdc/core/helpers/metrics/helpers'
|
|
20
|
+
import { getVizTitle, getVizSubType } from '@cdc/core/helpers/metrics/utils'
|
|
17
21
|
|
|
18
22
|
const getCountyTopoURL = year => {
|
|
19
23
|
return `https://www.cdc.gov/TemplatePackage/contrib/data/county-topography/cb_${year}_us_county_20m.json`
|
|
@@ -130,17 +134,18 @@ const CountyMap = () => {
|
|
|
130
134
|
const {
|
|
131
135
|
container,
|
|
132
136
|
containerEl,
|
|
133
|
-
|
|
137
|
+
runtimeData,
|
|
134
138
|
runtimeFilters,
|
|
135
139
|
runtimeLegend,
|
|
136
140
|
setConfig,
|
|
137
141
|
config,
|
|
138
142
|
tooltipId,
|
|
139
143
|
tooltipRef,
|
|
140
|
-
|
|
141
|
-
legendSpecialClassLastMemo
|
|
144
|
+
interactionLabel
|
|
142
145
|
} = useContext(ConfigContext)
|
|
143
146
|
|
|
147
|
+
const { legendMemo, legendSpecialClassLastMemo } = useLegendMemoContext()
|
|
148
|
+
|
|
144
149
|
// CREATE STATE LINES
|
|
145
150
|
const geoStrokeColor = getGeoStrokeColor(config)
|
|
146
151
|
const { geoClickHandler } = useGeoClickHandler()
|
|
@@ -198,7 +203,7 @@ const CountyMap = () => {
|
|
|
198
203
|
const canvasRef = useRef()
|
|
199
204
|
|
|
200
205
|
// If runtimeData is not defined, show loader
|
|
201
|
-
if (!
|
|
206
|
+
if (!runtimeData || !isTopoReady(topoData, config, runtimeFilters)) {
|
|
202
207
|
return (
|
|
203
208
|
<div style={{ height: 300 }}>
|
|
204
209
|
<Loading />
|
|
@@ -206,10 +211,18 @@ const CountyMap = () => {
|
|
|
206
211
|
)
|
|
207
212
|
}
|
|
208
213
|
|
|
209
|
-
const runtimeKeys = Object.keys(
|
|
214
|
+
const runtimeKeys = Object.keys(runtimeData)
|
|
210
215
|
const lineWidth = 1
|
|
211
216
|
|
|
212
217
|
const onReset = () => {
|
|
218
|
+
publishAnalyticsEvent({
|
|
219
|
+
vizType: config.type,
|
|
220
|
+
vizSubType: getVizSubType(config),
|
|
221
|
+
eventType: 'map_reset_zoom_level',
|
|
222
|
+
eventAction: 'click',
|
|
223
|
+
eventLabel: interactionLabel,
|
|
224
|
+
vizTitle: getVizTitle(config)
|
|
225
|
+
})
|
|
213
226
|
setConfig({
|
|
214
227
|
...config,
|
|
215
228
|
mapPosition: { coordinates: [0, 30], zoom: 1 }
|
|
@@ -253,8 +266,8 @@ const CountyMap = () => {
|
|
|
253
266
|
break
|
|
254
267
|
}
|
|
255
268
|
}
|
|
256
|
-
if (county &&
|
|
257
|
-
geoClickHandler(displayGeoName(county.id),
|
|
269
|
+
if (county && runtimeData[county.id]) {
|
|
270
|
+
geoClickHandler(displayGeoName(county.id), runtimeData[county.id])
|
|
258
271
|
}
|
|
259
272
|
}
|
|
260
273
|
|
|
@@ -268,17 +281,36 @@ const CountyMap = () => {
|
|
|
268
281
|
|
|
269
282
|
// Redraw with focus on state
|
|
270
283
|
setFocus({ id: clickedState.id, index: focusIndex, center: geoCentroid(clickedState), feature: clickedState })
|
|
284
|
+
publishAnalyticsEvent({
|
|
285
|
+
vizType: config.type,
|
|
286
|
+
vizSubType: getVizSubType(config),
|
|
287
|
+
eventType: `zoom_in`,
|
|
288
|
+
eventAction: 'click',
|
|
289
|
+
eventLabel: interactionLabel,
|
|
290
|
+
vizTitle: getVizTitle(config),
|
|
291
|
+
specifics: `zoom_level: 3, location: ${clickedState.properties.name}`
|
|
292
|
+
})
|
|
271
293
|
}
|
|
272
294
|
if (config.general.type === 'us-geocode') {
|
|
273
295
|
const geoRadius = (config.visual.geoCodeCircleSize || 5) * (focus.id ? 2 : 1)
|
|
274
296
|
let clickedGeo
|
|
275
297
|
for (let i = 0; i < runtimeKeys.length; i++) {
|
|
276
298
|
const pixelCoords = topoData.projection([
|
|
277
|
-
|
|
278
|
-
|
|
299
|
+
runtimeData[runtimeKeys[i]][config.columns.longitude.name],
|
|
300
|
+
runtimeData[runtimeKeys[i]][config.columns.latitude.name]
|
|
279
301
|
])
|
|
280
|
-
if (
|
|
281
|
-
|
|
302
|
+
if (
|
|
303
|
+
pixelCoords &&
|
|
304
|
+
Math.sqrt(Math.pow(pixelCoords[0] - x, 2) + Math.pow(pixelCoords[1] - y, 2)) < geoRadius &&
|
|
305
|
+
!isLegendItemDisabled(
|
|
306
|
+
runtimeData[runtimeKeys[i]],
|
|
307
|
+
runtimeLegend,
|
|
308
|
+
legendMemo,
|
|
309
|
+
legendSpecialClassLastMemo,
|
|
310
|
+
config
|
|
311
|
+
)
|
|
312
|
+
) {
|
|
313
|
+
clickedGeo = runtimeData[runtimeKeys[i]]
|
|
282
314
|
break
|
|
283
315
|
}
|
|
284
316
|
}
|
|
@@ -319,7 +351,7 @@ const CountyMap = () => {
|
|
|
319
351
|
if (
|
|
320
352
|
!isNaN(currentTooltipIndex) &&
|
|
321
353
|
applyLegendToRow(
|
|
322
|
-
|
|
354
|
+
runtimeData[topoData.mapData[currentTooltipIndex].id],
|
|
323
355
|
config,
|
|
324
356
|
runtimeLegend,
|
|
325
357
|
legendMemo,
|
|
@@ -327,7 +359,7 @@ const CountyMap = () => {
|
|
|
327
359
|
)
|
|
328
360
|
) {
|
|
329
361
|
context.fillStyle = applyLegendToRow(
|
|
330
|
-
|
|
362
|
+
runtimeData[topoData.mapData[currentTooltipIndex].id],
|
|
331
363
|
config,
|
|
332
364
|
runtimeLegend,
|
|
333
365
|
legendMemo,
|
|
@@ -364,10 +396,10 @@ const CountyMap = () => {
|
|
|
364
396
|
}
|
|
365
397
|
|
|
366
398
|
// If the hovered county is found, show the tooltip for that county, otherwise hide the tooltip
|
|
367
|
-
if (county &&
|
|
368
|
-
if (applyLegendToRow(
|
|
399
|
+
if (county && runtimeData[county.id]) {
|
|
400
|
+
if (applyLegendToRow(runtimeData[county.id], config, runtimeLegend, legendMemo, legendSpecialClassLastMemo)) {
|
|
369
401
|
let fillColor = applyLegendToRow(
|
|
370
|
-
|
|
402
|
+
runtimeData[county.id],
|
|
371
403
|
config,
|
|
372
404
|
runtimeLegend,
|
|
373
405
|
legendMemo,
|
|
@@ -384,6 +416,24 @@ const CountyMap = () => {
|
|
|
384
416
|
context.stroke()
|
|
385
417
|
}
|
|
386
418
|
|
|
419
|
+
// Track hover analytics event if this is a new location
|
|
420
|
+
if (isNaN(currentTooltipIndex) || currentTooltipIndex !== countyIndex) {
|
|
421
|
+
const countyName = displayGeoName(county.id).replace(/[^a-zA-Z0-9]/g, ' ')
|
|
422
|
+
const stateFips = county.id.slice(0, 2)
|
|
423
|
+
const stateName = supportedStatesFipsCodes[stateFips]?.replace(/[^a-zA-Z0-9]/g, '_') || 'unknown'
|
|
424
|
+
const locationName = `${countyName}, ${stateName}`
|
|
425
|
+
publishAnalyticsEvent({
|
|
426
|
+
vizType: config.type,
|
|
427
|
+
vizSubType: getVizSubType(config),
|
|
428
|
+
eventType: `map_hover`,
|
|
429
|
+
eventAction: 'hover',
|
|
430
|
+
eventLabel: interactionLabel,
|
|
431
|
+
vizTitle: getVizTitle(config),
|
|
432
|
+
location: displayGeoName(county.id),
|
|
433
|
+
specifics: `location: ${locationName?.toLowerCase()}`
|
|
434
|
+
})
|
|
435
|
+
}
|
|
436
|
+
|
|
387
437
|
tooltipRef.current.style.display = 'block'
|
|
388
438
|
tooltipRef.current.style.top = tooltipY + 'px'
|
|
389
439
|
if (tooltipX > containerBounds.width / 2) {
|
|
@@ -393,7 +443,7 @@ const CountyMap = () => {
|
|
|
393
443
|
tooltipRef.current.style.transform = 'translate(0, -50%)'
|
|
394
444
|
tooltipRef.current.style.left = tooltipX + 5 + 'px'
|
|
395
445
|
}
|
|
396
|
-
tooltipRef.current.innerHTML = applyTooltipsToGeo(displayGeoName(county.id),
|
|
446
|
+
tooltipRef.current.innerHTML = applyTooltipsToGeo(displayGeoName(county.id), runtimeData[county.id])
|
|
397
447
|
tooltipRef.current.setAttribute('data-index', countyIndex)
|
|
398
448
|
} else {
|
|
399
449
|
tooltipRef.current.style.display = 'none'
|
|
@@ -404,8 +454,8 @@ const CountyMap = () => {
|
|
|
404
454
|
// Handle geo map hover
|
|
405
455
|
if (!isNaN(currentTooltipIndex)) {
|
|
406
456
|
const pixelCoords = topoData.projection([
|
|
407
|
-
|
|
408
|
-
|
|
457
|
+
runtimeData[runtimeKeys[currentTooltipIndex]][config.columns.longitude.name],
|
|
458
|
+
runtimeData[runtimeKeys[currentTooltipIndex]][config.columns.latitude.name]
|
|
409
459
|
])
|
|
410
460
|
if (pixelCoords && Math.sqrt(Math.pow(pixelCoords[0] - x, 2) + Math.pow(pixelCoords[1] - y, 2)) < geoRadius) {
|
|
411
461
|
return // The user is still hovering over the previous geo point, don't redraw tooltip
|
|
@@ -419,17 +469,30 @@ const CountyMap = () => {
|
|
|
419
469
|
let hoveredGeoIndex
|
|
420
470
|
for (let i = 0; i < runtimeKeys.length; i++) {
|
|
421
471
|
const pixelCoords = topoData.projection([
|
|
422
|
-
|
|
423
|
-
|
|
472
|
+
runtimeData[runtimeKeys[i]][config.columns.longitude.name],
|
|
473
|
+
runtimeData[runtimeKeys[i]][config.columns.latitude.name]
|
|
424
474
|
])
|
|
425
475
|
let includedShapes = ['circle', 'diamond', 'star', 'triangle', 'square'].includes(config.visual.cityStyle)
|
|
426
476
|
if (
|
|
427
477
|
includedShapes &&
|
|
428
478
|
pixelCoords &&
|
|
429
479
|
Math.sqrt(Math.pow(pixelCoords[0] - x, 2) + Math.pow(pixelCoords[1] - y, 2)) < geoRadius &&
|
|
430
|
-
applyLegendToRow(
|
|
480
|
+
applyLegendToRow(
|
|
481
|
+
runtimeData[runtimeKeys[i]],
|
|
482
|
+
config,
|
|
483
|
+
runtimeLegend,
|
|
484
|
+
legendMemo,
|
|
485
|
+
legendSpecialClassLastMemo
|
|
486
|
+
) &&
|
|
487
|
+
!isLegendItemDisabled(
|
|
488
|
+
runtimeData[runtimeKeys[i]],
|
|
489
|
+
runtimeLegend,
|
|
490
|
+
legendMemo,
|
|
491
|
+
legendSpecialClassLastMemo,
|
|
492
|
+
config
|
|
493
|
+
)
|
|
431
494
|
) {
|
|
432
|
-
hoveredGeo =
|
|
495
|
+
hoveredGeo = runtimeData[runtimeKeys[i]]
|
|
433
496
|
hoveredGeoIndex = i
|
|
434
497
|
break
|
|
435
498
|
}
|
|
@@ -438,9 +501,10 @@ const CountyMap = () => {
|
|
|
438
501
|
const distance = Math.hypot(pixelCoords[0] - x, pixelCoords[1] - y)
|
|
439
502
|
if (
|
|
440
503
|
distance < 15 &&
|
|
441
|
-
applyLegendToRow(
|
|
504
|
+
applyLegendToRow(runtimeData[runtimeKeys[i]], config, runtimeLegend, legendMemo, legendSpecialClassLastMemo) &&
|
|
505
|
+
!isLegendItemDisabled(runtimeData[runtimeKeys[i]], runtimeLegend, legendMemo, legendSpecialClassLastMemo, config)
|
|
442
506
|
) {
|
|
443
|
-
hoveredGeo =
|
|
507
|
+
hoveredGeo = runtimeData[runtimeKeys[i]]
|
|
444
508
|
hoveredGeoIndex = i
|
|
445
509
|
break
|
|
446
510
|
}
|
|
@@ -448,6 +512,21 @@ const CountyMap = () => {
|
|
|
448
512
|
}
|
|
449
513
|
|
|
450
514
|
if (hoveredGeo) {
|
|
515
|
+
// Track hover analytics event if this is a new location
|
|
516
|
+
if (isNaN(currentTooltipIndex) || currentTooltipIndex !== hoveredGeoIndex) {
|
|
517
|
+
const locationName = displayGeoName(hoveredGeo[config.columns.geo.name]).replace(/[^a-zA-Z0-9]/g, '_')
|
|
518
|
+
publishAnalyticsEvent({
|
|
519
|
+
vizType: config.type,
|
|
520
|
+
vizSubType: getVizSubType(config),
|
|
521
|
+
eventType: `map_hover`,
|
|
522
|
+
eventAction: 'hover',
|
|
523
|
+
eventLabel: interactionLabel,
|
|
524
|
+
vizTitle: getVizTitle(config),
|
|
525
|
+
location: displayGeoName(hoveredGeo[config.columns.geo.name]),
|
|
526
|
+
specifics: `location: ${locationName?.toLowerCase()}`
|
|
527
|
+
})
|
|
528
|
+
}
|
|
529
|
+
|
|
451
530
|
tooltipRef.current.style.display = 'block'
|
|
452
531
|
tooltipRef.current.style.top = tooltipY + 'px'
|
|
453
532
|
if (tooltipX > containerBounds.width / 2) {
|
|
@@ -524,7 +603,7 @@ const CountyMap = () => {
|
|
|
524
603
|
if (!focus.id && config.general.type === 'us-geocode' && geo.id.length > 2) return
|
|
525
604
|
|
|
526
605
|
// Gets numeric data associated with the topo data for this state/county
|
|
527
|
-
const geoData =
|
|
606
|
+
const geoData = runtimeData[geo.id]
|
|
528
607
|
|
|
529
608
|
// Renders state/county
|
|
530
609
|
const legendValues =
|
|
@@ -569,7 +648,7 @@ const CountyMap = () => {
|
|
|
569
648
|
context.strokeStyle = geoStrokeColor
|
|
570
649
|
const geoRadius = (config.visual.geoCodeCircleSize || 5) * (focus.id ? 2 : 1)
|
|
571
650
|
const { additionalCityStyles } = config.visual || []
|
|
572
|
-
const cityStyles = Object.values(
|
|
651
|
+
const cityStyles = Object.values(runtimeData)
|
|
573
652
|
.filter(d => additionalCityStyles.some(style => String(d[style.column]) === String(style.value)))
|
|
574
653
|
.map(d => {
|
|
575
654
|
const conditionsMatched = additionalCityStyles.find(
|
|
@@ -587,7 +666,7 @@ const CountyMap = () => {
|
|
|
587
666
|
|
|
588
667
|
if (cityPixelCoords) {
|
|
589
668
|
const legendValues = applyLegendToRow(
|
|
590
|
-
|
|
669
|
+
runtimeData[city?.value],
|
|
591
670
|
config,
|
|
592
671
|
runtimeLegend,
|
|
593
672
|
legendMemo,
|
|
@@ -608,13 +687,13 @@ const CountyMap = () => {
|
|
|
608
687
|
const citiesList = new Set(cityStyles.map(item => item.value))
|
|
609
688
|
|
|
610
689
|
const pixelCoords = topoData.projection([
|
|
611
|
-
|
|
612
|
-
|
|
690
|
+
runtimeData[key][config.columns.longitude.name],
|
|
691
|
+
runtimeData[key][config.columns.latitude.name]
|
|
613
692
|
])
|
|
614
693
|
if (pixelCoords && !citiesList.has(key)) {
|
|
615
694
|
const legendValues =
|
|
616
|
-
|
|
617
|
-
? applyLegendToRow(
|
|
695
|
+
runtimeData[key] !== undefined
|
|
696
|
+
? applyLegendToRow(runtimeData[key], config, runtimeLegend, legendMemo, legendSpecialClassLastMemo)
|
|
618
697
|
: false
|
|
619
698
|
if (legendValues) {
|
|
620
699
|
if (legendValues?.[0] === '#000000' || legendValues?.[0] === DEFAULT_MAP_BACKGROUND) return
|
|
@@ -7,7 +7,10 @@ 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'
|
|
12
15
|
|
|
13
16
|
// Data
|
|
@@ -51,7 +54,8 @@ const Rect: React.FC<RectProps> = ({ label, text, stroke, strokeWidth, ...props
|
|
|
51
54
|
}
|
|
52
55
|
|
|
53
56
|
const UsaRegionMap = () => {
|
|
54
|
-
const {
|
|
57
|
+
const { runtimeData, config, tooltipId, runtimeLegend, interactionLabel } = useContext(ConfigContext)
|
|
58
|
+
const { legendMemo, legendSpecialClassLastMemo } = useLegendMemoContext()
|
|
55
59
|
const [focusedStates, setFocusedStates] = useState(null)
|
|
56
60
|
const { geoClickHandler } = useGeoClickHandler()
|
|
57
61
|
const { applyTooltipsToGeo } = useApplyTooltipsToGeo()
|
|
@@ -75,10 +79,10 @@ const UsaRegionMap = () => {
|
|
|
75
79
|
|
|
76
80
|
useEffect(() => {
|
|
77
81
|
// 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 =>
|
|
82
|
+
const territoriesList = territoriesKeys.filter(key => runtimeData[key])
|
|
79
83
|
|
|
80
84
|
setTerritoriesData(territoriesList)
|
|
81
|
-
}, [
|
|
85
|
+
}, [runtimeData])
|
|
82
86
|
|
|
83
87
|
if (!focusedStates) {
|
|
84
88
|
return <></>
|
|
@@ -90,7 +94,7 @@ const UsaRegionMap = () => {
|
|
|
90
94
|
const territories = territoriesData.map(territory => {
|
|
91
95
|
const Shape = Rect
|
|
92
96
|
|
|
93
|
-
const territoryData =
|
|
97
|
+
const territoryData = runtimeData[territory]
|
|
94
98
|
|
|
95
99
|
let toolTip: string
|
|
96
100
|
|
|
@@ -162,7 +166,7 @@ const UsaRegionMap = () => {
|
|
|
162
166
|
|
|
163
167
|
if (!geoKey) return
|
|
164
168
|
|
|
165
|
-
const geoData =
|
|
169
|
+
const geoData = runtimeData[geoKey]
|
|
166
170
|
|
|
167
171
|
let legendColors
|
|
168
172
|
// Once we receive data for this geographic item, setup variables.
|
|
@@ -215,6 +219,20 @@ const UsaRegionMap = () => {
|
|
|
215
219
|
data-tooltip-id={`tooltip__${tooltipId}`}
|
|
216
220
|
data-tooltip-html={toolTip}
|
|
217
221
|
tabIndex={-1}
|
|
222
|
+
onMouseEnter={() => {
|
|
223
|
+
// Track hover analytics event if this is a new location
|
|
224
|
+
const locationName = geoDisplayName.replace(/[^a-zA-Z0-9]/g, '_')
|
|
225
|
+
publishAnalyticsEvent({
|
|
226
|
+
vizType: config.type,
|
|
227
|
+
vizSubType: getVizSubType(config),
|
|
228
|
+
eventType: `map_hover`,
|
|
229
|
+
eventAction: 'hover',
|
|
230
|
+
eventLabel: interactionLabel,
|
|
231
|
+
vizTitle: getVizTitle(config),
|
|
232
|
+
location: geoDisplayName,
|
|
233
|
+
specifics: `location: ${locationName?.toLowerCase()}`
|
|
234
|
+
})
|
|
235
|
+
}}
|
|
218
236
|
>
|
|
219
237
|
<path tabIndex={-1} className='single-geo' stroke={geoStrokeColor} strokeWidth={1} d={path} />
|
|
220
238
|
<g id={`region-${index + 1}-label`}>
|