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