@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.
Files changed (49) hide show
  1. package/dist/cdcmap.js +34008 -33475
  2. package/examples/default-geocode.json +13 -4
  3. package/examples/default-usa-regions.json +267 -117
  4. package/examples/example-city-state.json +6 -3
  5. package/examples/pattern.json +861 -0
  6. package/examples/private/DEV-9989.json +229 -0
  7. package/examples/private/ardi.json +180 -0
  8. package/examples/private/colors 2.json +416 -0
  9. package/examples/private/colors.json +416 -0
  10. package/examples/private/colors.json.zip +0 -0
  11. package/examples/private/customColors.json +45348 -0
  12. package/examples/private/default-patterns.json +867 -0
  13. package/examples/private/test.json +1632 -0
  14. package/index.html +4 -5
  15. package/package.json +3 -3
  16. package/src/CdcMap.tsx +82 -79
  17. package/src/_stories/{CdcMapLegend.stories.tsx → CdcMap.Legend.Gradient.stories.tsx} +1 -20
  18. package/src/_stories/CdcMap.Legend.stories.tsx +40 -0
  19. package/src/_stories/CdcMap.Patterns.stories.tsx +29 -0
  20. package/src/_stories/CdcMap.stories.tsx +59 -0
  21. package/src/_stories/UsaMap.NoData.stories.tsx +19 -0
  22. package/src/_stories/_mock/custom-layer-map.json +1117 -0
  23. package/src/_stories/_mock/default-patterns.json +865 -0
  24. package/src/_stories/_mock/example-city-state.json +858 -0
  25. package/src/components/CityList.tsx +5 -2
  26. package/src/components/EditorPanel/components/EditorPanel.tsx +39 -256
  27. package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +1 -1
  28. package/src/components/Legend/components/Legend.tsx +22 -6
  29. package/src/components/Legend/components/index.scss +16 -23
  30. package/src/components/UsaMap/components/SingleState/SingleState.CountyOutput.tsx +40 -6
  31. package/src/components/UsaMap/components/SingleState/SingleState.StateOutput.tsx +10 -2
  32. package/src/components/UsaMap/components/Territory/Territory.Hexagon.tsx +1 -1
  33. package/src/components/UsaMap/components/Territory/Territory.Rectangle.tsx +14 -13
  34. package/src/components/UsaMap/components/UsaMap.County.tsx +11 -13
  35. package/src/components/UsaMap/components/UsaMap.Region.tsx +59 -16
  36. package/src/components/UsaMap/components/UsaMap.SingleState.tsx +2 -1
  37. package/src/components/UsaMap/components/UsaMap.State.tsx +58 -60
  38. package/src/components/WorldMap/WorldMap.tsx +77 -16
  39. package/src/data/initial-state.js +2 -1
  40. package/src/helpers/applyColorToLegend.ts +80 -0
  41. package/src/helpers/colors.ts +23 -0
  42. package/src/hooks/useTooltip.ts +9 -6
  43. package/src/scss/filters.scss +0 -3
  44. package/src/scss/main.scss +0 -1
  45. package/src/scss/map.scss +11 -59
  46. package/src/types/MapConfig.ts +7 -1
  47. package/src/types/MapContext.ts +1 -0
  48. package/examples/default-patterns.json +0 -579
  49. 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={state.general.geoBorderColor === 'sameAsBackground' ? '#ffffff' : '#000000'}
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: state.general.geoBorderColor === 'sameAsBackground' ? '#ffffff' : '#000000',
191
+ stroke: geoStrokeColor,
189
192
  strokeWidth: '2px',
190
193
  tabIndex: -1,
191
194
  ...additionalProps
@@ -1,4 +1,4 @@
1
- import React, { useState, useEffect, useCallback, memo, useContext } from 'react'
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
- position,
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 = (section, subsection, fieldName, newValue) => {
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 ? runtimeLegend.filter(item => !item.special).map(runtimeLegendItem => runtimeLegendItem.value) : []
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().filter(item => !item.special).map((value, index) => (
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
- <button
2255
- className='btn btn-primary full-width'
2256
- onClick={e => {
2257
- e.preventDefault()
2258
- editColumn('primary', 'specialClassAdd', {})
2259
- }}
2260
- >
2261
- Add Special Class
2262
- </button>
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
- {filtersJSX.length > 0 && (
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
- {(filtersJSX.length > 0 || state.general.type === 'bubble' || state.general.geoType === 'us') && (
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
- {filtersJSX.length > 0 ? (
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(state.legend.style === 'gradient')
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='legends'>
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={containerWidthPadding + (legend.hideBorder ? 0 : LEGEND_PADDING)}
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 && <Button onClick={handleReset}>Reset</Button>}
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(--lightGray) 1px solid;
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(--lightGray) 1px solid;
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(--lightGray) 1px solid;
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(--lightGray) 1px solid;
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
- font-size: 0.75em;
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
- padding-bottom: 1em;
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;