@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
package/src/CdcMapComponent.tsx
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
import React, { useEffect, useRef, useId, useReducer, useContext, useMemo } from 'react'
|
|
3
3
|
import 'whatwg-fetch'
|
|
4
4
|
import { Tooltip as ReactTooltip } from 'react-tooltip'
|
|
5
|
-
import Papa from 'papaparse'
|
|
6
5
|
import parse from 'html-react-parser'
|
|
7
6
|
import 'react-tooltip/dist/react-tooltip.css'
|
|
8
7
|
|
|
@@ -10,7 +9,6 @@ import 'react-tooltip/dist/react-tooltip.css'
|
|
|
10
9
|
import DataTable from '@cdc/core/components/DataTable'
|
|
11
10
|
import Filters from '@cdc/core/components/Filters'
|
|
12
11
|
import Layout from '@cdc/core/components/Layout'
|
|
13
|
-
import MediaControls from '@cdc/core/components/MediaControls'
|
|
14
12
|
import SkipTo from '@cdc/core/components/elements/SkipTo'
|
|
15
13
|
import Title from '@cdc/core/components/ui/Title'
|
|
16
14
|
import Waiting from '@cdc/core/components/Waiting'
|
|
@@ -25,13 +23,11 @@ import './scss/main.scss'
|
|
|
25
23
|
import './cdcMapComponent.styles.css'
|
|
26
24
|
|
|
27
25
|
// Core Helpers
|
|
28
|
-
import { DataTransform } from '@cdc/core/helpers/DataTransform'
|
|
29
26
|
import { getQueryStringFilterValue } from '@cdc/core/helpers/queryStringUtils'
|
|
30
|
-
import { isSolrCsv, isSolrJson } from '@cdc/core/helpers/isSolr'
|
|
31
|
-
import { publish } from '@cdc/core/helpers/events'
|
|
32
27
|
import { generateRuntimeFilters } from './helpers/generateRuntimeFilters'
|
|
33
28
|
import { type MapReducerType, MapState } from './store/map.reducer'
|
|
34
29
|
import { addValuesToFilters } from '@cdc/core/helpers/addValuesToFilters'
|
|
30
|
+
import { processMarkupVariables } from '@cdc/core/helpers/markupProcessor'
|
|
35
31
|
|
|
36
32
|
// Map Helpers
|
|
37
33
|
import {
|
|
@@ -46,6 +42,10 @@ import {
|
|
|
46
42
|
} from './helpers'
|
|
47
43
|
import generateRuntimeLegend from './helpers/generateRuntimeLegend'
|
|
48
44
|
import generateRuntimeData from './helpers/generateRuntimeData'
|
|
45
|
+
import { reloadURLData } from './helpers/urlDataHelpers'
|
|
46
|
+
import { observeMapSvgLoaded } from './helpers/mapObserverHelpers'
|
|
47
|
+
import { buildSectionClassNames } from './helpers/componentHelpers'
|
|
48
|
+
import { shouldShowDataTable } from './helpers/dataTableHelpers'
|
|
49
49
|
|
|
50
50
|
// Child Components
|
|
51
51
|
import Annotation from './components/Annotation'
|
|
@@ -53,21 +53,24 @@ import ConfigContext, { MapDispatchContext } from './context'
|
|
|
53
53
|
import EditorPanel from './components/EditorPanel'
|
|
54
54
|
import Error from './components/EditorPanel/components/Error'
|
|
55
55
|
import Legend from './components/Legend'
|
|
56
|
-
import
|
|
56
|
+
import MapContainer from './components/MapContainer'
|
|
57
|
+
import MapControls from './components/MapControls'
|
|
57
58
|
import NavigationMenu from './components/NavigationMenu'
|
|
58
|
-
import UsaMap from './components/UsaMap'
|
|
59
|
-
import WorldMap from './components/WorldMap'
|
|
60
|
-
import GoogleMap from './components/GoogleMap'
|
|
61
59
|
|
|
62
60
|
// hooks
|
|
63
61
|
import useResizeObserver from './hooks/useResizeObserver'
|
|
62
|
+
import useLegendMemo from './hooks/useLegendMemo'
|
|
63
|
+
import { LegendMemoProvider } from './context/LegendMemoContext'
|
|
64
64
|
import { VizFilter } from '@cdc/core/types/VizFilter'
|
|
65
65
|
import { getInitialState, mapReducer } from './store/map.reducer'
|
|
66
66
|
import { RuntimeData } from './types/RuntimeData'
|
|
67
|
-
import EditorContext from '@cdc/
|
|
67
|
+
import EditorContext from '@cdc/core/contexts/EditorContext'
|
|
68
68
|
import MapActions from './store/map.actions'
|
|
69
69
|
import _ from 'lodash'
|
|
70
|
+
import { cloneConfig } from '@cdc/core/helpers/cloneConfig'
|
|
70
71
|
import useModal from './hooks/useModal'
|
|
72
|
+
import { publishAnalyticsEvent } from '@cdc/core/helpers/metrics/helpers'
|
|
73
|
+
import { getVizTitle, getVizSubType } from '@cdc/core/helpers/metrics/utils'
|
|
71
74
|
|
|
72
75
|
type CdcMapComponent = {
|
|
73
76
|
config: MapConfig
|
|
@@ -78,7 +81,10 @@ type CdcMapComponent = {
|
|
|
78
81
|
navigationHandler: Function
|
|
79
82
|
setSharedFilter: Function
|
|
80
83
|
setSharedFilterValue: Function
|
|
84
|
+
setConfig?: Function
|
|
85
|
+
loadConfig?: Function
|
|
81
86
|
datasets?: Datasets
|
|
87
|
+
interactionLabel: string
|
|
82
88
|
}
|
|
83
89
|
|
|
84
90
|
const CdcMapComponent: React.FC<CdcMapComponent> = ({
|
|
@@ -92,7 +98,8 @@ const CdcMapComponent: React.FC<CdcMapComponent> = ({
|
|
|
92
98
|
link,
|
|
93
99
|
setConfig: setParentConfig,
|
|
94
100
|
loadConfig,
|
|
95
|
-
datasets
|
|
101
|
+
datasets,
|
|
102
|
+
interactionLabel = 'no link provided'
|
|
96
103
|
}) => {
|
|
97
104
|
const initialState = getInitialState(configObj)
|
|
98
105
|
|
|
@@ -112,7 +119,7 @@ const CdcMapComponent: React.FC<CdcMapComponent> = ({
|
|
|
112
119
|
scale,
|
|
113
120
|
translate,
|
|
114
121
|
projection,
|
|
115
|
-
|
|
122
|
+
statesToShow,
|
|
116
123
|
requiredColumns,
|
|
117
124
|
topoData,
|
|
118
125
|
coveLoadedHasRan,
|
|
@@ -129,151 +136,44 @@ const CdcMapComponent: React.FC<CdcMapComponent> = ({
|
|
|
129
136
|
}
|
|
130
137
|
|
|
131
138
|
useEffect(() => {
|
|
132
|
-
const _newConfig = getInitialState(
|
|
139
|
+
const _newConfig = getInitialState(cloneConfig(configObj)).config
|
|
133
140
|
if (configObj.data) {
|
|
134
141
|
_newConfig.data = configObj.data
|
|
135
142
|
}
|
|
136
143
|
setConfig(_newConfig)
|
|
137
144
|
}, [configObj.data]) // eslint-disable-line
|
|
138
145
|
|
|
139
|
-
const setRuntimeData = (data: RuntimeData) => {
|
|
140
|
-
dispatch({ type: 'SET_RUNTIME_DATA', payload: data })
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
const setRuntimeFilters = (filters: VizFilter[]) => {
|
|
144
|
-
dispatch({ type: 'SET_RUNTIME_FILTERS', payload: filters })
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
const setRuntimeLegend = legend => {
|
|
148
|
-
dispatch({ type: 'SET_RUNTIME_LEGEND', payload: legend })
|
|
149
|
-
}
|
|
150
|
-
|
|
151
146
|
const _setRuntimeData = (data: any) => {
|
|
152
147
|
const _newFilters = addValuesToFilters(data, [])
|
|
153
148
|
setConfig({ ...config, filters: _newFilters })
|
|
154
149
|
if (config) {
|
|
155
|
-
|
|
150
|
+
dispatch({ type: 'SET_RUNTIME_DATA', payload: data })
|
|
156
151
|
} else {
|
|
157
|
-
|
|
152
|
+
dispatch({ type: 'SET_RUNTIME_FILTERS', payload: data })
|
|
158
153
|
}
|
|
159
154
|
}
|
|
160
|
-
const transform = new DataTransform()
|
|
161
155
|
|
|
162
156
|
// Refs
|
|
163
157
|
const innerContainerRef = useRef()
|
|
164
|
-
const legendMemo = useRef(new Map())
|
|
165
158
|
const legendRef = useRef(null)
|
|
166
|
-
const legendSpecialClassLastMemo = useRef(new Map())
|
|
167
159
|
const mapSvg = useRef(null)
|
|
168
160
|
const tooltipRef = useRef(null)
|
|
169
161
|
|
|
162
|
+
// Legend memo hook
|
|
163
|
+
const { legendMemo, legendSpecialClassLastMemo } = useLegendMemo()
|
|
164
|
+
|
|
170
165
|
// IDs
|
|
171
|
-
const imageId = useMemo(() => `download-id-${Math.random().toString(36).
|
|
166
|
+
const imageId = useMemo(() => `download-id-${Math.random().toString(36).substring(2, 11)}`, [])
|
|
172
167
|
const legendId = useId()
|
|
173
168
|
const mapId = useId()
|
|
174
|
-
const tooltipId = '
|
|
169
|
+
const tooltipId = 'tooltipId'
|
|
175
170
|
|
|
176
171
|
// hooks
|
|
177
172
|
const { currentViewport, dimensions, container, outerContainerRef } = useResizeObserver(isEditor)
|
|
178
173
|
|
|
179
|
-
const reloadURLData = async () => {
|
|
180
|
-
if (config.dataUrl) {
|
|
181
|
-
const dataUrl = new URL(config.runtimeDataUrl || config.dataUrl, window.location.origin)
|
|
182
|
-
let qsParams = Object.fromEntries(new URLSearchParams(dataUrl.search))
|
|
183
|
-
|
|
184
|
-
let isUpdateNeeded = false
|
|
185
|
-
config.filters.forEach(filter => {
|
|
186
|
-
if (filter.type === 'url' && qsParams[filter.queryParameter] !== decodeURIComponent(filter.active)) {
|
|
187
|
-
qsParams[filter.queryParameter] = filter.active
|
|
188
|
-
isUpdateNeeded = true
|
|
189
|
-
}
|
|
190
|
-
})
|
|
191
|
-
|
|
192
|
-
if (!isUpdateNeeded) return
|
|
193
|
-
|
|
194
|
-
let dataUrlFinal = `${dataUrl.origin}${dataUrl.pathname}${Object.keys(qsParams)
|
|
195
|
-
.map((param, i) => {
|
|
196
|
-
let qs = i === 0 ? '?' : '&'
|
|
197
|
-
qs += param + '='
|
|
198
|
-
qs += qsParams[param]
|
|
199
|
-
return qs
|
|
200
|
-
})
|
|
201
|
-
.join('')}`
|
|
202
|
-
|
|
203
|
-
let data
|
|
204
|
-
|
|
205
|
-
try {
|
|
206
|
-
const regex = /(?:\.([^.]+))?$/
|
|
207
|
-
|
|
208
|
-
const ext = regex.exec(dataUrl.pathname)[1]
|
|
209
|
-
if ('csv' === ext || isSolrCsv(dataUrlFinal)) {
|
|
210
|
-
data = await fetch(dataUrlFinal)
|
|
211
|
-
.then(response => response.text())
|
|
212
|
-
.then(responseText => {
|
|
213
|
-
const parsedCsv = Papa.parse(responseText, {
|
|
214
|
-
header: true,
|
|
215
|
-
dynamicTyping: true,
|
|
216
|
-
skipEmptyLines: true,
|
|
217
|
-
encoding: 'utf-8'
|
|
218
|
-
})
|
|
219
|
-
return parsedCsv.data
|
|
220
|
-
})
|
|
221
|
-
} else if ('json' === ext || isSolrJson(dataUrlFinal)) {
|
|
222
|
-
data = await fetch(dataUrlFinal).then(response => response.json())
|
|
223
|
-
} else {
|
|
224
|
-
data = []
|
|
225
|
-
}
|
|
226
|
-
} catch (e) {
|
|
227
|
-
console.error(`Cannot parse URL: ${dataUrlFinal}`) // eslint-disable-line
|
|
228
|
-
console.log(e) // eslint-disable-line
|
|
229
|
-
data = []
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
if (config.dataDescription) {
|
|
233
|
-
data = transform.autoStandardize(data)
|
|
234
|
-
data = transform.developerStandardize(data, config.dataDescription)
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
const newConfig = _.cloneDeep(config)
|
|
238
|
-
newConfig.data = data
|
|
239
|
-
newConfig.runtimeDataUrl = dataUrlFinal
|
|
240
|
-
|
|
241
|
-
setConfig(newConfig)
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
/**
|
|
246
|
-
* Publishes 'cove_loaded' only after the map SVG is rendered in the DOM.
|
|
247
|
-
* Checks immediately, then uses a MutationObserver as a fallback for async rendering.
|
|
248
|
-
* Update the mapSvg ref if the map container changes.
|
|
249
|
-
*/
|
|
250
|
-
const observeMapSvgLoaded = (mapSvgRef, config, coveLoadedHasRan, publish, dispatch) => {
|
|
251
|
-
// Immediate check in case SVG is already present
|
|
252
|
-
const svgEl = mapSvgRef.current?.querySelector('svg')
|
|
253
|
-
if (svgEl && svgEl.childNodes.length > 0) {
|
|
254
|
-
publish('cove_loaded', { config })
|
|
255
|
-
dispatch({ type: 'SET_COVE_LOADED_HAS_RAN', payload: true })
|
|
256
|
-
return () => {}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
// Fallback to observer for async SVG rendering
|
|
260
|
-
const observer = new MutationObserver(() => {
|
|
261
|
-
const svgEl = mapSvgRef.current?.querySelector('svg')
|
|
262
|
-
if (svgEl && svgEl.childNodes.length > 0) {
|
|
263
|
-
publish('cove_loaded', { config })
|
|
264
|
-
dispatch({ type: 'SET_COVE_LOADED_HAS_RAN', payload: true })
|
|
265
|
-
observer.disconnect()
|
|
266
|
-
}
|
|
267
|
-
})
|
|
268
|
-
|
|
269
|
-
observer.observe(mapSvgRef.current, { childList: true, subtree: true })
|
|
270
|
-
|
|
271
|
-
return () => observer.disconnect()
|
|
272
|
-
}
|
|
273
|
-
|
|
274
174
|
useEffect(() => {
|
|
275
175
|
if (!mapSvg.current || coveLoadedHasRan) return
|
|
276
|
-
return observeMapSvgLoaded(mapSvg, config, coveLoadedHasRan,
|
|
176
|
+
return observeMapSvgLoaded(mapSvg, config, coveLoadedHasRan, dispatch)
|
|
277
177
|
}, [config, loading, runtimeData, coveLoadedHasRan])
|
|
278
178
|
|
|
279
179
|
useEffect(() => {
|
|
@@ -296,7 +196,7 @@ const CdcMapComponent: React.FC<CdcMapComponent> = ({
|
|
|
296
196
|
filters[index].active = queryStringFilterValue
|
|
297
197
|
}
|
|
298
198
|
})
|
|
299
|
-
|
|
199
|
+
dispatch({ type: 'SET_RUNTIME_FILTERS', payload: filters })
|
|
300
200
|
}
|
|
301
201
|
}
|
|
302
202
|
|
|
@@ -325,7 +225,7 @@ const CdcMapComponent: React.FC<CdcMapComponent> = ({
|
|
|
325
225
|
isCategoryLegend,
|
|
326
226
|
config.table.showNonGeoData
|
|
327
227
|
)
|
|
328
|
-
|
|
228
|
+
dispatch({ type: 'SET_RUNTIME_DATA', payload: newRuntimeData })
|
|
329
229
|
} else {
|
|
330
230
|
if (hashLegend !== runtimeLegend?.fromHash && undefined === runtimeData?.init) {
|
|
331
231
|
const legend = generateRuntimeLegend(
|
|
@@ -337,7 +237,7 @@ const CdcMapComponent: React.FC<CdcMapComponent> = ({
|
|
|
337
237
|
legendMemo,
|
|
338
238
|
legendSpecialClassLastMemo
|
|
339
239
|
)
|
|
340
|
-
|
|
240
|
+
dispatch({ type: 'SET_RUNTIME_LEGEND', payload: legend })
|
|
341
241
|
}
|
|
342
242
|
}
|
|
343
243
|
}, [config, configObj.data])
|
|
@@ -353,21 +253,68 @@ const CdcMapComponent: React.FC<CdcMapComponent> = ({
|
|
|
353
253
|
legendMemo,
|
|
354
254
|
legendSpecialClassLastMemo
|
|
355
255
|
)
|
|
356
|
-
|
|
256
|
+
dispatch({ type: 'SET_RUNTIME_LEGEND', payload: legend })
|
|
357
257
|
}, [runtimeData, config, runtimeFilters])
|
|
358
258
|
|
|
359
259
|
useEffect(() => {
|
|
360
260
|
if (!isDashboard) {
|
|
361
|
-
reloadURLData()
|
|
261
|
+
reloadURLData(config, setConfig)
|
|
362
262
|
}
|
|
363
263
|
}, [JSON.stringify(config.filters)])
|
|
364
264
|
|
|
365
265
|
const { general, tooltips, table, columns } = config
|
|
366
|
-
const { subtext = '', geoType } = general
|
|
367
|
-
const { showDownloadImgButton, showDownloadPdfButton, headerColor, introText } = general
|
|
266
|
+
const { subtext = '', geoType, showDownloadImgButton, showDownloadPdfButton, headerColor, introText } = general
|
|
368
267
|
const { closeModal } = useModal()
|
|
369
268
|
|
|
370
269
|
let title = config.general.title
|
|
270
|
+
let processedSuperTitle = general.superTitle
|
|
271
|
+
let processedSubtext = subtext
|
|
272
|
+
let processedIntroText = introText
|
|
273
|
+
let processedFootnotes = general.footnotes
|
|
274
|
+
|
|
275
|
+
// Process markup variables if enabled
|
|
276
|
+
if (config.enableMarkupVariables && config.markupVariables?.length > 0) {
|
|
277
|
+
// Combine viz filters with dashboard filters for markup processing
|
|
278
|
+
const combinedFilters = [...(config.filters || []), ...(config.dashboardFilters || [])]
|
|
279
|
+
|
|
280
|
+
const markupOptions = { isEditor, filters: combinedFilters }
|
|
281
|
+
|
|
282
|
+
if (title) {
|
|
283
|
+
title = processMarkupVariables(title, config.data || [], config.markupVariables, markupOptions).processedContent
|
|
284
|
+
}
|
|
285
|
+
if (general.superTitle) {
|
|
286
|
+
processedSuperTitle = processMarkupVariables(
|
|
287
|
+
general.superTitle,
|
|
288
|
+
config.data || [],
|
|
289
|
+
config.markupVariables,
|
|
290
|
+
markupOptions
|
|
291
|
+
).processedContent
|
|
292
|
+
}
|
|
293
|
+
if (subtext) {
|
|
294
|
+
processedSubtext = processMarkupVariables(
|
|
295
|
+
subtext,
|
|
296
|
+
config.data || [],
|
|
297
|
+
config.markupVariables,
|
|
298
|
+
markupOptions
|
|
299
|
+
).processedContent
|
|
300
|
+
}
|
|
301
|
+
if (introText) {
|
|
302
|
+
processedIntroText = processMarkupVariables(
|
|
303
|
+
introText,
|
|
304
|
+
config.data || [],
|
|
305
|
+
config.markupVariables,
|
|
306
|
+
markupOptions
|
|
307
|
+
).processedContent
|
|
308
|
+
}
|
|
309
|
+
if (general.footnotes) {
|
|
310
|
+
processedFootnotes = processMarkupVariables(
|
|
311
|
+
general.footnotes,
|
|
312
|
+
config.data || [],
|
|
313
|
+
config.markupVariables,
|
|
314
|
+
markupOptions
|
|
315
|
+
).processedContent
|
|
316
|
+
}
|
|
317
|
+
}
|
|
371
318
|
|
|
372
319
|
if (isEditor) {
|
|
373
320
|
if (!title || title === '') title = 'Map Title'
|
|
@@ -381,17 +328,12 @@ const CdcMapComponent: React.FC<CdcMapComponent> = ({
|
|
|
381
328
|
content: modal,
|
|
382
329
|
currentViewport,
|
|
383
330
|
customNavigationHandler,
|
|
384
|
-
data: runtimeData,
|
|
385
331
|
dimensions,
|
|
386
332
|
filteredCountryCode,
|
|
387
|
-
innerContainerRef,
|
|
388
333
|
isDashboard,
|
|
389
334
|
isEditor,
|
|
390
|
-
legendMemo,
|
|
391
|
-
legendSpecialClassLastMemo,
|
|
392
335
|
logo,
|
|
393
336
|
mapId,
|
|
394
|
-
outerContainerRef,
|
|
395
337
|
position,
|
|
396
338
|
projection,
|
|
397
339
|
runtimeData,
|
|
@@ -399,19 +341,17 @@ const CdcMapComponent: React.FC<CdcMapComponent> = ({
|
|
|
399
341
|
runtimeLegend,
|
|
400
342
|
scale,
|
|
401
343
|
setConfig,
|
|
402
|
-
setRuntimeData,
|
|
403
|
-
setRuntimeFilters,
|
|
404
|
-
setRuntimeLegend,
|
|
405
344
|
setSharedFilter,
|
|
406
345
|
setSharedFilterValue,
|
|
407
346
|
config,
|
|
408
|
-
|
|
347
|
+
statesToShow,
|
|
409
348
|
tooltipId,
|
|
410
349
|
tooltipRef,
|
|
411
350
|
topoData,
|
|
412
351
|
translate,
|
|
413
352
|
isDraggingAnnotation,
|
|
414
|
-
loadConfig
|
|
353
|
+
loadConfig,
|
|
354
|
+
interactionLabel
|
|
415
355
|
}
|
|
416
356
|
|
|
417
357
|
if (!config.data) return <></>
|
|
@@ -420,148 +360,132 @@ const CdcMapComponent: React.FC<CdcMapComponent> = ({
|
|
|
420
360
|
|
|
421
361
|
// this only shows in Dashboard config mode and only if Show Table is also set
|
|
422
362
|
const tableLink = (
|
|
423
|
-
<a
|
|
363
|
+
<a
|
|
364
|
+
href={`#data-table-${config.dataKey}`}
|
|
365
|
+
className='margin-left-href'
|
|
366
|
+
onClick={() => {
|
|
367
|
+
publishAnalyticsEvent({
|
|
368
|
+
vizType: config.type,
|
|
369
|
+
vizSubType: getVizSubType(config),
|
|
370
|
+
eventType: `link_to_data_table_click`,
|
|
371
|
+
eventAction: 'click',
|
|
372
|
+
eventLabel: `${interactionLabel}`,
|
|
373
|
+
vizTitle: getVizTitle(config),
|
|
374
|
+
specifics: `table: #data-table-${config.dataKey}`
|
|
375
|
+
})
|
|
376
|
+
}}
|
|
377
|
+
>
|
|
424
378
|
{config.dataKey} (Go to Table)
|
|
425
379
|
</a>
|
|
426
380
|
)
|
|
427
381
|
|
|
428
|
-
const sectionClassNames = () => {
|
|
429
|
-
const classes = ['cove-component__content', 'cdc-map-inner-container', `${currentViewport}`, `${headerColor}`]
|
|
430
|
-
if (config?.runtime?.editorErrorMessage.length > 0) classes.push('type-map--has-error')
|
|
431
|
-
return classes.join(' ')
|
|
432
|
-
}
|
|
433
|
-
|
|
434
382
|
return (
|
|
435
|
-
<
|
|
436
|
-
<
|
|
437
|
-
<
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
{
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
{config?.filters?.length > 0 && (
|
|
467
|
-
<Filters
|
|
383
|
+
<LegendMemoProvider legendMemo={legendMemo} legendSpecialClassLastMemo={legendSpecialClassLastMemo}>
|
|
384
|
+
<ConfigContext.Provider value={mapProps}>
|
|
385
|
+
<MapDispatchContext.Provider value={dispatch}>
|
|
386
|
+
<Layout.VisualizationWrapper
|
|
387
|
+
config={config}
|
|
388
|
+
isEditor={isEditor}
|
|
389
|
+
ref={outerContainerRef}
|
|
390
|
+
currentViewport={currentViewport}
|
|
391
|
+
imageId={imageId}
|
|
392
|
+
showEditorPanel={config.showEditorPanel}
|
|
393
|
+
>
|
|
394
|
+
{isEditor && <EditorPanel datasets={datasets} />}
|
|
395
|
+
<Layout.Responsive isEditor={isEditor}>
|
|
396
|
+
{requiredColumns?.length > 0 && (
|
|
397
|
+
<Waiting requiredColumns={requiredColumns} className={displayPanel ? `waiting` : `waiting collapsed`} />
|
|
398
|
+
)}
|
|
399
|
+
{!runtimeData.init && (general.type === 'navigation' || runtimeLegend) && (
|
|
400
|
+
<section
|
|
401
|
+
className={buildSectionClassNames(
|
|
402
|
+
currentViewport,
|
|
403
|
+
headerColor,
|
|
404
|
+
config?.runtime?.editorErrorMessage.length > 0
|
|
405
|
+
)}
|
|
406
|
+
aria-label={'Map: ' + title}
|
|
407
|
+
ref={innerContainerRef}
|
|
408
|
+
>
|
|
409
|
+
{config?.runtime?.editorErrorMessage.length > 0 && <Error />}
|
|
410
|
+
<Title
|
|
411
|
+
title={title}
|
|
412
|
+
superTitle={processedSuperTitle}
|
|
468
413
|
config={config}
|
|
469
|
-
|
|
470
|
-
filteredData={runtimeFilters}
|
|
471
|
-
setFilters={_setRuntimeData}
|
|
472
|
-
dimensions={dimensions}
|
|
473
|
-
standaloneMap={!config}
|
|
414
|
+
classes={['map-title', general.showTitle === true ? 'visible' : 'hidden', `${headerColor}`]}
|
|
474
415
|
/>
|
|
475
|
-
|
|
416
|
+
<SkipTo skipId={tabId} skipMessage='Skip Over Map Container' />
|
|
417
|
+
{config?.annotations?.length > 0 && (
|
|
418
|
+
<SkipTo skipId={tabId} skipMessage={`Skip over annotations`} key={`skip-annotations`} />
|
|
419
|
+
)}
|
|
476
420
|
|
|
477
|
-
|
|
478
|
-
role='region'
|
|
479
|
-
tabIndex={0}
|
|
480
|
-
className={getMapContainerClasses(config, modal).join(' ')}
|
|
481
|
-
onClick={e => closeModal(e, modal)}
|
|
482
|
-
onKeyDown={e => {
|
|
483
|
-
if (e.key === 'Enter') {
|
|
484
|
-
closeModal(e, modal)
|
|
485
|
-
}
|
|
486
|
-
}}
|
|
487
|
-
>
|
|
488
|
-
<section
|
|
489
|
-
className='outline-none geography-container w-100 position-relative'
|
|
490
|
-
ref={mapSvg}
|
|
491
|
-
tabIndex='0'
|
|
492
|
-
>
|
|
493
|
-
{currentViewport && (
|
|
494
|
-
<>
|
|
495
|
-
{modal && <Modal />}
|
|
496
|
-
{'single-state' === geoType && <UsaMap.SingleState />}
|
|
497
|
-
{'us' === geoType && 'us-geocode' !== config.general.type && <UsaMap.State />}
|
|
498
|
-
{'us-region' === geoType && <UsaMap.Region />}
|
|
499
|
-
{'us-county' === geoType && <UsaMap.County />}
|
|
500
|
-
{'world' === geoType && <WorldMap />}
|
|
501
|
-
{'google-map' === geoType && <GoogleMap />}
|
|
502
|
-
{
|
|
503
|
-
/* logo is handled in UsaMap.State when applicable */
|
|
504
|
-
// prettier-ignore
|
|
505
|
-
'data' === general.type && logo && ('us' !== geoType || 'us-geocode' === general.type) && (
|
|
506
|
-
<img src={logo} alt='' className='map-logo' style={{ maxWidth: '50px' }} />
|
|
507
|
-
)
|
|
508
|
-
}
|
|
509
|
-
</>
|
|
510
|
-
)}
|
|
511
|
-
</section>
|
|
421
|
+
{processedIntroText && <section className='introText mb-4'>{parse(processedIntroText)}</section>}
|
|
512
422
|
|
|
513
|
-
{
|
|
514
|
-
<
|
|
423
|
+
{config?.filters?.length > 0 && (
|
|
424
|
+
<Filters
|
|
425
|
+
config={config}
|
|
426
|
+
setConfig={setConfig}
|
|
427
|
+
filteredData={runtimeFilters}
|
|
428
|
+
setFilters={_setRuntimeData}
|
|
515
429
|
dimensions={dimensions}
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
containerWidthPadding={0}
|
|
519
|
-
currentViewport={currentViewport}
|
|
430
|
+
standaloneMap={!config}
|
|
431
|
+
interactionLabel={interactionLabel}
|
|
520
432
|
/>
|
|
521
433
|
)}
|
|
522
|
-
</div>
|
|
523
|
-
|
|
524
|
-
{'navigation' === general.type && (
|
|
525
|
-
<NavigationMenu
|
|
526
|
-
mapTabbingID={tabId}
|
|
527
|
-
displayGeoName={displayGeoName}
|
|
528
|
-
data={runtimeData}
|
|
529
|
-
options={general}
|
|
530
|
-
columns={config.columns}
|
|
531
|
-
navigationHandler={val => navigationHandler('_blank', val, customNavigationHandler)}
|
|
532
|
-
/>
|
|
533
|
-
)}
|
|
534
434
|
|
|
535
|
-
|
|
536
|
-
|
|
435
|
+
<div
|
|
436
|
+
role='region'
|
|
437
|
+
tabIndex={0}
|
|
438
|
+
className={getMapContainerClasses(config, modal).join(' ')}
|
|
439
|
+
onClick={e => closeModal(e, modal)}
|
|
440
|
+
onKeyDown={e => {
|
|
441
|
+
if (e.key === 'Enter') {
|
|
442
|
+
closeModal(e, modal)
|
|
443
|
+
}
|
|
444
|
+
}}
|
|
445
|
+
>
|
|
446
|
+
<MapContainer
|
|
447
|
+
config={config}
|
|
448
|
+
modal={modal}
|
|
449
|
+
currentViewport={currentViewport}
|
|
450
|
+
geoType={geoType}
|
|
451
|
+
general={general}
|
|
452
|
+
logo={logo}
|
|
453
|
+
mapSvgRef={mapSvg}
|
|
454
|
+
/>
|
|
537
455
|
|
|
538
|
-
|
|
456
|
+
{general.showSidebar && 'navigation' !== general.type && (
|
|
457
|
+
<Legend
|
|
458
|
+
dimensions={dimensions}
|
|
459
|
+
ref={legendRef}
|
|
460
|
+
skipId={tabId}
|
|
461
|
+
containerWidthPadding={0}
|
|
462
|
+
currentViewport={currentViewport}
|
|
463
|
+
interactionLabel={interactionLabel}
|
|
464
|
+
/>
|
|
465
|
+
)}
|
|
466
|
+
</div>
|
|
539
467
|
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
/>
|
|
549
|
-
)}
|
|
550
|
-
{showDownloadPdfButton && (
|
|
551
|
-
<MediaControls.Button
|
|
552
|
-
text='Download PDF'
|
|
553
|
-
title='Download Chart as PDF'
|
|
554
|
-
type='pdf'
|
|
555
|
-
state={config}
|
|
556
|
-
elementToCapture={imageId}
|
|
468
|
+
{'navigation' === general.type && (
|
|
469
|
+
<NavigationMenu
|
|
470
|
+
mapTabbingID={tabId}
|
|
471
|
+
displayGeoName={displayGeoName}
|
|
472
|
+
data={runtimeData}
|
|
473
|
+
options={general}
|
|
474
|
+
columns={config.columns}
|
|
475
|
+
navigationHandler={val => navigationHandler('_blank', val, customNavigationHandler)}
|
|
557
476
|
/>
|
|
558
477
|
)}
|
|
559
|
-
</MediaControls.Section>
|
|
560
478
|
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
479
|
+
{/* Link (to data table?) */}
|
|
480
|
+
{isDashboard && config.table?.forceDisplay && config.table.showDataTableLink
|
|
481
|
+
? tableLink
|
|
482
|
+
: link && link}
|
|
483
|
+
|
|
484
|
+
{processedSubtext.length > 0 && <p className='subtext mt-4'>{parse(processedSubtext)}</p>}
|
|
485
|
+
|
|
486
|
+
<MapControls config={config} imageId={imageId} interactionLabel={interactionLabel} />
|
|
487
|
+
|
|
488
|
+
{shouldShowDataTable(config, table, general, loading) && (
|
|
565
489
|
<DataTable
|
|
566
490
|
columns={columns}
|
|
567
491
|
config={config}
|
|
@@ -588,47 +512,47 @@ const CdcMapComponent: React.FC<CdcMapComponent> = ({
|
|
|
588
512
|
tableTitle={table.label}
|
|
589
513
|
vizTitle={general.title}
|
|
590
514
|
wrapColumns={table.wrapColumns}
|
|
515
|
+
interactionLabel={interactionLabel}
|
|
591
516
|
/>
|
|
592
517
|
)}
|
|
593
518
|
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
)}
|
|
599
|
-
|
|
600
|
-
<div aria-live='assertive' className='cdcdataviz-sr-only'>
|
|
601
|
-
{accessibleStatus}
|
|
602
|
-
</div>
|
|
603
|
-
|
|
604
|
-
{!isDraggingAnnotation &&
|
|
605
|
-
!window.matchMedia('(any-hover: none)').matches &&
|
|
606
|
-
'hover' === tooltips.appearanceType && (
|
|
607
|
-
<ReactTooltip
|
|
608
|
-
id={`tooltip__${tooltipId}`}
|
|
609
|
-
float={true}
|
|
610
|
-
className={`${
|
|
611
|
-
tooltips.capitalizeLabels ? 'capitalize tooltip tooltip-test' : 'tooltip tooltip-test'
|
|
612
|
-
}`}
|
|
613
|
-
style={{ background: `rgba(255,255,255, ${config.tooltips.opacity / 100})`, color: 'black' }}
|
|
614
|
-
/>
|
|
519
|
+
{config.annotations?.length > 0 && <Annotation.Dropdown />}
|
|
520
|
+
|
|
521
|
+
{processedFootnotes && <section className='footnotes pt-2 mt-4'>{parse(processedFootnotes)}</section>}
|
|
522
|
+
</section>
|
|
615
523
|
)}
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
524
|
+
|
|
525
|
+
<div aria-live='assertive' className='cdcdataviz-sr-only'>
|
|
526
|
+
{accessibleStatus}
|
|
527
|
+
</div>
|
|
528
|
+
|
|
529
|
+
{!isDraggingAnnotation &&
|
|
530
|
+
!window.matchMedia('(any-hover: none)').matches &&
|
|
531
|
+
'hover' === tooltips.appearanceType && (
|
|
532
|
+
<ReactTooltip
|
|
533
|
+
id={`tooltip__${tooltipId}`}
|
|
534
|
+
float={true}
|
|
535
|
+
className={`tooltip tooltip-test`}
|
|
536
|
+
style={{ background: `rgba(255,255,255, ${config.tooltips.opacity / 100})`, color: 'black' }}
|
|
537
|
+
/>
|
|
538
|
+
)}
|
|
539
|
+
<div
|
|
540
|
+
ref={tooltipRef}
|
|
541
|
+
id={`tooltip__${tooltipId}-canvas`}
|
|
542
|
+
className='tooltip'
|
|
543
|
+
style={{
|
|
544
|
+
background: `rgba(255,255,255,${config.tooltips.opacity / 100})`,
|
|
545
|
+
position: 'absolute',
|
|
546
|
+
whiteSpace: 'nowrap',
|
|
547
|
+
display: 'none' // can't use d-none here
|
|
548
|
+
}}
|
|
549
|
+
></div>
|
|
550
|
+
<FootnotesStandAlone config={config.footnotes} filters={config.filters?.filter(f => f.filterFootnotes)} />
|
|
551
|
+
</Layout.Responsive>
|
|
552
|
+
</Layout.VisualizationWrapper>
|
|
553
|
+
</MapDispatchContext.Provider>
|
|
554
|
+
</ConfigContext.Provider>
|
|
555
|
+
</LegendMemoProvider>
|
|
632
556
|
)
|
|
633
557
|
}
|
|
634
558
|
|