@cdc/map 4.25.8 → 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/dist/cdcmap.js +54263 -52600
- 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/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 +35 -34
- package/package.json +26 -5
- package/src/CdcMap.tsx +23 -8
- package/src/CdcMapComponent.tsx +215 -309
- 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 +15 -5
- 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/us-bubble-cities.json +306 -0
- package/src/components/BubbleList.tsx +16 -12
- package/src/components/CityList.tsx +85 -107
- package/src/components/DataTable.tsx +37 -9
- package/src/components/EditorPanel/components/EditorPanel.tsx +177 -165
- 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 +109 -73
- 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/NavigationMenu.tsx +11 -2
- package/src/components/UsaMap/components/SingleState/SingleState.CountyOutput.tsx +24 -7
- package/src/components/UsaMap/components/UsaMap.County.tsx +111 -37
- package/src/components/UsaMap/components/UsaMap.Region.tsx +23 -5
- package/src/components/UsaMap/components/UsaMap.SingleState.tsx +6 -6
- package/src/components/UsaMap/components/UsaMap.State.tsx +28 -10
- package/src/components/UsaMap/helpers/map.ts +2 -2
- package/src/components/WorldMap/WorldMap.tsx +113 -25
- 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 +143 -130
- package/src/data/supported-geos.js +17 -2
- package/src/helpers/applyColorToLegend.ts +116 -20
- package/src/helpers/applyLegendToRow.ts +10 -6
- 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 +1 -1
- package/src/helpers/generateRuntimeLegend.ts +44 -8
- 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 +8 -5
- 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 +25 -6
- package/src/hooks/useTooltip.ts +1 -2
- package/src/index.jsx +0 -2
- package/src/store/map.reducer.ts +17 -6
- package/src/test/CdcMap.test.jsx +11 -0
- package/src/types/MapConfig.ts +23 -14
- package/src/types/MapContext.ts +0 -7
- package/src/types/runtimeLegend.ts +17 -1
- package/vite.config.js +2 -7
- package/vitest.config.ts +16 -0
- 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
|
@@ -7,14 +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'
|
|
17
19
|
import { publishAnalyticsEvent } from '@cdc/core/helpers/metrics/helpers'
|
|
20
|
+
import { getVizTitle, getVizSubType } from '@cdc/core/helpers/metrics/utils'
|
|
18
21
|
|
|
19
22
|
const getCountyTopoURL = year => {
|
|
20
23
|
return `https://www.cdc.gov/TemplatePackage/contrib/data/county-topography/cb_${year}_us_county_20m.json`
|
|
@@ -131,18 +134,18 @@ const CountyMap = () => {
|
|
|
131
134
|
const {
|
|
132
135
|
container,
|
|
133
136
|
containerEl,
|
|
134
|
-
|
|
137
|
+
runtimeData,
|
|
135
138
|
runtimeFilters,
|
|
136
139
|
runtimeLegend,
|
|
137
140
|
setConfig,
|
|
138
141
|
config,
|
|
139
142
|
tooltipId,
|
|
140
143
|
tooltipRef,
|
|
141
|
-
|
|
142
|
-
legendSpecialClassLastMemo,
|
|
143
|
-
configUrl
|
|
144
|
+
interactionLabel
|
|
144
145
|
} = useContext(ConfigContext)
|
|
145
146
|
|
|
147
|
+
const { legendMemo, legendSpecialClassLastMemo } = useLegendMemoContext()
|
|
148
|
+
|
|
146
149
|
// CREATE STATE LINES
|
|
147
150
|
const geoStrokeColor = getGeoStrokeColor(config)
|
|
148
151
|
const { geoClickHandler } = useGeoClickHandler()
|
|
@@ -200,7 +203,7 @@ const CountyMap = () => {
|
|
|
200
203
|
const canvasRef = useRef()
|
|
201
204
|
|
|
202
205
|
// If runtimeData is not defined, show loader
|
|
203
|
-
if (!
|
|
206
|
+
if (!runtimeData || !isTopoReady(topoData, config, runtimeFilters)) {
|
|
204
207
|
return (
|
|
205
208
|
<div style={{ height: 300 }}>
|
|
206
209
|
<Loading />
|
|
@@ -208,11 +211,18 @@ const CountyMap = () => {
|
|
|
208
211
|
)
|
|
209
212
|
}
|
|
210
213
|
|
|
211
|
-
const runtimeKeys = Object.keys(
|
|
214
|
+
const runtimeKeys = Object.keys(runtimeData)
|
|
212
215
|
const lineWidth = 1
|
|
213
216
|
|
|
214
217
|
const onReset = () => {
|
|
215
|
-
publishAnalyticsEvent(
|
|
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
|
+
})
|
|
216
226
|
setConfig({
|
|
217
227
|
...config,
|
|
218
228
|
mapPosition: { coordinates: [0, 30], zoom: 1 }
|
|
@@ -256,8 +266,8 @@ const CountyMap = () => {
|
|
|
256
266
|
break
|
|
257
267
|
}
|
|
258
268
|
}
|
|
259
|
-
if (county &&
|
|
260
|
-
geoClickHandler(displayGeoName(county.id),
|
|
269
|
+
if (county && runtimeData[county.id]) {
|
|
270
|
+
geoClickHandler(displayGeoName(county.id), runtimeData[county.id])
|
|
261
271
|
}
|
|
262
272
|
}
|
|
263
273
|
|
|
@@ -271,19 +281,36 @@ const CountyMap = () => {
|
|
|
271
281
|
|
|
272
282
|
// Redraw with focus on state
|
|
273
283
|
setFocus({ id: clickedState.id, index: focusIndex, center: geoCentroid(clickedState), feature: clickedState })
|
|
274
|
-
publishAnalyticsEvent(
|
|
275
|
-
|
|
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
|
+
})
|
|
276
293
|
}
|
|
277
294
|
if (config.general.type === 'us-geocode') {
|
|
278
295
|
const geoRadius = (config.visual.geoCodeCircleSize || 5) * (focus.id ? 2 : 1)
|
|
279
296
|
let clickedGeo
|
|
280
297
|
for (let i = 0; i < runtimeKeys.length; i++) {
|
|
281
298
|
const pixelCoords = topoData.projection([
|
|
282
|
-
|
|
283
|
-
|
|
299
|
+
runtimeData[runtimeKeys[i]][config.columns.longitude.name],
|
|
300
|
+
runtimeData[runtimeKeys[i]][config.columns.latitude.name]
|
|
284
301
|
])
|
|
285
|
-
if (
|
|
286
|
-
|
|
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]]
|
|
287
314
|
break
|
|
288
315
|
}
|
|
289
316
|
}
|
|
@@ -324,7 +351,7 @@ const CountyMap = () => {
|
|
|
324
351
|
if (
|
|
325
352
|
!isNaN(currentTooltipIndex) &&
|
|
326
353
|
applyLegendToRow(
|
|
327
|
-
|
|
354
|
+
runtimeData[topoData.mapData[currentTooltipIndex].id],
|
|
328
355
|
config,
|
|
329
356
|
runtimeLegend,
|
|
330
357
|
legendMemo,
|
|
@@ -332,7 +359,7 @@ const CountyMap = () => {
|
|
|
332
359
|
)
|
|
333
360
|
) {
|
|
334
361
|
context.fillStyle = applyLegendToRow(
|
|
335
|
-
|
|
362
|
+
runtimeData[topoData.mapData[currentTooltipIndex].id],
|
|
336
363
|
config,
|
|
337
364
|
runtimeLegend,
|
|
338
365
|
legendMemo,
|
|
@@ -369,10 +396,10 @@ const CountyMap = () => {
|
|
|
369
396
|
}
|
|
370
397
|
|
|
371
398
|
// If the hovered county is found, show the tooltip for that county, otherwise hide the tooltip
|
|
372
|
-
if (county &&
|
|
373
|
-
if (applyLegendToRow(
|
|
399
|
+
if (county && runtimeData[county.id]) {
|
|
400
|
+
if (applyLegendToRow(runtimeData[county.id], config, runtimeLegend, legendMemo, legendSpecialClassLastMemo)) {
|
|
374
401
|
let fillColor = applyLegendToRow(
|
|
375
|
-
|
|
402
|
+
runtimeData[county.id],
|
|
376
403
|
config,
|
|
377
404
|
runtimeLegend,
|
|
378
405
|
legendMemo,
|
|
@@ -389,6 +416,24 @@ const CountyMap = () => {
|
|
|
389
416
|
context.stroke()
|
|
390
417
|
}
|
|
391
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
|
+
|
|
392
437
|
tooltipRef.current.style.display = 'block'
|
|
393
438
|
tooltipRef.current.style.top = tooltipY + 'px'
|
|
394
439
|
if (tooltipX > containerBounds.width / 2) {
|
|
@@ -398,7 +443,7 @@ const CountyMap = () => {
|
|
|
398
443
|
tooltipRef.current.style.transform = 'translate(0, -50%)'
|
|
399
444
|
tooltipRef.current.style.left = tooltipX + 5 + 'px'
|
|
400
445
|
}
|
|
401
|
-
tooltipRef.current.innerHTML = applyTooltipsToGeo(displayGeoName(county.id),
|
|
446
|
+
tooltipRef.current.innerHTML = applyTooltipsToGeo(displayGeoName(county.id), runtimeData[county.id])
|
|
402
447
|
tooltipRef.current.setAttribute('data-index', countyIndex)
|
|
403
448
|
} else {
|
|
404
449
|
tooltipRef.current.style.display = 'none'
|
|
@@ -409,8 +454,8 @@ const CountyMap = () => {
|
|
|
409
454
|
// Handle geo map hover
|
|
410
455
|
if (!isNaN(currentTooltipIndex)) {
|
|
411
456
|
const pixelCoords = topoData.projection([
|
|
412
|
-
|
|
413
|
-
|
|
457
|
+
runtimeData[runtimeKeys[currentTooltipIndex]][config.columns.longitude.name],
|
|
458
|
+
runtimeData[runtimeKeys[currentTooltipIndex]][config.columns.latitude.name]
|
|
414
459
|
])
|
|
415
460
|
if (pixelCoords && Math.sqrt(Math.pow(pixelCoords[0] - x, 2) + Math.pow(pixelCoords[1] - y, 2)) < geoRadius) {
|
|
416
461
|
return // The user is still hovering over the previous geo point, don't redraw tooltip
|
|
@@ -424,17 +469,30 @@ const CountyMap = () => {
|
|
|
424
469
|
let hoveredGeoIndex
|
|
425
470
|
for (let i = 0; i < runtimeKeys.length; i++) {
|
|
426
471
|
const pixelCoords = topoData.projection([
|
|
427
|
-
|
|
428
|
-
|
|
472
|
+
runtimeData[runtimeKeys[i]][config.columns.longitude.name],
|
|
473
|
+
runtimeData[runtimeKeys[i]][config.columns.latitude.name]
|
|
429
474
|
])
|
|
430
475
|
let includedShapes = ['circle', 'diamond', 'star', 'triangle', 'square'].includes(config.visual.cityStyle)
|
|
431
476
|
if (
|
|
432
477
|
includedShapes &&
|
|
433
478
|
pixelCoords &&
|
|
434
479
|
Math.sqrt(Math.pow(pixelCoords[0] - x, 2) + Math.pow(pixelCoords[1] - y, 2)) < geoRadius &&
|
|
435
|
-
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
|
+
)
|
|
436
494
|
) {
|
|
437
|
-
hoveredGeo =
|
|
495
|
+
hoveredGeo = runtimeData[runtimeKeys[i]]
|
|
438
496
|
hoveredGeoIndex = i
|
|
439
497
|
break
|
|
440
498
|
}
|
|
@@ -443,9 +501,10 @@ const CountyMap = () => {
|
|
|
443
501
|
const distance = Math.hypot(pixelCoords[0] - x, pixelCoords[1] - y)
|
|
444
502
|
if (
|
|
445
503
|
distance < 15 &&
|
|
446
|
-
applyLegendToRow(
|
|
504
|
+
applyLegendToRow(runtimeData[runtimeKeys[i]], config, runtimeLegend, legendMemo, legendSpecialClassLastMemo) &&
|
|
505
|
+
!isLegendItemDisabled(runtimeData[runtimeKeys[i]], runtimeLegend, legendMemo, legendSpecialClassLastMemo, config)
|
|
447
506
|
) {
|
|
448
|
-
hoveredGeo =
|
|
507
|
+
hoveredGeo = runtimeData[runtimeKeys[i]]
|
|
449
508
|
hoveredGeoIndex = i
|
|
450
509
|
break
|
|
451
510
|
}
|
|
@@ -453,6 +512,21 @@ const CountyMap = () => {
|
|
|
453
512
|
}
|
|
454
513
|
|
|
455
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
|
+
|
|
456
530
|
tooltipRef.current.style.display = 'block'
|
|
457
531
|
tooltipRef.current.style.top = tooltipY + 'px'
|
|
458
532
|
if (tooltipX > containerBounds.width / 2) {
|
|
@@ -529,7 +603,7 @@ const CountyMap = () => {
|
|
|
529
603
|
if (!focus.id && config.general.type === 'us-geocode' && geo.id.length > 2) return
|
|
530
604
|
|
|
531
605
|
// Gets numeric data associated with the topo data for this state/county
|
|
532
|
-
const geoData =
|
|
606
|
+
const geoData = runtimeData[geo.id]
|
|
533
607
|
|
|
534
608
|
// Renders state/county
|
|
535
609
|
const legendValues =
|
|
@@ -574,7 +648,7 @@ const CountyMap = () => {
|
|
|
574
648
|
context.strokeStyle = geoStrokeColor
|
|
575
649
|
const geoRadius = (config.visual.geoCodeCircleSize || 5) * (focus.id ? 2 : 1)
|
|
576
650
|
const { additionalCityStyles } = config.visual || []
|
|
577
|
-
const cityStyles = Object.values(
|
|
651
|
+
const cityStyles = Object.values(runtimeData)
|
|
578
652
|
.filter(d => additionalCityStyles.some(style => String(d[style.column]) === String(style.value)))
|
|
579
653
|
.map(d => {
|
|
580
654
|
const conditionsMatched = additionalCityStyles.find(
|
|
@@ -592,7 +666,7 @@ const CountyMap = () => {
|
|
|
592
666
|
|
|
593
667
|
if (cityPixelCoords) {
|
|
594
668
|
const legendValues = applyLegendToRow(
|
|
595
|
-
|
|
669
|
+
runtimeData[city?.value],
|
|
596
670
|
config,
|
|
597
671
|
runtimeLegend,
|
|
598
672
|
legendMemo,
|
|
@@ -613,13 +687,13 @@ const CountyMap = () => {
|
|
|
613
687
|
const citiesList = new Set(cityStyles.map(item => item.value))
|
|
614
688
|
|
|
615
689
|
const pixelCoords = topoData.projection([
|
|
616
|
-
|
|
617
|
-
|
|
690
|
+
runtimeData[key][config.columns.longitude.name],
|
|
691
|
+
runtimeData[key][config.columns.latitude.name]
|
|
618
692
|
])
|
|
619
693
|
if (pixelCoords && !citiesList.has(key)) {
|
|
620
694
|
const legendValues =
|
|
621
|
-
|
|
622
|
-
? applyLegendToRow(
|
|
695
|
+
runtimeData[key] !== undefined
|
|
696
|
+
? applyLegendToRow(runtimeData[key], config, runtimeLegend, legendMemo, legendSpecialClassLastMemo)
|
|
623
697
|
: false
|
|
624
698
|
if (legendValues) {
|
|
625
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`}>
|
|
@@ -41,7 +41,7 @@ const SingleStateMap: React.FC = () => {
|
|
|
41
41
|
} = useContext<MapContext>(ConfigContext)
|
|
42
42
|
|
|
43
43
|
const dispatch = useContext(MapDispatchContext)
|
|
44
|
-
const { handleMoveEnd, handleZoomIn, handleZoomOut,
|
|
44
|
+
const { handleMoveEnd, handleZoomIn, handleZoomOut, handleZoomReset, projection } = useStateZoom(topoData)
|
|
45
45
|
|
|
46
46
|
// Memoize statesPicked to prevent creating new arrays on every render
|
|
47
47
|
const statesPicked = useMemo(() => {
|
|
@@ -70,7 +70,7 @@ const SingleStateMap: React.FC = () => {
|
|
|
70
70
|
dispatch({ type: 'SET_TOPO_DATA', payload: response })
|
|
71
71
|
})
|
|
72
72
|
}
|
|
73
|
-
}, [
|
|
73
|
+
}, [config.general.countyCensusYear, config.general.filterControlsCountyYear, JSON.stringify(runtimeFilters)])
|
|
74
74
|
|
|
75
75
|
if (!isTopoReady(topoData, config, runtimeFilters)) {
|
|
76
76
|
return (
|
|
@@ -129,7 +129,7 @@ const SingleStateMap: React.FC = () => {
|
|
|
129
129
|
}
|
|
130
130
|
return (
|
|
131
131
|
<ErrorBoundary component='SingleStateMap'>
|
|
132
|
-
{statesPicked.length && config.general.allowMapZoom && statesPicked.some(sp => sp.fipsCode) && (
|
|
132
|
+
{!!statesPicked.length && config.general.allowMapZoom && statesPicked.some(sp => sp.fipsCode) && (
|
|
133
133
|
<svg
|
|
134
134
|
viewBox={SVG_VIEWBOX}
|
|
135
135
|
preserveAspectRatio='xMinYMin'
|
|
@@ -192,7 +192,7 @@ const SingleStateMap: React.FC = () => {
|
|
|
192
192
|
</ZoomableGroup>
|
|
193
193
|
</svg>
|
|
194
194
|
)}
|
|
195
|
-
{statesPicked && !config.general.allowMapZoom && statesPicked.some(sp => sp.fipsCode) && (
|
|
195
|
+
{!!statesPicked && !config.general.allowMapZoom && statesPicked.some(sp => sp.fipsCode) && (
|
|
196
196
|
<svg
|
|
197
197
|
viewBox={SVG_VIEWBOX}
|
|
198
198
|
preserveAspectRatio='xMinYMin'
|
|
@@ -262,7 +262,7 @@ const SingleStateMap: React.FC = () => {
|
|
|
262
262
|
fontSize={18}
|
|
263
263
|
style={{ fontSize: '28px', height: '18px' }}
|
|
264
264
|
>
|
|
265
|
-
{config.general.
|
|
265
|
+
{config.general.noDataMessage}
|
|
266
266
|
</Text>
|
|
267
267
|
</svg>
|
|
268
268
|
)}
|
|
@@ -270,7 +270,7 @@ const SingleStateMap: React.FC = () => {
|
|
|
270
270
|
// prettier-ignore
|
|
271
271
|
handleZoomIn={handleZoomIn}
|
|
272
272
|
handleZoomOut={handleZoomOut}
|
|
273
|
-
|
|
273
|
+
handleZoomReset={handleZoomReset}
|
|
274
274
|
/>
|
|
275
275
|
</ErrorBoundary>
|
|
276
276
|
)
|
|
@@ -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,6 +22,7 @@ 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'
|
|
@@ -69,20 +72,21 @@ const nudges = {
|
|
|
69
72
|
|
|
70
73
|
const UsaMap = () => {
|
|
71
74
|
const {
|
|
72
|
-
|
|
75
|
+
runtimeData,
|
|
73
76
|
setSharedFilterValue,
|
|
74
77
|
config,
|
|
75
78
|
setConfig,
|
|
76
79
|
tooltipId,
|
|
77
80
|
mapId,
|
|
78
81
|
logo,
|
|
79
|
-
legendMemo,
|
|
80
|
-
legendSpecialClassLastMemo,
|
|
81
82
|
currentViewport,
|
|
82
83
|
translate,
|
|
83
|
-
runtimeLegend
|
|
84
|
+
runtimeLegend,
|
|
85
|
+
interactionLabel
|
|
84
86
|
} = useContext<MapContext>(ConfigContext)
|
|
85
87
|
|
|
88
|
+
const { legendMemo, legendSpecialClassLastMemo } = useLegendMemoContext()
|
|
89
|
+
|
|
86
90
|
let isFilterValueSupported = false
|
|
87
91
|
const { general, columns, tooltips, hexMap, map, annotations } = config
|
|
88
92
|
const { displayAsHex } = general
|
|
@@ -120,14 +124,14 @@ const UsaMap = () => {
|
|
|
120
124
|
|
|
121
125
|
const legendMemoUpdated = focusedStates?.every(geo => {
|
|
122
126
|
const geoKey = geo.properties.iso
|
|
123
|
-
const geoData =
|
|
127
|
+
const geoData = runtimeData[geoKey]
|
|
124
128
|
const hash = hashObj(geoData)
|
|
125
129
|
return legendMemo.current.has(hash)
|
|
126
130
|
})
|
|
127
131
|
|
|
128
132
|
// we use dataRef so that we can use the old data when legendMemo has not been updated yet
|
|
129
133
|
// prevents flickering of the map when filter is changed
|
|
130
|
-
if (legendMemoUpdated) dataRef.current =
|
|
134
|
+
if (legendMemoUpdated) dataRef.current = runtimeData
|
|
131
135
|
|
|
132
136
|
useEffect(() => {
|
|
133
137
|
const fetchData = async () => {
|
|
@@ -153,10 +157,10 @@ const UsaMap = () => {
|
|
|
153
157
|
setTerritoriesData(territoriesKeys)
|
|
154
158
|
} else {
|
|
155
159
|
// 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 =>
|
|
160
|
+
const territoriesList = territoriesKeys.filter(key => runtimeData?.[key])
|
|
157
161
|
setTerritoriesData(territoriesList)
|
|
158
162
|
}
|
|
159
|
-
}, [
|
|
163
|
+
}, [runtimeData, dataRef.current, general.territoriesAlwaysShow])
|
|
160
164
|
|
|
161
165
|
const geoStrokeColor = getGeoStrokeColor(config)
|
|
162
166
|
const geoFillColor = getGeoFillColor(config)
|
|
@@ -164,7 +168,7 @@ const UsaMap = () => {
|
|
|
164
168
|
const territories = territoriesData.map((territory, territoryIndex) => {
|
|
165
169
|
const Shape = displayAsHex ? Territory.Hexagon : Territory.Rectangle
|
|
166
170
|
|
|
167
|
-
const territoryData =
|
|
171
|
+
const territoryData = runtimeData?.[territory]
|
|
168
172
|
|
|
169
173
|
let toolTip
|
|
170
174
|
|
|
@@ -290,7 +294,7 @@ const UsaMap = () => {
|
|
|
290
294
|
|
|
291
295
|
if (!geoKey) return
|
|
292
296
|
|
|
293
|
-
const geoData =
|
|
297
|
+
const geoData = runtimeData?.[geoKey]
|
|
294
298
|
|
|
295
299
|
let legendColors
|
|
296
300
|
|
|
@@ -440,6 +444,20 @@ const UsaMap = () => {
|
|
|
440
444
|
data-tooltip-id={`tooltip__${tooltipId}`}
|
|
441
445
|
data-tooltip-html={tooltip}
|
|
442
446
|
tabIndex={-1}
|
|
447
|
+
onMouseEnter={() => {
|
|
448
|
+
// Track hover analytics event if this is a new location
|
|
449
|
+
const locationName = geoDisplayName.replace(/[^a-zA-Z0-9]/g, '_')
|
|
450
|
+
publishAnalyticsEvent({
|
|
451
|
+
vizType: config.type,
|
|
452
|
+
vizSubType: getVizSubType(config),
|
|
453
|
+
eventType: `map_hover`,
|
|
454
|
+
eventAction: 'hover',
|
|
455
|
+
eventLabel: interactionLabel,
|
|
456
|
+
vizTitle: getVizTitle(config),
|
|
457
|
+
location: geoDisplayName,
|
|
458
|
+
specifics: `location: ${locationName?.toLowerCase()}`
|
|
459
|
+
})
|
|
460
|
+
}}
|
|
443
461
|
>
|
|
444
462
|
{/* state path */}
|
|
445
463
|
<path tabIndex={-1} className='single-geo' strokeWidth={1} d={path} />
|
|
@@ -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) || []
|