@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.
Files changed (84) hide show
  1. package/.claude/settings.local.json +30 -0
  2. package/dist/cdcmap.js +54263 -52600
  3. package/examples/private/c.json +290 -0
  4. package/examples/private/canvas-city-hover.json +787 -0
  5. package/examples/private/d.json +345 -0
  6. package/examples/private/g.json +1 -0
  7. package/examples/private/h.json +105911 -0
  8. package/examples/private/measles-data.json +378 -0
  9. package/examples/private/measles.json +211 -0
  10. package/examples/private/north-dakota.json +1132 -0
  11. package/examples/private/state-with-pattern.json +883 -0
  12. package/index.html +35 -34
  13. package/package.json +26 -5
  14. package/src/CdcMap.tsx +23 -8
  15. package/src/CdcMapComponent.tsx +215 -309
  16. package/src/_stories/CdcMap.Filters.stories.tsx +2 -2
  17. package/src/_stories/CdcMap.Legend.Gradient.stories.tsx +3 -3
  18. package/src/_stories/CdcMap.Legend.stories.tsx +7 -4
  19. package/src/_stories/CdcMap.Patterns.stories.tsx +2 -2
  20. package/src/_stories/CdcMap.Table.stories.tsx +2 -2
  21. package/src/_stories/CdcMap.stories.tsx +15 -5
  22. package/src/_stories/GoogleMap.stories.tsx +2 -2
  23. package/src/_stories/UsaMap.NoData.stories.tsx +2 -2
  24. package/src/_stories/_mock/equal-number.json +1109 -0
  25. package/src/_stories/_mock/us-bubble-cities.json +306 -0
  26. package/src/components/BubbleList.tsx +16 -12
  27. package/src/components/CityList.tsx +85 -107
  28. package/src/components/DataTable.tsx +37 -9
  29. package/src/components/EditorPanel/components/EditorPanel.tsx +177 -165
  30. package/src/components/EditorPanel/components/HexShapeSettings.tsx +3 -2
  31. package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +7 -5
  32. package/src/components/Geo.tsx +2 -0
  33. package/src/components/Legend/components/Legend.tsx +109 -73
  34. package/src/components/Legend/components/LegendGroup/Legend.Group.tsx +10 -7
  35. package/src/components/MapContainer.tsx +52 -0
  36. package/src/components/MapControls.tsx +44 -0
  37. package/src/components/NavigationMenu.tsx +11 -2
  38. package/src/components/UsaMap/components/SingleState/SingleState.CountyOutput.tsx +24 -7
  39. package/src/components/UsaMap/components/UsaMap.County.tsx +111 -37
  40. package/src/components/UsaMap/components/UsaMap.Region.tsx +23 -5
  41. package/src/components/UsaMap/components/UsaMap.SingleState.tsx +6 -6
  42. package/src/components/UsaMap/components/UsaMap.State.tsx +28 -10
  43. package/src/components/UsaMap/helpers/map.ts +2 -2
  44. package/src/components/WorldMap/WorldMap.tsx +113 -25
  45. package/src/components/ZoomControls.tsx +6 -9
  46. package/src/context/LegendMemoContext.tsx +30 -0
  47. package/src/context.ts +1 -40
  48. package/src/data/initial-state.js +143 -130
  49. package/src/data/supported-geos.js +17 -2
  50. package/src/helpers/applyColorToLegend.ts +116 -20
  51. package/src/helpers/applyLegendToRow.ts +10 -6
  52. package/src/helpers/componentHelpers.ts +8 -0
  53. package/src/helpers/constants.ts +12 -0
  54. package/src/helpers/dataTableHelpers.ts +6 -0
  55. package/src/helpers/displayGeoName.ts +1 -1
  56. package/src/helpers/generateRuntimeLegend.ts +44 -8
  57. package/src/helpers/generateRuntimeLegendHash.ts +4 -2
  58. package/src/helpers/getColumnNames.ts +1 -1
  59. package/src/helpers/getPatternForRow.ts +36 -0
  60. package/src/helpers/getStatesPicked.ts +8 -5
  61. package/src/helpers/index.ts +11 -3
  62. package/src/helpers/isLegendItemDisabled.ts +16 -0
  63. package/src/helpers/mapObserverHelpers.ts +40 -0
  64. package/src/helpers/resetLegendToggles.ts +3 -2
  65. package/src/helpers/toggleLegendActive.ts +6 -11
  66. package/src/helpers/urlDataHelpers.ts +70 -0
  67. package/src/hooks/useGeoClickHandler.ts +35 -1
  68. package/src/hooks/useLegendMemo.ts +17 -0
  69. package/src/hooks/useMapLayers.tsx +5 -4
  70. package/src/hooks/useStateZoom.tsx +25 -6
  71. package/src/hooks/useTooltip.ts +1 -2
  72. package/src/index.jsx +0 -2
  73. package/src/store/map.reducer.ts +17 -6
  74. package/src/test/CdcMap.test.jsx +11 -0
  75. package/src/types/MapConfig.ts +23 -14
  76. package/src/types/MapContext.ts +0 -7
  77. package/src/types/runtimeLegend.ts +17 -1
  78. package/vite.config.js +2 -7
  79. package/vitest.config.ts +16 -0
  80. package/src/coreStyles_map.scss +0 -3
  81. package/src/helpers/colorDistributions.ts +0 -12
  82. package/src/helpers/generateColorsArray.ts +0 -14
  83. package/src/helpers/tests/generateColorsArray.test.ts +0 -18
  84. package/src/helpers/tests/generateRuntimeLegendHash.test.ts +0 -11
@@ -1,6 +1,7 @@
1
1
  //TODO: Move legends to core
2
- import { forwardRef, useContext } from 'react'
2
+ import { forwardRef, useContext, useMemo } from 'react'
3
3
  import parse from 'html-react-parser'
4
+ import { processMarkupVariables } from '@cdc/core/helpers/markupProcessor'
4
5
 
5
6
  //types
6
7
  import { DimensionsType } from '@cdc/core/types/Dimensions'
@@ -20,11 +21,12 @@ import './index.scss'
20
21
  import { type ViewPort } from '@cdc/core/types/ViewPort'
21
22
  import { isBelowBreakpoint, isMobileFontViewport } from '@cdc/core/helpers/viewports'
22
23
  import { displayDataAsText } from '@cdc/core/helpers/displayDataAsText'
23
- import { toggleLegendActive } from '@cdc/map/src/helpers/toggleLegendActive'
24
+ import { toggleLegendActive } from '../../../helpers/toggleLegendActive'
24
25
  import { resetLegendToggles } from '../../../helpers'
25
26
  import { MapContext } from '../../../types/MapContext'
26
27
  import LegendGroup from './LegendGroup/Legend.Group'
27
28
  import { publishAnalyticsEvent } from '@cdc/core/helpers/metrics/helpers'
29
+ import { getVizTitle, getVizSubType } from '@cdc/core/helpers/metrics/utils'
28
30
 
29
31
  const LEGEND_PADDING = 30
30
32
 
@@ -45,8 +47,7 @@ const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
45
47
  dimensions,
46
48
  mapId,
47
49
  runtimeFilters,
48
- runtimeLegend,
49
- setRuntimeLegend
50
+ runtimeLegend
50
51
  } = useContext<MapContext>(ConfigContext)
51
52
 
52
53
  const dispatch = useContext(MapDispatchContext)
@@ -61,7 +62,10 @@ const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
61
62
 
62
63
  const getFormattedLegendItems = () => {
63
64
  try {
64
- if (!runtimeLegend.items) Error('No runtime legend data')
65
+ if (!runtimeLegend.items) {
66
+ console.warn('No runtime legend data available')
67
+ return []
68
+ }
65
69
  return runtimeLegend.items.map((entry, idx) => {
66
70
  const entryMax = displayDataAsText(entry.max, 'primary', config)
67
71
 
@@ -120,34 +124,36 @@ const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
120
124
  return classes.join(' ')
121
125
  }
122
126
 
123
- const setAccessibleStatus = (message: string) => {
124
- dispatch({ type: 'SET_ACCESSIBLE_STATUS', payload: message })
125
- }
126
-
127
127
  return (
128
128
  <li
129
129
  className={handleListItemClass()}
130
130
  key={idx}
131
131
  title={`Legend item ${item.label} - Click to disable`}
132
132
  onClick={() => {
133
- toggleLegendActive(idx, item.label, runtimeLegend, setRuntimeLegend, setAccessibleStatus)
134
- publishAnalyticsEvent(
135
- `map_legend_item_toggled--isolate-mode`,
136
- 'click',
137
- `${interactionLabel}|${item.label}`,
138
- 'map'
139
- )
133
+ toggleLegendActive(idx, item.label, runtimeLegend, dispatch)
134
+ publishAnalyticsEvent({
135
+ vizType: config.type,
136
+ vizSubType: getVizSubType(config),
137
+ eventType: `map_legend_item_toggled`,
138
+ eventAction: 'click',
139
+ eventLabel: `${interactionLabel}`,
140
+ vizTitle: getVizTitle(config),
141
+ specifics: `mode: isolate, label: ${item.label}`
142
+ })
140
143
  }}
141
144
  onKeyDown={e => {
142
145
  if (e.key === 'Enter') {
143
146
  e.preventDefault()
144
- toggleLegendActive(idx, item.label, runtimeLegend, setRuntimeLegend, setAccessibleStatus)
145
- publishAnalyticsEvent(
146
- `map_legend_item_toggled--isolate-mode`,
147
- 'keydown',
148
- `${interactionLabel}|${item.label}`,
149
- 'map'
150
- )
147
+ toggleLegendActive(idx, item.label, runtimeLegend, dispatch)
148
+ publishAnalyticsEvent({
149
+ vizType: config.type,
150
+ vizSubType: getVizSubType(config),
151
+ eventType: `map_legend_item_toggled`,
152
+ eventAction: 'keydown',
153
+ eventLabel: `${interactionLabel}`,
154
+ vizTitle: getVizTitle(config),
155
+ specifics: `mode: isolate, label: ${item.label}`
156
+ })
151
157
  }
152
158
  }}
153
159
  tabIndex={0}
@@ -165,9 +171,9 @@ const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
165
171
  const { pattern, dataKey, size } = patternData
166
172
  let defaultPatternColor = 'black'
167
173
  const sizes = {
168
- small: '8',
169
- medium: '10',
170
- large: '12'
174
+ small: 8,
175
+ medium: 10,
176
+ large: 12
171
177
  }
172
178
 
173
179
  const legendSize = 16
@@ -240,8 +246,15 @@ const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
240
246
  if (e) {
241
247
  e.preventDefault()
242
248
  }
243
- publishAnalyticsEvent('map_legend_reset', 'click', interactionLabel, 'map')
244
- resetLegendToggles(runtimeLegend, setRuntimeLegend)
249
+ publishAnalyticsEvent({
250
+ vizType: config.type,
251
+ vizSubType: getVizSubType(config),
252
+ eventType: 'map_legend_reset',
253
+ eventAction: 'click',
254
+ eventLabel: interactionLabel,
255
+ vizTitle: getVizTitle(config)
256
+ })
257
+ resetLegendToggles(runtimeLegend, dispatch)
245
258
  dispatch({
246
259
  type: 'SET_ACCESSIBLE_STATUS',
247
260
  payload: 'Legend has been reset, please reference the data table to see updated values.'
@@ -261,14 +274,17 @@ const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
261
274
  />
262
275
  )
263
276
 
264
- const cityStyleShapes = {
265
- pin: pin,
266
- circle: <GlyphCircle color='#000' size={150} />,
267
- square: <GlyphSquare color='#000' size={150} />,
268
- diamond: <GlyphDiamond color='#000' size={150} />,
269
- star: <GlyphStar color='#000' size={150} />,
270
- triangle: <GlyphTriangle color='#000' size={150} />
271
- }
277
+ const cityStyleShapes = useMemo(
278
+ () => ({
279
+ pin: pin,
280
+ circle: <GlyphCircle color='#000' size={150} />,
281
+ square: <GlyphSquare color='#000' size={150} />,
282
+ diamond: <GlyphDiamond color='#000' size={150} />,
283
+ star: <GlyphStar color='#000' size={150} />,
284
+ triangle: <GlyphTriangle color='#000' size={150} />
285
+ }),
286
+ [pin]
287
+ )
272
288
 
273
289
  const shouldRenderLegendList = legendListItems.length > 0 && ['Select Option', ''].includes(config.legend.groupBy)
274
290
 
@@ -286,9 +302,29 @@ const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
286
302
  <section className={legendClasses.section.join(' ') || ''} aria-label='Map Legend'>
287
303
  {(legend.title || legend.description || legend.dynamicDescription) && (
288
304
  <div className='mb-3'>
289
- {legend.title && <h3 className={legendClasses.title.join(' ') || ''}>{parse(legend.title)}</h3>}
305
+ {legend.title && (
306
+ <h3 className={legendClasses.title.join(' ') || ''}>
307
+ {parse(
308
+ config.enableMarkupVariables && config.markupVariables?.length > 0
309
+ ? processMarkupVariables(legend.title, config.data || [], config.markupVariables, {
310
+ isEditor: false,
311
+ filters: config.filters || []
312
+ }).processedContent
313
+ : legend.title
314
+ )}
315
+ </h3>
316
+ )}
290
317
  {legend.dynamicDescription === false && legend.description && (
291
- <p className={legendClasses.description.join(' ') || ''}>{parse(legend.description)}</p>
318
+ <p className={legendClasses.description.join(' ') || ''}>
319
+ {parse(
320
+ config.enableMarkupVariables && config.markupVariables?.length > 0
321
+ ? processMarkupVariables(legend.description, config.data || [], config.markupVariables, {
322
+ isEditor: false,
323
+ filters: config.filters || []
324
+ }).processedContent
325
+ : legend.description
326
+ )}
327
+ </p>
292
328
  )}
293
329
  {legend.dynamicDescription === true &&
294
330
  runtimeFilters.map((filter, idx) => {
@@ -331,41 +367,41 @@ const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
331
367
 
332
368
  {((config.visual.additionalCityStyles && config.visual.additionalCityStyles.some(c => c.label)) ||
333
369
  config.visual.cityStyleLabel) && (
334
- <>
335
- <hr />
336
- <div className={legendClasses.div.join(' ') || ''}>
337
- {config.visual.cityStyleLabel && (
338
- <div>
339
- <svg>
340
- <Group
341
- top={
342
- config.visual.cityStyle === 'pin' ? 19 : config.visual.cityStyle === 'triangle' ? 13 : 11
343
- }
344
- left={10}
345
- >
346
- {cityStyleShapes[config.visual.cityStyle.toLowerCase()]}
347
- </Group>
348
- </svg>
349
- <p>{config.visual.cityStyleLabel}</p>
350
- </div>
351
- )}
352
-
353
- {config.visual.additionalCityStyles.map(
354
- ({ shape, label }) =>
355
- label && (
356
- <div>
357
- <svg>
358
- <Group top={shape === 'Pin' ? 19 : shape === 'Triangle' ? 13 : 11} left={10}>
359
- {cityStyleShapes[shape.toLowerCase()]}
360
- </Group>
361
- </svg>
362
- <p>{label}</p>
363
- </div>
364
- )
365
- )}
366
- </div>
367
- </>
368
- )}
370
+ <>
371
+ <hr />
372
+ <div className={legendClasses.div.join(' ') || ''}>
373
+ {config.visual.cityStyleLabel && (
374
+ <div>
375
+ <svg>
376
+ <Group
377
+ top={
378
+ config.visual.cityStyle === 'pin' ? 19 : config.visual.cityStyle === 'triangle' ? 13 : 11
379
+ }
380
+ left={10}
381
+ >
382
+ {cityStyleShapes[config.visual.cityStyle.toLowerCase()]}
383
+ </Group>
384
+ </svg>
385
+ <p>{config.visual.cityStyleLabel}</p>
386
+ </div>
387
+ )}
388
+
389
+ {config.visual.additionalCityStyles.map(
390
+ ({ shape, label }, index) =>
391
+ label && (
392
+ <div key={`additional-city-style-${index}-${shape}`}>
393
+ <svg>
394
+ <Group top={shape === 'Pin' ? 19 : shape === 'Triangle' ? 13 : 11} left={10}>
395
+ {cityStyleShapes[shape.toLowerCase()]}
396
+ </Group>
397
+ </svg>
398
+ <p>{label}</p>
399
+ </div>
400
+ )
401
+ )}
402
+ </div>
403
+ </>
404
+ )}
369
405
  {runtimeLegend.disabledAmt > 0 && (
370
406
  <Button className={legendClasses.showAllButton.join(' ')} onClick={handleReset}>
371
407
  Show All
@@ -1,7 +1,7 @@
1
1
  import { useContext, useMemo } from 'react'
2
2
  import './legend.group.css'
3
3
  import LegendShape from '@cdc/core/components/LegendShape'
4
- import { toggleLegendActive } from '@cdc/map/src/helpers/toggleLegendActive'
4
+ import { toggleLegendActive } from '../../../../helpers/toggleLegendActive'
5
5
  import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
6
6
  import ConfigContext, { MapDispatchContext } from '../../../../context'
7
7
 
@@ -17,7 +17,7 @@ interface GroupedData {
17
17
  }
18
18
 
19
19
  const LegendGroup = ({ legendItems }) => {
20
- const { runtimeLegend, setRuntimeLegend, config } = useContext(ConfigContext)
20
+ const { runtimeLegend, config } = useContext(ConfigContext)
21
21
  const dispatch = useContext(MapDispatchContext)
22
22
  const groupLegendItems = (items: LegendItem[], data: object[], groupByKey: string): GroupedData => {
23
23
  if (!groupByKey || !data || !items) return {}
@@ -59,10 +59,13 @@ const LegendGroup = ({ legendItems }) => {
59
59
  const wasDisabled = runtimeLegend.items.find(i => i.value === item.label)?.disabled
60
60
  const delta = wasDisabled ? -1 : 1
61
61
 
62
- setRuntimeLegend({
63
- ...runtimeLegend,
64
- items: newItems,
65
- disabledAmt: (runtimeLegend.disabledAmt ?? 0) + delta
62
+ dispatch({
63
+ type: 'SET_RUNTIME_LEGEND',
64
+ payload: {
65
+ ...runtimeLegend,
66
+ items: newItems,
67
+ disabledAmt: (runtimeLegend.disabledAmt ?? 0) + delta
68
+ }
66
69
  })
67
70
  const message = `${wasDisabled ? 'Enabled' : 'Disabled'} legend item ${
68
71
  item.label
@@ -109,7 +112,7 @@ const LegendGroup = ({ legendItems }) => {
109
112
  onKeyDown={e => {
110
113
  if (e.key === 'Enter' || e.key === ' ') {
111
114
  e.preventDefault()
112
- toggleLegendActive(index, item.label, runtimeLegend, setRuntimeLegend, setAccessibleStatus)
115
+ toggleLegendActive(index, item.label, runtimeLegend, dispatch)
113
116
  }
114
117
  }}
115
118
  >
@@ -0,0 +1,52 @@
1
+ import React from 'react'
2
+ import Modal from './Modal'
3
+ import UsaMap from './UsaMap'
4
+ import WorldMap from './WorldMap'
5
+ import GoogleMap from './GoogleMap'
6
+ import { MapConfig } from '../types/MapConfig'
7
+ import { LOGO_MAX_WIDTH } from '../helpers/constants'
8
+
9
+ interface MapContainerProps {
10
+ config: MapConfig
11
+ modal: any
12
+ currentViewport: string
13
+ geoType: string
14
+ general: any
15
+ logo: string
16
+ mapSvgRef: React.RefObject<HTMLElement>
17
+ }
18
+
19
+ const MapContainer: React.FC<MapContainerProps> = ({
20
+ config,
21
+ modal,
22
+ currentViewport,
23
+ geoType,
24
+ general,
25
+ logo,
26
+ mapSvgRef
27
+ }) => {
28
+ return (
29
+ <section className='outline-none geography-container w-100 position-relative' ref={mapSvgRef} tabIndex={0}>
30
+ {currentViewport && (
31
+ <>
32
+ {modal && <Modal />}
33
+ {'single-state' === geoType && <UsaMap.SingleState />}
34
+ {'us' === geoType && 'us-geocode' !== config.general.type && <UsaMap.State />}
35
+ {'us-region' === geoType && <UsaMap.Region />}
36
+ {'us-county' === geoType && <UsaMap.County />}
37
+ {'world' === geoType && <WorldMap />}
38
+ {'google-map' === geoType && <GoogleMap />}
39
+ {
40
+ /* logo is handled in UsaMap.State when applicable */
41
+ // prettier-ignore
42
+ 'data' === general.type && logo && ('us' !== geoType || 'us-geocode' === general.type) && (
43
+ <img src={logo} alt='' className='map-logo' style={{ maxWidth: LOGO_MAX_WIDTH }} />
44
+ )
45
+ }
46
+ </>
47
+ )}
48
+ </section>
49
+ )
50
+ }
51
+
52
+ export default MapContainer
@@ -0,0 +1,44 @@
1
+ import React from 'react'
2
+ import MediaControls from '@cdc/core/components/MediaControls'
3
+ import { MapConfig } from '../types/MapConfig'
4
+
5
+ interface MapControlsProps {
6
+ config: MapConfig
7
+ imageId: string
8
+ interactionLabel: string
9
+ }
10
+
11
+ const MapControls: React.FC<MapControlsProps> = ({ config, imageId, interactionLabel }) => {
12
+ const { showDownloadImgButton, showDownloadPdfButton } = config.general
13
+
14
+ if (!showDownloadImgButton && !showDownloadPdfButton) {
15
+ return null
16
+ }
17
+
18
+ return (
19
+ <MediaControls.Section classes={['download-buttons']}>
20
+ {showDownloadImgButton && (
21
+ <MediaControls.Button
22
+ text='Download Image'
23
+ title='Download Chart as Image'
24
+ type='image'
25
+ state={config}
26
+ elementToCapture={imageId}
27
+ interactionLabel={interactionLabel}
28
+ />
29
+ )}
30
+ {showDownloadPdfButton && (
31
+ <MediaControls.Button
32
+ text='Download PDF'
33
+ title='Download Chart as PDF'
34
+ type='pdf'
35
+ state={config}
36
+ interactionLabel={interactionLabel}
37
+ elementToCapture={imageId}
38
+ />
39
+ )}
40
+ </MediaControls.Section>
41
+ )
42
+ }
43
+
44
+ export default MapControls
@@ -1,9 +1,10 @@
1
1
  import React, { useContext, useEffect, useState } from 'react'
2
2
  import ConfigContext from '../context'
3
3
  import { publishAnalyticsEvent } from '@cdc/core/helpers/metrics/helpers'
4
+ import { getVizTitle, getVizSubType } from '@cdc/core/helpers/metrics/utils'
4
5
 
5
6
  const NavigationMenu = ({ data, navigationHandler, options, columns, displayGeoName, mapTabbingID }) => {
6
- const { interactionLabel } = useContext(ConfigContext)
7
+ const { interactionLabel, config } = useContext(ConfigContext)
7
8
  const [activeGeo, setActiveGeo] = useState('')
8
9
  const [dropdownItems, setDropdownItems] = useState({})
9
10
 
@@ -12,7 +13,15 @@ const NavigationMenu = ({ data, navigationHandler, options, columns, displayGeoN
12
13
  if (activeGeo !== '') {
13
14
  const urlString = data[dropdownItems[activeGeo]][columns.navigate.name]
14
15
 
15
- publishAnalyticsEvent('map_navigation_menu', 'submit', `${interactionLabel}|${urlString}`)
16
+ publishAnalyticsEvent({
17
+ vizType: config.type,
18
+ vizSubType: getVizSubType(config),
19
+ eventType: `map_navigation_menu`,
20
+ eventAction: 'submit',
21
+ eventLabel: `${interactionLabel}`,
22
+ vizTitle: getVizTitle(config),
23
+ specifics: `url: ${urlString}, activeGeo: ${activeGeo}`
24
+ })
16
25
 
17
26
  navigationHandler(urlString)
18
27
  }
@@ -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, data, legendMemo, legendSpecialClassLastMemo, runtimeLegend } = useContext<MapContext>(ConfigContext)
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 = data[county.id]
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
- geoData[config.columns.geo.name]
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}