@cdc/map 4.24.10 → 4.24.12
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 +27324 -27152
- package/examples/default-geocode.json +13 -4
- package/examples/default-usa-regions.json +267 -117
- package/examples/example-city-state.json +6 -3
- package/examples/pattern.json +861 -0
- package/examples/private/DEV-9644.json +184 -0
- package/examples/private/default-patterns.json +867 -0
- package/index.html +4 -5
- package/package.json +3 -3
- package/src/CdcMap.tsx +53 -52
- package/src/_stories/CdcMap.Legend.Gradient.stories.tsx +67 -0
- package/src/_stories/CdcMap.Legend.stories.tsx +40 -0
- package/src/_stories/CdcMap.Patterns.stories.tsx +29 -0
- package/src/_stories/CdcMap.stories.tsx +59 -0
- package/src/_stories/UsaMap.NoData.stories.tsx +19 -0
- package/src/_stories/_mock/custom-layer-map.json +1117 -0
- package/src/_stories/_mock/default-patterns.json +865 -0
- package/src/_stories/_mock/example-city-state.json +858 -0
- package/src/_stories/_mock/usa-state-gradient.json +238 -0
- package/src/_stories/_mock/wastewater-map.json +208 -0
- package/src/components/CityList.tsx +5 -2
- package/src/components/EditorPanel/components/EditorPanel.tsx +81 -61
- package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +27 -23
- package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +75 -16
- package/src/components/Legend/components/Legend.tsx +42 -20
- package/src/components/Legend/components/index.scss +24 -24
- package/src/components/UsaMap/components/HexIcon.tsx +7 -1
- package/src/components/UsaMap/components/SingleState/SingleState.CountyOutput.tsx +40 -6
- package/src/components/UsaMap/components/SingleState/SingleState.StateOutput.tsx +10 -2
- package/src/components/UsaMap/components/Territory/Territory.Hexagon.tsx +57 -12
- package/src/components/UsaMap/components/Territory/Territory.Rectangle.tsx +95 -21
- package/src/components/UsaMap/components/Territory/TerritoryShape.ts +13 -0
- package/src/components/UsaMap/components/UsaMap.County.tsx +11 -13
- package/src/components/UsaMap/components/UsaMap.Region.tsx +59 -16
- package/src/components/UsaMap/components/UsaMap.SingleState.tsx +2 -1
- package/src/components/UsaMap/components/UsaMap.State.tsx +60 -62
- package/src/components/UsaMap/helpers/shapes.ts +5 -4
- package/src/components/WorldMap/WorldMap.tsx +77 -16
- package/src/data/initial-state.js +2 -1
- package/src/helpers/applyColorToLegend.ts +80 -0
- package/src/helpers/colors.ts +23 -0
- package/src/hooks/useTooltip.ts +9 -6
- package/src/scss/editor-panel.scss +0 -3
- package/src/scss/filters.scss +1 -9
- package/src/scss/main.scss +0 -5
- package/src/scss/map.scss +11 -63
- package/src/types/MapConfig.ts +6 -1
- package/src/types/MapContext.ts +1 -0
- package/examples/default-patterns.json +0 -579
- package/src/scss/datatable.scss +0 -6
|
@@ -15,18 +15,22 @@ import useDataVizClasses from '@cdc/core/helpers/useDataVizClasses'
|
|
|
15
15
|
import ConfigContext from '../../../context'
|
|
16
16
|
import { PatternLines, PatternCircles, PatternWaves } from '@visx/pattern'
|
|
17
17
|
import { GlyphStar, GlyphTriangle, GlyphDiamond, GlyphSquare, GlyphCircle } from '@visx/glyph'
|
|
18
|
-
import { type ViewportSize } from '../../../types/MapConfig'
|
|
19
18
|
import { Group } from '@visx/group'
|
|
20
19
|
import './index.scss'
|
|
20
|
+
import { ViewportSize } from '@cdc/chart/src/types/ChartConfig'
|
|
21
|
+
import { isBelowBreakpoint, isMobileHeightViewport } from '@cdc/core/helpers/viewports'
|
|
22
|
+
|
|
23
|
+
const LEGEND_PADDING = 30
|
|
21
24
|
|
|
22
25
|
type LegendProps = {
|
|
23
26
|
skipId: string
|
|
24
|
-
currentViewport: ViewportSize
|
|
25
27
|
dimensions: DimensionsType
|
|
28
|
+
containerWidthPadding: number
|
|
29
|
+
currentViewport: ViewportSize
|
|
26
30
|
}
|
|
27
31
|
|
|
28
32
|
const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
|
|
29
|
-
const { skipId,
|
|
33
|
+
const { skipId, dimensions, containerWidthPadding, currentViewport } = props
|
|
30
34
|
|
|
31
35
|
const {
|
|
32
36
|
// prettier-ignore
|
|
@@ -42,6 +46,12 @@ const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
|
|
|
42
46
|
} = useContext(ConfigContext)
|
|
43
47
|
|
|
44
48
|
const { legend } = state
|
|
49
|
+
const isLegendGradient = legend.style === 'gradient'
|
|
50
|
+
const boxDynamicallyHidden = isBelowBreakpoint('md', currentViewport)
|
|
51
|
+
const legendWrapping =
|
|
52
|
+
(legend.position === 'left' || legend.position === 'right') && isBelowBreakpoint('md', currentViewport)
|
|
53
|
+
const legendOnBottom = legend.position === 'bottom' || legendWrapping
|
|
54
|
+
const needsTopMargin = legend.hideBorder && legendOnBottom
|
|
45
55
|
|
|
46
56
|
// Toggles if a legend is active and being applied to the map and data table.
|
|
47
57
|
const toggleLegendActive = (i, legendLabel) => {
|
|
@@ -83,6 +93,10 @@ const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
|
|
|
83
93
|
formattedText = '0'
|
|
84
94
|
}
|
|
85
95
|
|
|
96
|
+
if (entry.max === null && entry.min === null) {
|
|
97
|
+
formattedText = 'No data'
|
|
98
|
+
}
|
|
99
|
+
|
|
86
100
|
let legendLabel = formattedText
|
|
87
101
|
|
|
88
102
|
if (entry.hasOwnProperty('special')) {
|
|
@@ -99,13 +113,14 @@ const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
|
|
|
99
113
|
})
|
|
100
114
|
}
|
|
101
115
|
|
|
102
|
-
const legendList = () => {
|
|
103
|
-
const formattedItems = getFormattedLegendItems()
|
|
116
|
+
const legendList = (patternsOnly = false) => {
|
|
117
|
+
const formattedItems = patternsOnly ? [] : getFormattedLegendItems()
|
|
118
|
+
const patternsOnlyFont = isMobileHeightViewport(currentViewport) ? '12px' : '14px'
|
|
104
119
|
let legendItems
|
|
105
120
|
|
|
106
121
|
legendItems = formattedItems.map((item, idx) => {
|
|
107
122
|
const handleListItemClass = () => {
|
|
108
|
-
let classes = ['legend-container__li']
|
|
123
|
+
let classes = ['legend-container__li', 'd-flex', 'align-items-center']
|
|
109
124
|
if (item.disabled) classes.push('legend-container__li--disabled')
|
|
110
125
|
if (item.special) classes.push('legend-container__li--special-class')
|
|
111
126
|
return classes.join(' ')
|
|
@@ -126,11 +141,7 @@ const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
|
|
|
126
141
|
}}
|
|
127
142
|
tabIndex={0}
|
|
128
143
|
>
|
|
129
|
-
<LegendShape
|
|
130
|
-
shape={state.legend.style === 'boxes' ? 'square' : 'circle'}
|
|
131
|
-
viewport={viewport}
|
|
132
|
-
fill={item.color}
|
|
133
|
-
/>
|
|
144
|
+
<LegendShape shape={state.legend.style === 'boxes' ? 'square' : 'circle'} fill={item.color} />
|
|
134
145
|
<span>{item.label}</span>
|
|
135
146
|
</li>
|
|
136
147
|
)
|
|
@@ -195,7 +206,9 @@ const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
|
|
|
195
206
|
/>
|
|
196
207
|
</svg>
|
|
197
208
|
</span>
|
|
198
|
-
<p style={{ lineHeight: '22.4px'
|
|
209
|
+
<p style={{ lineHeight: '22.4px', fontSize: patternsOnly ? patternsOnlyFont : '16px' }}>
|
|
210
|
+
{patternData.label || patternData.dataValue || ''}
|
|
211
|
+
</p>
|
|
199
212
|
</li>
|
|
200
213
|
</>
|
|
201
214
|
)
|
|
@@ -204,6 +217,8 @@ const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
|
|
|
204
217
|
|
|
205
218
|
return legendItems
|
|
206
219
|
}
|
|
220
|
+
const legendListItems = legendList(isLegendGradient)
|
|
221
|
+
|
|
207
222
|
const { legendClasses } = useDataVizClasses(state, viewport)
|
|
208
223
|
|
|
209
224
|
const handleReset = e => {
|
|
@@ -239,13 +254,13 @@ const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
|
|
|
239
254
|
|
|
240
255
|
return (
|
|
241
256
|
<ErrorBoundary component='Sidebar'>
|
|
242
|
-
<div className=
|
|
257
|
+
<div className={`legends ${needsTopMargin ? 'mt-1' : ''}`}>
|
|
243
258
|
<aside
|
|
244
259
|
id={skipId || 'legend'}
|
|
245
260
|
className={legendClasses.aside.join(' ') || ''}
|
|
246
261
|
role='region'
|
|
247
262
|
aria-label='Legend'
|
|
248
|
-
tabIndex={0}
|
|
263
|
+
tabIndex={isLegendGradient ? -1 : 0}
|
|
249
264
|
ref={ref}
|
|
250
265
|
>
|
|
251
266
|
<section className={legendClasses.section.join(' ') || ''} aria-label='Map Legend'>
|
|
@@ -273,14 +288,17 @@ const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
|
|
|
273
288
|
<LegendGradient
|
|
274
289
|
labels={getFormattedLegendItems().map(item => item?.label) ?? []}
|
|
275
290
|
colors={getFormattedLegendItems().map(item => item?.color) ?? []}
|
|
276
|
-
values={getFormattedLegendItems().map(item => item?.value) ?? []}
|
|
277
291
|
dimensions={dimensions}
|
|
278
|
-
|
|
292
|
+
parentPaddingToSubtract={
|
|
293
|
+
containerWidthPadding + (legend.hideBorder || boxDynamicallyHidden ? 0 : LEGEND_PADDING)
|
|
294
|
+
}
|
|
279
295
|
config={state}
|
|
280
296
|
/>
|
|
281
|
-
|
|
282
|
-
{
|
|
283
|
-
|
|
297
|
+
{!!legendListItems.length && (
|
|
298
|
+
<ul className={legendClasses.ul.join(' ') || ''} aria-label='Legend items'>
|
|
299
|
+
{legendListItems}
|
|
300
|
+
</ul>
|
|
301
|
+
)}
|
|
284
302
|
{(state.visual.additionalCityStyles.some(c => c.label) || state.visual.cityStyleLabel) && (
|
|
285
303
|
<>
|
|
286
304
|
<hr />
|
|
@@ -315,7 +333,11 @@ const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
|
|
|
315
333
|
</div>
|
|
316
334
|
</>
|
|
317
335
|
)}
|
|
318
|
-
{runtimeLegend.disabledAmt > 0 &&
|
|
336
|
+
{runtimeLegend.disabledAmt > 0 && (
|
|
337
|
+
<Button className={legendClasses.resetButton.join(' ')} onClick={handleReset}>
|
|
338
|
+
Reset
|
|
339
|
+
</Button>
|
|
340
|
+
)}
|
|
319
341
|
</section>
|
|
320
342
|
</aside>
|
|
321
343
|
{state.hexMap.shapeGroups?.length > 0 && state.hexMap.type === 'shapes' && state.general.displayAsHex && (
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
}
|
|
7
7
|
@include breakpointClass(md) {
|
|
8
8
|
.map-container.world aside.side {
|
|
9
|
-
border-top: var(--
|
|
9
|
+
border-top: var(--cool-gray-10) 1px solid;
|
|
10
10
|
position: absolute;
|
|
11
11
|
box-shadow: rgba(0, 0, 0, 0.2) 0 10px 18px;
|
|
12
12
|
}
|
|
@@ -15,10 +15,15 @@
|
|
|
15
15
|
aside {
|
|
16
16
|
background-color: #fff;
|
|
17
17
|
z-index: 6;
|
|
18
|
-
border-top: var(--
|
|
18
|
+
border-top: var(--cool-gray-10) 1px solid;
|
|
19
|
+
|
|
19
20
|
@include breakpointClass(md) {
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
.legend-container.legend-padding {
|
|
22
|
+
padding: 15px;
|
|
23
|
+
}
|
|
24
|
+
&.bottom,
|
|
25
|
+
&.top {
|
|
26
|
+
border: var(--cool-gray-10) 1px solid;
|
|
22
27
|
}
|
|
23
28
|
&.side {
|
|
24
29
|
z-index: 1;
|
|
@@ -29,7 +34,7 @@
|
|
|
29
34
|
align-self: flex-start;
|
|
30
35
|
z-index: 4;
|
|
31
36
|
right: 1em;
|
|
32
|
-
border: var(--
|
|
37
|
+
border: var(--cool-gray-10) 1px solid;
|
|
33
38
|
top: 2em;
|
|
34
39
|
right: 1em;
|
|
35
40
|
|
|
@@ -65,8 +70,11 @@
|
|
|
65
70
|
}
|
|
66
71
|
|
|
67
72
|
.legend-container {
|
|
68
|
-
|
|
73
|
+
--space-between-legend-items: 0.6em;
|
|
69
74
|
position: relative;
|
|
75
|
+
&.legend-padding {
|
|
76
|
+
padding-top: 15px;
|
|
77
|
+
}
|
|
70
78
|
.legend-container__title {
|
|
71
79
|
font-size: 1.3em;
|
|
72
80
|
padding-bottom: 0;
|
|
@@ -79,17 +87,7 @@
|
|
|
79
87
|
padding-top: 1em;
|
|
80
88
|
}
|
|
81
89
|
.legend-container__reset-button {
|
|
82
|
-
|
|
83
|
-
right: 1em;
|
|
84
|
-
text-transform: uppercase;
|
|
85
|
-
transition: 0.2s all;
|
|
86
|
-
padding: 0.2em 0.5em;
|
|
87
|
-
border: rgba(0, 0, 0, 0.2) 1px solid;
|
|
88
|
-
padding: 0.375rem;
|
|
89
|
-
&:hover {
|
|
90
|
-
text-decoration: none;
|
|
91
|
-
transition: 0.2s all;
|
|
92
|
-
}
|
|
90
|
+
margin-top: 1em;
|
|
93
91
|
}
|
|
94
92
|
p {
|
|
95
93
|
line-height: 1.4em;
|
|
@@ -114,23 +112,21 @@
|
|
|
114
112
|
|
|
115
113
|
&:not(.vertical-sorted, .legend-container__ul--single-column, .single-row) {
|
|
116
114
|
width: 100%;
|
|
117
|
-
@include breakpoint(sm) {
|
|
118
|
-
.legend-container__li {
|
|
119
|
-
width: 50%;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
115
|
}
|
|
123
116
|
.legend-container__li {
|
|
124
117
|
flex-shrink: 0;
|
|
125
118
|
display: inline-block;
|
|
126
119
|
padding-right: 1em;
|
|
127
|
-
|
|
120
|
+
margin-bottom: var(--space-between-legend-items);
|
|
128
121
|
vertical-align: middle;
|
|
129
122
|
transition: 0.1s opacity;
|
|
130
123
|
display: flex;
|
|
131
124
|
cursor: pointer;
|
|
132
125
|
white-space: wrap;
|
|
133
126
|
flex-grow: 1;
|
|
127
|
+
&:last-child {
|
|
128
|
+
margin-bottom: 0;
|
|
129
|
+
}
|
|
134
130
|
@include breakpoint(md) {
|
|
135
131
|
white-space: nowrap;
|
|
136
132
|
}
|
|
@@ -142,16 +138,17 @@
|
|
|
142
138
|
}
|
|
143
139
|
.legend-container__ul.single-row {
|
|
144
140
|
width: 100%;
|
|
141
|
+
cursor: pointer;
|
|
145
142
|
list-style: none;
|
|
146
143
|
display: flex;
|
|
147
144
|
flex-direction: row;
|
|
148
145
|
align-items: center;
|
|
149
146
|
justify-content: flex-start;
|
|
150
147
|
flex-wrap: wrap;
|
|
148
|
+
row-gap: var(--space-between-legend-items);
|
|
151
149
|
|
|
152
150
|
& > li {
|
|
153
151
|
margin-right: 1em;
|
|
154
|
-
margin-bottom: 1em;
|
|
155
152
|
white-space: wrap;
|
|
156
153
|
display: flex;
|
|
157
154
|
justify-content: center;
|
|
@@ -166,6 +163,9 @@
|
|
|
166
163
|
}
|
|
167
164
|
}
|
|
168
165
|
}
|
|
166
|
+
.legend-container__ul.patterns-only {
|
|
167
|
+
margin-top: 10px;
|
|
168
|
+
}
|
|
169
169
|
}
|
|
170
170
|
|
|
171
171
|
.bottom .legend-container__ul--single-column:not(.vertical-sorted) {
|
|
@@ -30,7 +30,13 @@ const HexIcon: React.FC<HexIconProps> = props => {
|
|
|
30
30
|
)
|
|
31
31
|
}
|
|
32
32
|
return (
|
|
33
|
-
<Group
|
|
33
|
+
<Group
|
|
34
|
+
top={centroid[1] - 5}
|
|
35
|
+
left={centroid[0] - iconSize}
|
|
36
|
+
color={textColor}
|
|
37
|
+
textAnchor='start'
|
|
38
|
+
key={`hex--${item.key}-${item.value}-${index}`}
|
|
39
|
+
>
|
|
34
40
|
{item.shape === 'Arrow Down' && <AiOutlineArrowDown />}
|
|
35
41
|
{item.shape === 'Arrow Up' && <AiOutlineArrowUp />}
|
|
36
42
|
{item.shape === 'Arrow Right' && <AiOutlineArrowRight />}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React, { useContext } from 'react'
|
|
2
2
|
import ConfigContext from '../../../../context'
|
|
3
3
|
import { MapContext } from '../../../../types/MapContext'
|
|
4
|
+
import { getGeoFillColor } from '../../../../helpers/colors'
|
|
4
5
|
|
|
5
6
|
interface CountyOutputProps {
|
|
6
7
|
counties: any[]
|
|
@@ -11,7 +12,10 @@ interface CountyOutputProps {
|
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
const CountyOutput: React.FC<CountyOutputProps> = ({ path, counties, scale, geoStrokeColor, tooltipId }) => {
|
|
14
|
-
const { applyTooltipsToGeo, applyLegendToRow, displayGeoName, state, data, geoClickHandler } =
|
|
15
|
+
const { applyTooltipsToGeo, applyLegendToRow, displayGeoName, state, data, geoClickHandler } =
|
|
16
|
+
useContext<MapContext>(ConfigContext)
|
|
17
|
+
|
|
18
|
+
const geoFillColor = getGeoFillColor(state)
|
|
15
19
|
return (
|
|
16
20
|
<>
|
|
17
21
|
{counties.map(county => {
|
|
@@ -50,19 +54,49 @@ const CountyOutput: React.FC<CountyOutputProps> = ({ path, counties, scale, geoS
|
|
|
50
54
|
}
|
|
51
55
|
|
|
52
56
|
// When to add pointer cursor
|
|
53
|
-
if (
|
|
57
|
+
if (
|
|
58
|
+
(state.columns.navigate && geoData[state.columns.navigate.name]) ||
|
|
59
|
+
state.tooltips.appearanceType === 'hover'
|
|
60
|
+
) {
|
|
54
61
|
styles.cursor = 'pointer'
|
|
55
62
|
}
|
|
56
63
|
|
|
57
64
|
return (
|
|
58
|
-
<g
|
|
59
|
-
|
|
65
|
+
<g
|
|
66
|
+
key={`key--${county.id}`}
|
|
67
|
+
className={`county county--${geoDisplayName.split(' ').join('')} county--${
|
|
68
|
+
geoData[state.columns.geo.name]
|
|
69
|
+
}`}
|
|
70
|
+
style={styles}
|
|
71
|
+
onClick={() => geoClickHandler(geoDisplayName, geoData)}
|
|
72
|
+
data-tooltip-id={`tooltip__${tooltipId}`}
|
|
73
|
+
data-tooltip-html={toolTip}
|
|
74
|
+
>
|
|
75
|
+
<path
|
|
76
|
+
tabIndex={-1}
|
|
77
|
+
className={`county`}
|
|
78
|
+
stroke={geoStrokeColor}
|
|
79
|
+
d={countyPath}
|
|
80
|
+
strokeWidth={0.75 / scale}
|
|
81
|
+
/>
|
|
60
82
|
</g>
|
|
61
83
|
)
|
|
62
84
|
} else {
|
|
63
85
|
return (
|
|
64
|
-
<g
|
|
65
|
-
|
|
86
|
+
<g
|
|
87
|
+
key={`key--${county.id}`}
|
|
88
|
+
className={`county county--${geoDisplayName.split(' ').join('')}`}
|
|
89
|
+
style={{ fill: geoFillColor }}
|
|
90
|
+
data-tooltip-id={`tooltip__${tooltipId}`}
|
|
91
|
+
data-tooltip-html={toolTip}
|
|
92
|
+
>
|
|
93
|
+
<path
|
|
94
|
+
tabIndex={-1}
|
|
95
|
+
className={`county`}
|
|
96
|
+
stroke={geoStrokeColor}
|
|
97
|
+
d={countyPath}
|
|
98
|
+
strokeWidth={0.75 / scale}
|
|
99
|
+
/>
|
|
66
100
|
</g>
|
|
67
101
|
)
|
|
68
102
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useContext } from 'react'
|
|
2
2
|
import { mesh, Topology } from 'topojson-client'
|
|
3
3
|
import ConfigContext from '../../../../context'
|
|
4
|
+
import { getGeoFillColor, getGeoStrokeColor } from '../../../../helpers/colors'
|
|
4
5
|
|
|
5
6
|
type StateOutputProps = {
|
|
6
7
|
topoData: Topology
|
|
@@ -15,12 +16,19 @@ const StateOutput: React.FC<StateOutputProps> = ({ topoData, path, scale, stateT
|
|
|
15
16
|
return s.properties.name === state.general.statePicked.stateName
|
|
16
17
|
})
|
|
17
18
|
|
|
18
|
-
const geoStrokeColor = state
|
|
19
|
+
const geoStrokeColor = getGeoStrokeColor(state)
|
|
20
|
+
const geoFillColor = getGeoFillColor(state)
|
|
19
21
|
|
|
20
22
|
let stateLines = path(mesh(topoData, geo[0]))
|
|
21
23
|
|
|
22
24
|
return (
|
|
23
|
-
<g
|
|
25
|
+
<g
|
|
26
|
+
key={'single-state'}
|
|
27
|
+
className='single-state'
|
|
28
|
+
style={{ fill: geoFillColor }}
|
|
29
|
+
stroke={geoStrokeColor}
|
|
30
|
+
strokeWidth={0.95 / scale}
|
|
31
|
+
>
|
|
24
32
|
<path tabIndex={-1} className='state-path' d={stateLines} />
|
|
25
33
|
</g>
|
|
26
34
|
)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useContext } from 'react'
|
|
2
|
-
import { geoCentroid
|
|
2
|
+
import { geoCentroid } from 'd3-geo'
|
|
3
3
|
import ConfigContext from './../../../../context'
|
|
4
4
|
import { MapContext } from './../../../../types/MapContext'
|
|
5
5
|
import HexIcon from '../HexIcon'
|
|
@@ -31,7 +31,19 @@ const nudges = {
|
|
|
31
31
|
|
|
32
32
|
// todo: combine hexagonLabel & geoLabel functions
|
|
33
33
|
// todo: move geoLabel functions outside of components for reusability
|
|
34
|
-
const TerritoryHexagon = ({
|
|
34
|
+
const TerritoryHexagon = ({
|
|
35
|
+
dataTooltipHtml,
|
|
36
|
+
dataTooltipId,
|
|
37
|
+
handleShapeClick,
|
|
38
|
+
label,
|
|
39
|
+
stroke,
|
|
40
|
+
strokeWidth,
|
|
41
|
+
territory,
|
|
42
|
+
territoryData,
|
|
43
|
+
text,
|
|
44
|
+
textColor,
|
|
45
|
+
...props
|
|
46
|
+
}) => {
|
|
35
47
|
const { state } = useContext<MapContext>(ConfigContext)
|
|
36
48
|
|
|
37
49
|
const isHex = state.general.displayAsHex
|
|
@@ -115,7 +127,17 @@ const TerritoryHexagon = ({ label, text, stroke, strokeWidth, textColor, territo
|
|
|
115
127
|
let y = state.hexMap.type === 'shapes' ? '30%' : '50%'
|
|
116
128
|
return (
|
|
117
129
|
<>
|
|
118
|
-
<Text
|
|
130
|
+
<Text
|
|
131
|
+
fontSize={14}
|
|
132
|
+
x={'50%'}
|
|
133
|
+
y={y}
|
|
134
|
+
style={{ fill: 'currentColor', stroke: 'initial', fontWeight: 400, opacity: 1, fillOpacity: 1 }}
|
|
135
|
+
textAnchor='middle'
|
|
136
|
+
verticalAnchor='middle'
|
|
137
|
+
onClick={handleShapeClick}
|
|
138
|
+
data-tooltip-id={dataTooltipId}
|
|
139
|
+
data-tooltip-html={dataTooltipHtml}
|
|
140
|
+
>
|
|
119
141
|
{abbr.substring(3)}
|
|
120
142
|
</Text>
|
|
121
143
|
{state.general.displayAsHex && state.hexMap.type === 'shapes' && getArrowDirection(territoryData, geo, true)}
|
|
@@ -127,21 +149,44 @@ const TerritoryHexagon = ({ label, text, stroke, strokeWidth, textColor, territo
|
|
|
127
149
|
|
|
128
150
|
return (
|
|
129
151
|
<g>
|
|
130
|
-
<line
|
|
131
|
-
|
|
152
|
+
<line
|
|
153
|
+
x1={centroid[0]}
|
|
154
|
+
y1={centroid[1]}
|
|
155
|
+
x2={centroid[0] + dx}
|
|
156
|
+
y2={centroid[1] + dy}
|
|
157
|
+
stroke='rgba(0,0,0,.5)'
|
|
158
|
+
strokeWidth={1}
|
|
159
|
+
/>
|
|
160
|
+
<text
|
|
161
|
+
x={4}
|
|
162
|
+
strokeWidth='0'
|
|
163
|
+
fontSize={13}
|
|
164
|
+
style={{ fill: '#202020' }}
|
|
165
|
+
alignmentBaseline='middle'
|
|
166
|
+
transform={`translate(${centroid[0] + dx}, ${centroid[1] + dy})`}
|
|
167
|
+
onClick={handleShapeClick}
|
|
168
|
+
data-tooltip-id={dataTooltipId}
|
|
169
|
+
data-tooltip-html={dataTooltipHtml}
|
|
170
|
+
>
|
|
132
171
|
{abbr.substring(3)}
|
|
133
172
|
</text>
|
|
134
173
|
</g>
|
|
135
174
|
)
|
|
136
175
|
}
|
|
137
176
|
|
|
138
|
-
return
|
|
139
|
-
|
|
140
|
-
<
|
|
141
|
-
<
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
177
|
+
return (
|
|
178
|
+
territoryData && (
|
|
179
|
+
<svg viewBox='-1 -1 46 53' className='territory-wrapper--hex'>
|
|
180
|
+
<g {...props} data-tooltip-html={dataTooltipHtml} data-tooltip-id={dataTooltipId} onClick={handleShapeClick}>
|
|
181
|
+
<polygon
|
|
182
|
+
stroke={stroke}
|
|
183
|
+
strokeWidth={strokeWidth}
|
|
184
|
+
points='22 0 44 12.702 44 38.105 22 50.807 0 38.105 0 12.702'
|
|
185
|
+
/>
|
|
186
|
+
{state.general.displayAsHex && hexagonLabel(territoryData, stroke, false)}
|
|
187
|
+
</g>
|
|
188
|
+
</svg>
|
|
189
|
+
)
|
|
145
190
|
)
|
|
146
191
|
}
|
|
147
192
|
|
|
@@ -2,47 +2,121 @@ import { useContext } from 'react'
|
|
|
2
2
|
import { PatternLines, PatternCircles, PatternWaves } from '@visx/pattern'
|
|
3
3
|
import ConfigContext from './../../../../context'
|
|
4
4
|
import { type MapContext } from '../../../../types/MapContext'
|
|
5
|
-
import { patternSizes } from '
|
|
5
|
+
import { patternSizes } from '../../helpers/patternSizes'
|
|
6
6
|
import { getContrastColor } from '@cdc/core/helpers/cove/accessibility'
|
|
7
|
+
import { type TerritoryShape } from './TerritoryShape'
|
|
7
8
|
|
|
8
|
-
const TerritoryRectangle = ({
|
|
9
|
-
|
|
9
|
+
const TerritoryRectangle: React.FC<TerritoryShape> = ({
|
|
10
|
+
dataTooltipId,
|
|
11
|
+
dataTooltipHtml,
|
|
12
|
+
handleShapeClick,
|
|
13
|
+
hasPattern,
|
|
14
|
+
label,
|
|
15
|
+
stroke,
|
|
16
|
+
strokeWidth,
|
|
17
|
+
territory,
|
|
18
|
+
text,
|
|
19
|
+
textColor,
|
|
20
|
+
backgroundColor,
|
|
21
|
+
...props
|
|
22
|
+
}) => {
|
|
23
|
+
const { state } = useContext<MapContext>(ConfigContext)
|
|
10
24
|
const { territoryData, ...otherProps } = props
|
|
25
|
+
const rectanglePath =
|
|
26
|
+
'M42,0.5 C42.8284271,0.5 43.5,1.17157288 43.5,2 L43.5,2 L43.5,26 C43.5,26.8284271 42.8284271,27.5 42,27.5 L42,27.5 L3,27.5 C2.17157288,27.5 1.5,26.8284271 1.5,26 L1.5,26 L1.5,2 C1.5,1.17157288 2.17157288,0.5 3,0.5 L3,0.5 Z'
|
|
11
27
|
|
|
12
28
|
return (
|
|
13
|
-
<svg viewBox='0 0 45
|
|
14
|
-
<g
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
29
|
+
<svg viewBox='0 0 45 29' key={territory} className={territory}>
|
|
30
|
+
<g
|
|
31
|
+
{...otherProps}
|
|
32
|
+
strokeLinejoin='round'
|
|
33
|
+
tabIndex={-1}
|
|
34
|
+
onClick={handleShapeClick}
|
|
35
|
+
data-tooltip-id={dataTooltipId}
|
|
36
|
+
data-tooltip-html={dataTooltipHtml}
|
|
37
|
+
>
|
|
38
|
+
<path stroke={stroke} strokeWidth={strokeWidth} d={rectanglePath} {...otherProps} />
|
|
39
|
+
<text
|
|
40
|
+
textAnchor='middle'
|
|
41
|
+
dominantBaseline='middle'
|
|
42
|
+
x='50%'
|
|
43
|
+
y='54%'
|
|
44
|
+
fill={text}
|
|
45
|
+
style={{ stroke: 'none' }}
|
|
46
|
+
className='territory-text'
|
|
47
|
+
paintOrder='stroke'
|
|
48
|
+
onClick={handleShapeClick}
|
|
49
|
+
data-tooltip-id={dataTooltipId}
|
|
50
|
+
data-tooltip-html={dataTooltipHtml}
|
|
51
|
+
>
|
|
22
52
|
{label}
|
|
23
53
|
</text>
|
|
24
54
|
|
|
25
55
|
{state.map.patterns.map((patternData, patternIndex) => {
|
|
26
|
-
const patternColor = getContrastColor('#FFF',
|
|
27
|
-
const hasMatchingValues =
|
|
56
|
+
const patternColor = patternData.color || getContrastColor('#FFF', backgroundColor)
|
|
57
|
+
const hasMatchingValues = patternData.dataValue === territoryData[patternData.dataKey]
|
|
28
58
|
|
|
29
59
|
if (!hasMatchingValues) return null
|
|
30
60
|
if (!patternData.pattern) return null
|
|
31
61
|
|
|
32
62
|
return (
|
|
33
63
|
<>
|
|
34
|
-
{patternData?.pattern === 'waves' &&
|
|
35
|
-
|
|
36
|
-
|
|
64
|
+
{patternData?.pattern === 'waves' && (
|
|
65
|
+
<PatternWaves
|
|
66
|
+
id={`territory-${territory}-${patternData?.dataKey}--${patternIndex}`}
|
|
67
|
+
height={patternSizes[patternData?.size] ?? 10}
|
|
68
|
+
width={patternSizes[patternData?.size] ?? 10}
|
|
69
|
+
fill={patternColor}
|
|
70
|
+
complement
|
|
71
|
+
/>
|
|
72
|
+
)}
|
|
73
|
+
{patternData?.pattern === 'circles' && (
|
|
74
|
+
<PatternCircles
|
|
75
|
+
id={`territory-${territory}-${patternData?.dataKey}--${patternIndex}`}
|
|
76
|
+
height={patternSizes[patternData?.size] ?? 10}
|
|
77
|
+
width={patternSizes[patternData?.size] ?? 10}
|
|
78
|
+
fill={patternColor}
|
|
79
|
+
complement
|
|
80
|
+
/>
|
|
81
|
+
)}
|
|
82
|
+
{patternData?.pattern === 'lines' && (
|
|
83
|
+
<PatternLines
|
|
84
|
+
id={`territory-${territory}-${patternData?.dataKey}--${patternIndex}`}
|
|
85
|
+
height={patternSizes[patternData?.size] ?? 6}
|
|
86
|
+
width={patternSizes[patternData?.size] ?? 6}
|
|
87
|
+
stroke={patternColor}
|
|
88
|
+
strokeWidth={1}
|
|
89
|
+
orientation={['diagonalRightToLeft']}
|
|
90
|
+
/>
|
|
91
|
+
)}
|
|
37
92
|
<path
|
|
38
93
|
stroke={stroke}
|
|
39
94
|
strokeWidth={strokeWidth}
|
|
40
|
-
d=
|
|
41
|
-
fill={`url(#territory-${patternData?.dataKey}--${patternIndex})`}
|
|
95
|
+
d={rectanglePath}
|
|
96
|
+
fill={`url(#territory-${territory}-${patternData?.dataKey}--${patternIndex})`}
|
|
42
97
|
color={patternData ? 'white' : textColor}
|
|
43
|
-
className={[
|
|
98
|
+
className={[
|
|
99
|
+
`territory-pattern-${patternData.dataKey}`,
|
|
100
|
+
`territory-pattern-${patternData.dataKey}--${patternData.dataValue}`
|
|
101
|
+
].join(' ')}
|
|
44
102
|
/>
|
|
45
|
-
<text
|
|
103
|
+
<text
|
|
104
|
+
textAnchor='middle'
|
|
105
|
+
dominantBaseline='middle'
|
|
106
|
+
x='50%'
|
|
107
|
+
y='54%'
|
|
108
|
+
fill={text}
|
|
109
|
+
style={{
|
|
110
|
+
fill: patternData ? 'white' : 'black',
|
|
111
|
+
stroke: patternData ? 'black' : textColor,
|
|
112
|
+
strokeWidth: patternData ? 6 : 0
|
|
113
|
+
}}
|
|
114
|
+
className='territory-text'
|
|
115
|
+
paint-order='stroke'
|
|
116
|
+
onClick={handleShapeClick}
|
|
117
|
+
data-tooltip-id={dataTooltipId}
|
|
118
|
+
data-tooltip-html={dataTooltipHtml}
|
|
119
|
+
>
|
|
46
120
|
{label}
|
|
47
121
|
</text>
|
|
48
122
|
</>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type TerritoryShape = {
|
|
2
|
+
handleShapeClick: () => void
|
|
3
|
+
dataTooltipHtml: string
|
|
4
|
+
dataTooltipId: string
|
|
5
|
+
hasPattern: boolean
|
|
6
|
+
label: string
|
|
7
|
+
stroke: string
|
|
8
|
+
strokeWidth: number
|
|
9
|
+
territory: string
|
|
10
|
+
territoryData: object
|
|
11
|
+
text: string
|
|
12
|
+
textColor: string
|
|
13
|
+
}
|