@cdc/map 4.24.10 → 4.24.12
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 +27324 -27152
- package/examples/default-geocode.json +13 -4
- package/examples/default-usa-regions.json +267 -117
- package/examples/example-city-state.json +6 -3
- package/examples/pattern.json +861 -0
- package/examples/private/DEV-9644.json +184 -0
- package/examples/private/default-patterns.json +867 -0
- package/index.html +4 -5
- package/package.json +3 -3
- package/src/CdcMap.tsx +53 -52
- package/src/_stories/CdcMap.Legend.Gradient.stories.tsx +67 -0
- package/src/_stories/CdcMap.Legend.stories.tsx +40 -0
- package/src/_stories/CdcMap.Patterns.stories.tsx +29 -0
- package/src/_stories/CdcMap.stories.tsx +59 -0
- package/src/_stories/UsaMap.NoData.stories.tsx +19 -0
- package/src/_stories/_mock/custom-layer-map.json +1117 -0
- package/src/_stories/_mock/default-patterns.json +865 -0
- package/src/_stories/_mock/example-city-state.json +858 -0
- package/src/_stories/_mock/usa-state-gradient.json +238 -0
- package/src/_stories/_mock/wastewater-map.json +208 -0
- package/src/components/CityList.tsx +5 -2
- package/src/components/EditorPanel/components/EditorPanel.tsx +81 -61
- package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +27 -23
- package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +75 -16
- package/src/components/Legend/components/Legend.tsx +42 -20
- package/src/components/Legend/components/index.scss +24 -24
- package/src/components/UsaMap/components/HexIcon.tsx +7 -1
- package/src/components/UsaMap/components/SingleState/SingleState.CountyOutput.tsx +40 -6
- package/src/components/UsaMap/components/SingleState/SingleState.StateOutput.tsx +10 -2
- package/src/components/UsaMap/components/Territory/Territory.Hexagon.tsx +57 -12
- package/src/components/UsaMap/components/Territory/Territory.Rectangle.tsx +95 -21
- package/src/components/UsaMap/components/Territory/TerritoryShape.ts +13 -0
- package/src/components/UsaMap/components/UsaMap.County.tsx +11 -13
- package/src/components/UsaMap/components/UsaMap.Region.tsx +59 -16
- package/src/components/UsaMap/components/UsaMap.SingleState.tsx +2 -1
- package/src/components/UsaMap/components/UsaMap.State.tsx +60 -62
- package/src/components/UsaMap/helpers/shapes.ts +5 -4
- package/src/components/WorldMap/WorldMap.tsx +77 -16
- package/src/data/initial-state.js +2 -1
- package/src/helpers/applyColorToLegend.ts +80 -0
- package/src/helpers/colors.ts +23 -0
- package/src/hooks/useTooltip.ts +9 -6
- package/src/scss/editor-panel.scss +0 -3
- package/src/scss/filters.scss +1 -9
- package/src/scss/main.scss +0 -5
- package/src/scss/map.scss +11 -63
- package/src/types/MapConfig.ts +6 -1
- package/src/types/MapContext.ts +1 -0
- package/examples/default-patterns.json +0 -579
- package/src/scss/datatable.scss +0 -6
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useState, useEffect,
|
|
1
|
+
import React, { useState, useEffect, useContext } from 'react'
|
|
2
2
|
|
|
3
3
|
// Third Party
|
|
4
4
|
import {
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
} from 'react-accessible-accordion'
|
|
11
11
|
import { DragDropContext, Droppable, Draggable } from '@hello-pangea/dnd'
|
|
12
12
|
import { useDebounce } from 'use-debounce'
|
|
13
|
+
import _ from 'lodash'
|
|
13
14
|
// import ReactTags from 'react-tag-autocomplete'
|
|
14
15
|
import { Tooltip as ReactTooltip } from 'react-tooltip'
|
|
15
16
|
import Panels from './Panels'
|
|
@@ -42,6 +43,7 @@ import HexSetting from './HexShapeSettings.jsx'
|
|
|
42
43
|
import ConfigContext from '../../../context.ts'
|
|
43
44
|
import { MapContext } from '../../../types/MapContext.js'
|
|
44
45
|
import { TextField } from './Inputs'
|
|
46
|
+
import Alert from '@cdc/core/components/Alert'
|
|
45
47
|
|
|
46
48
|
// Todo: move to useReducer, seperate files out.
|
|
47
49
|
const EditorPanel = ({ columnsRequiredChecker }) => {
|
|
@@ -63,8 +65,7 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
|
|
|
63
65
|
runtimeData,
|
|
64
66
|
setRuntimeData,
|
|
65
67
|
generateRuntimeData,
|
|
66
|
-
|
|
67
|
-
topoData,
|
|
68
|
+
|
|
68
69
|
|
|
69
70
|
} = useContext<MapContext>(ConfigContext)
|
|
70
71
|
|
|
@@ -109,12 +110,18 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
|
|
|
109
110
|
} = useMapLayers(state, setState, false, tooltipId)
|
|
110
111
|
|
|
111
112
|
const categoryMove = (idx1, idx2) => {
|
|
112
|
-
let categoryValuesOrder =
|
|
113
|
+
let categoryValuesOrder = getCategoryValuesOrder()
|
|
113
114
|
|
|
114
115
|
let [movedItem] = categoryValuesOrder.splice(idx1, 1)
|
|
115
116
|
|
|
116
117
|
categoryValuesOrder.splice(idx2, 0, movedItem)
|
|
117
118
|
|
|
119
|
+
state.legend.categoryValuesOrder?.forEach(value => {
|
|
120
|
+
if (categoryValuesOrder.indexOf(value) === -1) {
|
|
121
|
+
categoryValuesOrder.push(value)
|
|
122
|
+
}
|
|
123
|
+
})
|
|
124
|
+
|
|
118
125
|
setState({
|
|
119
126
|
...state,
|
|
120
127
|
legend: {
|
|
@@ -371,7 +378,8 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
|
|
|
371
378
|
...state,
|
|
372
379
|
legend: {
|
|
373
380
|
...state.legend,
|
|
374
|
-
position: value
|
|
381
|
+
position: value,
|
|
382
|
+
hideBorder: _.includes(['top', 'bottom'], value)
|
|
375
383
|
}
|
|
376
384
|
})
|
|
377
385
|
break
|
|
@@ -1087,6 +1095,15 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
|
|
|
1087
1095
|
</option>
|
|
1088
1096
|
</select>
|
|
1089
1097
|
</label>
|
|
1098
|
+
<label>
|
|
1099
|
+
<TextField
|
|
1100
|
+
type='textarea'
|
|
1101
|
+
value={state.filterIntro}
|
|
1102
|
+
fieldName='filterIntro'
|
|
1103
|
+
label='Filter Intro text'
|
|
1104
|
+
updateField={updateField}
|
|
1105
|
+
/>
|
|
1106
|
+
</label>
|
|
1090
1107
|
{filtersJSX}
|
|
1091
1108
|
</>
|
|
1092
1109
|
)
|
|
@@ -1219,40 +1236,6 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
|
|
|
1219
1236
|
columnsRequiredChecker()
|
|
1220
1237
|
}, [state]) // eslint-disable-line
|
|
1221
1238
|
|
|
1222
|
-
useEffect(() => {
|
|
1223
|
-
//If a categorical map is used and the order is either not defined or incorrect, fix it
|
|
1224
|
-
if ('category' === state.legend.type && runtimeLegend && runtimeLegend.runtimeDataHash) {
|
|
1225
|
-
let valid = true
|
|
1226
|
-
if (state.legend.categoryValuesOrder) {
|
|
1227
|
-
runtimeLegend.forEach(item => {
|
|
1228
|
-
if (!item.special && state.legend.categoryValuesOrder.indexOf(item.value) === -1) {
|
|
1229
|
-
valid = false
|
|
1230
|
-
}
|
|
1231
|
-
})
|
|
1232
|
-
let runtimeLegendKeys = runtimeLegend.map(item => item.value)
|
|
1233
|
-
state.legend.categoryValuesOrder.forEach(category => {
|
|
1234
|
-
if (runtimeLegendKeys.indexOf(category) === -1) {
|
|
1235
|
-
valid = false
|
|
1236
|
-
}
|
|
1237
|
-
})
|
|
1238
|
-
} else {
|
|
1239
|
-
valid = false
|
|
1240
|
-
}
|
|
1241
|
-
|
|
1242
|
-
if (!valid) {
|
|
1243
|
-
let arr = runtimeLegend.filter(item => !item.special).map(({ value }) => value)
|
|
1244
|
-
|
|
1245
|
-
setState({
|
|
1246
|
-
...state,
|
|
1247
|
-
legend: {
|
|
1248
|
-
...state.legend,
|
|
1249
|
-
categoryValuesOrder: arr
|
|
1250
|
-
}
|
|
1251
|
-
})
|
|
1252
|
-
}
|
|
1253
|
-
}
|
|
1254
|
-
}, [runtimeLegend]) // eslint-disable-line
|
|
1255
|
-
|
|
1256
1239
|
const columnsOptions = [
|
|
1257
1240
|
<option value='' key={'Select Option'}>
|
|
1258
1241
|
- Select Option -
|
|
@@ -1289,6 +1272,14 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
|
|
|
1289
1272
|
})
|
|
1290
1273
|
|
|
1291
1274
|
const updateField = (section, subsection, fieldName, newValue) => {
|
|
1275
|
+
if (!section) {
|
|
1276
|
+
setState({
|
|
1277
|
+
...state,
|
|
1278
|
+
[fieldName]: newValue
|
|
1279
|
+
})
|
|
1280
|
+
return
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1292
1283
|
const isArray = Array.isArray(state[section])
|
|
1293
1284
|
|
|
1294
1285
|
let sectionValue = isArray ? [...state[section], newValue] : { ...state[section], [fieldName]: newValue }
|
|
@@ -1435,7 +1426,7 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
|
|
|
1435
1426
|
{filter.order === 'cust' && (
|
|
1436
1427
|
<DragDropContext
|
|
1437
1428
|
onDragEnd={({ source, destination }) =>
|
|
1438
|
-
handleFilterOrder(source.index, destination
|
|
1429
|
+
handleFilterOrder(source.index, destination?.index, index, state.filters?.[index])
|
|
1439
1430
|
}
|
|
1440
1431
|
>
|
|
1441
1432
|
<Droppable droppableId='filter_order'>
|
|
@@ -1530,9 +1521,29 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
|
|
|
1530
1521
|
...draggableStyle
|
|
1531
1522
|
})
|
|
1532
1523
|
|
|
1524
|
+
const getCategoryValuesOrder = () => {
|
|
1525
|
+
let values = runtimeLegend
|
|
1526
|
+
? runtimeLegend.filter(item => !item.special).map(runtimeLegendItem => runtimeLegendItem.value)
|
|
1527
|
+
: []
|
|
1528
|
+
|
|
1529
|
+
if (state.legend.cateogryValuesOrder) {
|
|
1530
|
+
return values.sort((a, b) => {
|
|
1531
|
+
let aVal = state.legend.cateogryValuesOrder.indexOf(a)
|
|
1532
|
+
let bVal = state.legend.cateogryValuesOrder.indexOf(b)
|
|
1533
|
+
if (aVal === bVal) return 0
|
|
1534
|
+
if (aVal === -1) return 1
|
|
1535
|
+
if (bVal === -1) return -1
|
|
1536
|
+
return aVal - bVal
|
|
1537
|
+
})
|
|
1538
|
+
} else {
|
|
1539
|
+
return values
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1533
1543
|
const CategoryList = () => {
|
|
1534
|
-
return
|
|
1535
|
-
|
|
1544
|
+
return getCategoryValuesOrder()
|
|
1545
|
+
.filter(item => !item?.special)
|
|
1546
|
+
.map((value, index) => (
|
|
1536
1547
|
<Draggable key={value} draggableId={`item-${value}`} index={index}>
|
|
1537
1548
|
{(provided, snapshot) => (
|
|
1538
1549
|
<li style={{ position: 'relative' }}>
|
|
@@ -1549,9 +1560,6 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
|
|
|
1549
1560
|
)}
|
|
1550
1561
|
</Draggable>
|
|
1551
1562
|
))
|
|
1552
|
-
) : (
|
|
1553
|
-
<></>
|
|
1554
|
-
)
|
|
1555
1563
|
}
|
|
1556
1564
|
|
|
1557
1565
|
const isLoadedFromUrl = state?.dataKey?.includes('http://') || state?.dataKey?.includes('https://')
|
|
@@ -2205,6 +2213,13 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
|
|
|
2205
2213
|
</Tooltip>
|
|
2206
2214
|
</span>
|
|
2207
2215
|
</label>
|
|
2216
|
+
{state.legend.specialClasses.length === 2 && (
|
|
2217
|
+
<Alert
|
|
2218
|
+
type='info'
|
|
2219
|
+
message='If a third special class is needed you can apply a pattern to set it apart.'
|
|
2220
|
+
showCloseButton={false}
|
|
2221
|
+
/>
|
|
2222
|
+
)}
|
|
2208
2223
|
{specialClasses.map((specialClass, i) => (
|
|
2209
2224
|
<div className='edit-block' key={`special-class-${i}`}>
|
|
2210
2225
|
<button
|
|
@@ -2265,15 +2280,17 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
|
|
|
2265
2280
|
</label>
|
|
2266
2281
|
</div>
|
|
2267
2282
|
))}
|
|
2268
|
-
<
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
e
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2283
|
+
{state.legend.specialClasses.length < 2 && (
|
|
2284
|
+
<button
|
|
2285
|
+
className='btn btn-primary full-width'
|
|
2286
|
+
onClick={e => {
|
|
2287
|
+
e.preventDefault()
|
|
2288
|
+
editColumn('primary', 'specialClassAdd', {})
|
|
2289
|
+
}}
|
|
2290
|
+
>
|
|
2291
|
+
Add Special Class
|
|
2292
|
+
</button>
|
|
2293
|
+
)}
|
|
2277
2294
|
</fieldset>
|
|
2278
2295
|
)}
|
|
2279
2296
|
|
|
@@ -2417,7 +2434,7 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
|
|
|
2417
2434
|
</fieldset>
|
|
2418
2435
|
))}
|
|
2419
2436
|
<button
|
|
2420
|
-
className={'btn full-width'}
|
|
2437
|
+
className={'btn btn-primary full-width'}
|
|
2421
2438
|
onClick={event => {
|
|
2422
2439
|
event.preventDefault()
|
|
2423
2440
|
addAdditionalColumn(additionalColumns.length + 1)
|
|
@@ -2473,7 +2490,7 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
|
|
|
2473
2490
|
</fieldset>
|
|
2474
2491
|
))}
|
|
2475
2492
|
<button
|
|
2476
|
-
className={'btn full-width'}
|
|
2493
|
+
className={'btn btn-primary full-width'}
|
|
2477
2494
|
onClick={event => {
|
|
2478
2495
|
event.preventDefault()
|
|
2479
2496
|
const updatedAdditionaCategories = [...(state.legend.additionalCategories || [])]
|
|
@@ -2784,7 +2801,7 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
|
|
|
2784
2801
|
)}
|
|
2785
2802
|
</Droppable>
|
|
2786
2803
|
</DragDropContext>
|
|
2787
|
-
{
|
|
2804
|
+
{runtimeLegend && runtimeLegend.length >= 10 && (
|
|
2788
2805
|
<section className='error-box my-2'>
|
|
2789
2806
|
<div>
|
|
2790
2807
|
<strong className='pt-1'>Warning</strong>
|
|
@@ -2911,7 +2928,7 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
|
|
|
2911
2928
|
<p style={{ textAlign: 'center' }}>There are currently no filters.</p>
|
|
2912
2929
|
)}
|
|
2913
2930
|
<button
|
|
2914
|
-
className={'btn full-width'}
|
|
2931
|
+
className={'btn btn-primary full-width'}
|
|
2915
2932
|
onClick={event => {
|
|
2916
2933
|
event.preventDefault()
|
|
2917
2934
|
changeFilter(null, 'addNew')
|
|
@@ -3243,7 +3260,6 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
|
|
|
3243
3260
|
<label>
|
|
3244
3261
|
<span className='edit-label'>Map Color Palette</span>
|
|
3245
3262
|
</label>
|
|
3246
|
-
{/* <InputCheckbox section="general" subsection="palette" fieldName='isReversed' size='small' label='Use selected palette in reverse order' updateField={updateField} value={isPaletteReversed} /> */}
|
|
3247
3263
|
<InputToggle
|
|
3248
3264
|
type='3d'
|
|
3249
3265
|
section='general'
|
|
@@ -3527,7 +3543,11 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
|
|
|
3527
3543
|
)
|
|
3528
3544
|
})}
|
|
3529
3545
|
|
|
3530
|
-
<button
|
|
3546
|
+
<button
|
|
3547
|
+
type='button'
|
|
3548
|
+
onClick={() => editCityStyles('add', 0, '', '')}
|
|
3549
|
+
className='btn btn-primary full-width'
|
|
3550
|
+
>
|
|
3531
3551
|
Add city style
|
|
3532
3552
|
</button>
|
|
3533
3553
|
</>
|
|
@@ -3629,7 +3649,7 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
|
|
|
3629
3649
|
</>
|
|
3630
3650
|
)
|
|
3631
3651
|
})}
|
|
3632
|
-
<button className={'btn full-width'} onClick={handleAddLayer}>
|
|
3652
|
+
<button className={'btn btn-primary full-width'} onClick={handleAddLayer}>
|
|
3633
3653
|
Add Map Layer
|
|
3634
3654
|
</button>
|
|
3635
3655
|
<p className='layer-purpose-details'>
|
|
@@ -11,7 +11,12 @@ import ConfigContext from '../../../../context'
|
|
|
11
11
|
// styles
|
|
12
12
|
|
|
13
13
|
const PanelAnnotate: React.FC = props => {
|
|
14
|
-
const {
|
|
14
|
+
const {
|
|
15
|
+
state: config,
|
|
16
|
+
setState: updateConfig,
|
|
17
|
+
dimensions,
|
|
18
|
+
isDraggingAnnotation
|
|
19
|
+
} = useContext<MapContext>(ConfigContext)
|
|
15
20
|
const getColumns = (filter = true) => {
|
|
16
21
|
const columns = {}
|
|
17
22
|
config.data.forEach(row => {
|
|
@@ -20,7 +25,10 @@ const PanelAnnotate: React.FC = props => {
|
|
|
20
25
|
|
|
21
26
|
if (filter) {
|
|
22
27
|
Object.keys(columns).forEach(key => {
|
|
23
|
-
if (
|
|
28
|
+
if (
|
|
29
|
+
(config.series && config.series.filter(series => series.dataKey === key).length > 0) ||
|
|
30
|
+
(config.confidenceKeys && Object.keys(config.confidenceKeys).includes(key))
|
|
31
|
+
) {
|
|
24
32
|
delete columns[key]
|
|
25
33
|
}
|
|
26
34
|
})
|
|
@@ -41,7 +49,9 @@ const PanelAnnotate: React.FC = props => {
|
|
|
41
49
|
}
|
|
42
50
|
|
|
43
51
|
const handleAddAnnotation = () => {
|
|
44
|
-
const svgContainer = document
|
|
52
|
+
const svgContainer = document
|
|
53
|
+
.querySelector('.map-container > section > svg, .map-container > section > canvas')
|
|
54
|
+
?.getBoundingClientRect()
|
|
45
55
|
const newSvgDims = [svgContainer.width, svgContainer.height]
|
|
46
56
|
|
|
47
57
|
const newAnnotation = {
|
|
@@ -139,11 +149,17 @@ const PanelAnnotate: React.FC = props => {
|
|
|
139
149
|
{config?.annotations &&
|
|
140
150
|
config?.annotations.map((annotation, index) => (
|
|
141
151
|
<Accordion>
|
|
142
|
-
<Accordion.Section
|
|
152
|
+
<Accordion.Section
|
|
153
|
+
title={annotation.text ? annotation.text.substring(0, 15) + '...' : `Annotation ${index + 1}`}
|
|
154
|
+
>
|
|
143
155
|
<div className='annotation-group'>
|
|
144
156
|
<label>
|
|
145
157
|
Annotation Text:
|
|
146
|
-
<textarea
|
|
158
|
+
<textarea
|
|
159
|
+
rows={5}
|
|
160
|
+
value={annotation.text}
|
|
161
|
+
onChange={e => handleAnnotationUpdate(e.target.value, 'text', index)}
|
|
162
|
+
/>
|
|
147
163
|
</label>
|
|
148
164
|
{/* <label>
|
|
149
165
|
Vertical Anchor
|
|
@@ -304,30 +320,18 @@ const PanelAnnotate: React.FC = props => {
|
|
|
304
320
|
</select>
|
|
305
321
|
</label>
|
|
306
322
|
|
|
307
|
-
{
|
|
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)}>
|
|
323
|
+
<Button className='btn btn-danger' onClick={() => handleRemoveAnnotation(index)}>
|
|
324
324
|
Delete Annotation
|
|
325
325
|
</Button>
|
|
326
326
|
</div>
|
|
327
327
|
</Accordion.Section>
|
|
328
328
|
</Accordion>
|
|
329
329
|
))}
|
|
330
|
-
{config?.annotations?.length < 3 &&
|
|
330
|
+
{config?.annotations?.length < 3 && (
|
|
331
|
+
<button className='btn btn-primary full-width' onClick={handleAddAnnotation}>
|
|
332
|
+
Add Annotation
|
|
333
|
+
</button>
|
|
334
|
+
)}
|
|
331
335
|
</Accordion.Section>
|
|
332
336
|
</Accordion>
|
|
333
337
|
)
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { useContext } from 'react'
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
Accordion,
|
|
4
|
+
AccordionItem,
|
|
5
|
+
AccordionItemHeading,
|
|
6
|
+
AccordionItemPanel,
|
|
7
|
+
AccordionItemButton
|
|
8
|
+
} from 'react-accessible-accordion'
|
|
3
9
|
import ConfigContext from '../../../../context'
|
|
4
10
|
import { type MapContext } from '../../../../types/MapContext'
|
|
5
11
|
import Button from '@cdc/core/components/elements/Button'
|
|
@@ -41,7 +47,11 @@ const PatternSettings = ({ name }: PanelProps) => {
|
|
|
41
47
|
}
|
|
42
48
|
|
|
43
49
|
/** Updates the map pattern at a given index */
|
|
44
|
-
const handleUpdateGeoPattern = (
|
|
50
|
+
const handleUpdateGeoPattern = (
|
|
51
|
+
value: string,
|
|
52
|
+
index: number,
|
|
53
|
+
keyToUpdate: 'dataKey' | 'pattern' | 'dataValue' | 'size' | 'label' | 'color'
|
|
54
|
+
) => {
|
|
45
55
|
const { features: unitedStates } = feature(topoJSON, topoJSON.objects.states)
|
|
46
56
|
const updatedPatterns = [...state.map.patterns]
|
|
47
57
|
|
|
@@ -68,7 +78,9 @@ const PatternSettings = ({ name }: PanelProps) => {
|
|
|
68
78
|
|
|
69
79
|
// Log a warning if the contrast check fails
|
|
70
80
|
if (!contrastCheck) {
|
|
71
|
-
console.warn(`COVE: pattern contrast check failed on ${geoData?.[state.columns.geo.name]} for ${
|
|
81
|
+
console.warn(`COVE: pattern contrast check failed on ${geoData?.[state.columns.geo.name]} for ${
|
|
82
|
+
patternData.dataKey
|
|
83
|
+
} with:
|
|
72
84
|
pattern color: ${patternColor}
|
|
73
85
|
contrast: ${getColorContrast(currentFill, patternColor)}
|
|
74
86
|
`)
|
|
@@ -78,7 +90,9 @@ const PatternSettings = ({ name }: PanelProps) => {
|
|
|
78
90
|
})
|
|
79
91
|
})
|
|
80
92
|
|
|
81
|
-
const editorErrorMessage = updatedPatterns.some(pattern => pattern.contrastCheck === false)
|
|
93
|
+
const editorErrorMessage = updatedPatterns.some(pattern => pattern.contrastCheck === false)
|
|
94
|
+
? 'One or more patterns do not pass the WCAG 2.1 contrast ratio of 3:1.'
|
|
95
|
+
: ''
|
|
82
96
|
|
|
83
97
|
// Update the state with the new patterns and error message
|
|
84
98
|
setState(prevState => ({
|
|
@@ -116,7 +130,12 @@ const PatternSettings = ({ name }: PanelProps) => {
|
|
|
116
130
|
<AccordionItemButton>{name}</AccordionItemButton>
|
|
117
131
|
</AccordionItemHeading>
|
|
118
132
|
<AccordionItemPanel>
|
|
119
|
-
{patterns.length > 0 &&
|
|
133
|
+
{patterns.length > 0 && (
|
|
134
|
+
<Alert
|
|
135
|
+
type={checkPatternContrasts() ? 'success' : 'danger'}
|
|
136
|
+
message='Pattern colors must comply with <br /> <a href="https://www.w3.org/TR/WCAG21/">WCAG 2.1</a> 3:1 contrast ratio.'
|
|
137
|
+
/>
|
|
138
|
+
)}
|
|
120
139
|
<br />
|
|
121
140
|
|
|
122
141
|
{patterns &&
|
|
@@ -133,13 +152,26 @@ const PatternSettings = ({ name }: PanelProps) => {
|
|
|
133
152
|
<Accordion allowZeroExpanded key={`accordion-pattern--${patternIndex}`}>
|
|
134
153
|
<AccordionItem>
|
|
135
154
|
<AccordionItemHeading>
|
|
136
|
-
<AccordionItemButton>
|
|
155
|
+
<AccordionItemButton>
|
|
156
|
+
{pattern.dataKey ? `${pattern.dataKey}: ${pattern.dataValue ?? 'No Value'}` : 'Select Column'}
|
|
157
|
+
</AccordionItemButton>
|
|
137
158
|
</AccordionItemHeading>
|
|
138
159
|
<AccordionItemPanel>
|
|
139
160
|
<>
|
|
140
|
-
{pattern.contrastCheck ?? true ?
|
|
161
|
+
{pattern.contrastCheck ?? true ? (
|
|
162
|
+
<Alert type='success' message='This pattern passes contrast checks' />
|
|
163
|
+
) : (
|
|
164
|
+
<Alert
|
|
165
|
+
type='danger'
|
|
166
|
+
message='Error: <a href="https://webaim.org/resources/contrastchecker/" target="_blank"> Review Color Contrast</a>'
|
|
167
|
+
/>
|
|
168
|
+
)}{' '}
|
|
141
169
|
<label htmlFor={`pattern-dataKey--${patternIndex}`}>Data Key:</label>
|
|
142
|
-
<select
|
|
170
|
+
<select
|
|
171
|
+
id={`pattern-dataKey--${patternIndex}`}
|
|
172
|
+
value={pattern.dataKey !== '' ? pattern.dataKey : 'Select'}
|
|
173
|
+
onChange={e => handleUpdateGeoPattern(e.target.value, patternIndex, 'dataKey')}
|
|
174
|
+
>
|
|
143
175
|
{/* TODO: sort these? */}
|
|
144
176
|
{dataKeyOptions.map((d, index) => {
|
|
145
177
|
return (
|
|
@@ -151,14 +183,28 @@ const PatternSettings = ({ name }: PanelProps) => {
|
|
|
151
183
|
</select>
|
|
152
184
|
<label htmlFor={`pattern-dataValue--${patternIndex}`}>
|
|
153
185
|
Data Value:
|
|
154
|
-
<input
|
|
186
|
+
<input
|
|
187
|
+
type='text'
|
|
188
|
+
onChange={e => handleUpdateGeoPattern(e.target.value, patternIndex, 'dataValue')}
|
|
189
|
+
id={`pattern-dataValue--${patternIndex}`}
|
|
190
|
+
value={pattern.dataValue === '' ? '' : pattern.dataValue}
|
|
191
|
+
/>
|
|
155
192
|
</label>
|
|
156
193
|
<label htmlFor={`pattern-label--${patternIndex}`}>
|
|
157
194
|
Label (optional):
|
|
158
|
-
<input
|
|
195
|
+
<input
|
|
196
|
+
type='text'
|
|
197
|
+
onChange={e => handleUpdateGeoPattern(e.target.value, patternIndex, 'label')}
|
|
198
|
+
id={`pattern-dataValue--${patternIndex}`}
|
|
199
|
+
value={pattern.label === '' ? '' : pattern.label}
|
|
200
|
+
/>
|
|
159
201
|
</label>
|
|
160
202
|
<label htmlFor={`pattern-type--${patternIndex}`}>Pattern Type:</label>
|
|
161
|
-
<select
|
|
203
|
+
<select
|
|
204
|
+
id={`pattern-type--${patternIndex}`}
|
|
205
|
+
value={pattern?.pattern}
|
|
206
|
+
onChange={e => handleUpdateGeoPattern(e.target.value, patternIndex, 'pattern')}
|
|
207
|
+
>
|
|
162
208
|
{patternTypes.map((patternName, index) => (
|
|
163
209
|
<option value={patternName} key={index}>
|
|
164
210
|
{patternName}
|
|
@@ -166,7 +212,11 @@ const PatternSettings = ({ name }: PanelProps) => {
|
|
|
166
212
|
))}
|
|
167
213
|
</select>
|
|
168
214
|
<label htmlFor={`pattern-size--${patternIndex}`}>Pattern Size:</label>
|
|
169
|
-
<select
|
|
215
|
+
<select
|
|
216
|
+
id={`pattern-size--${patternIndex}`}
|
|
217
|
+
value={pattern?.size}
|
|
218
|
+
onChange={e => handleUpdateGeoPattern(e.target.value, patternIndex, 'size')}
|
|
219
|
+
>
|
|
170
220
|
{['small', 'medium', 'large'].map((size, index) => (
|
|
171
221
|
<option value={size} key={index}>
|
|
172
222
|
{size}
|
|
@@ -178,16 +228,25 @@ const PatternSettings = ({ name }: PanelProps) => {
|
|
|
178
228
|
Pattern Color
|
|
179
229
|
<Tooltip style={{ textTransform: 'none' }}>
|
|
180
230
|
<Tooltip.Target>
|
|
181
|
-
<Icon
|
|
231
|
+
<Icon
|
|
232
|
+
display='question'
|
|
233
|
+
style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }}
|
|
234
|
+
/>
|
|
182
235
|
</Tooltip.Target>
|
|
183
236
|
<Tooltip.Content>
|
|
184
237
|
<p>{`If this setting is used, it is the reponsibility of the visualization author to verify the visualization colors meet WCAG 3:1 contrast ratios.`}</p>
|
|
185
238
|
</Tooltip.Content>
|
|
186
239
|
</Tooltip>
|
|
187
|
-
<input
|
|
240
|
+
<input
|
|
241
|
+
type='text'
|
|
242
|
+
value={pattern.color || ''}
|
|
243
|
+
id='patternColor'
|
|
244
|
+
name='patternColor'
|
|
245
|
+
onChange={e => handleUpdateGeoPattern(e.target.value, patternIndex, 'color')}
|
|
246
|
+
/>
|
|
188
247
|
</label>
|
|
189
248
|
</div>
|
|
190
|
-
<Button onClick={e => handleRemovePattern(patternIndex)} className='btn btn
|
|
249
|
+
<Button onClick={e => handleRemovePattern(patternIndex)} className='btn btn-danger'>
|
|
191
250
|
Remove Pattern
|
|
192
251
|
</Button>
|
|
193
252
|
</>
|
|
@@ -196,7 +255,7 @@ const PatternSettings = ({ name }: PanelProps) => {
|
|
|
196
255
|
</Accordion>
|
|
197
256
|
)
|
|
198
257
|
})}
|
|
199
|
-
<button className='btn full-width' onClick={handleAddGeoPattern}>
|
|
258
|
+
<button className='btn btn-primary full-width mt-2' onClick={handleAddGeoPattern}>
|
|
200
259
|
Add Geo Pattern
|
|
201
260
|
</button>
|
|
202
261
|
</AccordionItemPanel>
|