@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
package/src/CdcMap.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import React, { useState, useEffect, useRef, memo, useCallback } from 'react';
2
+ import * as d3 from 'd3';
2
3
 
3
4
  // IE11
4
5
  import 'core-js/stable'
@@ -8,31 +9,42 @@ import ResizeObserver from 'resize-observer-polyfill';
8
9
  // Third party
9
10
  import ReactTooltip from 'react-tooltip';
10
11
  import chroma from 'chroma-js';
11
- import Papa from 'papaparse';
12
12
  import parse from 'html-react-parser';
13
13
  import html2pdf from 'html2pdf.js'
14
14
  import html2canvas from 'html2canvas';
15
15
  import Canvg from 'canvg';
16
16
 
17
17
  // Data
18
+ import colorPalettes from '../../core/data/colorPalettes';
18
19
  import ExternalIcon from './images/external-link.svg';
19
- import { supportedStates, supportedTerritories, supportedCountries, supportedCounties, supportedCities, supportedStatesFipsCodes } from './data/supported-geos';
20
- import colorPalettes from './data/color-palettes';
20
+ import {
21
+ supportedStates,
22
+ supportedTerritories,
23
+ supportedCountries,
24
+ supportedCounties,
25
+ supportedCities,
26
+ supportedStatesFipsCodes,
27
+ stateFipsToTwoDigit,
28
+ supportedRegions
29
+ } from './data/supported-geos';
21
30
  import initialState from './data/initial-state';
31
+ import { countryCoordinates } from './data/country-coordinates';
22
32
 
23
33
  // Sass
24
34
  import './scss/main.scss';
25
35
  import './scss/btn.scss'
26
36
 
27
37
  // Images
38
+ // TODO: Move to Icon component
28
39
  import DownloadImg from './images/icon-download-img.svg'
29
40
  import DownloadPdf from './images/icon-download-pdf.svg'
30
41
 
31
42
  // Core
32
43
  import Loading from '@cdc/core/components/Loading';
33
- import DataTransform from '@cdc/core/components/DataTransform';
44
+ import { DataTransform } from '@cdc/core/helpers/DataTransform';
34
45
  import getViewport from '@cdc/core/helpers/getViewport';
35
- import numberFromString from '@cdc/core/helpers/numberFromString'
46
+ import numberFromString from '@cdc/core/helpers/numberFromString';
47
+ import fetchRemoteData from '@cdc/core/helpers/fetchRemoteData';
36
48
 
37
49
 
38
50
  // Child Components
@@ -40,19 +52,24 @@ import Sidebar from './components/Sidebar';
40
52
  import Modal from './components/Modal';
41
53
  import EditorPanel from './components/EditorPanel'; // Future: Lazy
42
54
  import UsaMap from './components/UsaMap'; // Future: Lazy
55
+ import UsaRegionMap from './components/UsaRegionMap'; // Future: Lazy
43
56
  import CountyMap from './components/CountyMap'; // Future: Lazy
44
57
  import DataTable from './components/DataTable'; // Future: Lazy
45
58
  import NavigationMenu from './components/NavigationMenu'; // Future: Lazy
46
59
  import WorldMap from './components/WorldMap'; // Future: Lazy
47
60
  import SingleStateMap from './components/SingleStateMap'; // Future: Lazy
48
61
 
62
+ import { publish } from '@cdc/core/helpers/events';
63
+
49
64
  // Data props
50
65
  const stateKeys = Object.keys(supportedStates)
51
66
  const territoryKeys = Object.keys(supportedTerritories)
67
+ const regionKeys = Object.keys(supportedRegions)
52
68
  const countryKeys = Object.keys(supportedCountries)
53
69
  const countyKeys = Object.keys(supportedCounties)
54
70
  const cityKeys = Object.keys(supportedCities)
55
71
 
72
+
56
73
  const generateColorsArray = (color = '#000000', special = false) => {
57
74
  let colorObj = chroma(color)
58
75
 
@@ -96,8 +113,8 @@ const getUniqueValues = (data, columnName) => {
96
113
  return Object.keys(result)
97
114
  }
98
115
 
99
- const CdcMap = ({className, config, navigationHandler: customNavigationHandler, isDashboard = false, isEditor = false, configUrl, logo = null, setConfig, hostname}) => {
100
-
116
+ const CdcMap = ({className, config, navigationHandler: customNavigationHandler, isDashboard = false, isEditor = false, configUrl, logo = null, setConfig, setSharedFilter, setSharedFilterValue, hostname = "localhost:8080",link}) => {
117
+
101
118
  const [showLoadingMessage, setShowLoadingMessage] = useState(false)
102
119
  const transform = new DataTransform()
103
120
  const [state, setState] = useState( {...initialState} )
@@ -108,29 +125,72 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
108
125
  const [runtimeData, setRuntimeData] = useState({init: true})
109
126
  const [modal, setModal] = useState(null)
110
127
  const [accessibleStatus, setAccessibleStatus] = useState('')
128
+ const [filteredCountryCode, setFilteredCountryCode] = useState()
129
+ const [position, setPosition] = useState(state.mapPosition);
130
+ const [coveLoadedHasRan, setCoveLoadedHasRan] = useState(false)
131
+ const [container, setContainer] = useState()
132
+
133
+
111
134
  let legendMemo = useRef(new Map())
112
135
 
136
+ useEffect(() => {
137
+ try {
138
+ if (filteredCountryCode) {
139
+ const filteredCountryObj = runtimeData[filteredCountryCode]
140
+ const coordinates = countryCoordinates[filteredCountryCode]
141
+ const long = coordinates[1]
142
+ const lat = coordinates[0]
143
+ const reversedCoordinates = [long, lat];
144
+
145
+ setState({
146
+ ...state,
147
+ mapPosition: { coordinates: reversedCoordinates, zoom: 3 }
148
+ })
149
+
150
+ }
151
+ } catch(e) {
152
+ console.error('Failed to set world map zoom.')
153
+ }
154
+
155
+ }, [filteredCountryCode]);
156
+
157
+ useEffect(() => {
158
+
159
+ setTimeout( () => {
160
+ if (filteredCountryCode) {
161
+ const filteredCountryObj = runtimeData[filteredCountryCode]
162
+
163
+ const tmpData = {
164
+ [filteredCountryCode]: filteredCountryObj
165
+ }
166
+
167
+ setRuntimeData(tmpData)
168
+ }
169
+ }, 100)
170
+
171
+ }, [filteredCountryCode]);
172
+
173
+ useEffect(() => {
174
+ if (state.mapPosition) {
175
+ setPosition(state.mapPosition)
176
+ }
177
+ }, [state.mapPosition, setPosition]);
113
178
 
179
+ const setZoom = (reversedCoordinates) => {
180
+ setState({
181
+ ...state,
182
+ mapPosition: { coordinates: reversedCoordinates, zoom: 3 }
183
+ })
184
+ };
114
185
 
115
186
  const resizeObserver = new ResizeObserver(entries => {
116
187
  for (let entry of entries) {
117
188
  let newViewport = getViewport(entry.contentRect.width)
118
-
189
+
119
190
  setCurrentViewport(newViewport)
120
191
  }
121
192
  });
122
193
 
123
- // *******START SCREEN READER DEBUG*******
124
- // const focusedElement = useActiveElement();
125
-
126
- // useEffect(() => {
127
- // if (focusedElement) {
128
- // focusedElement.value && console.log(focusedElement.value);
129
- // }
130
- // console.log(focusedElement);
131
- // }, [focusedElement])
132
- // *******END SCREEN READER DEBUG*******
133
-
134
194
  // Tag each row with a UID. Helps with filtering/placing geos. Not enumerable so doesn't show up in loops/console logs except when directly addressed ex row.uid
135
195
  // We are mutating state in place here (depending on where called) - but it's okay, this isn't used for rerender
136
196
  const addUIDs = useCallback((obj, fromColumn) => {
@@ -141,8 +201,16 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
141
201
  if(row.uid) row.uid = null // Wipe existing UIDs
142
202
 
143
203
  // United States check
144
- if("us" === obj.general.geoType) {
145
- const geoName = row[obj.columns.geo.name] ? row[obj.columns.geo.name].toUpperCase() : '';
204
+ if("us" === obj.general.geoType && obj.columns.geo.name) {
205
+
206
+ // const geoName = row[obj.columns.geo.name] && typeof row[obj.columns.geo.name] === "string" ? row[obj.columns.geo.name].toUpperCase() : '';
207
+
208
+ let geoName = '';
209
+ if (row[obj.columns.geo.name] !== undefined && row[obj.columns.geo.name] !== null) {
210
+
211
+ geoName = String(row[obj.columns.geo.name])
212
+ geoName = geoName.toUpperCase()
213
+ }
146
214
 
147
215
  // States
148
216
  uid = stateKeys.find( (key) => supportedStates[key].includes(geoName) )
@@ -158,6 +226,23 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
158
226
  }
159
227
  }
160
228
 
229
+ if("us-region" === obj.general.geoType && obj.columns.geo.name) {
230
+
231
+ // const geoName = row[obj.columns.geo.name] && typeof row[obj.columns.geo.name] === "string" ? row[obj.columns.geo.name].toUpperCase() : '';
232
+
233
+ let geoName = '';
234
+ if (row[obj.columns.geo.name] !== undefined && row[obj.columns.geo.name] !== null) {
235
+
236
+ geoName = String(row[obj.columns.geo.name])
237
+ geoName = geoName.toUpperCase()
238
+ }
239
+
240
+ // Regions
241
+ uid = regionKeys.find( (key) => supportedRegions[key].includes(geoName) )
242
+
243
+
244
+ }
245
+
161
246
  // World Check
162
247
  if("world" === obj.general.geoType) {
163
248
  const geoName = row[obj.columns.geo.name]
@@ -189,6 +274,9 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
189
274
 
190
275
  const
191
276
  primaryCol = obj.columns.primary.name,
277
+ isData = obj.general.type === 'data',
278
+ isBubble = obj.general.type === 'bubble',
279
+ categoricalCol = obj.columns.categorical ? obj.columns.categorical.name : undefined,
192
280
  type = obj.legend.type,
193
281
  number = obj.legend.numberOfItems,
194
282
  result = [];
@@ -210,13 +298,23 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
210
298
  6: [ 0, 2, 3, 4, 5, 7 ],
211
299
  7: [ 0, 2, 3, 4, 5, 6, 7 ],
212
300
  8: [ 0, 2, 3, 4, 5, 6, 7, 8 ],
213
- 9: [ 0, 1, 2, 3, 4, 5, 6, 7, 8 ]
301
+ 9: [ 0, 1, 2, 3, 4, 5, 6, 7, 8 ],
302
+ 10: [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
214
303
  }
215
304
 
216
305
  const applyColorToLegend = (legendIdx) => {
217
306
  // Default to "bluegreen" color scheme if the passed color isn't valid
218
307
  let mapColorPalette = obj.customColors || colorPalettes[obj.color] || colorPalettes['bluegreen']
219
308
 
309
+ // Handle Region Maps need for a 10th color
310
+ if( general.geoType === 'us-region' && mapColorPalette.length < 10 && mapColorPalette.length > 8 ) {
311
+ if(!general.palette.isReversed) {
312
+ mapColorPalette.push( chroma(mapColorPalette[8]).darken(0.75).hex() )
313
+ } else {
314
+ mapColorPalette.unshift( chroma(mapColorPalette[0]).darken(0.75).hex() )
315
+ }
316
+ }
317
+
220
318
  let colorIdx = legendIdx - specialClasses
221
319
 
222
320
  // Special Classes (No Data)
@@ -262,8 +360,8 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
262
360
  }
263
361
 
264
362
  let specialColor = '';
265
-
266
- // color the state if val is in row
363
+
364
+ // color the state if val is in row
267
365
  specialColor = result.findIndex(p => p.value === val)
268
366
 
269
367
  newLegendMemo.set( hashObj(row), specialColor)
@@ -293,10 +391,10 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
293
391
 
294
392
  specialClasses += 1;
295
393
  }
296
-
394
+
297
395
  let specialColor = '';
298
-
299
- // color the state if val is in row
396
+
397
+ // color the state if val is in row
300
398
  if ( Object.values(row).includes(val) ) {
301
399
  specialColor = result.findIndex(p => p.value === val)
302
400
  }
@@ -317,8 +415,7 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
317
415
 
318
416
  for(let i = 0; i < dataSet.length; i++) {
319
417
  let row = dataSet[i]
320
- let value = row[primaryCol]
321
-
418
+ let value = isBubble && categoricalCol && row[categoricalCol] ? row[categoricalCol] : row[primaryCol]
322
419
  if(undefined === value) continue
323
420
 
324
421
  if(false === uniqueValues.has(value)) {
@@ -328,7 +425,7 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
328
425
  uniqueValues.get(value).push(hashObj(row))
329
426
  }
330
427
 
331
- if(count === 9) break // Can only have 9 categorical items for now
428
+ if(count === 10) break // Can only have 10 categorical items for now
332
429
  }
333
430
 
334
431
  let sorted = [...uniqueValues.keys()]
@@ -380,7 +477,7 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
380
477
  let legendNumber = Math.min(number, Object.keys(uniqueValues).length);
381
478
 
382
479
  // Separate zero
383
- if(true === obj.legend.separateZero) {
480
+ if(true === obj.legend.separateZero && !state.general.equalNumberOptIn) {
384
481
  let addLegendItem = false;
385
482
 
386
483
  for(let i = 0; i < dataSet.length; i++) {
@@ -419,47 +516,172 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
419
516
  })
420
517
 
421
518
  // Equal Number
422
- if(type === 'equalnumber') {
423
- let numberOfRows = dataSet.length
519
+ if (type === 'equalnumber') {
520
+ // start work on changing legend functionality
521
+ // FALSE === ignore old version for now.
522
+ if (!state.general.equalNumberOptIn) {
523
+ let numberOfRows = dataSet.length
524
+
525
+ let remainder
526
+ let changingNumber = legendNumber
424
527
 
425
- let remainder
426
- let changingNumber = legendNumber
528
+ let chunkAmt
427
529
 
428
- let chunkAmt
530
+ // Loop through the array until it has been split into equal subarrays
531
+ while (numberOfRows > 0) {
532
+ remainder = numberOfRows % changingNumber
429
533
 
430
- // Loop through the array until it has been split into equal subarrays
431
- while ( numberOfRows > 0 ) {
432
- remainder = numberOfRows % changingNumber
534
+ chunkAmt = Math.floor(numberOfRows / changingNumber)
433
535
 
434
- chunkAmt = Math.floor(numberOfRows / changingNumber)
536
+ if (remainder > 0) {
537
+ chunkAmt += 1
538
+ }
539
+
540
+ let removedRows = dataSet.splice(0, chunkAmt);
541
+
542
+ let min = removedRows[0][primaryCol],
543
+ max = removedRows[removedRows.length - 1][primaryCol]
544
+
545
+ removedRows.forEach(row => {
546
+ newLegendMemo.set(hashObj(row), result.length)
547
+ })
548
+
549
+ result.push({
550
+ min,
551
+ max
552
+ })
435
553
 
436
- if (remainder > 0) {
437
- chunkAmt += 1
554
+ result[result.length - 1].color = applyColorToLegend(result.length - 1)
555
+
556
+ changingNumber -= 1
557
+ numberOfRows -= chunkAmt
438
558
  }
559
+ } else {
560
+ // get nums
561
+ let hasZeroInData = dataSet.filter(obj => obj[state.columns.primary.name] === 0).length > 0
562
+ let domainNums = new Set(dataSet.map(item => item[state.columns.primary.name]))
439
563
 
440
- let removedRows = dataSet.splice(0, chunkAmt);
564
+ domainNums = d3.extent(domainNums)
565
+ let colors = colorPalettes[state.color]
441
566
 
442
- let min = removedRows[0][primaryCol],
443
- max = removedRows[removedRows.length - 1][primaryCol]
567
+ let colorRange = colors.slice(0, state.legend.numberOfItems )
444
568
 
445
- removedRows.forEach(row => {
446
- newLegendMemo.set( hashObj(row), result.length )
447
- })
569
+ let scale = d3.scaleQuantile()
570
+ .domain([... new Set(dataSet.map(item => Math.round(item[state.columns.primary.name])))]) // min/max values
571
+ .range(colorRange) // set range to our colors array
448
572
 
449
- result.push({
450
- min,
451
- max
452
- })
573
+ let breaks = scale.quantiles();
453
574
 
454
- result[result.length - 1].color = applyColorToLegend(result.length - 1)
575
+ breaks = breaks.map( item => Math.round(item))
576
+
577
+ // always start with zero for new quantile
578
+ // we can't start at the first break, because there will be items missing.
579
+ // if(d3.extent(domainNums)?.[0] !== 0 && Math.min.apply(null, domainNums) !== 0) {
580
+ // console.log(`Adding: ${d3.extent(domainNums)?.[0]}`)
581
+ // breaks.unshift(d3.extent(domainNums)?.[0])
582
+ // }
583
+
584
+
585
+ // if seperating zero force it into breaks
586
+ if(breaks[0] !== 0) {
587
+ breaks.unshift(0)
588
+ }
589
+
590
+ breaks.map( (item, index) => {
591
+
592
+ const setMin = (index) => {
593
+
594
+ //debugger;
595
+ let min = breaks[index];
596
+
597
+ // if first break is a seperated zero, min is zero
598
+ if( index === 0 && state.legend.separateZero) {
599
+ min = 0;
600
+ }
601
+
602
+ // if we're on the second break, and seperating out zero, increment min to 1.
603
+ if( index === 1 && state.legend.separateZero) {
604
+ min = 1
605
+ }
606
+
607
+ // // in starting position and zero in the data
608
+ // if((index === state.legend.specialClasses?.length ) && (state.legend.specialClasses.length !== 0)) {
609
+ // min = breaks[index]
610
+ // }
611
+ return min;
612
+
613
+ }
614
+
615
+ const setMax = (index, min) => {
616
+
617
+ let max = breaks[index + 1] - 1;
618
+
619
+ // check if min and max range are the same
620
+ // if (min === max + 1) {
621
+ // max = breaks[index + 1]
622
+ // }
623
+
624
+ if(index === 0 && state.legend.separateZero) {
625
+ max = 0;
626
+ }
627
+ // if ((index === state.legend.specialClasses.length && state.legend.specialClasses.length !== 0) && !state.legend.separateZero && hasZeroInData) {
628
+ // max = 0;
629
+ // }
630
+
631
+ if(index + 1 === breaks.length) {
632
+ max = domainNums[1]
633
+ }
634
+
635
+ return max;
636
+ }
637
+
638
+ let min = setMin(index)
639
+ let max = setMax(index, min)
640
+
641
+ result.push({
642
+ min,
643
+ max,
644
+ color: scale(item)
645
+ })
646
+
647
+
648
+ dataSet.forEach( (row, dataIndex) => {
649
+ let number = row[state.columns.primary.name]
650
+
651
+ let updated = 0
652
+
653
+ // check if we're seperating zero out
654
+ updated = state.legend.separateZero && hasZeroInData ? index : index;
655
+
656
+ // check for special classes
657
+ updated = state.legend.specialClasses ? updated + state.legend.specialClasses.length : index;
658
+
659
+ if (result[updated]?.min === (null || undefined) || result[updated]?.max === (null || undefined)) return;
660
+
661
+ if(number >= result[updated].min && number <= result[updated].max) {
662
+ newLegendMemo.set(hashObj(row), updated)
663
+ }
664
+ })
665
+
666
+
667
+ })
455
668
 
456
- changingNumber -= 1
457
- numberOfRows -= chunkAmt
458
669
  }
459
670
  }
460
671
 
461
672
  // Equal Interval
462
- if(type === 'equalinterval') {
673
+
674
+ if(type === 'equalinterval' && dataSet?.length !== 0) {
675
+ if(!dataSet || dataSet.length === 0) {
676
+ setState({
677
+ ...state,
678
+ runtime: {
679
+ ...state.runtime,
680
+ editorErrorMessage: 'Error setting equal interval legend type'
681
+ }
682
+ })
683
+ return;
684
+ }
463
685
  dataSet = dataSet.filter(row => row[primaryCol] !== undefined)
464
686
  let dataMin = dataSet[0][primaryCol]
465
687
  let dataMax = dataSet[dataSet.length - 1][primaryCol]
@@ -552,7 +774,7 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
552
774
  })
553
775
 
554
776
  // Calculates what's going to be displayed on the map and data table at render.
555
- const generateRuntimeData = useCallback((obj, filters, hash) => {
777
+ const generateRuntimeData = useCallback((obj, filters, hash, test) => {
556
778
  const result = {}
557
779
 
558
780
  if(hash) {
@@ -561,14 +783,19 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
561
783
  value : hash
562
784
  });
563
785
  }
564
-
786
+
565
787
 
566
788
  obj.data.forEach(row => {
789
+
790
+ if(test) {
791
+ console.log('object', obj)
792
+ console.log('row', row)
793
+ }
567
794
  if(undefined === row.uid) return false // No UID for this row, we can't use for mapping
568
795
 
569
796
  // When on a single state map filter runtime data by state
570
797
  if (
571
- !(row[obj.columns.geo.name].substring(0, 2) === obj.general?.statePicked?.fipsCode) &&
798
+ !(String(row[obj.columns.geo.name]).substring(0, 2) === obj.general?.statePicked?.fipsCode) &&
572
799
  obj.general.geoType === 'single-state'
573
800
  ) {
574
801
  return false;
@@ -576,13 +803,13 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
576
803
 
577
804
 
578
805
  if(row[obj.columns.primary.name]) {
579
- row[obj.columns.primary.name] = numberFromString(row[obj.columns.primary.name])
806
+ row[obj.columns.primary.name] = numberFromString(row[obj.columns.primary.name], state)
580
807
  }
581
808
 
582
809
  // If this is a navigation only map, skip if it doesn't have a URL
583
810
  if("navigation" === obj.general.type ) {
584
811
  let navigateUrl = row[obj.columns.navigate.name] || "";
585
-
812
+
586
813
  if ( undefined !== navigateUrl && typeof navigateUrl === "string" ) {
587
814
  // Strip hidden characters before we check length
588
815
  navigateUrl = navigateUrl.replace( /(\r\n|\n|\r)/gm, '' );
@@ -614,6 +841,7 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
614
841
  if (node !== null) {
615
842
  resizeObserver.observe(node);
616
843
  }
844
+ setContainer(node)
617
845
  },[]);
618
846
 
619
847
  const mapSvg = useRef(null);
@@ -729,7 +957,7 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
729
957
 
730
958
  filters[idx].active = activeValue
731
959
  const newData = generateRuntimeData(state, filters)
732
-
960
+
733
961
  // throw an error if newData is empty
734
962
  if (isEmpty(newData)) throw new Error('Cove Filter Error: No runtime data to set for this filter')
735
963
 
@@ -741,12 +969,14 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
741
969
  }
742
970
 
743
971
  }
744
-
745
972
  const displayDataAsText = (value, columnName) => {
746
- if(value === null) {
973
+ if(value === null || value === "" || value === undefined ) {
747
974
  return ""
748
975
  }
749
-
976
+ if(typeof value === 'string' && value.length > 0 && state.legend.type==='equalnumber'){
977
+ return value
978
+ }
979
+
750
980
  let formattedValue = value
751
981
 
752
982
  let columnObj = state.columns[columnName]
@@ -754,25 +984,23 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
754
984
  if (columnObj) {
755
985
  // If value is a number, apply specific formattings
756
986
  if (Number(value)) {
987
+ const decimalPoint = columnObj.roundToPlace ? Number(columnObj.roundToPlace) : 0
988
+
757
989
  // Rounding
758
990
  if(columnObj.hasOwnProperty('roundToPlace') && columnObj.roundToPlace !== "None") {
759
-
760
- const decimalPoint = columnObj.roundToPlace
761
-
762
991
  formattedValue = Number(value).toFixed(decimalPoint)
763
-
992
+
764
993
  }
765
994
 
766
995
  if(columnObj.hasOwnProperty('useCommas') && columnObj.useCommas === true) {
767
-
768
- formattedValue = Number(value).toLocaleString('en-US', { style: 'decimal'})
769
-
996
+ formattedValue = Number(value).toLocaleString('en-US', { style: 'decimal', minimumFractionDigits: decimalPoint, maximumFractionDigits: decimalPoint })
770
997
  }
998
+
771
999
  }
772
1000
 
773
1001
  // Check if it's a special value. If it is not, apply the designated prefix and suffix
774
1002
  if (false === state.legend.specialClasses.includes(String(value))) {
775
- formattedValue = columnObj.prefix + formattedValue + columnObj.suffix
1003
+ formattedValue = (columnObj.prefix || '') + formattedValue + (columnObj.suffix || '')
776
1004
  }
777
1005
  }
778
1006
 
@@ -803,28 +1031,29 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
803
1031
 
804
1032
  const applyTooltipsToGeo = (geoName, row, returnType = 'string') => {
805
1033
  let toolTipText = '';
1034
+
1035
+ // Adds geo label, ie State: Georgia
806
1036
  let stateOrCounty =
807
1037
  state.general.geoType === 'us' ? 'State: ' :
808
1038
  (state.general.geoType === 'us-county' || state.general.geoType === 'single-state') ? 'County: ':
809
1039
  '';
1040
+
810
1041
  if (state.general.geoType === 'us-county') {
811
1042
  let stateFipsCode = row[state.columns.geo.name].substring(0,2)
812
1043
  const stateName = supportedStatesFipsCodes[stateFipsCode];
813
1044
 
814
- //supportedStatesFipsCodes[]
815
- toolTipText += `<strong>State: ${stateName}</strong><br/>`;
1045
+ toolTipText += !state.general.hideGeoColumnInTooltip ? `<strong>State: ${stateName}</strong><br/>` : `<strong>${stateName}</strong><br/>` ;
816
1046
  }
817
1047
 
818
- toolTipText += `<strong>${stateOrCounty}${displayGeoName(geoName)}</strong>`
1048
+ toolTipText += !state.general.hideGeoColumnInTooltip ? `<strong>${stateOrCounty}${displayGeoName(geoName)}</strong>` : `<strong>${displayGeoName(geoName)}</strong>`
819
1049
 
820
- if('data' === state.general.type && undefined !== row) {
1050
+ if( ('data' === state.general.type || state.general.type === 'bubble') && undefined !== row) {
821
1051
  toolTipText += `<dl>`
822
1052
 
823
1053
  Object.keys(state.columns).forEach((columnKey) => {
824
1054
  const column = state.columns[columnKey]
825
1055
 
826
1056
  if (true === column.tooltip) {
827
-
828
1057
  let label = column.label.length > 0 ? column.label : '';
829
1058
 
830
1059
  let value;
@@ -843,7 +1072,7 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
843
1072
  }
844
1073
 
845
1074
  if(0 < value.length) { // Only spit out the tooltip if there's a value there
846
- toolTipText += `<div><dt>${label}</dt><dd>${value}</dd></div>`
1075
+ toolTipText += state.general.hidePrimaryColumnInTooltip ? `<div><dd>${value}</dd></div>` : `<div><dt>${label}</dt><dd>${value}</dd></div>`
847
1076
  }
848
1077
 
849
1078
  }
@@ -880,43 +1109,20 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
880
1109
  setRuntimeLegend(newLegend)
881
1110
  }
882
1111
 
883
- // Supports JSON or CSV
884
- const fetchRemoteData = async (url) => {
885
- try {
886
- const urlObj = new URL(url);
887
- const regex = /(?:\.([^.]+))?$/
888
-
889
- let data = []
890
-
891
- const ext = (regex.exec(urlObj.pathname)[1])
892
- if ('csv' === ext) {
893
- data = await fetch(url)
894
- .then(response => response.text())
895
- .then(responseText => {
896
- const parsedCsv = Papa.parse(responseText, {
897
- header: true,
898
- dynamicTyping: true,
899
- skipEmptyLines: true
900
- })
901
- return parsedCsv.data
902
- })
903
- }
1112
+ const formatLegendLocation = (key) => {
1113
+ let value = key;
1114
+ var formattedName = '';
1115
+ let stateName = stateFipsToTwoDigit[key.substring(0, 2)]
904
1116
 
905
- if ('json' === ext) {
906
- data = await fetch(url)
907
- .then(response => response.json())
908
- }
1117
+ if(stateName) {
1118
+ formattedName += stateName
1119
+ }
909
1120
 
910
- return data;
911
- } catch {
912
- // If we can't parse it, still attempt to fetch it
913
- try {
914
- let response = await (await fetch(configUrl)).json()
915
- return response
916
- } catch {
917
- console.error(`Cannot parse URL: ${url}`);
918
- }
1121
+ if (countyKeys.includes(value)) {
1122
+ formattedName += ', ' + titleCase(supportedCounties[key])
919
1123
  }
1124
+
1125
+ return formattedName;
920
1126
  }
921
1127
 
922
1128
  // Attempts to find the corresponding value
@@ -941,13 +1147,16 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
941
1147
  }
942
1148
 
943
1149
  const dict = {
944
- "District of Columbia" : "Washington D.C."
1150
+ "Washington D.C." : "District of Columbia",
1151
+ "WASHINGTON DC":"District of Columbia",
1152
+ "DC":"District of Columbia",
1153
+ "WASHINGTON DC.":"District of Columbia",
1154
+ "Congo": "Republic of the Congo"
945
1155
  }
946
1156
 
947
1157
  if(true === Object.keys(dict).includes(value)) {
948
1158
  value = dict[value]
949
1159
  }
950
-
951
1160
  return titleCase(value);
952
1161
  }
953
1162
 
@@ -970,6 +1179,10 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
970
1179
  }
971
1180
 
972
1181
  const geoClickHandler = (key, value) => {
1182
+ if(setSharedFilter){
1183
+ setSharedFilter(state.uid, value);
1184
+ }
1185
+
973
1186
  // If modals are set or we are on a mobile viewport, display modal
974
1187
  if ('xs' === currentViewport || 'xxs' === currentViewport || 'click' === state.tooltips.appearanceType) {
975
1188
  setModal({
@@ -991,16 +1204,53 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
991
1204
 
992
1205
  newState?.data.forEach(dataPiece => {
993
1206
  if(dataPiece[newState.columns.geo.name]) {
1207
+
994
1208
  if(!isNaN(parseInt(dataPiece[newState.columns.geo.name])) && dataPiece[newState.columns.geo.name].length === 4) {
995
1209
  dataPiece[newState.columns.geo.name] = 0 + dataPiece[newState.columns.geo.name]
996
1210
  }
1211
+ dataPiece[newState.columns.geo.name] = dataPiece[newState.columns.geo.name].toString()
997
1212
  }
998
1213
  })
999
1214
  }
1000
1215
  return newState;
1001
1216
  }
1002
1217
 
1003
- const loadConfig = async (configObj) => {
1218
+ const handleMapAriaLabels = (state = '', testing = false) => {
1219
+ if(testing) console.log(`handleMapAriaLabels Testing On: ${state}`);
1220
+ try {
1221
+ if(!state.general.geoType) throw Error('handleMapAriaLabels: no geoType found in state');
1222
+ let ariaLabel = '';
1223
+ switch(state.general.geoType) {
1224
+ case 'world':
1225
+ ariaLabel += 'World map'
1226
+ break;
1227
+ case 'us':
1228
+ ariaLabel += 'United States map'
1229
+ break;
1230
+ case 'us-county':
1231
+ ariaLabel += `United States county map`
1232
+ break;
1233
+ case 'single-state':
1234
+ ariaLabel += `${state.general.statePicked.stateName} county map`
1235
+ break;
1236
+ case 'us-region':
1237
+ ariaLabel += `United States HHS Region map`
1238
+ break;
1239
+ default:
1240
+ ariaLabel = 'Data visualization container'
1241
+ break;
1242
+ }
1243
+
1244
+ if(state.general.title) {
1245
+ ariaLabel += ` with the title: ${state.general.title}`
1246
+ }
1247
+ return ariaLabel;
1248
+ } catch(e) {
1249
+ console.error(e.message)
1250
+ }
1251
+ }
1252
+
1253
+ const loadConfig = async (configObj) => {
1004
1254
  // Set loading flag
1005
1255
  if(!loading) setLoading(true)
1006
1256
 
@@ -1010,13 +1260,20 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
1010
1260
  ...configObj
1011
1261
  }
1012
1262
 
1263
+ const round = 1000 * 60 * 15;
1264
+ const date = new Date();
1265
+ let cacheBustingString = new Date(date.getTime() - (date.getTime() % round)).toISOString();
1266
+
1013
1267
  // If a dataUrl property exists, always pull from that.
1014
1268
  if (newState.dataUrl) {
1015
1269
  if(newState.dataUrl[0] === '/') {
1016
- newState.dataUrl = 'https://' + hostname + newState.dataUrl
1270
+ newState.dataUrl = 'http://' + hostname + newState.dataUrl
1017
1271
  }
1018
1272
 
1019
- let newData = await fetchRemoteData(newState.dataUrl)
1273
+ // handle urls with spaces in the name.
1274
+ if (newState.dataUrl) newState.dataUrl = encodeURI(newState.dataUrl + '?v=' + cacheBustingString )
1275
+
1276
+ let newData = await fetchRemoteData(newState.dataUrl )
1020
1277
 
1021
1278
  if(newData && newState.dataDescription) {
1022
1279
  newData = transform.autoStandardize(newData);
@@ -1052,11 +1309,8 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
1052
1309
  newState.dataTable.forceDisplay = !isDashboard;
1053
1310
  }
1054
1311
 
1055
-
1056
1312
  validateFipsCodeLength(newState);
1057
1313
  setState(newState)
1058
-
1059
- // Done loading
1060
1314
  setLoading(false)
1061
1315
  }
1062
1316
 
@@ -1084,6 +1338,25 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
1084
1338
  init()
1085
1339
  }, [])
1086
1340
 
1341
+ useEffect(() => {
1342
+ if (state && !coveLoadedHasRan && container) {
1343
+ publish('cove_loaded', { config: state })
1344
+ setCoveLoadedHasRan(true)
1345
+ }
1346
+ }, [state, container]);
1347
+
1348
+ // useEffect(() => {
1349
+ // if(state.focusedCountry && state.data) {
1350
+ // let newRuntimeData = state.data.filter(item => item[state.columns.geo.name] === state.focusedCountry[state.columns.geo.name])
1351
+ // let temp = {
1352
+ // ...state,
1353
+ // data: newRuntimeData
1354
+ // }
1355
+ // setRuntimeData(temp)
1356
+ // }
1357
+
1358
+ // }, [state.focusedCountry]);
1359
+
1087
1360
  useEffect(() => {
1088
1361
  if (state.data) {
1089
1362
  let newData = generateRuntimeData(state);
@@ -1092,19 +1365,16 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
1092
1365
  }, [state.general.statePicked]);
1093
1366
 
1094
1367
 
1095
-
1096
1368
  // When geotype changes
1097
1369
  useEffect(() => {
1098
-
1099
1370
  // UID
1100
1371
  if(state.data && state.columns.geo.name) {
1101
1372
  addUIDs(state, state.columns.geo.name)
1102
1373
  }
1103
-
1104
- }, [state.general.geoType]);
1105
1374
 
1106
- useEffect(() => {
1375
+ }, [state]);
1107
1376
 
1377
+ useEffect(() => {
1108
1378
  // UID
1109
1379
  if(state.data && state.columns.geo.name && state.columns.geo.name !== state.data.fromColumn) {
1110
1380
  addUIDs(state, state.columns.geo.name)
@@ -1131,7 +1401,8 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
1131
1401
  categoryValuesOrder: state.legend.categoryValuesOrder,
1132
1402
  specialClasses: state.legend.specialClasses,
1133
1403
  geoType: state.general.geoType,
1134
- data: state.data
1404
+ data: state.data,
1405
+ ...runtimeLegend
1135
1406
  })
1136
1407
 
1137
1408
  const hashData = hashObj({
@@ -1141,14 +1412,15 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
1141
1412
  geo: state.columns.geo.name,
1142
1413
  primary: state.columns.primary.name,
1143
1414
  data: state.data,
1144
- ...runtimeFilters
1415
+ ...runtimeFilters,
1416
+ mapPosition: state.mapPosition
1145
1417
  })
1146
1418
 
1147
1419
  // Data
1148
1420
  let newRuntimeData;
1149
1421
  if(hashData !== runtimeData.fromHash && state.data?.fromColumn) {
1150
- newRuntimeData = generateRuntimeData(state, filters || runtimeFilters, hashData)
1151
- setRuntimeData(newRuntimeData)
1422
+ const newRuntimeData = generateRuntimeData(state, filters || runtimeFilters, hashData)
1423
+ setRuntimeData(newRuntimeData)
1152
1424
  }
1153
1425
 
1154
1426
  // Legend
@@ -1156,6 +1428,7 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
1156
1428
  const legend = generateRuntimeLegend(state, newRuntimeData || runtimeData, hashLegend)
1157
1429
  setRuntimeLegend(legend)
1158
1430
  }
1431
+
1159
1432
  }, [state])
1160
1433
 
1161
1434
  useEffect(() => {
@@ -1170,12 +1443,13 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
1170
1443
  geoType: state.general.geoType,
1171
1444
  data: state.data
1172
1445
  })
1173
-
1446
+
1174
1447
  // Legend - Update when runtimeData does
1175
1448
  if(hashLegend !== runtimeLegend.fromHash && undefined === runtimeData.init) {
1176
1449
  const legend = generateRuntimeLegend(state, runtimeData)
1177
1450
  setRuntimeLegend(legend)
1178
1451
  }
1452
+
1179
1453
  }, [runtimeData])
1180
1454
 
1181
1455
  if(config) {
@@ -1186,8 +1460,8 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
1186
1460
 
1187
1461
  // Destructuring for more readable JSX
1188
1462
  const { general, tooltips, dataTable } = state
1189
- const { title = '', subtext = ''} = general
1190
-
1463
+ const { title = '', subtext = '' } = general
1464
+
1191
1465
  // Outer container classes
1192
1466
  let outerContainerClasses = [
1193
1467
  'cdc-open-viz-module',
@@ -1204,7 +1478,8 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
1204
1478
  'map-container',
1205
1479
  state.legend.position,
1206
1480
  state.general.type,
1207
- state.general.geoType
1481
+ state.general.geoType,
1482
+ 'outline-none'
1208
1483
  ]
1209
1484
 
1210
1485
  if(modal) {
@@ -1228,13 +1503,47 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
1228
1503
  displayGeoName,
1229
1504
  runtimeLegend,
1230
1505
  generateColorsArray,
1231
- titleCase
1506
+ titleCase,
1507
+ setState,
1508
+ setRuntimeData,
1509
+ generateRuntimeData,
1510
+ setFilteredCountryCode,
1511
+ filteredCountryCode,
1512
+ position,
1513
+ setPosition,
1514
+ setSharedFilterValue,
1515
+ hasZoom : state.general.allowMapZoom,
1516
+ handleMapAriaLabels
1232
1517
  }
1233
1518
 
1234
1519
  if (!mapProps.data || !state.data) return <Loading />;
1235
1520
 
1236
- const handleMapTabbing = general.showSidebar ? `#legend` : state.general.title ? `#dataTableSection__${state.general.title.replace(/\s/g, '')}` : `#dataTableSection`
1237
-
1521
+ const hasDataTable = state.runtime.editorErrorMessage.length === 0 && true === dataTable.forceDisplay && general.type !== 'navigation' && false === loading;
1522
+
1523
+ const handleMapTabbing = () => {
1524
+ let tabbingID;
1525
+
1526
+ // 1) skip to legend
1527
+ if (general.showSidebar) {
1528
+ tabbingID = '#legend'
1529
+ }
1530
+
1531
+ // 2) skip to data table if it exists and not a navigation map
1532
+ if (hasDataTable && !general.showSidebar) {
1533
+ tabbingID = `#dataTableSection__${Date.now()}`;
1534
+ }
1535
+
1536
+ // 3) if its a navigation map skip to the dropdown.
1537
+ if (state.general.type === 'navigation') {
1538
+ tabbingID = `#dropdown-${Date.now()}`;
1539
+ }
1540
+
1541
+ // 4) handle other options
1542
+ return tabbingID || '#!';
1543
+
1544
+ }
1545
+
1546
+ const tabId = handleMapTabbing()
1238
1547
 
1239
1548
  return (
1240
1549
  <div className={outerContainerClasses.join(' ')} ref={outerContainerRef}>
@@ -1251,7 +1560,7 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
1251
1560
  columnsInData={Object.keys(state.data[0])}
1252
1561
  />
1253
1562
  )}
1254
- {!runtimeData.init && (general.type === 'navigation' || runtimeLegend.length !== 0) && <section className={`cdc-map-inner-container ${currentViewport}`} aria-label={'Map: ' + title}>
1563
+ {!runtimeData.init && (general.type === 'navigation' || runtimeLegend) && <section className={`cdc-map-inner-container ${currentViewport}`} aria-label={'Map: ' + title}>
1255
1564
  {['lg', 'md'].includes(currentViewport) && 'hover' === tooltips.appearanceType && (
1256
1565
  <ReactTooltip
1257
1566
  id='tooltip'
@@ -1260,13 +1569,27 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
1260
1569
  html={true}
1261
1570
  className={tooltips.capitalizeLabels ? 'capitalize tooltip' : 'tooltip'}
1262
1571
  />
1263
- )}
1264
- <header className={general.showTitle === true ? '' : 'hidden'} aria-hidden='true'>
1265
- <div role='heading' className={'map-title ' + general.headerColor} tabIndex="0">
1266
- {parse(title)}
1267
- </div>
1268
- </header>
1269
- <section className={mapContainerClasses.join(' ')} onClick={(e) => closeModal(e)}>
1572
+ )}
1573
+ {state.general.title &&
1574
+ <header className={general.showTitle === true ? 'visible' : 'hidden'} {...(!general.showTitle || !state.general.title ? { "aria-hidden": true } : { "aria-hidden": false })}>
1575
+ <div role='heading' className={'map-title ' + general.headerColor} tabIndex="0" aria-level="2">
1576
+ <sup>{general.superTitle}</sup>
1577
+ <div>{parse(title)}</div>
1578
+ </div>
1579
+ </header>
1580
+ }
1581
+
1582
+ <div>
1583
+ {general.introText && <section className="introText">{parse(general.introText)}</section>}
1584
+ </div>
1585
+
1586
+ <section
1587
+ role="button"
1588
+ tabIndex="0"
1589
+ className={mapContainerClasses.join(' ')}
1590
+ onClick={(e) => closeModal(e)}
1591
+ onKeyDown={(e) => { if (e.keyCode === 13) { closeModal(e) } }}
1592
+ >
1270
1593
  {general.showDownloadMediaButton === true && (
1271
1594
  <div className='map-downloads' data-html2canvas-ignore>
1272
1595
  <div className='map-downloads__ui btn-group'>
@@ -1288,12 +1611,13 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
1288
1611
  </div>
1289
1612
  )}
1290
1613
 
1291
- <a id='skip-geo-container' className='cdcdataviz-sr-only-focusable' href={handleMapTabbing}>
1614
+ <a id='skip-geo-container' className='cdcdataviz-sr-only-focusable' href={tabId}>
1292
1615
  Skip Over Map Container
1293
1616
  </a>
1294
- <section className='geography-container' aria-hidden='true' ref={mapSvg}>
1617
+
1618
+ <section className='geography-container outline-none' ref={mapSvg} tabIndex="0">
1295
1619
  {currentViewport && (
1296
- <section className='geography-container' aria-hidden='true' ref={mapSvg}>
1620
+ <section className='geography-container' ref={mapSvg}>
1297
1621
  {modal && (
1298
1622
  <Modal
1299
1623
  type={general.type}
@@ -1308,7 +1632,10 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
1308
1632
  <SingleStateMap supportedTerritories={supportedTerritories} {...mapProps} />
1309
1633
  )}
1310
1634
  {'us' === general.geoType && (
1311
- <UsaMap supportedTerritories={supportedTerritories} {...mapProps} />
1635
+ <UsaMap supportedTerritories={supportedTerritories} {...mapProps} />
1636
+ )}
1637
+ {'us-region' === general.geoType && (
1638
+ <UsaRegionMap supportedTerritories={supportedTerritories} {...mapProps} />
1312
1639
  )}
1313
1640
  {'world' === general.geoType && (
1314
1641
  <WorldMap supportedCountries={supportedCountries} {...mapProps} />
@@ -1321,10 +1648,10 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
1321
1648
  )}
1322
1649
  {'data' === general.type && logo && <img src={logo} alt='' className='map-logo' />}
1323
1650
  </section>
1324
-
1651
+
1325
1652
  )}
1326
1653
  </section>
1327
-
1654
+
1328
1655
  {general.showSidebar && 'navigation' !== general.type && (
1329
1656
  <Sidebar
1330
1657
  viewport={currentViewport}
@@ -1340,19 +1667,25 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
1340
1667
  resetLegendToggles={resetLegendToggles}
1341
1668
  changeFilterActive={changeFilterActive}
1342
1669
  setAccessibleStatus={setAccessibleStatus}
1670
+ displayDataAsText={displayDataAsText}
1343
1671
  />
1344
1672
  )}
1345
1673
  </section>
1346
1674
  {'navigation' === general.type && (
1347
- <NavigationMenu
1675
+ <NavigationMenu
1676
+ mapTabbingID={tabId}
1348
1677
  displayGeoName={displayGeoName}
1349
1678
  data={runtimeData}
1350
1679
  options={general}
1351
1680
  columns={state.columns}
1352
1681
  navigationHandler={(val) => navigationHandler(val)}
1353
1682
  />
1354
- )}
1355
- {true === dataTable.forceDisplay && general.type !== 'navigation' && false === loading && (
1683
+ )}
1684
+ {link && link}
1685
+
1686
+ {subtext.length > 0 && <p className='subtext'>{parse(subtext)}</p>}
1687
+
1688
+ {state.runtime.editorErrorMessage.length === 0 && true === dataTable.forceDisplay && general.type !== 'navigation' && false === loading && (
1356
1689
  <DataTable
1357
1690
  state={state}
1358
1691
  rawData={state.data}
@@ -1367,13 +1700,18 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
1367
1700
  displayGeoName={displayGeoName}
1368
1701
  applyLegendToRow={applyLegendToRow}
1369
1702
  tableTitle={dataTable.title}
1370
- indexTitle={dataTable.indexTitle}
1703
+ indexTitle={dataTable.indexLabel}
1371
1704
  mapTitle={general.title}
1372
1705
  viewport={currentViewport}
1706
+ formatLegendLocation={formatLegendLocation}
1707
+ setFilteredCountryCode={setFilteredCountryCode}
1708
+ tabbingId={tabId}
1373
1709
  />
1374
- )}
1375
- {subtext.length > 0 && <p className='subtext'>{parse(subtext)}</p>}
1376
- </section>}
1710
+ )}
1711
+
1712
+ {general.footnotes && <section className="footnotes">{parse(general.footnotes)}</section>}
1713
+ </section>}
1714
+
1377
1715
  <div aria-live='assertive' className='cdcdataviz-sr-only'>
1378
1716
  {accessibleStatus}
1379
1717
  </div>