@cdc/map 4.25.3 → 4.25.6
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/.idea/map.iml +12 -0
- package/.idea/modules.xml +8 -0
- package/.idea/vcs.xml +6 -0
- package/dist/cdcmap.js +31254 -32242
- package/examples/hex-colors.json +3 -3
- package/examples/m2.json +32904 -0
- package/examples/private/test.json +470 -1457
- package/examples/private/{mmr.json → wastewatermap.json} +86 -115
- package/index.html +36 -63
- package/package.json +7 -19
- package/src/CdcMap.tsx +56 -1552
- package/src/CdcMapComponent.tsx +608 -0
- package/src/_stories/CdcMap.Legend.Gradient.stories.tsx +10 -0
- package/src/_stories/CdcMap.Legend.stories.tsx +67 -0
- package/src/_stories/CdcMap.Table.stories.tsx +19 -0
- package/src/_stories/CdcMap.stories.tsx +12 -1
- package/src/_stories/UsaMap.NoData.stories.tsx +4 -4
- package/src/_stories/_mock/default-patterns.json +8 -5
- package/src/_stories/_mock/legend-bins.json +428 -0
- package/{examples/private/default-patterns.json → src/_stories/_mock/legends/legend-tests.json} +36 -131
- package/src/cdcMapComponent.styles.css +9 -0
- package/src/components/Annotation/Annotation.Draggable.tsx +27 -26
- package/src/components/Annotation/AnnotationDropdown.tsx +5 -6
- package/src/components/BubbleList.tsx +135 -49
- package/src/components/CityList.tsx +89 -87
- package/src/components/DataTable.tsx +8 -8
- package/src/components/EditorPanel/components/EditorPanel.tsx +823 -885
- package/src/components/EditorPanel/components/Error.tsx +9 -2
- package/src/components/EditorPanel/components/HexShapeSettings.tsx +127 -141
- package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +55 -86
- package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +89 -75
- package/src/components/EditorPanel/components/editorPanel.styles.css +95 -0
- package/src/components/Geo.tsx +9 -1
- package/src/components/GoogleMap/components/GoogleMap.tsx +1 -1
- package/src/components/Legend/components/Legend.tsx +92 -87
- package/src/components/Legend/components/LegendGroup/Legend.Group.tsx +128 -0
- package/src/components/Legend/components/LegendGroup/legend.group.css +27 -0
- package/src/components/Legend/components/LegendItem.Hex.tsx +4 -1
- package/src/components/Legend/components/index.scss +74 -17
- package/src/components/Modal.tsx +17 -7
- package/src/components/NavigationMenu.tsx +11 -9
- package/src/components/UsaMap/components/SingleState/SingleState.CountyOutput.tsx +12 -8
- package/src/components/UsaMap/components/SingleState/SingleState.StateOutput.tsx +4 -4
- package/src/components/UsaMap/components/TerritoriesSection.tsx +33 -10
- package/src/components/UsaMap/components/Territory/Territory.Hexagon.tsx +12 -10
- package/src/components/UsaMap/components/Territory/Territory.Rectangle.tsx +12 -14
- package/src/components/UsaMap/components/Territory/TerritoryShape.ts +2 -1
- package/src/components/UsaMap/components/UsaMap.County.tsx +138 -96
- package/src/components/UsaMap/components/UsaMap.Region.styles.css +72 -0
- package/src/components/UsaMap/components/UsaMap.Region.tsx +56 -103
- package/src/components/UsaMap/components/UsaMap.SingleState.styles.css +10 -0
- package/src/components/UsaMap/components/UsaMap.SingleState.tsx +65 -74
- package/src/components/UsaMap/components/UsaMap.State.tsx +112 -91
- package/src/components/UsaMap/helpers/map.ts +1 -1
- package/src/components/UsaMap/helpers/shapes.ts +20 -7
- package/src/components/WorldMap/WorldMap.tsx +64 -118
- package/src/components/WorldMap/worldMap.styles.css +28 -0
- package/src/components/ZoomControls.tsx +15 -13
- package/src/components/zoomControls.styles.css +53 -0
- package/src/context.ts +17 -9
- package/src/data/initial-state.js +5 -2
- package/src/helpers/addUIDs.ts +150 -0
- package/src/helpers/applyColorToLegend.ts +39 -64
- package/src/helpers/applyLegendToRow.ts +51 -0
- package/src/helpers/colorDistributions.ts +12 -0
- package/src/helpers/constants.ts +44 -0
- package/src/helpers/displayGeoName.ts +9 -2
- package/src/helpers/formatLegendLocation.ts +3 -2
- package/src/helpers/generateColorsArray.ts +2 -1
- package/src/helpers/generateRuntimeData.ts +78 -0
- package/src/helpers/generateRuntimeFilters.ts +63 -0
- package/src/helpers/generateRuntimeLegend.ts +566 -0
- package/src/helpers/generateRuntimeLegendHash.ts +16 -15
- package/src/helpers/getColumnNames.ts +19 -0
- package/src/helpers/getMapContainerClasses.ts +23 -0
- package/src/helpers/getStatePicked.ts +8 -0
- package/src/helpers/handleMapTabbing.ts +31 -0
- package/src/helpers/hashObj.ts +1 -1
- package/src/helpers/index.ts +22 -0
- package/src/helpers/navigationHandler.ts +3 -3
- package/src/helpers/resetLegendToggles.ts +13 -0
- package/src/helpers/setBinNumbers.ts +5 -0
- package/src/helpers/sortSpecialClassesLast.ts +7 -0
- package/src/helpers/tests/getColumnNames.test.ts +52 -0
- package/src/helpers/titleCase.ts +1 -1
- package/src/helpers/toggleLegendActive.ts +25 -0
- package/src/hooks/useApplyTooltipsToGeo.tsx +51 -0
- package/src/hooks/useColumnsRequiredChecker.ts +51 -0
- package/src/hooks/useGeoClickHandler.ts +45 -0
- package/src/hooks/useLegendSeparators.ts +26 -0
- package/src/hooks/useMapLayers.tsx +34 -60
- package/src/hooks/useModal.ts +22 -0
- package/src/hooks/useResizeObserver.ts +4 -5
- package/src/hooks/useStateZoom.tsx +52 -75
- package/src/hooks/useTooltip.ts +2 -3
- package/src/index.jsx +3 -9
- package/src/scss/editor-panel.scss +3 -99
- package/src/scss/main.scss +1 -19
- package/src/scss/map.scss +15 -220
- package/src/store/map.actions.ts +46 -0
- package/src/store/map.reducer.ts +96 -0
- package/src/types/Annotations.ts +24 -0
- package/src/types/MapConfig.ts +23 -3
- package/src/types/MapContext.ts +36 -35
- package/src/types/Modal.ts +1 -0
- package/src/types/RuntimeData.ts +3 -0
- package/examples/private/DEV-9644.json +0 -184
- package/examples/private/DEV-9989.json +0 -229
- package/examples/private/ardi.json +0 -180
- package/examples/private/colors 2.json +0 -416
- package/examples/private/colors.json +0 -416
- package/examples/private/colors.json.zip +0 -0
- package/examples/private/customColors.json +0 -45348
- package/examples/test.json +0 -183
- package/src/helpers/closeModal.ts +0 -9
- package/src/scss/btn.scss +0 -69
- package/src/scss/filters.scss +0 -27
- package/src/scss/variables.scss +0 -1
- /package/src/hooks/{useActiveElement.js → useActiveElement.ts} +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import React, { memo, useContext, useEffect, useState } from 'react'
|
|
2
2
|
|
|
3
3
|
// 3rd party
|
|
4
4
|
import { geoCentroid } from 'd3-geo'
|
|
@@ -15,10 +15,25 @@ import { supportedTerritories } from '../../../data/supported-geos'
|
|
|
15
15
|
|
|
16
16
|
// Helpers
|
|
17
17
|
import { getContrastColor } from '@cdc/core/helpers/cove/accessibility'
|
|
18
|
-
import { getGeoFillColor, getGeoStrokeColor } from '../../../helpers
|
|
19
|
-
import
|
|
18
|
+
import { displayGeoName, getGeoFillColor, getGeoStrokeColor, handleMapAriaLabels, SVG_VIEWBOX } from '../../../helpers'
|
|
19
|
+
import useGeoClickHandler from '../../../hooks/useGeoClickHandler'
|
|
20
|
+
import useApplyTooltipsToGeo from '../../../hooks/useApplyTooltipsToGeo'
|
|
21
|
+
import './UsaMap.Region.styles.css'
|
|
22
|
+
import { applyLegendToRow } from '../../../helpers/applyLegendToRow'
|
|
23
|
+
|
|
24
|
+
type TerritoryRectProps = {
|
|
25
|
+
posX?: number
|
|
26
|
+
tName: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
type RectProps = {
|
|
30
|
+
label: string
|
|
31
|
+
text: string
|
|
32
|
+
stroke: string
|
|
33
|
+
strokeWidth: number
|
|
34
|
+
}
|
|
20
35
|
|
|
21
|
-
const Rect = ({ label, text, stroke, strokeWidth, ...props }) => {
|
|
36
|
+
const Rect: React.FC<RectProps> = ({ label, text, stroke, strokeWidth, ...props }) => {
|
|
22
37
|
return (
|
|
23
38
|
<svg viewBox='0 0 45 28'>
|
|
24
39
|
<g {...props} strokeLinejoin='round'>
|
|
@@ -35,22 +50,17 @@ const Rect = ({ label, text, stroke, strokeWidth, ...props }) => {
|
|
|
35
50
|
)
|
|
36
51
|
}
|
|
37
52
|
|
|
38
|
-
const UsaRegionMap =
|
|
39
|
-
|
|
40
|
-
const {
|
|
41
|
-
applyLegendToRow,
|
|
42
|
-
applyTooltipsToGeo,
|
|
43
|
-
data,
|
|
44
|
-
displayGeoName,
|
|
45
|
-
geoClickHandler,
|
|
46
|
-
state,
|
|
47
|
-
tooltipId
|
|
48
|
-
} = useContext(ConfigContext)
|
|
49
|
-
|
|
50
|
-
// "Choose State" options
|
|
51
|
-
const [extent, setExtent] = useState(null)
|
|
53
|
+
const UsaRegionMap = () => {
|
|
54
|
+
const { data, config, tooltipId, legendMemo, legendSpecialClassLastMemo, runtimeLegend } = useContext(ConfigContext)
|
|
52
55
|
const [focusedStates, setFocusedStates] = useState(null)
|
|
53
|
-
const
|
|
56
|
+
const { geoClickHandler } = useGeoClickHandler()
|
|
57
|
+
const { applyTooltipsToGeo } = useApplyTooltipsToGeo()
|
|
58
|
+
const { general } = config
|
|
59
|
+
const { displayStateLabels, territoriesLabel, displayAsHex, type } = general
|
|
60
|
+
const tooltipInteractionType = config.tooltips.appearanceType
|
|
61
|
+
const isHex = displayAsHex
|
|
62
|
+
const [territoriesData, setTerritoriesData] = useState([])
|
|
63
|
+
const CIRCLE_RADIUS = 15
|
|
54
64
|
|
|
55
65
|
useEffect(() => {
|
|
56
66
|
const fetchData = async () => {
|
|
@@ -61,16 +71,6 @@ const UsaRegionMap = props => {
|
|
|
61
71
|
fetchData()
|
|
62
72
|
}, [])
|
|
63
73
|
|
|
64
|
-
// When returning from another map we want to reset the state
|
|
65
|
-
useEffect(() => {
|
|
66
|
-
setTranslate([455, 250])
|
|
67
|
-
setExtent(null)
|
|
68
|
-
}, [state.general.geoType])
|
|
69
|
-
|
|
70
|
-
const isHex = state.general.displayAsHex
|
|
71
|
-
|
|
72
|
-
const [territoriesData, setTerritoriesData] = useState([])
|
|
73
|
-
|
|
74
74
|
const territoriesKeys = Object.keys(supportedTerritories) // data will have already mapped abbreviated territories to their full names
|
|
75
75
|
|
|
76
76
|
useEffect(() => {
|
|
@@ -84,19 +84,18 @@ const UsaRegionMap = props => {
|
|
|
84
84
|
return <></>
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
const geoStrokeColor = getGeoStrokeColor(
|
|
88
|
-
const geoFillColor = getGeoFillColor(
|
|
87
|
+
const geoStrokeColor = getGeoStrokeColor(config)
|
|
88
|
+
const geoFillColor = getGeoFillColor(config)
|
|
89
89
|
|
|
90
90
|
const territories = territoriesData.map(territory => {
|
|
91
91
|
const Shape = Rect
|
|
92
92
|
|
|
93
93
|
const territoryData = data[territory]
|
|
94
94
|
|
|
95
|
-
let toolTip
|
|
95
|
+
let toolTip: string
|
|
96
96
|
|
|
97
97
|
let styles: React.CSSProperties = {
|
|
98
|
-
fill: geoFillColor
|
|
99
|
-
color: '#202020'
|
|
98
|
+
fill: geoFillColor
|
|
100
99
|
}
|
|
101
100
|
|
|
102
101
|
const label = supportedTerritories[territory][1]
|
|
@@ -105,7 +104,7 @@ const UsaRegionMap = props => {
|
|
|
105
104
|
|
|
106
105
|
toolTip = applyTooltipsToGeo(displayGeoName(territory), territoryData)
|
|
107
106
|
|
|
108
|
-
const legendColors = applyLegendToRow(territoryData)
|
|
107
|
+
const legendColors = applyLegendToRow(territoryData, config, runtimeLegend, legendMemo, legendSpecialClassLastMemo)
|
|
109
108
|
|
|
110
109
|
if (legendColors) {
|
|
111
110
|
const textColor = getContrastColor('#FFF', legendColors[0])
|
|
@@ -114,8 +113,8 @@ const UsaRegionMap = props => {
|
|
|
114
113
|
|
|
115
114
|
// If we need to add a pointer cursor
|
|
116
115
|
if (
|
|
117
|
-
(
|
|
118
|
-
|
|
116
|
+
(config.columns.navigate && territoryData[config.columns.navigate.name]) ||
|
|
117
|
+
tooltipInteractionType === 'click'
|
|
119
118
|
) {
|
|
120
119
|
needsPointer = true
|
|
121
120
|
}
|
|
@@ -148,46 +147,9 @@ const UsaRegionMap = props => {
|
|
|
148
147
|
}
|
|
149
148
|
})
|
|
150
149
|
|
|
151
|
-
const geoLabel = (geo, bgColor = '#FFFFFF', projection) => {
|
|
152
|
-
let centroid = projection(geoCentroid(geo))
|
|
153
|
-
let abbr = geo.properties.iso
|
|
154
|
-
|
|
155
|
-
if (undefined === abbr) return null
|
|
156
|
-
|
|
157
|
-
const textColor = getContrastColor('#FFF', bgColor)
|
|
158
|
-
|
|
159
|
-
let x = 0,
|
|
160
|
-
y = 5
|
|
161
|
-
|
|
162
|
-
return (
|
|
163
|
-
<g>
|
|
164
|
-
<line
|
|
165
|
-
x1={centroid[0]}
|
|
166
|
-
y1={centroid[1]}
|
|
167
|
-
x2={centroid[0] + x}
|
|
168
|
-
y2={centroid[1] + y}
|
|
169
|
-
stroke='rgba(0,0,0,.5)'
|
|
170
|
-
strokeWidth={1}
|
|
171
|
-
/>
|
|
172
|
-
<text
|
|
173
|
-
x={4}
|
|
174
|
-
strokeWidth='0'
|
|
175
|
-
fontSize={13}
|
|
176
|
-
style={{ fill: '#202020' }}
|
|
177
|
-
alignmentBaseline='middle'
|
|
178
|
-
transform={`translate(${centroid[0] + x}, ${centroid[1] + y})`}
|
|
179
|
-
>
|
|
180
|
-
{abbr.substring(3)}
|
|
181
|
-
</text>
|
|
182
|
-
</g>
|
|
183
|
-
)
|
|
184
|
-
}
|
|
185
|
-
|
|
186
150
|
// Constructs and displays markup for all geos on the map (except territories right now)
|
|
187
151
|
const constructGeoJsx = (geographies, projection) => {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
const geosJsx = geographies.map(({ feature: geo, path = '', index }) => {
|
|
152
|
+
return geographies.map(({ feature: geo, path = '', index }) => {
|
|
191
153
|
const key = isHex ? geo.properties.iso + '-hex-group' : geo.properties.iso + '-group'
|
|
192
154
|
|
|
193
155
|
let styles = {
|
|
@@ -198,8 +160,6 @@ const UsaRegionMap = props => {
|
|
|
198
160
|
// Map the name from the geo data with the appropriate key for the processed data
|
|
199
161
|
let geoKey = geo.properties.iso
|
|
200
162
|
|
|
201
|
-
// Manually add Washington D.C. in for Hex maps
|
|
202
|
-
|
|
203
163
|
if (!geoKey) return
|
|
204
164
|
|
|
205
165
|
const geoData = data[geoKey]
|
|
@@ -207,7 +167,7 @@ const UsaRegionMap = props => {
|
|
|
207
167
|
let legendColors
|
|
208
168
|
// Once we receive data for this geographic item, setup variables.
|
|
209
169
|
if (geoData !== undefined) {
|
|
210
|
-
legendColors = applyLegendToRow(geoData)
|
|
170
|
+
legendColors = applyLegendToRow(geoData, config, runtimeLegend, legendMemo, legendSpecialClassLastMemo)
|
|
211
171
|
}
|
|
212
172
|
|
|
213
173
|
const geoDisplayName = displayGeoName(geoKey)
|
|
@@ -217,28 +177,25 @@ const UsaRegionMap = props => {
|
|
|
217
177
|
const toolTip = applyTooltipsToGeo(geoDisplayName, geoData)
|
|
218
178
|
|
|
219
179
|
styles = {
|
|
220
|
-
fill:
|
|
180
|
+
fill: type !== 'bubble' ? legendColors[0] : geoFillColor,
|
|
221
181
|
cursor: 'default',
|
|
222
182
|
'&:hover': {
|
|
223
|
-
fill:
|
|
183
|
+
fill: type !== 'bubble' ? legendColors[1] : geoFillColor
|
|
224
184
|
},
|
|
225
185
|
'&:active': {
|
|
226
|
-
fill:
|
|
186
|
+
fill: type !== 'bubble' ? legendColors[2] : geoFillColor
|
|
227
187
|
}
|
|
228
188
|
}
|
|
229
189
|
|
|
230
190
|
// When to add pointer cursor
|
|
231
|
-
if (
|
|
232
|
-
(state.columns.navigate && geoData[state.columns.navigate.name]) ||
|
|
233
|
-
state.tooltips.appearanceType === 'click'
|
|
234
|
-
) {
|
|
191
|
+
if ((config.columns.navigate && geoData[config.columns.navigate.name]) || tooltipInteractionType === 'click') {
|
|
235
192
|
styles.cursor = 'pointer'
|
|
236
193
|
}
|
|
237
194
|
|
|
238
|
-
const
|
|
195
|
+
const TerritoryRect: React.FC<TerritoryRectProps> = props => {
|
|
239
196
|
const { posX = 0, tName } = props
|
|
240
197
|
const textColor = getContrastColor('#FFF', legendColors[0])
|
|
241
|
-
const geoStrokeColor = getGeoStrokeColor(
|
|
198
|
+
const geoStrokeColor = getGeoStrokeColor(config)
|
|
242
199
|
return (
|
|
243
200
|
<>
|
|
244
201
|
<rect x={posX} width='36' height='24' rx='2' stroke={geoStrokeColor} strokeWidth='1' />
|
|
@@ -249,8 +206,6 @@ const UsaRegionMap = props => {
|
|
|
249
206
|
)
|
|
250
207
|
}
|
|
251
208
|
|
|
252
|
-
const circleRadius = 15
|
|
253
|
-
|
|
254
209
|
return (
|
|
255
210
|
<g
|
|
256
211
|
key={key}
|
|
@@ -263,29 +218,29 @@ const UsaRegionMap = props => {
|
|
|
263
218
|
>
|
|
264
219
|
<path tabIndex={-1} className='single-geo' stroke={geoStrokeColor} strokeWidth={1} d={path} />
|
|
265
220
|
<g id={`region-${index + 1}-label`}>
|
|
266
|
-
<circle fill='#fff' stroke='#999' cx={
|
|
221
|
+
<circle fill='#fff' stroke='#999' cx={CIRCLE_RADIUS} cy={CIRCLE_RADIUS} r={CIRCLE_RADIUS} />
|
|
267
222
|
<text fill='#333' x='15px' y='20px' textAnchor='middle'>
|
|
268
223
|
{index + 1}
|
|
269
224
|
</text>
|
|
270
225
|
</g>
|
|
271
226
|
{geoKey === 'region 2' && (
|
|
272
227
|
<g id='region-2-territories'>
|
|
273
|
-
<
|
|
274
|
-
<
|
|
228
|
+
<TerritoryRect tName='PR' />
|
|
229
|
+
<TerritoryRect posX={45} tName='VI' />
|
|
275
230
|
</g>
|
|
276
231
|
)}
|
|
277
232
|
|
|
278
233
|
{geoKey === 'region 9' && (
|
|
279
234
|
<g id='region-9-territories'>
|
|
280
235
|
<g className='region-9-row1'>
|
|
281
|
-
<
|
|
282
|
-
<
|
|
283
|
-
<
|
|
236
|
+
<TerritoryRect tName='AS' />
|
|
237
|
+
<TerritoryRect posX={45} tName='GU' />
|
|
238
|
+
<TerritoryRect posX={90} tName='MP' />
|
|
284
239
|
</g>
|
|
285
240
|
<g className='region-9-row2'>
|
|
286
|
-
<
|
|
287
|
-
<
|
|
288
|
-
<
|
|
241
|
+
<TerritoryRect tName='FM' />
|
|
242
|
+
<TerritoryRect posX={45} tName='PW' />
|
|
243
|
+
<TerritoryRect posX={90} tName='MH' />
|
|
289
244
|
</g>
|
|
290
245
|
</g>
|
|
291
246
|
)}
|
|
@@ -297,24 +252,22 @@ const UsaRegionMap = props => {
|
|
|
297
252
|
return (
|
|
298
253
|
<g key={key} className='geo-group' style={styles}>
|
|
299
254
|
<path tabIndex={-1} className='single-geo' stroke={geoStrokeColor} strokeWidth={1} d={path} />
|
|
300
|
-
{(isHex || showLabel) && geoLabel(geo, styles.fill, projection)}
|
|
301
255
|
</g>
|
|
302
256
|
)
|
|
303
257
|
})
|
|
304
|
-
return geosJsx
|
|
305
258
|
}
|
|
306
259
|
|
|
307
260
|
return (
|
|
308
261
|
<ErrorBoundary component='UsaRegionMap'>
|
|
309
|
-
<svg viewBox=
|
|
262
|
+
<svg viewBox={SVG_VIEWBOX} role='img' aria-label={handleMapAriaLabels(config)}>
|
|
310
263
|
<Mercator data={focusedStates} scale={620} translate={[1500, 735]}>
|
|
311
264
|
{({ features, projection }) => constructGeoJsx(features, projection)}
|
|
312
265
|
</Mercator>
|
|
313
|
-
{
|
|
266
|
+
{config.annotations.length > 0 && <Annotation.Draggable />}
|
|
314
267
|
</svg>
|
|
315
268
|
{territories.length > 0 && (
|
|
316
269
|
<section className='territories'>
|
|
317
|
-
<span className='label'>{
|
|
270
|
+
<span className='label'>{territoriesLabel}</span>
|
|
318
271
|
{territories}
|
|
319
272
|
</section>
|
|
320
273
|
)}
|
|
@@ -1,75 +1,71 @@
|
|
|
1
|
-
import { useEffect, memo, useContext
|
|
1
|
+
import { useEffect, memo, useContext } from 'react'
|
|
2
2
|
import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
3
3
|
import { geoPath } from 'd3-geo'
|
|
4
4
|
import { CustomProjection } from '@visx/geo'
|
|
5
5
|
import Loading from '@cdc/core/components/Loading'
|
|
6
6
|
import { geoAlbersUsaTerritories } from 'd3-composite-projections'
|
|
7
7
|
import CityList from '../../CityList'
|
|
8
|
-
import ConfigContext from '../../../context'
|
|
8
|
+
import ConfigContext, { MapDispatchContext } from '../../../context'
|
|
9
9
|
import Annotation from '../../Annotation'
|
|
10
10
|
import SingleState from './SingleState'
|
|
11
|
-
import { getTopoData, getCurrentTopoYear, isTopoReady } from './../helpers/map'
|
|
12
11
|
import ZoomableGroup from '../../ZoomableGroup'
|
|
13
12
|
import ZoomControls from '../../ZoomControls'
|
|
14
13
|
import { MapContext } from '../../../types/MapContext'
|
|
15
14
|
import useStateZoom from '../../../hooks/useStateZoom'
|
|
16
15
|
import { Text } from '@visx/text'
|
|
17
|
-
import { getGeoStrokeColor } from '../../../helpers/colors'
|
|
18
|
-
import { handleMapAriaLabels } from '../../../helpers/handleMapAriaLabels'
|
|
19
|
-
import { titleCase } from '../../../helpers/titleCase'
|
|
20
16
|
|
|
21
|
-
|
|
22
|
-
const WIDTH = 880
|
|
23
|
-
const HEIGHT = 500
|
|
24
|
-
const PADDING = 25
|
|
17
|
+
import './UsaMap.SingleState.styles.css'
|
|
25
18
|
|
|
26
|
-
|
|
19
|
+
// map-level helpers
|
|
20
|
+
import { titleCase, handleMapAriaLabels, getGeoStrokeColor, MAX_ZOOM_LEVEL } from '../../../helpers'
|
|
21
|
+
|
|
22
|
+
// state-level helpers
|
|
23
|
+
import { getTopoData, getCurrentTopoYear, isTopoReady } from '../helpers/map'
|
|
24
|
+
import useGeoClickHandler from '../../../hooks/useGeoClickHandler'
|
|
25
|
+
import { SVG_WIDTH, SVG_HEIGHT, SVG_PADDING, SVG_VIEWBOX } from '../../../helpers'
|
|
26
|
+
import _ from 'lodash'
|
|
27
|
+
import { getStatePicked } from '../../../helpers/getStatePicked'
|
|
28
|
+
|
|
29
|
+
const SingleStateMap: React.FC = () => {
|
|
27
30
|
const {
|
|
28
|
-
|
|
29
|
-
applyTooltipsToGeo,
|
|
30
|
-
data,
|
|
31
|
-
geoClickHandler,
|
|
32
|
-
applyLegendToRow,
|
|
33
|
-
displayGeoName,
|
|
31
|
+
config,
|
|
34
32
|
setSharedFilterValue,
|
|
35
33
|
isFilterValueSupported,
|
|
36
34
|
runtimeFilters,
|
|
35
|
+
runtimeData,
|
|
37
36
|
tooltipId,
|
|
38
37
|
position,
|
|
39
|
-
setPosition,
|
|
40
|
-
stateToShow,
|
|
41
38
|
topoData,
|
|
42
|
-
setTopoData,
|
|
43
39
|
scale,
|
|
44
|
-
translate
|
|
45
|
-
setStateToShow
|
|
40
|
+
translate
|
|
46
41
|
} = useContext<MapContext>(ConfigContext)
|
|
47
42
|
|
|
48
|
-
const
|
|
43
|
+
const dispatch = useContext(MapDispatchContext)
|
|
44
|
+
const { handleMoveEnd, handleZoomIn, handleZoomOut, handleReset, projection } = useStateZoom(topoData)
|
|
45
|
+
const statePicked = getStatePicked(config, runtimeData)
|
|
46
|
+
const stateToShow = topoData?.states?.find(s => s.properties.name === statePicked.stateName)
|
|
47
|
+
|
|
48
|
+
const { geoClickHandler } = useGeoClickHandler()
|
|
49
49
|
|
|
50
50
|
const cityListProjection = geoAlbersUsaTerritories()
|
|
51
|
-
.translate([
|
|
51
|
+
.translate([SVG_WIDTH / 2, SVG_HEIGHT / 2])
|
|
52
52
|
.scale(1)
|
|
53
|
-
const geoStrokeColor = getGeoStrokeColor(
|
|
53
|
+
const geoStrokeColor = getGeoStrokeColor(config)
|
|
54
54
|
const path = geoPath().projection(projection)
|
|
55
55
|
|
|
56
56
|
useEffect(() => {
|
|
57
|
-
|
|
58
|
-
}, [statePicked])
|
|
59
|
-
|
|
60
|
-
useEffect(() => {
|
|
61
|
-
let currentYear = getCurrentTopoYear(state, runtimeFilters)
|
|
57
|
+
let currentYear = getCurrentTopoYear(config, runtimeFilters)
|
|
62
58
|
|
|
63
|
-
if (currentYear !== topoData
|
|
59
|
+
if (currentYear !== topoData?.year) {
|
|
64
60
|
getTopoData(currentYear).then(response => {
|
|
65
|
-
|
|
61
|
+
dispatch({ type: 'SET_TOPO_DATA', payload: response })
|
|
66
62
|
})
|
|
67
63
|
}
|
|
68
|
-
}, [
|
|
64
|
+
}, [config.general.countyCensusYear, config.general.filterControlsCountyYear, JSON.stringify(runtimeFilters)])
|
|
69
65
|
|
|
70
|
-
if (!isTopoReady(topoData,
|
|
66
|
+
if (!isTopoReady(topoData, config, runtimeFilters)) {
|
|
71
67
|
return (
|
|
72
|
-
<div style={{ height: `${
|
|
68
|
+
<div style={{ height: `${SVG_HEIGHT}px` }}>
|
|
73
69
|
<Loading />
|
|
74
70
|
</div>
|
|
75
71
|
)
|
|
@@ -77,16 +73,16 @@ const SingleStateMap = props => {
|
|
|
77
73
|
|
|
78
74
|
const checkForNoData = () => {
|
|
79
75
|
// If no statePicked, return true
|
|
80
|
-
if (!
|
|
76
|
+
if (!statePicked.fipsCode) return true
|
|
81
77
|
}
|
|
82
78
|
|
|
83
79
|
// Constructs and displays markup for all geos on the map (except territories right now)
|
|
84
|
-
const constructGeoJsx =
|
|
80
|
+
const constructGeoJsx = geographies => {
|
|
85
81
|
const counties = geographies[0].feature.counties
|
|
86
82
|
|
|
87
83
|
let geosJsx = []
|
|
88
84
|
|
|
89
|
-
// Push
|
|
85
|
+
// Push config lines
|
|
90
86
|
geosJsx.push(
|
|
91
87
|
// prettier-ignore
|
|
92
88
|
<SingleState.StateOutput
|
|
@@ -113,12 +109,7 @@ const SingleStateMap = props => {
|
|
|
113
109
|
<CityList
|
|
114
110
|
projection={cityListProjection}
|
|
115
111
|
key='cities'
|
|
116
|
-
data={data}
|
|
117
|
-
state={state}
|
|
118
112
|
geoClickHandler={geoClickHandler}
|
|
119
|
-
applyTooltipsToGeo={applyTooltipsToGeo}
|
|
120
|
-
displayGeoName={displayGeoName}
|
|
121
|
-
applyLegendToRow={applyLegendToRow}
|
|
122
113
|
titleCase={titleCase}
|
|
123
114
|
setSharedFilterValue={setSharedFilterValue}
|
|
124
115
|
isFilterValueSupported={isFilterValueSupported}
|
|
@@ -130,28 +121,28 @@ const SingleStateMap = props => {
|
|
|
130
121
|
}
|
|
131
122
|
return (
|
|
132
123
|
<ErrorBoundary component='SingleStateMap'>
|
|
133
|
-
{statePicked &&
|
|
124
|
+
{statePicked && config.general.allowMapZoom && statePicked.fipsCode && (
|
|
134
125
|
<svg
|
|
135
|
-
viewBox={
|
|
126
|
+
viewBox={SVG_VIEWBOX}
|
|
136
127
|
preserveAspectRatio='xMinYMin'
|
|
137
128
|
className='svg-container'
|
|
138
129
|
role='img'
|
|
139
|
-
aria-label={handleMapAriaLabels(
|
|
130
|
+
aria-label={handleMapAriaLabels(config)}
|
|
140
131
|
>
|
|
141
132
|
<ZoomableGroup
|
|
142
133
|
center={position.coordinates}
|
|
143
134
|
zoom={position.zoom}
|
|
144
135
|
minZoom={1} // Adjust this value if needed
|
|
145
|
-
maxZoom={
|
|
136
|
+
maxZoom={MAX_ZOOM_LEVEL}
|
|
146
137
|
onMoveEnd={handleMoveEnd}
|
|
147
138
|
projection={projection}
|
|
148
|
-
width={
|
|
149
|
-
height={
|
|
139
|
+
width={SVG_WIDTH}
|
|
140
|
+
height={SVG_HEIGHT}
|
|
150
141
|
>
|
|
151
142
|
<rect
|
|
152
143
|
className='background center-container ocean'
|
|
153
|
-
width={
|
|
154
|
-
height={
|
|
144
|
+
width={SVG_WIDTH}
|
|
145
|
+
height={SVG_HEIGHT}
|
|
155
146
|
fillOpacity={1}
|
|
156
147
|
fill='white'
|
|
157
148
|
></rect>
|
|
@@ -159,14 +150,14 @@ const SingleStateMap = props => {
|
|
|
159
150
|
data={[
|
|
160
151
|
{
|
|
161
152
|
states: topoData?.states,
|
|
162
|
-
counties: topoData.counties.filter(c => c.id.substring(0, 2) ===
|
|
153
|
+
counties: topoData.counties.filter(c => c.id.substring(0, 2) === statePicked.fipsCode)
|
|
163
154
|
}
|
|
164
155
|
]}
|
|
165
156
|
projection={geoAlbersUsaTerritories}
|
|
166
157
|
fitExtent={[
|
|
167
158
|
[
|
|
168
|
-
[
|
|
169
|
-
[
|
|
159
|
+
[SVG_PADDING, SVG_PADDING],
|
|
160
|
+
[SVG_WIDTH - SVG_PADDING, SVG_HEIGHT - SVG_PADDING]
|
|
170
161
|
],
|
|
171
162
|
stateToShow
|
|
172
163
|
]}
|
|
@@ -176,7 +167,7 @@ const SingleStateMap = props => {
|
|
|
176
167
|
<g
|
|
177
168
|
id='mapGroup'
|
|
178
169
|
className={`countyMapGroup ${
|
|
179
|
-
|
|
170
|
+
config.general.geoType === 'single-state' ? `countyMapGroup--no-transition` : ''
|
|
180
171
|
}`}
|
|
181
172
|
transform={`translate(${translate}) scale(${scale})`}
|
|
182
173
|
data-scale=''
|
|
@@ -187,22 +178,22 @@ const SingleStateMap = props => {
|
|
|
187
178
|
)
|
|
188
179
|
}}
|
|
189
180
|
</CustomProjection>
|
|
190
|
-
{
|
|
181
|
+
{config.annotations.length > 0 && <Annotation.Draggable />}
|
|
191
182
|
</ZoomableGroup>
|
|
192
183
|
</svg>
|
|
193
184
|
)}
|
|
194
|
-
{statePicked && !
|
|
185
|
+
{statePicked && !config.general.allowMapZoom && statePicked.fipsCode && (
|
|
195
186
|
<svg
|
|
196
|
-
viewBox={
|
|
187
|
+
viewBox={SVG_VIEWBOX}
|
|
197
188
|
preserveAspectRatio='xMinYMin'
|
|
198
189
|
className='svg-container'
|
|
199
190
|
role='img'
|
|
200
|
-
aria-label={handleMapAriaLabels(
|
|
191
|
+
aria-label={handleMapAriaLabels(config)}
|
|
201
192
|
>
|
|
202
193
|
<rect
|
|
203
194
|
className='background center-container ocean'
|
|
204
|
-
width={
|
|
205
|
-
height={
|
|
195
|
+
width={SVG_WIDTH}
|
|
196
|
+
height={SVG_HEIGHT}
|
|
206
197
|
fillOpacity={1}
|
|
207
198
|
fill='white'
|
|
208
199
|
></rect>
|
|
@@ -210,56 +201,56 @@ const SingleStateMap = props => {
|
|
|
210
201
|
data={[
|
|
211
202
|
{
|
|
212
203
|
states: topoData?.states,
|
|
213
|
-
counties: topoData.counties.filter(c => c.id.substring(0, 2) ===
|
|
204
|
+
counties: topoData.counties.filter(c => c.id.substring(0, 2) === statePicked.fipsCode)
|
|
214
205
|
}
|
|
215
206
|
]}
|
|
216
207
|
projection={geoAlbersUsaTerritories}
|
|
217
208
|
fitExtent={[
|
|
218
209
|
[
|
|
219
|
-
[
|
|
220
|
-
[
|
|
210
|
+
[SVG_PADDING, SVG_PADDING],
|
|
211
|
+
[SVG_WIDTH - SVG_PADDING, SVG_HEIGHT - SVG_PADDING]
|
|
221
212
|
],
|
|
222
213
|
stateToShow
|
|
223
214
|
]}
|
|
224
215
|
>
|
|
225
|
-
{({ features
|
|
216
|
+
{({ features }) => {
|
|
226
217
|
return (
|
|
227
218
|
<g
|
|
228
219
|
id='mapGroup'
|
|
229
220
|
className={`countyMapGroup ${
|
|
230
|
-
|
|
221
|
+
config.general.geoType === 'single-state' ? `countyMapGroup--no-transition` : ''
|
|
231
222
|
}`}
|
|
232
223
|
transform={`translate(${translate}) scale(${scale})`}
|
|
233
224
|
data-scale=''
|
|
234
225
|
key='countyMapGroup'
|
|
235
226
|
>
|
|
236
|
-
{constructGeoJsx(features
|
|
227
|
+
{constructGeoJsx(features)}
|
|
237
228
|
</g>
|
|
238
229
|
)
|
|
239
230
|
}}
|
|
240
231
|
</CustomProjection>
|
|
241
|
-
{
|
|
232
|
+
{config.annotations.length > 0 && <Annotation.Draggable />}
|
|
242
233
|
</svg>
|
|
243
234
|
)}
|
|
244
235
|
|
|
245
236
|
{checkForNoData() && (
|
|
246
237
|
<svg
|
|
247
|
-
viewBox={
|
|
238
|
+
viewBox={SVG_VIEWBOX}
|
|
248
239
|
preserveAspectRatio='xMinYMin'
|
|
249
240
|
className='svg-container'
|
|
250
241
|
role='img'
|
|
251
|
-
aria-label={handleMapAriaLabels(
|
|
242
|
+
aria-label={handleMapAriaLabels(config)}
|
|
252
243
|
>
|
|
253
244
|
<Text
|
|
254
245
|
verticalAnchor='start'
|
|
255
246
|
textAnchor='middle'
|
|
256
|
-
x={
|
|
257
|
-
width={
|
|
258
|
-
y={
|
|
247
|
+
x={SVG_WIDTH / 2}
|
|
248
|
+
width={SVG_WIDTH}
|
|
249
|
+
y={SVG_HEIGHT / 2}
|
|
259
250
|
fontSize={18}
|
|
260
251
|
style={{ fontSize: '28px', height: '18px' }}
|
|
261
252
|
>
|
|
262
|
-
{
|
|
253
|
+
{config.general.noStateFoundMessage}
|
|
263
254
|
</Text>
|
|
264
255
|
</svg>
|
|
265
256
|
)}
|