@cdc/map 4.25.8 → 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/dist/cdcmap.js +54263 -52600
- 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/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/state-with-pattern.json +883 -0
- package/index.html +35 -34
- package/package.json +26 -5
- package/src/CdcMap.tsx +23 -8
- package/src/CdcMapComponent.tsx +215 -309
- 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 +15 -5
- 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/us-bubble-cities.json +306 -0
- package/src/components/BubbleList.tsx +16 -12
- package/src/components/CityList.tsx +85 -107
- package/src/components/DataTable.tsx +37 -9
- package/src/components/EditorPanel/components/EditorPanel.tsx +177 -165
- 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 +109 -73
- 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/NavigationMenu.tsx +11 -2
- package/src/components/UsaMap/components/SingleState/SingleState.CountyOutput.tsx +24 -7
- package/src/components/UsaMap/components/UsaMap.County.tsx +111 -37
- package/src/components/UsaMap/components/UsaMap.Region.tsx +23 -5
- package/src/components/UsaMap/components/UsaMap.SingleState.tsx +6 -6
- package/src/components/UsaMap/components/UsaMap.State.tsx +28 -10
- package/src/components/UsaMap/helpers/map.ts +2 -2
- package/src/components/WorldMap/WorldMap.tsx +113 -25
- package/src/components/ZoomControls.tsx +6 -9
- package/src/context/LegendMemoContext.tsx +30 -0
- package/src/context.ts +1 -40
- package/src/data/initial-state.js +143 -130
- package/src/data/supported-geos.js +17 -2
- package/src/helpers/applyColorToLegend.ts +116 -20
- package/src/helpers/applyLegendToRow.ts +10 -6
- 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 +1 -1
- package/src/helpers/generateRuntimeLegend.ts +44 -8
- 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 +8 -5
- 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 +25 -6
- package/src/hooks/useTooltip.ts +1 -2
- package/src/index.jsx +0 -2
- package/src/store/map.reducer.ts +17 -6
- package/src/test/CdcMap.test.jsx +11 -0
- package/src/types/MapConfig.ts +23 -14
- package/src/types/MapContext.ts +0 -7
- package/src/types/runtimeLegend.ts +17 -1
- package/vite.config.js +2 -7
- package/vitest.config.ts +16 -0
- package/src/coreStyles_map.scss +0 -3
- package/src/helpers/colorDistributions.ts +0 -12
- package/src/helpers/generateColorsArray.ts +0 -14
- 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,22 +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'
|
|
71
72
|
import { publishAnalyticsEvent } from '@cdc/core/helpers/metrics/helpers'
|
|
73
|
+
import { getVizTitle, getVizSubType } from '@cdc/core/helpers/metrics/utils'
|
|
72
74
|
|
|
73
75
|
type CdcMapComponent = {
|
|
74
76
|
config: MapConfig
|
|
@@ -79,6 +81,8 @@ type CdcMapComponent = {
|
|
|
79
81
|
navigationHandler: Function
|
|
80
82
|
setSharedFilter: Function
|
|
81
83
|
setSharedFilterValue: Function
|
|
84
|
+
setConfig?: Function
|
|
85
|
+
loadConfig?: Function
|
|
82
86
|
datasets?: Datasets
|
|
83
87
|
interactionLabel: string
|
|
84
88
|
}
|
|
@@ -95,7 +99,7 @@ const CdcMapComponent: React.FC<CdcMapComponent> = ({
|
|
|
95
99
|
setConfig: setParentConfig,
|
|
96
100
|
loadConfig,
|
|
97
101
|
datasets,
|
|
98
|
-
interactionLabel = ''
|
|
102
|
+
interactionLabel = 'no link provided'
|
|
99
103
|
}) => {
|
|
100
104
|
const initialState = getInitialState(configObj)
|
|
101
105
|
|
|
@@ -132,151 +136,44 @@ const CdcMapComponent: React.FC<CdcMapComponent> = ({
|
|
|
132
136
|
}
|
|
133
137
|
|
|
134
138
|
useEffect(() => {
|
|
135
|
-
const _newConfig = getInitialState(
|
|
139
|
+
const _newConfig = getInitialState(cloneConfig(configObj)).config
|
|
136
140
|
if (configObj.data) {
|
|
137
141
|
_newConfig.data = configObj.data
|
|
138
142
|
}
|
|
139
143
|
setConfig(_newConfig)
|
|
140
144
|
}, [configObj.data]) // eslint-disable-line
|
|
141
145
|
|
|
142
|
-
const setRuntimeData = (data: RuntimeData) => {
|
|
143
|
-
dispatch({ type: 'SET_RUNTIME_DATA', payload: data })
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const setRuntimeFilters = (filters: VizFilter[]) => {
|
|
147
|
-
dispatch({ type: 'SET_RUNTIME_FILTERS', payload: filters })
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
const setRuntimeLegend = legend => {
|
|
151
|
-
dispatch({ type: 'SET_RUNTIME_LEGEND', payload: legend })
|
|
152
|
-
}
|
|
153
|
-
|
|
154
146
|
const _setRuntimeData = (data: any) => {
|
|
155
147
|
const _newFilters = addValuesToFilters(data, [])
|
|
156
148
|
setConfig({ ...config, filters: _newFilters })
|
|
157
149
|
if (config) {
|
|
158
|
-
|
|
150
|
+
dispatch({ type: 'SET_RUNTIME_DATA', payload: data })
|
|
159
151
|
} else {
|
|
160
|
-
|
|
152
|
+
dispatch({ type: 'SET_RUNTIME_FILTERS', payload: data })
|
|
161
153
|
}
|
|
162
154
|
}
|
|
163
|
-
const transform = new DataTransform()
|
|
164
155
|
|
|
165
156
|
// Refs
|
|
166
157
|
const innerContainerRef = useRef()
|
|
167
|
-
const legendMemo = useRef(new Map())
|
|
168
158
|
const legendRef = useRef(null)
|
|
169
|
-
const legendSpecialClassLastMemo = useRef(new Map())
|
|
170
159
|
const mapSvg = useRef(null)
|
|
171
160
|
const tooltipRef = useRef(null)
|
|
172
161
|
|
|
162
|
+
// Legend memo hook
|
|
163
|
+
const { legendMemo, legendSpecialClassLastMemo } = useLegendMemo()
|
|
164
|
+
|
|
173
165
|
// IDs
|
|
174
|
-
const imageId = useMemo(() => `download-id-${Math.random().toString(36).
|
|
166
|
+
const imageId = useMemo(() => `download-id-${Math.random().toString(36).substring(2, 11)}`, [])
|
|
175
167
|
const legendId = useId()
|
|
176
168
|
const mapId = useId()
|
|
177
|
-
const tooltipId = '
|
|
169
|
+
const tooltipId = 'tooltipId'
|
|
178
170
|
|
|
179
171
|
// hooks
|
|
180
172
|
const { currentViewport, dimensions, container, outerContainerRef } = useResizeObserver(isEditor)
|
|
181
173
|
|
|
182
|
-
const reloadURLData = async () => {
|
|
183
|
-
if (config.dataUrl) {
|
|
184
|
-
const dataUrl = new URL(config.runtimeDataUrl || config.dataUrl, window.location.origin)
|
|
185
|
-
let qsParams = Object.fromEntries(new URLSearchParams(dataUrl.search))
|
|
186
|
-
|
|
187
|
-
let isUpdateNeeded = false
|
|
188
|
-
config.filters.forEach(filter => {
|
|
189
|
-
if (filter.type === 'url' && qsParams[filter.queryParameter] !== decodeURIComponent(filter.active)) {
|
|
190
|
-
qsParams[filter.queryParameter] = filter.active
|
|
191
|
-
isUpdateNeeded = true
|
|
192
|
-
}
|
|
193
|
-
})
|
|
194
|
-
|
|
195
|
-
if (!isUpdateNeeded) return
|
|
196
|
-
|
|
197
|
-
let dataUrlFinal = `${dataUrl.origin}${dataUrl.pathname}${Object.keys(qsParams)
|
|
198
|
-
.map((param, i) => {
|
|
199
|
-
let qs = i === 0 ? '?' : '&'
|
|
200
|
-
qs += param + '='
|
|
201
|
-
qs += qsParams[param]
|
|
202
|
-
return qs
|
|
203
|
-
})
|
|
204
|
-
.join('')}`
|
|
205
|
-
|
|
206
|
-
let data
|
|
207
|
-
|
|
208
|
-
try {
|
|
209
|
-
const regex = /(?:\.([^.]+))?$/
|
|
210
|
-
|
|
211
|
-
const ext = regex.exec(dataUrl.pathname)[1]
|
|
212
|
-
if ('csv' === ext || isSolrCsv(dataUrlFinal)) {
|
|
213
|
-
data = await fetch(dataUrlFinal)
|
|
214
|
-
.then(response => response.text())
|
|
215
|
-
.then(responseText => {
|
|
216
|
-
const parsedCsv = Papa.parse(responseText, {
|
|
217
|
-
header: true,
|
|
218
|
-
dynamicTyping: true,
|
|
219
|
-
skipEmptyLines: true,
|
|
220
|
-
encoding: 'utf-8'
|
|
221
|
-
})
|
|
222
|
-
return parsedCsv.data
|
|
223
|
-
})
|
|
224
|
-
} else if ('json' === ext || isSolrJson(dataUrlFinal)) {
|
|
225
|
-
data = await fetch(dataUrlFinal).then(response => response.json())
|
|
226
|
-
} else {
|
|
227
|
-
data = []
|
|
228
|
-
}
|
|
229
|
-
} catch (e) {
|
|
230
|
-
console.error(`Cannot parse URL: ${dataUrlFinal}`) // eslint-disable-line
|
|
231
|
-
console.log(e) // eslint-disable-line
|
|
232
|
-
data = []
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
if (config.dataDescription) {
|
|
236
|
-
data = transform.autoStandardize(data)
|
|
237
|
-
data = transform.developerStandardize(data, config.dataDescription)
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
const newConfig = _.cloneDeep(config)
|
|
241
|
-
newConfig.data = data
|
|
242
|
-
newConfig.runtimeDataUrl = dataUrlFinal
|
|
243
|
-
|
|
244
|
-
setConfig(newConfig)
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
/**
|
|
249
|
-
* Publishes 'cove_loaded' only after the map SVG is rendered in the DOM.
|
|
250
|
-
* Checks immediately, then uses a MutationObserver as a fallback for async rendering.
|
|
251
|
-
* Update the mapSvg ref if the map container changes.
|
|
252
|
-
*/
|
|
253
|
-
const observeMapSvgLoaded = (mapSvgRef, config, coveLoadedHasRan, publish, dispatch) => {
|
|
254
|
-
// Immediate check in case SVG is already present
|
|
255
|
-
const svgEl = mapSvgRef.current?.querySelector('svg')
|
|
256
|
-
if (svgEl && svgEl.childNodes.length > 0) {
|
|
257
|
-
publish('cove_loaded', { config })
|
|
258
|
-
dispatch({ type: 'SET_COVE_LOADED_HAS_RAN', payload: true })
|
|
259
|
-
return () => {}
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
// Fallback to observer for async SVG rendering
|
|
263
|
-
const observer = new MutationObserver(() => {
|
|
264
|
-
const svgEl = mapSvgRef.current?.querySelector('svg')
|
|
265
|
-
if (svgEl && svgEl.childNodes.length > 0) {
|
|
266
|
-
publish('cove_loaded', { config })
|
|
267
|
-
dispatch({ type: 'SET_COVE_LOADED_HAS_RAN', payload: true })
|
|
268
|
-
observer.disconnect()
|
|
269
|
-
}
|
|
270
|
-
})
|
|
271
|
-
|
|
272
|
-
observer.observe(mapSvgRef.current, { childList: true, subtree: true })
|
|
273
|
-
|
|
274
|
-
return () => observer.disconnect()
|
|
275
|
-
}
|
|
276
|
-
|
|
277
174
|
useEffect(() => {
|
|
278
175
|
if (!mapSvg.current || coveLoadedHasRan) return
|
|
279
|
-
return observeMapSvgLoaded(mapSvg, config, coveLoadedHasRan,
|
|
176
|
+
return observeMapSvgLoaded(mapSvg, config, coveLoadedHasRan, dispatch)
|
|
280
177
|
}, [config, loading, runtimeData, coveLoadedHasRan])
|
|
281
178
|
|
|
282
179
|
useEffect(() => {
|
|
@@ -299,7 +196,7 @@ const CdcMapComponent: React.FC<CdcMapComponent> = ({
|
|
|
299
196
|
filters[index].active = queryStringFilterValue
|
|
300
197
|
}
|
|
301
198
|
})
|
|
302
|
-
|
|
199
|
+
dispatch({ type: 'SET_RUNTIME_FILTERS', payload: filters })
|
|
303
200
|
}
|
|
304
201
|
}
|
|
305
202
|
|
|
@@ -328,7 +225,7 @@ const CdcMapComponent: React.FC<CdcMapComponent> = ({
|
|
|
328
225
|
isCategoryLegend,
|
|
329
226
|
config.table.showNonGeoData
|
|
330
227
|
)
|
|
331
|
-
|
|
228
|
+
dispatch({ type: 'SET_RUNTIME_DATA', payload: newRuntimeData })
|
|
332
229
|
} else {
|
|
333
230
|
if (hashLegend !== runtimeLegend?.fromHash && undefined === runtimeData?.init) {
|
|
334
231
|
const legend = generateRuntimeLegend(
|
|
@@ -340,7 +237,7 @@ const CdcMapComponent: React.FC<CdcMapComponent> = ({
|
|
|
340
237
|
legendMemo,
|
|
341
238
|
legendSpecialClassLastMemo
|
|
342
239
|
)
|
|
343
|
-
|
|
240
|
+
dispatch({ type: 'SET_RUNTIME_LEGEND', payload: legend })
|
|
344
241
|
}
|
|
345
242
|
}
|
|
346
243
|
}, [config, configObj.data])
|
|
@@ -356,21 +253,68 @@ const CdcMapComponent: React.FC<CdcMapComponent> = ({
|
|
|
356
253
|
legendMemo,
|
|
357
254
|
legendSpecialClassLastMemo
|
|
358
255
|
)
|
|
359
|
-
|
|
256
|
+
dispatch({ type: 'SET_RUNTIME_LEGEND', payload: legend })
|
|
360
257
|
}, [runtimeData, config, runtimeFilters])
|
|
361
258
|
|
|
362
259
|
useEffect(() => {
|
|
363
260
|
if (!isDashboard) {
|
|
364
|
-
reloadURLData()
|
|
261
|
+
reloadURLData(config, setConfig)
|
|
365
262
|
}
|
|
366
263
|
}, [JSON.stringify(config.filters)])
|
|
367
264
|
|
|
368
265
|
const { general, tooltips, table, columns } = config
|
|
369
|
-
const { subtext = '', geoType } = general
|
|
370
|
-
const { showDownloadImgButton, showDownloadPdfButton, headerColor, introText } = general
|
|
266
|
+
const { subtext = '', geoType, showDownloadImgButton, showDownloadPdfButton, headerColor, introText } = general
|
|
371
267
|
const { closeModal } = useModal()
|
|
372
268
|
|
|
373
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
|
+
}
|
|
374
318
|
|
|
375
319
|
if (isEditor) {
|
|
376
320
|
if (!title || title === '') title = 'Map Title'
|
|
@@ -384,17 +328,12 @@ const CdcMapComponent: React.FC<CdcMapComponent> = ({
|
|
|
384
328
|
content: modal,
|
|
385
329
|
currentViewport,
|
|
386
330
|
customNavigationHandler,
|
|
387
|
-
data: runtimeData,
|
|
388
331
|
dimensions,
|
|
389
332
|
filteredCountryCode,
|
|
390
|
-
innerContainerRef,
|
|
391
333
|
isDashboard,
|
|
392
334
|
isEditor,
|
|
393
|
-
legendMemo,
|
|
394
|
-
legendSpecialClassLastMemo,
|
|
395
335
|
logo,
|
|
396
336
|
mapId,
|
|
397
|
-
outerContainerRef,
|
|
398
337
|
position,
|
|
399
338
|
projection,
|
|
400
339
|
runtimeData,
|
|
@@ -402,9 +341,6 @@ const CdcMapComponent: React.FC<CdcMapComponent> = ({
|
|
|
402
341
|
runtimeLegend,
|
|
403
342
|
scale,
|
|
404
343
|
setConfig,
|
|
405
|
-
setRuntimeData,
|
|
406
|
-
setRuntimeFilters,
|
|
407
|
-
setRuntimeLegend,
|
|
408
344
|
setSharedFilter,
|
|
409
345
|
setSharedFilterValue,
|
|
410
346
|
config,
|
|
@@ -428,159 +364,128 @@ const CdcMapComponent: React.FC<CdcMapComponent> = ({
|
|
|
428
364
|
href={`#data-table-${config.dataKey}`}
|
|
429
365
|
className='margin-left-href'
|
|
430
366
|
onClick={() => {
|
|
431
|
-
publishAnalyticsEvent(
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
'
|
|
436
|
-
|
|
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
|
+
})
|
|
437
376
|
}}
|
|
438
377
|
>
|
|
439
378
|
{config.dataKey} (Go to Table)
|
|
440
379
|
</a>
|
|
441
380
|
)
|
|
442
381
|
|
|
443
|
-
const sectionClassNames = () => {
|
|
444
|
-
const classes = ['cove-component__content', 'cdc-map-inner-container', `${currentViewport}`, `${headerColor}`]
|
|
445
|
-
if (config?.runtime?.editorErrorMessage.length > 0) classes.push('type-map--has-error')
|
|
446
|
-
return classes.join(' ')
|
|
447
|
-
}
|
|
448
|
-
|
|
449
382
|
return (
|
|
450
|
-
<
|
|
451
|
-
<
|
|
452
|
-
<
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
{
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
{config?.filters?.length > 0 && (
|
|
482
|
-
<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}
|
|
483
413
|
config={config}
|
|
484
|
-
|
|
485
|
-
filteredData={runtimeFilters}
|
|
486
|
-
setFilters={_setRuntimeData}
|
|
487
|
-
dimensions={dimensions}
|
|
488
|
-
standaloneMap={!config}
|
|
489
|
-
interactionLabel={interactionLabel}
|
|
414
|
+
classes={['map-title', general.showTitle === true ? 'visible' : 'hidden', `${headerColor}`]}
|
|
490
415
|
/>
|
|
491
|
-
|
|
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
|
+
)}
|
|
492
420
|
|
|
493
|
-
|
|
494
|
-
role='region'
|
|
495
|
-
tabIndex={0}
|
|
496
|
-
className={getMapContainerClasses(config, modal).join(' ')}
|
|
497
|
-
onClick={e => closeModal(e, modal)}
|
|
498
|
-
onKeyDown={e => {
|
|
499
|
-
if (e.key === 'Enter') {
|
|
500
|
-
closeModal(e, modal)
|
|
501
|
-
}
|
|
502
|
-
}}
|
|
503
|
-
>
|
|
504
|
-
<section
|
|
505
|
-
className='outline-none geography-container w-100 position-relative'
|
|
506
|
-
ref={mapSvg}
|
|
507
|
-
tabIndex='0'
|
|
508
|
-
>
|
|
509
|
-
{currentViewport && (
|
|
510
|
-
<>
|
|
511
|
-
{modal && <Modal />}
|
|
512
|
-
{'single-state' === geoType && <UsaMap.SingleState />}
|
|
513
|
-
{'us' === geoType && 'us-geocode' !== config.general.type && <UsaMap.State />}
|
|
514
|
-
{'us-region' === geoType && <UsaMap.Region />}
|
|
515
|
-
{'us-county' === geoType && <UsaMap.County />}
|
|
516
|
-
{'world' === geoType && <WorldMap />}
|
|
517
|
-
{'google-map' === geoType && <GoogleMap />}
|
|
518
|
-
{
|
|
519
|
-
/* logo is handled in UsaMap.State when applicable */
|
|
520
|
-
// prettier-ignore
|
|
521
|
-
'data' === general.type && logo && ('us' !== geoType || 'us-geocode' === general.type) && (
|
|
522
|
-
<img src={logo} alt='' className='map-logo' style={{ maxWidth: '50px' }} />
|
|
523
|
-
)
|
|
524
|
-
}
|
|
525
|
-
</>
|
|
526
|
-
)}
|
|
527
|
-
</section>
|
|
421
|
+
{processedIntroText && <section className='introText mb-4'>{parse(processedIntroText)}</section>}
|
|
528
422
|
|
|
529
|
-
{
|
|
530
|
-
<
|
|
423
|
+
{config?.filters?.length > 0 && (
|
|
424
|
+
<Filters
|
|
425
|
+
config={config}
|
|
426
|
+
setConfig={setConfig}
|
|
427
|
+
filteredData={runtimeFilters}
|
|
428
|
+
setFilters={_setRuntimeData}
|
|
531
429
|
dimensions={dimensions}
|
|
532
|
-
|
|
533
|
-
skipId={tabId}
|
|
534
|
-
containerWidthPadding={0}
|
|
535
|
-
currentViewport={currentViewport}
|
|
430
|
+
standaloneMap={!config}
|
|
536
431
|
interactionLabel={interactionLabel}
|
|
537
432
|
/>
|
|
538
433
|
)}
|
|
539
|
-
</div>
|
|
540
|
-
|
|
541
|
-
{'navigation' === general.type && (
|
|
542
|
-
<NavigationMenu
|
|
543
|
-
mapTabbingID={tabId}
|
|
544
|
-
displayGeoName={displayGeoName}
|
|
545
|
-
data={runtimeData}
|
|
546
|
-
options={general}
|
|
547
|
-
columns={config.columns}
|
|
548
|
-
navigationHandler={val => navigationHandler('_blank', val, customNavigationHandler)}
|
|
549
|
-
/>
|
|
550
|
-
)}
|
|
551
434
|
|
|
552
|
-
|
|
553
|
-
|
|
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
|
+
/>
|
|
554
455
|
|
|
555
|
-
|
|
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>
|
|
556
467
|
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
interactionLabel={interactionLabel}
|
|
566
|
-
/>
|
|
567
|
-
)}
|
|
568
|
-
{showDownloadPdfButton && (
|
|
569
|
-
<MediaControls.Button
|
|
570
|
-
text='Download PDF'
|
|
571
|
-
title='Download Chart as PDF'
|
|
572
|
-
type='pdf'
|
|
573
|
-
state={config}
|
|
574
|
-
interactionLabel={interactionLabel}
|
|
575
|
-
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)}
|
|
576
476
|
/>
|
|
577
477
|
)}
|
|
578
|
-
</MediaControls.Section>
|
|
579
478
|
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
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) && (
|
|
584
489
|
<DataTable
|
|
585
490
|
columns={columns}
|
|
586
491
|
config={config}
|
|
@@ -611,42 +516,43 @@ const CdcMapComponent: React.FC<CdcMapComponent> = ({
|
|
|
611
516
|
/>
|
|
612
517
|
)}
|
|
613
518
|
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
)}
|
|
619
|
-
|
|
620
|
-
<div aria-live='assertive' className='cdcdataviz-sr-only'>
|
|
621
|
-
{accessibleStatus}
|
|
622
|
-
</div>
|
|
623
|
-
|
|
624
|
-
{!isDraggingAnnotation &&
|
|
625
|
-
!window.matchMedia('(any-hover: none)').matches &&
|
|
626
|
-
'hover' === tooltips.appearanceType && (
|
|
627
|
-
<ReactTooltip
|
|
628
|
-
id={`tooltip__${tooltipId}`}
|
|
629
|
-
float={true}
|
|
630
|
-
className={`tooltip tooltip-test`}
|
|
631
|
-
style={{ background: `rgba(255,255,255, ${config.tooltips.opacity / 100})`, color: 'black' }}
|
|
632
|
-
/>
|
|
519
|
+
{config.annotations?.length > 0 && <Annotation.Dropdown />}
|
|
520
|
+
|
|
521
|
+
{processedFootnotes && <section className='footnotes pt-2 mt-4'>{parse(processedFootnotes)}</section>}
|
|
522
|
+
</section>
|
|
633
523
|
)}
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
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>
|
|
650
556
|
)
|
|
651
557
|
}
|
|
652
558
|
|