@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
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { useContext } from 'react'
|
|
2
|
+
import { geoCentroid, geoPath } from 'd3-geo'
|
|
3
|
+
import ConfigContext from './../../../../context'
|
|
4
|
+
import { MapContext } from './../../../../types/MapContext'
|
|
5
|
+
import { AiOutlineArrowUp, AiOutlineArrowDown, AiOutlineArrowRight } from 'react-icons/ai'
|
|
6
|
+
import { Group } from '@visx/group'
|
|
7
|
+
import { Text } from '@visx/text'
|
|
8
|
+
import chroma from 'chroma-js'
|
|
9
|
+
|
|
10
|
+
const offsets = {
|
|
11
|
+
'US-VT': [50, -8],
|
|
12
|
+
'US-NH': [34, 2],
|
|
13
|
+
'US-MA': [30, -1],
|
|
14
|
+
'US-RI': [28, 2],
|
|
15
|
+
'US-CT': [35, 10],
|
|
16
|
+
'US-NJ': [42, 1],
|
|
17
|
+
'US-DE': [33, 0],
|
|
18
|
+
'US-MD': [47, 10]
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const nudges = {
|
|
22
|
+
'US-FL': [15, 3],
|
|
23
|
+
'US-AK': [0, -8],
|
|
24
|
+
'US-CA': [-10, 0],
|
|
25
|
+
'US-NY': [5, 0],
|
|
26
|
+
'US-MI': [13, 20],
|
|
27
|
+
'US-LA': [-10, -3],
|
|
28
|
+
'US-HI': [-10, 10],
|
|
29
|
+
'US-ID': [0, 10],
|
|
30
|
+
'US-WV': [-2, 2]
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// todo: combine hexagonLabel & geoLabel functions
|
|
34
|
+
// todo: move geoLabel functions outside of components for reusability
|
|
35
|
+
const TerritoryHexagon = ({ label, text, stroke, strokeWidth, textColor, territory, territoryData, ...props }) => {
|
|
36
|
+
const { state } = useContext<MapContext>(ConfigContext)
|
|
37
|
+
|
|
38
|
+
const isHex = state.general.displayAsHex
|
|
39
|
+
|
|
40
|
+
// Labels
|
|
41
|
+
const hexagonLabel = (geo, bgColor = '#FFFFFF', projection) => {
|
|
42
|
+
let centroid = projection ? projection(geoCentroid(geo)) : [22, 17.5]
|
|
43
|
+
|
|
44
|
+
let abbr = geo?.properties?.iso ? geo.properties.iso : geo.uid
|
|
45
|
+
|
|
46
|
+
const getArrowDirection = (geoData, geo, isTerritory = false) => {
|
|
47
|
+
if (!isTerritory) {
|
|
48
|
+
centroid = projection(geoCentroid(geo))
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<>
|
|
53
|
+
{state.hexMap.shapeGroups.map((group, groupIndex) => {
|
|
54
|
+
return group.items.map((item, itemIndex) => {
|
|
55
|
+
if (item.operator === '=') {
|
|
56
|
+
if (geoData[item.key] === item.value || Number(geoData[item.key]) === Number(item.value)) {
|
|
57
|
+
return (
|
|
58
|
+
<Group style={{ transform: `translate(36%, 50%)`, fill: 'currentColor' }} key={`territory-hex--${itemIndex}`}>
|
|
59
|
+
{item.shape === 'Arrow Down' && <AiOutlineArrowDown size={12} stroke='none' fontWeight={100} />}
|
|
60
|
+
{item.shape === 'Arrow Up' && <AiOutlineArrowUp size={12} stroke='none' fontWeight={100} />}
|
|
61
|
+
{item.shape === 'Arrow Right' && <AiOutlineArrowRight size={12} stroke='none' fontWeight={100} />}
|
|
62
|
+
</Group>
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
})}
|
|
68
|
+
</>
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (undefined === abbr) return null
|
|
73
|
+
|
|
74
|
+
let textColor = '#FFF'
|
|
75
|
+
|
|
76
|
+
// Dynamic text color
|
|
77
|
+
if (chroma.contrast(textColor, bgColor) < 3.5) {
|
|
78
|
+
textColor = '#202020' // dark gray
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// always make HI black since it is off to the side
|
|
82
|
+
if (abbr === 'US-HI') {
|
|
83
|
+
textColor = '#000'
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
let x = 0,
|
|
87
|
+
y = state.hexMap.type === 'shapes' ? -10 : 5
|
|
88
|
+
|
|
89
|
+
// used to nudge/move some of the labels for better readability
|
|
90
|
+
if (nudges[abbr] && false === isHex) {
|
|
91
|
+
x += nudges[abbr][0]
|
|
92
|
+
y += nudges[abbr][1]
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (undefined === offsets[abbr] || isHex) {
|
|
96
|
+
let y = state.hexMap.type === 'shapes' ? '30%' : '50%'
|
|
97
|
+
return (
|
|
98
|
+
<>
|
|
99
|
+
<Text fontSize={14} x={'50%'} y={y} style={{ fill: 'currentColor', stroke: 'initial', fontWeight: 400, opacity: 1, fillOpacity: 1 }} textAnchor='middle' verticalAnchor='middle'>
|
|
100
|
+
{abbr.substring(3)}
|
|
101
|
+
</Text>
|
|
102
|
+
{state.general.displayAsHex && state.hexMap.type === 'shapes' && getArrowDirection(territoryData, geo, true)}
|
|
103
|
+
</>
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
let [dx, dy] = offsets[abbr]
|
|
108
|
+
|
|
109
|
+
return (
|
|
110
|
+
<g>
|
|
111
|
+
<line x1={centroid[0]} y1={centroid[1]} x2={centroid[0] + dx} y2={centroid[1] + dy} stroke='rgba(0,0,0,.5)' strokeWidth={1} />
|
|
112
|
+
<text x={4} strokeWidth='0' fontSize={13} style={{ fill: '#202020' }} alignmentBaseline='middle' transform={`translate(${centroid[0] + dx}, ${centroid[1] + dy})`}>
|
|
113
|
+
{abbr.substring(3)}
|
|
114
|
+
</text>
|
|
115
|
+
</g>
|
|
116
|
+
)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return (
|
|
120
|
+
<svg viewBox='0 0 45 51' className='territory-wrapper--hex'>
|
|
121
|
+
<g {...props}>
|
|
122
|
+
<polygon stroke={stroke} strokeWidth={strokeWidth} points='22 0 44 12.702 44 38.105 22 50.807 0 38.105 0 12.702' />
|
|
123
|
+
{state.general.displayAsHex && hexagonLabel(territoryData ? territoryData : geo, stroke, false)}
|
|
124
|
+
</g>
|
|
125
|
+
</svg>
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export default TerritoryHexagon
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { useContext } from 'react'
|
|
2
|
+
import { PatternLines, PatternCircles, PatternWaves } from '@visx/pattern'
|
|
3
|
+
import chroma from 'chroma-js'
|
|
4
|
+
import ConfigContext from './../../../../context'
|
|
5
|
+
import { type MapContext } from '../../../../types/MapContext'
|
|
6
|
+
// todo: move this somewhere that makes better sense for pattern sizes.
|
|
7
|
+
const sizes = {
|
|
8
|
+
small: '8',
|
|
9
|
+
medium: '10',
|
|
10
|
+
large: '12'
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const TerritoryRectangle = ({ label, text, stroke, strokeWidth, textColor, hasPattern, territory, ...props }) => {
|
|
14
|
+
const { state, supportedTerritories } = useContext<MapContext>(ConfigContext)
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<svg viewBox='0 0 45 28'>
|
|
18
|
+
<g {...props} strokeLinejoin='round'>
|
|
19
|
+
<path
|
|
20
|
+
stroke={stroke}
|
|
21
|
+
strokeWidth={strokeWidth}
|
|
22
|
+
d='M40,0.5 C41.2426407,0.5 42.3676407,1.00367966 43.1819805,1.81801948 C43.9963203,2.63235931 44.5,3.75735931 44.5,5 L44.5,5 L44.5,23 C44.5,24.2426407 43.9963203,25.3676407 43.1819805,26.1819805 C42.3676407,26.9963203 41.2426407,27.5 40,27.5 L40,27.5 L5,27.5 C3.75735931,27.5 2.63235931,26.9963203 1.81801948,26.1819805 C1.00367966,25.3676407 0.5,24.2426407 0.5,23 L0.5,23 L0.5,5 C0.5,3.75735931 1.00367966,2.63235931 1.81801948,1.81801948 C2.63235931,1.00367966 3.75735931,0.5 5,0.5 L5,0.5 Z'
|
|
23
|
+
{...props}
|
|
24
|
+
/>
|
|
25
|
+
<text textAnchor='middle' dominantBaseline='middle' x='50%' y='54%' fill={text} style={{ stroke: textColor, strokeWidth: 1 }} className='territory-text' paint-order='stroke'>
|
|
26
|
+
{label}
|
|
27
|
+
</text>
|
|
28
|
+
|
|
29
|
+
{state.map.patterns.map((patternData, patternIndex) => {
|
|
30
|
+
let defaultPatternColor = 'black'
|
|
31
|
+
|
|
32
|
+
const hasMatchingValues = supportedTerritories[territory].includes(patternData?.dataValue)
|
|
33
|
+
console.log('has match', patternData)
|
|
34
|
+
|
|
35
|
+
if (chroma.contrast(defaultPatternColor, props.style.fill) < 3.5) {
|
|
36
|
+
defaultPatternColor = 'white'
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (!hasMatchingValues) return null
|
|
40
|
+
if (!patternData.pattern) return null
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<>
|
|
44
|
+
{patternData?.pattern === 'waves' && <PatternWaves id={`territory-${patternData?.dataKey}--${patternIndex}`} height={sizes[patternData?.size] ?? 10} width={sizes[patternData?.size] ?? 10} fill={defaultPatternColor} complement />}
|
|
45
|
+
{patternData?.pattern === 'circles' && <PatternCircles id={`territory-${patternData?.dataKey}--${patternIndex}`} height={sizes[patternData?.size] ?? 10} width={sizes[patternData?.size] ?? 10} fill={defaultPatternColor} complement />}
|
|
46
|
+
{patternData?.pattern === 'lines' && <PatternLines id={`territory-${patternData?.dataKey}--${patternIndex}`} height={sizes[patternData?.size] ?? 6} width={sizes[patternData?.size] ?? 6} stroke={defaultPatternColor} strokeWidth={1} orientation={['diagonalRightToLeft']} />}
|
|
47
|
+
<path
|
|
48
|
+
stroke={stroke}
|
|
49
|
+
strokeWidth={strokeWidth}
|
|
50
|
+
d='M40,0.5 C41.2426407,0.5 42.3676407,1.00367966 43.1819805,1.81801948 C43.9963203,2.63235931 44.5,3.75735931 44.5,5 L44.5,5 L44.5,23 C44.5,24.2426407 43.9963203,25.3676407 43.1819805,26.1819805 C42.3676407,26.9963203 41.2426407,27.5 40,27.5 L40,27.5 L5,27.5 C3.75735931,27.5 2.63235931,26.9963203 1.81801948,26.1819805 C1.00367966,25.3676407 0.5,24.2426407 0.5,23 L0.5,23 L0.5,5 C0.5,3.75735931 1.00367966,2.63235931 1.81801948,1.81801948 C2.63235931,1.00367966 3.75735931,0.5 5,0.5 L5,0.5 Z'
|
|
51
|
+
fill={`url(#territory-${patternData?.dataKey}--${patternIndex})`}
|
|
52
|
+
color='white'
|
|
53
|
+
className={[`territory-pattern-${patternData.dataKey}`, `territory-pattern-${patternData.dataKey}--${patternData.dataValue}`].join(' ')}
|
|
54
|
+
/>
|
|
55
|
+
<text textAnchor='middle' dominantBaseline='middle' x='50%' y='54%' fill={text} style={{ stroke: patternData ? 'black' : textColor, strokeWidth: patternData ? 6 : 0 }} className='territory-text' paint-order='stroke'>
|
|
56
|
+
{label}
|
|
57
|
+
</text>
|
|
58
|
+
</>
|
|
59
|
+
)
|
|
60
|
+
})}
|
|
61
|
+
</g>
|
|
62
|
+
</svg>
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export default TerritoryRectangle
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { useEffect, useState, useRef, useContext } from 'react'
|
|
2
2
|
|
|
3
3
|
import { geoCentroid, geoPath, geoContains } from 'd3-geo'
|
|
4
4
|
import { feature } from 'topojson-client'
|
|
@@ -8,11 +8,11 @@ import debounce from 'lodash.debounce'
|
|
|
8
8
|
import Loading from '@cdc/core/components/Loading'
|
|
9
9
|
import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
10
10
|
|
|
11
|
-
import useMapLayers from '
|
|
12
|
-
import ConfigContext from '
|
|
11
|
+
import useMapLayers from '../../../hooks/useMapLayers'
|
|
12
|
+
import ConfigContext from '../../../context'
|
|
13
13
|
|
|
14
|
-
const getCountyTopoURL =
|
|
15
|
-
return `https://www.cdc.gov/TemplatePackage/contrib/data/county-topography/cb_${year}_us_county_20m.json
|
|
14
|
+
const getCountyTopoURL = year => {
|
|
15
|
+
return `https://www.cdc.gov/TemplatePackage/contrib/data/county-topography/cb_${year}_us_county_20m.json`
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
const sortById = (a, b) => {
|
|
@@ -23,11 +23,11 @@ const sortById = (a, b) => {
|
|
|
23
23
|
|
|
24
24
|
const getTopoData = year => {
|
|
25
25
|
return new Promise((resolve, reject) => {
|
|
26
|
-
const resolveWithTopo = async
|
|
27
|
-
if(response.status !== 200){
|
|
28
|
-
response = await import('
|
|
26
|
+
const resolveWithTopo = async response => {
|
|
27
|
+
if (response.status !== 200) {
|
|
28
|
+
response = await import('./../data/cb_2019_us_county_20m.json')
|
|
29
29
|
} else {
|
|
30
|
-
response = await response.json()
|
|
30
|
+
response = await response.json()
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
let topoData = {}
|
|
@@ -7,7 +7,7 @@ import { feature } from 'topojson-client'
|
|
|
7
7
|
import topoJSON from '../data/us-regions-topo-2.json'
|
|
8
8
|
import { Mercator } from '@visx/geo'
|
|
9
9
|
import chroma from 'chroma-js'
|
|
10
|
-
import ConfigContext from '
|
|
10
|
+
import ConfigContext from '../../../context'
|
|
11
11
|
|
|
12
12
|
const { features: unitedStates } = feature(topoJSON, topoJSON.objects.regions)
|
|
13
13
|
|
|
@@ -36,11 +36,9 @@ const UsaRegionMap = props => {
|
|
|
36
36
|
data,
|
|
37
37
|
displayGeoName,
|
|
38
38
|
geoClickHandler,
|
|
39
|
-
handleCircleClick,
|
|
40
39
|
handleMapAriaLabels,
|
|
41
40
|
state,
|
|
42
41
|
supportedTerritories,
|
|
43
|
-
titleCase,
|
|
44
42
|
} = useContext(ConfigContext)
|
|
45
43
|
|
|
46
44
|
// "Choose State" options
|
|
@@ -6,28 +6,27 @@ import { geoPath } from 'd3-geo'
|
|
|
6
6
|
import { feature, mesh } from 'topojson-client'
|
|
7
7
|
import { CustomProjection } from '@visx/geo'
|
|
8
8
|
import Loading from '@cdc/core/components/Loading'
|
|
9
|
-
import colorPalettes from '
|
|
9
|
+
import colorPalettes from '@cdc/core/data/colorPalettes'
|
|
10
10
|
import { geoAlbersUsaTerritories } from 'd3-composite-projections'
|
|
11
|
-
import CityList from '
|
|
12
|
-
import ConfigContext from '
|
|
11
|
+
import CityList from '../../CityList'
|
|
12
|
+
import ConfigContext from '../../../context'
|
|
13
13
|
|
|
14
14
|
// SVG ITEMS
|
|
15
15
|
const WIDTH = 880
|
|
16
16
|
const HEIGHT = 500
|
|
17
17
|
const PADDING = 25
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
return `https://www.cdc.gov/TemplatePackage/contrib/data/county-topography/cb_${year}_us_county_20m.json`;
|
|
19
|
+
const getCountyTopoURL = year => {
|
|
20
|
+
return `https://www.cdc.gov/TemplatePackage/contrib/data/county-topography/cb_${year}_us_county_20m.json`
|
|
22
21
|
}
|
|
23
22
|
|
|
24
23
|
const getTopoData = year => {
|
|
25
24
|
return new Promise((resolve, reject) => {
|
|
26
25
|
const resolveWithTopo = async response => {
|
|
27
|
-
if(response.status !== 200){
|
|
28
|
-
response = await import('
|
|
26
|
+
if (response.status !== 200) {
|
|
27
|
+
response = await import('./../data/cb_2019_us_county_20m.json')
|
|
29
28
|
} else {
|
|
30
|
-
response = await response.json()
|
|
29
|
+
response = await response.json()
|
|
31
30
|
}
|
|
32
31
|
let topoData = {}
|
|
33
32
|
|
|
@@ -265,7 +264,6 @@ const SingleStateMap = props => {
|
|
|
265
264
|
titleCase={titleCase}
|
|
266
265
|
setSharedFilterValue={setSharedFilterValue}
|
|
267
266
|
isFilterValueSupported={isFilterValueSupported}
|
|
268
|
-
isGeoCodeMap={state.general.type === 'us-geocode'}
|
|
269
267
|
/>
|
|
270
268
|
)
|
|
271
269
|
|
|
@@ -1,138 +1,33 @@
|
|
|
1
1
|
import React, { useState, useEffect, memo, useContext } from 'react'
|
|
2
2
|
|
|
3
3
|
import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
|
|
5
|
+
// United States Topojson resources
|
|
6
6
|
import topoJSON from '../data/us-topo.json'
|
|
7
7
|
import hexTopoJSON from '../data/us-hex-topo.json'
|
|
8
|
+
|
|
9
|
+
import { geoCentroid, geoPath } from 'd3-geo'
|
|
10
|
+
import { feature } from 'topojson-client'
|
|
8
11
|
import { AlbersUsa, Mercator } from '@visx/geo'
|
|
9
12
|
import chroma from 'chroma-js'
|
|
10
|
-
import CityList from '
|
|
11
|
-
import BubbleList from '
|
|
12
|
-
import { supportedCities, supportedStates } from '
|
|
13
|
+
import CityList from '../../CityList'
|
|
14
|
+
import BubbleList from '../../BubbleList'
|
|
15
|
+
import { supportedCities, supportedStates } from '../../../data/supported-geos'
|
|
13
16
|
import { geoAlbersUsa } from 'd3-composite-projections'
|
|
14
17
|
import { Group } from '@visx/group'
|
|
15
18
|
import { Text } from '@visx/text'
|
|
16
|
-
|
|
19
|
+
import { PatternLines, PatternCircles, PatternWaves } from '@visx/pattern'
|
|
17
20
|
import { AiOutlineArrowUp, AiOutlineArrowDown, AiOutlineArrowRight } from 'react-icons/ai'
|
|
18
21
|
|
|
19
|
-
import
|
|
20
|
-
|
|
22
|
+
import Territory from './Territory'
|
|
23
|
+
|
|
24
|
+
import useMapLayers from '../../../hooks/useMapLayers'
|
|
25
|
+
import ConfigContext from '../../../context'
|
|
26
|
+
import { MapContext } from '../../../types/MapContext'
|
|
21
27
|
|
|
22
28
|
const { features: unitedStates } = feature(topoJSON, topoJSON.objects.states)
|
|
23
29
|
const { features: unitedStatesHex } = feature(hexTopoJSON, hexTopoJSON.objects.states)
|
|
24
30
|
|
|
25
|
-
// todo: combine hexagonLabel & geoLabel functions
|
|
26
|
-
// todo: move geoLabel functions outside of components for reusability
|
|
27
|
-
const Hexagon = ({ label, text, stroke, strokeWidth, textColor, territory, territoryData, ...props }) => {
|
|
28
|
-
const { state } = useContext(ConfigContext)
|
|
29
|
-
|
|
30
|
-
// Labels
|
|
31
|
-
const hexagonLabel = (geo, bgColor = '#FFFFFF', projection) => {
|
|
32
|
-
let centroid = projection ? projection(geoCentroid(geo)) : [22, 17.5]
|
|
33
|
-
|
|
34
|
-
let abbr = geo?.properties?.iso ? geo.properties.iso : geo.uid
|
|
35
|
-
|
|
36
|
-
const getArrowDirection = (geoData, geo, isTerritory = false) => {
|
|
37
|
-
if (!isTerritory) {
|
|
38
|
-
centroid = projection(geoCentroid(geo))
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
return (
|
|
42
|
-
<>
|
|
43
|
-
{state.hexMap.shapeGroups.map((group, groupIndex) => {
|
|
44
|
-
return group.items.map((item, itemIndex) => {
|
|
45
|
-
if (item.operator === '=') {
|
|
46
|
-
if (geoData[item.key] === item.value) {
|
|
47
|
-
return (
|
|
48
|
-
<Group style={{ transform: `translate(36%, 50%)`, fill: 'currentColor' }}>
|
|
49
|
-
{item.shape === 'Arrow Down' && <AiOutlineArrowDown size={12} stroke='none' fontWeight={100} />}
|
|
50
|
-
{item.shape === 'Arrow Up' && <AiOutlineArrowUp size={12} stroke='none' fontWeight={100} />}
|
|
51
|
-
{item.shape === 'Arrow Right' && <AiOutlineArrowRight size={12} stroke='none' fontWeight={100} />}
|
|
52
|
-
</Group>
|
|
53
|
-
)
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
})
|
|
57
|
-
})}
|
|
58
|
-
</>
|
|
59
|
-
)
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
if (undefined === abbr) return null
|
|
63
|
-
|
|
64
|
-
let textColor = '#FFF'
|
|
65
|
-
|
|
66
|
-
// Dynamic text color
|
|
67
|
-
if (chroma.contrast(textColor, bgColor) < 3.5) {
|
|
68
|
-
textColor = '#202020' // dark gray
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// always make HI black since it is off to the side
|
|
72
|
-
if (abbr === 'US-HI') {
|
|
73
|
-
textColor = '#000'
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
let x = 0,
|
|
77
|
-
y = state.hexMap.type === 'shapes' ? -10 : 5
|
|
78
|
-
|
|
79
|
-
// used to nudge/move some of the labels for better readability
|
|
80
|
-
if (nudges[abbr] && false === isHex) {
|
|
81
|
-
x += nudges[abbr][0]
|
|
82
|
-
y += nudges[abbr][1]
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
if (undefined === offsets[abbr] || isHex) {
|
|
86
|
-
let y = state.hexMap.type === 'shapes' ? '30%' : '50%'
|
|
87
|
-
return (
|
|
88
|
-
<>
|
|
89
|
-
<Text fontSize={14} x={'50%'} y={y} style={{ fill: 'currentColor', stroke: 'initial', fontWeight: 400, opacity: 1, fillOpacity: 1 }} textAnchor='middle' verticalAnchor='middle'>
|
|
90
|
-
{abbr.substring(3)}
|
|
91
|
-
</Text>
|
|
92
|
-
{state.general.displayAsHex && state.hexMap.type === 'shapes' && getArrowDirection(territoryData, geo, true)}
|
|
93
|
-
</>
|
|
94
|
-
)
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
let [dx, dy] = offsets[abbr]
|
|
98
|
-
|
|
99
|
-
return (
|
|
100
|
-
<g>
|
|
101
|
-
<line x1={centroid[0]} y1={centroid[1]} x2={centroid[0] + dx} y2={centroid[1] + dy} stroke='rgba(0,0,0,.5)' strokeWidth={1} />
|
|
102
|
-
<text x={4} strokeWidth='0' fontSize={13} style={{ fill: '#202020' }} alignmentBaseline='middle' transform={`translate(${centroid[0] + dx}, ${centroid[1] + dy})`}>
|
|
103
|
-
{abbr.substring(3)}
|
|
104
|
-
</text>
|
|
105
|
-
</g>
|
|
106
|
-
)
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
return (
|
|
110
|
-
<svg viewBox='0 0 45 51' className='territory-wrapper--hex'>
|
|
111
|
-
<g {...props}>
|
|
112
|
-
<polygon stroke={stroke} strokeWidth={strokeWidth} points='22 0 44 12.702 44 38.105 22 50.807 0 38.105 0 12.702' />
|
|
113
|
-
{state.general.displayAsHex && hexagonLabel(territoryData ? territoryData : geo, stroke, false)}
|
|
114
|
-
</g>
|
|
115
|
-
</svg>
|
|
116
|
-
)
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
const Rect = ({ label, text, stroke, strokeWidth, textColor, ...props }) => {
|
|
120
|
-
return (
|
|
121
|
-
<svg viewBox='0 0 45 28'>
|
|
122
|
-
<g {...props} strokeLinejoin='round'>
|
|
123
|
-
<path
|
|
124
|
-
stroke={stroke}
|
|
125
|
-
strokeWidth={strokeWidth}
|
|
126
|
-
d='M40,0.5 C41.2426407,0.5 42.3676407,1.00367966 43.1819805,1.81801948 C43.9963203,2.63235931 44.5,3.75735931 44.5,5 L44.5,5 L44.5,23 C44.5,24.2426407 43.9963203,25.3676407 43.1819805,26.1819805 C42.3676407,26.9963203 41.2426407,27.5 40,27.5 L40,27.5 L5,27.5 C3.75735931,27.5 2.63235931,26.9963203 1.81801948,26.1819805 C1.00367966,25.3676407 0.5,24.2426407 0.5,23 L0.5,23 L0.5,5 C0.5,3.75735931 1.00367966,2.63235931 1.81801948,1.81801948 C2.63235931,1.00367966 3.75735931,0.5 5,0.5 L5,0.5 Z'
|
|
127
|
-
/>
|
|
128
|
-
<text textAnchor='middle' dominantBaseline='middle' x='50%' y='54%' fill={text} stroke={textColor}>
|
|
129
|
-
{label}
|
|
130
|
-
</text>
|
|
131
|
-
</g>
|
|
132
|
-
</svg>
|
|
133
|
-
)
|
|
134
|
-
}
|
|
135
|
-
|
|
136
31
|
const offsets = {
|
|
137
32
|
'US-VT': [50, -8],
|
|
138
33
|
'US-NH': [34, 2],
|
|
@@ -156,7 +51,7 @@ const nudges = {
|
|
|
156
51
|
'US-WV': [-2, 2]
|
|
157
52
|
}
|
|
158
53
|
|
|
159
|
-
const UsaMap =
|
|
54
|
+
const UsaMap = () => {
|
|
160
55
|
// prettier-ignore
|
|
161
56
|
const {
|
|
162
57
|
applyLegendToRow,
|
|
@@ -170,7 +65,7 @@ const UsaMap = props => {
|
|
|
170
65
|
state,
|
|
171
66
|
supportedTerritories,
|
|
172
67
|
titleCase,
|
|
173
|
-
} = useContext(ConfigContext)
|
|
68
|
+
} = useContext<MapContext>(ConfigContext)
|
|
174
69
|
|
|
175
70
|
let isFilterValueSupported = false
|
|
176
71
|
|
|
@@ -224,7 +119,7 @@ const UsaMap = props => {
|
|
|
224
119
|
const geoStrokeColor = state.general.geoBorderColor === 'darkGray' ? 'rgba(0, 0, 0, 0.2)' : 'rgba(255,255,255,0.7)'
|
|
225
120
|
|
|
226
121
|
const territories = territoriesData.map(territory => {
|
|
227
|
-
const Shape = isHex ? Hexagon :
|
|
122
|
+
const Shape = isHex ? Territory.Hexagon : Territory.Rectangle
|
|
228
123
|
|
|
229
124
|
const territoryData = data[territory]
|
|
230
125
|
|
|
@@ -271,11 +166,18 @@ const UsaMap = props => {
|
|
|
271
166
|
fill: legendColors[2]
|
|
272
167
|
}
|
|
273
168
|
}
|
|
274
|
-
|
|
169
|
+
|
|
170
|
+
return (
|
|
171
|
+
<>
|
|
172
|
+
<Shape key={label} label={label} style={styles} text={styles.color} strokeWidth={1.5} textColor={textColor} onClick={() => geoClickHandler(territory, territoryData)} data-tooltip-id='tooltip' data-tooltip-html={toolTip} territory={territory} territoryData={territoryData} />
|
|
173
|
+
</>
|
|
174
|
+
)
|
|
275
175
|
}
|
|
276
176
|
})
|
|
277
177
|
|
|
278
178
|
let pathGenerator = geoPath().projection(geoAlbersUsa().translate(translate))
|
|
179
|
+
|
|
180
|
+
// Note: Layers are different than patterns
|
|
279
181
|
const { pathArray } = useMapLayers(state, '', pathGenerator)
|
|
280
182
|
|
|
281
183
|
// Constructs and displays markup for all geos on the map (except territories right now)
|
|
@@ -368,9 +270,9 @@ const UsaMap = props => {
|
|
|
368
270
|
{state.hexMap.shapeGroups.map((group, groupIndex) => {
|
|
369
271
|
return group.items.map((item, itemIndex) => {
|
|
370
272
|
if (item.operator === '=') {
|
|
371
|
-
if (geoData[item.key] === item.value) {
|
|
273
|
+
if (geoData[item.key] === item.value || Number(geoData[item.key]) == Number(item.value)) {
|
|
372
274
|
return (
|
|
373
|
-
<Group top={centroid[1] - 5} left={centroid[0] - iconSize} color={textColor} textAnchor='start'>
|
|
275
|
+
<Group top={centroid[1] - 5} left={centroid[0] - iconSize} color={textColor} textAnchor='start' key={`hex--${item.key}-${item.value}-${itemIndex}`}>
|
|
374
276
|
{item.shape === 'Arrow Down' && <AiOutlineArrowDown />}
|
|
375
277
|
{item.shape === 'Arrow Up' && <AiOutlineArrowUp />}
|
|
376
278
|
{item.shape === 'Arrow Right' && <AiOutlineArrowRight />}
|
|
@@ -384,10 +286,37 @@ const UsaMap = props => {
|
|
|
384
286
|
)
|
|
385
287
|
}
|
|
386
288
|
|
|
289
|
+
const sizes = {
|
|
290
|
+
small: '8',
|
|
291
|
+
medium: '10',
|
|
292
|
+
large: '12'
|
|
293
|
+
}
|
|
294
|
+
|
|
387
295
|
return (
|
|
388
296
|
<g data-name={geoName} key={key}>
|
|
389
297
|
<g className='geo-group' style={styles} onClick={() => geoClickHandler(geoDisplayName, geoData)} id={geoName} data-tooltip-id='tooltip' data-tooltip-html={tooltip}>
|
|
390
298
|
<path tabIndex={-1} className='single-geo' strokeWidth={1.3} d={path} />
|
|
299
|
+
{state.map.patterns.map((patternData, patternIndex) => {
|
|
300
|
+
let { pattern, dataKey, size } = patternData
|
|
301
|
+
let defaultPatternColor = 'black'
|
|
302
|
+
|
|
303
|
+
const hasMatchingValues = patternData.dataValue === geoData[patternData.dataKey]
|
|
304
|
+
|
|
305
|
+
if (chroma.contrast(defaultPatternColor, legendColors[0]) < 3.5) {
|
|
306
|
+
defaultPatternColor = 'white'
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return (
|
|
310
|
+
hasMatchingValues && (
|
|
311
|
+
<>
|
|
312
|
+
{pattern === 'waves' && <PatternWaves id={`${dataKey}--${patternIndex}`} height={sizes[size] ?? 10} width={sizes[size] ?? 10} fill={defaultPatternColor} />}
|
|
313
|
+
{pattern === 'circles' && <PatternCircles id={`${dataKey}--${patternIndex}`} height={sizes[size] ?? 10} width={sizes[size] ?? 10} fill={defaultPatternColor} />}
|
|
314
|
+
{pattern === 'lines' && <PatternLines id={`${dataKey}--${patternIndex}`} height={sizes[size] ?? 6} width={sizes[size] ?? 6} stroke={defaultPatternColor} strokeWidth={1} orientation={['diagonalRightToLeft']} />}
|
|
315
|
+
<path className={`pattern-geoKey--${dataKey}`} tabIndex={-1} stroke='transparent' d={path} fill={`url(#${dataKey}--${patternIndex})`} />
|
|
316
|
+
</>
|
|
317
|
+
)
|
|
318
|
+
)
|
|
319
|
+
})}
|
|
391
320
|
{(isHex || showLabel) && geoLabel(geo, legendColors[0], projection)}
|
|
392
321
|
{isHex && state.hexMap.type === 'shapes' && getArrowDirection(geoData, geo, legendColors[0])}
|
|
393
322
|
</g>
|
|
@@ -417,7 +346,6 @@ const UsaMap = props => {
|
|
|
417
346
|
displayGeoName={displayGeoName}
|
|
418
347
|
geoClickHandler={geoClickHandler}
|
|
419
348
|
isFilterValueSupported={isFilterValueSupported}
|
|
420
|
-
isGeoCodeMap={state.general.type === 'us-geocode'}
|
|
421
349
|
key='cities'
|
|
422
350
|
projection={projection}
|
|
423
351
|
setSharedFilterValue={setSharedFilterValue}
|
|
@@ -511,7 +439,7 @@ const UsaMap = props => {
|
|
|
511
439
|
<span className='territories-label label'>{state.general.territoriesLabel}</span>
|
|
512
440
|
</div>
|
|
513
441
|
<div>
|
|
514
|
-
<span className={window.visualViewport.width <
|
|
442
|
+
<span className={window.visualViewport.width < 700 ? 'territories--mobile' : 'territories'}>{territories}</span>
|
|
515
443
|
</div>
|
|
516
444
|
</div>
|
|
517
445
|
</>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import UsaStateMap from './components/UsaMap.State'
|
|
2
|
+
import UsaRegionMap from './components/UsaMap.Region.jsx'
|
|
3
|
+
import UsaCountyMap from './components/UsaMap.County'
|
|
4
|
+
import UsaSingleStateMap from './components/UsaMap.SingleState.jsx'
|
|
5
|
+
|
|
6
|
+
const UsaMap = {
|
|
7
|
+
State: UsaStateMap,
|
|
8
|
+
Region: UsaRegionMap,
|
|
9
|
+
County: UsaCountyMap,
|
|
10
|
+
SingleState: UsaSingleStateMap
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export default UsaMap
|
|
@@ -5,12 +5,12 @@ import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
|
5
5
|
import { geoMercator } from 'd3-geo'
|
|
6
6
|
import { Mercator } from '@visx/geo'
|
|
7
7
|
import { feature } from 'topojson-client'
|
|
8
|
-
import topoJSON from '
|
|
9
|
-
import ZoomableGroup from '
|
|
10
|
-
import Geo from '
|
|
11
|
-
import CityList from '
|
|
12
|
-
import BubbleList from '
|
|
13
|
-
import ConfigContext from '
|
|
8
|
+
import topoJSON from './../data/world-topo.json'
|
|
9
|
+
import ZoomableGroup from '../../ZoomableGroup'
|
|
10
|
+
import Geo from '../../Geo'
|
|
11
|
+
import CityList from '../../CityList'
|
|
12
|
+
import BubbleList from '../../BubbleList'
|
|
13
|
+
import ConfigContext from '../../../context'
|
|
14
14
|
|
|
15
15
|
const { features: world } = feature(topoJSON, topoJSON.objects.countries)
|
|
16
16
|
|
|
@@ -101,14 +101,22 @@ const WorldMap = props => {
|
|
|
101
101
|
|
|
102
102
|
const constructGeoJsx = geographies => {
|
|
103
103
|
const geosJsx = geographies.map(({ feature: geo, path }, i) => {
|
|
104
|
-
|
|
104
|
+
// If the geo.properties.state value is found in the data use that, otherwise fall back to geo.properties.iso
|
|
105
|
+
const dataHasStateName = state.data.some(d => d[state.columns.geo.name] === geo.properties.state)
|
|
106
|
+
const geoKey = geo.properties.state && data[geo.properties.state] ? geo.properties.state : geo.properties.name ? geo.properties.name : geo.properties.iso
|
|
105
107
|
|
|
108
|
+
const additionalData = {
|
|
109
|
+
name: geo.properties.name
|
|
110
|
+
}
|
|
106
111
|
if (!geoKey) return null
|
|
107
112
|
|
|
108
|
-
|
|
113
|
+
let geoData = data[geoKey]
|
|
109
114
|
|
|
110
|
-
|
|
115
|
+
// if ((geoKey === 'Alaska' || geoKey === 'Hawaii') && !geoData) {
|
|
116
|
+
// geoData = data['United States']
|
|
117
|
+
// }
|
|
111
118
|
|
|
119
|
+
const geoDisplayName = displayGeoName(supportedCountries[geoKey]?.[0])
|
|
112
120
|
let legendColors
|
|
113
121
|
|
|
114
122
|
// Once we receive data for this geographic item, setup variables.
|
|
@@ -146,17 +154,15 @@ const WorldMap = props => {
|
|
|
146
154
|
styles.cursor = 'pointer'
|
|
147
155
|
}
|
|
148
156
|
|
|
149
|
-
return <Geo key={i + '-geo'}
|
|
157
|
+
return <Geo additionalData={additionalData} geoData={geoData} state={state} key={i + '-geo'} style={styles} path={path} stroke={geoStrokeColor} strokeWidth={strokeWidth} onClick={() => geoClickHandler(geoDisplayName, geoData)} data-tooltip-id='tooltip' data-tooltip-html={toolTip} />
|
|
150
158
|
}
|
|
151
159
|
|
|
152
160
|
// Default return state, just geo with no additional information
|
|
153
|
-
return <Geo key={i + '-geo'} stroke={geoStrokeColor} strokeWidth={strokeWidth}
|
|
161
|
+
return <Geo additionalData={additionalData} geoData={geoData} state={state} key={i + '-geo'} stroke={geoStrokeColor} strokeWidth={strokeWidth} style={styles} path={path} />
|
|
154
162
|
})
|
|
155
163
|
|
|
156
164
|
// Cities
|
|
157
|
-
geosJsx.push(
|
|
158
|
-
<CityList applyLegendToRow={applyLegendToRow} applyTooltipsToGeo={applyTooltipsToGeo} data={data} displayGeoName={displayGeoName} geoClickHandler={geoClickHandler} isGeoCodeMap={state.general.type === 'world-geocode'} key='cities' projection={projection} state={state} titleCase={titleCase} />
|
|
159
|
-
)
|
|
165
|
+
geosJsx.push(<CityList applyLegendToRow={applyLegendToRow} applyTooltipsToGeo={applyTooltipsToGeo} data={data} displayGeoName={displayGeoName} geoClickHandler={geoClickHandler} key='cities' projection={projection} state={state} titleCase={titleCase} />)
|
|
160
166
|
|
|
161
167
|
// Bubbles
|
|
162
168
|
if (state.general.type === 'bubble') {
|