@cdc/map 4.26.3 → 4.26.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/CONFIG.md +235 -0
  2. package/README.md +70 -24
  3. package/dist/cdcmap-CY9IcPSi.es.js +6 -0
  4. package/dist/cdcmap-DlpiY3fQ.es.js +4 -0
  5. package/dist/cdcmap.js +27405 -26257
  6. package/examples/{testing-layer-2.json → __data__/testing-layer-2.json} +1 -1
  7. package/examples/{testing-layer.json → __data__/testing-layer.json} +1 -1
  8. package/examples/county-hsa-toggle.json +51993 -0
  9. package/examples/custom-map-layers.json +2 -2
  10. package/examples/default-county.json +3 -3
  11. package/examples/minimal-example.json +69 -0
  12. package/examples/private/annotation-bug.json +2 -2
  13. package/examples/private/css-issue.json +314 -0
  14. package/examples/private/region-breaking.json +1639 -0
  15. package/examples/private/test1.json +27247 -0
  16. package/package.json +4 -4
  17. package/src/CdcMapComponent.tsx +96 -13
  18. package/src/_stories/CdcMap.Editor.ColumnsSectionTests.stories.tsx +601 -0
  19. package/src/_stories/CdcMap.Editor.DataTableSectionTests.stories.tsx +404 -0
  20. package/src/_stories/CdcMap.Editor.FiltersSectionTests.stories.tsx +229 -0
  21. package/src/_stories/CdcMap.Editor.GeneralSectionTests.stories.tsx +262 -0
  22. package/src/_stories/CdcMap.Editor.LegendSectionTests.stories.tsx +541 -0
  23. package/src/_stories/CdcMap.Editor.MultiCountryWorldMapTests.stories.tsx +359 -0
  24. package/src/_stories/CdcMap.Editor.PatternSettingsSectionTests.stories.tsx +516 -0
  25. package/src/_stories/CdcMap.Editor.SmallMultiplesSectionTests.stories.tsx +165 -0
  26. package/src/_stories/CdcMap.Editor.TextAnnotationsSectionTests.stories.tsx +145 -0
  27. package/src/_stories/CdcMap.Editor.TypeSectionTests.stories.tsx +312 -0
  28. package/src/_stories/CdcMap.Editor.VisualSectionTests.stories.tsx +359 -0
  29. package/src/_stories/CdcMap.Editor.ZoomControlsTests.stories.tsx +88 -0
  30. package/src/_stories/{CdcMap.stories.tsx → CdcMap.smoke.stories.tsx} +12 -0
  31. package/src/_stories/_mock/legends/legend-tests.json +3 -3
  32. package/src/components/Annotation/AnnotationList.tsx +1 -1
  33. package/src/components/EditorPanel/components/EditorPanel.tsx +504 -383
  34. package/src/components/EditorPanel/components/HexShapeSettings.tsx +1 -1
  35. package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +112 -117
  36. package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +26 -13
  37. package/src/components/EditorPanel/components/editorPanel.styles.css +22 -2
  38. package/src/components/Legend/components/Legend.tsx +3 -3
  39. package/src/components/Legend/components/LegendItem.Hex.tsx +4 -2
  40. package/src/components/SmallMultiples/SynchronizedTooltip.tsx +1 -1
  41. package/src/components/UsaMap/components/UsaMap.County.tsx +271 -100
  42. package/src/components/UsaMap/components/UsaMap.State.tsx +1 -1
  43. package/src/components/UsaMap/data/cb_2019_us_county_20m.json +75817 -1
  44. package/src/components/UsaMap/data/hsa_fips_mapping.json +3144 -0
  45. package/src/components/WorldMap/data/world-topo.json +1 -1
  46. package/src/data/initial-state.js +1 -0
  47. package/src/data/supported-counties.json +1 -1
  48. package/src/helpers/countyTerritories.ts +38 -0
  49. package/src/helpers/dataTableHelpers.ts +35 -6
  50. package/src/helpers/tests/countyTerritories.test.ts +87 -0
  51. package/src/hooks/useApplyTooltipsToGeo.tsx +7 -4
  52. package/src/hooks/useMapLayers.tsx +1 -1
  53. package/src/hooks/useTooltip.ts +18 -7
  54. package/src/store/map.actions.ts +5 -2
  55. package/src/store/map.reducer.ts +12 -3
  56. package/src/test/CdcMap.test.jsx +24 -0
  57. package/src/types/MapConfig.ts +6 -0
  58. package/src/types/MapContext.ts +3 -1
  59. package/topojson-updater/README.txt +1 -1
  60. package/LICENSE +0 -201
  61. package/dist/cdcmap-vr9HZwRt.es.js +0 -6
  62. package/examples/__data__/city-state-data.json +0 -668
  63. package/examples/city-state.json +0 -434
  64. package/examples/default-world-data.json +0 -1450
  65. package/examples/new-cities.json +0 -656
  66. package/src/_stories/CdcMap.Editor.stories.tsx +0 -3648
  67. package/topojson-updater/package-lock.json +0 -223
  68. /package/src/_stories/{CdcMap.ColumnWrap.stories.tsx → CdcMap.ColumnWrap.smoke.stories.tsx} +0 -0
  69. /package/src/_stories/{CdcMap.Defaults.stories.tsx → CdcMap.Defaults.smoke.stories.tsx} +0 -0
  70. /package/src/_stories/{CdcMap.DistrictOfColumbia.stories.tsx → CdcMap.DistrictOfColumbia.smoke.stories.tsx} +0 -0
  71. /package/src/_stories/{CdcMap.Filters.stories.tsx → CdcMap.Filters.smoke.stories.tsx} +0 -0
  72. /package/src/_stories/{CdcMap.Legend.Gradient.stories.tsx → CdcMap.Legend.Gradient.smoke.stories.tsx} +0 -0
  73. /package/src/_stories/{CdcMap.Legend.stories.tsx → CdcMap.Legend.smoke.stories.tsx} +0 -0
  74. /package/src/_stories/{CdcMap.Patterns.stories.tsx → CdcMap.Patterns.smoke.stories.tsx} +0 -0
  75. /package/src/_stories/{CdcMap.SmallMultiples.stories.tsx → CdcMap.SmallMultiples.smoke.stories.tsx} +0 -0
  76. /package/src/_stories/{CdcMap.Table.stories.tsx → CdcMap.Table.smoke.stories.tsx} +0 -0
  77. /package/src/_stories/{CdcMap.ZeroColor.stories.tsx → CdcMap.ZeroColor.smoke.stories.tsx} +0 -0
  78. /package/src/_stories/{GoogleMap.stories.tsx → GoogleMap.smoke.stories.tsx} +0 -0
  79. /package/src/_stories/{UsaMap.NoData.stories.tsx → UsaMap.NoData.smoke.stories.tsx} +0 -0
@@ -29,6 +29,7 @@ import { EditorPanel as BaseEditorPanel } from '@cdc/core/components/EditorPanel
29
29
  import AdvancedEditor from '@cdc/core/components/AdvancedEditor'
30
30
  import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
31
31
  import Icon from '@cdc/core/components/ui/Icon'
32
+ import GroupedList from '@cdc/core/components/EditorPanel/GroupedList'
32
33
  import InputToggle from '@cdc/core/components/inputs/InputToggle'
33
34
  import Tooltip from '@cdc/core/components/ui/Tooltip'
34
35
  import VizFilterEditor from '@cdc/core/components/EditorPanel/VizFilterEditor'
@@ -50,6 +51,8 @@ import { MapContext } from '../../../types/MapContext.js'
50
51
  import Alert from '@cdc/core/components/Alert'
51
52
  import { updateFieldFactory } from '@cdc/core/helpers/updateFieldFactory'
52
53
  import { CheckBox, Select, TextField } from '@cdc/core/components/EditorPanel/Inputs'
54
+ import DownloadUrlControls from '@cdc/core/components/EditorPanel/DownloadUrlControls'
55
+ import Button from '@cdc/core/components/elements/Button'
53
56
  import StyleTreatmentSection from '@cdc/core/components/EditorPanel/sections/StyleTreatmentSection'
54
57
  import { HeaderThemeSelector } from '@cdc/core/components/HeaderThemeSelector'
55
58
  import useColumnsRequiredChecker from '../../../hooks/useColumnsRequiredChecker'
@@ -1243,7 +1246,11 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
1243
1246
  ))
1244
1247
  }
1245
1248
 
1246
- const isLoadedFromUrl = config?.dataKey?.includes('http://') || config?.dataKey?.includes('https://')
1249
+ const isLoadedFromUrl =
1250
+ config?.dataFileSourceType === 'url' ||
1251
+ Boolean(config?.runtimeDataUrl || config?.dataUrl || config?.dataFileName) ||
1252
+ config?.dataKey?.includes('http://') ||
1253
+ config?.dataKey?.includes('https://')
1247
1254
 
1248
1255
  // Custom convertStateToConfig for map with map-specific logic
1249
1256
  const customConvertStateToConfig = () => {
@@ -1362,6 +1369,41 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
1362
1369
  }}
1363
1370
  />
1364
1371
  )}
1372
+ {config.general.geoType === 'us-county' && (
1373
+ <>
1374
+ <CheckBox
1375
+ value={config.general.showNeighboringStates || false}
1376
+ fieldName='showNeighboringStates'
1377
+ label="Show Neighboring States' Data"
1378
+ updateField={updateField}
1379
+ section='general'
1380
+ />
1381
+ <CheckBox
1382
+ value={config.general.showHSABoundaries || false}
1383
+ fieldName='showHSABoundaries'
1384
+ label='Show HSA Boundaries'
1385
+ updateField={updateField}
1386
+ section='general'
1387
+ tooltip={
1388
+ <Tooltip style={{ textTransform: 'none' }}>
1389
+ <Tooltip.Target>
1390
+ <Icon
1391
+ display='question'
1392
+ style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }}
1393
+ />
1394
+ </Tooltip.Target>
1395
+ <Tooltip.Content>
1396
+ <p>
1397
+ Health Service Areas (HSAs) are a county or cluster of contiguous counties which are
1398
+ relatively self-contained with respect to hospital care. Set HSA description in
1399
+ tooltip under the Columns accordion.
1400
+ </p>
1401
+ </Tooltip.Content>
1402
+ </Tooltip>
1403
+ }
1404
+ />
1405
+ </>
1406
+ )}
1365
1407
  {(config.general.geoType === 'us-county' || config.general.geoType === 'single-state') && (
1366
1408
  <Select
1367
1409
  label='County Census Year'
@@ -1599,14 +1641,26 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
1599
1641
  />
1600
1642
  )}
1601
1643
 
1602
- {'us' === config.general.geoType && (
1644
+ {['us', 'us-county'].includes(config.general.geoType) && (
1603
1645
  <CheckBox
1604
- value={general.territoriesAlwaysShow || false}
1646
+ value={general.territoriesAlwaysShow ?? true}
1605
1647
  section='general'
1606
1648
  subsection={null}
1607
1649
  fieldName='territoriesAlwaysShow'
1608
- label='Show All Territories'
1609
- updateField={updateField}
1650
+ label='Show Available Territories'
1651
+ updateField={() => {
1652
+ setConfig({
1653
+ ...config,
1654
+ general: {
1655
+ ...config.general,
1656
+ territoriesAlwaysShow: !(general.territoriesAlwaysShow ?? true)
1657
+ },
1658
+ migrations: {
1659
+ ...config.migrations,
1660
+ showPuertoRico: false
1661
+ }
1662
+ })
1663
+ }}
1610
1664
  />
1611
1665
  )}
1612
1666
  </AccordionItemPanel>
@@ -1923,6 +1977,26 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
1923
1977
  />
1924
1978
  </label>
1925
1979
  </ColumnSection>
1980
+ {config.general.geoType === 'us-county' && config.general.showHSABoundaries && (
1981
+ <Select
1982
+ label='HSA Description Column'
1983
+ value={config.columns.hsa?.name}
1984
+ options={columnsOptions.map(c => c.key)}
1985
+ onChange={e => {
1986
+ editColumn('hsa', 'name', e.target.value)
1987
+ }}
1988
+ tooltip={
1989
+ <Tooltip style={{ textTransform: 'none' }}>
1990
+ <Tooltip.Target>
1991
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
1992
+ </Tooltip.Target>
1993
+ <Tooltip.Content>
1994
+ <p>Select the source column containing the HSA description.</p>
1995
+ </Tooltip.Content>
1996
+ </Tooltip>
1997
+ }
1998
+ />
1999
+ )}
1926
2000
  {'navigation' !== config.general.type && (
1927
2001
  <ColumnSection
1928
2002
  fieldKey='primary'
@@ -2101,105 +2175,6 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
2101
2175
  </>
2102
2176
  }
2103
2177
 
2104
- {'navigation' !== config.general.type && (
2105
- <fieldset className='primary-fieldset edit-block'>
2106
- <label>
2107
- <span className='edit-label'>
2108
- Special Classes
2109
- <Tooltip style={{ textTransform: 'none' }}>
2110
- <Tooltip.Target>
2111
- <Icon display='question' style={{ marginLeft: '0.5rem' }} />
2112
- </Tooltip.Target>
2113
- <Tooltip.Content>
2114
- <p>
2115
- For secondary values such as "NA", the system can automatically color-code them in
2116
- shades of gray, one shade for each special class.
2117
- </p>
2118
- </Tooltip.Content>
2119
- </Tooltip>
2120
- </span>
2121
- </label>
2122
- {config.legend.specialClasses.length === 2 && (
2123
- <Alert
2124
- type='info'
2125
- message='If a third special class is needed you can apply a pattern to set it apart.'
2126
- showCloseButton={false}
2127
- />
2128
- )}
2129
- {specialClasses.map((specialClass, i) => (
2130
- <div className='edit-block' key={`special-class-${i}`}>
2131
- <button
2132
- className='remove-column'
2133
- onClick={e => {
2134
- e.preventDefault()
2135
- editColumn('primary', 'specialClassDelete', i)
2136
- }}
2137
- >
2138
- Remove
2139
- </button>
2140
- <p>Special Class {i + 1}</p>
2141
- <Select
2142
- label='Data Key'
2143
- value={specialClass.key}
2144
- options={columnsOptions.map(option => ({
2145
- value: option.key,
2146
- label: option.key
2147
- }))}
2148
- onChange={event => {
2149
- editColumn('primary', 'specialClassEdit', {
2150
- prop: 'key',
2151
- index: i,
2152
- value: event.target.value
2153
- })
2154
- }}
2155
- />
2156
- <Select
2157
- label='Value'
2158
- value={specialClass.value}
2159
- options={[
2160
- { value: '', label: '- Select Value -' },
2161
- ...(columnsByKey[specialClass.key] || [])
2162
- .sort()
2163
- .map(option => ({ value: option, label: option }))
2164
- ]}
2165
- onChange={event => {
2166
- editColumn('primary', 'specialClassEdit', {
2167
- prop: 'value',
2168
- index: i,
2169
- value: event.target.value
2170
- })
2171
- }}
2172
- />
2173
- <label>
2174
- <span className='edit-label column-heading'>Label</span>
2175
- <input
2176
- type='text'
2177
- value={specialClass.label}
2178
- onChange={e => {
2179
- editColumn('primary', 'specialClassEdit', {
2180
- prop: 'label',
2181
- index: i,
2182
- value: e.target.value
2183
- })
2184
- }}
2185
- />
2186
- </label>
2187
- </div>
2188
- ))}
2189
- {config.legend.specialClasses.length < 2 && (
2190
- <button
2191
- className='btn btn-primary full-width'
2192
- onClick={e => {
2193
- e.preventDefault()
2194
- editColumn('primary', 'specialClassAdd', {})
2195
- }}
2196
- >
2197
- Add Special Class
2198
- </button>
2199
- )}
2200
- </fieldset>
2201
- )}
2202
-
2203
2178
  <label className='edit-block navigate column-heading'>
2204
2179
  <span className='edit-label column-heading'>
2205
2180
  Navigation
@@ -2227,144 +2202,261 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
2227
2202
  <fieldset className='primary-fieldset edit-block'>
2228
2203
  <label>
2229
2204
  <span className='edit-label'>
2230
- Additional Columns
2205
+ Special Classes
2231
2206
  <Tooltip style={{ textTransform: 'none' }}>
2232
2207
  <Tooltip.Target>
2233
2208
  <Icon display='question' style={{ marginLeft: '0.5rem' }} />
2234
2209
  </Tooltip.Target>
2235
2210
  <Tooltip.Content>
2236
2211
  <p>
2237
- You can specify additional columns to display in tooltips and / or the supporting data
2238
- table.
2212
+ For secondary values such as "NA", the system can automatically color-code them in
2213
+ shades of gray, one shade for each special class.
2239
2214
  </p>
2240
2215
  </Tooltip.Content>
2241
2216
  </Tooltip>
2242
2217
  </span>
2243
2218
  </label>
2244
- {additionalColumns.map(val => (
2245
- <fieldset className='edit-block' key={val}>
2246
- <button
2247
- className='remove-column'
2248
- onClick={event => {
2249
- event.preventDefault()
2250
- removeAdditionalColumn(val)
2251
- }}
2252
- >
2253
- Remove
2254
- </button>
2255
- <Select
2256
- label='Column'
2257
- value={config.columns[val] ? config.columns[val].name : ''}
2258
- options={columnsOptions
2259
- .filter(option => {
2260
- const optionValue = option.props.value
2261
- return (
2262
- optionValue === '' ||
2263
- optionValue !== config.columns.geo?.name ||
2264
- optionValue === config.columns[val]?.name
2265
- )
2266
- })
2267
- .map(option => ({
2268
- value: option.props.value,
2269
- label: option.props.children
2270
- }))}
2271
- onChange={event => {
2272
- editColumn(val, 'name', event.target.value)
2273
- }}
2274
- />
2275
- <TextField
2276
- value={columns[val].label}
2277
- section='columns'
2278
- subsection={val}
2279
- fieldName='label'
2280
- label='Label'
2281
- updateField={updateField}
2282
- />
2283
- <ul className='column-edit'>
2284
- <li className='three-col'>
2285
- <TextField
2286
- value={columns[val].prefix}
2287
- section='columns'
2288
- subsection={val}
2289
- fieldName='prefix'
2290
- label='Prefix'
2291
- updateField={updateField}
2292
- />
2293
- <TextField
2294
- value={columns[val].suffix}
2295
- section='columns'
2296
- subsection={val}
2297
- fieldName='suffix'
2298
- label='Suffix'
2299
- updateField={updateField}
2300
- />
2301
- <TextField
2302
- type='number'
2303
- value={columns[val].roundToPlace}
2304
- section='columns'
2305
- subsection={val}
2306
- fieldName='roundToPlace'
2307
- label='Round'
2308
- updateField={updateField}
2309
- />
2310
- </li>
2311
- <CheckBox
2312
- value={config.columns[val].useCommas}
2313
- section='columns'
2314
- subsection={val}
2315
- fieldName='useCommas'
2316
- label='Add Commas to Numbers'
2317
- updateField={updateField}
2318
- onChange={event => {
2319
- editColumn(val, 'useCommas', event.target.checked)
2320
- }}
2321
- />
2322
- <CheckBox
2323
- value={config.columns[val].dataTable}
2324
- section='columns'
2325
- subsection={val}
2326
- fieldName='dataTable'
2327
- label='Show in Data Table'
2328
- updateField={updateField}
2329
- onChange={event => {
2330
- editColumn(val, 'dataTable', event.target.checked)
2331
- }}
2332
- />
2333
- <CheckBox
2334
- value={config.columns[val].tooltip}
2335
- section='columns'
2336
- subsection={val}
2337
- fieldName='tooltip'
2338
- label='Show in Tooltips'
2339
- updateField={updateField}
2340
- onChange={event => {
2341
- editColumn(val, 'tooltip', event.target.checked)
2342
- }}
2343
- />
2344
- <label>
2345
- <span className='edit-label column-heading'>Order</span>
2346
- <input
2347
- onWheel={e => e.currentTarget.blur()}
2348
- type='number'
2349
- min='1'
2350
- value={config.columns[val]?.order ?? ''}
2351
- onChange={event => {
2352
- updateColumnOrder(val, event.target.value)
2353
- }}
2354
- />
2355
- </label>
2356
- </ul>
2357
- </fieldset>
2358
- ))}
2359
- <button
2360
- className={'btn btn-primary full-width'}
2361
- onClick={event => {
2362
- event.preventDefault()
2363
- addAdditionalColumn(additionalColumns.length + 1)
2364
- }}
2219
+ {config.legend.specialClasses.length === 2 && (
2220
+ <Alert
2221
+ type='info'
2222
+ message='If a third special class is needed you can apply a pattern to set it apart.'
2223
+ showCloseButton={false}
2224
+ />
2225
+ )}
2226
+ <GroupedList
2227
+ items={specialClasses}
2228
+ label='Special Classes'
2229
+ droppableId='map-special-classes'
2230
+ draggable={false}
2231
+ renderItem={(specialClass, i) => (
2232
+ <Accordion allowZeroExpanded key={`special-class-${i}`}>
2233
+ <AccordionItem className='series-item series-item--chart'>
2234
+ <AccordionItemHeading className='series-item__title'>
2235
+ <AccordionItemButton className='accordion__button'>
2236
+ {specialClass.label || specialClass.value || `Special Class ${i + 1}`}
2237
+ </AccordionItemButton>
2238
+ </AccordionItemHeading>
2239
+ <AccordionItemPanel>
2240
+ <div className='series-item__panel-actions'>
2241
+ <Button
2242
+ type='button'
2243
+ variant='danger'
2244
+ size='sm'
2245
+ className='grouped-list__remove'
2246
+ onClick={() => editColumn('primary', 'specialClassDelete', i)}
2247
+ >
2248
+ Remove
2249
+ </Button>
2250
+ </div>
2251
+ <Select
2252
+ label='Data Key'
2253
+ value={specialClass.key}
2254
+ options={columnsOptions.map(option => ({
2255
+ value: option.key,
2256
+ label: option.key
2257
+ }))}
2258
+ onChange={event => {
2259
+ editColumn('primary', 'specialClassEdit', {
2260
+ prop: 'key',
2261
+ index: i,
2262
+ value: event.target.value
2263
+ })
2264
+ }}
2265
+ />
2266
+ <Select
2267
+ label='Value'
2268
+ value={specialClass.value}
2269
+ options={[
2270
+ { value: '', label: '- Select Value -' },
2271
+ ...(columnsByKey[specialClass.key] || [])
2272
+ .sort()
2273
+ .map(option => ({ value: option, label: option }))
2274
+ ]}
2275
+ onChange={event => {
2276
+ editColumn('primary', 'specialClassEdit', {
2277
+ prop: 'value',
2278
+ index: i,
2279
+ value: event.target.value
2280
+ })
2281
+ }}
2282
+ />
2283
+ <label>
2284
+ <span className='edit-label column-heading'>Label</span>
2285
+ <input
2286
+ type='text'
2287
+ value={specialClass.label}
2288
+ onChange={e => {
2289
+ editColumn('primary', 'specialClassEdit', {
2290
+ prop: 'label',
2291
+ index: i,
2292
+ value: e.target.value
2293
+ })
2294
+ }}
2295
+ />
2296
+ </label>
2297
+ </AccordionItemPanel>
2298
+ </AccordionItem>
2299
+ </Accordion>
2300
+ )}
2301
+ />
2302
+ {config.legend.specialClasses.length < 2 && (
2303
+ <Button
2304
+ type='button'
2305
+ variant='editor-primary'
2306
+ onClick={() => editColumn('primary', 'specialClassAdd', {})}
2307
+ >
2308
+ Add Special Class
2309
+ </Button>
2310
+ )}
2311
+ </fieldset>
2312
+ )}
2313
+ {'navigation' !== config.general.type && (
2314
+ <fieldset className='primary-fieldset edit-block'>
2315
+ <GroupedList
2316
+ items={additionalColumns}
2317
+ label={
2318
+ <>
2319
+ Additional Columns
2320
+ <Tooltip style={{ textTransform: 'none' }}>
2321
+ <Tooltip.Target>
2322
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
2323
+ </Tooltip.Target>
2324
+ <Tooltip.Content>
2325
+ <p>
2326
+ You can specify additional columns to display in tooltips and / or the supporting
2327
+ data table.
2328
+ </p>
2329
+ </Tooltip.Content>
2330
+ </Tooltip>
2331
+ </>
2332
+ }
2333
+ droppableId='map-additional-columns'
2334
+ draggable={false}
2335
+ renderItem={val => (
2336
+ <Accordion allowZeroExpanded key={val}>
2337
+ <AccordionItem className='series-item series-item--chart'>
2338
+ <AccordionItemHeading className='series-item__title'>
2339
+ <AccordionItemButton className='accordion__button'>
2340
+ {columns[val]?.label || config.columns[val]?.name || 'New Column'}
2341
+ </AccordionItemButton>
2342
+ </AccordionItemHeading>
2343
+ <AccordionItemPanel>
2344
+ <div className='series-item__panel-actions'>
2345
+ <Button
2346
+ type='button'
2347
+ variant='danger'
2348
+ size='sm'
2349
+ className='grouped-list__remove'
2350
+ onClick={() => removeAdditionalColumn(val)}
2351
+ >
2352
+ Remove
2353
+ </Button>
2354
+ </div>
2355
+ <Select
2356
+ label='Column'
2357
+ value={config.columns[val] ? config.columns[val].name : ''}
2358
+ options={columnsOptions.map(option => ({
2359
+ value: option.props.value,
2360
+ label: option.props.children
2361
+ }))}
2362
+ onChange={event => {
2363
+ editColumn(val, 'name', event.target.value)
2364
+ }}
2365
+ />
2366
+ <TextField
2367
+ value={columns[val].label}
2368
+ section='columns'
2369
+ subsection={val}
2370
+ fieldName='label'
2371
+ label='Label'
2372
+ updateField={updateField}
2373
+ />
2374
+ <ul className='column-edit'>
2375
+ <li className='three-col'>
2376
+ <TextField
2377
+ value={columns[val].prefix}
2378
+ section='columns'
2379
+ subsection={val}
2380
+ fieldName='prefix'
2381
+ label='Prefix'
2382
+ updateField={updateField}
2383
+ />
2384
+ <TextField
2385
+ value={columns[val].suffix}
2386
+ section='columns'
2387
+ subsection={val}
2388
+ fieldName='suffix'
2389
+ label='Suffix'
2390
+ updateField={updateField}
2391
+ />
2392
+ <TextField
2393
+ type='number'
2394
+ value={columns[val].roundToPlace}
2395
+ section='columns'
2396
+ subsection={val}
2397
+ fieldName='roundToPlace'
2398
+ label='Round'
2399
+ updateField={updateField}
2400
+ />
2401
+ </li>
2402
+ <CheckBox
2403
+ value={config.columns[val].useCommas}
2404
+ section='columns'
2405
+ subsection={val}
2406
+ fieldName='useCommas'
2407
+ label='Add Commas to Numbers'
2408
+ updateField={updateField}
2409
+ onChange={event => {
2410
+ editColumn(val, 'useCommas', event.target.checked)
2411
+ }}
2412
+ />
2413
+ <CheckBox
2414
+ value={config.columns[val].dataTable}
2415
+ section='columns'
2416
+ subsection={val}
2417
+ fieldName='dataTable'
2418
+ label='Show in Data Table'
2419
+ updateField={updateField}
2420
+ onChange={event => {
2421
+ editColumn(val, 'dataTable', event.target.checked)
2422
+ }}
2423
+ />
2424
+ <CheckBox
2425
+ value={config.columns[val].tooltip}
2426
+ section='columns'
2427
+ subsection={val}
2428
+ fieldName='tooltip'
2429
+ label='Show in Tooltips'
2430
+ updateField={updateField}
2431
+ onChange={event => {
2432
+ editColumn(val, 'tooltip', event.target.checked)
2433
+ }}
2434
+ />
2435
+ <label>
2436
+ <span className='edit-label column-heading'>Order</span>
2437
+ <input
2438
+ onWheel={e => e.currentTarget.blur()}
2439
+ type='number'
2440
+ min='1'
2441
+ value={config.columns[val]?.order ?? ''}
2442
+ onChange={event => {
2443
+ updateColumnOrder(val, event.target.value)
2444
+ }}
2445
+ />
2446
+ </label>
2447
+ </ul>
2448
+ </AccordionItemPanel>
2449
+ </AccordionItem>
2450
+ </Accordion>
2451
+ )}
2452
+ />
2453
+ <Button
2454
+ type='button'
2455
+ variant='editor-primary'
2456
+ onClick={() => addAdditionalColumn(additionalColumns.length + 1)}
2365
2457
  >
2366
2458
  Add Column
2367
- </button>
2459
+ </Button>
2368
2460
  </fieldset>
2369
2461
  )}
2370
2462
  {'category' === config.legend.type && (
@@ -2412,8 +2504,8 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
2412
2504
  </label>
2413
2505
  </fieldset>
2414
2506
  ))}
2415
- <button
2416
- className={'btn btn-primary full-width'}
2507
+ <Button
2508
+ className={'btn btn-primary full-width editor-panel-action-button'}
2417
2509
  onClick={event => {
2418
2510
  event.preventDefault()
2419
2511
  const updatedAdditionaCategories = [...(config.legend.additionalCategories || [])]
@@ -2422,7 +2514,7 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
2422
2514
  }}
2423
2515
  >
2424
2516
  Add Category
2425
- </button>
2517
+ </Button>
2426
2518
  </fieldset>
2427
2519
  )}
2428
2520
  </AccordionItemPanel>
@@ -2920,6 +3012,15 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
2920
3012
  <AccordionItemButton>Filters</AccordionItemButton>
2921
3013
  </AccordionItemHeading>
2922
3014
  <AccordionItemPanel>
3015
+ {config.general.geoType === 'us-county' && (
3016
+ <CheckBox
3017
+ value={config.general.showStateDropdown || false}
3018
+ fieldName='showStateDropdown'
3019
+ label='Show State Dropdown'
3020
+ updateField={updateField}
3021
+ section='general'
3022
+ />
3023
+ )}
2923
3024
  <VizFilterEditor
2924
3025
  config={config}
2925
3026
  updateField={updateField}
@@ -3242,16 +3343,12 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
3242
3343
  updateField={updateField}
3243
3344
  />
3244
3345
  )}
3245
- {isLoadedFromUrl && (
3246
- <CheckBox
3247
- value={config.table.showDownloadUrl}
3248
- section='table'
3249
- subsection={null}
3250
- fieldName='showDownloadUrl'
3251
- label='Show URL to Automatically Updated Data'
3252
- updateField={updateField}
3253
- />
3254
- )}
3346
+ <DownloadUrlControls
3347
+ hasUrlBackedDataSource={Boolean(isLoadedFromUrl)}
3348
+ showDownloadUrl={config.table.showDownloadUrl}
3349
+ downloadUrlLabel={config.table.downloadUrlLabel}
3350
+ updateField={updateField}
3351
+ />
3255
3352
  <CheckBox
3256
3353
  value={config.table.showFullGeoNameInCSV}
3257
3354
  section='table'
@@ -3712,73 +3809,84 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
3712
3809
  )}
3713
3810
  {/* <AdditionalCityStyles /> */}
3714
3811
  <>
3715
- {config.visual.additionalCityStyles.length > 0 &&
3716
- config.visual.additionalCityStyles.map(({ label, column, value, shape }, i) => {
3717
- return (
3718
- <div className='edit-block' key={`additional-city-style-${i}`}>
3719
- <button
3720
- className='remove-column'
3721
- onClick={e => {
3722
- e.preventDefault()
3723
- editCityStyles('remove', i, '', '')
3724
- }}
3725
- >
3726
- Remove
3727
- </button>
3728
- <p>City Style {i + 1}</p>
3729
- <Select
3730
- label='Column with configuration value'
3731
- value={column}
3732
- options={columnsOptions.map(c => c.key)}
3733
- onChange={e => {
3734
- editCityStyles('update', i, 'column', e.target.value)
3735
- }}
3736
- />
3737
- <label>
3738
- <span className='edit-label column-heading'>Value to Trigger</span>
3739
- <input
3740
- type='text'
3741
- value={value}
3812
+ <GroupedList
3813
+ items={config.visual.additionalCityStyles}
3814
+ label='Additional City Styles'
3815
+ droppableId='map-city-styles'
3816
+ draggable={false}
3817
+ renderItem={({ label, column, value, shape }, i) => (
3818
+ <Accordion allowZeroExpanded key={`additional-city-style-${i}`}>
3819
+ <AccordionItem className='series-item series-item--chart'>
3820
+ <AccordionItemHeading className='series-item__title'>
3821
+ <AccordionItemButton className='accordion__button'>
3822
+ {label || column || `City Style ${i + 1}`}
3823
+ </AccordionItemButton>
3824
+ </AccordionItemHeading>
3825
+ <AccordionItemPanel>
3826
+ <div className='series-item__panel-actions'>
3827
+ <Button
3828
+ type='button'
3829
+ variant='danger'
3830
+ size='sm'
3831
+ className='grouped-list__remove'
3832
+ onClick={() => editCityStyles('remove', i, '', '')}
3833
+ >
3834
+ Remove
3835
+ </Button>
3836
+ </div>
3837
+ <Select
3838
+ label='Column with configuration value'
3839
+ value={column}
3840
+ options={columnsOptions.map(c => c.key)}
3742
3841
  onChange={e => {
3743
- editCityStyles('update', i, 'value', e.target.value)
3842
+ editCityStyles('update', i, 'column', e.target.value)
3744
3843
  }}
3745
- ></input>
3746
- </label>
3747
- <Select
3748
- label='Shape'
3749
- value={shape}
3750
- options={[
3751
- { value: '', label: '- Select Option -' },
3752
- ...['Circle', 'Square', 'Triangle', 'Diamond', 'Star', 'Pin']
3753
- .filter(val => String(config.visual.cityStyle).toLowerCase() !== val.toLowerCase())
3754
- .map(val => ({ value: val, label: val }))
3755
- ]}
3756
- onChange={e => {
3757
- editCityStyles('update', i, 'shape', e.target.value)
3758
- }}
3759
- />
3760
- <label>
3761
- <span className='edit-label column-heading'>Label</span>
3762
- <input
3763
- key={i}
3764
- type='text'
3765
- value={label}
3844
+ />
3845
+ <label>
3846
+ <span className='edit-label column-heading'>Value to Trigger</span>
3847
+ <input
3848
+ type='text'
3849
+ value={value}
3850
+ onChange={e => {
3851
+ editCityStyles('update', i, 'value', e.target.value)
3852
+ }}
3853
+ />
3854
+ </label>
3855
+ <Select
3856
+ label='Shape'
3857
+ value={shape}
3858
+ options={[
3859
+ { value: '', label: '- Select Option -' },
3860
+ ...['Circle', 'Square', 'Triangle', 'Diamond', 'Star', 'Pin']
3861
+ .filter(
3862
+ val => String(config.visual.cityStyle).toLowerCase() !== val.toLowerCase()
3863
+ )
3864
+ .map(val => ({ value: val, label: val }))
3865
+ ]}
3766
3866
  onChange={e => {
3767
- editCityStyles('update', i, 'label', e.target.value)
3867
+ editCityStyles('update', i, 'shape', e.target.value)
3768
3868
  }}
3769
3869
  />
3770
- </label>
3771
- </div>
3772
- )
3773
- })}
3774
-
3775
- <button
3776
- type='button'
3777
- onClick={() => editCityStyles('add', 0, '', '')}
3778
- className='btn btn-primary full-width'
3779
- >
3870
+ <label>
3871
+ <span className='edit-label column-heading'>Label</span>
3872
+ <input
3873
+ key={i}
3874
+ type='text'
3875
+ value={label}
3876
+ onChange={e => {
3877
+ editCityStyles('update', i, 'label', e.target.value)
3878
+ }}
3879
+ />
3880
+ </label>
3881
+ </AccordionItemPanel>
3882
+ </AccordionItem>
3883
+ </Accordion>
3884
+ )}
3885
+ />
3886
+
3887
+ <Button type='button' variant='editor-primary' onClick={() => editCityStyles('add', 0, '', '')}>
3780
3888
  Add city style
3781
- </button>
3889
+ </Button>
3782
3890
  </>
3783
3891
  <label htmlFor='opacity'>
3784
3892
  <TextField
@@ -3821,87 +3929,100 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
3821
3929
  </AccordionItemHeading>
3822
3930
  <AccordionItemPanel>
3823
3931
  {config.map.layers.length === 0 && <p>There are currently no layers.</p>}
3824
-
3825
- {config.map.layers.map((layer, index) => {
3826
- return (
3827
- <>
3828
- <Accordion allowZeroExpanded>
3829
- <AccordionItem className='series-item map-layers-list'>
3830
- <AccordionItemHeading className='series-item__title map-layers-list--title'>
3831
- <AccordionItemButton>{`Layer ${index + 1}: ${layer.name}`}</AccordionItemButton>
3832
- </AccordionItemHeading>
3833
- <AccordionItemPanel>
3834
- <div className='map-layers-panel'>
3835
- <label htmlFor='layerName'>Layer Name:</label>
3836
- <input
3837
- type='text'
3838
- name='layerName'
3839
- value={layer.name}
3840
- onChange={e => handleMapLayer(e, index, 'name')}
3841
- />
3842
- <label htmlFor='layerFilename'>File:</label>
3843
- <input
3844
- type='text'
3845
- name='layerFilename'
3846
- value={layer.url}
3847
- onChange={e => handleMapLayer(e, index, 'url')}
3848
- />
3849
- <label htmlFor='layerNamespace'>TOPOJSON Namespace:</label>
3850
- <input
3851
- type='text'
3852
- name='layerNamespace'
3853
- value={layer.namespace}
3854
- onChange={e => handleMapLayer(e, index, 'namespace')}
3855
- />
3856
- <label htmlFor='layerFill'>Fill Color:</label>
3857
- <input
3858
- type='text'
3859
- name='layerFill'
3860
- value={layer.fill}
3861
- onChange={e => handleMapLayer(e, index, 'fill')}
3862
- />
3863
- <label htmlFor='layerFill'>Fill Opacity (%):</label>
3864
- <input
3865
- type='number'
3866
- min={0}
3867
- max={100}
3868
- name='layerFill'
3869
- value={layer.fillOpacity ? layer.fillOpacity * 100 : ''}
3870
- onChange={e => handleMapLayer(e, index, 'fillOpacity')}
3871
- />
3872
- <label htmlFor='layerStroke'>Stroke Color:</label>
3873
- <input
3874
- type='text'
3875
- name='layerStroke'
3876
- value={layer.stroke}
3877
- onChange={e => handleMapLayer(e, index, 'stroke')}
3878
- />
3879
- <label htmlFor='layerStroke'>Stroke Width:</label>
3880
- <input
3881
- type='number'
3882
- min={0}
3883
- max={5}
3884
- name='layerStrokeWidth'
3885
- value={layer.strokeWidth}
3886
- onChange={e => handleMapLayer(e, index, 'strokeWidth')}
3887
- />
3888
- <label htmlFor='layerTooltip'>Tooltip:</label>
3889
- <textarea
3890
- name='layerTooltip'
3891
- value={layer.tooltip}
3892
- onChange={e => handleMapLayer(e, index, 'tooltip')}
3893
- ></textarea>
3894
- <button onClick={e => handleRemoveLayer(e, index)}>Remove Layer</button>
3895
- </div>
3896
- </AccordionItemPanel>
3897
- </AccordionItem>
3898
- </Accordion>
3899
- </>
3900
- )
3901
- })}
3902
- <button className={'btn btn-primary full-width'} onClick={handleAddLayer}>
3932
+ <GroupedList
3933
+ items={config.map.layers}
3934
+ label='Custom Map Layers'
3935
+ droppableId='map-custom-layers'
3936
+ draggable={false}
3937
+ renderItem={(layer, index) => (
3938
+ <Accordion allowZeroExpanded key={`map-layer-${index}`}>
3939
+ <AccordionItem className='series-item series-item--chart map-layers-list'>
3940
+ <AccordionItemHeading className='series-item__title map-layers-list--title'>
3941
+ <AccordionItemButton className='accordion__button'>
3942
+ {`Layer ${index + 1}: ${layer.name}`}
3943
+ </AccordionItemButton>
3944
+ </AccordionItemHeading>
3945
+ <AccordionItemPanel>
3946
+ <div className='series-item__panel-actions'>
3947
+ <Button
3948
+ type='button'
3949
+ variant='danger'
3950
+ size='sm'
3951
+ className='grouped-list__remove'
3952
+ onClick={e => handleRemoveLayer(e, index)}
3953
+ >
3954
+ Remove Layer
3955
+ </Button>
3956
+ </div>
3957
+ <div className='map-layers-panel'>
3958
+ <label htmlFor='layerName'>Layer Name:</label>
3959
+ <input
3960
+ type='text'
3961
+ name='layerName'
3962
+ value={layer.name}
3963
+ onChange={e => handleMapLayer(e, index, 'name')}
3964
+ />
3965
+ <label htmlFor='layerFilename'>File:</label>
3966
+ <input
3967
+ type='text'
3968
+ name='layerFilename'
3969
+ value={layer.url}
3970
+ onChange={e => handleMapLayer(e, index, 'url')}
3971
+ />
3972
+ <label htmlFor='layerNamespace'>TOPOJSON Namespace:</label>
3973
+ <input
3974
+ type='text'
3975
+ name='layerNamespace'
3976
+ value={layer.namespace}
3977
+ onChange={e => handleMapLayer(e, index, 'namespace')}
3978
+ />
3979
+ <label htmlFor='layerFill'>Fill Color:</label>
3980
+ <input
3981
+ type='text'
3982
+ name='layerFill'
3983
+ value={layer.fill}
3984
+ onChange={e => handleMapLayer(e, index, 'fill')}
3985
+ />
3986
+ <label htmlFor='layerFill'>Fill Opacity (%):</label>
3987
+ <input
3988
+ type='number'
3989
+ min={0}
3990
+ max={100}
3991
+ name='layerFill'
3992
+ value={layer.fillOpacity ? layer.fillOpacity * 100 : ''}
3993
+ onChange={e => handleMapLayer(e, index, 'fillOpacity')}
3994
+ />
3995
+ <label htmlFor='layerStroke'>Stroke Color:</label>
3996
+ <input
3997
+ type='text'
3998
+ name='layerStroke'
3999
+ value={layer.stroke}
4000
+ onChange={e => handleMapLayer(e, index, 'stroke')}
4001
+ />
4002
+ <label htmlFor='layerStroke'>Stroke Width:</label>
4003
+ <input
4004
+ type='number'
4005
+ min={0}
4006
+ max={5}
4007
+ name='layerStrokeWidth'
4008
+ value={layer.strokeWidth}
4009
+ onChange={e => handleMapLayer(e, index, 'strokeWidth')}
4010
+ />
4011
+ <label htmlFor='layerTooltip'>Tooltip:</label>
4012
+ <textarea
4013
+ name='layerTooltip'
4014
+ value={layer.tooltip}
4015
+ onChange={e => handleMapLayer(e, index, 'tooltip')}
4016
+ ></textarea>
4017
+ </div>
4018
+ </AccordionItemPanel>
4019
+ </AccordionItem>
4020
+ </Accordion>
4021
+ )}
4022
+ />
4023
+ <Button type='button' variant='editor-primary' onClick={handleAddLayer}>
3903
4024
  Add Map Layer
3904
- </button>
4025
+ </Button>
3905
4026
  <p className='layer-purpose-details'>
3906
4027
  Context should be added to your visualization or associated page to describe the significance of
3907
4028
  layers that are added to maps.