@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.
- package/dist/cdcmap.js +52413 -46767
- package/examples/default-patterns.json +581 -0
- package/examples/default-usa.json +159 -57
- package/examples/test.json +0 -9614
- package/examples/zika.json +1194 -0
- package/index.html +17 -6
- package/package.json +3 -3
- package/src/CdcMap.tsx +39 -31
- package/src/components/CityList.jsx +34 -23
- package/src/components/{EditorPanel.jsx → EditorPanel/components/EditorPanel.tsx} +31 -64
- package/src/components/{HexShapeSettings.jsx → EditorPanel/components/HexShapeSettings.tsx} +2 -51
- package/src/components/EditorPanel/components/Inputs.tsx +59 -0
- package/src/components/EditorPanel/components/Panel.PatternSettings.tsx +140 -0
- package/src/components/EditorPanel/components/Panels.tsx +7 -0
- package/src/components/EditorPanel/index.tsx +3 -0
- package/src/components/Geo.jsx +4 -2
- package/src/components/Legend/components/Legend.tsx +183 -0
- package/src/components/Legend/components/LegendItem.Hex.tsx +49 -0
- package/src/components/Legend/components/index.scss +235 -0
- package/src/components/Legend/index.tsx +3 -0
- package/src/components/UsaMap/components/Territory/Territory.Hexagon.tsx +129 -0
- package/src/components/UsaMap/components/Territory/Territory.Rectangle.tsx +66 -0
- package/src/components/UsaMap/components/Territory/index.tsx +9 -0
- package/src/components/{CountyMap.jsx → UsaMap/components/UsaMap.County.tsx} +9 -9
- package/src/components/{UsaRegionMap.jsx → UsaMap/components/UsaMap.Region.tsx} +1 -3
- package/src/components/{SingleStateMap.jsx → UsaMap/components/UsaMap.SingleState.tsx} +8 -10
- package/src/components/{UsaMap.jsx → UsaMap/components/UsaMap.State.tsx} +55 -127
- package/src/components/UsaMap/index.tsx +13 -0
- package/src/components/{WorldMap.jsx → WorldMap/components/WorldMap.jsx} +20 -14
- package/src/components/WorldMap/data/world-topo-guiana-update.json +1 -0
- package/src/components/WorldMap/data/world-topo-old.json +1 -0
- package/src/components/WorldMap/data/world-topo-recent.json +39194 -0
- package/src/components/WorldMap/data/world-topo.json +1 -0
- package/src/components/WorldMap/index.tsx +3 -0
- package/src/context.ts +2 -1
- package/src/data/initial-state.js +5 -3
- package/src/data/supported-geos.js +21 -1
- package/src/hooks/useTooltip.ts +4 -4
- package/src/scss/editor-panel.scss +2 -3
- package/src/scss/main.scss +11 -1
- package/src/scss/map.scss +22 -12
- package/src/types/MapConfig.ts +149 -0
- package/src/types/MapContext.ts +45 -0
- package/src/types/runtimeLegend.ts +1 -0
- package/examples/world-geocode-data.json +0 -18
- package/examples/world-geocode.json +0 -108
- package/src/components/Sidebar.tsx +0 -142
- package/src/data/abbreviations.js +0 -57
- package/src/data/feature-test.json +0 -73
- package/src/data/newtest.json +0 -1
- package/src/data/state-abbreviations.js +0 -60
- package/src/data/supported-cities.csv +0 -165
- package/src/data/test.json +0 -1
- package/src/data/world-topo.json +0 -1
- package/src/scss/sidebar.scss +0 -230
- /package/src/{data → components/UsaMap/data}/cb_2019_us_county_20m.json +0 -0
- /package/src/{data → components/UsaMap/data}/us-hex-topo.json +0 -0
- /package/src/{data → components/UsaMap/data}/us-regions-topo-2.json +0 -0
- /package/src/{data → components/UsaMap/data}/us-regions-topo.json +0 -0
- /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
|
|
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--maps" data-config="/examples/default-geocode.json"></div> -->
|
|
25
37
|
<!-- <div class="react-container react-container--maps" data-config="/examples/default-usa-regions.json"></div> -->
|
|
26
38
|
<!-- <div class="react-container react-container--maps" data-config="/examples/default-single-state.json"></div> -->
|
|
27
|
-
<!-- <div class="react-container react-container--maps" data-config="/examples/default-world.json"></div> -->
|
|
28
39
|
<!-- <div class="react-container react-container--maps" data-config="/examples/bubble-us.json"></div> -->
|
|
29
40
|
<!-- <div class="react-container react-container--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/
|
|
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/
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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": "
|
|
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
|
|
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,
|
|
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 =
|
|
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 =
|
|
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 && <
|
|
1645
|
-
{'us' === geoType && 'us-geocode' !== state.general.type && <UsaMap />}
|
|
1646
|
-
{'us-region' === geoType && <
|
|
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 && <
|
|
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
|
|
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
|
-
|
|
12
|
-
const citiesList = Object.keys(data).filter(item => Object.keys(supportedCities).includes(item))
|
|
11
|
+
const citiesDictionary = {}
|
|
13
12
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
19
|
+
|
|
20
|
+
setCitiesData(citiesDictionary)
|
|
21
|
+
}, [data])
|
|
25
22
|
|
|
26
23
|
if (state.general.type === 'bubble') {
|
|
27
|
-
const maxDataValue = Math.max(...
|
|
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 =
|
|
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
|
-
|
|
40
|
-
|
|
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 =
|
|
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,
|
|
55
|
+
const toolTip = applyTooltipsToGeo(cityDisplayName, geoData || data[city])
|
|
49
56
|
|
|
50
|
-
const radius = state.
|
|
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 (!
|
|
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 (
|
|
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 '
|
|
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 '
|
|
28
|
-
import usaDefaultConfig from '
|
|
29
|
-
import countyDefaultConfig from '
|
|
30
|
-
import useMapLayers from '
|
|
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 '
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
{(
|
|
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
|
-
{
|
|
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
|
|
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
|
|
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
|