@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.
Files changed (99) hide show
  1. package/.claude/settings.local.json +30 -0
  2. package/CLAUDE.local.md +0 -0
  3. package/dist/cdcmap.js +54785 -53159
  4. package/examples/private/c.json +290 -0
  5. package/examples/private/canvas-city-hover.json +787 -0
  6. package/examples/private/d.json +345 -0
  7. package/examples/private/filter-map.json +909 -0
  8. package/examples/private/g.json +1 -0
  9. package/examples/private/h.json +105911 -0
  10. package/examples/private/measles-data.json +378 -0
  11. package/examples/private/measles.json +211 -0
  12. package/examples/private/north-dakota.json +1132 -0
  13. package/examples/private/rsv-data.json +532 -0
  14. package/examples/private/state-with-pattern.json +883 -0
  15. package/examples/private/test.json +222 -640
  16. package/index.html +1 -1
  17. package/package.json +26 -5
  18. package/src/CdcMap.tsx +28 -8
  19. package/src/CdcMapComponent.tsx +230 -306
  20. package/src/_stories/CdcMap.Filters.stories.tsx +2 -2
  21. package/src/_stories/CdcMap.Legend.Gradient.stories.tsx +3 -3
  22. package/src/_stories/CdcMap.Legend.stories.tsx +7 -4
  23. package/src/_stories/CdcMap.Patterns.stories.tsx +2 -2
  24. package/src/_stories/CdcMap.Table.stories.tsx +2 -2
  25. package/src/_stories/CdcMap.stories.tsx +18 -11
  26. package/src/_stories/GoogleMap.stories.tsx +2 -2
  27. package/src/_stories/UsaMap.NoData.stories.tsx +2 -2
  28. package/src/_stories/_mock/equal-number.json +1109 -0
  29. package/src/_stories/_mock/multi-state.json +21389 -0
  30. package/src/_stories/_mock/us-bubble-cities.json +306 -0
  31. package/src/components/BubbleList.tsx +16 -12
  32. package/src/components/CityList.tsx +88 -110
  33. package/src/components/DataTable.tsx +44 -12
  34. package/src/components/EditorPanel/components/EditorPanel.tsx +201 -203
  35. package/src/components/EditorPanel/components/HexShapeSettings.tsx +3 -2
  36. package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +7 -5
  37. package/src/components/Geo.tsx +2 -0
  38. package/src/components/Legend/components/Legend.tsx +117 -93
  39. package/src/components/Legend/components/LegendGroup/Legend.Group.tsx +10 -7
  40. package/src/components/MapContainer.tsx +52 -0
  41. package/src/components/MapControls.tsx +44 -0
  42. package/src/components/Modal.tsx +2 -8
  43. package/src/components/NavigationMenu.tsx +13 -1
  44. package/src/components/UsaMap/components/SingleState/SingleState.CountyOutput.tsx +24 -7
  45. package/src/components/UsaMap/components/SingleState/SingleState.StateOutput.tsx +21 -15
  46. package/src/components/UsaMap/components/TerritoriesSection.tsx +2 -2
  47. package/src/components/UsaMap/components/UsaMap.County.tsx +112 -33
  48. package/src/components/UsaMap/components/UsaMap.Region.tsx +23 -5
  49. package/src/components/UsaMap/components/UsaMap.SingleState.tsx +38 -26
  50. package/src/components/UsaMap/components/UsaMap.State.tsx +28 -10
  51. package/src/components/UsaMap/helpers/map.ts +16 -8
  52. package/src/components/WorldMap/WorldMap.tsx +116 -11
  53. package/src/components/ZoomControls.tsx +6 -9
  54. package/src/context/LegendMemoContext.tsx +30 -0
  55. package/src/context.ts +1 -39
  56. package/src/data/initial-state.js +143 -128
  57. package/src/data/supported-geos.js +202 -4
  58. package/src/helpers/addUIDs.ts +8 -8
  59. package/src/helpers/applyColorToLegend.ts +122 -45
  60. package/src/helpers/applyLegendToRow.ts +15 -13
  61. package/src/helpers/componentHelpers.ts +8 -0
  62. package/src/helpers/constants.ts +12 -0
  63. package/src/helpers/dataTableHelpers.ts +6 -0
  64. package/src/helpers/displayGeoName.ts +12 -7
  65. package/src/helpers/formatLegendLocation.ts +1 -3
  66. package/src/helpers/generateRuntimeLegend.ts +192 -340
  67. package/src/helpers/generateRuntimeLegendHash.ts +4 -2
  68. package/src/helpers/getColumnNames.ts +1 -1
  69. package/src/helpers/getPatternForRow.ts +36 -0
  70. package/src/helpers/getStatesPicked.ts +14 -0
  71. package/src/helpers/handleMapAriaLabels.ts +2 -2
  72. package/src/helpers/index.ts +11 -3
  73. package/src/helpers/isLegendItemDisabled.ts +16 -0
  74. package/src/helpers/mapObserverHelpers.ts +40 -0
  75. package/src/helpers/resetLegendToggles.ts +3 -2
  76. package/src/helpers/toggleLegendActive.ts +6 -11
  77. package/src/helpers/urlDataHelpers.ts +70 -0
  78. package/src/hooks/useGeoClickHandler.ts +35 -1
  79. package/src/hooks/useLegendMemo.ts +17 -0
  80. package/src/hooks/useMapLayers.tsx +5 -4
  81. package/src/hooks/useStateZoom.tsx +137 -88
  82. package/src/hooks/useTooltip.ts +1 -2
  83. package/src/index.jsx +6 -3
  84. package/src/scss/main.scss +23 -12
  85. package/src/store/map.actions.ts +2 -2
  86. package/src/store/map.reducer.ts +21 -10
  87. package/src/test/CdcMap.test.jsx +11 -0
  88. package/src/types/MapConfig.ts +25 -17
  89. package/src/types/MapContext.ts +2 -8
  90. package/src/types/runtimeLegend.ts +12 -10
  91. package/vite.config.js +2 -7
  92. package/vitest.config.ts +16 -0
  93. package/src/_stories/_mock/floating-point.json +0 -427
  94. package/src/coreStyles_map.scss +0 -3
  95. package/src/helpers/colorDistributions.ts +0 -12
  96. package/src/helpers/generateColorsArray.ts +0 -14
  97. package/src/helpers/getStatePicked.ts +0 -8
  98. package/src/helpers/tests/generateColorsArray.test.ts +0 -18
  99. 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 { getFilterControllingStatePicked } from '../components/UsaMap/helpers/map'
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 _ from 'lodash'
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
- const fipsCode = Object.keys(supportedStatesFipsCodes).find(key => supportedStatesFipsCodes[key] === statePicked)
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: 'SET_CONFIG', payload: newConfig })
36
- dispatch({ type: 'SET_STATE_TO_SHOW', payload: stateToShow })
37
- }, [topoData])
128
+ dispatch({ type: 'SET_STATES_TO_SHOW', payload: statesPicked })
129
+ }, [topoData, statesPicked, dispatch])
38
130
 
39
131
  useEffect(() => {
40
- const fipsCode = Object.keys(supportedStatesFipsCodes).find(key => supportedStatesFipsCodes[key] === statePicked)
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
- // Work for centering the state.
64
- let [x, y] = newProjection.translate()
65
- x = x - SVG_WIDTH / 2
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
- const path: GeoPath = geoPath().projection(projection)
69
- const featureCenter = path.centroid(_statePickedData)
70
- const stateCenter = newProjection.invert(featureCenter)
138
+ if (alreadySet) return
71
139
 
72
- const switchState = () => {
73
- dispatch({ type: 'SET_STATE_TO_SHOW', payload: _statePickedData })
74
- setScaleAndTranslate('reset')
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 setScaleAndTranslate = (zoomFunction: string = '') => {
78
- const _prevPosition = position
79
- let newZoom = _prevPosition.zoom
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 = position => {
115
- dispatch({ type: 'SET_POSITION', payload: position })
116
- }
157
+ const handleMoveEnd = useCallback(
158
+ position => {
159
+ dispatch({ type: 'SET_POSITION', payload: position })
160
+ },
161
+ [dispatch]
162
+ )
117
163
 
118
- const handleReset = () => {
119
- setScaleAndTranslate('reset')
120
- }
164
+ const handleZoomReset = useCallback(
165
+ _setRuntimeData => {
166
+ setScaleAndTranslate('reset')
167
+ },
168
+ [setScaleAndTranslate]
169
+ )
121
170
 
122
171
  return {
123
- statePicked,
172
+ statesPicked,
124
173
  setScaleAndTranslate,
125
174
  switchState,
126
175
  handleZoomIn,
127
176
  handleZoomOut,
128
177
  handleMoveEnd,
129
- handleReset,
130
- projection
178
+ handleZoomReset,
179
+ projection: projectionData.projection
131
180
  }
132
181
  }
133
182
 
@@ -1,10 +1,9 @@
1
- import { displayDataAsText } from '../../../core/helpers/displayDataAsText'
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 isEditor={isEditor} configUrl={domContainer.attributes['data-config'].value} containerEl={domContainer} />
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
  )
@@ -15,7 +15,8 @@
15
15
  .cdc-map-outer-container {
16
16
  position: relative;
17
17
  display: flex; // Needed for the main content
18
- .loading > div.la-ball-beat {
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; /* Constrain the modal's height to 90% of the viewport */
84
- overflow-y: auto; /* Enable vertical scrolling if content overflows */
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%; /* Adjust width to fit smaller screens */
133
- top: 10%; /* Offset from the top for better usability */
134
- transform: translate(-50%, 0); /* Remove vertical centering */
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
- > div.select-heading {
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
+ }
@@ -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 SET_STATE_TO_SHOW = Action<'SET_STATE_TO_SHOW', string>
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
- | SET_STATE_TO_SHOW
42
+ | SET_STATES_TO_SHOW
43
43
  | SET_TOPO_DATA
44
44
  | SET_TRANSLATE
45
45
 
@@ -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({}, defaults, configObj),
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
- stateToShow: ''
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: object | null
46
- runtimeData: object
47
- runtimeFilters: object[]
48
- runtimeLegend: object[]
49
- stateToShow: string
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 'SET_STATE_TO_SHOW':
88
- return { ...state, stateToShow: action.payload }
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
+ })
@@ -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
- export type GeoColumnProperties = Pick<EditorColumnProperties, 'name' | 'label' | 'tooltip' | 'dataTable'>
42
- export type LatitudeColumnProperties = Pick<EditorColumnProperties, 'name'>
43
- export type LongitudeColumnProperties = Pick<EditorColumnProperties, 'name'>
44
- export type NavigateColumnProperties = Pick<EditorColumnProperties, 'name'>
45
- export type PrimaryColumnProperties = Pick<
46
- EditorColumnProperties,
47
- 'dataTable' | 'label' | 'name' | 'prefix' | 'suffix' | 'tooltip'
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]: any // allowing any additional properties with a dynamic key (e.g., for `configPrimaryName`)
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
- statePicked: {
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
@@ -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
- stateToShow: string
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
  }