@cdc/map 4.24.12-2 → 4.25.1

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 (52) hide show
  1. package/dist/cdcmap.js +47146 -45979
  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/private/mmr.json +246 -0
  6. package/index.html +12 -14
  7. package/package.json +8 -3
  8. package/src/CdcMap.tsx +85 -362
  9. package/src/_stories/CdcMap.Legend.Gradient.stories.tsx +9 -0
  10. package/src/_stories/CdcMap.stories.tsx +1 -1
  11. package/src/_stories/GoogleMap.stories.tsx +19 -0
  12. package/src/_stories/_mock/DEV-10148.json +859 -0
  13. package/src/_stories/_mock/DEV-9989.json +229 -0
  14. package/src/_stories/_mock/example-city-state.json +1 -1
  15. package/src/_stories/_mock/google-map.json +819 -0
  16. package/src/components/Annotation/Annotation.Draggable.tsx +34 -43
  17. package/src/components/Annotation/AnnotationDropdown.tsx +4 -4
  18. package/src/components/CityList.tsx +2 -2
  19. package/src/components/DataTable.tsx +8 -9
  20. package/src/components/EditorPanel/components/EditorPanel.tsx +90 -17
  21. package/src/components/GoogleMap/components/GoogleMap.tsx +67 -0
  22. package/src/components/GoogleMap/index.tsx +3 -0
  23. package/src/components/Legend/components/Legend.tsx +40 -30
  24. package/src/components/Legend/components/LegendItem.Hex.tsx +7 -3
  25. package/src/components/Legend/components/index.scss +22 -16
  26. package/src/components/Modal.tsx +6 -5
  27. package/src/components/NavigationMenu.tsx +5 -4
  28. package/src/components/UsaMap/components/TerritoriesSection.tsx +56 -0
  29. package/src/components/UsaMap/components/UsaMap.County.tsx +1 -1
  30. package/src/components/UsaMap/components/UsaMap.Region.tsx +12 -8
  31. package/src/components/UsaMap/components/UsaMap.SingleState.tsx +2 -2
  32. package/src/components/UsaMap/components/UsaMap.State.tsx +22 -28
  33. package/src/components/WorldMap/WorldMap.tsx +3 -5
  34. package/src/context.ts +0 -12
  35. package/src/data/initial-state.js +2 -2
  36. package/src/data/supported-geos.js +23 -3
  37. package/src/helpers/applyColorToLegend.ts +3 -3
  38. package/src/helpers/closeModal.ts +9 -0
  39. package/src/helpers/handleMapAriaLabels.ts +38 -0
  40. package/src/helpers/indexOfIgnoreType.ts +8 -0
  41. package/src/helpers/navigationHandler.ts +21 -0
  42. package/src/helpers/toTitleCase.ts +44 -0
  43. package/src/helpers/validateFipsCodeLength.ts +30 -0
  44. package/src/hooks/useResizeObserver.ts +42 -0
  45. package/src/hooks/useTooltip.ts +4 -2
  46. package/src/index.jsx +1 -0
  47. package/src/scss/editor-panel.scss +2 -1
  48. package/src/scss/filters.scss +0 -5
  49. package/src/scss/main.scss +57 -61
  50. package/src/scss/map.scss +1 -13
  51. package/src/types/MapConfig.ts +19 -11
  52. package/src/types/MapContext.ts +4 -12
@@ -1,15 +1,9 @@
1
- import { useContext, useState, useEffect, useRef } from 'react'
2
-
3
- // helpers
4
- import { applyBandScaleOffset, handleConnectionHorizontalType, handleConnectionVerticalType, createPoints } from '@cdc/chart/src/components/Annotations/components/helpers'
1
+ import { useContext, useState, useRef } from 'react'
5
2
 
6
3
  // visx
7
- import { HtmlLabel, CircleSubject, LineSubject, EditableAnnotation, Connector, Annotation as VisxAnnotation } from '@visx/annotation'
4
+ import { HtmlLabel, CircleSubject, EditableAnnotation, Connector, Annotation as VisxAnnotation } from '@visx/annotation'
8
5
  import { Drag, raise } from '@visx/drag'
9
6
  import { MarkerArrow } from '@visx/marker'
10
- import { LinePath } from '@visx/shape'
11
- import * as allCurves from '@visx/curve'
12
- import { Annotation } from '@cdc/core/types/Annotation'
13
7
 
14
8
  // styles
15
9
  import './Annotation.Draggable.styles.css'
@@ -18,41 +12,18 @@ import { MapContext } from '../../types/MapContext'
18
12
 
19
13
  const Annotations = ({ xScale, yScale, xMax, svgRef, onDragStateChange }) => {
20
14
  const [draggingItems, setDraggingItems] = useState([])
21
- const { state: config, dimensions, setState: updateConfig, isEditor, isDraggingAnnotation } = useContext<MapContext>(ConfigContext)
15
+ const {
16
+ state: config,
17
+ setState: updateConfig,
18
+ isDraggingAnnotation,
19
+ isEditor,
20
+ dimensions
21
+ } = useContext<MapContext>(ConfigContext)
22
22
  const [width, height] = dimensions
23
23
  const { annotations } = config
24
- // const { colorScale } = useColorScale()
25
24
  const prevDimensions = useRef(dimensions)
26
25
  const AnnotationComponent = isEditor ? EditableAnnotation : VisxAnnotation
27
26
 
28
- const handleMobileXPosition = annotation => {
29
- if (annotation.snapToNearestPoint) {
30
- return Number(annotation.dx) + xScale(annotation.xKey) + (config.xAxis.type !== 'date-time' ? xScale.bandwidth() / 2 : 0) + Number(config.yAxis.size)
31
- }
32
- return Number(annotation.x) + Number(annotation.dx)
33
- }
34
-
35
- const handleMobileYPosition = annotation => {
36
- if (annotation.snapToNearestPoint) {
37
- return yScale(annotation.yKey) + Number(annotation.dy)
38
- }
39
- return Number(annotation.dy) + Number(annotation.y)
40
- }
41
-
42
- const handleTextX = annotation => {
43
- if (annotation.snapToNearestPoint) {
44
- return Number(annotation.dx) + Number(xScale(annotation.xKey)) + (config.xAxis.type !== 'date-time' ? xScale.bandwidth() / 2 : 0) + Number(config.yAxis.size) - 16 / 3
45
- }
46
- return Number(annotation.dx) + Number(annotation.x) - 16 / 3
47
- }
48
-
49
- const handleTextY = annotation => {
50
- if (annotation.snapToNearestPoint) {
51
- return yScale(annotation.yKey) + Number(annotation.dy) + 5
52
- }
53
- return Number(annotation.y) + Number(annotation.dy) + 16 / 3
54
- }
55
-
56
27
  return (
57
28
  annotations &&
58
29
  annotations.map((annotation, index) => {
@@ -118,7 +89,9 @@ const Annotations = ({ xScale, yScale, xMax, svgRef, onDragStateChange }) => {
118
89
  style={{
119
90
  padding: '10px',
120
91
  borderRadius: 5, // Optional: set border radius
121
- backgroundColor: `rgba(255, 255, 255, ${annotation?.opacity ? Number(annotation?.opacity) / 100 : 1})`
92
+ backgroundColor: `rgba(255, 255, 255, ${
93
+ annotation?.opacity ? Number(annotation?.opacity) / 100 : 1
94
+ })`
122
95
  }}
123
96
  role='presentation'
124
97
  // ! IMPORTANT: Workaround for 508
@@ -131,13 +104,31 @@ const Annotations = ({ xScale, yScale, xMax, svgRef, onDragStateChange }) => {
131
104
  />
132
105
  </HtmlLabel>
133
106
 
134
- {annotation.connectionType === 'line' && <Connector type='line' pathProps={{ markerStart: 'url(#marker-start)' }} />}
107
+ {annotation.connectionType === 'line' && (
108
+ <Connector type='line' pathProps={{ markerStart: 'url(#marker-start)' }} />
109
+ )}
135
110
 
136
- {annotation.connectionType === 'elbow' && <Connector type='elbow' pathProps={{ markerStart: 'url(#marker-start)' }} />}
111
+ {annotation.connectionType === 'elbow' && (
112
+ <Connector type='elbow' pathProps={{ markerStart: 'url(#marker-start)' }} />
113
+ )}
137
114
 
138
115
  {/* MARKERS */}
139
- {annotation.marker === 'circle' && <CircleSubject className='circle-subject' stroke={'black'} radius={8} />}
140
- {annotation.marker === 'arrow' && <MarkerArrow fill='black' id='marker-start' x={annotation.x} y={annotation.dy} stroke='#333' markerWidth={10} size={10} strokeWidth={1} orient='auto-start-reverse' />}
116
+ {annotation.marker === 'circle' && (
117
+ <CircleSubject className='circle-subject' stroke={'black'} radius={8} />
118
+ )}
119
+ {annotation.marker === 'arrow' && (
120
+ <MarkerArrow
121
+ fill='black'
122
+ id='marker-start'
123
+ x={annotation.x}
124
+ y={annotation.dy}
125
+ stroke='#333'
126
+ markerWidth={10}
127
+ size={10}
128
+ strokeWidth={1}
129
+ orient='auto-start-reverse'
130
+ />
131
+ )}
141
132
  </AnnotationComponent>
142
133
  </>
143
134
  )
@@ -2,14 +2,14 @@ import React, { useContext, useState } from 'react'
2
2
  import ConfigContext from '../../context'
3
3
  import './AnnotationDropdown.styles.css'
4
4
  import Icon from '@cdc/core/components/ui/Icon'
5
- import { fontSizes } from '@cdc/core/helpers/cove/fontSettings'
5
+ import { appFontSize } from '@cdc/core/helpers/cove/fontSettings'
6
6
  import AnnotationList from './AnnotationList'
7
7
 
8
8
  const AnnotationDropdown = () => {
9
- const { currentViewport: viewport, state: config } = useContext(ConfigContext)
9
+ const { state: config, isEditor, currentViewport: viewport } = useContext(ConfigContext)
10
10
  const [expanded, setExpanded] = useState(false)
11
11
 
12
- const titleFontSize = ['sm', 'xs', 'xxs'].includes(viewport) ? '13px' : `${fontSizes[config?.fontSize]}px`
12
+ const titleFontSize = ['sm', 'xs', 'xxs'].includes(viewport) ? '13px' : `${appFontSize}px`
13
13
 
14
14
  const annotations = config?.annotations || []
15
15
 
@@ -19,7 +19,7 @@ const AnnotationDropdown = () => {
19
19
  }
20
20
 
21
21
  const handleAccordionClassName = () => {
22
- const classNames = ['data-table-heading', 'annotation__dropdown-list']
22
+ const classNames = ['data-table-heading', 'annotation__dropdown-list', 'p-3']
23
23
  if (!expanded) {
24
24
  classNames.push('collapsed')
25
25
  }
@@ -5,6 +5,7 @@ import { supportedCities } from '../data/supported-geos'
5
5
  import { scaleLinear } from 'd3-scale'
6
6
  import { GlyphStar, GlyphTriangle, GlyphDiamond, GlyphSquare, GlyphCircle } from '@visx/glyph'
7
7
  import { getFilterControllingStatePicked } from './UsaMap/helpers/map'
8
+ import { titleCase } from '../helpers/titleCase'
8
9
 
9
10
  import ConfigContext from '../context'
10
11
  import { getGeoStrokeColor } from '../helpers/colors'
@@ -15,14 +16,13 @@ const CityList = ({
15
16
  applyTooltipsToGeo,
16
17
  displayGeoName,
17
18
  applyLegendToRow,
18
- titleCase,
19
19
  setSharedFilterValue,
20
20
  isFilterValueSupported,
21
21
  tooltipId,
22
22
  projection
23
23
  }) => {
24
24
  const [citiesData, setCitiesData] = useState({})
25
- const { scale, state, topoData, runtimeData, position } = useContext(ConfigContext)
25
+ const { state, topoData, runtimeData, position } = useContext(ConfigContext)
26
26
  if (!projection) return
27
27
 
28
28
  useEffect(() => {
@@ -1,4 +1,4 @@
1
- import React, { useEffect, useState, memo } from 'react'
1
+ import React, { useEffect, useState, memo, useContext } from 'react'
2
2
 
3
3
  import Papa from 'papaparse'
4
4
  import ExternalIcon from '../images/external-link.svg' // TODO: Move to Icon component
@@ -10,6 +10,8 @@ import MediaControls from '@cdc/core/components/MediaControls'
10
10
  import SkipTo from '@cdc/core/components/elements/SkipTo'
11
11
 
12
12
  import Loading from '@cdc/core/components/Loading'
13
+ import { navigationHandler } from '../helpers/navigationHandler'
14
+ import ConfigContext from '../context'
13
15
 
14
16
  /* eslint-disable jsx-a11y/no-noninteractive-tabindex, jsx-a11y/no-static-element-interactions */
15
17
  const DataTable = props => {
@@ -26,18 +28,15 @@ const DataTable = props => {
26
28
  displayDataAsText,
27
29
  applyLegendToRow,
28
30
  displayGeoName,
29
- navigationHandler,
30
- viewport,
31
31
  formatLegendLocation,
32
32
  tabbingId,
33
33
  setFilteredCountryCode
34
34
  } = props
35
35
 
36
+ const { currentViewport: viewport } = useContext(ConfigContext)
36
37
  const [expanded, setExpanded] = useState(expandDataTable)
37
38
  const [sortBy, setSortBy] = useState({ column: 'geo', asc: false })
38
-
39
39
  const [accessibilityLabel, setAccessibilityLabel] = useState('')
40
-
41
40
  const fileName = `${mapTitle || 'data-table'}.csv`
42
41
 
43
42
  // Catch all sorting method used on load by default but also on user click
@@ -118,14 +117,14 @@ const DataTable = props => {
118
117
  if (columns.navigate && row[columns.navigate.name]) {
119
118
  markup = (
120
119
  <span
121
- onClick={() => navigationHandler(row[columns.navigate.name])}
120
+ onClick={() => navigationHandler(state.general.navigationTarget, row[columns.navigate.name])}
122
121
  className='table-link'
123
122
  title='Click for more information (Opens in a new window)'
124
123
  role='link'
125
124
  tabIndex='0'
126
125
  onKeyDown={e => {
127
126
  if (e.keyCode === 13) {
128
- navigationHandler(row[columns.navigate.name])
127
+ navigationHandler(state.general.navigationTarget, row[columns.navigate.name])
129
128
  }
130
129
  }}
131
130
  >
@@ -192,7 +191,7 @@ const DataTable = props => {
192
191
 
193
192
  const TableMediaControls = ({ belowTable }) => {
194
193
  return (
195
- <MediaControls.Section classes={['download-links'] + (belowTable ? 'below-table' : '')}>
194
+ <MediaControls.Section classes={['download-links']}>
196
195
  <MediaControls.Link config={state} />
197
196
  {state.general.showDownloadButton && <DownloadButton />}
198
197
  </MediaControls.Section>
@@ -345,7 +344,7 @@ const DataTable = props => {
345
344
  </>
346
345
  )
347
346
  } else {
348
- cellValue = displayDataAsText(runtimeData[row][state.columns[column].name], column)
347
+ cellValue = displayDataAsText(runtimeData[row][state.columns[column].name], column, state)
349
348
  }
350
349
 
351
350
  return (
@@ -44,12 +44,12 @@ import { MapContext } from '../../../types/MapContext.js'
44
44
  import { TextField } from './Inputs'
45
45
  import Alert from '@cdc/core/components/Alert'
46
46
  import { updateFieldFactory } from '@cdc/core/helpers/updateFieldFactory'
47
+ import { Select } from '@cdc/core/components/EditorPanel/Inputs'
47
48
 
48
49
  // Todo: move to useReducer, seperate files out.
49
50
  const EditorPanel = ({ columnsRequiredChecker }) => {
50
51
  // prettier-ignore
51
52
  const {
52
- columnsInData = [],
53
53
  isDashboard,
54
54
  isDebug,
55
55
  loadConfig,
@@ -67,6 +67,7 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
67
67
  } = useContext<MapContext>(ConfigContext)
68
68
 
69
69
  const { general, columns, legend, table, tooltips } = state
70
+ const columnsInData = state?.data?.[0] ? Object.keys(state.data[0]) : []
70
71
 
71
72
  const [configTextboxValue, setConfigTextbox] = useState({}) // eslint-disable-line
72
73
 
@@ -231,6 +232,15 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
231
232
 
232
233
  const handleEditorChanges = async (property, value) => {
233
234
  switch (property) {
235
+ case 'navigationTarget':
236
+ setState({
237
+ ...state,
238
+ general: {
239
+ ...state.general,
240
+ navigationTarget: value
241
+ }
242
+ })
243
+ break
234
244
  // change these to be more generic.
235
245
  // updateVisualPropertyValue
236
246
  // updateGeneralPropertyValue, etc.
@@ -715,6 +725,14 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
715
725
  }
716
726
  })
717
727
  break
728
+ case 'google-map':
729
+ setState({
730
+ ...state,
731
+ general: {
732
+ ...state.general,
733
+ geoType: 'google-map'
734
+ }
735
+ })
718
736
  default:
719
737
  break
720
738
  }
@@ -1369,13 +1387,29 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
1369
1387
  </AccordionItemHeading>
1370
1388
  <AccordionItemPanel>
1371
1389
  {/* Geography */}
1390
+ {/*<Select*/}
1391
+ {/* options={[*/}
1392
+ {/* { value: 'us', label: 'United States' },*/}
1393
+ {/* { value: 'us-region', label: 'U.S. Region' },*/}
1394
+ {/* { value: 'world', label: 'World' },*/}
1395
+ {/* { value: 'single-state', label: 'U.S. State' },*/}
1396
+ {/* { value: 'google-map', label: 'Google Map API' }*/}
1397
+ {/* ]}*/}
1398
+ {/* section={'general'}*/}
1399
+ {/* fieldName={'geoType'}*/}
1400
+ {/* label='Geography'*/}
1401
+ {/* updateField={updateField}*/}
1402
+ {/*/>*/}
1403
+
1372
1404
  <label>
1373
1405
  <span className='edit-label column-heading'>
1374
1406
  <span>Geography</span>
1375
1407
  </span>
1376
- <ul className='geo-buttons'>
1408
+ <ul className='geo-buttons d-grid' style={{ gridTemplateColumns: 'repeat(2, 1fr)', gap: '8px' }}>
1377
1409
  <button
1378
- className={state.general.geoType === 'us' || state.general.geoType === 'us-county' ? 'active' : ''}
1410
+ className={`${
1411
+ state.general.geoType === 'us' || state.general.geoType === 'us-county' ? 'active' : ''
1412
+ } full-width`}
1379
1413
  onClick={e => {
1380
1414
  e.preventDefault()
1381
1415
  handleEditorChanges('geoType', 'us')
@@ -1385,7 +1419,7 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
1385
1419
  <span>United States</span>
1386
1420
  </button>
1387
1421
  <button
1388
- className={state.general.geoType === 'us-region' ? 'active' : ''}
1422
+ className={`${state.general.geoType === 'us-region' ? 'active' : ''} full-width`}
1389
1423
  onClick={e => {
1390
1424
  e.preventDefault()
1391
1425
  handleEditorChanges('geoType', 'us-region')
@@ -1395,7 +1429,7 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
1395
1429
  <span>U.S. Region</span>
1396
1430
  </button>
1397
1431
  <button
1398
- className={state.general.geoType === 'world' ? 'active' : ''}
1432
+ className={`${state.general.geoType === 'world' ? 'active' : ''} full-width`}
1399
1433
  onClick={e => {
1400
1434
  e.preventDefault()
1401
1435
  handleEditorChanges('geoType', 'world')
@@ -1405,7 +1439,7 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
1405
1439
  <span>World</span>
1406
1440
  </button>
1407
1441
  <button
1408
- className={state.general.geoType === 'single-state' ? 'active' : ''}
1442
+ className={`${state.general.geoType === 'single-state' ? 'active' : ''} full-width`}
1409
1443
  onClick={e => {
1410
1444
  e.preventDefault()
1411
1445
  handleEditorChanges('geoType', 'single-state')
@@ -1414,6 +1448,16 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
1414
1448
  <AlabamaGraphic />
1415
1449
  <span>U.S. State</span>
1416
1450
  </button>
1451
+ {/* <button
1452
+ className={`${state.general.geoType === 'google-map' ? 'active' : ''} full-width`}
1453
+ onClick={e => {
1454
+ e.preventDefault()
1455
+ handleEditorChanges('geoType', 'google-map')
1456
+ }}
1457
+ >
1458
+ <UsaGraphic />
1459
+ <span>Google Map Api</span>
1460
+ </button> */}
1417
1461
  </ul>
1418
1462
  </label>
1419
1463
  {/* Select > State or County Map */}
@@ -1526,6 +1570,23 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
1526
1570
  )}
1527
1571
  </select>
1528
1572
  </label>
1573
+
1574
+ {/* Navigation Behavior */}
1575
+ {(state.general.type === 'navigation' || state.general.type === 'data') && (
1576
+ <label>
1577
+ <span className='edit-label column-heading'>Navigation Behavior</span>
1578
+ <select
1579
+ value={state.general.navigationTarget}
1580
+ onChange={event => {
1581
+ event.preventDefault()
1582
+ handleEditorChanges('navigationTarget', event.target.value)
1583
+ }}
1584
+ >
1585
+ <option value='_self'>Same Window</option>
1586
+ <option value='_blank'>New Window</option>
1587
+ </select>
1588
+ </label>
1589
+ )}
1529
1590
  <label>
1530
1591
  <span className='edit-label'>Data Classification Type</span>
1531
1592
  <div>
@@ -1567,7 +1628,17 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
1567
1628
  handleEditorChanges('displayStateLabels', event.target.checked)
1568
1629
  }}
1569
1630
  />
1570
- <span className='edit-label'>Show state labels</span>
1631
+ <span className='edit-label'>
1632
+ Show state labels
1633
+ <Tooltip style={{ textTransform: 'none' }}>
1634
+ <Tooltip.Target>
1635
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
1636
+ </Tooltip.Target>
1637
+ <Tooltip.Content>
1638
+ <p>Recommended set to display for Section 508 compliance.</p>
1639
+ </Tooltip.Content>
1640
+ </Tooltip>
1641
+ </span>
1571
1642
  </label>
1572
1643
  )}
1573
1644
  </AccordionItemPanel>
@@ -1685,16 +1756,6 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
1685
1756
  </Tooltip>
1686
1757
  }
1687
1758
  />
1688
- {'us' === state.general.geoType && (
1689
- <TextField
1690
- value={general.territoriesLabel}
1691
- updateField={updateField}
1692
- section='general'
1693
- fieldName='territoriesLabel'
1694
- label='Territories Label'
1695
- placeholder='Territories'
1696
- />
1697
- )}
1698
1759
  {'us' === state.general.geoType && (
1699
1760
  <label className='checkbox'>
1700
1761
  <input
@@ -3316,6 +3377,18 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
3316
3377
  updateField={updateField}
3317
3378
  />
3318
3379
  </label>
3380
+ {/* Leaflet Map Type */}
3381
+ {state.general.geoType === 'leaflet' && (
3382
+ <>
3383
+ <Select
3384
+ label='Leaflet Theme'
3385
+ options={layerOptions}
3386
+ section={'leaflet'}
3387
+ fieldName='theme'
3388
+ updateField={updateField}
3389
+ />
3390
+ </>
3391
+ )}
3319
3392
  </AccordionItemPanel>
3320
3393
  </AccordionItem>
3321
3394
  <AccordionItem>
@@ -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>