@cdc/map 4.23.11 → 4.24.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 (51) hide show
  1. package/dist/cdcmap.js +41327 -40764
  2. package/examples/default-patterns.json +581 -0
  3. package/examples/default-usa.json +159 -57
  4. package/examples/private/map-text-wrap.json +574 -0
  5. package/examples/private/map-world-data.json +1046 -0
  6. package/examples/private/new-world.json +38337 -0
  7. package/examples/private/zika-issue.json +1198 -0
  8. package/index.html +10 -4
  9. package/package.json +3 -3
  10. package/src/CdcMap.tsx +10 -15
  11. package/src/components/{EditorPanel.jsx → EditorPanel/components/EditorPanel.tsx} +30 -62
  12. package/src/components/{HexShapeSettings.jsx → EditorPanel/components/HexShapeSettings.tsx} +0 -49
  13. package/src/components/EditorPanel/components/Inputs.tsx +59 -0
  14. package/src/components/EditorPanel/components/Panel.PatternSettings.tsx +140 -0
  15. package/src/components/EditorPanel/components/Panels.tsx +7 -0
  16. package/src/components/EditorPanel/index.tsx +3 -0
  17. package/src/components/Legend/components/Legend.tsx +183 -0
  18. package/src/components/Legend/components/LegendItem.Hex.tsx +53 -0
  19. package/src/components/Legend/components/index.scss +235 -0
  20. package/src/components/Legend/index.tsx +3 -0
  21. package/src/components/UsaMap/components/Territory/Territory.Hexagon.tsx +129 -0
  22. package/src/components/UsaMap/components/Territory/Territory.Rectangle.tsx +66 -0
  23. package/src/components/UsaMap/components/Territory/index.tsx +9 -0
  24. package/src/components/{CountyMap.jsx → UsaMap/components/UsaMap.County.tsx} +9 -9
  25. package/src/components/{UsaRegionMap.jsx → UsaMap/components/UsaMap.Region.tsx} +1 -3
  26. package/src/components/{SingleStateMap.jsx → UsaMap/components/UsaMap.SingleState.tsx} +8 -9
  27. package/src/components/{UsaMap.jsx → UsaMap/components/UsaMap.State.tsx} +52 -123
  28. package/src/components/UsaMap/index.tsx +13 -0
  29. package/src/components/{WorldMap.jsx → WorldMap/components/WorldMap.jsx} +6 -6
  30. package/src/components/WorldMap/data/world-topo.json +1 -0
  31. package/src/components/WorldMap/index.tsx +3 -0
  32. package/src/context.ts +2 -1
  33. package/src/data/initial-state.js +3 -1
  34. package/src/scss/main.scss +11 -1
  35. package/src/types/MapConfig.ts +149 -0
  36. package/src/types/MapContext.ts +45 -0
  37. package/src/types/runtimeLegend.ts +1 -0
  38. package/src/components/Sidebar.tsx +0 -142
  39. package/src/data/abbreviations.js +0 -57
  40. package/src/data/feature-test.json +0 -73
  41. package/src/data/newtest.json +0 -1
  42. package/src/data/state-abbreviations.js +0 -60
  43. package/src/data/supported-cities.csv +0 -165
  44. package/src/data/test.json +0 -1
  45. package/src/data/world-topo.json +0 -1
  46. package/src/scss/sidebar.scss +0 -230
  47. /package/src/{data → components/UsaMap/data}/cb_2019_us_county_20m.json +0 -0
  48. /package/src/{data → components/UsaMap/data}/us-hex-topo.json +0 -0
  49. /package/src/{data → components/UsaMap/data}/us-regions-topo-2.json +0 -0
  50. /package/src/{data → components/UsaMap/data}/us-regions-topo.json +0 -0
  51. /package/src/{data → components/UsaMap/data}/us-topo.json +0 -0
package/index.html CHANGED
@@ -16,7 +16,14 @@
16
16
 
17
17
  <body>
18
18
  <!-- DEFAULT EXAMPLES -->
19
- <div class="react-container react-container--maps" data-config="/examples/private/tooltip-issue.json"></div>
19
+ <!-- <div class="react-container" data-config="/examples/private/zika-issue.json"></div> -->
20
+
21
+ <!-- <div class="react-container react-container--maps" data-config="/examples/private/tooltip-issue.json"></div> -->
22
+ <!-- <div class="react-container react-container--maps" data-config="/examples/test.json"></div> -->
23
+ <!-- <div class="react-container react-container--maps" data-config="/examples/default-patterns.json"></div> -->
24
+ <!-- <div class="react-container react-container--maps" data-config="/examples/test.json"></div> -->
25
+ <!-- <div class="react-container react-container--maps" data-config="/examples/private/map-text-wrap.json"></div> -->
26
+ <!-- <div class="react-container react-container--maps" data-config="/examples/private/tooltip-issue.json"></div> -->
20
27
  <!-- <div class="react-container react-container--maps" data-config="/examples/test.json"></div> -->
21
28
  <!-- <div class="react-container react-container--maps" data-config="/examples/default-county.json"></div> -->
22
29
  <!-- <div class="react-container react-container--maps" data-config="/examples/default-usa.json"></div> -->
@@ -24,7 +31,6 @@
24
31
  <!-- <div class="react-container react-container&#45;&#45;maps" data-config="/examples/default-geocode.json"></div> -->
25
32
  <!-- <div class="react-container react-container&#45;&#45;maps" data-config="/examples/default-usa-regions.json"></div> -->
26
33
  <!-- <div class="react-container react-container&#45;&#45;maps" data-config="/examples/default-single-state.json"></div> -->
27
- <!-- <div class="react-container react-container&#45;&#45;maps" data-config="/examples/default-world.json"></div> -->
28
34
  <!-- <div class="react-container react-container&#45;&#45;maps" data-config="/examples/bubble-us.json"></div> -->
29
35
  <!-- <div class="react-container react-container&#45;&#45;maps" data-config="/examples/bubble-world.json"></div> -->
30
36
 
@@ -35,9 +41,9 @@
35
41
  <!-- <div class="react-container react-container--maps" data-config="/examples/example-city-state.json"></div> -->
36
42
  <!-- <div class="react-container react-container--maps" data-config="/examples/custom-map-layers.json"></div> -->
37
43
  <!-- <div class="react-container react-container--maps" data-config="/examples/example-city-stateBAD.json"></div> -->
38
- <!-- <div class="react-container react-container--maps" data-config="/examples/example-world-map.json"></div> -->
44
+ <div class="react-container react-container--maps" data-config="/examples/example-world-map.json"></div>
39
45
  <!-- <div class="react-container react-container--maps" data-config="/examples/default-hex.json"></div> -->
40
- <div class="react-container react-container--maps" data-config="/examples/hex-with-arrows.json"></div>
46
+ <!-- <div class="react-container react-container--maps" data-config="/examples/hex-with-arrows.json"></div> -->
41
47
 
42
48
  <!-- TP4 EXAMPLES -->
43
49
  <!-- <div class="react-container react-container--maps" data-config="/examples/example-city-state.json"></div> -->
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cdc/map",
3
- "version": "4.23.11",
3
+ "version": "4.24.1",
4
4
  "description": "React component for visualizing tabular data on a map of the United States or the world.",
5
5
  "moduleName": "CdcMap",
6
6
  "main": "dist/cdcmap",
@@ -24,7 +24,7 @@
24
24
  },
25
25
  "license": "Apache-2.0",
26
26
  "dependencies": {
27
- "@cdc/core": "^4.23.11",
27
+ "@cdc/core": "^4.24.1",
28
28
  "@emotion/core": "^10.0.28",
29
29
  "@emotion/react": "^11.1.5",
30
30
  "@hello-pangea/dnd": "^16.2.0",
@@ -51,5 +51,5 @@
51
51
  "react": "^18.2.0",
52
52
  "react-dom": "^18.2.0"
53
53
  },
54
- "gitHead": "ecad213667a3cb96c921eaddba43a31c84caaa08"
54
+ "gitHead": "a352a3f74f4b681191e3244061dbb3621f36eec3"
55
55
  }
package/src/CdcMap.tsx CHANGED
@@ -45,14 +45,11 @@ import DataTable from '@cdc/core/components/DataTable' // Future: Lazy
45
45
  import ConfigContext from './context'
46
46
  import Filters, { useFilters } from '@cdc/core/components/Filters'
47
47
  import Modal from './components/Modal'
48
- import Sidebar from './components/Sidebar'
48
+ import Legend from './components/Legend'
49
49
 
50
- import CountyMap from './components/CountyMap' // Future: Lazy
51
50
  import EditorPanel from './components/EditorPanel' // Future: Lazy
52
51
  import NavigationMenu from './components/NavigationMenu' // Future: Lazy
53
- import SingleStateMap from './components/SingleStateMap' // Future: Lazy
54
52
  import UsaMap from './components/UsaMap' // Future: Lazy
55
- import UsaRegionMap from './components/UsaRegionMap' // Future: Lazy
56
53
  import WorldMap from './components/WorldMap' // Future: Lazy
57
54
  import useTooltip from './hooks/useTooltip'
58
55
 
@@ -118,7 +115,7 @@ const getUniqueValues = (data, columnName) => {
118
115
  return Object.keys(result)
119
116
  }
120
117
 
121
- const CdcMap = ({ className, config, navigationHandler: customNavigationHandler, isDashboard = false, isEditor = false, isDebug = false, configUrl, logo = '', setConfig, setSharedFilter, setSharedFilterValue, hostname = 'localhost:8080', link }) => {
118
+ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler, isDashboard = false, isEditor = false, isDebug = false, configUrl, logo = '', setConfig, setSharedFilter, setSharedFilterValue, link }) => {
122
119
  const transform = new DataTransform()
123
120
  const [state, setState] = useState({ ...initialState })
124
121
  const [loading, setLoading] = useState(true)
@@ -1313,10 +1310,6 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
1313
1310
  const urlFilters = newState.filters ? (newState.filters.filter(filter => filter.type === 'url').length > 0 ? true : false) : false
1314
1311
 
1315
1312
  if (newState.dataUrl && !urlFilters) {
1316
- if (newState.dataUrl[0] === '/') {
1317
- newState.dataUrl = 'http://' + hostname + newState.dataUrl
1318
- }
1319
-
1320
1313
  // handle urls with spaces in the name.
1321
1314
  if (newState.dataUrl) newState.dataUrl = `${newState.dataUrl}`
1322
1315
  let newData = await fetchRemoteData(newState.dataUrl, 'map')
@@ -1462,6 +1455,7 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
1462
1455
  // Data
1463
1456
  if (hashData !== runtimeData.fromHash && state.data?.fromColumn) {
1464
1457
  const newRuntimeData = generateRuntimeData(state, filters || runtimeFilters, hashData)
1458
+
1465
1459
  setRuntimeData(newRuntimeData)
1466
1460
  } else {
1467
1461
  if (hashLegend !== runtimeLegend.fromHash && undefined === runtimeData.init) {
@@ -1477,7 +1471,7 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
1477
1471
  // Legend - Update when runtimeData does
1478
1472
  const legend = generateRuntimeLegend(state, runtimeData, hashLegend)
1479
1473
  setRuntimeLegend(legend)
1480
- }, [runtimeData, state.legend.unified, state.legend.showSpecialClassesLast, state.legend.separateZero, state.general.equalNumberOptIn, state.legend.numberOfItems, state.legend.specialClasses]) // eslint-disable-line
1474
+ }, [runtimeData, state.legend.unified, state.legend.showSpecialClassesLast, state.legend.separateZero, state.general.equalNumberOptIn, state.legend.numberOfItems, state.legend.specialClasses, state.legend.additionalCategories]) // eslint-disable-line
1481
1475
 
1482
1476
  useEffect(() => {
1483
1477
  reloadURLData()
@@ -1641,17 +1635,17 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
1641
1635
  {currentViewport && (
1642
1636
  <>
1643
1637
  {modal && <Modal />}
1644
- {'single-state' === geoType && <SingleStateMap />}
1645
- {'us' === geoType && 'us-geocode' !== state.general.type && <UsaMap />}
1646
- {'us-region' === geoType && <UsaRegionMap />}
1638
+ {'single-state' === geoType && <UsaMap.SingleState />}
1639
+ {'us' === geoType && 'us-geocode' !== state.general.type && <UsaMap.State />}
1640
+ {'us-region' === geoType && <UsaMap.Region />}
1641
+ {'us-county' === geoType && <UsaMap.County />}
1647
1642
  {'world' === geoType && <WorldMap />}
1648
- {'us-county' === geoType && <CountyMap />}
1649
1643
  {'data' === general.type && logo && <img src={logo} alt='' className='map-logo' />}
1650
1644
  </>
1651
1645
  )}
1652
1646
  </section>
1653
1647
 
1654
- {general.showSidebar && 'navigation' !== general.type && <Sidebar />}
1648
+ {general.showSidebar && 'navigation' !== general.type && <Legend />}
1655
1649
  </div>
1656
1650
 
1657
1651
  {'navigation' === general.type && <NavigationMenu mapTabbingID={tabId} displayGeoName={displayGeoName} data={runtimeData} options={general} columns={state.columns} navigationHandler={val => navigationHandler(val)} />}
@@ -1694,6 +1688,7 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
1694
1688
  outerContainerRef={outerContainerRef}
1695
1689
  imageRef={imageId}
1696
1690
  isDebug={isDebug}
1691
+ wrapColumns={table.wrapColumns}
1697
1692
  />
1698
1693
  )}
1699
1694
 
@@ -6,10 +6,11 @@ import { DragDropContext, Droppable, Draggable } from '@hello-pangea/dnd'
6
6
  import { useDebounce } from 'use-debounce'
7
7
  // import ReactTags from 'react-tag-autocomplete'
8
8
  import { Tooltip as ReactTooltip } from 'react-tooltip'
9
+ import Panels from './Panels.tsx'
9
10
 
10
11
  // Data
11
12
  import colorPalettes from '@cdc/core/data/colorPalettes'
12
- import { supportedStatesFipsCodes } from '../data/supported-geos'
13
+ import { supportedStatesFipsCodes } from '../../../data/supported-geos.js'
13
14
 
14
15
  // Components - Core
15
16
  import AdvancedEditor from '@cdc/core/components/AdvancedEditor'
@@ -24,51 +25,17 @@ import UsaGraphic from '@cdc/core/assets/icon-map-usa.svg'
24
25
  import UsaRegionGraphic from '@cdc/core/assets/usa-region-graphic.svg'
25
26
  import WorldGraphic from '@cdc/core/assets/icon-map-world.svg'
26
27
  import AlabamaGraphic from '@cdc/core/assets/icon-map-alabama.svg'
27
- import worldDefaultConfig from '../../examples/default-world.json'
28
- import usaDefaultConfig from '../../examples/default-usa.json'
29
- import countyDefaultConfig from '../../examples/default-county.json'
30
- import useMapLayers from '../hooks/useMapLayers'
28
+ import worldDefaultConfig from '../../../../examples/default-world.json'
29
+ import usaDefaultConfig from '../../../../examples/default-usa.json'
30
+ import countyDefaultConfig from '../../../../examples/default-county.json'
31
+ import useMapLayers from '../../../hooks/useMapLayers.tsx'
31
32
 
32
33
  import { useFilters } from '@cdc/core/components/Filters'
33
34
 
34
- import HexSetting from './HexShapeSettings'
35
- import ConfigContext from '../context'
36
-
37
- const TextField = ({ label, section = null, subsection = null, fieldName, updateField, value: stateValue, type = 'input', tooltip, ...attributes }) => {
38
- const [value, setValue] = useState(stateValue)
39
-
40
- const [debouncedValue] = useDebounce(value, 500)
41
-
42
- useEffect(() => {
43
- if ('string' === typeof debouncedValue && stateValue !== debouncedValue) {
44
- updateField(section, subsection, fieldName, debouncedValue)
45
- }
46
- }, [debouncedValue]) // eslint-disable-line
47
-
48
- let name = subsection ? `${section}-${subsection}-${fieldName}` : `${section}-${subsection}-${fieldName}`
49
-
50
- const onChange = e => setValue(e.target.value)
51
-
52
- let formElement = <input type='text' name={name} onChange={onChange} {...attributes} value={value} />
53
-
54
- if ('textarea' === type) {
55
- formElement = <textarea name={name} onChange={onChange} {...attributes} value={value}></textarea>
56
- }
57
-
58
- if ('number' === type) {
59
- formElement = <input type='number' name={name} onChange={onChange} {...attributes} value={value} />
60
- }
61
-
62
- return (
63
- <label>
64
- <span className='edit-label column-heading'>
65
- {label}
66
- {tooltip}
67
- </span>
68
- {formElement}
69
- </label>
70
- )
71
- }
35
+ import HexSetting from './HexShapeSettings.jsx'
36
+ import ConfigContext from '../../../context.ts'
37
+ import { MapContext } from '../../../types/MapContext.js'
38
+ import { Checkbox, TextField } from './Inputs'
72
39
 
73
40
  // Todo: move to useReducer, seperate files out.
74
41
  const EditorPanel = props => {
@@ -86,7 +53,7 @@ const EditorPanel = props => {
86
53
  setRuntimeFilters,
87
54
  setState,
88
55
  state,
89
- } = useContext(ConfigContext)
56
+ } = useContext<MapContext>(ConfigContext)
90
57
 
91
58
  const { general, columns, legend, table, tooltips } = state
92
59
 
@@ -150,24 +117,6 @@ const EditorPanel = props => {
150
117
  specialClasses = legend.specialClasses || []
151
118
  }
152
119
 
153
- const CheckBox = memo(({ label, value, fieldName, section = null, subsection = null, tooltip, updateField, ...attributes }) => (
154
- <label className='checkbox column-heading'>
155
- <input
156
- type='checkbox'
157
- name={fieldName}
158
- checked={value}
159
- onChange={e => {
160
- updateField(section, subsection, fieldName, !value)
161
- }}
162
- {...attributes}
163
- />
164
- <span className='edit-label'>
165
- {label}
166
- {tooltip}
167
- </span>
168
- </label>
169
- ))
170
-
171
120
  const DynamicDesc = ({ label, fieldName, value: stateValue, type = 'input', ...attributes }) => {
172
121
  const [value, setValue] = useState(stateValue)
173
122
 
@@ -185,6 +134,8 @@ const EditorPanel = props => {
185
134
  }
186
135
 
187
136
  const handleEditorChanges = async (property, value) => {
137
+ console.log('prop', property)
138
+ console.log('value', value)
188
139
  switch (property) {
189
140
  // change these to be more generic.
190
141
  // updateVisualPropertyValue
@@ -2566,6 +2517,22 @@ const EditorPanel = props => {
2566
2517
  </Tooltip>
2567
2518
  }
2568
2519
  />
2520
+ <label className='checkbox'>
2521
+ <input
2522
+ type='checkbox'
2523
+ checked={state.table.wrapColumns}
2524
+ onChange={event => {
2525
+ setState({
2526
+ ...state,
2527
+ table: {
2528
+ ...state.table,
2529
+ wrapColumns: event.target.checked
2530
+ }
2531
+ })
2532
+ }}
2533
+ />
2534
+ <span className='edit-label column-heading'>WRAP DATA TABLE COLUMNS</span>
2535
+ </label>
2569
2536
  <label className='checkbox'>
2570
2537
  <input
2571
2538
  type='checkbox'
@@ -2997,6 +2964,7 @@ const EditorPanel = props => {
2997
2964
  </button>
2998
2965
  </AccordionItemPanel>
2999
2966
  </AccordionItem>
2967
+ {state.general.geoType === 'us' && <Panels.PatternSettings name='Pattern Settings' />}
3000
2968
  </Accordion>
3001
2969
  <AdvancedEditor loadConfig={loadConfig} state={state} convertStateToConfig={convertStateToConfig} />
3002
2970
  </section>
@@ -330,59 +330,10 @@ const HexSettingShapeColumns = props => {
330
330
  )
331
331
  }
332
332
 
333
- const HexMapShapeLegend = props => {
334
- const { state, runtimeLegend, viewport } = props
335
- const { legend } = state
336
- const { title } = state.general
337
-
338
- const columnLogic = legend.position === 'side' && legend.singleColumn ? 'single-column' : legend.position === 'bottom' && legend.singleRow ? 'single-row' : ''
339
-
340
- const getItemShape = shape => {
341
- switch (shape) {
342
- case 'Arrow Down':
343
- return <AiOutlineArrowDown />
344
- case 'Arrow Up':
345
- return <AiOutlineArrowUp />
346
- case 'Arrow Right':
347
- return <AiOutlineArrowRight />
348
- default:
349
- return
350
- }
351
- }
352
-
353
- const { legendClasses } = useDataVizClasses(state, viewport)
354
-
355
- // TODO: create core legend for reusability
356
- return (
357
- state.hexMap.type === 'shapes' &&
358
- state.hexMap.shapeGroups.map((shapeGroup, shapeGroupIndex) => {
359
- return (
360
- <aside id='legend' className={legendClasses.aside.join(' ')} role='region' aria-label='Legend' tabIndex='0'>
361
- <section className={legendClasses.section.join(' ')} aria-label='Map Legend'>
362
- {legend.title && <span className={legendClasses.title.join(' ')}>{parse(shapeGroup.legendTitle)}</span>}
363
- {legend.dynamicDescription === false && legend.description && <p className={legendClasses.description.join(' ')}>{parse(shapeGroup.legendDescription)}</p>}
364
-
365
- <ul className={legendClasses.ul.join(' ')} aria-label='Legend items' style={{ listStyle: 'none' }}>
366
- {shapeGroup.items.map((item, itemIndex) => {
367
- return (
368
- <li className={legendClasses.li.join(' ')}>
369
- {getItemShape(item.shape)} {item.value}
370
- </li>
371
- )
372
- })}
373
- </ul>
374
- </section>
375
- </aside>
376
- )
377
- })
378
- )
379
- }
380
-
381
333
  const HexSetting = () => props.children
382
334
 
383
335
  HexSetting.DisplayShapesOnHex = HexSettingDisplayShapesOnHex
384
336
  HexSetting.DisplayAsHexMap = HexSettingDisplayAsHexMap
385
337
  HexSetting.ShapeColumns = HexSettingShapeColumns
386
- HexSetting.Legend = HexMapShapeLegend
387
338
 
388
339
  export default HexSetting
@@ -0,0 +1,59 @@
1
+ import { memo, useState, useEffect } from 'react'
2
+ import { useDebounce } from 'use-debounce'
3
+
4
+ // todo: look into combining these with core
5
+ const CheckBox = memo(({ label, value, fieldName, section = null, subsection = null, tooltip, updateField, ...attributes }) => (
6
+ <label className='checkbox column-heading'>
7
+ <input
8
+ type='checkbox'
9
+ name={fieldName}
10
+ checked={value}
11
+ onChange={e => {
12
+ updateField(section, subsection, fieldName, !value)
13
+ }}
14
+ {...attributes}
15
+ />
16
+ <span className='edit-label'>
17
+ {label}
18
+ {tooltip}
19
+ </span>
20
+ </label>
21
+ ))
22
+
23
+ const TextField = ({ label, section = null, subsection = null, fieldName, updateField, value: stateValue, type = 'input', tooltip, ...attributes }) => {
24
+ const [value, setValue] = useState(stateValue)
25
+
26
+ const [debouncedValue] = useDebounce(value, 500)
27
+
28
+ useEffect(() => {
29
+ if ('string' === typeof debouncedValue && stateValue !== debouncedValue) {
30
+ updateField(section, subsection, fieldName, debouncedValue)
31
+ }
32
+ }, [debouncedValue]) // eslint-disable-line
33
+
34
+ let name = subsection ? `${section}-${subsection}-${fieldName}` : `${section}-${subsection}-${fieldName}`
35
+
36
+ const onChange = e => setValue(e.target.value)
37
+
38
+ let formElement = <input type='text' name={name} onChange={onChange} {...attributes} value={value} />
39
+
40
+ if ('textarea' === type) {
41
+ formElement = <textarea name={name} onChange={onChange} {...attributes} value={value}></textarea>
42
+ }
43
+
44
+ if ('number' === type) {
45
+ formElement = <input type='number' name={name} onChange={onChange} {...attributes} value={value} />
46
+ }
47
+
48
+ return (
49
+ <label>
50
+ <span className='edit-label column-heading'>
51
+ {label}
52
+ {tooltip}
53
+ </span>
54
+ {formElement}
55
+ </label>
56
+ )
57
+ }
58
+
59
+ export { CheckBox, TextField }
@@ -0,0 +1,140 @@
1
+ import { useContext } from 'react'
2
+ import { Accordion, AccordionItem, AccordionItemHeading, AccordionItemPanel, AccordionItemButton } from 'react-accessible-accordion'
3
+ import ConfigContext from '../../../context'
4
+ import { type MapContext } from '../../../types/MapContext'
5
+ import Button from '@cdc/core/components/elements/Button'
6
+
7
+ type PanelProps = {
8
+ name: string
9
+ }
10
+
11
+ const PatternSettings = ({ name }: PanelProps) => {
12
+ const { state, setState } = useContext<MapContext>(ConfigContext)
13
+ const defaultPattern = 'circles'
14
+ const patternTypes = ['circles', 'waves', 'lines']
15
+
16
+ const {
17
+ map: { patterns },
18
+ data
19
+ } = state
20
+
21
+ /** Updates the map config with a new pattern item */
22
+ const handleAddGeoPattern = () => {
23
+ let patterns = [...state.map.patterns]
24
+ patterns.push({ dataKey: '', pattern: defaultPattern })
25
+ setState({
26
+ ...state,
27
+ map: {
28
+ ...state.map,
29
+ patterns
30
+ }
31
+ })
32
+ }
33
+
34
+ /** Updates the map pattern at a given index */
35
+ const handleUpdateGeoPattern = (value: string, index: number, keyToUpdate: 'dataKey' | 'pattern' | 'dataValue' | 'size' | 'label') => {
36
+ const updatedPatterns = [...state.map.patterns]
37
+ updatedPatterns[index] = { ...updatedPatterns[index], [keyToUpdate]: value }
38
+
39
+ setState({
40
+ ...state,
41
+ map: {
42
+ ...state.map,
43
+ patterns: updatedPatterns
44
+ }
45
+ })
46
+ }
47
+
48
+ const handleRemovePattern = index => {
49
+ const updatedPatterns = state.map.patterns.filter((pattern, i) => i !== index)
50
+
51
+ setState({
52
+ ...state,
53
+ map: {
54
+ ...state.map,
55
+ patterns: updatedPatterns
56
+ }
57
+ })
58
+ }
59
+
60
+ return (
61
+ <AccordionItem>
62
+ <AccordionItemHeading>
63
+ <AccordionItemButton>{name}</AccordionItemButton>
64
+ </AccordionItemHeading>
65
+ <AccordionItemPanel>
66
+ {patterns &&
67
+ patterns.map((pattern, patternIndex) => {
68
+ const dataValueOptions = [...new Set(data?.map(d => d?.[pattern?.dataKey]))]
69
+ const dataKeyOptions = Object.keys(data[0])
70
+ dataValueOptions.unshift('Select')
71
+ dataKeyOptions.unshift('Select')
72
+
73
+ dataValueOptions.sort()
74
+ dataKeyOptions.sort()
75
+
76
+ return (
77
+ <Accordion allowZeroExpanded>
78
+ <AccordionItem>
79
+ <AccordionItemHeading>
80
+ <AccordionItemButton>{pattern.dataKey ? `${pattern.dataKey}: ${pattern.dataValue ?? 'No Value'}` : 'Select Column'}</AccordionItemButton>
81
+ </AccordionItemHeading>
82
+ <AccordionItemPanel>
83
+ <>
84
+ <label htmlFor={`pattern-dataKey--${patternIndex}`}>Data Key:</label>
85
+ <select id={`pattern-dataKey--${patternIndex}`} value={pattern.dataKey !== '' ? pattern.dataKey : 'Select'} onChange={e => handleUpdateGeoPattern(e.target.value, patternIndex, 'dataKey')}>
86
+ {/* TODO: sort these? */}
87
+ {dataKeyOptions.map((d, index) => {
88
+ return (
89
+ <option value={d} key={index}>
90
+ {d}
91
+ </option>
92
+ )
93
+ })}
94
+ </select>
95
+
96
+ <label htmlFor={`pattern-dataValue--${patternIndex}`}>
97
+ Data Value:
98
+ <input type='text' onChange={e => handleUpdateGeoPattern(e.target.value, patternIndex, 'dataValue')} id={`pattern-dataValue--${patternIndex}`} value={pattern.dataValue === '' ? '' : pattern.dataValue} />
99
+ </label>
100
+
101
+ <label htmlFor={`pattern-label--${patternIndex}`}>
102
+ Label (optional):
103
+ <input type='text' onChange={e => handleUpdateGeoPattern(e.target.value, patternIndex, 'label')} id={`pattern-dataValue--${patternIndex}`} value={pattern.label === '' ? '' : pattern.label} />
104
+ </label>
105
+
106
+ <label htmlFor={`pattern-type--${patternIndex}`}>Pattern Type:</label>
107
+ <select id={`pattern-type--${patternIndex}`} value={pattern?.pattern} onChange={e => handleUpdateGeoPattern(e.target.value, patternIndex, 'pattern')}>
108
+ {patternTypes.map((patternName, index) => (
109
+ <option value={patternName} key={index}>
110
+ {patternName}
111
+ </option>
112
+ ))}
113
+ </select>
114
+
115
+ <label htmlFor={`pattern-size--${patternIndex}`}>Pattern Size:</label>
116
+ <select id={`pattern-size--${patternIndex}`} value={pattern?.size} onChange={e => handleUpdateGeoPattern(e.target.value, patternIndex, 'size')}>
117
+ {['small', 'medium', 'large'].map((size, index) => (
118
+ <option value={size} key={index}>
119
+ {size}
120
+ </option>
121
+ ))}
122
+ </select>
123
+ <Button onClick={e => handleRemovePattern(patternIndex)} className='btn btn--remove warn'>
124
+ Remove Pattern
125
+ </Button>
126
+ </>
127
+ </AccordionItemPanel>
128
+ </AccordionItem>
129
+ </Accordion>
130
+ )
131
+ })}
132
+ <button className='btn full-width' onClick={handleAddGeoPattern}>
133
+ Add Geo Pattern
134
+ </button>
135
+ </AccordionItemPanel>
136
+ </AccordionItem>
137
+ )
138
+ }
139
+
140
+ export default PatternSettings
@@ -0,0 +1,7 @@
1
+ import PatternSettings from './Panel.PatternSettings'
2
+
3
+ const Panels = {
4
+ PatternSettings
5
+ }
6
+
7
+ export default Panels
@@ -0,0 +1,3 @@
1
+ import EditorPanel from './components/EditorPanel'
2
+
3
+ export default EditorPanel