@cdc/map 4.24.11 → 4.24.12-2
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 +34008 -33475
- 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-9989.json +229 -0
- package/examples/private/ardi.json +180 -0
- package/examples/private/colors 2.json +416 -0
- package/examples/private/colors.json +416 -0
- package/examples/private/colors.json.zip +0 -0
- package/examples/private/customColors.json +45348 -0
- package/examples/private/default-patterns.json +867 -0
- package/examples/private/test.json +1632 -0
- package/index.html +4 -5
- package/package.json +3 -3
- package/src/CdcMap.tsx +82 -79
- package/src/_stories/{CdcMapLegend.stories.tsx → CdcMap.Legend.Gradient.stories.tsx} +1 -20
- 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/components/CityList.tsx +5 -2
- package/src/components/EditorPanel/components/EditorPanel.tsx +39 -256
- package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +1 -1
- package/src/components/Legend/components/Legend.tsx +22 -6
- package/src/components/Legend/components/index.scss +16 -23
- 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 +1 -1
- package/src/components/UsaMap/components/Territory/Territory.Rectangle.tsx +14 -13
- 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 +58 -60
- 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/filters.scss +0 -3
- package/src/scss/main.scss +0 -1
- package/src/scss/map.scss +11 -59
- package/src/types/MapConfig.ts +7 -1
- package/src/types/MapContext.ts +1 -0
- package/examples/default-patterns.json +0 -579
- package/src/scss/datatable.scss +0 -6
|
@@ -7,6 +7,7 @@ import { GlyphStar, GlyphTriangle, GlyphDiamond, GlyphSquare, GlyphCircle } from
|
|
|
7
7
|
import { getFilterControllingStatePicked } from './UsaMap/helpers/map'
|
|
8
8
|
|
|
9
9
|
import ConfigContext from '../context'
|
|
10
|
+
import { getGeoStrokeColor } from '../helpers/colors'
|
|
10
11
|
|
|
11
12
|
const CityList = ({
|
|
12
13
|
data,
|
|
@@ -79,6 +80,8 @@ const CityList = ({
|
|
|
79
80
|
fillOpacity: state.general.type === 'bubble' ? 0.4 : 1
|
|
80
81
|
}
|
|
81
82
|
|
|
83
|
+
const geoStrokeColor = getGeoStrokeColor(state)
|
|
84
|
+
|
|
82
85
|
const pin = (
|
|
83
86
|
<path
|
|
84
87
|
className='marker'
|
|
@@ -88,7 +91,7 @@ const CityList = ({
|
|
|
88
91
|
data-tooltip-id={`tooltip__${tooltipId}`}
|
|
89
92
|
data-tooltip-html={toolTip}
|
|
90
93
|
transform={`scale(${radius / 9})`}
|
|
91
|
-
stroke={
|
|
94
|
+
stroke={geoStrokeColor}
|
|
92
95
|
strokeWidth={'2px'}
|
|
93
96
|
tabIndex='-1'
|
|
94
97
|
{...additionalProps}
|
|
@@ -185,7 +188,7 @@ const CityList = ({
|
|
|
185
188
|
title: 'Select for more information',
|
|
186
189
|
'data-tooltip-id': `tooltip__${tooltipId}`,
|
|
187
190
|
'data-tooltip-html': toolTip,
|
|
188
|
-
stroke:
|
|
191
|
+
stroke: geoStrokeColor,
|
|
189
192
|
strokeWidth: '2px',
|
|
190
193
|
tabIndex: -1,
|
|
191
194
|
...additionalProps
|
|
@@ -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'
|
|
@@ -25,6 +26,7 @@ import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
|
25
26
|
import Icon from '@cdc/core/components/ui/Icon'
|
|
26
27
|
import InputToggle from '@cdc/core/components/inputs/InputToggle'
|
|
27
28
|
import Tooltip from '@cdc/core/components/ui/Tooltip'
|
|
29
|
+
import VizFilterEditor from '@cdc/core/components/EditorPanel/VizFilterEditor'
|
|
28
30
|
|
|
29
31
|
// Assets
|
|
30
32
|
import UsaGraphic from '@cdc/core/assets/icon-map-usa.svg'
|
|
@@ -36,35 +38,31 @@ import usaDefaultConfig from '../../../../examples/default-usa.json'
|
|
|
36
38
|
import countyDefaultConfig from '../../../../examples/default-county.json'
|
|
37
39
|
import useMapLayers from '../../../hooks/useMapLayers.tsx'
|
|
38
40
|
|
|
39
|
-
import { useFilters } from '@cdc/core/components/Filters'
|
|
40
|
-
|
|
41
41
|
import HexSetting from './HexShapeSettings.jsx'
|
|
42
42
|
import ConfigContext from '../../../context.ts'
|
|
43
43
|
import { MapContext } from '../../../types/MapContext.js'
|
|
44
44
|
import { TextField } from './Inputs'
|
|
45
|
+
import Alert from '@cdc/core/components/Alert'
|
|
46
|
+
import { updateFieldFactory } from '@cdc/core/helpers/updateFieldFactory'
|
|
45
47
|
|
|
46
48
|
// Todo: move to useReducer, seperate files out.
|
|
47
49
|
const EditorPanel = ({ columnsRequiredChecker }) => {
|
|
48
50
|
// prettier-ignore
|
|
49
51
|
const {
|
|
50
|
-
changeFilterActive,
|
|
51
52
|
columnsInData = [],
|
|
52
53
|
isDashboard,
|
|
53
54
|
isDebug,
|
|
54
|
-
isEditor,
|
|
55
55
|
loadConfig,
|
|
56
56
|
runtimeFilters,
|
|
57
57
|
runtimeLegend,
|
|
58
58
|
setParentConfig,
|
|
59
|
-
setRuntimeFilters,
|
|
60
59
|
setState,
|
|
61
60
|
state,
|
|
62
61
|
tooltipId,
|
|
63
62
|
runtimeData,
|
|
64
63
|
setRuntimeData,
|
|
65
64
|
generateRuntimeData,
|
|
66
|
-
|
|
67
|
-
topoData,
|
|
65
|
+
|
|
68
66
|
|
|
69
67
|
} = useContext<MapContext>(ConfigContext)
|
|
70
68
|
|
|
@@ -78,13 +76,6 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
|
|
|
78
76
|
|
|
79
77
|
const [activeFilterValueForDescription, setActiveFilterValueForDescription] = useState([0, 0])
|
|
80
78
|
|
|
81
|
-
const { handleFilterOrder, filterOrderOptions, filterStyleOptions } = useFilters({
|
|
82
|
-
config: state,
|
|
83
|
-
setConfig: setState,
|
|
84
|
-
filteredData: runtimeFilters,
|
|
85
|
-
setFilteredData: setRuntimeFilters
|
|
86
|
-
})
|
|
87
|
-
|
|
88
79
|
const headerColors = [
|
|
89
80
|
'theme-blue',
|
|
90
81
|
'theme-purple',
|
|
@@ -116,7 +107,7 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
|
|
|
116
107
|
categoryValuesOrder.splice(idx2, 0, movedItem)
|
|
117
108
|
|
|
118
109
|
state.legend.categoryValuesOrder?.forEach(value => {
|
|
119
|
-
if(categoryValuesOrder.indexOf(value) === -1){
|
|
110
|
+
if (categoryValuesOrder.indexOf(value) === -1) {
|
|
120
111
|
categoryValuesOrder.push(value)
|
|
121
112
|
}
|
|
122
113
|
})
|
|
@@ -377,7 +368,8 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
|
|
|
377
368
|
...state,
|
|
378
369
|
legend: {
|
|
379
370
|
...state.legend,
|
|
380
|
-
position: value
|
|
371
|
+
position: value,
|
|
372
|
+
hideBorder: _.includes(['top', 'bottom'], value)
|
|
381
373
|
}
|
|
382
374
|
})
|
|
383
375
|
break
|
|
@@ -1071,33 +1063,6 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
|
|
|
1071
1063
|
})
|
|
1072
1064
|
}
|
|
1073
1065
|
|
|
1074
|
-
const MapFilters = () => {
|
|
1075
|
-
return (
|
|
1076
|
-
<>
|
|
1077
|
-
<label>
|
|
1078
|
-
Filter Behavior
|
|
1079
|
-
<select
|
|
1080
|
-
value={state.filterBehavior}
|
|
1081
|
-
onChange={e => {
|
|
1082
|
-
setState({
|
|
1083
|
-
...state,
|
|
1084
|
-
filterBehavior: e.target.value
|
|
1085
|
-
})
|
|
1086
|
-
}}
|
|
1087
|
-
>
|
|
1088
|
-
<option key='Apply Button' value='Apply Button'>
|
|
1089
|
-
Apply Button
|
|
1090
|
-
</option>
|
|
1091
|
-
<option key='Filter Change' value='Filter Change'>
|
|
1092
|
-
Filter Change
|
|
1093
|
-
</option>
|
|
1094
|
-
</select>
|
|
1095
|
-
</label>
|
|
1096
|
-
{filtersJSX}
|
|
1097
|
-
</>
|
|
1098
|
-
)
|
|
1099
|
-
}
|
|
1100
|
-
|
|
1101
1066
|
const removeAdditionalColumn = columnName => {
|
|
1102
1067
|
const newColumns = state.columns
|
|
1103
1068
|
|
|
@@ -1260,30 +1225,7 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
|
|
|
1260
1225
|
return true
|
|
1261
1226
|
})
|
|
1262
1227
|
|
|
1263
|
-
const updateField = (
|
|
1264
|
-
const isArray = Array.isArray(state[section])
|
|
1265
|
-
|
|
1266
|
-
let sectionValue = isArray ? [...state[section], newValue] : { ...state[section], [fieldName]: newValue }
|
|
1267
|
-
|
|
1268
|
-
if (null !== subsection) {
|
|
1269
|
-
if (isArray) {
|
|
1270
|
-
sectionValue = [...state[section]]
|
|
1271
|
-
sectionValue[subsection] = { ...sectionValue[subsection], [fieldName]: newValue }
|
|
1272
|
-
} else {
|
|
1273
|
-
sectionValue = {
|
|
1274
|
-
...state[section],
|
|
1275
|
-
[subsection]: { ...state[section][subsection], [fieldName]: newValue }
|
|
1276
|
-
}
|
|
1277
|
-
}
|
|
1278
|
-
}
|
|
1279
|
-
|
|
1280
|
-
let updatedState = {
|
|
1281
|
-
...state,
|
|
1282
|
-
[section]: sectionValue
|
|
1283
|
-
}
|
|
1284
|
-
|
|
1285
|
-
setState(updatedState)
|
|
1286
|
-
}
|
|
1228
|
+
const updateField = updateFieldFactory(state, setState)
|
|
1287
1229
|
|
|
1288
1230
|
const onBackClick = () => {
|
|
1289
1231
|
setDisplayPanel(!displayPanel)
|
|
@@ -1295,163 +1237,6 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
|
|
|
1295
1237
|
|
|
1296
1238
|
const usedFilterColumns = {}
|
|
1297
1239
|
|
|
1298
|
-
const filtersJSX = state.filters.map((filter, index) => {
|
|
1299
|
-
if (filter.type === 'url') return <></>
|
|
1300
|
-
|
|
1301
|
-
if (filter.columnName) {
|
|
1302
|
-
usedFilterColumns[filter.columnName] = true
|
|
1303
|
-
}
|
|
1304
|
-
|
|
1305
|
-
return (
|
|
1306
|
-
<>
|
|
1307
|
-
<fieldset className='edit-block' key={`filter-${index}`}>
|
|
1308
|
-
<button
|
|
1309
|
-
className='remove-column'
|
|
1310
|
-
onClick={e => {
|
|
1311
|
-
e.preventDefault()
|
|
1312
|
-
changeFilter(index, 'remove')
|
|
1313
|
-
}}
|
|
1314
|
-
>
|
|
1315
|
-
Remove
|
|
1316
|
-
</button>
|
|
1317
|
-
<TextField
|
|
1318
|
-
value={state.filters[index].label}
|
|
1319
|
-
section='filters'
|
|
1320
|
-
subsection={index}
|
|
1321
|
-
fieldName='label'
|
|
1322
|
-
label='Label'
|
|
1323
|
-
updateField={updateField}
|
|
1324
|
-
/>
|
|
1325
|
-
<label>
|
|
1326
|
-
<span className='edit-label column-heading'>
|
|
1327
|
-
Filter Column
|
|
1328
|
-
<Tooltip style={{ textTransform: 'none' }}>
|
|
1329
|
-
<Tooltip.Target>
|
|
1330
|
-
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
1331
|
-
</Tooltip.Target>
|
|
1332
|
-
<Tooltip.Content>
|
|
1333
|
-
<p>
|
|
1334
|
-
Selecting a column will add a dropdown menu below the map legend and allow users to filter based on
|
|
1335
|
-
the values in this column.
|
|
1336
|
-
</p>
|
|
1337
|
-
</Tooltip.Content>
|
|
1338
|
-
</Tooltip>
|
|
1339
|
-
</span>
|
|
1340
|
-
<select
|
|
1341
|
-
value={filter.columnName}
|
|
1342
|
-
onChange={event => {
|
|
1343
|
-
changeFilter(index, 'columnName', event.target.value)
|
|
1344
|
-
}}
|
|
1345
|
-
>
|
|
1346
|
-
{columnsOptions.filter(({ key }) => undefined === usedFilterColumns[key] || filter.columnName === key)}
|
|
1347
|
-
</select>
|
|
1348
|
-
</label>
|
|
1349
|
-
|
|
1350
|
-
<label>
|
|
1351
|
-
<span className='edit-showDropdown column-heading'>Show Filter Input</span>
|
|
1352
|
-
<input
|
|
1353
|
-
type='checkbox'
|
|
1354
|
-
checked={filter.showDropdown === undefined ? true : filter.showDropdown}
|
|
1355
|
-
onChange={e => {
|
|
1356
|
-
changeFilter(index, 'showDropdown', e.target.checked)
|
|
1357
|
-
}}
|
|
1358
|
-
/>
|
|
1359
|
-
</label>
|
|
1360
|
-
|
|
1361
|
-
<label>
|
|
1362
|
-
<span className='edit-filterOrder column-heading'>Filter Style</span>
|
|
1363
|
-
<select
|
|
1364
|
-
value={filter.filterStyle}
|
|
1365
|
-
onChange={e => {
|
|
1366
|
-
changeFilter(index, 'filterStyle', e.target.value)
|
|
1367
|
-
}}
|
|
1368
|
-
>
|
|
1369
|
-
{filterStyleOptions.map((option, index) => {
|
|
1370
|
-
return (
|
|
1371
|
-
<option value={option} key={`filter-${option}--${index}`}>
|
|
1372
|
-
{option}
|
|
1373
|
-
</option>
|
|
1374
|
-
)
|
|
1375
|
-
})}
|
|
1376
|
-
</select>
|
|
1377
|
-
</label>
|
|
1378
|
-
|
|
1379
|
-
<label>
|
|
1380
|
-
<span className='edit-filterOrder column-heading'>Filter Order</span>
|
|
1381
|
-
<select
|
|
1382
|
-
value={filter.order}
|
|
1383
|
-
onChange={e => {
|
|
1384
|
-
changeFilter(index, 'filterOrder', e.target.value)
|
|
1385
|
-
changeFilterActive(index, filter.values[0])
|
|
1386
|
-
}}
|
|
1387
|
-
>
|
|
1388
|
-
{filterOrderOptions.map((option, index) => {
|
|
1389
|
-
return (
|
|
1390
|
-
<option value={option.value} key={`filter-${index}`}>
|
|
1391
|
-
{option.label}
|
|
1392
|
-
</option>
|
|
1393
|
-
)
|
|
1394
|
-
})}
|
|
1395
|
-
</select>
|
|
1396
|
-
</label>
|
|
1397
|
-
|
|
1398
|
-
<TextField
|
|
1399
|
-
value={state.filters[index].setByQueryParameter}
|
|
1400
|
-
section='filters'
|
|
1401
|
-
subsection={index}
|
|
1402
|
-
fieldName='setByQueryParameter'
|
|
1403
|
-
label='Default Value Set By Query String Parameter'
|
|
1404
|
-
updateField={updateField}
|
|
1405
|
-
/>
|
|
1406
|
-
|
|
1407
|
-
{filter.order === 'cust' && (
|
|
1408
|
-
<DragDropContext
|
|
1409
|
-
onDragEnd={({ source, destination }) =>
|
|
1410
|
-
handleFilterOrder(source.index, destination.index, index, state.filters[index])
|
|
1411
|
-
}
|
|
1412
|
-
>
|
|
1413
|
-
<Droppable droppableId='filter_order'>
|
|
1414
|
-
{provided => (
|
|
1415
|
-
<ul
|
|
1416
|
-
{...provided.droppableProps}
|
|
1417
|
-
className='sort-list'
|
|
1418
|
-
ref={provided.innerRef}
|
|
1419
|
-
style={{ marginTop: '1em' }}
|
|
1420
|
-
>
|
|
1421
|
-
{state.filters[index]?.values.map((value, index) => {
|
|
1422
|
-
return (
|
|
1423
|
-
<Draggable key={value} draggableId={`draggableFilter-${value}`} index={index}>
|
|
1424
|
-
{(provided, snapshot) => (
|
|
1425
|
-
<li>
|
|
1426
|
-
<div
|
|
1427
|
-
className={snapshot.isDragging ? 'currently-dragging' : ''}
|
|
1428
|
-
style={getItemStyle(
|
|
1429
|
-
snapshot.isDragging,
|
|
1430
|
-
provided.draggableProps.style,
|
|
1431
|
-
sortableItemStyles
|
|
1432
|
-
)}
|
|
1433
|
-
ref={provided.innerRef}
|
|
1434
|
-
{...provided.draggableProps}
|
|
1435
|
-
{...provided.dragHandleProps}
|
|
1436
|
-
>
|
|
1437
|
-
{value}
|
|
1438
|
-
</div>
|
|
1439
|
-
</li>
|
|
1440
|
-
)}
|
|
1441
|
-
</Draggable>
|
|
1442
|
-
)
|
|
1443
|
-
})}
|
|
1444
|
-
{provided.placeholder}
|
|
1445
|
-
</ul>
|
|
1446
|
-
)}
|
|
1447
|
-
</Droppable>
|
|
1448
|
-
</DragDropContext>
|
|
1449
|
-
)}
|
|
1450
|
-
</fieldset>
|
|
1451
|
-
</>
|
|
1452
|
-
)
|
|
1453
|
-
})
|
|
1454
|
-
|
|
1455
1240
|
const StateOptionList = () => {
|
|
1456
1241
|
const arrOfArrays = Object.entries(supportedStatesFipsCodes)
|
|
1457
1242
|
|
|
@@ -1503,9 +1288,11 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
|
|
|
1503
1288
|
})
|
|
1504
1289
|
|
|
1505
1290
|
const getCategoryValuesOrder = () => {
|
|
1506
|
-
let values = runtimeLegend
|
|
1291
|
+
let values = runtimeLegend
|
|
1292
|
+
? runtimeLegend.filter(item => !item.special).map(runtimeLegendItem => runtimeLegendItem.value)
|
|
1293
|
+
: []
|
|
1507
1294
|
|
|
1508
|
-
if(state.legend.cateogryValuesOrder){
|
|
1295
|
+
if (state.legend.cateogryValuesOrder) {
|
|
1509
1296
|
return values.sort((a, b) => {
|
|
1510
1297
|
let aVal = state.legend.cateogryValuesOrder.indexOf(a)
|
|
1511
1298
|
let bVal = state.legend.cateogryValuesOrder.indexOf(b)
|
|
@@ -1517,11 +1304,12 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
|
|
|
1517
1304
|
} else {
|
|
1518
1305
|
return values
|
|
1519
1306
|
}
|
|
1520
|
-
|
|
1521
1307
|
}
|
|
1522
1308
|
|
|
1523
1309
|
const CategoryList = () => {
|
|
1524
|
-
return getCategoryValuesOrder()
|
|
1310
|
+
return getCategoryValuesOrder()
|
|
1311
|
+
.filter(item => !item?.special)
|
|
1312
|
+
.map((value, index) => (
|
|
1525
1313
|
<Draggable key={value} draggableId={`item-${value}`} index={index}>
|
|
1526
1314
|
{(provided, snapshot) => (
|
|
1527
1315
|
<li style={{ position: 'relative' }}>
|
|
@@ -1537,7 +1325,7 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
|
|
|
1537
1325
|
</li>
|
|
1538
1326
|
)}
|
|
1539
1327
|
</Draggable>
|
|
1540
|
-
|
|
1328
|
+
))
|
|
1541
1329
|
}
|
|
1542
1330
|
|
|
1543
1331
|
const isLoadedFromUrl = state?.dataKey?.includes('http://') || state?.dataKey?.includes('https://')
|
|
@@ -2191,6 +1979,13 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
|
|
|
2191
1979
|
</Tooltip>
|
|
2192
1980
|
</span>
|
|
2193
1981
|
</label>
|
|
1982
|
+
{state.legend.specialClasses.length === 2 && (
|
|
1983
|
+
<Alert
|
|
1984
|
+
type='info'
|
|
1985
|
+
message='If a third special class is needed you can apply a pattern to set it apart.'
|
|
1986
|
+
showCloseButton={false}
|
|
1987
|
+
/>
|
|
1988
|
+
)}
|
|
2194
1989
|
{specialClasses.map((specialClass, i) => (
|
|
2195
1990
|
<div className='edit-block' key={`special-class-${i}`}>
|
|
2196
1991
|
<button
|
|
@@ -2251,15 +2046,17 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
|
|
|
2251
2046
|
</label>
|
|
2252
2047
|
</div>
|
|
2253
2048
|
))}
|
|
2254
|
-
<
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
e
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2049
|
+
{state.legend.specialClasses.length < 2 && (
|
|
2050
|
+
<button
|
|
2051
|
+
className='btn btn-primary full-width'
|
|
2052
|
+
onClick={e => {
|
|
2053
|
+
e.preventDefault()
|
|
2054
|
+
editColumn('primary', 'specialClassAdd', {})
|
|
2055
|
+
}}
|
|
2056
|
+
>
|
|
2057
|
+
Add Special Class
|
|
2058
|
+
</button>
|
|
2059
|
+
)}
|
|
2263
2060
|
</fieldset>
|
|
2264
2061
|
)}
|
|
2265
2062
|
|
|
@@ -2826,7 +2623,7 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
|
|
|
2826
2623
|
</label>
|
|
2827
2624
|
</React.Fragment>
|
|
2828
2625
|
)}
|
|
2829
|
-
{
|
|
2626
|
+
{state.filters.length > 0 && (
|
|
2830
2627
|
<label className='checkbox'>
|
|
2831
2628
|
<input
|
|
2832
2629
|
type='checkbox'
|
|
@@ -2854,7 +2651,7 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
|
|
|
2854
2651
|
</span>
|
|
2855
2652
|
</label>
|
|
2856
2653
|
)}
|
|
2857
|
-
{(
|
|
2654
|
+
{(state.filters.length > 0 || state.general.type === 'bubble' || state.general.geoType === 'us') && (
|
|
2858
2655
|
<label className='checkbox'>
|
|
2859
2656
|
<input
|
|
2860
2657
|
type='checkbox'
|
|
@@ -2891,20 +2688,7 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
|
|
|
2891
2688
|
<AccordionItemButton>Filters</AccordionItemButton>
|
|
2892
2689
|
</AccordionItemHeading>
|
|
2893
2690
|
<AccordionItemPanel>
|
|
2894
|
-
{
|
|
2895
|
-
<MapFilters />
|
|
2896
|
-
) : (
|
|
2897
|
-
<p style={{ textAlign: 'center' }}>There are currently no filters.</p>
|
|
2898
|
-
)}
|
|
2899
|
-
<button
|
|
2900
|
-
className={'btn btn-primary full-width'}
|
|
2901
|
-
onClick={event => {
|
|
2902
|
-
event.preventDefault()
|
|
2903
|
-
changeFilter(null, 'addNew')
|
|
2904
|
-
}}
|
|
2905
|
-
>
|
|
2906
|
-
Add Filter
|
|
2907
|
-
</button>
|
|
2691
|
+
<VizFilterEditor config={state} updateField={updateField} rawData={state.data} />
|
|
2908
2692
|
</AccordionItemPanel>
|
|
2909
2693
|
</AccordionItem>
|
|
2910
2694
|
)}
|
|
@@ -3229,7 +3013,6 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
|
|
|
3229
3013
|
<label>
|
|
3230
3014
|
<span className='edit-label'>Map Color Palette</span>
|
|
3231
3015
|
</label>
|
|
3232
|
-
{/* <InputCheckbox section="general" subsection="palette" fieldName='isReversed' size='small' label='Use selected palette in reverse order' updateField={updateField} value={isPaletteReversed} /> */}
|
|
3233
3016
|
<InputToggle
|
|
3234
3017
|
type='3d'
|
|
3235
3018
|
section='general'
|
|
@@ -255,7 +255,7 @@ const PatternSettings = ({ name }: PanelProps) => {
|
|
|
255
255
|
</Accordion>
|
|
256
256
|
)
|
|
257
257
|
})}
|
|
258
|
-
<button className='btn btn-primary full-width' onClick={handleAddGeoPattern}>
|
|
258
|
+
<button className='btn btn-primary full-width mt-2' onClick={handleAddGeoPattern}>
|
|
259
259
|
Add Geo Pattern
|
|
260
260
|
</button>
|
|
261
261
|
</AccordionItemPanel>
|
|
@@ -18,7 +18,7 @@ import { GlyphStar, GlyphTriangle, GlyphDiamond, GlyphSquare, GlyphCircle } from
|
|
|
18
18
|
import { Group } from '@visx/group'
|
|
19
19
|
import './index.scss'
|
|
20
20
|
import { ViewportSize } from '@cdc/chart/src/types/ChartConfig'
|
|
21
|
-
import { isMobileHeightViewport } from '@cdc/core/helpers/viewports'
|
|
21
|
+
import { isBelowBreakpoint, isMobileHeightViewport } from '@cdc/core/helpers/viewports'
|
|
22
22
|
|
|
23
23
|
const LEGEND_PADDING = 30
|
|
24
24
|
|
|
@@ -46,6 +46,12 @@ const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
|
|
|
46
46
|
} = useContext(ConfigContext)
|
|
47
47
|
|
|
48
48
|
const { legend } = state
|
|
49
|
+
const isLegendGradient = legend.style === 'gradient'
|
|
50
|
+
const boxDynamicallyHidden = isBelowBreakpoint('md', currentViewport)
|
|
51
|
+
const legendWrapping =
|
|
52
|
+
(legend.position === 'left' || legend.position === 'right') && isBelowBreakpoint('md', currentViewport)
|
|
53
|
+
const legendOnBottom = legend.position === 'bottom' || legendWrapping
|
|
54
|
+
const needsTopMargin = legend.hideBorder && legendOnBottom
|
|
49
55
|
|
|
50
56
|
// Toggles if a legend is active and being applied to the map and data table.
|
|
51
57
|
const toggleLegendActive = (i, legendLabel) => {
|
|
@@ -87,6 +93,10 @@ const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
|
|
|
87
93
|
formattedText = '0'
|
|
88
94
|
}
|
|
89
95
|
|
|
96
|
+
if (entry.max === null && entry.min === null) {
|
|
97
|
+
formattedText = 'No data'
|
|
98
|
+
}
|
|
99
|
+
|
|
90
100
|
let legendLabel = formattedText
|
|
91
101
|
|
|
92
102
|
if (entry.hasOwnProperty('special')) {
|
|
@@ -207,7 +217,7 @@ const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
|
|
|
207
217
|
|
|
208
218
|
return legendItems
|
|
209
219
|
}
|
|
210
|
-
const legendListItems = legendList(
|
|
220
|
+
const legendListItems = legendList(isLegendGradient)
|
|
211
221
|
|
|
212
222
|
const { legendClasses } = useDataVizClasses(state, viewport)
|
|
213
223
|
|
|
@@ -244,13 +254,13 @@ const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
|
|
|
244
254
|
|
|
245
255
|
return (
|
|
246
256
|
<ErrorBoundary component='Sidebar'>
|
|
247
|
-
<div className=
|
|
257
|
+
<div className={`legends ${needsTopMargin ? 'mt-1' : ''}`}>
|
|
248
258
|
<aside
|
|
249
259
|
id={skipId || 'legend'}
|
|
250
260
|
className={legendClasses.aside.join(' ') || ''}
|
|
251
261
|
role='region'
|
|
252
262
|
aria-label='Legend'
|
|
253
|
-
tabIndex={0}
|
|
263
|
+
tabIndex={isLegendGradient ? -1 : 0}
|
|
254
264
|
ref={ref}
|
|
255
265
|
>
|
|
256
266
|
<section className={legendClasses.section.join(' ') || ''} aria-label='Map Legend'>
|
|
@@ -279,7 +289,9 @@ const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
|
|
|
279
289
|
labels={getFormattedLegendItems().map(item => item?.label) ?? []}
|
|
280
290
|
colors={getFormattedLegendItems().map(item => item?.color) ?? []}
|
|
281
291
|
dimensions={dimensions}
|
|
282
|
-
parentPaddingToSubtract={
|
|
292
|
+
parentPaddingToSubtract={
|
|
293
|
+
containerWidthPadding + (legend.hideBorder || boxDynamicallyHidden ? 0 : LEGEND_PADDING)
|
|
294
|
+
}
|
|
283
295
|
config={state}
|
|
284
296
|
/>
|
|
285
297
|
{!!legendListItems.length && (
|
|
@@ -321,7 +333,11 @@ const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
|
|
|
321
333
|
</div>
|
|
322
334
|
</>
|
|
323
335
|
)}
|
|
324
|
-
{runtimeLegend.disabledAmt > 0 &&
|
|
336
|
+
{runtimeLegend.disabledAmt > 0 && (
|
|
337
|
+
<Button className={legendClasses.resetButton.join(' ')} onClick={handleReset}>
|
|
338
|
+
Reset
|
|
339
|
+
</Button>
|
|
340
|
+
)}
|
|
325
341
|
</section>
|
|
326
342
|
</aside>
|
|
327
343
|
{state.hexMap.shapeGroups?.length > 0 && state.hexMap.type === 'shapes' && state.general.displayAsHex && (
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
}
|
|
7
7
|
@include breakpointClass(md) {
|
|
8
8
|
.map-container.world aside.side {
|
|
9
|
-
border-top: var(--
|
|
9
|
+
border-top: var(--cool-gray-10) 1px solid;
|
|
10
10
|
position: absolute;
|
|
11
11
|
box-shadow: rgba(0, 0, 0, 0.2) 0 10px 18px;
|
|
12
12
|
}
|
|
@@ -15,12 +15,15 @@
|
|
|
15
15
|
aside {
|
|
16
16
|
background-color: #fff;
|
|
17
17
|
z-index: 6;
|
|
18
|
-
border-top: var(--
|
|
18
|
+
border-top: var(--cool-gray-10) 1px solid;
|
|
19
19
|
|
|
20
20
|
@include breakpointClass(md) {
|
|
21
|
+
.legend-container.legend-padding {
|
|
22
|
+
padding: 15px;
|
|
23
|
+
}
|
|
21
24
|
&.bottom,
|
|
22
25
|
&.top {
|
|
23
|
-
border: var(--
|
|
26
|
+
border: var(--cool-gray-10) 1px solid;
|
|
24
27
|
}
|
|
25
28
|
&.side {
|
|
26
29
|
z-index: 1;
|
|
@@ -31,7 +34,7 @@
|
|
|
31
34
|
align-self: flex-start;
|
|
32
35
|
z-index: 4;
|
|
33
36
|
right: 1em;
|
|
34
|
-
border: var(--
|
|
37
|
+
border: var(--cool-gray-10) 1px solid;
|
|
35
38
|
top: 2em;
|
|
36
39
|
right: 1em;
|
|
37
40
|
|
|
@@ -67,9 +70,10 @@
|
|
|
67
70
|
}
|
|
68
71
|
|
|
69
72
|
.legend-container {
|
|
73
|
+
--space-between-legend-items: 0.6em;
|
|
70
74
|
position: relative;
|
|
71
75
|
&.legend-padding {
|
|
72
|
-
padding: 15px;
|
|
76
|
+
padding-top: 15px;
|
|
73
77
|
}
|
|
74
78
|
.legend-container__title {
|
|
75
79
|
font-size: 1.3em;
|
|
@@ -83,17 +87,7 @@
|
|
|
83
87
|
padding-top: 1em;
|
|
84
88
|
}
|
|
85
89
|
.legend-container__reset-button {
|
|
86
|
-
|
|
87
|
-
right: 1em;
|
|
88
|
-
text-transform: uppercase;
|
|
89
|
-
transition: 0.2s all;
|
|
90
|
-
padding: 0.2em 0.5em;
|
|
91
|
-
border: rgba(0, 0, 0, 0.2) 1px solid;
|
|
92
|
-
padding: 0.375rem;
|
|
93
|
-
&:hover {
|
|
94
|
-
text-decoration: none;
|
|
95
|
-
transition: 0.2s all;
|
|
96
|
-
}
|
|
90
|
+
margin-top: 1em;
|
|
97
91
|
}
|
|
98
92
|
p {
|
|
99
93
|
line-height: 1.4em;
|
|
@@ -118,23 +112,21 @@
|
|
|
118
112
|
|
|
119
113
|
&:not(.vertical-sorted, .legend-container__ul--single-column, .single-row) {
|
|
120
114
|
width: 100%;
|
|
121
|
-
@include breakpoint(sm) {
|
|
122
|
-
.legend-container__li {
|
|
123
|
-
width: 50%;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
115
|
}
|
|
127
116
|
.legend-container__li {
|
|
128
117
|
flex-shrink: 0;
|
|
129
118
|
display: inline-block;
|
|
130
119
|
padding-right: 1em;
|
|
131
|
-
|
|
120
|
+
margin-bottom: var(--space-between-legend-items);
|
|
132
121
|
vertical-align: middle;
|
|
133
122
|
transition: 0.1s opacity;
|
|
134
123
|
display: flex;
|
|
135
124
|
cursor: pointer;
|
|
136
125
|
white-space: wrap;
|
|
137
126
|
flex-grow: 1;
|
|
127
|
+
&:last-child {
|
|
128
|
+
margin-bottom: 0;
|
|
129
|
+
}
|
|
138
130
|
@include breakpoint(md) {
|
|
139
131
|
white-space: nowrap;
|
|
140
132
|
}
|
|
@@ -146,16 +138,17 @@
|
|
|
146
138
|
}
|
|
147
139
|
.legend-container__ul.single-row {
|
|
148
140
|
width: 100%;
|
|
141
|
+
cursor: pointer;
|
|
149
142
|
list-style: none;
|
|
150
143
|
display: flex;
|
|
151
144
|
flex-direction: row;
|
|
152
145
|
align-items: center;
|
|
153
146
|
justify-content: flex-start;
|
|
154
147
|
flex-wrap: wrap;
|
|
148
|
+
row-gap: var(--space-between-legend-items);
|
|
155
149
|
|
|
156
150
|
& > li {
|
|
157
151
|
margin-right: 1em;
|
|
158
|
-
margin-bottom: 1em;
|
|
159
152
|
white-space: wrap;
|
|
160
153
|
display: flex;
|
|
161
154
|
justify-content: center;
|