@cdc/map 4.23.3 → 4.23.4
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 +22422 -22053
- package/examples/custom-map-layers.json +764 -0
- package/examples/default-county.json +169 -155
- package/examples/example-city-state.json +31 -9
- package/examples/testing-layer-2.json +1 -0
- package/examples/testing-layer.json +96 -0
- package/index.html +6 -5
- package/package.json +3 -3
- package/src/CdcMap.jsx +55 -50
- package/src/components/CountyMap.jsx +30 -5
- package/src/components/EditorPanel.jsx +255 -129
- package/src/components/UsaMap.jsx +17 -11
- package/src/data/initial-state.js +7 -3
- package/src/hooks/useMapLayers.jsx +243 -0
- package/src/index.jsx +4 -8
- package/src/scss/editor-panel.scss +97 -97
- package/src/scss/filters.scss +0 -2
- package/src/scss/main.scss +25 -26
- package/src/components/Filters.jsx +0 -113
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { useEffect, useId, useState } from 'react'
|
|
2
|
+
import { feature } from 'topojson-client'
|
|
3
|
+
import { Group } from '@visx/group'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* This is the starting structure for adding custom geoJSON shape layers to a projection.
|
|
7
|
+
* The expectation should be that geoJSON is saved somewhere externally.
|
|
8
|
+
*
|
|
9
|
+
* todo: save map layers to local state and add debounce fn to improve performance
|
|
10
|
+
* todo: usaMap is using objects.cove which needs to be converted to a dynamic value
|
|
11
|
+
*
|
|
12
|
+
* User Interface Expectations:
|
|
13
|
+
* 1) Direct users to https://www.google.com/maps/about/mymaps to create a map
|
|
14
|
+
* 2) Export the shape layer as a kml file and import into mapshaper.org
|
|
15
|
+
* 3) Clean (ie. mapshaper -clean) and edit the shape as needed and export the new layer as geoJSON
|
|
16
|
+
* 4) Save the geoJSON somewhere external.
|
|
17
|
+
*/
|
|
18
|
+
export default function useMapLayers(config, setConfig, pathGenerator) {
|
|
19
|
+
const [fetchedTopoJSON, setFetchedTopoJSON] = useState([])
|
|
20
|
+
const geoId = useId()
|
|
21
|
+
|
|
22
|
+
// small reminder that we export the feature and the path as options
|
|
23
|
+
const [pathArray, setPathArray] = useState([])
|
|
24
|
+
const [featureArray, setFeatureArray] = useState([])
|
|
25
|
+
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
fetchGeoJSONLayers()
|
|
28
|
+
}, []) //eslint-disable-line
|
|
29
|
+
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
fetchGeoJSONLayers()
|
|
32
|
+
}, [config.map.layers]) //eslint-disable-line
|
|
33
|
+
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
if (pathGenerator) {
|
|
36
|
+
generateCustomLayers()
|
|
37
|
+
}
|
|
38
|
+
}, [fetchedTopoJSON]) //eslint-disable-line
|
|
39
|
+
|
|
40
|
+
const fetchGeoJSONLayers = async () => {
|
|
41
|
+
let geos = await getMapTopoJSONLayers()
|
|
42
|
+
setFetchedTopoJSON(geos)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Removes a custom map layer from the config.
|
|
47
|
+
* @param { Event } e Remove onclick event
|
|
48
|
+
* @param { Integer } index index of layer to remove
|
|
49
|
+
*/
|
|
50
|
+
const handleRemoveLayer = (e, index) => {
|
|
51
|
+
e.preventDefault()
|
|
52
|
+
|
|
53
|
+
const updatedState = {
|
|
54
|
+
...config,
|
|
55
|
+
map: {
|
|
56
|
+
...config.map,
|
|
57
|
+
layers: config.map.layers.filter((layer, i) => i !== index)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
setConfig(updatedState)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Adds a new custom map layer to the config
|
|
66
|
+
* @param { Event } e Add onclick event
|
|
67
|
+
*/
|
|
68
|
+
const handleAddLayer = e => {
|
|
69
|
+
e.preventDefault()
|
|
70
|
+
const updatedState = {
|
|
71
|
+
...config,
|
|
72
|
+
map: {
|
|
73
|
+
...config.map,
|
|
74
|
+
layers: [
|
|
75
|
+
...config.map.layers,
|
|
76
|
+
{
|
|
77
|
+
name: 'New Custom Layer',
|
|
78
|
+
url: ''
|
|
79
|
+
}
|
|
80
|
+
]
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
setConfig(updatedState)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Updates the index of the layer tooltip
|
|
88
|
+
* @param {Event} e
|
|
89
|
+
* @param {Integer} index
|
|
90
|
+
*/
|
|
91
|
+
const handleMapLayerTooltip = (e, index) => {
|
|
92
|
+
e.preventDefault()
|
|
93
|
+
let newLayers = [...config.map.layers]
|
|
94
|
+
|
|
95
|
+
newLayers[index].tooltip = e.target.value
|
|
96
|
+
|
|
97
|
+
setConfig({
|
|
98
|
+
...config,
|
|
99
|
+
map: {
|
|
100
|
+
...config.map,
|
|
101
|
+
layers: newLayers
|
|
102
|
+
}
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Changes the map layer url for a given index
|
|
108
|
+
* @param {Event} e - on add custom layer click
|
|
109
|
+
* @param {Integer} index - index of layer to update
|
|
110
|
+
*/
|
|
111
|
+
const handleMapLayerUrl = (e, index) => {
|
|
112
|
+
e.preventDefault()
|
|
113
|
+
let newLayers = [...config.map.layers]
|
|
114
|
+
|
|
115
|
+
newLayers[index].url = e.target.value
|
|
116
|
+
|
|
117
|
+
setConfig({
|
|
118
|
+
...config,
|
|
119
|
+
map: {
|
|
120
|
+
...config.map,
|
|
121
|
+
layers: newLayers
|
|
122
|
+
}
|
|
123
|
+
})
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Changes the map layer name for a given index
|
|
128
|
+
* @param {Event} e - on add custom layer click
|
|
129
|
+
* @param {Integer} index - index of layer to update
|
|
130
|
+
*/
|
|
131
|
+
const handleMapLayerName = (e, index) => {
|
|
132
|
+
e.preventDefault()
|
|
133
|
+
|
|
134
|
+
let newLayers = [...config.map.layers]
|
|
135
|
+
|
|
136
|
+
newLayers[index].name = e.target.value
|
|
137
|
+
|
|
138
|
+
setConfig({
|
|
139
|
+
...config,
|
|
140
|
+
map: {
|
|
141
|
+
...config.map,
|
|
142
|
+
layers: newLayers
|
|
143
|
+
}
|
|
144
|
+
})
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Changes the map layer namespace for a given index
|
|
149
|
+
* @param {Event} e - on add custom layer click
|
|
150
|
+
* @param {Integer} index - index of layer to update
|
|
151
|
+
*/
|
|
152
|
+
const handleMapLayerNamespace = (e, index) => {
|
|
153
|
+
e.preventDefault()
|
|
154
|
+
|
|
155
|
+
let newLayers = [...config.map.layers]
|
|
156
|
+
|
|
157
|
+
newLayers[index].namespace = e.target.value
|
|
158
|
+
|
|
159
|
+
setConfig({
|
|
160
|
+
...config,
|
|
161
|
+
map: {
|
|
162
|
+
...config.map,
|
|
163
|
+
layers: newLayers
|
|
164
|
+
}
|
|
165
|
+
})
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Fetches TopoJSON urls found in config.map.layers and stores it locally.
|
|
170
|
+
* @returns
|
|
171
|
+
*/
|
|
172
|
+
const getMapTopoJSONLayers = async () => {
|
|
173
|
+
let TopoJSONObjects = []
|
|
174
|
+
if (!config.map.layers) return
|
|
175
|
+
|
|
176
|
+
for (const mapLayer of config.map.layers) {
|
|
177
|
+
let newLayerItem = await fetch(mapLayer.url)
|
|
178
|
+
.then(res => res.json())
|
|
179
|
+
.catch(e => console.warn('error with newLayer item'))
|
|
180
|
+
if (!newLayerItem) newLayerItem = []
|
|
181
|
+
TopoJSONObjects.push(newLayerItem)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return TopoJSONObjects
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Updates the custom map layers based on the topojson data
|
|
189
|
+
* @returns {void} new map layers to the config
|
|
190
|
+
*/
|
|
191
|
+
const generateCustomLayers = () => {
|
|
192
|
+
if (fetchedTopoJSON.length === 0 || !fetchedTopoJSON) return false
|
|
193
|
+
let tempArr = []
|
|
194
|
+
let tempFeatureArray = []
|
|
195
|
+
|
|
196
|
+
// loop on each file.
|
|
197
|
+
fetchedTopoJSON?.map((layer, index) => {
|
|
198
|
+
if (layer.length === 0) return null
|
|
199
|
+
let layerObjects = layer.objects[config.map.layers[index].namespace]
|
|
200
|
+
if (!layerObjects) return null
|
|
201
|
+
|
|
202
|
+
let layerData = feature(layer, layerObjects).features
|
|
203
|
+
|
|
204
|
+
// now loop on each feature
|
|
205
|
+
layerData.forEach(item => {
|
|
206
|
+
let layerClasses = [`custom-map-layer`, `custom-map-layer--${item.properties.name.replace(' ', '-')}`]
|
|
207
|
+
|
|
208
|
+
// feature array for county maps
|
|
209
|
+
tempFeatureArray.push(item)
|
|
210
|
+
|
|
211
|
+
tempArr.push(
|
|
212
|
+
<Group className={layerClasses.join(' ')} key={`customMapLayer-${item.properties.name.replace(' ', '-')}-${index}`}>
|
|
213
|
+
{/* prettier-ignore */}
|
|
214
|
+
<path
|
|
215
|
+
d={pathGenerator(item)}
|
|
216
|
+
fill={item.properties.fill}
|
|
217
|
+
fillOpacity={item.properties['fill-opacity']}
|
|
218
|
+
key={geoId} data-id={geoId}
|
|
219
|
+
stroke={item.properties.stroke}
|
|
220
|
+
strokeWidth={item.properties['stroke-width']}
|
|
221
|
+
data-tooltip-id='tooltip'
|
|
222
|
+
data-tooltip-html={config.map.layers[index].tooltip ? config.map.layers[index].tooltip : ''}
|
|
223
|
+
/>
|
|
224
|
+
</Group>
|
|
225
|
+
)
|
|
226
|
+
})
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
// export options for either the feature or the path
|
|
230
|
+
setPathArray(tempArr)
|
|
231
|
+
setFeatureArray(tempFeatureArray)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const MapLayerHandlers = () => null
|
|
235
|
+
MapLayerHandlers.handleRemoveLayer = handleRemoveLayer
|
|
236
|
+
MapLayerHandlers.handleAddLayer = handleAddLayer
|
|
237
|
+
MapLayerHandlers.handleMapLayerUrl = handleMapLayerUrl
|
|
238
|
+
MapLayerHandlers.handleMapLayerName = handleMapLayerName
|
|
239
|
+
MapLayerHandlers.handleMapLayerNamespace = handleMapLayerNamespace
|
|
240
|
+
MapLayerHandlers.handleMapLayerTooltip = handleMapLayerTooltip
|
|
241
|
+
|
|
242
|
+
return { pathArray, featureArray, MapLayerHandlers }
|
|
243
|
+
}
|
package/src/index.jsx
CHANGED
|
@@ -1,20 +1,16 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import ReactDOM from 'react-dom/client'
|
|
3
3
|
|
|
4
|
-
import CdcMap from './CdcMap'
|
|
4
|
+
import CdcMap from './CdcMap'
|
|
5
5
|
|
|
6
6
|
import 'react-tooltip/dist/react-tooltip.css'
|
|
7
7
|
|
|
8
8
|
let isEditor = window.location.href.includes('editor=true')
|
|
9
|
-
|
|
9
|
+
let isDebug = window.location.href.includes('debug=true')
|
|
10
10
|
let domContainer = document.getElementsByClassName('react-container')[0]
|
|
11
11
|
|
|
12
12
|
ReactDOM.createRoot(domContainer).render(
|
|
13
13
|
<React.StrictMode>
|
|
14
|
-
<CdcMap
|
|
15
|
-
|
|
16
|
-
configUrl={domContainer.attributes['data-config'].value}
|
|
17
|
-
containerEl={domContainer}
|
|
18
|
-
/>
|
|
19
|
-
</React.StrictMode>,
|
|
14
|
+
<CdcMap isEditor={isEditor} isDebug={isDebug} configUrl={domContainer.attributes['data-config'].value} containerEl={domContainer} />
|
|
15
|
+
</React.StrictMode>
|
|
20
16
|
)
|