@cdc/map 2.6.2 → 2.6.3
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 +27 -27
- package/examples/default-county.json +105 -0
- package/examples/default-single-state.json +109 -0
- package/examples/default-usa.json +744 -603
- package/examples/example-city-state.json +474 -0
- package/examples/example-world-map.json +1596 -0
- package/examples/gender-rate-map.json +1 -0
- package/package.json +50 -47
- package/src/CdcMap.js +422 -159
- package/src/components/CityList.js +3 -2
- package/src/components/CountyMap.js +556 -0
- package/src/components/DataTable.js +73 -19
- package/src/components/EditorPanel.js +2088 -1230
- package/src/components/Sidebar.js +5 -5
- package/src/components/SingleStateMap.js +326 -0
- package/src/components/UsaMap.js +20 -3
- package/src/data/abbreviations.js +57 -0
- package/src/data/color-palettes.js +10 -1
- package/src/data/county-map-halfquality.json +58453 -0
- package/src/data/county-map-quarterquality.json +1 -0
- package/src/data/county-topo.json +1 -0
- package/src/data/dfc-map.json +1 -0
- package/src/data/initial-state.js +2 -2
- package/src/data/newtest.json +1 -0
- package/src/data/state-abbreviations.js +60 -0
- package/src/data/supported-geos.js +3504 -151
- package/src/data/test.json +1 -0
- package/src/hooks/useActiveElement.js +19 -0
- package/src/index.html +27 -20
- package/src/index.js +8 -4
- package/src/scss/datatable.scss +2 -1
- package/src/scss/main.scss +10 -1
- package/src/scss/map.scss +153 -123
- package/src/scss/sidebar.scss +0 -1
- package/uploads/upload-example-city-state.json +392 -0
- package/uploads/upload-example-world-data.json +1490 -0
- package/LICENSE +0 -201
|
@@ -10,7 +10,8 @@ const CityList = (({
|
|
|
10
10
|
applyTooltipsToGeo,
|
|
11
11
|
displayGeoName,
|
|
12
12
|
applyLegendToRow,
|
|
13
|
-
projection
|
|
13
|
+
projection,
|
|
14
|
+
titleCase
|
|
14
15
|
}) => {
|
|
15
16
|
const [citiesData, setCitiesData] = useState({});
|
|
16
17
|
|
|
@@ -27,7 +28,7 @@ const CityList = (({
|
|
|
27
28
|
const cityList = Object.keys(citiesData).filter((c) => undefined !== data[c]);
|
|
28
29
|
|
|
29
30
|
const cities = cityList.map((city, i) => {
|
|
30
|
-
const cityDisplayName = displayGeoName(city);
|
|
31
|
+
const cityDisplayName = titleCase( displayGeoName(city) );
|
|
31
32
|
|
|
32
33
|
const legendColors = applyLegendToRow(data[city]);
|
|
33
34
|
|
|
@@ -0,0 +1,556 @@
|
|
|
1
|
+
import React, { useState, useEffect, memo, useRef } from 'react';
|
|
2
|
+
/** @jsx jsx */
|
|
3
|
+
import { jsx } from '@emotion/react';
|
|
4
|
+
import ErrorBoundary from '@cdc/core/components/ErrorBoundary';
|
|
5
|
+
import { geoCentroid, geoPath } from 'd3-geo';
|
|
6
|
+
import { feature, mesh } from 'topojson-client';
|
|
7
|
+
import { CustomProjection } from '@visx/geo';
|
|
8
|
+
import colorPalettes from '../data/color-palettes';
|
|
9
|
+
import { geoAlbersUsaTerritories } from 'd3-composite-projections';
|
|
10
|
+
import testJSON from '../data/dfc-map.json';
|
|
11
|
+
import { abbrs } from '../data/abbreviations';
|
|
12
|
+
|
|
13
|
+
const offsets = {
|
|
14
|
+
Vermont: [50, -8],
|
|
15
|
+
'New Hampshire': [34, 5],
|
|
16
|
+
Massachusetts: [30, -5],
|
|
17
|
+
'Rhode Island': [28, 4],
|
|
18
|
+
Connecticut: [35, 16],
|
|
19
|
+
'New Jersey': [42, 0],
|
|
20
|
+
Delaware: [33, 0],
|
|
21
|
+
Maryland: [47, 10],
|
|
22
|
+
'District of Columbia': [30, 20],
|
|
23
|
+
'Puerto Rico': [10, -20],
|
|
24
|
+
'Virgin Islands': [10, -10],
|
|
25
|
+
Guam: [10, -5],
|
|
26
|
+
'American Samoa': [10, 0],
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// SVG ITEMS
|
|
30
|
+
const WIDTH = 880;
|
|
31
|
+
const HEIGHT = 500;
|
|
32
|
+
const PADDING = 25;
|
|
33
|
+
|
|
34
|
+
// DATA
|
|
35
|
+
let { features: counties } = feature(testJSON, testJSON.objects.counties);
|
|
36
|
+
let { features: states } = feature(testJSON, testJSON.objects.states);
|
|
37
|
+
|
|
38
|
+
// CONSTANTS
|
|
39
|
+
const STATE_BORDER = '#c0cad4';
|
|
40
|
+
const STATE_INACTIVE_FILL = '#F4F7FA';
|
|
41
|
+
|
|
42
|
+
// CREATE STATE LINES
|
|
43
|
+
const projection = geoAlbersUsaTerritories().translate([WIDTH / 2, HEIGHT / 2]);
|
|
44
|
+
const path = geoPath().projection(projection);
|
|
45
|
+
const stateLines = path(mesh(testJSON, testJSON.objects.states));
|
|
46
|
+
|
|
47
|
+
const nudges = {
|
|
48
|
+
'US-FL': [15, 3],
|
|
49
|
+
'US-AK': [0, -8],
|
|
50
|
+
'US-CA': [-10, 0],
|
|
51
|
+
'US-NY': [5, 0],
|
|
52
|
+
'US-MI': [13, 20],
|
|
53
|
+
'US-LA': [-10, -3],
|
|
54
|
+
'US-HI': [-10, 10],
|
|
55
|
+
'US-ID': [0, 10],
|
|
56
|
+
'US-WV': [-2, 2],
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
function CountyMapChecks(prevState, nextState) {
|
|
60
|
+
const equalColumnName = prevState.state.general.type && nextState.state.general.type;
|
|
61
|
+
const equalNavColumn = prevState.state.columns.navigate && nextState.state.columns.navigate;
|
|
62
|
+
const equalLegend = prevState.runtimeLegend === nextState.runtimeLegend;
|
|
63
|
+
const equalBorderColors = prevState.state.general.geoBorderColor === nextState.state.general.geoBorderColor; // update when geoborder color changes
|
|
64
|
+
const equalMapColors = prevState.state.color === nextState.state.color; // update when map colors change
|
|
65
|
+
const equalData = prevState.data === nextState.data; // update when data changes
|
|
66
|
+
return equalMapColors && equalData && equalBorderColors && equalLegend && equalColumnName && equalNavColumn ? true : false;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const CountyMap = (props) => {
|
|
70
|
+
|
|
71
|
+
let mapData = states.concat(counties);
|
|
72
|
+
|
|
73
|
+
const {
|
|
74
|
+
state,
|
|
75
|
+
applyTooltipsToGeo,
|
|
76
|
+
data,
|
|
77
|
+
geoClickHandler,
|
|
78
|
+
applyLegendToRow,
|
|
79
|
+
displayGeoName,
|
|
80
|
+
rebuildTooltips,
|
|
81
|
+
containerEl,
|
|
82
|
+
} = props;
|
|
83
|
+
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
if(containerEl) {
|
|
86
|
+
if (containerEl.className.indexOf('loaded') === -1) {
|
|
87
|
+
containerEl.className += ' loaded';
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Use State
|
|
93
|
+
const [scale, setScale] = useState(0.85);
|
|
94
|
+
const [startingLineWidth, setStartingLineWidth] = useState(1.3);
|
|
95
|
+
const [translate, setTranslate] = useState([0, 0]);
|
|
96
|
+
const [mapColorPalette, setMapColorPalette] = useState(colorPalettes[state.color] || '#fff');
|
|
97
|
+
const [focusedState, setFocusedState] = useState(null);
|
|
98
|
+
const [showLabel, setShowLabels] = useState(true);
|
|
99
|
+
|
|
100
|
+
const resetButton = useRef();
|
|
101
|
+
const focusedBorderPath = useRef();
|
|
102
|
+
const stateLinesPath = useRef();
|
|
103
|
+
const mapGroup = useRef();
|
|
104
|
+
|
|
105
|
+
let focusedBorderColor = mapColorPalette[3];
|
|
106
|
+
let geoStrokeColor = state.general.geoBorderColor === 'darkGray' ? 'rgba(0, 0, 0, 0.2)' : 'rgba(255,255,255,0.7)';
|
|
107
|
+
|
|
108
|
+
// Use Effect
|
|
109
|
+
useEffect(() => rebuildTooltips());
|
|
110
|
+
|
|
111
|
+
const geoLabel = (geo, projection) => {
|
|
112
|
+
let [x, y] = projection(geoCentroid(geo));
|
|
113
|
+
let abbr = abbrs[geo.properties.name];
|
|
114
|
+
if (abbr === 'NJ') x += 3;
|
|
115
|
+
if (undefined === abbr) return null;
|
|
116
|
+
let [dx, dy] = offsets[geo.properties.name];
|
|
117
|
+
|
|
118
|
+
return (
|
|
119
|
+
<>
|
|
120
|
+
<line className='abbrLine' x1={x} y1={y} x2={x + dx} y2={y + dy} stroke='black' strokeWidth={0.85} />
|
|
121
|
+
<text
|
|
122
|
+
className='abbrText'
|
|
123
|
+
x={4}
|
|
124
|
+
strokeWidth='0'
|
|
125
|
+
fontSize={13}
|
|
126
|
+
style={{ fill: '#202020' }}
|
|
127
|
+
alignmentBaseline='middle'
|
|
128
|
+
transform={`translate(${x + dx}, ${y + dy})`}
|
|
129
|
+
>
|
|
130
|
+
{abbr}
|
|
131
|
+
</text>
|
|
132
|
+
</>
|
|
133
|
+
);
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const focusGeo = (geoKey, geo) => {
|
|
137
|
+
if (!geoKey) {
|
|
138
|
+
console.log('County Map: no geoKey provided to focusGeo');
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// 1) Get the state the county is in.
|
|
143
|
+
let myState = states.find((s) => s.id === geoKey);
|
|
144
|
+
|
|
145
|
+
// 2) Set projections translation & scale to the geographic center of the passed geo.
|
|
146
|
+
const projection = geoAlbersUsaTerritories().translate([WIDTH / 2, HEIGHT / 2]);
|
|
147
|
+
const newProjection = projection.fitExtent(
|
|
148
|
+
[
|
|
149
|
+
[PADDING, PADDING],
|
|
150
|
+
[WIDTH - PADDING, HEIGHT - PADDING],
|
|
151
|
+
],
|
|
152
|
+
myState
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
// 3) Gets the new scale
|
|
156
|
+
const newScale = newProjection.scale();
|
|
157
|
+
const hypot = Math.hypot(880, 500);
|
|
158
|
+
const newScaleWithHypot = newScale / 1070;
|
|
159
|
+
|
|
160
|
+
// 4) Pull the x & y out, divide by half the viewport for some reason
|
|
161
|
+
let [x, y] = newProjection.translate();
|
|
162
|
+
x = x - WIDTH / 2;
|
|
163
|
+
y = y - HEIGHT / 2;
|
|
164
|
+
|
|
165
|
+
// 5) Debug if needed
|
|
166
|
+
const debug = {
|
|
167
|
+
width: WIDTH,
|
|
168
|
+
height: HEIGHT,
|
|
169
|
+
beginX: 0,
|
|
170
|
+
beginY: 0,
|
|
171
|
+
hypot: hypot,
|
|
172
|
+
x: x,
|
|
173
|
+
y: y,
|
|
174
|
+
newScale: newScale,
|
|
175
|
+
newScaleWithHypot: newScaleWithHypot,
|
|
176
|
+
geoKey: geoKey,
|
|
177
|
+
geo: geo,
|
|
178
|
+
};
|
|
179
|
+
//console.table(debug)
|
|
180
|
+
|
|
181
|
+
mapGroup.current.setAttribute('transform', `translate(${[x, y]}) scale(${newScaleWithHypot})`);
|
|
182
|
+
resetButton.current.style.display = 'block';
|
|
183
|
+
|
|
184
|
+
// set the states border
|
|
185
|
+
let allStates = document.querySelectorAll('.state path');
|
|
186
|
+
let allCounties = document.querySelectorAll('.county path');
|
|
187
|
+
let currentState = document.querySelector(`.state--${myState.id}`);
|
|
188
|
+
let otherStates = document.querySelectorAll(`.state:not(.state--${myState.id})`);
|
|
189
|
+
let svgContainer = document.querySelector('.svg-container')
|
|
190
|
+
svgContainer.setAttribute('data-scaleZoom', newScaleWithHypot)
|
|
191
|
+
|
|
192
|
+
const state = testJSON.objects.states.geometries.filter((el, index) => {
|
|
193
|
+
return el.id === myState.id;
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
const focusedStateLine = path(mesh(testJSON, state[0]));
|
|
197
|
+
|
|
198
|
+
currentState.style.display = 'none';
|
|
199
|
+
|
|
200
|
+
allStates.forEach((state) => (state.style.strokeWidth = 0.75 / newScaleWithHypot));
|
|
201
|
+
allCounties.forEach((county) => (county.style.strokeWidth = 0.75 / newScaleWithHypot));
|
|
202
|
+
otherStates.forEach((el) => (el.style.display = 'block'));
|
|
203
|
+
|
|
204
|
+
// State Line Updates
|
|
205
|
+
stateLinesPath.current.setAttribute('stroke-width', 0.75 / newScaleWithHypot);
|
|
206
|
+
stateLinesPath.current.setAttribute('stroke', geoStrokeColor);
|
|
207
|
+
|
|
208
|
+
// Set Focus Border
|
|
209
|
+
focusedBorderPath.current.style.display = 'block';
|
|
210
|
+
focusedBorderPath.current.setAttribute('d', focusedStateLine);
|
|
211
|
+
//focusedBorderPath.current.setAttribute('stroke-width', 0.75 / newScaleWithHypot);
|
|
212
|
+
//focusedBorderPath.current.setAttribute('stroke', focusedBorderColor)
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
const onReset = (e) => {
|
|
216
|
+
e.preventDefault();
|
|
217
|
+
const svg = document.querySelector('.svg-container')
|
|
218
|
+
|
|
219
|
+
svg.setAttribute('data-scaleZoom', 0)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
const allStates = document.querySelectorAll('.state path');
|
|
223
|
+
const allCounties = document.querySelectorAll('.county path');
|
|
224
|
+
|
|
225
|
+
stateLinesPath.current.setAttribute('stroke', geoStrokeColor);
|
|
226
|
+
stateLinesPath.current.setAttribute('stroke-width', startingLineWidth);
|
|
227
|
+
|
|
228
|
+
let otherStates = document.querySelectorAll(`.state--inactive`);
|
|
229
|
+
otherStates.forEach((el) => (el.style.display = 'none'));
|
|
230
|
+
allCounties.forEach((el) => (el.style.strokeWidth = 0.85));
|
|
231
|
+
allStates.forEach((state) => state.setAttribute('stroke-width', .75 / .85 ));
|
|
232
|
+
|
|
233
|
+
mapGroup.current.setAttribute('transform', `translate(${[0, 0]}) scale(${0.85})`);
|
|
234
|
+
|
|
235
|
+
// reset button
|
|
236
|
+
resetButton.current.style.display = 'none';
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
function setStateLeave() {
|
|
240
|
+
focusedBorderPath.current.setAttribute('d', '');
|
|
241
|
+
focusedBorderPath.current.setAttribute('stroke', '');
|
|
242
|
+
focusedBorderPath.current.setAttribute('stroke-width', 0.75 / scale);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function setStateEnter(id) {
|
|
246
|
+
const svg = document.querySelector('.svg-container')
|
|
247
|
+
const scale = svg.getAttribute('data-scaleZoom');
|
|
248
|
+
|
|
249
|
+
let myState = id.substring(0, 2);
|
|
250
|
+
const allStates = document.querySelectorAll('.state path');
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
let state = testJSON.objects.states.geometries.filter((el, index) => {
|
|
254
|
+
return el.id === myState;
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
let focusedStateLine = path(mesh(testJSON, state[0]));
|
|
258
|
+
focusedBorderPath.current.style.display = 'block';
|
|
259
|
+
focusedBorderPath.current.setAttribute('d', focusedStateLine);
|
|
260
|
+
focusedBorderPath.current.setAttribute('stroke', '#000');
|
|
261
|
+
|
|
262
|
+
if(scale) {
|
|
263
|
+
allStates.forEach( state => state.setAttribute('stroke-width', 0.75 / scale))
|
|
264
|
+
focusedBorderPath.current.setAttribute('stroke-width', 0.75 / scale );
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const StateLines = memo(({ stateLines, lineWidth, geoStrokeColor }) => {
|
|
270
|
+
return (
|
|
271
|
+
<g className='stateLines' key='state-line'>
|
|
272
|
+
<path
|
|
273
|
+
id='stateLinesPath'
|
|
274
|
+
ref={stateLinesPath}
|
|
275
|
+
d={stateLines}
|
|
276
|
+
strokeWidth={lineWidth}
|
|
277
|
+
stroke={geoStrokeColor}
|
|
278
|
+
fill='none'
|
|
279
|
+
fillOpacity='1'
|
|
280
|
+
/>
|
|
281
|
+
</g>
|
|
282
|
+
);
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
const FocusedStateBorder = memo(() => {
|
|
286
|
+
return (
|
|
287
|
+
<g id='focusedBorder' key='focusedStateBorder'>
|
|
288
|
+
<path
|
|
289
|
+
ref={focusedBorderPath}
|
|
290
|
+
d=''
|
|
291
|
+
strokeWidth=''
|
|
292
|
+
stroke={focusedBorderColor}
|
|
293
|
+
fill='none'
|
|
294
|
+
fillOpacity='1'
|
|
295
|
+
/>
|
|
296
|
+
</g>
|
|
297
|
+
);
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
const CountyOutput = memo(({ geographies, counties }) => {
|
|
301
|
+
let output = [];
|
|
302
|
+
output.push(
|
|
303
|
+
counties.map(({ feature: geo, path = '' }) => {
|
|
304
|
+
const key = geo.id + '-group';
|
|
305
|
+
|
|
306
|
+
// COUNTY GROUPS
|
|
307
|
+
let styles = {
|
|
308
|
+
fillOpacity: '1',
|
|
309
|
+
fill: '#E6E6E6',
|
|
310
|
+
cursor: 'default',
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
// Map the name from the geo data with the appropriate key for the processed data
|
|
314
|
+
let geoKey = geo.id;
|
|
315
|
+
|
|
316
|
+
if (!geoKey) return null;
|
|
317
|
+
|
|
318
|
+
const geoData = data[geoKey];
|
|
319
|
+
|
|
320
|
+
let legendColors;
|
|
321
|
+
|
|
322
|
+
// Once we receive data for this geographic item, setup variables.
|
|
323
|
+
if (geoData !== undefined) {
|
|
324
|
+
legendColors = applyLegendToRow(geoData);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const geoDisplayName = displayGeoName(geoKey);
|
|
328
|
+
|
|
329
|
+
// For some reason, these two geos are breaking the display.
|
|
330
|
+
if (geoDisplayName === 'Franklin City' || geoDisplayName === 'Waynesboro') return null;
|
|
331
|
+
|
|
332
|
+
// If a legend applies, return it with appropriate information.
|
|
333
|
+
if (legendColors && legendColors[0] !== '#000000') {
|
|
334
|
+
const tooltip = applyTooltipsToGeo(geoDisplayName, geoData);
|
|
335
|
+
|
|
336
|
+
styles = {
|
|
337
|
+
fill: legendColors[0],
|
|
338
|
+
cursor: 'default',
|
|
339
|
+
'&:hover': {
|
|
340
|
+
fill: legendColors[1],
|
|
341
|
+
},
|
|
342
|
+
'&:active': {
|
|
343
|
+
fill: legendColors[2],
|
|
344
|
+
},
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
// When to add pointer cursor
|
|
348
|
+
if (
|
|
349
|
+
(state.columns.navigate && geoData[state.columns.navigate.name]) ||
|
|
350
|
+
state.tooltips.appearanceType === 'hover'
|
|
351
|
+
) {
|
|
352
|
+
styles.cursor = 'pointer';
|
|
353
|
+
}
|
|
354
|
+
let stateFipsCode = geoData[state.columns.geo.name].substring(0, 2);
|
|
355
|
+
|
|
356
|
+
return (
|
|
357
|
+
<g
|
|
358
|
+
tabIndex="-1"
|
|
359
|
+
data-for='tooltip'
|
|
360
|
+
data-tip={tooltip}
|
|
361
|
+
key={`county--${key}`}
|
|
362
|
+
className={`county county--${geoDisplayName.split(' ').join('')} county--${
|
|
363
|
+
geoData[state.columns.geo.name]
|
|
364
|
+
}`}
|
|
365
|
+
css={styles}
|
|
366
|
+
onMouseEnter={() => {
|
|
367
|
+
setStateEnter(geo.id);
|
|
368
|
+
}}
|
|
369
|
+
onMouseLeave={() => {
|
|
370
|
+
setStateLeave();
|
|
371
|
+
}}
|
|
372
|
+
onClick={
|
|
373
|
+
// default
|
|
374
|
+
(e) => {
|
|
375
|
+
e.stopPropagation();
|
|
376
|
+
e.nativeEvent.stopImmediatePropagation();
|
|
377
|
+
geoClickHandler(geoDisplayName, geoData);
|
|
378
|
+
focusGeo(stateFipsCode, geo);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
>
|
|
382
|
+
<path
|
|
383
|
+
tabIndex={-1}
|
|
384
|
+
className={`county county--${geoDisplayName}`}
|
|
385
|
+
stroke={geoStrokeColor}
|
|
386
|
+
d={path}
|
|
387
|
+
strokeWidth='.5'
|
|
388
|
+
/>
|
|
389
|
+
</g>
|
|
390
|
+
);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// default county
|
|
394
|
+
return (
|
|
395
|
+
<g
|
|
396
|
+
key={`county--default-${key}`}
|
|
397
|
+
className={`county county--${geoDisplayName}`}
|
|
398
|
+
css={styles}
|
|
399
|
+
strokeWidth=''
|
|
400
|
+
onMouseEnter={() => {
|
|
401
|
+
setStateEnter(geo.id);
|
|
402
|
+
}}
|
|
403
|
+
onMouseLeave={() => {
|
|
404
|
+
setStateLeave();
|
|
405
|
+
}}
|
|
406
|
+
onClick={
|
|
407
|
+
// default
|
|
408
|
+
(e) => {
|
|
409
|
+
e.stopPropagation();
|
|
410
|
+
e.nativeEvent.stopImmediatePropagation();
|
|
411
|
+
let countyFipsCode = geo.id;
|
|
412
|
+
let stateFipsCode = countyFipsCode.substring(0, 2);
|
|
413
|
+
focusGeo(stateFipsCode, geo);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
>
|
|
417
|
+
<path tabIndex={-1} className='single-geo' stroke={geoStrokeColor} d={path} strokeWidth='.85' />
|
|
418
|
+
</g>
|
|
419
|
+
);
|
|
420
|
+
})
|
|
421
|
+
);
|
|
422
|
+
return output;
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
const StateOutput = memo(({ geographies, states }) => {
|
|
426
|
+
let output = [];
|
|
427
|
+
output.push(
|
|
428
|
+
states.map(({ feature: geo, path = '' }) => {
|
|
429
|
+
const key = geo.id + '-group';
|
|
430
|
+
|
|
431
|
+
// Map the name from the geo data with the appropriate key for the processed data
|
|
432
|
+
let geoKey = geo.id;
|
|
433
|
+
|
|
434
|
+
if (!geoKey) return;
|
|
435
|
+
|
|
436
|
+
const geoData = data[geoKey];
|
|
437
|
+
|
|
438
|
+
let legendColors;
|
|
439
|
+
|
|
440
|
+
// Once we receive data for this geographic item, setup variables.
|
|
441
|
+
if (geoData !== undefined) {
|
|
442
|
+
legendColors = applyLegendToRow(geoData);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
const geoDisplayName = displayGeoName(geoKey);
|
|
446
|
+
|
|
447
|
+
let stateStyles = {
|
|
448
|
+
cursor: 'default',
|
|
449
|
+
stroke: STATE_BORDER,
|
|
450
|
+
strokeWidth: 0.75 / scale,
|
|
451
|
+
display: !focusedState ? 'none' : focusedState && focusedState !== geo.id ? 'block' : 'none',
|
|
452
|
+
fill: focusedState && focusedState !== geo.id ? STATE_INACTIVE_FILL : 'none',
|
|
453
|
+
};
|
|
454
|
+
|
|
455
|
+
let stateSelectedStyles = {
|
|
456
|
+
fillOpacity: 1,
|
|
457
|
+
cursor: 'default',
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
let stateClasses = ['state', `state--${geo.properties.name}`, `state--${geo.id}`];
|
|
461
|
+
focusedState === geo.id ? stateClasses.push('state--focused') : stateClasses.push('state--inactive');
|
|
462
|
+
|
|
463
|
+
return (
|
|
464
|
+
<React.Fragment key={`state--${key}`}>
|
|
465
|
+
<g key={`state--${key}`} className={stateClasses.join(' ')} style={stateStyles} tabIndex="-1">
|
|
466
|
+
<>
|
|
467
|
+
<path
|
|
468
|
+
tabIndex={-1}
|
|
469
|
+
className='state-path'
|
|
470
|
+
d={path}
|
|
471
|
+
fillOpacity={`${focusedState !== geo.id ? '1' : '0'}`}
|
|
472
|
+
fill={STATE_INACTIVE_FILL}
|
|
473
|
+
css={stateSelectedStyles}
|
|
474
|
+
onClick={(e) => {
|
|
475
|
+
e.stopPropagation();
|
|
476
|
+
e.nativeEvent.stopImmediatePropagation();
|
|
477
|
+
focusGeo(geo.id, geo);
|
|
478
|
+
}}
|
|
479
|
+
onMouseEnter={(e) => {
|
|
480
|
+
e.target.attributes.fill.value = colorPalettes[state.color][3];
|
|
481
|
+
}}
|
|
482
|
+
onMouseLeave={(e) => {
|
|
483
|
+
e.target.attributes.fill.value = STATE_INACTIVE_FILL;
|
|
484
|
+
}}
|
|
485
|
+
/>
|
|
486
|
+
</>
|
|
487
|
+
</g>
|
|
488
|
+
<g key={`label--${key}`}>
|
|
489
|
+
{offsets[geo.properties.name] &&
|
|
490
|
+
geoLabel(geo, geoAlbersUsaTerritories().translate([WIDTH / 2, HEIGHT / 2]))}
|
|
491
|
+
</g>
|
|
492
|
+
</React.Fragment>
|
|
493
|
+
);
|
|
494
|
+
})
|
|
495
|
+
);
|
|
496
|
+
return output;
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
// Constructs and displays markup for all geos on the map (except territories right now)
|
|
500
|
+
const constructGeoJsx = (geographies, projection) => {
|
|
501
|
+
const states = geographies.slice(0, 56);
|
|
502
|
+
const counties = geographies.slice(56);
|
|
503
|
+
let geosJsx = [];
|
|
504
|
+
geosJsx.push(<CountyOutput geographies={geographies} counties={counties} key="county-key" />);
|
|
505
|
+
geosJsx.push(<StateOutput geographies={geographies} states={states} key="state-key" />);
|
|
506
|
+
geosJsx.push(
|
|
507
|
+
<StateLines
|
|
508
|
+
key='stateLines'
|
|
509
|
+
lineWidth={startingLineWidth}
|
|
510
|
+
geoStrokeColor={geoStrokeColor}
|
|
511
|
+
stateLines={stateLines}
|
|
512
|
+
/>
|
|
513
|
+
);
|
|
514
|
+
geosJsx.push(<FocusedStateBorder key="focused-border-key" />);
|
|
515
|
+
return geosJsx;
|
|
516
|
+
};
|
|
517
|
+
|
|
518
|
+
return (
|
|
519
|
+
<ErrorBoundary component='CountyMap'>
|
|
520
|
+
<svg viewBox={`0 0 ${WIDTH} ${HEIGHT}`} preserveAspectRatio='xMinYMin' className='svg-container' data-scale={scale ? scale : ''} data-translate={translate ? translate : ''}>
|
|
521
|
+
<rect
|
|
522
|
+
className='background center-container ocean'
|
|
523
|
+
width={WIDTH}
|
|
524
|
+
height={HEIGHT}
|
|
525
|
+
fillOpacity={1}
|
|
526
|
+
fill='white'
|
|
527
|
+
onClick={(e) => onReset(e)}
|
|
528
|
+
tabIndex="0"
|
|
529
|
+
></rect>
|
|
530
|
+
<CustomProjection
|
|
531
|
+
data={mapData}
|
|
532
|
+
translate={[WIDTH / 2, HEIGHT / 2]}
|
|
533
|
+
projection={geoAlbersUsaTerritories}
|
|
534
|
+
>
|
|
535
|
+
{({ features, projection }) => {
|
|
536
|
+
return (
|
|
537
|
+
<g
|
|
538
|
+
ref={mapGroup}
|
|
539
|
+
className='countyMapGroup'
|
|
540
|
+
transform={`translate(${translate}) scale(${scale})`}
|
|
541
|
+
key='countyMapGroup'
|
|
542
|
+
>
|
|
543
|
+
{constructGeoJsx(features, geoAlbersUsaTerritories)}
|
|
544
|
+
</g>
|
|
545
|
+
);
|
|
546
|
+
}}
|
|
547
|
+
</CustomProjection>
|
|
548
|
+
</svg>
|
|
549
|
+
<button className={`btn btn--reset`} onClick={onReset} ref={resetButton} style={{ display: 'none' }} tabIndex="0">
|
|
550
|
+
Reset Zoom
|
|
551
|
+
</button>
|
|
552
|
+
</ErrorBoundary>
|
|
553
|
+
);
|
|
554
|
+
};
|
|
555
|
+
|
|
556
|
+
export default memo(CountyMap, CountyMapChecks);
|