@cdc/map 4.25.8 → 4.25.10
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/.claude/settings.local.json +30 -0
- package/dist/cdcmap.js +54263 -52600
- package/examples/private/c.json +290 -0
- package/examples/private/canvas-city-hover.json +787 -0
- package/examples/private/d.json +345 -0
- package/examples/private/g.json +1 -0
- package/examples/private/h.json +105911 -0
- package/examples/private/measles-data.json +378 -0
- package/examples/private/measles.json +211 -0
- package/examples/private/north-dakota.json +1132 -0
- package/examples/private/state-with-pattern.json +883 -0
- package/index.html +35 -34
- package/package.json +26 -5
- package/src/CdcMap.tsx +23 -8
- package/src/CdcMapComponent.tsx +215 -309
- package/src/_stories/CdcMap.Filters.stories.tsx +2 -2
- package/src/_stories/CdcMap.Legend.Gradient.stories.tsx +3 -3
- package/src/_stories/CdcMap.Legend.stories.tsx +7 -4
- package/src/_stories/CdcMap.Patterns.stories.tsx +2 -2
- package/src/_stories/CdcMap.Table.stories.tsx +2 -2
- package/src/_stories/CdcMap.stories.tsx +15 -5
- package/src/_stories/GoogleMap.stories.tsx +2 -2
- package/src/_stories/UsaMap.NoData.stories.tsx +2 -2
- package/src/_stories/_mock/equal-number.json +1109 -0
- package/src/_stories/_mock/us-bubble-cities.json +306 -0
- package/src/components/BubbleList.tsx +16 -12
- package/src/components/CityList.tsx +85 -107
- package/src/components/DataTable.tsx +37 -9
- package/src/components/EditorPanel/components/EditorPanel.tsx +177 -165
- package/src/components/EditorPanel/components/HexShapeSettings.tsx +3 -2
- package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +7 -5
- package/src/components/Geo.tsx +2 -0
- package/src/components/Legend/components/Legend.tsx +109 -73
- package/src/components/Legend/components/LegendGroup/Legend.Group.tsx +10 -7
- package/src/components/MapContainer.tsx +52 -0
- package/src/components/MapControls.tsx +44 -0
- package/src/components/NavigationMenu.tsx +11 -2
- package/src/components/UsaMap/components/SingleState/SingleState.CountyOutput.tsx +24 -7
- package/src/components/UsaMap/components/UsaMap.County.tsx +111 -37
- package/src/components/UsaMap/components/UsaMap.Region.tsx +23 -5
- package/src/components/UsaMap/components/UsaMap.SingleState.tsx +6 -6
- package/src/components/UsaMap/components/UsaMap.State.tsx +28 -10
- package/src/components/UsaMap/helpers/map.ts +2 -2
- package/src/components/WorldMap/WorldMap.tsx +113 -25
- package/src/components/ZoomControls.tsx +6 -9
- package/src/context/LegendMemoContext.tsx +30 -0
- package/src/context.ts +1 -40
- package/src/data/initial-state.js +143 -130
- package/src/data/supported-geos.js +17 -2
- package/src/helpers/applyColorToLegend.ts +116 -20
- package/src/helpers/applyLegendToRow.ts +10 -6
- package/src/helpers/componentHelpers.ts +8 -0
- package/src/helpers/constants.ts +12 -0
- package/src/helpers/dataTableHelpers.ts +6 -0
- package/src/helpers/displayGeoName.ts +1 -1
- package/src/helpers/generateRuntimeLegend.ts +44 -8
- package/src/helpers/generateRuntimeLegendHash.ts +4 -2
- package/src/helpers/getColumnNames.ts +1 -1
- package/src/helpers/getPatternForRow.ts +36 -0
- package/src/helpers/getStatesPicked.ts +8 -5
- package/src/helpers/index.ts +11 -3
- package/src/helpers/isLegendItemDisabled.ts +16 -0
- package/src/helpers/mapObserverHelpers.ts +40 -0
- package/src/helpers/resetLegendToggles.ts +3 -2
- package/src/helpers/toggleLegendActive.ts +6 -11
- package/src/helpers/urlDataHelpers.ts +70 -0
- package/src/hooks/useGeoClickHandler.ts +35 -1
- package/src/hooks/useLegendMemo.ts +17 -0
- package/src/hooks/useMapLayers.tsx +5 -4
- package/src/hooks/useStateZoom.tsx +25 -6
- package/src/hooks/useTooltip.ts +1 -2
- package/src/index.jsx +0 -2
- package/src/store/map.reducer.ts +17 -6
- package/src/test/CdcMap.test.jsx +11 -0
- package/src/types/MapConfig.ts +23 -14
- package/src/types/MapContext.ts +0 -7
- package/src/types/runtimeLegend.ts +17 -1
- package/vite.config.js +2 -7
- package/vitest.config.ts +16 -0
- package/src/coreStyles_map.scss +0 -3
- package/src/helpers/colorDistributions.ts +0 -12
- package/src/helpers/generateColorsArray.ts +0 -14
- package/src/helpers/tests/generateColorsArray.test.ts +0 -18
- package/src/helpers/tests/generateRuntimeLegendHash.test.ts +0 -11
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import React, { useContext, useEffect, useState } from 'react'
|
|
1
|
+
import React, { useContext, useEffect, useState, useMemo } from 'react'
|
|
2
|
+
import { filterColorPalettes } from '@cdc/core/helpers/filterColorPalettes'
|
|
3
|
+
import { cloneConfig } from '@cdc/core/helpers/cloneConfig'
|
|
2
4
|
|
|
3
5
|
// Third Party
|
|
4
6
|
import {
|
|
@@ -12,11 +14,12 @@ import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd'
|
|
|
12
14
|
import { useDebounce } from 'use-debounce'
|
|
13
15
|
import _ from 'lodash'
|
|
14
16
|
import { Tooltip as ReactTooltip } from 'react-tooltip'
|
|
17
|
+
import 'react-tooltip/dist/react-tooltip.css'
|
|
15
18
|
import Panels from './Panels'
|
|
16
19
|
import Layout from '@cdc/core/components/Layout'
|
|
17
20
|
|
|
18
21
|
// Data
|
|
19
|
-
import colorPalettes from '@cdc/core/data/colorPalettes'
|
|
22
|
+
import { mapColorPalettes as colorPalettes } from '@cdc/core/data/colorPalettes'
|
|
20
23
|
import { supportedStatesFipsCodes } from '../../../data/supported-geos.js'
|
|
21
24
|
|
|
22
25
|
// Components - Core
|
|
@@ -26,6 +29,7 @@ import Icon from '@cdc/core/components/ui/Icon'
|
|
|
26
29
|
import InputToggle from '@cdc/core/components/inputs/InputToggle'
|
|
27
30
|
import Tooltip from '@cdc/core/components/ui/Tooltip'
|
|
28
31
|
import VizFilterEditor from '@cdc/core/components/EditorPanel/VizFilterEditor'
|
|
32
|
+
import PanelMarkup from '@cdc/core/components/EditorPanel/components/PanelMarkup'
|
|
29
33
|
|
|
30
34
|
// Assets
|
|
31
35
|
import UsaGraphic from '@cdc/core/assets/icon-map-usa.svg'
|
|
@@ -49,6 +53,11 @@ import './editorPanel.styles.css'
|
|
|
49
53
|
import FootnotesEditor from '@cdc/core/components/EditorPanel/FootnotesEditor'
|
|
50
54
|
import { Datasets } from '@cdc/core/types/DataSet'
|
|
51
55
|
import MultiSelect from '@cdc/core/components/MultiSelect'
|
|
56
|
+
import { paletteMigrationMap } from '@cdc/core/helpers/palettes/migratePaletteName'
|
|
57
|
+
import { isV1Palette, getCurrentPaletteName, migratePaletteWithMap } from '@cdc/core/helpers/palettes/utils'
|
|
58
|
+
import { USE_V2_MIGRATION } from '@cdc/core/helpers/constants'
|
|
59
|
+
import { PaletteSelector, DeveloperPaletteRollback } from '@cdc/core/components/PaletteSelector'
|
|
60
|
+
import PaletteConversionModal from '@cdc/core/components/PaletteConversionModal'
|
|
52
61
|
|
|
53
62
|
type MapEditorPanelProps = {
|
|
54
63
|
datasets?: Datasets
|
|
@@ -65,8 +74,7 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
|
|
|
65
74
|
setConfig,
|
|
66
75
|
config,
|
|
67
76
|
tooltipId,
|
|
68
|
-
runtimeData
|
|
69
|
-
setRuntimeData
|
|
77
|
+
runtimeData
|
|
70
78
|
} = useContext<MapContext>(ConfigContext)
|
|
71
79
|
|
|
72
80
|
const { columnsRequiredChecker } = useColumnsRequiredChecker()
|
|
@@ -77,6 +85,11 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
|
|
|
77
85
|
const [loadedDefault, setLoadedDefault] = useState(false)
|
|
78
86
|
const [displayPanel, setDisplayPanel] = useState(true)
|
|
79
87
|
const [activeFilterValueForDescription, setActiveFilterValueForDescription] = useState([0, 0])
|
|
88
|
+
const [showConversionModal, setShowConversionModal] = useState(false)
|
|
89
|
+
const [pendingPaletteSelection, setPendingPaletteSelection] = useState<{
|
|
90
|
+
palette: string
|
|
91
|
+
action: () => void
|
|
92
|
+
} | null>(null)
|
|
80
93
|
|
|
81
94
|
const {
|
|
82
95
|
MapLayerHandlers: { handleMapLayer, handleAddLayer, handleRemoveLayer }
|
|
@@ -691,7 +704,7 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
|
|
|
691
704
|
|
|
692
705
|
if (config) {
|
|
693
706
|
const newData = generateRuntimeData(config)
|
|
694
|
-
|
|
707
|
+
dispatch({ type: 'SET_RUNTIME_DATA', payload: newData })
|
|
695
708
|
}
|
|
696
709
|
break
|
|
697
710
|
case 'classificationType':
|
|
@@ -868,7 +881,7 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
|
|
|
868
881
|
}
|
|
869
882
|
|
|
870
883
|
const convertStateToConfig = () => {
|
|
871
|
-
let strippedState =
|
|
884
|
+
let strippedState = cloneConfig(config) // Deep copy
|
|
872
885
|
|
|
873
886
|
// Strip ref
|
|
874
887
|
delete strippedState['']
|
|
@@ -900,46 +913,75 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
|
|
|
900
913
|
|
|
901
914
|
const isReversed = config.general.palette.isReversed
|
|
902
915
|
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
if (
|
|
925
|
-
|
|
926
|
-
}
|
|
927
|
-
if (paletteName.includes('colorblindsafe') && paletteName.endsWith('reverse')) {
|
|
928
|
-
accessibleColors.push(paletteName)
|
|
929
|
-
}
|
|
930
|
-
if (
|
|
931
|
-
!paletteName.includes('qualitative') &&
|
|
932
|
-
!paletteName.includes('colorblindsafe') &&
|
|
933
|
-
paletteName.endsWith('reverse')
|
|
934
|
-
) {
|
|
935
|
-
sequential.push(paletteName)
|
|
916
|
+
const { sequential, nonSequential, accessibleColors } = useMemo(
|
|
917
|
+
() => filterColorPalettes({ config, isReversed, colorPalettes }),
|
|
918
|
+
[isReversed, colorPalettes, config.general.palette.version]
|
|
919
|
+
)
|
|
920
|
+
|
|
921
|
+
// Helper function to handle palette selection with conversion prompt
|
|
922
|
+
const handlePaletteSelection = (palette: string) => {
|
|
923
|
+
const isV1PaletteConfig = isV1Palette(config)
|
|
924
|
+
|
|
925
|
+
const executeSelection = () => {
|
|
926
|
+
const _newConfig = _.cloneDeep(config)
|
|
927
|
+
|
|
928
|
+
// If v2 migration is disabled, use the original palette name and keep v1 version
|
|
929
|
+
if (!USE_V2_MIGRATION) {
|
|
930
|
+
_newConfig.general.palette.name = palette
|
|
931
|
+
_newConfig.general.palette.version = '1.0'
|
|
932
|
+
} else {
|
|
933
|
+
// V2 migration logic
|
|
934
|
+
_newConfig.general.palette.name = palette
|
|
935
|
+
? migratePaletteWithMap(palette, paletteMigrationMap, false)
|
|
936
|
+
: undefined
|
|
937
|
+
if (isV1PaletteConfig) {
|
|
938
|
+
_newConfig.general.palette.version = '2.0'
|
|
936
939
|
}
|
|
937
940
|
}
|
|
941
|
+
setConfig(_newConfig)
|
|
938
942
|
}
|
|
939
943
|
|
|
940
|
-
|
|
944
|
+
if (isV1PaletteConfig) {
|
|
945
|
+
setPendingPaletteSelection({ palette, action: executeSelection })
|
|
946
|
+
setShowConversionModal(true)
|
|
947
|
+
} else {
|
|
948
|
+
executeSelection()
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
// Modal handlers
|
|
953
|
+
const handleConversionConfirm = () => {
|
|
954
|
+
if (pendingPaletteSelection) {
|
|
955
|
+
pendingPaletteSelection.action()
|
|
956
|
+
}
|
|
957
|
+
setShowConversionModal(false)
|
|
958
|
+
setPendingPaletteSelection(null)
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
const handleConversionCancel = () => {
|
|
962
|
+
setShowConversionModal(false)
|
|
963
|
+
setPendingPaletteSelection(null)
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
const handleReturnToV1 = () => {
|
|
967
|
+
if (pendingPaletteSelection) {
|
|
968
|
+
const _newConfig = cloneConfig(config)
|
|
969
|
+
_newConfig.general.palette.name = pendingPaletteSelection.palette
|
|
970
|
+
_newConfig.general.palette.version = '1.0'
|
|
971
|
+
setConfig(_newConfig)
|
|
972
|
+
}
|
|
973
|
+
setShowConversionModal(false)
|
|
974
|
+
setPendingPaletteSelection(null)
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
// Helper function to determine if a palette should be marked as selected
|
|
978
|
+
const getPaletteClassName = (palette: string) => {
|
|
979
|
+
const currentPaletteName = config.general.palette.name || ''
|
|
980
|
+
|
|
981
|
+
// Direct comparison since the UI filters palettes by version
|
|
982
|
+
// When v1 is selected, UI shows v1 palettes; when v2 is selected, UI shows v2 palettes
|
|
983
|
+
return currentPaletteName === palette ? 'selected' : ''
|
|
941
984
|
}
|
|
942
|
-
const [sequential, nonSequential, accessibleColors] = filterColorPalettes()
|
|
943
985
|
|
|
944
986
|
useEffect(() => {
|
|
945
987
|
setLoadedDefault(config.defaultData)
|
|
@@ -1263,7 +1305,7 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
|
|
|
1263
1305
|
{ value: '_blank', label: 'New Window' }
|
|
1264
1306
|
]}
|
|
1265
1307
|
onChange={event => {
|
|
1266
|
-
const _newConfig =
|
|
1308
|
+
const _newConfig = cloneConfig(config)
|
|
1267
1309
|
_newConfig.general.navigationTarget = event.target.value
|
|
1268
1310
|
setConfig(_newConfig)
|
|
1269
1311
|
}}
|
|
@@ -1302,7 +1344,7 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
|
|
|
1302
1344
|
type='checkbox'
|
|
1303
1345
|
checked={config.general.displayAsHex}
|
|
1304
1346
|
onChange={event => {
|
|
1305
|
-
const _newConfig =
|
|
1347
|
+
const _newConfig = cloneConfig(config)
|
|
1306
1348
|
_newConfig.general.displayAsHex = event.target.checked
|
|
1307
1349
|
setConfig(_newConfig)
|
|
1308
1350
|
}}
|
|
@@ -1358,7 +1400,7 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
|
|
|
1358
1400
|
type='checkbox'
|
|
1359
1401
|
checked={general.territoriesAlwaysShow || false}
|
|
1360
1402
|
onChange={event => {
|
|
1361
|
-
const _newConfig =
|
|
1403
|
+
const _newConfig = cloneConfig(config)
|
|
1362
1404
|
_newConfig.general.territoriesAlwaysShow = event.target.checked
|
|
1363
1405
|
setConfig(_newConfig)
|
|
1364
1406
|
}}
|
|
@@ -1402,7 +1444,7 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
|
|
|
1402
1444
|
type='checkbox'
|
|
1403
1445
|
checked={config.general.showTitle || false}
|
|
1404
1446
|
onChange={event => {
|
|
1405
|
-
const _newConfig =
|
|
1447
|
+
const _newConfig = cloneConfig(config)
|
|
1406
1448
|
_newConfig.general.showTitle = event.target.checked
|
|
1407
1449
|
setConfig(_newConfig)
|
|
1408
1450
|
}}
|
|
@@ -1546,7 +1588,7 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
|
|
|
1546
1588
|
type='checkbox'
|
|
1547
1589
|
checked={config.general.hideGeoColumnInTooltip || false}
|
|
1548
1590
|
onChange={event => {
|
|
1549
|
-
const _newConfig =
|
|
1591
|
+
const _newConfig = cloneConfig(config)
|
|
1550
1592
|
_newConfig.general.hideGeoColumnInTooltip = event.target.checked
|
|
1551
1593
|
setConfig(_newConfig)
|
|
1552
1594
|
}}
|
|
@@ -1579,7 +1621,7 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
|
|
|
1579
1621
|
value={columns.primary.name}
|
|
1580
1622
|
options={columnsOptions.map(c => c.key)}
|
|
1581
1623
|
onChange={event => {
|
|
1582
|
-
const _state =
|
|
1624
|
+
const _state = cloneConfig(config)
|
|
1583
1625
|
_state.columns.primary.name = event.target.value
|
|
1584
1626
|
_state.columns.primary.label = event.target.value
|
|
1585
1627
|
setConfig(_state)
|
|
@@ -2083,7 +2125,7 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
|
|
|
2083
2125
|
messages = []
|
|
2084
2126
|
}
|
|
2085
2127
|
|
|
2086
|
-
const _newConfig =
|
|
2128
|
+
const _newConfig = cloneConfig(config)
|
|
2087
2129
|
_newConfig.legend.type = event.target.value
|
|
2088
2130
|
_newConfig.runtime.editorErrorMessage = messages
|
|
2089
2131
|
setConfig(_newConfig)
|
|
@@ -2096,7 +2138,7 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
|
|
|
2096
2138
|
type='checkbox'
|
|
2097
2139
|
checked={config.general.showSidebar || false}
|
|
2098
2140
|
onChange={event => {
|
|
2099
|
-
const _newConfig =
|
|
2141
|
+
const _newConfig = cloneConfig(config)
|
|
2100
2142
|
_newConfig.general.showSidebar = event.target.checked
|
|
2101
2143
|
setConfig(_newConfig)
|
|
2102
2144
|
}}
|
|
@@ -2236,7 +2278,7 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
|
|
|
2236
2278
|
type='checkbox'
|
|
2237
2279
|
checked={legend.singleColumn}
|
|
2238
2280
|
onChange={event => {
|
|
2239
|
-
const _newConfig =
|
|
2281
|
+
const _newConfig = cloneConfig(config)
|
|
2240
2282
|
_newConfig.legend.singleColumn = event.target.checked
|
|
2241
2283
|
_newConfig.legend.singleRow = false
|
|
2242
2284
|
_newConfig.legend.verticalSorted = false
|
|
@@ -2253,7 +2295,7 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
|
|
|
2253
2295
|
type='checkbox'
|
|
2254
2296
|
checked={legend.singleRow}
|
|
2255
2297
|
onChange={event => {
|
|
2256
|
-
const _newConfig =
|
|
2298
|
+
const _newConfig = cloneConfig(config)
|
|
2257
2299
|
_newConfig.legend.singleRow = event.target.checked
|
|
2258
2300
|
_newConfig.legend.singleColumn = false
|
|
2259
2301
|
_newConfig.legend.verticalSorted = false
|
|
@@ -2271,7 +2313,7 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
|
|
|
2271
2313
|
value={legend.groupBy || ''}
|
|
2272
2314
|
options={columnsOptions.map(c => c.key)}
|
|
2273
2315
|
onChange={event => {
|
|
2274
|
-
const _newConfig =
|
|
2316
|
+
const _newConfig = cloneConfig(config)
|
|
2275
2317
|
_newConfig.legend.groupBy = event.target.value
|
|
2276
2318
|
setConfig(_newConfig)
|
|
2277
2319
|
}}
|
|
@@ -2283,7 +2325,7 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
|
|
|
2283
2325
|
type='checkbox'
|
|
2284
2326
|
checked={legend.verticalSorted}
|
|
2285
2327
|
onChange={event => {
|
|
2286
|
-
const _newConfig =
|
|
2328
|
+
const _newConfig = cloneConfig(config)
|
|
2287
2329
|
_newConfig.legend.verticalSorted = event.target.checked
|
|
2288
2330
|
setConfig(_newConfig)
|
|
2289
2331
|
}}
|
|
@@ -2311,7 +2353,7 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
|
|
|
2311
2353
|
type='checkbox'
|
|
2312
2354
|
checked={legend.separateZero || false}
|
|
2313
2355
|
onChange={event => {
|
|
2314
|
-
const _newConfig =
|
|
2356
|
+
const _newConfig = cloneConfig(config)
|
|
2315
2357
|
_newConfig.legend.separateZero = event.target.checked
|
|
2316
2358
|
return setConfig(_newConfig)
|
|
2317
2359
|
}}
|
|
@@ -2771,7 +2813,7 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
|
|
|
2771
2813
|
type='checkbox'
|
|
2772
2814
|
checked={config.table.showDataTableLink}
|
|
2773
2815
|
onChange={event => {
|
|
2774
|
-
const _newConfig =
|
|
2816
|
+
const _newConfig = cloneConfig(config)
|
|
2775
2817
|
_newConfig.table.showDataTableLink = event.target.checked
|
|
2776
2818
|
setConfig(_newConfig)
|
|
2777
2819
|
}}
|
|
@@ -2785,7 +2827,7 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
|
|
|
2785
2827
|
type='checkbox'
|
|
2786
2828
|
checked={config.table.showDownloadUrl}
|
|
2787
2829
|
onChange={event => {
|
|
2788
|
-
const _newConfig =
|
|
2830
|
+
const _newConfig = cloneConfig(config)
|
|
2789
2831
|
_newConfig.table.showDownloadUrl = event.target.checked
|
|
2790
2832
|
setConfig(_newConfig)
|
|
2791
2833
|
}}
|
|
@@ -2911,7 +2953,7 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
|
|
|
2911
2953
|
type='checkbox'
|
|
2912
2954
|
checked={config.general.fullBorder || false}
|
|
2913
2955
|
onChange={event => {
|
|
2914
|
-
const _newConfig =
|
|
2956
|
+
const _newConfig = cloneConfig(config)
|
|
2915
2957
|
_newConfig.general.fullBorder = event.target.checked
|
|
2916
2958
|
setConfig(_newConfig)
|
|
2917
2959
|
}}
|
|
@@ -2933,6 +2975,15 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
|
|
|
2933
2975
|
<label>
|
|
2934
2976
|
<span className='edit-label'>Map Color Palette</span>
|
|
2935
2977
|
</label>
|
|
2978
|
+
<div className='mb-2'>
|
|
2979
|
+
<small className='text-muted'>
|
|
2980
|
+
Review color contrasts{' '}
|
|
2981
|
+
<a href='https://webaim.org/resources/contrastchecker/' target='_blank' rel='noopener noreferrer'>
|
|
2982
|
+
here
|
|
2983
|
+
</a>
|
|
2984
|
+
</small>
|
|
2985
|
+
</div>
|
|
2986
|
+
<DeveloperPaletteRollback config={config} updateConfig={setConfig} />
|
|
2936
2987
|
<InputToggle
|
|
2937
2988
|
type='3d'
|
|
2938
2989
|
section='general'
|
|
@@ -2941,127 +2992,71 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
|
|
|
2941
2992
|
size='small'
|
|
2942
2993
|
label='Use selected palette in reverse order'
|
|
2943
2994
|
onClick={() => {
|
|
2944
|
-
const _state =
|
|
2995
|
+
const _state = cloneConfig(config)
|
|
2996
|
+
const currentPaletteName = config.general.palette?.name || ''
|
|
2945
2997
|
_state.general.palette.isReversed = !_state.general.palette.isReversed
|
|
2946
2998
|
let paletteName = ''
|
|
2947
|
-
if (_state.general.palette.isReversed && !
|
|
2948
|
-
paletteName =
|
|
2999
|
+
if (_state.general.palette.isReversed && !currentPaletteName.endsWith('reverse')) {
|
|
3000
|
+
paletteName = currentPaletteName + 'reverse'
|
|
2949
3001
|
}
|
|
2950
|
-
if (!_state.general.palette.isReversed &&
|
|
2951
|
-
paletteName =
|
|
3002
|
+
if (!_state.general.palette.isReversed && currentPaletteName.endsWith('reverse')) {
|
|
3003
|
+
paletteName = currentPaletteName.slice(0, -7)
|
|
2952
3004
|
}
|
|
2953
3005
|
if (paletteName) {
|
|
2954
|
-
_state.
|
|
3006
|
+
_state.general.palette.name = paletteName
|
|
2955
3007
|
}
|
|
2956
3008
|
setConfig(_state)
|
|
2957
3009
|
}}
|
|
2958
3010
|
value={config.general.palette.isReversed}
|
|
2959
3011
|
/>
|
|
2960
3012
|
<span>Sequential</span>
|
|
2961
|
-
<
|
|
2962
|
-
{sequential
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
backgroundColor: colorPalettes[palette][6]
|
|
2973
|
-
}
|
|
2974
|
-
|
|
2975
|
-
return (
|
|
2976
|
-
<li
|
|
2977
|
-
title={palette}
|
|
2978
|
-
key={palette}
|
|
2979
|
-
onClick={() => {
|
|
2980
|
-
const _newConfig = _.cloneDeep(config)
|
|
2981
|
-
_newConfig.color = palette
|
|
2982
|
-
setConfig(_newConfig)
|
|
2983
|
-
}}
|
|
2984
|
-
className={config.color === palette ? 'selected' : ''}
|
|
2985
|
-
>
|
|
2986
|
-
<span style={colorOne}></span>
|
|
2987
|
-
<span style={colorTwo}></span>
|
|
2988
|
-
<span style={colorThree}></span>
|
|
2989
|
-
</li>
|
|
2990
|
-
)
|
|
2991
|
-
})}
|
|
2992
|
-
</ul>
|
|
3013
|
+
<PaletteSelector
|
|
3014
|
+
palettes={sequential}
|
|
3015
|
+
colorPalettes={colorPalettes}
|
|
3016
|
+
config={config}
|
|
3017
|
+
onPaletteSelect={handlePaletteSelection}
|
|
3018
|
+
selectedPalette={getCurrentPaletteName(config)}
|
|
3019
|
+
colorIndices={[2, 3, 5]}
|
|
3020
|
+
className='color-palette'
|
|
3021
|
+
element='li'
|
|
3022
|
+
getItemClassName={getPaletteClassName}
|
|
3023
|
+
/>
|
|
2993
3024
|
<span>Non-Sequential</span>
|
|
2994
|
-
<
|
|
2995
|
-
{nonSequential
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3025
|
+
<PaletteSelector
|
|
3026
|
+
palettes={nonSequential}
|
|
3027
|
+
colorPalettes={colorPalettes}
|
|
3028
|
+
config={config}
|
|
3029
|
+
onPaletteSelect={handlePaletteSelection}
|
|
3030
|
+
selectedPalette={getCurrentPaletteName(config)}
|
|
3031
|
+
colorIndices={[2, 3, 5]}
|
|
3032
|
+
className='color-palette'
|
|
3033
|
+
element='li'
|
|
3034
|
+
getItemClassName={getPaletteClassName}
|
|
3035
|
+
minColorsForFilter={(_, paletteAccessor, config) => {
|
|
3036
|
+
if (paletteAccessor.length <= 8 && config.general.geoType === 'us-region') {
|
|
3037
|
+
return false
|
|
3002
3038
|
}
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
}
|
|
3007
|
-
|
|
3008
|
-
// hide palettes with too few colors for region maps
|
|
3009
|
-
if (colorPalettes[palette].length <= 8 && config.general.geoType === 'us-region') {
|
|
3010
|
-
return ''
|
|
3011
|
-
}
|
|
3012
|
-
return (
|
|
3013
|
-
<li
|
|
3014
|
-
title={palette}
|
|
3015
|
-
key={palette}
|
|
3016
|
-
onClick={() => {
|
|
3017
|
-
const _newConfig = _.cloneDeep(config)
|
|
3018
|
-
_newConfig.color = palette
|
|
3019
|
-
setConfig(_newConfig)
|
|
3020
|
-
}}
|
|
3021
|
-
className={config.color === palette ? 'selected' : ''}
|
|
3022
|
-
>
|
|
3023
|
-
<span style={colorOne}></span>
|
|
3024
|
-
<span style={colorTwo}></span>
|
|
3025
|
-
<span style={colorThree}></span>
|
|
3026
|
-
</li>
|
|
3027
|
-
)
|
|
3028
|
-
})}
|
|
3029
|
-
</ul>
|
|
3039
|
+
return true
|
|
3040
|
+
}}
|
|
3041
|
+
/>
|
|
3030
3042
|
<span>Colorblind Safe</span>
|
|
3031
|
-
<
|
|
3032
|
-
{accessibleColors
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
// hide palettes with too few colors for region maps
|
|
3046
|
-
if (colorPalettes[palette].length <= 8 && config.general.geoType === 'us-region') {
|
|
3047
|
-
return ''
|
|
3043
|
+
<PaletteSelector
|
|
3044
|
+
palettes={accessibleColors}
|
|
3045
|
+
colorPalettes={colorPalettes}
|
|
3046
|
+
config={config}
|
|
3047
|
+
onPaletteSelect={handlePaletteSelection}
|
|
3048
|
+
selectedPalette={getCurrentPaletteName(config)}
|
|
3049
|
+
colorIndices={[2, 3, 5]}
|
|
3050
|
+
className='color-palette'
|
|
3051
|
+
element='li'
|
|
3052
|
+
getItemClassName={getPaletteClassName}
|
|
3053
|
+
minColorsForFilter={(_, paletteAccessor, config) => {
|
|
3054
|
+
if (paletteAccessor.length <= 8 && config.general.geoType === 'us-region') {
|
|
3055
|
+
return false
|
|
3048
3056
|
}
|
|
3049
|
-
return
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
key={palette}
|
|
3053
|
-
onClick={() => {
|
|
3054
|
-
handleEditorChanges('color', palette)
|
|
3055
|
-
}}
|
|
3056
|
-
className={config.color === palette ? 'selected' : ''}
|
|
3057
|
-
>
|
|
3058
|
-
<span style={colorOne}></span>
|
|
3059
|
-
<span style={colorTwo}></span>
|
|
3060
|
-
<span style={colorThree}></span>
|
|
3061
|
-
</li>
|
|
3062
|
-
)
|
|
3063
|
-
})}
|
|
3064
|
-
</ul>
|
|
3057
|
+
return true
|
|
3058
|
+
}}
|
|
3059
|
+
/>
|
|
3065
3060
|
<label>
|
|
3066
3061
|
Geocode Settings
|
|
3067
3062
|
<TextField
|
|
@@ -3116,7 +3111,7 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
|
|
|
3116
3111
|
type='checkbox'
|
|
3117
3112
|
checked={config.general.allowMapZoom}
|
|
3118
3113
|
onChange={event => {
|
|
3119
|
-
const _newConfig =
|
|
3114
|
+
const _newConfig = cloneConfig(config)
|
|
3120
3115
|
_newConfig.general.allowMapZoom = event.target.checked
|
|
3121
3116
|
_newConfig.mapPosition.coordinates = config.general.geoType === 'world' ? [0, 30] : [0, 0]
|
|
3122
3117
|
_newConfig.mapPosition.zoom = 1
|
|
@@ -3132,7 +3127,7 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
|
|
|
3132
3127
|
type='checkbox'
|
|
3133
3128
|
checked={config.visual.extraBubbleBorder}
|
|
3134
3129
|
onChange={event => {
|
|
3135
|
-
const _newConfig =
|
|
3130
|
+
const _newConfig = cloneConfig(config)
|
|
3136
3131
|
_newConfig.visual.extraBubbleBorder = event.target.checked
|
|
3137
3132
|
setConfig(_newConfig)
|
|
3138
3133
|
}}
|
|
@@ -3371,9 +3366,26 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
|
|
|
3371
3366
|
</AccordionItem>
|
|
3372
3367
|
{config.general.geoType === 'us' && <Panels.PatternSettings name='Pattern Settings' />}
|
|
3373
3368
|
{config.general.geoType !== 'us-county' && <Panels.Annotate name='Text Annotations' />}
|
|
3369
|
+
<PanelMarkup
|
|
3370
|
+
name='Markup Variables'
|
|
3371
|
+
markupVariables={config.markupVariables || []}
|
|
3372
|
+
data={config.data || []}
|
|
3373
|
+
enableMarkupVariables={config.enableMarkupVariables || false}
|
|
3374
|
+
onMarkupVariablesChange={variables => setConfig({ ...config, markupVariables: variables })}
|
|
3375
|
+
onToggleEnable={enabled => setConfig({ ...config, enableMarkupVariables: enabled })}
|
|
3376
|
+
/>
|
|
3374
3377
|
</Accordion>
|
|
3375
3378
|
<AdvancedEditor loadConfig={setConfig} config={config} convertStateToConfig={convertStateToConfig} />
|
|
3376
3379
|
</Layout.Sidebar>
|
|
3380
|
+
|
|
3381
|
+
{showConversionModal && (
|
|
3382
|
+
<PaletteConversionModal
|
|
3383
|
+
onConfirm={handleConversionConfirm}
|
|
3384
|
+
onCancel={handleConversionCancel}
|
|
3385
|
+
onReturnToV1={handleReturnToV1}
|
|
3386
|
+
paletteName={pendingPaletteSelection?.palette}
|
|
3387
|
+
/>
|
|
3388
|
+
)}
|
|
3377
3389
|
</ErrorBoundary>
|
|
3378
3390
|
)
|
|
3379
3391
|
}
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
} from 'react-accessible-accordion'
|
|
9
9
|
import ConfigContext from '../../../context'
|
|
10
10
|
import _ from 'lodash'
|
|
11
|
+
import { cloneConfig } from '@cdc/core/helpers/cloneConfig'
|
|
11
12
|
|
|
12
13
|
const shapeOptions = ['Arrow Up', 'Arrow Down', 'Arrow Right', 'Arrow Left', 'None']
|
|
13
14
|
|
|
@@ -105,7 +106,7 @@ const HexSettingShapeColumns = props => {
|
|
|
105
106
|
type='text'
|
|
106
107
|
value={shapeGroup.legendTitle || ''}
|
|
107
108
|
onChange={e => {
|
|
108
|
-
const newConfig =
|
|
109
|
+
const newConfig = cloneConfig(config)
|
|
109
110
|
newConfig.hexMap.shapeGroups[shapeGroupIndex].legendTitle = e.target.value
|
|
110
111
|
setConfig(newConfig)
|
|
111
112
|
}}
|
|
@@ -243,7 +244,7 @@ const HexSettingShapeColumns = props => {
|
|
|
243
244
|
className='cove-button'
|
|
244
245
|
style={{ marginTop: '15px' }}
|
|
245
246
|
onClick={() => {
|
|
246
|
-
const newConfig =
|
|
247
|
+
const newConfig = cloneConfig(config)
|
|
247
248
|
_.set(
|
|
248
249
|
newConfig,
|
|
249
250
|
'hexMap.shapeGroups',
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
AccordionItemButton
|
|
8
8
|
} from 'react-accessible-accordion'
|
|
9
9
|
import ConfigContext from '../../../../context'
|
|
10
|
+
import { useLegendMemoContext } from '../../../../context/LegendMemoContext'
|
|
10
11
|
import { type MapContext } from '../../../../types/MapContext'
|
|
11
12
|
import Button from '@cdc/core/components/elements/Button'
|
|
12
13
|
import Tooltip from '@cdc/core/components/ui/Tooltip'
|
|
@@ -14,6 +15,7 @@ import Icon from '@cdc/core/components/ui/Icon'
|
|
|
14
15
|
import './Panel.PatternSettings-style.css'
|
|
15
16
|
import Alert from '@cdc/core/components/Alert'
|
|
16
17
|
import _ from 'lodash'
|
|
18
|
+
import { cloneConfig } from '@cdc/core/helpers/cloneConfig'
|
|
17
19
|
|
|
18
20
|
// topojson helpers for checking color contrasts
|
|
19
21
|
import { feature } from 'topojson-client'
|
|
@@ -26,8 +28,8 @@ type PanelProps = {
|
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
const PatternSettings = ({ name }: PanelProps) => {
|
|
29
|
-
const { config, setConfig, runtimeData,
|
|
30
|
-
|
|
31
|
+
const { config, setConfig, runtimeData, runtimeLegend } = useContext<MapContext>(ConfigContext)
|
|
32
|
+
const { legendMemo, legendSpecialClassLastMemo } = useLegendMemoContext()
|
|
31
33
|
const defaultPattern = 'circles'
|
|
32
34
|
const patternTypes = ['circles', 'waves', 'lines']
|
|
33
35
|
|
|
@@ -114,14 +116,14 @@ const PatternSettings = ({ name }: PanelProps) => {
|
|
|
114
116
|
}
|
|
115
117
|
|
|
116
118
|
const handlePatternFieldUpdate = (field: string, color: string, patternIndex: number) => {
|
|
117
|
-
const _newConfig =
|
|
119
|
+
const _newConfig = cloneConfig(config)
|
|
118
120
|
_newConfig.map.patterns[patternIndex][field] = color
|
|
119
121
|
reviewColorContrast(_newConfig, patternIndex)
|
|
120
122
|
setConfig(_newConfig)
|
|
121
123
|
}
|
|
122
124
|
|
|
123
125
|
const handleRemovePattern = index => {
|
|
124
|
-
const _newConfig =
|
|
126
|
+
const _newConfig = cloneConfig(config)
|
|
125
127
|
const updatedPatterns = config.map.patterns.filter((pattern, i) => i !== index)
|
|
126
128
|
_newConfig.map.patterns = updatedPatterns
|
|
127
129
|
if (checkPatternContrasts()) {
|
|
@@ -161,7 +163,7 @@ const PatternSettings = ({ name }: PanelProps) => {
|
|
|
161
163
|
{patterns.length > 0 && (
|
|
162
164
|
<Alert
|
|
163
165
|
type={checkPatternContrasts() ? 'success' : 'danger'}
|
|
164
|
-
message='Pattern colors must comply with <
|
|
166
|
+
message='Pattern colors must comply with <a href="https://www.w3.org/TR/WCAG21/">WCAG 2.1</a> 3:1 contrast ratio.'
|
|
165
167
|
/>
|
|
166
168
|
)}
|
|
167
169
|
<br />
|
package/src/components/Geo.tsx
CHANGED