@cdc/map 2.6.3 → 2.6.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 (46) hide show
  1. package/dist/cdcmap.js +26 -18
  2. package/examples/bubble-us.json +363 -0
  3. package/examples/bubble-world.json +427 -0
  4. package/examples/default-hex.json +475 -0
  5. package/examples/default-usa-regions.json +118 -0
  6. package/examples/default-usa.json +1 -1
  7. package/examples/default-world-data.json +1450 -0
  8. package/examples/default-world.json +5 -3
  9. package/examples/example-city-state.json +36 -0
  10. package/examples/private/atsdr.json +439 -0
  11. package/examples/private/atsdr_new.json +436 -0
  12. package/examples/private/bubble.json +285 -0
  13. package/examples/private/default-world-data.json +1444 -0
  14. package/examples/private/default.json +968 -0
  15. package/examples/private/map.csv +60 -0
  16. package/examples/private/mdx.json +210 -0
  17. package/examples/private/regions.json +52 -0
  18. package/examples/private/wcmsrd-13881-data.json +2858 -0
  19. package/examples/private/wcmsrd-13881.json +5823 -0
  20. package/examples/private/wcmsrd-test.json +268 -0
  21. package/examples/private/world.json +1580 -0
  22. package/examples/private/worldmap.json +1490 -0
  23. package/package.json +51 -50
  24. package/src/CdcMap.js +340 -79
  25. package/src/components/BubbleList.js +240 -0
  26. package/src/components/CityList.js +19 -1
  27. package/src/components/CountyMap.js +3 -2
  28. package/src/components/DataTable.js +17 -10
  29. package/src/components/EditorPanel.js +741 -348
  30. package/src/components/Geo.js +1 -1
  31. package/src/components/SingleStateMap.js +1 -1
  32. package/src/components/UsaMap.js +22 -7
  33. package/src/components/UsaRegionMap.js +319 -0
  34. package/src/components/WorldMap.js +112 -35
  35. package/src/data/country-coordinates.js +250 -0
  36. package/src/data/initial-state.js +19 -2
  37. package/src/data/state-coordinates.js +55 -0
  38. package/src/data/supported-geos.js +91 -15
  39. package/src/data/us-regions-topo-2.json +360525 -0
  40. package/src/data/us-regions-topo.json +37894 -0
  41. package/src/hooks/useColorPalette.ts +96 -0
  42. package/src/index.html +10 -2
  43. package/src/scss/editor-panel.scss +76 -55
  44. package/src/scss/map.scss +108 -2
  45. package/src/data/color-palettes.js +0 -200
  46. 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'
@@ -15,10 +16,20 @@ import html2canvas from 'html2canvas';
15
16
  import Canvg from 'canvg';
16
17
 
17
18
  // Data
19
+ import colorPalettes from '../../core/data/colorPalettes';
18
20
  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';
21
+ import {
22
+ supportedStates,
23
+ supportedTerritories,
24
+ supportedCountries,
25
+ supportedCounties,
26
+ supportedCities,
27
+ supportedStatesFipsCodes,
28
+ stateFipsToTwoDigit,
29
+ supportedRegions
30
+ } from './data/supported-geos';
21
31
  import initialState from './data/initial-state';
32
+ import { countryCoordinates } from './data/country-coordinates';
22
33
 
23
34
  // Sass
24
35
  import './scss/main.scss';
@@ -33,6 +44,7 @@ import Loading from '@cdc/core/components/Loading';
33
44
  import DataTransform from '@cdc/core/components/DataTransform';
34
45
  import getViewport from '@cdc/core/helpers/getViewport';
35
46
  import numberFromString from '@cdc/core/helpers/numberFromString'
47
+ import validateFipsCodeLength from '@cdc/core/helpers/validateFipsCodeLength'
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
 
@@ -108,8 +125,64 @@ 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]);
178
+
179
+ const setZoom = (reversedCoordinates) => {
180
+ setState({
181
+ ...state,
182
+ mapPosition: { coordinates: reversedCoordinates, zoom: 3 }
183
+ })
184
+ };
185
+
113
186
 
114
187
 
115
188
  const resizeObserver = new ResizeObserver(entries => {
@@ -120,17 +193,6 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
120
193
  }
121
194
  });
122
195
 
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
196
  // 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
197
  // We are mutating state in place here (depending on where called) - but it's okay, this isn't used for rerender
136
198
  const addUIDs = useCallback((obj, fromColumn) => {
@@ -141,8 +203,16 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
141
203
  if(row.uid) row.uid = null // Wipe existing UIDs
142
204
 
143
205
  // United States check
144
- if("us" === obj.general.geoType) {
145
- const geoName = row[obj.columns.geo.name] ? row[obj.columns.geo.name].toUpperCase() : '';
206
+ if("us" === obj.general.geoType && obj.columns.geo.name) {
207
+
208
+ // const geoName = row[obj.columns.geo.name] && typeof row[obj.columns.geo.name] === "string" ? row[obj.columns.geo.name].toUpperCase() : '';
209
+
210
+ let geoName = '';
211
+ if (row[obj.columns.geo.name] !== undefined && row[obj.columns.geo.name] !== null) {
212
+
213
+ geoName = String(row[obj.columns.geo.name])
214
+ geoName = geoName.toUpperCase()
215
+ }
146
216
 
147
217
  // States
148
218
  uid = stateKeys.find( (key) => supportedStates[key].includes(geoName) )
@@ -158,6 +228,23 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
158
228
  }
159
229
  }
160
230
 
231
+ if("us-region" === obj.general.geoType && obj.columns.geo.name) {
232
+
233
+ // const geoName = row[obj.columns.geo.name] && typeof row[obj.columns.geo.name] === "string" ? row[obj.columns.geo.name].toUpperCase() : '';
234
+
235
+ let geoName = '';
236
+ if (row[obj.columns.geo.name] !== undefined && row[obj.columns.geo.name] !== null) {
237
+
238
+ geoName = String(row[obj.columns.geo.name])
239
+ geoName = geoName.toUpperCase()
240
+ }
241
+
242
+ // Regions
243
+ uid = regionKeys.find( (key) => supportedRegions[key].includes(geoName) )
244
+
245
+
246
+ }
247
+
161
248
  // World Check
162
249
  if("world" === obj.general.geoType) {
163
250
  const geoName = row[obj.columns.geo.name]
@@ -189,6 +276,9 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
189
276
 
190
277
  const
191
278
  primaryCol = obj.columns.primary.name,
279
+ isData = obj.general.type === 'data',
280
+ isBubble = obj.general.type === 'bubble',
281
+ categoricalCol = obj.columns.categorical ? obj.columns.categorical.name : undefined,
192
282
  type = obj.legend.type,
193
283
  number = obj.legend.numberOfItems,
194
284
  result = [];
@@ -210,13 +300,23 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
210
300
  6: [ 0, 2, 3, 4, 5, 7 ],
211
301
  7: [ 0, 2, 3, 4, 5, 6, 7 ],
212
302
  8: [ 0, 2, 3, 4, 5, 6, 7, 8 ],
213
- 9: [ 0, 1, 2, 3, 4, 5, 6, 7, 8 ]
303
+ 9: [ 0, 1, 2, 3, 4, 5, 6, 7, 8 ],
304
+ 10: [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
214
305
  }
215
306
 
216
307
  const applyColorToLegend = (legendIdx) => {
217
308
  // Default to "bluegreen" color scheme if the passed color isn't valid
218
309
  let mapColorPalette = obj.customColors || colorPalettes[obj.color] || colorPalettes['bluegreen']
219
310
 
311
+ // Handle Region Maps need for a 10th color
312
+ if( general.geoType === 'us-region' && mapColorPalette.length < 10 ) {
313
+ if(!general.palette.isReversed) {
314
+ mapColorPalette.push( chroma(mapColorPalette[8]).darken(0.75).hex() )
315
+ } else {
316
+ mapColorPalette.unshift( chroma(mapColorPalette[0]).darken(0.75).hex() )
317
+ }
318
+ }
319
+
220
320
  let colorIdx = legendIdx - specialClasses
221
321
 
222
322
  // Special Classes (No Data)
@@ -317,8 +417,7 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
317
417
 
318
418
  for(let i = 0; i < dataSet.length; i++) {
319
419
  let row = dataSet[i]
320
- let value = row[primaryCol]
321
-
420
+ let value = isBubble && categoricalCol && row[categoricalCol] ? row[categoricalCol] : row[primaryCol]
322
421
  if(undefined === value) continue
323
422
 
324
423
  if(false === uniqueValues.has(value)) {
@@ -328,7 +427,7 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
328
427
  uniqueValues.get(value).push(hashObj(row))
329
428
  }
330
429
 
331
- if(count === 9) break // Can only have 9 categorical items for now
430
+ if(count === 10) break // Can only have 10 categorical items for now
332
431
  }
333
432
 
334
433
  let sorted = [...uniqueValues.keys()]
@@ -419,47 +518,145 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
419
518
  })
420
519
 
421
520
  // Equal Number
422
- if(type === 'equalnumber') {
423
- let numberOfRows = dataSet.length
521
+ if (type === 'equalnumber') {
522
+ // start work on changing legend functionality
523
+ // FALSE === ignore old version for now.
524
+ if (!state.general.equalNumberOptIn) {
525
+ let numberOfRows = dataSet.length
526
+
527
+ let remainder
528
+ let changingNumber = legendNumber
529
+
530
+ let chunkAmt
531
+
532
+ // Loop through the array until it has been split into equal subarrays
533
+ while (numberOfRows > 0) {
534
+ remainder = numberOfRows % changingNumber
424
535
 
425
- let remainder
426
- let changingNumber = legendNumber
536
+ chunkAmt = Math.floor(numberOfRows / changingNumber)
427
537
 
428
- let chunkAmt
538
+ if (remainder > 0) {
539
+ chunkAmt += 1
540
+ }
541
+
542
+ let removedRows = dataSet.splice(0, chunkAmt);
429
543
 
430
- // Loop through the array until it has been split into equal subarrays
431
- while ( numberOfRows > 0 ) {
432
- remainder = numberOfRows % changingNumber
544
+ let min = removedRows[0][primaryCol],
545
+ max = removedRows[removedRows.length - 1][primaryCol]
433
546
 
434
- chunkAmt = Math.floor(numberOfRows / changingNumber)
547
+ removedRows.forEach(row => {
548
+ newLegendMemo.set(hashObj(row), result.length)
549
+ })
435
550
 
436
- if (remainder > 0) {
437
- chunkAmt += 1
551
+ result.push({
552
+ min,
553
+ max
554
+ })
555
+
556
+ result[result.length - 1].color = applyColorToLegend(result.length - 1)
557
+
558
+ changingNumber -= 1
559
+ numberOfRows -= chunkAmt
438
560
  }
561
+ } else {
562
+ // get nums
563
+ let hasZeroInData = dataSet.filter(obj => obj[state.columns.primary.name] === 0).length > 0
564
+ let domainNums = new Set(dataSet.map(item => item[state.columns.primary.name]))
565
+ console.log('hasZeroInData', hasZeroInData)
566
+ if(hasZeroInData && state.legend.separateZero) { domainNums.add(0) }
567
+
568
+ domainNums = d3.extent(domainNums)
569
+ let colors = colorPalettes[state.color]
570
+ let colorRange = colors.slice(0, state.legend.separateZero ? state.legend.numberOfItems - 1 : state.legend.numberOfItems)
571
+ let scale = d3.scaleQuantile()
572
+ .domain(dataSet.map(item => Math.round(item[state.columns.primary.name]))) // min/max values
573
+ //.domain(domainNums)
574
+ .range(colorRange) // set range to our colors array
575
+
576
+ let breaks = scale.quantiles();
577
+ breaks = breaks.map( item => Math.round(item))
578
+
579
+ console.log('breaks', breaks)
580
+ console.log('d', domainNums)
439
581
 
440
- let removedRows = dataSet.splice(0, chunkAmt);
582
+
583
+ // always start with domain beginning breakpoint
584
+ breaks.unshift(d3.extent(domainNums)?.[0])
585
+
586
+ if (state.legend.separateZero && !hasZeroInData) {
587
+ breaks.splice(1, 0, 1);
588
+ }
589
+
590
+ breaks.map( (item, index) => {
441
591
 
442
- let min = removedRows[0][primaryCol],
443
- max = removedRows[removedRows.length - 1][primaryCol]
592
+ let min = breaks[index];
593
+ let max = breaks[index + 1] - 1;
444
594
 
445
- removedRows.forEach(row => {
446
- newLegendMemo.set( hashObj(row), result.length )
447
- })
595
+ const setMin = () => {
596
+ // in starting position and zero in the data
597
+ if(index === 0 && state.legend.separateZero) {
598
+ min = 0;
599
+ }
448
600
 
449
- result.push({
450
- min,
451
- max
452
- })
601
+ if(index === 0 && !state.legend.separateZero) {
602
+ min = domainNums[0]
603
+ }
453
604
 
454
- result[result.length - 1].color = applyColorToLegend(result.length - 1)
605
+ }
606
+
607
+ const setMax = () => {
608
+ if(index === 0 && state.legend.separateZero) {
609
+ max = 0;
610
+ }
611
+
612
+ if(index + 1 === breaks.length) {
613
+ max = domainNums[1]
614
+ }
615
+ }
616
+
617
+ setMin()
618
+ setMax()
619
+ console.log('res', result)
620
+
621
+ if(index === 0 && result[index]?.max === 0 && state.legend.separateZero) return true;
622
+ result.push({
623
+ min,
624
+ max,
625
+ color: scale(item)
626
+ })
627
+
628
+
629
+ dataSet.forEach( (row, dataIndex) => {
630
+ let number = row[state.columns.primary.name]
631
+
632
+ let updated = state.legend.separateZero ? index : index;
633
+
634
+ if (result[updated]?.min === (null || undefined) || result[updated]?.max === (null || undefined)) return;
635
+
636
+ if(number >= result[updated].min && number <= result[updated].max) {
637
+ newLegendMemo.set(hashObj(row), updated)
638
+ }
639
+ })
640
+
641
+
642
+ })
455
643
 
456
- changingNumber -= 1
457
- numberOfRows -= chunkAmt
458
644
  }
459
645
  }
460
646
 
461
647
  // Equal Interval
462
- if(type === 'equalinterval') {
648
+
649
+ if(type === 'equalinterval' && dataSet?.length !== 0) {
650
+ if(!dataSet || dataSet.length === 0) {
651
+ setState({
652
+ ...state,
653
+ runtime: {
654
+ ...state.runtime,
655
+ editorErrorMessage: 'Error setting equal interval legend type'
656
+ }
657
+ })
658
+ return;
659
+ }
463
660
  dataSet = dataSet.filter(row => row[primaryCol] !== undefined)
464
661
  let dataMin = dataSet[0][primaryCol]
465
662
  let dataMax = dataSet[dataSet.length - 1][primaryCol]
@@ -552,7 +749,7 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
552
749
  })
553
750
 
554
751
  // Calculates what's going to be displayed on the map and data table at render.
555
- const generateRuntimeData = useCallback((obj, filters, hash) => {
752
+ const generateRuntimeData = useCallback((obj, filters, hash, test) => {
556
753
  const result = {}
557
754
 
558
755
  if(hash) {
@@ -564,11 +761,16 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
564
761
 
565
762
 
566
763
  obj.data.forEach(row => {
764
+
765
+ if(test) {
766
+ console.log('object', obj)
767
+ console.log('row', row)
768
+ }
567
769
  if(undefined === row.uid) return false // No UID for this row, we can't use for mapping
568
770
 
569
771
  // When on a single state map filter runtime data by state
570
772
  if (
571
- !(row[obj.columns.geo.name].substring(0, 2) === obj.general?.statePicked?.fipsCode) &&
773
+ !(String(row[obj.columns.geo.name]).substring(0, 2) === obj.general?.statePicked?.fipsCode) &&
572
774
  obj.general.geoType === 'single-state'
573
775
  ) {
574
776
  return false;
@@ -576,7 +778,7 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
576
778
 
577
779
 
578
780
  if(row[obj.columns.primary.name]) {
579
- row[obj.columns.primary.name] = numberFromString(row[obj.columns.primary.name])
781
+ row[obj.columns.primary.name] = numberFromString(row[obj.columns.primary.name], state)
580
782
  }
581
783
 
582
784
  // If this is a navigation only map, skip if it doesn't have a URL
@@ -614,6 +816,7 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
614
816
  if (node !== null) {
615
817
  resizeObserver.observe(node);
616
818
  }
819
+ setContainer(node)
617
820
  },[]);
618
821
 
619
822
  const mapSvg = useRef(null);
@@ -772,7 +975,7 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
772
975
 
773
976
  // Check if it's a special value. If it is not, apply the designated prefix and suffix
774
977
  if (false === state.legend.specialClasses.includes(String(value))) {
775
- formattedValue = columnObj.prefix + formattedValue + columnObj.suffix
978
+ formattedValue = (columnObj.prefix || '') + formattedValue + (columnObj.suffix || '')
776
979
  }
777
980
  }
778
981
 
@@ -803,28 +1006,29 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
803
1006
 
804
1007
  const applyTooltipsToGeo = (geoName, row, returnType = 'string') => {
805
1008
  let toolTipText = '';
1009
+
1010
+ // Adds geo label, ie State: Georgia
806
1011
  let stateOrCounty =
807
1012
  state.general.geoType === 'us' ? 'State: ' :
808
1013
  (state.general.geoType === 'us-county' || state.general.geoType === 'single-state') ? 'County: ':
809
1014
  '';
1015
+
810
1016
  if (state.general.geoType === 'us-county') {
811
1017
  let stateFipsCode = row[state.columns.geo.name].substring(0,2)
812
1018
  const stateName = supportedStatesFipsCodes[stateFipsCode];
813
1019
 
814
- //supportedStatesFipsCodes[]
815
- toolTipText += `<strong>State: ${stateName}</strong><br/>`;
1020
+ toolTipText += !state.general.hideGeoColumnInTooltip ? `<strong>State: ${stateName}</strong><br/>` : `<strong>${stateName}</strong><br/>` ;
816
1021
  }
817
1022
 
818
- toolTipText += `<strong>${stateOrCounty}${displayGeoName(geoName)}</strong>`
1023
+ toolTipText += !state.general.hideGeoColumnInTooltip ? `<strong>${stateOrCounty}${displayGeoName(geoName)}</strong>` : `<strong>${displayGeoName(geoName)}</strong>`
819
1024
 
820
- if('data' === state.general.type && undefined !== row) {
1025
+ if( ('data' === state.general.type || state.general.type === 'bubble') && undefined !== row) {
821
1026
  toolTipText += `<dl>`
822
1027
 
823
1028
  Object.keys(state.columns).forEach((columnKey) => {
824
1029
  const column = state.columns[columnKey]
825
1030
 
826
1031
  if (true === column.tooltip) {
827
-
828
1032
  let label = column.label.length > 0 ? column.label : '';
829
1033
 
830
1034
  let value;
@@ -843,7 +1047,7 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
843
1047
  }
844
1048
 
845
1049
  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>`
1050
+ toolTipText += state.general.hidePrimaryColumnInTooltip ? `<div><dd>${value}</dd></div>` : `<div><dt>${label}</dt><dd>${value}</dd></div>`
847
1051
  }
848
1052
 
849
1053
  }
@@ -919,6 +1123,22 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
919
1123
  }
920
1124
  }
921
1125
 
1126
+ const formatLegendLocation = (key) => {
1127
+ let value = key;
1128
+ var formattedName = '';
1129
+ let stateName = stateFipsToTwoDigit[key.substring(0, 2)]
1130
+
1131
+ if(stateName) {
1132
+ formattedName += stateName
1133
+ }
1134
+
1135
+ if (countyKeys.includes(value)) {
1136
+ formattedName += ', ' + titleCase(supportedCounties[key])
1137
+ }
1138
+
1139
+ return formattedName;
1140
+ }
1141
+
922
1142
  // Attempts to find the corresponding value
923
1143
  const displayGeoName = (key) => {
924
1144
  let value = key
@@ -941,13 +1161,13 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
941
1161
  }
942
1162
 
943
1163
  const dict = {
944
- "District of Columbia" : "Washington D.C."
1164
+ "District of Columbia" : "Washington D.C.",
1165
+ "Congo": "Republic of the Congo"
945
1166
  }
946
1167
 
947
1168
  if(true === Object.keys(dict).includes(value)) {
948
1169
  value = dict[value]
949
1170
  }
950
-
951
1171
  return titleCase(value);
952
1172
  }
953
1173
 
@@ -991,16 +1211,18 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
991
1211
 
992
1212
  newState?.data.forEach(dataPiece => {
993
1213
  if(dataPiece[newState.columns.geo.name]) {
1214
+
994
1215
  if(!isNaN(parseInt(dataPiece[newState.columns.geo.name])) && dataPiece[newState.columns.geo.name].length === 4) {
995
1216
  dataPiece[newState.columns.geo.name] = 0 + dataPiece[newState.columns.geo.name]
996
1217
  }
1218
+ dataPiece[newState.columns.geo.name] = dataPiece[newState.columns.geo.name].toString()
997
1219
  }
998
1220
  })
999
1221
  }
1000
1222
  return newState;
1001
1223
  }
1002
1224
 
1003
- const loadConfig = async (configObj) => {
1225
+ const loadConfig = async (configObj) => {
1004
1226
  // Set loading flag
1005
1227
  if(!loading) setLoading(true)
1006
1228
 
@@ -1010,13 +1232,17 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
1010
1232
  ...configObj
1011
1233
  }
1012
1234
 
1235
+ const round = 1000 * 60 * 15;
1236
+ const date = new Date();
1237
+ let cacheBustingString = new Date(date.getTime() - (date.getTime() % round)).toISOString();
1238
+
1013
1239
  // If a dataUrl property exists, always pull from that.
1014
1240
  if (newState.dataUrl) {
1015
1241
  if(newState.dataUrl[0] === '/') {
1016
- newState.dataUrl = 'https://' + hostname + newState.dataUrl
1242
+ newState.dataUrl = 'https://' + hostname + newState.dataUrl + '?v=' + cacheBustingString
1017
1243
  }
1018
1244
 
1019
- let newData = await fetchRemoteData(newState.dataUrl)
1245
+ let newData = await fetchRemoteData(newState.dataUrl + '?v=' + cacheBustingString )
1020
1246
 
1021
1247
  if(newData && newState.dataDescription) {
1022
1248
  newData = transform.autoStandardize(newData);
@@ -1052,11 +1278,8 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
1052
1278
  newState.dataTable.forceDisplay = !isDashboard;
1053
1279
  }
1054
1280
 
1055
-
1056
1281
  validateFipsCodeLength(newState);
1057
1282
  setState(newState)
1058
-
1059
- // Done loading
1060
1283
  setLoading(false)
1061
1284
  }
1062
1285
 
@@ -1084,6 +1307,25 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
1084
1307
  init()
1085
1308
  }, [])
1086
1309
 
1310
+ useEffect(() => {
1311
+ if (state && !coveLoadedHasRan && container) {
1312
+ publish('cove_loaded', { config: state })
1313
+ setCoveLoadedHasRan(true)
1314
+ }
1315
+ }, [state, container]);
1316
+
1317
+ // useEffect(() => {
1318
+ // if(state.focusedCountry && state.data) {
1319
+ // let newRuntimeData = state.data.filter(item => item[state.columns.geo.name] === state.focusedCountry[state.columns.geo.name])
1320
+ // let temp = {
1321
+ // ...state,
1322
+ // data: newRuntimeData
1323
+ // }
1324
+ // setRuntimeData(temp)
1325
+ // }
1326
+
1327
+ // }, [state.focusedCountry]);
1328
+
1087
1329
  useEffect(() => {
1088
1330
  if (state.data) {
1089
1331
  let newData = generateRuntimeData(state);
@@ -1092,19 +1334,16 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
1092
1334
  }, [state.general.statePicked]);
1093
1335
 
1094
1336
 
1095
-
1096
1337
  // When geotype changes
1097
1338
  useEffect(() => {
1098
-
1099
1339
  // UID
1100
1340
  if(state.data && state.columns.geo.name) {
1101
1341
  addUIDs(state, state.columns.geo.name)
1102
1342
  }
1103
-
1104
- }, [state.general.geoType]);
1105
1343
 
1106
- useEffect(() => {
1344
+ }, [state]);
1107
1345
 
1346
+ useEffect(() => {
1108
1347
  // UID
1109
1348
  if(state.data && state.columns.geo.name && state.columns.geo.name !== state.data.fromColumn) {
1110
1349
  addUIDs(state, state.columns.geo.name)
@@ -1131,7 +1370,8 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
1131
1370
  categoryValuesOrder: state.legend.categoryValuesOrder,
1132
1371
  specialClasses: state.legend.specialClasses,
1133
1372
  geoType: state.general.geoType,
1134
- data: state.data
1373
+ data: state.data,
1374
+ ...runtimeLegend
1135
1375
  })
1136
1376
 
1137
1377
  const hashData = hashObj({
@@ -1141,14 +1381,15 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
1141
1381
  geo: state.columns.geo.name,
1142
1382
  primary: state.columns.primary.name,
1143
1383
  data: state.data,
1144
- ...runtimeFilters
1384
+ ...runtimeFilters,
1385
+ mapPosition: state.mapPosition
1145
1386
  })
1146
1387
 
1147
1388
  // Data
1148
1389
  let newRuntimeData;
1149
1390
  if(hashData !== runtimeData.fromHash && state.data?.fromColumn) {
1150
- newRuntimeData = generateRuntimeData(state, filters || runtimeFilters, hashData)
1151
- setRuntimeData(newRuntimeData)
1391
+ const data = generateRuntimeData(state, filters || runtimeFilters, hashData)
1392
+ setRuntimeData(data)
1152
1393
  }
1153
1394
 
1154
1395
  // Legend
@@ -1156,6 +1397,7 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
1156
1397
  const legend = generateRuntimeLegend(state, newRuntimeData || runtimeData, hashLegend)
1157
1398
  setRuntimeLegend(legend)
1158
1399
  }
1400
+
1159
1401
  }, [state])
1160
1402
 
1161
1403
  useEffect(() => {
@@ -1176,6 +1418,7 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
1176
1418
  const legend = generateRuntimeLegend(state, runtimeData)
1177
1419
  setRuntimeLegend(legend)
1178
1420
  }
1421
+
1179
1422
  }, [runtimeData])
1180
1423
 
1181
1424
  if(config) {
@@ -1228,14 +1471,21 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
1228
1471
  displayGeoName,
1229
1472
  runtimeLegend,
1230
1473
  generateColorsArray,
1231
- titleCase
1474
+ titleCase,
1475
+ setState,
1476
+ setRuntimeData,
1477
+ generateRuntimeData,
1478
+ setFilteredCountryCode,
1479
+ filteredCountryCode,
1480
+ position,
1481
+ setPosition,
1482
+ hasZoom : state.general.allowMapZoom
1232
1483
  }
1233
1484
 
1234
1485
  if (!mapProps.data || !state.data) return <Loading />;
1235
1486
 
1236
1487
  const handleMapTabbing = general.showSidebar ? `#legend` : state.general.title ? `#dataTableSection__${state.general.title.replace(/\s/g, '')}` : `#dataTableSection`
1237
1488
 
1238
-
1239
1489
  return (
1240
1490
  <div className={outerContainerClasses.join(' ')} ref={outerContainerRef}>
1241
1491
  {isEditor && (
@@ -1251,7 +1501,7 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
1251
1501
  columnsInData={Object.keys(state.data[0])}
1252
1502
  />
1253
1503
  )}
1254
- {!runtimeData.init && (general.type === 'navigation' || runtimeLegend.length !== 0) && <section className={`cdc-map-inner-container ${currentViewport}`} aria-label={'Map: ' + title}>
1504
+ {!runtimeData.init && (general.type === 'navigation' || runtimeLegend) && <section className={`cdc-map-inner-container ${currentViewport}`} aria-label={'Map: ' + title}>
1255
1505
  {['lg', 'md'].includes(currentViewport) && 'hover' === tooltips.appearanceType && (
1256
1506
  <ReactTooltip
1257
1507
  id='tooltip'
@@ -1261,12 +1511,18 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
1261
1511
  className={tooltips.capitalizeLabels ? 'capitalize tooltip' : 'tooltip'}
1262
1512
  />
1263
1513
  )}
1264
- <header className={general.showTitle === true ? '' : 'hidden'} aria-hidden='true'>
1265
- <div role='heading' className={'map-title ' + general.headerColor} tabIndex="0">
1514
+ <header className={general.showTitle === true ? 'visible' : 'hidden'} {...(!general.showTitle || !state.general.title ? { "aria-hidden": true } : { "aria-hidden": false } )}>
1515
+ <div role='heading' className={'map-title ' + general.headerColor} tabIndex="0" aria-level="2">
1266
1516
  {parse(title)}
1267
1517
  </div>
1268
1518
  </header>
1269
- <section className={mapContainerClasses.join(' ')} onClick={(e) => closeModal(e)}>
1519
+ <section
1520
+ role="button"
1521
+ tabIndex="0"
1522
+ className={mapContainerClasses.join(' ')}
1523
+ onClick={(e) => closeModal(e)}
1524
+ onKeyDown={(e) => { if (e.keyCode === 13) { closeModal(e) } }}
1525
+ >
1270
1526
  {general.showDownloadMediaButton === true && (
1271
1527
  <div className='map-downloads' data-html2canvas-ignore>
1272
1528
  <div className='map-downloads__ui btn-group'>
@@ -1308,7 +1564,10 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
1308
1564
  <SingleStateMap supportedTerritories={supportedTerritories} {...mapProps} />
1309
1565
  )}
1310
1566
  {'us' === general.geoType && (
1311
- <UsaMap supportedTerritories={supportedTerritories} {...mapProps} />
1567
+ <UsaMap supportedTerritories={supportedTerritories} {...mapProps} />
1568
+ )}
1569
+ {'us-region' === general.geoType && (
1570
+ <UsaRegionMap supportedTerritories={supportedTerritories} {...mapProps} />
1312
1571
  )}
1313
1572
  {'world' === general.geoType && (
1314
1573
  <WorldMap supportedCountries={supportedCountries} {...mapProps} />
@@ -1352,7 +1611,7 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
1352
1611
  navigationHandler={(val) => navigationHandler(val)}
1353
1612
  />
1354
1613
  )}
1355
- {true === dataTable.forceDisplay && general.type !== 'navigation' && false === loading && (
1614
+ {state.runtime.editorErrorMessage.length === 0 && true === dataTable.forceDisplay && general.type !== 'navigation' && false === loading && (
1356
1615
  <DataTable
1357
1616
  state={state}
1358
1617
  rawData={state.data}
@@ -1370,6 +1629,8 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
1370
1629
  indexTitle={dataTable.indexTitle}
1371
1630
  mapTitle={general.title}
1372
1631
  viewport={currentViewport}
1632
+ formatLegendLocation={formatLegendLocation}
1633
+ setFilteredCountryCode={setFilteredCountryCode}
1373
1634
  />
1374
1635
  )}
1375
1636
  {subtext.length > 0 && <p className='subtext'>{parse(subtext)}</p>}