@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.
- package/dist/cdcmap.js +26 -18
- package/examples/bubble-us.json +363 -0
- package/examples/bubble-world.json +427 -0
- package/examples/default-hex.json +475 -0
- package/examples/default-usa-regions.json +118 -0
- package/examples/default-usa.json +1 -1
- package/examples/default-world-data.json +1450 -0
- package/examples/default-world.json +5 -3
- package/examples/example-city-state.json +36 -0
- package/examples/private/atsdr.json +439 -0
- package/examples/private/atsdr_new.json +436 -0
- package/examples/private/bubble.json +285 -0
- package/examples/private/default-world-data.json +1444 -0
- package/examples/private/default.json +968 -0
- package/examples/private/map.csv +60 -0
- package/examples/private/mdx.json +210 -0
- package/examples/private/regions.json +52 -0
- package/examples/private/wcmsrd-13881-data.json +2858 -0
- package/examples/private/wcmsrd-13881.json +5823 -0
- package/examples/private/wcmsrd-test.json +268 -0
- package/examples/private/world.json +1580 -0
- package/examples/private/worldmap.json +1490 -0
- package/package.json +51 -50
- package/src/CdcMap.js +340 -79
- package/src/components/BubbleList.js +240 -0
- package/src/components/CityList.js +19 -1
- package/src/components/CountyMap.js +3 -2
- package/src/components/DataTable.js +17 -10
- package/src/components/EditorPanel.js +741 -348
- package/src/components/Geo.js +1 -1
- package/src/components/SingleStateMap.js +1 -1
- package/src/components/UsaMap.js +22 -7
- package/src/components/UsaRegionMap.js +319 -0
- package/src/components/WorldMap.js +112 -35
- package/src/data/country-coordinates.js +250 -0
- package/src/data/initial-state.js +19 -2
- package/src/data/state-coordinates.js +55 -0
- package/src/data/supported-geos.js +91 -15
- package/src/data/us-regions-topo-2.json +360525 -0
- package/src/data/us-regions-topo.json +37894 -0
- package/src/hooks/useColorPalette.ts +96 -0
- package/src/index.html +10 -2
- package/src/scss/editor-panel.scss +76 -55
- package/src/scss/map.scss +108 -2
- package/src/data/color-palettes.js +0 -200
- 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 {
|
|
20
|
-
|
|
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
|
-
|
|
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 ===
|
|
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
|
-
|
|
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
|
-
|
|
426
|
-
let changingNumber = legendNumber
|
|
536
|
+
chunkAmt = Math.floor(numberOfRows / changingNumber)
|
|
427
537
|
|
|
428
|
-
|
|
538
|
+
if (remainder > 0) {
|
|
539
|
+
chunkAmt += 1
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
let removedRows = dataSet.splice(0, chunkAmt);
|
|
429
543
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
remainder = numberOfRows % changingNumber
|
|
544
|
+
let min = removedRows[0][primaryCol],
|
|
545
|
+
max = removedRows[removedRows.length - 1][primaryCol]
|
|
433
546
|
|
|
434
|
-
|
|
547
|
+
removedRows.forEach(row => {
|
|
548
|
+
newLegendMemo.set(hashObj(row), result.length)
|
|
549
|
+
})
|
|
435
550
|
|
|
436
|
-
|
|
437
|
-
|
|
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
|
-
|
|
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
|
-
|
|
443
|
-
max =
|
|
592
|
+
let min = breaks[index];
|
|
593
|
+
let max = breaks[index + 1] - 1;
|
|
444
594
|
|
|
445
|
-
|
|
446
|
-
|
|
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
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
})
|
|
601
|
+
if(index === 0 && !state.legend.separateZero) {
|
|
602
|
+
min = domainNums[0]
|
|
603
|
+
}
|
|
453
604
|
|
|
454
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1151
|
-
setRuntimeData(
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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>}
|