@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
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import React, {memo, useState, useEffect} from 'react'
|
|
2
|
+
import { scaleLinear } from 'd3-scale';
|
|
3
|
+
import { countryCoordinates } from '../data/country-coordinates';
|
|
4
|
+
import stateCoordinates from '../data/state-coordinates';
|
|
5
|
+
import ReactTooltip from 'react-tooltip'
|
|
6
|
+
|
|
7
|
+
export const BubbleList = (
|
|
8
|
+
{
|
|
9
|
+
data: dataImport,
|
|
10
|
+
state,
|
|
11
|
+
projection,
|
|
12
|
+
applyLegendToRow,
|
|
13
|
+
applyTooltipsToGeo,
|
|
14
|
+
handleCircleClick,
|
|
15
|
+
runtimeData,
|
|
16
|
+
displayGeoName
|
|
17
|
+
}) => {
|
|
18
|
+
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
ReactTooltip.hide()
|
|
21
|
+
}, [runtimeData]);
|
|
22
|
+
|
|
23
|
+
const maxDataValue = Math.max(...dataImport.map(d => d[state.columns.primary.name]))
|
|
24
|
+
const hasBubblesWithZeroOnMap = state.visual.showBubbleZeros ? 0 : 1;
|
|
25
|
+
// sort runtime data. Smaller bubbles should appear on top.
|
|
26
|
+
const sortedRuntimeData = Object.values(runtimeData).sort((a, b) => a[state.columns.primary.name] < b[state.columns.primary.name] ? 1 : -1 )
|
|
27
|
+
if(!sortedRuntimeData) return;
|
|
28
|
+
|
|
29
|
+
const clickTolerance = 10;
|
|
30
|
+
|
|
31
|
+
// Set bubble sizes
|
|
32
|
+
var size = scaleLinear()
|
|
33
|
+
.domain([hasBubblesWithZeroOnMap, maxDataValue])
|
|
34
|
+
.range([state.visual.minBubbleSize, state.visual.maxBubbleSize])
|
|
35
|
+
|
|
36
|
+
// Start looping through the countries to create the bubbles.
|
|
37
|
+
if(state.general.geoType === 'world') {
|
|
38
|
+
const countries = sortedRuntimeData && sortedRuntimeData.map( (country, index) => {
|
|
39
|
+
|
|
40
|
+
let coordinates = countryCoordinates[country.uid]
|
|
41
|
+
|
|
42
|
+
if(!coordinates) return true;
|
|
43
|
+
|
|
44
|
+
const countryName = displayGeoName(country[state.columns.geo.name]);
|
|
45
|
+
const toolTip = applyTooltipsToGeo(countryName, country);
|
|
46
|
+
const legendColors = applyLegendToRow(country);
|
|
47
|
+
|
|
48
|
+
let primaryKey = state.columns.primary.name
|
|
49
|
+
if ((Math.floor(Number(size(country[primaryKey]))) === 0 || country[primaryKey] === "") && !state.visual.showBubbleZeros) return;
|
|
50
|
+
|
|
51
|
+
let transform = `translate(${projection([coordinates[1], coordinates[0]])})`
|
|
52
|
+
|
|
53
|
+
let pointerX, pointerY;
|
|
54
|
+
|
|
55
|
+
if( !projection(coordinates) ) return true;
|
|
56
|
+
|
|
57
|
+
const circle = (
|
|
58
|
+
<>
|
|
59
|
+
<circle
|
|
60
|
+
key={`circle-${countryName.replace(' ', '')}`}
|
|
61
|
+
data-tip={toolTip}
|
|
62
|
+
data-for="tooltip"
|
|
63
|
+
className={`bubble country--${countryName}`}
|
|
64
|
+
cx={ Number(projection(coordinates[1], coordinates[0])[0]) || 0 } // || 0 handles error on loads where the data isn't ready
|
|
65
|
+
cy={ Number(projection(coordinates[1], coordinates[0])[1]) || 0 }
|
|
66
|
+
r={ Number(size(country[primaryKey])) }
|
|
67
|
+
fill={legendColors[0] }
|
|
68
|
+
stroke={legendColors[0]}
|
|
69
|
+
strokeWidth={1.25}
|
|
70
|
+
fillOpacity={.4}
|
|
71
|
+
onPointerDown={(e) => {
|
|
72
|
+
pointerX = e.clientX;
|
|
73
|
+
pointerY = e.clientY;
|
|
74
|
+
}}
|
|
75
|
+
onPointerUp={(e) => {
|
|
76
|
+
if(pointerX && pointerY &&
|
|
77
|
+
e.clientX > (pointerX - clickTolerance) &&
|
|
78
|
+
e.clientX < (pointerX + clickTolerance) &&
|
|
79
|
+
e.clientY > (pointerY - clickTolerance) &&
|
|
80
|
+
e.clientY < (pointerY + clickTolerance)
|
|
81
|
+
){
|
|
82
|
+
handleCircleClick(country)
|
|
83
|
+
pointerX = undefined;
|
|
84
|
+
pointerY = undefined;
|
|
85
|
+
}
|
|
86
|
+
}}
|
|
87
|
+
transform={transform}
|
|
88
|
+
style={{ transition: 'all .25s ease-in-out', cursor: "pointer" }}
|
|
89
|
+
/>
|
|
90
|
+
|
|
91
|
+
{state.visual.extraBubbleBorder &&
|
|
92
|
+
<circle
|
|
93
|
+
key={`circle-${countryName.replace(' ', '')}`}
|
|
94
|
+
data-tip={toolTip}
|
|
95
|
+
data-for="tooltip"
|
|
96
|
+
className="bubble"
|
|
97
|
+
cx={ Number(projection(coordinates[1], coordinates[0])[0]) || 0 } // || 0 handles error on loads where the data isn't ready
|
|
98
|
+
cy={ Number(projection(coordinates[1], coordinates[0])[1]) || 0 }
|
|
99
|
+
r={ Number(size(country[primaryKey])) + 1 }
|
|
100
|
+
fill={ "transparent" }
|
|
101
|
+
stroke={ "white" }
|
|
102
|
+
strokeWidth={.5}
|
|
103
|
+
onPointerDown={(e) => {
|
|
104
|
+
pointerX = e.clientX;
|
|
105
|
+
pointerY = e.clientY;
|
|
106
|
+
}}
|
|
107
|
+
onPointerUp={(e) => {
|
|
108
|
+
if(pointerX && pointerY &&
|
|
109
|
+
e.clientX > (pointerX - clickTolerance) &&
|
|
110
|
+
e.clientX < (pointerX + clickTolerance) &&
|
|
111
|
+
e.clientY > (pointerY - clickTolerance) &&
|
|
112
|
+
e.clientY < (pointerY + clickTolerance)
|
|
113
|
+
){
|
|
114
|
+
handleCircleClick(country)
|
|
115
|
+
pointerX = undefined;
|
|
116
|
+
pointerY = undefined;
|
|
117
|
+
}
|
|
118
|
+
}}
|
|
119
|
+
transform={transform}
|
|
120
|
+
style={{ transition: 'all .25s ease-in-out', cursor: "pointer" }}
|
|
121
|
+
/>
|
|
122
|
+
}
|
|
123
|
+
</>
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
return (
|
|
128
|
+
<g key={`group-${countryName.replace(' ', '')}`}>
|
|
129
|
+
{circle}
|
|
130
|
+
</g>
|
|
131
|
+
)
|
|
132
|
+
})
|
|
133
|
+
return countries;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if(state.general.geoType === 'us') {
|
|
137
|
+
const bubbles = sortedRuntimeData && sortedRuntimeData.map( (item, index) => {
|
|
138
|
+
let stateData = stateCoordinates[item.uid]
|
|
139
|
+
let primaryKey = state?.columns?.primary?.name
|
|
140
|
+
if ( Number(size(item[primaryKey])) === 0) return;
|
|
141
|
+
|
|
142
|
+
if (item[primaryKey] === null) item[primaryKey] = ""
|
|
143
|
+
|
|
144
|
+
// Return if hiding zeros on the map
|
|
145
|
+
if( (Math.floor(Number(size(item[primaryKey]))) === 0 || item[primaryKey] === "" )&& !state.visual.showBubbleZeros ) return;
|
|
146
|
+
|
|
147
|
+
if(!stateData) return true;
|
|
148
|
+
let longitude = Number( stateData.Longitude);
|
|
149
|
+
let latitude = Number( stateData.Latitude);
|
|
150
|
+
let coordinates = [longitude, latitude]
|
|
151
|
+
//console.log('projection', projection([longitude, latitude]))
|
|
152
|
+
let stateName = stateData.Name;
|
|
153
|
+
if (!coordinates) return true;
|
|
154
|
+
|
|
155
|
+
stateName = displayGeoName(stateName);
|
|
156
|
+
const toolTip = applyTooltipsToGeo(stateName, item);
|
|
157
|
+
const legendColors = applyLegendToRow(item);
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
let transform = `translate(${projection([coordinates[1], coordinates[0]])})`
|
|
161
|
+
|
|
162
|
+
if ( !projection(coordinates) ) return true;
|
|
163
|
+
|
|
164
|
+
let pointerX, pointerY;
|
|
165
|
+
const circle = (
|
|
166
|
+
<>
|
|
167
|
+
<circle
|
|
168
|
+
key={`circle-${stateName.replace(' ', '')}`}
|
|
169
|
+
data-tip={toolTip}
|
|
170
|
+
data-for="tooltip"
|
|
171
|
+
className="bubble"
|
|
172
|
+
cx={projection(coordinates)[0] || 0} // || 0 handles error on loads where the data isn't ready
|
|
173
|
+
cy={projection(coordinates)[1] || 0}
|
|
174
|
+
r={Number(size(item[primaryKey]))}
|
|
175
|
+
fill={legendColors[0]}
|
|
176
|
+
stroke={legendColors[0]}
|
|
177
|
+
strokeWidth={1.25}
|
|
178
|
+
fillOpacity={.4}
|
|
179
|
+
onPointerDown={(e) => {
|
|
180
|
+
pointerX = e.clientX;
|
|
181
|
+
pointerY = e.clientY;
|
|
182
|
+
}}
|
|
183
|
+
onPointerUp={(e) => {
|
|
184
|
+
if (pointerX && pointerY &&
|
|
185
|
+
e.clientX > (pointerX - clickTolerance) &&
|
|
186
|
+
e.clientX < (pointerX + clickTolerance) &&
|
|
187
|
+
e.clientY > (pointerY - clickTolerance) &&
|
|
188
|
+
e.clientY < (pointerY + clickTolerance)
|
|
189
|
+
) {
|
|
190
|
+
handleCircleClick(state)
|
|
191
|
+
pointerX = undefined;
|
|
192
|
+
pointerY = undefined;
|
|
193
|
+
}
|
|
194
|
+
}}
|
|
195
|
+
transform={transform}
|
|
196
|
+
style={{ transition: 'all .25s ease-in-out', cursor: "pointer" }}
|
|
197
|
+
/>
|
|
198
|
+
{ state.visual.extraBubbleBorder &&
|
|
199
|
+
<circle
|
|
200
|
+
key={`circle-${stateName.replace(' ', '')}`}
|
|
201
|
+
data-tip={toolTip}
|
|
202
|
+
data-for="tooltip"
|
|
203
|
+
className="bubble"
|
|
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 }
|
|
207
|
+
fill={"transparent"}
|
|
208
|
+
stroke={"white"}
|
|
209
|
+
strokeWidth={.5}
|
|
210
|
+
fillOpacity={.4}
|
|
211
|
+
onPointerDown={(e) => {
|
|
212
|
+
pointerX = e.clientX;
|
|
213
|
+
pointerY = e.clientY;
|
|
214
|
+
}}
|
|
215
|
+
onPointerUp={(e) => {
|
|
216
|
+
if (pointerX && pointerY &&
|
|
217
|
+
e.clientX > (pointerX - clickTolerance) &&
|
|
218
|
+
e.clientX < (pointerX + clickTolerance) &&
|
|
219
|
+
e.clientY > (pointerY - clickTolerance) &&
|
|
220
|
+
e.clientY < (pointerY + clickTolerance)
|
|
221
|
+
) {
|
|
222
|
+
handleCircleClick(state)
|
|
223
|
+
pointerX = undefined;
|
|
224
|
+
pointerY = undefined;
|
|
225
|
+
}
|
|
226
|
+
}}
|
|
227
|
+
transform={transform}
|
|
228
|
+
style={{ transition: 'all .25s ease-in-out', cursor: "pointer" }}
|
|
229
|
+
/>
|
|
230
|
+
}
|
|
231
|
+
</>
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
return (
|
|
236
|
+
<g key={`group-${stateName.replace(' ', '')}`}>
|
|
237
|
+
{circle}
|
|
238
|
+
</g>
|
|
239
|
+
)
|
|
240
|
+
})
|
|
241
|
+
return bubbles;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
export default BubbleList
|
|
@@ -2,6 +2,7 @@ import React, { useState, useEffect, useContext } from 'react';
|
|
|
2
2
|
/** @jsx jsx */
|
|
3
3
|
import { jsx } from '@emotion/react'
|
|
4
4
|
import { supportedCities } from '../data/supported-geos';
|
|
5
|
+
import { scaleLinear } from 'd3-scale';
|
|
5
6
|
|
|
6
7
|
const CityList = (({
|
|
7
8
|
data,
|
|
@@ -11,7 +12,9 @@ const CityList = (({
|
|
|
11
12
|
displayGeoName,
|
|
12
13
|
applyLegendToRow,
|
|
13
14
|
projection,
|
|
14
|
-
titleCase
|
|
15
|
+
titleCase,
|
|
16
|
+
setSharedFilterValue,
|
|
17
|
+
isFilterValueSupported
|
|
15
18
|
}) => {
|
|
16
19
|
const [citiesData, setCitiesData] = useState({});
|
|
17
20
|
|
|
@@ -25,6 +28,18 @@ const CityList = (({
|
|
|
25
28
|
setCitiesData(citiesDictionary);
|
|
26
29
|
}, [data]);
|
|
27
30
|
|
|
31
|
+
if (state.general.type === 'bubble') {
|
|
32
|
+
const maxDataValue = Math.max(...state.data.map(d => d[state.columns.primary.name]))
|
|
33
|
+
const sortedRuntimeData = Object.values(data).sort((a, b) => a[state.columns.primary.name] < b[state.columns.primary.name] ? 1 : -1)
|
|
34
|
+
if (!sortedRuntimeData) return;
|
|
35
|
+
|
|
36
|
+
// Set bubble sizes
|
|
37
|
+
var size = scaleLinear()
|
|
38
|
+
.domain([1, maxDataValue])
|
|
39
|
+
.range([state.visual.minBubbleSize, state.visual.maxBubbleSize])
|
|
40
|
+
|
|
41
|
+
}
|
|
42
|
+
|
|
28
43
|
const cityList = Object.keys(citiesData).filter((c) => undefined !== data[c]);
|
|
29
44
|
|
|
30
45
|
const cities = cityList.map((city, i) => {
|
|
@@ -38,8 +53,8 @@ const CityList = (({
|
|
|
38
53
|
|
|
39
54
|
const styles = {
|
|
40
55
|
fill: legendColors[0],
|
|
41
|
-
|
|
42
|
-
stroke: 'rgba(0, 0, 0, 0.4)',
|
|
56
|
+
opacity: setSharedFilterValue && isFilterValueSupported && data[city][state.columns.geo.name] !== setSharedFilterValue ? .5 : 1,
|
|
57
|
+
stroke: setSharedFilterValue && isFilterValueSupported && data[city][state.columns.geo.name] === setSharedFilterValue ? 'rgba(0, 0, 0, 1)' : 'rgba(0, 0, 0, 0.4)',
|
|
43
58
|
'&:hover': {
|
|
44
59
|
fill: legendColors[1],
|
|
45
60
|
outline: 0
|
|
@@ -61,18 +76,38 @@ const CityList = (({
|
|
|
61
76
|
|
|
62
77
|
const radius = state.general.geoType === 'us' ? 8 : 4;
|
|
63
78
|
|
|
79
|
+
const additionalProps = {
|
|
80
|
+
fillOpacity: state.general.type === 'bubble' ? .4 : 1
|
|
81
|
+
}
|
|
82
|
+
|
|
64
83
|
const circle = (
|
|
65
84
|
<circle
|
|
66
85
|
data-tip={toolTip}
|
|
67
86
|
data-for="tooltip"
|
|
68
87
|
cx={0}
|
|
69
88
|
cy={0}
|
|
70
|
-
r={radius}
|
|
89
|
+
r={ state.general.type === 'bubble' ? size(geoData[state.columns.primary.name]) : radius}
|
|
71
90
|
title="Click for more information"
|
|
72
91
|
onClick={() => geoClickHandler(cityDisplayName, geoData)}
|
|
92
|
+
{...additionalProps}
|
|
73
93
|
/>
|
|
74
94
|
);
|
|
75
95
|
|
|
96
|
+
const pin = (
|
|
97
|
+
<path
|
|
98
|
+
className="marker"
|
|
99
|
+
d="M0,0l-8.8-17.7C-12.1-24.3-7.4-32,0-32h0c7.4,0,12.1,7.7,8.8,14.3L0,0z"
|
|
100
|
+
title="Click for more information"
|
|
101
|
+
onClick={() => geoClickHandler(cityDisplayName, geoData)}
|
|
102
|
+
data-tip={toolTip}
|
|
103
|
+
data-for="tooltip"
|
|
104
|
+
strokeWidth={2}
|
|
105
|
+
stroke={'black'}
|
|
106
|
+
{...additionalProps}
|
|
107
|
+
>
|
|
108
|
+
</path>
|
|
109
|
+
);
|
|
110
|
+
|
|
76
111
|
let transform = `translate(${projection(supportedCities[city])})`
|
|
77
112
|
|
|
78
113
|
return (
|
|
@@ -82,7 +117,8 @@ const CityList = (({
|
|
|
82
117
|
css={styles}
|
|
83
118
|
className="geo-point"
|
|
84
119
|
>
|
|
85
|
-
{circle}
|
|
120
|
+
{state.visual.cityStyle === 'circle' && circle }
|
|
121
|
+
{state.visual.cityStyle === 'pin' && pin }
|
|
86
122
|
</g>
|
|
87
123
|
);
|
|
88
124
|
});
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import React, { useState, useEffect, memo, useRef } from 'react';
|
|
2
|
+
import Loading from '@cdc/core/components/Loading';
|
|
2
3
|
/** @jsx jsx */
|
|
3
4
|
import { jsx } from '@emotion/react';
|
|
4
5
|
import ErrorBoundary from '@cdc/core/components/ErrorBoundary';
|
|
5
6
|
import { geoCentroid, geoPath } from 'd3-geo';
|
|
6
7
|
import { feature, mesh } from 'topojson-client';
|
|
7
8
|
import { CustomProjection } from '@visx/geo';
|
|
8
|
-
import colorPalettes from '
|
|
9
|
+
import colorPalettes from '../../../core/data/colorPalettes'
|
|
9
10
|
import { geoAlbersUsaTerritories } from 'd3-composite-projections';
|
|
10
|
-
import testJSON from '../data/
|
|
11
|
+
import testJSON from '../data/county-map.json';
|
|
11
12
|
import { abbrs } from '../data/abbreviations';
|
|
12
13
|
|
|
13
14
|
const offsets = {
|
|
@@ -57,17 +58,17 @@ const nudges = {
|
|
|
57
58
|
};
|
|
58
59
|
|
|
59
60
|
function CountyMapChecks(prevState, nextState) {
|
|
61
|
+
const equalNumberOptIn = prevState.state.general.equalNumberOptIn && nextState.state.general.equalNumberOptIn;
|
|
60
62
|
const equalColumnName = prevState.state.general.type && nextState.state.general.type;
|
|
61
63
|
const equalNavColumn = prevState.state.columns.navigate && nextState.state.columns.navigate;
|
|
62
64
|
const equalLegend = prevState.runtimeLegend === nextState.runtimeLegend;
|
|
63
65
|
const equalBorderColors = prevState.state.general.geoBorderColor === nextState.state.general.geoBorderColor; // update when geoborder color changes
|
|
64
66
|
const equalMapColors = prevState.state.color === nextState.state.color; // update when map colors change
|
|
65
67
|
const equalData = prevState.data === nextState.data; // update when data changes
|
|
66
|
-
return equalMapColors && equalData && equalBorderColors && equalLegend && equalColumnName && equalNavColumn ? true : false;
|
|
68
|
+
return equalMapColors && equalData && equalBorderColors && equalLegend && equalColumnName && equalNavColumn && equalNumberOptIn ? true : false;
|
|
67
69
|
}
|
|
68
70
|
|
|
69
71
|
const CountyMap = (props) => {
|
|
70
|
-
|
|
71
72
|
let mapData = states.concat(counties);
|
|
72
73
|
|
|
73
74
|
const {
|
|
@@ -79,6 +80,7 @@ const CountyMap = (props) => {
|
|
|
79
80
|
displayGeoName,
|
|
80
81
|
rebuildTooltips,
|
|
81
82
|
containerEl,
|
|
83
|
+
handleMapAriaLabels
|
|
82
84
|
} = props;
|
|
83
85
|
|
|
84
86
|
useEffect(() => {
|
|
@@ -514,10 +516,18 @@ const CountyMap = (props) => {
|
|
|
514
516
|
geosJsx.push(<FocusedStateBorder key="focused-border-key" />);
|
|
515
517
|
return geosJsx;
|
|
516
518
|
};
|
|
517
|
-
|
|
519
|
+
if(!data) <Loading />
|
|
518
520
|
return (
|
|
519
521
|
<ErrorBoundary component='CountyMap'>
|
|
520
|
-
<svg
|
|
522
|
+
<svg
|
|
523
|
+
viewBox={`0 0 ${WIDTH} ${HEIGHT}`}
|
|
524
|
+
preserveAspectRatio='xMinYMin'
|
|
525
|
+
className='svg-container'
|
|
526
|
+
data-scale={scale ? scale : ''}
|
|
527
|
+
data-translate={translate ? translate : ''}
|
|
528
|
+
role="img"
|
|
529
|
+
aria-label={handleMapAriaLabels(state)}
|
|
530
|
+
>
|
|
521
531
|
<rect
|
|
522
532
|
className='background center-container ocean'
|
|
523
533
|
width={WIDTH}
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
useTable, useSortBy, useResizeColumns, useBlockLayout
|
|
6
6
|
} from 'react-table';
|
|
7
7
|
import Papa from 'papaparse';
|
|
8
|
-
import ExternalIcon from '../images/external-link.svg';
|
|
8
|
+
import ExternalIcon from '../images/external-link.svg'; // TODO: Move to Icon component
|
|
9
9
|
|
|
10
10
|
import ErrorBoundary from '@cdc/core/components/ErrorBoundary';
|
|
11
11
|
import LegendCircle from '@cdc/core/components/LegendCircle';
|
|
@@ -30,7 +30,10 @@ const DataTable = (props) => {
|
|
|
30
30
|
applyLegendToRow,
|
|
31
31
|
displayGeoName,
|
|
32
32
|
navigationHandler,
|
|
33
|
-
viewport
|
|
33
|
+
viewport,
|
|
34
|
+
formatLegendLocation,
|
|
35
|
+
tabbingId,
|
|
36
|
+
setFilteredCountryCode
|
|
34
37
|
} = props;
|
|
35
38
|
|
|
36
39
|
const [expanded, setExpanded] = useState(expandDataTable);
|
|
@@ -39,7 +42,7 @@ const DataTable = (props) => {
|
|
|
39
42
|
|
|
40
43
|
const [ready, setReady] = useState(false)
|
|
41
44
|
|
|
42
|
-
const fileName = `${mapTitle}.csv`;
|
|
45
|
+
const fileName = `${mapTitle || 'data-table'}.csv`;
|
|
43
46
|
|
|
44
47
|
|
|
45
48
|
// Catch all sorting method used on load by default but also on user click
|
|
@@ -164,7 +167,6 @@ const DataTable = (props) => {
|
|
|
164
167
|
id={`${skipId}`}
|
|
165
168
|
data-html2canvas-ignore
|
|
166
169
|
role="button"
|
|
167
|
-
tabIndex="-1"
|
|
168
170
|
>
|
|
169
171
|
Download Data (CSV)
|
|
170
172
|
</a>
|
|
@@ -176,9 +178,9 @@ const DataTable = (props) => {
|
|
|
176
178
|
const newTableColumns = [];
|
|
177
179
|
|
|
178
180
|
Object.keys(columns).forEach((column) => {
|
|
179
|
-
if (columns[column].dataTable === true &&
|
|
181
|
+
if (columns[column].dataTable === true && columns[column].name) {
|
|
180
182
|
const newCol = {
|
|
181
|
-
Header: columns[column].label
|
|
183
|
+
Header: columns[column].label ? columns[column].label : columns[column].name,
|
|
182
184
|
id: column,
|
|
183
185
|
accessor: (row) => {
|
|
184
186
|
if (runtimeData) {
|
|
@@ -204,7 +206,11 @@ const DataTable = (props) => {
|
|
|
204
206
|
|
|
205
207
|
const legendColor = applyLegendToRow(rowObj);
|
|
206
208
|
|
|
207
|
-
|
|
209
|
+
if(state.general.geoType !== 'us-county') {
|
|
210
|
+
var labelValue = displayGeoName(row.original);
|
|
211
|
+
} else {
|
|
212
|
+
var labelValue = formatLegendLocation(row.original)
|
|
213
|
+
}
|
|
208
214
|
|
|
209
215
|
labelValue = getCellAnchor(labelValue, rowObj);
|
|
210
216
|
|
|
@@ -230,11 +236,11 @@ const DataTable = (props) => {
|
|
|
230
236
|
});
|
|
231
237
|
|
|
232
238
|
return newTableColumns;
|
|
233
|
-
}, [indexTitle, columns, runtimeData,
|
|
239
|
+
}, [indexTitle, columns, runtimeData,getCellAnchor,displayDataAsText,applyLegendToRow,customSort,displayGeoName,state.legend.specialClasses]);
|
|
234
240
|
|
|
235
241
|
const tableData = useMemo(
|
|
236
242
|
() => Object.keys(runtimeData).filter((key) => applyLegendToRow(runtimeData[key])).sort((a, b) => customSort(a, b)),
|
|
237
|
-
[
|
|
243
|
+
[ runtimeData, applyLegendToRow, customSort]
|
|
238
244
|
);
|
|
239
245
|
|
|
240
246
|
// Change accessibility label depending on expanded status
|
|
@@ -282,7 +288,7 @@ const DataTable = (props) => {
|
|
|
282
288
|
if(!state.data) return <Loading />
|
|
283
289
|
return (
|
|
284
290
|
<ErrorBoundary component="DataTable">
|
|
285
|
-
<section id={
|
|
291
|
+
<section id={tabbingId.replace('#', '')} className={`data-table-container ${viewport}`} aria-label={accessibilityLabel}>
|
|
286
292
|
<a id='skip-nav' className='cdcdataviz-sr-only-focusable' href={`#${skipId}`}>
|
|
287
293
|
Skip Navigation or Skip to Content
|
|
288
294
|
</a>
|
|
@@ -292,17 +298,17 @@ const DataTable = (props) => {
|
|
|
292
298
|
tabIndex="0"
|
|
293
299
|
onKeyDown={(e) => { if (e.keyCode === 13) { setExpanded(!expanded); } }}
|
|
294
300
|
>
|
|
295
|
-
|
|
301
|
+
|
|
296
302
|
{tableTitle}
|
|
297
303
|
</div>
|
|
298
|
-
<div
|
|
304
|
+
<div
|
|
299
305
|
className="table-container"
|
|
300
|
-
style={ { maxHeight: state.dataTable.limitHeight && `${state.dataTable.height}px`, overflowY: 'scroll' } }
|
|
306
|
+
style={ { maxHeight: state.dataTable.limitHeight && `${state.dataTable.height}px`, overflowY: 'scroll' } }
|
|
301
307
|
>
|
|
302
308
|
<table
|
|
303
|
-
height={expanded ? null : 0} {...getTableProps()}
|
|
304
|
-
aria-live="assertive"
|
|
305
|
-
className={expanded ? 'data-table' : 'data-table cdcdataviz-sr-only'}
|
|
309
|
+
height={expanded ? null : 0} {...getTableProps()}
|
|
310
|
+
aria-live="assertive"
|
|
311
|
+
className={expanded ? 'data-table' : 'data-table cdcdataviz-sr-only'}
|
|
306
312
|
hidden={!expanded}
|
|
307
313
|
aria-rowcount={state?.data.length ? state.data.length : '-1' }
|
|
308
314
|
>
|
|
@@ -311,7 +317,8 @@ const DataTable = (props) => {
|
|
|
311
317
|
{headerGroups.map((headerGroup) => (
|
|
312
318
|
<tr {...headerGroup.getHeaderGroupProps()}>
|
|
313
319
|
{headerGroup.headers.map((column) => (
|
|
314
|
-
<th
|
|
320
|
+
<th
|
|
321
|
+
tabIndex="0"
|
|
315
322
|
title={column.Header}
|
|
316
323
|
role="columnheader"
|
|
317
324
|
scope="col"
|
|
@@ -338,7 +345,7 @@ const DataTable = (props) => {
|
|
|
338
345
|
return (
|
|
339
346
|
<tr {...row.getRowProps()} role="row">
|
|
340
347
|
{row.cells.map((cell) => (
|
|
341
|
-
<td tabIndex="0" {...cell.getCellProps()} role="gridcell">
|
|
348
|
+
<td tabIndex="0" {...cell.getCellProps()} role="gridcell" onClick={ (e) => (state.general.type === 'bubble' && state.general.allowMapZoom && state.general.geoType === 'world') ? setFilteredCountryCode(cell.row.original) : true }>
|
|
342
349
|
{cell.render('Cell')}
|
|
343
350
|
</td>
|
|
344
351
|
))}
|