@cdc/map 4.24.7 → 4.24.9
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/dist/cdcmap.js +40720 -38422
- package/examples/county-year.csv +10 -0
- package/examples/default-geocode.json +44 -10
- package/examples/default-patterns.json +0 -2
- package/examples/default-single-state.json +279 -108
- package/examples/map-issue-3.json +646 -0
- package/examples/single-state-filter.json +153 -0
- package/index.html +9 -6
- package/package.json +3 -3
- package/src/CdcMap.tsx +322 -126
- package/src/_stories/CdcMap.stories.tsx +7 -0
- package/src/_stories/_mock/DEV-8942.json +270 -0
- package/src/components/Annotation/AnnotationDropdown.tsx +1 -0
- package/src/components/{BubbleList.jsx → BubbleList.tsx} +1 -1
- package/src/components/{CityList.jsx → CityList.tsx} +28 -2
- package/src/components/{DataTable.jsx → DataTable.tsx} +2 -2
- package/src/components/EditorPanel/components/EditorPanel.tsx +647 -127
- package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +0 -22
- package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +61 -11
- package/src/components/Legend/components/Legend.tsx +125 -36
- package/src/components/Legend/components/index.scss +42 -42
- package/src/components/Modal.tsx +25 -0
- package/src/components/UsaMap/components/SingleState/SingleState.CountyOutput.tsx +74 -0
- package/src/components/UsaMap/components/SingleState/SingleState.StateOutput.tsx +29 -0
- package/src/components/UsaMap/components/SingleState/index.tsx +9 -0
- package/src/components/UsaMap/components/UsaMap.County.tsx +84 -33
- package/src/components/UsaMap/components/UsaMap.SingleState.tsx +173 -206
- package/src/components/UsaMap/components/UsaMap.State.tsx +161 -26
- package/src/components/UsaMap/data/us-extended-geography.json +1 -0
- package/src/components/UsaMap/helpers/map.ts +111 -0
- package/src/components/WorldMap/WorldMap.tsx +17 -32
- package/src/components/ZoomControls.tsx +41 -0
- package/src/data/initial-state.js +7 -1
- package/src/data/supported-geos.js +15 -4
- package/src/helpers/generateRuntimeLegendHash.ts +2 -2
- package/src/hooks/useStateZoom.tsx +157 -0
- package/src/hooks/{useZoomPan.js → useZoomPan.ts} +6 -5
- package/src/scss/editor-panel.scss +0 -4
- package/src/scss/main.scss +23 -1
- package/src/scss/map.scss +8 -0
- package/src/types/MapConfig.ts +9 -1
- package/src/types/MapContext.ts +14 -2
- package/src/components/Modal.jsx +0 -22
- /package/src/components/{Geo.jsx → Geo.tsx} +0 -0
- /package/src/components/{NavigationMenu.jsx → NavigationMenu.tsx} +0 -0
- /package/src/components/{ZoomableGroup.jsx → ZoomableGroup.tsx} +0 -0
|
@@ -101,8 +101,6 @@ const PanelAnnotate: React.FC = props => {
|
|
|
101
101
|
return (
|
|
102
102
|
<Accordion>
|
|
103
103
|
<Accordion.Section title={props.name}>
|
|
104
|
-
<p>Dragging state: {isDraggingAnnotation ? 'Dragging' : 'Not dragging'}</p>
|
|
105
|
-
|
|
106
104
|
<label>
|
|
107
105
|
Show Annotation Dropdown
|
|
108
106
|
<input
|
|
@@ -225,26 +223,6 @@ const PanelAnnotate: React.FC = props => {
|
|
|
225
223
|
}}
|
|
226
224
|
/>
|
|
227
225
|
</label>
|
|
228
|
-
<label>
|
|
229
|
-
Associated Series:
|
|
230
|
-
<select
|
|
231
|
-
onChange={e => {
|
|
232
|
-
const updatedAnnotations = [...config?.annotations]
|
|
233
|
-
updatedAnnotations[index].seriesKey = e.target.value
|
|
234
|
-
updateConfig({
|
|
235
|
-
...config,
|
|
236
|
-
annotations: updatedAnnotations
|
|
237
|
-
})
|
|
238
|
-
}}
|
|
239
|
-
>
|
|
240
|
-
<option key='none' value='none'>
|
|
241
|
-
None
|
|
242
|
-
</option>
|
|
243
|
-
{getColumns(false).map((column, columnIndex) => {
|
|
244
|
-
return <option>{column}</option>
|
|
245
|
-
})}
|
|
246
|
-
</select>
|
|
247
|
-
</label>
|
|
248
226
|
|
|
249
227
|
<label>
|
|
250
228
|
Connection Type:
|
|
@@ -6,13 +6,19 @@ import Button from '@cdc/core/components/elements/Button'
|
|
|
6
6
|
import Tooltip from '@cdc/core/components/ui/Tooltip'
|
|
7
7
|
import Icon from '@cdc/core/components/ui/Icon'
|
|
8
8
|
import './Panel.PatternSettings-style.css'
|
|
9
|
+
import Alert from '@cdc/core/components/Alert'
|
|
10
|
+
|
|
11
|
+
// topojson helpers for checking color contrasts
|
|
12
|
+
import { feature } from 'topojson-client'
|
|
13
|
+
import { checkColorContrast, getContrastColor, getColorContrast } from '@cdc/core/helpers/cove/accessibility'
|
|
14
|
+
import topoJSON from '../../../UsaMap/data/us-topo.json'
|
|
9
15
|
|
|
10
16
|
type PanelProps = {
|
|
11
17
|
name: string
|
|
12
18
|
}
|
|
13
19
|
|
|
14
20
|
const PatternSettings = ({ name }: PanelProps) => {
|
|
15
|
-
const { state, setState } = useContext<MapContext>(ConfigContext)
|
|
21
|
+
const { state, setState, applyLegendToRow, runtimeData } = useContext<MapContext>(ConfigContext)
|
|
16
22
|
const defaultPattern = 'circles'
|
|
17
23
|
const patternTypes = ['circles', 'waves', 'lines']
|
|
18
24
|
|
|
@@ -24,7 +30,7 @@ const PatternSettings = ({ name }: PanelProps) => {
|
|
|
24
30
|
/** Updates the map config with a new pattern item */
|
|
25
31
|
const handleAddGeoPattern = () => {
|
|
26
32
|
let patterns = [...state.map.patterns]
|
|
27
|
-
patterns.push({ dataKey: '', pattern: defaultPattern })
|
|
33
|
+
patterns.push({ dataKey: '', pattern: defaultPattern, contrastCheck: true })
|
|
28
34
|
setState({
|
|
29
35
|
...state,
|
|
30
36
|
map: {
|
|
@@ -36,16 +42,56 @@ const PatternSettings = ({ name }: PanelProps) => {
|
|
|
36
42
|
|
|
37
43
|
/** Updates the map pattern at a given index */
|
|
38
44
|
const handleUpdateGeoPattern = (value: string, index: number, keyToUpdate: 'dataKey' | 'pattern' | 'dataValue' | 'size' | 'label' | 'color') => {
|
|
45
|
+
const { features: unitedStates } = feature(topoJSON, topoJSON.objects.states)
|
|
39
46
|
const updatedPatterns = [...state.map.patterns]
|
|
47
|
+
|
|
48
|
+
// Update the specific pattern with the new value
|
|
40
49
|
updatedPatterns[index] = { ...updatedPatterns[index], [keyToUpdate]: value }
|
|
41
50
|
|
|
42
|
-
|
|
43
|
-
|
|
51
|
+
// Iterate over each state feature
|
|
52
|
+
unitedStates.forEach(geo => {
|
|
53
|
+
const geoKey = geo.properties.iso
|
|
54
|
+
if (!geoKey || !runtimeData) return
|
|
55
|
+
|
|
56
|
+
const legendColors = runtimeData[geoKey] ? applyLegendToRow(runtimeData[geoKey]) : undefined
|
|
57
|
+
const geoData = runtimeData[geoKey]
|
|
58
|
+
if (!geoData) return
|
|
59
|
+
|
|
60
|
+
// Iterate over each pattern
|
|
61
|
+
state.map.patterns.forEach((patternData, patternIndex) => {
|
|
62
|
+
const hasMatchingValues = patternData.dataValue === geoData[patternData.dataKey]
|
|
63
|
+
if (!hasMatchingValues) return
|
|
64
|
+
|
|
65
|
+
const currentFill = legendColors[0]
|
|
66
|
+
const patternColor = keyToUpdate === 'color' && value !== '' ? value : getContrastColor('#000', currentFill)
|
|
67
|
+
const contrastCheck = checkColorContrast(currentFill, patternColor)
|
|
68
|
+
|
|
69
|
+
// Log a warning if the contrast check fails
|
|
70
|
+
if (!contrastCheck) {
|
|
71
|
+
console.warn(`COVE: pattern contrast check failed on ${geoData?.[state.columns.geo.name]} for ${patternData.dataKey} with:
|
|
72
|
+
pattern color: ${patternColor}
|
|
73
|
+
contrast: ${getColorContrast(currentFill, patternColor)}
|
|
74
|
+
`)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
updatedPatterns[index] = { ...updatedPatterns[index], [keyToUpdate]: value, contrastCheck }
|
|
78
|
+
})
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
const editorErrorMessage = updatedPatterns.some(pattern => pattern.contrastCheck === false) ? 'One or more patterns do not pass the WCAG 2.1 contrast ratio of 3:1.' : ''
|
|
82
|
+
|
|
83
|
+
// Update the state with the new patterns and error message
|
|
84
|
+
setState(prevState => ({
|
|
85
|
+
...prevState,
|
|
44
86
|
map: {
|
|
45
|
-
...
|
|
87
|
+
...prevState.map,
|
|
46
88
|
patterns: updatedPatterns
|
|
89
|
+
},
|
|
90
|
+
runtime: {
|
|
91
|
+
...prevState.runtime,
|
|
92
|
+
editorErrorMessage
|
|
47
93
|
}
|
|
48
|
-
})
|
|
94
|
+
}))
|
|
49
95
|
}
|
|
50
96
|
|
|
51
97
|
const handleRemovePattern = index => {
|
|
@@ -60,12 +106,19 @@ const PatternSettings = ({ name }: PanelProps) => {
|
|
|
60
106
|
})
|
|
61
107
|
}
|
|
62
108
|
|
|
109
|
+
const checkPatternContrasts = () => {
|
|
110
|
+
return state.map.patterns.every(pattern => pattern.contrastCheck !== false)
|
|
111
|
+
}
|
|
112
|
+
|
|
63
113
|
return (
|
|
64
114
|
<AccordionItem>
|
|
65
115
|
<AccordionItemHeading>
|
|
66
116
|
<AccordionItemButton>{name}</AccordionItemButton>
|
|
67
117
|
</AccordionItemHeading>
|
|
68
118
|
<AccordionItemPanel>
|
|
119
|
+
{patterns.length > 0 && <Alert type={checkPatternContrasts() ? 'success' : 'danger'} message='Pattern colors must comply with <br /> <a href="https://www.w3.org/TR/WCAG21/">WCAG 2.1</a> 3:1 contrast ratio.' />}
|
|
120
|
+
<br />
|
|
121
|
+
|
|
69
122
|
{patterns &&
|
|
70
123
|
patterns.map((pattern, patternIndex) => {
|
|
71
124
|
const dataValueOptions = [...new Set(data?.map(d => d?.[pattern?.dataKey]))]
|
|
@@ -77,13 +130,14 @@ const PatternSettings = ({ name }: PanelProps) => {
|
|
|
77
130
|
dataKeyOptions.sort()
|
|
78
131
|
|
|
79
132
|
return (
|
|
80
|
-
<Accordion allowZeroExpanded>
|
|
133
|
+
<Accordion allowZeroExpanded key={`accordion-pattern--${patternIndex}`}>
|
|
81
134
|
<AccordionItem>
|
|
82
135
|
<AccordionItemHeading>
|
|
83
136
|
<AccordionItemButton>{pattern.dataKey ? `${pattern.dataKey}: ${pattern.dataValue ?? 'No Value'}` : 'Select Column'}</AccordionItemButton>
|
|
84
137
|
</AccordionItemHeading>
|
|
85
138
|
<AccordionItemPanel>
|
|
86
139
|
<>
|
|
140
|
+
{pattern.contrastCheck ?? true ? <Alert type='success' message='This pattern passes contrast checks' /> : <Alert type='danger' message='Error: <a href="https://webaim.org/resources/contrastchecker/" target="_blank"> Review Color Contrast</a>' />}{' '}
|
|
87
141
|
<label htmlFor={`pattern-dataKey--${patternIndex}`}>Data Key:</label>
|
|
88
142
|
<select id={`pattern-dataKey--${patternIndex}`} value={pattern.dataKey !== '' ? pattern.dataKey : 'Select'} onChange={e => handleUpdateGeoPattern(e.target.value, patternIndex, 'dataKey')}>
|
|
89
143
|
{/* TODO: sort these? */}
|
|
@@ -95,17 +149,14 @@ const PatternSettings = ({ name }: PanelProps) => {
|
|
|
95
149
|
)
|
|
96
150
|
})}
|
|
97
151
|
</select>
|
|
98
|
-
|
|
99
152
|
<label htmlFor={`pattern-dataValue--${patternIndex}`}>
|
|
100
153
|
Data Value:
|
|
101
154
|
<input type='text' onChange={e => handleUpdateGeoPattern(e.target.value, patternIndex, 'dataValue')} id={`pattern-dataValue--${patternIndex}`} value={pattern.dataValue === '' ? '' : pattern.dataValue} />
|
|
102
155
|
</label>
|
|
103
|
-
|
|
104
156
|
<label htmlFor={`pattern-label--${patternIndex}`}>
|
|
105
157
|
Label (optional):
|
|
106
158
|
<input type='text' onChange={e => handleUpdateGeoPattern(e.target.value, patternIndex, 'label')} id={`pattern-dataValue--${patternIndex}`} value={pattern.label === '' ? '' : pattern.label} />
|
|
107
159
|
</label>
|
|
108
|
-
|
|
109
160
|
<label htmlFor={`pattern-type--${patternIndex}`}>Pattern Type:</label>
|
|
110
161
|
<select id={`pattern-type--${patternIndex}`} value={pattern?.pattern} onChange={e => handleUpdateGeoPattern(e.target.value, patternIndex, 'pattern')}>
|
|
111
162
|
{patternTypes.map((patternName, index) => (
|
|
@@ -114,7 +165,6 @@ const PatternSettings = ({ name }: PanelProps) => {
|
|
|
114
165
|
</option>
|
|
115
166
|
))}
|
|
116
167
|
</select>
|
|
117
|
-
|
|
118
168
|
<label htmlFor={`pattern-size--${patternIndex}`}>Pattern Size:</label>
|
|
119
169
|
<select id={`pattern-size--${patternIndex}`} value={pattern?.size} onChange={e => handleUpdateGeoPattern(e.target.value, patternIndex, 'size')}>
|
|
120
170
|
{['small', 'medium', 'large'].map((size, index) => (
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
//TODO: Move legends to core
|
|
2
|
-
import { forwardRef, useContext } from 'react'
|
|
2
|
+
import { forwardRef, useContext, useId } from 'react'
|
|
3
3
|
import parse from 'html-react-parser'
|
|
4
4
|
|
|
5
|
+
//types
|
|
6
|
+
import { DimensionsType } from '@cdc/core/types/Dimensions'
|
|
7
|
+
|
|
5
8
|
import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
6
|
-
import
|
|
9
|
+
import LegendShape from '@cdc/core/components/LegendShape'
|
|
10
|
+
import LegendGradient from '@cdc/core/components/Legend/Legend.Gradient'
|
|
7
11
|
import LegendItemHex from './LegendItem.Hex'
|
|
8
12
|
import Button from '@cdc/core/components/elements/Button'
|
|
9
13
|
|
|
@@ -11,18 +15,21 @@ import useDataVizClasses from '@cdc/core/helpers/useDataVizClasses'
|
|
|
11
15
|
import ConfigContext from '../../../context'
|
|
12
16
|
import { PatternLines, PatternCircles, PatternWaves } from '@visx/pattern'
|
|
13
17
|
import { GlyphStar, GlyphTriangle, GlyphDiamond, GlyphSquare, GlyphCircle } from '@visx/glyph'
|
|
18
|
+
import { type ViewportSize } from '../../../types/MapConfig'
|
|
14
19
|
import { Group } from '@visx/group'
|
|
15
20
|
import './index.scss'
|
|
16
21
|
|
|
17
22
|
type LegendProps = {
|
|
18
23
|
skipId: string
|
|
24
|
+
currentViewport: ViewportSize
|
|
25
|
+
dimensions: DimensionsType
|
|
19
26
|
}
|
|
20
27
|
|
|
21
|
-
const Legend = forwardRef((props, ref) => {
|
|
22
|
-
const { skipId } = props
|
|
28
|
+
const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
|
|
29
|
+
const { skipId, currentViewport, dimensions } = props
|
|
23
30
|
|
|
24
|
-
// prettier-ignore
|
|
25
31
|
const {
|
|
32
|
+
// prettier-ignore
|
|
26
33
|
displayDataAsText,
|
|
27
34
|
resetLegendToggles,
|
|
28
35
|
runtimeFilters,
|
|
@@ -31,6 +38,8 @@ const Legend = forwardRef((props, ref) => {
|
|
|
31
38
|
setRuntimeLegend,
|
|
32
39
|
state,
|
|
33
40
|
viewport,
|
|
41
|
+
getTextWidth,
|
|
42
|
+
mapId
|
|
34
43
|
} = useContext(ConfigContext)
|
|
35
44
|
|
|
36
45
|
const { legend } = state
|
|
@@ -51,17 +60,15 @@ const Legend = forwardRef((props, ref) => {
|
|
|
51
60
|
|
|
52
61
|
setRuntimeLegend(newLegend)
|
|
53
62
|
|
|
54
|
-
setAccessibleStatus(
|
|
63
|
+
setAccessibleStatus(
|
|
64
|
+
`Disabled legend item ${legendLabel ?? ''}. Please reference the data table to see updated values.`
|
|
65
|
+
)
|
|
55
66
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
let legendItems
|
|
59
|
-
|
|
60
|
-
legendItems = runtimeLegend.map((entry, idx) => {
|
|
67
|
+
const getFormattedLegendItems = () => {
|
|
68
|
+
return runtimeLegend.map((entry, idx) => {
|
|
61
69
|
const entryMax = displayDataAsText(entry.max, 'primary')
|
|
62
70
|
|
|
63
71
|
const entryMin = displayDataAsText(entry.min, 'primary')
|
|
64
|
-
|
|
65
72
|
let formattedText = `${entryMin}${entryMax !== entryMin ? ` - ${entryMax}` : ''}`
|
|
66
73
|
|
|
67
74
|
// If interval, add some formatting
|
|
@@ -69,8 +76,6 @@ const Legend = forwardRef((props, ref) => {
|
|
|
69
76
|
formattedText = `${entryMin} - < ${entryMax}`
|
|
70
77
|
}
|
|
71
78
|
|
|
72
|
-
const { disabled } = entry
|
|
73
|
-
|
|
74
79
|
if (legend.type === 'category') {
|
|
75
80
|
formattedText = displayDataAsText(entry.value, 'primary')
|
|
76
81
|
}
|
|
@@ -85,31 +90,49 @@ const Legend = forwardRef((props, ref) => {
|
|
|
85
90
|
legendLabel = entry.label || entry.value
|
|
86
91
|
}
|
|
87
92
|
|
|
93
|
+
return {
|
|
94
|
+
color: entry.color,
|
|
95
|
+
label: legendLabel,
|
|
96
|
+
disabled: entry.disabled,
|
|
97
|
+
special: entry.hasOwnProperty('special'),
|
|
98
|
+
value: [entry.min, entry.max]
|
|
99
|
+
}
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const legendList = () => {
|
|
104
|
+
const formattedItems = getFormattedLegendItems()
|
|
105
|
+
let legendItems
|
|
106
|
+
|
|
107
|
+
legendItems = formattedItems.map((item, idx) => {
|
|
88
108
|
const handleListItemClass = () => {
|
|
89
109
|
let classes = ['legend-container__li']
|
|
90
|
-
if (disabled) classes.push('legend-container__li--disabled')
|
|
91
|
-
if (
|
|
92
|
-
return classes
|
|
110
|
+
if (item.disabled) classes.push('legend-container__li--disabled')
|
|
111
|
+
if (item.special) classes.push('legend-container__li--special-class')
|
|
112
|
+
return classes.join(' ')
|
|
93
113
|
}
|
|
94
114
|
|
|
95
115
|
return (
|
|
96
116
|
// eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-noninteractive-element-interactions
|
|
97
117
|
<li
|
|
98
|
-
className={handleListItemClass()
|
|
118
|
+
className={handleListItemClass()}
|
|
99
119
|
key={idx}
|
|
100
|
-
title={`Legend item ${
|
|
101
|
-
onClick={() =>
|
|
102
|
-
toggleLegendActive(idx, legendLabel)
|
|
103
|
-
}}
|
|
120
|
+
title={`Legend item ${item.label} - Click to disable`}
|
|
121
|
+
onClick={() => toggleLegendActive(idx, item.label)}
|
|
104
122
|
onKeyDown={e => {
|
|
105
123
|
if (e.key === 'Enter') {
|
|
106
124
|
e.preventDefault()
|
|
107
|
-
toggleLegendActive(idx,
|
|
125
|
+
toggleLegendActive(idx, item.label)
|
|
108
126
|
}
|
|
109
127
|
}}
|
|
110
128
|
tabIndex={0}
|
|
111
129
|
>
|
|
112
|
-
<
|
|
130
|
+
<LegendShape
|
|
131
|
+
shape={state.legend.style === 'boxes' ? 'square' : 'circle'}
|
|
132
|
+
viewport={viewport}
|
|
133
|
+
fill={item.color}
|
|
134
|
+
/>
|
|
135
|
+
<span>{item.label}</span>
|
|
113
136
|
</li>
|
|
114
137
|
)
|
|
115
138
|
})
|
|
@@ -129,13 +152,48 @@ const Legend = forwardRef((props, ref) => {
|
|
|
129
152
|
|
|
130
153
|
legendItems.push(
|
|
131
154
|
<>
|
|
132
|
-
<li
|
|
155
|
+
<li
|
|
156
|
+
className={`legend-container__li legend-container__li--geo-pattern`}
|
|
157
|
+
aria-label='You are on a pattern button. We dont support toggling patterns on this legend at the moment, but provide the area as being focusable for congruity.'
|
|
158
|
+
tabIndex={0}
|
|
159
|
+
>
|
|
133
160
|
<span className='legend-item' style={{ border: 'unset' }}>
|
|
134
161
|
<svg width={legendSize} height={legendSize}>
|
|
135
|
-
{pattern === 'waves' &&
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
162
|
+
{pattern === 'waves' && (
|
|
163
|
+
<PatternWaves
|
|
164
|
+
id={`${mapId}--${dataKey}--${patternDataIndex}`}
|
|
165
|
+
height={sizes[size] ?? 10}
|
|
166
|
+
width={sizes[size] ?? 10}
|
|
167
|
+
fill={defaultPatternColor}
|
|
168
|
+
/>
|
|
169
|
+
)}
|
|
170
|
+
{pattern === 'circles' && (
|
|
171
|
+
<PatternCircles
|
|
172
|
+
id={`${mapId}--${dataKey}--${patternDataIndex}`}
|
|
173
|
+
height={sizes[size] ?? 10}
|
|
174
|
+
width={sizes[size] ?? 10}
|
|
175
|
+
fill={defaultPatternColor}
|
|
176
|
+
/>
|
|
177
|
+
)}
|
|
178
|
+
{pattern === 'lines' && (
|
|
179
|
+
<PatternLines
|
|
180
|
+
id={`${mapId}--${dataKey}--${patternDataIndex}`}
|
|
181
|
+
height={sizes[size] ?? 6}
|
|
182
|
+
width={sizes[size] ?? 10}
|
|
183
|
+
stroke={defaultPatternColor}
|
|
184
|
+
strokeWidth={2}
|
|
185
|
+
orientation={['diagonalRightToLeft']}
|
|
186
|
+
/>
|
|
187
|
+
)}
|
|
188
|
+
<circle
|
|
189
|
+
id={dataKey}
|
|
190
|
+
fill={`url(#${mapId}--${dataKey}--${patternDataIndex})`}
|
|
191
|
+
r={legendSize / 2}
|
|
192
|
+
cx={legendSize / 2}
|
|
193
|
+
cy={legendSize / 2}
|
|
194
|
+
stroke='#0000004d'
|
|
195
|
+
strokeWidth={1}
|
|
196
|
+
/>
|
|
139
197
|
</svg>
|
|
140
198
|
</span>
|
|
141
199
|
<p style={{ lineHeight: '22.4px' }}>{patternData.label || patternData.dataValue || ''}</p>
|
|
@@ -147,7 +205,6 @@ const Legend = forwardRef((props, ref) => {
|
|
|
147
205
|
|
|
148
206
|
return legendItems
|
|
149
207
|
}
|
|
150
|
-
|
|
151
208
|
const { legendClasses } = useDataVizClasses(state, viewport)
|
|
152
209
|
|
|
153
210
|
const handleReset = e => {
|
|
@@ -162,7 +219,15 @@ const Legend = forwardRef((props, ref) => {
|
|
|
162
219
|
}
|
|
163
220
|
}
|
|
164
221
|
|
|
165
|
-
const pin =
|
|
222
|
+
const pin = (
|
|
223
|
+
<path
|
|
224
|
+
className='marker'
|
|
225
|
+
d='M0,0l-8.8-17.7C-12.1-24.3-7.4-32,0-32h0c7.4,0,12.1,7.7,8.8,14.3L0,0z'
|
|
226
|
+
strokeWidth={2}
|
|
227
|
+
stroke={'black'}
|
|
228
|
+
transform={`scale(0.5)`}
|
|
229
|
+
/>
|
|
230
|
+
)
|
|
166
231
|
|
|
167
232
|
const cityStyleShapes = {
|
|
168
233
|
pin: pin,
|
|
@@ -176,10 +241,19 @@ const Legend = forwardRef((props, ref) => {
|
|
|
176
241
|
return (
|
|
177
242
|
<ErrorBoundary component='Sidebar'>
|
|
178
243
|
<div className='legends'>
|
|
179
|
-
<aside
|
|
244
|
+
<aside
|
|
245
|
+
id={skipId || 'legend'}
|
|
246
|
+
className={legendClasses.aside.join(' ') || ''}
|
|
247
|
+
role='region'
|
|
248
|
+
aria-label='Legend'
|
|
249
|
+
tabIndex={0}
|
|
250
|
+
ref={ref}
|
|
251
|
+
>
|
|
180
252
|
<section className={legendClasses.section.join(' ') || ''} aria-label='Map Legend'>
|
|
181
253
|
{legend.title && <h3 className={legendClasses.title.join(' ') || ''}>{parse(legend.title)}</h3>}
|
|
182
|
-
{legend.dynamicDescription === false && legend.description &&
|
|
254
|
+
{legend.dynamicDescription === false && legend.description && (
|
|
255
|
+
<p className={legendClasses.description.join(' ') || ''}>{parse(legend.description)}</p>
|
|
256
|
+
)}
|
|
183
257
|
{legend.dynamicDescription === true &&
|
|
184
258
|
runtimeFilters.map((filter, idx) => {
|
|
185
259
|
const lookupStr = `${idx},${filter.values.indexOf(String(filter.active))}`
|
|
@@ -196,8 +270,18 @@ const Legend = forwardRef((props, ref) => {
|
|
|
196
270
|
}
|
|
197
271
|
return true
|
|
198
272
|
})}
|
|
273
|
+
|
|
274
|
+
<LegendGradient
|
|
275
|
+
labels={getFormattedLegendItems().map(item => item?.label) ?? []}
|
|
276
|
+
colors={getFormattedLegendItems().map(item => item?.color) ?? []}
|
|
277
|
+
values={getFormattedLegendItems().map(item => item?.value) ?? []}
|
|
278
|
+
dimensions={dimensions}
|
|
279
|
+
currentViewport={currentViewport}
|
|
280
|
+
config={state}
|
|
281
|
+
getTextWidth={getTextWidth}
|
|
282
|
+
/>
|
|
199
283
|
<ul className={legendClasses.ul.join(' ') || ''} aria-label='Legend items'>
|
|
200
|
-
{legendList()}
|
|
284
|
+
{state.legend.style === 'gradient' ? '' : legendList()}
|
|
201
285
|
</ul>
|
|
202
286
|
{(state.visual.additionalCityStyles.some(c => c.label) || state.visual.cityStyleLabel) && (
|
|
203
287
|
<>
|
|
@@ -206,7 +290,10 @@ const Legend = forwardRef((props, ref) => {
|
|
|
206
290
|
{state.visual.cityStyleLabel && (
|
|
207
291
|
<div>
|
|
208
292
|
<svg>
|
|
209
|
-
<Group
|
|
293
|
+
<Group
|
|
294
|
+
top={state.visual.cityStyle === 'pin' ? 19 : state.visual.cityStyle === 'triangle' ? 13 : 11}
|
|
295
|
+
left={10}
|
|
296
|
+
>
|
|
210
297
|
{cityStyleShapes[state.visual.cityStyle.toLowerCase()]}
|
|
211
298
|
</Group>
|
|
212
299
|
</svg>
|
|
@@ -233,7 +320,9 @@ const Legend = forwardRef((props, ref) => {
|
|
|
233
320
|
{runtimeLegend.disabledAmt > 0 && <Button onClick={handleReset}>Reset</Button>}
|
|
234
321
|
</section>
|
|
235
322
|
</aside>
|
|
236
|
-
{state.hexMap.shapeGroups?.length > 0 && state.hexMap.type === 'shapes' && state.general.displayAsHex &&
|
|
323
|
+
{state.hexMap.shapeGroups?.length > 0 && state.hexMap.type === 'shapes' && state.general.displayAsHex && (
|
|
324
|
+
<LegendItemHex state={state} runtimeLegend={runtimeLegend} viewport={viewport} />
|
|
325
|
+
)}
|
|
237
326
|
</div>
|
|
238
327
|
</ErrorBoundary>
|
|
239
328
|
)
|
|
@@ -17,8 +17,10 @@
|
|
|
17
17
|
background-color: #fff;
|
|
18
18
|
z-index: 6;
|
|
19
19
|
border-top: $lightGray 1px solid;
|
|
20
|
+
|
|
20
21
|
@include breakpointClass(md) {
|
|
21
|
-
&.bottom
|
|
22
|
+
&.bottom,
|
|
23
|
+
&.top {
|
|
22
24
|
border: $lightGray 1px solid;
|
|
23
25
|
}
|
|
24
26
|
&.side {
|
|
@@ -35,8 +37,12 @@
|
|
|
35
37
|
right: 1em;
|
|
36
38
|
|
|
37
39
|
ul.vertical-sorted {
|
|
40
|
+
display: block;
|
|
38
41
|
column-count: 2;
|
|
39
42
|
column-fill: balance;
|
|
43
|
+
& > li {
|
|
44
|
+
white-space: nowrap;
|
|
45
|
+
}
|
|
40
46
|
}
|
|
41
47
|
|
|
42
48
|
ul:not(.vertical-sorted) {
|
|
@@ -47,29 +53,17 @@
|
|
|
47
53
|
flex-wrap: wrap;
|
|
48
54
|
}
|
|
49
55
|
}
|
|
56
|
+
&.no-border {
|
|
57
|
+
border: none;
|
|
58
|
+
}
|
|
50
59
|
|
|
51
|
-
&.bottom
|
|
60
|
+
&.bottom,
|
|
61
|
+
&.top {
|
|
52
62
|
ul.legend-container__ul.vertical-sorted {
|
|
53
63
|
display: block;
|
|
54
64
|
column-count: 2;
|
|
55
65
|
column-fill: balance;
|
|
56
66
|
}
|
|
57
|
-
|
|
58
|
-
ul.legend-container__ul {
|
|
59
|
-
display: flex;
|
|
60
|
-
flex-direction: row;
|
|
61
|
-
flex-wrap: wrap;
|
|
62
|
-
|
|
63
|
-
li {
|
|
64
|
-
width: 50%;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
ul.single-row {
|
|
69
|
-
display: block;
|
|
70
|
-
column-count: initial;
|
|
71
|
-
column-fill: auto;
|
|
72
|
-
}
|
|
73
67
|
}
|
|
74
68
|
}
|
|
75
69
|
|
|
@@ -103,9 +97,12 @@
|
|
|
103
97
|
p {
|
|
104
98
|
line-height: 1.4em;
|
|
105
99
|
}
|
|
106
|
-
.legend-container__ul {
|
|
100
|
+
.legend-container__ul:not(.single-row) {
|
|
107
101
|
list-style: none;
|
|
108
102
|
padding-top: 1em;
|
|
103
|
+
display: grid;
|
|
104
|
+
grid-template-columns: 1fr 1fr;
|
|
105
|
+
|
|
109
106
|
button {
|
|
110
107
|
font-size: unset;
|
|
111
108
|
background: transparent;
|
|
@@ -132,6 +129,7 @@
|
|
|
132
129
|
transition: 0.1s opacity;
|
|
133
130
|
display: flex;
|
|
134
131
|
cursor: pointer;
|
|
132
|
+
white-space: nowrap;
|
|
135
133
|
flex-grow: 1;
|
|
136
134
|
|
|
137
135
|
&.legend-container__li--disabled {
|
|
@@ -139,6 +137,29 @@
|
|
|
139
137
|
}
|
|
140
138
|
}
|
|
141
139
|
}
|
|
140
|
+
.legend-container__ul.single-row {
|
|
141
|
+
width: 100%;
|
|
142
|
+
list-style: none;
|
|
143
|
+
display: flex;
|
|
144
|
+
flex-direction: row;
|
|
145
|
+
align-items: center;
|
|
146
|
+
justify-content: flex-start;
|
|
147
|
+
flex-wrap: wrap;
|
|
148
|
+
|
|
149
|
+
& > li {
|
|
150
|
+
margin-right: 1em;
|
|
151
|
+
margin-bottom: 1em;
|
|
152
|
+
white-space: nowrap;
|
|
153
|
+
display: flex;
|
|
154
|
+
justify-content: center;
|
|
155
|
+
align-items: center;
|
|
156
|
+
vertical-align: middle;
|
|
157
|
+
|
|
158
|
+
& svg {
|
|
159
|
+
vertical-align: baseline;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
142
163
|
}
|
|
143
164
|
|
|
144
165
|
.bottom .legend-container__ul--single-column:not(.vertical-sorted) {
|
|
@@ -150,6 +171,7 @@
|
|
|
150
171
|
|
|
151
172
|
.legend-container__li {
|
|
152
173
|
width: 100%;
|
|
174
|
+
white-space: nowrap;
|
|
153
175
|
}
|
|
154
176
|
}
|
|
155
177
|
|
|
@@ -175,30 +197,8 @@
|
|
|
175
197
|
}
|
|
176
198
|
}
|
|
177
199
|
|
|
178
|
-
&.bottom.single-row {
|
|
179
|
-
width: 100%;
|
|
180
|
-
.legend-container ul {
|
|
181
|
-
flex-direction: row;
|
|
182
|
-
align-items: baseline;
|
|
183
|
-
justify-content: flex-start;
|
|
184
|
-
flex-wrap: wrap;
|
|
185
|
-
li {
|
|
186
|
-
justify-items: center;
|
|
187
|
-
line-break: loose;
|
|
188
|
-
align-items: center;
|
|
189
|
-
width: auto;
|
|
190
|
-
padding-right: 1em;
|
|
191
|
-
padding-bottom: 1em;
|
|
192
|
-
display: inline-block;
|
|
193
|
-
& > span {
|
|
194
|
-
margin: 0 !important;
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
200
|
@include breakpointClass(sm) {
|
|
201
|
-
.legend-container ul {
|
|
201
|
+
.legend-container ul:not(.single-row) {
|
|
202
202
|
align-items: flex-start;
|
|
203
203
|
justify-content: space-between;
|
|
204
204
|
li {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { useContext } from 'react'
|
|
2
|
+
import LegendShape from '@cdc/core/components/LegendShape'
|
|
3
|
+
import ConfigContext from '../context'
|
|
4
|
+
import Icon from '@cdc/core/components/ui/Icon'
|
|
5
|
+
|
|
6
|
+
const Modal = () => {
|
|
7
|
+
const { applyTooltipsToGeo, capitalize, applyLegendToRow, viewport, type, content } = useContext(ConfigContext)
|
|
8
|
+
|
|
9
|
+
const tooltip = applyTooltipsToGeo(content.geoName, content.keyedData, 'jsx')
|
|
10
|
+
|
|
11
|
+
const legendColors = applyLegendToRow(content.keyedData)
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<section
|
|
15
|
+
className={capitalize ? 'modal-content tooltip capitalize ' + viewport : 'modal-content tooltip ' + viewport}
|
|
16
|
+
aria-hidden='true'
|
|
17
|
+
>
|
|
18
|
+
{type === 'data' && <LegendShape fill={legendColors[0]} />}
|
|
19
|
+
<div className='content'>{tooltip}</div>
|
|
20
|
+
<Icon display='close' alt='Close Modal' size={20} color='#000' className='modal-close' />
|
|
21
|
+
</section>
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export default Modal
|