@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
package/src/CdcMap.jsx
CHANGED
|
@@ -37,7 +37,7 @@ import numberFromString from '@cdc/core/helpers/numberFromString'
|
|
|
37
37
|
|
|
38
38
|
// Child Components
|
|
39
39
|
import ConfigContext from './context'
|
|
40
|
-
import Filters from '
|
|
40
|
+
import Filters, { useFilters } from '@cdc/core/components/Filters'
|
|
41
41
|
import Modal from './components/Modal'
|
|
42
42
|
import Sidebar from './components/Sidebar'
|
|
43
43
|
|
|
@@ -82,7 +82,7 @@ const hashObj = row => {
|
|
|
82
82
|
|
|
83
83
|
return hash
|
|
84
84
|
} catch (e) {
|
|
85
|
-
console.error(e)
|
|
85
|
+
console.error('COVE: ', e) // eslint-disable-line
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
88
|
|
|
@@ -112,7 +112,7 @@ const getUniqueValues = (data, columnName) => {
|
|
|
112
112
|
return Object.keys(result)
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
const CdcMap = ({ className, config, navigationHandler: customNavigationHandler, isDashboard = false, isEditor = false, configUrl, logo = null, setConfig, setSharedFilter, setSharedFilterValue, hostname = 'localhost:8080', link }) => {
|
|
115
|
+
const CdcMap = ({ className, config, navigationHandler: customNavigationHandler, isDashboard = false, isEditor = false, isDebug = false, configUrl, logo = null, setConfig, setSharedFilter, setSharedFilterValue, hostname = 'localhost:8080', link }) => {
|
|
116
116
|
const transform = new DataTransform()
|
|
117
117
|
const [state, setState] = useState({ ...initialState })
|
|
118
118
|
const [loading, setLoading] = useState(true)
|
|
@@ -127,7 +127,9 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
|
|
|
127
127
|
const [coveLoadedHasRan, setCoveLoadedHasRan] = useState(false)
|
|
128
128
|
const [container, setContainer] = useState()
|
|
129
129
|
const [imageId, setImageId] = useState(`cove-${Math.random().toString(16).slice(-4)}`) // eslint-disable-line
|
|
130
|
+
const [dimensions, setDimensions] = useState()
|
|
130
131
|
|
|
132
|
+
const { changeFilterActive, handleSorting } = useFilters({ config: state, setConfig: setState })
|
|
131
133
|
let legendMemo = useRef(new Map())
|
|
132
134
|
let innerContainerRef = useRef()
|
|
133
135
|
|
|
@@ -145,7 +147,7 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
|
|
|
145
147
|
})
|
|
146
148
|
}
|
|
147
149
|
} catch (e) {
|
|
148
|
-
console.error('Failed to set world map zoom.')
|
|
150
|
+
console.error('COVE: Failed to set world map zoom.') // eslint-disable-line
|
|
149
151
|
}
|
|
150
152
|
}, [filteredCountryCode]) // eslint-disable-line
|
|
151
153
|
|
|
@@ -169,6 +171,9 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
|
|
|
169
171
|
|
|
170
172
|
const generateRuntimeLegendHash = () => {
|
|
171
173
|
return hashObj({
|
|
174
|
+
unified: state.legend.unified ?? false,
|
|
175
|
+
equalNumberOptIn: state.general.equalNumberOptIn ?? false,
|
|
176
|
+
specialClassesLast: state.legend.showSpecialClassesLast ?? false,
|
|
172
177
|
color: state.color,
|
|
173
178
|
customColors: state.customColors,
|
|
174
179
|
numberOfItems: state.legend.numberOfItems,
|
|
@@ -179,15 +184,26 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
|
|
|
179
184
|
specialClasses: state.legend.specialClasses,
|
|
180
185
|
geoType: state.general.geoType,
|
|
181
186
|
data: state.data,
|
|
182
|
-
...runtimeFilters
|
|
187
|
+
...runtimeFilters,
|
|
188
|
+
filters: {
|
|
189
|
+
...state.filters
|
|
190
|
+
}
|
|
183
191
|
})
|
|
184
192
|
}
|
|
185
193
|
|
|
186
194
|
const resizeObserver = new ResizeObserver(entries => {
|
|
187
195
|
for (let entry of entries) {
|
|
196
|
+
let { width, height } = entry.contentRect
|
|
188
197
|
let newViewport = getViewport(entry.contentRect.width)
|
|
198
|
+
let svgMarginWidth = 32
|
|
199
|
+
let editorWidth = 350
|
|
189
200
|
|
|
190
201
|
setCurrentViewport(newViewport)
|
|
202
|
+
|
|
203
|
+
if (isEditor) {
|
|
204
|
+
width = width - editorWidth
|
|
205
|
+
}
|
|
206
|
+
setDimensions([width, height])
|
|
191
207
|
}
|
|
192
208
|
})
|
|
193
209
|
|
|
@@ -285,6 +301,8 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
|
|
|
285
301
|
result.fromHash = hash
|
|
286
302
|
}
|
|
287
303
|
|
|
304
|
+
result.runtimeDataHash = runtimeData.fromHash
|
|
305
|
+
|
|
288
306
|
// Unified will based the legend off ALL of the data maps received. Otherwise, it will use
|
|
289
307
|
let dataSet = obj.legend.unified ? obj.data : Object.values(runtimeData)
|
|
290
308
|
|
|
@@ -785,10 +803,13 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
|
|
|
785
803
|
newFilter = {}
|
|
786
804
|
}
|
|
787
805
|
|
|
806
|
+
newFilter.order = obj.filters[idx].order ? obj.filters[idx].order : 'asc'
|
|
788
807
|
newFilter.label = label ?? ''
|
|
789
808
|
newFilter.columnName = columnName
|
|
790
809
|
newFilter.values = values
|
|
791
|
-
newFilter
|
|
810
|
+
handleSorting(newFilter)
|
|
811
|
+
newFilter.active = active ?? values[0] // Default to first found value
|
|
812
|
+
newFilter.filterStyle = obj.filters[idx].filterStyle ? obj.filters[idx].filterStyle : 'dropdown'
|
|
792
813
|
|
|
793
814
|
filters.push(newFilter)
|
|
794
815
|
})
|
|
@@ -810,8 +831,8 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
|
|
|
810
831
|
addUIDs(obj, obj.columns.geo.name)
|
|
811
832
|
obj.data.forEach(row => {
|
|
812
833
|
if (test) {
|
|
813
|
-
console.log('object', obj)
|
|
814
|
-
console.log('row', row)
|
|
834
|
+
console.log('object', obj) // eslint-disable-line
|
|
835
|
+
console.log('row', row) // eslint-disable-line
|
|
815
836
|
}
|
|
816
837
|
|
|
817
838
|
if (undefined === row.uid) return false // No UID for this row, we can't use for mapping
|
|
@@ -854,7 +875,7 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
|
|
|
854
875
|
|
|
855
876
|
return result
|
|
856
877
|
} catch (e) {
|
|
857
|
-
console.error(e)
|
|
878
|
+
console.error('COVE: ', e) // eslint-disable-line
|
|
858
879
|
}
|
|
859
880
|
})
|
|
860
881
|
|
|
@@ -873,33 +894,6 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
|
|
|
873
894
|
}
|
|
874
895
|
}
|
|
875
896
|
|
|
876
|
-
const changeFilterActive = async (idx, activeValue) => {
|
|
877
|
-
// Reset active legend toggles
|
|
878
|
-
resetLegendToggles()
|
|
879
|
-
|
|
880
|
-
try {
|
|
881
|
-
const isEmpty = obj => {
|
|
882
|
-
return Object.keys(obj).length === 0
|
|
883
|
-
}
|
|
884
|
-
|
|
885
|
-
let filters = [...runtimeFilters]
|
|
886
|
-
|
|
887
|
-
filters[idx] = { ...filters[idx] }
|
|
888
|
-
filters[idx].active = activeValue
|
|
889
|
-
|
|
890
|
-
const newData = generateRuntimeData(state, filters)
|
|
891
|
-
|
|
892
|
-
// throw an error if newData is empty
|
|
893
|
-
if (isEmpty(newData)) throw new Error('Cove Filter Error: No runtime data to set for this filter')
|
|
894
|
-
|
|
895
|
-
// set the runtime filters and data
|
|
896
|
-
setRuntimeData(newData)
|
|
897
|
-
setRuntimeFilters(filters)
|
|
898
|
-
} catch (e) {
|
|
899
|
-
console.error(e.message)
|
|
900
|
-
}
|
|
901
|
-
}
|
|
902
|
-
|
|
903
897
|
const displayDataAsText = (value, columnName) => {
|
|
904
898
|
if (value === null || value === '' || value === undefined) {
|
|
905
899
|
return ''
|
|
@@ -970,7 +964,7 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
|
|
|
970
964
|
// Fail state
|
|
971
965
|
return generateColorsArray()
|
|
972
966
|
} catch (e) {
|
|
973
|
-
console.error(e)
|
|
967
|
+
console.error('COVE: ', e) // eslint-disable-line
|
|
974
968
|
}
|
|
975
969
|
}
|
|
976
970
|
|
|
@@ -1083,6 +1077,8 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
|
|
|
1083
1077
|
delete legendItem.disabled
|
|
1084
1078
|
})
|
|
1085
1079
|
|
|
1080
|
+
newLegend.runtimeDataHash = runtimeLegend.runtimeDataHash
|
|
1081
|
+
|
|
1086
1082
|
setRuntimeLegend(newLegend)
|
|
1087
1083
|
}
|
|
1088
1084
|
|
|
@@ -1201,7 +1197,7 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
|
|
|
1201
1197
|
}
|
|
1202
1198
|
|
|
1203
1199
|
const handleMapAriaLabels = (state = '', testing = false) => {
|
|
1204
|
-
if (testing) console.log(`handleMapAriaLabels Testing On: ${state}`)
|
|
1200
|
+
if (testing) console.log(`handleMapAriaLabels Testing On: ${state}`) // eslint-disable-line
|
|
1205
1201
|
try {
|
|
1206
1202
|
if (!state.general.geoType) throw Error('handleMapAriaLabels: no geoType found in state')
|
|
1207
1203
|
let ariaLabel = ''
|
|
@@ -1232,7 +1228,7 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
|
|
|
1232
1228
|
|
|
1233
1229
|
return ariaLabel
|
|
1234
1230
|
} catch (e) {
|
|
1235
|
-
console.error(e.message)
|
|
1231
|
+
console.error('COVE: ', e.message) // eslint-disable-line
|
|
1236
1232
|
}
|
|
1237
1233
|
}
|
|
1238
1234
|
|
|
@@ -1391,16 +1387,14 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
|
|
|
1391
1387
|
})
|
|
1392
1388
|
|
|
1393
1389
|
// Data
|
|
1394
|
-
let newRuntimeData
|
|
1395
1390
|
if (hashData !== runtimeData.fromHash && state.data?.fromColumn) {
|
|
1396
1391
|
const newRuntimeData = generateRuntimeData(state, filters || runtimeFilters, hashData)
|
|
1397
1392
|
setRuntimeData(newRuntimeData)
|
|
1398
|
-
}
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
setRuntimeLegend(legend)
|
|
1393
|
+
} else {
|
|
1394
|
+
if (hashLegend !== runtimeLegend.fromHash && undefined === runtimeData.init) {
|
|
1395
|
+
const legend = generateRuntimeLegend(state, runtimeData, hashLegend)
|
|
1396
|
+
setRuntimeLegend(legend)
|
|
1397
|
+
}
|
|
1404
1398
|
}
|
|
1405
1399
|
}, [state]) // eslint-disable-line
|
|
1406
1400
|
|
|
@@ -1412,7 +1406,7 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
|
|
|
1412
1406
|
const legend = generateRuntimeLegend(state, runtimeData, hashLegend)
|
|
1413
1407
|
setRuntimeLegend(legend)
|
|
1414
1408
|
}
|
|
1415
|
-
}, [runtimeData]) // eslint-disable-line
|
|
1409
|
+
}, [runtimeData, state.legend.unified, state.legend.showSpecialClassesLast, state.legend.separateZero, state.general.equalNumberOptIn, state.legend.numberOfItems, state.legend.specialClasses]) // eslint-disable-line
|
|
1416
1410
|
|
|
1417
1411
|
if (config) {
|
|
1418
1412
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
@@ -1475,7 +1469,8 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
|
|
|
1475
1469
|
runtimeFilters,
|
|
1476
1470
|
setRuntimeFilters,
|
|
1477
1471
|
innerContainerRef,
|
|
1478
|
-
currentViewport
|
|
1472
|
+
currentViewport,
|
|
1473
|
+
isDebug
|
|
1479
1474
|
}
|
|
1480
1475
|
|
|
1481
1476
|
if (!mapProps.data || !state.data) return <Loading />
|
|
@@ -1506,12 +1501,20 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
|
|
|
1506
1501
|
|
|
1507
1502
|
const tabId = handleMapTabbing()
|
|
1508
1503
|
|
|
1504
|
+
// this only shows in Dashboard config mode and only if Show Table is also set
|
|
1505
|
+
const tableLink = (
|
|
1506
|
+
<a href={`#data-table-${state.general.dataKey}`} className='margin-left-href'>
|
|
1507
|
+
{state.general.dataKey} (Go to Table)
|
|
1508
|
+
</a>
|
|
1509
|
+
)
|
|
1510
|
+
|
|
1509
1511
|
return (
|
|
1510
1512
|
<ConfigContext.Provider value={mapProps}>
|
|
1511
1513
|
<div className={outerContainerClasses.join(' ')} ref={outerContainerRef} data-download-id={imageId}>
|
|
1512
1514
|
{isEditor && (
|
|
1513
1515
|
<EditorPanel
|
|
1514
1516
|
isDashboard={isDashboard}
|
|
1517
|
+
isDebug={isDebug}
|
|
1515
1518
|
state={state}
|
|
1516
1519
|
setState={setState}
|
|
1517
1520
|
loadConfig={loadConfig}
|
|
@@ -1537,7 +1540,8 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
|
|
|
1537
1540
|
)}
|
|
1538
1541
|
{general.introText && <section className='introText'>{parse(general.introText)}</section>}
|
|
1539
1542
|
|
|
1540
|
-
|
|
1543
|
+
{/* prettier-ignore */}
|
|
1544
|
+
{state?.filters?.length > 0 && <Filters config={state} setConfig={setState} filteredData={runtimeFilters} setFilteredData={setRuntimeFilters} dimensions={dimensions} />}
|
|
1541
1545
|
|
|
1542
1546
|
<div
|
|
1543
1547
|
role='button'
|
|
@@ -1591,7 +1595,8 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
|
|
|
1591
1595
|
|
|
1592
1596
|
{'navigation' === general.type && <NavigationMenu mapTabbingID={tabId} displayGeoName={displayGeoName} data={runtimeData} options={general} columns={state.columns} navigationHandler={val => navigationHandler(val)} />}
|
|
1593
1597
|
|
|
1594
|
-
{
|
|
1598
|
+
{/* Link */}
|
|
1599
|
+
{isDashboard && config.dataTable.forceDisplay && config.table.showDataTableLink ? tableLink : link && link}
|
|
1595
1600
|
|
|
1596
1601
|
{subtext.length > 0 && <p className='subtext'>{parse(subtext)}</p>}
|
|
1597
1602
|
|
|
@@ -10,6 +10,7 @@ import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
|
10
10
|
|
|
11
11
|
import topoJSON from '../data/county-map.json'
|
|
12
12
|
import { formatPrefix } from 'd3'
|
|
13
|
+
import useMapLayers from '../hooks/useMapLayers'
|
|
13
14
|
|
|
14
15
|
const sortById = (a, b) => {
|
|
15
16
|
if (a.id < b.id) return -1
|
|
@@ -63,6 +64,10 @@ const CountyMap = props => {
|
|
|
63
64
|
|
|
64
65
|
const [focus, setFocus] = useState({})
|
|
65
66
|
|
|
67
|
+
const pathGenerator = geoPath().projection(geoAlbersUsaTerritories())
|
|
68
|
+
|
|
69
|
+
const { featureArray } = useMapLayers(state, '', pathGenerator, false)
|
|
70
|
+
|
|
66
71
|
useEffect(() => {
|
|
67
72
|
if (containerEl) {
|
|
68
73
|
if (containerEl.className.indexOf('loaded') === -1) {
|
|
@@ -216,6 +221,9 @@ const CountyMap = props => {
|
|
|
216
221
|
}
|
|
217
222
|
}
|
|
218
223
|
|
|
224
|
+
// todo: current item is a custom map layer
|
|
225
|
+
// if(currentItem === customMapLayer) show layer.tooltip
|
|
226
|
+
|
|
219
227
|
let hoveredGeo
|
|
220
228
|
let hoveredGeoIndex
|
|
221
229
|
for (let i = 0; i < runtimeKeys.length; i++) {
|
|
@@ -309,6 +317,20 @@ const CountyMap = props => {
|
|
|
309
317
|
context.stroke()
|
|
310
318
|
}
|
|
311
319
|
|
|
320
|
+
// add in custom map layers
|
|
321
|
+
if (featureArray.length > 0) {
|
|
322
|
+
featureArray.map(layer => {
|
|
323
|
+
context.beginPath()
|
|
324
|
+
path(layer)
|
|
325
|
+
context.fillStyle = layer.properties.fill
|
|
326
|
+
context.globalAlpha = layer.properties['fill-opacity']
|
|
327
|
+
context.strokeStyle = layer.properties['stroke']
|
|
328
|
+
context.lineWidth = layer.properties['stroke-width']
|
|
329
|
+
context.fill()
|
|
330
|
+
context.stroke()
|
|
331
|
+
})
|
|
332
|
+
}
|
|
333
|
+
|
|
312
334
|
if (state.general.type === 'us-geocode') {
|
|
313
335
|
context.strokeStyle = 'black'
|
|
314
336
|
const geoRadius = (state.visual.geoCodeCircleSize || 5) * (focus.id ? 2 : 1)
|
|
@@ -317,11 +339,14 @@ const CountyMap = props => {
|
|
|
317
339
|
const pixelCoords = projection([data[key][state.columns.longitude.name], data[key][state.columns.latitude.name]])
|
|
318
340
|
|
|
319
341
|
if (pixelCoords) {
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
342
|
+
const legendValues = data[key] !== undefined ? applyLegendToRow(data[key]) : false
|
|
343
|
+
if (legendValues) {
|
|
344
|
+
context.fillStyle = legendValues[0]
|
|
345
|
+
context.beginPath()
|
|
346
|
+
context.arc(pixelCoords[0], pixelCoords[1], geoRadius, 0, 2 * Math.PI)
|
|
347
|
+
context.fill()
|
|
348
|
+
context.stroke()
|
|
349
|
+
}
|
|
325
350
|
}
|
|
326
351
|
})
|
|
327
352
|
}
|