@cdc/map 4.25.10 → 4.26.1

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 (107) hide show
  1. package/.claude/agents/typescript-organizer.md +118 -0
  2. package/dist/{cdcmap-fce76882.es.js → cdcmap-BnB1QM5d.es.js} +6 -13
  3. package/dist/{cdcmap-c55ac1ea.es.js → cdcmap-D6CG2-Hb.es.js} +5 -12
  4. package/dist/{cdcmap-31a33da1.es.js → cdcmap-MXgURbdZ.es.js} +6 -13
  5. package/dist/{cdcmap-1a1724a1.es.js → cdcmap-dgT_1dIT.es.js} +136 -151
  6. package/dist/cdcmap.js +58397 -55987
  7. package/examples/example-city-state.json +9 -1
  8. package/examples/multi-country-centering.json +45 -0
  9. package/examples/private/city_styles_variable.json +877 -0
  10. package/examples/private/colors-2.json +221 -0
  11. package/examples/private/colors.json +221 -0
  12. package/examples/private/map-filter-issue.json +2260 -0
  13. package/examples/private/map-legend.json +5303 -0
  14. package/index.html +27 -36
  15. package/package.json +6 -5
  16. package/src/CdcMapComponent.tsx +86 -26
  17. package/src/_stories/CdcMap.ColumnWrap.stories.tsx +31 -0
  18. package/src/_stories/CdcMap.DistrictOfColumbia.stories.tsx +320 -0
  19. package/src/_stories/CdcMap.Editor.stories.tsx +3426 -0
  20. package/src/_stories/CdcMap.SmallMultiples.stories.tsx +35 -0
  21. package/src/_stories/CdcMap.stories.tsx +116 -4
  22. package/src/_stories/_mock/column-wrap-test.json +265 -0
  23. package/src/_stories/_mock/multi-country-hide.json +78 -0
  24. package/src/_stories/_mock/multi-country.json +95 -0
  25. package/src/_stories/_mock/multi-state.json +887 -20403
  26. package/src/_stories/_mock/small_multiples/multi-state-small-multiples.json +8399 -0
  27. package/src/_stories/_mock/small_multiples/region-small-multiples.json +657 -0
  28. package/src/_stories/_mock/small_multiples/wastewater-map-small-multiples.json +221 -0
  29. package/src/_stories/_mock/usa-state-gradient.json +3 -4
  30. package/src/components/BubbleList.tsx +1 -1
  31. package/src/components/CityList.tsx +24 -18
  32. package/src/components/EditorPanel/components/EditorPanel.tsx +2380 -2206
  33. package/src/components/EditorPanel/components/HexShapeSettings.tsx +55 -93
  34. package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +0 -19
  35. package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +27 -37
  36. package/src/components/EditorPanel/components/Panels/Panel.SmallMultiples.tsx +351 -0
  37. package/src/components/EditorPanel/components/Panels/index.tsx +3 -1
  38. package/src/components/Geo.tsx +20 -3
  39. package/src/components/Legend/components/Legend.tsx +58 -75
  40. package/src/components/Legend/components/LegendGroup/Legend.Group.tsx +1 -1
  41. package/src/components/Legend/components/index.scss +23 -6
  42. package/src/components/NavigationMenu.tsx +16 -13
  43. package/src/components/SmallMultiples/SmallMultipleTile.tsx +163 -0
  44. package/src/components/SmallMultiples/SmallMultiples.css +32 -0
  45. package/src/components/SmallMultiples/SmallMultiples.tsx +150 -0
  46. package/src/components/SmallMultiples/SynchronizedTooltip.tsx +105 -0
  47. package/src/components/SmallMultiples/index.tsx +3 -0
  48. package/src/components/UsaMap/components/SingleState/SingleState.CountyOutput.tsx +18 -3
  49. package/src/components/UsaMap/components/TerritoriesSection.tsx +26 -12
  50. package/src/components/UsaMap/components/Territory/Territory.Hexagon.tsx +30 -4
  51. package/src/components/UsaMap/components/Territory/Territory.Rectangle.tsx +29 -9
  52. package/src/components/UsaMap/components/Territory/TerritoryShape.ts +7 -0
  53. package/src/components/UsaMap/components/UsaMap.County.tsx +16 -4
  54. package/src/components/UsaMap/components/UsaMap.Region.tsx +14 -1
  55. package/src/components/UsaMap/components/UsaMap.SingleState.tsx +29 -12
  56. package/src/components/UsaMap/components/UsaMap.State.tsx +30 -5
  57. package/src/components/UsaMap/helpers/map.ts +2 -2
  58. package/src/components/UsaMap/helpers/shapes.ts +9 -6
  59. package/src/components/WorldMap/WorldMap.tsx +81 -11
  60. package/src/data/initial-state.js +11 -0
  61. package/src/data/supported-geos.js +8 -76
  62. package/src/helpers/addUIDs.ts +13 -2
  63. package/src/helpers/applyColorToLegend.ts +25 -1
  64. package/src/helpers/applyLegendToRow.ts +5 -3
  65. package/src/helpers/constants.ts +3 -15
  66. package/src/helpers/displayGeoName.ts +22 -4
  67. package/src/helpers/generateRuntimeFilters.ts +1 -1
  68. package/src/helpers/generateRuntimeLegend.ts +1 -3
  69. package/src/helpers/generateRuntimeLegendHash.ts +1 -1
  70. package/src/helpers/getCountriesPicked.ts +103 -0
  71. package/src/helpers/getMapContainerClasses.ts +7 -0
  72. package/src/helpers/getPatternForRow.ts +2 -5
  73. package/src/helpers/index.ts +2 -4
  74. package/src/helpers/isLegendItemDisabled.ts +2 -2
  75. package/src/helpers/resetLegendToggles.ts +1 -0
  76. package/src/helpers/smallMultiplesHelpers.ts +359 -0
  77. package/src/helpers/tests/hashObj.test.ts +1 -1
  78. package/src/helpers/tests/titleCase.test.ts +76 -0
  79. package/src/helpers/titleCase.ts +13 -13
  80. package/src/helpers/toggleLegendActive.ts +76 -8
  81. package/src/helpers/urlDataHelpers.ts +1 -1
  82. package/src/hooks/useCountryZoom.tsx +241 -0
  83. package/src/hooks/useGeoClickHandler.ts +1 -1
  84. package/src/hooks/useProgrammaticMapTooltip.ts +110 -0
  85. package/src/hooks/useResizeObserver.ts +8 -2
  86. package/src/hooks/useStateZoom.tsx +7 -4
  87. package/src/hooks/useSynchronizedGeographies.ts +56 -0
  88. package/src/index.jsx +1 -0
  89. package/src/scss/editor-panel.scss +4 -440
  90. package/src/scss/main.scss +1 -1
  91. package/src/scss/map.scss +12 -15
  92. package/src/store/map.actions.ts +7 -7
  93. package/src/test/CdcMap.test.jsx +1 -1
  94. package/src/types/MapConfig.ts +32 -11
  95. package/src/types/MapContext.ts +6 -0
  96. package/src/types/runtimeLegend.ts +2 -1
  97. package/LICENSE +0 -201
  98. package/src/components/DataTable.tsx +0 -413
  99. package/src/components/EditorPanel/components/Inputs.tsx +0 -59
  100. package/src/components/MapControls.tsx +0 -44
  101. package/src/helpers/getUniqueValues.ts +0 -19
  102. package/src/helpers/hashObj.ts +0 -25
  103. package/src/hooks/useActiveElement.ts +0 -19
  104. package/src/hooks/useLegendSeparators.ts +0 -26
  105. package/src/scss/mixins.scss +0 -47
  106. package/src/types/Annotations.ts +0 -24
  107. /package/dist/{cdcmap-548642e6.es.js → cdcmap-Ct2SB0vL.es.js} +0 -0
@@ -1,4 +1,4 @@
1
- import React, { useContext, useEffect, useState, useMemo } from 'react'
1
+ import React, { useContext, useEffect, useState, useMemo, useRef } from 'react'
2
2
  import { filterColorPalettes } from '@cdc/core/helpers/filterColorPalettes'
3
3
  import { cloneConfig } from '@cdc/core/helpers/cloneConfig'
4
4
 
@@ -20,9 +20,11 @@ import Layout from '@cdc/core/components/Layout'
20
20
 
21
21
  // Data
22
22
  import { mapColorPalettes as colorPalettes } from '@cdc/core/data/colorPalettes'
23
- import { supportedStatesFipsCodes } from '../../../data/supported-geos.js'
23
+ import { supportedStatesFipsCodes, supportedCountries } from '../../../data/supported-geos.js'
24
+ import { getSupportedCountryOptions } from '../../../helpers/getCountriesPicked'
24
25
 
25
26
  // Components - Core
27
+ import { EditorPanel as BaseEditorPanel } from '@cdc/core/components/EditorPanel/EditorPanel'
26
28
  import AdvancedEditor from '@cdc/core/components/AdvancedEditor'
27
29
  import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
28
30
  import Icon from '@cdc/core/components/ui/Icon'
@@ -47,8 +49,12 @@ import { MapContext } from '../../../types/MapContext.js'
47
49
  import Alert from '@cdc/core/components/Alert'
48
50
  import { updateFieldFactory } from '@cdc/core/helpers/updateFieldFactory'
49
51
  import { CheckBox, Select, TextField } from '@cdc/core/components/EditorPanel/Inputs'
52
+ import { HeaderThemeSelector } from '@cdc/core/components/HeaderThemeSelector'
50
53
  import useColumnsRequiredChecker from '../../../hooks/useColumnsRequiredChecker'
51
- import { addUIDs, HEADER_COLORS } from '../../../helpers'
54
+ import { addUIDs } from '../../../helpers'
55
+ import generateRuntimeData from '../../../helpers/generateRuntimeData'
56
+
57
+ import '@cdc/core/styles/v2/components/editor.scss'
52
58
  import './editorPanel.styles.css'
53
59
  import FootnotesEditor from '@cdc/core/components/EditorPanel/FootnotesEditor'
54
60
  import { Datasets } from '@cdc/core/types/DataSet'
@@ -56,8 +62,10 @@ import MultiSelect from '@cdc/core/components/MultiSelect'
56
62
  import { paletteMigrationMap } from '@cdc/core/helpers/palettes/migratePaletteName'
57
63
  import { isV1Palette, getCurrentPaletteName, migratePaletteWithMap } from '@cdc/core/helpers/palettes/utils'
58
64
  import { USE_V2_MIGRATION } from '@cdc/core/helpers/constants'
65
+ import { isCoveDeveloperMode } from '@cdc/core/helpers/queryStringUtils'
59
66
  import { PaletteSelector, DeveloperPaletteRollback } from '@cdc/core/components/PaletteSelector'
60
67
  import PaletteConversionModal from '@cdc/core/components/PaletteConversionModal'
68
+ import { CustomColorsEditor } from '@cdc/core/components/CustomColorsEditor'
61
69
 
62
70
  type MapEditorPanelProps = {
63
71
  datasets?: Datasets
@@ -80,10 +88,30 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
80
88
  const { columnsRequiredChecker } = useColumnsRequiredChecker()
81
89
  const dispatch = useContext(MapDispatchContext)
82
90
  const { general, columns, legend, table, tooltips } = config
83
- const columnsInData = config?.data?.[0] ? Object.keys(config.data[0]) : []
91
+
92
+ // Get columns from data with fallback to datasets (for dashboard context)
93
+ const columnsInData = useMemo(() => {
94
+ // First try config.data
95
+ if (config?.data?.[0]) {
96
+ return Object.keys(config.data[0])
97
+ }
98
+
99
+ // Fallback to datasets using config.dataKey (for dashboard visualizations)
100
+ if (datasets && config?.dataKey) {
101
+ const assignedDataset = datasets[config.dataKey]
102
+ if (assignedDataset?.data?.[0]) {
103
+ return Object.keys(assignedDataset.data[0])
104
+ }
105
+ }
106
+
107
+ return []
108
+ }, [
109
+ config?.data?.length > 0 ? JSON.stringify(Object.keys(config.data[0])) : null,
110
+ datasets?.[config?.dataKey as string]?.data?.length,
111
+ config?.dataKey
112
+ ])
84
113
 
85
114
  const [loadedDefault, setLoadedDefault] = useState(false)
86
- const [displayPanel, setDisplayPanel] = useState(true)
87
115
  const [activeFilterValueForDescription, setActiveFilterValueForDescription] = useState([0, 0])
88
116
  const [showConversionModal, setShowConversionModal] = useState(false)
89
117
  const [pendingPaletteSelection, setPendingPaletteSelection] = useState<{
@@ -95,13 +123,6 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
95
123
  MapLayerHandlers: { handleMapLayer, handleAddLayer, handleRemoveLayer }
96
124
  } = useMapLayers(config, setConfig, false, tooltipId)
97
125
 
98
- useEffect(() => {
99
- // Pass up to Editor if needed
100
- if (setParentConfig) {
101
- setParentConfig(convertStateToConfig())
102
- }
103
- }, [config])
104
-
105
126
  const categoryMove = (idx1, idx2) => {
106
127
  let categoryValuesOrder = getCategoryValuesOrder()
107
128
  let [movedItem] = categoryValuesOrder.splice(idx1, 1)
@@ -703,7 +724,26 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
703
724
  })
704
725
 
705
726
  if (config) {
706
- const newData = generateRuntimeData(config)
727
+ const newData = generateRuntimeData(config, [], 0, legend.type === 'category')
728
+ dispatch({ type: 'SET_RUNTIME_DATA', payload: newData })
729
+ }
730
+ break
731
+ case 'chooseCountry':
732
+ let countryData = value.map(countryName => ({
733
+ iso: Object.keys(supportedCountries).find(key => supportedCountries[key][0] === countryName),
734
+ name: countryName
735
+ }))
736
+
737
+ setConfig({
738
+ ...config,
739
+ general: {
740
+ ...config.general,
741
+ countriesPicked: countryData
742
+ }
743
+ })
744
+
745
+ if (config) {
746
+ const newData = generateRuntimeData(config, [], 0, legend.type === 'category')
707
747
  dispatch({ type: 'SET_RUNTIME_DATA', payload: newData })
708
748
  }
709
749
  break
@@ -988,13 +1028,6 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
988
1028
  columnsRequiredChecker()
989
1029
  }, [config])
990
1030
 
991
- useEffect(() => {
992
- const newConfig = convertStateToConfig()
993
- if (isEditor && setParentConfig) {
994
- setParentConfig(newConfig)
995
- }
996
- }, [config])
997
-
998
1031
  const columnsOptions = [
999
1032
  <option value='' key={'Select Option'}>
1000
1033
  - Select Option -
@@ -1029,14 +1062,6 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
1029
1062
 
1030
1063
  const updateField = updateFieldFactory(config, setConfig)
1031
1064
 
1032
- const onBackClick = () => {
1033
- setDisplayPanel(!displayPanel)
1034
- setConfig({
1035
- ...config,
1036
- showEditorPanel: !displayPanel
1037
- })
1038
- }
1039
-
1040
1065
  const StateOptionList = () => {
1041
1066
  const arrOfArrays = Object.entries(supportedStatesFipsCodes)
1042
1067
 
@@ -1056,6 +1081,16 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
1056
1081
  return options
1057
1082
  }
1058
1083
 
1084
+ const CountryOptionList = () => {
1085
+ const countryOptions = getSupportedCountryOptions()
1086
+
1087
+ return countryOptions.map(({ value, label }) => (
1088
+ <option key={value} value={label}>
1089
+ {label}
1090
+ </option>
1091
+ ))
1092
+ }
1093
+
1059
1094
  const filterValueOptionList = []
1060
1095
 
1061
1096
  if (runtimeFilters.length > 0) {
@@ -1116,2267 +1151,2406 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
1116
1151
 
1117
1152
  const isLoadedFromUrl = config?.dataKey?.includes('http://') || config?.dataKey?.includes('https://')
1118
1153
 
1154
+ // Custom convertStateToConfig for map with map-specific logic
1155
+ const customConvertStateToConfig = () => {
1156
+ let strippedState = cloneConfig(config) // Deep copy
1157
+
1158
+ // Strip ref
1159
+ delete strippedState['']
1160
+
1161
+ if (strippedState.columns.geo.name && strippedState.columns.primary.name) {
1162
+ delete strippedState.newViz
1163
+ }
1164
+
1165
+ // Remove the legend
1166
+ let strippedLegend = _.cloneDeep(config.legend)
1167
+
1168
+ delete strippedLegend.disabledAmt
1169
+
1170
+ strippedState.legend = strippedLegend
1171
+
1172
+ // Remove default data marker if the user started this map from default data
1173
+ delete strippedState.defaultData
1174
+
1175
+ // Remove tooltips if they're active in the editor
1176
+ strippedState.general = _.cloneDeep(config.general)
1177
+
1178
+ strippedState.general.showSidebar = 'hidden'
1179
+
1180
+ return strippedState
1181
+ }
1182
+
1119
1183
  return (
1120
- <ErrorBoundary component='EditorPanel'>
1121
- <Layout.Sidebar
1184
+ <React.Fragment>
1185
+ <BaseEditorPanel
1186
+ config={config}
1187
+ updateConfig={setConfig as (config: any) => void}
1188
+ loading={false}
1189
+ setParentConfig={setParentConfig as ((config: any) => void) | undefined}
1122
1190
  isDashboard={isDashboard}
1123
- displayPanel={displayPanel}
1124
1191
  title='Configure Map'
1125
- onBackClick={onBackClick}
1126
1192
  >
1127
- <ReactTooltip multiline={true} />
1128
- <Accordion allowZeroExpanded={true}>
1129
- <AccordionItem>
1130
- {' '}
1131
- {/* Type */}
1132
- <AccordionItemHeading>
1133
- <AccordionItemButton>Type</AccordionItemButton>
1134
- </AccordionItemHeading>
1135
- <AccordionItemPanel>
1136
- <label>
1137
- <span className='edit-label column-heading'>
1138
- <span>Geography</span>
1139
- </span>
1140
- <ul className='geo-buttons d-grid' style={{ gridTemplateColumns: 'repeat(2, 1fr)', gap: '8px' }}>
1141
- <button
1142
- className={`${
1143
- config.general.geoType === 'us' || config.general.geoType === 'us-county' ? 'active' : ''
1144
- } full-width`}
1145
- onClick={e => {
1146
- e.preventDefault()
1147
- handleEditorChanges('geoType', 'us')
1148
- }}
1149
- >
1150
- <UsaGraphic />
1151
- <span>United States</span>
1152
- </button>
1153
- <button
1154
- className={`${config.general.geoType === 'us-region' ? 'active' : ''} full-width`}
1155
- onClick={e => {
1156
- e.preventDefault()
1157
- handleEditorChanges('geoType', 'us-region')
1158
- }}
1159
- >
1160
- <UsaRegionGraphic />
1161
- <span>U.S. Region</span>
1162
- </button>
1163
- <button
1164
- className={`${config.general.geoType === 'world' ? 'active' : ''} full-width`}
1165
- onClick={e => {
1166
- e.preventDefault()
1167
- handleEditorChanges('geoType', 'world')
1168
- }}
1169
- >
1170
- <WorldGraphic />
1171
- <span>World</span>
1172
- </button>
1173
- <button
1174
- className={`${config.general.geoType === 'single-state' ? 'active' : ''} full-width`}
1175
- onClick={e => {
1176
- e.preventDefault()
1177
- handleEditorChanges('geoType', 'single-state')
1178
- }}
1179
- >
1180
- <AlabamaGraphic />
1181
- <span>U.S. State</span>
1182
- </button>
1183
- </ul>
1184
- </label>
1185
- {/* Select > State or County Map */}
1186
- {(config.general.geoType === 'us' || config.general.geoType === 'us-county') && (
1187
- <Select
1188
- label='Geography Subtype'
1189
- value={config.general.geoType}
1190
- options={[
1191
- { value: 'us', label: 'US State-Level' },
1192
- { value: 'us-county', label: 'US County-Level' }
1193
- ]}
1194
- onChange={event => {
1195
- handleEditorChanges('geoType', event.target.value)
1196
- }}
1197
- />
1198
- )}
1199
- {(config.general.geoType === 'us-county' || config.general.geoType === 'single-state') && (
1200
- <Select
1201
- label='County Census Year'
1202
- value={config.general.countyCensusYear || '2019'}
1203
- options={[
1204
- { value: '2022', label: '2022' },
1205
- { value: '2021', label: '2021' },
1206
- { value: '2020', label: '2020' },
1207
- { value: '2019', label: '2019' },
1208
- { value: '2015', label: '2015' },
1209
- { value: '2014', label: '2014' },
1210
- { value: '2013', label: '2013' }
1211
- ]}
1212
- onChange={event => {
1213
- handleEditorChanges('countyCensusYear', event.target.value)
1214
- }}
1215
- />
1216
- )}
1217
- {(config.general.geoType === 'us-county' || config.general.geoType === 'single-state') && (
1218
- <Select
1219
- label='Filter Controlling County Census Year'
1220
- value={config.general.filterControlsCountyYear || ''}
1221
- options={[
1222
- { value: '', label: 'None' },
1223
- ...(config.filters
1224
- ? config.filters.map(filter => ({ value: filter.columnName, label: filter.columnName }))
1225
- : [])
1226
- ]}
1227
- onChange={event => {
1228
- handleEditorChanges('filterControlsCountyYear', event.target.value)
1229
- }}
1230
- />
1231
- )}
1232
-
1233
- {config.general.geoType === 'single-state' && runtimeData && (
1234
- <Select
1235
- label='Filter Controlling State Picked'
1236
- value={config.general.filterControlsStatesPicked || ''}
1237
- options={[
1238
- { value: '', label: 'None' },
1239
- ...(runtimeData && columnsInData?.map(col => ({ value: col, label: col })))
1240
- ]}
1241
- onChange={event => {
1242
- handleEditorChanges('filterControlsStatesPicked', event.target.value)
1243
- }}
1244
- />
1245
- )}
1246
-
1247
- {/* Type */}
1248
- {/* Select > Filter a state */}
1249
- {config.general.geoType === 'single-state' && (
1250
- <label>
1251
- <span>States Selector</span>
1252
- <MultiSelect
1253
- selected={config.general.statesPicked.map(state => state.stateName)}
1254
- options={StateOptionList().map(option => ({
1255
- value: option.props.value,
1256
- label: option.props.children
1257
- }))}
1258
- fieldName={'statesPicked'}
1259
- updateField={(_, __, ___, selectedOptions) => {
1260
- handleEditorChanges('chooseState', selectedOptions)
1261
- }}
1262
- />
1263
- </label>
1264
- )}
1265
- {/* Type */}
1266
- <Select
1267
- label={
1268
- <>
1269
- Map Type
1270
- <Tooltip style={{ textTransform: 'none' }}>
1271
- <Tooltip.Target>
1272
- <Icon display='question' style={{ marginLeft: '0.5rem' }} />
1273
- </Tooltip.Target>
1274
- <Tooltip.Content>
1275
- <p>
1276
- Select "Data" to create a color-coded data map. To create a navigation-only map, select
1277
- "Navigation."
1278
- </p>
1279
- </Tooltip.Content>
1280
- </Tooltip>
1281
- </>
1282
- }
1283
- value={config.general.type}
1284
- options={[
1285
- { value: 'data', label: 'Data' },
1286
- ...(config.general.geoType === 'us-county' ? [{ value: 'us-geocode', label: 'Geocode' }] : []),
1287
- ...(config.general.geoType === 'world' ? [{ value: 'world-geocode', label: 'Geocode' }] : []),
1288
- ...(config.general.geoType !== 'us-county' ? [{ value: 'navigation', label: 'Navigation' }] : []),
1289
- ...(config.general.geoType === 'world' || config.general.geoType === 'us'
1290
- ? [{ value: 'bubble', label: 'Bubble' }]
1291
- : [])
1292
- ]}
1293
- onChange={event => {
1294
- handleEditorChanges('editorMapType', event.target.value)
1295
- }}
1296
- />
1297
-
1298
- {/* Navigation Behavior */}
1299
- {(config.general.type === 'navigation' || config.general.type === 'data') && (
1300
- <Select
1301
- label='Navigation Behavior'
1302
- value={config.general.navigationTarget}
1303
- options={[
1304
- { value: '_self', label: 'Same Window' },
1305
- { value: '_blank', label: 'New Window' }
1306
- ]}
1307
- onChange={event => {
1308
- const _newConfig = cloneConfig(config)
1309
- _newConfig.general.navigationTarget = event.target.value
1310
- setConfig(_newConfig)
1311
- }}
1312
- />
1313
- )}
1314
- <label>
1315
- <span className='edit-label'>Data Classification Type</span>
1316
- <div>
1317
- <label>
1318
- <input
1319
- type='radio'
1320
- name='equalnumber'
1321
- value='equalnumber'
1322
- checked={config.legend.type === 'equalnumber' || config.legend.type === 'equalinterval'}
1323
- onChange={e => handleEditorChanges('classificationType', e.target.value)}
1324
- />
1325
- Numeric/Quantitative
1326
- </label>
1327
- <label>
1328
- <input
1329
- type='radio'
1330
- name='category'
1331
- value='category'
1332
- checked={config.legend.type === 'category'}
1333
- onChange={e => handleEditorChanges('classificationType', e.target.value)}
1334
- />
1335
- Categorical
1336
- </label>
1337
- </div>
1338
- </label>
1339
-
1340
- {/* Display as Hex */}
1341
- {general.geoType === 'us' && general.type !== 'navigation' && general.type !== 'bubble' && (
1342
- <label className='checkbox mt-4'>
1343
- <input
1344
- type='checkbox'
1345
- checked={config.general.displayAsHex}
1346
- onChange={event => {
1347
- const _newConfig = cloneConfig(config)
1348
- _newConfig.general.displayAsHex = event.target.checked
1349
- setConfig(_newConfig)
1350
- }}
1351
- />
1352
- <span className='edit-label'>Display As Hex Map</span>
1353
- </label>
1354
- )}
1355
-
1356
- {/* Shapes on Hex */}
1357
- <label className='checkbox mt-4'>
1358
- <input
1359
- type='checkbox'
1360
- checked={config.hexMap.type === 'shapes'}
1361
- onChange={event => {
1362
- setConfig({
1363
- ...config,
1364
- hexMap: {
1365
- ...config.hexMap,
1366
- type: event.target.checked ? 'shapes' : 'standard'
1367
- }
1368
- })
1369
- }}
1370
- />
1371
- <span className='edit-label'>Display Shapes on Hex Map</span>
1372
- </label>
1373
- <HexSetting.ShapeColumns columnsOptions={columnsOptions} />
1374
-
1375
- {'us' === config.general.geoType &&
1376
- 'bubble' !== config.general.type &&
1377
- false === config.general.displayAsHex && (
1378
- <CheckBox
1379
- label='Show state labels'
1380
- checked={config.general.displayStateLabels}
1381
- onChange={event => {
1382
- handleEditorChanges('displayStateLabels', event.target.checked)
1383
- }}
1384
- tooltip={
1385
- <Tooltip style={{ textTransform: 'none' }}>
1386
- <Tooltip.Target>
1387
- <Icon display='question' style={{ marginLeft: '0.5rem' }} />
1388
- </Tooltip.Target>
1389
- <Tooltip.Content>
1390
- <p>Recommended set to display for Section 508 compliance.</p>
1391
- </Tooltip.Content>
1392
- </Tooltip>
1393
- }
1394
- />
1395
- )}
1396
-
1397
- {'us' === config.general.geoType && (
1398
- <label className='checkbox'>
1399
- <input
1400
- type='checkbox'
1401
- checked={general.territoriesAlwaysShow || false}
1402
- onChange={event => {
1403
- const _newConfig = cloneConfig(config)
1404
- _newConfig.general.territoriesAlwaysShow = event.target.checked
1405
- setConfig(_newConfig)
1406
- }}
1407
- />
1408
- <span className='edit-label'>Show All Territories</span>
1409
- </label>
1410
- )}
1411
- </AccordionItemPanel>
1412
- </AccordionItem>
1413
- <AccordionItem>
1414
- {' '}
1415
- {/* General */}
1416
- <AccordionItemHeading>
1417
- <AccordionItemButton>General</AccordionItemButton>
1418
- </AccordionItemHeading>
1419
- <AccordionItemPanel>
1420
- <TextField
1421
- value={general.title}
1422
- data-testid='title-input'
1423
- updateField={updateField}
1424
- section='general'
1425
- fieldName='title'
1426
- id='title'
1427
- label='Title'
1428
- placeholder='Map Title'
1429
- tooltip={
1430
- <Tooltip style={{ textTransform: 'none' }}>
1431
- <Tooltip.Target>
1432
- <Icon display='question' style={{ marginLeft: '0.5rem' }} />
1433
- </Tooltip.Target>
1434
- <Tooltip.Content>
1435
- <p>
1436
- Title is required to set the name of the download file but can be hidden using the option below.
1437
- </p>
1438
- </Tooltip.Content>
1439
- </Tooltip>
1440
- }
1441
- />
1442
- <label className='checkbox'>
1443
- <input
1444
- type='checkbox'
1445
- checked={config.general.showTitle || false}
1446
- onChange={event => {
1447
- const _newConfig = cloneConfig(config)
1448
- _newConfig.general.showTitle = event.target.checked
1449
- setConfig(_newConfig)
1450
- }}
1451
- />
1452
- <span className='edit-label'>Show Title</span>
1453
- </label>
1454
- <TextField
1455
- value={general.superTitle || ''}
1456
- updateField={updateField}
1457
- section='general'
1458
- fieldName='superTitle'
1459
- label='Super Title'
1460
- tooltip={
1461
- <Tooltip style={{ textTransform: 'none' }}>
1462
- <Tooltip.Target>
1463
- <Icon display='question' style={{ marginLeft: '0.5rem' }} />
1464
- </Tooltip.Target>
1465
- <Tooltip.Content>
1466
- <p>Super Title</p>
1467
- </Tooltip.Content>
1468
- </Tooltip>
1469
- }
1470
- />
1471
- <TextField
1472
- type='textarea'
1473
- value={general.introText}
1474
- updateField={updateField}
1475
- section='general'
1476
- fieldName='introText'
1477
- label='Message'
1478
- tooltip={
1479
- <Tooltip style={{ textTransform: 'none' }}>
1480
- <Tooltip.Target>
1481
- <Icon display='question' style={{ marginLeft: '0.5rem' }} />
1482
- </Tooltip.Target>
1483
- <Tooltip.Content>
1484
- <p>Intro Text</p>
1485
- </Tooltip.Content>
1486
- </Tooltip>
1487
- }
1488
- />
1489
- <TextField
1490
- type='textarea'
1491
- value={general.subtext}
1492
- updateField={updateField}
1493
- section='general'
1494
- fieldName='subtext'
1495
- label='Subtext'
1496
- tooltip={
1497
- <Tooltip style={{ textTransform: 'none' }}>
1498
- <Tooltip.Target>
1499
- <Icon display='question' style={{ marginLeft: '0.5rem' }} />
1500
- </Tooltip.Target>
1501
- <Tooltip.Content>
1502
- <p>
1503
- Enter supporting text to display below the data visualization, if applicable. The following HTML
1504
- tags are supported: strong, em, sup, and sub.
1505
- </p>
1506
- </Tooltip.Content>
1507
- </Tooltip>
1508
- }
1509
- />
1510
- <TextField
1511
- type='textarea'
1512
- value={general.footnotes}
1513
- updateField={updateField}
1514
- section='general'
1515
- fieldName='footnotes'
1516
- label='Footnotes'
1517
- tooltip={
1518
- <Tooltip style={{ textTransform: 'none' }}>
1519
- <Tooltip.Target>
1520
- <Icon display='question' style={{ marginLeft: '0.5rem' }} />
1521
- </Tooltip.Target>
1522
- <Tooltip.Content>
1523
- <p>Footnotes</p>
1524
- </Tooltip.Content>
1525
- </Tooltip>
1526
- }
1527
- />
1528
-
1529
- {/* <label className="checkbox mt-4">
1530
- <input type="checkbox" checked={ state.general.showDownloadMediaButton } onChange={(event) => { handleEditorChanges("toggleDownloadMediaButton", event.target.checked) }} />
1531
- <span className="edit-label">Enable Media Download</span>
1532
- </label> */}
1533
- </AccordionItemPanel>
1534
- </AccordionItem>
1535
- <AccordionItem>
1536
- {' '}
1537
- {/* Columns */}
1538
- <AccordionItemHeading>
1539
- <AccordionItemButton>Columns</AccordionItemButton>
1540
- </AccordionItemHeading>
1541
- <AccordionItemPanel>
1542
- <fieldset className='primary-fieldset edit-block'>
1543
- <label>
1544
- <span className='edit-label column-heading'>
1545
- Geography
1546
- <Tooltip style={{ textTransform: 'none' }}>
1547
- <Tooltip.Target>
1548
- <Icon display='question' style={{ marginLeft: '0.5rem' }} />
1549
- </Tooltip.Target>
1550
- <Tooltip.Content>
1551
- <p>
1552
- Select the source column containing the map location names or, for county-level maps, the FIPS
1553
- codes.
1554
- </p>
1555
- </Tooltip.Content>
1556
- </Tooltip>
1557
- </span>
1558
- <Select
1559
- value={config.columns.geo ? config.columns.geo.name : columnsOptions[0]}
1560
- options={columnsOptions.map(c => c.key)}
1561
- onChange={event => {
1562
- editColumn('geo', 'name', event.target.value)
1563
- checkConfigurationNeeded(config)
1564
- }}
1565
- />
1566
- </label>
1567
- {config.general.type === 'us-geocode' && (
1568
- <label className='checkbox'>
1569
- <input
1570
- type='checkbox'
1571
- checked={config.general.convertFipsCodes}
1572
- onChange={event => {
1573
- setConfig({
1574
- ...config,
1575
- general: {
1576
- ...config.general,
1577
- convertFipsCodes: event.target.checked
1578
- }
1579
- })
1580
- }}
1581
- />
1582
- <span className='edit-label'>Convert FIPS Codes to Geography Name</span>
1583
- </label>
1584
- )}
1193
+ {({ displayPanel, convertStateToConfig }) => {
1194
+ // Use custom convertStateToConfig for map-specific logic
1195
+ const mapConvertStateToConfig = customConvertStateToConfig
1585
1196
 
1586
- <label className='checkbox'>
1587
- <input
1588
- type='checkbox'
1589
- checked={config.general.hideGeoColumnInTooltip || false}
1590
- onChange={event => {
1591
- const _newConfig = cloneConfig(config)
1592
- _newConfig.general.hideGeoColumnInTooltip = event.target.checked
1593
- setConfig(_newConfig)
1594
- }}
1595
- />
1596
- <span className='edit-label'>Hide Geography Column Name in Tooltip</span>
1597
- </label>
1598
- <TextField
1599
- value={config.general.geoLabelOverride}
1600
- section='general'
1601
- fieldName='geoLabelOverride'
1602
- label='Geography Label'
1603
- className='edit-label'
1604
- updateField={updateField}
1605
- tooltip={
1606
- <Tooltip style={{ textTransform: 'none' }}>
1607
- <Tooltip.Target>
1608
- <Icon display='question' style={{ marginLeft: '0.5rem' }} />
1609
- </Tooltip.Target>
1610
- <Tooltip.Content>
1611
- <p>Enter a geography label for use in tooltips.</p>
1612
- </Tooltip.Content>
1613
- </Tooltip>
1614
- }
1615
- />
1616
- </fieldset>
1617
- {'navigation' !== config.general.type && (
1618
- <fieldset className='primary-fieldset edit-block'>
1619
- <Select
1620
- label='Data Column'
1621
- value={columns.primary.name}
1622
- options={columnsOptions.map(c => c.key)}
1623
- onChange={event => {
1624
- const _state = cloneConfig(config)
1625
- _state.columns.primary.name = event.target.value
1626
- _state.columns.primary.label = event.target.value
1627
- setConfig(_state)
1628
- checkConfigurationNeeded(_state)
1629
- }}
1630
- tooltip={
1631
- <Tooltip style={{ textTransform: 'none' }}>
1632
- <Tooltip.Target>
1633
- <Icon display='question' style={{ marginLeft: '0.5rem' }} />
1634
- </Tooltip.Target>
1635
- <Tooltip.Content>
1636
- <p>Select the source column containing the categorical or numeric values to be mapped.</p>
1637
- </Tooltip.Content>
1638
- </Tooltip>
1639
- }
1640
- />
1641
- <label className='checkbox'>
1642
- <input
1643
- type='checkbox'
1644
- checked={config.general.hidePrimaryColumnInTooltip || false}
1645
- onChange={event => {
1646
- handleEditorChanges('hidePrimaryColumnInTooltip', event.target.checked)
1647
- }}
1648
- />
1649
- <span className='edit-label'>Hide Data Column Name in Tooltip</span>
1650
- </label>
1651
- <TextField
1652
- value={columns.primary.label}
1653
- section='columns'
1654
- subsection='primary'
1655
- fieldName='label'
1656
- label='Data Label'
1657
- updateField={updateField}
1658
- tooltip={
1659
- <Tooltip style={{ textTransform: 'none' }}>
1660
- <Tooltip.Target>
1661
- <Icon display='question' style={{ marginLeft: '0.5rem' }} />
1662
- </Tooltip.Target>
1663
- <Tooltip.Content>
1664
- <p>Enter a data label for use in tooltips and the data table.</p>
1665
- </Tooltip.Content>
1666
- </Tooltip>
1667
- }
1668
- />
1669
- <ul className='column-edit'>
1670
- <li className='three-col'>
1671
- <TextField
1672
- value={columns.primary.prefix}
1673
- section='columns'
1674
- subsection='primary'
1675
- fieldName='prefix'
1676
- label='Prefix'
1677
- updateField={updateField}
1678
- />
1679
- <TextField
1680
- value={columns.primary.suffix}
1681
- section='columns'
1682
- subsection='primary'
1683
- fieldName='suffix'
1684
- label='Suffix'
1685
- updateField={updateField}
1686
- />
1687
- <TextField
1688
- type='number'
1689
- value={columns.primary.roundToPlace}
1690
- section='columns'
1691
- subsection='primary'
1692
- fieldName='roundToPlace'
1693
- label='Round'
1694
- updateField={updateField}
1695
- min={0}
1696
- />
1697
- </li>
1698
- <li>
1699
- <label className='checkbox'>
1700
- <input
1701
- type='checkbox'
1702
- checked={config.columns.primary.useCommas}
1703
- onChange={event => {
1704
- editColumn('primary', 'useCommas', event.target.checked)
1197
+ return (
1198
+ <>
1199
+ <ReactTooltip multiline={true} />
1200
+ <Accordion allowZeroExpanded={true}>
1201
+ <AccordionItem>
1202
+ {' '}
1203
+ {/* Type */}
1204
+ <AccordionItemHeading>
1205
+ <AccordionItemButton>Type</AccordionItemButton>
1206
+ </AccordionItemHeading>
1207
+ <AccordionItemPanel>
1208
+ <label>
1209
+ <span className='edit-label column-heading'>
1210
+ <span>Geography</span>
1211
+ </span>
1212
+ <ul className='geo-buttons d-grid' style={{ gridTemplateColumns: 'repeat(2, 1fr)', gap: '8px' }}>
1213
+ <button
1214
+ className={`${
1215
+ config.general.geoType === 'us' || config.general.geoType === 'us-county' ? 'active' : ''
1216
+ } full-width`}
1217
+ onClick={e => {
1218
+ e.preventDefault()
1219
+ handleEditorChanges('geoType', 'us')
1705
1220
  }}
1706
- />
1707
- <span className='edit-label'>Add Commas to Numbers</span>
1708
- </label>
1709
- </li>
1710
- <li>
1711
- <label className='checkbox'>
1712
- <input
1713
- type='checkbox'
1714
- checked={config.columns.primary.dataTable || false}
1715
- onChange={event => {
1716
- editColumn('primary', 'dataTable', event.target.checked)
1221
+ >
1222
+ <UsaGraphic />
1223
+ <span>United States</span>
1224
+ </button>
1225
+ <button
1226
+ className={`${config.general.geoType === 'us-region' ? 'active' : ''} full-width`}
1227
+ onClick={e => {
1228
+ e.preventDefault()
1229
+ handleEditorChanges('geoType', 'us-region')
1717
1230
  }}
1718
- />
1719
- <span className='edit-label'>Show in Data Table</span>
1720
- </label>
1721
- </li>
1722
- <li>
1723
- <label className='checkbox'>
1724
- <input
1725
- type='checkbox'
1726
- checked={config.columns.primary.tooltip || false}
1727
- onChange={event => {
1728
- editColumn('primary', 'tooltip', event.target.checked)
1231
+ >
1232
+ <UsaRegionGraphic />
1233
+ <span>U.S. Region</span>
1234
+ </button>
1235
+ <button
1236
+ className={`${config.general.geoType === 'world' ? 'active' : ''} full-width`}
1237
+ onClick={e => {
1238
+ e.preventDefault()
1239
+ handleEditorChanges('geoType', 'world')
1729
1240
  }}
1730
- />
1731
- <span className='edit-label'>Show in Tooltips</span>
1732
- </label>
1733
- </li>
1734
- </ul>
1735
- </fieldset>
1736
- )}
1737
-
1738
- {config.general.type === 'bubble' && config.legend.type === 'category' && (
1739
- <fieldset className='primary-fieldset edit-block'>
1740
- <label>
1741
- <span className='edit-label column-heading'>
1742
- Category Column
1743
- <Tooltip style={{ textTransform: 'none' }}>
1744
- <Tooltip.Target>
1745
- <Icon display='question' style={{ marginLeft: '0.5rem' }} />
1746
- </Tooltip.Target>
1747
- <Tooltip.Content>
1748
- <p>Select the source column containing the categorical bubble values to be mapped.</p>
1749
- </Tooltip.Content>
1750
- </Tooltip>
1751
- </span>
1752
- <select
1753
- value={config.columns.categorical ? config.columns.categorical.name : columnsOptions[0]}
1754
- onChange={event => {
1755
- editColumn('categorical', 'name', event.target.value)
1756
- }}
1757
- >
1758
- {columnsOptions}
1759
- </select>
1760
- </label>
1761
- </fieldset>
1762
- )}
1763
- {
1764
- <>
1765
- <Select
1766
- label='Latitude Column'
1767
- value={config.columns.latitude.name}
1768
- options={columnsOptions.map(c => c.key)}
1769
- onChange={e => {
1770
- editColumn('latitude', 'name', e.target.value)
1771
- }}
1772
- />
1773
- <Select
1774
- label='Longitude Column'
1775
- value={config.columns.longitude.name}
1776
- options={columnsOptions.map(c => c.key)}
1777
- onChange={e => {
1778
- editColumn('longitude', 'name', e.target.value)
1779
- }}
1780
- />
1781
- </>
1782
- }
1783
-
1784
- {'navigation' !== config.general.type && (
1785
- <fieldset className='primary-fieldset edit-block'>
1786
- <label>
1787
- <span className='edit-label'>
1788
- Special Classes
1789
- <Tooltip style={{ textTransform: 'none' }}>
1790
- <Tooltip.Target>
1791
- <Icon display='question' style={{ marginLeft: '0.5rem' }} />
1792
- </Tooltip.Target>
1793
- <Tooltip.Content>
1794
- <p>
1795
- For secondary values such as "NA", the system can automatically color-code them in shades of
1796
- gray, one shade for each special class.
1797
- </p>
1798
- </Tooltip.Content>
1799
- </Tooltip>
1800
- </span>
1801
- </label>
1802
- {config.legend.specialClasses.length === 2 && (
1803
- <Alert
1804
- type='info'
1805
- message='If a third special class is needed you can apply a pattern to set it apart.'
1806
- showCloseButton={false}
1807
- />
1808
- )}
1809
- {specialClasses.map((specialClass, i) => (
1810
- <div className='edit-block' key={`special-class-${i}`}>
1811
- <button
1812
- className='remove-column'
1813
- onClick={e => {
1814
- e.preventDefault()
1815
- editColumn('primary', 'specialClassDelete', i)
1816
- }}
1817
- >
1818
- Remove
1819
- </button>
1820
- <p>Special Class {i + 1}</p>
1241
+ >
1242
+ <WorldGraphic />
1243
+ <span>World</span>
1244
+ </button>
1245
+ <button
1246
+ className={`${config.general.geoType === 'single-state' ? 'active' : ''} full-width`}
1247
+ onClick={e => {
1248
+ e.preventDefault()
1249
+ handleEditorChanges('geoType', 'single-state')
1250
+ }}
1251
+ >
1252
+ <AlabamaGraphic />
1253
+ <span>U.S. State</span>
1254
+ </button>
1255
+ </ul>
1256
+ </label>
1257
+ {/* Select > State or County Map */}
1258
+ {(config.general.geoType === 'us' || config.general.geoType === 'us-county') && (
1821
1259
  <Select
1822
- label='Data Key'
1823
- value={specialClass.key}
1824
- options={columnsOptions.map(option => ({
1825
- value: option.key,
1826
- label: option.key
1827
- }))}
1260
+ label='Geography Subtype'
1261
+ value={config.general.geoType}
1262
+ options={[
1263
+ { value: 'us', label: 'US State-Level' },
1264
+ { value: 'us-county', label: 'US County-Level' }
1265
+ ]}
1828
1266
  onChange={event => {
1829
- editColumn('primary', 'specialClassEdit', {
1830
- prop: 'key',
1831
- index: i,
1832
- value: event.target.value
1833
- })
1267
+ handleEditorChanges('geoType', event.target.value)
1834
1268
  }}
1835
1269
  />
1270
+ )}
1271
+ {(config.general.geoType === 'us-county' || config.general.geoType === 'single-state') && (
1836
1272
  <Select
1837
- label='Value'
1838
- value={specialClass.value}
1273
+ label='County Census Year'
1274
+ value={config.general.countyCensusYear || '2019'}
1839
1275
  options={[
1840
- { value: '', label: '- Select Value -' },
1841
- ...(columnsByKey[specialClass.key] || [])
1842
- .sort()
1843
- .map(option => ({ value: option, label: option }))
1276
+ { value: '2022', label: '2022' },
1277
+ { value: '2021', label: '2021' },
1278
+ { value: '2020', label: '2020' },
1279
+ { value: '2019', label: '2019' },
1280
+ { value: '2015', label: '2015' },
1281
+ { value: '2014', label: '2014' },
1282
+ { value: '2013', label: '2013' }
1844
1283
  ]}
1845
1284
  onChange={event => {
1846
- editColumn('primary', 'specialClassEdit', {
1847
- prop: 'value',
1848
- index: i,
1849
- value: event.target.value
1850
- })
1285
+ handleEditorChanges('countyCensusYear', event.target.value)
1851
1286
  }}
1852
1287
  />
1853
- <label>
1854
- <span className='edit-label column-heading'>Label</span>
1855
- <input
1856
- type='text'
1857
- value={specialClass.label}
1858
- onChange={e => {
1859
- editColumn('primary', 'specialClassEdit', {
1860
- prop: 'label',
1861
- index: i,
1862
- value: e.target.value
1863
- })
1864
- }}
1865
- />
1866
- </label>
1867
- </div>
1868
- ))}
1869
- {config.legend.specialClasses.length < 2 && (
1870
- <button
1871
- className='btn btn-primary full-width'
1872
- onClick={e => {
1873
- e.preventDefault()
1874
- editColumn('primary', 'specialClassAdd', {})
1875
- }}
1876
- >
1877
- Add Special Class
1878
- </button>
1879
- )}
1880
- </fieldset>
1881
- )}
1882
-
1883
- <label className='edit-block navigate column-heading'>
1884
- <span className='edit-label column-heading'>
1885
- Navigation
1886
- <Tooltip style={{ textTransform: 'none' }}>
1887
- <Tooltip.Target>
1888
- <Icon display='question' style={{ marginLeft: '0.5rem' }} />
1889
- </Tooltip.Target>
1890
- <Tooltip.Content>
1891
- <p>
1892
- To provide end users with navigation functionality, select the source column containing the
1893
- navigation URLs.
1894
- </p>
1895
- </Tooltip.Content>
1896
- </Tooltip>
1897
- </span>
1898
- <Select
1899
- value={config.columns.navigate ? config.columns.navigate.name : ''}
1900
- options={columnsOptions.map(c => c.key)}
1901
- onChange={event => {
1902
- editColumn('navigate', 'name', event.target.value)
1903
- }}
1904
- />
1905
- </label>
1906
- {'navigation' !== config.general.type && (
1907
- <fieldset className='primary-fieldset edit-block'>
1908
- <label>
1909
- <span className='edit-label'>
1910
- Additional Columns
1911
- <Tooltip style={{ textTransform: 'none' }}>
1912
- <Tooltip.Target>
1913
- <Icon display='question' style={{ marginLeft: '0.5rem' }} />
1914
- </Tooltip.Target>
1915
- <Tooltip.Content>
1916
- <p>
1917
- You can specify additional columns to display in tooltips and / or the supporting data
1918
- table.
1919
- </p>
1920
- </Tooltip.Content>
1921
- </Tooltip>
1922
- </span>
1923
- </label>
1924
- {additionalColumns.map(val => (
1925
- <fieldset className='edit-block' key={val}>
1926
- <button
1927
- className='remove-column'
1928
- onClick={event => {
1929
- event.preventDefault()
1930
- removeAdditionalColumn(val)
1931
- }}
1932
- >
1933
- Remove
1934
- </button>
1288
+ )}
1289
+ {(config.general.geoType === 'us-county' || config.general.geoType === 'single-state') && (
1935
1290
  <Select
1936
- label='Column'
1937
- value={config.columns[val] ? config.columns[val].name : ''}
1938
- options={columnsOptions.map(option => ({
1939
- value: option.props.value,
1940
- label: option.props.children
1941
- }))}
1291
+ label='Filter Controlling County Census Year'
1292
+ value={config.general.filterControlsCountyYear || ''}
1293
+ options={[
1294
+ { value: '', label: 'None' },
1295
+ ...(config.filters
1296
+ ? config.filters.map(filter => ({ value: filter.columnName, label: filter.columnName }))
1297
+ : [])
1298
+ ]}
1942
1299
  onChange={event => {
1943
- editColumn(val, 'name', event.target.value)
1300
+ handleEditorChanges('filterControlsCountyYear', event.target.value)
1944
1301
  }}
1945
1302
  />
1946
- <TextField
1947
- value={columns[val].label}
1948
- section='columns'
1949
- subsection={val}
1950
- fieldName='label'
1951
- label='Label'
1952
- updateField={updateField}
1303
+ )}
1304
+
1305
+ {config.general.geoType === 'single-state' && runtimeData && (
1306
+ <Select
1307
+ label='Filter Controlling State Picked'
1308
+ value={config.general.filterControlsStatesPicked || ''}
1309
+ options={[
1310
+ { value: '', label: 'None' },
1311
+ ...(runtimeData && columnsInData?.map(col => ({ value: col, label: col })))
1312
+ ]}
1313
+ onChange={event => {
1314
+ handleEditorChanges('filterControlsStatesPicked', event.target.value)
1315
+ }}
1953
1316
  />
1954
- <ul className='column-edit'>
1955
- <li className='three-col'>
1956
- <TextField
1957
- value={columns[val].prefix}
1958
- section='columns'
1959
- subsection={val}
1960
- fieldName='prefix'
1961
- label='Prefix'
1962
- updateField={updateField}
1963
- />
1964
- <TextField
1965
- value={columns[val].suffix}
1966
- section='columns'
1967
- subsection={val}
1968
- fieldName='suffix'
1969
- label='Suffix'
1970
- updateField={updateField}
1971
- />
1972
- <TextField
1973
- type='number'
1974
- value={columns[val].roundToPlace}
1975
- section='columns'
1976
- subsection={val}
1977
- fieldName='roundToPlace'
1978
- label='Round'
1979
- updateField={updateField}
1980
- />
1981
- </li>
1982
- <li>
1983
- <label className='checkbox'>
1984
- <input
1985
- type='checkbox'
1986
- checked={config.columns[val].useCommas}
1987
- onChange={event => {
1988
- editColumn(val, 'useCommas', event.target.checked)
1989
- }}
1990
- />
1991
- <span className='edit-label'>Add Commas to Numbers</span>
1992
- </label>
1993
- </li>
1994
- <li>
1995
- <label className='checkbox'>
1996
- <input
1997
- type='checkbox'
1998
- checked={config.columns[val].dataTable}
1999
- onChange={event => {
2000
- editColumn(val, 'dataTable', event.target.checked)
2001
- }}
2002
- />
2003
- <span className='edit-label'>Show in Data Table</span>
2004
- </label>
2005
- </li>
2006
- <li>
2007
- <label className='checkbox'>
2008
- <input
2009
- type='checkbox'
2010
- checked={config.columns[val].tooltip}
2011
- onChange={event => {
2012
- editColumn(val, 'tooltip', event.target.checked)
2013
- }}
2014
- />
2015
- <span className='edit-label'>Show in Tooltips</span>
2016
- </label>
2017
- </li>
2018
- </ul>
2019
- </fieldset>
2020
- ))}
2021
- <button
2022
- className={'btn btn-primary full-width'}
2023
- onClick={event => {
2024
- event.preventDefault()
2025
- addAdditionalColumn(additionalColumns.length + 1)
2026
- }}
2027
- >
2028
- Add Column
2029
- </button>
2030
- </fieldset>
2031
- )}
2032
- {'category' === config.legend.type && (
2033
- <fieldset className='primary-fieldset edit-block'>
2034
- <label>
2035
- <span className='edit-label'>
2036
- Additional Category
2037
- <Tooltip style={{ textTransform: 'none' }}>
2038
- <Tooltip.Target>
2039
- <Icon display='question' style={{ marginLeft: '0.5rem' }} />
2040
- </Tooltip.Target>
2041
- <Tooltip.Content>
2042
- <p>You can provide additional categories to ensure they appear in the legend</p>
2043
- </Tooltip.Content>
2044
- </Tooltip>
2045
- </span>
2046
- </label>
2047
- {config.legend.additionalCategories &&
2048
- config.legend.additionalCategories.map((val, i) => (
2049
- <fieldset className='edit-block' key={val}>
2050
- <button
2051
- className='remove-column'
2052
- onClick={event => {
2053
- event.preventDefault()
2054
- const updatedAdditionaCategories = [...config.legend.additionalCategories]
2055
- updatedAdditionaCategories.splice(i, 1)
2056
- updateField('legend', null, 'additionalCategories', updatedAdditionaCategories)
1317
+ )}
1318
+
1319
+ {/* Type */}
1320
+ {/* Select > Filter a state */}
1321
+ {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)
2057
1333
  }}
2058
- >
2059
- Remove
2060
- </button>
1334
+ />
1335
+ </label>
1336
+ )}
1337
+ {/* Country Selection for World Maps */}
1338
+ {config.general.geoType === 'world' && (
1339
+ <>
2061
1340
  <label>
2062
- <span className='edit-label column-heading'>Category</span>
2063
- <TextField
2064
- value={val}
2065
- section='legend'
2066
- subsection={null}
2067
- fieldName='additionalCategories'
2068
- updateField={(section, subsection, fieldName, value) => {
2069
- const updatedAdditionaCategories = [...config.legend.additionalCategories]
2070
- updatedAdditionaCategories[i] = value
2071
- updateField(section, subsection, fieldName, updatedAdditionaCategories)
1341
+ <span>Countries Selector</span>
1342
+ <MultiSelect
1343
+ selected={(config.general.countriesPicked || []).map(country => country.name)}
1344
+ options={CountryOptionList().map(option => ({
1345
+ value: option.props.value,
1346
+ label: option.props.children
1347
+ }))}
1348
+ fieldName={'countriesPicked'}
1349
+ updateField={(_, __, ___, selectedOptions) => {
1350
+ handleEditorChanges('chooseCountry', selectedOptions)
2072
1351
  }}
2073
1352
  />
2074
1353
  </label>
2075
- </fieldset>
2076
- ))}
2077
- <button
2078
- className={'btn btn-primary full-width'}
2079
- onClick={event => {
2080
- event.preventDefault()
2081
- const updatedAdditionaCategories = [...(config.legend.additionalCategories || [])]
2082
- updatedAdditionaCategories.push('')
2083
- updateField('legend', null, 'additionalCategories', updatedAdditionaCategories)
2084
- }}
2085
- >
2086
- Add Category
2087
- </button>
2088
- </fieldset>
2089
- )}
2090
- </AccordionItemPanel>
2091
- </AccordionItem>{' '}
2092
- {/* Columns */}
2093
- {'navigation' !== config.general.type && (
2094
- <AccordionItem>
2095
- {' '}
2096
- {/* Legend */}
2097
- <AccordionItemHeading>
2098
- <AccordionItemButton>Legend</AccordionItemButton>
2099
- </AccordionItemHeading>
2100
- <AccordionItemPanel>
2101
- {(config.legend.type === 'equalnumber' || config.legend.type === 'equalinterval') && (
2102
- <Select
2103
- label='Legend Type'
2104
- value={legend.type}
2105
- options={[
2106
- { value: 'equalnumber', label: 'Equal Number (Quantiles)' },
2107
- { value: 'equalinterval', label: 'Equal Interval' }
2108
- ]}
2109
- onChange={event => {
2110
- let testForType = Number(typeof config.data[0][config.columns.primary.name])
2111
- let hasValue = config.data[0][config.columns.primary.name]
2112
- let messages = []
2113
-
2114
- if (!hasValue) {
2115
- messages.push(
2116
- `There appears to be values missing for data in the primary column ${config.columns.primary.name}`
2117
- )
2118
- }
2119
-
2120
- if (testForType === 'string' && isNaN(testForType) && value !== 'category') {
2121
- messages.push(
2122
- 'Error with legend. Primary columns that are text must use a categorical legend type. Try changing the legend type to DEV-12345categorical.'
2123
- )
2124
- } else {
2125
- messages = []
2126
- }
2127
-
2128
- const _newConfig = cloneConfig(config)
2129
- _newConfig.legend.type = event.target.value
2130
- _newConfig.runtime.editorErrorMessage = messages
2131
- setConfig(_newConfig)
2132
- }}
2133
- />
2134
- )}
2135
- {'navigation' !== config.general.type && (
2136
- <label className='checkbox'>
2137
- <input
2138
- type='checkbox'
2139
- checked={config.general.showSidebar || false}
2140
- onChange={event => {
2141
- const _newConfig = cloneConfig(config)
2142
- _newConfig.general.showSidebar = event.target.checked
2143
- setConfig(_newConfig)
2144
- }}
2145
- />
2146
- <span className='edit-label'>Show Legend</span>
2147
- </label>
2148
- )}
2149
- {'navigation' !== config.general.type && (
2150
- <>
1354
+ {config.general.countriesPicked && config.general.countriesPicked.length > 0 && (
1355
+ <CheckBox
1356
+ value={config.general.hideUnselectedCountries || false}
1357
+ fieldName='hideUnselectedCountries'
1358
+ label='Hide Unselected Countries'
1359
+ updateField={updateField}
1360
+ section='general'
1361
+ />
1362
+ )}
1363
+ </>
1364
+ )}
1365
+ {/* Type */}
2151
1366
  <Select
2152
- label='Legend Position'
2153
- value={legend.position || ''}
1367
+ label={
1368
+ <>
1369
+ Map Type
1370
+ <Tooltip style={{ textTransform: 'none' }}>
1371
+ <Tooltip.Target>
1372
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
1373
+ </Tooltip.Target>
1374
+ <Tooltip.Content>
1375
+ <p>
1376
+ Select "Data" to create a color-coded data map. To create a navigation-only map, select
1377
+ "Navigation."
1378
+ </p>
1379
+ </Tooltip.Content>
1380
+ </Tooltip>
1381
+ </>
1382
+ }
1383
+ value={config.general.type}
2154
1384
  options={[
2155
- { value: 'side', label: 'Side' },
2156
- { value: 'bottom', label: 'Bottom' },
2157
- { value: 'top', label: 'Top' }
1385
+ { value: 'data', label: 'Data' },
1386
+ ...(config.general.geoType === 'us-county' ? [{ value: 'us-geocode', label: 'Geocode' }] : []),
1387
+ ...(config.general.geoType === 'world' ? [{ value: 'world-geocode', label: 'Geocode' }] : []),
1388
+ ...(config.general.geoType !== 'us-county'
1389
+ ? [{ value: 'navigation', label: 'Navigation' }]
1390
+ : []),
1391
+ ...(config.general.geoType === 'world' || config.general.geoType === 'us'
1392
+ ? [{ value: 'bubble', label: 'Bubble' }]
1393
+ : [])
2158
1394
  ]}
2159
1395
  onChange={event => {
2160
- handleEditorChanges('sidebarPosition', event.target.value)
1396
+ handleEditorChanges('editorMapType', event.target.value)
2161
1397
  }}
2162
1398
  />
2163
- {(config.legend.position === 'side' || !config.legend.position) &&
2164
- config.legend.style === 'gradient' && (
2165
- <span style={{ color: 'red', fontSize: '14px' }}>
2166
- Position must be set to top or bottom to use gradient style.
2167
- </span>
1399
+
1400
+ {/* Navigation Behavior */}
1401
+ {(config.general.type === 'navigation' || config.general.type === 'data') && (
1402
+ <Select
1403
+ label='Navigation Behavior'
1404
+ value={config.general.navigationTarget}
1405
+ options={[
1406
+ { value: '_self', label: 'Same Window' },
1407
+ { value: '_blank', label: 'New Window' }
1408
+ ]}
1409
+ onChange={event => {
1410
+ const _newConfig = cloneConfig(config)
1411
+ _newConfig.general.navigationTarget = event.target.value
1412
+ setConfig(_newConfig)
1413
+ }}
1414
+ />
1415
+ )}
1416
+ <label>
1417
+ <span className='edit-label'>Data Classification Type</span>
1418
+ <div>
1419
+ <label>
1420
+ <input
1421
+ type='radio'
1422
+ name='equalnumber'
1423
+ value='equalnumber'
1424
+ checked={config.legend.type === 'equalnumber' || config.legend.type === 'equalinterval'}
1425
+ onChange={e => handleEditorChanges('classificationType', e.target.value)}
1426
+ />
1427
+ Numeric/Quantitative
1428
+ </label>
1429
+ <label>
1430
+ <input
1431
+ type='radio'
1432
+ name='category'
1433
+ value='category'
1434
+ checked={config.legend.type === 'category'}
1435
+ onChange={e => handleEditorChanges('classificationType', e.target.value)}
1436
+ />
1437
+ Categorical
1438
+ </label>
1439
+ </div>
1440
+ </label>
1441
+
1442
+ {/* Display as Hex */}
1443
+ {general.geoType === 'us' && general.type !== 'navigation' && general.type !== 'bubble' && (
1444
+ <CheckBox
1445
+ value={config.general.displayAsHex}
1446
+ section='general'
1447
+ subsection={null}
1448
+ fieldName='displayAsHex'
1449
+ label='Display As Hex Map'
1450
+ updateField={updateField}
1451
+ className=''
1452
+ />
1453
+ )}
1454
+
1455
+ {/* Shapes on Hex */}
1456
+ <CheckBox
1457
+ value={config.hexMap.type === 'shapes'}
1458
+ section='hexMap'
1459
+ subsection={null}
1460
+ fieldName='type'
1461
+ label='Display Shapes on Hex Map'
1462
+ updateField={updateField}
1463
+ onChange={event => {
1464
+ setConfig({
1465
+ ...config,
1466
+ hexMap: {
1467
+ ...config.hexMap,
1468
+ type: event.target.checked ? 'shapes' : 'standard'
1469
+ }
1470
+ })
1471
+ }}
1472
+ />
1473
+ <HexSetting.ShapeColumns columnsOptions={columnsOptions} />
1474
+
1475
+ {'us' === config.general.geoType &&
1476
+ 'bubble' !== config.general.type &&
1477
+ false === config.general.displayAsHex && (
1478
+ <CheckBox
1479
+ label='Show state labels'
1480
+ checked={config.general.displayStateLabels}
1481
+ onChange={event => {
1482
+ handleEditorChanges('displayStateLabels', event.target.checked)
1483
+ }}
1484
+ tooltip={
1485
+ <Tooltip style={{ textTransform: 'none' }}>
1486
+ <Tooltip.Target>
1487
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
1488
+ </Tooltip.Target>
1489
+ <Tooltip.Content>
1490
+ <p>Recommended set to display for Section 508 compliance.</p>
1491
+ </Tooltip.Content>
1492
+ </Tooltip>
1493
+ }
1494
+ />
2168
1495
  )}
2169
- </>
2170
- )}
2171
- {'navigation' !== config.general.type && (
2172
- <Select
2173
- label={
2174
- <>
2175
- Legend Style
1496
+
1497
+ {'us' === config.general.geoType && (
1498
+ <CheckBox
1499
+ value={general.territoriesAlwaysShow || false}
1500
+ section='general'
1501
+ subsection={null}
1502
+ fieldName='territoriesAlwaysShow'
1503
+ label='Show All Territories'
1504
+ updateField={updateField}
1505
+ />
1506
+ )}
1507
+ </AccordionItemPanel>
1508
+ </AccordionItem>
1509
+ <AccordionItem>
1510
+ {' '}
1511
+ {/* General */}
1512
+ <AccordionItemHeading>
1513
+ <AccordionItemButton>General</AccordionItemButton>
1514
+ </AccordionItemHeading>
1515
+ <AccordionItemPanel>
1516
+ <TextField
1517
+ value={general.title}
1518
+ data-testid='title-input'
1519
+ updateField={updateField}
1520
+ section='general'
1521
+ fieldName='title'
1522
+ id='title'
1523
+ label='Title'
1524
+ placeholder='Map Title'
1525
+ tooltip={
2176
1526
  <Tooltip style={{ textTransform: 'none' }}>
2177
1527
  <Tooltip.Target>
2178
- <Icon
2179
- display='question'
2180
- style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }}
2181
- />
1528
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
2182
1529
  </Tooltip.Target>
2183
1530
  <Tooltip.Content>
2184
1531
  <p>
2185
- If using gradient style, limit the legend to five items for better mobile visibility, and
2186
- position the legend at the top or bottom.
1532
+ Title is required to set the name of the download file but can be hidden using the option
1533
+ below.
2187
1534
  </p>
2188
1535
  </Tooltip.Content>
2189
1536
  </Tooltip>
2190
- </>
2191
- }
2192
- value={legend.style || ''}
2193
- options={[
2194
- { value: 'circles', label: 'circles' },
2195
- { value: 'boxes', label: 'boxes' },
2196
- ...(legend.position !== 'side' ? [{ value: 'gradient', label: 'gradient' }] : [])
2197
- ]}
2198
- onChange={event => {
2199
- handleEditorChanges('legendStyle', event.target.value)
2200
- }}
2201
- />
2202
- )}
2203
- {'navigation' !== config.general.type && config.legend.style === 'gradient' && (
2204
- <label>
2205
- <span className='edit-label'>Gradient Style</span>
2206
- <select
2207
- value={legend.subStyle || ''}
2208
- onChange={event => {
2209
- handleEditorChanges('legendSubStyle', event.target.value)
2210
- }}
2211
- >
2212
- <option value='linear blocks'>linear blocks</option>
2213
- <option value='smooth'>smooth</option>
2214
- </select>
2215
- </label>
2216
- )}
2217
- {allowLegendSeparators && (
2218
- <TextField
2219
- value={legend.separators}
2220
- updateField={updateField}
2221
- section='legend'
2222
- fieldName='separators'
2223
- label='Legend Separators'
2224
- placeholder='ex: 1,4'
2225
- tooltip={
2226
- <Tooltip style={{ textTransform: 'none' }}>
2227
- <Tooltip.Target>
2228
- <Icon display='question' style={{ marginLeft: '0.5rem' }} />
2229
- </Tooltip.Target>
2230
- <Tooltip.Content>
2231
- <p>
2232
- Separators between legend items represented by the legend item numbers separated by commas.
2233
- </p>
2234
- </Tooltip.Content>
2235
- </Tooltip>
2236
- }
2237
- />
2238
- )}
2239
- {'navigation' !== config.general.type && config.legend.style === 'gradient' && (
2240
- <label>
2241
- <span className='edit-label'>Tick Rotation (Degrees)</span>
2242
- <input
2243
- type='number'
2244
- className='number-narrow'
2245
- value={legend.tickRotation || ''}
2246
- onChange={event => {
2247
- handleEditorChanges('legendTickRotation', event.target.value)
2248
- }}
2249
- ></input>
2250
- </label>
2251
- )}
2252
- {
2253
- <label className='checkbox'>
2254
- <input
2255
- type='checkbox'
2256
- checked={legend.hideBorder}
2257
- onChange={event => {
2258
- handleEditorChanges('legendBorder', event.target.checked)
2259
- }}
2260
- />
2261
- <span className='edit-label column-heading'>Hide Legend Box</span>
2262
- <Tooltip style={{ textTransform: 'none' }}>
2263
- <Tooltip.Target>
2264
- <Icon
2265
- display='question'
2266
- style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }}
2267
- />
2268
- </Tooltip.Target>
2269
- <Tooltip.Content>
2270
- <p> Default option for top and bottom legends is ‘No Box.’</p>
2271
- </Tooltip.Content>
2272
- </Tooltip>
2273
- </label>
2274
- }
2275
- {'side' === legend.position && (
2276
- <label className='checkbox'>
2277
- <input
2278
- type='checkbox'
2279
- checked={legend.singleColumn}
2280
- onChange={event => {
2281
- const _newConfig = cloneConfig(config)
2282
- _newConfig.legend.singleColumn = event.target.checked
2283
- _newConfig.legend.singleRow = false
2284
- _newConfig.legend.verticalSorted = false
2285
-
2286
- setConfig(_newConfig)
2287
- }}
2288
- />
2289
- <span className='edit-label'>Single Column Legend</span>
2290
- </label>
2291
- )}
2292
- {'side' !== legend.position && legend.style !== 'gradient' && (
2293
- <label className='checkbox'>
2294
- <input
2295
- type='checkbox'
2296
- checked={legend.singleRow}
2297
- onChange={event => {
2298
- const _newConfig = cloneConfig(config)
2299
- _newConfig.legend.singleRow = event.target.checked
2300
- _newConfig.legend.singleColumn = false
2301
- _newConfig.legend.verticalSorted = false
2302
-
2303
- setConfig(_newConfig)
2304
- }}
1537
+ }
2305
1538
  />
2306
- <span className='edit-label'>Single Row Legend</span>
2307
- </label>
2308
- )}
2309
-
2310
- {'navigation' !== config.general.type && config.legend.type === 'category' && (
2311
- <Select
2312
- label='Legend Group By :'
2313
- value={legend.groupBy || ''}
2314
- options={columnsOptions.map(c => c.key)}
2315
- onChange={event => {
2316
- const _newConfig = cloneConfig(config)
2317
- _newConfig.legend.groupBy = event.target.value
2318
- setConfig(_newConfig)
2319
- }}
2320
- />
2321
- )}
2322
- {config.legend.style !== 'gradient' && (
2323
- <label className='checkbox'>
2324
- <input
2325
- type='checkbox'
2326
- checked={legend.verticalSorted}
2327
- onChange={event => {
2328
- const _newConfig = cloneConfig(config)
2329
- _newConfig.legend.verticalSorted = event.target.checked
2330
- setConfig(_newConfig)
2331
- }}
1539
+ <Select
1540
+ value={general.titleStyle}
1541
+ section='general'
1542
+ fieldName='titleStyle'
1543
+ label='Title Style'
1544
+ updateField={updateField}
1545
+ options={[
1546
+ { value: 'small', label: 'Small (h3)' },
1547
+ { value: 'large', label: 'Large (h2)' },
1548
+ { value: 'legacy', label: 'Legacy' }
1549
+ ]}
1550
+ tooltip={
1551
+ <Tooltip style={{ textTransform: 'none' }}>
1552
+ <Tooltip.Target>
1553
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
1554
+ </Tooltip.Target>
1555
+ <Tooltip.Content>
1556
+ <p>
1557
+ Choose the visual style for the map title. Consider heading order on your page when
1558
+ selecting the title style. For 508 reasons, ensure your page follows a proper heading
1559
+ order.
1560
+ </p>
1561
+ </Tooltip.Content>
1562
+ </Tooltip>
1563
+ }
2332
1564
  />
2333
- <span className='edit-label'>Vertical sorted legend</span>
2334
- </label>
2335
- )}
2336
-
2337
- {/* always show */}
2338
- {
2339
- <label className='checkbox'>
2340
- <input
2341
- type='checkbox'
2342
- checked={legend.showSpecialClassesLast}
2343
- onChange={event => {
2344
- handleEditorChanges('legendShowSpecialClassesLast', event.target.checked)
2345
- }}
1565
+ <CheckBox
1566
+ value={config.general.showTitle || false}
1567
+ section='general'
1568
+ subsection={null}
1569
+ fieldName='showTitle'
1570
+ label='Show Title'
1571
+ updateField={updateField}
2346
1572
  />
2347
- <span className='edit-label'>Show Special Classes Last</span>
2348
- </label>
2349
- }
2350
- {'category' !== legend.type && (
2351
- <label className='checkbox'>
2352
- <input
2353
- type='checkbox'
2354
- checked={legend.separateZero || false}
2355
- onChange={event => {
2356
- const _newConfig = cloneConfig(config)
2357
- _newConfig.legend.separateZero = event.target.checked
2358
- return setConfig(_newConfig)
2359
- }}
1573
+ <TextField
1574
+ value={general.superTitle || ''}
1575
+ updateField={updateField}
1576
+ section='general'
1577
+ fieldName='superTitle'
1578
+ label='Super Title'
1579
+ tooltip={
1580
+ <Tooltip style={{ textTransform: 'none' }}>
1581
+ <Tooltip.Target>
1582
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
1583
+ </Tooltip.Target>
1584
+ <Tooltip.Content>
1585
+ <p>Super Title</p>
1586
+ </Tooltip.Content>
1587
+ </Tooltip>
1588
+ }
2360
1589
  />
2361
- <span className='edit-label column-heading'>
2362
- Separate Zero
2363
- <Tooltip style={{ textTransform: 'none' }}>
2364
- <Tooltip.Target>
2365
- <Icon
2366
- display='question'
2367
- style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }}
2368
- />
2369
- </Tooltip.Target>
2370
- <Tooltip.Content>
2371
- <p>For numeric data, you can separate the zero value as its own data class.</p>
2372
- </Tooltip.Content>
2373
- </Tooltip>
2374
- </span>
2375
- </label>
2376
- )}
2377
-
2378
- {/* Temp Checkbox */}
2379
- {config.legend.type === 'equalnumber' && (
2380
- <label className='checkbox'>
2381
- <input
2382
- type='checkbox'
2383
- checked={config.general.equalNumberOptIn}
2384
- onChange={event => {
2385
- const _newConfig = _.clone(config)
2386
- _newConfig.general.equalNumberOptIn = event.target.checked
2387
- setConfig(_newConfig)
2388
- }}
1590
+ <TextField
1591
+ type='textarea'
1592
+ value={general.introText}
1593
+ updateField={updateField}
1594
+ section='general'
1595
+ fieldName='introText'
1596
+ label='Message'
1597
+ tooltip={
1598
+ <Tooltip style={{ textTransform: 'none' }}>
1599
+ <Tooltip.Target>
1600
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
1601
+ </Tooltip.Target>
1602
+ <Tooltip.Content>
1603
+ <p>Intro Text</p>
1604
+ </Tooltip.Content>
1605
+ </Tooltip>
1606
+ }
2389
1607
  />
2390
- <span className='edit-label column-heading'>Use new quantile legend</span>
2391
- <Tooltip style={{ textTransform: 'none' }}>
2392
- <Tooltip.Target>
2393
- <Icon
2394
- display='question'
2395
- style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }}
2396
- />
2397
- </Tooltip.Target>
2398
- <Tooltip.Content>
2399
- <p>This prevents numbers from being used in more than one category (ie. 0-1, 1-2, 2-3) </p>
2400
- </Tooltip.Content>
2401
- </Tooltip>
2402
- </label>
2403
- )}
2404
-
2405
- {'category' !== legend.type && (
2406
- <Select
2407
- label={
2408
- <>
2409
- Number of Items
1608
+ <TextField
1609
+ type='textarea'
1610
+ value={general.subtext}
1611
+ updateField={updateField}
1612
+ section='general'
1613
+ fieldName='subtext'
1614
+ label='Subtext'
1615
+ tooltip={
2410
1616
  <Tooltip style={{ textTransform: 'none' }}>
2411
1617
  <Tooltip.Target>
2412
1618
  <Icon display='question' style={{ marginLeft: '0.5rem' }} />
2413
1619
  </Tooltip.Target>
2414
1620
  <Tooltip.Content>
2415
1621
  <p>
2416
- For numeric maps, select the number of data classes. Do not include designated special
2417
- classes.
1622
+ Enter supporting text to display below the data visualization, if applicable. The
1623
+ following HTML tags are supported: strong, em, sup, and sub.
2418
1624
  </p>
2419
1625
  </Tooltip.Content>
2420
1626
  </Tooltip>
2421
- </>
2422
- }
2423
- value={legend.numberOfItems}
2424
- options={[...Array(numberOfItemsLimit).keys()].map(num => ({
2425
- value: num + 1,
2426
- label: num + 1
2427
- }))}
2428
- onChange={event => {
2429
- handleEditorChanges('legendNumber', event.target.value)
2430
- }}
2431
- />
2432
- )}
2433
- {'category' === legend.type && (
2434
- <React.Fragment>
2435
- <label>
2436
- <span className='edit-label'>
2437
- Category Order
1627
+ }
1628
+ />
1629
+ <TextField
1630
+ type='textarea'
1631
+ value={general.footnotes}
1632
+ updateField={updateField}
1633
+ section='general'
1634
+ fieldName='footnotes'
1635
+ label='Footnotes'
1636
+ tooltip={
2438
1637
  <Tooltip style={{ textTransform: 'none' }}>
2439
1638
  <Tooltip.Target>
2440
1639
  <Icon display='question' style={{ marginLeft: '0.5rem' }} />
2441
1640
  </Tooltip.Target>
2442
1641
  <Tooltip.Content>
2443
- <p>Drag map categories into preferred legend order. </p>
1642
+ <p>Footnotes</p>
2444
1643
  </Tooltip.Content>
2445
1644
  </Tooltip>
2446
- </span>
2447
- </label>
2448
- {/* TODO: Swap out this drag and drop library back to something simpler. I had to remove the old one because it hadn't been updated and wouldn't work with Webpack 5. This is overkill for our needs. */}
2449
- <DragDropContext
2450
- onDragEnd={({ source, destination }) => categoryMove(source.index, destination.index)}
2451
- >
2452
- <Droppable droppableId='category_order'>
2453
- {provided => (
2454
- <ul {...provided.droppableProps} className='sort-list' ref={provided.innerRef}>
2455
- <CategoryList />
2456
- {provided.placeholder}
2457
- </ul>
2458
- )}
2459
- </Droppable>
2460
- </DragDropContext>
2461
- {runtimeLegend && runtimeLegend.length >= 10 && (
2462
- <section className='error-box my-2'>
2463
- <div>
2464
- <strong className='pt-1'>Warning</strong>
2465
- <p>
2466
- The maximum number of categorical legend items is 10. If your data has more than 10
2467
- categories your map will not display properly.
2468
- </p>
2469
- </div>
2470
- </section>
2471
- )}
2472
- </React.Fragment>
2473
- )}
2474
- <TextField
2475
- value={legend.title}
2476
- updateField={updateField}
2477
- section='legend'
2478
- fieldName='title'
2479
- label='Legend Title'
2480
- placeholder='Legend Title'
2481
- />
2482
- {false === legend.dynamicDescription && (
2483
- <TextField
2484
- type='textarea'
2485
- value={legend.description}
2486
- updateField={updateField}
2487
- section='legend'
2488
- fieldName='description'
2489
- label='Legend Description'
2490
- />
2491
- )}
2492
- {true === legend.dynamicDescription && (
2493
- <React.Fragment>
2494
- <label>
2495
- <span>Legend Description</span>
2496
- <span className='subtext'>For {displayFilterLegendValue(activeFilterValueForDescription)}</span>
2497
- <DynamicDesc value={legend.descriptions[String(activeFilterValueForDescription)]} />
2498
- </label>
2499
- <label>
2500
- <Select
2501
- label='Filter Value'
2502
- value={String(activeFilterValueForDescription)}
2503
- options={filterValueOptionList.map(arr => ({
2504
- value: arr,
2505
- label: displayFilterLegendValue(arr)
2506
- }))}
2507
- onChange={event => {
2508
- handleEditorChanges('changeActiveFilterValue', event.target.value)
2509
- }}
2510
- />
2511
- </label>
2512
- </React.Fragment>
2513
- )}
2514
- {config.filters.length > 0 && (
2515
- <label className='checkbox'>
2516
- <input
2517
- type='checkbox'
2518
- checked={legend.dynamicDescription}
2519
- onChange={() => {
2520
- handleEditorChanges('dynamicDescription', filterValueOptionList[0])
2521
- }}
2522
- />
2523
- <span className='edit-label column-heading'>
2524
- Dynamic Legend Description
2525
- <Tooltip style={{ textTransform: 'none' }}>
2526
- <Tooltip.Target>
2527
- <Icon
2528
- display='question'
2529
- style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }}
2530
- />
2531
- </Tooltip.Target>
2532
- <Tooltip.Content>
2533
- <p>
2534
- Check this option if the map has multiple filter controls and you want to specify a
2535
- description for each filter selection.
2536
- </p>
2537
- </Tooltip.Content>
2538
- </Tooltip>
2539
- </span>
2540
- </label>
2541
- )}
2542
- {(config.filters.length > 0 || config.general.type === 'bubble' || config.general.geoType === 'us') && (
2543
- <label className='checkbox'>
2544
- <input
2545
- type='checkbox'
2546
- checked={legend.unified}
2547
- onChange={event => handleEditorChanges('unifiedLegend', event.target.checked)}
1645
+ }
2548
1646
  />
2549
- <span className='edit-label column-heading'>
2550
- Unified Legend
2551
- <Tooltip style={{ textTransform: 'none' }}>
2552
- <Tooltip.Target>
2553
- <Icon
2554
- display='question'
2555
- style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }}
2556
- />
2557
- </Tooltip.Target>
2558
- <Tooltip.Content>
2559
- <p>
2560
- For a map with filters, check this option if you want the high and low values in the legend
2561
- to be based on <em>all</em> mapped values.
2562
- </p>
2563
- </Tooltip.Content>
2564
- </Tooltip>
2565
- </span>
2566
- </label>
2567
- )}
2568
- </AccordionItemPanel>
2569
- </AccordionItem>
2570
- )}
2571
- {'navigation' !== config.general.type && (
2572
- <>
2573
- <AccordionItem>
2574
- {/* Filters */}
2575
- <AccordionItemHeading>
2576
- <AccordionItemButton>Filters</AccordionItemButton>
2577
- </AccordionItemHeading>
2578
- <AccordionItemPanel>
2579
- <VizFilterEditor
2580
- config={config}
2581
- updateField={updateField}
2582
- rawData={config.data}
2583
- hasFootnotes={isDashboard}
2584
- />
2585
- </AccordionItemPanel>
2586
- </AccordionItem>
2587
- <AccordionItem>
2588
- <AccordionItemHeading>
2589
- <AccordionItemButton>Footnotes</AccordionItemButton>
2590
- </AccordionItemHeading>
2591
- <AccordionItemPanel>
2592
- <FootnotesEditor config={config} updateField={updateField} datasets={datasets} />
2593
- </AccordionItemPanel>
2594
- </AccordionItem>
2595
- </>
2596
- )}
2597
- {'navigation' !== config.general.type && (
2598
- <AccordionItem>
2599
- {' '}
2600
- {/* Data Table */}
2601
- <AccordionItemHeading>
2602
- <AccordionItemButton>Data Table</AccordionItemButton>
2603
- </AccordionItemHeading>
2604
- <AccordionItemPanel>
2605
- <TextField
2606
- value={table.label}
2607
- updateField={updateField}
2608
- section='table'
2609
- fieldName='label'
2610
- id='dataTableTitle'
2611
- label='Data Table Title'
2612
- placeholder='Data Table'
2613
- tooltip={
2614
- <Tooltip style={{ textTransform: 'none' }}>
2615
- <Tooltip.Target>
2616
- <Icon display='question' style={{ marginLeft: '0.5rem' }} />
2617
- </Tooltip.Target>
2618
- <Tooltip.Content>
2619
- <p>Label is required for Data Table for 508 Compliance</p>
2620
- </Tooltip.Content>
2621
- </Tooltip>
2622
- }
2623
- />
2624
- <label className='checkbox'>
2625
- <input
2626
- type='checkbox'
2627
- checked={config.table.wrapColumns}
2628
- onChange={event => {
2629
- setConfig({
2630
- ...config,
2631
- table: {
2632
- ...config.table,
2633
- wrapColumns: event.target.checked
2634
- }
2635
- })
2636
- }}
2637
- />
2638
- <span className='edit-label column-heading'>WRAP DATA TABLE COLUMNS</span>
2639
- </label>
2640
- <label className='checkbox'>
2641
- <input
2642
- type='checkbox'
2643
- checked={config.table.forceDisplay !== undefined ? config.table.forceDisplay : !isDashboard}
2644
- onChange={event => {
2645
- handleEditorChanges('showDataTable', event.target.checked)
2646
- }}
2647
- />
2648
- <span className='edit-label column-heading'>
2649
- Show Data Table
2650
- <Tooltip style={{ textTransform: 'none' }}>
2651
- <Tooltip.Target>
2652
- <Icon
2653
- display='question'
2654
- style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }}
1647
+
1648
+ {/* <label className="checkbox mt-4">
1649
+ <input type="checkbox" checked={ state.general.showDownloadMediaButton } onChange={(event) => { handleEditorChanges("toggleDownloadMediaButton", event.target.checked) }} />
1650
+ <span className="edit-label">Enable Media Download</span>
1651
+ </label> */}
1652
+ </AccordionItemPanel>
1653
+ </AccordionItem>
1654
+ <AccordionItem>
1655
+ {' '}
1656
+ {/* Columns */}
1657
+ <AccordionItemHeading>
1658
+ <AccordionItemButton>Columns</AccordionItemButton>
1659
+ </AccordionItemHeading>
1660
+ <AccordionItemPanel>
1661
+ <fieldset className='primary-fieldset edit-block'>
1662
+ <label>
1663
+ <span className='edit-label column-heading'>
1664
+ Geography
1665
+ <Tooltip style={{ textTransform: 'none' }}>
1666
+ <Tooltip.Target>
1667
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
1668
+ </Tooltip.Target>
1669
+ <Tooltip.Content>
1670
+ <p>
1671
+ Select the source column containing the map location names or, for county-level maps,
1672
+ the FIPS codes.
1673
+ </p>
1674
+ </Tooltip.Content>
1675
+ </Tooltip>
1676
+ </span>
1677
+ <Select
1678
+ value={config.columns.geo ? config.columns.geo.name : columnsOptions[0]}
1679
+ options={columnsOptions.map(c => c.key)}
1680
+ onChange={event => {
1681
+ editColumn('geo', 'name', event.target.value)
1682
+ }}
2655
1683
  />
2656
- </Tooltip.Target>
2657
- <Tooltip.Content>
2658
- <p>
2659
- Data tables are required for 508 compliance. When choosing to hide this data table, replace
2660
- with your own version.
2661
- </p>
2662
- </Tooltip.Content>
2663
- </Tooltip>
2664
- </span>
2665
- </label>
2666
- <label className='checkbox'>
2667
- <input
2668
- type='checkbox'
2669
- checked={config.table.showNonGeoData}
2670
- onChange={event => {
2671
- setConfig({
2672
- ...config,
2673
- table: {
2674
- ...config.table,
2675
- showNonGeoData: event.target.checked
2676
- }
2677
- })
2678
- }}
2679
- />
2680
- <span className='edit-label column-heading'>
2681
- Show Non Geographic Data
2682
- <Tooltip style={{ textTransform: 'none' }}>
2683
- <Tooltip.Target>
2684
- <Icon
2685
- display='question'
2686
- style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }}
1684
+ </label>
1685
+ {config.general.type === 'us-geocode' && (
1686
+ <CheckBox
1687
+ value={config.general.convertFipsCodes}
1688
+ section='general'
1689
+ subsection={null}
1690
+ fieldName='convertFipsCodes'
1691
+ label='Convert FIPS Codes to Geography Name'
1692
+ updateField={updateField}
2687
1693
  />
2688
- </Tooltip.Target>
2689
- <Tooltip.Content>
2690
- <p>Show any data not associated with a geographic location</p>
2691
- </Tooltip.Content>
2692
- </Tooltip>
2693
- </span>
2694
- </label>
2695
- <TextField
2696
- value={table.indexLabel || ''}
2697
- updateField={updateField}
2698
- section='table'
2699
- fieldName='indexLabel'
2700
- label='Index Column Header'
2701
- placeholder='Location'
2702
- tooltip={
2703
- <Tooltip style={{ textTransform: 'none' }}>
2704
- <Tooltip.Target>
2705
- <Icon display='question' style={{ marginLeft: '0.5rem' }} />
2706
- </Tooltip.Target>
2707
- <Tooltip.Content>
2708
- <p>
2709
- To comply with 508 standards, if the first column in the data table has no header, enter a
2710
- brief one here.
2711
- </p>
2712
- </Tooltip.Content>
2713
- </Tooltip>
2714
- }
2715
- />
2716
- <TextField
2717
- value={config.table.caption}
2718
- updateField={updateField}
2719
- section='table'
2720
- fieldName='caption'
2721
- label='Screen Reader Description'
2722
- placeholder='Data Table'
2723
- tooltip={
2724
- <Tooltip style={{ textTransform: 'none' }}>
2725
- <Tooltip.Target>
2726
- <Icon display='question' style={{ marginLeft: '0.5rem' }} />
2727
- </Tooltip.Target>
2728
- <Tooltip.Content>
2729
- <p>Enter a description of the data table to be read by screen readers.</p>
2730
- </Tooltip.Content>
2731
- </Tooltip>
2732
- }
2733
- type='textarea'
2734
- />
2735
- <label className='checkbox'>
2736
- <input
2737
- type='checkbox'
2738
- checked={config.table.limitHeight}
2739
- onChange={event => {
2740
- handleEditorChanges('limitDataTableHeight', event.target.checked)
2741
- }}
2742
- />
2743
- <span className='edit-label'>Limit Table Height</span>
2744
- </label>
2745
- {config.table.limitHeight && (
2746
- <TextField
2747
- value={table.height}
2748
- updateField={updateField}
2749
- section='table'
2750
- fieldName='height'
2751
- label='Data Table Height'
2752
- placeholder='Height(px)'
2753
- type='number'
2754
- min='0'
2755
- max='500'
2756
- />
2757
- )}
2758
-
2759
- <TextField
2760
- value={table.cellMinWidth}
2761
- updateField={updateField}
2762
- section='table'
2763
- fieldName='cellMinWidth'
2764
- label='Table Cell Min Width'
2765
- type='number'
2766
- min='0'
2767
- max='500'
2768
- />
1694
+ )}
2769
1695
 
2770
- <label className='checkbox'>
2771
- <input
2772
- type='checkbox'
2773
- checked={config.table.expanded || false}
2774
- onChange={event => {
2775
- handleEditorChanges('expandDataTable', event.target.checked)
2776
- }}
2777
- />
2778
- <span className='edit-label'>Map loads with data table expanded</span>
2779
- </label>
2780
- <CheckBox
2781
- value={config.table.download}
2782
- fieldName='download'
2783
- label='Show Download CSV Link'
2784
- section='table'
2785
- updateField={updateField}
2786
- />
2787
- {config.table.download && (
2788
- <>
2789
- <label className='checkbox'>
2790
- <input
2791
- type='checkbox'
2792
- className='ms-4'
2793
- checked={config.table.showDownloadLinkBelow}
2794
- onChange={event => {
2795
- handleEditorChanges('toggleDownloadLinkBelow', event.target.checked)
2796
- }}
1696
+ <CheckBox
1697
+ value={config.general.hideGeoColumnInTooltip || false}
1698
+ section='general'
1699
+ subsection={null}
1700
+ fieldName='hideGeoColumnInTooltip'
1701
+ label='Hide Geography Column Name in Tooltip'
1702
+ updateField={updateField}
2797
1703
  />
2798
- <span className='edit-label'>Show Link Below Table</span>
2799
- </label>
2800
- <CheckBox
2801
- value={config.table.downloadVisibleDataOnly}
2802
- fieldName='downloadVisibleDataOnly'
2803
- className='ms-4'
2804
- label='Download only visible data'
2805
- section='table'
2806
- updateField={updateField}
2807
- />
2808
- </>
2809
- )}
2810
- {isDashboard && (
2811
- <label className='checkbox'>
2812
- <input
2813
- type='checkbox'
2814
- checked={config.table.showDataTableLink}
2815
- onChange={event => {
2816
- const _newConfig = cloneConfig(config)
2817
- _newConfig.table.showDataTableLink = event.target.checked
2818
- setConfig(_newConfig)
2819
- }}
2820
- />
2821
- <span className='edit-label'>Show Data Table Name & Link</span>
2822
- </label>
2823
- )}
2824
- {isLoadedFromUrl && (
2825
- <label className='checkbox'>
2826
- <input
2827
- type='checkbox'
2828
- checked={config.table.showDownloadUrl}
2829
- onChange={event => {
2830
- const _newConfig = cloneConfig(config)
2831
- _newConfig.table.showDownloadUrl = event.target.checked
2832
- setConfig(_newConfig)
2833
- }}
2834
- />
2835
- <span className='edit-label'>Show URL to Automatically Updated Data</span>
2836
- </label>
2837
- )}
2838
- <label className='checkbox'>
2839
- <input
2840
- type='checkbox'
2841
- checked={config.table.showFullGeoNameInCSV}
2842
- onChange={event => {
2843
- handleEditorChanges('toggleShowFullGeoNameInCSV', event.target.checked)
2844
- }}
2845
- />
2846
- <span className='edit-label'>Include Full Geo Name in CSV Download</span>
2847
- </label>
2848
- <label className='checkbox'>
2849
- <input
2850
- type='checkbox'
2851
- checked={config.general.showDownloadImgButton}
2852
- onChange={event => {
2853
- handleEditorChanges('toggleDownloadImgButton', event.target.checked)
2854
- }}
2855
- />
2856
- <span className='edit-label'>Enable Image Download</span>
2857
- </label>
2858
-
2859
- {/* <label className='checkbox'>
2860
- <input
2861
- type='checkbox'
2862
- checked={state.general.showDownloadPdfButton}
2863
- onChange={event => {
2864
- handleEditorChanges('toggleDownloadPdfButton', event.target.checked)
2865
- }}
1704
+ <TextField
1705
+ value={config.general.geoLabelOverride}
1706
+ section='general'
1707
+ fieldName='geoLabelOverride'
1708
+ label='Geography Label'
1709
+ className='edit-label'
1710
+ updateField={updateField}
1711
+ tooltip={
1712
+ <Tooltip style={{ textTransform: 'none' }}>
1713
+ <Tooltip.Target>
1714
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
1715
+ </Tooltip.Target>
1716
+ <Tooltip.Content>
1717
+ <p>Enter a geography label for use in tooltips.</p>
1718
+ </Tooltip.Content>
1719
+ </Tooltip>
1720
+ }
2866
1721
  />
2867
- <span className='edit-label'>Enable Pdf Download</span>
2868
- </label> */}
2869
- </AccordionItemPanel>
2870
- </AccordionItem>
2871
- )}
2872
- <AccordionItem>
2873
- {' '}
2874
- {/* Tooltips */}
2875
- <AccordionItemHeading>
2876
- <AccordionItemButton>Interactivity</AccordionItemButton>
2877
- </AccordionItemHeading>
2878
- <AccordionItemPanel>
2879
- <Select
2880
- label={
2881
- <>
2882
- Detail displays on{' '}
2883
- <Tooltip style={{ textTransform: 'none' }}>
2884
- <Tooltip.Target>
2885
- <Icon display='question' style={{ marginLeft: '0.5rem' }} />
2886
- </Tooltip.Target>
2887
- <Tooltip.Content>
2888
- <p>
2889
- At mobile sizes, information always appears in a popover modal when a user taps on an item.
2890
- </p>
2891
- </Tooltip.Content>
2892
- </Tooltip>
2893
- </>
2894
- }
2895
- value={config.tooltips.appearanceType}
2896
- options={[
2897
- { value: 'hover', label: 'Hover - Tooltip' },
2898
- { value: 'click', label: 'Click - Popover Modal' }
2899
- ]}
2900
- onChange={event => {
2901
- handleEditorChanges('appearanceType', event.target.value)
2902
- }}
2903
- />
2904
- {'click' === config.tooltips.appearanceType && (
2905
- <TextField
2906
- value={tooltips.linkLabel}
2907
- section='tooltips'
2908
- fieldName='linkLabel'
2909
- label='Tooltips Link Label'
2910
- updateField={updateField}
2911
- />
2912
- )}
2913
- </AccordionItemPanel>
2914
- </AccordionItem>
2915
- <AccordionItem>
2916
- {' '}
2917
- {/* Visual */}
2918
- <AccordionItemHeading>
2919
- <AccordionItemButton>Visual</AccordionItemButton>
2920
- </AccordionItemHeading>
2921
- <AccordionItemPanel>
2922
- <label>
2923
- <span className='edit-label'>Header Theme</span>
2924
- <ul className='color-palette'>
2925
- {HEADER_COLORS.map(palette => {
2926
- return (
2927
- <li
2928
- title={palette}
2929
- key={palette}
2930
- onClick={() => {
2931
- handleEditorChanges('headerColor', palette)
2932
- }}
2933
- className={config.general.headerColor === palette ? 'selected ' + palette : palette}
2934
- ></li>
2935
- )
2936
- })}
2937
- </ul>
2938
- </label>
2939
- <label className='checkbox'>
2940
- <input
2941
- type='checkbox'
2942
- checked={config.general.showTitle || false}
2943
- onChange={event => {
2944
- handleEditorChanges('showTitle', event.target.checked)
2945
- }}
2946
- />
2947
- <span className='edit-label'>Show Title</span>
2948
- </label>
2949
-
2950
- {'navigation' === config.general.type && (
2951
- <label className='checkbox'>
2952
- <input
2953
- type='checkbox'
2954
- checked={config.general.fullBorder || false}
2955
- onChange={event => {
2956
- const _newConfig = cloneConfig(config)
2957
- _newConfig.general.fullBorder = event.target.checked
2958
- setConfig(_newConfig)
2959
- }}
2960
- />
2961
- <span className='edit-label'>Add border around map</span>
2962
- </label>
2963
- )}
2964
- <Select
2965
- label='Geo Border Color'
2966
- value={config.general.geoBorderColor || ''}
2967
- options={[
2968
- { value: 'darkGray', label: 'Dark Gray (Default)' },
2969
- { value: 'sameAsBackground', label: 'White' }
2970
- ]}
2971
- onChange={event => {
2972
- handleEditorChanges('geoBorderColor', event.target.value)
2973
- }}
2974
- />
2975
- <label>
2976
- <span className='edit-label'>Map Color Palette</span>
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} />
2987
- <InputToggle
2988
- type='3d'
2989
- section='general'
2990
- subsection='palette'
2991
- fieldName='isReversed'
2992
- size='small'
2993
- label='Use selected palette in reverse order'
2994
- onClick={() => {
2995
- const _state = cloneConfig(config)
2996
- const currentPaletteName = config.general.palette?.name || ''
2997
- _state.general.palette.isReversed = !_state.general.palette.isReversed
2998
- let paletteName = ''
2999
- if (_state.general.palette.isReversed && !currentPaletteName.endsWith('reverse')) {
3000
- paletteName = currentPaletteName + 'reverse'
3001
- }
3002
- if (!_state.general.palette.isReversed && currentPaletteName.endsWith('reverse')) {
3003
- paletteName = currentPaletteName.slice(0, -7)
3004
- }
3005
- if (paletteName) {
3006
- _state.general.palette.name = paletteName
3007
- }
3008
- setConfig(_state)
3009
- }}
3010
- value={config.general.palette.isReversed}
3011
- />
3012
- <span>Sequential</span>
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
- />
3024
- <span>Non-Sequential</span>
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
3038
- }
3039
- return true
3040
- }}
3041
- />
3042
- <span>Colorblind Safe</span>
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
3056
- }
3057
- return true
3058
- }}
3059
- />
3060
- <label>
3061
- Geocode Settings
3062
- <TextField
3063
- type='number'
3064
- value={config.visual.geoCodeCircleSize}
3065
- section='visual'
3066
- max='10'
3067
- fieldName='geoCodeCircleSize'
3068
- label='Geocode Circle Size'
3069
- updateField={updateField}
3070
- />
3071
- </label>
3072
-
3073
- {config.general.type === 'bubble' && (
3074
- <>
3075
- <TextField
3076
- type='number'
3077
- value={config.visual.minBubbleSize}
3078
- section='visual'
3079
- fieldName='minBubbleSize'
3080
- label='Minimum Bubble Size'
3081
- updateField={updateField}
3082
- />
3083
- <TextField
3084
- type='number'
3085
- value={config.visual.maxBubbleSize}
3086
- section='visual'
3087
- fieldName='maxBubbleSize'
3088
- label='Maximum Bubble Size'
3089
- updateField={updateField}
3090
- />
3091
- </>
3092
- )}
3093
- {(config.general.geoType === 'world' ||
3094
- (config.general.geoType === 'us' && config.general.type === 'bubble')) && (
3095
- <label className='checkbox'>
3096
- <input
3097
- type='checkbox'
3098
- checked={config.visual.showBubbleZeros}
3099
- onChange={event => {
3100
- const _newConfig = _.cloneDeep(config)
3101
- _newConfig.visual.showBubbleZeros = event.target.checked
3102
- setConfig(_newConfig)
3103
- }}
3104
- />
3105
- <span className='edit-label'>Show Data with Zero's on Bubble Map</span>
3106
- </label>
3107
- )}
3108
- {(config.general.geoType === 'world' || config.general.geoType === 'single-state') && (
3109
- <label className='checkbox'>
3110
- <input
3111
- type='checkbox'
3112
- checked={config.general.allowMapZoom}
3113
- onChange={event => {
3114
- const _newConfig = cloneConfig(config)
3115
- _newConfig.general.allowMapZoom = event.target.checked
3116
- _newConfig.mapPosition.coordinates = config.general.geoType === 'world' ? [0, 30] : [0, 0]
3117
- _newConfig.mapPosition.zoom = 1
3118
- setConfig(_newConfig)
3119
- }}
3120
- />
3121
- <span className='edit-label'>Allow Map Zooming</span>
3122
- </label>
3123
- )}
3124
- {config.general.type === 'bubble' && (
3125
- <label className='checkbox'>
3126
- <input
3127
- type='checkbox'
3128
- checked={config.visual.extraBubbleBorder}
3129
- onChange={event => {
3130
- const _newConfig = cloneConfig(config)
3131
- _newConfig.visual.extraBubbleBorder = event.target.checked
3132
- setConfig(_newConfig)
3133
- }}
3134
- />
3135
- <span className='edit-label'>Bubble Map has extra border</span>
3136
- </label>
3137
- )}
3138
- {(config.general.geoType === 'us' ||
3139
- config.general.geoType === 'us-county' ||
3140
- config.general.geoType === 'world') && (
3141
- <>
3142
- <label>
3143
- <span className='edit-label'>Default City Style</span>
3144
- <select
3145
- value={config.visual.cityStyle || false}
3146
- onChange={event => {
3147
- handleEditorChanges('handleCityStyle', event.target.value)
3148
- }}
3149
- >
3150
- <option value='circle'>Circle</option>
3151
- <option value='pin'>Pin</option>
3152
- <option value='square'>Square</option>
3153
- <option value='triangle'>Triangle</option>
3154
- <option value='diamond'>Diamond</option>
3155
- <option value='star'>Star</option>
3156
- </select>
3157
- </label>
3158
- <TextField
3159
- value={config.visual.cityStyleLabel}
3160
- section='visual'
3161
- fieldName='cityStyleLabel'
3162
- label='Label (Optional) '
3163
- updateField={updateField}
3164
- tooltip={
3165
- <Tooltip style={{ textTransform: 'none' }}>
3166
- <Tooltip.Target>
3167
- <Icon display='question' style={{ marginLeft: '0.5rem' }} />
3168
- </Tooltip.Target>
3169
- <Tooltip.Content>
3170
- <p>When a label is provided, the default city style will appear in the legend.</p>
3171
- </Tooltip.Content>
3172
- </Tooltip>
3173
- }
3174
- />
3175
- </>
3176
- )}
3177
- {/* <AdditionalCityStyles /> */}
3178
- <>
3179
- {config.visual.additionalCityStyles.length > 0 &&
3180
- config.visual.additionalCityStyles.map(({ label, column, value, shape }, i) => {
3181
- return (
3182
- <div className='edit-block' key={`additional-city-style-${i}`}>
3183
- <button
3184
- className='remove-column'
3185
- onClick={e => {
3186
- e.preventDefault()
3187
- editCityStyles('remove', i, '', '')
1722
+ </fieldset>
1723
+ {'navigation' !== config.general.type && (
1724
+ <fieldset className='primary-fieldset edit-block'>
1725
+ <Select
1726
+ label='Data Column'
1727
+ value={columns.primary.name}
1728
+ options={columnsOptions.map(c => c.key)}
1729
+ onChange={event => {
1730
+ const _state = cloneConfig(config)
1731
+ _state.columns.primary.name = event.target.value
1732
+ _state.columns.primary.label = event.target.value
1733
+ setConfig(_state)
3188
1734
  }}
3189
- >
3190
- Remove
3191
- </button>
3192
- <p>City Style {i + 1}</p>
3193
- <label>
3194
- <span className='edit-label column-heading'>Column with configuration value</span>
3195
- <select
3196
- value={column}
3197
- onChange={e => {
3198
- editCityStyles('update', i, 'column', e.target.value)
3199
- }}
3200
- >
3201
- {columnsOptions}
3202
- </select>
3203
- </label>
3204
- <label>
3205
- <span className='edit-label column-heading'>Value to Trigger</span>
3206
- <input
3207
- type='text'
3208
- value={value}
3209
- onChange={e => {
3210
- editCityStyles('update', i, 'value', e.target.value)
3211
- }}
3212
- ></input>
3213
- </label>
3214
- <label>
3215
- <span className='edit-label column-heading'>Shape</span>
3216
- <select
3217
- value={shape}
3218
- onChange={e => {
3219
- editCityStyles('update', i, 'shape', e.target.value)
3220
- }}
3221
- >
3222
- {getCityStyleOptions('value')}
3223
- </select>
3224
- </label>
1735
+ tooltip={
1736
+ <Tooltip style={{ textTransform: 'none' }}>
1737
+ <Tooltip.Target>
1738
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
1739
+ </Tooltip.Target>
1740
+ <Tooltip.Content>
1741
+ <p>
1742
+ Select the source column containing the categorical or numeric values to be mapped.
1743
+ </p>
1744
+ </Tooltip.Content>
1745
+ </Tooltip>
1746
+ }
1747
+ />
3225
1748
  <label>
3226
- <span className='edit-label column-heading'>Label</span>
3227
- <input
3228
- key={i}
3229
- type='text'
3230
- value={label}
3231
- onChange={e => {
3232
- editCityStyles('update', i, 'label', e.target.value)
1749
+ <CheckBox
1750
+ value={config.general.hidePrimaryColumnInTooltip || false}
1751
+ section='general'
1752
+ subsection={null}
1753
+ fieldName='hidePrimaryColumnInTooltip'
1754
+ label='Hide Data Column Name in Tooltip'
1755
+ updateField={updateField}
1756
+ onChange={event => {
1757
+ handleEditorChanges('hidePrimaryColumnInTooltip', event.target.checked)
3233
1758
  }}
3234
1759
  />
3235
1760
  </label>
3236
- </div>
3237
- )
3238
- })}
3239
-
3240
- <button
3241
- type='button'
3242
- onClick={() => editCityStyles('add', 0, '', '')}
3243
- className='btn btn-primary full-width'
3244
- >
3245
- Add city style
3246
- </button>
3247
- </>
3248
- <label htmlFor='opacity'>
3249
- <TextField
3250
- type='number'
3251
- min={0}
3252
- max={100}
3253
- value={config.tooltips.opacity ? config.tooltips.opacity : 100}
3254
- section='tooltips'
3255
- fieldName='opacity'
3256
- label='Tooltip Opacity (%)'
3257
- updateField={updateField}
3258
- />
3259
- </label>
3260
- {/* Leaflet Map Type */}
3261
- {config.general.geoType === 'leaflet' && (
3262
- <>
3263
- <Select
3264
- label='Leaflet Theme'
3265
- options={layerOptions}
3266
- section={'leaflet'}
3267
- fieldName='theme'
3268
- updateField={updateField}
3269
- />
3270
- </>
3271
- )}
3272
- </AccordionItemPanel>
3273
- </AccordionItem>
3274
- <AccordionItem>
3275
- <AccordionItemHeading>
3276
- <AccordionItemButton>Custom Map Layers</AccordionItemButton>
3277
- </AccordionItemHeading>
3278
- <AccordionItemPanel>
3279
- {config.map.layers.length === 0 && <p>There are currently no layers.</p>}
3280
-
3281
- {config.map.layers.map((layer, index) => {
3282
- return (
3283
- <>
3284
- <Accordion allowZeroExpanded>
3285
- <AccordionItem className='series-item map-layers-list'>
3286
- <AccordionItemHeading className='series-item__title map-layers-list--title'>
3287
- <AccordionItemButton>{`Layer ${index + 1}: ${layer.name}`}</AccordionItemButton>
3288
- </AccordionItemHeading>
3289
- <AccordionItemPanel>
3290
- <div className='map-layers-panel'>
3291
- <label htmlFor='layerName'>Layer Name:</label>
3292
- <input
3293
- type='text'
3294
- name='layerName'
3295
- value={layer.name}
3296
- onChange={e => handleMapLayer(e, index, 'name')}
3297
- />
3298
- <label htmlFor='layerFilename'>File:</label>
3299
- <input
3300
- type='text'
3301
- name='layerFilename'
3302
- value={layer.url}
3303
- onChange={e => handleMapLayer(e, index, 'url')}
3304
- />
3305
- <label htmlFor='layerNamespace'>TOPOJSON Namespace:</label>
3306
- <input
3307
- type='text'
3308
- name='layerNamespace'
3309
- value={layer.namespace}
3310
- onChange={e => handleMapLayer(e, index, 'namespace')}
1761
+ <TextField
1762
+ value={columns.primary.label}
1763
+ section='columns'
1764
+ subsection='primary'
1765
+ fieldName='label'
1766
+ label='Data Label'
1767
+ updateField={updateField}
1768
+ tooltip={
1769
+ <Tooltip style={{ textTransform: 'none' }}>
1770
+ <Tooltip.Target>
1771
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
1772
+ </Tooltip.Target>
1773
+ <Tooltip.Content>
1774
+ <p>Enter a data label for use in tooltips and the data table.</p>
1775
+ </Tooltip.Content>
1776
+ </Tooltip>
1777
+ }
1778
+ />
1779
+ <ul className='column-edit'>
1780
+ <li className='three-col'>
1781
+ <TextField
1782
+ value={columns.primary.prefix}
1783
+ section='columns'
1784
+ subsection='primary'
1785
+ fieldName='prefix'
1786
+ label='Prefix'
1787
+ updateField={updateField}
3311
1788
  />
3312
- <label htmlFor='layerFill'>Fill Color:</label>
3313
- <input
3314
- type='text'
3315
- name='layerFill'
3316
- value={layer.fill}
3317
- onChange={e => handleMapLayer(e, index, 'fill')}
1789
+ <TextField
1790
+ value={columns.primary.suffix}
1791
+ section='columns'
1792
+ subsection='primary'
1793
+ fieldName='suffix'
1794
+ label='Suffix'
1795
+ updateField={updateField}
3318
1796
  />
3319
- <label htmlFor='layerFill'>Fill Opacity (%):</label>
3320
- <input
1797
+ <TextField
3321
1798
  type='number'
1799
+ value={columns.primary.roundToPlace}
1800
+ section='columns'
1801
+ subsection='primary'
1802
+ fieldName='roundToPlace'
1803
+ label='Round'
1804
+ updateField={updateField}
3322
1805
  min={0}
3323
- max={100}
3324
- name='layerFill'
3325
- value={layer.fillOpacity ? layer.fillOpacity * 100 : ''}
3326
- onChange={e => handleMapLayer(e, index, 'fillOpacity')}
3327
1806
  />
3328
- <label htmlFor='layerStroke'>Stroke Color:</label>
3329
- <input
3330
- type='text'
3331
- name='layerStroke'
3332
- value={layer.stroke}
3333
- onChange={e => handleMapLayer(e, index, 'stroke')}
1807
+ </li>
1808
+ <CheckBox
1809
+ value={config.columns.primary.useCommas}
1810
+ section='columns'
1811
+ subsection='primary'
1812
+ fieldName='useCommas'
1813
+ label='Add Commas to Numbers'
1814
+ updateField={updateField}
1815
+ />
1816
+ <CheckBox
1817
+ value={config.columns.primary.dataTable || false}
1818
+ section='columns'
1819
+ subsection='primary'
1820
+ fieldName='dataTable'
1821
+ label='Show in Data Table'
1822
+ updateField={updateField}
1823
+ />
1824
+ <CheckBox
1825
+ value={config.columns.primary.tooltip || false}
1826
+ section='columns'
1827
+ subsection='primary'
1828
+ fieldName='tooltip'
1829
+ label='Show in Tooltips'
1830
+ updateField={updateField}
1831
+ />
1832
+ </ul>
1833
+ </fieldset>
1834
+ )}
1835
+
1836
+ {config.general.type === 'bubble' && config.legend.type === 'category' && (
1837
+ <fieldset className='primary-fieldset edit-block'>
1838
+ <label>
1839
+ <span className='edit-label column-heading'>
1840
+ Category Column
1841
+ <Tooltip style={{ textTransform: 'none' }}>
1842
+ <Tooltip.Target>
1843
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
1844
+ </Tooltip.Target>
1845
+ <Tooltip.Content>
1846
+ <p>Select the source column containing the categorical bubble values to be mapped.</p>
1847
+ </Tooltip.Content>
1848
+ </Tooltip>
1849
+ </span>
1850
+ <Select
1851
+ label=''
1852
+ value={
1853
+ config.columns.categorical ? config.columns.categorical.name : columnsOptions[0]?.key
1854
+ }
1855
+ options={columnsOptions.map(c => c.key)}
1856
+ onChange={event => {
1857
+ editColumn('categorical', 'name', event.target.value)
1858
+ }}
1859
+ />
1860
+ </label>
1861
+ </fieldset>
1862
+ )}
1863
+ {
1864
+ <>
1865
+ <Select
1866
+ label='Latitude Column'
1867
+ value={config.columns.latitude.name}
1868
+ options={columnsOptions.map(c => c.key)}
1869
+ onChange={e => {
1870
+ editColumn('latitude', 'name', e.target.value)
1871
+ }}
1872
+ />
1873
+ <Select
1874
+ label='Longitude Column'
1875
+ value={config.columns.longitude.name}
1876
+ options={columnsOptions.map(c => c.key)}
1877
+ onChange={e => {
1878
+ editColumn('longitude', 'name', e.target.value)
1879
+ }}
1880
+ />
1881
+ </>
1882
+ }
1883
+
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
+ }}
3334
1935
  />
3335
- <label htmlFor='layerStroke'>Stroke Width:</label>
3336
- <input
3337
- type='number'
3338
- min={0}
3339
- max={5}
3340
- name='layerStrokeWidth'
3341
- value={layer.strokeWidth}
3342
- onChange={e => handleMapLayer(e, index, 'strokeWidth')}
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
+ }}
3343
1952
  />
3344
- <label htmlFor='layerTooltip'>Tooltip:</label>
3345
- <textarea
3346
- name='layerTooltip'
3347
- value={layer.tooltip}
3348
- onChange={e => handleMapLayer(e, index, 'tooltip')}
3349
- ></textarea>
3350
- <button onClick={e => handleRemoveLayer(e, index)}>Remove Layer</button>
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>
3351
1967
  </div>
3352
- </AccordionItemPanel>
3353
- </AccordionItem>
3354
- </Accordion>
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
+ <label className='edit-block navigate column-heading'>
1984
+ <span className='edit-label column-heading'>
1985
+ Navigation
1986
+ <Tooltip style={{ textTransform: 'none' }}>
1987
+ <Tooltip.Target>
1988
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
1989
+ </Tooltip.Target>
1990
+ <Tooltip.Content>
1991
+ <p>
1992
+ To provide end users with navigation functionality, select the source column containing
1993
+ the navigation URLs.
1994
+ </p>
1995
+ </Tooltip.Content>
1996
+ </Tooltip>
1997
+ </span>
1998
+ <Select
1999
+ value={config.columns.navigate ? config.columns.navigate.name : ''}
2000
+ options={columnsOptions.map(c => c.key)}
2001
+ onChange={event => {
2002
+ editColumn('navigate', 'name', event.target.value)
2003
+ }}
2004
+ />
2005
+ </label>
2006
+ {'navigation' !== config.general.type && (
2007
+ <fieldset className='primary-fieldset edit-block'>
2008
+ <label>
2009
+ <span className='edit-label'>
2010
+ Additional Columns
2011
+ <Tooltip style={{ textTransform: 'none' }}>
2012
+ <Tooltip.Target>
2013
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
2014
+ </Tooltip.Target>
2015
+ <Tooltip.Content>
2016
+ <p>
2017
+ You can specify additional columns to display in tooltips and / or the supporting data
2018
+ table.
2019
+ </p>
2020
+ </Tooltip.Content>
2021
+ </Tooltip>
2022
+ </span>
2023
+ </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
+ }}
2124
+ >
2125
+ Add Column
2126
+ </button>
2127
+ </fieldset>
2128
+ )}
2129
+ {'category' === config.legend.type && (
2130
+ <fieldset className='primary-fieldset edit-block'>
2131
+ <label>
2132
+ <span className='edit-label'>
2133
+ Additional Category
2134
+ <Tooltip style={{ textTransform: 'none' }}>
2135
+ <Tooltip.Target>
2136
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
2137
+ </Tooltip.Target>
2138
+ <Tooltip.Content>
2139
+ <p>You can provide additional categories to ensure they appear in the legend</p>
2140
+ </Tooltip.Content>
2141
+ </Tooltip>
2142
+ </span>
2143
+ </label>
2144
+ {config.legend.additionalCategories &&
2145
+ config.legend.additionalCategories.map((val, i) => (
2146
+ <fieldset className='edit-block' key={val}>
2147
+ <button
2148
+ className='remove-column'
2149
+ onClick={event => {
2150
+ event.preventDefault()
2151
+ const updatedAdditionaCategories = [...config.legend.additionalCategories]
2152
+ updatedAdditionaCategories.splice(i, 1)
2153
+ updateField('legend', null, 'additionalCategories', updatedAdditionaCategories)
2154
+ }}
2155
+ >
2156
+ Remove
2157
+ </button>
2158
+ <label>
2159
+ <span className='edit-label column-heading'>Category</span>
2160
+ <TextField
2161
+ value={val}
2162
+ section='legend'
2163
+ subsection={null}
2164
+ fieldName='additionalCategories'
2165
+ updateField={(section, subsection, fieldName, value) => {
2166
+ const updatedAdditionaCategories = [...config.legend.additionalCategories]
2167
+ updatedAdditionaCategories[i] = value
2168
+ updateField(section, subsection, fieldName, updatedAdditionaCategories)
2169
+ }}
2170
+ />
2171
+ </label>
2172
+ </fieldset>
2173
+ ))}
2174
+ <button
2175
+ className={'btn btn-primary full-width'}
2176
+ onClick={event => {
2177
+ event.preventDefault()
2178
+ const updatedAdditionaCategories = [...(config.legend.additionalCategories || [])]
2179
+ updatedAdditionaCategories.push('')
2180
+ updateField('legend', null, 'additionalCategories', updatedAdditionaCategories)
2181
+ }}
2182
+ >
2183
+ Add Category
2184
+ </button>
2185
+ </fieldset>
2186
+ )}
2187
+ </AccordionItemPanel>
2188
+ </AccordionItem>{' '}
2189
+ {/* Columns */}
2190
+ {'navigation' !== config.general.type && (
2191
+ <AccordionItem>
2192
+ {' '}
2193
+ {/* Legend */}
2194
+ <AccordionItemHeading>
2195
+ <AccordionItemButton>Legend</AccordionItemButton>
2196
+ </AccordionItemHeading>
2197
+ <AccordionItemPanel>
2198
+ {(config.legend.type === 'equalnumber' || config.legend.type === 'equalinterval') && (
2199
+ <Select
2200
+ label='Legend Type'
2201
+ value={legend.type}
2202
+ options={[
2203
+ { value: 'equalnumber', label: 'Equal Number (Quantiles)' },
2204
+ { value: 'equalinterval', label: 'Equal Interval' }
2205
+ ]}
2206
+ onChange={event => {
2207
+ let testForType = Number(typeof config.data[0][config.columns.primary.name])
2208
+ let hasValue = config.data[0][config.columns.primary.name]
2209
+ let messages = []
2210
+
2211
+ if (!hasValue) {
2212
+ messages.push(
2213
+ `There appears to be values missing for data in the primary column ${config.columns.primary.name}`
2214
+ )
2215
+ }
2216
+
2217
+ if (testForType === 'string' && isNaN(testForType) && value !== 'category') {
2218
+ messages.push(
2219
+ 'Error with legend. Primary columns that are text must use a categorical legend type. Try changing the legend type to DEV-12345categorical.'
2220
+ )
2221
+ } else {
2222
+ messages = []
2223
+ }
2224
+
2225
+ const _newConfig = cloneConfig(config)
2226
+ _newConfig.legend.type = event.target.value
2227
+ _newConfig.runtime.editorErrorMessage = messages
2228
+ setConfig(_newConfig)
2229
+ }}
2230
+ />
2231
+ )}
2232
+ {'navigation' !== config.general.type && (
2233
+ <CheckBox
2234
+ value={config.general.showSidebar || false}
2235
+ section='general'
2236
+ subsection={null}
2237
+ fieldName='showSidebar'
2238
+ label='Show Legend'
2239
+ updateField={updateField}
2240
+ />
2241
+ )}
2242
+ {'navigation' !== config.general.type && (
2243
+ <>
2244
+ <Select
2245
+ label='Legend Position'
2246
+ value={legend.position || ''}
2247
+ options={[
2248
+ { value: 'side', label: 'Side' },
2249
+ { value: 'bottom', label: 'Bottom' },
2250
+ { value: 'top', label: 'Top' }
2251
+ ]}
2252
+ onChange={event => {
2253
+ handleEditorChanges('sidebarPosition', event.target.value)
2254
+ }}
2255
+ />
2256
+ {(config.legend.position === 'side' || !config.legend.position) &&
2257
+ config.legend.style === 'gradient' && (
2258
+ <span style={{ color: 'red', fontSize: '14px' }}>
2259
+ Position must be set to top or bottom to use gradient style.
2260
+ </span>
2261
+ )}
2262
+ </>
2263
+ )}
2264
+ {'navigation' !== config.general.type && (
2265
+ <Select
2266
+ label={
2267
+ <>
2268
+ Legend Style
2269
+ <Tooltip style={{ textTransform: 'none' }}>
2270
+ <Tooltip.Target>
2271
+ <Icon
2272
+ display='question'
2273
+ style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }}
2274
+ />
2275
+ </Tooltip.Target>
2276
+ <Tooltip.Content>
2277
+ <p>
2278
+ If using gradient style, limit the legend to five items for better mobile
2279
+ visibility, and position the legend at the top or bottom.
2280
+ </p>
2281
+ </Tooltip.Content>
2282
+ </Tooltip>
2283
+ </>
2284
+ }
2285
+ value={legend.style || ''}
2286
+ options={[
2287
+ { value: 'circles', label: 'circles' },
2288
+ { value: 'boxes', label: 'boxes' },
2289
+ ...(legend.position !== 'side' ? [{ value: 'gradient', label: 'gradient' }] : [])
2290
+ ]}
2291
+ onChange={event => {
2292
+ handleEditorChanges('legendStyle', event.target.value)
2293
+ }}
2294
+ />
2295
+ )}
2296
+ {'navigation' !== config.general.type && config.legend.style === 'gradient' && (
2297
+ <Select
2298
+ label='Gradient Style'
2299
+ value={legend.subStyle || ''}
2300
+ options={['linear blocks', 'smooth']}
2301
+ onChange={event => {
2302
+ handleEditorChanges('legendSubStyle', event.target.value)
2303
+ }}
2304
+ />
2305
+ )}
2306
+ {allowLegendSeparators && (
2307
+ <TextField
2308
+ value={legend.separators}
2309
+ updateField={updateField}
2310
+ section='legend'
2311
+ fieldName='separators'
2312
+ label='Legend Separators'
2313
+ placeholder='ex: 1,4'
2314
+ tooltip={
2315
+ <Tooltip style={{ textTransform: 'none' }}>
2316
+ <Tooltip.Target>
2317
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
2318
+ </Tooltip.Target>
2319
+ <Tooltip.Content>
2320
+ <p>
2321
+ Separators between legend items represented by the legend item numbers separated by
2322
+ commas.
2323
+ </p>
2324
+ </Tooltip.Content>
2325
+ </Tooltip>
2326
+ }
2327
+ />
2328
+ )}
2329
+ {'navigation' !== config.general.type && config.legend.style === 'gradient' && (
2330
+ <label>
2331
+ <span className='edit-label'>Tick Rotation (Degrees)</span>
2332
+ <input
2333
+ type='number'
2334
+ className='number-narrow'
2335
+ value={legend.tickRotation || ''}
2336
+ onChange={event => {
2337
+ handleEditorChanges('legendTickRotation', event.target.value)
2338
+ }}
2339
+ ></input>
2340
+ </label>
2341
+ )}
2342
+ {
2343
+ // TODO: DEV-7271 Follow-up to implement option to isolate on legend click. For now, always highlight.
2344
+ /*
2345
+ <Select
2346
+ value={config.legend.behavior}
2347
+ section='legend'
2348
+ fieldName='behavior'
2349
+ label='Legend Behavior (When clicked)'
2350
+ updateField={updateField}
2351
+ options={['highlight', 'isolate']}
2352
+ />
2353
+ */
2354
+ }
2355
+ {
2356
+ <CheckBox
2357
+ value={legend.hideBorder}
2358
+ section='legend'
2359
+ subsection={null}
2360
+ fieldName='hideBorder'
2361
+ label='Hide Legend Box'
2362
+ updateField={updateField}
2363
+ onChange={event => {
2364
+ handleEditorChanges('legendBorder', event.target.checked)
2365
+ }}
2366
+ tooltip={
2367
+ <Tooltip style={{ textTransform: 'none' }}>
2368
+ <Tooltip.Target>
2369
+ <Icon
2370
+ display='question'
2371
+ style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }}
2372
+ />
2373
+ </Tooltip.Target>
2374
+ <Tooltip.Content>
2375
+ <p> Default option for top and bottom legends is 'No Box.'</p>
2376
+ </Tooltip.Content>
2377
+ </Tooltip>
2378
+ }
2379
+ />
2380
+ }
2381
+ {'side' === legend.position && (
2382
+ <CheckBox
2383
+ value={legend.singleColumn}
2384
+ section='legend'
2385
+ subsection={null}
2386
+ fieldName='singleColumn'
2387
+ label='Single Column Legend'
2388
+ updateField={updateField}
2389
+ onChange={event => {
2390
+ const _newConfig = cloneConfig(config)
2391
+ _newConfig.legend.singleColumn = event.target.checked
2392
+ _newConfig.legend.singleRow = false
2393
+ _newConfig.legend.verticalSorted = false
2394
+
2395
+ setConfig(_newConfig)
2396
+ }}
2397
+ />
2398
+ )}
2399
+ {'side' !== legend.position && legend.style !== 'gradient' && (
2400
+ <CheckBox
2401
+ value={legend.singleRow}
2402
+ section='legend'
2403
+ subsection={null}
2404
+ fieldName='singleRow'
2405
+ label='Single Row Legend'
2406
+ updateField={updateField}
2407
+ onChange={event => {
2408
+ const _newConfig = cloneConfig(config)
2409
+ _newConfig.legend.singleRow = event.target.checked
2410
+ _newConfig.legend.singleColumn = false
2411
+ _newConfig.legend.verticalSorted = false
2412
+
2413
+ setConfig(_newConfig)
2414
+ }}
2415
+ />
2416
+ )}
2417
+
2418
+ {'navigation' !== config.general.type && config.legend.type === 'category' && (
2419
+ <Select
2420
+ label='Legend Group By :'
2421
+ value={legend.groupBy || ''}
2422
+ options={columnsOptions.map(c => c.key)}
2423
+ onChange={event => {
2424
+ const _newConfig = cloneConfig(config)
2425
+ _newConfig.legend.groupBy = event.target.value
2426
+ setConfig(_newConfig)
2427
+ }}
2428
+ />
2429
+ )}
2430
+ {config.legend.style !== 'gradient' && (
2431
+ <CheckBox
2432
+ value={legend.verticalSorted}
2433
+ section='legend'
2434
+ subsection={null}
2435
+ fieldName='verticalSorted'
2436
+ label='Vertical sorted legend'
2437
+ updateField={updateField}
2438
+ />
2439
+ )}
2440
+
2441
+ {/* always show */}
2442
+ {
2443
+ <CheckBox
2444
+ value={legend.showSpecialClassesLast}
2445
+ section='legend'
2446
+ subsection={null}
2447
+ fieldName='showSpecialClassesLast'
2448
+ label='Show Special Classes Last'
2449
+ updateField={updateField}
2450
+ onChange={event => {
2451
+ handleEditorChanges('legendShowSpecialClassesLast', event.target.checked)
2452
+ }}
2453
+ />
2454
+ }
2455
+ {'category' !== legend.type && (
2456
+ <CheckBox
2457
+ value={legend.separateZero || false}
2458
+ section='legend'
2459
+ subsection={null}
2460
+ fieldName='separateZero'
2461
+ label='Separate Zero'
2462
+ updateField={updateField}
2463
+ tooltip={
2464
+ <Tooltip style={{ textTransform: 'none' }}>
2465
+ <Tooltip.Target>
2466
+ <Icon
2467
+ display='question'
2468
+ style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }}
2469
+ />
2470
+ </Tooltip.Target>
2471
+ <Tooltip.Content>
2472
+ <p>For numeric data, you can separate the zero value as its own data class.</p>
2473
+ </Tooltip.Content>
2474
+ </Tooltip>
2475
+ }
2476
+ />
2477
+ )}
2478
+
2479
+ {/* Temp Checkbox */}
2480
+ {config.legend.type === 'equalnumber' && (
2481
+ <CheckBox
2482
+ value={config.general.equalNumberOptIn}
2483
+ section='general'
2484
+ subsection={null}
2485
+ fieldName='equalNumberOptIn'
2486
+ label='Use new quantile legend'
2487
+ updateField={updateField}
2488
+ tooltip={
2489
+ <Tooltip style={{ textTransform: 'none' }}>
2490
+ <Tooltip.Target>
2491
+ <Icon
2492
+ display='question'
2493
+ style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }}
2494
+ />
2495
+ </Tooltip.Target>
2496
+ <Tooltip.Content>
2497
+ <p>
2498
+ This prevents numbers from being used in more than one category (ie. 0-1, 1-2, 2-3){' '}
2499
+ </p>
2500
+ </Tooltip.Content>
2501
+ </Tooltip>
2502
+ }
2503
+ />
2504
+ )}
2505
+
2506
+ {'category' !== legend.type && (
2507
+ <Select
2508
+ label={
2509
+ <>
2510
+ Number of Items
2511
+ <Tooltip style={{ textTransform: 'none' }}>
2512
+ <Tooltip.Target>
2513
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
2514
+ </Tooltip.Target>
2515
+ <Tooltip.Content>
2516
+ <p>
2517
+ For numeric maps, select the number of data classes. Do not include designated
2518
+ special classes.
2519
+ </p>
2520
+ </Tooltip.Content>
2521
+ </Tooltip>
2522
+ </>
2523
+ }
2524
+ value={legend.numberOfItems}
2525
+ options={[...Array(numberOfItemsLimit).keys()].map(num => ({
2526
+ value: num + 1,
2527
+ label: num + 1
2528
+ }))}
2529
+ onChange={event => {
2530
+ handleEditorChanges('legendNumber', event.target.value)
2531
+ }}
2532
+ />
2533
+ )}
2534
+ {'category' === legend.type && (
2535
+ <React.Fragment>
2536
+ <label>
2537
+ <span className='edit-label'>
2538
+ Category Order
2539
+ <Tooltip style={{ textTransform: 'none' }}>
2540
+ <Tooltip.Target>
2541
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
2542
+ </Tooltip.Target>
2543
+ <Tooltip.Content>
2544
+ <p>Drag map categories into preferred legend order. </p>
2545
+ </Tooltip.Content>
2546
+ </Tooltip>
2547
+ </span>
2548
+ </label>
2549
+ {/* TODO: Swap out this drag and drop library back to something simpler. I had to remove the old one because it hadn't been updated and wouldn't work with Webpack 5. This is overkill for our needs. */}
2550
+ <DragDropContext
2551
+ onDragEnd={({ source, destination }) => categoryMove(source.index, destination.index)}
2552
+ >
2553
+ <Droppable droppableId='category_order'>
2554
+ {provided => (
2555
+ <ul {...provided.droppableProps} className='sort-list' ref={provided.innerRef}>
2556
+ <CategoryList />
2557
+ {provided.placeholder}
2558
+ </ul>
2559
+ )}
2560
+ </Droppable>
2561
+ </DragDropContext>
2562
+ {runtimeLegend && runtimeLegend.length >= 10 && (
2563
+ <section className='error-box my-2'>
2564
+ <div>
2565
+ <strong className='pt-1'>Warning</strong>
2566
+ <p>
2567
+ The maximum number of categorical legend items is 10. If your data has more than 10
2568
+ categories your map will not display properly.
2569
+ </p>
2570
+ </div>
2571
+ </section>
2572
+ )}
2573
+ </React.Fragment>
2574
+ )}
2575
+ <TextField
2576
+ value={legend.title}
2577
+ updateField={updateField}
2578
+ section='legend'
2579
+ fieldName='title'
2580
+ label='Legend Title'
2581
+ placeholder='Legend Title'
2582
+ />
2583
+ {false === legend.dynamicDescription && (
2584
+ <TextField
2585
+ type='textarea'
2586
+ value={legend.description}
2587
+ updateField={updateField}
2588
+ section='legend'
2589
+ fieldName='description'
2590
+ label='Legend Description'
2591
+ />
2592
+ )}
2593
+ {true === legend.dynamicDescription && (
2594
+ <React.Fragment>
2595
+ <label>
2596
+ <span>Legend Description</span>
2597
+ <span className='subtext'>
2598
+ For {displayFilterLegendValue(activeFilterValueForDescription)}
2599
+ </span>
2600
+ <DynamicDesc value={legend.descriptions[String(activeFilterValueForDescription)]} />
2601
+ </label>
2602
+ <label>
2603
+ <Select
2604
+ label='Filter Value'
2605
+ value={String(activeFilterValueForDescription)}
2606
+ options={filterValueOptionList.map(arr => ({
2607
+ value: arr,
2608
+ label: displayFilterLegendValue(arr)
2609
+ }))}
2610
+ onChange={event => {
2611
+ handleEditorChanges('changeActiveFilterValue', event.target.value)
2612
+ }}
2613
+ />
2614
+ </label>
2615
+ </React.Fragment>
2616
+ )}
2617
+ {config.filters.length > 0 && (
2618
+ <label className='checkbox column-heading'>
2619
+ <CheckBox
2620
+ value={legend.dynamicDescription}
2621
+ section='legend'
2622
+ subsection={null}
2623
+ fieldName='dynamicDescription'
2624
+ label='Dynamic Legend Description'
2625
+ updateField={updateField}
2626
+ onChange={() => {
2627
+ handleEditorChanges('dynamicDescription', filterValueOptionList[0])
2628
+ }}
2629
+ />
2630
+ <Tooltip style={{ textTransform: 'none' }}>
2631
+ <Tooltip.Target>
2632
+ <Icon
2633
+ display='question'
2634
+ style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }}
2635
+ />
2636
+ </Tooltip.Target>
2637
+ <Tooltip.Content>
2638
+ <p>
2639
+ Check this option if the map has multiple filter controls and you want to specify a
2640
+ description for each filter selection.
2641
+ </p>
2642
+ </Tooltip.Content>
2643
+ </Tooltip>
2644
+ </label>
2645
+ )}
2646
+ <span className='d-flex mt-2'>
2647
+ <CheckBox
2648
+ value={legend.unified}
2649
+ section='legend'
2650
+ subsection={null}
2651
+ fieldName='unified'
2652
+ label='Unified Legend'
2653
+ updateField={updateField}
2654
+ onChange={event => handleEditorChanges('unifiedLegend', event.target.checked)}
2655
+ />
2656
+ <Tooltip style={{ textTransform: 'none' }}>
2657
+ <Tooltip.Target>
2658
+ <Icon
2659
+ display='question'
2660
+ style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }}
2661
+ />
2662
+ </Tooltip.Target>
2663
+ <Tooltip.Content>
2664
+ <p>
2665
+ Check this option if you want the high and low values in the legend to be based on{' '}
2666
+ <em>all</em> mapped values (useful for maps with filters or small multiples).
2667
+ </p>
2668
+ </Tooltip.Content>
2669
+ </Tooltip>
2670
+ </span>
2671
+ </AccordionItemPanel>
2672
+ </AccordionItem>
2673
+ )}
2674
+ {'navigation' !== config.general.type && (
2675
+ <>
2676
+ <AccordionItem>
2677
+ {/* Filters */}
2678
+ <AccordionItemHeading>
2679
+ <AccordionItemButton>Filters</AccordionItemButton>
2680
+ </AccordionItemHeading>
2681
+ <AccordionItemPanel>
2682
+ <VizFilterEditor
2683
+ config={config}
2684
+ updateField={updateField}
2685
+ rawData={config.data}
2686
+ hasFootnotes={isDashboard}
2687
+ />
2688
+ </AccordionItemPanel>
2689
+ </AccordionItem>
2690
+ <AccordionItem>
2691
+ <AccordionItemHeading>
2692
+ <AccordionItemButton>Footnotes</AccordionItemButton>
2693
+ </AccordionItemHeading>
2694
+ <AccordionItemPanel>
2695
+ <FootnotesEditor config={config} updateField={updateField} datasets={datasets} />
2696
+ </AccordionItemPanel>
2697
+ </AccordionItem>
3355
2698
  </>
3356
- )
3357
- })}
3358
- <button className={'btn btn-primary full-width'} onClick={handleAddLayer}>
3359
- Add Map Layer
3360
- </button>
3361
- <p className='layer-purpose-details'>
3362
- Context should be added to your visualization or associated page to describe the significance of layers
3363
- that are added to maps.
3364
- </p>
3365
- </AccordionItemPanel>
3366
- </AccordionItem>
3367
- {config.general.geoType === 'us' && <Panels.PatternSettings name='Pattern Settings' />}
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
- />
3377
- </Accordion>
3378
- <AdvancedEditor loadConfig={setConfig} config={config} convertStateToConfig={convertStateToConfig} />
3379
- </Layout.Sidebar>
2699
+ )}
2700
+ {'navigation' !== config.general.type && (
2701
+ <AccordionItem>
2702
+ {' '}
2703
+ {/* Data Table */}
2704
+ <AccordionItemHeading>
2705
+ <AccordionItemButton>Data Table</AccordionItemButton>
2706
+ </AccordionItemHeading>
2707
+ <AccordionItemPanel>
2708
+ <TextField
2709
+ value={table.label}
2710
+ updateField={updateField}
2711
+ section='table'
2712
+ fieldName='label'
2713
+ id='dataTableTitle'
2714
+ label='Data Table Title'
2715
+ placeholder='Data Table'
2716
+ tooltip={
2717
+ <Tooltip style={{ textTransform: 'none' }}>
2718
+ <Tooltip.Target>
2719
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
2720
+ </Tooltip.Target>
2721
+ <Tooltip.Content>
2722
+ <p>Label is required for Data Table for 508 Compliance</p>
2723
+ </Tooltip.Content>
2724
+ </Tooltip>
2725
+ }
2726
+ />
2727
+ <CheckBox
2728
+ value={config.table.wrapColumns}
2729
+ section='table'
2730
+ subsection={null}
2731
+ fieldName='wrapColumns'
2732
+ label='WRAP DATA TABLE COLUMNS'
2733
+ updateField={updateField}
2734
+ className='column-heading'
2735
+ />
2736
+ <CheckBox
2737
+ value={config.table.forceDisplay !== undefined ? config.table.forceDisplay : !isDashboard}
2738
+ section='table'
2739
+ subsection={null}
2740
+ fieldName='forceDisplay'
2741
+ label='Show Data Table'
2742
+ updateField={updateField}
2743
+ onChange={event => {
2744
+ handleEditorChanges('showDataTable', event.target.checked)
2745
+ }}
2746
+ tooltip={
2747
+ <Tooltip style={{ textTransform: 'none' }}>
2748
+ <Tooltip.Target>
2749
+ <Icon
2750
+ display='question'
2751
+ style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }}
2752
+ />
2753
+ </Tooltip.Target>
2754
+ <Tooltip.Content>
2755
+ <p>
2756
+ Data tables are required for 508 compliance. When choosing to hide this data table,
2757
+ replace with your own version.
2758
+ </p>
2759
+ </Tooltip.Content>
2760
+ </Tooltip>
2761
+ }
2762
+ />
2763
+
2764
+ <CheckBox
2765
+ value={config.table.showNonGeoData}
2766
+ section='table'
2767
+ subsection={null}
2768
+ fieldName='showNonGeoData'
2769
+ label='Show Non Geographic Data'
2770
+ updateField={updateField}
2771
+ tooltip={
2772
+ <Tooltip style={{ textTransform: 'none' }}>
2773
+ <Tooltip.Target>
2774
+ <Icon
2775
+ display='question'
2776
+ style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }}
2777
+ />
2778
+ </Tooltip.Target>
2779
+ <Tooltip.Content>
2780
+ <p>Show any data not associated with a geographic location</p>
2781
+ </Tooltip.Content>
2782
+ </Tooltip>
2783
+ }
2784
+ />
2785
+
2786
+ <TextField
2787
+ value={table.indexLabel || ''}
2788
+ updateField={updateField}
2789
+ section='table'
2790
+ fieldName='indexLabel'
2791
+ label='Index Column Header'
2792
+ placeholder='Location'
2793
+ tooltip={
2794
+ <Tooltip style={{ textTransform: 'none' }}>
2795
+ <Tooltip.Target>
2796
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
2797
+ </Tooltip.Target>
2798
+ <Tooltip.Content>
2799
+ <p>
2800
+ To comply with 508 standards, if the first column in the data table has no header, enter
2801
+ a brief one here.
2802
+ </p>
2803
+ </Tooltip.Content>
2804
+ </Tooltip>
2805
+ }
2806
+ />
2807
+ <TextField
2808
+ value={config.table.caption}
2809
+ updateField={updateField}
2810
+ section='table'
2811
+ fieldName='caption'
2812
+ label='Screen Reader Description'
2813
+ placeholder='Data Table'
2814
+ tooltip={
2815
+ <Tooltip style={{ textTransform: 'none' }}>
2816
+ <Tooltip.Target>
2817
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
2818
+ </Tooltip.Target>
2819
+ <Tooltip.Content>
2820
+ <p>Enter a description of the data table to be read by screen readers.</p>
2821
+ </Tooltip.Content>
2822
+ </Tooltip>
2823
+ }
2824
+ type='textarea'
2825
+ />
2826
+ <CheckBox
2827
+ value={config.table.limitHeight}
2828
+ section='table'
2829
+ subsection={null}
2830
+ fieldName='limitHeight'
2831
+ label='Limit Table Height'
2832
+ updateField={updateField}
2833
+ onChange={event => {
2834
+ handleEditorChanges('limitDataTableHeight', event.target.checked)
2835
+ }}
2836
+ />
2837
+ {config.table.limitHeight && (
2838
+ <TextField
2839
+ value={table.height}
2840
+ updateField={updateField}
2841
+ section='table'
2842
+ fieldName='height'
2843
+ label='Data Table Height'
2844
+ placeholder='Height(px)'
2845
+ type='number'
2846
+ min='0'
2847
+ max='500'
2848
+ />
2849
+ )}
2850
+
2851
+ <TextField
2852
+ value={table.cellMinWidth}
2853
+ updateField={updateField}
2854
+ section='table'
2855
+ fieldName='cellMinWidth'
2856
+ label='Table Cell Min Width'
2857
+ type='number'
2858
+ min='0'
2859
+ max='500'
2860
+ />
2861
+
2862
+ <CheckBox
2863
+ value={config.table.expanded || false}
2864
+ section='table'
2865
+ subsection={null}
2866
+ fieldName='expanded'
2867
+ label='Map loads with data table expanded'
2868
+ updateField={updateField}
2869
+ onChange={event => {
2870
+ handleEditorChanges('expandDataTable', event.target.checked)
2871
+ }}
2872
+ />
2873
+ <CheckBox
2874
+ value={config.table.download}
2875
+ fieldName='download'
2876
+ label='Show Download CSV Link'
2877
+ section='table'
2878
+ updateField={updateField}
2879
+ />
2880
+ {config.table.download && (
2881
+ <>
2882
+ <CheckBox
2883
+ value={config.table.showDownloadLinkBelow}
2884
+ section='table'
2885
+ subsection={null}
2886
+ fieldName='showDownloadLinkBelow'
2887
+ label='Show Link Below Table'
2888
+ updateField={updateField}
2889
+ className='ms-4'
2890
+ onChange={event => {
2891
+ handleEditorChanges('toggleDownloadLinkBelow', event.target.checked)
2892
+ }}
2893
+ />
2894
+ <CheckBox
2895
+ value={config.table.downloadVisibleDataOnly}
2896
+ fieldName='downloadVisibleDataOnly'
2897
+ className='ms-4'
2898
+ label='Download only visible data'
2899
+ section='table'
2900
+ updateField={updateField}
2901
+ />
2902
+ </>
2903
+ )}
2904
+ {isDashboard && (
2905
+ <CheckBox
2906
+ value={config.table.showDataTableLink}
2907
+ section='table'
2908
+ subsection={null}
2909
+ fieldName='showDataTableLink'
2910
+ label='Show Data Table Name & Link'
2911
+ updateField={updateField}
2912
+ />
2913
+ )}
2914
+ {isLoadedFromUrl && (
2915
+ <CheckBox
2916
+ value={config.table.showDownloadUrl}
2917
+ section='table'
2918
+ subsection={null}
2919
+ fieldName='showDownloadUrl'
2920
+ label='Show URL to Automatically Updated Data'
2921
+ updateField={updateField}
2922
+ />
2923
+ )}
2924
+ <CheckBox
2925
+ value={config.table.showFullGeoNameInCSV}
2926
+ section='table'
2927
+ subsection={null}
2928
+ fieldName='showFullGeoNameInCSV'
2929
+ label='Include Full Geo Name in CSV Download'
2930
+ updateField={updateField}
2931
+ onChange={event => {
2932
+ handleEditorChanges('toggleShowFullGeoNameInCSV', event.target.checked)
2933
+ }}
2934
+ />
2935
+ <CheckBox
2936
+ value={config.general.showDownloadImgButton}
2937
+ section='general'
2938
+ subsection={null}
2939
+ fieldName='showDownloadImgButton'
2940
+ label='Enable Image Download'
2941
+ updateField={updateField}
2942
+ onChange={event => {
2943
+ handleEditorChanges('toggleDownloadImgButton', event.target.checked)
2944
+ }}
2945
+ />
2946
+ {config.general.showDownloadImgButton && (
2947
+ <CheckBox
2948
+ value={config.general.includeContextInDownload}
2949
+ section='general'
2950
+ subsection={null}
2951
+ className='ms-4'
2952
+ fieldName='includeContextInDownload'
2953
+ label='Include Heading & Context'
2954
+ updateField={updateField}
2955
+ tooltip={
2956
+ <Tooltip style={{ textTransform: 'none' }}>
2957
+ <Tooltip.Target>
2958
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
2959
+ </Tooltip.Target>
2960
+ <Tooltip.Content>
2961
+ <p>
2962
+ When enabled, the image download will include the section heading (H2 or H3) and any
2963
+ explanatory paragraphs that appear before the visualization
2964
+ </p>
2965
+ </Tooltip.Content>
2966
+ </Tooltip>
2967
+ }
2968
+ />
2969
+ )}
2970
+
2971
+ {/* <label className='checkbox'>
2972
+ <input
2973
+ type='checkbox'
2974
+ checked={state.general.showDownloadPdfButton}
2975
+ onChange={event => {
2976
+ handleEditorChanges('toggleDownloadPdfButton', event.target.checked)
2977
+ }}
2978
+ />
2979
+ <span className='edit-label'>Enable Pdf Download</span>
2980
+ </label> */}
2981
+ </AccordionItemPanel>
2982
+ </AccordionItem>
2983
+ )}
2984
+ <AccordionItem>
2985
+ {' '}
2986
+ {/* Tooltips */}
2987
+ <AccordionItemHeading>
2988
+ <AccordionItemButton>Interactivity</AccordionItemButton>
2989
+ </AccordionItemHeading>
2990
+ <AccordionItemPanel>
2991
+ <Select
2992
+ label={
2993
+ <>
2994
+ Detail displays on{' '}
2995
+ <Tooltip style={{ textTransform: 'none' }}>
2996
+ <Tooltip.Target>
2997
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
2998
+ </Tooltip.Target>
2999
+ <Tooltip.Content>
3000
+ <p>
3001
+ At mobile sizes, information always appears in a popover modal when a user taps on an
3002
+ item.
3003
+ </p>
3004
+ </Tooltip.Content>
3005
+ </Tooltip>
3006
+ </>
3007
+ }
3008
+ value={config.tooltips.appearanceType}
3009
+ options={[
3010
+ { value: 'hover', label: 'Hover - Tooltip' },
3011
+ { value: 'click', label: 'Click - Popover Modal' }
3012
+ ]}
3013
+ onChange={event => {
3014
+ handleEditorChanges('appearanceType', event.target.value)
3015
+ }}
3016
+ />
3017
+ {'click' === config.tooltips.appearanceType && (
3018
+ <TextField
3019
+ value={tooltips.linkLabel}
3020
+ section='tooltips'
3021
+ fieldName='linkLabel'
3022
+ label='Tooltips Link Label'
3023
+ updateField={updateField}
3024
+ />
3025
+ )}
3026
+ </AccordionItemPanel>
3027
+ </AccordionItem>
3028
+ <AccordionItem>
3029
+ {' '}
3030
+ {/* Visual */}
3031
+ <AccordionItemHeading>
3032
+ <AccordionItemButton>Visual</AccordionItemButton>
3033
+ </AccordionItemHeading>
3034
+ <AccordionItemPanel>
3035
+ <HeaderThemeSelector
3036
+ selectedTheme={config.general.headerColor}
3037
+ onThemeSelect={palette => handleEditorChanges('headerColor', palette)}
3038
+ label='Header Theme'
3039
+ />
3040
+ <CheckBox
3041
+ value={config.general.showTitle || false}
3042
+ section='general'
3043
+ subsection={null}
3044
+ fieldName='showTitle'
3045
+ label='Show Title'
3046
+ updateField={updateField}
3047
+ onChange={event => {
3048
+ handleEditorChanges('showTitle', event.target.checked)
3049
+ }}
3050
+ />
3051
+
3052
+ {'navigation' === config.general.type && (
3053
+ <CheckBox
3054
+ value={config.general.fullBorder || false}
3055
+ section='general'
3056
+ subsection={null}
3057
+ fieldName='fullBorder'
3058
+ label='Add border around map'
3059
+ updateField={updateField}
3060
+ />
3061
+ )}
3062
+ <Select
3063
+ label='Geo Border Color'
3064
+ value={config.general.geoBorderColor || ''}
3065
+ options={[
3066
+ { value: 'darkGray', label: 'Dark Gray (Default)' },
3067
+ { value: 'sameAsBackground', label: 'White' }
3068
+ ]}
3069
+ onChange={event => {
3070
+ handleEditorChanges('geoBorderColor', event.target.value)
3071
+ }}
3072
+ />
3073
+ <label>
3074
+ <span className='edit-label'>Map Color Palette</span>
3075
+ </label>
3076
+ <div className='mb-2'>
3077
+ <small className='text-muted'>
3078
+ Review color contrasts{' '}
3079
+ <a
3080
+ href='https://webaim.org/resources/contrastchecker/'
3081
+ target='_blank'
3082
+ rel='noopener noreferrer'
3083
+ >
3084
+ here
3085
+ </a>
3086
+ </small>
3087
+ </div>
3088
+ <DeveloperPaletteRollback config={config} updateConfig={setConfig} />
3089
+ <InputToggle
3090
+ type='3d'
3091
+ section='general'
3092
+ subsection='palette'
3093
+ fieldName='isReversed'
3094
+ size='small'
3095
+ label='Use selected palette in reverse order'
3096
+ onClick={() => {
3097
+ const _state = cloneConfig(config)
3098
+ const currentPaletteName = config.general.palette?.name || ''
3099
+ _state.general.palette.isReversed = !_state.general.palette.isReversed
3100
+ let paletteName = ''
3101
+ if (_state.general.palette.isReversed && !currentPaletteName.endsWith('reverse')) {
3102
+ paletteName = currentPaletteName + 'reverse'
3103
+ }
3104
+ if (!_state.general.palette.isReversed && currentPaletteName.endsWith('reverse')) {
3105
+ paletteName = currentPaletteName.slice(0, -7)
3106
+ }
3107
+ if (paletteName) {
3108
+ _state.general.palette.name = paletteName
3109
+ }
3110
+ setConfig(_state)
3111
+ }}
3112
+ value={config.general.palette.isReversed}
3113
+ />
3114
+ <span>Sequential</span>
3115
+ <PaletteSelector
3116
+ palettes={sequential}
3117
+ colorPalettes={colorPalettes}
3118
+ config={config}
3119
+ onPaletteSelect={handlePaletteSelection}
3120
+ selectedPalette={getCurrentPaletteName(config)}
3121
+ colorIndices={[2, 3, 5]}
3122
+ className='color-palette'
3123
+ element='button'
3124
+ getItemClassName={getPaletteClassName}
3125
+ />
3126
+ <span>Non-Sequential</span>
3127
+ <PaletteSelector
3128
+ palettes={nonSequential}
3129
+ colorPalettes={colorPalettes}
3130
+ config={config}
3131
+ onPaletteSelect={handlePaletteSelection}
3132
+ selectedPalette={getCurrentPaletteName(config)}
3133
+ colorIndices={[2, 3, 5]}
3134
+ className='color-palette'
3135
+ element='button'
3136
+ getItemClassName={getPaletteClassName}
3137
+ minColorsForFilter={(_, paletteAccessor, config) => {
3138
+ if (paletteAccessor.length <= 8 && config.general.geoType === 'us-region') {
3139
+ return false
3140
+ }
3141
+ return true
3142
+ }}
3143
+ />
3144
+ <span>Colorblind Safe</span>
3145
+ <PaletteSelector
3146
+ palettes={accessibleColors}
3147
+ colorPalettes={colorPalettes}
3148
+ config={config}
3149
+ onPaletteSelect={handlePaletteSelection}
3150
+ selectedPalette={getCurrentPaletteName(config)}
3151
+ colorIndices={[2, 3, 5]}
3152
+ className='color-palette'
3153
+ element='button'
3154
+ getItemClassName={getPaletteClassName}
3155
+ minColorsForFilter={(_, paletteAccessor, config) => {
3156
+ if (paletteAccessor.length <= 8 && config.general.geoType === 'us-region') {
3157
+ return false
3158
+ }
3159
+ return true
3160
+ }}
3161
+ />
3162
+
3163
+ {isCoveDeveloperMode() && (
3164
+ <>
3165
+ <div className='mt-3'>
3166
+ <label className='checkbox'>
3167
+ <input
3168
+ type='checkbox'
3169
+ checked={!!config.general.palette.customColorsOrdered}
3170
+ onChange={e => {
3171
+ const _state = cloneConfig(config)
3172
+ if (e.target.checked) {
3173
+ // Extract actual colors from runtime legend if available
3174
+ if (runtimeLegend?.items && runtimeLegend.items.length > 0) {
3175
+ const extractedColors = []
3176
+ for (const item of runtimeLegend.items) {
3177
+ // Skip special classes (like "No Data")
3178
+ if (item.special) continue
3179
+ // Add the color if it exists and hasn't been added yet
3180
+ if (item.color && !extractedColors.includes(item.color)) {
3181
+ extractedColors.push(item.color)
3182
+ }
3183
+ }
3184
+ _state.general.palette.customColorsOrdered =
3185
+ extractedColors.length > 0
3186
+ ? extractedColors
3187
+ : ['#3366cc', '#5588dd', '#77aaee', '#99ccff']
3188
+ } else {
3189
+ // Fallback to default colors if runtime legend not available
3190
+ _state.general.palette.customColorsOrdered = [
3191
+ '#3366cc',
3192
+ '#5588dd',
3193
+ '#77aaee',
3194
+ '#99ccff'
3195
+ ]
3196
+ }
3197
+ } else {
3198
+ // Remove custom colors and revert to default palette
3199
+ delete _state.general.palette.customColorsOrdered
3200
+ delete _state.general.palette.customColors
3201
+ // Set default palette if none exists
3202
+ if (!_state.general.palette.name) {
3203
+ _state.general.palette.name = 'sequential_blue_green'
3204
+ _state.general.palette.version = '2.0'
3205
+ }
3206
+ }
3207
+ setConfig(_state)
3208
+ }}
3209
+ />
3210
+ Use Custom Colors
3211
+ </label>
3212
+ </div>
3213
+
3214
+ {config.general.palette.customColorsOrdered && (
3215
+ <div className='mt-2'>
3216
+ <CustomColorsEditor
3217
+ colors={config.general.palette.customColorsOrdered}
3218
+ onChange={newColors => {
3219
+ const _state = cloneConfig(config)
3220
+ _state.general.palette.customColorsOrdered = newColors
3221
+ setConfig(_state)
3222
+ }}
3223
+ label='Custom Color Order'
3224
+ minColors={1}
3225
+ maxColors={20}
3226
+ />
3227
+ </div>
3228
+ )}
3229
+ </>
3230
+ )}
3231
+
3232
+ <label>
3233
+ Geocode Settings
3234
+ <TextField
3235
+ type='number'
3236
+ value={config.visual.geoCodeCircleSize}
3237
+ section='visual'
3238
+ max='10'
3239
+ fieldName='geoCodeCircleSize'
3240
+ label='Geocode Circle Size'
3241
+ updateField={updateField}
3242
+ />
3243
+ </label>
3244
+
3245
+ {config.general.type === 'bubble' && (
3246
+ <>
3247
+ <TextField
3248
+ type='number'
3249
+ value={config.visual.minBubbleSize}
3250
+ section='visual'
3251
+ fieldName='minBubbleSize'
3252
+ label='Minimum Bubble Size'
3253
+ updateField={updateField}
3254
+ />
3255
+ <TextField
3256
+ type='number'
3257
+ value={config.visual.maxBubbleSize}
3258
+ section='visual'
3259
+ fieldName='maxBubbleSize'
3260
+ label='Maximum Bubble Size'
3261
+ updateField={updateField}
3262
+ />
3263
+ </>
3264
+ )}
3265
+ {(config.general.geoType === 'world' ||
3266
+ (config.general.geoType === 'us' && config.general.type === 'bubble')) && (
3267
+ <label className='checkbox'>
3268
+ <input
3269
+ type='checkbox'
3270
+ checked={config.visual.showBubbleZeros}
3271
+ onChange={event => {
3272
+ const _newConfig = _.cloneDeep(config)
3273
+ _newConfig.visual.showBubbleZeros = event.target.checked
3274
+ setConfig(_newConfig)
3275
+ }}
3276
+ />
3277
+ <span className='edit-label'>Show Data with Zero's on Bubble Map</span>
3278
+ </label>
3279
+ )}
3280
+ {(config.general.geoType === 'world' || config.general.geoType === 'single-state') && (
3281
+ <label className='checkbox'>
3282
+ <input
3283
+ type='checkbox'
3284
+ checked={config.general.allowMapZoom}
3285
+ onChange={event => {
3286
+ const _newConfig = cloneConfig(config)
3287
+ _newConfig.general.allowMapZoom = event.target.checked
3288
+ _newConfig.mapPosition.coordinates = config.general.geoType === 'world' ? [0, 30] : [0, 0]
3289
+ _newConfig.mapPosition.zoom = 1
3290
+ setConfig(_newConfig)
3291
+ }}
3292
+ />
3293
+ <span className='edit-label'>Allow Map Zooming</span>
3294
+ </label>
3295
+ )}
3296
+ {config.general.type === 'bubble' && (
3297
+ <label className='checkbox'>
3298
+ <input
3299
+ type='checkbox'
3300
+ checked={config.visual.extraBubbleBorder}
3301
+ onChange={event => {
3302
+ const _newConfig = cloneConfig(config)
3303
+ _newConfig.visual.extraBubbleBorder = event.target.checked
3304
+ setConfig(_newConfig)
3305
+ }}
3306
+ />
3307
+ <span className='edit-label'>Bubble Map has extra border</span>
3308
+ </label>
3309
+ )}
3310
+ {(config.general.geoType === 'us' ||
3311
+ config.general.geoType === 'us-county' ||
3312
+ config.general.geoType === 'world') && (
3313
+ <>
3314
+ <Select
3315
+ label='Default City Style'
3316
+ value={config.visual.cityStyle || 'circle'}
3317
+ options={[
3318
+ { value: 'circle', label: 'Circle' },
3319
+ { value: 'pin', label: 'Pin' },
3320
+ { value: 'square', label: 'Square' },
3321
+ { value: 'triangle', label: 'Triangle' },
3322
+ { value: 'diamond', label: 'Diamond' },
3323
+ { value: 'star', label: 'Star' }
3324
+ ]}
3325
+ onChange={event => {
3326
+ handleEditorChanges('handleCityStyle', event.target.value)
3327
+ }}
3328
+ />
3329
+ <TextField
3330
+ value={config.visual.cityStyleLabel}
3331
+ section='visual'
3332
+ fieldName='cityStyleLabel'
3333
+ label='Label (Optional) '
3334
+ updateField={updateField}
3335
+ tooltip={
3336
+ <Tooltip style={{ textTransform: 'none' }}>
3337
+ <Tooltip.Target>
3338
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
3339
+ </Tooltip.Target>
3340
+ <Tooltip.Content>
3341
+ <p>When a label is provided, the default city style will appear in the legend.</p>
3342
+ </Tooltip.Content>
3343
+ </Tooltip>
3344
+ }
3345
+ />
3346
+ </>
3347
+ )}
3348
+ {/* <AdditionalCityStyles /> */}
3349
+ <>
3350
+ {config.visual.additionalCityStyles.length > 0 &&
3351
+ config.visual.additionalCityStyles.map(({ label, column, value, shape }, i) => {
3352
+ return (
3353
+ <div className='edit-block' key={`additional-city-style-${i}`}>
3354
+ <button
3355
+ className='remove-column'
3356
+ onClick={e => {
3357
+ e.preventDefault()
3358
+ editCityStyles('remove', i, '', '')
3359
+ }}
3360
+ >
3361
+ Remove
3362
+ </button>
3363
+ <p>City Style {i + 1}</p>
3364
+ <Select
3365
+ label='Column with configuration value'
3366
+ value={column}
3367
+ options={columnsOptions.map(c => c.key)}
3368
+ onChange={e => {
3369
+ editCityStyles('update', i, 'column', e.target.value)
3370
+ }}
3371
+ />
3372
+ <label>
3373
+ <span className='edit-label column-heading'>Value to Trigger</span>
3374
+ <input
3375
+ type='text'
3376
+ value={value}
3377
+ onChange={e => {
3378
+ editCityStyles('update', i, 'value', e.target.value)
3379
+ }}
3380
+ ></input>
3381
+ </label>
3382
+ <Select
3383
+ label='Shape'
3384
+ value={shape}
3385
+ options={[
3386
+ { value: '', label: '- Select Option -' },
3387
+ ...['Circle', 'Square', 'Triangle', 'Diamond', 'Star', 'Pin']
3388
+ .filter(val => String(config.visual.cityStyle).toLowerCase() !== val.toLowerCase())
3389
+ .map(val => ({ value: val, label: val }))
3390
+ ]}
3391
+ onChange={e => {
3392
+ editCityStyles('update', i, 'shape', e.target.value)
3393
+ }}
3394
+ />
3395
+ <label>
3396
+ <span className='edit-label column-heading'>Label</span>
3397
+ <input
3398
+ key={i}
3399
+ type='text'
3400
+ value={label}
3401
+ onChange={e => {
3402
+ editCityStyles('update', i, 'label', e.target.value)
3403
+ }}
3404
+ />
3405
+ </label>
3406
+ </div>
3407
+ )
3408
+ })}
3409
+
3410
+ <button
3411
+ type='button'
3412
+ onClick={() => editCityStyles('add', 0, '', '')}
3413
+ className='btn btn-primary full-width'
3414
+ >
3415
+ Add city style
3416
+ </button>
3417
+ </>
3418
+ <label htmlFor='opacity'>
3419
+ <TextField
3420
+ type='number'
3421
+ min={0}
3422
+ max={100}
3423
+ value={config.tooltips.opacity ? config.tooltips.opacity : 100}
3424
+ section='tooltips'
3425
+ fieldName='opacity'
3426
+ label='Tooltip Opacity (%)'
3427
+ updateField={updateField}
3428
+ />
3429
+ </label>
3430
+ {/* Leaflet Map Type */}
3431
+ {config.general.geoType === 'leaflet' && (
3432
+ <>
3433
+ <Select
3434
+ label='Leaflet Theme'
3435
+ options={layerOptions}
3436
+ section={'leaflet'}
3437
+ fieldName='theme'
3438
+ updateField={updateField}
3439
+ />
3440
+ </>
3441
+ )}
3442
+ </AccordionItemPanel>
3443
+ </AccordionItem>
3444
+ <AccordionItem>
3445
+ <AccordionItemHeading>
3446
+ <AccordionItemButton>Custom Map Layers</AccordionItemButton>
3447
+ </AccordionItemHeading>
3448
+ <AccordionItemPanel>
3449
+ {config.map.layers.length === 0 && <p>There are currently no layers.</p>}
3450
+
3451
+ {config.map.layers.map((layer, index) => {
3452
+ return (
3453
+ <>
3454
+ <Accordion allowZeroExpanded>
3455
+ <AccordionItem className='series-item map-layers-list'>
3456
+ <AccordionItemHeading className='series-item__title map-layers-list--title'>
3457
+ <AccordionItemButton>{`Layer ${index + 1}: ${layer.name}`}</AccordionItemButton>
3458
+ </AccordionItemHeading>
3459
+ <AccordionItemPanel>
3460
+ <div className='map-layers-panel'>
3461
+ <label htmlFor='layerName'>Layer Name:</label>
3462
+ <input
3463
+ type='text'
3464
+ name='layerName'
3465
+ value={layer.name}
3466
+ onChange={e => handleMapLayer(e, index, 'name')}
3467
+ />
3468
+ <label htmlFor='layerFilename'>File:</label>
3469
+ <input
3470
+ type='text'
3471
+ name='layerFilename'
3472
+ value={layer.url}
3473
+ onChange={e => handleMapLayer(e, index, 'url')}
3474
+ />
3475
+ <label htmlFor='layerNamespace'>TOPOJSON Namespace:</label>
3476
+ <input
3477
+ type='text'
3478
+ name='layerNamespace'
3479
+ value={layer.namespace}
3480
+ onChange={e => handleMapLayer(e, index, 'namespace')}
3481
+ />
3482
+ <label htmlFor='layerFill'>Fill Color:</label>
3483
+ <input
3484
+ type='text'
3485
+ name='layerFill'
3486
+ value={layer.fill}
3487
+ onChange={e => handleMapLayer(e, index, 'fill')}
3488
+ />
3489
+ <label htmlFor='layerFill'>Fill Opacity (%):</label>
3490
+ <input
3491
+ type='number'
3492
+ min={0}
3493
+ max={100}
3494
+ name='layerFill'
3495
+ value={layer.fillOpacity ? layer.fillOpacity * 100 : ''}
3496
+ onChange={e => handleMapLayer(e, index, 'fillOpacity')}
3497
+ />
3498
+ <label htmlFor='layerStroke'>Stroke Color:</label>
3499
+ <input
3500
+ type='text'
3501
+ name='layerStroke'
3502
+ value={layer.stroke}
3503
+ onChange={e => handleMapLayer(e, index, 'stroke')}
3504
+ />
3505
+ <label htmlFor='layerStroke'>Stroke Width:</label>
3506
+ <input
3507
+ type='number'
3508
+ min={0}
3509
+ max={5}
3510
+ name='layerStrokeWidth'
3511
+ value={layer.strokeWidth}
3512
+ onChange={e => handleMapLayer(e, index, 'strokeWidth')}
3513
+ />
3514
+ <label htmlFor='layerTooltip'>Tooltip:</label>
3515
+ <textarea
3516
+ name='layerTooltip'
3517
+ value={layer.tooltip}
3518
+ onChange={e => handleMapLayer(e, index, 'tooltip')}
3519
+ ></textarea>
3520
+ <button onClick={e => handleRemoveLayer(e, index)}>Remove Layer</button>
3521
+ </div>
3522
+ </AccordionItemPanel>
3523
+ </AccordionItem>
3524
+ </Accordion>
3525
+ </>
3526
+ )
3527
+ })}
3528
+ <button className={'btn btn-primary full-width'} onClick={handleAddLayer}>
3529
+ Add Map Layer
3530
+ </button>
3531
+ <p className='layer-purpose-details'>
3532
+ Context should be added to your visualization or associated page to describe the significance of
3533
+ layers that are added to maps.
3534
+ </p>
3535
+ </AccordionItemPanel>
3536
+ </AccordionItem>
3537
+ {config.general.geoType === 'us' && <Panels.PatternSettings name='Pattern Settings' />}
3538
+ {config.general.geoType !== 'us-county' && <Panels.Annotate name='Text Annotations' />}
3539
+ <PanelMarkup
3540
+ name='Markup Variables'
3541
+ markupVariables={config.markupVariables || []}
3542
+ data={config.data || []}
3543
+ enableMarkupVariables={config.enableMarkupVariables || false}
3544
+ onMarkupVariablesChange={variables => setConfig({ ...config, markupVariables: variables })}
3545
+ onToggleEnable={enabled => setConfig({ ...config, enableMarkupVariables: enabled })}
3546
+ />
3547
+ <Panels.SmallMultiples name='Small Multiples' />
3548
+ </Accordion>
3549
+ <AdvancedEditor loadConfig={setConfig} config={config} convertStateToConfig={mapConvertStateToConfig} />
3550
+ </>
3551
+ )
3552
+ }}
3553
+ </BaseEditorPanel>
3380
3554
 
3381
3555
  {showConversionModal && (
3382
3556
  <PaletteConversionModal
@@ -3386,7 +3560,7 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
3386
3560
  paletteName={pendingPaletteSelection?.palette}
3387
3561
  />
3388
3562
  )}
3389
- </ErrorBoundary>
3563
+ </React.Fragment>
3390
3564
  )
3391
3565
  }
3392
3566