@cdc/map 4.25.7 → 4.25.10
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/.claude/settings.local.json +30 -0
- package/CLAUDE.local.md +0 -0
- package/dist/cdcmap.js +54785 -53159
- package/examples/private/c.json +290 -0
- package/examples/private/canvas-city-hover.json +787 -0
- package/examples/private/d.json +345 -0
- package/examples/private/filter-map.json +909 -0
- package/examples/private/g.json +1 -0
- package/examples/private/h.json +105911 -0
- package/examples/private/measles-data.json +378 -0
- package/examples/private/measles.json +211 -0
- package/examples/private/north-dakota.json +1132 -0
- package/examples/private/rsv-data.json +532 -0
- package/examples/private/state-with-pattern.json +883 -0
- package/examples/private/test.json +222 -640
- package/index.html +1 -1
- package/package.json +26 -5
- package/src/CdcMap.tsx +28 -8
- package/src/CdcMapComponent.tsx +230 -306
- package/src/_stories/CdcMap.Filters.stories.tsx +2 -2
- package/src/_stories/CdcMap.Legend.Gradient.stories.tsx +3 -3
- package/src/_stories/CdcMap.Legend.stories.tsx +7 -4
- package/src/_stories/CdcMap.Patterns.stories.tsx +2 -2
- package/src/_stories/CdcMap.Table.stories.tsx +2 -2
- package/src/_stories/CdcMap.stories.tsx +18 -11
- package/src/_stories/GoogleMap.stories.tsx +2 -2
- package/src/_stories/UsaMap.NoData.stories.tsx +2 -2
- package/src/_stories/_mock/equal-number.json +1109 -0
- package/src/_stories/_mock/multi-state.json +21389 -0
- package/src/_stories/_mock/us-bubble-cities.json +306 -0
- package/src/components/BubbleList.tsx +16 -12
- package/src/components/CityList.tsx +88 -110
- package/src/components/DataTable.tsx +44 -12
- package/src/components/EditorPanel/components/EditorPanel.tsx +201 -203
- package/src/components/EditorPanel/components/HexShapeSettings.tsx +3 -2
- package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +7 -5
- package/src/components/Geo.tsx +2 -0
- package/src/components/Legend/components/Legend.tsx +117 -93
- package/src/components/Legend/components/LegendGroup/Legend.Group.tsx +10 -7
- package/src/components/MapContainer.tsx +52 -0
- package/src/components/MapControls.tsx +44 -0
- package/src/components/Modal.tsx +2 -8
- package/src/components/NavigationMenu.tsx +13 -1
- package/src/components/UsaMap/components/SingleState/SingleState.CountyOutput.tsx +24 -7
- package/src/components/UsaMap/components/SingleState/SingleState.StateOutput.tsx +21 -15
- package/src/components/UsaMap/components/TerritoriesSection.tsx +2 -2
- package/src/components/UsaMap/components/UsaMap.County.tsx +112 -33
- package/src/components/UsaMap/components/UsaMap.Region.tsx +23 -5
- package/src/components/UsaMap/components/UsaMap.SingleState.tsx +38 -26
- package/src/components/UsaMap/components/UsaMap.State.tsx +28 -10
- package/src/components/UsaMap/helpers/map.ts +16 -8
- package/src/components/WorldMap/WorldMap.tsx +116 -11
- package/src/components/ZoomControls.tsx +6 -9
- package/src/context/LegendMemoContext.tsx +30 -0
- package/src/context.ts +1 -39
- package/src/data/initial-state.js +143 -128
- package/src/data/supported-geos.js +202 -4
- package/src/helpers/addUIDs.ts +8 -8
- package/src/helpers/applyColorToLegend.ts +122 -45
- package/src/helpers/applyLegendToRow.ts +15 -13
- package/src/helpers/componentHelpers.ts +8 -0
- package/src/helpers/constants.ts +12 -0
- package/src/helpers/dataTableHelpers.ts +6 -0
- package/src/helpers/displayGeoName.ts +12 -7
- package/src/helpers/formatLegendLocation.ts +1 -3
- package/src/helpers/generateRuntimeLegend.ts +192 -340
- package/src/helpers/generateRuntimeLegendHash.ts +4 -2
- package/src/helpers/getColumnNames.ts +1 -1
- package/src/helpers/getPatternForRow.ts +36 -0
- package/src/helpers/getStatesPicked.ts +14 -0
- package/src/helpers/handleMapAriaLabels.ts +2 -2
- package/src/helpers/index.ts +11 -3
- package/src/helpers/isLegendItemDisabled.ts +16 -0
- package/src/helpers/mapObserverHelpers.ts +40 -0
- package/src/helpers/resetLegendToggles.ts +3 -2
- package/src/helpers/toggleLegendActive.ts +6 -11
- package/src/helpers/urlDataHelpers.ts +70 -0
- package/src/hooks/useGeoClickHandler.ts +35 -1
- package/src/hooks/useLegendMemo.ts +17 -0
- package/src/hooks/useMapLayers.tsx +5 -4
- package/src/hooks/useStateZoom.tsx +137 -88
- package/src/hooks/useTooltip.ts +1 -2
- package/src/index.jsx +6 -3
- package/src/scss/main.scss +23 -12
- package/src/store/map.actions.ts +2 -2
- package/src/store/map.reducer.ts +21 -10
- package/src/test/CdcMap.test.jsx +11 -0
- package/src/types/MapConfig.ts +25 -17
- package/src/types/MapContext.ts +2 -8
- package/src/types/runtimeLegend.ts +12 -10
- package/vite.config.js +2 -7
- package/vitest.config.ts +16 -0
- package/src/_stories/_mock/floating-point.json +0 -427
- package/src/coreStyles_map.scss +0 -3
- package/src/helpers/colorDistributions.ts +0 -12
- package/src/helpers/generateColorsArray.ts +0 -14
- package/src/helpers/getStatePicked.ts +0 -8
- package/src/helpers/tests/generateColorsArray.test.ts +0 -18
- package/src/helpers/tests/generateRuntimeLegendHash.test.ts +0 -11
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import { useContext, useEffect } from 'react'
|
|
1
|
+
import { useContext, useEffect, useMemo, useCallback } from 'react'
|
|
2
2
|
import ConfigContext, { MapDispatchContext } from '../context'
|
|
3
3
|
import { geoAlbersUsaTerritories } from 'd3-composite-projections'
|
|
4
4
|
import { MapContext } from '../types/MapContext'
|
|
5
5
|
import { geoPath, GeoPath } from 'd3-geo'
|
|
6
|
-
import {
|
|
6
|
+
import { getFilterControllingStatesPicked } from '../components/UsaMap/helpers/map'
|
|
7
7
|
import { supportedStatesFipsCodes } from '../data/supported-geos'
|
|
8
8
|
import { SVG_HEIGHT, SVG_WIDTH, SVG_PADDING } from '../helpers'
|
|
9
|
-
import
|
|
9
|
+
import { publishAnalyticsEvent } from '@cdc/core/helpers/metrics/helpers'
|
|
10
|
+
import { getVizTitle, getVizSubType } from '@cdc/core/helpers/metrics/utils'
|
|
10
11
|
|
|
11
12
|
interface StateData {
|
|
12
13
|
geometry: { type: 'MultiPolygon'; coordinates: number[][][][] }
|
|
@@ -18,116 +19,164 @@ interface StateData {
|
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
const useSetScaleAndTranslate = (topoData: { states: StateData[] }) => {
|
|
21
|
-
const { config, runtimeData, position } = useContext<MapContext>(ConfigContext)
|
|
22
|
-
const statePicked = getFilterControllingStatePicked(config, runtimeData)
|
|
22
|
+
const { config, runtimeData, position, interactionLabel } = useContext<MapContext>(ConfigContext)
|
|
23
23
|
const dispatch = useContext(MapDispatchContext)
|
|
24
24
|
|
|
25
|
+
// Get statesPicked with memoization
|
|
26
|
+
const statesPicked = useMemo(() => {
|
|
27
|
+
const result = getFilterControllingStatesPicked(config, runtimeData)
|
|
28
|
+
if (!result) return []
|
|
29
|
+
if (!Array.isArray(result)) return [result]
|
|
30
|
+
return result
|
|
31
|
+
}, [config.general.statesPicked, runtimeData])
|
|
32
|
+
|
|
33
|
+
// Memoize expensive computations
|
|
34
|
+
const statesData = useMemo(() => {
|
|
35
|
+
return statesPicked.map(state => ({
|
|
36
|
+
fipsCode: Object.keys(supportedStatesFipsCodes).find(key => supportedStatesFipsCodes[key] === state),
|
|
37
|
+
stateName: state
|
|
38
|
+
}))
|
|
39
|
+
}, [statesPicked])
|
|
40
|
+
|
|
41
|
+
// Memoize projection calculations
|
|
42
|
+
const projectionData = useMemo(() => {
|
|
43
|
+
const projection = geoAlbersUsaTerritories()
|
|
44
|
+
.translate([SVG_WIDTH / 2, SVG_HEIGHT / 2])
|
|
45
|
+
.scale(1)
|
|
46
|
+
|
|
47
|
+
const _statesPickedData = topoData?.states?.filter(s => statesPicked.includes(s.properties.name))
|
|
48
|
+
|
|
49
|
+
const combinedData = _statesPickedData?.length
|
|
50
|
+
? {
|
|
51
|
+
type: 'FeatureCollection',
|
|
52
|
+
features: _statesPickedData
|
|
53
|
+
}
|
|
54
|
+
: null
|
|
55
|
+
|
|
56
|
+
const newProjection = combinedData
|
|
57
|
+
? projection.fitExtent(
|
|
58
|
+
[
|
|
59
|
+
[SVG_PADDING, SVG_PADDING],
|
|
60
|
+
[SVG_WIDTH - SVG_PADDING, SVG_HEIGHT - SVG_PADDING]
|
|
61
|
+
],
|
|
62
|
+
combinedData
|
|
63
|
+
)
|
|
64
|
+
: projection
|
|
65
|
+
|
|
66
|
+
const path: GeoPath = geoPath().projection(projection)
|
|
67
|
+
const featureCenter = combinedData ? path.centroid(combinedData as any) : [0, 0]
|
|
68
|
+
const stateCenter = newProjection.invert(featureCenter)
|
|
69
|
+
|
|
70
|
+
return { projection, newProjection, stateCenter }
|
|
71
|
+
}, [topoData, statesPicked])
|
|
72
|
+
|
|
73
|
+
const setScaleAndTranslate = useCallback(
|
|
74
|
+
(zoomFunction: string = '') => {
|
|
75
|
+
const _prevPosition = config.mapPosition
|
|
76
|
+
let newZoom = _prevPosition.zoom
|
|
77
|
+
let newCoordinates = _prevPosition.coordinates
|
|
78
|
+
if (zoomFunction === 'zoomIn' && _prevPosition.zoom < 4) {
|
|
79
|
+
newZoom = _prevPosition.zoom * 1.5
|
|
80
|
+
newCoordinates =
|
|
81
|
+
_prevPosition.coordinates[0] !== 0 && _prevPosition.coordinates[1] !== 0
|
|
82
|
+
? _prevPosition.coordinates
|
|
83
|
+
: projectionData.stateCenter
|
|
84
|
+
publishAnalyticsEvent({
|
|
85
|
+
vizType: 'map',
|
|
86
|
+
vizSubType: getVizSubType(config),
|
|
87
|
+
eventType: 'zoom_in',
|
|
88
|
+
eventAction: 'click',
|
|
89
|
+
eventLabel: interactionLabel,
|
|
90
|
+
vizTitle: getVizTitle(config),
|
|
91
|
+
specifics: `zoom: ${newZoom}, coordinates:${newCoordinates}`
|
|
92
|
+
})
|
|
93
|
+
} else if (zoomFunction === 'zoomOut' && _prevPosition.zoom > 1) {
|
|
94
|
+
newZoom = _prevPosition.zoom / 1.5
|
|
95
|
+
newCoordinates =
|
|
96
|
+
_prevPosition.coordinates[0] !== 0 && _prevPosition.coordinates[1] !== 0
|
|
97
|
+
? _prevPosition.coordinates
|
|
98
|
+
: projectionData.stateCenter
|
|
99
|
+
} else if (zoomFunction === 'reset') {
|
|
100
|
+
newZoom = 1
|
|
101
|
+
newCoordinates = projectionData.stateCenter
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
dispatch({ type: 'SET_POSITION', payload: { coordinates: newCoordinates, zoom: newZoom } })
|
|
105
|
+
|
|
106
|
+
if (zoomFunction === 'reset') {
|
|
107
|
+
dispatch({ type: 'SET_TRANSLATE', payload: [0, 0] }) // needed for state switcher
|
|
108
|
+
dispatch({ type: 'SET_SCALE', payload: 1 }) // needed for state switcher
|
|
109
|
+
publishAnalyticsEvent({
|
|
110
|
+
vizType: 'map',
|
|
111
|
+
vizSubType: getVizSubType(config),
|
|
112
|
+
eventType: 'map_reset_zoom_level',
|
|
113
|
+
eventAction: 'click',
|
|
114
|
+
eventLabel: interactionLabel,
|
|
115
|
+
vizTitle: getVizTitle(config)
|
|
116
|
+
})
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
[config.mapPosition, projectionData.stateCenter, interactionLabel, dispatch]
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
// Essential fix: Remove config from dependencies to prevent infinite loops
|
|
25
123
|
useEffect(() => {
|
|
26
|
-
|
|
27
|
-
const stateName = statePicked
|
|
28
|
-
const stateData = { fipsCode, stateName }
|
|
29
|
-
const newConfig = _.cloneDeep(config)
|
|
30
|
-
newConfig.general.statePicked = stateData
|
|
31
|
-
const stateToShow = topoData?.states?.find(s => s.properties.name === statePicked)
|
|
124
|
+
if (!topoData) return
|
|
32
125
|
|
|
33
126
|
dispatch({ type: 'SET_SCALE', payload: 1 })
|
|
34
127
|
dispatch({ type: 'SET_TRANSLATE', payload: [0, 0] })
|
|
35
|
-
dispatch({ type: '
|
|
36
|
-
|
|
37
|
-
}, [topoData])
|
|
128
|
+
dispatch({ type: 'SET_STATES_TO_SHOW', payload: statesPicked })
|
|
129
|
+
}, [topoData, statesPicked, dispatch])
|
|
38
130
|
|
|
39
131
|
useEffect(() => {
|
|
40
|
-
const
|
|
41
|
-
const stateName = statePicked
|
|
42
|
-
const stateData = { fipsCode, stateName }
|
|
43
|
-
const newConfig = _.cloneDeep(config)
|
|
44
|
-
newConfig.general.statePicked = stateData
|
|
45
|
-
dispatch({ type: 'SET_CONFIG', payload: newConfig })
|
|
46
|
-
setScaleAndTranslate('reset')
|
|
47
|
-
}, [statePicked])
|
|
48
|
-
|
|
49
|
-
// TODO: same as city list projection?
|
|
50
|
-
const projection = geoAlbersUsaTerritories()
|
|
51
|
-
.translate([SVG_WIDTH / 2, SVG_HEIGHT / 2])
|
|
52
|
-
.scale(1)
|
|
53
|
-
|
|
54
|
-
const _statePickedData = topoData?.states?.find(s => s.properties.name === statePicked)
|
|
55
|
-
const newProjection = projection.fitExtent(
|
|
56
|
-
[
|
|
57
|
-
[SVG_PADDING, SVG_PADDING],
|
|
58
|
-
[SVG_WIDTH - SVG_PADDING, SVG_HEIGHT - SVG_PADDING]
|
|
59
|
-
],
|
|
60
|
-
_statePickedData
|
|
61
|
-
)
|
|
132
|
+
const currentStatesPicked = config.general.statesPicked?.map(state => state.stateName) || []
|
|
62
133
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
y = y - SVG_HEIGHT / 2
|
|
134
|
+
const alreadySet =
|
|
135
|
+
currentStatesPicked.length === statesPicked.length &&
|
|
136
|
+
currentStatesPicked.every((s: string) => statesPicked.includes(s))
|
|
67
137
|
|
|
68
|
-
|
|
69
|
-
const featureCenter = path.centroid(_statePickedData)
|
|
70
|
-
const stateCenter = newProjection.invert(featureCenter)
|
|
138
|
+
if (alreadySet) return
|
|
71
139
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
140
|
+
const newConfig = { ...config }
|
|
141
|
+
newConfig.general = { ...config.general, statesPicked: statesData }
|
|
142
|
+
dispatch({ type: 'SET_CONFIG', payload: newConfig })
|
|
143
|
+
}, [statesPicked, statesData, dispatch])
|
|
76
144
|
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
let newCoordinates = _prevPosition.coordinates
|
|
81
|
-
if (zoomFunction === 'zoomIn' && _prevPosition.zoom < 4) {
|
|
82
|
-
newZoom = _prevPosition.zoom * 1.5
|
|
83
|
-
newCoordinates =
|
|
84
|
-
_prevPosition.coordinates[0] !== 0 && _prevPosition.coordinates[1] !== 0
|
|
85
|
-
? _prevPosition.coordinates
|
|
86
|
-
: stateCenter
|
|
87
|
-
} else if (zoomFunction === 'zoomOut' && _prevPosition.zoom > 1) {
|
|
88
|
-
newZoom = _prevPosition.zoom / 1.5
|
|
89
|
-
newCoordinates =
|
|
90
|
-
_prevPosition.coordinates[0] !== 0 && _prevPosition.coordinates[1] !== 0
|
|
91
|
-
? _prevPosition.coordinates
|
|
92
|
-
: stateCenter
|
|
93
|
-
} else if (zoomFunction === 'reset') {
|
|
94
|
-
newZoom = 1
|
|
95
|
-
newCoordinates = stateCenter
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
dispatch({ type: 'SET_POSITION', payload: { coordinates: newCoordinates, zoom: newZoom } })
|
|
99
|
-
|
|
100
|
-
if (zoomFunction === 'reset') {
|
|
101
|
-
dispatch({ type: 'SET_TRANSLATE', payload: [0, 0] }) // needed for state switcher
|
|
102
|
-
dispatch({ type: 'SET_SCALE', payload: 1 }) // needed for state switcher
|
|
103
|
-
}
|
|
104
|
-
}
|
|
145
|
+
const switchState = useCallback(() => {
|
|
146
|
+
dispatch({ type: 'SET_STATES_TO_SHOW', payload: statesPicked })
|
|
147
|
+
}, [statesPicked, setScaleAndTranslate, dispatch])
|
|
105
148
|
|
|
106
|
-
const handleZoomIn = () => {
|
|
149
|
+
const handleZoomIn = useCallback(() => {
|
|
107
150
|
setScaleAndTranslate('zoomIn')
|
|
108
|
-
}
|
|
151
|
+
}, [setScaleAndTranslate])
|
|
109
152
|
|
|
110
|
-
const handleZoomOut = () => {
|
|
153
|
+
const handleZoomOut = useCallback(() => {
|
|
111
154
|
setScaleAndTranslate('zoomOut')
|
|
112
|
-
}
|
|
155
|
+
}, [setScaleAndTranslate])
|
|
113
156
|
|
|
114
|
-
const handleMoveEnd =
|
|
115
|
-
|
|
116
|
-
|
|
157
|
+
const handleMoveEnd = useCallback(
|
|
158
|
+
position => {
|
|
159
|
+
dispatch({ type: 'SET_POSITION', payload: position })
|
|
160
|
+
},
|
|
161
|
+
[dispatch]
|
|
162
|
+
)
|
|
117
163
|
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
164
|
+
const handleZoomReset = useCallback(
|
|
165
|
+
_setRuntimeData => {
|
|
166
|
+
setScaleAndTranslate('reset')
|
|
167
|
+
},
|
|
168
|
+
[setScaleAndTranslate]
|
|
169
|
+
)
|
|
121
170
|
|
|
122
171
|
return {
|
|
123
|
-
|
|
172
|
+
statesPicked,
|
|
124
173
|
setScaleAndTranslate,
|
|
125
174
|
switchState,
|
|
126
175
|
handleZoomIn,
|
|
127
176
|
handleZoomOut,
|
|
128
177
|
handleMoveEnd,
|
|
129
|
-
|
|
130
|
-
projection
|
|
178
|
+
handleZoomReset,
|
|
179
|
+
projection: projectionData.projection
|
|
131
180
|
}
|
|
132
181
|
}
|
|
133
182
|
|
package/src/hooks/useTooltip.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import { displayDataAsText } from '
|
|
1
|
+
import { displayDataAsText } from '@cdc/core/helpers/displayDataAsText'
|
|
2
2
|
import { displayGeoName } from '../helpers/displayGeoName'
|
|
3
3
|
|
|
4
4
|
const useTooltip = props => {
|
|
5
5
|
const { config, supportedStatesFipsCodes } = props
|
|
6
6
|
|
|
7
|
-
|
|
8
7
|
/**
|
|
9
8
|
* On county maps there's a need to append the state name
|
|
10
9
|
* @param {String} toolTipText - previous tooltip text to build upon
|
package/src/index.jsx
CHANGED
|
@@ -2,8 +2,6 @@ import React from 'react'
|
|
|
2
2
|
import ReactDOM from 'react-dom/client'
|
|
3
3
|
|
|
4
4
|
import '@cdc/core/styles/cove-main.scss'
|
|
5
|
-
import 'react-tooltip/dist/react-tooltip.css'
|
|
6
|
-
import './coreStyles_map.scss'
|
|
7
5
|
|
|
8
6
|
import CdcMap from './CdcMap'
|
|
9
7
|
|
|
@@ -12,6 +10,11 @@ let domContainer = document.getElementsByClassName('react-container')[0]
|
|
|
12
10
|
|
|
13
11
|
ReactDOM.createRoot(domContainer).render(
|
|
14
12
|
<React.StrictMode>
|
|
15
|
-
<CdcMap
|
|
13
|
+
<CdcMap
|
|
14
|
+
isEditor={isEditor}
|
|
15
|
+
configUrl={domContainer.attributes['data-config'].value}
|
|
16
|
+
interactionLabel={domContainer.attributes['data-config'].value}
|
|
17
|
+
containerEl={domContainer}
|
|
18
|
+
/>
|
|
16
19
|
</React.StrictMode>
|
|
17
20
|
)
|
package/src/scss/main.scss
CHANGED
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
.cdc-map-outer-container {
|
|
16
16
|
position: relative;
|
|
17
17
|
display: flex; // Needed for the main content
|
|
18
|
-
|
|
18
|
+
|
|
19
|
+
.loading>div.la-ball-beat {
|
|
19
20
|
margin-top: 20%;
|
|
20
21
|
}
|
|
21
22
|
|
|
@@ -52,12 +53,14 @@
|
|
|
52
53
|
&.bottom {
|
|
53
54
|
flex-direction: column;
|
|
54
55
|
}
|
|
56
|
+
|
|
55
57
|
&.top {
|
|
56
58
|
flex-direction: column-reverse;
|
|
57
59
|
}
|
|
58
60
|
|
|
59
61
|
&.modal-background {
|
|
60
62
|
position: relative;
|
|
63
|
+
|
|
61
64
|
&::before {
|
|
62
65
|
content: ' ';
|
|
63
66
|
position: absolute;
|
|
@@ -65,6 +68,7 @@
|
|
|
65
68
|
bottom: 0;
|
|
66
69
|
z-index: 7;
|
|
67
70
|
}
|
|
71
|
+
|
|
68
72
|
.modal-content {
|
|
69
73
|
background: #fff;
|
|
70
74
|
position: absolute;
|
|
@@ -80,8 +84,10 @@
|
|
|
80
84
|
padding: 16px 40px;
|
|
81
85
|
min-width: 250px;
|
|
82
86
|
width: auto;
|
|
83
|
-
max-height: 90vh;
|
|
84
|
-
|
|
87
|
+
max-height: 90vh;
|
|
88
|
+
/* Constrain the modal's height to 90% of the viewport */
|
|
89
|
+
overflow-y: auto;
|
|
90
|
+
/* Enable vertical scrolling if content overflows */
|
|
85
91
|
font-size: 1rem;
|
|
86
92
|
line-height: 1.4em;
|
|
87
93
|
}
|
|
@@ -122,16 +128,15 @@
|
|
|
122
128
|
margin-left: 0.5em;
|
|
123
129
|
}
|
|
124
130
|
|
|
125
|
-
.modal-content.capitalize p {
|
|
126
|
-
text-transform: capitalize;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
131
|
/* Responsive adjustments for smaller screens */
|
|
130
132
|
@media (max-width: 1048px) {
|
|
131
133
|
.modal-content {
|
|
132
|
-
width: 90%;
|
|
133
|
-
|
|
134
|
-
|
|
134
|
+
width: 90%;
|
|
135
|
+
/* Adjust width to fit smaller screens */
|
|
136
|
+
top: 10%;
|
|
137
|
+
/* Offset from the top for better usability */
|
|
138
|
+
transform: translate(-50%, 0);
|
|
139
|
+
/* Remove vertical centering */
|
|
135
140
|
}
|
|
136
141
|
}
|
|
137
142
|
}
|
|
@@ -141,6 +146,7 @@
|
|
|
141
146
|
em {
|
|
142
147
|
font-style: italic;
|
|
143
148
|
}
|
|
149
|
+
|
|
144
150
|
strong {
|
|
145
151
|
font-weight: bold;
|
|
146
152
|
}
|
|
@@ -167,25 +173,30 @@
|
|
|
167
173
|
z-index: 6;
|
|
168
174
|
width: 100%;
|
|
169
175
|
border-top: var(--lightGray) 1px solid;
|
|
176
|
+
|
|
170
177
|
label {
|
|
171
178
|
flex-grow: 1;
|
|
172
|
-
|
|
179
|
+
|
|
180
|
+
>div.select-heading {
|
|
173
181
|
font-size: 1.1em;
|
|
174
182
|
font-weight: 600;
|
|
175
183
|
margin-bottom: 0.75em;
|
|
176
184
|
}
|
|
177
185
|
}
|
|
186
|
+
|
|
178
187
|
form {
|
|
179
188
|
max-width: 400px;
|
|
180
189
|
display: flex;
|
|
181
190
|
align-items: flex-end;
|
|
182
191
|
}
|
|
192
|
+
|
|
183
193
|
select {
|
|
184
194
|
font-size: 1.2em;
|
|
185
195
|
display: inline-block;
|
|
186
196
|
vertical-align: top;
|
|
187
197
|
width: 100%;
|
|
188
198
|
}
|
|
199
|
+
|
|
189
200
|
input {
|
|
190
201
|
color: #fff;
|
|
191
202
|
font-weight: 700;
|
|
@@ -202,4 +213,4 @@
|
|
|
202
213
|
[tabIndex]:focus {
|
|
203
214
|
outline-color: rgb(0, 95, 204);
|
|
204
215
|
}
|
|
205
|
-
}
|
|
216
|
+
}
|
package/src/store/map.actions.ts
CHANGED
|
@@ -20,7 +20,7 @@ type SET_RUNTIME_DATA = Action<'SET_RUNTIME_DATA', RuntimeData>
|
|
|
20
20
|
type SET_RUNTIME_FILTERS = Action<'SET_RUNTIME_FILTERS', VizFilter[]>
|
|
21
21
|
type SET_RUNTIME_LEGEND = Action<'SET_RUNTIME_LEGEND', GeneratedLegend | []>
|
|
22
22
|
type SET_SCALE = Action<'SET_SCALE', number>
|
|
23
|
-
type
|
|
23
|
+
type SET_STATES_TO_SHOW = Action<'SET_STATES_TO_SHOW', string[]>
|
|
24
24
|
type SET_TOPO_DATA = Action<'SET_TOPO_DATA', any>
|
|
25
25
|
type SET_TRANSLATE = Action<'SET_TRANSLATE', [number, number]>
|
|
26
26
|
|
|
@@ -39,7 +39,7 @@ export type MapActions =
|
|
|
39
39
|
| SET_RUNTIME_FILTERS
|
|
40
40
|
| SET_RUNTIME_LEGEND
|
|
41
41
|
| SET_SCALE
|
|
42
|
-
|
|
|
42
|
+
| SET_STATES_TO_SHOW
|
|
43
43
|
| SET_TOPO_DATA
|
|
44
44
|
| SET_TRANSLATE
|
|
45
45
|
|
package/src/store/map.reducer.ts
CHANGED
|
@@ -1,13 +1,24 @@
|
|
|
1
|
-
import { MapConfig } from '../types/MapConfig'
|
|
1
|
+
import { MapConfig, RuntimeFilters } from '../types/MapConfig'
|
|
2
2
|
import MapActions from './map.actions'
|
|
3
3
|
import defaults from './../data/initial-state'
|
|
4
4
|
import { devToolsWrapper } from '@cdc/core/helpers/withDevTools'
|
|
5
5
|
import _ from 'lodash'
|
|
6
|
+
import { Modal } from '../types/Modal'
|
|
7
|
+
import { GeneratedLegend } from '../helpers/generateRuntimeLegend'
|
|
8
|
+
import { RuntimeData } from '../types/RuntimeData'
|
|
6
9
|
|
|
7
10
|
export const getInitialState = (configObj = {}): MapState => {
|
|
11
|
+
// Create defaults without palette version to avoid overriding legacy configs
|
|
12
|
+
const defaultsWithoutPaletteaName = { ...defaults }
|
|
13
|
+
|
|
14
|
+
// Only apply palette defaults if the loaded config explicitly has general.palette
|
|
15
|
+
// if (!configObj?.general?.palette?.name) {
|
|
16
|
+
// delete defaultsWithoutPaletteaName.general?.palette.name
|
|
17
|
+
// }
|
|
18
|
+
|
|
8
19
|
return {
|
|
9
20
|
dataUrl: configObj.dataUrl || '',
|
|
10
|
-
config: _.merge({},
|
|
21
|
+
config: _.merge({}, defaultsWithoutPaletteaName, configObj),
|
|
11
22
|
loading: false,
|
|
12
23
|
accessibleStatus: '',
|
|
13
24
|
coveLoadedHasRan: false,
|
|
@@ -24,7 +35,7 @@ export const getInitialState = (configObj = {}): MapState => {
|
|
|
24
35
|
runtimeData: { init: true },
|
|
25
36
|
runtimeFilters: [],
|
|
26
37
|
runtimeLegend: [],
|
|
27
|
-
|
|
38
|
+
statesToShow: []
|
|
28
39
|
}
|
|
29
40
|
}
|
|
30
41
|
|
|
@@ -42,11 +53,11 @@ export type MapState = {
|
|
|
42
53
|
projection: object | null
|
|
43
54
|
requiredColumns: string[]
|
|
44
55
|
scale: number
|
|
45
|
-
modal:
|
|
46
|
-
runtimeData:
|
|
47
|
-
runtimeFilters:
|
|
48
|
-
runtimeLegend:
|
|
49
|
-
|
|
56
|
+
modal: Modal | null
|
|
57
|
+
runtimeData: RuntimeData | { init: boolean }
|
|
58
|
+
runtimeFilters: RuntimeFilters
|
|
59
|
+
runtimeLegend: GeneratedLegend | []
|
|
60
|
+
statesToShow: string[]
|
|
50
61
|
dataUrl: string
|
|
51
62
|
}
|
|
52
63
|
|
|
@@ -84,8 +95,8 @@ const reducer = (state: MapState, action: MapActions): MapState => {
|
|
|
84
95
|
return { ...state, runtimeFilters: action.payload }
|
|
85
96
|
case 'SET_RUNTIME_LEGEND':
|
|
86
97
|
return { ...state, runtimeLegend: action.payload }
|
|
87
|
-
case '
|
|
88
|
-
return { ...state,
|
|
98
|
+
case 'SET_STATES_TO_SHOW':
|
|
99
|
+
return { ...state, statesToShow: action.payload }
|
|
89
100
|
default:
|
|
90
101
|
return state
|
|
91
102
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import path from 'path'
|
|
2
|
+
import { testStandaloneBuild } from '@cdc/core/helpers/tests/testStandaloneBuild.ts'
|
|
3
|
+
import { describe, it, expect } from 'vitest'
|
|
4
|
+
|
|
5
|
+
describe('Map', () => {
|
|
6
|
+
it('Can be built in isolation', async () => {
|
|
7
|
+
const pkgDir = path.join(__dirname, '..')
|
|
8
|
+
const result = testStandaloneBuild(pkgDir)
|
|
9
|
+
expect(result).toBe(true)
|
|
10
|
+
})
|
|
11
|
+
})
|
package/src/types/MapConfig.ts
CHANGED
|
@@ -3,6 +3,10 @@ import { type Visualization } from '@cdc/core/types/Visualization'
|
|
|
3
3
|
import { type EditorColumnProperties } from '@cdc/core/types/EditorColumnProperties'
|
|
4
4
|
import { type Version } from '@cdc/core/types/Version'
|
|
5
5
|
import { type VizFilter } from '@cdc/core/types/VizFilter'
|
|
6
|
+
import { MarkupConfig } from '@cdc/core/types/MarkupVariable'
|
|
7
|
+
|
|
8
|
+
// Runtime data types
|
|
9
|
+
export type RuntimeFilters = VizFilter[] & { fromHash?: number }
|
|
6
10
|
|
|
7
11
|
export type MapVisualSettings = {
|
|
8
12
|
/** minBubbleSize - Minimum Circle Size when the map has a type of bubble */
|
|
@@ -38,14 +42,19 @@ export type PatternSelection = {
|
|
|
38
42
|
contrastCheck: boolean
|
|
39
43
|
}
|
|
40
44
|
|
|
41
|
-
|
|
42
|
-
export type
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
// Base column properties with name required, all others optional
|
|
46
|
+
export type BaseColumnProperties = Pick<EditorColumnProperties, 'name'> &
|
|
47
|
+
Partial<Pick<EditorColumnProperties, 'label' | 'tooltip' | 'dataTable' | 'prefix' | 'suffix'>>
|
|
48
|
+
|
|
49
|
+
// Simple column type for name-only columns
|
|
50
|
+
export type SimpleColumnProperties = Pick<EditorColumnProperties, 'name'>
|
|
51
|
+
|
|
52
|
+
// Specific column types for better semantics
|
|
53
|
+
export type GeoColumnProperties = BaseColumnProperties
|
|
54
|
+
export type LatitudeColumnProperties = SimpleColumnProperties
|
|
55
|
+
export type LongitudeColumnProperties = SimpleColumnProperties
|
|
56
|
+
export type NavigateColumnProperties = SimpleColumnProperties
|
|
57
|
+
export type PrimaryColumnProperties = BaseColumnProperties
|
|
49
58
|
|
|
50
59
|
export type LegendShapeItem = {
|
|
51
60
|
column: string
|
|
@@ -70,15 +79,13 @@ export type Coordinate = [number, number]
|
|
|
70
79
|
|
|
71
80
|
export type DataRow = {
|
|
72
81
|
uid?: string // optional 'uid' property
|
|
73
|
-
[key: string]:
|
|
82
|
+
[key: string]: string | number | boolean | null | undefined // allowing primitive data types for dynamic columns
|
|
74
83
|
}
|
|
75
84
|
|
|
76
85
|
export type MapConfig = Visualization & {
|
|
77
86
|
annotations: Annotation[]
|
|
78
87
|
// map color palette
|
|
79
88
|
color: string
|
|
80
|
-
// custom color palette
|
|
81
|
-
customColors: string[]
|
|
82
89
|
columns: {
|
|
83
90
|
geo: GeoColumnProperties
|
|
84
91
|
primary: PrimaryColumnProperties
|
|
@@ -93,6 +100,7 @@ export type MapConfig = Visualization & {
|
|
|
93
100
|
filters: VizFilter[]
|
|
94
101
|
general: {
|
|
95
102
|
navigationTarget: '_self' | '_blank'
|
|
103
|
+
noDataMessage: string // single-state no data message
|
|
96
104
|
subtext: string
|
|
97
105
|
introText: string
|
|
98
106
|
allowMapZoom: boolean
|
|
@@ -121,16 +129,19 @@ export type MapConfig = Visualization & {
|
|
|
121
129
|
language: string
|
|
122
130
|
palette: {
|
|
123
131
|
isReversed: boolean
|
|
132
|
+
name: string
|
|
133
|
+
version: string
|
|
134
|
+
customColors?: string[]
|
|
124
135
|
}
|
|
125
136
|
showDownloadMediaButton: boolean
|
|
126
137
|
showDownloadImgButton: boolean
|
|
127
138
|
showDownloadPdfButton: boolean
|
|
128
139
|
showSidebar: boolean
|
|
129
140
|
showTitle: boolean
|
|
130
|
-
|
|
141
|
+
statesPicked: {
|
|
131
142
|
fipsCode: string
|
|
132
143
|
stateName: string
|
|
133
|
-
}
|
|
144
|
+
}[]
|
|
134
145
|
territoriesAlwaysShow: boolean
|
|
135
146
|
territoriesLabel: string
|
|
136
147
|
title: string
|
|
@@ -176,13 +187,10 @@ export type MapConfig = Visualization & {
|
|
|
176
187
|
tooltips: {
|
|
177
188
|
appearanceType: 'hover' | 'click'
|
|
178
189
|
linkLabel: string
|
|
179
|
-
capitalizeLabels: boolean
|
|
180
190
|
opacity: number
|
|
181
191
|
}
|
|
182
192
|
runtime: {
|
|
183
193
|
editorErrorMessage: string[]
|
|
184
|
-
// when a single state map doesn't include a fips code show a message...
|
|
185
|
-
noStateFoundMessage: string
|
|
186
194
|
}
|
|
187
195
|
mapPosition: { coordinates: Coordinate; zoom: number }
|
|
188
196
|
map: {
|
|
@@ -197,4 +205,4 @@ export type MapConfig = Visualization & {
|
|
|
197
205
|
type: 'map'
|
|
198
206
|
// version of the map
|
|
199
207
|
version: Version
|
|
200
|
-
}
|
|
208
|
+
} & MarkupConfig
|
package/src/types/MapContext.ts
CHANGED
|
@@ -2,12 +2,10 @@ import { DataRow, type MapConfig } from './MapConfig'
|
|
|
2
2
|
import { type ViewPort } from '@cdc/core/types/ViewPort'
|
|
3
3
|
import { DimensionsType } from '@cdc/core/types/Dimensions'
|
|
4
4
|
import { VizFilter } from '@cdc/core/types/VizFilter'
|
|
5
|
-
import { type RefObject } from 'react'
|
|
6
5
|
|
|
7
6
|
export type MapContext = {
|
|
8
7
|
currentViewport: ViewPort
|
|
9
8
|
content: { geoName: string; keyedData: Record<string, any> }
|
|
10
|
-
data: DataRow[]
|
|
11
9
|
dimensions: DimensionsType
|
|
12
10
|
displayDataAsText: string | number
|
|
13
11
|
displayGeoName: (key: string, convertFipsCodes: boolean) => string
|
|
@@ -22,7 +20,6 @@ export type MapContext = {
|
|
|
22
20
|
handleCircleClick: Function
|
|
23
21
|
handleDragStateChange: Function
|
|
24
22
|
isDraggingAnnotation: boolean
|
|
25
|
-
innerContainerRef: RefObject<HTMLDivElement>
|
|
26
23
|
isDashboard: boolean
|
|
27
24
|
isEditor: boolean
|
|
28
25
|
isFilterValueSupported: boolean
|
|
@@ -32,21 +29,18 @@ export type MapContext = {
|
|
|
32
29
|
position: 'side' | 'top' | 'bottom'
|
|
33
30
|
resetLegendToggles: Function
|
|
34
31
|
runtimeFilters: Function
|
|
35
|
-
legendMemo: Function
|
|
36
|
-
legendSpecialClassLastMemo: Function
|
|
37
32
|
runtimeLegend
|
|
38
33
|
setParentConfig: Function
|
|
39
34
|
setRuntimeData: Function
|
|
40
|
-
setRuntimeFilters: Function
|
|
41
|
-
setRuntimeLegend: Function
|
|
42
35
|
setSharedFilterValue: Function
|
|
43
36
|
setConfig: (newState: MapConfig) => MapConfig
|
|
44
37
|
config: MapConfig
|
|
45
38
|
viewport: ViewPort
|
|
46
|
-
|
|
39
|
+
statesToShow: string[]
|
|
47
40
|
scale: number
|
|
48
41
|
translate: [number, number]
|
|
49
42
|
topoData: object
|
|
50
43
|
runtimeData: Object[]
|
|
51
44
|
tooltipId: string
|
|
45
|
+
interactionLabel?: string
|
|
52
46
|
}
|