@cdc/map 4.23.6 → 4.23.7
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 +21749 -21467
- package/index.html +7 -6
- package/package.json +3 -3
- package/src/CdcMap.jsx +28 -76
- package/src/components/CountyMap.jsx +11 -3
- package/src/components/EditorPanel.jsx +68 -67
- package/src/data/initial-state.js +2 -1
- package/src/hooks/useTooltip.js +135 -0
- package/src/scss/editor-panel.scss +1 -7
- package/src/scss/main.scss +1 -1
- package/src/scss/map.scss +202 -203
- package/LICENSE +0 -201
- package/src/scss/tooltips.scss +0 -36
package/index.html
CHANGED
|
@@ -16,12 +16,13 @@
|
|
|
16
16
|
|
|
17
17
|
<body>
|
|
18
18
|
<!-- DEFAULT EXAMPLES -->
|
|
19
|
-
<div class="react-container react-container--maps" data-config="/examples/default-usa.json"></div>
|
|
19
|
+
<!-- <div class="react-container react-container--maps" data-config="/examples/default-usa.json"></div> -->
|
|
20
|
+
<div class="react-container react-container--maps" data-config="/examples/default-county.json"></div>
|
|
20
21
|
<!-- <div class="react-container react-container--maps" data-config="/examples/default-geocode.json"></div> -->
|
|
21
22
|
<!-- <div class="react-container react-container--maps" data-config="/examples/default-usa-regions.json"></div> -->
|
|
22
23
|
<!-- <div class="react-container react-container--maps" data-config="/examples/default-single-state.json"></div> -->
|
|
23
24
|
<!-- <div class="react-container react-container--maps" data-config="/examples/default-world.json"></div> -->
|
|
24
|
-
<!--
|
|
25
|
+
<!-- <div class="react-container react-container--maps" data-config="/examples/bubble-us.json"></div> -->
|
|
25
26
|
<!-- <div class="react-container react-container--maps" data-config="/examples/bubble-world.json"></div> -->
|
|
26
27
|
|
|
27
28
|
<!-- TP4 EXAMPLES -->
|
|
@@ -29,15 +30,15 @@
|
|
|
29
30
|
<!-- <div class="react-container react-container--maps" data-config="/examples/custom-map-layers.json"></div> -->
|
|
30
31
|
<!-- <div class="react-container react-container--maps" data-config="/examples/example-city-stateBAD.json"></div> -->
|
|
31
32
|
<!-- <div class="react-container react-container--maps" data-config="/examples/example-world-map.json"></div> -->
|
|
32
|
-
<!-- <div class="react-container react-container--maps" data-config="/examples/default-hex.json"></div>
|
|
33
|
+
<!-- <div class="react-container react-container--maps" data-config="/examples/default-hex.json"></div> -->
|
|
33
34
|
|
|
34
35
|
<!-- TP4 EXAMPLES -->
|
|
35
36
|
<!-- <div class="react-container react-container--maps" data-config="/examples/example-city-state.json"></div> -->
|
|
36
|
-
|
|
37
|
+
<div class="react-container react-container--maps" data-config="/examples/example-city-state-no-territories.json"></div>
|
|
37
38
|
<!-- <div class="react-container react-container--maps" data-config="/examples/example-world-map.json"></div> -->
|
|
38
|
-
<!-- <div class="react-container react-container--maps" data-config="/examples/default-hex.json"></div>
|
|
39
|
+
<!-- <div class="react-container react-container--maps" data-config="/examples/default-hex.json"></div> -->
|
|
39
40
|
|
|
40
|
-
<div class="react-container" data-config="/examples/example-hex-map-with-filter.json"></div>
|
|
41
|
+
<!-- <div class="react-container" data-config="/examples/example-hex-map-with-filter.json"></div> -->
|
|
41
42
|
|
|
42
43
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
|
43
44
|
<script type="module" src="./src/index.jsx"></script>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cdc/map",
|
|
3
|
-
"version": "4.23.
|
|
3
|
+
"version": "4.23.7",
|
|
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.
|
|
27
|
+
"@cdc/core": "^4.23.7",
|
|
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": "6c7ac5215dcf3bc1cc7d199089c8c2e75f53a93e"
|
|
55
55
|
}
|
package/src/CdcMap.jsx
CHANGED
|
@@ -51,6 +51,7 @@ import SingleStateMap from './components/SingleStateMap' // Future: Lazy
|
|
|
51
51
|
import UsaMap from './components/UsaMap' // Future: Lazy
|
|
52
52
|
import UsaRegionMap from './components/UsaRegionMap' // Future: Lazy
|
|
53
53
|
import WorldMap from './components/WorldMap' // Future: Lazy
|
|
54
|
+
import useTooltip from './hooks/useTooltip'
|
|
54
55
|
|
|
55
56
|
// Data props
|
|
56
57
|
const stateKeys = Object.keys(supportedStates)
|
|
@@ -999,81 +1000,6 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
|
|
|
999
1000
|
}
|
|
1000
1001
|
}
|
|
1001
1002
|
|
|
1002
|
-
const applyTooltipsToGeo = (geoName, row, returnType = 'string') => {
|
|
1003
|
-
if (!row) return
|
|
1004
|
-
let toolTipText = ''
|
|
1005
|
-
|
|
1006
|
-
// Adds geo label, ie State: Georgia
|
|
1007
|
-
let stateOrCounty = state.general.geoType === 'us' ? 'State: ' : state.general.geoType === 'us-county' || state.general.geoType === 'single-state' ? 'County: ' : ''
|
|
1008
|
-
|
|
1009
|
-
// check the override
|
|
1010
|
-
stateOrCounty = state.general.geoLabelOverride !== '' ? state.general.geoLabelOverride + ': ' : stateOrCounty
|
|
1011
|
-
|
|
1012
|
-
if (state.general.geoType === 'us-county' && state.general.type !== 'us-geocode') {
|
|
1013
|
-
let stateFipsCode = row[state.columns.geo.name].substring(0, 2)
|
|
1014
|
-
const stateName = supportedStatesFipsCodes[stateFipsCode]
|
|
1015
|
-
|
|
1016
|
-
toolTipText += !state.general.hideGeoColumnInTooltip ? `<strong>Location: ${stateName}</strong><br/>` : `<strong>${stateName}</strong><br/>`
|
|
1017
|
-
}
|
|
1018
|
-
|
|
1019
|
-
toolTipText += !state.general.hideGeoColumnInTooltip ? `<strong>${stateOrCounty}${displayGeoName(geoName)}</strong>` : `<strong>${displayGeoName(geoName)}</strong>`
|
|
1020
|
-
|
|
1021
|
-
if (('data' === state.general.type || state.general.type === 'bubble' || state.general.type === 'us-geocode' || state.general.type === 'world-geocode') && undefined !== row) {
|
|
1022
|
-
toolTipText += `<dl>`
|
|
1023
|
-
|
|
1024
|
-
Object.keys(state.columns).forEach(columnKey => {
|
|
1025
|
-
const column = state.columns[columnKey]
|
|
1026
|
-
|
|
1027
|
-
if (true === column.tooltip) {
|
|
1028
|
-
let label = column.label?.length > 0 ? column.label : ''
|
|
1029
|
-
|
|
1030
|
-
let value
|
|
1031
|
-
|
|
1032
|
-
if (state.legend.specialClasses && state.legend.specialClasses.length && typeof state.legend.specialClasses[0] === 'object') {
|
|
1033
|
-
// THIS CODE SHOULD NOT ACT ON THE ENTIRE ROW OF KEYS BUT ONLY THE ONE KEY IN THE SPECIAL CLASS
|
|
1034
|
-
for (let i = 0; i < state.legend.specialClasses.length; i++) {
|
|
1035
|
-
// Special Classes label in HOVERS should only apply to selected special class key
|
|
1036
|
-
// - you have to ALSO check that the key matches - putting here otherwise the if stmt too long
|
|
1037
|
-
if (column.name === state.legend.specialClasses[i].key) {
|
|
1038
|
-
if (String(row[state.legend.specialClasses[i].key]) === state.legend.specialClasses[i].value) {
|
|
1039
|
-
value = displayDataAsText(state.legend.specialClasses[i].label, columnKey)
|
|
1040
|
-
break
|
|
1041
|
-
}
|
|
1042
|
-
}
|
|
1043
|
-
}
|
|
1044
|
-
}
|
|
1045
|
-
|
|
1046
|
-
if (!value) {
|
|
1047
|
-
value = displayDataAsText(row[column.name], columnKey)
|
|
1048
|
-
}
|
|
1049
|
-
|
|
1050
|
-
if (0 < value.length) {
|
|
1051
|
-
// Only spit out the tooltip if there's a value there
|
|
1052
|
-
toolTipText += state.general.hidePrimaryColumnInTooltip ? `<div><dd>${value}</dd></div>` : `<div><dt>${label}</dt><dd>${value}</dd></div>`
|
|
1053
|
-
}
|
|
1054
|
-
}
|
|
1055
|
-
})
|
|
1056
|
-
toolTipText += `</dl>`
|
|
1057
|
-
}
|
|
1058
|
-
|
|
1059
|
-
// We convert the markup into JSX and add a navigation link if it's going into a modal.
|
|
1060
|
-
if ('jsx' === returnType) {
|
|
1061
|
-
toolTipText = [<div key='modal-content'>{parse(toolTipText)}</div>]
|
|
1062
|
-
|
|
1063
|
-
if (state.columns.hasOwnProperty('navigate') && row[state.columns.navigate.name]) {
|
|
1064
|
-
toolTipText.push(
|
|
1065
|
-
// eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions
|
|
1066
|
-
<span className='navigation-link' key='modal-navigation-link' onClick={() => navigationHandler(row[state.columns.navigate.name])}>
|
|
1067
|
-
{state.tooltips.linkLabel}
|
|
1068
|
-
<ExternalIcon className='inline-icon ml-1' />
|
|
1069
|
-
</span>
|
|
1070
|
-
)
|
|
1071
|
-
}
|
|
1072
|
-
}
|
|
1073
|
-
|
|
1074
|
-
return toolTipText
|
|
1075
|
-
}
|
|
1076
|
-
|
|
1077
1003
|
// if city has a hyphen then in tooltip it ends up UPPER CASE instead of just regular Upper Case
|
|
1078
1004
|
// - this function is used to prevent that and instead give the formatting that is wanted
|
|
1079
1005
|
// Example: Desired city display in tooltip on map: "Inter-Tribal Indian Reservation"
|
|
@@ -1173,6 +1099,30 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
|
|
|
1173
1099
|
}
|
|
1174
1100
|
}
|
|
1175
1101
|
|
|
1102
|
+
// todo: convert to store or context eventually.
|
|
1103
|
+
const { buildTooltip } = useTooltip({ state, displayGeoName, displayDataAsText, supportedStatesFipsCodes })
|
|
1104
|
+
|
|
1105
|
+
const applyTooltipsToGeo = (geoName, row, returnType = 'string') => {
|
|
1106
|
+
let toolTipText = buildTooltip(row, geoName, '')
|
|
1107
|
+
|
|
1108
|
+
// We convert the markup into JSX and add a navigation link if it's going into a modal.
|
|
1109
|
+
if ('jsx' === returnType) {
|
|
1110
|
+
toolTipText = [<div key='modal-content'>{parse(toolTipText)}</div>]
|
|
1111
|
+
|
|
1112
|
+
if (state.columns.hasOwnProperty('navigate') && row[state.columns.navigate.name]) {
|
|
1113
|
+
toolTipText.push(
|
|
1114
|
+
// eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions
|
|
1115
|
+
<ul className='navigation-link' key='modal-navigation-link' onClick={() => navigationHandler(row[state.columns.navigate.name])}>
|
|
1116
|
+
{state.tooltips.linkLabel}
|
|
1117
|
+
<ExternalIcon className='inline-icon ml-1' />
|
|
1118
|
+
</ul>
|
|
1119
|
+
)
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
return toolTipText
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1176
1126
|
const navigationHandler = urlString => {
|
|
1177
1127
|
// Call custom navigation method if passed
|
|
1178
1128
|
if (customNavigationHandler) {
|
|
@@ -1639,7 +1589,9 @@ const CdcMap = ({ className, config, navigationHandler: customNavigationHandler,
|
|
|
1639
1589
|
)}
|
|
1640
1590
|
{!runtimeData.init && (general.type === 'navigation' || runtimeLegend) && (
|
|
1641
1591
|
<section className={`cdc-map-inner-container ${currentViewport}`} aria-label={'Map: ' + title} ref={innerContainerRef}>
|
|
1642
|
-
{!window.matchMedia('(any-hover: none)').matches && 'hover' === tooltips.appearanceType &&
|
|
1592
|
+
{!window.matchMedia('(any-hover: none)').matches && 'hover' === tooltips.appearanceType && (
|
|
1593
|
+
<ReactTooltip id='tooltip' float={true} className={`${tooltips.capitalizeLabels ? 'capitalize tooltip' : 'tooltip'}`} style={{ background: `rgba(255,255,255, ${state.tooltips.opacity / 100})`, color: 'black' }} />
|
|
1594
|
+
)}
|
|
1643
1595
|
{title && (
|
|
1644
1596
|
<header className={general.showTitle === true ? 'visible' : 'hidden'} {...(!general.showTitle || !state.general.title ? { 'aria-hidden': true } : { 'aria-hidden': false })}>
|
|
1645
1597
|
{/* eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex */}
|
|
@@ -9,7 +9,6 @@ import Loading from '@cdc/core/components/Loading'
|
|
|
9
9
|
import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
10
10
|
|
|
11
11
|
import topoJSON from '../data/county-map.json'
|
|
12
|
-
import { formatPrefix } from 'd3'
|
|
13
12
|
import useMapLayers from '../hooks/useMapLayers'
|
|
14
13
|
|
|
15
14
|
const sortById = (a, b) => {
|
|
@@ -379,8 +378,17 @@ const CountyMap = props => {
|
|
|
379
378
|
|
|
380
379
|
return (
|
|
381
380
|
<ErrorBoundary component='CountyMap'>
|
|
382
|
-
<canvas
|
|
383
|
-
|
|
381
|
+
<canvas
|
|
382
|
+
ref={canvasRef}
|
|
383
|
+
aria-label={handleMapAriaLabels(state)}
|
|
384
|
+
onMouseMove={canvasHover}
|
|
385
|
+
onMouseOut={() => {
|
|
386
|
+
tooltipRef.current.style.display = 'none'
|
|
387
|
+
tooltipRef.current.setAttribute('data-index', null)
|
|
388
|
+
}}
|
|
389
|
+
onClick={canvasClick}
|
|
390
|
+
></canvas>
|
|
391
|
+
<div ref={tooltipRef} id='canvas-tooltip' className='tooltip' style={{ background: `rgba(255,255,255,${state.tooltips.opacity / 100})` }}></div>
|
|
384
392
|
<button className={`btn btn--reset`} onClick={onReset} ref={resetButton} tabIndex='0'>
|
|
385
393
|
Reset Zoom
|
|
386
394
|
</button>
|
|
@@ -1573,7 +1573,7 @@ const EditorPanel = props => {
|
|
|
1573
1573
|
handleEditorChanges('displayStateLabels', event.target.checked)
|
|
1574
1574
|
}}
|
|
1575
1575
|
/>
|
|
1576
|
-
<span className='edit-label'>
|
|
1576
|
+
<span className='edit-label'>Show state labels</span>
|
|
1577
1577
|
</label>
|
|
1578
1578
|
)}
|
|
1579
1579
|
</AccordionItemPanel>
|
|
@@ -1841,7 +1841,7 @@ const EditorPanel = props => {
|
|
|
1841
1841
|
editColumn('primary', 'dataTable', event.target.checked)
|
|
1842
1842
|
}}
|
|
1843
1843
|
/>
|
|
1844
|
-
<span className='edit-label'>
|
|
1844
|
+
<span className='edit-label'>Show in Data Table</span>
|
|
1845
1845
|
</label>
|
|
1846
1846
|
</li>
|
|
1847
1847
|
<li>
|
|
@@ -1853,7 +1853,7 @@ const EditorPanel = props => {
|
|
|
1853
1853
|
editColumn('primary', 'tooltip', event.target.checked)
|
|
1854
1854
|
}}
|
|
1855
1855
|
/>
|
|
1856
|
-
<span className='edit-label'>
|
|
1856
|
+
<span className='edit-label'>Show in Tooltips</span>
|
|
1857
1857
|
</label>
|
|
1858
1858
|
</li>
|
|
1859
1859
|
</ul>
|
|
@@ -2068,7 +2068,7 @@ const EditorPanel = props => {
|
|
|
2068
2068
|
editColumn(val, 'dataTable', event.target.checked)
|
|
2069
2069
|
}}
|
|
2070
2070
|
/>
|
|
2071
|
-
<span className='edit-label'>
|
|
2071
|
+
<span className='edit-label'>Show in Data Table</span>
|
|
2072
2072
|
</label>
|
|
2073
2073
|
</li>
|
|
2074
2074
|
<li>
|
|
@@ -2080,7 +2080,7 @@ const EditorPanel = props => {
|
|
|
2080
2080
|
editColumn(val, 'tooltip', event.target.checked)
|
|
2081
2081
|
}}
|
|
2082
2082
|
/>
|
|
2083
|
-
<span className='edit-label'>
|
|
2083
|
+
<span className='edit-label'>Show in Tooltips</span>
|
|
2084
2084
|
</label>
|
|
2085
2085
|
</li>
|
|
2086
2086
|
</ul>
|
|
@@ -2829,70 +2829,71 @@ const EditorPanel = props => {
|
|
|
2829
2829
|
<span className='edit-label'>Bubble Map has extra border</span>
|
|
2830
2830
|
</label>
|
|
2831
2831
|
)}
|
|
2832
|
-
{state.general.geoType === 'us' ||
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
<
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
>
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
)
|
|
2832
|
+
{(state.general.geoType === 'us' || state.general.geoType === 'us-county' || state.general.geoType === 'world') && (
|
|
2833
|
+
<label>
|
|
2834
|
+
<span className='edit-label'>City Style</span>
|
|
2835
|
+
<select
|
|
2836
|
+
value={state.visual.cityStyle || false}
|
|
2837
|
+
onChange={event => {
|
|
2838
|
+
handleEditorChanges('handleCityStyle', event.target.value)
|
|
2839
|
+
}}
|
|
2840
|
+
>
|
|
2841
|
+
<option value='circle'>Circle</option>
|
|
2842
|
+
<option value='pin'>Pin</option>
|
|
2843
|
+
</select>
|
|
2844
|
+
</label>
|
|
2845
|
+
)}
|
|
2846
|
+
<label htmlFor='opacity'>
|
|
2847
|
+
<TextField type='number' min={0} max={100} value={state.tooltips.opacity ? state.tooltips.opacity : 100} section='tooltips' fieldName='opacity' label='Tooltip Opacity (%)' updateField={updateField} />
|
|
2848
|
+
</label>
|
|
2849
|
+
</AccordionItemPanel>
|
|
2850
|
+
</AccordionItem>
|
|
2851
|
+
<AccordionItem>
|
|
2852
|
+
<AccordionItemHeading>
|
|
2853
|
+
<AccordionItemButton>Custom Map Layers</AccordionItemButton>
|
|
2854
|
+
</AccordionItemHeading>
|
|
2855
|
+
<AccordionItemPanel>
|
|
2856
|
+
{state.map.layers.length === 0 && <p>There are currently no layers.</p>}
|
|
2857
|
+
|
|
2858
|
+
{state.map.layers.map((layer, index) => {
|
|
2859
|
+
return (
|
|
2860
|
+
<>
|
|
2861
|
+
<Accordion allowZeroExpanded>
|
|
2862
|
+
<AccordionItem className='series-item map-layers-list'>
|
|
2863
|
+
<AccordionItemHeading className='series-item__title map-layers-list--title'>
|
|
2864
|
+
<AccordionItemButton>{`Layer ${index + 1}: ${layer.name}`}</AccordionItemButton>
|
|
2865
|
+
</AccordionItemHeading>
|
|
2866
|
+
<AccordionItemPanel>
|
|
2867
|
+
<div className='map-layers-panel'>
|
|
2868
|
+
<label htmlFor='layerName'>Layer Name:</label>
|
|
2869
|
+
<input type='text' name='layerName' value={layer.name} onChange={e => handleMapLayer(e, index, 'name')} />
|
|
2870
|
+
<label htmlFor='layerFilename'>File:</label>
|
|
2871
|
+
<input type='text' name='layerFilename' value={layer.url} onChange={e => handleMapLayer(e, index, 'url')} />
|
|
2872
|
+
<label htmlFor='layerNamespace'>TOPOJSON Namespace:</label>
|
|
2873
|
+
<input type='text' name='layerNamespace' value={layer.namespace} onChange={e => handleMapLayer(e, index, 'namespace')} />
|
|
2874
|
+
<label htmlFor='layerFill'>Fill Color:</label>
|
|
2875
|
+
<input type='text' name='layerFill' value={layer.fill} onChange={e => handleMapLayer(e, index, 'fill')} />
|
|
2876
|
+
<label htmlFor='layerFill'>Fill Opacity (%):</label>
|
|
2877
|
+
<input type='number' min={0} max={100} name='layerFill' value={layer.fillOpacity ? layer.fillOpacity * 100 : ''} onChange={e => handleMapLayer(e, index, 'fillOpacity')} />
|
|
2878
|
+
<label htmlFor='layerStroke'>Stroke Color:</label>
|
|
2879
|
+
<input type='text' name='layerStroke' value={layer.stroke} onChange={e => handleMapLayer(e, index, 'stroke')} />
|
|
2880
|
+
<label htmlFor='layerStroke'>Stroke Width:</label>
|
|
2881
|
+
<input type='number' min={0} max={5} name='layerStrokeWidth' value={layer.strokeWidth} onChange={e => handleMapLayer(e, index, 'strokeWidth')} />
|
|
2882
|
+
<label htmlFor='layerTooltip'>Tooltip:</label>
|
|
2883
|
+
<textarea name='layerTooltip' value={layer.tooltip} onChange={e => handleMapLayer(e, index, 'tooltip')}></textarea>
|
|
2884
|
+
<button onClick={e => handleRemoveLayer(e, index)}>Remove Layer</button>
|
|
2885
|
+
</div>
|
|
2886
|
+
</AccordionItemPanel>
|
|
2887
|
+
</AccordionItem>
|
|
2888
|
+
</Accordion>
|
|
2889
|
+
</>
|
|
2890
|
+
)
|
|
2891
|
+
})}
|
|
2892
|
+
<button className={'btn full-width'} onClick={handleAddLayer}>
|
|
2893
|
+
Add Map Layer
|
|
2894
|
+
</button>
|
|
2848
2895
|
</AccordionItemPanel>
|
|
2849
2896
|
</AccordionItem>
|
|
2850
|
-
{/* <AccordionItem>
|
|
2851
|
-
<AccordionItemHeading>
|
|
2852
|
-
<AccordionItemButton>Custom Map Layers</AccordionItemButton>
|
|
2853
|
-
</AccordionItemHeading>
|
|
2854
|
-
<AccordionItemPanel>
|
|
2855
|
-
{state.map.layers.length === 0 && <p>There are currently no layers.</p>}
|
|
2856
|
-
|
|
2857
|
-
{state.map.layers.map((layer, index) => {
|
|
2858
|
-
return (
|
|
2859
|
-
<>
|
|
2860
|
-
<Accordion allowZeroExpanded>
|
|
2861
|
-
<AccordionItem className='series-item map-layers-list'>
|
|
2862
|
-
<AccordionItemHeading className='series-item__title map-layers-list--title'>
|
|
2863
|
-
<AccordionItemButton>{`Layer ${index + 1}: ${layer.name}`}</AccordionItemButton>
|
|
2864
|
-
</AccordionItemHeading>
|
|
2865
|
-
<AccordionItemPanel>
|
|
2866
|
-
<div className='map-layers-panel'>
|
|
2867
|
-
<label htmlFor='layerName'>Layer Name:</label>
|
|
2868
|
-
<input type='text' name='layerName' value={layer.name} onChange={e => handleMapLayer(e, index, 'name')} />
|
|
2869
|
-
<label htmlFor='layerFilename'>File:</label>
|
|
2870
|
-
<input type='text' name='layerFilename' value={layer.url} onChange={e => handleMapLayer(e, index, 'url')} />
|
|
2871
|
-
<label htmlFor='layerNamespace'>TOPOJSON Namespace:</label>
|
|
2872
|
-
<input type='text' name='layerNamespace' value={layer.namespace} onChange={e => handleMapLayer(e, index, 'namespace')} />
|
|
2873
|
-
<label htmlFor='layerFill'>Fill Color:</label>
|
|
2874
|
-
<input type='text' name='layerFill' value={layer.fill} onChange={e => handleMapLayer(e, index, 'fill')} />
|
|
2875
|
-
<label htmlFor='layerFill'>Fill Opacity (%):</label>
|
|
2876
|
-
<input type='number' min={0} max={100} name='layerFill' value={layer.fillOpacity ? layer.fillOpacity * 100 : ''} onChange={e => handleMapLayer(e, index, 'fillOpacity')} />
|
|
2877
|
-
<label htmlFor='layerStroke'>Stroke Color:</label>
|
|
2878
|
-
<input type='text' name='layerStroke' value={layer.stroke} onChange={e => handleMapLayer(e, index, 'stroke')} />
|
|
2879
|
-
<label htmlFor='layerStroke'>Stroke Width:</label>
|
|
2880
|
-
<input type='number' min={0} max={5} name='layerStrokeWidth' value={layer.strokeWidth} onChange={e => handleMapLayer(e, index, 'strokeWidth')} />
|
|
2881
|
-
<label htmlFor='layerTooltip'>Tooltip:</label>
|
|
2882
|
-
<textarea name='layerTooltip' value={layer.tooltip} onChange={e => handleMapLayer(e, index, 'tooltip')}></textarea>
|
|
2883
|
-
<button onClick={e => handleRemoveLayer(e, index)}>Remove Layer</button>
|
|
2884
|
-
</div>
|
|
2885
|
-
</AccordionItemPanel>
|
|
2886
|
-
</AccordionItem>
|
|
2887
|
-
</Accordion>
|
|
2888
|
-
</>
|
|
2889
|
-
)
|
|
2890
|
-
})}
|
|
2891
|
-
<button className={'btn full-width'} onClick={handleAddLayer}>
|
|
2892
|
-
Add Map Layer
|
|
2893
|
-
</button>
|
|
2894
|
-
</AccordionItemPanel>
|
|
2895
|
-
</AccordionItem> */}
|
|
2896
2897
|
</Accordion>
|
|
2897
2898
|
<AdvancedEditor loadConfig={loadConfig} state={state} convertStateToConfig={convertStateToConfig} />
|
|
2898
2899
|
</section>
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
const useTooltip = props => {
|
|
2
|
+
const { state, displayGeoName, displayDataAsText, supportedStatesFipsCodes } = props
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* On county maps there's a need to append the state name
|
|
6
|
+
* @param {String} toolTipText - previous tooltip text to build upon
|
|
7
|
+
* @param {Object} row - row of data to lookup fips code with using the geo column.
|
|
8
|
+
* @returns {String} toolTipText - new toolTipText
|
|
9
|
+
*/
|
|
10
|
+
const handleTooltipStateNameColumn = (toolTipText, row) => {
|
|
11
|
+
const { geoType, type, hideGeoColumnInTooltip } = state.general
|
|
12
|
+
if (geoType === 'us-county' && type !== 'us-geocode') {
|
|
13
|
+
let stateFipsCode = row[state.columns.geo.name].substring(0, 2)
|
|
14
|
+
const stateName = supportedStatesFipsCodes[stateFipsCode]
|
|
15
|
+
toolTipText += hideGeoColumnInTooltip ? `<strong>${stateName}</strong><br/>` : `<strong>Location: ${stateName}</strong><br/>`
|
|
16
|
+
}
|
|
17
|
+
return toolTipText
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* On county and state maps, adds the ability to hide the geo column name (prefix)
|
|
22
|
+
* @param {String} geoName - feature name
|
|
23
|
+
* @returns {String} text to be appended to toolTipText
|
|
24
|
+
*/
|
|
25
|
+
const handleTooltipGeoColumn = geoName => {
|
|
26
|
+
const { hideGeoColumnInTooltip } = state.general
|
|
27
|
+
|
|
28
|
+
const handleTooltipPrefix = toolTipText => {
|
|
29
|
+
const { geoType, geoLabelOverride } = state.general
|
|
30
|
+
switch (geoType) {
|
|
31
|
+
case 'us':
|
|
32
|
+
toolTipText = 'State: '
|
|
33
|
+
break
|
|
34
|
+
case 'us-county':
|
|
35
|
+
toolTipText = 'County: '
|
|
36
|
+
break
|
|
37
|
+
case 'single-state':
|
|
38
|
+
toolTipText = 'County: '
|
|
39
|
+
break
|
|
40
|
+
default:
|
|
41
|
+
toolTipText = ''
|
|
42
|
+
break
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (geoLabelOverride) toolTipText = `${geoLabelOverride}: `
|
|
46
|
+
|
|
47
|
+
return toolTipText
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const prefix = handleTooltipPrefix()
|
|
51
|
+
|
|
52
|
+
if (hideGeoColumnInTooltip) return `<strong>${displayGeoName(geoName)}</strong>`
|
|
53
|
+
return `<p class="tooltip-heading">${prefix}${displayGeoName(geoName)}</p>`
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Handles special class descriptions in tooltips
|
|
58
|
+
*/
|
|
59
|
+
const handleTooltipSpecialClassText = (specialClasses, column, row, value, columnKey) => {
|
|
60
|
+
if (specialClasses && specialClasses.length && typeof specialClasses[0] === 'object') {
|
|
61
|
+
for (const specialClass of specialClasses) {
|
|
62
|
+
if (column.name === specialClass.key && String(row[specialClass.key]) === specialClass.value) {
|
|
63
|
+
value = displayDataAsText(specialClass.label, columnKey)
|
|
64
|
+
break
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return value
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const handleTooltipPrimaryColumn = (tooltipValue, column) => {
|
|
72
|
+
const { hidePrimaryColumnInTooltip } = state.general
|
|
73
|
+
let tooltipPrefix = column.label?.length > 0 ? column.label : ''
|
|
74
|
+
if (hidePrimaryColumnInTooltip || !tooltipPrefix) return `<li class="tooltip-body">${tooltipValue}</li>`
|
|
75
|
+
return `<li class="tooltip-body">${tooltipPrefix}: ${tooltipValue}</li>`
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
*
|
|
80
|
+
* @param {String} toolTipText - previous tooltipText to build upon
|
|
81
|
+
* @param {Object} row - row of data
|
|
82
|
+
* @returns {String} new tooltipText value
|
|
83
|
+
*/
|
|
84
|
+
const handleTooltipColumns = (toolTipText, row) => {
|
|
85
|
+
const tooltipEnabledMaps = ['data', 'bubble', 'us-geocode', 'world-geocode', 'map']
|
|
86
|
+
const {
|
|
87
|
+
general: { type: currentMapType },
|
|
88
|
+
columns,
|
|
89
|
+
legend: { specialClasses }
|
|
90
|
+
} = state
|
|
91
|
+
|
|
92
|
+
if (tooltipEnabledMaps.includes(currentMapType) && undefined !== row) {
|
|
93
|
+
toolTipText += `<ul className="capitalize">`
|
|
94
|
+
|
|
95
|
+
// if tooltips are allowed, loop through each column
|
|
96
|
+
Object.keys(columns).forEach(columnKey => {
|
|
97
|
+
const column = state.columns[columnKey]
|
|
98
|
+
|
|
99
|
+
if (column.tooltip) {
|
|
100
|
+
let tooltipValue = handleTooltipSpecialClassText(specialClasses, column, row, '', columnKey)
|
|
101
|
+
|
|
102
|
+
if (!tooltipValue) {
|
|
103
|
+
tooltipValue = displayDataAsText(row[column.name], columnKey)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
toolTipText += handleTooltipPrimaryColumn(tooltipValue, column)
|
|
107
|
+
}
|
|
108
|
+
})
|
|
109
|
+
toolTipText += `</ul>`
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return toolTipText
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const buildTooltip = (row, geoName, toolTipText = '') => {
|
|
116
|
+
if (!row) return
|
|
117
|
+
|
|
118
|
+
// Handle County Location Columns
|
|
119
|
+
toolTipText += handleTooltipStateNameColumn(toolTipText, row)
|
|
120
|
+
|
|
121
|
+
// Handle Data > Geo Column In tooltips
|
|
122
|
+
toolTipText += handleTooltipGeoColumn(geoName)
|
|
123
|
+
|
|
124
|
+
// Handle Data > Primary Column in tooltips
|
|
125
|
+
toolTipText = handleTooltipColumns(toolTipText, row)
|
|
126
|
+
|
|
127
|
+
return toolTipText
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
buildTooltip
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export default useTooltip
|
package/src/scss/main.scss
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
@import 'variables';
|
|
4
4
|
@import 'editor-panel';
|
|
5
5
|
@import 'filters';
|
|
6
|
+
@import '@cdc/core/styles/v2/components/ui/tooltip';
|
|
6
7
|
|
|
7
8
|
.cdc-map-outer-container {
|
|
8
9
|
position: relative;
|
|
@@ -20,7 +21,6 @@
|
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
.cdc-map-inner-container {
|
|
23
|
-
@import './tooltips';
|
|
24
24
|
@import './map';
|
|
25
25
|
@import './sidebar';
|
|
26
26
|
@import './datatable';
|