@cdc/map 4.26.2 → 4.26.4

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 (118) hide show
  1. package/CONFIG.md +235 -0
  2. package/README.md +70 -24
  3. package/dist/cdcmap-CY9IcPSi.es.js +6 -0
  4. package/dist/cdcmap-DlpiY3fQ.es.js +4 -0
  5. package/dist/cdcmap.js +31260 -27946
  6. package/examples/{testing-layer-2.json → __data__/testing-layer-2.json} +1 -1
  7. package/examples/{testing-layer.json → __data__/testing-layer.json} +1 -1
  8. package/examples/county-hsa-toggle.json +51993 -0
  9. package/examples/custom-map-layers.json +2 -2
  10. package/examples/default-county.json +3 -3
  11. package/examples/minimal-example.json +69 -0
  12. package/examples/private/annotation-bug.json +642 -0
  13. package/examples/private/css-issue.json +314 -0
  14. package/examples/private/region-breaking.json +1639 -0
  15. package/examples/private/test1.json +27247 -0
  16. package/package.json +4 -4
  17. package/src/CdcMap.tsx +3 -14
  18. package/src/CdcMapComponent.tsx +302 -164
  19. package/src/_stories/CdcMap.Defaults.smoke.stories.tsx +76 -0
  20. package/src/_stories/CdcMap.Editor.ColumnsSectionTests.stories.tsx +601 -0
  21. package/src/_stories/CdcMap.Editor.DataTableSectionTests.stories.tsx +404 -0
  22. package/src/_stories/CdcMap.Editor.FiltersSectionTests.stories.tsx +229 -0
  23. package/src/_stories/CdcMap.Editor.GeneralSectionTests.stories.tsx +262 -0
  24. package/src/_stories/CdcMap.Editor.LegendSectionTests.stories.tsx +541 -0
  25. package/src/_stories/CdcMap.Editor.MultiCountryWorldMapTests.stories.tsx +359 -0
  26. package/src/_stories/CdcMap.Editor.PatternSettingsSectionTests.stories.tsx +516 -0
  27. package/src/_stories/CdcMap.Editor.SmallMultiplesSectionTests.stories.tsx +165 -0
  28. package/src/_stories/CdcMap.Editor.TextAnnotationsSectionTests.stories.tsx +145 -0
  29. package/src/_stories/CdcMap.Editor.TypeSectionTests.stories.tsx +312 -0
  30. package/src/_stories/CdcMap.Editor.VisualSectionTests.stories.tsx +359 -0
  31. package/src/_stories/CdcMap.Editor.ZoomControlsTests.stories.tsx +88 -0
  32. package/src/_stories/{CdcMap.stories.tsx → CdcMap.smoke.stories.tsx} +23 -1
  33. package/src/_stories/Map.HTMLInDataTable.stories.tsx +385 -0
  34. package/src/_stories/_mock/legends/legend-tests.json +3 -3
  35. package/src/_stories/_mock/multi-state-show-unselected.json +82 -0
  36. package/src/cdcMapComponent.styles.css +2 -2
  37. package/src/components/Annotation/Annotation.Draggable.styles.css +4 -4
  38. package/src/components/Annotation/AnnotationDropdown.styles.css +1 -1
  39. package/src/components/Annotation/AnnotationList.styles.css +13 -13
  40. package/src/components/Annotation/AnnotationList.tsx +1 -1
  41. package/src/components/EditorPanel/components/EditorPanel.tsx +905 -416
  42. package/src/components/EditorPanel/components/HexShapeSettings.tsx +1 -1
  43. package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +112 -117
  44. package/src/components/EditorPanel/components/Panels/Panel.PatternSettings-style.css +1 -1
  45. package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +31 -15
  46. package/src/components/EditorPanel/components/editorPanel.styles.css +55 -25
  47. package/src/components/Legend/components/Legend.tsx +12 -7
  48. package/src/components/Legend/components/LegendGroup/legend.group.css +5 -5
  49. package/src/components/Legend/components/LegendItem.Hex.tsx +4 -2
  50. package/src/components/Legend/components/index.scss +2 -3
  51. package/src/components/NavigationMenu.tsx +2 -1
  52. package/src/components/SmallMultiples/SmallMultiples.css +5 -5
  53. package/src/components/SmallMultiples/SynchronizedTooltip.tsx +1 -1
  54. package/src/components/UsaMap/components/SingleState/SingleState.StateOutput.tsx +32 -17
  55. package/src/components/UsaMap/components/TerritoriesSection.tsx +3 -2
  56. package/src/components/UsaMap/components/Territory/Territory.Rectangle.tsx +13 -8
  57. package/src/components/UsaMap/components/UsaMap.County.tsx +629 -231
  58. package/src/components/UsaMap/components/UsaMap.Region.styles.css +1 -1
  59. package/src/components/UsaMap/components/UsaMap.SingleState.styles.css +2 -2
  60. package/src/components/UsaMap/components/UsaMap.State.tsx +14 -9
  61. package/src/components/UsaMap/data/cb_2019_us_county_20m.json +75817 -1
  62. package/src/components/UsaMap/data/hsa_fips_mapping.json +3144 -0
  63. package/src/components/WorldMap/WorldMap.tsx +10 -13
  64. package/src/components/WorldMap/data/world-topo-updated.json +1 -0
  65. package/src/components/WorldMap/data/world-topo.json +1 -1
  66. package/src/components/WorldMap/worldMap.styles.css +1 -1
  67. package/src/components/ZoomControls.tsx +49 -18
  68. package/src/components/zoomControls.styles.css +27 -11
  69. package/src/data/initial-state.js +15 -5
  70. package/src/data/legacy-defaults.ts +8 -0
  71. package/src/data/supported-counties.json +1 -1
  72. package/src/data/supported-geos.js +19 -0
  73. package/src/helpers/colors.ts +2 -1
  74. package/src/helpers/countyTerritories.ts +38 -0
  75. package/src/helpers/dataTableHelpers.ts +85 -0
  76. package/src/helpers/displayGeoName.ts +19 -11
  77. package/src/helpers/getMapContainerClasses.ts +8 -2
  78. package/src/helpers/getMatchingPatternForRow.ts +67 -0
  79. package/src/helpers/getPatternForRow.ts +11 -18
  80. package/src/helpers/tests/countyTerritories.test.ts +87 -0
  81. package/src/helpers/tests/dataTableHelpers.test.ts +78 -0
  82. package/src/helpers/tests/displayGeoName.test.ts +17 -0
  83. package/src/helpers/tests/getMatchingPatternForRow.test.ts +150 -0
  84. package/src/helpers/tests/getPatternForRow.test.ts +140 -2
  85. package/src/helpers/urlDataHelpers.ts +7 -1
  86. package/src/hooks/useApplyTooltipsToGeo.tsx +7 -4
  87. package/src/hooks/useMapLayers.tsx +1 -1
  88. package/src/hooks/useResizeObserver.ts +36 -22
  89. package/src/hooks/useTooltip.test.tsx +64 -0
  90. package/src/hooks/useTooltip.ts +46 -15
  91. package/src/scss/editor-panel.scss +1 -1
  92. package/src/scss/main.scss +140 -6
  93. package/src/scss/map.scss +9 -4
  94. package/src/store/map.actions.ts +5 -0
  95. package/src/store/map.reducer.ts +13 -0
  96. package/src/test/CdcMap.test.jsx +26 -2
  97. package/src/types/MapConfig.ts +28 -4
  98. package/src/types/MapContext.ts +5 -1
  99. package/topojson-updater/README.txt +1 -1
  100. package/dist/cdcmap-Cf9_fbQf.es.js +0 -6
  101. package/examples/__data__/city-state-data.json +0 -668
  102. package/examples/city-state.json +0 -434
  103. package/examples/default-world-data.json +0 -1450
  104. package/examples/new-cities.json +0 -656
  105. package/src/_stories/CdcMap.Editor.stories.tsx +0 -3475
  106. package/src/helpers/componentHelpers.ts +0 -8
  107. package/topojson-updater/package-lock.json +0 -223
  108. /package/src/_stories/{CdcMap.ColumnWrap.stories.tsx → CdcMap.ColumnWrap.smoke.stories.tsx} +0 -0
  109. /package/src/_stories/{CdcMap.DistrictOfColumbia.stories.tsx → CdcMap.DistrictOfColumbia.smoke.stories.tsx} +0 -0
  110. /package/src/_stories/{CdcMap.Filters.stories.tsx → CdcMap.Filters.smoke.stories.tsx} +0 -0
  111. /package/src/_stories/{CdcMap.Legend.Gradient.stories.tsx → CdcMap.Legend.Gradient.smoke.stories.tsx} +0 -0
  112. /package/src/_stories/{CdcMap.Legend.stories.tsx → CdcMap.Legend.smoke.stories.tsx} +0 -0
  113. /package/src/_stories/{CdcMap.Patterns.stories.tsx → CdcMap.Patterns.smoke.stories.tsx} +0 -0
  114. /package/src/_stories/{CdcMap.SmallMultiples.stories.tsx → CdcMap.SmallMultiples.smoke.stories.tsx} +0 -0
  115. /package/src/_stories/{CdcMap.Table.stories.tsx → CdcMap.Table.smoke.stories.tsx} +0 -0
  116. /package/src/_stories/{CdcMap.ZeroColor.stories.tsx → CdcMap.ZeroColor.smoke.stories.tsx} +0 -0
  117. /package/src/_stories/{GoogleMap.stories.tsx → GoogleMap.smoke.stories.tsx} +0 -0
  118. /package/src/_stories/{UsaMap.NoData.stories.tsx → UsaMap.NoData.smoke.stories.tsx} +0 -0
@@ -12,22 +12,24 @@ import {
12
12
  } from 'react-accessible-accordion'
13
13
  import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd'
14
14
  import { useDebounce } from 'use-debounce'
15
- import _ from 'lodash'
15
+ import cloneDeep from 'lodash/cloneDeep'
16
+ import includes from 'lodash/includes'
16
17
  import { Tooltip as ReactTooltip } from 'react-tooltip'
17
18
  import 'react-tooltip/dist/react-tooltip.css'
18
19
  import Panels from './Panels'
19
- import Layout from '@cdc/core/components/Layout'
20
20
 
21
21
  // Data
22
22
  import { mapColorPalettes as colorPalettes } from '@cdc/core/data/colorPalettes'
23
23
  import { supportedStatesFipsCodes, supportedCountries } from '../../../data/supported-geos.js'
24
24
  import { getSupportedCountryOptions } from '../../../helpers/getCountriesPicked'
25
+ import { displayGeoName } from '../../../helpers/displayGeoName'
25
26
 
26
27
  // Components - Core
27
28
  import { EditorPanel as BaseEditorPanel } from '@cdc/core/components/EditorPanel/EditorPanel'
28
29
  import AdvancedEditor from '@cdc/core/components/AdvancedEditor'
29
30
  import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
30
31
  import Icon from '@cdc/core/components/ui/Icon'
32
+ import GroupedList from '@cdc/core/components/EditorPanel/GroupedList'
31
33
  import InputToggle from '@cdc/core/components/inputs/InputToggle'
32
34
  import Tooltip from '@cdc/core/components/ui/Tooltip'
33
35
  import VizFilterEditor from '@cdc/core/components/EditorPanel/VizFilterEditor'
@@ -49,19 +51,27 @@ import { MapContext } from '../../../types/MapContext.js'
49
51
  import Alert from '@cdc/core/components/Alert'
50
52
  import { updateFieldFactory } from '@cdc/core/helpers/updateFieldFactory'
51
53
  import { CheckBox, Select, TextField } from '@cdc/core/components/EditorPanel/Inputs'
54
+ import DownloadUrlControls from '@cdc/core/components/EditorPanel/DownloadUrlControls'
55
+ import Button from '@cdc/core/components/elements/Button'
56
+ import StyleTreatmentSection from '@cdc/core/components/EditorPanel/sections/StyleTreatmentSection'
52
57
  import { HeaderThemeSelector } from '@cdc/core/components/HeaderThemeSelector'
53
58
  import useColumnsRequiredChecker from '../../../hooks/useColumnsRequiredChecker'
54
59
  import { addUIDs } from '../../../helpers'
55
60
  import generateRuntimeData from '../../../helpers/generateRuntimeData'
56
61
 
57
- import '@cdc/core/styles/v2/components/editor.scss'
62
+ import '@cdc/core/components/EditorPanel/editor.scss'
58
63
  import './editorPanel.styles.css'
59
64
  import FootnotesEditor from '@cdc/core/components/EditorPanel/FootnotesEditor'
65
+ import CustomSortOrder from '@cdc/core/components/EditorPanel/CustomSortOrder'
60
66
  import { Datasets } from '@cdc/core/types/DataSet'
61
67
  import MultiSelect from '@cdc/core/components/MultiSelect'
62
68
  import { paletteMigrationMap } from '@cdc/core/helpers/palettes/migratePaletteName'
63
69
  import { isV1Palette, getCurrentPaletteName, migratePaletteWithMap } from '@cdc/core/helpers/palettes/utils'
64
- import { USE_V2_MIGRATION } from '@cdc/core/helpers/constants'
70
+ import {
71
+ ENABLE_CHART_MAP_TP5_TREATMENT_SELECTION,
72
+ ENABLE_MAP_DATA_BITE_VISUAL_SETTINGS,
73
+ USE_V2_MIGRATION
74
+ } from '@cdc/core/helpers/constants'
65
75
  import { isCoveDeveloperMode } from '@cdc/core/helpers/queryStringUtils'
66
76
  import { PaletteSelector, DeveloperPaletteRollback } from '@cdc/core/components/PaletteSelector'
67
77
  import PaletteConversionModal from '@cdc/core/components/PaletteConversionModal'
@@ -71,6 +81,39 @@ type MapEditorPanelProps = {
71
81
  datasets?: Datasets
72
82
  }
73
83
 
84
+ type ColumnSectionProps = {
85
+ fieldKey: 'geo' | 'primary'
86
+ fieldName: string
87
+ show: boolean
88
+ setShow: (fieldKey: 'geo' | 'primary', value: boolean) => void
89
+ children: React.ReactNode
90
+ }
91
+
92
+ const ColumnSection = ({ fieldKey, fieldName, show, setShow, children }: ColumnSectionProps) => {
93
+ if (!show) {
94
+ return (
95
+ <div className='mb-1'>
96
+ <button type='button' className='btn btn-light' onClick={() => setShow(fieldKey, true)}>
97
+ <Icon display='caretDown' />
98
+ </button>
99
+ <span> {fieldName}</span>
100
+ </div>
101
+ )
102
+ }
103
+
104
+ return (
105
+ <fieldset className='primary-fieldset edit-block column-section' key={fieldKey}>
106
+ <div className='column-section__header'>
107
+ <button type='button' className='btn btn-light' onClick={() => setShow(fieldKey, false)}>
108
+ <Icon display='caretUp' />
109
+ </button>
110
+ <span className='column-section__title'>{fieldName}</span>
111
+ </div>
112
+ {children}
113
+ </fieldset>
114
+ )
115
+ }
116
+
74
117
  const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
75
118
  const {
76
119
  setParentConfig,
@@ -114,11 +157,16 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
114
157
  const [loadedDefault, setLoadedDefault] = useState(false)
115
158
  const [activeFilterValueForDescription, setActiveFilterValueForDescription] = useState([0, 0])
116
159
  const [showConversionModal, setShowConversionModal] = useState(false)
160
+ const [columnSectionsOpen, setColumnSectionsOpen] = useState({ geo: true, primary: true })
117
161
  const [pendingPaletteSelection, setPendingPaletteSelection] = useState<{
118
162
  palette: string
119
163
  action: () => void
120
164
  } | null>(null)
121
165
 
166
+ const setColumnSectionOpen = (fieldKey: 'geo' | 'primary', value: boolean) => {
167
+ setColumnSectionsOpen(prev => ({ ...prev, [fieldKey]: value }))
168
+ }
169
+
122
170
  const {
123
171
  MapLayerHandlers: { handleMapLayer, handleAddLayer, handleRemoveLayer }
124
172
  } = useMapLayers(config, setConfig, false, tooltipId)
@@ -296,7 +344,7 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
296
344
  legend: {
297
345
  ...config.legend,
298
346
  position: value,
299
- hideBorder: _.includes(['top', 'bottom'], value)
347
+ hideBorder: includes(['top', 'bottom'], value)
300
348
  }
301
349
  })
302
350
  break
@@ -518,7 +566,13 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
518
566
  break
519
567
  case 'geoType':
520
568
  addUIDs(config, config.columns.geo.name)
521
- dispatch({ type: 'SET_POSITION', payload: [0, 30] })
569
+ dispatch({
570
+ type: 'SET_POSITION',
571
+ payload: {
572
+ coordinates: value === 'world' ? [0, 30] : [0, 0],
573
+ zoom: 1
574
+ }
575
+ })
522
576
 
523
577
  // If we're still working with default data, switch to the world default to show it as an example
524
578
  if (true === loadedDefault && 'world' === value) {
@@ -866,6 +920,11 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
866
920
  }
867
921
  }
868
922
 
923
+ const updateColumnOrder = (columnName, value) => {
924
+ const parsedValue = Number.parseInt(value, 10)
925
+ editColumn(columnName, 'order', Number.isNaN(parsedValue) ? undefined : parsedValue)
926
+ }
927
+
869
928
  // just adds a new column but not set to any data yet
870
929
  const addAdditionalColumn = number => {
871
930
  const columnKey = `additionalColumn${number}`
@@ -931,7 +990,7 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
931
990
  }
932
991
 
933
992
  // Remove the legend
934
- let strippedLegend = _.cloneDeep(config.legend)
993
+ let strippedLegend = cloneDeep(config.legend)
935
994
 
936
995
  delete strippedLegend.disabledAmt
937
996
 
@@ -941,7 +1000,7 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
941
1000
  delete strippedState.defaultData
942
1001
 
943
1002
  // Remove tooltips if they're active in the editor
944
- strippedState.general = _.cloneDeep(config.general)
1003
+ strippedState.general = cloneDeep(config.general)
945
1004
 
946
1005
  // Add columns property back to data if it's there
947
1006
  if (config.columns) {
@@ -963,7 +1022,7 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
963
1022
  const isV1PaletteConfig = isV1Palette(config)
964
1023
 
965
1024
  const executeSelection = () => {
966
- const _newConfig = _.cloneDeep(config)
1025
+ const _newConfig = cloneDeep(config)
967
1026
 
968
1027
  // If v2 migration is disabled, use the original palette name and keep v1 version
969
1028
  if (!USE_V2_MIGRATION) {
@@ -1062,6 +1121,44 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
1062
1121
 
1063
1122
  const updateField = updateFieldFactory(config, setConfig)
1064
1123
 
1124
+ const handleStyleTreatmentChange = (value: string) => {
1125
+ const useTp5Treatment = value === 'tp5'
1126
+
1127
+ setConfig({
1128
+ ...config,
1129
+ general: {
1130
+ ...config.general,
1131
+ titleStyle: useTp5Treatment ? 'small' : 'legacy'
1132
+ },
1133
+ visual: {
1134
+ ...config.visual,
1135
+ tp5Treatment: useTp5Treatment,
1136
+ border: useTp5Treatment ? false : config.visual?.border,
1137
+ borderColorTheme: useTp5Treatment ? false : config.visual?.borderColorTheme,
1138
+ accent: useTp5Treatment ? false : config.visual?.accent
1139
+ }
1140
+ })
1141
+ }
1142
+
1143
+ const handleTitleStyleChange = (newTitleStyle: string) => {
1144
+ setConfig({
1145
+ ...config,
1146
+ general: {
1147
+ ...config.general,
1148
+ titleStyle: newTitleStyle
1149
+ },
1150
+ visual:
1151
+ newTitleStyle === 'legacy'
1152
+ ? config.visual
1153
+ : {
1154
+ ...config.visual,
1155
+ border: undefined,
1156
+ borderColorTheme: undefined,
1157
+ accent: undefined
1158
+ }
1159
+ })
1160
+ }
1161
+
1065
1162
  const StateOptionList = () => {
1066
1163
  const arrOfArrays = Object.entries(supportedStatesFipsCodes)
1067
1164
 
@@ -1149,7 +1246,11 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
1149
1246
  ))
1150
1247
  }
1151
1248
 
1152
- const isLoadedFromUrl = config?.dataKey?.includes('http://') || config?.dataKey?.includes('https://')
1249
+ const isLoadedFromUrl =
1250
+ config?.dataFileSourceType === 'url' ||
1251
+ Boolean(config?.runtimeDataUrl || config?.dataUrl || config?.dataFileName) ||
1252
+ config?.dataKey?.includes('http://') ||
1253
+ config?.dataKey?.includes('https://')
1153
1254
 
1154
1255
  // Custom convertStateToConfig for map with map-specific logic
1155
1256
  const customConvertStateToConfig = () => {
@@ -1163,7 +1264,7 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
1163
1264
  }
1164
1265
 
1165
1266
  // Remove the legend
1166
- let strippedLegend = _.cloneDeep(config.legend)
1267
+ let strippedLegend = cloneDeep(config.legend)
1167
1268
 
1168
1269
  delete strippedLegend.disabledAmt
1169
1270
 
@@ -1173,7 +1274,7 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
1173
1274
  delete strippedState.defaultData
1174
1275
 
1175
1276
  // Remove tooltips if they're active in the editor
1176
- strippedState.general = _.cloneDeep(config.general)
1277
+ strippedState.general = cloneDeep(config.general)
1177
1278
 
1178
1279
  strippedState.general.showSidebar = 'hidden'
1179
1280
 
@@ -1268,6 +1369,41 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
1268
1369
  }}
1269
1370
  />
1270
1371
  )}
1372
+ {config.general.geoType === 'us-county' && (
1373
+ <>
1374
+ <CheckBox
1375
+ value={config.general.showNeighboringStates || false}
1376
+ fieldName='showNeighboringStates'
1377
+ label="Show Neighboring States' Data"
1378
+ updateField={updateField}
1379
+ section='general'
1380
+ />
1381
+ <CheckBox
1382
+ value={config.general.showHSABoundaries || false}
1383
+ fieldName='showHSABoundaries'
1384
+ label='Show HSA Boundaries'
1385
+ updateField={updateField}
1386
+ section='general'
1387
+ tooltip={
1388
+ <Tooltip style={{ textTransform: 'none' }}>
1389
+ <Tooltip.Target>
1390
+ <Icon
1391
+ display='question'
1392
+ style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }}
1393
+ />
1394
+ </Tooltip.Target>
1395
+ <Tooltip.Content>
1396
+ <p>
1397
+ Health Service Areas (HSAs) are a county or cluster of contiguous counties which are
1398
+ relatively self-contained with respect to hospital care. Set HSA description in
1399
+ tooltip under the Columns accordion.
1400
+ </p>
1401
+ </Tooltip.Content>
1402
+ </Tooltip>
1403
+ }
1404
+ />
1405
+ </>
1406
+ )}
1271
1407
  {(config.general.geoType === 'us-county' || config.general.geoType === 'single-state') && (
1272
1408
  <Select
1273
1409
  label='County Census Year'
@@ -1319,20 +1455,31 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
1319
1455
  {/* Type */}
1320
1456
  {/* Select > Filter a state */}
1321
1457
  {config.general.geoType === 'single-state' && (
1322
- <label>
1323
- <span>States Selector</span>
1324
- <MultiSelect
1325
- selected={config.general.statesPicked.map(state => state.stateName)}
1326
- options={StateOptionList().map(option => ({
1327
- value: option.props.value,
1328
- label: option.props.children
1329
- }))}
1330
- fieldName={'statesPicked'}
1331
- updateField={(_, __, ___, selectedOptions) => {
1332
- handleEditorChanges('chooseState', selectedOptions)
1333
- }}
1334
- />
1335
- </label>
1458
+ <>
1459
+ <label>
1460
+ <span>States Selector</span>
1461
+ <MultiSelect
1462
+ selected={config.general.statesPicked.map(state => state.stateName)}
1463
+ options={StateOptionList().map(option => ({
1464
+ value: option.props.value,
1465
+ label: option.props.children
1466
+ }))}
1467
+ fieldName={'statesPicked'}
1468
+ updateField={(_, __, ___, selectedOptions) => {
1469
+ handleEditorChanges('chooseState', selectedOptions)
1470
+ }}
1471
+ />
1472
+ </label>
1473
+ {config.general.statesPicked && config.general.statesPicked.length > 0 && (
1474
+ <CheckBox
1475
+ value={config.general.hideUnselectedStates !== false}
1476
+ fieldName='hideUnselectedStates'
1477
+ label='Hide Unselected States'
1478
+ updateField={updateField}
1479
+ section='general'
1480
+ />
1481
+ )}
1482
+ </>
1336
1483
  )}
1337
1484
  {/* Country Selection for World Maps */}
1338
1485
  {config.general.geoType === 'world' && (
@@ -1494,14 +1641,26 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
1494
1641
  />
1495
1642
  )}
1496
1643
 
1497
- {'us' === config.general.geoType && (
1644
+ {['us', 'us-county'].includes(config.general.geoType) && (
1498
1645
  <CheckBox
1499
- value={general.territoriesAlwaysShow || false}
1646
+ value={general.territoriesAlwaysShow ?? true}
1500
1647
  section='general'
1501
1648
  subsection={null}
1502
1649
  fieldName='territoriesAlwaysShow'
1503
- label='Show All Territories'
1504
- updateField={updateField}
1650
+ label='Show Available Territories'
1651
+ updateField={() => {
1652
+ setConfig({
1653
+ ...config,
1654
+ general: {
1655
+ ...config.general,
1656
+ territoriesAlwaysShow: !(general.territoriesAlwaysShow ?? true)
1657
+ },
1658
+ migrations: {
1659
+ ...config.migrations,
1660
+ showPuertoRico: false
1661
+ }
1662
+ })
1663
+ }}
1505
1664
  />
1506
1665
  )}
1507
1666
  </AccordionItemPanel>
@@ -1541,12 +1700,12 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
1541
1700
  section='general'
1542
1701
  fieldName='titleStyle'
1543
1702
  label='Title Style'
1544
- updateField={updateField}
1545
1703
  options={[
1546
1704
  { value: 'small', label: 'Small (h3)' },
1547
1705
  { value: 'large', label: 'Large (h2)' },
1548
1706
  { value: 'legacy', label: 'Legacy' }
1549
1707
  ]}
1708
+ onChange={event => handleTitleStyleChange(event.target.value)}
1550
1709
  tooltip={
1551
1710
  <Tooltip style={{ textTransform: 'none' }}>
1552
1711
  <Tooltip.Target>
@@ -1649,6 +1808,29 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
1649
1808
  <input type="checkbox" checked={ state.general.showDownloadMediaButton } onChange={(event) => { handleEditorChanges("toggleDownloadMediaButton", event.target.checked) }} />
1650
1809
  <span className="edit-label">Enable Media Download</span>
1651
1810
  </label> */}
1811
+ <Select
1812
+ value={config.locale}
1813
+ fieldName='locale'
1814
+ label='Language for dates and numbers'
1815
+ updateField={updateField}
1816
+ options={[
1817
+ { value: 'en-US', label: 'English (en-US)' },
1818
+ { value: 'es-MX', label: 'Spanish (es-MX)' }
1819
+ ]}
1820
+ tooltip={
1821
+ <Tooltip style={{ textTransform: 'none' }}>
1822
+ <Tooltip.Target>
1823
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
1824
+ </Tooltip.Target>
1825
+ <Tooltip.Content>
1826
+ <p>
1827
+ Change the language (locale) for this visualization to alter the way dates and numbers are
1828
+ formatted.
1829
+ </p>
1830
+ </Tooltip.Content>
1831
+ </Tooltip>
1832
+ }
1833
+ />
1652
1834
  </AccordionItemPanel>
1653
1835
  </AccordionItem>
1654
1836
  <AccordionItem>
@@ -1658,7 +1840,12 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
1658
1840
  <AccordionItemButton>Columns</AccordionItemButton>
1659
1841
  </AccordionItemHeading>
1660
1842
  <AccordionItemPanel>
1661
- <fieldset className='primary-fieldset edit-block'>
1843
+ <ColumnSection
1844
+ fieldKey='geo'
1845
+ fieldName='Geography'
1846
+ show={columnSectionsOpen.geo}
1847
+ setShow={setColumnSectionOpen}
1848
+ >
1662
1849
  <label>
1663
1850
  <span className='edit-label column-heading'>
1664
1851
  Geography
@@ -1701,6 +1888,24 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
1701
1888
  label='Hide Geography Column Name in Tooltip'
1702
1889
  updateField={updateField}
1703
1890
  />
1891
+ <TextField
1892
+ value={columns.geo.label}
1893
+ section='columns'
1894
+ subsection='geo'
1895
+ fieldName='label'
1896
+ label='Geography Column Label'
1897
+ updateField={updateField}
1898
+ tooltip={
1899
+ <Tooltip style={{ textTransform: 'none' }}>
1900
+ <Tooltip.Target>
1901
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
1902
+ </Tooltip.Target>
1903
+ <Tooltip.Content>
1904
+ <p>Enter a label for the geography column in tooltips and the data table.</p>
1905
+ </Tooltip.Content>
1906
+ </Tooltip>
1907
+ }
1908
+ />
1704
1909
  <TextField
1705
1910
  value={config.general.geoLabelOverride}
1706
1911
  section='general'
@@ -1719,9 +1924,86 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
1719
1924
  </Tooltip>
1720
1925
  }
1721
1926
  />
1722
- </fieldset>
1927
+ <label className='mt-2'>
1928
+ <span className='edit-label column-heading'>
1929
+ Geography Display Column
1930
+ <Tooltip style={{ textTransform: 'none' }}>
1931
+ <Tooltip.Target>
1932
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
1933
+ </Tooltip.Target>
1934
+ <Tooltip.Content>
1935
+ <p>
1936
+ Optional. Select a column containing alternate display names for geographies (e.g.,
1937
+ translated names). These will be shown in tooltips, the data table, and navigation menus
1938
+ instead of the geography column values.
1939
+ </p>
1940
+ </Tooltip.Content>
1941
+ </Tooltip>
1942
+ </span>
1943
+ <Select
1944
+ value={config.columns.geo?.displayColumn || ''}
1945
+ options={columnsOptions.map(c => c.key)}
1946
+ onChange={event => {
1947
+ editColumn('geo', 'displayColumn', event.target.value)
1948
+ }}
1949
+ />
1950
+ </label>
1951
+ <CheckBox
1952
+ value={config.columns.geo?.dataTable || false}
1953
+ section='columns'
1954
+ subsection='geo'
1955
+ fieldName='dataTable'
1956
+ label='Show in Data Table'
1957
+ updateField={updateField}
1958
+ />
1959
+ <CheckBox
1960
+ value={config.columns.geo?.tooltip || false}
1961
+ section='columns'
1962
+ subsection='geo'
1963
+ fieldName='tooltip'
1964
+ label='Show in Tooltips'
1965
+ updateField={updateField}
1966
+ />
1967
+ <label className='mt-2'>
1968
+ <span className='edit-label column-heading'>Order</span>
1969
+ <input
1970
+ onWheel={e => e.currentTarget.blur()}
1971
+ type='number'
1972
+ min='1'
1973
+ value={config.columns.geo?.order ?? ''}
1974
+ onChange={event => {
1975
+ updateColumnOrder('geo', event.target.value)
1976
+ }}
1977
+ />
1978
+ </label>
1979
+ </ColumnSection>
1980
+ {config.general.geoType === 'us-county' && config.general.showHSABoundaries && (
1981
+ <Select
1982
+ label='HSA Description Column'
1983
+ value={config.columns.hsa?.name}
1984
+ options={columnsOptions.map(c => c.key)}
1985
+ onChange={e => {
1986
+ editColumn('hsa', 'name', e.target.value)
1987
+ }}
1988
+ tooltip={
1989
+ <Tooltip style={{ textTransform: 'none' }}>
1990
+ <Tooltip.Target>
1991
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
1992
+ </Tooltip.Target>
1993
+ <Tooltip.Content>
1994
+ <p>Select the source column containing the HSA description.</p>
1995
+ </Tooltip.Content>
1996
+ </Tooltip>
1997
+ }
1998
+ />
1999
+ )}
1723
2000
  {'navigation' !== config.general.type && (
1724
- <fieldset className='primary-fieldset edit-block'>
2001
+ <ColumnSection
2002
+ fieldKey='primary'
2003
+ fieldName='Data'
2004
+ show={columnSectionsOpen.primary}
2005
+ setShow={setColumnSectionOpen}
2006
+ >
1725
2007
  <Select
1726
2008
  label='Data Column'
1727
2009
  value={columns.primary.name}
@@ -1829,8 +2111,20 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
1829
2111
  label='Show in Tooltips'
1830
2112
  updateField={updateField}
1831
2113
  />
2114
+ <label>
2115
+ <span className='edit-label column-heading'>Order</span>
2116
+ <input
2117
+ onWheel={e => e.currentTarget.blur()}
2118
+ type='number'
2119
+ min='1'
2120
+ value={config.columns.primary?.order ?? ''}
2121
+ onChange={event => {
2122
+ updateColumnOrder('primary', event.target.value)
2123
+ }}
2124
+ />
2125
+ </label>
1832
2126
  </ul>
1833
- </fieldset>
2127
+ </ColumnSection>
1834
2128
  )}
1835
2129
 
1836
2130
  {config.general.type === 'bubble' && config.legend.type === 'category' && (
@@ -1881,105 +2175,6 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
1881
2175
  </>
1882
2176
  }
1883
2177
 
1884
- {'navigation' !== config.general.type && (
1885
- <fieldset className='primary-fieldset edit-block'>
1886
- <label>
1887
- <span className='edit-label'>
1888
- Special Classes
1889
- <Tooltip style={{ textTransform: 'none' }}>
1890
- <Tooltip.Target>
1891
- <Icon display='question' style={{ marginLeft: '0.5rem' }} />
1892
- </Tooltip.Target>
1893
- <Tooltip.Content>
1894
- <p>
1895
- For secondary values such as "NA", the system can automatically color-code them in
1896
- shades of gray, one shade for each special class.
1897
- </p>
1898
- </Tooltip.Content>
1899
- </Tooltip>
1900
- </span>
1901
- </label>
1902
- {config.legend.specialClasses.length === 2 && (
1903
- <Alert
1904
- type='info'
1905
- message='If a third special class is needed you can apply a pattern to set it apart.'
1906
- showCloseButton={false}
1907
- />
1908
- )}
1909
- {specialClasses.map((specialClass, i) => (
1910
- <div className='edit-block' key={`special-class-${i}`}>
1911
- <button
1912
- className='remove-column'
1913
- onClick={e => {
1914
- e.preventDefault()
1915
- editColumn('primary', 'specialClassDelete', i)
1916
- }}
1917
- >
1918
- Remove
1919
- </button>
1920
- <p>Special Class {i + 1}</p>
1921
- <Select
1922
- label='Data Key'
1923
- value={specialClass.key}
1924
- options={columnsOptions.map(option => ({
1925
- value: option.key,
1926
- label: option.key
1927
- }))}
1928
- onChange={event => {
1929
- editColumn('primary', 'specialClassEdit', {
1930
- prop: 'key',
1931
- index: i,
1932
- value: event.target.value
1933
- })
1934
- }}
1935
- />
1936
- <Select
1937
- label='Value'
1938
- value={specialClass.value}
1939
- options={[
1940
- { value: '', label: '- Select Value -' },
1941
- ...(columnsByKey[specialClass.key] || [])
1942
- .sort()
1943
- .map(option => ({ value: option, label: option }))
1944
- ]}
1945
- onChange={event => {
1946
- editColumn('primary', 'specialClassEdit', {
1947
- prop: 'value',
1948
- index: i,
1949
- value: event.target.value
1950
- })
1951
- }}
1952
- />
1953
- <label>
1954
- <span className='edit-label column-heading'>Label</span>
1955
- <input
1956
- type='text'
1957
- value={specialClass.label}
1958
- onChange={e => {
1959
- editColumn('primary', 'specialClassEdit', {
1960
- prop: 'label',
1961
- index: i,
1962
- value: e.target.value
1963
- })
1964
- }}
1965
- />
1966
- </label>
1967
- </div>
1968
- ))}
1969
- {config.legend.specialClasses.length < 2 && (
1970
- <button
1971
- className='btn btn-primary full-width'
1972
- onClick={e => {
1973
- e.preventDefault()
1974
- editColumn('primary', 'specialClassAdd', {})
1975
- }}
1976
- >
1977
- Add Special Class
1978
- </button>
1979
- )}
1980
- </fieldset>
1981
- )}
1982
-
1983
2178
  <label className='edit-block navigate column-heading'>
1984
2179
  <span className='edit-label column-heading'>
1985
2180
  Navigation
@@ -2007,123 +2202,261 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
2007
2202
  <fieldset className='primary-fieldset edit-block'>
2008
2203
  <label>
2009
2204
  <span className='edit-label'>
2010
- Additional Columns
2205
+ Special Classes
2011
2206
  <Tooltip style={{ textTransform: 'none' }}>
2012
2207
  <Tooltip.Target>
2013
2208
  <Icon display='question' style={{ marginLeft: '0.5rem' }} />
2014
2209
  </Tooltip.Target>
2015
2210
  <Tooltip.Content>
2016
2211
  <p>
2017
- You can specify additional columns to display in tooltips and / or the supporting data
2018
- table.
2212
+ For secondary values such as "NA", the system can automatically color-code them in
2213
+ shades of gray, one shade for each special class.
2019
2214
  </p>
2020
2215
  </Tooltip.Content>
2021
2216
  </Tooltip>
2022
2217
  </span>
2023
2218
  </label>
2024
- {additionalColumns.map(val => (
2025
- <fieldset className='edit-block' key={val}>
2026
- <button
2027
- className='remove-column'
2028
- onClick={event => {
2029
- event.preventDefault()
2030
- removeAdditionalColumn(val)
2031
- }}
2032
- >
2033
- Remove
2034
- </button>
2035
- <Select
2036
- label='Column'
2037
- value={config.columns[val] ? config.columns[val].name : ''}
2038
- options={columnsOptions.map(option => ({
2039
- value: option.props.value,
2040
- label: option.props.children
2041
- }))}
2042
- onChange={event => {
2043
- editColumn(val, 'name', event.target.value)
2044
- }}
2045
- />
2046
- <TextField
2047
- value={columns[val].label}
2048
- section='columns'
2049
- subsection={val}
2050
- fieldName='label'
2051
- label='Label'
2052
- updateField={updateField}
2053
- />
2054
- <ul className='column-edit'>
2055
- <li className='three-col'>
2056
- <TextField
2057
- value={columns[val].prefix}
2058
- section='columns'
2059
- subsection={val}
2060
- fieldName='prefix'
2061
- label='Prefix'
2062
- updateField={updateField}
2063
- />
2064
- <TextField
2065
- value={columns[val].suffix}
2066
- section='columns'
2067
- subsection={val}
2068
- fieldName='suffix'
2069
- label='Suffix'
2070
- updateField={updateField}
2071
- />
2072
- <TextField
2073
- type='number'
2074
- value={columns[val].roundToPlace}
2075
- section='columns'
2076
- subsection={val}
2077
- fieldName='roundToPlace'
2078
- label='Round'
2079
- updateField={updateField}
2080
- />
2081
- </li>
2082
- <CheckBox
2083
- value={config.columns[val].useCommas}
2084
- section='columns'
2085
- subsection={val}
2086
- fieldName='useCommas'
2087
- label='Add Commas to Numbers'
2088
- updateField={updateField}
2089
- onChange={event => {
2090
- editColumn(val, 'useCommas', event.target.checked)
2091
- }}
2092
- />
2093
- <CheckBox
2094
- value={config.columns[val].dataTable}
2095
- section='columns'
2096
- subsection={val}
2097
- fieldName='dataTable'
2098
- label='Show in Data Table'
2099
- updateField={updateField}
2100
- onChange={event => {
2101
- editColumn(val, 'dataTable', event.target.checked)
2102
- }}
2103
- />
2104
- <CheckBox
2105
- value={config.columns[val].tooltip}
2106
- section='columns'
2107
- subsection={val}
2108
- fieldName='tooltip'
2109
- label='Show in Tooltips'
2110
- updateField={updateField}
2111
- onChange={event => {
2112
- editColumn(val, 'tooltip', event.target.checked)
2113
- }}
2114
- />
2115
- </ul>
2116
- </fieldset>
2117
- ))}
2118
- <button
2119
- className={'btn btn-primary full-width'}
2120
- onClick={event => {
2121
- event.preventDefault()
2122
- addAdditionalColumn(additionalColumns.length + 1)
2123
- }}
2219
+ {config.legend.specialClasses.length === 2 && (
2220
+ <Alert
2221
+ type='info'
2222
+ message='If a third special class is needed you can apply a pattern to set it apart.'
2223
+ showCloseButton={false}
2224
+ />
2225
+ )}
2226
+ <GroupedList
2227
+ items={specialClasses}
2228
+ label='Special Classes'
2229
+ droppableId='map-special-classes'
2230
+ draggable={false}
2231
+ renderItem={(specialClass, i) => (
2232
+ <Accordion allowZeroExpanded key={`special-class-${i}`}>
2233
+ <AccordionItem className='series-item series-item--chart'>
2234
+ <AccordionItemHeading className='series-item__title'>
2235
+ <AccordionItemButton className='accordion__button'>
2236
+ {specialClass.label || specialClass.value || `Special Class ${i + 1}`}
2237
+ </AccordionItemButton>
2238
+ </AccordionItemHeading>
2239
+ <AccordionItemPanel>
2240
+ <div className='series-item__panel-actions'>
2241
+ <Button
2242
+ type='button'
2243
+ variant='danger'
2244
+ size='sm'
2245
+ className='grouped-list__remove'
2246
+ onClick={() => editColumn('primary', 'specialClassDelete', i)}
2247
+ >
2248
+ Remove
2249
+ </Button>
2250
+ </div>
2251
+ <Select
2252
+ label='Data Key'
2253
+ value={specialClass.key}
2254
+ options={columnsOptions.map(option => ({
2255
+ value: option.key,
2256
+ label: option.key
2257
+ }))}
2258
+ onChange={event => {
2259
+ editColumn('primary', 'specialClassEdit', {
2260
+ prop: 'key',
2261
+ index: i,
2262
+ value: event.target.value
2263
+ })
2264
+ }}
2265
+ />
2266
+ <Select
2267
+ label='Value'
2268
+ value={specialClass.value}
2269
+ options={[
2270
+ { value: '', label: '- Select Value -' },
2271
+ ...(columnsByKey[specialClass.key] || [])
2272
+ .sort()
2273
+ .map(option => ({ value: option, label: option }))
2274
+ ]}
2275
+ onChange={event => {
2276
+ editColumn('primary', 'specialClassEdit', {
2277
+ prop: 'value',
2278
+ index: i,
2279
+ value: event.target.value
2280
+ })
2281
+ }}
2282
+ />
2283
+ <label>
2284
+ <span className='edit-label column-heading'>Label</span>
2285
+ <input
2286
+ type='text'
2287
+ value={specialClass.label}
2288
+ onChange={e => {
2289
+ editColumn('primary', 'specialClassEdit', {
2290
+ prop: 'label',
2291
+ index: i,
2292
+ value: e.target.value
2293
+ })
2294
+ }}
2295
+ />
2296
+ </label>
2297
+ </AccordionItemPanel>
2298
+ </AccordionItem>
2299
+ </Accordion>
2300
+ )}
2301
+ />
2302
+ {config.legend.specialClasses.length < 2 && (
2303
+ <Button
2304
+ type='button'
2305
+ variant='editor-primary'
2306
+ onClick={() => editColumn('primary', 'specialClassAdd', {})}
2307
+ >
2308
+ Add Special Class
2309
+ </Button>
2310
+ )}
2311
+ </fieldset>
2312
+ )}
2313
+ {'navigation' !== config.general.type && (
2314
+ <fieldset className='primary-fieldset edit-block'>
2315
+ <GroupedList
2316
+ items={additionalColumns}
2317
+ label={
2318
+ <>
2319
+ Additional Columns
2320
+ <Tooltip style={{ textTransform: 'none' }}>
2321
+ <Tooltip.Target>
2322
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
2323
+ </Tooltip.Target>
2324
+ <Tooltip.Content>
2325
+ <p>
2326
+ You can specify additional columns to display in tooltips and / or the supporting
2327
+ data table.
2328
+ </p>
2329
+ </Tooltip.Content>
2330
+ </Tooltip>
2331
+ </>
2332
+ }
2333
+ droppableId='map-additional-columns'
2334
+ draggable={false}
2335
+ renderItem={val => (
2336
+ <Accordion allowZeroExpanded key={val}>
2337
+ <AccordionItem className='series-item series-item--chart'>
2338
+ <AccordionItemHeading className='series-item__title'>
2339
+ <AccordionItemButton className='accordion__button'>
2340
+ {columns[val]?.label || config.columns[val]?.name || 'New Column'}
2341
+ </AccordionItemButton>
2342
+ </AccordionItemHeading>
2343
+ <AccordionItemPanel>
2344
+ <div className='series-item__panel-actions'>
2345
+ <Button
2346
+ type='button'
2347
+ variant='danger'
2348
+ size='sm'
2349
+ className='grouped-list__remove'
2350
+ onClick={() => removeAdditionalColumn(val)}
2351
+ >
2352
+ Remove
2353
+ </Button>
2354
+ </div>
2355
+ <Select
2356
+ label='Column'
2357
+ value={config.columns[val] ? config.columns[val].name : ''}
2358
+ options={columnsOptions.map(option => ({
2359
+ value: option.props.value,
2360
+ label: option.props.children
2361
+ }))}
2362
+ onChange={event => {
2363
+ editColumn(val, 'name', event.target.value)
2364
+ }}
2365
+ />
2366
+ <TextField
2367
+ value={columns[val].label}
2368
+ section='columns'
2369
+ subsection={val}
2370
+ fieldName='label'
2371
+ label='Label'
2372
+ updateField={updateField}
2373
+ />
2374
+ <ul className='column-edit'>
2375
+ <li className='three-col'>
2376
+ <TextField
2377
+ value={columns[val].prefix}
2378
+ section='columns'
2379
+ subsection={val}
2380
+ fieldName='prefix'
2381
+ label='Prefix'
2382
+ updateField={updateField}
2383
+ />
2384
+ <TextField
2385
+ value={columns[val].suffix}
2386
+ section='columns'
2387
+ subsection={val}
2388
+ fieldName='suffix'
2389
+ label='Suffix'
2390
+ updateField={updateField}
2391
+ />
2392
+ <TextField
2393
+ type='number'
2394
+ value={columns[val].roundToPlace}
2395
+ section='columns'
2396
+ subsection={val}
2397
+ fieldName='roundToPlace'
2398
+ label='Round'
2399
+ updateField={updateField}
2400
+ />
2401
+ </li>
2402
+ <CheckBox
2403
+ value={config.columns[val].useCommas}
2404
+ section='columns'
2405
+ subsection={val}
2406
+ fieldName='useCommas'
2407
+ label='Add Commas to Numbers'
2408
+ updateField={updateField}
2409
+ onChange={event => {
2410
+ editColumn(val, 'useCommas', event.target.checked)
2411
+ }}
2412
+ />
2413
+ <CheckBox
2414
+ value={config.columns[val].dataTable}
2415
+ section='columns'
2416
+ subsection={val}
2417
+ fieldName='dataTable'
2418
+ label='Show in Data Table'
2419
+ updateField={updateField}
2420
+ onChange={event => {
2421
+ editColumn(val, 'dataTable', event.target.checked)
2422
+ }}
2423
+ />
2424
+ <CheckBox
2425
+ value={config.columns[val].tooltip}
2426
+ section='columns'
2427
+ subsection={val}
2428
+ fieldName='tooltip'
2429
+ label='Show in Tooltips'
2430
+ updateField={updateField}
2431
+ onChange={event => {
2432
+ editColumn(val, 'tooltip', event.target.checked)
2433
+ }}
2434
+ />
2435
+ <label>
2436
+ <span className='edit-label column-heading'>Order</span>
2437
+ <input
2438
+ onWheel={e => e.currentTarget.blur()}
2439
+ type='number'
2440
+ min='1'
2441
+ value={config.columns[val]?.order ?? ''}
2442
+ onChange={event => {
2443
+ updateColumnOrder(val, event.target.value)
2444
+ }}
2445
+ />
2446
+ </label>
2447
+ </ul>
2448
+ </AccordionItemPanel>
2449
+ </AccordionItem>
2450
+ </Accordion>
2451
+ )}
2452
+ />
2453
+ <Button
2454
+ type='button'
2455
+ variant='editor-primary'
2456
+ onClick={() => addAdditionalColumn(additionalColumns.length + 1)}
2124
2457
  >
2125
2458
  Add Column
2126
- </button>
2459
+ </Button>
2127
2460
  </fieldset>
2128
2461
  )}
2129
2462
  {'category' === config.legend.type && (
@@ -2171,8 +2504,8 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
2171
2504
  </label>
2172
2505
  </fieldset>
2173
2506
  ))}
2174
- <button
2175
- className={'btn btn-primary full-width'}
2507
+ <Button
2508
+ className={'btn btn-primary full-width editor-panel-action-button'}
2176
2509
  onClick={event => {
2177
2510
  event.preventDefault()
2178
2511
  const updatedAdditionaCategories = [...(config.legend.additionalCategories || [])]
@@ -2181,7 +2514,7 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
2181
2514
  }}
2182
2515
  >
2183
2516
  Add Category
2184
- </button>
2517
+ </Button>
2185
2518
  </fieldset>
2186
2519
  )}
2187
2520
  </AccordionItemPanel>
@@ -2679,6 +3012,15 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
2679
3012
  <AccordionItemButton>Filters</AccordionItemButton>
2680
3013
  </AccordionItemHeading>
2681
3014
  <AccordionItemPanel>
3015
+ {config.general.geoType === 'us-county' && (
3016
+ <CheckBox
3017
+ value={config.general.showStateDropdown || false}
3018
+ fieldName='showStateDropdown'
3019
+ label='Show State Dropdown'
3020
+ updateField={updateField}
3021
+ section='general'
3022
+ />
3023
+ )}
2682
3024
  <VizFilterEditor
2683
3025
  config={config}
2684
3026
  updateField={updateField}
@@ -2878,6 +3220,78 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
2878
3220
  label='Show collapse below table'
2879
3221
  updateField={updateField}
2880
3222
  />
3223
+ <Select
3224
+ value={config.table.defaultSort?.column || ''}
3225
+ fieldName='column'
3226
+ section='table'
3227
+ subsection='defaultSort'
3228
+ label='Default Sort Column'
3229
+ initial='-Select-'
3230
+ options={Object.keys(config.columns)
3231
+ .filter(key => config.columns[key].dataTable !== false && config.columns[key].name)
3232
+ .map(key => ({
3233
+ label: config.columns[key].label || config.columns[key].name || key,
3234
+ value: key
3235
+ }))}
3236
+ updateField={(_section, _subSection, _fieldName, value) => {
3237
+ if (value === '' || value === '-Select-') {
3238
+ updateField('table', null, 'defaultSort', {})
3239
+ } else {
3240
+ updateField('table', null, 'defaultSort', { column: value, sortDirection: 'asc' })
3241
+ }
3242
+ }}
3243
+ tooltip={
3244
+ <Tooltip style={{ textTransform: 'none' }}>
3245
+ <Tooltip.Target>
3246
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
3247
+ </Tooltip.Target>
3248
+ <Tooltip.Content>
3249
+ <p>Choose a column to sort the data table by when it first loads.</p>
3250
+ </Tooltip.Content>
3251
+ </Tooltip>
3252
+ }
3253
+ />
3254
+ {config.table.defaultSort?.column && (
3255
+ <Select
3256
+ value={config.table.defaultSort?.sortDirection || 'asc'}
3257
+ fieldName='sortDirection'
3258
+ section='table'
3259
+ subsection='defaultSort'
3260
+ label='Sort Direction'
3261
+ options={[
3262
+ { label: 'Ascending', value: 'asc' },
3263
+ { label: 'Descending', value: 'desc' },
3264
+ { label: 'Custom', value: 'custom' }
3265
+ ]}
3266
+ updateField={(_section, _subSection, _fieldName, value) => {
3267
+ const newDefaultSort = { ...config.table.defaultSort, sortDirection: value }
3268
+ if (value !== 'custom') {
3269
+ delete newDefaultSort.customOrder
3270
+ } else if (!newDefaultSort.customOrder?.length) {
3271
+ // Auto-populate customOrder with unique values so the table updates immediately
3272
+ const col = newDefaultSort.column
3273
+ const dataCol = config.columns?.[col]?.name || col
3274
+ if (dataCol && config.data?.length) {
3275
+ newDefaultSort.customOrder = Array.from(
3276
+ new Set(config.data.map(row => String(row[dataCol] ?? '')).filter(v => v !== ''))
3277
+ )
3278
+ }
3279
+ }
3280
+ updateField('table', null, 'defaultSort', newDefaultSort)
3281
+ }}
3282
+ />
3283
+ )}
3284
+ {config.table.defaultSort?.column && config.table.defaultSort?.sortDirection === 'custom' && (
3285
+ <CustomSortOrder
3286
+ column={
3287
+ config.columns[config.table.defaultSort.column]?.name || config.table.defaultSort.column
3288
+ }
3289
+ data={config.data}
3290
+ customOrder={config.table.defaultSort.customOrder}
3291
+ updateField={updateField}
3292
+ displayTransform={config.table.defaultSort.column === 'geo' ? displayGeoName : undefined}
3293
+ />
3294
+ )}
2881
3295
  <CheckBox
2882
3296
  value={config.table.download}
2883
3297
  fieldName='download'
@@ -2907,6 +3321,16 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
2907
3321
  section='table'
2908
3322
  updateField={updateField}
2909
3323
  />
3324
+ <div className='ms-4 mt-2' style={{ maxWidth: 'calc(100% - 1.5rem)' }}>
3325
+ <TextField
3326
+ value={config.table.downloadDataLabel}
3327
+ section='table'
3328
+ fieldName='downloadDataLabel'
3329
+ label='Download Data Link Text'
3330
+ placeholder='Download Data (CSV)'
3331
+ updateField={updateField}
3332
+ />
3333
+ </div>
2910
3334
  </>
2911
3335
  )}
2912
3336
  {isDashboard && (
@@ -2919,16 +3343,12 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
2919
3343
  updateField={updateField}
2920
3344
  />
2921
3345
  )}
2922
- {isLoadedFromUrl && (
2923
- <CheckBox
2924
- value={config.table.showDownloadUrl}
2925
- section='table'
2926
- subsection={null}
2927
- fieldName='showDownloadUrl'
2928
- label='Show URL to Automatically Updated Data'
2929
- updateField={updateField}
2930
- />
2931
- )}
3346
+ <DownloadUrlControls
3347
+ hasUrlBackedDataSource={Boolean(isLoadedFromUrl)}
3348
+ showDownloadUrl={config.table.showDownloadUrl}
3349
+ downloadUrlLabel={config.table.downloadUrlLabel}
3350
+ updateField={updateField}
3351
+ />
2932
3352
  <CheckBox
2933
3353
  value={config.table.showFullGeoNameInCSV}
2934
3354
  section='table'
@@ -2952,28 +3372,40 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
2952
3372
  }}
2953
3373
  />
2954
3374
  {config.general.showDownloadImgButton && (
2955
- <CheckBox
2956
- value={config.general.includeContextInDownload}
2957
- section='general'
2958
- subsection={null}
2959
- className='ms-4'
2960
- fieldName='includeContextInDownload'
2961
- label='Include Heading & Context'
2962
- updateField={updateField}
2963
- tooltip={
2964
- <Tooltip style={{ textTransform: 'none' }}>
2965
- <Tooltip.Target>
2966
- <Icon display='question' style={{ marginLeft: '0.5rem' }} />
2967
- </Tooltip.Target>
2968
- <Tooltip.Content>
2969
- <p>
2970
- When enabled, the image download will include the section heading (H2 or H3) and any
2971
- explanatory paragraphs that appear before the visualization
2972
- </p>
2973
- </Tooltip.Content>
2974
- </Tooltip>
2975
- }
2976
- />
3375
+ <>
3376
+ <CheckBox
3377
+ value={config.general.includeContextInDownload}
3378
+ section='general'
3379
+ subsection={null}
3380
+ className='ms-4'
3381
+ fieldName='includeContextInDownload'
3382
+ label='Include Heading & Context'
3383
+ updateField={updateField}
3384
+ tooltip={
3385
+ <Tooltip style={{ textTransform: 'none' }}>
3386
+ <Tooltip.Target>
3387
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
3388
+ </Tooltip.Target>
3389
+ <Tooltip.Content>
3390
+ <p>
3391
+ When enabled, the image download will include the section heading (H2 or H3) and any
3392
+ explanatory paragraphs that appear before the visualization
3393
+ </p>
3394
+ </Tooltip.Content>
3395
+ </Tooltip>
3396
+ }
3397
+ />
3398
+ <div className='ms-4 mt-2' style={{ maxWidth: 'calc(100% - 1.5rem)' }}>
3399
+ <TextField
3400
+ value={config.table.downloadImageLabel}
3401
+ section='table'
3402
+ fieldName='downloadImageLabel'
3403
+ label='Download Image Link Text'
3404
+ placeholder='Download Map (PNG)'
3405
+ updateField={updateField}
3406
+ />
3407
+ </div>
3408
+ </>
2977
3409
  )}
2978
3410
 
2979
3411
  {/* <label className='checkbox'>
@@ -3031,6 +3463,14 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
3031
3463
  updateField={updateField}
3032
3464
  />
3033
3465
  )}
3466
+ <TextField
3467
+ value={tooltips.noDataLabel}
3468
+ section='tooltips'
3469
+ fieldName='noDataLabel'
3470
+ label='No Data Tooltip Text'
3471
+ placeholder='No Data'
3472
+ updateField={updateField}
3473
+ />
3034
3474
  </AccordionItemPanel>
3035
3475
  </AccordionItem>
3036
3476
  <AccordionItem>
@@ -3045,6 +3485,19 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
3045
3485
  onThemeSelect={palette => handleEditorChanges('headerColor', palette)}
3046
3486
  label='Header Theme'
3047
3487
  />
3488
+ {ENABLE_MAP_DATA_BITE_VISUAL_SETTINGS && (
3489
+ <StyleTreatmentSection
3490
+ styleTreatment={
3491
+ config.general.titleStyle === 'legacy' && !config.visual?.tp5Treatment ? 'legacy' : 'tp5'
3492
+ }
3493
+ onStyleTreatmentChange={handleStyleTreatmentChange}
3494
+ showStyleTreatment={ENABLE_CHART_MAP_TP5_TREATMENT_SELECTION}
3495
+ border={config.visual?.border}
3496
+ borderColorTheme={config.visual?.borderColorTheme}
3497
+ accent={config.visual?.accent}
3498
+ updateField={updateField}
3499
+ />
3500
+ )}
3048
3501
  <CheckBox
3049
3502
  value={config.general.showTitle || false}
3050
3503
  section='general'
@@ -3276,7 +3729,7 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
3276
3729
  type='checkbox'
3277
3730
  checked={config.visual.showBubbleZeros}
3278
3731
  onChange={event => {
3279
- const _newConfig = _.cloneDeep(config)
3732
+ const _newConfig = cloneDeep(config)
3280
3733
  _newConfig.visual.showBubbleZeros = event.target.checked
3281
3734
  setConfig(_newConfig)
3282
3735
  }}
@@ -3284,7 +3737,9 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
3284
3737
  <span className='edit-label'>Show Data with Zero's on Bubble Map</span>
3285
3738
  </label>
3286
3739
  )}
3287
- {(config.general.geoType === 'world' || config.general.geoType === 'single-state') && (
3740
+ {(config.general.geoType === 'world' ||
3741
+ config.general.geoType === 'single-state' ||
3742
+ config.general.geoType === 'us-county') && (
3288
3743
  <label className='checkbox'>
3289
3744
  <input
3290
3745
  type='checkbox'
@@ -3354,73 +3809,84 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
3354
3809
  )}
3355
3810
  {/* <AdditionalCityStyles /> */}
3356
3811
  <>
3357
- {config.visual.additionalCityStyles.length > 0 &&
3358
- config.visual.additionalCityStyles.map(({ label, column, value, shape }, i) => {
3359
- return (
3360
- <div className='edit-block' key={`additional-city-style-${i}`}>
3361
- <button
3362
- className='remove-column'
3363
- onClick={e => {
3364
- e.preventDefault()
3365
- editCityStyles('remove', i, '', '')
3366
- }}
3367
- >
3368
- Remove
3369
- </button>
3370
- <p>City Style {i + 1}</p>
3371
- <Select
3372
- label='Column with configuration value'
3373
- value={column}
3374
- options={columnsOptions.map(c => c.key)}
3375
- onChange={e => {
3376
- editCityStyles('update', i, 'column', e.target.value)
3377
- }}
3378
- />
3379
- <label>
3380
- <span className='edit-label column-heading'>Value to Trigger</span>
3381
- <input
3382
- type='text'
3383
- value={value}
3812
+ <GroupedList
3813
+ items={config.visual.additionalCityStyles}
3814
+ label='Additional City Styles'
3815
+ droppableId='map-city-styles'
3816
+ draggable={false}
3817
+ renderItem={({ label, column, value, shape }, i) => (
3818
+ <Accordion allowZeroExpanded key={`additional-city-style-${i}`}>
3819
+ <AccordionItem className='series-item series-item--chart'>
3820
+ <AccordionItemHeading className='series-item__title'>
3821
+ <AccordionItemButton className='accordion__button'>
3822
+ {label || column || `City Style ${i + 1}`}
3823
+ </AccordionItemButton>
3824
+ </AccordionItemHeading>
3825
+ <AccordionItemPanel>
3826
+ <div className='series-item__panel-actions'>
3827
+ <Button
3828
+ type='button'
3829
+ variant='danger'
3830
+ size='sm'
3831
+ className='grouped-list__remove'
3832
+ onClick={() => editCityStyles('remove', i, '', '')}
3833
+ >
3834
+ Remove
3835
+ </Button>
3836
+ </div>
3837
+ <Select
3838
+ label='Column with configuration value'
3839
+ value={column}
3840
+ options={columnsOptions.map(c => c.key)}
3384
3841
  onChange={e => {
3385
- editCityStyles('update', i, 'value', e.target.value)
3842
+ editCityStyles('update', i, 'column', e.target.value)
3386
3843
  }}
3387
- ></input>
3388
- </label>
3389
- <Select
3390
- label='Shape'
3391
- value={shape}
3392
- options={[
3393
- { value: '', label: '- Select Option -' },
3394
- ...['Circle', 'Square', 'Triangle', 'Diamond', 'Star', 'Pin']
3395
- .filter(val => String(config.visual.cityStyle).toLowerCase() !== val.toLowerCase())
3396
- .map(val => ({ value: val, label: val }))
3397
- ]}
3398
- onChange={e => {
3399
- editCityStyles('update', i, 'shape', e.target.value)
3400
- }}
3401
- />
3402
- <label>
3403
- <span className='edit-label column-heading'>Label</span>
3404
- <input
3405
- key={i}
3406
- type='text'
3407
- value={label}
3844
+ />
3845
+ <label>
3846
+ <span className='edit-label column-heading'>Value to Trigger</span>
3847
+ <input
3848
+ type='text'
3849
+ value={value}
3850
+ onChange={e => {
3851
+ editCityStyles('update', i, 'value', e.target.value)
3852
+ }}
3853
+ />
3854
+ </label>
3855
+ <Select
3856
+ label='Shape'
3857
+ value={shape}
3858
+ options={[
3859
+ { value: '', label: '- Select Option -' },
3860
+ ...['Circle', 'Square', 'Triangle', 'Diamond', 'Star', 'Pin']
3861
+ .filter(
3862
+ val => String(config.visual.cityStyle).toLowerCase() !== val.toLowerCase()
3863
+ )
3864
+ .map(val => ({ value: val, label: val }))
3865
+ ]}
3408
3866
  onChange={e => {
3409
- editCityStyles('update', i, 'label', e.target.value)
3867
+ editCityStyles('update', i, 'shape', e.target.value)
3410
3868
  }}
3411
3869
  />
3412
- </label>
3413
- </div>
3414
- )
3415
- })}
3416
-
3417
- <button
3418
- type='button'
3419
- onClick={() => editCityStyles('add', 0, '', '')}
3420
- className='btn btn-primary full-width'
3421
- >
3870
+ <label>
3871
+ <span className='edit-label column-heading'>Label</span>
3872
+ <input
3873
+ key={i}
3874
+ type='text'
3875
+ value={label}
3876
+ onChange={e => {
3877
+ editCityStyles('update', i, 'label', e.target.value)
3878
+ }}
3879
+ />
3880
+ </label>
3881
+ </AccordionItemPanel>
3882
+ </AccordionItem>
3883
+ </Accordion>
3884
+ )}
3885
+ />
3886
+
3887
+ <Button type='button' variant='editor-primary' onClick={() => editCityStyles('add', 0, '', '')}>
3422
3888
  Add city style
3423
- </button>
3889
+ </Button>
3424
3890
  </>
3425
3891
  <label htmlFor='opacity'>
3426
3892
  <TextField
@@ -3446,6 +3912,15 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
3446
3912
  />
3447
3913
  </>
3448
3914
  )}
3915
+ {isCoveDeveloperMode() && (
3916
+ <CheckBox
3917
+ value={config.visual?.highlightWrappers}
3918
+ section='visual'
3919
+ fieldName='highlightWrappers'
3920
+ label='Highlight Layout Wrappers'
3921
+ updateField={updateField}
3922
+ />
3923
+ )}
3449
3924
  </AccordionItemPanel>
3450
3925
  </AccordionItem>
3451
3926
  <AccordionItem>
@@ -3454,87 +3929,100 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
3454
3929
  </AccordionItemHeading>
3455
3930
  <AccordionItemPanel>
3456
3931
  {config.map.layers.length === 0 && <p>There are currently no layers.</p>}
3457
-
3458
- {config.map.layers.map((layer, index) => {
3459
- return (
3460
- <>
3461
- <Accordion allowZeroExpanded>
3462
- <AccordionItem className='series-item map-layers-list'>
3463
- <AccordionItemHeading className='series-item__title map-layers-list--title'>
3464
- <AccordionItemButton>{`Layer ${index + 1}: ${layer.name}`}</AccordionItemButton>
3465
- </AccordionItemHeading>
3466
- <AccordionItemPanel>
3467
- <div className='map-layers-panel'>
3468
- <label htmlFor='layerName'>Layer Name:</label>
3469
- <input
3470
- type='text'
3471
- name='layerName'
3472
- value={layer.name}
3473
- onChange={e => handleMapLayer(e, index, 'name')}
3474
- />
3475
- <label htmlFor='layerFilename'>File:</label>
3476
- <input
3477
- type='text'
3478
- name='layerFilename'
3479
- value={layer.url}
3480
- onChange={e => handleMapLayer(e, index, 'url')}
3481
- />
3482
- <label htmlFor='layerNamespace'>TOPOJSON Namespace:</label>
3483
- <input
3484
- type='text'
3485
- name='layerNamespace'
3486
- value={layer.namespace}
3487
- onChange={e => handleMapLayer(e, index, 'namespace')}
3488
- />
3489
- <label htmlFor='layerFill'>Fill Color:</label>
3490
- <input
3491
- type='text'
3492
- name='layerFill'
3493
- value={layer.fill}
3494
- onChange={e => handleMapLayer(e, index, 'fill')}
3495
- />
3496
- <label htmlFor='layerFill'>Fill Opacity (%):</label>
3497
- <input
3498
- type='number'
3499
- min={0}
3500
- max={100}
3501
- name='layerFill'
3502
- value={layer.fillOpacity ? layer.fillOpacity * 100 : ''}
3503
- onChange={e => handleMapLayer(e, index, 'fillOpacity')}
3504
- />
3505
- <label htmlFor='layerStroke'>Stroke Color:</label>
3506
- <input
3507
- type='text'
3508
- name='layerStroke'
3509
- value={layer.stroke}
3510
- onChange={e => handleMapLayer(e, index, 'stroke')}
3511
- />
3512
- <label htmlFor='layerStroke'>Stroke Width:</label>
3513
- <input
3514
- type='number'
3515
- min={0}
3516
- max={5}
3517
- name='layerStrokeWidth'
3518
- value={layer.strokeWidth}
3519
- onChange={e => handleMapLayer(e, index, 'strokeWidth')}
3520
- />
3521
- <label htmlFor='layerTooltip'>Tooltip:</label>
3522
- <textarea
3523
- name='layerTooltip'
3524
- value={layer.tooltip}
3525
- onChange={e => handleMapLayer(e, index, 'tooltip')}
3526
- ></textarea>
3527
- <button onClick={e => handleRemoveLayer(e, index)}>Remove Layer</button>
3528
- </div>
3529
- </AccordionItemPanel>
3530
- </AccordionItem>
3531
- </Accordion>
3532
- </>
3533
- )
3534
- })}
3535
- <button className={'btn btn-primary full-width'} onClick={handleAddLayer}>
3932
+ <GroupedList
3933
+ items={config.map.layers}
3934
+ label='Custom Map Layers'
3935
+ droppableId='map-custom-layers'
3936
+ draggable={false}
3937
+ renderItem={(layer, index) => (
3938
+ <Accordion allowZeroExpanded key={`map-layer-${index}`}>
3939
+ <AccordionItem className='series-item series-item--chart map-layers-list'>
3940
+ <AccordionItemHeading className='series-item__title map-layers-list--title'>
3941
+ <AccordionItemButton className='accordion__button'>
3942
+ {`Layer ${index + 1}: ${layer.name}`}
3943
+ </AccordionItemButton>
3944
+ </AccordionItemHeading>
3945
+ <AccordionItemPanel>
3946
+ <div className='series-item__panel-actions'>
3947
+ <Button
3948
+ type='button'
3949
+ variant='danger'
3950
+ size='sm'
3951
+ className='grouped-list__remove'
3952
+ onClick={e => handleRemoveLayer(e, index)}
3953
+ >
3954
+ Remove Layer
3955
+ </Button>
3956
+ </div>
3957
+ <div className='map-layers-panel'>
3958
+ <label htmlFor='layerName'>Layer Name:</label>
3959
+ <input
3960
+ type='text'
3961
+ name='layerName'
3962
+ value={layer.name}
3963
+ onChange={e => handleMapLayer(e, index, 'name')}
3964
+ />
3965
+ <label htmlFor='layerFilename'>File:</label>
3966
+ <input
3967
+ type='text'
3968
+ name='layerFilename'
3969
+ value={layer.url}
3970
+ onChange={e => handleMapLayer(e, index, 'url')}
3971
+ />
3972
+ <label htmlFor='layerNamespace'>TOPOJSON Namespace:</label>
3973
+ <input
3974
+ type='text'
3975
+ name='layerNamespace'
3976
+ value={layer.namespace}
3977
+ onChange={e => handleMapLayer(e, index, 'namespace')}
3978
+ />
3979
+ <label htmlFor='layerFill'>Fill Color:</label>
3980
+ <input
3981
+ type='text'
3982
+ name='layerFill'
3983
+ value={layer.fill}
3984
+ onChange={e => handleMapLayer(e, index, 'fill')}
3985
+ />
3986
+ <label htmlFor='layerFill'>Fill Opacity (%):</label>
3987
+ <input
3988
+ type='number'
3989
+ min={0}
3990
+ max={100}
3991
+ name='layerFill'
3992
+ value={layer.fillOpacity ? layer.fillOpacity * 100 : ''}
3993
+ onChange={e => handleMapLayer(e, index, 'fillOpacity')}
3994
+ />
3995
+ <label htmlFor='layerStroke'>Stroke Color:</label>
3996
+ <input
3997
+ type='text'
3998
+ name='layerStroke'
3999
+ value={layer.stroke}
4000
+ onChange={e => handleMapLayer(e, index, 'stroke')}
4001
+ />
4002
+ <label htmlFor='layerStroke'>Stroke Width:</label>
4003
+ <input
4004
+ type='number'
4005
+ min={0}
4006
+ max={5}
4007
+ name='layerStrokeWidth'
4008
+ value={layer.strokeWidth}
4009
+ onChange={e => handleMapLayer(e, index, 'strokeWidth')}
4010
+ />
4011
+ <label htmlFor='layerTooltip'>Tooltip:</label>
4012
+ <textarea
4013
+ name='layerTooltip'
4014
+ value={layer.tooltip}
4015
+ onChange={e => handleMapLayer(e, index, 'tooltip')}
4016
+ ></textarea>
4017
+ </div>
4018
+ </AccordionItemPanel>
4019
+ </AccordionItem>
4020
+ </Accordion>
4021
+ )}
4022
+ />
4023
+ <Button type='button' variant='editor-primary' onClick={handleAddLayer}>
3536
4024
  Add Map Layer
3537
- </button>
4025
+ </Button>
3538
4026
  <p className='layer-purpose-details'>
3539
4027
  Context should be added to your visualization or associated page to describe the significance of
3540
4028
  layers that are added to maps.
@@ -3552,6 +4040,7 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
3552
4040
  enableMarkupVariables={config.enableMarkupVariables || false}
3553
4041
  onMarkupVariablesChange={variables => setConfig({ ...config, markupVariables: variables })}
3554
4042
  onToggleEnable={enabled => setConfig({ ...config, enableMarkupVariables: enabled })}
4043
+ dataMetadata={config.dataMetadata}
3555
4044
  />
3556
4045
  <Panels.SmallMultiples name='Small Multiples' />
3557
4046
  </Accordion>