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