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