@cdc/map 4.23.11 → 4.24.2

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 (60) hide show
  1. package/dist/cdcmap.js +52413 -46767
  2. package/examples/default-patterns.json +581 -0
  3. package/examples/default-usa.json +159 -57
  4. package/examples/test.json +0 -9614
  5. package/examples/zika.json +1194 -0
  6. package/index.html +17 -6
  7. package/package.json +3 -3
  8. package/src/CdcMap.tsx +39 -31
  9. package/src/components/CityList.jsx +34 -23
  10. package/src/components/{EditorPanel.jsx → EditorPanel/components/EditorPanel.tsx} +31 -64
  11. package/src/components/{HexShapeSettings.jsx → EditorPanel/components/HexShapeSettings.tsx} +2 -51
  12. package/src/components/EditorPanel/components/Inputs.tsx +59 -0
  13. package/src/components/EditorPanel/components/Panel.PatternSettings.tsx +140 -0
  14. package/src/components/EditorPanel/components/Panels.tsx +7 -0
  15. package/src/components/EditorPanel/index.tsx +3 -0
  16. package/src/components/Geo.jsx +4 -2
  17. package/src/components/Legend/components/Legend.tsx +183 -0
  18. package/src/components/Legend/components/LegendItem.Hex.tsx +49 -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 -10
  27. package/src/components/{UsaMap.jsx → UsaMap/components/UsaMap.State.tsx} +55 -127
  28. package/src/components/UsaMap/index.tsx +13 -0
  29. package/src/components/{WorldMap.jsx → WorldMap/components/WorldMap.jsx} +20 -14
  30. package/src/components/WorldMap/data/world-topo-guiana-update.json +1 -0
  31. package/src/components/WorldMap/data/world-topo-old.json +1 -0
  32. package/src/components/WorldMap/data/world-topo-recent.json +39194 -0
  33. package/src/components/WorldMap/data/world-topo.json +1 -0
  34. package/src/components/WorldMap/index.tsx +3 -0
  35. package/src/context.ts +2 -1
  36. package/src/data/initial-state.js +5 -3
  37. package/src/data/supported-geos.js +21 -1
  38. package/src/hooks/useTooltip.ts +4 -4
  39. package/src/scss/editor-panel.scss +2 -3
  40. package/src/scss/main.scss +11 -1
  41. package/src/scss/map.scss +22 -12
  42. package/src/types/MapConfig.ts +149 -0
  43. package/src/types/MapContext.ts +45 -0
  44. package/src/types/runtimeLegend.ts +1 -0
  45. package/examples/world-geocode-data.json +0 -18
  46. package/examples/world-geocode.json +0 -108
  47. package/src/components/Sidebar.tsx +0 -142
  48. package/src/data/abbreviations.js +0 -57
  49. package/src/data/feature-test.json +0 -73
  50. package/src/data/newtest.json +0 -1
  51. package/src/data/state-abbreviations.js +0 -60
  52. package/src/data/supported-cities.csv +0 -165
  53. package/src/data/test.json +0 -1
  54. package/src/data/world-topo.json +0 -1
  55. package/src/scss/sidebar.scss +0 -230
  56. /package/src/{data → components/UsaMap/data}/cb_2019_us_county_20m.json +0 -0
  57. /package/src/{data → components/UsaMap/data}/us-hex-topo.json +0 -0
  58. /package/src/{data → components/UsaMap/data}/us-regions-topo-2.json +0 -0
  59. /package/src/{data → components/UsaMap/data}/us-regions-topo.json +0 -0
  60. /package/src/{data → components/UsaMap/data}/us-topo.json +0 -0
package/index.html CHANGED
@@ -11,12 +11,24 @@
11
11
  .cdc-map-outer-container {
12
12
  min-height: 100vh;
13
13
  }
14
+ /* .alaska,
15
+ .hawaii {
16
+ display: none;
17
+ } */
14
18
  </style>
15
19
  </head>
16
20
 
17
21
  <body>
18
22
  <!-- DEFAULT EXAMPLES -->
19
- <div class="react-container react-container--maps" data-config="/examples/private/tooltip-issue.json"></div>
23
+ <!-- <div class="react-container" data-config="/examples/private/antarctica.json"></div> -->
24
+ <!-- <div class="react-container" data-config="/examples/private/zika-issue.json"></div> -->
25
+
26
+ <!-- <div class="react-container react-container--maps" data-config="/examples/private/tooltip-issue.json"></div> -->
27
+ <!-- <div class="react-container react-container--maps" data-config="/examples/test.json"></div> -->
28
+ <!-- <div class="react-container react-container--maps" data-config="/examples/default-patterns.json"></div> -->
29
+ <!-- <div class="react-container react-container--maps" data-config="/examples/test.json"></div> -->
30
+ <!-- <div class="react-container react-container--maps" data-config="/examples/private/map-text-wrap.json"></div> -->
31
+ <!-- <div class="react-container react-container--maps" data-config="/examples/private/tooltip-issue.json"></div> -->
20
32
  <!-- <div class="react-container react-container--maps" data-config="/examples/test.json"></div> -->
21
33
  <!-- <div class="react-container react-container--maps" data-config="/examples/default-county.json"></div> -->
22
34
  <!-- <div class="react-container react-container--maps" data-config="/examples/default-usa.json"></div> -->
@@ -24,7 +36,6 @@
24
36
  <!-- <div class="react-container react-container&#45;&#45;maps" data-config="/examples/default-geocode.json"></div> -->
25
37
  <!-- <div class="react-container react-container&#45;&#45;maps" data-config="/examples/default-usa-regions.json"></div> -->
26
38
  <!-- <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
39
  <!-- <div class="react-container react-container&#45;&#45;maps" data-config="/examples/bubble-us.json"></div> -->
29
40
  <!-- <div class="react-container react-container&#45;&#45;maps" data-config="/examples/bubble-world.json"></div> -->
30
41
 
@@ -32,15 +43,15 @@
32
43
  <!-- <div class="react-container" data-config="/examples/private/wastewater.json"></div> -->
33
44
 
34
45
  <!-- TP4 EXAMPLES -->
35
- <!-- <div class="react-container react-container--maps" data-config="/examples/example-city-state.json"></div> -->
46
+ <!-- <div class="react-container react-container--maps" data-config="/examples/private/solr.json"></div> -->
36
47
  <!-- <div class="react-container react-container--maps" data-config="/examples/custom-map-layers.json"></div> -->
37
48
  <!-- <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> -->
49
+ <!-- <div class="react-container react-container--maps" data-config="/examples/private/world-map.json"></div> -->
39
50
  <!-- <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>
51
+ <!-- <div class="react-container react-container--maps" data-config="/examples/hex-with-arrows.json"></div> -->
41
52
 
42
53
  <!-- TP4 EXAMPLES -->
43
- <!-- <div class="react-container react-container--maps" data-config="/examples/example-city-state.json"></div> -->
54
+ <div class="react-container react-container--maps" data-config="/examples/example-city-state.json"></div>
44
55
  <!-- <div class="react-container react-container--maps" data-config="/examples/example-city-state-no-territories.json"></div> -->
45
56
  <!-- <div class="react-container react-container--maps" data-config="/examples/example-world-map.json"></div> -->
46
57
  <!-- <div class="react-container react-container--maps" data-config="/examples/default-hex.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.2",
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.2",
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": "edde49c96dee146de5e3a4537880b1bcf4dbee08"
55
55
  }
package/src/CdcMap.tsx CHANGED
@@ -45,16 +45,14 @@ 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'
55
+ import { isSolrCsv, isSolrJson } from '@cdc/core/helpers/isSolr'
58
56
 
59
57
  // Data props
60
58
  const stateKeys = Object.keys(supportedStates)
@@ -118,7 +116,7 @@ const getUniqueValues = (data, columnName) => {
118
116
  return Object.keys(result)
119
117
  }
120
118
 
121
- const CdcMap = ({ className, config, navigationHandler: customNavigationHandler, isDashboard = false, isEditor = false, isDebug = false, configUrl, logo = '', setConfig, setSharedFilter, setSharedFilterValue, hostname = 'localhost:8080', link }) => {
119
+ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler, isDashboard = false, isEditor = false, isDebug = false, configUrl, logo = '', setConfig, setSharedFilter, setSharedFilterValue, link }) => {
122
120
  const transform = new DataTransform()
123
121
  const [state, setState] = useState({ ...initialState })
124
122
  const [loading, setLoading] = useState(true)
@@ -242,8 +240,8 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
242
240
  }
243
241
 
244
242
  // Cities
245
- if (!uid) {
246
- uid = cityKeys.find(key => key === geoName)
243
+ if (!uid && geoName) {
244
+ uid = cityKeys.find(key => key === geoName.toUpperCase())
247
245
  }
248
246
  }
249
247
 
@@ -270,6 +268,11 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
270
268
  if (!uid && 'world-geocode' === state.general.type) {
271
269
  uid = cityKeys.find(key => key === geoName?.toUpperCase())
272
270
  }
271
+
272
+ // Cities
273
+ if (!uid && geoName) {
274
+ uid = cityKeys.find(key => key === geoName.toUpperCase())
275
+ }
273
276
  }
274
277
 
275
278
  // County Check
@@ -282,6 +285,10 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
282
285
  uid = row[state.columns.geo.name]
283
286
  }
284
287
 
288
+ if (!uid && state.columns.latitude?.name && state.columns.longitude?.name && row[state.columns.latitude?.name] && row[state.columns.longitude?.name]) {
289
+ uid = row[state.columns.geo.name]
290
+ }
291
+
285
292
  if (uid) {
286
293
  Object.defineProperty(row, 'uid', {
287
294
  value: uid,
@@ -690,12 +697,7 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
690
697
 
691
698
  dataSet.forEach((row, dataIndex) => {
692
699
  let number = row[state.columns.primary.name]
693
- let updated = 0
694
-
695
- // check if we're seperating zero out
696
- updated = state.legend.separateZero && hasZeroInData ? index : index
697
- // check for special classes
698
- updated = state.legend.specialClasses ? updated + state.legend.specialClasses.length : index
700
+ let updated = result.length - 1
699
701
 
700
702
  if (result[updated]?.min === (null || undefined) || result[updated]?.max === (null || undefined)) return
701
703
 
@@ -1077,6 +1079,12 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
1077
1079
  // Attempts to find the corresponding value
1078
1080
  const displayGeoName = key => {
1079
1081
  if (!state.general.convertFipsCodes) return key
1082
+
1083
+ // World Map
1084
+ // If we're returning a city name instead of a country ISO code, capitalize it for the data table.
1085
+ if (state.type === 'map' && state.general.geoType === 'world') {
1086
+ if (String(key).length > 3) return titleCase(key)
1087
+ }
1080
1088
  let value = key
1081
1089
  // Map to first item in values array which is the preferred label
1082
1090
  if (stateKeys.includes(value)) {
@@ -1088,7 +1096,7 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
1088
1096
  }
1089
1097
 
1090
1098
  if (countryKeys.includes(value)) {
1091
- value = titleCase(supportedCountries[key][0])
1099
+ value = supportedCountries[key][0]
1092
1100
  }
1093
1101
 
1094
1102
  if (countyKeys.includes(value)) {
@@ -1269,18 +1277,19 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
1269
1277
  const regex = /(?:\.([^.]+))?$/
1270
1278
 
1271
1279
  const ext = regex.exec(dataUrl.pathname)[1]
1272
- if ('csv' === ext) {
1280
+ if ('csv' === ext || isSolrCsv(dataUrlFinal)) {
1273
1281
  data = await fetch(dataUrlFinal)
1274
1282
  .then(response => response.text())
1275
1283
  .then(responseText => {
1276
1284
  const parsedCsv = Papa.parse(responseText, {
1277
1285
  header: true,
1278
1286
  dynamicTyping: true,
1279
- skipEmptyLines: true
1287
+ skipEmptyLines: true,
1288
+ encoding: 'utf-8'
1280
1289
  })
1281
1290
  return parsedCsv.data
1282
1291
  })
1283
- } else if ('json' === ext) {
1292
+ } else if ('json' === ext || isSolrJson(dataUrlFinal)) {
1284
1293
  data = await fetch(dataUrlFinal).then(response => response.json())
1285
1294
  } else {
1286
1295
  data = []
@@ -1296,6 +1305,8 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
1296
1305
  data = transform.developerStandardize(data, state.dataDescription)
1297
1306
  }
1298
1307
 
1308
+ console.log('data', data)
1309
+
1299
1310
  setState({ ...state, runtimeDataUrl: dataUrlFinal, data })
1300
1311
  }
1301
1312
  }
@@ -1313,10 +1324,6 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
1313
1324
  const urlFilters = newState.filters ? (newState.filters.filter(filter => filter.type === 'url').length > 0 ? true : false) : false
1314
1325
 
1315
1326
  if (newState.dataUrl && !urlFilters) {
1316
- if (newState.dataUrl[0] === '/') {
1317
- newState.dataUrl = 'http://' + hostname + newState.dataUrl
1318
- }
1319
-
1320
1327
  // handle urls with spaces in the name.
1321
1328
  if (newState.dataUrl) newState.dataUrl = `${newState.dataUrl}`
1322
1329
  let newData = await fetchRemoteData(newState.dataUrl, 'map')
@@ -1462,6 +1469,7 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
1462
1469
  // Data
1463
1470
  if (hashData !== runtimeData.fromHash && state.data?.fromColumn) {
1464
1471
  const newRuntimeData = generateRuntimeData(state, filters || runtimeFilters, hashData)
1472
+
1465
1473
  setRuntimeData(newRuntimeData)
1466
1474
  } else {
1467
1475
  if (hashLegend !== runtimeLegend.fromHash && undefined === runtimeData.init) {
@@ -1477,7 +1485,7 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
1477
1485
  // Legend - Update when runtimeData does
1478
1486
  const legend = generateRuntimeLegend(state, runtimeData, hashLegend)
1479
1487
  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
1488
+ }, [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
1489
 
1482
1490
  useEffect(() => {
1483
1491
  reloadURLData()
@@ -1616,6 +1624,9 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
1616
1624
  config={config}
1617
1625
  classes={['map-title', general.showTitle === true ? 'visible' : 'hidden', `${general.headerColor}`]}
1618
1626
  />
1627
+ <a id='skip-geo-container' className='cdcdataviz-sr-only-focusable' href={tabId}>
1628
+ Skip Over Map Container
1629
+ </a>
1619
1630
  {general.introText && <section className='introText'>{parse(general.introText)}</section>}
1620
1631
 
1621
1632
  {/* prettier-ignore */}
@@ -1632,26 +1643,22 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
1632
1643
  }
1633
1644
  }}
1634
1645
  >
1635
- <a id='skip-geo-container' className='cdcdataviz-sr-only-focusable' href={tabId}>
1636
- Skip Over Map Container
1637
- </a>
1638
-
1639
1646
  {/* eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex */}
1640
1647
  <section className='outline-none geography-container' ref={mapSvg} tabIndex='0' style={{ width: '100%' }}>
1641
1648
  {currentViewport && (
1642
1649
  <>
1643
1650
  {modal && <Modal />}
1644
- {'single-state' === geoType && <SingleStateMap />}
1645
- {'us' === geoType && 'us-geocode' !== state.general.type && <UsaMap />}
1646
- {'us-region' === geoType && <UsaRegionMap />}
1651
+ {'single-state' === geoType && <UsaMap.SingleState />}
1652
+ {'us' === geoType && 'us-geocode' !== state.general.type && <UsaMap.State />}
1653
+ {'us-region' === geoType && <UsaMap.Region />}
1654
+ {'us-county' === geoType && <UsaMap.County />}
1647
1655
  {'world' === geoType && <WorldMap />}
1648
- {'us-county' === geoType && <CountyMap />}
1649
1656
  {'data' === general.type && logo && <img src={logo} alt='' className='map-logo' />}
1650
1657
  </>
1651
1658
  )}
1652
1659
  </section>
1653
1660
 
1654
- {general.showSidebar && 'navigation' !== general.type && <Sidebar />}
1661
+ {general.showSidebar && 'navigation' !== general.type && <Legend />}
1655
1662
  </div>
1656
1663
 
1657
1664
  {'navigation' === general.type && <NavigationMenu mapTabbingID={tabId} displayGeoName={displayGeoName} data={runtimeData} options={general} columns={state.columns} navigationHandler={val => navigationHandler(val)} />}
@@ -1694,6 +1701,7 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
1694
1701
  outerContainerRef={outerContainerRef}
1695
1702
  imageRef={imageId}
1696
1703
  isDebug={isDebug}
1704
+ wrapColumns={table.wrapColumns}
1697
1705
  />
1698
1706
  )}
1699
1707
 
@@ -4,50 +4,57 @@ import { jsx } from '@emotion/react'
4
4
  import { supportedCities } from '../data/supported-geos'
5
5
  import { scaleLinear } from 'd3-scale'
6
6
 
7
- const CityList = ({ data, state, geoClickHandler, applyTooltipsToGeo, displayGeoName, applyLegendToRow, projection, titleCase, setSharedFilterValue, isFilterValueSupported, isGeoCodeMap }) => {
7
+ const CityList = ({ data, state, geoClickHandler, applyTooltipsToGeo, displayGeoName, applyLegendToRow, projection, titleCase, setSharedFilterValue, isFilterValueSupported }) => {
8
8
  const [citiesData, setCitiesData] = useState({})
9
9
 
10
10
  useEffect(() => {
11
- if (!isGeoCodeMap) {
12
- const citiesList = Object.keys(data).filter(item => Object.keys(supportedCities).includes(item))
11
+ const citiesDictionary = {}
13
12
 
14
- const citiesDictionary = {}
15
-
16
- citiesList.map(city => (citiesDictionary[city] = data[city]))
17
-
18
- setCitiesData(citiesDictionary)
19
- } else {
20
- const citiesDictionary = {}
21
- state.data.map(city => (citiesDictionary[city[state.columns.geo.name]] = city))
22
- setCitiesData(citiesDictionary)
13
+ if (data) {
14
+ Object.keys(data).forEach(key => {
15
+ const city = data[key]
16
+ citiesDictionary[city[state.columns.geo.name]] = city
17
+ })
23
18
  }
24
- }, [data, state.data])
19
+
20
+ setCitiesData(citiesDictionary)
21
+ }, [data])
25
22
 
26
23
  if (state.general.type === 'bubble') {
27
- const maxDataValue = Math.max(...state.data.map(d => d[state.columns.primary.name]))
24
+ const maxDataValue = Math.max(...(data ? Object.keys(data).map(key => data[key][state.columns.primary.name]) : [0]))
28
25
  const sortedRuntimeData = Object.values(data).sort((a, b) => (a[state.columns.primary.name] < b[state.columns.primary.name] ? 1 : -1))
29
26
  if (!sortedRuntimeData) return
30
27
 
31
28
  // Set bubble sizes
32
29
  var size = scaleLinear().domain([1, maxDataValue]).range([state.visual.minBubbleSize, state.visual.maxBubbleSize])
33
30
  }
34
- let cityList = isGeoCodeMap ? Object.keys(citiesData).filter(c => undefined !== c) : Object.keys(citiesData).filter(c => undefined !== data[c])
31
+ let cityList = Object.keys(citiesData).filter(c => undefined !== c || undefined !== data[c])
35
32
  if (!cityList) return true
36
33
 
37
34
  // Cities output
38
35
  const cities = cityList.map((city, i) => {
39
- const geoData = isGeoCodeMap ? state.data.filter(item => city === item[state.columns.geo.name])[0] : data[city]
40
- const cityDisplayName = isGeoCodeMap ? city : titleCase(displayGeoName(city))
36
+ let geoData
37
+ if (data) {
38
+ Object.keys(data).forEach(key => {
39
+ if (city === data[key][state.columns.geo.name]) {
40
+ geoData = data[key]
41
+ }
42
+ })
43
+ }
44
+ if (!geoData) {
45
+ geoData = data ? data[city] : undefined
46
+ }
47
+ const cityDisplayName = titleCase(displayGeoName(city))
41
48
 
42
- const legendColors = isGeoCodeMap && geoData ? applyLegendToRow(geoData) : data[city] ? applyLegendToRow(data[city]) : false
49
+ const legendColors = geoData ? applyLegendToRow(geoData) : data[city] ? applyLegendToRow(data[city]) : false
43
50
 
44
51
  if (legendColors === false) {
45
52
  return true
46
53
  }
47
54
 
48
- const toolTip = applyTooltipsToGeo(cityDisplayName, isGeoCodeMap ? geoData : data[city])
55
+ const toolTip = applyTooltipsToGeo(cityDisplayName, geoData || data[city])
49
56
 
50
- const radius = state.general.geoType === 'us' && !isGeoCodeMap ? 8 : isGeoCodeMap ? state.visual.geoCodeCircleSize : 4
57
+ const radius = state.visual.geoCodeCircleSize || 8
51
58
 
52
59
  const additionalProps = {
53
60
  fillOpacity: state.general.type === 'bubble' ? 0.4 : 1
@@ -71,18 +78,22 @@ const CityList = ({ data, state, geoClickHandler, applyTooltipsToGeo, displayGeo
71
78
 
72
79
  let transform = ''
73
80
 
74
- if (!isGeoCodeMap) {
75
- transform = `translate(${projection(supportedCities[city])})`
81
+ if (!geoData?.[state.columns.longitude.name] && !geoData?.[state.columns.latitude.name] && city && supportedCities[city.toUpperCase()]) {
82
+ transform = `translate(${projection(supportedCities[city.toUpperCase()])})`
76
83
  }
77
84
 
78
85
  let needsPointer = false
79
86
 
80
- if (isGeoCodeMap) {
87
+ if (geoData?.[state.columns.longitude.name] && geoData?.[state.columns.latitude.name]) {
81
88
  let coords = [Number(geoData?.[state.columns.longitude.name]), Number(geoData?.[state.columns.latitude.name])]
82
89
  transform = `translate(${projection(coords)})`
83
90
  needsPointer = true
84
91
  }
85
92
 
93
+ if (!transform) {
94
+ return
95
+ }
96
+
86
97
  const styles = {
87
98
  fill: legendColors[0],
88
99
  opacity: setSharedFilterValue && isFilterValueSupported && data[city][state.columns.geo.name] !== setSharedFilterValue ? 0.5 : 1,
@@ -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
 
@@ -327,6 +276,7 @@ const EditorPanel = props => {
327
276
  setState({
328
277
  ...state,
329
278
  visual: {
279
+ ...state.visual,
330
280
  cityStyle: value
331
281
  }
332
282
  })
@@ -1978,7 +1928,7 @@ const EditorPanel = props => {
1978
1928
  </label>
1979
1929
  </fieldset>
1980
1930
  )}
1981
- {('us-geocode' === state.general.type || 'world-geocode' === state.general.type) && (
1931
+ {(
1982
1932
  <>
1983
1933
  <label>Latitude Column</label>
1984
1934
  <select
@@ -2566,6 +2516,22 @@ const EditorPanel = props => {
2566
2516
  </Tooltip>
2567
2517
  }
2568
2518
  />
2519
+ <label className='checkbox'>
2520
+ <input
2521
+ type='checkbox'
2522
+ checked={state.table.wrapColumns}
2523
+ onChange={event => {
2524
+ setState({
2525
+ ...state,
2526
+ table: {
2527
+ ...state.table,
2528
+ wrapColumns: event.target.checked
2529
+ }
2530
+ })
2531
+ }}
2532
+ />
2533
+ <span className='edit-label column-heading'>WRAP DATA TABLE COLUMNS</span>
2534
+ </label>
2569
2535
  <label className='checkbox'>
2570
2536
  <input
2571
2537
  type='checkbox'
@@ -2883,7 +2849,7 @@ const EditorPanel = props => {
2883
2849
  )
2884
2850
  })}
2885
2851
  </ul>
2886
- {('us-geocode' === state.general.type || 'world-geocode' === state.general.type) && state.visual.cityStyle === 'circle' && (
2852
+ {state.visual.cityStyle === 'circle' && (
2887
2853
  <label>
2888
2854
  Geocode Settings
2889
2855
  <TextField type='number' value={state.visual.geoCodeCircleSize} section='visual' max='10' fieldName='geoCodeCircleSize' label='Geocode Circle Size' updateField={updateField} />
@@ -2997,6 +2963,7 @@ const EditorPanel = props => {
2997
2963
  </button>
2998
2964
  </AccordionItemPanel>
2999
2965
  </AccordionItem>
2966
+ {state.general.geoType === 'us' && <Panels.PatternSettings name='Pattern Settings' />}
3000
2967
  </Accordion>
3001
2968
  <AdvancedEditor loadConfig={loadConfig} state={state} convertStateToConfig={convertStateToConfig} />
3002
2969
  </section>
@@ -255,7 +255,7 @@ const HexSettingShapeColumns = props => {
255
255
  ...group.items,
256
256
  {
257
257
  key: '',
258
- shape: 'Arrow up',
258
+ shape: 'Arrow Up',
259
259
  column: '',
260
260
  operator: '=',
261
261
  value: ''
@@ -309,7 +309,7 @@ const HexSettingShapeColumns = props => {
309
309
  copy.push({
310
310
  legendTitle: '',
311
311
  legendDescription: '',
312
- items: [{ key: '', shape: 'Arrow up', column: '', operator: '=', value: '' }]
312
+ items: [{ key: '', shape: 'Arrow Up', column: '', operator: '=', value: '' }]
313
313
  })
314
314
  copy.legendTitle = ''
315
315
  copy.legendDescription = ''
@@ -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