@cdc/map 2.6.0 → 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/convert-topojson.js +70 -0
- package/dist/cdcmap.js +190 -0
- package/examples/default-county.json +105 -0
- package/examples/default-single-state.json +109 -0
- package/examples/default-usa.json +968 -0
- package/examples/default-world.json +1495 -0
- 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 -50
- package/src/CdcMap.js +1384 -0
- package/src/components/CityList.js +93 -0
- package/src/components/CountyMap.js +556 -0
- package/src/components/DataTable.js +357 -0
- package/src/components/EditorPanel.js +2111 -0
- package/src/components/Geo.js +21 -0
- package/src/components/Modal.js +31 -0
- package/src/components/NavigationMenu.js +66 -0
- package/src/components/Sidebar.js +167 -0
- package/src/components/SingleStateMap.js +326 -0
- package/src/components/UsaMap.js +342 -0
- package/src/components/WorldMap.js +175 -0
- package/src/components/ZoomableGroup.js +47 -0
- package/src/data/abbreviations.js +57 -0
- package/src/data/color-palettes.js +200 -0
- 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 +60 -0
- package/src/data/newtest.json +1 -0
- package/src/data/state-abbreviations.js +60 -0
- package/src/data/supported-geos.js +3775 -0
- package/src/data/test.json +1 -0
- package/src/data/us-hex-topo.json +1 -0
- package/src/data/us-topo.json +1 -0
- package/src/data/world-topo.json +1 -0
- package/src/hooks/useActiveElement.js +19 -0
- package/src/hooks/useZoomPan.js +110 -0
- package/src/images/active-checkmark.svg +1 -0
- package/src/images/asc.svg +1 -0
- package/src/images/close.svg +1 -0
- package/src/images/desc.svg +1 -0
- package/src/images/external-link.svg +1 -0
- package/src/images/icon-download-img.svg +1 -0
- package/src/images/icon-download-pdf.svg +1 -0
- package/src/images/inactive-checkmark.svg +1 -0
- package/src/images/map-folded.svg +1 -0
- package/src/index.html +29 -0
- package/src/index.js +20 -0
- package/src/scss/btn.scss +69 -0
- package/src/scss/datatable.scss +7 -0
- package/src/scss/editor-panel.scss +654 -0
- package/src/scss/main.scss +224 -0
- package/src/scss/map.scss +188 -0
- package/src/scss/sidebar.scss +146 -0
- package/src/scss/tooltips.scss +30 -0
- package/src/scss/variables.scss +1 -0
- package/uploads/upload-example-city-state.json +392 -0
- package/uploads/upload-example-world-data.json +1490 -0
- package/LICENSE +0 -201
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import React, { memo } from 'react';
|
|
2
|
+
|
|
3
|
+
const Geo = ({path, styles, stroke, strokeWidth, ...props}) => {
|
|
4
|
+
return (
|
|
5
|
+
<g
|
|
6
|
+
className="geo-group"
|
|
7
|
+
css={styles}
|
|
8
|
+
{...props}
|
|
9
|
+
>
|
|
10
|
+
<path
|
|
11
|
+
tabIndex={-1}
|
|
12
|
+
className='single-geo'
|
|
13
|
+
stroke={stroke}
|
|
14
|
+
strokeWidth={strokeWidth}
|
|
15
|
+
d={path}
|
|
16
|
+
/>
|
|
17
|
+
</g>
|
|
18
|
+
)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export default memo(Geo)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import closeIcon from '../images/close.svg?inline';
|
|
3
|
+
import LegendCircle from '@cdc/core/components/LegendCircle';
|
|
4
|
+
|
|
5
|
+
const Modal = (props) => {
|
|
6
|
+
const {
|
|
7
|
+
applyTooltipsToGeo,
|
|
8
|
+
content,
|
|
9
|
+
capitalize,
|
|
10
|
+
applyLegendToRow,
|
|
11
|
+
viewport,
|
|
12
|
+
type
|
|
13
|
+
} = props;
|
|
14
|
+
|
|
15
|
+
const tooltip = applyTooltipsToGeo(content.geoName, content.keyedData, 'jsx');
|
|
16
|
+
|
|
17
|
+
const legendColors = applyLegendToRow(content.keyedData);
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<section className={capitalize ? 'modal-content tooltip capitalize ' + viewport : 'modal-content tooltip ' + viewport} aria-hidden="true">
|
|
21
|
+
<img src={closeIcon} className="modal-close" alt="Close Modal" />
|
|
22
|
+
{type === 'data' && <LegendCircle fill={legendColors[0]} />}
|
|
23
|
+
<div className="content">
|
|
24
|
+
{tooltip}
|
|
25
|
+
</div>
|
|
26
|
+
</section>
|
|
27
|
+
);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
export default Modal;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
const NavigationMenu = ({
|
|
4
|
+
data, navigationHandler, options, columns, displayGeoName
|
|
5
|
+
}) => {
|
|
6
|
+
const [activeGeo, setActiveGeo] = useState('');
|
|
7
|
+
|
|
8
|
+
const [dropdownItems, setDropdownItems] = useState({});
|
|
9
|
+
|
|
10
|
+
const handleSubmit = (event) => {
|
|
11
|
+
event.preventDefault();
|
|
12
|
+
if (activeGeo !== '') {
|
|
13
|
+
const urlString = data[dropdownItems[activeGeo]][columns.navigate.name];
|
|
14
|
+
|
|
15
|
+
navigationHandler(urlString);
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
let navSelect; let
|
|
19
|
+
navGo;
|
|
20
|
+
|
|
21
|
+
switch (options.language) {
|
|
22
|
+
case 'es':
|
|
23
|
+
navSelect = 'Selecciona un Artículo';
|
|
24
|
+
navGo = 'Ir';
|
|
25
|
+
break;
|
|
26
|
+
default:
|
|
27
|
+
navSelect = 'Select an Item';
|
|
28
|
+
navGo = 'Go';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
const sortedOptions = {};
|
|
33
|
+
|
|
34
|
+
const processedDropdown = {};
|
|
35
|
+
|
|
36
|
+
Object.keys(data).forEach((val) => {
|
|
37
|
+
const fullName = displayGeoName(val);
|
|
38
|
+
|
|
39
|
+
processedDropdown[fullName] = val;
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
Object.keys(processedDropdown).sort().forEach((key) => {
|
|
43
|
+
sortedOptions[key] = processedDropdown[key];
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
setDropdownItems(sortedOptions);
|
|
47
|
+
|
|
48
|
+
setActiveGeo(Object.keys(sortedOptions)[0]);
|
|
49
|
+
}, [data, displayGeoName]);
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<section className="navigation-menu">
|
|
53
|
+
<form onSubmit={handleSubmit} type="get">
|
|
54
|
+
<label htmlFor="dropdown">
|
|
55
|
+
<div className="select-heading">{navSelect}</div>
|
|
56
|
+
<select value={activeGeo} id="dropdown" onChange={(e) => setActiveGeo(e.target.value)}>
|
|
57
|
+
{Object.keys(dropdownItems).map((key, i) => <option key={key} value={key}>{key}</option>)}
|
|
58
|
+
</select>
|
|
59
|
+
</label>
|
|
60
|
+
<input type="submit" value={navGo} className={`${options.headerColor} btn`} id="cdcnavmap-dropdown-go" />
|
|
61
|
+
</form>
|
|
62
|
+
</section>
|
|
63
|
+
);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export default NavigationMenu;
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import parse from 'html-react-parser';
|
|
3
|
+
|
|
4
|
+
import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
5
|
+
import LegendCircle from '@cdc/core/components/LegendCircle'
|
|
6
|
+
|
|
7
|
+
const Sidebar = (props) => {
|
|
8
|
+
const {
|
|
9
|
+
legend,
|
|
10
|
+
runtimeFilters,
|
|
11
|
+
columns,
|
|
12
|
+
setAccessibleStatus,
|
|
13
|
+
changeFilterActive,
|
|
14
|
+
resetLegendToggles,
|
|
15
|
+
runtimeLegend,
|
|
16
|
+
setRuntimeLegend,
|
|
17
|
+
prefix,
|
|
18
|
+
suffix,
|
|
19
|
+
viewport
|
|
20
|
+
} = props;
|
|
21
|
+
|
|
22
|
+
const addCommas = (value) => {
|
|
23
|
+
// If value is a number, apply specific formattings
|
|
24
|
+
if (value && columns.primary.hasOwnProperty('useCommas') && columns.primary.useCommas === true) {
|
|
25
|
+
return value.toLocaleString('en-US', { style: 'decimal' });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return value;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// Toggles if a legend is active and being applied to the map and data table.
|
|
32
|
+
const toggleLegendActive = (i, legendLabel) => {
|
|
33
|
+
const newValue = !runtimeLegend[i].disabled;
|
|
34
|
+
|
|
35
|
+
runtimeLegend[i].disabled = newValue; // Toggle!
|
|
36
|
+
|
|
37
|
+
let newLegend = [...runtimeLegend]
|
|
38
|
+
|
|
39
|
+
newLegend[i].disabled = newValue
|
|
40
|
+
|
|
41
|
+
const disabledAmt = runtimeLegend.disabledAmt ?? 0
|
|
42
|
+
|
|
43
|
+
newLegend['disabledAmt'] = newValue ? disabledAmt + 1 : disabledAmt - 1
|
|
44
|
+
|
|
45
|
+
setRuntimeLegend(newLegend)
|
|
46
|
+
|
|
47
|
+
setAccessibleStatus(`Disabled legend item ${legendLabel ?? ''}. Please reference the data table to see updated values.`);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const legendList = runtimeLegend.map((entry, idx) => {
|
|
51
|
+
const entryMax = addCommas(entry.max);
|
|
52
|
+
|
|
53
|
+
const entryMin = addCommas(entry.min);
|
|
54
|
+
|
|
55
|
+
let formattedText = `${prefix + entryMin + suffix}${entryMax !== entryMin ? ` - ${prefix + entryMax + suffix}` : ''}`;
|
|
56
|
+
|
|
57
|
+
// If interval, add some formatting
|
|
58
|
+
if (legend.type === 'equalinterval' && idx !== runtimeLegend.length - 1) {
|
|
59
|
+
formattedText = `${prefix + entryMin + suffix} - < ${prefix + entryMax + suffix}`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const { disabled } = entry;
|
|
63
|
+
|
|
64
|
+
if (legend.type === 'category') {
|
|
65
|
+
formattedText = prefix + entry.value + suffix;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (entry.max === 0 && entry.min === 0) {
|
|
69
|
+
formattedText = '0';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
let legendLabel = formattedText;
|
|
73
|
+
|
|
74
|
+
if (entry.hasOwnProperty('special')) {
|
|
75
|
+
legendLabel = entry.label || entry.value;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<li
|
|
80
|
+
key={idx}
|
|
81
|
+
title={`Legend item ${legendLabel} - Click to disable`}
|
|
82
|
+
onClick={() => { toggleLegendActive(idx, legendLabel); }}
|
|
83
|
+
className={disabled ? 'disabled single-legend' : 'single-legend'}
|
|
84
|
+
><LegendCircle fill={entry.color} /> <span className="label">{legendLabel}</span>
|
|
85
|
+
</li>
|
|
86
|
+
);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const filtersList = runtimeFilters.map((singleFilter, idx) => {
|
|
90
|
+
const values = [];
|
|
91
|
+
|
|
92
|
+
if(undefined === singleFilter.active) return null
|
|
93
|
+
|
|
94
|
+
singleFilter.values.forEach((filterOption, idx) => {
|
|
95
|
+
values.push(<option
|
|
96
|
+
key={idx}
|
|
97
|
+
value={filterOption}
|
|
98
|
+
>{filterOption}
|
|
99
|
+
</option>);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<section className="filter-col" key={idx}>
|
|
104
|
+
{singleFilter.label.length > 0 && <label htmlFor={`filter-${idx}`}>{singleFilter.label}</label>}
|
|
105
|
+
<select
|
|
106
|
+
id={`filter-${idx}`}
|
|
107
|
+
className="filter-select"
|
|
108
|
+
value={singleFilter.active}
|
|
109
|
+
onChange={(val) => {
|
|
110
|
+
changeFilterActive(idx, val.target.value);
|
|
111
|
+
setAccessibleStatus(`Filter ${singleFilter.label} value has been changed to ${val.target.value}, please reference the data table to see updated values.`);
|
|
112
|
+
}}
|
|
113
|
+
>
|
|
114
|
+
{values}
|
|
115
|
+
</select>
|
|
116
|
+
</section>
|
|
117
|
+
);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
return (
|
|
121
|
+
<ErrorBoundary component="Sidebar">
|
|
122
|
+
<aside id="legend" className={`${legend.position} ${legend.singleColumn ? 'single-column cdcdataviz-sr-focusable' : 'cdcdataviz-sr-focusable'} ${viewport}`} role="region" aria-label="Legend" tabIndex="0">
|
|
123
|
+
<section className="legend-section" aria-label="Map Legend">
|
|
124
|
+
{runtimeLegend.disabledAmt > 0 &&
|
|
125
|
+
(
|
|
126
|
+
<button
|
|
127
|
+
onClick={(e) => {
|
|
128
|
+
e.preventDefault();
|
|
129
|
+
resetLegendToggles();
|
|
130
|
+
setAccessibleStatus('Legend has been reset, please reference the data table to see updated values.');
|
|
131
|
+
}}
|
|
132
|
+
className="clear btn"
|
|
133
|
+
>Clear
|
|
134
|
+
</button>
|
|
135
|
+
)}
|
|
136
|
+
{legend.title && <span className="heading-2">{ parse(legend.title) }</span>}
|
|
137
|
+
{legend.dynamicDescription === false && legend.description
|
|
138
|
+
&& <p>{ parse(legend.description) }</p>}
|
|
139
|
+
{legend.dynamicDescription === true && runtimeFilters.map((filter, idx) => {
|
|
140
|
+
const lookupStr = `${idx},${filter.values.indexOf(String(filter.active))}`;
|
|
141
|
+
|
|
142
|
+
// Do we have a custom description for this?
|
|
143
|
+
const desc = legend.descriptions[lookupStr] || '';
|
|
144
|
+
|
|
145
|
+
if (desc.length > 0) {
|
|
146
|
+
return (<p key={`dynamic-description-${lookupStr}`}>{desc}</p>);
|
|
147
|
+
}
|
|
148
|
+
return true;
|
|
149
|
+
})}
|
|
150
|
+
<ul className={legend.singleColumn ? 'single-column' : ''} aria-label="Legend items">
|
|
151
|
+
{legendList}
|
|
152
|
+
</ul>
|
|
153
|
+
</section>
|
|
154
|
+
{filtersList.length > 0 &&
|
|
155
|
+
<section className="filters-section" aria-label="Map Filters">
|
|
156
|
+
<span className="heading-3">Filters</span>
|
|
157
|
+
<form>
|
|
158
|
+
{filtersList}
|
|
159
|
+
</form>
|
|
160
|
+
</section>
|
|
161
|
+
}
|
|
162
|
+
</aside>
|
|
163
|
+
</ErrorBoundary>
|
|
164
|
+
);
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
export default Sidebar;
|
|
@@ -0,0 +1,326 @@
|
|
|
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
|
+
|
|
12
|
+
const abbrs = {
|
|
13
|
+
Alabama: 'AL',
|
|
14
|
+
Alaska: 'AK',
|
|
15
|
+
Arizona: 'AZ',
|
|
16
|
+
Arkansas: 'AR',
|
|
17
|
+
California: 'CA',
|
|
18
|
+
Colorado: 'CO',
|
|
19
|
+
Connecticut: 'CT',
|
|
20
|
+
Delaware: 'DE',
|
|
21
|
+
Florida: 'FL',
|
|
22
|
+
Georgia: 'GA',
|
|
23
|
+
Hawaii: 'HI',
|
|
24
|
+
Idaho: 'ID',
|
|
25
|
+
Illinois: 'IL',
|
|
26
|
+
Indiana: 'IN',
|
|
27
|
+
Iowa: 'IA',
|
|
28
|
+
Kansas: 'KS',
|
|
29
|
+
Kentucky: 'KY',
|
|
30
|
+
Louisiana: 'LA',
|
|
31
|
+
Maine: 'ME',
|
|
32
|
+
Maryland: 'MD',
|
|
33
|
+
Massachusetts: 'MA',
|
|
34
|
+
Michigan: 'MI',
|
|
35
|
+
Minnesota: 'MN',
|
|
36
|
+
Mississippi: 'MS',
|
|
37
|
+
Missouri: 'MO',
|
|
38
|
+
Montana: 'MT',
|
|
39
|
+
Nebraska: 'NE',
|
|
40
|
+
Nevada: 'NV',
|
|
41
|
+
'New Hampshire': 'NH',
|
|
42
|
+
'New Jersey': 'NJ',
|
|
43
|
+
'New Mexico': 'NM',
|
|
44
|
+
'New York': 'NY',
|
|
45
|
+
'North Carolina': 'NC',
|
|
46
|
+
'North Dakota': 'ND',
|
|
47
|
+
Ohio: 'OH',
|
|
48
|
+
Oklahoma: 'OK',
|
|
49
|
+
Oregon: 'OR',
|
|
50
|
+
Pennsylvania: 'PA',
|
|
51
|
+
'Rhode Island': 'RI',
|
|
52
|
+
'South Carolina': 'SC',
|
|
53
|
+
'South Dakota': 'SD',
|
|
54
|
+
Tennessee: 'TN',
|
|
55
|
+
Texas: 'TX',
|
|
56
|
+
Utah: 'UT',
|
|
57
|
+
Vermont: 'VT',
|
|
58
|
+
Virginia: 'VA',
|
|
59
|
+
Washington: 'WA',
|
|
60
|
+
'West Virginia': 'WV',
|
|
61
|
+
Wisconsin: 'WI',
|
|
62
|
+
Wyoming: 'WY',
|
|
63
|
+
'District of Columbia': 'DC',
|
|
64
|
+
Guam: 'GU',
|
|
65
|
+
'Virgin Islands': 'VI',
|
|
66
|
+
'Puerto Rico': 'PR',
|
|
67
|
+
'American Samoa': 'AS'
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
// SVG ITEMS
|
|
73
|
+
const WIDTH = 880;
|
|
74
|
+
const HEIGHT = 500;
|
|
75
|
+
const PADDING = 25;
|
|
76
|
+
|
|
77
|
+
// DATA
|
|
78
|
+
let { features: counties } = feature(testJSON, testJSON.objects.counties)
|
|
79
|
+
let { features: states } = feature(testJSON, testJSON.objects.states);
|
|
80
|
+
|
|
81
|
+
// CONSTANTS
|
|
82
|
+
const STATE_BORDER = '#c0cad4';
|
|
83
|
+
const STATE_INACTIVE_FILL = '#F4F7FA';
|
|
84
|
+
|
|
85
|
+
// CREATE STATE LINES
|
|
86
|
+
// const projection = geoAlbersUsaTerritories().translate([WIDTH/2,HEIGHT/2])
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
const SingleStateMap = (props) => {
|
|
91
|
+
|
|
92
|
+
const {
|
|
93
|
+
state,
|
|
94
|
+
applyTooltipsToGeo,
|
|
95
|
+
data,
|
|
96
|
+
geoClickHandler,
|
|
97
|
+
applyLegendToRow,
|
|
98
|
+
displayGeoName,
|
|
99
|
+
supportedTerritories,
|
|
100
|
+
rebuildTooltips,
|
|
101
|
+
runtimeLegend,
|
|
102
|
+
generateColorsArray
|
|
103
|
+
} = props;
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
const projection = geoAlbersUsaTerritories().translate([WIDTH/2, HEIGHT/2])
|
|
108
|
+
|
|
109
|
+
const geoStrokeColor = state.general.geoBorderColor === 'darkGray' ? 'rgba(0, 0, 0, 0.2)' : 'rgba(255,255,255,0.7)'
|
|
110
|
+
|
|
111
|
+
const [stateToShow, setStateToShow ] = useState( null )
|
|
112
|
+
const [countiesToShow, setCountiesToShow] = useState( null )
|
|
113
|
+
const [translate, setTranslate] = useState()
|
|
114
|
+
const [scale, setScale] = useState()
|
|
115
|
+
const [strokeWidth, setStrokeWidth ] = useState(.75)
|
|
116
|
+
|
|
117
|
+
let mapColorPalette = colorPalettes[state.color] || '#fff';
|
|
118
|
+
let focusedBorderColor = mapColorPalette[3];
|
|
119
|
+
useEffect(() => rebuildTooltips());
|
|
120
|
+
|
|
121
|
+
const path = geoPath().projection(projection)
|
|
122
|
+
|
|
123
|
+
// When choosing a state changes...
|
|
124
|
+
useEffect(() => {
|
|
125
|
+
if(state.general.hasOwnProperty('statePicked')) {
|
|
126
|
+
let statePicked = state.general.statePicked.stateName;
|
|
127
|
+
let statePickedData = states.find(s => s.properties.name === statePicked)
|
|
128
|
+
setStateToShow(statePickedData)
|
|
129
|
+
|
|
130
|
+
let countiesFound = counties.filter( c => c.id.substring(0,2) === state.general.statePicked.fipsCode)
|
|
131
|
+
|
|
132
|
+
setCountiesToShow(countiesFound)
|
|
133
|
+
|
|
134
|
+
const projection = geoAlbersUsaTerritories().translate([WIDTH/2,HEIGHT/2])
|
|
135
|
+
const newProjection = projection.fitExtent([[PADDING, PADDING], [WIDTH - PADDING, HEIGHT - PADDING]], statePickedData)
|
|
136
|
+
const newScale = newProjection.scale();
|
|
137
|
+
const newScaleWithHypot = newScale / 1070;
|
|
138
|
+
|
|
139
|
+
let [x, y] = newProjection.translate()
|
|
140
|
+
x = (x - WIDTH/2);
|
|
141
|
+
y = (y - HEIGHT/2);
|
|
142
|
+
|
|
143
|
+
setTranslate([x,y])
|
|
144
|
+
setScale(newScaleWithHypot)
|
|
145
|
+
|
|
146
|
+
}
|
|
147
|
+
}, [state.general.statePicked]);
|
|
148
|
+
|
|
149
|
+
const geoLabel = (geo, projection) => {
|
|
150
|
+
//projection = geoAlbersUsaTerritories().translate([WIDTH/2,HEIGHT/2])
|
|
151
|
+
//const newProjection = projection.fitExtent([[PADDING, PADDING], [WIDTH - PADDING, HEIGHT - PADDING]], geo)
|
|
152
|
+
let [x, y] = projection( geoCentroid(geo) )
|
|
153
|
+
let abbr = abbrs[geo.properties.name]
|
|
154
|
+
if (abbr === 'NJ') x += 3
|
|
155
|
+
if(undefined === abbr) return null
|
|
156
|
+
let [dx, dy] = offsets[geo.properties.name]
|
|
157
|
+
|
|
158
|
+
return (
|
|
159
|
+
<>
|
|
160
|
+
<line className="abbrLine" x1={x} y1={y} x2={x + dx} y2={y + dy} stroke="black" strokeWidth={1} />
|
|
161
|
+
<text className="abbrText" x={4} strokeWidth="0" fontSize={13} style={{fill: "#202020"}} alignmentBaseline="middle" transform={`translate(${x + dx}, ${y + dy})`}>
|
|
162
|
+
{abbr}
|
|
163
|
+
</text>
|
|
164
|
+
</>
|
|
165
|
+
)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Constructs and displays markup for all geos on the map (except territories right now)
|
|
169
|
+
const constructGeoJsx = (geographies, projection) => {
|
|
170
|
+
const statePassed = geographies[0].feature.states;
|
|
171
|
+
const counties = geographies[0].feature.counties;
|
|
172
|
+
|
|
173
|
+
let geosJsx = [];
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
const StateOutput = () => {
|
|
177
|
+
|
|
178
|
+
let geo = testJSON.objects.states.geometries.filter( s => {
|
|
179
|
+
return s.id === statePassed.id
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
// const stateLine = path(mesh(testJSON, lines ))
|
|
184
|
+
let stateLines = path(mesh(testJSON, geo[0]))
|
|
185
|
+
return (
|
|
186
|
+
<g
|
|
187
|
+
key={'single-state'}
|
|
188
|
+
className="single-state"
|
|
189
|
+
style={{ fill: '#E6E6E6'}}
|
|
190
|
+
stroke={geoStrokeColor}
|
|
191
|
+
strokeWidth={ .95 / scale }
|
|
192
|
+
>
|
|
193
|
+
<path
|
|
194
|
+
tabIndex={-1}
|
|
195
|
+
className='state-path'
|
|
196
|
+
d={stateLines}
|
|
197
|
+
/>
|
|
198
|
+
</g>
|
|
199
|
+
)
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
const countyOutput = ( counties.map(( county ) => {
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
// Map the name from the geo data with the appropriate key for the processed data
|
|
206
|
+
let geoKey = county.id;
|
|
207
|
+
|
|
208
|
+
if(!geoKey) return;
|
|
209
|
+
|
|
210
|
+
let countyPath = path(county);
|
|
211
|
+
|
|
212
|
+
let geoData = data[county.id];
|
|
213
|
+
let legendColors;
|
|
214
|
+
|
|
215
|
+
// Once we receive data for this geographic item, setup variables.
|
|
216
|
+
if (geoData !== undefined ) {
|
|
217
|
+
legendColors = applyLegendToRow(geoData);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const geoDisplayName = displayGeoName(geoKey);
|
|
221
|
+
|
|
222
|
+
// For some reason, these two geos are breaking the display.
|
|
223
|
+
if (geoDisplayName === 'Franklin City' || geoDisplayName === 'Waynesboro') return null;
|
|
224
|
+
|
|
225
|
+
const tooltip = applyTooltipsToGeo(geoDisplayName, geoData);
|
|
226
|
+
|
|
227
|
+
if (legendColors && legendColors[0] !== '#000000') {
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
let styles = {
|
|
231
|
+
fill: legendColors[0],
|
|
232
|
+
cursor: 'default',
|
|
233
|
+
'&:hover': {
|
|
234
|
+
fill: legendColors[1],
|
|
235
|
+
},
|
|
236
|
+
'&:active': {
|
|
237
|
+
fill: legendColors[2],
|
|
238
|
+
},
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
// When to add pointer cursor
|
|
242
|
+
if ((state.columns.navigate && geoData[state.columns.navigate.name]) || state.tooltips.appearanceType === 'hover') {
|
|
243
|
+
styles.cursor = 'pointer'
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return (
|
|
247
|
+
<g
|
|
248
|
+
data-for="tooltip"
|
|
249
|
+
data-tip={tooltip}
|
|
250
|
+
key={`key--${county.id}`}
|
|
251
|
+
className={`county county--${geoDisplayName.split(" ").join("")} county--${geoData[state.columns.geo.name]}`}
|
|
252
|
+
css={styles}
|
|
253
|
+
onClick={() => geoClickHandler(geoDisplayName, geoData)}
|
|
254
|
+
>
|
|
255
|
+
<path
|
|
256
|
+
tabIndex={-1}
|
|
257
|
+
className={`county`}
|
|
258
|
+
stroke={geoStrokeColor}
|
|
259
|
+
d={countyPath}
|
|
260
|
+
strokeWidth={ .75 / scale }
|
|
261
|
+
/>
|
|
262
|
+
</g>
|
|
263
|
+
)
|
|
264
|
+
} else {
|
|
265
|
+
return (
|
|
266
|
+
<g
|
|
267
|
+
data-for="tooltip"
|
|
268
|
+
data-tip={tooltip}
|
|
269
|
+
key={`key--${county.id}`}
|
|
270
|
+
className={`county county--${geoDisplayName.split(" ").join("")}`}
|
|
271
|
+
style={{ fill : '#e6e6e6'}}
|
|
272
|
+
>
|
|
273
|
+
<path
|
|
274
|
+
tabIndex={-1}
|
|
275
|
+
className={`county`}
|
|
276
|
+
stroke={geoStrokeColor}
|
|
277
|
+
d={countyPath}
|
|
278
|
+
strokeWidth={ .75 / scale }
|
|
279
|
+
/>
|
|
280
|
+
</g>
|
|
281
|
+
)
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
}));
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
geosJsx.push( <StateOutput /> );
|
|
288
|
+
geosJsx.push( countyOutput );
|
|
289
|
+
|
|
290
|
+
return geosJsx;
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
return (
|
|
295
|
+
<ErrorBoundary component="SingleStateMap">
|
|
296
|
+
{stateToShow &&
|
|
297
|
+
<svg viewBox={`0 0 ${WIDTH} ${HEIGHT}`} preserveAspectRatio="xMinYMin" className="svg-container">
|
|
298
|
+
<rect className="background center-container ocean" width={WIDTH} height={HEIGHT} fillOpacity={1} fill="white"></rect>
|
|
299
|
+
<CustomProjection
|
|
300
|
+
data={ [{states: stateToShow, counties: countiesToShow }] }
|
|
301
|
+
// translate={translate}
|
|
302
|
+
// scale={scale}
|
|
303
|
+
projection={geoAlbersUsaTerritories}
|
|
304
|
+
fitExtent={[[[PADDING, PADDING], [WIDTH - PADDING, HEIGHT - PADDING]], stateToShow]}
|
|
305
|
+
>
|
|
306
|
+
{ ({ features, projection }) => {
|
|
307
|
+
return (
|
|
308
|
+
<g
|
|
309
|
+
id="mapGroup"
|
|
310
|
+
className="countyMapGroup"
|
|
311
|
+
transform={`translate(${translate}) scale(${scale})`}
|
|
312
|
+
data-scale=""
|
|
313
|
+
key="countyMapGroup">
|
|
314
|
+
{ constructGeoJsx(features, geoAlbersUsaTerritories) }
|
|
315
|
+
</g>
|
|
316
|
+
)
|
|
317
|
+
}}
|
|
318
|
+
</CustomProjection>
|
|
319
|
+
</svg>
|
|
320
|
+
}
|
|
321
|
+
{!stateToShow && 'No State Picked'}
|
|
322
|
+
</ErrorBoundary>
|
|
323
|
+
);
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
export default memo(SingleStateMap)
|