@cdc/map 4.24.5 → 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 +71853 -64936
- package/examples/annotation/index.json +552 -0
- package/examples/annotation/usa-map.json +900 -0
- 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 +10 -6
- package/package.json +6 -5
- package/src/CdcMap.tsx +367 -199
- package/src/_stories/CdcMap.stories.tsx +14 -0
- package/src/_stories/_mock/DEV-7286.json +165 -0
- package/src/_stories/_mock/DEV-8942.json +270 -0
- package/src/components/Annotation/Annotation.Draggable.styles.css +18 -0
- package/src/components/Annotation/Annotation.Draggable.tsx +152 -0
- package/src/components/Annotation/AnnotationDropdown.styles.css +14 -0
- package/src/components/Annotation/AnnotationDropdown.tsx +70 -0
- package/src/components/Annotation/AnnotationList.styles.css +45 -0
- package/src/components/Annotation/AnnotationList.tsx +42 -0
- package/src/components/Annotation/index.tsx +11 -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 +650 -129
- package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +336 -0
- package/src/components/EditorPanel/components/{Panel.PatternSettings.tsx → Panels/Panel.PatternSettings.tsx} +63 -13
- package/src/components/EditorPanel/components/{Panels.tsx → Panels/index.tsx} +3 -0
- package/src/components/Legend/components/Legend.tsx +125 -42
- 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/Territory/Territory.Rectangle.tsx +4 -3
- package/src/components/UsaMap/components/UsaMap.County.tsx +114 -36
- package/src/components/UsaMap/components/UsaMap.Region.tsx +2 -0
- package/src/components/UsaMap/components/UsaMap.SingleState.tsx +175 -206
- package/src/components/UsaMap/components/UsaMap.State.tsx +188 -44
- 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 +11 -2
- package/src/data/supported-geos.js +15 -4
- package/src/helpers/generateColorsArray.ts +13 -0
- package/src/helpers/generateRuntimeLegendHash.ts +23 -0
- package/src/helpers/getUniqueValues.ts +19 -0
- package/src/helpers/hashObj.ts +25 -0
- package/src/helpers/tests/generateColorsArray.test.ts +18 -0
- package/src/helpers/tests/generateRuntimeLegendHash.test.ts +11 -0
- package/src/helpers/tests/hashObj.test.ts +10 -0
- 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 +14 -3
- package/src/types/MapConfig.ts +9 -1
- package/src/types/MapContext.ts +16 -2
- package/LICENSE +0 -201
- package/src/components/Modal.jsx +0 -22
- package/src/test/CdcMap.test.jsx +0 -19
- /package/src/components/EditorPanel/components/{Panel.PatternSettings-style.css → Panels/Panel.PatternSettings-style.css} +0 -0
- /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
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
import React, { useContext } from 'react'
|
|
2
|
+
|
|
3
|
+
// CDC Core
|
|
4
|
+
import { approvedCurveTypes } from '@cdc/core/helpers/lineChartHelpers'
|
|
5
|
+
import Accordion from '@cdc/core/components/ui/Accordion'
|
|
6
|
+
import Button from '@cdc/core/components/elements/Button'
|
|
7
|
+
import { MapContext } from '../../../../types/MapContext'
|
|
8
|
+
import ConfigContext from '../../../../context'
|
|
9
|
+
|
|
10
|
+
// types
|
|
11
|
+
// styles
|
|
12
|
+
|
|
13
|
+
const PanelAnnotate: React.FC = props => {
|
|
14
|
+
const { state: config, setState: updateConfig, dimensions, isDraggingAnnotation } = useContext<MapContext>(ConfigContext)
|
|
15
|
+
const getColumns = (filter = true) => {
|
|
16
|
+
const columns = {}
|
|
17
|
+
config.data.forEach(row => {
|
|
18
|
+
Object.keys(row).forEach(columnName => (columns[columnName] = true))
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
if (filter) {
|
|
22
|
+
Object.keys(columns).forEach(key => {
|
|
23
|
+
if ((config.series && config.series.filter(series => series.dataKey === key).length > 0) || (config.confidenceKeys && Object.keys(config.confidenceKeys).includes(key))) {
|
|
24
|
+
delete columns[key]
|
|
25
|
+
}
|
|
26
|
+
})
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return Object.keys(columns)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const handleAnnotationUpdate = (value, property, index) => {
|
|
33
|
+
const annotations = [...config?.annotations]
|
|
34
|
+
annotations[index][property] = value
|
|
35
|
+
annotations[index].savedDimensions = [dimensions[0] * 0.73, dimensions[1]]
|
|
36
|
+
|
|
37
|
+
updateConfig({
|
|
38
|
+
...config,
|
|
39
|
+
annotations
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const handleAddAnnotation = () => {
|
|
44
|
+
const svgContainer = document.querySelector('.map-container > section > svg, .map-container > section > canvas')?.getBoundingClientRect()
|
|
45
|
+
const newSvgDims = [svgContainer.width, svgContainer.height]
|
|
46
|
+
|
|
47
|
+
const newAnnotation = {
|
|
48
|
+
text: 'New Annotation',
|
|
49
|
+
snapToNearestPoint: false,
|
|
50
|
+
fontSize: 16,
|
|
51
|
+
show: {
|
|
52
|
+
desktop: true,
|
|
53
|
+
tablet: true,
|
|
54
|
+
mobile: true
|
|
55
|
+
},
|
|
56
|
+
markerType: 'arrow',
|
|
57
|
+
connectorType: 'line',
|
|
58
|
+
colors: {
|
|
59
|
+
label: 'black',
|
|
60
|
+
connector: 'black',
|
|
61
|
+
marker: 'black'
|
|
62
|
+
},
|
|
63
|
+
selected: true,
|
|
64
|
+
anchor: {
|
|
65
|
+
vertical: false,
|
|
66
|
+
horizontal: false
|
|
67
|
+
},
|
|
68
|
+
connectionType: 'line',
|
|
69
|
+
marker: 'arrow',
|
|
70
|
+
edit: {
|
|
71
|
+
subject: true,
|
|
72
|
+
label: true
|
|
73
|
+
},
|
|
74
|
+
seriesKey: '',
|
|
75
|
+
x: Number(newSvgDims?.[0]) / 2,
|
|
76
|
+
y: Number(newSvgDims?.[1] / 2),
|
|
77
|
+
xKey: null,
|
|
78
|
+
yKey: null,
|
|
79
|
+
dx: 0,
|
|
80
|
+
dy: 0,
|
|
81
|
+
opacity: '100',
|
|
82
|
+
savedDimensions: [dimensions[0] * 0.73, dimensions[1]]
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const annotations = Array.isArray(config.annotations) ? config.annotations : []
|
|
86
|
+
|
|
87
|
+
updateConfig({
|
|
88
|
+
...config,
|
|
89
|
+
annotations: [...annotations, newAnnotation]
|
|
90
|
+
})
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const handleRemoveAnnotation = (annotationIndex: number) => {
|
|
94
|
+
const updated = config.annotations.filter((_, index) => index !== annotationIndex)
|
|
95
|
+
updateConfig({
|
|
96
|
+
...config,
|
|
97
|
+
annotations: updated
|
|
98
|
+
})
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return (
|
|
102
|
+
<Accordion>
|
|
103
|
+
<Accordion.Section title={props.name}>
|
|
104
|
+
<label>
|
|
105
|
+
Show Annotation Dropdown
|
|
106
|
+
<input
|
|
107
|
+
type='checkbox'
|
|
108
|
+
checked={config?.general?.showAnnotationDropdown}
|
|
109
|
+
onClick={e => {
|
|
110
|
+
updateConfig({
|
|
111
|
+
...config,
|
|
112
|
+
general: {
|
|
113
|
+
...config.general,
|
|
114
|
+
showAnnotationDropdown: e.target.checked
|
|
115
|
+
}
|
|
116
|
+
})
|
|
117
|
+
}}
|
|
118
|
+
/>
|
|
119
|
+
</label>
|
|
120
|
+
|
|
121
|
+
<label>
|
|
122
|
+
Annotation Dropdown Title:
|
|
123
|
+
<input
|
|
124
|
+
type='text'
|
|
125
|
+
style={{ marginBottom: '10px' }}
|
|
126
|
+
value={config?.general?.annotationDropdownText}
|
|
127
|
+
onChange={e => {
|
|
128
|
+
updateConfig({
|
|
129
|
+
...config,
|
|
130
|
+
general: {
|
|
131
|
+
...config.general,
|
|
132
|
+
annotationDropdownText: e.target.value
|
|
133
|
+
}
|
|
134
|
+
})
|
|
135
|
+
}}
|
|
136
|
+
/>
|
|
137
|
+
</label>
|
|
138
|
+
|
|
139
|
+
{config?.annotations &&
|
|
140
|
+
config?.annotations.map((annotation, index) => (
|
|
141
|
+
<Accordion>
|
|
142
|
+
<Accordion.Section title={annotation.text ? annotation.text.substring(0, 15) + '...' : `Annotation ${index + 1}`}>
|
|
143
|
+
<div className='annotation-group'>
|
|
144
|
+
<label>
|
|
145
|
+
Annotation Text:
|
|
146
|
+
<textarea rows={5} value={annotation.text} onChange={e => handleAnnotationUpdate(e.target.value, 'text', index)} />
|
|
147
|
+
</label>
|
|
148
|
+
{/* <label>
|
|
149
|
+
Vertical Anchor
|
|
150
|
+
<input
|
|
151
|
+
type='checkbox'
|
|
152
|
+
checked={config?.annotations[index].anchor.vertical}
|
|
153
|
+
onClick={e => {
|
|
154
|
+
const updatedAnnotations = [...config?.annotations]
|
|
155
|
+
updatedAnnotations[index].anchor.vertical = e.target.checked
|
|
156
|
+
updateConfig({
|
|
157
|
+
...config,
|
|
158
|
+
annotations: updatedAnnotations
|
|
159
|
+
})
|
|
160
|
+
}}
|
|
161
|
+
/>
|
|
162
|
+
</label>
|
|
163
|
+
<label>
|
|
164
|
+
Horizontal Anchor
|
|
165
|
+
<input
|
|
166
|
+
type='checkbox'
|
|
167
|
+
checked={config?.annotations[index].anchor.horizontal}
|
|
168
|
+
onClick={e => {
|
|
169
|
+
const updatedAnnotations = [...config?.annotations]
|
|
170
|
+
updatedAnnotations[index].anchor.horizontal = e.target.checked
|
|
171
|
+
updateConfig({
|
|
172
|
+
...config,
|
|
173
|
+
annotations: updatedAnnotations
|
|
174
|
+
})
|
|
175
|
+
}}
|
|
176
|
+
/>
|
|
177
|
+
</label> */}
|
|
178
|
+
|
|
179
|
+
<label>
|
|
180
|
+
Opacity
|
|
181
|
+
<br />
|
|
182
|
+
<input
|
|
183
|
+
type='range'
|
|
184
|
+
onChange={e => {
|
|
185
|
+
const updatedAnnotations = [...config?.annotations]
|
|
186
|
+
updatedAnnotations[index].opacity = e.target.value
|
|
187
|
+
updateConfig({
|
|
188
|
+
...config,
|
|
189
|
+
annotations: updatedAnnotations
|
|
190
|
+
})
|
|
191
|
+
}}
|
|
192
|
+
value={config?.annotations?.[index]?.opacity || '100'}
|
|
193
|
+
/>
|
|
194
|
+
</label>
|
|
195
|
+
|
|
196
|
+
<label>
|
|
197
|
+
Edit Subject
|
|
198
|
+
<input
|
|
199
|
+
type='checkbox'
|
|
200
|
+
checked={config?.annotations[index]?.edit?.subject}
|
|
201
|
+
onClick={e => {
|
|
202
|
+
const updatedAnnotations = [...config?.annotations]
|
|
203
|
+
updatedAnnotations[index].edit.subject = e.target.checked
|
|
204
|
+
updateConfig({
|
|
205
|
+
...config,
|
|
206
|
+
annotations: updatedAnnotations
|
|
207
|
+
})
|
|
208
|
+
}}
|
|
209
|
+
/>
|
|
210
|
+
</label>
|
|
211
|
+
<label>
|
|
212
|
+
Edit Label
|
|
213
|
+
<input
|
|
214
|
+
type='checkbox'
|
|
215
|
+
checked={config?.annotations[index]?.edit?.label}
|
|
216
|
+
onClick={e => {
|
|
217
|
+
const updatedAnnotations = [...config?.annotations]
|
|
218
|
+
updatedAnnotations[index].edit.label = e.target.checked
|
|
219
|
+
updateConfig({
|
|
220
|
+
...config,
|
|
221
|
+
annotations: updatedAnnotations
|
|
222
|
+
})
|
|
223
|
+
}}
|
|
224
|
+
/>
|
|
225
|
+
</label>
|
|
226
|
+
|
|
227
|
+
<label>
|
|
228
|
+
Connection Type:
|
|
229
|
+
<select
|
|
230
|
+
onChange={e => {
|
|
231
|
+
const updatedAnnotations = [...config?.annotations]
|
|
232
|
+
updatedAnnotations[index].connectionType = e.target.value
|
|
233
|
+
updateConfig({
|
|
234
|
+
...config,
|
|
235
|
+
annotations: updatedAnnotations
|
|
236
|
+
})
|
|
237
|
+
}}
|
|
238
|
+
>
|
|
239
|
+
{['curve', 'line', 'elbow', 'none'].map((side, index) => (
|
|
240
|
+
<option key={side} value={side}>
|
|
241
|
+
{side}
|
|
242
|
+
</option>
|
|
243
|
+
))}
|
|
244
|
+
</select>
|
|
245
|
+
</label>
|
|
246
|
+
|
|
247
|
+
{annotation.connectionType === 'curve' && (
|
|
248
|
+
<label>
|
|
249
|
+
Line Type:
|
|
250
|
+
<select
|
|
251
|
+
onChange={e => {
|
|
252
|
+
const updatedAnnotations = [...config?.annotations]
|
|
253
|
+
updatedAnnotations[index].lineType = e.target.value
|
|
254
|
+
updateConfig({
|
|
255
|
+
...config,
|
|
256
|
+
annotations: updatedAnnotations
|
|
257
|
+
})
|
|
258
|
+
}}
|
|
259
|
+
>
|
|
260
|
+
{Object.entries(approvedCurveTypes).map(([value, key]) => (
|
|
261
|
+
<option key={key} value={key}>
|
|
262
|
+
{value}
|
|
263
|
+
</option>
|
|
264
|
+
))}
|
|
265
|
+
</select>
|
|
266
|
+
</label>
|
|
267
|
+
)}
|
|
268
|
+
|
|
269
|
+
{/* <label>
|
|
270
|
+
Connection Location:
|
|
271
|
+
<select
|
|
272
|
+
onChange={e => {
|
|
273
|
+
const updatedAnnotations = [...config?.annotations]
|
|
274
|
+
updatedAnnotations[index].connectionLocation = e.target.value
|
|
275
|
+
updateConfig({
|
|
276
|
+
...config,
|
|
277
|
+
annotations: updatedAnnotations
|
|
278
|
+
})
|
|
279
|
+
}}
|
|
280
|
+
>
|
|
281
|
+
{['auto', 'left', 'top', 'bottom', 'right'].map((side, index) => (
|
|
282
|
+
<option key={side} value={side}>
|
|
283
|
+
{side}
|
|
284
|
+
</option>
|
|
285
|
+
))}
|
|
286
|
+
</select>
|
|
287
|
+
</label> */}
|
|
288
|
+
|
|
289
|
+
<label>
|
|
290
|
+
Marker
|
|
291
|
+
<select
|
|
292
|
+
onChange={e => {
|
|
293
|
+
const updatedAnnotations = [...config?.annotations]
|
|
294
|
+
updatedAnnotations[index].marker = e.target.value
|
|
295
|
+
updateConfig({
|
|
296
|
+
...config,
|
|
297
|
+
annotations: updatedAnnotations
|
|
298
|
+
})
|
|
299
|
+
}}
|
|
300
|
+
>
|
|
301
|
+
{['circle', 'arrow'].map((column, columnIndex) => {
|
|
302
|
+
return <option>{column}</option>
|
|
303
|
+
})}
|
|
304
|
+
</select>
|
|
305
|
+
</label>
|
|
306
|
+
|
|
307
|
+
{/* <label>
|
|
308
|
+
Snap to Nearest Point
|
|
309
|
+
<input
|
|
310
|
+
type='checkbox'
|
|
311
|
+
checked={config?.annotations[index]?.snapToNearestPoint}
|
|
312
|
+
onClick={e => {
|
|
313
|
+
const updatedAnnotations = [...config?.annotations]
|
|
314
|
+
updatedAnnotations[index].snapToNearestPoint = e.target.checked
|
|
315
|
+
updateConfig({
|
|
316
|
+
...config,
|
|
317
|
+
annotations: updatedAnnotations
|
|
318
|
+
})
|
|
319
|
+
}}
|
|
320
|
+
/>
|
|
321
|
+
</label> */}
|
|
322
|
+
|
|
323
|
+
<Button className='warn btn-warn btn btn-remove delete' onClick={() => handleRemoveAnnotation(index)}>
|
|
324
|
+
Delete Annotation
|
|
325
|
+
</Button>
|
|
326
|
+
</div>
|
|
327
|
+
</Accordion.Section>
|
|
328
|
+
</Accordion>
|
|
329
|
+
))}
|
|
330
|
+
{config?.annotations?.length < 3 && <Button onClick={handleAddAnnotation}>Add Annotation</Button>}
|
|
331
|
+
</Accordion.Section>
|
|
332
|
+
</Accordion>
|
|
333
|
+
)
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
export default PanelAnnotate
|
|
@@ -1,18 +1,24 @@
|
|
|
1
1
|
import { useContext } from 'react'
|
|
2
2
|
import { Accordion, AccordionItem, AccordionItemHeading, AccordionItemPanel, AccordionItemButton } from 'react-accessible-accordion'
|
|
3
|
-
import ConfigContext from '
|
|
4
|
-
import { type MapContext } from '
|
|
3
|
+
import ConfigContext from '../../../../context'
|
|
4
|
+
import { type MapContext } from '../../../../types/MapContext'
|
|
5
5
|
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) => (
|