@cdc/map 4.24.2 → 4.24.4

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 (38) hide show
  1. package/dist/cdcmap.js +33089 -32197
  2. package/examples/508.json +548 -0
  3. package/examples/default-county.json +0 -28
  4. package/examples/default-hex.json +110 -13
  5. package/examples/default-usa.json +69 -28
  6. package/examples/example-city-state.json +51 -17
  7. package/examples/hex-colors.json +507 -0
  8. package/examples/usa-special-class-legend.json +501 -0
  9. package/index.html +10 -9
  10. package/package.json +3 -3
  11. package/src/CdcMap.tsx +200 -125
  12. package/src/components/BubbleList.jsx +16 -6
  13. package/src/components/CityList.jsx +64 -12
  14. package/src/components/DataTable.jsx +7 -7
  15. package/src/components/EditorPanel/components/EditorPanel.tsx +1457 -1367
  16. package/src/components/EditorPanel/components/Error.tsx +12 -0
  17. package/src/components/EditorPanel/components/HexShapeSettings.tsx +16 -1
  18. package/src/components/EditorPanel/components/Panel.PatternSettings-style.css +5 -0
  19. package/src/components/EditorPanel/components/Panel.PatternSettings.tsx +18 -1
  20. package/src/components/Legend/components/Legend.tsx +80 -15
  21. package/src/components/Legend/components/LegendItem.Hex.tsx +1 -1
  22. package/src/components/Legend/components/index.scss +31 -5
  23. package/src/components/UsaMap/components/HexIcon.tsx +41 -0
  24. package/src/components/UsaMap/components/Territory/Territory.Hexagon.tsx +38 -19
  25. package/src/components/UsaMap/components/Territory/Territory.Rectangle.tsx +11 -22
  26. package/src/components/UsaMap/components/UsaMap.County.tsx +7 -4
  27. package/src/components/UsaMap/components/UsaMap.Region.tsx +13 -38
  28. package/src/components/UsaMap/components/UsaMap.SingleState.tsx +5 -3
  29. package/src/components/UsaMap/components/UsaMap.State.tsx +77 -60
  30. package/src/components/UsaMap/helpers/patternSizes.tsx +5 -0
  31. package/src/components/WorldMap/components/WorldMap.jsx +20 -3
  32. package/src/data/initial-state.js +3 -0
  33. package/src/data/supported-geos.js +2 -1
  34. package/src/hooks/useMapLayers.tsx +2 -2
  35. package/src/scss/editor-panel.scss +4 -12
  36. package/src/scss/main.scss +3 -1
  37. package/src/scss/map.scss +1 -1
  38. package/src/types/MapConfig.ts +7 -0
package/src/CdcMap.tsx CHANGED
@@ -1,5 +1,8 @@
1
- import React, { useState, useEffect, useRef, useCallback } from 'react'
1
+ import React, { useState, useEffect, useRef, useCallback, useId } from 'react'
2
2
  import * as d3 from 'd3'
3
+ import Layout from '@cdc/core/components/Layout'
4
+ import Waiting from '@cdc/core/components/Waiting'
5
+ import Error from './components/EditorPanel/components/Error'
3
6
 
4
7
  // IE11
5
8
  import 'whatwg-fetch'
@@ -15,12 +18,13 @@ import 'react-tooltip/dist/react-tooltip.css'
15
18
  // Helpers
16
19
  import { publish } from '@cdc/core/helpers/events'
17
20
  import coveUpdateWorker from '@cdc/core/helpers/coveUpdateWorker'
21
+ import { getQueryStringFilterValue } from '@cdc/core/helpers/queryStringUtils'
18
22
  import Title from '@cdc/core/components/ui/Title'
19
23
 
20
24
  // Data
21
25
  import { countryCoordinates } from './data/country-coordinates'
22
26
  import { supportedStates, supportedTerritories, supportedCountries, supportedCounties, supportedCities, supportedStatesFipsCodes, stateFipsToTwoDigit, supportedRegions } from './data/supported-geos'
23
- import colorPalettes from '../../core/data/colorPalettes'
27
+ import colorPalettes from '@cdc/core/data/colorPalettes'
24
28
  import initialState from './data/initial-state'
25
29
 
26
30
  // Assets
@@ -37,6 +41,7 @@ import { DataTransform } from '@cdc/core/helpers/DataTransform'
37
41
  import MediaControls from '@cdc/core/components/MediaControls'
38
42
  import fetchRemoteData from '@cdc/core/helpers/fetchRemoteData'
39
43
  import getViewport from '@cdc/core/helpers/getViewport'
44
+ import isDomainExternal from '@cdc/core/helpers/isDomainExternal'
40
45
  import Loading from '@cdc/core/components/Loading'
41
46
  import numberFromString from '@cdc/core/helpers/numberFromString'
42
47
  import DataTable from '@cdc/core/components/DataTable' // Future: Lazy
@@ -53,6 +58,7 @@ import UsaMap from './components/UsaMap' // Future: Lazy
53
58
  import WorldMap from './components/WorldMap' // Future: Lazy
54
59
  import useTooltip from './hooks/useTooltip'
55
60
  import { isSolrCsv, isSolrJson } from '@cdc/core/helpers/isSolr'
61
+ import SkipTo from '@cdc/core/components/elements/SkipTo'
56
62
 
57
63
  // Data props
58
64
  const stateKeys = Object.keys(supportedStates)
@@ -120,6 +126,7 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
120
126
  const transform = new DataTransform()
121
127
  const [state, setState] = useState({ ...initialState })
122
128
  const [loading, setLoading] = useState(true)
129
+ const [displayPanel, setDisplayPanel] = useState(true)
123
130
  const [currentViewport, setCurrentViewport] = useState()
124
131
  const [runtimeFilters, setRuntimeFilters] = useState([])
125
132
  const [runtimeLegend, setRuntimeLegend] = useState([])
@@ -132,12 +139,49 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
132
139
  const [container, setContainer] = useState()
133
140
  const [imageId, setImageId] = useState(`cove-${Math.random().toString(16).slice(-4)}`) // eslint-disable-line
134
141
  const [dimensions, setDimensions] = useState()
142
+ const [requiredColumns, setRequiredColumns] = useState(null) // Simple state so we know if we need more information before parsing the map
143
+
144
+ const legendRef = useRef(null)
145
+ const legendId = useId()
146
+ const tooltipId = useId()
135
147
 
136
148
  const { changeFilterActive, handleSorting } = useFilters({ config: state, setConfig: setState })
137
149
  let legendMemo = useRef(new Map())
150
+ let legendSpecialClassLastMemo = useRef(new Map())
138
151
  let innerContainerRef = useRef()
139
152
 
140
- if (isDebug) console.log('CdcMap state=', state) // eslint-disable-line
153
+ if (isDebug) console.log('CdcMap state=', state) // <eslint-disable-line></eslint-disable-line>
154
+
155
+ const columnsRequiredChecker = useCallback(() => {
156
+ let columnList = []
157
+
158
+ // Geo is always required
159
+ if ('' === state.columns.geo.name) {
160
+ columnList.push('Geography')
161
+ }
162
+
163
+ // Primary is required if we're on a data map or a point map
164
+ if ('navigation' !== state.general.type && '' === state.columns.primary.name) {
165
+ columnList.push('Primary')
166
+ }
167
+
168
+ // Navigate is required for navigation maps
169
+ if ('navigation' === state.general.type && ('' === state.columns.navigate.name || undefined === state.columns.navigate)) {
170
+ columnList.push('Navigation')
171
+ }
172
+
173
+ if (('us-geocode' === state.general.type || 'world-geocode' === state.general.type) && '' === state.columns.latitude.name) {
174
+ columnList.push('Latitude')
175
+ }
176
+
177
+ if (('us-geocode' === state.general.type || 'world-geocode' === state.general.type) && '' === state.columns.longitude.name) {
178
+ columnList.push('Longitude')
179
+ }
180
+
181
+ if (columnList.length === 0) columnList = null
182
+
183
+ setRequiredColumns(columnList)
184
+ }, [state.columns, state.general.type])
141
185
 
142
186
  useEffect(() => {
143
187
  try {
@@ -303,6 +347,7 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
303
347
  // eslint-disable-next-line
304
348
  const generateRuntimeLegend = useCallback((obj, runtimeData, hash) => {
305
349
  const newLegendMemo = new Map() // Reset memoization
350
+ const newLegendSpecialClassLastMemo = new Map() // Reset bin memoization
306
351
  let primaryCol = obj.columns.primary.name,
307
352
  isBubble = obj.general.type === 'bubble',
308
353
  categoricalCol = obj.columns.categorical ? obj.columns.categorical.name : undefined,
@@ -524,6 +569,13 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
524
569
  result = [...otherRows, ...specialRows]
525
570
  }
526
571
 
572
+ const assignSpecialClassLastIndex = (value, key) => {
573
+ const newIndex = result.findIndex(d => d.bin === value)
574
+ newLegendSpecialClassLastMemo.set(key, newIndex)
575
+ }
576
+ newLegendMemo.forEach(assignSpecialClassLastIndex)
577
+ legendSpecialClassLastMemo.current = newLegendSpecialClassLastMemo
578
+
527
579
  return result
528
580
  }
529
581
 
@@ -775,6 +827,13 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
775
827
  }
776
828
  //-----------
777
829
 
830
+ const assignSpecialClassLastIndex = (value, key) => {
831
+ const newIndex = result.findIndex(d => d.bin === value)
832
+ newLegendSpecialClassLastMemo.set(key, newIndex)
833
+ }
834
+ newLegendMemo.forEach(assignSpecialClassLastIndex)
835
+ legendSpecialClassLastMemo.current = newLegendSpecialClassLastMemo
836
+
778
837
  return result
779
838
  })
780
839
 
@@ -786,7 +845,7 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
786
845
 
787
846
  if (hash) filters.fromHash = hash
788
847
 
789
- obj?.filters.forEach(({ columnName, label, labels, queryParameter, orderedValues, active, values, type, showDropdown }, idx) => {
848
+ obj?.filters.forEach(({ columnName, label, labels, queryParameter, orderedValues, active, values, type, showDropdown, setByQueryParameter }, idx) => {
790
849
  let newFilter = runtimeFilters[idx]
791
850
 
792
851
  const sortAsc = (a, b) => {
@@ -829,6 +888,7 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
829
888
  newFilter.queryParameter = queryParameter
830
889
  newFilter.labels = labels
831
890
  newFilter.values = values
891
+ newFilter.setByQueryParameter = setByQueryParameter
832
892
  handleSorting(newFilter)
833
893
  newFilter.active = active ?? values[0] // Default to first found value
834
894
  newFilter.filterStyle = obj.filters[idx].filterStyle ? obj.filters[idx].filterStyle : 'dropdown'
@@ -988,7 +1048,13 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
988
1048
 
989
1049
  if (legendMemo.current.has(hash)) {
990
1050
  let idx = legendMemo.current.get(hash)
991
- if (runtimeLegend[idx]?.disabled) return false
1051
+ let disabledIdx = idx
1052
+
1053
+ if (state.legend.showSpecialClassesLast) {
1054
+ disabledIdx = legendSpecialClassLastMemo.current.get(hash)
1055
+ }
1056
+
1057
+ if (runtimeLegend[disabledIdx]?.disabled) return false
992
1058
 
993
1059
  // changed to use bin prop to get color instead of idx
994
1060
  // bc we re-order legend when showSpecialClassesLast is checked
@@ -1135,11 +1201,19 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
1135
1201
 
1136
1202
  if (state.columns.hasOwnProperty('navigate') && row[state.columns.navigate.name]) {
1137
1203
  toolTipText.push(
1138
- // eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions
1139
- <ul className='navigation-link' key='modal-navigation-link' onClick={() => navigationHandler(row[state.columns.navigate.name])}>
1204
+ // eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions,jsx-a11y/anchor-is-valid
1205
+ <a
1206
+ href='#'
1207
+ className='navigation-link'
1208
+ key='modal-navigation-link'
1209
+ onClick={e => {
1210
+ e.preventDefault()
1211
+ navigationHandler(row[state.columns.navigate.name])
1212
+ }}
1213
+ >
1140
1214
  {state.tooltips.linkLabel}
1141
- <ExternalIcon className='inline-icon ml-1' />
1142
- </ul>
1215
+ {isDomainExternal(row[state.columns.navigate.name]) && <ExternalIcon className='inline-icon ml-1' />}
1216
+ </a>
1143
1217
  )
1144
1218
  }
1145
1219
  }
@@ -1305,8 +1379,6 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
1305
1379
  data = transform.developerStandardize(data, state.dataDescription)
1306
1380
  }
1307
1381
 
1308
- console.log('data', data)
1309
-
1310
1382
  setState({ ...state, runtimeDataUrl: dataUrlFinal, data })
1311
1383
  }
1312
1384
  }
@@ -1449,6 +1521,12 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
1449
1521
  filters = generateRuntimeFilters(state, hashFilters, runtimeFilters)
1450
1522
 
1451
1523
  if (filters) {
1524
+ filters.forEach((filter, index) => {
1525
+ const queryStringFilterValue = getQueryStringFilterValue(filter)
1526
+ if (queryStringFilterValue) {
1527
+ filters[index].active = queryStringFilterValue
1528
+ }
1529
+ })
1452
1530
  setRuntimeFilters(filters)
1453
1531
  }
1454
1532
  }
@@ -1508,13 +1586,6 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
1508
1586
  }
1509
1587
  if (!table.label || table.label === '') table.label = 'Data Table'
1510
1588
 
1511
- // Outer container classes
1512
- let outerContainerClasses = ['cdc-open-viz-module', 'cdc-map-outer-container', currentViewport]
1513
-
1514
- if (className) {
1515
- outerContainerClasses.push(className)
1516
- }
1517
-
1518
1589
  // Map container classes
1519
1590
  let mapContainerClasses = ['map-container', state.legend.position, state.general.type, state.general.geoType, 'outline-none']
1520
1591
 
@@ -1570,7 +1641,8 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
1570
1641
  supportedTerritories,
1571
1642
  titleCase,
1572
1643
  type: general.type,
1573
- viewport: currentViewport
1644
+ viewport: currentViewport,
1645
+ tooltipId
1574
1646
  }
1575
1647
 
1576
1648
  if (!mapProps.data || !state.data) return <></>
@@ -1582,21 +1654,21 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
1582
1654
 
1583
1655
  // 1) skip to legend
1584
1656
  if (general.showSidebar) {
1585
- tabbingID = '#legend'
1657
+ tabbingID = legendId
1586
1658
  }
1587
1659
 
1588
1660
  // 2) skip to data table if it exists and not a navigation map
1589
1661
  if (hasDataTable && !general.showSidebar) {
1590
- tabbingID = `#dataTableSection__${Date.now()}`
1662
+ tabbingID = `dataTableSection__${Date.now()}`
1591
1663
  }
1592
1664
 
1593
1665
  // 3) if it's a navigation map skip to the dropdown.
1594
1666
  if (state.general.type === 'navigation') {
1595
- tabbingID = `#dropdown-${Date.now()}`
1667
+ tabbingID = `dropdown-${Date.now()}`
1596
1668
  }
1597
1669
 
1598
1670
  // 4) handle other options
1599
- return tabbingID || '#!'
1671
+ return tabbingID || '!'
1600
1672
  }
1601
1673
 
1602
1674
  const tabId = handleMapTabbing()
@@ -1610,109 +1682,112 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
1610
1682
 
1611
1683
  return (
1612
1684
  <ConfigContext.Provider value={mapProps}>
1613
- <div className={outerContainerClasses.join(' ')} ref={outerContainerRef} data-download-id={imageId}>
1614
- {isEditor && <EditorPanel />}
1615
- {!runtimeData.init && (general.type === 'navigation' || runtimeLegend) && (
1616
- <section className={`cdc-map-inner-container ${currentViewport}`} aria-label={'Map: ' + title} ref={innerContainerRef}>
1617
- {!window.matchMedia('(any-hover: none)').matches && 'hover' === tooltips.appearanceType && (
1618
- <ReactTooltip id='tooltip' float={true} className={`${tooltips.capitalizeLabels ? 'capitalize tooltip' : 'tooltip'}`} style={{ background: `rgba(255,255,255, ${state.tooltips.opacity / 100})`, color: 'black' }} />
1619
- )}
1620
- {/* prettier-ignore */}
1621
- <Title
1622
- title={title}
1623
- superTitle={general.superTitle}
1624
- config={config}
1625
- classes={['map-title', general.showTitle === true ? 'visible' : 'hidden', `${general.headerColor}`]}
1626
- />
1627
- <a id='skip-geo-container' className='cdcdataviz-sr-only-focusable' href={tabId}>
1628
- Skip Over Map Container
1629
- </a>
1630
- {general.introText && <section className='introText'>{parse(general.introText)}</section>}
1631
-
1632
- {/* prettier-ignore */}
1633
- {state?.filters?.length > 0 && <Filters config={state} setConfig={setState} filteredData={runtimeFilters} setFilteredData={setRuntimeFilters} dimensions={dimensions} />}
1634
-
1635
- <div
1636
- role='button'
1637
- tabIndex='0'
1638
- className={mapContainerClasses.join(' ')}
1639
- onClick={e => closeModal(e)}
1640
- onKeyDown={e => {
1641
- if (e.keyCode === 13) {
1642
- closeModal(e)
1643
- }
1644
- }}
1645
- >
1646
- {/* eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex */}
1647
- <section className='outline-none geography-container' ref={mapSvg} tabIndex='0' style={{ width: '100%' }}>
1648
- {currentViewport && (
1649
- <>
1650
- {modal && <Modal />}
1651
- {'single-state' === geoType && <UsaMap.SingleState />}
1652
- {'us' === geoType && 'us-geocode' !== state.general.type && <UsaMap.State />}
1653
- {'us-region' === geoType && <UsaMap.Region />}
1654
- {'us-county' === geoType && <UsaMap.County />}
1655
- {'world' === geoType && <WorldMap />}
1656
- {'data' === general.type && logo && <img src={logo} alt='' className='map-logo' />}
1657
- </>
1658
- )}
1659
- </section>
1660
-
1661
- {general.showSidebar && 'navigation' !== general.type && <Legend />}
1662
- </div>
1663
-
1664
- {'navigation' === general.type && <NavigationMenu mapTabbingID={tabId} displayGeoName={displayGeoName} data={runtimeData} options={general} columns={state.columns} navigationHandler={val => navigationHandler(val)} />}
1665
-
1666
- {/* Link */}
1667
- {isDashboard && config.table?.forceDisplay && config.table.showDataTableLink ? tableLink : link && link}
1668
-
1669
- {subtext.length > 0 && <p className='subtext'>{parse(subtext)}</p>}
1670
-
1671
- <MediaControls.Section classes={['download-buttons']}>
1672
- {state.general.showDownloadImgButton && <MediaControls.Button text='Download Image' title='Download Chart as Image' type='image' state={state} elementToCapture={imageId} />}
1673
- {state.general.showDownloadPdfButton && <MediaControls.Button text='Download PDF' title='Download Chart as PDF' type='pdf' state={state} elementToCapture={imageId} />}
1674
- </MediaControls.Section>
1675
-
1676
- {state.runtime.editorErrorMessage.length === 0 && true === table.forceDisplay && general.type !== 'navigation' && false === loading && (
1677
- <DataTable
1678
- config={state}
1679
- rawData={state.data}
1680
- navigationHandler={navigationHandler}
1681
- expandDataTable={general.expandDataTable ? general.expandDataTable : table.expanded ? table.expanded : false}
1682
- headerColor={general.headerColor}
1683
- columns={state.columns}
1684
- showDownloadButton={general.showDownloadButton}
1685
- showFullGeoNameInCSV={table.showFullGeoNameInCSV}
1686
- runtimeLegend={runtimeLegend}
1687
- runtimeData={runtimeData}
1688
- displayDataAsText={displayDataAsText}
1689
- displayGeoName={displayGeoName}
1690
- applyLegendToRow={applyLegendToRow}
1691
- tableTitle={table.label}
1692
- indexTitle={table.indexLabel}
1693
- vizTitle={general.title}
1694
- viewport={currentViewport}
1695
- formatLegendLocation={formatLegendLocation}
1696
- setFilteredCountryCode={setFilteredCountryCode}
1697
- tabbingId={tabId}
1698
- showDownloadImgButton={state.general.showDownloadImgButton}
1699
- showDownloadPdfButton={state.general.showDownloadPdfButton}
1700
- innerContainerRef={innerContainerRef}
1701
- outerContainerRef={outerContainerRef}
1702
- imageRef={imageId}
1703
- isDebug={isDebug}
1704
- wrapColumns={table.wrapColumns}
1685
+ <Layout.VisualizationWrapper config={state} isEditor={isEditor} ref={outerContainerRef} imageId={imageId} showEditorPanel={state.showEditorPanel}>
1686
+ {isEditor && <EditorPanel columnsRequiredChecker={columnsRequiredChecker} />}
1687
+ <Layout.Responsive isEditor={isEditor}>
1688
+ {state?.runtime?.editorErrorMessage.length > 0 && <Error state={state} />}
1689
+ {requiredColumns && <Waiting requiredColumns={requiredColumns} className={displayPanel ? `waiting` : `waiting collapsed`} />}
1690
+ {!runtimeData.init && (general.type === 'navigation' || runtimeLegend) && (
1691
+ <section className={`cove-component__content cdc-map-inner-container ${currentViewport}`} aria-label={'Map: ' + title} ref={innerContainerRef}>
1692
+ {!window.matchMedia('(any-hover: none)').matches && 'hover' === tooltips.appearanceType && (
1693
+ <ReactTooltip id={`tooltip__${tooltipId}`} float={true} className={`${tooltips.capitalizeLabels ? 'capitalize tooltip tooltip-test' : 'tooltip tooltip-test'}`} style={{ background: `rgba(255,255,255, ${state.tooltips.opacity / 100})`, color: 'black' }} />
1694
+ )}
1695
+ {/* prettier-ignore */}
1696
+ <Title
1697
+ title={title}
1698
+ superTitle={general.superTitle}
1699
+ config={config}
1700
+ classes={['map-title', general.showTitle === true ? 'visible' : 'hidden', `${general.headerColor}`]}
1705
1701
  />
1706
- )}
1707
-
1708
- {general.footnotes && <section className='footnotes'>{parse(general.footnotes)}</section>}
1709
- </section>
1710
- )}
1711
-
1712
- <div aria-live='assertive' className='cdcdataviz-sr-only'>
1713
- {accessibleStatus}
1714
- </div>
1715
- </div>
1702
+ <SkipTo skipId={tabId} skipMessage='Skip Over Map Container' />
1703
+
1704
+ {general.introText && <section className='introText'>{parse(general.introText)}</section>}
1705
+
1706
+ {/* prettier-ignore */}
1707
+ {state?.filters?.length > 0 && <Filters config={state} setConfig={setState} filteredData={runtimeFilters} setFilteredData={setRuntimeFilters} dimensions={dimensions} />}
1708
+
1709
+ <div
1710
+ role='region'
1711
+ tabIndex='0'
1712
+ className={mapContainerClasses.join(' ')}
1713
+ onClick={e => closeModal(e)}
1714
+ onKeyDown={e => {
1715
+ if (e.keyCode === 13) {
1716
+ closeModal(e)
1717
+ }
1718
+ }}
1719
+ >
1720
+ {/* eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex */}
1721
+ <section className='outline-none geography-container' ref={mapSvg} tabIndex='0' style={{ width: '100%' }}>
1722
+ {currentViewport && (
1723
+ <>
1724
+ {modal && <Modal />}
1725
+ {'single-state' === geoType && <UsaMap.SingleState />}
1726
+ {'us' === geoType && 'us-geocode' !== state.general.type && <UsaMap.State />}
1727
+ {'us-region' === geoType && <UsaMap.Region />}
1728
+ {'us-county' === geoType && <UsaMap.County />}
1729
+ {'world' === geoType && <WorldMap />}
1730
+ {'data' === general.type && logo && <img src={logo} alt='' className='map-logo' />}
1731
+ </>
1732
+ )}
1733
+ </section>
1734
+
1735
+ {general.showSidebar && 'navigation' !== general.type && <Legend ref={legendRef} skipId={tabId} />}
1736
+ </div>
1737
+
1738
+ {'navigation' === general.type && <NavigationMenu mapTabbingID={tabId} displayGeoName={displayGeoName} data={runtimeData} options={general} columns={state.columns} navigationHandler={val => navigationHandler(val)} />}
1739
+
1740
+ {/* Link */}
1741
+ {isDashboard && config.table?.forceDisplay && config.table.showDataTableLink ? tableLink : link && link}
1742
+
1743
+ {subtext.length > 0 && <p className='subtext'>{parse(subtext)}</p>}
1744
+
1745
+ <MediaControls.Section classes={['download-buttons']}>
1746
+ {state.general.showDownloadImgButton && <MediaControls.Button text='Download Image' title='Download Chart as Image' type='image' state={state} elementToCapture={imageId} />}
1747
+ {state.general.showDownloadPdfButton && <MediaControls.Button text='Download PDF' title='Download Chart as PDF' type='pdf' state={state} elementToCapture={imageId} />}
1748
+ </MediaControls.Section>
1749
+
1750
+ {state.runtime.editorErrorMessage.length === 0 && true === table.forceDisplay && general.type !== 'navigation' && false === loading && (
1751
+ <DataTable
1752
+ config={state}
1753
+ rawData={state.data}
1754
+ navigationHandler={navigationHandler}
1755
+ expandDataTable={table.expanded}
1756
+ headerColor={general.headerColor}
1757
+ columns={state.columns}
1758
+ showDownloadButton={general.showDownloadButton}
1759
+ showFullGeoNameInCSV={table.showFullGeoNameInCSV}
1760
+ runtimeLegend={runtimeLegend}
1761
+ runtimeData={runtimeData}
1762
+ displayDataAsText={displayDataAsText}
1763
+ displayGeoName={displayGeoName}
1764
+ applyLegendToRow={applyLegendToRow}
1765
+ tableTitle={table.label}
1766
+ indexTitle={table.indexLabel}
1767
+ vizTitle={general.title}
1768
+ viewport={currentViewport}
1769
+ formatLegendLocation={formatLegendLocation}
1770
+ setFilteredCountryCode={setFilteredCountryCode}
1771
+ tabbingId={tabId}
1772
+ showDownloadImgButton={state.general.showDownloadImgButton}
1773
+ showDownloadPdfButton={state.general.showDownloadPdfButton}
1774
+ innerContainerRef={innerContainerRef}
1775
+ outerContainerRef={outerContainerRef}
1776
+ imageRef={imageId}
1777
+ isDebug={isDebug}
1778
+ wrapColumns={table.wrapColumns}
1779
+ />
1780
+ )}
1781
+
1782
+ {general.footnotes && <section className='footnotes'>{parse(general.footnotes)}</section>}
1783
+ </section>
1784
+ )}
1785
+
1786
+ <div aria-live='assertive' className='cdcdataviz-sr-only'>
1787
+ {accessibleStatus}
1788
+ </div>
1789
+ </Layout.Responsive>
1790
+ </Layout.VisualizationWrapper>
1716
1791
  </ConfigContext.Provider>
1717
1792
  )
1718
1793
  }
@@ -1,10 +1,12 @@
1
- import React, { useEffect } from 'react'
1
+ import React, { useEffect, useContext } from 'react'
2
2
  import { scaleLinear } from 'd3-scale'
3
3
  import { countryCoordinates } from '../data/country-coordinates'
4
4
  import stateCoordinates from '../data/state-coordinates'
5
+ import ConfigContext from './../../src/context'
5
6
 
6
7
  export const BubbleList = ({ data: dataImport, state, projection, applyLegendToRow, applyTooltipsToGeo, handleCircleClick, runtimeData, displayGeoName }) => {
7
8
  const maxDataValue = Math.max(...dataImport.map(d => d[state.columns.primary.name]))
9
+ const { tooltipId } = useContext(ConfigContext)
8
10
  const hasBubblesWithZeroOnMap = state.visual.showBubbleZeros ? 0 : 1
9
11
  // sort runtime data. Smaller bubbles should appear on top.
10
12
  const sortedRuntimeData = Object.values(runtimeData).sort((a, b) => (a[state.columns.primary.name] < b[state.columns.primary.name] ? 1 : -1))
@@ -39,6 +41,7 @@ export const BubbleList = ({ data: dataImport, state, projection, applyLegendToR
39
41
  const circle = (
40
42
  <>
41
43
  <circle
44
+ tabIndex={-1}
42
45
  key={`circle-${countryName.replace(' ', '')}`}
43
46
  className={`bubble country--${countryName}`}
44
47
  cx={Number(projection(coordinates[1], coordinates[0])[0]) || 0} // || 0 handles error on loads where the data isn't ready
@@ -61,12 +64,13 @@ export const BubbleList = ({ data: dataImport, state, projection, applyLegendToR
61
64
  }}
62
65
  transform={transform}
63
66
  style={{ transition: 'all .25s ease-in-out', cursor: 'pointer' }}
64
- data-tooltip-id='tooltip'
67
+ data-tooltip-id={`tooltip__${tooltipId}`}
65
68
  data-tooltip-html={toolTip}
66
69
  />
67
70
 
68
71
  {state.visual.extraBubbleBorder && (
69
72
  <circle
73
+ tabIndex={-1}
70
74
  key={`circle-${countryName.replace(' ', '')}`}
71
75
  className='bubble'
72
76
  cx={Number(projection(coordinates[1], coordinates[0])[0]) || 0} // || 0 handles error on loads where the data isn't ready
@@ -88,14 +92,18 @@ export const BubbleList = ({ data: dataImport, state, projection, applyLegendToR
88
92
  }}
89
93
  transform={transform}
90
94
  style={{ transition: 'all .25s ease-in-out', cursor: 'pointer' }}
91
- data-tooltip-id='tooltip'
95
+ data-tooltip-id={`tooltip__${tooltipId}`}
92
96
  data-tooltip-html={toolTip}
93
97
  />
94
98
  )}
95
99
  </>
96
100
  )
97
101
 
98
- return <g key={`group-${countryName.replace(' ', '')}`}>{circle}</g>
102
+ return (
103
+ <g key={`group-${countryName.replace(' ', '')}`} tabIndex={-1}>
104
+ {circle}
105
+ </g>
106
+ )
99
107
  })
100
108
  return countries
101
109
  }
@@ -132,6 +140,7 @@ export const BubbleList = ({ data: dataImport, state, projection, applyLegendToR
132
140
  const circle = (
133
141
  <>
134
142
  <circle
143
+ tabIndex={-1}
135
144
  key={`circle-${stateName.replace(' ', '')}`}
136
145
  className='bubble'
137
146
  cx={projection(coordinates)[0] || 0} // || 0 handles error on loads where the data isn't ready
@@ -154,11 +163,12 @@ export const BubbleList = ({ data: dataImport, state, projection, applyLegendToR
154
163
  }}
155
164
  transform={transform}
156
165
  style={{ transition: 'all .25s ease-in-out', cursor: 'pointer' }}
157
- data-tooltip-id='tooltip'
166
+ data-tooltip-id={`tooltip__${tooltipId}`}
158
167
  data-tooltip-html={toolTip}
159
168
  />
160
169
  {state.visual.extraBubbleBorder && (
161
170
  <circle
171
+ tabIndex={-1}
162
172
  key={`circle-${stateName.replace(' ', '')}`}
163
173
  className='bubble'
164
174
  cx={projection(coordinates)[0] || 0} // || 0 handles error on loads where the data isn't ready
@@ -181,7 +191,7 @@ export const BubbleList = ({ data: dataImport, state, projection, applyLegendToR
181
191
  }}
182
192
  transform={transform}
183
193
  style={{ transition: 'all .25s ease-in-out', cursor: 'pointer' }}
184
- data-tooltip-id='tooltip'
194
+ data-tooltip-id={`tooltip__${tooltipId}`}
185
195
  data-tooltip-html={toolTip}
186
196
  />
187
197
  )}