@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
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
} from 'react-accessible-accordion'
|
|
9
9
|
import ConfigContext from '../../../context'
|
|
10
10
|
import _ from 'lodash'
|
|
11
|
+
import { cloneConfig } from '@cdc/core/helpers/cloneConfig'
|
|
11
12
|
|
|
12
13
|
const shapeOptions = ['Arrow Up', 'Arrow Down', 'Arrow Right', 'Arrow Left', 'None']
|
|
13
14
|
|
|
@@ -105,7 +106,7 @@ const HexSettingShapeColumns = props => {
|
|
|
105
106
|
type='text'
|
|
106
107
|
value={shapeGroup.legendTitle || ''}
|
|
107
108
|
onChange={e => {
|
|
108
|
-
const newConfig =
|
|
109
|
+
const newConfig = cloneConfig(config)
|
|
109
110
|
newConfig.hexMap.shapeGroups[shapeGroupIndex].legendTitle = e.target.value
|
|
110
111
|
setConfig(newConfig)
|
|
111
112
|
}}
|
|
@@ -243,7 +244,7 @@ const HexSettingShapeColumns = props => {
|
|
|
243
244
|
className='cove-button'
|
|
244
245
|
style={{ marginTop: '15px' }}
|
|
245
246
|
onClick={() => {
|
|
246
|
-
const newConfig =
|
|
247
|
+
const newConfig = cloneConfig(config)
|
|
247
248
|
_.set(
|
|
248
249
|
newConfig,
|
|
249
250
|
'hexMap.shapeGroups',
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
AccordionItemButton
|
|
8
8
|
} from 'react-accessible-accordion'
|
|
9
9
|
import ConfigContext from '../../../../context'
|
|
10
|
+
import { useLegendMemoContext } from '../../../../context/LegendMemoContext'
|
|
10
11
|
import { type MapContext } from '../../../../types/MapContext'
|
|
11
12
|
import Button from '@cdc/core/components/elements/Button'
|
|
12
13
|
import Tooltip from '@cdc/core/components/ui/Tooltip'
|
|
@@ -14,6 +15,7 @@ import Icon from '@cdc/core/components/ui/Icon'
|
|
|
14
15
|
import './Panel.PatternSettings-style.css'
|
|
15
16
|
import Alert from '@cdc/core/components/Alert'
|
|
16
17
|
import _ from 'lodash'
|
|
18
|
+
import { cloneConfig } from '@cdc/core/helpers/cloneConfig'
|
|
17
19
|
|
|
18
20
|
// topojson helpers for checking color contrasts
|
|
19
21
|
import { feature } from 'topojson-client'
|
|
@@ -26,8 +28,8 @@ type PanelProps = {
|
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
const PatternSettings = ({ name }: PanelProps) => {
|
|
29
|
-
const { config, setConfig, runtimeData,
|
|
30
|
-
|
|
31
|
+
const { config, setConfig, runtimeData, runtimeLegend } = useContext<MapContext>(ConfigContext)
|
|
32
|
+
const { legendMemo, legendSpecialClassLastMemo } = useLegendMemoContext()
|
|
31
33
|
const defaultPattern = 'circles'
|
|
32
34
|
const patternTypes = ['circles', 'waves', 'lines']
|
|
33
35
|
|
|
@@ -114,14 +116,14 @@ const PatternSettings = ({ name }: PanelProps) => {
|
|
|
114
116
|
}
|
|
115
117
|
|
|
116
118
|
const handlePatternFieldUpdate = (field: string, color: string, patternIndex: number) => {
|
|
117
|
-
const _newConfig =
|
|
119
|
+
const _newConfig = cloneConfig(config)
|
|
118
120
|
_newConfig.map.patterns[patternIndex][field] = color
|
|
119
121
|
reviewColorContrast(_newConfig, patternIndex)
|
|
120
122
|
setConfig(_newConfig)
|
|
121
123
|
}
|
|
122
124
|
|
|
123
125
|
const handleRemovePattern = index => {
|
|
124
|
-
const _newConfig =
|
|
126
|
+
const _newConfig = cloneConfig(config)
|
|
125
127
|
const updatedPatterns = config.map.patterns.filter((pattern, i) => i !== index)
|
|
126
128
|
_newConfig.map.patterns = updatedPatterns
|
|
127
129
|
if (checkPatternContrasts()) {
|
|
@@ -161,7 +163,7 @@ const PatternSettings = ({ name }: PanelProps) => {
|
|
|
161
163
|
{patterns.length > 0 && (
|
|
162
164
|
<Alert
|
|
163
165
|
type={checkPatternContrasts() ? 'success' : 'danger'}
|
|
164
|
-
message='Pattern colors must comply with <
|
|
166
|
+
message='Pattern colors must comply with <a href="https://www.w3.org/TR/WCAG21/">WCAG 2.1</a> 3:1 contrast ratio.'
|
|
165
167
|
/>
|
|
166
168
|
)}
|
|
167
169
|
<br />
|
package/src/components/Geo.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
//TODO: Move legends to core
|
|
2
|
-
import { forwardRef, useContext } from 'react'
|
|
2
|
+
import { forwardRef, useContext, useMemo } from 'react'
|
|
3
3
|
import parse from 'html-react-parser'
|
|
4
|
+
import { processMarkupVariables } from '@cdc/core/helpers/markupProcessor'
|
|
4
5
|
|
|
5
6
|
//types
|
|
6
7
|
import { DimensionsType } from '@cdc/core/types/Dimensions'
|
|
@@ -18,12 +19,14 @@ import { GlyphStar, GlyphTriangle, GlyphDiamond, GlyphSquare, GlyphCircle } from
|
|
|
18
19
|
import { Group } from '@visx/group'
|
|
19
20
|
import './index.scss'
|
|
20
21
|
import { type ViewPort } from '@cdc/core/types/ViewPort'
|
|
21
|
-
import { isBelowBreakpoint,
|
|
22
|
+
import { isBelowBreakpoint, isMobileFontViewport } from '@cdc/core/helpers/viewports'
|
|
22
23
|
import { displayDataAsText } from '@cdc/core/helpers/displayDataAsText'
|
|
23
|
-
import { toggleLegendActive } from '
|
|
24
|
+
import { toggleLegendActive } from '../../../helpers/toggleLegendActive'
|
|
24
25
|
import { resetLegendToggles } from '../../../helpers'
|
|
25
26
|
import { MapContext } from '../../../types/MapContext'
|
|
26
27
|
import LegendGroup from './LegendGroup/Legend.Group'
|
|
28
|
+
import { publishAnalyticsEvent } from '@cdc/core/helpers/metrics/helpers'
|
|
29
|
+
import { getVizTitle, getVizSubType } from '@cdc/core/helpers/metrics/utils'
|
|
27
30
|
|
|
28
31
|
const LEGEND_PADDING = 30
|
|
29
32
|
|
|
@@ -32,10 +35,11 @@ type LegendProps = {
|
|
|
32
35
|
dimensions: DimensionsType
|
|
33
36
|
containerWidthPadding: number
|
|
34
37
|
currentViewport: ViewPort
|
|
38
|
+
interactionLabel: string
|
|
35
39
|
}
|
|
36
40
|
|
|
37
41
|
const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
|
|
38
|
-
const { skipId, containerWidthPadding } = props
|
|
42
|
+
const { skipId, containerWidthPadding, interactionLabel } = props
|
|
39
43
|
|
|
40
44
|
const {
|
|
41
45
|
config,
|
|
@@ -43,8 +47,7 @@ const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
|
|
|
43
47
|
dimensions,
|
|
44
48
|
mapId,
|
|
45
49
|
runtimeFilters,
|
|
46
|
-
runtimeLegend
|
|
47
|
-
setRuntimeLegend
|
|
50
|
+
runtimeLegend
|
|
48
51
|
} = useContext<MapContext>(ConfigContext)
|
|
49
52
|
|
|
50
53
|
const dispatch = useContext(MapDispatchContext)
|
|
@@ -59,7 +62,10 @@ const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
|
|
|
59
62
|
|
|
60
63
|
const getFormattedLegendItems = () => {
|
|
61
64
|
try {
|
|
62
|
-
if (!runtimeLegend.items)
|
|
65
|
+
if (!runtimeLegend.items) {
|
|
66
|
+
console.warn('No runtime legend data available')
|
|
67
|
+
return []
|
|
68
|
+
}
|
|
63
69
|
return runtimeLegend.items.map((entry, idx) => {
|
|
64
70
|
const entryMax = displayDataAsText(entry.max, 'primary', config)
|
|
65
71
|
|
|
@@ -94,8 +100,7 @@ const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
|
|
|
94
100
|
label: parse(legendLabel),
|
|
95
101
|
disabled: entry.disabled,
|
|
96
102
|
special: entry.hasOwnProperty('special'),
|
|
97
|
-
value:
|
|
98
|
-
categoryValue: legend.type === 'category' ? entry.value : undefined
|
|
103
|
+
value: [entry.min, entry.max]
|
|
99
104
|
}
|
|
100
105
|
})
|
|
101
106
|
} catch (e) {
|
|
@@ -106,7 +111,7 @@ const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
|
|
|
106
111
|
|
|
107
112
|
const legendList = (patternsOnly = false) => {
|
|
108
113
|
const formattedItems = patternsOnly ? [] : getFormattedLegendItems()
|
|
109
|
-
const patternsOnlyFont =
|
|
114
|
+
const patternsOnlyFont = isMobileFontViewport(viewport) ? '12px' : '14px'
|
|
110
115
|
const hasDisabledItems = formattedItems.some(item => item.disabled)
|
|
111
116
|
let legendItems
|
|
112
117
|
|
|
@@ -119,48 +124,36 @@ const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
|
|
|
119
124
|
return classes.join(' ')
|
|
120
125
|
}
|
|
121
126
|
|
|
122
|
-
const setAccessibleStatus = (message: string) => {
|
|
123
|
-
dispatch({ type: 'SET_ACCESSIBLE_STATUS', payload: message })
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Find the correct runtime index for toggling
|
|
127
|
-
// This is needed because special classes may have been moved to the end
|
|
128
|
-
const findRuntimeIndex = () => {
|
|
129
|
-
if (!runtimeLegend.items) return idx
|
|
130
|
-
|
|
131
|
-
return runtimeLegend.items.findIndex(runtimeItem => {
|
|
132
|
-
if (item.special && runtimeItem.special) {
|
|
133
|
-
// For special classes, match by label (since formatted item label comes from runtime item)
|
|
134
|
-
const runtimeLabel = runtimeItem.label || runtimeItem.value
|
|
135
|
-
const itemLabel = typeof item.label === 'string' ? item.label : item.label?.props?.children || item.label
|
|
136
|
-
return runtimeLabel === itemLabel
|
|
137
|
-
} else if (!item.special && !runtimeItem.special) {
|
|
138
|
-
// For categorical/qualitative items, match by single value
|
|
139
|
-
if (config.legend.type === 'category' && item.categoryValue !== undefined) {
|
|
140
|
-
return runtimeItem.value === item.categoryValue
|
|
141
|
-
}
|
|
142
|
-
// For numeric items, match by min/max values
|
|
143
|
-
return runtimeItem.min === item.value?.[0] && runtimeItem.max === item.value?.[1]
|
|
144
|
-
}
|
|
145
|
-
return false
|
|
146
|
-
})
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
const runtimeIndex = findRuntimeIndex()
|
|
150
|
-
const safeRuntimeIndex = runtimeIndex >= 0 ? runtimeIndex : idx
|
|
151
|
-
|
|
152
127
|
return (
|
|
153
128
|
<li
|
|
154
129
|
className={handleListItemClass()}
|
|
155
130
|
key={idx}
|
|
156
131
|
title={`Legend item ${item.label} - Click to disable`}
|
|
157
|
-
onClick={() =>
|
|
158
|
-
toggleLegendActive(
|
|
159
|
-
|
|
132
|
+
onClick={() => {
|
|
133
|
+
toggleLegendActive(idx, item.label, runtimeLegend, dispatch)
|
|
134
|
+
publishAnalyticsEvent({
|
|
135
|
+
vizType: config.type,
|
|
136
|
+
vizSubType: getVizSubType(config),
|
|
137
|
+
eventType: `map_legend_item_toggled`,
|
|
138
|
+
eventAction: 'click',
|
|
139
|
+
eventLabel: `${interactionLabel}`,
|
|
140
|
+
vizTitle: getVizTitle(config),
|
|
141
|
+
specifics: `mode: isolate, label: ${item.label}`
|
|
142
|
+
})
|
|
143
|
+
}}
|
|
160
144
|
onKeyDown={e => {
|
|
161
145
|
if (e.key === 'Enter') {
|
|
162
146
|
e.preventDefault()
|
|
163
|
-
toggleLegendActive(
|
|
147
|
+
toggleLegendActive(idx, item.label, runtimeLegend, dispatch)
|
|
148
|
+
publishAnalyticsEvent({
|
|
149
|
+
vizType: config.type,
|
|
150
|
+
vizSubType: getVizSubType(config),
|
|
151
|
+
eventType: `map_legend_item_toggled`,
|
|
152
|
+
eventAction: 'keydown',
|
|
153
|
+
eventLabel: `${interactionLabel}`,
|
|
154
|
+
vizTitle: getVizTitle(config),
|
|
155
|
+
specifics: `mode: isolate, label: ${item.label}`
|
|
156
|
+
})
|
|
164
157
|
}
|
|
165
158
|
}}
|
|
166
159
|
tabIndex={0}
|
|
@@ -178,9 +171,9 @@ const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
|
|
|
178
171
|
const { pattern, dataKey, size } = patternData
|
|
179
172
|
let defaultPatternColor = 'black'
|
|
180
173
|
const sizes = {
|
|
181
|
-
small:
|
|
182
|
-
medium:
|
|
183
|
-
large:
|
|
174
|
+
small: 8,
|
|
175
|
+
medium: 10,
|
|
176
|
+
large: 12
|
|
184
177
|
}
|
|
185
178
|
|
|
186
179
|
const legendSize = 16
|
|
@@ -253,7 +246,15 @@ const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
|
|
|
253
246
|
if (e) {
|
|
254
247
|
e.preventDefault()
|
|
255
248
|
}
|
|
256
|
-
|
|
249
|
+
publishAnalyticsEvent({
|
|
250
|
+
vizType: config.type,
|
|
251
|
+
vizSubType: getVizSubType(config),
|
|
252
|
+
eventType: 'map_legend_reset',
|
|
253
|
+
eventAction: 'click',
|
|
254
|
+
eventLabel: interactionLabel,
|
|
255
|
+
vizTitle: getVizTitle(config)
|
|
256
|
+
})
|
|
257
|
+
resetLegendToggles(runtimeLegend, dispatch)
|
|
257
258
|
dispatch({
|
|
258
259
|
type: 'SET_ACCESSIBLE_STATUS',
|
|
259
260
|
payload: 'Legend has been reset, please reference the data table to see updated values.'
|
|
@@ -273,14 +274,17 @@ const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
|
|
|
273
274
|
/>
|
|
274
275
|
)
|
|
275
276
|
|
|
276
|
-
const cityStyleShapes =
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
277
|
+
const cityStyleShapes = useMemo(
|
|
278
|
+
() => ({
|
|
279
|
+
pin: pin,
|
|
280
|
+
circle: <GlyphCircle color='#000' size={150} />,
|
|
281
|
+
square: <GlyphSquare color='#000' size={150} />,
|
|
282
|
+
diamond: <GlyphDiamond color='#000' size={150} />,
|
|
283
|
+
star: <GlyphStar color='#000' size={150} />,
|
|
284
|
+
triangle: <GlyphTriangle color='#000' size={150} />
|
|
285
|
+
}),
|
|
286
|
+
[pin]
|
|
287
|
+
)
|
|
284
288
|
|
|
285
289
|
const shouldRenderLegendList = legendListItems.length > 0 && ['Select Option', ''].includes(config.legend.groupBy)
|
|
286
290
|
|
|
@@ -298,9 +302,29 @@ const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
|
|
|
298
302
|
<section className={legendClasses.section.join(' ') || ''} aria-label='Map Legend'>
|
|
299
303
|
{(legend.title || legend.description || legend.dynamicDescription) && (
|
|
300
304
|
<div className='mb-3'>
|
|
301
|
-
{legend.title &&
|
|
305
|
+
{legend.title && (
|
|
306
|
+
<h3 className={legendClasses.title.join(' ') || ''}>
|
|
307
|
+
{parse(
|
|
308
|
+
config.enableMarkupVariables && config.markupVariables?.length > 0
|
|
309
|
+
? processMarkupVariables(legend.title, config.data || [], config.markupVariables, {
|
|
310
|
+
isEditor: false,
|
|
311
|
+
filters: config.filters || []
|
|
312
|
+
}).processedContent
|
|
313
|
+
: legend.title
|
|
314
|
+
)}
|
|
315
|
+
</h3>
|
|
316
|
+
)}
|
|
302
317
|
{legend.dynamicDescription === false && legend.description && (
|
|
303
|
-
<p className={legendClasses.description.join(' ') || ''}>
|
|
318
|
+
<p className={legendClasses.description.join(' ') || ''}>
|
|
319
|
+
{parse(
|
|
320
|
+
config.enableMarkupVariables && config.markupVariables?.length > 0
|
|
321
|
+
? processMarkupVariables(legend.description, config.data || [], config.markupVariables, {
|
|
322
|
+
isEditor: false,
|
|
323
|
+
filters: config.filters || []
|
|
324
|
+
}).processedContent
|
|
325
|
+
: legend.description
|
|
326
|
+
)}
|
|
327
|
+
</p>
|
|
304
328
|
)}
|
|
305
329
|
{legend.dynamicDescription === true &&
|
|
306
330
|
runtimeFilters.map((filter, idx) => {
|
|
@@ -343,41 +367,41 @@ const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
|
|
|
343
367
|
|
|
344
368
|
{((config.visual.additionalCityStyles && config.visual.additionalCityStyles.some(c => c.label)) ||
|
|
345
369
|
config.visual.cityStyleLabel) && (
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
370
|
+
<>
|
|
371
|
+
<hr />
|
|
372
|
+
<div className={legendClasses.div.join(' ') || ''}>
|
|
373
|
+
{config.visual.cityStyleLabel && (
|
|
374
|
+
<div>
|
|
375
|
+
<svg>
|
|
376
|
+
<Group
|
|
377
|
+
top={
|
|
378
|
+
config.visual.cityStyle === 'pin' ? 19 : config.visual.cityStyle === 'triangle' ? 13 : 11
|
|
379
|
+
}
|
|
380
|
+
left={10}
|
|
381
|
+
>
|
|
382
|
+
{cityStyleShapes[config.visual.cityStyle.toLowerCase()]}
|
|
383
|
+
</Group>
|
|
384
|
+
</svg>
|
|
385
|
+
<p>{config.visual.cityStyleLabel}</p>
|
|
386
|
+
</div>
|
|
387
|
+
)}
|
|
388
|
+
|
|
389
|
+
{config.visual.additionalCityStyles.map(
|
|
390
|
+
({ shape, label }, index) =>
|
|
391
|
+
label && (
|
|
392
|
+
<div key={`additional-city-style-${index}-${shape}`}>
|
|
393
|
+
<svg>
|
|
394
|
+
<Group top={shape === 'Pin' ? 19 : shape === 'Triangle' ? 13 : 11} left={10}>
|
|
395
|
+
{cityStyleShapes[shape.toLowerCase()]}
|
|
396
|
+
</Group>
|
|
397
|
+
</svg>
|
|
398
|
+
<p>{label}</p>
|
|
399
|
+
</div>
|
|
400
|
+
)
|
|
401
|
+
)}
|
|
402
|
+
</div>
|
|
403
|
+
</>
|
|
404
|
+
)}
|
|
381
405
|
{runtimeLegend.disabledAmt > 0 && (
|
|
382
406
|
<Button className={legendClasses.showAllButton.join(' ')} onClick={handleReset}>
|
|
383
407
|
Show All
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useContext, useMemo } from 'react'
|
|
2
2
|
import './legend.group.css'
|
|
3
3
|
import LegendShape from '@cdc/core/components/LegendShape'
|
|
4
|
-
import { toggleLegendActive } from '
|
|
4
|
+
import { toggleLegendActive } from '../../../../helpers/toggleLegendActive'
|
|
5
5
|
import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
6
6
|
import ConfigContext, { MapDispatchContext } from '../../../../context'
|
|
7
7
|
|
|
@@ -17,7 +17,7 @@ interface GroupedData {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
const LegendGroup = ({ legendItems }) => {
|
|
20
|
-
const { runtimeLegend,
|
|
20
|
+
const { runtimeLegend, config } = useContext(ConfigContext)
|
|
21
21
|
const dispatch = useContext(MapDispatchContext)
|
|
22
22
|
const groupLegendItems = (items: LegendItem[], data: object[], groupByKey: string): GroupedData => {
|
|
23
23
|
if (!groupByKey || !data || !items) return {}
|
|
@@ -59,10 +59,13 @@ const LegendGroup = ({ legendItems }) => {
|
|
|
59
59
|
const wasDisabled = runtimeLegend.items.find(i => i.value === item.label)?.disabled
|
|
60
60
|
const delta = wasDisabled ? -1 : 1
|
|
61
61
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
62
|
+
dispatch({
|
|
63
|
+
type: 'SET_RUNTIME_LEGEND',
|
|
64
|
+
payload: {
|
|
65
|
+
...runtimeLegend,
|
|
66
|
+
items: newItems,
|
|
67
|
+
disabledAmt: (runtimeLegend.disabledAmt ?? 0) + delta
|
|
68
|
+
}
|
|
66
69
|
})
|
|
67
70
|
const message = `${wasDisabled ? 'Enabled' : 'Disabled'} legend item ${
|
|
68
71
|
item.label
|
|
@@ -109,7 +112,7 @@ const LegendGroup = ({ legendItems }) => {
|
|
|
109
112
|
onKeyDown={e => {
|
|
110
113
|
if (e.key === 'Enter' || e.key === ' ') {
|
|
111
114
|
e.preventDefault()
|
|
112
|
-
toggleLegendActive(index, item.label, runtimeLegend,
|
|
115
|
+
toggleLegendActive(index, item.label, runtimeLegend, dispatch)
|
|
113
116
|
}
|
|
114
117
|
}}
|
|
115
118
|
>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import Modal from './Modal'
|
|
3
|
+
import UsaMap from './UsaMap'
|
|
4
|
+
import WorldMap from './WorldMap'
|
|
5
|
+
import GoogleMap from './GoogleMap'
|
|
6
|
+
import { MapConfig } from '../types/MapConfig'
|
|
7
|
+
import { LOGO_MAX_WIDTH } from '../helpers/constants'
|
|
8
|
+
|
|
9
|
+
interface MapContainerProps {
|
|
10
|
+
config: MapConfig
|
|
11
|
+
modal: any
|
|
12
|
+
currentViewport: string
|
|
13
|
+
geoType: string
|
|
14
|
+
general: any
|
|
15
|
+
logo: string
|
|
16
|
+
mapSvgRef: React.RefObject<HTMLElement>
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const MapContainer: React.FC<MapContainerProps> = ({
|
|
20
|
+
config,
|
|
21
|
+
modal,
|
|
22
|
+
currentViewport,
|
|
23
|
+
geoType,
|
|
24
|
+
general,
|
|
25
|
+
logo,
|
|
26
|
+
mapSvgRef
|
|
27
|
+
}) => {
|
|
28
|
+
return (
|
|
29
|
+
<section className='outline-none geography-container w-100 position-relative' ref={mapSvgRef} tabIndex={0}>
|
|
30
|
+
{currentViewport && (
|
|
31
|
+
<>
|
|
32
|
+
{modal && <Modal />}
|
|
33
|
+
{'single-state' === geoType && <UsaMap.SingleState />}
|
|
34
|
+
{'us' === geoType && 'us-geocode' !== config.general.type && <UsaMap.State />}
|
|
35
|
+
{'us-region' === geoType && <UsaMap.Region />}
|
|
36
|
+
{'us-county' === geoType && <UsaMap.County />}
|
|
37
|
+
{'world' === geoType && <WorldMap />}
|
|
38
|
+
{'google-map' === geoType && <GoogleMap />}
|
|
39
|
+
{
|
|
40
|
+
/* logo is handled in UsaMap.State when applicable */
|
|
41
|
+
// prettier-ignore
|
|
42
|
+
'data' === general.type && logo && ('us' !== geoType || 'us-geocode' === general.type) && (
|
|
43
|
+
<img src={logo} alt='' className='map-logo' style={{ maxWidth: LOGO_MAX_WIDTH }} />
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
</>
|
|
47
|
+
)}
|
|
48
|
+
</section>
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export default MapContainer
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import MediaControls from '@cdc/core/components/MediaControls'
|
|
3
|
+
import { MapConfig } from '../types/MapConfig'
|
|
4
|
+
|
|
5
|
+
interface MapControlsProps {
|
|
6
|
+
config: MapConfig
|
|
7
|
+
imageId: string
|
|
8
|
+
interactionLabel: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const MapControls: React.FC<MapControlsProps> = ({ config, imageId, interactionLabel }) => {
|
|
12
|
+
const { showDownloadImgButton, showDownloadPdfButton } = config.general
|
|
13
|
+
|
|
14
|
+
if (!showDownloadImgButton && !showDownloadPdfButton) {
|
|
15
|
+
return null
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<MediaControls.Section classes={['download-buttons']}>
|
|
20
|
+
{showDownloadImgButton && (
|
|
21
|
+
<MediaControls.Button
|
|
22
|
+
text='Download Image'
|
|
23
|
+
title='Download Chart as Image'
|
|
24
|
+
type='image'
|
|
25
|
+
state={config}
|
|
26
|
+
elementToCapture={imageId}
|
|
27
|
+
interactionLabel={interactionLabel}
|
|
28
|
+
/>
|
|
29
|
+
)}
|
|
30
|
+
{showDownloadPdfButton && (
|
|
31
|
+
<MediaControls.Button
|
|
32
|
+
text='Download PDF'
|
|
33
|
+
title='Download Chart as PDF'
|
|
34
|
+
type='pdf'
|
|
35
|
+
state={config}
|
|
36
|
+
interactionLabel={interactionLabel}
|
|
37
|
+
elementToCapture={imageId}
|
|
38
|
+
/>
|
|
39
|
+
)}
|
|
40
|
+
</MediaControls.Section>
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export default MapControls
|
package/src/components/Modal.tsx
CHANGED
|
@@ -5,19 +5,13 @@ import useApplyTooltipsToGeo from '../hooks/useApplyTooltipsToGeo'
|
|
|
5
5
|
import { MapContext } from '../types/MapContext'
|
|
6
6
|
|
|
7
7
|
const Modal = () => {
|
|
8
|
-
const { content,
|
|
9
|
-
const { capitalizeLabels } = config.tooltips
|
|
8
|
+
const { content, currentViewport: viewport } = useContext<MapContext>(ConfigContext)
|
|
10
9
|
const { applyTooltipsToGeo } = useApplyTooltipsToGeo()
|
|
11
10
|
const tooltip = applyTooltipsToGeo(content.geoName, content.keyedData, 'jsx')
|
|
12
11
|
const dispatch = useContext(MapDispatchContext)
|
|
13
12
|
|
|
14
13
|
return (
|
|
15
|
-
<section
|
|
16
|
-
className={
|
|
17
|
-
capitalizeLabels ? 'modal-content tooltip capitalize ' + viewport : 'modal-content tooltip ' + viewport
|
|
18
|
-
}
|
|
19
|
-
aria-hidden='true'
|
|
20
|
-
>
|
|
14
|
+
<section className={'modal-content tooltip ' + viewport} aria-hidden='true'>
|
|
21
15
|
<div className='content'>{tooltip}</div>
|
|
22
16
|
<Icon
|
|
23
17
|
display='close'
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import React, { useContext, useEffect, useState } from 'react'
|
|
2
2
|
import ConfigContext from '../context'
|
|
3
|
+
import { publishAnalyticsEvent } from '@cdc/core/helpers/metrics/helpers'
|
|
4
|
+
import { getVizTitle, getVizSubType } from '@cdc/core/helpers/metrics/utils'
|
|
3
5
|
|
|
4
6
|
const NavigationMenu = ({ data, navigationHandler, options, columns, displayGeoName, mapTabbingID }) => {
|
|
5
|
-
const {
|
|
7
|
+
const { interactionLabel, config } = useContext(ConfigContext)
|
|
6
8
|
const [activeGeo, setActiveGeo] = useState('')
|
|
7
9
|
const [dropdownItems, setDropdownItems] = useState({})
|
|
8
10
|
|
|
@@ -11,6 +13,16 @@ const NavigationMenu = ({ data, navigationHandler, options, columns, displayGeoN
|
|
|
11
13
|
if (activeGeo !== '') {
|
|
12
14
|
const urlString = data[dropdownItems[activeGeo]][columns.navigate.name]
|
|
13
15
|
|
|
16
|
+
publishAnalyticsEvent({
|
|
17
|
+
vizType: config.type,
|
|
18
|
+
vizSubType: getVizSubType(config),
|
|
19
|
+
eventType: `map_navigation_menu`,
|
|
20
|
+
eventAction: 'submit',
|
|
21
|
+
eventLabel: `${interactionLabel}`,
|
|
22
|
+
vizTitle: getVizTitle(config),
|
|
23
|
+
specifics: `url: ${urlString}, activeGeo: ${activeGeo}`
|
|
24
|
+
})
|
|
25
|
+
|
|
14
26
|
navigationHandler(urlString)
|
|
15
27
|
}
|
|
16
28
|
}
|