@cdc/map 2.6.2 → 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 (67) hide show
  1. package/dist/cdcmap.js +37 -29
  2. package/examples/bubble-us.json +363 -0
  3. package/examples/bubble-world.json +427 -0
  4. package/examples/default-county.json +105 -0
  5. package/examples/default-hex.json +475 -0
  6. package/examples/default-single-state.json +109 -0
  7. package/examples/default-usa-regions.json +118 -0
  8. package/examples/default-usa.json +744 -603
  9. package/examples/default-world-data.json +1450 -0
  10. package/examples/default-world.json +5 -3
  11. package/examples/example-city-state.json +510 -0
  12. package/examples/example-world-map.json +1596 -0
  13. package/examples/gender-rate-map.json +1 -0
  14. package/examples/private/atsdr.json +439 -0
  15. package/examples/private/atsdr_new.json +436 -0
  16. package/examples/private/bubble.json +285 -0
  17. package/examples/private/default-world-data.json +1444 -0
  18. package/examples/private/default.json +968 -0
  19. package/examples/private/map.csv +60 -0
  20. package/examples/private/mdx.json +210 -0
  21. package/examples/private/regions.json +52 -0
  22. package/examples/private/wcmsrd-13881-data.json +2858 -0
  23. package/examples/private/wcmsrd-13881.json +5823 -0
  24. package/examples/private/wcmsrd-test.json +268 -0
  25. package/examples/private/world.json +1580 -0
  26. package/examples/private/worldmap.json +1490 -0
  27. package/package.json +11 -7
  28. package/src/CdcMap.js +726 -202
  29. package/src/components/BubbleList.js +240 -0
  30. package/src/components/CityList.js +22 -3
  31. package/src/components/CountyMap.js +557 -0
  32. package/src/components/DataTable.js +85 -24
  33. package/src/components/EditorPanel.js +2455 -1204
  34. package/src/components/Geo.js +1 -1
  35. package/src/components/Sidebar.js +5 -5
  36. package/src/components/SingleStateMap.js +326 -0
  37. package/src/components/UsaMap.js +41 -9
  38. package/src/components/UsaRegionMap.js +319 -0
  39. package/src/components/WorldMap.js +112 -35
  40. package/src/data/abbreviations.js +57 -0
  41. package/src/data/country-coordinates.js +250 -0
  42. package/src/data/county-map-halfquality.json +58453 -0
  43. package/src/data/county-map-quarterquality.json +1 -0
  44. package/src/data/county-topo.json +1 -0
  45. package/src/data/dfc-map.json +1 -0
  46. package/src/data/initial-state.js +21 -4
  47. package/src/data/newtest.json +1 -0
  48. package/src/data/state-abbreviations.js +60 -0
  49. package/src/data/state-coordinates.js +55 -0
  50. package/src/data/supported-geos.js +3592 -163
  51. package/src/data/test.json +1 -0
  52. package/src/data/us-regions-topo-2.json +360525 -0
  53. package/src/data/us-regions-topo.json +37894 -0
  54. package/src/hooks/useActiveElement.js +19 -0
  55. package/src/hooks/useColorPalette.ts +96 -0
  56. package/src/index.html +35 -20
  57. package/src/index.js +8 -4
  58. package/src/scss/datatable.scss +2 -1
  59. package/src/scss/editor-panel.scss +76 -55
  60. package/src/scss/main.scss +10 -1
  61. package/src/scss/map.scss +257 -121
  62. package/src/scss/sidebar.scss +0 -1
  63. package/uploads/upload-example-city-state.json +392 -0
  64. package/uploads/upload-example-world-data.json +1490 -0
  65. package/LICENSE +0 -201
  66. package/src/data/color-palettes.js +0 -191
  67. 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, supportedCities } 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,22 +44,32 @@ 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'
48
+
36
49
 
37
50
  // Child Components
38
51
  import Sidebar from './components/Sidebar';
39
52
  import Modal from './components/Modal';
40
53
  import EditorPanel from './components/EditorPanel'; // Future: Lazy
41
54
  import UsaMap from './components/UsaMap'; // Future: Lazy
55
+ import UsaRegionMap from './components/UsaRegionMap'; // Future: Lazy
56
+ import CountyMap from './components/CountyMap'; // Future: Lazy
42
57
  import DataTable from './components/DataTable'; // Future: Lazy
43
58
  import NavigationMenu from './components/NavigationMenu'; // Future: Lazy
44
59
  import WorldMap from './components/WorldMap'; // Future: Lazy
60
+ import SingleStateMap from './components/SingleStateMap'; // Future: Lazy
61
+
62
+ import { publish } from '@cdc/core/helpers/events';
45
63
 
46
64
  // Data props
47
65
  const stateKeys = Object.keys(supportedStates)
48
66
  const territoryKeys = Object.keys(supportedTerritories)
67
+ const regionKeys = Object.keys(supportedRegions)
49
68
  const countryKeys = Object.keys(supportedCountries)
69
+ const countyKeys = Object.keys(supportedCounties)
50
70
  const cityKeys = Object.keys(supportedCities)
51
71
 
72
+
52
73
  const generateColorsArray = (color = '#000000', special = false) => {
53
74
  let colorObj = chroma(color)
54
75
 
@@ -65,7 +86,7 @@ const hashObj = (row) => {
65
86
  let str = JSON.stringify(row)
66
87
 
67
88
  let hash = 0;
68
- if (str.length == 0) return hash;
89
+ if (str.length === 0) return hash;
69
90
  for (let i = 0; i < str.length; i++) {
70
91
  let char = str.charCodeAt(i);
71
92
  hash = ((hash<<5)-hash) + char;
@@ -93,23 +114,81 @@ const getUniqueValues = (data, columnName) => {
93
114
  }
94
115
 
95
116
  const CdcMap = ({className, config, navigationHandler: customNavigationHandler, isDashboard = false, isEditor = false, configUrl, logo = null, setConfig, hostname}) => {
117
+
118
+ const [showLoadingMessage, setShowLoadingMessage] = useState(false)
96
119
  const transform = new DataTransform()
97
-
98
120
  const [state, setState] = useState( {...initialState} )
99
121
  const [loading, setLoading] = useState(true)
100
- const [currentViewport, setCurrentViewport] = useState('lg')
122
+ const [currentViewport, setCurrentViewport] = useState()
101
123
  const [runtimeFilters, setRuntimeFilters] = useState([])
102
124
  const [runtimeLegend, setRuntimeLegend] = useState([])
103
125
  const [runtimeData, setRuntimeData] = useState({init: true})
104
126
  const [modal, setModal] = useState(null)
105
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()
106
132
 
133
+
107
134
  let legendMemo = useRef(new Map())
108
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
+
186
+
187
+
109
188
  const resizeObserver = new ResizeObserver(entries => {
110
189
  for (let entry of entries) {
111
190
  let newViewport = getViewport(entry.contentRect.width)
112
-
191
+
113
192
  setCurrentViewport(newViewport)
114
193
  }
115
194
  });
@@ -117,14 +196,23 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
117
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
118
197
  // We are mutating state in place here (depending on where called) - but it's okay, this isn't used for rerender
119
198
  const addUIDs = useCallback((obj, fromColumn) => {
199
+
120
200
  obj.data.forEach(row => {
121
201
  let uid = null
122
202
 
123
203
  if(row.uid) row.uid = null // Wipe existing UIDs
124
204
 
125
205
  // United States check
126
- if("us" === obj.general.geoType) {
127
- const geoName = row[obj.columns.geo.name]
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
+ }
128
216
 
129
217
  // States
130
218
  uid = stateKeys.find( (key) => supportedStates[key].includes(geoName) )
@@ -140,6 +228,23 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
140
228
  }
141
229
  }
142
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
+
143
248
  // World Check
144
249
  if("world" === obj.general.geoType) {
145
250
  const geoName = row[obj.columns.geo.name]
@@ -147,8 +252,13 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
147
252
  uid = countryKeys.find( (key) => supportedCountries[key].includes(geoName) )
148
253
  }
149
254
 
150
- // TODO: Points
255
+ // County Check
256
+ if("us-county" === obj.general.geoType || "single-state" === obj.general.geoType) {
257
+ const fips = row[obj.columns.geo.name]
258
+ uid = countyKeys.find( (key) => key === fips )
259
+ }
151
260
 
261
+ // TODO: Points
152
262
  if(uid) {
153
263
  Object.defineProperty(row, 'uid', {
154
264
  value: uid,
@@ -161,10 +271,14 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
161
271
  })
162
272
 
163
273
  const generateRuntimeLegend = useCallback((obj, runtimeData, hash) => {
274
+
164
275
  const newLegendMemo = new Map(); // Reset memoization
165
276
 
166
277
  const
167
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,
168
282
  type = obj.legend.type,
169
283
  number = obj.legend.numberOfItems,
170
284
  result = [];
@@ -186,12 +300,22 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
186
300
  6: [ 0, 2, 3, 4, 5, 7 ],
187
301
  7: [ 0, 2, 3, 4, 5, 6, 7 ],
188
302
  8: [ 0, 2, 3, 4, 5, 6, 7, 8 ],
189
- 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 ]
190
305
  }
191
306
 
192
307
  const applyColorToLegend = (legendIdx) => {
193
308
  // Default to "bluegreen" color scheme if the passed color isn't valid
194
- let mapColorPalette = colorPalettes[obj.color] || colorPalettes['bluegreen']
309
+ let mapColorPalette = obj.customColors || colorPalettes[obj.color] || colorPalettes['bluegreen']
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
+ }
195
319
 
196
320
  let colorIdx = legendIdx - specialClasses
197
321
 
@@ -217,30 +341,73 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
217
341
 
218
342
  // Special classes
219
343
  if (obj.legend.specialClasses.length) {
220
- dataSet = dataSet.filter(row => {
221
- const val = row[primaryCol]
344
+ if(typeof obj.legend.specialClasses[0] === 'object'){
345
+ obj.legend.specialClasses.forEach(specialClass => {
346
+ dataSet = dataSet.filter(row => {
347
+ const val = String(row[specialClass.key]);
222
348
 
223
- if( obj.legend.specialClasses.includes(val) ) {
224
- if(undefined === specialClassesHash[val]) {
225
- specialClassesHash[val] = true
349
+ if(specialClass.value === val){
350
+ if(undefined === specialClassesHash[val]) {
351
+ specialClassesHash[val] = true;
226
352
 
227
- result.push({
228
- special: true,
229
- value: val
230
- })
353
+ result.push({
354
+ special: true,
355
+ value: val,
356
+ label: specialClass.label
357
+ });
231
358
 
232
- result[result.length - 1].color = applyColorToLegend(result.length - 1)
359
+ result[result.length - 1].color = applyColorToLegend(result.length - 1);
233
360
 
234
- specialClasses += 1
235
- }
361
+ specialClasses += 1;
362
+ }
236
363
 
237
- newLegendMemo.set( hashObj(row), result.length - 1)
364
+ let specialColor = '';
365
+
366
+ // color the state if val is in row
367
+ specialColor = result.findIndex(p => p.value === val)
238
368
 
239
- return false
240
- }
369
+ newLegendMemo.set( hashObj(row), specialColor)
241
370
 
242
- return true
243
- })
371
+ return false;
372
+ }
373
+
374
+ return true;
375
+ });
376
+ });
377
+ } else {
378
+ dataSet = dataSet.filter(row => {
379
+ const val = row[primaryCol]
380
+
381
+ if( obj.legend.specialClasses.includes(val) ) {
382
+
383
+ // apply the special color to the legend
384
+ if(undefined === specialClassesHash[val]) {
385
+ specialClassesHash[val] = true;
386
+
387
+ result.push({
388
+ special: true,
389
+ value: val
390
+ });
391
+
392
+ result[result.length - 1].color = applyColorToLegend(result.length - 1);
393
+
394
+ specialClasses += 1;
395
+ }
396
+
397
+ let specialColor = '';
398
+
399
+ // color the state if val is in row
400
+ if ( Object.values(row).includes(val) ) {
401
+ specialColor = result.findIndex(p => p.value === val)
402
+ }
403
+ newLegendMemo.set( hashObj(row), specialColor)
404
+
405
+ return false
406
+ }
407
+
408
+ return true
409
+ })
410
+ }
244
411
  }
245
412
 
246
413
  // Category
@@ -250,8 +417,7 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
250
417
 
251
418
  for(let i = 0; i < dataSet.length; i++) {
252
419
  let row = dataSet[i]
253
- let value = row[primaryCol]
254
-
420
+ let value = isBubble && categoricalCol && row[categoricalCol] ? row[categoricalCol] : row[primaryCol]
255
421
  if(undefined === value) continue
256
422
 
257
423
  if(false === uniqueValues.has(value)) {
@@ -261,7 +427,7 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
261
427
  uniqueValues.get(value).push(hashObj(row))
262
428
  }
263
429
 
264
- 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
265
431
  }
266
432
 
267
433
  let sorted = [...uniqueValues.keys()]
@@ -305,7 +471,12 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
305
471
  return result
306
472
  }
307
473
 
308
- let legendNumber = number
474
+ let uniqueValues = {};
475
+ dataSet.forEach(datum => {
476
+ uniqueValues[datum[primaryCol]] = true;
477
+ });
478
+
479
+ let legendNumber = Math.min(number, Object.keys(uniqueValues).length);
309
480
 
310
481
  // Separate zero
311
482
  if(true === obj.legend.separateZero) {
@@ -347,48 +518,146 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
347
518
  })
348
519
 
349
520
  // Equal Number
350
- if(type === 'equalnumber') {
351
- 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
352
529
 
353
- let remainder
354
- let changingNumber = legendNumber
530
+ let chunkAmt
355
531
 
356
- let chunkAmt
532
+ // Loop through the array until it has been split into equal subarrays
533
+ while (numberOfRows > 0) {
534
+ remainder = numberOfRows % changingNumber
535
+
536
+ chunkAmt = Math.floor(numberOfRows / changingNumber)
537
+
538
+ if (remainder > 0) {
539
+ chunkAmt += 1
540
+ }
357
541
 
358
- // Loop through the array until it has been split into equal subarrays
359
- while ( numberOfRows > 0 ) {
360
- remainder = numberOfRows % changingNumber
542
+ let removedRows = dataSet.splice(0, chunkAmt);
361
543
 
362
- chunkAmt = Math.floor(numberOfRows / changingNumber)
544
+ let min = removedRows[0][primaryCol],
545
+ max = removedRows[removedRows.length - 1][primaryCol]
363
546
 
364
- if (remainder > 0) {
365
- chunkAmt += 1
547
+ removedRows.forEach(row => {
548
+ newLegendMemo.set(hashObj(row), result.length)
549
+ })
550
+
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
366
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)
581
+
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) => {
591
+
592
+ let min = breaks[index];
593
+ let max = breaks[index + 1] - 1;
594
+
595
+ const setMin = () => {
596
+ // in starting position and zero in the data
597
+ if(index === 0 && state.legend.separateZero) {
598
+ min = 0;
599
+ }
367
600
 
368
- let removedRows = dataSet.splice(0, chunkAmt);
601
+ if(index === 0 && !state.legend.separateZero) {
602
+ min = domainNums[0]
603
+ }
369
604
 
370
- let min = removedRows[0][primaryCol],
371
- max = removedRows[removedRows.length - 1][primaryCol]
605
+ }
372
606
 
373
- removedRows.forEach(row => {
374
- newLegendMemo.set( hashObj(row), result.length )
375
- })
607
+ const setMax = () => {
608
+ if(index === 0 && state.legend.separateZero) {
609
+ max = 0;
610
+ }
376
611
 
377
- result.push({
378
- min,
379
- max
380
- })
612
+ if(index + 1 === breaks.length) {
613
+ max = domainNums[1]
614
+ }
615
+ }
381
616
 
382
- result[result.length - 1].color = applyColorToLegend(result.length - 1)
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
+ })
383
643
 
384
- changingNumber -= 1
385
- numberOfRows -= chunkAmt
386
644
  }
387
645
  }
388
646
 
389
647
  // Equal Interval
390
- if(type === 'equalinterval') {
391
- dataSet = dataSet.filter(row => row[primaryCol])
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
+ }
660
+ dataSet = dataSet.filter(row => row[primaryCol] !== undefined)
392
661
  let dataMin = dataSet[0][primaryCol]
393
662
  let dataMax = dataSet[dataSet.length - 1][primaryCol]
394
663
 
@@ -435,11 +704,34 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
435
704
 
436
705
  if(hash) filters.fromHash = hash
437
706
 
438
- obj.filters.forEach(({columnName, label}, idx) => {
707
+ obj?.filters.forEach(({columnName, label, active, values}, idx) => {
439
708
  if(undefined === columnName) return
440
709
 
441
710
  let newFilter = runtimeFilters[idx]
442
- let values = getUniqueValues(state.data, columnName)
711
+
712
+ const sortAsc = (a, b) => {
713
+ return a.toString().localeCompare(b.toString(), 'en', { numeric: true })
714
+ };
715
+
716
+ const sortDesc = (a, b) => {
717
+ return b.toString().localeCompare(a.toString(), 'en', { numeric: true })
718
+ };
719
+
720
+ values = getUniqueValues(state.data, columnName)
721
+
722
+ if(obj.filters[idx].order === 'asc') {
723
+ values = values.sort(sortAsc)
724
+ }
725
+
726
+ if(obj.filters[idx].order === 'desc') {
727
+ values = values.sort(sortDesc)
728
+ }
729
+
730
+ if(obj.filters[idx].order === 'cust') {
731
+ if(obj.filters[idx]?.values.length > 0) {
732
+ values = obj.filters[idx].values
733
+ }
734
+ }
443
735
 
444
736
  if(undefined === newFilter) {
445
737
  newFilter = {}
@@ -448,7 +740,7 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
448
740
  newFilter.label = label ?? ''
449
741
  newFilter.columnName = columnName
450
742
  newFilter.values = values
451
- newFilter.active = values[0] // Default to first found value
743
+ newFilter.active = active || values[0] // Default to first found value
452
744
 
453
745
  filters.push(newFilter)
454
746
  })
@@ -457,7 +749,7 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
457
749
  })
458
750
 
459
751
  // Calculates what's going to be displayed on the map and data table at render.
460
- const generateRuntimeData = useCallback((obj, filters, hash) => {
752
+ const generateRuntimeData = useCallback((obj, filters, hash, test) => {
461
753
  const result = {}
462
754
 
463
755
  if(hash) {
@@ -466,18 +758,34 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
466
758
  value : hash
467
759
  });
468
760
  }
761
+
469
762
 
470
763
  obj.data.forEach(row => {
764
+
765
+ if(test) {
766
+ console.log('object', obj)
767
+ console.log('row', row)
768
+ }
471
769
  if(undefined === row.uid) return false // No UID for this row, we can't use for mapping
472
770
 
771
+ // When on a single state map filter runtime data by state
772
+ if (
773
+ !(String(row[obj.columns.geo.name]).substring(0, 2) === obj.general?.statePicked?.fipsCode) &&
774
+ obj.general.geoType === 'single-state'
775
+ ) {
776
+ return false;
777
+ }
778
+
779
+
473
780
  if(row[obj.columns.primary.name]) {
474
- row[obj.columns.primary.name] = numberFromString(row[obj.columns.primary.name])
781
+ row[obj.columns.primary.name] = numberFromString(row[obj.columns.primary.name], state)
475
782
  }
476
783
 
477
784
  // If this is a navigation only map, skip if it doesn't have a URL
478
785
  if("navigation" === obj.general.type ) {
479
786
  let navigateUrl = row[obj.columns.navigate.name] || "";
480
- if ( undefined !== navigateUrl ) {
787
+
788
+ if ( undefined !== navigateUrl && typeof navigateUrl === "string" ) {
481
789
  // Strip hidden characters before we check length
482
790
  navigateUrl = navigateUrl.replace( /(\r\n|\n|\r)/gm, '' );
483
791
  }
@@ -487,11 +795,11 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
487
795
  }
488
796
 
489
797
  // Filters
490
- if(filters.length) {
798
+ if(filters?.length) {
491
799
  for(let i = 0; i < filters.length; i++) {
492
800
  const {columnName, active} = filters[i]
493
801
 
494
- if (row[columnName] != active) return false // Bail out, not part of filter
802
+ if (String(row[columnName]) !== String(active)) return false // Bail out, not part of filter
495
803
  }
496
804
  }
497
805
 
@@ -508,6 +816,7 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
508
816
  if (node !== null) {
509
817
  resizeObserver.observe(node);
510
818
  }
819
+ setContainer(node)
511
820
  },[]);
512
821
 
513
822
  const mapSvg = useRef(null);
@@ -611,16 +920,29 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
611
920
  // Reset active legend toggles
612
921
  resetLegendToggles()
613
922
 
614
- let filters = [...runtimeFilters]
923
+ try {
924
+
925
+ const isEmpty = (obj) => {
926
+ return Object.keys(obj).length === 0;
927
+ }
928
+
929
+ let filters = [...runtimeFilters]
615
930
 
616
- filters[idx] = {...filters[idx]}
931
+ filters[idx] = { ...filters[idx] }
617
932
 
618
- filters[idx].active = activeValue
933
+ filters[idx].active = activeValue
934
+ const newData = generateRuntimeData(state, filters)
935
+
936
+ // throw an error if newData is empty
937
+ if (isEmpty(newData)) throw new Error('Cove Filter Error: No runtime data to set for this filter')
619
938
 
620
- const newData = generateRuntimeData(state, filters)
939
+ // set the runtime filters and data
940
+ setRuntimeData(newData)
941
+ setRuntimeFilters(filters)
942
+ } catch(e) {
943
+ console.error(e.message)
944
+ }
621
945
 
622
- setRuntimeData(newData)
623
- setRuntimeFilters(filters)
624
946
  }
625
947
 
626
948
  const displayDataAsText = (value, columnName) => {
@@ -652,8 +974,8 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
652
974
  }
653
975
 
654
976
  // Check if it's a special value. If it is not, apply the designated prefix and suffix
655
- if (false === state.legend.specialClasses.includes(value)) {
656
- formattedValue = columnObj.prefix + formattedValue + columnObj.suffix
977
+ if (false === state.legend.specialClasses.includes(String(value))) {
978
+ formattedValue = (columnObj.prefix || '') + formattedValue + (columnObj.suffix || '')
657
979
  }
658
980
  }
659
981
 
@@ -683,22 +1005,49 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
683
1005
  }
684
1006
 
685
1007
  const applyTooltipsToGeo = (geoName, row, returnType = 'string') => {
686
- let toolTipText = `<strong>${displayGeoName(geoName)}</strong>`
1008
+ let toolTipText = '';
1009
+
1010
+ // Adds geo label, ie State: Georgia
1011
+ let stateOrCounty =
1012
+ state.general.geoType === 'us' ? 'State: ' :
1013
+ (state.general.geoType === 'us-county' || state.general.geoType === 'single-state') ? 'County: ':
1014
+ '';
1015
+
1016
+ if (state.general.geoType === 'us-county') {
1017
+ let stateFipsCode = row[state.columns.geo.name].substring(0,2)
1018
+ const stateName = supportedStatesFipsCodes[stateFipsCode];
1019
+
1020
+ toolTipText += !state.general.hideGeoColumnInTooltip ? `<strong>State: ${stateName}</strong><br/>` : `<strong>${stateName}</strong><br/>` ;
1021
+ }
1022
+
1023
+ toolTipText += !state.general.hideGeoColumnInTooltip ? `<strong>${stateOrCounty}${displayGeoName(geoName)}</strong>` : `<strong>${displayGeoName(geoName)}</strong>`
687
1024
 
688
- if('data' === state.general.type) {
1025
+ if( ('data' === state.general.type || state.general.type === 'bubble') && undefined !== row) {
689
1026
  toolTipText += `<dl>`
690
1027
 
691
1028
  Object.keys(state.columns).forEach((columnKey) => {
692
1029
  const column = state.columns[columnKey]
693
1030
 
694
1031
  if (true === column.tooltip) {
695
-
696
1032
  let label = column.label.length > 0 ? column.label : '';
697
1033
 
698
- let value = displayDataAsText(row[column.name], columnKey);
1034
+ let value;
1035
+
1036
+ if(state.legend.specialClasses && state.legend.specialClasses.length && typeof state.legend.specialClasses[0] === 'object'){
1037
+ for(let i = 0; i < state.legend.specialClasses.length; i++){
1038
+ if(String(row[state.legend.specialClasses[i].key]) === state.legend.specialClasses[i].value){
1039
+ value = displayDataAsText(state.legend.specialClasses[i].label, columnKey);
1040
+ break;
1041
+ }
1042
+ }
1043
+ }
1044
+
1045
+ if(!value){
1046
+ value = displayDataAsText(row[column.name], columnKey);
1047
+ }
699
1048
 
700
1049
  if(0 < value.length) { // Only spit out the tooltip if there's a value there
701
- 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>`
702
1051
  }
703
1052
 
704
1053
  }
@@ -720,6 +1069,10 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
720
1069
 
721
1070
  }
722
1071
 
1072
+ const titleCase = (string) => {
1073
+ return string.split(' ').map(word => word.charAt(0).toUpperCase() + word.substring(1).toLowerCase()).join(' ');
1074
+ }
1075
+
723
1076
  // This resets all active legend toggles.
724
1077
  const resetLegendToggles = async () => {
725
1078
  let newLegend = [...runtimeLegend]
@@ -746,7 +1099,8 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
746
1099
  .then(responseText => {
747
1100
  const parsedCsv = Papa.parse(responseText, {
748
1101
  header: true,
749
- dynamicTyping: true
1102
+ dynamicTyping: true,
1103
+ skipEmptyLines: true
750
1104
  })
751
1105
  return parsedCsv.data
752
1106
  })
@@ -769,32 +1123,52 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
769
1123
  }
770
1124
  }
771
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
+
772
1142
  // Attempts to find the corresponding value
773
1143
  const displayGeoName = (key) => {
774
1144
  let value = key
775
1145
 
776
1146
  // Map to first item in values array which is the preferred label
777
1147
  if(stateKeys.includes(value)) {
778
- value = supportedStates[key][0]
1148
+ value = titleCase(supportedStates[key][0])
779
1149
  }
780
1150
 
781
1151
  if(territoryKeys.includes(value)) {
782
- value = supportedTerritories[key][0]
1152
+ value = titleCase(supportedTerritories[key][0])
783
1153
  }
784
1154
 
785
1155
  if(countryKeys.includes(value)) {
786
- value = supportedCountries[key][0]
1156
+ value = titleCase(supportedCountries[key][0])
1157
+ }
1158
+
1159
+ if(countyKeys.includes(value)) {
1160
+ value = titleCase(supportedCounties[key])
787
1161
  }
788
1162
 
789
1163
  const dict = {
790
- "District of Columbia" : "Washington D.C."
1164
+ "District of Columbia" : "Washington D.C.",
1165
+ "Congo": "Republic of the Congo"
791
1166
  }
792
1167
 
793
1168
  if(true === Object.keys(dict).includes(value)) {
794
1169
  value = dict[value]
795
1170
  }
796
-
797
- return value
1171
+ return titleCase(value);
798
1172
  }
799
1173
 
800
1174
  const navigationHandler = (urlString) => {
@@ -832,7 +1206,23 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
832
1206
  }
833
1207
  }
834
1208
 
835
- const loadConfig = async (configObj) => {
1209
+ const validateFipsCodeLength = (newState) => {
1210
+ if(newState.general.geoType === 'us-county' || newState.general.geoType === 'single-state' || newState.general.geoType === 'us' && newState?.data) {
1211
+
1212
+ newState?.data.forEach(dataPiece => {
1213
+ if(dataPiece[newState.columns.geo.name]) {
1214
+
1215
+ if(!isNaN(parseInt(dataPiece[newState.columns.geo.name])) && dataPiece[newState.columns.geo.name].length === 4) {
1216
+ dataPiece[newState.columns.geo.name] = 0 + dataPiece[newState.columns.geo.name]
1217
+ }
1218
+ dataPiece[newState.columns.geo.name] = dataPiece[newState.columns.geo.name].toString()
1219
+ }
1220
+ })
1221
+ }
1222
+ return newState;
1223
+ }
1224
+
1225
+ const loadConfig = async (configObj) => {
836
1226
  // Set loading flag
837
1227
  if(!loading) setLoading(true)
838
1228
 
@@ -842,17 +1232,21 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
842
1232
  ...configObj
843
1233
  }
844
1234
 
1235
+ const round = 1000 * 60 * 15;
1236
+ const date = new Date();
1237
+ let cacheBustingString = new Date(date.getTime() - (date.getTime() % round)).toISOString();
1238
+
845
1239
  // If a dataUrl property exists, always pull from that.
846
1240
  if (newState.dataUrl) {
847
1241
  if(newState.dataUrl[0] === '/') {
848
- newState.dataUrl = 'https://' + hostname + newState.dataUrl
1242
+ newState.dataUrl = 'https://' + hostname + newState.dataUrl + '?v=' + cacheBustingString
849
1243
  }
850
1244
 
851
- let newData = await fetchRemoteData(newState.dataUrl)
1245
+ let newData = await fetchRemoteData(newState.dataUrl + '?v=' + cacheBustingString )
852
1246
 
853
1247
  if(newData && newState.dataDescription) {
854
- newData = transform.autoStandardize(data);
855
- newData = transform.developerStandardize(data, newState.dataDescription);
1248
+ newData = transform.autoStandardize(newData);
1249
+ newData = transform.developerStandardize(newData, newState.dataDescription);
856
1250
  }
857
1251
 
858
1252
  if(newData) {
@@ -876,17 +1270,16 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
876
1270
  })
877
1271
 
878
1272
  // If there's a name for the geo, add UIDs
879
- if(newState.columns.geo.name) {
880
- addUIDs(newState, newState.columns.geo.name)
1273
+ if(newState.columns.geo.name || newState.columns.geo.fips) {
1274
+ addUIDs(newState, newState.columns.geo.name || newState.columns.geo.fips)
881
1275
  }
882
1276
 
883
1277
  if(newState.dataTable.forceDisplay === undefined){
884
1278
  newState.dataTable.forceDisplay = !isDashboard;
885
1279
  }
886
1280
 
1281
+ validateFipsCodeLength(newState);
887
1282
  setState(newState)
888
-
889
- // Done loading
890
1283
  setLoading(false)
891
1284
  }
892
1285
 
@@ -914,6 +1307,42 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
914
1307
  init()
915
1308
  }, [])
916
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
+
1329
+ useEffect(() => {
1330
+ if (state.data) {
1331
+ let newData = generateRuntimeData(state);
1332
+ setRuntimeData(newData);
1333
+ }
1334
+ }, [state.general.statePicked]);
1335
+
1336
+
1337
+ // When geotype changes
1338
+ useEffect(() => {
1339
+ // UID
1340
+ if(state.data && state.columns.geo.name) {
1341
+ addUIDs(state, state.columns.geo.name)
1342
+ }
1343
+
1344
+ }, [state]);
1345
+
917
1346
  useEffect(() => {
918
1347
  // UID
919
1348
  if(state.data && state.columns.geo.name && state.columns.geo.name !== state.data.fromColumn) {
@@ -922,9 +1351,10 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
922
1351
 
923
1352
  // Filters
924
1353
  const hashFilters = hashObj(state.filters)
1354
+ let filters;
925
1355
 
926
1356
  if(state.filters && hashFilters !== runtimeFilters.fromHash) {
927
- const filters = generateRuntimeFilters(state, hashFilters, runtimeFilters)
1357
+ filters = generateRuntimeFilters(state, hashFilters, runtimeFilters)
928
1358
 
929
1359
  if(filters) {
930
1360
  setRuntimeFilters(filters)
@@ -933,44 +1363,62 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
933
1363
 
934
1364
  const hashLegend = hashObj({
935
1365
  color: state.color,
1366
+ customColors: state.customColors,
936
1367
  numberOfItems: state.legend.numberOfItems,
937
1368
  type: state.legend.type,
938
1369
  separateZero: state.legend.separateZero ?? false,
939
1370
  categoryValuesOrder: state.legend.categoryValuesOrder,
940
1371
  specialClasses: state.legend.specialClasses,
941
- geoType: state.general.geoType
1372
+ geoType: state.general.geoType,
1373
+ data: state.data,
1374
+ ...runtimeLegend
942
1375
  })
943
1376
 
944
- // Legend
945
- if(hashLegend !== runtimeLegend.fromHash && undefined === runtimeData.init) {
946
- const legend = generateRuntimeLegend(state, runtimeData, hashLegend)
947
-
948
- setRuntimeLegend(legend)
949
- }
950
-
951
1377
  const hashData = hashObj({
1378
+ columns: state.columns,
952
1379
  geoType: state.general.geoType,
953
1380
  type: state.general.type,
954
1381
  geo: state.columns.geo.name,
955
1382
  primary: state.columns.primary.name,
956
- ...runtimeFilters
1383
+ data: state.data,
1384
+ ...runtimeFilters,
1385
+ mapPosition: state.mapPosition
957
1386
  })
958
1387
 
959
1388
  // Data
1389
+ let newRuntimeData;
960
1390
  if(hashData !== runtimeData.fromHash && state.data?.fromColumn) {
961
- const data = generateRuntimeData(state, runtimeFilters, hashData)
962
-
1391
+ const data = generateRuntimeData(state, filters || runtimeFilters, hashData)
963
1392
  setRuntimeData(data)
964
1393
  }
1394
+
1395
+ // Legend
1396
+ if (hashLegend !== runtimeLegend.fromHash && (undefined === runtimeData.init || newRuntimeData)) {
1397
+ const legend = generateRuntimeLegend(state, newRuntimeData || runtimeData, hashLegend)
1398
+ setRuntimeLegend(legend)
1399
+ }
1400
+
965
1401
  }, [state])
966
1402
 
967
1403
  useEffect(() => {
1404
+ const hashLegend = hashObj({
1405
+ color: state.color,
1406
+ customColors: state.customColors,
1407
+ numberOfItems: state.legend.numberOfItems,
1408
+ type: state.legend.type,
1409
+ separateZero: state.legend.separateZero ?? false,
1410
+ categoryValuesOrder: state.legend.categoryValuesOrder,
1411
+ specialClasses: state.legend.specialClasses,
1412
+ geoType: state.general.geoType,
1413
+ data: state.data
1414
+ })
1415
+
968
1416
  // Legend - Update when runtimeData does
969
- if(undefined === runtimeData.init) {
1417
+ if(hashLegend !== runtimeLegend.fromHash && undefined === runtimeData.init) {
970
1418
  const legend = generateRuntimeLegend(state, runtimeData)
971
-
972
1419
  setRuntimeLegend(legend)
973
1420
  }
1421
+
974
1422
  }, [runtimeData])
975
1423
 
976
1424
  if(config) {
@@ -1010,8 +1458,6 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
1010
1458
  mapContainerClasses.push('full-border')
1011
1459
  }
1012
1460
 
1013
- if(loading) return <Loading />
1014
-
1015
1461
  // Props passed to all map types
1016
1462
  const mapProps = {
1017
1463
  state,
@@ -1022,100 +1468,178 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
1022
1468
  navigationHandler,
1023
1469
  geoClickHandler,
1024
1470
  applyLegendToRow,
1025
- displayGeoName
1471
+ displayGeoName,
1472
+ runtimeLegend,
1473
+ generateColorsArray,
1474
+ titleCase,
1475
+ setState,
1476
+ setRuntimeData,
1477
+ generateRuntimeData,
1478
+ setFilteredCountryCode,
1479
+ filteredCountryCode,
1480
+ position,
1481
+ setPosition,
1482
+ hasZoom : state.general.allowMapZoom
1026
1483
  }
1027
1484
 
1485
+ if (!mapProps.data || !state.data) return <Loading />;
1486
+
1487
+ const handleMapTabbing = general.showSidebar ? `#legend` : state.general.title ? `#dataTableSection__${state.general.title.replace(/\s/g, '')}` : `#dataTableSection`
1488
+
1028
1489
  return (
1029
- <div className={outerContainerClasses.join(' ')} ref={outerContainerRef}>
1030
- {isEditor && <EditorPanel isDashboard={isDashboard} state={state} setState={setState} loadConfig={loadConfig} setParentConfig={setConfig} runtimeFilters={runtimeFilters} runtimeLegend={runtimeLegend} columnsInData={Object.keys(state.data[0])} />}
1031
- <section className={`cdc-map-inner-container ${currentViewport}`} aria-label={'Map: ' + title}>
1032
- {['lg', 'md'].includes(currentViewport) && 'hover' === tooltips.appearanceType &&
1033
- <ReactTooltip
1034
- id="tooltip"
1035
- place="right"
1036
- type="light"
1037
- html={true}
1038
- className={tooltips.capitalizeLabels ? 'capitalize tooltip' : 'tooltip'}
1039
- />
1040
- }
1041
- <header className={general.showTitle === true ? '' : 'hidden'} aria-hidden="true">
1042
- <div role="heading" className={'map-title ' + general.headerColor}>
1043
- { parse(title) }
1044
- </div>
1045
- </header>
1046
- <section className={mapContainerClasses.join(' ')} onClick={(e) => closeModal(e)}>
1047
- {general.showDownloadMediaButton === true &&
1048
- <div className="map-downloads" data-html2canvas-ignore>
1049
- <div className="map-downloads__ui btn-group">
1050
- <button className="btn" title="Download Map as Image"
1051
- onClick={() => generateMedia(outerContainerRef.current, 'image')}>
1052
- <DownloadImg className="btn__icon" title='Download Map as Image'/>
1053
- </button>
1054
- <button className="btn" title="Download Map as PDF"
1055
- onClick={() => generateMedia(outerContainerRef.current, 'pdf')}>
1056
- <DownloadPdf className="btn__icon" title='Download Map as PDF'/>
1057
- </button>
1058
- </div>
1059
- </div>
1060
- }
1061
- <section className="geography-container" aria-hidden="true" ref={mapSvg}>
1062
- {modal && <Modal type={general.type} viewport={currentViewport} applyTooltipsToGeo={applyTooltipsToGeo} applyLegendToRow={applyLegendToRow} capitalize={state.tooltips.capitalizeLabels} content={modal} />}
1063
- {'us' === general.geoType && <UsaMap supportedTerritories={supportedTerritories} {...mapProps} />}
1064
- {'world' === general.geoType && <WorldMap supportedCountries={supportedCountries} {...mapProps} />}
1065
- {"data" === general.type && logo && <img src={logo} alt="" className="map-logo"/>}
1066
- </section>
1067
- {general.showSidebar && 'navigation' !== general.type && false === loading &&
1068
- <Sidebar
1069
- viewport={currentViewport}
1070
- legend={state.legend}
1071
- runtimeLegend={runtimeLegend}
1072
- setRuntimeLegend={setRuntimeLegend}
1073
- runtimeFilters={runtimeFilters}
1074
- columns={state.columns}
1075
- sharing={state.sharing}
1076
- prefix={state.columns.primary.prefix}
1077
- suffix={state.columns.primary.suffix}
1078
- setState={setState}
1079
- resetLegendToggles={resetLegendToggles}
1080
- changeFilterActive={changeFilterActive}
1081
- setAccessibleStatus={setAccessibleStatus}
1082
- />
1083
- }
1084
- </section>
1085
- {"navigation" === general.type &&
1086
- <NavigationMenu
1087
- displayGeoName={displayGeoName}
1088
- data={runtimeData}
1089
- options={general}
1090
- columns={state.columns}
1091
- navigationHandler={(val) => navigationHandler(val)}
1092
- />
1093
- }
1094
- {true === dataTable.forceDisplay && general.type !== "navigation" && false === loading &&
1095
- <DataTable
1096
- state={state}
1097
- rawData={state.data}
1098
- navigationHandler={navigationHandler}
1099
- expandDataTable={general.expandDataTable}
1100
- headerColor={general.headerColor}
1101
- columns={state.columns}
1102
- showDownloadButton={general.showDownloadButton}
1103
- runtimeLegend={runtimeLegend}
1104
- runtimeData={runtimeData}
1105
- displayDataAsText={displayDataAsText}
1106
- displayGeoName={displayGeoName}
1107
- applyLegendToRow={applyLegendToRow}
1108
- tableTitle={dataTable.title}
1109
- indexTitle={dataTable.indexTitle}
1110
- mapTitle={general.title}
1111
- viewport={currentViewport}
1112
- />
1113
- }
1114
- {subtext.length > 0 && <p className="subtext">{ parse(subtext) }</p>}
1115
- </section>
1116
- <div aria-live="assertive" className="cdcdataviz-sr-only">{ accessibleStatus }</div>
1117
- </div>
1118
- )
1490
+ <div className={outerContainerClasses.join(' ')} ref={outerContainerRef}>
1491
+ {isEditor && (
1492
+ <EditorPanel
1493
+ isDashboard={isDashboard}
1494
+ state={state}
1495
+ setState={setState}
1496
+ loadConfig={loadConfig}
1497
+ setParentConfig={setConfig}
1498
+ setRuntimeFilters={setRuntimeFilters}
1499
+ runtimeFilters={runtimeFilters}
1500
+ runtimeLegend={runtimeLegend}
1501
+ columnsInData={Object.keys(state.data[0])}
1502
+ />
1503
+ )}
1504
+ {!runtimeData.init && (general.type === 'navigation' || runtimeLegend) && <section className={`cdc-map-inner-container ${currentViewport}`} aria-label={'Map: ' + title}>
1505
+ {['lg', 'md'].includes(currentViewport) && 'hover' === tooltips.appearanceType && (
1506
+ <ReactTooltip
1507
+ id='tooltip'
1508
+ place='right'
1509
+ type='light'
1510
+ html={true}
1511
+ className={tooltips.capitalizeLabels ? 'capitalize tooltip' : 'tooltip'}
1512
+ />
1513
+ )}
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">
1516
+ {parse(title)}
1517
+ </div>
1518
+ </header>
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
+ >
1526
+ {general.showDownloadMediaButton === true && (
1527
+ <div className='map-downloads' data-html2canvas-ignore>
1528
+ <div className='map-downloads__ui btn-group'>
1529
+ <button
1530
+ className='btn'
1531
+ title='Download Map as Image'
1532
+ onClick={() => generateMedia(outerContainerRef.current, 'image')}
1533
+ >
1534
+ <DownloadImg className='btn__icon' title='Download Map as Image' />
1535
+ </button>
1536
+ <button
1537
+ className='btn'
1538
+ title='Download Map as PDF'
1539
+ onClick={() => generateMedia(outerContainerRef.current, 'pdf')}
1540
+ >
1541
+ <DownloadPdf className='btn__icon' title='Download Map as PDF' />
1542
+ </button>
1543
+ </div>
1544
+ </div>
1545
+ )}
1546
+
1547
+ <a id='skip-geo-container' className='cdcdataviz-sr-only-focusable' href={handleMapTabbing}>
1548
+ Skip Over Map Container
1549
+ </a>
1550
+ <section className='geography-container' aria-hidden='true' ref={mapSvg}>
1551
+ {currentViewport && (
1552
+ <section className='geography-container' aria-hidden='true' ref={mapSvg}>
1553
+ {modal && (
1554
+ <Modal
1555
+ type={general.type}
1556
+ viewport={currentViewport}
1557
+ applyTooltipsToGeo={applyTooltipsToGeo}
1558
+ applyLegendToRow={applyLegendToRow}
1559
+ capitalize={state.tooltips.capitalizeLabels}
1560
+ content={modal}
1561
+ />
1562
+ )}
1563
+ {'single-state' === general.geoType && (
1564
+ <SingleStateMap supportedTerritories={supportedTerritories} {...mapProps} />
1565
+ )}
1566
+ {'us' === general.geoType && (
1567
+ <UsaMap supportedTerritories={supportedTerritories} {...mapProps} />
1568
+ )}
1569
+ {'us-region' === general.geoType && (
1570
+ <UsaRegionMap supportedTerritories={supportedTerritories} {...mapProps} />
1571
+ )}
1572
+ {'world' === general.geoType && (
1573
+ <WorldMap supportedCountries={supportedCountries} {...mapProps} />
1574
+ )}
1575
+ {'us-county' === general.geoType && (
1576
+ <CountyMap
1577
+ supportedCountries={supportedCountries}
1578
+ {...mapProps}
1579
+ />
1580
+ )}
1581
+ {'data' === general.type && logo && <img src={logo} alt='' className='map-logo' />}
1582
+ </section>
1583
+
1584
+ )}
1585
+ </section>
1586
+
1587
+ {general.showSidebar && 'navigation' !== general.type && (
1588
+ <Sidebar
1589
+ viewport={currentViewport}
1590
+ legend={state.legend}
1591
+ runtimeLegend={runtimeLegend}
1592
+ setRuntimeLegend={setRuntimeLegend}
1593
+ runtimeFilters={runtimeFilters}
1594
+ columns={state.columns}
1595
+ sharing={state.sharing}
1596
+ prefix={state.columns.primary.prefix}
1597
+ suffix={state.columns.primary.suffix}
1598
+ setState={setState}
1599
+ resetLegendToggles={resetLegendToggles}
1600
+ changeFilterActive={changeFilterActive}
1601
+ setAccessibleStatus={setAccessibleStatus}
1602
+ />
1603
+ )}
1604
+ </section>
1605
+ {'navigation' === general.type && (
1606
+ <NavigationMenu
1607
+ displayGeoName={displayGeoName}
1608
+ data={runtimeData}
1609
+ options={general}
1610
+ columns={state.columns}
1611
+ navigationHandler={(val) => navigationHandler(val)}
1612
+ />
1613
+ )}
1614
+ {state.runtime.editorErrorMessage.length === 0 && true === dataTable.forceDisplay && general.type !== 'navigation' && false === loading && (
1615
+ <DataTable
1616
+ state={state}
1617
+ rawData={state.data}
1618
+ navigationHandler={navigationHandler}
1619
+ expandDataTable={general.expandDataTable}
1620
+ headerColor={general.headerColor}
1621
+ columns={state.columns}
1622
+ showDownloadButton={general.showDownloadButton}
1623
+ runtimeLegend={runtimeLegend}
1624
+ runtimeData={runtimeData}
1625
+ displayDataAsText={displayDataAsText}
1626
+ displayGeoName={displayGeoName}
1627
+ applyLegendToRow={applyLegendToRow}
1628
+ tableTitle={dataTable.title}
1629
+ indexTitle={dataTable.indexTitle}
1630
+ mapTitle={general.title}
1631
+ viewport={currentViewport}
1632
+ formatLegendLocation={formatLegendLocation}
1633
+ setFilteredCountryCode={setFilteredCountryCode}
1634
+ />
1635
+ )}
1636
+ {subtext.length > 0 && <p className='subtext'>{parse(subtext)}</p>}
1637
+ </section>}
1638
+ <div aria-live='assertive' className='cdcdataviz-sr-only'>
1639
+ {accessibleStatus}
1640
+ </div>
1641
+ </div>
1642
+ );
1119
1643
  }
1120
1644
 
1121
1645
  export default memo(CdcMap)