@cdc/map 2.6.3 → 4.22.10-alpha.1
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/LICENSE +201 -0
- 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-geocode.json +746 -0
- 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/city-state2.json +434 -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 +1384 -1075
- package/src/components/BubbleList.js +244 -0
- package/src/components/CityList.js +79 -17
- package/src/components/CountyMap.js +104 -44
- package/src/components/DataTable.js +32 -22
- package/src/components/EditorPanel.js +977 -414
- 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 +178 -249
- package/src/components/UsaMap.js +104 -36
- package/src/components/UsaRegionMap.js +320 -0
- package/src/components/WorldMap.js +117 -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 +23 -3
- package/src/data/state-coordinates.js +55 -0
- package/src/data/supported-geos.js +101 -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 +8 -4
- package/src/scss/editor-panel.scss +78 -57
- package/src/scss/main.scss +1 -6
- package/src/scss/map.scss +126 -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/components/UsaMap.js
CHANGED
|
@@ -9,6 +9,8 @@ import hexTopoJSON from '../data/us-hex-topo.json';
|
|
|
9
9
|
import { AlbersUsa, Mercator } from '@visx/geo';
|
|
10
10
|
import chroma from 'chroma-js';
|
|
11
11
|
import CityList from './CityList';
|
|
12
|
+
import BubbleList from './BubbleList';
|
|
13
|
+
import { supportedCities, supportedStates } from '../data/supported-geos';
|
|
12
14
|
|
|
13
15
|
const { features: unitedStates } = feature(topoJSON, topoJSON.objects.states)
|
|
14
16
|
const { features: unitedStatesHex } = feature(hexTopoJSON, hexTopoJSON.objects.states)
|
|
@@ -68,9 +70,32 @@ const UsaMap = (props) => {
|
|
|
68
70
|
displayGeoName,
|
|
69
71
|
supportedTerritories,
|
|
70
72
|
rebuildTooltips,
|
|
71
|
-
titleCase
|
|
73
|
+
titleCase,
|
|
74
|
+
handleCircleClick,
|
|
75
|
+
setSharedFilterValue,
|
|
76
|
+
handleMapAriaLabels
|
|
72
77
|
} = props;
|
|
73
78
|
|
|
79
|
+
let isFilterValueSupported = false;
|
|
80
|
+
|
|
81
|
+
if(setSharedFilterValue){
|
|
82
|
+
Object.keys(supportedStates).forEach(supportedState => {
|
|
83
|
+
if(supportedStates[supportedState].indexOf(setSharedFilterValue.toUpperCase()) !== -1){
|
|
84
|
+
isFilterValueSupported = true;
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
Object.keys(supportedTerritories).forEach(supportedTerritory => {
|
|
88
|
+
if(supportedTerritories[supportedTerritory].indexOf(setSharedFilterValue.toUpperCase()) !== -1){
|
|
89
|
+
isFilterValueSupported = true;
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
Object.keys(supportedCities).forEach(supportedCity => {
|
|
93
|
+
if(supportedCity === setSharedFilterValue.toUpperCase()){
|
|
94
|
+
isFilterValueSupported = true;
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
74
99
|
// "Choose State" options
|
|
75
100
|
const [extent, setExtent] = useState(null)
|
|
76
101
|
const [focusedStates, setFocusedStates] = useState(unitedStates)
|
|
@@ -123,7 +148,7 @@ const UsaMap = (props) => {
|
|
|
123
148
|
|
|
124
149
|
if (legendColors) {
|
|
125
150
|
// Use white text if the background is dark, and dark grey if it's light
|
|
126
|
-
if (chroma.contrast(textColor, legendColors[0]) <
|
|
151
|
+
if (chroma.contrast(textColor, legendColors[0]) < 3.5) {
|
|
127
152
|
textColor = '#202020';
|
|
128
153
|
}
|
|
129
154
|
|
|
@@ -137,6 +162,8 @@ const UsaMap = (props) => {
|
|
|
137
162
|
styles = {
|
|
138
163
|
color: textColor,
|
|
139
164
|
fill: legendColors[0],
|
|
165
|
+
opacity: setSharedFilterValue && isFilterValueSupported && setSharedFilterValue !== territoryData[state.columns.geo.name] ? .5 : 1,
|
|
166
|
+
stroke: setSharedFilterValue && isFilterValueSupported && setSharedFilterValue === territoryData[state.columns.geo.name] ? 'rgba(0, 0, 0, 1)' : geoStrokeColor,
|
|
140
167
|
cursor: needsPointer ? 'pointer' : 'default',
|
|
141
168
|
'&:hover': {
|
|
142
169
|
fill: legendColors[1],
|
|
@@ -153,7 +180,6 @@ const UsaMap = (props) => {
|
|
|
153
180
|
text={styles.color}
|
|
154
181
|
data-tip={toolTip}
|
|
155
182
|
data-for="tooltip"
|
|
156
|
-
stroke={geoStrokeColor}
|
|
157
183
|
strokeWidth={1.5}
|
|
158
184
|
onClick={() => geoClickHandler(territory, territoryData)}
|
|
159
185
|
/>)
|
|
@@ -169,7 +195,7 @@ const UsaMap = (props) => {
|
|
|
169
195
|
let textColor = "#FFF"
|
|
170
196
|
|
|
171
197
|
// Dynamic text color
|
|
172
|
-
if (chroma.contrast(textColor, bgColor) <
|
|
198
|
+
if (chroma.contrast(textColor, bgColor) < 3.5 ) {
|
|
173
199
|
textColor = '#202020';
|
|
174
200
|
}
|
|
175
201
|
|
|
@@ -206,6 +232,25 @@ const UsaMap = (props) => {
|
|
|
206
232
|
const constructGeoJsx = (geographies, projection) => {
|
|
207
233
|
let showLabel = state.general.displayStateLabels
|
|
208
234
|
|
|
235
|
+
// Order alphabetically. Important for accessibility if ever read out loud.
|
|
236
|
+
geographies.map ( state => {
|
|
237
|
+
if(!state.feature.properties.iso) return;
|
|
238
|
+
state.feature.properties.name = titleCase(supportedStates[state.feature.properties.iso][0])
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
geographies.sort( (a,b) => {
|
|
242
|
+
const first = a.feature.properties.name.toUpperCase(); // ignore upper and lowercase
|
|
243
|
+
const second = b.feature.properties.name.toUpperCase(); // ignore upper and lowercase
|
|
244
|
+
if (first < second) {
|
|
245
|
+
return -1;
|
|
246
|
+
}
|
|
247
|
+
if (first > second) {
|
|
248
|
+
return 1;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// names must be equal
|
|
252
|
+
return 0;
|
|
253
|
+
})
|
|
209
254
|
const geosJsx = geographies.map(( {feature: geo, path = ''}) => {
|
|
210
255
|
const key = isHex ? geo.properties.iso + '-hex-group' : geo.properties.iso + '-group'
|
|
211
256
|
|
|
@@ -216,11 +261,9 @@ const UsaMap = (props) => {
|
|
|
216
261
|
|
|
217
262
|
// Map the name from the geo data with the appropriate key for the processed data
|
|
218
263
|
let geoKey = geo.properties.iso;
|
|
264
|
+
let geoName = geo.properties.name;
|
|
219
265
|
|
|
220
266
|
// Manually add Washington D.C. in for Hex maps
|
|
221
|
-
if(isHex && geoKey === 'US-DC') {
|
|
222
|
-
geoKey = 'District of Columbia'
|
|
223
|
-
}
|
|
224
267
|
|
|
225
268
|
if(!geoKey) return
|
|
226
269
|
|
|
@@ -240,13 +283,15 @@ const UsaMap = (props) => {
|
|
|
240
283
|
const tooltip = applyTooltipsToGeo(geoDisplayName, geoData);
|
|
241
284
|
|
|
242
285
|
styles = {
|
|
243
|
-
fill: legendColors[0],
|
|
286
|
+
fill: state.general.type !== 'bubble' ? legendColors[0] : '#E6E6E6',
|
|
287
|
+
opacity: setSharedFilterValue && isFilterValueSupported && setSharedFilterValue !== geoData[state.columns.geo.name] ? .5 : 1,
|
|
288
|
+
stroke: setSharedFilterValue && isFilterValueSupported && setSharedFilterValue === geoData[state.columns.geo.name] ? 'rgba(0, 0, 0, 1)' : geoStrokeColor,
|
|
244
289
|
cursor: 'default',
|
|
245
290
|
'&:hover': {
|
|
246
|
-
fill: legendColors[1],
|
|
291
|
+
fill: state.general.type !== 'bubble' ? legendColors[1] : '#e6e6e6',
|
|
247
292
|
},
|
|
248
293
|
'&:active': {
|
|
249
|
-
fill: legendColors[2],
|
|
294
|
+
fill: state.general.type !== 'bubble' ? legendColors[2] : '#e6e6e6',
|
|
250
295
|
},
|
|
251
296
|
};
|
|
252
297
|
|
|
@@ -254,43 +299,43 @@ const UsaMap = (props) => {
|
|
|
254
299
|
if ((state.columns.navigate && geoData[state.columns.navigate.name]) || state.tooltips.appearanceType === 'click') {
|
|
255
300
|
styles.cursor = 'pointer'
|
|
256
301
|
}
|
|
257
|
-
|
|
258
302
|
return (
|
|
303
|
+
<g data-name={geoName} key={key}>
|
|
304
|
+
<g
|
|
305
|
+
data-for="tooltip"
|
|
306
|
+
data-tip={tooltip}
|
|
307
|
+
className="geo-group"
|
|
308
|
+
css={styles}
|
|
309
|
+
onClick={() => geoClickHandler(geoDisplayName, geoData)}
|
|
310
|
+
>
|
|
311
|
+
<path
|
|
312
|
+
tabIndex={-1}
|
|
313
|
+
className='single-geo'
|
|
314
|
+
strokeWidth={1.3}
|
|
315
|
+
d={path}
|
|
316
|
+
/>
|
|
317
|
+
{(isHex || showLabel) && geoLabel(geo, legendColors[0], projection)}
|
|
318
|
+
</g>
|
|
319
|
+
</g>
|
|
320
|
+
)
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Default return state, just geo with no additional information
|
|
324
|
+
return (
|
|
325
|
+
<g data-name={geoName} key={key}>
|
|
259
326
|
<g
|
|
260
|
-
data-for="tooltip"
|
|
261
|
-
data-tip={tooltip}
|
|
262
|
-
key={key}
|
|
263
327
|
className="geo-group"
|
|
264
328
|
css={styles}
|
|
265
|
-
onClick={() => geoClickHandler(geoDisplayName, geoData)}
|
|
266
329
|
>
|
|
267
330
|
<path
|
|
268
331
|
tabIndex={-1}
|
|
269
332
|
className='single-geo'
|
|
270
333
|
stroke={geoStrokeColor}
|
|
271
|
-
strokeWidth={1.3}
|
|
334
|
+
strokeWidth={1.3}
|
|
272
335
|
d={path}
|
|
273
336
|
/>
|
|
274
|
-
{(isHex || showLabel) && geoLabel(geo,
|
|
337
|
+
{(isHex || showLabel) && geoLabel(geo, styles.fill, projection)}
|
|
275
338
|
</g>
|
|
276
|
-
)
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
// Default return state, just geo with no additional information
|
|
280
|
-
return (
|
|
281
|
-
<g
|
|
282
|
-
key={key}
|
|
283
|
-
className="geo-group"
|
|
284
|
-
css={styles}
|
|
285
|
-
>
|
|
286
|
-
<path
|
|
287
|
-
tabIndex={-1}
|
|
288
|
-
className='single-geo'
|
|
289
|
-
stroke={geoStrokeColor}
|
|
290
|
-
strokeWidth={1.3}
|
|
291
|
-
d={path}
|
|
292
|
-
/>
|
|
293
|
-
{(isHex || showLabel) && geoLabel(geo, styles.fill, projection)}
|
|
294
339
|
</g>
|
|
295
340
|
)
|
|
296
341
|
});
|
|
@@ -308,14 +353,37 @@ const UsaMap = (props) => {
|
|
|
308
353
|
displayGeoName={displayGeoName}
|
|
309
354
|
applyLegendToRow={applyLegendToRow}
|
|
310
355
|
titleCase={titleCase}
|
|
356
|
+
setSharedFilterValue={setSharedFilterValue}
|
|
357
|
+
isFilterValueSupported={isFilterValueSupported}
|
|
358
|
+
isGeoCodeMap={state.general.type === 'us-geocode'}
|
|
311
359
|
/>)
|
|
312
360
|
|
|
361
|
+
// Bubbles
|
|
362
|
+
if (state.general.type === 'bubble') {
|
|
363
|
+
geosJsx.push(
|
|
364
|
+
<BubbleList
|
|
365
|
+
key="bubbles"
|
|
366
|
+
data={state.data}
|
|
367
|
+
runtimeData={data}
|
|
368
|
+
state={state}
|
|
369
|
+
projection={projection}
|
|
370
|
+
applyLegendToRow={applyLegendToRow}
|
|
371
|
+
applyTooltipsToGeo={applyTooltipsToGeo}
|
|
372
|
+
displayGeoName={displayGeoName}
|
|
373
|
+
/>
|
|
374
|
+
)
|
|
375
|
+
}
|
|
376
|
+
|
|
313
377
|
return geosJsx;
|
|
314
378
|
};
|
|
315
379
|
|
|
316
380
|
return (
|
|
317
381
|
<ErrorBoundary component="UsaMap">
|
|
318
|
-
<svg
|
|
382
|
+
<svg
|
|
383
|
+
viewBox="0 0 880 500"
|
|
384
|
+
role="img"
|
|
385
|
+
aria-label={handleMapAriaLabels(state)}
|
|
386
|
+
>
|
|
319
387
|
{state.general.displayAsHex ?
|
|
320
388
|
(<Mercator data={unitedStatesHex} scale={650} translate={[1600, 775]}>
|
|
321
389
|
{({ features, projection }) => constructGeoJsx(features, projection)}
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import React, { useState, useEffect, memo } from 'react';
|
|
2
|
+
/** @jsx jsx */
|
|
3
|
+
import { jsx } from '@emotion/react'
|
|
4
|
+
import ErrorBoundary from '@cdc/core/components/ErrorBoundary';
|
|
5
|
+
import { geoCentroid } from "d3-geo";
|
|
6
|
+
import { feature } from "topojson-client";
|
|
7
|
+
import topoJSON from '../data/us-regions-topo-2.json';
|
|
8
|
+
import { Mercator } from '@visx/geo';
|
|
9
|
+
import chroma from 'chroma-js';
|
|
10
|
+
|
|
11
|
+
const { features: unitedStates } = feature(topoJSON, topoJSON.objects.regions)
|
|
12
|
+
|
|
13
|
+
const Rect = ({label, text, stroke, strokeWidth, ...props}) => {
|
|
14
|
+
return (
|
|
15
|
+
<svg viewBox="0 0 45 28">
|
|
16
|
+
<g {...props} strokeLinejoin="round">
|
|
17
|
+
<path stroke={stroke} strokeWidth={strokeWidth} d="M40,0.5 C41.2426407,0.5 42.3676407,1.00367966 43.1819805,1.81801948 C43.9963203,2.63235931 44.5,3.75735931 44.5,5 L44.5,5 L44.5,23 C44.5,24.2426407 43.9963203,25.3676407 43.1819805,26.1819805 C42.3676407,26.9963203 41.2426407,27.5 40,27.5 L40,27.5 L5,27.5 C3.75735931,27.5 2.63235931,26.9963203 1.81801948,26.1819805 C1.00367966,25.3676407 0.5,24.2426407 0.5,23 L0.5,23 L0.5,5 C0.5,3.75735931 1.00367966,2.63235931 1.81801948,1.81801948 C2.63235931,1.00367966 3.75735931,0.5 5,0.5 L5,0.5 Z" />
|
|
18
|
+
<text textAnchor="middle" dominantBaseline="middle" x="50%" y="54%" fill={text}>{label}</text>
|
|
19
|
+
</g>
|
|
20
|
+
</svg>
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const UsaRegionMap = (props) => {
|
|
25
|
+
const {
|
|
26
|
+
state,
|
|
27
|
+
applyTooltipsToGeo,
|
|
28
|
+
data,
|
|
29
|
+
geoClickHandler,
|
|
30
|
+
applyLegendToRow,
|
|
31
|
+
displayGeoName,
|
|
32
|
+
supportedTerritories,
|
|
33
|
+
rebuildTooltips,
|
|
34
|
+
titleCase,
|
|
35
|
+
handleCircleClick,
|
|
36
|
+
handleMapAriaLabels
|
|
37
|
+
} = props;
|
|
38
|
+
|
|
39
|
+
// "Choose State" options
|
|
40
|
+
const [extent, setExtent] = useState(null)
|
|
41
|
+
const [focusedStates, setFocusedStates] = useState(unitedStates)
|
|
42
|
+
const [translate, setTranslate] = useState([455,200])
|
|
43
|
+
|
|
44
|
+
// When returning from another map we want to reset the state
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
setTranslate( [455,250] )
|
|
47
|
+
setExtent( null )
|
|
48
|
+
}, [state.general.geoType]);
|
|
49
|
+
|
|
50
|
+
const isHex = state.general.displayAsHex
|
|
51
|
+
|
|
52
|
+
const [territoriesData, setTerritoriesData] = useState([]);
|
|
53
|
+
|
|
54
|
+
const territoriesKeys = Object.keys(supportedTerritories); // data will have already mapped abbreviated territories to their full names
|
|
55
|
+
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
// Territories need to show up if they're in the data at all, not just if they're "active". That's why this is different from Cities
|
|
58
|
+
const territoriesList = territoriesKeys.filter(key => data[key]);
|
|
59
|
+
|
|
60
|
+
setTerritoriesData(territoriesList);
|
|
61
|
+
}, [data]);
|
|
62
|
+
|
|
63
|
+
useEffect(() => rebuildTooltips());
|
|
64
|
+
|
|
65
|
+
const geoStrokeColor = state.general.geoBorderColor === 'darkGray' ? 'rgba(0, 0, 0, 0.2)' : 'rgba(255,255,255,0.7)'
|
|
66
|
+
|
|
67
|
+
const territories = territoriesData.map(territory => {
|
|
68
|
+
const Shape = Rect
|
|
69
|
+
|
|
70
|
+
const territoryData = data[territory];
|
|
71
|
+
|
|
72
|
+
let toolTip;
|
|
73
|
+
|
|
74
|
+
let styles = {
|
|
75
|
+
fill: '#E6E6E6',
|
|
76
|
+
color: '#202020',
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const label = supportedTerritories[territory][1]
|
|
80
|
+
|
|
81
|
+
if(!territoryData) return <Shape key={label} label={label} css={styles} text={styles.color} />
|
|
82
|
+
|
|
83
|
+
toolTip = applyTooltipsToGeo(displayGeoName(territory), territoryData);
|
|
84
|
+
|
|
85
|
+
const legendColors = applyLegendToRow(territoryData);
|
|
86
|
+
|
|
87
|
+
let textColor = '#FFF';
|
|
88
|
+
|
|
89
|
+
if (legendColors) {
|
|
90
|
+
// Use white text if the background is dark, and dark grey if it's light
|
|
91
|
+
if (chroma.contrast(textColor, legendColors[0]) < 3.5) {
|
|
92
|
+
textColor = '#202020';
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
let needsPointer = false;
|
|
96
|
+
|
|
97
|
+
// If we need to add a pointer cursor
|
|
98
|
+
if ((state.columns.navigate && territoryData[state.columns.navigate.name]) || state.tooltips.appearanceType === 'click') {
|
|
99
|
+
needsPointer = true;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
styles = {
|
|
103
|
+
color: textColor,
|
|
104
|
+
fill: legendColors[0],
|
|
105
|
+
cursor: needsPointer ? 'pointer' : 'default',
|
|
106
|
+
'&:hover': {
|
|
107
|
+
fill: legendColors[1],
|
|
108
|
+
},
|
|
109
|
+
'&:active': {
|
|
110
|
+
fill: legendColors[2],
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
return (<Shape
|
|
115
|
+
key={label}
|
|
116
|
+
label={label}
|
|
117
|
+
css={styles}
|
|
118
|
+
text={styles.color}
|
|
119
|
+
data-tip={toolTip}
|
|
120
|
+
data-for="tooltip"
|
|
121
|
+
stroke={geoStrokeColor}
|
|
122
|
+
strokeWidth={1.5}
|
|
123
|
+
onClick={() => geoClickHandler(territory, territoryData)}
|
|
124
|
+
/>)
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
const geoLabel = (geo, bgColor = "#FFFFFF", projection) => {
|
|
129
|
+
let centroid = projection(geoCentroid(geo))
|
|
130
|
+
let abbr = geo.properties.iso
|
|
131
|
+
|
|
132
|
+
if(undefined === abbr) return null
|
|
133
|
+
|
|
134
|
+
let textColor = "#FFF"
|
|
135
|
+
|
|
136
|
+
// Dynamic text color
|
|
137
|
+
if (chroma.contrast(textColor, bgColor) < 3.5 ) {
|
|
138
|
+
textColor = '#202020';
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
let x = 0, y = 5
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
return (
|
|
145
|
+
<g>
|
|
146
|
+
<line x1={centroid[0]} y1={centroid[1]} x2={centroid[0] + dx} y2={centroid[1] + dy} stroke="rgba(0,0,0,.5)" strokeWidth={1} />
|
|
147
|
+
<text x={4} strokeWidth="0" fontSize={13} style={{fill: "#202020"}} alignmentBaseline="middle" transform={`translate(${centroid[0] + dx}, ${centroid[1] + dy})`}>
|
|
148
|
+
{abbr.substring(3)}
|
|
149
|
+
</text>
|
|
150
|
+
</g>
|
|
151
|
+
)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Constructs and displays markup for all geos on the map (except territories right now)
|
|
155
|
+
const constructGeoJsx = (geographies, projection) => {
|
|
156
|
+
let showLabel = state.general.displayStateLabels
|
|
157
|
+
|
|
158
|
+
const geosJsx = geographies.map(( {feature: geo, path = '', index}) => {
|
|
159
|
+
const key = isHex ? geo.properties.iso + '-hex-group' : geo.properties.iso + '-group'
|
|
160
|
+
|
|
161
|
+
let styles = {
|
|
162
|
+
fill: '#E6E6E6',
|
|
163
|
+
cursor: 'default'
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Map the name from the geo data with the appropriate key for the processed data
|
|
167
|
+
let geoKey = geo.properties.iso;
|
|
168
|
+
|
|
169
|
+
// Manually add Washington D.C. in for Hex maps
|
|
170
|
+
|
|
171
|
+
if(!geoKey) return
|
|
172
|
+
|
|
173
|
+
const geoData = data[geoKey];
|
|
174
|
+
|
|
175
|
+
let legendColors;
|
|
176
|
+
// Once we receive data for this geographic item, setup variables.
|
|
177
|
+
if (geoData !== undefined) {
|
|
178
|
+
legendColors = applyLegendToRow(geoData);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const geoDisplayName = displayGeoName(geoKey);
|
|
182
|
+
|
|
183
|
+
// If a legend applies, return it with appropriate information.
|
|
184
|
+
if (legendColors && legendColors[0] !== '#000000') {
|
|
185
|
+
const tooltip = applyTooltipsToGeo(geoDisplayName, geoData);
|
|
186
|
+
|
|
187
|
+
styles = {
|
|
188
|
+
fill: state.general.type !== 'bubble' ? legendColors[0] : '#E6E6E6',
|
|
189
|
+
cursor: 'default',
|
|
190
|
+
'&:hover': {
|
|
191
|
+
fill: state.general.type !== 'bubble' ? legendColors[1] : '#e6e6e6',
|
|
192
|
+
},
|
|
193
|
+
'&:active': {
|
|
194
|
+
fill: state.general.type !== 'bubble' ? legendColors[2] : '#e6e6e6',
|
|
195
|
+
},
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
// When to add pointer cursor
|
|
199
|
+
if ((state.columns.navigate && geoData[state.columns.navigate.name]) || state.tooltips.appearanceType === 'click') {
|
|
200
|
+
styles.cursor = 'pointer'
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const TerratoryRect = (props) => {
|
|
204
|
+
const { posX = 0, tName } = props
|
|
205
|
+
let textColor = "#fff"
|
|
206
|
+
|
|
207
|
+
if (chroma.contrast(textColor, legendColors[0]) < 4.5) {
|
|
208
|
+
textColor = '#202020';
|
|
209
|
+
}
|
|
210
|
+
return (
|
|
211
|
+
<>
|
|
212
|
+
<rect x={posX} width="36" height="24" rx="6" stroke="#fff" strokeWidth="1" />
|
|
213
|
+
<text x={posX + 8} y="17" fill={textColor}>{tName}</text>
|
|
214
|
+
</>
|
|
215
|
+
)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const circleRadius = 15;
|
|
219
|
+
|
|
220
|
+
// SIDE CHART EXPERIMENT
|
|
221
|
+
// const height = state.data[index].Change;
|
|
222
|
+
// const barHeight = Math.abs(height * 20 );
|
|
223
|
+
// const barPositive = height > 0;
|
|
224
|
+
// const barY = barPositive ? -barHeight + 15 : 15;
|
|
225
|
+
// const baseY = 14;
|
|
226
|
+
// const barFill = barPositive ? "#fff" : "#fff";
|
|
227
|
+
|
|
228
|
+
return (
|
|
229
|
+
<g
|
|
230
|
+
data-for="tooltip"
|
|
231
|
+
data-tip={tooltip}
|
|
232
|
+
key={key}
|
|
233
|
+
className="geo-group"
|
|
234
|
+
css={styles}
|
|
235
|
+
onClick={() => geoClickHandler(geoDisplayName, geoData)}
|
|
236
|
+
>
|
|
237
|
+
|
|
238
|
+
<path
|
|
239
|
+
tabIndex={-1}
|
|
240
|
+
className='single-geo'
|
|
241
|
+
stroke={geoStrokeColor}
|
|
242
|
+
strokeWidth={1.3}
|
|
243
|
+
d={path}
|
|
244
|
+
/>
|
|
245
|
+
<g id={`region-${index+1}-label`}>
|
|
246
|
+
<circle fill="#fff" stroke="#999" cx={circleRadius} cy={circleRadius} r={circleRadius}/>
|
|
247
|
+
<text fill="#333" x="15px" y="20px" textAnchor="middle">{index+1}</text>
|
|
248
|
+
{/* SIDE CHART EXPERIMENT */}
|
|
249
|
+
{/*<g y={barY*20}>*/}
|
|
250
|
+
{/* <rect x="-20" y={barY} width="10" height={barHeight} fill={barFill} stroke="#333"/>*/}
|
|
251
|
+
{/* <rect x="-23" y={baseY} width="16" height="2" fill="#000" />*/}
|
|
252
|
+
{/*</g>*/}
|
|
253
|
+
{/* / SIDE CHART EXPERIMENT */}
|
|
254
|
+
</g>
|
|
255
|
+
{geoKey === 'region 2' &&
|
|
256
|
+
<g id="region-2-territories">
|
|
257
|
+
<TerratoryRect tName="PR" />
|
|
258
|
+
<TerratoryRect posX={45} tName="VI" />
|
|
259
|
+
</g>
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
{ geoKey === 'region 9' &&
|
|
263
|
+
<g id="region-9-territories">
|
|
264
|
+
<g className="region-9-row1">
|
|
265
|
+
<TerratoryRect tName="AS" />
|
|
266
|
+
<TerratoryRect posX={45} tName="GU" />
|
|
267
|
+
<TerratoryRect posX={90} tName="MP" />
|
|
268
|
+
</g>
|
|
269
|
+
<g className="region-9-row2">
|
|
270
|
+
<TerratoryRect tName="FM" />
|
|
271
|
+
<TerratoryRect posX={45} tName="PW" />
|
|
272
|
+
<TerratoryRect posX={90} tName="MH" />
|
|
273
|
+
</g>
|
|
274
|
+
</g>
|
|
275
|
+
}
|
|
276
|
+
</g>
|
|
277
|
+
)
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Default return state, just geo with no additional information
|
|
281
|
+
return (
|
|
282
|
+
<g
|
|
283
|
+
key={key}
|
|
284
|
+
className="geo-group"
|
|
285
|
+
css={styles}
|
|
286
|
+
>
|
|
287
|
+
<path
|
|
288
|
+
tabIndex={-1}
|
|
289
|
+
className='single-geo'
|
|
290
|
+
stroke={geoStrokeColor}
|
|
291
|
+
strokeWidth={1.3}
|
|
292
|
+
d={path}
|
|
293
|
+
/>
|
|
294
|
+
{(isHex || showLabel) && geoLabel(geo, styles.fill, projection)}
|
|
295
|
+
</g>
|
|
296
|
+
)
|
|
297
|
+
});
|
|
298
|
+
return geosJsx;
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
return (
|
|
302
|
+
<ErrorBoundary component="UsaRegionMap">
|
|
303
|
+
<svg viewBox="0 0 880 500" role="img" aria-label={handleMapAriaLabels(state)}>
|
|
304
|
+
|
|
305
|
+
<Mercator data={focusedStates} scale={620} translate={[1500, 735]}>
|
|
306
|
+
{({ features, projection }) => constructGeoJsx(features, projection)}
|
|
307
|
+
</Mercator>
|
|
308
|
+
|
|
309
|
+
</svg>
|
|
310
|
+
{territories.length > 0 && (
|
|
311
|
+
<section className="territories">
|
|
312
|
+
<span className="label">{state.general.territoriesLabel}</span>
|
|
313
|
+
{territories}
|
|
314
|
+
</section>
|
|
315
|
+
)}
|
|
316
|
+
</ErrorBoundary>
|
|
317
|
+
);
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
export default memo(UsaRegionMap)
|