@cdc/map 2.6.4 → 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 +22 -16
- package/examples/default-county.json +64 -12
- package/examples/default-hex.json +3 -1
- package/examples/example-city-state.json +10 -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/city-state.json +428 -0
- package/examples/private/cty-issue.json +42768 -0
- package/examples/private/default-usa.json +460 -0
- package/examples/private/legend-issue.json +1 -0
- package/examples/private/map-rounding-error.json +42759 -0
- package/examples/private/monkeypox.json +376 -0
- package/examples/private/valid-data-map.csv +59 -0
- package/examples/private/wcmsrd-14492-data.json +292 -0
- package/examples/private/wcmsrd-14492.json +114 -0
- package/package.json +3 -3
- package/src/CdcMap.js +204 -127
- package/src/components/BubbleList.js +9 -5
- package/src/components/CityList.js +22 -4
- package/src/components/CountyMap.js +13 -4
- package/src/components/DataTable.js +9 -9
- package/src/components/EditorPanel.js +239 -121
- 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 +10 -4
- package/src/components/UsaMap.js +82 -30
- package/src/components/UsaRegionMap.js +3 -2
- package/src/components/WorldMap.js +7 -2
- package/src/data/{dfc-map.json → county-map.json} +0 -0
- package/src/data/initial-state.js +2 -1
- package/src/data/supported-geos.js +5 -0
- package/src/index.html +3 -8
- package/src/scss/editor-panel.scss +2 -2
- package/src/scss/main.scss +1 -1
- package/src/scss/map.scss +4 -0
- package/src/scss/sidebar.scss +2 -1
package/src/CdcMap.js
CHANGED
|
@@ -9,7 +9,6 @@ import ResizeObserver from 'resize-observer-polyfill';
|
|
|
9
9
|
// Third party
|
|
10
10
|
import ReactTooltip from 'react-tooltip';
|
|
11
11
|
import chroma from 'chroma-js';
|
|
12
|
-
import Papa from 'papaparse';
|
|
13
12
|
import parse from 'html-react-parser';
|
|
14
13
|
import html2pdf from 'html2pdf.js'
|
|
15
14
|
import html2canvas from 'html2canvas';
|
|
@@ -36,15 +35,16 @@ import './scss/main.scss';
|
|
|
36
35
|
import './scss/btn.scss'
|
|
37
36
|
|
|
38
37
|
// Images
|
|
38
|
+
// TODO: Move to Icon component
|
|
39
39
|
import DownloadImg from './images/icon-download-img.svg'
|
|
40
40
|
import DownloadPdf from './images/icon-download-pdf.svg'
|
|
41
41
|
|
|
42
42
|
// Core
|
|
43
43
|
import Loading from '@cdc/core/components/Loading';
|
|
44
|
-
import DataTransform from '@cdc/core/
|
|
44
|
+
import { DataTransform } from '@cdc/core/helpers/DataTransform';
|
|
45
45
|
import getViewport from '@cdc/core/helpers/getViewport';
|
|
46
|
-
import numberFromString from '@cdc/core/helpers/numberFromString'
|
|
47
|
-
import
|
|
46
|
+
import numberFromString from '@cdc/core/helpers/numberFromString';
|
|
47
|
+
import fetchRemoteData from '@cdc/core/helpers/fetchRemoteData';
|
|
48
48
|
|
|
49
49
|
|
|
50
50
|
// Child Components
|
|
@@ -113,8 +113,8 @@ const getUniqueValues = (data, columnName) => {
|
|
|
113
113
|
return Object.keys(result)
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
const CdcMap = ({className, config, navigationHandler: customNavigationHandler, isDashboard = false, isEditor = false, configUrl, logo = null, setConfig, hostname}) => {
|
|
117
|
-
|
|
116
|
+
const CdcMap = ({className, config, navigationHandler: customNavigationHandler, isDashboard = false, isEditor = false, configUrl, logo = null, setConfig, setSharedFilter, setSharedFilterValue, hostname = "localhost:8080",link}) => {
|
|
117
|
+
|
|
118
118
|
const [showLoadingMessage, setShowLoadingMessage] = useState(false)
|
|
119
119
|
const transform = new DataTransform()
|
|
120
120
|
const [state, setState] = useState( {...initialState} )
|
|
@@ -183,12 +183,10 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
|
|
|
183
183
|
})
|
|
184
184
|
};
|
|
185
185
|
|
|
186
|
-
|
|
187
|
-
|
|
188
186
|
const resizeObserver = new ResizeObserver(entries => {
|
|
189
187
|
for (let entry of entries) {
|
|
190
188
|
let newViewport = getViewport(entry.contentRect.width)
|
|
191
|
-
|
|
189
|
+
|
|
192
190
|
setCurrentViewport(newViewport)
|
|
193
191
|
}
|
|
194
192
|
});
|
|
@@ -309,7 +307,7 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
|
|
|
309
307
|
let mapColorPalette = obj.customColors || colorPalettes[obj.color] || colorPalettes['bluegreen']
|
|
310
308
|
|
|
311
309
|
// Handle Region Maps need for a 10th color
|
|
312
|
-
if( general.geoType === 'us-region' && mapColorPalette.length < 10 ) {
|
|
310
|
+
if( general.geoType === 'us-region' && mapColorPalette.length < 10 && mapColorPalette.length > 8 ) {
|
|
313
311
|
if(!general.palette.isReversed) {
|
|
314
312
|
mapColorPalette.push( chroma(mapColorPalette[8]).darken(0.75).hex() )
|
|
315
313
|
} else {
|
|
@@ -362,8 +360,8 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
|
|
|
362
360
|
}
|
|
363
361
|
|
|
364
362
|
let specialColor = '';
|
|
365
|
-
|
|
366
|
-
// color the state if val is in row
|
|
363
|
+
|
|
364
|
+
// color the state if val is in row
|
|
367
365
|
specialColor = result.findIndex(p => p.value === val)
|
|
368
366
|
|
|
369
367
|
newLegendMemo.set( hashObj(row), specialColor)
|
|
@@ -393,10 +391,10 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
|
|
|
393
391
|
|
|
394
392
|
specialClasses += 1;
|
|
395
393
|
}
|
|
396
|
-
|
|
394
|
+
|
|
397
395
|
let specialColor = '';
|
|
398
|
-
|
|
399
|
-
// color the state if val is in row
|
|
396
|
+
|
|
397
|
+
// color the state if val is in row
|
|
400
398
|
if ( Object.values(row).includes(val) ) {
|
|
401
399
|
specialColor = result.findIndex(p => p.value === val)
|
|
402
400
|
}
|
|
@@ -479,7 +477,7 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
|
|
|
479
477
|
let legendNumber = Math.min(number, Object.keys(uniqueValues).length);
|
|
480
478
|
|
|
481
479
|
// Separate zero
|
|
482
|
-
if(true === obj.legend.separateZero) {
|
|
480
|
+
if(true === obj.legend.separateZero && !state.general.equalNumberOptIn) {
|
|
483
481
|
let addLegendItem = false;
|
|
484
482
|
|
|
485
483
|
for(let i = 0; i < dataSet.length; i++) {
|
|
@@ -562,74 +560,101 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
|
|
|
562
560
|
// get nums
|
|
563
561
|
let hasZeroInData = dataSet.filter(obj => obj[state.columns.primary.name] === 0).length > 0
|
|
564
562
|
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
563
|
|
|
568
564
|
domainNums = d3.extent(domainNums)
|
|
569
565
|
let colors = colorPalettes[state.color]
|
|
570
|
-
|
|
566
|
+
|
|
567
|
+
let colorRange = colors.slice(0, state.legend.numberOfItems )
|
|
568
|
+
|
|
571
569
|
let scale = d3.scaleQuantile()
|
|
572
|
-
.domain(dataSet.map(item => Math.round(item[state.columns.primary.name]))) // min/max values
|
|
573
|
-
//.domain(domainNums)
|
|
570
|
+
.domain([... new Set(dataSet.map(item => Math.round(item[state.columns.primary.name])))]) // min/max values
|
|
574
571
|
.range(colorRange) // set range to our colors array
|
|
575
572
|
|
|
576
573
|
let breaks = scale.quantiles();
|
|
574
|
+
|
|
577
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
|
+
// }
|
|
578
583
|
|
|
579
|
-
console.log('breaks', breaks)
|
|
580
|
-
console.log('d', domainNums)
|
|
581
584
|
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
breaks.splice(1, 0, 1);
|
|
588
|
-
}
|
|
589
|
-
|
|
585
|
+
// if seperating zero force it into breaks
|
|
586
|
+
if(breaks[0] !== 0) {
|
|
587
|
+
breaks.unshift(0)
|
|
588
|
+
}
|
|
589
|
+
|
|
590
590
|
breaks.map( (item, index) => {
|
|
591
591
|
|
|
592
|
-
|
|
593
|
-
let max = breaks[index + 1] - 1;
|
|
592
|
+
const setMin = (index) => {
|
|
594
593
|
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
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) {
|
|
598
599
|
min = 0;
|
|
599
600
|
}
|
|
600
601
|
|
|
601
|
-
if
|
|
602
|
-
|
|
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
|
|
603
605
|
}
|
|
604
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
|
+
|
|
605
613
|
}
|
|
606
614
|
|
|
607
|
-
const setMax = () => {
|
|
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
|
+
|
|
608
624
|
if(index === 0 && state.legend.separateZero) {
|
|
609
625
|
max = 0;
|
|
610
626
|
}
|
|
627
|
+
// if ((index === state.legend.specialClasses.length && state.legend.specialClasses.length !== 0) && !state.legend.separateZero && hasZeroInData) {
|
|
628
|
+
// max = 0;
|
|
629
|
+
// }
|
|
611
630
|
|
|
612
631
|
if(index + 1 === breaks.length) {
|
|
613
632
|
max = domainNums[1]
|
|
614
633
|
}
|
|
634
|
+
|
|
635
|
+
return max;
|
|
615
636
|
}
|
|
616
637
|
|
|
617
|
-
setMin()
|
|
618
|
-
setMax()
|
|
619
|
-
console.log('res', result)
|
|
638
|
+
let min = setMin(index)
|
|
639
|
+
let max = setMax(index, min)
|
|
620
640
|
|
|
621
|
-
if(index === 0 && result[index]?.max === 0 && state.legend.separateZero) return true;
|
|
622
641
|
result.push({
|
|
623
642
|
min,
|
|
624
643
|
max,
|
|
625
644
|
color: scale(item)
|
|
626
645
|
})
|
|
627
|
-
|
|
646
|
+
|
|
628
647
|
|
|
629
648
|
dataSet.forEach( (row, dataIndex) => {
|
|
630
649
|
let number = row[state.columns.primary.name]
|
|
650
|
+
|
|
651
|
+
let updated = 0
|
|
631
652
|
|
|
632
|
-
|
|
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;
|
|
633
658
|
|
|
634
659
|
if (result[updated]?.min === (null || undefined) || result[updated]?.max === (null || undefined)) return;
|
|
635
660
|
|
|
@@ -758,7 +783,7 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
|
|
|
758
783
|
value : hash
|
|
759
784
|
});
|
|
760
785
|
}
|
|
761
|
-
|
|
786
|
+
|
|
762
787
|
|
|
763
788
|
obj.data.forEach(row => {
|
|
764
789
|
|
|
@@ -784,7 +809,7 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
|
|
|
784
809
|
// If this is a navigation only map, skip if it doesn't have a URL
|
|
785
810
|
if("navigation" === obj.general.type ) {
|
|
786
811
|
let navigateUrl = row[obj.columns.navigate.name] || "";
|
|
787
|
-
|
|
812
|
+
|
|
788
813
|
if ( undefined !== navigateUrl && typeof navigateUrl === "string" ) {
|
|
789
814
|
// Strip hidden characters before we check length
|
|
790
815
|
navigateUrl = navigateUrl.replace( /(\r\n|\n|\r)/gm, '' );
|
|
@@ -932,7 +957,7 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
|
|
|
932
957
|
|
|
933
958
|
filters[idx].active = activeValue
|
|
934
959
|
const newData = generateRuntimeData(state, filters)
|
|
935
|
-
|
|
960
|
+
|
|
936
961
|
// throw an error if newData is empty
|
|
937
962
|
if (isEmpty(newData)) throw new Error('Cove Filter Error: No runtime data to set for this filter')
|
|
938
963
|
|
|
@@ -944,12 +969,14 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
|
|
|
944
969
|
}
|
|
945
970
|
|
|
946
971
|
}
|
|
947
|
-
|
|
948
972
|
const displayDataAsText = (value, columnName) => {
|
|
949
|
-
if(value === null) {
|
|
973
|
+
if(value === null || value === "" || value === undefined ) {
|
|
950
974
|
return ""
|
|
951
975
|
}
|
|
952
|
-
|
|
976
|
+
if(typeof value === 'string' && value.length > 0 && state.legend.type==='equalnumber'){
|
|
977
|
+
return value
|
|
978
|
+
}
|
|
979
|
+
|
|
953
980
|
let formattedValue = value
|
|
954
981
|
|
|
955
982
|
let columnObj = state.columns[columnName]
|
|
@@ -957,20 +984,18 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
|
|
|
957
984
|
if (columnObj) {
|
|
958
985
|
// If value is a number, apply specific formattings
|
|
959
986
|
if (Number(value)) {
|
|
987
|
+
const decimalPoint = columnObj.roundToPlace ? Number(columnObj.roundToPlace) : 0
|
|
988
|
+
|
|
960
989
|
// Rounding
|
|
961
990
|
if(columnObj.hasOwnProperty('roundToPlace') && columnObj.roundToPlace !== "None") {
|
|
962
|
-
|
|
963
|
-
const decimalPoint = columnObj.roundToPlace
|
|
964
|
-
|
|
965
991
|
formattedValue = Number(value).toFixed(decimalPoint)
|
|
966
|
-
|
|
992
|
+
|
|
967
993
|
}
|
|
968
994
|
|
|
969
995
|
if(columnObj.hasOwnProperty('useCommas') && columnObj.useCommas === true) {
|
|
970
|
-
|
|
971
|
-
formattedValue = Number(value).toLocaleString('en-US', { style: 'decimal'})
|
|
972
|
-
|
|
996
|
+
formattedValue = Number(value).toLocaleString('en-US', { style: 'decimal', minimumFractionDigits: decimalPoint, maximumFractionDigits: decimalPoint })
|
|
973
997
|
}
|
|
998
|
+
|
|
974
999
|
}
|
|
975
1000
|
|
|
976
1001
|
// Check if it's a special value. If it is not, apply the designated prefix and suffix
|
|
@@ -1084,45 +1109,6 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
|
|
|
1084
1109
|
setRuntimeLegend(newLegend)
|
|
1085
1110
|
}
|
|
1086
1111
|
|
|
1087
|
-
// Supports JSON or CSV
|
|
1088
|
-
const fetchRemoteData = async (url) => {
|
|
1089
|
-
try {
|
|
1090
|
-
const urlObj = new URL(url);
|
|
1091
|
-
const regex = /(?:\.([^.]+))?$/
|
|
1092
|
-
|
|
1093
|
-
let data = []
|
|
1094
|
-
|
|
1095
|
-
const ext = (regex.exec(urlObj.pathname)[1])
|
|
1096
|
-
if ('csv' === ext) {
|
|
1097
|
-
data = await fetch(url)
|
|
1098
|
-
.then(response => response.text())
|
|
1099
|
-
.then(responseText => {
|
|
1100
|
-
const parsedCsv = Papa.parse(responseText, {
|
|
1101
|
-
header: true,
|
|
1102
|
-
dynamicTyping: true,
|
|
1103
|
-
skipEmptyLines: true
|
|
1104
|
-
})
|
|
1105
|
-
return parsedCsv.data
|
|
1106
|
-
})
|
|
1107
|
-
}
|
|
1108
|
-
|
|
1109
|
-
if ('json' === ext) {
|
|
1110
|
-
data = await fetch(url)
|
|
1111
|
-
.then(response => response.json())
|
|
1112
|
-
}
|
|
1113
|
-
|
|
1114
|
-
return data;
|
|
1115
|
-
} catch {
|
|
1116
|
-
// If we can't parse it, still attempt to fetch it
|
|
1117
|
-
try {
|
|
1118
|
-
let response = await (await fetch(configUrl)).json()
|
|
1119
|
-
return response
|
|
1120
|
-
} catch {
|
|
1121
|
-
console.error(`Cannot parse URL: ${url}`);
|
|
1122
|
-
}
|
|
1123
|
-
}
|
|
1124
|
-
}
|
|
1125
|
-
|
|
1126
1112
|
const formatLegendLocation = (key) => {
|
|
1127
1113
|
let value = key;
|
|
1128
1114
|
var formattedName = '';
|
|
@@ -1161,7 +1147,10 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
|
|
|
1161
1147
|
}
|
|
1162
1148
|
|
|
1163
1149
|
const dict = {
|
|
1164
|
-
"
|
|
1150
|
+
"Washington D.C." : "District of Columbia",
|
|
1151
|
+
"WASHINGTON DC":"District of Columbia",
|
|
1152
|
+
"DC":"District of Columbia",
|
|
1153
|
+
"WASHINGTON DC.":"District of Columbia",
|
|
1165
1154
|
"Congo": "Republic of the Congo"
|
|
1166
1155
|
}
|
|
1167
1156
|
|
|
@@ -1190,6 +1179,10 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
|
|
|
1190
1179
|
}
|
|
1191
1180
|
|
|
1192
1181
|
const geoClickHandler = (key, value) => {
|
|
1182
|
+
if(setSharedFilter){
|
|
1183
|
+
setSharedFilter(state.uid, value);
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1193
1186
|
// If modals are set or we are on a mobile viewport, display modal
|
|
1194
1187
|
if ('xs' === currentViewport || 'xxs' === currentViewport || 'click' === state.tooltips.appearanceType) {
|
|
1195
1188
|
setModal({
|
|
@@ -1222,6 +1215,41 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
|
|
|
1222
1215
|
return newState;
|
|
1223
1216
|
}
|
|
1224
1217
|
|
|
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
|
+
|
|
1225
1253
|
const loadConfig = async (configObj) => {
|
|
1226
1254
|
// Set loading flag
|
|
1227
1255
|
if(!loading) setLoading(true)
|
|
@@ -1239,10 +1267,13 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
|
|
|
1239
1267
|
// If a dataUrl property exists, always pull from that.
|
|
1240
1268
|
if (newState.dataUrl) {
|
|
1241
1269
|
if(newState.dataUrl[0] === '/') {
|
|
1242
|
-
newState.dataUrl = '
|
|
1270
|
+
newState.dataUrl = 'http://' + hostname + newState.dataUrl
|
|
1243
1271
|
}
|
|
1244
1272
|
|
|
1245
|
-
|
|
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 )
|
|
1246
1277
|
|
|
1247
1278
|
if(newData && newState.dataDescription) {
|
|
1248
1279
|
newData = transform.autoStandardize(newData);
|
|
@@ -1388,8 +1419,8 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
|
|
|
1388
1419
|
// Data
|
|
1389
1420
|
let newRuntimeData;
|
|
1390
1421
|
if(hashData !== runtimeData.fromHash && state.data?.fromColumn) {
|
|
1391
|
-
const
|
|
1392
|
-
setRuntimeData(
|
|
1422
|
+
const newRuntimeData = generateRuntimeData(state, filters || runtimeFilters, hashData)
|
|
1423
|
+
setRuntimeData(newRuntimeData)
|
|
1393
1424
|
}
|
|
1394
1425
|
|
|
1395
1426
|
// Legend
|
|
@@ -1397,7 +1428,7 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
|
|
|
1397
1428
|
const legend = generateRuntimeLegend(state, newRuntimeData || runtimeData, hashLegend)
|
|
1398
1429
|
setRuntimeLegend(legend)
|
|
1399
1430
|
}
|
|
1400
|
-
|
|
1431
|
+
|
|
1401
1432
|
}, [state])
|
|
1402
1433
|
|
|
1403
1434
|
useEffect(() => {
|
|
@@ -1412,7 +1443,7 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
|
|
|
1412
1443
|
geoType: state.general.geoType,
|
|
1413
1444
|
data: state.data
|
|
1414
1445
|
})
|
|
1415
|
-
|
|
1446
|
+
|
|
1416
1447
|
// Legend - Update when runtimeData does
|
|
1417
1448
|
if(hashLegend !== runtimeLegend.fromHash && undefined === runtimeData.init) {
|
|
1418
1449
|
const legend = generateRuntimeLegend(state, runtimeData)
|
|
@@ -1429,8 +1460,8 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
|
|
|
1429
1460
|
|
|
1430
1461
|
// Destructuring for more readable JSX
|
|
1431
1462
|
const { general, tooltips, dataTable } = state
|
|
1432
|
-
const { title = '', subtext = ''} = general
|
|
1433
|
-
|
|
1463
|
+
const { title = '', subtext = '' } = general
|
|
1464
|
+
|
|
1434
1465
|
// Outer container classes
|
|
1435
1466
|
let outerContainerClasses = [
|
|
1436
1467
|
'cdc-open-viz-module',
|
|
@@ -1447,7 +1478,8 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
|
|
|
1447
1478
|
'map-container',
|
|
1448
1479
|
state.legend.position,
|
|
1449
1480
|
state.general.type,
|
|
1450
|
-
state.general.geoType
|
|
1481
|
+
state.general.geoType,
|
|
1482
|
+
'outline-none'
|
|
1451
1483
|
]
|
|
1452
1484
|
|
|
1453
1485
|
if(modal) {
|
|
@@ -1479,13 +1511,40 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
|
|
|
1479
1511
|
filteredCountryCode,
|
|
1480
1512
|
position,
|
|
1481
1513
|
setPosition,
|
|
1482
|
-
|
|
1514
|
+
setSharedFilterValue,
|
|
1515
|
+
hasZoom : state.general.allowMapZoom,
|
|
1516
|
+
handleMapAriaLabels
|
|
1483
1517
|
}
|
|
1484
1518
|
|
|
1485
1519
|
if (!mapProps.data || !state.data) return <Loading />;
|
|
1486
1520
|
|
|
1487
|
-
const
|
|
1488
|
-
|
|
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()
|
|
1547
|
+
|
|
1489
1548
|
return (
|
|
1490
1549
|
<div className={outerContainerClasses.join(' ')} ref={outerContainerRef}>
|
|
1491
1550
|
{isEditor && (
|
|
@@ -1510,12 +1569,20 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
|
|
|
1510
1569
|
html={true}
|
|
1511
1570
|
className={tooltips.capitalizeLabels ? 'capitalize tooltip' : 'tooltip'}
|
|
1512
1571
|
/>
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
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
|
+
|
|
1519
1586
|
<section
|
|
1520
1587
|
role="button"
|
|
1521
1588
|
tabIndex="0"
|
|
@@ -1544,12 +1611,13 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
|
|
|
1544
1611
|
</div>
|
|
1545
1612
|
)}
|
|
1546
1613
|
|
|
1547
|
-
<a id='skip-geo-container' className='cdcdataviz-sr-only-focusable' href={
|
|
1614
|
+
<a id='skip-geo-container' className='cdcdataviz-sr-only-focusable' href={tabId}>
|
|
1548
1615
|
Skip Over Map Container
|
|
1549
1616
|
</a>
|
|
1550
|
-
|
|
1617
|
+
|
|
1618
|
+
<section className='geography-container outline-none' ref={mapSvg} tabIndex="0">
|
|
1551
1619
|
{currentViewport && (
|
|
1552
|
-
<section className='geography-container'
|
|
1620
|
+
<section className='geography-container' ref={mapSvg}>
|
|
1553
1621
|
{modal && (
|
|
1554
1622
|
<Modal
|
|
1555
1623
|
type={general.type}
|
|
@@ -1580,10 +1648,10 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
|
|
|
1580
1648
|
)}
|
|
1581
1649
|
{'data' === general.type && logo && <img src={logo} alt='' className='map-logo' />}
|
|
1582
1650
|
</section>
|
|
1583
|
-
|
|
1651
|
+
|
|
1584
1652
|
)}
|
|
1585
1653
|
</section>
|
|
1586
|
-
|
|
1654
|
+
|
|
1587
1655
|
{general.showSidebar && 'navigation' !== general.type && (
|
|
1588
1656
|
<Sidebar
|
|
1589
1657
|
viewport={currentViewport}
|
|
@@ -1599,18 +1667,24 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
|
|
|
1599
1667
|
resetLegendToggles={resetLegendToggles}
|
|
1600
1668
|
changeFilterActive={changeFilterActive}
|
|
1601
1669
|
setAccessibleStatus={setAccessibleStatus}
|
|
1670
|
+
displayDataAsText={displayDataAsText}
|
|
1602
1671
|
/>
|
|
1603
1672
|
)}
|
|
1604
1673
|
</section>
|
|
1605
1674
|
{'navigation' === general.type && (
|
|
1606
|
-
|
|
1675
|
+
<NavigationMenu
|
|
1676
|
+
mapTabbingID={tabId}
|
|
1607
1677
|
displayGeoName={displayGeoName}
|
|
1608
1678
|
data={runtimeData}
|
|
1609
1679
|
options={general}
|
|
1610
1680
|
columns={state.columns}
|
|
1611
1681
|
navigationHandler={(val) => navigationHandler(val)}
|
|
1612
1682
|
/>
|
|
1613
|
-
|
|
1683
|
+
)}
|
|
1684
|
+
{link && link}
|
|
1685
|
+
|
|
1686
|
+
{subtext.length > 0 && <p className='subtext'>{parse(subtext)}</p>}
|
|
1687
|
+
|
|
1614
1688
|
{state.runtime.editorErrorMessage.length === 0 && true === dataTable.forceDisplay && general.type !== 'navigation' && false === loading && (
|
|
1615
1689
|
<DataTable
|
|
1616
1690
|
state={state}
|
|
@@ -1626,15 +1700,18 @@ const CdcMap = ({className, config, navigationHandler: customNavigationHandler,
|
|
|
1626
1700
|
displayGeoName={displayGeoName}
|
|
1627
1701
|
applyLegendToRow={applyLegendToRow}
|
|
1628
1702
|
tableTitle={dataTable.title}
|
|
1629
|
-
indexTitle={dataTable.
|
|
1703
|
+
indexTitle={dataTable.indexLabel}
|
|
1630
1704
|
mapTitle={general.title}
|
|
1631
1705
|
viewport={currentViewport}
|
|
1632
1706
|
formatLegendLocation={formatLegendLocation}
|
|
1633
1707
|
setFilteredCountryCode={setFilteredCountryCode}
|
|
1708
|
+
tabbingId={tabId}
|
|
1634
1709
|
/>
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1710
|
+
)}
|
|
1711
|
+
|
|
1712
|
+
{general.footnotes && <section className="footnotes">{parse(general.footnotes)}</section>}
|
|
1713
|
+
</section>}
|
|
1714
|
+
|
|
1638
1715
|
<div aria-live='assertive' className='cdcdataviz-sr-only'>
|
|
1639
1716
|
{accessibleStatus}
|
|
1640
1717
|
</div>
|
|
@@ -2,6 +2,7 @@ import React, {memo, useState, useEffect} from 'react'
|
|
|
2
2
|
import { scaleLinear } from 'd3-scale';
|
|
3
3
|
import { countryCoordinates } from '../data/country-coordinates';
|
|
4
4
|
import stateCoordinates from '../data/state-coordinates';
|
|
5
|
+
import ReactTooltip from 'react-tooltip'
|
|
5
6
|
|
|
6
7
|
export const BubbleList = (
|
|
7
8
|
{
|
|
@@ -15,6 +16,9 @@ export const BubbleList = (
|
|
|
15
16
|
displayGeoName
|
|
16
17
|
}) => {
|
|
17
18
|
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
ReactTooltip.hide()
|
|
21
|
+
}, [runtimeData]);
|
|
18
22
|
|
|
19
23
|
const maxDataValue = Math.max(...dataImport.map(d => d[state.columns.primary.name]))
|
|
20
24
|
const hasBubblesWithZeroOnMap = state.visual.showBubbleZeros ? 0 : 1;
|
|
@@ -56,7 +60,7 @@ export const BubbleList = (
|
|
|
56
60
|
key={`circle-${countryName.replace(' ', '')}`}
|
|
57
61
|
data-tip={toolTip}
|
|
58
62
|
data-for="tooltip"
|
|
59
|
-
className=
|
|
63
|
+
className={`bubble country--${countryName}`}
|
|
60
64
|
cx={ Number(projection(coordinates[1], coordinates[0])[0]) || 0 } // || 0 handles error on loads where the data isn't ready
|
|
61
65
|
cy={ Number(projection(coordinates[1], coordinates[0])[1]) || 0 }
|
|
62
66
|
r={ Number(size(country[primaryKey])) }
|
|
@@ -197,9 +201,9 @@ export const BubbleList = (
|
|
|
197
201
|
data-tip={toolTip}
|
|
198
202
|
data-for="tooltip"
|
|
199
203
|
className="bubble"
|
|
200
|
-
cx={projection(coordinates)[0] || 0} // || 0 handles error on loads where the data isn't ready
|
|
201
|
-
cy={projection(coordinates)[1] || 0}
|
|
202
|
-
r={Number(size(item[primaryKey]) + 1
|
|
204
|
+
cx={ projection(coordinates)[0] || 0 } // || 0 handles error on loads where the data isn't ready
|
|
205
|
+
cy={ projection(coordinates)[1] || 0 }
|
|
206
|
+
r={ Number(size(item[primaryKey])) + 1 }
|
|
203
207
|
fill={"transparent"}
|
|
204
208
|
stroke={"white"}
|
|
205
209
|
strokeWidth={.5}
|
|
@@ -237,4 +241,4 @@ export const BubbleList = (
|
|
|
237
241
|
return bubbles;
|
|
238
242
|
}
|
|
239
243
|
}
|
|
240
|
-
export default
|
|
244
|
+
export default BubbleList
|