@cdc/map 2.6.3 → 9.22.9

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 (73) hide show
  1. package/dist/cdcmap.js +32 -18
  2. package/examples/bubble-us.json +363 -0
  3. package/examples/bubble-world.json +427 -0
  4. package/examples/default-county.json +64 -12
  5. package/examples/default-hex.json +477 -0
  6. package/examples/default-usa-regions.json +118 -0
  7. package/examples/default-usa.json +1 -1
  8. package/examples/default-world-data.json +1450 -0
  9. package/examples/default-world.json +5 -3
  10. package/examples/example-city-state.json +46 -1
  11. package/examples/gallery/categorical-qualitative.json +797 -0
  12. package/examples/gallery/categorical-scale-based.json +739 -0
  13. package/examples/gallery/city-state.json +479 -0
  14. package/examples/gallery/county.json +22731 -0
  15. package/examples/gallery/equal-interval.json +1027 -0
  16. package/examples/gallery/equal-number.json +1027 -0
  17. package/examples/gallery/filterable.json +909 -0
  18. package/examples/gallery/hex-filtered.json +420 -0
  19. package/examples/gallery/hex.json +413 -0
  20. package/examples/gallery/single-state.json +21402 -0
  21. package/examples/gallery/world.json +1592 -0
  22. package/examples/private/atsdr.json +439 -0
  23. package/examples/private/atsdr_new.json +436 -0
  24. package/examples/private/bubble.json +285 -0
  25. package/examples/private/city-state.json +428 -0
  26. package/examples/private/cty-issue.json +42768 -0
  27. package/examples/private/default-usa.json +460 -0
  28. package/examples/private/default-world-data.json +1444 -0
  29. package/examples/private/default.json +968 -0
  30. package/examples/private/legend-issue.json +1 -0
  31. package/examples/private/map-rounding-error.json +42759 -0
  32. package/examples/private/map.csv +60 -0
  33. package/examples/private/mdx.json +210 -0
  34. package/examples/private/monkeypox.json +376 -0
  35. package/examples/private/regions.json +52 -0
  36. package/examples/private/valid-data-map.csv +59 -0
  37. package/examples/private/wcmsrd-13881-data.json +2858 -0
  38. package/examples/private/wcmsrd-13881.json +5823 -0
  39. package/examples/private/wcmsrd-14492-data.json +292 -0
  40. package/examples/private/wcmsrd-14492.json +114 -0
  41. package/examples/private/wcmsrd-test.json +268 -0
  42. package/examples/private/world.json +1580 -0
  43. package/examples/private/worldmap.json +1490 -0
  44. package/package.json +51 -50
  45. package/src/CdcMap.js +496 -158
  46. package/src/components/BubbleList.js +244 -0
  47. package/src/components/CityList.js +41 -5
  48. package/src/components/CountyMap.js +16 -6
  49. package/src/components/DataTable.js +25 -18
  50. package/src/components/EditorPanel.js +915 -404
  51. package/src/components/Geo.js +1 -1
  52. package/src/components/Modal.js +2 -1
  53. package/src/components/NavigationMenu.js +4 -3
  54. package/src/components/Sidebar.js +14 -19
  55. package/src/components/SingleStateMap.js +11 -5
  56. package/src/components/UsaMap.js +103 -36
  57. package/src/components/UsaRegionMap.js +320 -0
  58. package/src/components/WorldMap.js +116 -34
  59. package/src/data/country-coordinates.js +250 -0
  60. package/src/data/{dfc-map.json → county-map.json} +0 -0
  61. package/src/data/initial-state.js +20 -2
  62. package/src/data/state-coordinates.js +55 -0
  63. package/src/data/supported-geos.js +96 -15
  64. package/src/data/us-regions-topo-2.json +360525 -0
  65. package/src/data/us-regions-topo.json +37894 -0
  66. package/src/hooks/useColorPalette.ts +96 -0
  67. package/src/index.html +7 -4
  68. package/src/scss/editor-panel.scss +78 -57
  69. package/src/scss/main.scss +1 -1
  70. package/src/scss/map.scss +112 -2
  71. package/src/scss/sidebar.scss +2 -1
  72. package/src/data/color-palettes.js +0 -200
  73. package/src/images/map-folded.svg +0 -1
@@ -0,0 +1,244 @@
1
+ import React, {memo, useState, useEffect} from 'react'
2
+ import { scaleLinear } from 'd3-scale';
3
+ import { countryCoordinates } from '../data/country-coordinates';
4
+ import stateCoordinates from '../data/state-coordinates';
5
+ import ReactTooltip from 'react-tooltip'
6
+
7
+ export const BubbleList = (
8
+ {
9
+ data: dataImport,
10
+ state,
11
+ projection,
12
+ applyLegendToRow,
13
+ applyTooltipsToGeo,
14
+ handleCircleClick,
15
+ runtimeData,
16
+ displayGeoName
17
+ }) => {
18
+
19
+ useEffect(() => {
20
+ ReactTooltip.hide()
21
+ }, [runtimeData]);
22
+
23
+ const maxDataValue = Math.max(...dataImport.map(d => d[state.columns.primary.name]))
24
+ const hasBubblesWithZeroOnMap = state.visual.showBubbleZeros ? 0 : 1;
25
+ // sort runtime data. Smaller bubbles should appear on top.
26
+ const sortedRuntimeData = Object.values(runtimeData).sort((a, b) => a[state.columns.primary.name] < b[state.columns.primary.name] ? 1 : -1 )
27
+ if(!sortedRuntimeData) return;
28
+
29
+ const clickTolerance = 10;
30
+
31
+ // Set bubble sizes
32
+ var size = scaleLinear()
33
+ .domain([hasBubblesWithZeroOnMap, maxDataValue])
34
+ .range([state.visual.minBubbleSize, state.visual.maxBubbleSize])
35
+
36
+ // Start looping through the countries to create the bubbles.
37
+ if(state.general.geoType === 'world') {
38
+ const countries = sortedRuntimeData && sortedRuntimeData.map( (country, index) => {
39
+
40
+ let coordinates = countryCoordinates[country.uid]
41
+
42
+ if(!coordinates) return true;
43
+
44
+ const countryName = displayGeoName(country[state.columns.geo.name]);
45
+ const toolTip = applyTooltipsToGeo(countryName, country);
46
+ const legendColors = applyLegendToRow(country);
47
+
48
+ let primaryKey = state.columns.primary.name
49
+ if ((Math.floor(Number(size(country[primaryKey]))) === 0 || country[primaryKey] === "") && !state.visual.showBubbleZeros) return;
50
+
51
+ let transform = `translate(${projection([coordinates[1], coordinates[0]])})`
52
+
53
+ let pointerX, pointerY;
54
+
55
+ if( !projection(coordinates) ) return true;
56
+
57
+ const circle = (
58
+ <>
59
+ <circle
60
+ key={`circle-${countryName.replace(' ', '')}`}
61
+ data-tip={toolTip}
62
+ data-for="tooltip"
63
+ className={`bubble country--${countryName}`}
64
+ cx={ Number(projection(coordinates[1], coordinates[0])[0]) || 0 } // || 0 handles error on loads where the data isn't ready
65
+ cy={ Number(projection(coordinates[1], coordinates[0])[1]) || 0 }
66
+ r={ Number(size(country[primaryKey])) }
67
+ fill={legendColors[0] }
68
+ stroke={legendColors[0]}
69
+ strokeWidth={1.25}
70
+ fillOpacity={.4}
71
+ onPointerDown={(e) => {
72
+ pointerX = e.clientX;
73
+ pointerY = e.clientY;
74
+ }}
75
+ onPointerUp={(e) => {
76
+ if(pointerX && pointerY &&
77
+ e.clientX > (pointerX - clickTolerance) &&
78
+ e.clientX < (pointerX + clickTolerance) &&
79
+ e.clientY > (pointerY - clickTolerance) &&
80
+ e.clientY < (pointerY + clickTolerance)
81
+ ){
82
+ handleCircleClick(country)
83
+ pointerX = undefined;
84
+ pointerY = undefined;
85
+ }
86
+ }}
87
+ transform={transform}
88
+ style={{ transition: 'all .25s ease-in-out', cursor: "pointer" }}
89
+ />
90
+
91
+ {state.visual.extraBubbleBorder &&
92
+ <circle
93
+ key={`circle-${countryName.replace(' ', '')}`}
94
+ data-tip={toolTip}
95
+ data-for="tooltip"
96
+ className="bubble"
97
+ cx={ Number(projection(coordinates[1], coordinates[0])[0]) || 0 } // || 0 handles error on loads where the data isn't ready
98
+ cy={ Number(projection(coordinates[1], coordinates[0])[1]) || 0 }
99
+ r={ Number(size(country[primaryKey])) + 1 }
100
+ fill={ "transparent" }
101
+ stroke={ "white" }
102
+ strokeWidth={.5}
103
+ onPointerDown={(e) => {
104
+ pointerX = e.clientX;
105
+ pointerY = e.clientY;
106
+ }}
107
+ onPointerUp={(e) => {
108
+ if(pointerX && pointerY &&
109
+ e.clientX > (pointerX - clickTolerance) &&
110
+ e.clientX < (pointerX + clickTolerance) &&
111
+ e.clientY > (pointerY - clickTolerance) &&
112
+ e.clientY < (pointerY + clickTolerance)
113
+ ){
114
+ handleCircleClick(country)
115
+ pointerX = undefined;
116
+ pointerY = undefined;
117
+ }
118
+ }}
119
+ transform={transform}
120
+ style={{ transition: 'all .25s ease-in-out', cursor: "pointer" }}
121
+ />
122
+ }
123
+ </>
124
+ );
125
+
126
+
127
+ return (
128
+ <g key={`group-${countryName.replace(' ', '')}`}>
129
+ {circle}
130
+ </g>
131
+ )
132
+ })
133
+ return countries;
134
+ }
135
+
136
+ if(state.general.geoType === 'us') {
137
+ const bubbles = sortedRuntimeData && sortedRuntimeData.map( (item, index) => {
138
+ let stateData = stateCoordinates[item.uid]
139
+ let primaryKey = state?.columns?.primary?.name
140
+ if ( Number(size(item[primaryKey])) === 0) return;
141
+
142
+ if (item[primaryKey] === null) item[primaryKey] = ""
143
+
144
+ // Return if hiding zeros on the map
145
+ if( (Math.floor(Number(size(item[primaryKey]))) === 0 || item[primaryKey] === "" )&& !state.visual.showBubbleZeros ) return;
146
+
147
+ if(!stateData) return true;
148
+ let longitude = Number( stateData.Longitude);
149
+ let latitude = Number( stateData.Latitude);
150
+ let coordinates = [longitude, latitude]
151
+ //console.log('projection', projection([longitude, latitude]))
152
+ let stateName = stateData.Name;
153
+ if (!coordinates) return true;
154
+
155
+ stateName = displayGeoName(stateName);
156
+ const toolTip = applyTooltipsToGeo(stateName, item);
157
+ const legendColors = applyLegendToRow(item);
158
+
159
+
160
+ let transform = `translate(${projection([coordinates[1], coordinates[0]])})`
161
+
162
+ if ( !projection(coordinates) ) return true;
163
+
164
+ let pointerX, pointerY;
165
+ const circle = (
166
+ <>
167
+ <circle
168
+ key={`circle-${stateName.replace(' ', '')}`}
169
+ data-tip={toolTip}
170
+ data-for="tooltip"
171
+ className="bubble"
172
+ cx={projection(coordinates)[0] || 0} // || 0 handles error on loads where the data isn't ready
173
+ cy={projection(coordinates)[1] || 0}
174
+ r={Number(size(item[primaryKey]))}
175
+ fill={legendColors[0]}
176
+ stroke={legendColors[0]}
177
+ strokeWidth={1.25}
178
+ fillOpacity={.4}
179
+ onPointerDown={(e) => {
180
+ pointerX = e.clientX;
181
+ pointerY = e.clientY;
182
+ }}
183
+ onPointerUp={(e) => {
184
+ if (pointerX && pointerY &&
185
+ e.clientX > (pointerX - clickTolerance) &&
186
+ e.clientX < (pointerX + clickTolerance) &&
187
+ e.clientY > (pointerY - clickTolerance) &&
188
+ e.clientY < (pointerY + clickTolerance)
189
+ ) {
190
+ handleCircleClick(state)
191
+ pointerX = undefined;
192
+ pointerY = undefined;
193
+ }
194
+ }}
195
+ transform={transform}
196
+ style={{ transition: 'all .25s ease-in-out', cursor: "pointer" }}
197
+ />
198
+ { state.visual.extraBubbleBorder &&
199
+ <circle
200
+ key={`circle-${stateName.replace(' ', '')}`}
201
+ data-tip={toolTip}
202
+ data-for="tooltip"
203
+ className="bubble"
204
+ cx={ projection(coordinates)[0] || 0 } // || 0 handles error on loads where the data isn't ready
205
+ cy={ projection(coordinates)[1] || 0 }
206
+ r={ Number(size(item[primaryKey])) + 1 }
207
+ fill={"transparent"}
208
+ stroke={"white"}
209
+ strokeWidth={.5}
210
+ fillOpacity={.4}
211
+ onPointerDown={(e) => {
212
+ pointerX = e.clientX;
213
+ pointerY = e.clientY;
214
+ }}
215
+ onPointerUp={(e) => {
216
+ if (pointerX && pointerY &&
217
+ e.clientX > (pointerX - clickTolerance) &&
218
+ e.clientX < (pointerX + clickTolerance) &&
219
+ e.clientY > (pointerY - clickTolerance) &&
220
+ e.clientY < (pointerY + clickTolerance)
221
+ ) {
222
+ handleCircleClick(state)
223
+ pointerX = undefined;
224
+ pointerY = undefined;
225
+ }
226
+ }}
227
+ transform={transform}
228
+ style={{ transition: 'all .25s ease-in-out', cursor: "pointer" }}
229
+ />
230
+ }
231
+ </>
232
+ );
233
+
234
+
235
+ return (
236
+ <g key={`group-${stateName.replace(' ', '')}`}>
237
+ {circle}
238
+ </g>
239
+ )
240
+ })
241
+ return bubbles;
242
+ }
243
+ }
244
+ export default BubbleList
@@ -2,6 +2,7 @@ import React, { useState, useEffect, useContext } from 'react';
2
2
  /** @jsx jsx */
3
3
  import { jsx } from '@emotion/react'
4
4
  import { supportedCities } from '../data/supported-geos';
5
+ import { scaleLinear } from 'd3-scale';
5
6
 
6
7
  const CityList = (({
7
8
  data,
@@ -11,7 +12,9 @@ const CityList = (({
11
12
  displayGeoName,
12
13
  applyLegendToRow,
13
14
  projection,
14
- titleCase
15
+ titleCase,
16
+ setSharedFilterValue,
17
+ isFilterValueSupported
15
18
  }) => {
16
19
  const [citiesData, setCitiesData] = useState({});
17
20
 
@@ -25,6 +28,18 @@ const CityList = (({
25
28
  setCitiesData(citiesDictionary);
26
29
  }, [data]);
27
30
 
31
+ if (state.general.type === 'bubble') {
32
+ const maxDataValue = Math.max(...state.data.map(d => d[state.columns.primary.name]))
33
+ const sortedRuntimeData = Object.values(data).sort((a, b) => a[state.columns.primary.name] < b[state.columns.primary.name] ? 1 : -1)
34
+ if (!sortedRuntimeData) return;
35
+
36
+ // Set bubble sizes
37
+ var size = scaleLinear()
38
+ .domain([1, maxDataValue])
39
+ .range([state.visual.minBubbleSize, state.visual.maxBubbleSize])
40
+
41
+ }
42
+
28
43
  const cityList = Object.keys(citiesData).filter((c) => undefined !== data[c]);
29
44
 
30
45
  const cities = cityList.map((city, i) => {
@@ -38,8 +53,8 @@ const CityList = (({
38
53
 
39
54
  const styles = {
40
55
  fill: legendColors[0],
41
- outline: 0,
42
- stroke: 'rgba(0, 0, 0, 0.4)',
56
+ opacity: setSharedFilterValue && isFilterValueSupported && data[city][state.columns.geo.name] !== setSharedFilterValue ? .5 : 1,
57
+ stroke: setSharedFilterValue && isFilterValueSupported && data[city][state.columns.geo.name] === setSharedFilterValue ? 'rgba(0, 0, 0, 1)' : 'rgba(0, 0, 0, 0.4)',
43
58
  '&:hover': {
44
59
  fill: legendColors[1],
45
60
  outline: 0
@@ -61,18 +76,38 @@ const CityList = (({
61
76
 
62
77
  const radius = state.general.geoType === 'us' ? 8 : 4;
63
78
 
79
+ const additionalProps = {
80
+ fillOpacity: state.general.type === 'bubble' ? .4 : 1
81
+ }
82
+
64
83
  const circle = (
65
84
  <circle
66
85
  data-tip={toolTip}
67
86
  data-for="tooltip"
68
87
  cx={0}
69
88
  cy={0}
70
- r={radius}
89
+ r={ state.general.type === 'bubble' ? size(geoData[state.columns.primary.name]) : radius}
71
90
  title="Click for more information"
72
91
  onClick={() => geoClickHandler(cityDisplayName, geoData)}
92
+ {...additionalProps}
73
93
  />
74
94
  );
75
95
 
96
+ const pin = (
97
+ <path
98
+ className="marker"
99
+ d="M0,0l-8.8-17.7C-12.1-24.3-7.4-32,0-32h0c7.4,0,12.1,7.7,8.8,14.3L0,0z"
100
+ title="Click for more information"
101
+ onClick={() => geoClickHandler(cityDisplayName, geoData)}
102
+ data-tip={toolTip}
103
+ data-for="tooltip"
104
+ strokeWidth={2}
105
+ stroke={'black'}
106
+ {...additionalProps}
107
+ >
108
+ </path>
109
+ );
110
+
76
111
  let transform = `translate(${projection(supportedCities[city])})`
77
112
 
78
113
  return (
@@ -82,7 +117,8 @@ const CityList = (({
82
117
  css={styles}
83
118
  className="geo-point"
84
119
  >
85
- {circle}
120
+ {state.visual.cityStyle === 'circle' && circle }
121
+ {state.visual.cityStyle === 'pin' && pin }
86
122
  </g>
87
123
  );
88
124
  });
@@ -1,13 +1,14 @@
1
1
  import React, { useState, useEffect, memo, useRef } from 'react';
2
+ import Loading from '@cdc/core/components/Loading';
2
3
  /** @jsx jsx */
3
4
  import { jsx } from '@emotion/react';
4
5
  import ErrorBoundary from '@cdc/core/components/ErrorBoundary';
5
6
  import { geoCentroid, geoPath } from 'd3-geo';
6
7
  import { feature, mesh } from 'topojson-client';
7
8
  import { CustomProjection } from '@visx/geo';
8
- import colorPalettes from '../data/color-palettes';
9
+ import colorPalettes from '../../../core/data/colorPalettes'
9
10
  import { geoAlbersUsaTerritories } from 'd3-composite-projections';
10
- import testJSON from '../data/dfc-map.json';
11
+ import testJSON from '../data/county-map.json';
11
12
  import { abbrs } from '../data/abbreviations';
12
13
 
13
14
  const offsets = {
@@ -57,17 +58,17 @@ const nudges = {
57
58
  };
58
59
 
59
60
  function CountyMapChecks(prevState, nextState) {
61
+ const equalNumberOptIn = prevState.state.general.equalNumberOptIn && nextState.state.general.equalNumberOptIn;
60
62
  const equalColumnName = prevState.state.general.type && nextState.state.general.type;
61
63
  const equalNavColumn = prevState.state.columns.navigate && nextState.state.columns.navigate;
62
64
  const equalLegend = prevState.runtimeLegend === nextState.runtimeLegend;
63
65
  const equalBorderColors = prevState.state.general.geoBorderColor === nextState.state.general.geoBorderColor; // update when geoborder color changes
64
66
  const equalMapColors = prevState.state.color === nextState.state.color; // update when map colors change
65
67
  const equalData = prevState.data === nextState.data; // update when data changes
66
- return equalMapColors && equalData && equalBorderColors && equalLegend && equalColumnName && equalNavColumn ? true : false;
68
+ return equalMapColors && equalData && equalBorderColors && equalLegend && equalColumnName && equalNavColumn && equalNumberOptIn ? true : false;
67
69
  }
68
70
 
69
71
  const CountyMap = (props) => {
70
-
71
72
  let mapData = states.concat(counties);
72
73
 
73
74
  const {
@@ -79,6 +80,7 @@ const CountyMap = (props) => {
79
80
  displayGeoName,
80
81
  rebuildTooltips,
81
82
  containerEl,
83
+ handleMapAriaLabels
82
84
  } = props;
83
85
 
84
86
  useEffect(() => {
@@ -514,10 +516,18 @@ const CountyMap = (props) => {
514
516
  geosJsx.push(<FocusedStateBorder key="focused-border-key" />);
515
517
  return geosJsx;
516
518
  };
517
-
519
+ if(!data) <Loading />
518
520
  return (
519
521
  <ErrorBoundary component='CountyMap'>
520
- <svg viewBox={`0 0 ${WIDTH} ${HEIGHT}`} preserveAspectRatio='xMinYMin' className='svg-container' data-scale={scale ? scale : ''} data-translate={translate ? translate : ''}>
522
+ <svg
523
+ viewBox={`0 0 ${WIDTH} ${HEIGHT}`}
524
+ preserveAspectRatio='xMinYMin'
525
+ className='svg-container'
526
+ data-scale={scale ? scale : ''}
527
+ data-translate={translate ? translate : ''}
528
+ role="img"
529
+ aria-label={handleMapAriaLabels(state)}
530
+ >
521
531
  <rect
522
532
  className='background center-container ocean'
523
533
  width={WIDTH}
@@ -5,7 +5,7 @@ import {
5
5
  useTable, useSortBy, useResizeColumns, useBlockLayout
6
6
  } from 'react-table';
7
7
  import Papa from 'papaparse';
8
- import ExternalIcon from '../images/external-link.svg';
8
+ import ExternalIcon from '../images/external-link.svg'; // TODO: Move to Icon component
9
9
 
10
10
  import ErrorBoundary from '@cdc/core/components/ErrorBoundary';
11
11
  import LegendCircle from '@cdc/core/components/LegendCircle';
@@ -30,7 +30,10 @@ const DataTable = (props) => {
30
30
  applyLegendToRow,
31
31
  displayGeoName,
32
32
  navigationHandler,
33
- viewport
33
+ viewport,
34
+ formatLegendLocation,
35
+ tabbingId,
36
+ setFilteredCountryCode
34
37
  } = props;
35
38
 
36
39
  const [expanded, setExpanded] = useState(expandDataTable);
@@ -39,7 +42,7 @@ const DataTable = (props) => {
39
42
 
40
43
  const [ready, setReady] = useState(false)
41
44
 
42
- const fileName = `${mapTitle}.csv`;
45
+ const fileName = `${mapTitle || 'data-table'}.csv`;
43
46
 
44
47
 
45
48
  // Catch all sorting method used on load by default but also on user click
@@ -164,7 +167,6 @@ const DataTable = (props) => {
164
167
  id={`${skipId}`}
165
168
  data-html2canvas-ignore
166
169
  role="button"
167
- tabIndex="-1"
168
170
  >
169
171
  Download Data (CSV)
170
172
  </a>
@@ -176,9 +178,9 @@ const DataTable = (props) => {
176
178
  const newTableColumns = [];
177
179
 
178
180
  Object.keys(columns).forEach((column) => {
179
- if (columns[column].dataTable === true && '' !== columns[column].name) {
181
+ if (columns[column].dataTable === true && columns[column].name) {
180
182
  const newCol = {
181
- Header: columns[column].label || columns[column].name,
183
+ Header: columns[column].label ? columns[column].label : columns[column].name,
182
184
  id: column,
183
185
  accessor: (row) => {
184
186
  if (runtimeData) {
@@ -204,7 +206,11 @@ const DataTable = (props) => {
204
206
 
205
207
  const legendColor = applyLegendToRow(rowObj);
206
208
 
207
- let labelValue = displayGeoName(row.original);
209
+ if(state.general.geoType !== 'us-county') {
210
+ var labelValue = displayGeoName(row.original);
211
+ } else {
212
+ var labelValue = formatLegendLocation(row.original)
213
+ }
208
214
 
209
215
  labelValue = getCellAnchor(labelValue, rowObj);
210
216
 
@@ -230,11 +236,11 @@ const DataTable = (props) => {
230
236
  });
231
237
 
232
238
  return newTableColumns;
233
- }, [indexTitle, columns, runtimeData, runtimeLegend]);
239
+ }, [indexTitle, columns, runtimeData,getCellAnchor,displayDataAsText,applyLegendToRow,customSort,displayGeoName,state.legend.specialClasses]);
234
240
 
235
241
  const tableData = useMemo(
236
242
  () => Object.keys(runtimeData).filter((key) => applyLegendToRow(runtimeData[key])).sort((a, b) => customSort(a, b)),
237
- [runtimeLegend, runtimeData, applyLegendToRow, customSort]
243
+ [ runtimeData, applyLegendToRow, customSort]
238
244
  );
239
245
 
240
246
  // Change accessibility label depending on expanded status
@@ -282,7 +288,7 @@ const DataTable = (props) => {
282
288
  if(!state.data) return <Loading />
283
289
  return (
284
290
  <ErrorBoundary component="DataTable">
285
- <section id={state.general.title ? `dataTableSection__${state.general.title.replace(/\s/g, '')}` : `dataTableSection`} className={`data-table-container ${viewport}`} aria-label={accessibilityLabel}>
291
+ <section id={tabbingId.replace('#', '')} className={`data-table-container ${viewport}`} aria-label={accessibilityLabel}>
286
292
  <a id='skip-nav' className='cdcdataviz-sr-only-focusable' href={`#${skipId}`}>
287
293
  Skip Navigation or Skip to Content
288
294
  </a>
@@ -292,17 +298,17 @@ const DataTable = (props) => {
292
298
  tabIndex="0"
293
299
  onKeyDown={(e) => { if (e.keyCode === 13) { setExpanded(!expanded); } }}
294
300
  >
295
-
301
+
296
302
  {tableTitle}
297
303
  </div>
298
- <div
304
+ <div
299
305
  className="table-container"
300
- style={ { maxHeight: state.dataTable.limitHeight && `${state.dataTable.height}px`, overflowY: 'scroll' } }
306
+ style={ { maxHeight: state.dataTable.limitHeight && `${state.dataTable.height}px`, overflowY: 'scroll' } }
301
307
  >
302
308
  <table
303
- height={expanded ? null : 0} {...getTableProps()}
304
- aria-live="assertive"
305
- className={expanded ? 'data-table' : 'data-table cdcdataviz-sr-only'}
309
+ height={expanded ? null : 0} {...getTableProps()}
310
+ aria-live="assertive"
311
+ className={expanded ? 'data-table' : 'data-table cdcdataviz-sr-only'}
306
312
  hidden={!expanded}
307
313
  aria-rowcount={state?.data.length ? state.data.length : '-1' }
308
314
  >
@@ -311,7 +317,8 @@ const DataTable = (props) => {
311
317
  {headerGroups.map((headerGroup) => (
312
318
  <tr {...headerGroup.getHeaderGroupProps()}>
313
319
  {headerGroup.headers.map((column) => (
314
- <th tabIndex="0"
320
+ <th
321
+ tabIndex="0"
315
322
  title={column.Header}
316
323
  role="columnheader"
317
324
  scope="col"
@@ -338,7 +345,7 @@ const DataTable = (props) => {
338
345
  return (
339
346
  <tr {...row.getRowProps()} role="row">
340
347
  {row.cells.map((cell) => (
341
- <td tabIndex="0" {...cell.getCellProps()} role="gridcell">
348
+ <td tabIndex="0" {...cell.getCellProps()} role="gridcell" onClick={ (e) => (state.general.type === 'bubble' && state.general.allowMapZoom && state.general.geoType === 'world') ? setFilteredCountryCode(cell.row.original) : true }>
342
349
  {cell.render('Cell')}
343
350
  </td>
344
351
  ))}