@cdc/map 4.24.10 → 4.24.11
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 +21026 -20881
- package/examples/private/DEV-9644.json +184 -0
- package/package.json +3 -3
- package/src/CdcMap.tsx +11 -4
- package/src/_stories/CdcMapLegend.stories.tsx +86 -0
- package/src/_stories/_mock/usa-state-gradient.json +238 -0
- package/src/_stories/_mock/wastewater-map.json +208 -0
- package/src/components/EditorPanel/components/EditorPanel.tsx +38 -48
- 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 +23 -17
- package/src/components/Legend/components/index.scss +9 -2
- package/src/components/UsaMap/components/HexIcon.tsx +7 -1
- package/src/components/UsaMap/components/Territory/Territory.Hexagon.tsx +57 -12
- package/src/components/UsaMap/components/Territory/Territory.Rectangle.tsx +88 -15
- package/src/components/UsaMap/components/Territory/TerritoryShape.ts +13 -0
- package/src/components/UsaMap/components/UsaMap.County.tsx +1 -1
- package/src/components/UsaMap/components/UsaMap.State.tsx +3 -3
- package/src/components/UsaMap/helpers/shapes.ts +5 -4
- package/src/scss/editor-panel.scss +0 -3
- package/src/scss/filters.scss +2 -7
- package/src/scss/main.scss +0 -4
- package/src/scss/map.scss +0 -4
- package/src/types/MapConfig.ts +1 -1
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
{
|
|
2
|
+
"annotations": [],
|
|
3
|
+
"general": {
|
|
4
|
+
"geoBorderColor": "darkGray",
|
|
5
|
+
"headerColor": "theme-blue",
|
|
6
|
+
"title": "",
|
|
7
|
+
"showTitle": true,
|
|
8
|
+
"showSidebar": true,
|
|
9
|
+
"showDownloadButton": true,
|
|
10
|
+
"showDownloadMediaButton": false,
|
|
11
|
+
"displayAsHex": false,
|
|
12
|
+
"displayStateLabels": false,
|
|
13
|
+
"territoriesLabel": "Territories",
|
|
14
|
+
"territoriesAlwaysShow": false,
|
|
15
|
+
"language": "en",
|
|
16
|
+
"geoType": "us",
|
|
17
|
+
"geoLabelOverride": "State/Territory",
|
|
18
|
+
"hasRegions": false,
|
|
19
|
+
"fullBorder": false,
|
|
20
|
+
"type": "map",
|
|
21
|
+
"convertFipsCodes": true,
|
|
22
|
+
"palette": { "isReversed": false },
|
|
23
|
+
"allowMapZoom": true,
|
|
24
|
+
"hideGeoColumnInTooltip": false,
|
|
25
|
+
"hidePrimaryColumnInTooltip": false,
|
|
26
|
+
"statePicked": { "fipsCode": "01", "stateName": "Alabama" },
|
|
27
|
+
"expandDataTable": false,
|
|
28
|
+
"annotationDropdownText": "Annotations",
|
|
29
|
+
"noStateFoundMessage": "Map Unavailable",
|
|
30
|
+
"subtext": "<div class=\"text-left\">Data last updated on <span data-timestamp=\"nwss_rsv_sc2_flua_combined_state_map:Data_as_of\"></span> and presented through <span data-timestamp=\"nwss_rsv_sc2_flua_combined_state_map:Data_Presented_Through\"></span>. "
|
|
31
|
+
},
|
|
32
|
+
"type": "map",
|
|
33
|
+
"color": "greenbluereverse",
|
|
34
|
+
"columns": {
|
|
35
|
+
"geo": {
|
|
36
|
+
"name": "State",
|
|
37
|
+
"label": "Location",
|
|
38
|
+
"tooltip": false,
|
|
39
|
+
"dataTable": true
|
|
40
|
+
},
|
|
41
|
+
"primary": {
|
|
42
|
+
"dataTable": true,
|
|
43
|
+
"tooltip": true,
|
|
44
|
+
"prefix": "",
|
|
45
|
+
"suffix": "",
|
|
46
|
+
"name": "activity_level_label",
|
|
47
|
+
"label": "Viral Activity Level",
|
|
48
|
+
"roundToPlace": 0
|
|
49
|
+
},
|
|
50
|
+
"navigate": { "name": "" },
|
|
51
|
+
"latitude": { "name": "" },
|
|
52
|
+
"longitude": { "name": "" },
|
|
53
|
+
"additionalColumn1": {
|
|
54
|
+
"label": "Sites Currently Reporting",
|
|
55
|
+
"dataTable": true,
|
|
56
|
+
"tooltips": false,
|
|
57
|
+
"prefix": "",
|
|
58
|
+
"suffix": "",
|
|
59
|
+
"name": "num_sites",
|
|
60
|
+
"tooltip": true
|
|
61
|
+
},
|
|
62
|
+
"additionalColumn2": {
|
|
63
|
+
"label": "Limited Coverage",
|
|
64
|
+
"dataTable": true,
|
|
65
|
+
"tooltips": false,
|
|
66
|
+
"prefix": "",
|
|
67
|
+
"suffix": "",
|
|
68
|
+
"tooltip": false,
|
|
69
|
+
"name": "hatch"
|
|
70
|
+
},
|
|
71
|
+
"additionalColumn3": {
|
|
72
|
+
"label": "",
|
|
73
|
+
"dataTable": false,
|
|
74
|
+
"tooltips": false,
|
|
75
|
+
"prefix": "",
|
|
76
|
+
"suffix": "",
|
|
77
|
+
"tooltip": true,
|
|
78
|
+
"useCommas": true,
|
|
79
|
+
"name": "hatch"
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
"legend": {
|
|
83
|
+
"descriptions": {},
|
|
84
|
+
"specialClasses": [],
|
|
85
|
+
"unified": false,
|
|
86
|
+
"singleColumn": false,
|
|
87
|
+
"singleRow": true,
|
|
88
|
+
"verticalSorted": false,
|
|
89
|
+
"showSpecialClassesLast": true,
|
|
90
|
+
"dynamicDescription": false,
|
|
91
|
+
"type": "category",
|
|
92
|
+
"numberOfItems": 8,
|
|
93
|
+
"position": "bottom",
|
|
94
|
+
"title": "Wastewater Viral Activity Level",
|
|
95
|
+
"categoryValuesOrder": [
|
|
96
|
+
"Very High",
|
|
97
|
+
"High",
|
|
98
|
+
"Moderate",
|
|
99
|
+
"Low",
|
|
100
|
+
"Minimal",
|
|
101
|
+
"No Data"
|
|
102
|
+
],
|
|
103
|
+
"additionalCategories": [
|
|
104
|
+
"No Data",
|
|
105
|
+
"Minimal",
|
|
106
|
+
"Low",
|
|
107
|
+
"Moderate",
|
|
108
|
+
"High",
|
|
109
|
+
"Very High"
|
|
110
|
+
],
|
|
111
|
+
"description": "",
|
|
112
|
+
"style": "gradient",
|
|
113
|
+
"subStyle": "linear blocks",
|
|
114
|
+
"tickRotation": "",
|
|
115
|
+
"singleColumnLegend": false,
|
|
116
|
+
"hideBorder": false
|
|
117
|
+
},
|
|
118
|
+
"filters": [
|
|
119
|
+
{
|
|
120
|
+
"order": "asc",
|
|
121
|
+
"label": "Select a virus from the dropdown:",
|
|
122
|
+
"columnName": "pathogen",
|
|
123
|
+
"values": ["COVID-19", "Influenza A", "RSV"],
|
|
124
|
+
"active": "COVID-19",
|
|
125
|
+
"filterStyle": "dropdown"
|
|
126
|
+
}
|
|
127
|
+
],
|
|
128
|
+
"table": {
|
|
129
|
+
"label": "Data Table",
|
|
130
|
+
"expanded": false,
|
|
131
|
+
"limitHeight": true,
|
|
132
|
+
"height": "500",
|
|
133
|
+
"caption": "",
|
|
134
|
+
"showDownloadUrl": true,
|
|
135
|
+
"showDataTableLink": true,
|
|
136
|
+
"showFullGeoNameInCSV": false,
|
|
137
|
+
"forceDisplay": true,
|
|
138
|
+
"download": true,
|
|
139
|
+
"indexLabel": "State/Territory",
|
|
140
|
+
"wrapColumns": false
|
|
141
|
+
},
|
|
142
|
+
"tooltips": {
|
|
143
|
+
"appearanceType": "hover",
|
|
144
|
+
"linkLabel": "Learn More",
|
|
145
|
+
"capitalizeLabels": true,
|
|
146
|
+
"opacity": 90
|
|
147
|
+
},
|
|
148
|
+
"visual": {
|
|
149
|
+
"minBubbleSize": 1,
|
|
150
|
+
"maxBubbleSize": 20,
|
|
151
|
+
"extraBubbleBorder": false,
|
|
152
|
+
"cityStyle": "circle",
|
|
153
|
+
"geoCodeCircleSize": 2,
|
|
154
|
+
"showBubbleZeros": false,
|
|
155
|
+
"cityStyleLabel": "",
|
|
156
|
+
"additionalCityStyles": []
|
|
157
|
+
},
|
|
158
|
+
"mapPosition": { "coordinates": [0, 30], "zoom": 1 },
|
|
159
|
+
"map": {
|
|
160
|
+
"layers": [],
|
|
161
|
+
"patterns": [
|
|
162
|
+
{
|
|
163
|
+
"dataKey": "hatch",
|
|
164
|
+
"pattern": "lines",
|
|
165
|
+
"dataValue": "Limited Coverage",
|
|
166
|
+
"label": "Limited Coverage*",
|
|
167
|
+
"size": "medium"
|
|
168
|
+
}
|
|
169
|
+
]
|
|
170
|
+
},
|
|
171
|
+
"hexMap": {
|
|
172
|
+
"type": "",
|
|
173
|
+
"shapeGroups": [
|
|
174
|
+
{
|
|
175
|
+
"legendTitle": "",
|
|
176
|
+
"legendDescription": "",
|
|
177
|
+
"items": [
|
|
178
|
+
{
|
|
179
|
+
"key": "",
|
|
180
|
+
"shape": "Arrow Up",
|
|
181
|
+
"column": "",
|
|
182
|
+
"operator": "=",
|
|
183
|
+
"value": ""
|
|
184
|
+
}
|
|
185
|
+
]
|
|
186
|
+
}
|
|
187
|
+
]
|
|
188
|
+
},
|
|
189
|
+
"filterBehavior": "Filter Change",
|
|
190
|
+
"customColors": [
|
|
191
|
+
"#34547B",
|
|
192
|
+
"#4B7F9B",
|
|
193
|
+
"#4B7F9B",
|
|
194
|
+
"#6BB0BD",
|
|
195
|
+
"#9FDAD0",
|
|
196
|
+
"#C8EFDA",
|
|
197
|
+
"#B4B4B4",
|
|
198
|
+
"#B4B4B4",
|
|
199
|
+
"#B4B4B4",
|
|
200
|
+
"#B4B4B4"
|
|
201
|
+
],
|
|
202
|
+
"datasets": {},
|
|
203
|
+
"dataFileName": "https://www.cdc.gov/wcms/vizdata/ncezid_didri/NWSSStateMapCombined.json",
|
|
204
|
+
"dataFileSourceType": "url",
|
|
205
|
+
"dataDescription": { "horizontal": false, "series": false },
|
|
206
|
+
"version": "4.24.9",
|
|
207
|
+
"dataUrl": "https://www.cdc.gov/wcms/vizdata/ncezid_didri/NWSSStateMapCombined.json"
|
|
208
|
+
}
|
|
@@ -109,12 +109,18 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
|
|
|
109
109
|
} = useMapLayers(state, setState, false, tooltipId)
|
|
110
110
|
|
|
111
111
|
const categoryMove = (idx1, idx2) => {
|
|
112
|
-
let categoryValuesOrder =
|
|
112
|
+
let categoryValuesOrder = getCategoryValuesOrder()
|
|
113
113
|
|
|
114
114
|
let [movedItem] = categoryValuesOrder.splice(idx1, 1)
|
|
115
115
|
|
|
116
116
|
categoryValuesOrder.splice(idx2, 0, movedItem)
|
|
117
117
|
|
|
118
|
+
state.legend.categoryValuesOrder?.forEach(value => {
|
|
119
|
+
if(categoryValuesOrder.indexOf(value) === -1){
|
|
120
|
+
categoryValuesOrder.push(value)
|
|
121
|
+
}
|
|
122
|
+
})
|
|
123
|
+
|
|
118
124
|
setState({
|
|
119
125
|
...state,
|
|
120
126
|
legend: {
|
|
@@ -1219,40 +1225,6 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
|
|
|
1219
1225
|
columnsRequiredChecker()
|
|
1220
1226
|
}, [state]) // eslint-disable-line
|
|
1221
1227
|
|
|
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
1228
|
const columnsOptions = [
|
|
1257
1229
|
<option value='' key={'Select Option'}>
|
|
1258
1230
|
- Select Option -
|
|
@@ -1530,9 +1502,26 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
|
|
|
1530
1502
|
...draggableStyle
|
|
1531
1503
|
})
|
|
1532
1504
|
|
|
1505
|
+
const getCategoryValuesOrder = () => {
|
|
1506
|
+
let values = runtimeLegend ? runtimeLegend.filter(item => !item.special).map(runtimeLegendItem => runtimeLegendItem.value) : []
|
|
1507
|
+
|
|
1508
|
+
if(state.legend.cateogryValuesOrder){
|
|
1509
|
+
return values.sort((a, b) => {
|
|
1510
|
+
let aVal = state.legend.cateogryValuesOrder.indexOf(a)
|
|
1511
|
+
let bVal = state.legend.cateogryValuesOrder.indexOf(b)
|
|
1512
|
+
if (aVal === bVal) return 0
|
|
1513
|
+
if (aVal === -1) return 1
|
|
1514
|
+
if (bVal === -1) return -1
|
|
1515
|
+
return aVal - bVal
|
|
1516
|
+
})
|
|
1517
|
+
} else {
|
|
1518
|
+
return values
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1533
1523
|
const CategoryList = () => {
|
|
1534
|
-
return
|
|
1535
|
-
state.legend.categoryValuesOrder.map((value, index) => (
|
|
1524
|
+
return getCategoryValuesOrder().filter(item => !item.special).map((value, index) => (
|
|
1536
1525
|
<Draggable key={value} draggableId={`item-${value}`} index={index}>
|
|
1537
1526
|
{(provided, snapshot) => (
|
|
1538
1527
|
<li style={{ position: 'relative' }}>
|
|
@@ -1548,10 +1537,7 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
|
|
|
1548
1537
|
</li>
|
|
1549
1538
|
)}
|
|
1550
1539
|
</Draggable>
|
|
1551
|
-
|
|
1552
|
-
) : (
|
|
1553
|
-
<></>
|
|
1554
|
-
)
|
|
1540
|
+
))
|
|
1555
1541
|
}
|
|
1556
1542
|
|
|
1557
1543
|
const isLoadedFromUrl = state?.dataKey?.includes('http://') || state?.dataKey?.includes('https://')
|
|
@@ -2266,7 +2252,7 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
|
|
|
2266
2252
|
</div>
|
|
2267
2253
|
))}
|
|
2268
2254
|
<button
|
|
2269
|
-
className='btn full-width'
|
|
2255
|
+
className='btn btn-primary full-width'
|
|
2270
2256
|
onClick={e => {
|
|
2271
2257
|
e.preventDefault()
|
|
2272
2258
|
editColumn('primary', 'specialClassAdd', {})
|
|
@@ -2417,7 +2403,7 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
|
|
|
2417
2403
|
</fieldset>
|
|
2418
2404
|
))}
|
|
2419
2405
|
<button
|
|
2420
|
-
className={'btn full-width'}
|
|
2406
|
+
className={'btn btn-primary full-width'}
|
|
2421
2407
|
onClick={event => {
|
|
2422
2408
|
event.preventDefault()
|
|
2423
2409
|
addAdditionalColumn(additionalColumns.length + 1)
|
|
@@ -2473,7 +2459,7 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
|
|
|
2473
2459
|
</fieldset>
|
|
2474
2460
|
))}
|
|
2475
2461
|
<button
|
|
2476
|
-
className={'btn full-width'}
|
|
2462
|
+
className={'btn btn-primary full-width'}
|
|
2477
2463
|
onClick={event => {
|
|
2478
2464
|
event.preventDefault()
|
|
2479
2465
|
const updatedAdditionaCategories = [...(state.legend.additionalCategories || [])]
|
|
@@ -2784,7 +2770,7 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
|
|
|
2784
2770
|
)}
|
|
2785
2771
|
</Droppable>
|
|
2786
2772
|
</DragDropContext>
|
|
2787
|
-
{
|
|
2773
|
+
{runtimeLegend && runtimeLegend.length >= 10 && (
|
|
2788
2774
|
<section className='error-box my-2'>
|
|
2789
2775
|
<div>
|
|
2790
2776
|
<strong className='pt-1'>Warning</strong>
|
|
@@ -2911,7 +2897,7 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
|
|
|
2911
2897
|
<p style={{ textAlign: 'center' }}>There are currently no filters.</p>
|
|
2912
2898
|
)}
|
|
2913
2899
|
<button
|
|
2914
|
-
className={'btn full-width'}
|
|
2900
|
+
className={'btn btn-primary full-width'}
|
|
2915
2901
|
onClick={event => {
|
|
2916
2902
|
event.preventDefault()
|
|
2917
2903
|
changeFilter(null, 'addNew')
|
|
@@ -3527,7 +3513,11 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
|
|
|
3527
3513
|
)
|
|
3528
3514
|
})}
|
|
3529
3515
|
|
|
3530
|
-
<button
|
|
3516
|
+
<button
|
|
3517
|
+
type='button'
|
|
3518
|
+
onClick={() => editCityStyles('add', 0, '', '')}
|
|
3519
|
+
className='btn btn-primary full-width'
|
|
3520
|
+
>
|
|
3531
3521
|
Add city style
|
|
3532
3522
|
</button>
|
|
3533
3523
|
</>
|
|
@@ -3629,7 +3619,7 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
|
|
|
3629
3619
|
</>
|
|
3630
3620
|
)
|
|
3631
3621
|
})}
|
|
3632
|
-
<button className={'btn full-width'} onClick={handleAddLayer}>
|
|
3622
|
+
<button className={'btn btn-primary full-width'} onClick={handleAddLayer}>
|
|
3633
3623
|
Add Map Layer
|
|
3634
3624
|
</button>
|
|
3635
3625
|
<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' onClick={handleAddGeoPattern}>
|
|
200
259
|
Add Geo Pattern
|
|
201
260
|
</button>
|
|
202
261
|
</AccordionItemPanel>
|