@cdc/map 4.24.12 → 4.25.2-25

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 (64) hide show
  1. package/dist/cdcmap.js +50119 -48822
  2. package/examples/annotation/index.json +1 -1
  3. package/examples/custom-map-layers.json +1 -1
  4. package/examples/default-geocode.json +2 -2
  5. package/examples/example-city-state.json +1 -1
  6. package/examples/private/DEV-9989.json +229 -0
  7. package/examples/private/ardi.json +180 -0
  8. package/examples/private/colors 2.json +416 -0
  9. package/examples/private/colors.json +416 -0
  10. package/examples/private/colors.json.zip +0 -0
  11. package/examples/private/customColors.json +45348 -0
  12. package/examples/private/mmr.json +246 -0
  13. package/examples/private/test.json +1632 -0
  14. package/index.html +12 -14
  15. package/package.json +8 -3
  16. package/src/CdcMap.tsx +126 -396
  17. package/src/_stories/CdcMap.Filters.stories.tsx +19 -0
  18. package/src/_stories/CdcMap.Legend.Gradient.stories.tsx +9 -0
  19. package/src/_stories/CdcMap.stories.tsx +1 -1
  20. package/src/_stories/GoogleMap.stories.tsx +19 -0
  21. package/src/_stories/_mock/DEV-10148.json +859 -0
  22. package/src/_stories/_mock/DEV-9989.json +229 -0
  23. package/src/_stories/_mock/example-city-state.json +1 -1
  24. package/src/_stories/_mock/google-map.json +819 -0
  25. package/src/_stories/_mock/wastewater-map.json +210 -206
  26. package/src/components/Annotation/Annotation.Draggable.tsx +34 -43
  27. package/src/components/Annotation/AnnotationDropdown.tsx +4 -4
  28. package/src/components/CityList.tsx +3 -9
  29. package/src/components/DataTable.tsx +8 -9
  30. package/src/components/EditorPanel/components/EditorPanel.tsx +255 -490
  31. package/src/components/GoogleMap/components/GoogleMap.tsx +67 -0
  32. package/src/components/GoogleMap/index.tsx +3 -0
  33. package/src/components/Legend/components/Legend.tsx +40 -30
  34. package/src/components/Legend/components/LegendItem.Hex.tsx +7 -3
  35. package/src/components/Legend/components/index.scss +22 -16
  36. package/src/components/Modal.tsx +6 -5
  37. package/src/components/NavigationMenu.tsx +4 -3
  38. package/src/components/UsaMap/components/TerritoriesSection.tsx +66 -0
  39. package/src/components/UsaMap/components/Territory/Territory.Hexagon.tsx +15 -16
  40. package/src/components/UsaMap/components/Territory/Territory.Rectangle.tsx +3 -3
  41. package/src/components/UsaMap/components/UsaMap.County.tsx +1 -1
  42. package/src/components/UsaMap/components/UsaMap.Region.tsx +12 -8
  43. package/src/components/UsaMap/components/UsaMap.SingleState.tsx +2 -2
  44. package/src/components/UsaMap/components/UsaMap.State.tsx +23 -29
  45. package/src/components/WorldMap/WorldMap.tsx +3 -5
  46. package/src/context.ts +0 -12
  47. package/src/data/initial-state.js +2 -2
  48. package/src/data/supported-geos.js +23 -3
  49. package/src/helpers/applyColorToLegend.ts +3 -3
  50. package/src/helpers/closeModal.ts +9 -0
  51. package/src/helpers/handleMapAriaLabels.ts +38 -0
  52. package/src/helpers/indexOfIgnoreType.ts +8 -0
  53. package/src/helpers/navigationHandler.ts +21 -0
  54. package/src/helpers/toTitleCase.ts +44 -0
  55. package/src/helpers/validateFipsCodeLength.ts +30 -0
  56. package/src/hooks/useResizeObserver.ts +42 -0
  57. package/src/hooks/useTooltip.ts +4 -2
  58. package/src/index.jsx +1 -0
  59. package/src/scss/editor-panel.scss +2 -1
  60. package/src/scss/filters.scss +0 -5
  61. package/src/scss/main.scss +57 -61
  62. package/src/scss/map.scss +1 -13
  63. package/src/types/MapConfig.ts +20 -11
  64. package/src/types/MapContext.ts +4 -12
@@ -0,0 +1,67 @@
1
+ import React, { useContext, useEffect, useRef } from 'react'
2
+ import { Loader } from '@googlemaps/js-api-loader'
3
+ import { MarkerClusterer } from '@googlemaps/markerclusterer'
4
+ import ConfigContext from '../../../context'
5
+
6
+ // center on USA
7
+ const center = {
8
+ lat: 37.09024,
9
+ lng: -95.712891
10
+ }
11
+
12
+ type GoogleMapComponentProps = {
13
+ apiKey?: string
14
+ }
15
+
16
+ const GoogleMapComponent: React.FC<GoogleMapComponentProps> = ({ apiKey = '' }) => {
17
+ const mapRef = useRef(null)
18
+ const { state } = useContext(ConfigContext)
19
+
20
+ useEffect(() => {
21
+ const loader = new Loader({
22
+ apiKey: apiKey,
23
+ version: 'weekly'
24
+ })
25
+
26
+ loader
27
+ .load()
28
+ .then(() => {
29
+ if (mapRef.current) {
30
+ const map = new window.google.maps.Map(mapRef.current, {
31
+ center: center,
32
+ zoom: 4
33
+ })
34
+
35
+ const markers = state.data.map(d => {
36
+ const markerContent = document.createElement('div')
37
+ markerContent.style.backgroundColor = 'orange' // Set the background color
38
+ markerContent.style.width = '25px'
39
+ markerContent.style.height = '25px'
40
+ markerContent.style.borderRadius = '50%'
41
+ markerContent.style.display = 'flex'
42
+ markerContent.style.alignItems = 'center'
43
+ markerContent.style.justifyContent = 'center'
44
+ markerContent.style.color = 'white'
45
+ markerContent.innerText = d[state.columns.geo.name]
46
+
47
+ const marker = new google.maps.Marker({
48
+ position: { lat: Number(d[state.columns.latitude.name]), lng: Number(d[state.columns.longitude.name]) },
49
+ title: d[state.columns.geo.name],
50
+ map: map
51
+ })
52
+
53
+ return marker
54
+ })
55
+
56
+ new MarkerClusterer({ markers, map })
57
+ }
58
+ })
59
+ .catch(e => {
60
+ console.error('Error loading Google Maps API:', e)
61
+ })
62
+ }, [apiKey, state])
63
+
64
+ return <div ref={mapRef} style={{ height: '500px', width: '100%' }} />
65
+ }
66
+
67
+ export default GoogleMapComponent
@@ -0,0 +1,3 @@
1
+ import GoogleMap from './components/GoogleMap'
2
+
3
+ export default GoogleMap
@@ -1,5 +1,5 @@
1
1
  //TODO: Move legends to core
2
- import { forwardRef, useContext, useId } from 'react'
2
+ import { forwardRef, useContext } from 'react'
3
3
  import parse from 'html-react-parser'
4
4
 
5
5
  //types
@@ -17,8 +17,9 @@ import { PatternLines, PatternCircles, PatternWaves } from '@visx/pattern'
17
17
  import { GlyphStar, GlyphTriangle, GlyphDiamond, GlyphSquare, GlyphCircle } from '@visx/glyph'
18
18
  import { Group } from '@visx/group'
19
19
  import './index.scss'
20
- import { ViewportSize } from '@cdc/chart/src/types/ChartConfig'
20
+ import { type ViewPort } from '@cdc/core/types/ViewPort'
21
21
  import { isBelowBreakpoint, isMobileHeightViewport } from '@cdc/core/helpers/viewports'
22
+ import { displayDataAsText } from '@cdc/core/helpers/displayDataAsText'
22
23
 
23
24
  const LEGEND_PADDING = 30
24
25
 
@@ -26,22 +27,22 @@ type LegendProps = {
26
27
  skipId: string
27
28
  dimensions: DimensionsType
28
29
  containerWidthPadding: number
29
- currentViewport: ViewportSize
30
+ currentViewport: ViewPort
30
31
  }
31
32
 
32
33
  const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
33
- const { skipId, dimensions, containerWidthPadding, currentViewport } = props
34
+ const { skipId, containerWidthPadding } = props
35
+ const { isEditor, dimensions, currentViewport } = useContext(ConfigContext)
34
36
 
35
37
  const {
36
38
  // prettier-ignore
37
- displayDataAsText,
38
39
  resetLegendToggles,
39
40
  runtimeFilters,
40
41
  runtimeLegend,
41
42
  setAccessibleStatus,
42
43
  setRuntimeLegend,
43
44
  state,
44
- viewport,
45
+ currentViewport: viewport,
45
46
  mapId
46
47
  } = useContext(ConfigContext)
47
48
 
@@ -75,9 +76,9 @@ const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
75
76
  }
76
77
  const getFormattedLegendItems = () => {
77
78
  return runtimeLegend.map((entry, idx) => {
78
- const entryMax = displayDataAsText(entry.max, 'primary')
79
+ const entryMax = displayDataAsText(entry.max, 'primary', state)
79
80
 
80
- const entryMin = displayDataAsText(entry.min, 'primary')
81
+ const entryMin = displayDataAsText(entry.min, 'primary', state)
81
82
  let formattedText = `${entryMin}${entryMax !== entryMin ? ` - ${entryMax}` : ''}`
82
83
 
83
84
  // If interval, add some formatting
@@ -86,7 +87,7 @@ const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
86
87
  }
87
88
 
88
89
  if (legend.type === 'category') {
89
- formattedText = displayDataAsText(entry.value, 'primary')
90
+ formattedText = displayDataAsText(entry.value, 'primary', state)
90
91
  }
91
92
 
92
93
  if (entry.max === 0 && entry.min === 0) {
@@ -116,12 +117,14 @@ const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
116
117
  const legendList = (patternsOnly = false) => {
117
118
  const formattedItems = patternsOnly ? [] : getFormattedLegendItems()
118
119
  const patternsOnlyFont = isMobileHeightViewport(currentViewport) ? '12px' : '14px'
120
+ const hasDisabledItems = formattedItems.some(item => item.disabled)
119
121
  let legendItems
120
122
 
121
123
  legendItems = formattedItems.map((item, idx) => {
122
124
  const handleListItemClass = () => {
123
125
  let classes = ['legend-container__li', 'd-flex', 'align-items-center']
124
126
  if (item.disabled) classes.push('legend-container__li--disabled')
127
+ else if (hasDisabledItems) classes.push('legend-container__li--not-disabled')
125
128
  if (item.special) classes.push('legend-container__li--special-class')
126
129
  return classes.join(' ')
127
130
  }
@@ -264,26 +267,33 @@ const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
264
267
  ref={ref}
265
268
  >
266
269
  <section className={legendClasses.section.join(' ') || ''} aria-label='Map Legend'>
267
- {legend.title && <h3 className={legendClasses.title.join(' ') || ''}>{parse(legend.title)}</h3>}
268
- {legend.dynamicDescription === false && legend.description && (
269
- <p className={legendClasses.description.join(' ') || ''}>{parse(legend.description)}</p>
270
+ {(legend.title || legend.description || legend.dynamicDescription) && (
271
+ <div className='mb-3'>
272
+ {legend.title && <h3 className={legendClasses.title.join(' ') || ''}>{parse(legend.title)}</h3>}
273
+ {legend.dynamicDescription === false && legend.description && (
274
+ <p className={legendClasses.description.join(' ') || ''}>{parse(legend.description)}</p>
275
+ )}
276
+ {legend.dynamicDescription === true &&
277
+ runtimeFilters.map((filter, idx) => {
278
+ const lookupStr = `${idx},${filter.values.indexOf(String(filter.active))}`
279
+
280
+ // Do we have a custom description for this?
281
+ const desc = legend.descriptions[lookupStr] || ''
282
+
283
+ if (desc.length > 0) {
284
+ return (
285
+ <p
286
+ key={`dynamic-description-${lookupStr}`}
287
+ className={`dynamic-legend-description-${lookupStr} mt-2`}
288
+ >
289
+ {desc}
290
+ </p>
291
+ )
292
+ }
293
+ return true
294
+ })}
295
+ </div>
270
296
  )}
271
- {legend.dynamicDescription === true &&
272
- runtimeFilters.map((filter, idx) => {
273
- const lookupStr = `${idx},${filter.values.indexOf(String(filter.active))}`
274
-
275
- // Do we have a custom description for this?
276
- const desc = legend.descriptions[lookupStr] || ''
277
-
278
- if (desc.length > 0) {
279
- return (
280
- <p key={`dynamic-description-${lookupStr}`} className={`dynamic-legend-description-${lookupStr}`}>
281
- {desc}
282
- </p>
283
- )
284
- }
285
- return true
286
- })}
287
297
 
288
298
  <LegendGradient
289
299
  labels={getFormattedLegendItems().map(item => item?.label) ?? []}
@@ -334,8 +344,8 @@ const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
334
344
  </>
335
345
  )}
336
346
  {runtimeLegend.disabledAmt > 0 && (
337
- <Button className={legendClasses.resetButton.join(' ')} onClick={handleReset}>
338
- Reset
347
+ <Button className={legendClasses.showAllButton.join(' ')} onClick={handleReset}>
348
+ Show All
339
349
  </Button>
340
350
  )}
341
351
  </section>
@@ -3,7 +3,7 @@ import { AiOutlineArrowUp, AiOutlineArrowDown, AiOutlineArrowRight } from 'react
3
3
  import parse from 'html-react-parser'
4
4
 
5
5
  const LegendItemHex = props => {
6
- const { state, viewport } = props
6
+ const { state, currentViewport: viewport } = props
7
7
 
8
8
  const getItemShape = shape => {
9
9
  switch (shape) {
@@ -27,8 +27,12 @@ const LegendItemHex = props => {
27
27
  return (
28
28
  <aside id='legend' className={legendClasses.aside.join(' ')} role='region' aria-label='Legend' tabIndex={0}>
29
29
  <section className={legendClasses.section.join(' ')} aria-label='Map Legend'>
30
- {shapeGroup.legendTitle && <h3 className={legendClasses.title.join(' ')}>{parse(shapeGroup.legendTitle)}</h3>}
31
- {shapeGroup.legendDescription && <p className={legendClasses.description.join(' ')}>{parse(shapeGroup.legendDescription)}</p>}
30
+ {shapeGroup.legendTitle && (
31
+ <h3 className={legendClasses.title.join(' ')}>{parse(shapeGroup.legendTitle)}</h3>
32
+ )}
33
+ {shapeGroup.legendDescription && (
34
+ <p className={legendClasses.description.join(' ')}>{parse(shapeGroup.legendDescription)}</p>
35
+ )}
32
36
 
33
37
  <ul className={legendClasses.ul.join(' ')} aria-label='Legend items' style={{ listStyle: 'none' }}>
34
38
  {shapeGroup.items.map((item, itemIndex) => {
@@ -16,11 +16,9 @@
16
16
  background-color: #fff;
17
17
  z-index: 6;
18
18
  border-top: var(--cool-gray-10) 1px solid;
19
+ border-radius: 6px;
19
20
 
20
21
  @include breakpointClass(md) {
21
- .legend-container.legend-padding {
22
- padding: 15px;
23
- }
24
22
  &.bottom,
25
23
  &.top {
26
24
  border: var(--cool-gray-10) 1px solid;
@@ -70,21 +68,21 @@
70
68
  }
71
69
 
72
70
  .legend-container {
73
- --space-between-legend-items: 0.6em;
74
71
  position: relative;
75
- &.legend-padding {
76
- padding-top: 15px;
72
+
73
+ .tspan {
74
+ font-size: var(--legend-item-font-size) !important;
77
75
  }
76
+
78
77
  .legend-container__title {
79
- font-size: 1.3em;
78
+ font-size: var(--legend-title-font-size);
79
+ font-weight: var(--legend-title-font-weight);
80
80
  padding-bottom: 0;
81
81
  display: inline-block;
82
+ color: black;
82
83
  }
83
- .legend-container__title + p,
84
- .legend-container__title + ul,
85
- p + ul,
86
- p + p {
87
- padding-top: 1em;
84
+ .legend-container__description {
85
+ font-size: var(--legend-description-font-size);
88
86
  }
89
87
  .legend-container__reset-button {
90
88
  margin-top: 1em;
@@ -92,11 +90,17 @@
92
90
  p {
93
91
  line-height: 1.4em;
94
92
  }
93
+
94
+ .legend-container__ul {
95
+ line-height: 1;
96
+ row-gap: var(--space-between-legend-item-rows);
97
+ column-gap: var(--space-between-legend-item-columns);
98
+ }
95
99
  .legend-container__ul:not(.single-row) {
96
100
  list-style: none;
97
- padding-top: 1em;
98
101
  display: grid;
99
102
  grid-template-columns: 1fr;
103
+
100
104
  @include breakpoint(md) {
101
105
  grid-template-columns: 1fr 1fr;
102
106
  }
@@ -117,7 +121,6 @@
117
121
  flex-shrink: 0;
118
122
  display: inline-block;
119
123
  padding-right: 1em;
120
- margin-bottom: var(--space-between-legend-items);
121
124
  vertical-align: middle;
122
125
  transition: 0.1s opacity;
123
126
  display: flex;
@@ -134,6 +137,11 @@
134
137
  &.legend-container__li--disabled {
135
138
  opacity: 0.4;
136
139
  }
140
+ &.legend-container__li--not-disabled {
141
+ outline: 1px solid #005ea2;
142
+ outline-offset: 5px;
143
+ border-radius: 1px;
144
+ }
137
145
  }
138
146
  }
139
147
  .legend-container__ul.single-row {
@@ -145,10 +153,8 @@
145
153
  align-items: center;
146
154
  justify-content: flex-start;
147
155
  flex-wrap: wrap;
148
- row-gap: var(--space-between-legend-items);
149
156
 
150
157
  & > li {
151
- margin-right: 1em;
152
158
  white-space: wrap;
153
159
  display: flex;
154
160
  justify-content: center;
@@ -4,18 +4,19 @@ import ConfigContext from '../context'
4
4
  import Icon from '@cdc/core/components/ui/Icon'
5
5
 
6
6
  const Modal = () => {
7
- const { applyTooltipsToGeo, capitalize, applyLegendToRow, viewport, type, content } = useContext(ConfigContext)
8
-
7
+ const { applyTooltipsToGeo, applyLegendToRow, content, state, currentViewport: viewport } = useContext(ConfigContext)
8
+ const { capitalizeLabels } = state.tooltips
9
9
  const tooltip = applyTooltipsToGeo(content.geoName, content.keyedData, 'jsx')
10
-
10
+ const type = state.general.type
11
11
  const legendColors = applyLegendToRow(content.keyedData)
12
12
 
13
13
  return (
14
14
  <section
15
- className={capitalize ? 'modal-content tooltip capitalize ' + viewport : 'modal-content tooltip ' + viewport}
15
+ className={
16
+ capitalizeLabels ? 'modal-content tooltip capitalize ' + viewport : 'modal-content tooltip ' + viewport
17
+ }
16
18
  aria-hidden='true'
17
19
  >
18
- {type === 'data' && <LegendShape fill={legendColors[0]} />}
19
20
  <div className='content'>{tooltip}</div>
20
21
  <Icon display='close' alt='Close Modal' size={20} color='#000' className='modal-close' />
21
22
  </section>
@@ -1,8 +1,9 @@
1
- import React, { useEffect, useState } from 'react'
1
+ import React, { useContext, useEffect, useState } from 'react'
2
+ import ConfigContext from '../context'
2
3
 
3
4
  const NavigationMenu = ({ data, navigationHandler, options, columns, displayGeoName, mapTabbingID }) => {
5
+ const { state } = useContext(ConfigContext)
4
6
  const [activeGeo, setActiveGeo] = useState('')
5
-
6
7
  const [dropdownItems, setDropdownItems] = useState({})
7
8
 
8
9
  const handleSubmit = event => {
@@ -55,7 +56,7 @@ const NavigationMenu = ({ data, navigationHandler, options, columns, displayGeoN
55
56
  <label htmlFor={mapTabbingID.replace('#', '')}>
56
57
  <div className='select-heading'>{navSelect}</div>
57
58
  <select value={activeGeo} id={mapTabbingID.replace('#', '')} onChange={e => setActiveGeo(e.target.value)}>
58
- {Object.keys(dropdownItems).map((key, i) => (
59
+ {Object.keys(dropdownItems).map(key => (
59
60
  <option key={key} value={key}>
60
61
  {key}
61
62
  </option>
@@ -0,0 +1,66 @@
1
+ import React from 'react'
2
+
3
+ type TerritoriesSectionProps = {
4
+ territories: JSX.Element[]
5
+ // Keys of the territories to display
6
+ territoresData: string[]
7
+ logo: string
8
+ config: any
9
+ }
10
+
11
+ const TerritoriesSection: React.FC<TerritoriesSectionProps> = ({ territories, logo, config, territoriesData }) => {
12
+ // filter territioriesData into the two groups below
13
+ const freelyAssociatedKeys = territoriesData.filter(territory => {
14
+ return ['US-FM', 'US-MH', 'US-PW'].includes(territory)
15
+ })
16
+ const usTerritoriesKeys = territoriesData.filter(territory => {
17
+ return ['US-AS', 'US-GU', 'US-MP', 'US-PR', 'US-VI'].includes(territory)
18
+ })
19
+
20
+ const usTerritories = territories
21
+ .filter(territory => {
22
+ return usTerritoriesKeys.includes(`US-${territory?.props?.label}`)
23
+ })
24
+ .sort((a, b) => {
25
+ return a.props.label.localeCompare(b.props.label)
26
+ })
27
+
28
+ const freelyAssociatedStates = territories
29
+ .filter(territory => {
30
+ return freelyAssociatedKeys.includes(`US-${territory?.props?.label}`)
31
+ })
32
+ .sort((a, b) => {
33
+ return a.props.label.localeCompare(b.props.label)
34
+ })
35
+
36
+ return (
37
+ territoriesData.length > 0 && (
38
+ <>
39
+ {/* Temporarily make the max width fit the image width */}
40
+ <div>
41
+ <div className='d-flex mt-4'>
42
+ {'data' === config.general.type && logo && (
43
+ <img src={logo} alt='' className='map-logo' style={{ maxWidth: '50px' }} />
44
+ )}
45
+ </div>
46
+ <div>
47
+ {(usTerritories.length > 0 || config.general.territoriesAlwaysShow) && (
48
+ <>
49
+ <h5 className='territories-label'>U.S. Territories</h5>
50
+ <span className='mt-1 mb-2 d-flex flex-wrap territories'>{usTerritories}</span>
51
+ </>
52
+ )}
53
+ {(freelyAssociatedStates.length > 0 || config.general.territoriesAlwaysShow) && (
54
+ <>
55
+ <h5 className='territories-label'>Freely Associated States</h5>
56
+ <span className='mt-1 mb-2 d-flex flex-wrap territories'>{freelyAssociatedStates}</span>
57
+ </>
58
+ )}
59
+ </div>
60
+ </div>
61
+ </>
62
+ )
63
+ )
64
+ }
65
+
66
+ export default TerritoriesSection
@@ -52,7 +52,7 @@ const TerritoryHexagon = ({
52
52
  const hexagonLabel = (geo, bgColor = '#FFFFFF', projection) => {
53
53
  let centroid = projection ? projection(geoCentroid(geo)) : [22, 17.5]
54
54
 
55
- let abbr = geo?.properties?.iso ? geo.properties.iso : geo.uid
55
+ let abbr = geo?.properties?.iso ? geo.properties.iso : geo?.uid ? geo.uid : `US-${label}`
56
56
 
57
57
  const getArrowDirection = (geoData, geo, isTerritory = false) => {
58
58
  if (!isTerritory) {
@@ -63,19 +63,20 @@ const TerritoryHexagon = ({
63
63
  <>
64
64
  {state.hexMap.shapeGroups.map((group, groupIndex) => {
65
65
  return group.items.map((item, itemIndex) => {
66
+ if (!geoData) return
66
67
  switch (item.operator) {
67
68
  case '=':
68
- if (geoData[item.key] === item.value || Number(geoData[item.key]) === Number(item.value)) {
69
+ if (geoData?.[item.key] === item.value || Number(geoData[item.key]) === Number(item.value)) {
69
70
  return <HexIcon item={item} index={itemIndex} centroid={centroid} isTerritory />
70
71
  }
71
72
  break
72
73
  case '≠':
73
- if (geoData[item.key] !== item.value && Number(geoData[item.key]) !== Number(item.value)) {
74
+ if (geoData?.[item.key] !== item.value && Number(geoData[item.key]) !== Number(item.value)) {
74
75
  return <HexIcon item={item} index={itemIndex} centroid={centroid} isTerritory />
75
76
  }
76
77
  break
77
78
  case '<':
78
- if (Number(geoData[item.key]) < Number(item.value)) {
79
+ if (Number(geoData?.[item.key]) < Number(item.value)) {
79
80
  return <HexIcon item={item} index={itemIndex} centroid={centroid} isTerritory />
80
81
  }
81
82
  break
@@ -175,18 +176,16 @@ const TerritoryHexagon = ({
175
176
  }
176
177
 
177
178
  return (
178
- territoryData && (
179
- <svg viewBox='-1 -1 46 53' className='territory-wrapper--hex'>
180
- <g {...props} data-tooltip-html={dataTooltipHtml} data-tooltip-id={dataTooltipId} onClick={handleShapeClick}>
181
- <polygon
182
- stroke={stroke}
183
- strokeWidth={strokeWidth}
184
- points='22 0 44 12.702 44 38.105 22 50.807 0 38.105 0 12.702'
185
- />
186
- {state.general.displayAsHex && hexagonLabel(territoryData, stroke, false)}
187
- </g>
188
- </svg>
189
- )
179
+ <svg viewBox='-1 -1 46 53' className='territory-wrapper--hex'>
180
+ <g {...props} data-tooltip-html={dataTooltipHtml} data-tooltip-id={dataTooltipId} onClick={handleShapeClick}>
181
+ <polygon
182
+ stroke={stroke}
183
+ strokeWidth={strokeWidth}
184
+ points='22 0 44 12.702 44 38.105 22 50.807 0 38.105 0 12.702'
185
+ />
186
+ {state.general.displayAsHex && hexagonLabel(territoryData, stroke, false)}
187
+ </g>
188
+ </svg>
190
189
  )
191
190
  }
192
191
 
@@ -54,7 +54,7 @@ const TerritoryRectangle: React.FC<TerritoryShape> = ({
54
54
 
55
55
  {state.map.patterns.map((patternData, patternIndex) => {
56
56
  const patternColor = patternData.color || getContrastColor('#FFF', backgroundColor)
57
- const hasMatchingValues = patternData.dataValue === territoryData[patternData.dataKey]
57
+ const hasMatchingValues = patternData.dataValue === territoryData?.[patternData.dataKey]
58
58
 
59
59
  if (!hasMatchingValues) return null
60
60
  if (!patternData.pattern) return null
@@ -96,8 +96,8 @@ const TerritoryRectangle: React.FC<TerritoryShape> = ({
96
96
  fill={`url(#territory-${territory}-${patternData?.dataKey}--${patternIndex})`}
97
97
  color={patternData ? 'white' : textColor}
98
98
  className={[
99
- `territory-pattern-${patternData.dataKey}`,
100
- `territory-pattern-${patternData.dataKey}--${patternData.dataValue}`
99
+ `territory-pattern-${patternData?.dataKey}`,
100
+ `territory-pattern-${patternData?.dataKey}--${patternData.dataValue}`
101
101
  ].join(' ')}
102
102
  />
103
103
  <text
@@ -22,6 +22,7 @@ import {
22
22
  createShapeProperties
23
23
  } from '../helpers/shapes'
24
24
  import { getGeoStrokeColor } from '../../../helpers/colors'
25
+ import { handleMapAriaLabels } from '../../../helpers/handleMapAriaLabels'
25
26
 
26
27
  const getCountyTopoURL = year => {
27
28
  return `https://www.cdc.gov/TemplatePackage/contrib/data/county-topography/cb_${year}_us_county_20m.json`
@@ -137,7 +138,6 @@ const CountyMap = props => {
137
138
  data,
138
139
  displayGeoName,
139
140
  geoClickHandler,
140
- handleMapAriaLabels,
141
141
  runtimeFilters,
142
142
  runtimeLegend,
143
143
  setState,
@@ -1,17 +1,23 @@
1
1
  import { useState, useEffect, memo, useContext } from 'react'
2
- import { getContrastColor } from '@cdc/core/helpers/cove/accessibility'
3
2
 
4
3
  // 3rd party
5
4
  import { geoCentroid } from 'd3-geo'
6
5
  import { feature } from 'topojson-client'
7
6
  import { Mercator } from '@visx/geo'
8
7
 
9
- // cdc
8
+ // Cdc Components
10
9
  import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
11
- import topoJSON from '../data/us-regions-topo-2.json'
12
10
  import ConfigContext from '../../../context'
13
11
  import Annotation from '../../Annotation'
12
+
13
+ // Data
14
+ import topoJSON from '../data/us-regions-topo-2.json'
15
+ import { supportedTerritories } from '../../../data/supported-geos'
16
+
17
+ // Helpers
18
+ import { getContrastColor } from '@cdc/core/helpers/cove/accessibility'
14
19
  import { getGeoFillColor, getGeoStrokeColor } from '../../../helpers/colors'
20
+ import { handleMapAriaLabels } from '../../../helpers/handleMapAriaLabels'
15
21
 
16
22
  const { features: unitedStates } = feature(topoJSON, topoJSON.objects.regions)
17
23
 
@@ -40,9 +46,7 @@ const UsaRegionMap = props => {
40
46
  data,
41
47
  displayGeoName,
42
48
  geoClickHandler,
43
- handleMapAriaLabels,
44
49
  state,
45
- supportedTerritories,
46
50
  tooltipId
47
51
  } = useContext(ConfigContext)
48
52
 
@@ -150,8 +154,8 @@ const UsaRegionMap = props => {
150
154
  <line
151
155
  x1={centroid[0]}
152
156
  y1={centroid[1]}
153
- x2={centroid[0] + dx}
154
- y2={centroid[1] + dy}
157
+ x2={centroid[0] + x}
158
+ y2={centroid[1] + y}
155
159
  stroke='rgba(0,0,0,.5)'
156
160
  strokeWidth={1}
157
161
  />
@@ -161,7 +165,7 @@ const UsaRegionMap = props => {
161
165
  fontSize={13}
162
166
  style={{ fill: '#202020' }}
163
167
  alignmentBaseline='middle'
164
- transform={`translate(${centroid[0] + dx}, ${centroid[1] + dy})`}
168
+ transform={`translate(${centroid[0] + x}, ${centroid[1] + y})`}
165
169
  >
166
170
  {abbr.substring(3)}
167
171
  </text>
@@ -15,6 +15,8 @@ import { MapContext } from '../../../types/MapContext'
15
15
  import useStateZoom from '../../../hooks/useStateZoom'
16
16
  import { Text } from '@visx/text'
17
17
  import { getGeoStrokeColor } from '../../../helpers/colors'
18
+ import { handleMapAriaLabels } from '../../../helpers/handleMapAriaLabels'
19
+ import { titleCase } from '../../../helpers/titleCase'
18
20
 
19
21
  // SVG ITEMS
20
22
  const WIDTH = 880
@@ -29,8 +31,6 @@ const SingleStateMap = props => {
29
31
  geoClickHandler,
30
32
  applyLegendToRow,
31
33
  displayGeoName,
32
- handleMapAriaLabels,
33
- titleCase,
34
34
  setSharedFilterValue,
35
35
  isFilterValueSupported,
36
36
  runtimeFilters,