@cdc/map 4.24.9 → 4.24.11
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/LICENSE +201 -0
- package/dist/cdcmap.js +26371 -25867
- package/examples/private/DEV-9644.json +184 -0
- package/package.json +3 -3
- package/src/CdcMap.tsx +25 -12
- package/src/_stories/CdcMap.stories.tsx +2 -6
- package/src/_stories/CdcMapLegend.stories.tsx +86 -0
- package/src/_stories/_mock/usa-state-gradient.json +238 -0
- package/src/_stories/_mock/wastewater-map.json +208 -0
- package/src/components/Annotation/AnnotationDropdown.styles.css +0 -1
- package/src/components/CityList.tsx +55 -10
- package/src/components/DataTable.tsx +94 -17
- package/src/components/EditorPanel/components/EditorPanel.tsx +118 -64
- 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 +23 -19
- package/src/components/Legend/components/index.scss +23 -10
- package/src/components/UsaMap/components/HexIcon.tsx +7 -1
- package/src/components/UsaMap/components/Territory/Territory.Hexagon.tsx +56 -11
- package/src/components/UsaMap/components/Territory/Territory.Rectangle.tsx +88 -15
- package/src/components/UsaMap/components/Territory/TerritoryShape.ts +13 -0
- package/src/components/UsaMap/components/UsaMap.County.tsx +49 -48
- package/src/components/UsaMap/components/UsaMap.State.tsx +5 -4
- package/src/components/UsaMap/helpers/shapes.ts +207 -0
- package/src/coreStyles_map.scss +3 -0
- package/src/data/initial-state.js +1 -0
- package/src/index.jsx +7 -1
- package/src/scss/editor-panel.scss +5 -13
- package/src/scss/filters.scss +2 -7
- package/src/scss/main.scss +4 -9
- package/src/scss/map.scss +6 -6
- package/src/scss/mixins.scss +47 -0
- package/src/types/MapConfig.ts +6 -3
- package/src/types/MapContext.ts +0 -1
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { useContext } from 'react'
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
Accordion,
|
|
4
|
+
AccordionItem,
|
|
5
|
+
AccordionItemHeading,
|
|
6
|
+
AccordionItemPanel,
|
|
7
|
+
AccordionItemButton
|
|
8
|
+
} from 'react-accessible-accordion'
|
|
3
9
|
import ConfigContext from '../../../../context'
|
|
4
10
|
import { type MapContext } from '../../../../types/MapContext'
|
|
5
11
|
import Button from '@cdc/core/components/elements/Button'
|
|
@@ -41,7 +47,11 @@ const PatternSettings = ({ name }: PanelProps) => {
|
|
|
41
47
|
}
|
|
42
48
|
|
|
43
49
|
/** Updates the map pattern at a given index */
|
|
44
|
-
const handleUpdateGeoPattern = (
|
|
50
|
+
const handleUpdateGeoPattern = (
|
|
51
|
+
value: string,
|
|
52
|
+
index: number,
|
|
53
|
+
keyToUpdate: 'dataKey' | 'pattern' | 'dataValue' | 'size' | 'label' | 'color'
|
|
54
|
+
) => {
|
|
45
55
|
const { features: unitedStates } = feature(topoJSON, topoJSON.objects.states)
|
|
46
56
|
const updatedPatterns = [...state.map.patterns]
|
|
47
57
|
|
|
@@ -68,7 +78,9 @@ const PatternSettings = ({ name }: PanelProps) => {
|
|
|
68
78
|
|
|
69
79
|
// Log a warning if the contrast check fails
|
|
70
80
|
if (!contrastCheck) {
|
|
71
|
-
console.warn(`COVE: pattern contrast check failed on ${geoData?.[state.columns.geo.name]} for ${
|
|
81
|
+
console.warn(`COVE: pattern contrast check failed on ${geoData?.[state.columns.geo.name]} for ${
|
|
82
|
+
patternData.dataKey
|
|
83
|
+
} with:
|
|
72
84
|
pattern color: ${patternColor}
|
|
73
85
|
contrast: ${getColorContrast(currentFill, patternColor)}
|
|
74
86
|
`)
|
|
@@ -78,7 +90,9 @@ const PatternSettings = ({ name }: PanelProps) => {
|
|
|
78
90
|
})
|
|
79
91
|
})
|
|
80
92
|
|
|
81
|
-
const editorErrorMessage = updatedPatterns.some(pattern => pattern.contrastCheck === false)
|
|
93
|
+
const editorErrorMessage = updatedPatterns.some(pattern => pattern.contrastCheck === false)
|
|
94
|
+
? 'One or more patterns do not pass the WCAG 2.1 contrast ratio of 3:1.'
|
|
95
|
+
: ''
|
|
82
96
|
|
|
83
97
|
// Update the state with the new patterns and error message
|
|
84
98
|
setState(prevState => ({
|
|
@@ -116,7 +130,12 @@ const PatternSettings = ({ name }: PanelProps) => {
|
|
|
116
130
|
<AccordionItemButton>{name}</AccordionItemButton>
|
|
117
131
|
</AccordionItemHeading>
|
|
118
132
|
<AccordionItemPanel>
|
|
119
|
-
{patterns.length > 0 &&
|
|
133
|
+
{patterns.length > 0 && (
|
|
134
|
+
<Alert
|
|
135
|
+
type={checkPatternContrasts() ? 'success' : 'danger'}
|
|
136
|
+
message='Pattern colors must comply with <br /> <a href="https://www.w3.org/TR/WCAG21/">WCAG 2.1</a> 3:1 contrast ratio.'
|
|
137
|
+
/>
|
|
138
|
+
)}
|
|
120
139
|
<br />
|
|
121
140
|
|
|
122
141
|
{patterns &&
|
|
@@ -133,13 +152,26 @@ const PatternSettings = ({ name }: PanelProps) => {
|
|
|
133
152
|
<Accordion allowZeroExpanded key={`accordion-pattern--${patternIndex}`}>
|
|
134
153
|
<AccordionItem>
|
|
135
154
|
<AccordionItemHeading>
|
|
136
|
-
<AccordionItemButton>
|
|
155
|
+
<AccordionItemButton>
|
|
156
|
+
{pattern.dataKey ? `${pattern.dataKey}: ${pattern.dataValue ?? 'No Value'}` : 'Select Column'}
|
|
157
|
+
</AccordionItemButton>
|
|
137
158
|
</AccordionItemHeading>
|
|
138
159
|
<AccordionItemPanel>
|
|
139
160
|
<>
|
|
140
|
-
{pattern.contrastCheck ?? true ?
|
|
161
|
+
{pattern.contrastCheck ?? true ? (
|
|
162
|
+
<Alert type='success' message='This pattern passes contrast checks' />
|
|
163
|
+
) : (
|
|
164
|
+
<Alert
|
|
165
|
+
type='danger'
|
|
166
|
+
message='Error: <a href="https://webaim.org/resources/contrastchecker/" target="_blank"> Review Color Contrast</a>'
|
|
167
|
+
/>
|
|
168
|
+
)}{' '}
|
|
141
169
|
<label htmlFor={`pattern-dataKey--${patternIndex}`}>Data Key:</label>
|
|
142
|
-
<select
|
|
170
|
+
<select
|
|
171
|
+
id={`pattern-dataKey--${patternIndex}`}
|
|
172
|
+
value={pattern.dataKey !== '' ? pattern.dataKey : 'Select'}
|
|
173
|
+
onChange={e => handleUpdateGeoPattern(e.target.value, patternIndex, 'dataKey')}
|
|
174
|
+
>
|
|
143
175
|
{/* TODO: sort these? */}
|
|
144
176
|
{dataKeyOptions.map((d, index) => {
|
|
145
177
|
return (
|
|
@@ -151,14 +183,28 @@ const PatternSettings = ({ name }: PanelProps) => {
|
|
|
151
183
|
</select>
|
|
152
184
|
<label htmlFor={`pattern-dataValue--${patternIndex}`}>
|
|
153
185
|
Data Value:
|
|
154
|
-
<input
|
|
186
|
+
<input
|
|
187
|
+
type='text'
|
|
188
|
+
onChange={e => handleUpdateGeoPattern(e.target.value, patternIndex, 'dataValue')}
|
|
189
|
+
id={`pattern-dataValue--${patternIndex}`}
|
|
190
|
+
value={pattern.dataValue === '' ? '' : pattern.dataValue}
|
|
191
|
+
/>
|
|
155
192
|
</label>
|
|
156
193
|
<label htmlFor={`pattern-label--${patternIndex}`}>
|
|
157
194
|
Label (optional):
|
|
158
|
-
<input
|
|
195
|
+
<input
|
|
196
|
+
type='text'
|
|
197
|
+
onChange={e => handleUpdateGeoPattern(e.target.value, patternIndex, 'label')}
|
|
198
|
+
id={`pattern-dataValue--${patternIndex}`}
|
|
199
|
+
value={pattern.label === '' ? '' : pattern.label}
|
|
200
|
+
/>
|
|
159
201
|
</label>
|
|
160
202
|
<label htmlFor={`pattern-type--${patternIndex}`}>Pattern Type:</label>
|
|
161
|
-
<select
|
|
203
|
+
<select
|
|
204
|
+
id={`pattern-type--${patternIndex}`}
|
|
205
|
+
value={pattern?.pattern}
|
|
206
|
+
onChange={e => handleUpdateGeoPattern(e.target.value, patternIndex, 'pattern')}
|
|
207
|
+
>
|
|
162
208
|
{patternTypes.map((patternName, index) => (
|
|
163
209
|
<option value={patternName} key={index}>
|
|
164
210
|
{patternName}
|
|
@@ -166,7 +212,11 @@ const PatternSettings = ({ name }: PanelProps) => {
|
|
|
166
212
|
))}
|
|
167
213
|
</select>
|
|
168
214
|
<label htmlFor={`pattern-size--${patternIndex}`}>Pattern Size:</label>
|
|
169
|
-
<select
|
|
215
|
+
<select
|
|
216
|
+
id={`pattern-size--${patternIndex}`}
|
|
217
|
+
value={pattern?.size}
|
|
218
|
+
onChange={e => handleUpdateGeoPattern(e.target.value, patternIndex, 'size')}
|
|
219
|
+
>
|
|
170
220
|
{['small', 'medium', 'large'].map((size, index) => (
|
|
171
221
|
<option value={size} key={index}>
|
|
172
222
|
{size}
|
|
@@ -178,16 +228,25 @@ const PatternSettings = ({ name }: PanelProps) => {
|
|
|
178
228
|
Pattern Color
|
|
179
229
|
<Tooltip style={{ textTransform: 'none' }}>
|
|
180
230
|
<Tooltip.Target>
|
|
181
|
-
<Icon
|
|
231
|
+
<Icon
|
|
232
|
+
display='question'
|
|
233
|
+
style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }}
|
|
234
|
+
/>
|
|
182
235
|
</Tooltip.Target>
|
|
183
236
|
<Tooltip.Content>
|
|
184
237
|
<p>{`If this setting is used, it is the reponsibility of the visualization author to verify the visualization colors meet WCAG 3:1 contrast ratios.`}</p>
|
|
185
238
|
</Tooltip.Content>
|
|
186
239
|
</Tooltip>
|
|
187
|
-
<input
|
|
240
|
+
<input
|
|
241
|
+
type='text'
|
|
242
|
+
value={pattern.color || ''}
|
|
243
|
+
id='patternColor'
|
|
244
|
+
name='patternColor'
|
|
245
|
+
onChange={e => handleUpdateGeoPattern(e.target.value, patternIndex, 'color')}
|
|
246
|
+
/>
|
|
188
247
|
</label>
|
|
189
248
|
</div>
|
|
190
|
-
<Button onClick={e => handleRemovePattern(patternIndex)} className='btn btn
|
|
249
|
+
<Button onClick={e => handleRemovePattern(patternIndex)} className='btn btn-danger'>
|
|
191
250
|
Remove Pattern
|
|
192
251
|
</Button>
|
|
193
252
|
</>
|
|
@@ -196,7 +255,7 @@ const PatternSettings = ({ name }: PanelProps) => {
|
|
|
196
255
|
</Accordion>
|
|
197
256
|
)
|
|
198
257
|
})}
|
|
199
|
-
<button className='btn full-width' onClick={handleAddGeoPattern}>
|
|
258
|
+
<button className='btn btn-primary full-width' onClick={handleAddGeoPattern}>
|
|
200
259
|
Add Geo Pattern
|
|
201
260
|
</button>
|
|
202
261
|
</AccordionItemPanel>
|
|
@@ -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 { 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
|
|
@@ -38,7 +42,6 @@ const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
|
|
|
38
42
|
setRuntimeLegend,
|
|
39
43
|
state,
|
|
40
44
|
viewport,
|
|
41
|
-
getTextWidth,
|
|
42
45
|
mapId
|
|
43
46
|
} = useContext(ConfigContext)
|
|
44
47
|
|
|
@@ -100,13 +103,14 @@ const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
|
|
|
100
103
|
})
|
|
101
104
|
}
|
|
102
105
|
|
|
103
|
-
const legendList = () => {
|
|
104
|
-
const formattedItems = getFormattedLegendItems()
|
|
106
|
+
const legendList = (patternsOnly = false) => {
|
|
107
|
+
const formattedItems = patternsOnly ? [] : getFormattedLegendItems()
|
|
108
|
+
const patternsOnlyFont = isMobileHeightViewport(currentViewport) ? '12px' : '14px'
|
|
105
109
|
let legendItems
|
|
106
110
|
|
|
107
111
|
legendItems = formattedItems.map((item, idx) => {
|
|
108
112
|
const handleListItemClass = () => {
|
|
109
|
-
let classes = ['legend-container__li']
|
|
113
|
+
let classes = ['legend-container__li', 'd-flex', 'align-items-center']
|
|
110
114
|
if (item.disabled) classes.push('legend-container__li--disabled')
|
|
111
115
|
if (item.special) classes.push('legend-container__li--special-class')
|
|
112
116
|
return classes.join(' ')
|
|
@@ -127,11 +131,7 @@ const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
|
|
|
127
131
|
}}
|
|
128
132
|
tabIndex={0}
|
|
129
133
|
>
|
|
130
|
-
<LegendShape
|
|
131
|
-
shape={state.legend.style === 'boxes' ? 'square' : 'circle'}
|
|
132
|
-
viewport={viewport}
|
|
133
|
-
fill={item.color}
|
|
134
|
-
/>
|
|
134
|
+
<LegendShape shape={state.legend.style === 'boxes' ? 'square' : 'circle'} fill={item.color} />
|
|
135
135
|
<span>{item.label}</span>
|
|
136
136
|
</li>
|
|
137
137
|
)
|
|
@@ -196,7 +196,9 @@ const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
|
|
|
196
196
|
/>
|
|
197
197
|
</svg>
|
|
198
198
|
</span>
|
|
199
|
-
<p style={{ lineHeight: '22.4px'
|
|
199
|
+
<p style={{ lineHeight: '22.4px', fontSize: patternsOnly ? patternsOnlyFont : '16px' }}>
|
|
200
|
+
{patternData.label || patternData.dataValue || ''}
|
|
201
|
+
</p>
|
|
200
202
|
</li>
|
|
201
203
|
</>
|
|
202
204
|
)
|
|
@@ -205,6 +207,8 @@ const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
|
|
|
205
207
|
|
|
206
208
|
return legendItems
|
|
207
209
|
}
|
|
210
|
+
const legendListItems = legendList(state.legend.style === 'gradient')
|
|
211
|
+
|
|
208
212
|
const { legendClasses } = useDataVizClasses(state, viewport)
|
|
209
213
|
|
|
210
214
|
const handleReset = e => {
|
|
@@ -274,15 +278,15 @@ const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
|
|
|
274
278
|
<LegendGradient
|
|
275
279
|
labels={getFormattedLegendItems().map(item => item?.label) ?? []}
|
|
276
280
|
colors={getFormattedLegendItems().map(item => item?.color) ?? []}
|
|
277
|
-
values={getFormattedLegendItems().map(item => item?.value) ?? []}
|
|
278
281
|
dimensions={dimensions}
|
|
279
|
-
|
|
282
|
+
parentPaddingToSubtract={containerWidthPadding + (legend.hideBorder ? 0 : LEGEND_PADDING)}
|
|
280
283
|
config={state}
|
|
281
|
-
getTextWidth={getTextWidth}
|
|
282
284
|
/>
|
|
283
|
-
|
|
284
|
-
{
|
|
285
|
-
|
|
285
|
+
{!!legendListItems.length && (
|
|
286
|
+
<ul className={legendClasses.ul.join(' ') || ''} aria-label='Legend items'>
|
|
287
|
+
{legendListItems}
|
|
288
|
+
</ul>
|
|
289
|
+
)}
|
|
286
290
|
{(state.visual.additionalCityStyles.some(c => c.label) || state.visual.cityStyleLabel) && (
|
|
287
291
|
<>
|
|
288
292
|
<hr />
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
@import '
|
|
2
|
-
@import '@cdc/core/styles/heading-colors';
|
|
1
|
+
@import '../../../scss/mixins';
|
|
3
2
|
|
|
4
3
|
.cdc-map-inner-container {
|
|
5
4
|
.map-container.world aside.side {
|
|
@@ -7,7 +6,7 @@
|
|
|
7
6
|
}
|
|
8
7
|
@include breakpointClass(md) {
|
|
9
8
|
.map-container.world aside.side {
|
|
10
|
-
border-top:
|
|
9
|
+
border-top: var(--lightGray) 1px solid;
|
|
11
10
|
position: absolute;
|
|
12
11
|
box-shadow: rgba(0, 0, 0, 0.2) 0 10px 18px;
|
|
13
12
|
}
|
|
@@ -16,12 +15,12 @@
|
|
|
16
15
|
aside {
|
|
17
16
|
background-color: #fff;
|
|
18
17
|
z-index: 6;
|
|
19
|
-
border-top:
|
|
18
|
+
border-top: var(--lightGray) 1px solid;
|
|
20
19
|
|
|
21
20
|
@include breakpointClass(md) {
|
|
22
21
|
&.bottom,
|
|
23
22
|
&.top {
|
|
24
|
-
border:
|
|
23
|
+
border: var(--lightGray) 1px solid;
|
|
25
24
|
}
|
|
26
25
|
&.side {
|
|
27
26
|
z-index: 1;
|
|
@@ -32,7 +31,7 @@
|
|
|
32
31
|
align-self: flex-start;
|
|
33
32
|
z-index: 4;
|
|
34
33
|
right: 1em;
|
|
35
|
-
border:
|
|
34
|
+
border: var(--lightGray) 1px solid;
|
|
36
35
|
top: 2em;
|
|
37
36
|
right: 1em;
|
|
38
37
|
|
|
@@ -68,8 +67,10 @@
|
|
|
68
67
|
}
|
|
69
68
|
|
|
70
69
|
.legend-container {
|
|
71
|
-
padding: 1em;
|
|
72
70
|
position: relative;
|
|
71
|
+
&.legend-padding {
|
|
72
|
+
padding: 15px;
|
|
73
|
+
}
|
|
73
74
|
.legend-container__title {
|
|
74
75
|
font-size: 1.3em;
|
|
75
76
|
padding-bottom: 0;
|
|
@@ -101,7 +102,10 @@
|
|
|
101
102
|
list-style: none;
|
|
102
103
|
padding-top: 1em;
|
|
103
104
|
display: grid;
|
|
104
|
-
grid-template-columns: 1fr
|
|
105
|
+
grid-template-columns: 1fr;
|
|
106
|
+
@include breakpoint(md) {
|
|
107
|
+
grid-template-columns: 1fr 1fr;
|
|
108
|
+
}
|
|
105
109
|
|
|
106
110
|
button {
|
|
107
111
|
font-size: unset;
|
|
@@ -129,8 +133,11 @@
|
|
|
129
133
|
transition: 0.1s opacity;
|
|
130
134
|
display: flex;
|
|
131
135
|
cursor: pointer;
|
|
132
|
-
white-space:
|
|
136
|
+
white-space: wrap;
|
|
133
137
|
flex-grow: 1;
|
|
138
|
+
@include breakpoint(md) {
|
|
139
|
+
white-space: nowrap;
|
|
140
|
+
}
|
|
134
141
|
|
|
135
142
|
&.legend-container__li--disabled {
|
|
136
143
|
opacity: 0.4;
|
|
@@ -149,17 +156,23 @@
|
|
|
149
156
|
& > li {
|
|
150
157
|
margin-right: 1em;
|
|
151
158
|
margin-bottom: 1em;
|
|
152
|
-
white-space:
|
|
159
|
+
white-space: wrap;
|
|
153
160
|
display: flex;
|
|
154
161
|
justify-content: center;
|
|
155
162
|
align-items: center;
|
|
156
163
|
vertical-align: middle;
|
|
164
|
+
@include breakpoint(md) {
|
|
165
|
+
white-space: nowrap;
|
|
166
|
+
}
|
|
157
167
|
|
|
158
168
|
& svg {
|
|
159
169
|
vertical-align: baseline;
|
|
160
170
|
}
|
|
161
171
|
}
|
|
162
172
|
}
|
|
173
|
+
.legend-container__ul.patterns-only {
|
|
174
|
+
margin-top: 10px;
|
|
175
|
+
}
|
|
163
176
|
}
|
|
164
177
|
|
|
165
178
|
.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,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,8 +149,25 @@ 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>
|
|
@@ -136,12 +175,18 @@ const TerritoryHexagon = ({ label, text, stroke, strokeWidth, textColor, territo
|
|
|
136
175
|
}
|
|
137
176
|
|
|
138
177
|
return (
|
|
139
|
-
|
|
140
|
-
<
|
|
141
|
-
<
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
178
|
+
territoryData && (
|
|
179
|
+
<svg viewBox='0 0 45 51' 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
|
|
|
@@ -4,21 +4,50 @@ import ConfigContext from './../../../../context'
|
|
|
4
4
|
import { type MapContext } from '../../../../types/MapContext'
|
|
5
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
|
+
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
|
+
...props
|
|
21
|
+
}) => {
|
|
9
22
|
const { state, supportedTerritories } = useContext<MapContext>(ConfigContext)
|
|
10
23
|
const { territoryData, ...otherProps } = props
|
|
24
|
+
const rectanglePath =
|
|
25
|
+
'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'
|
|
11
26
|
|
|
12
27
|
return (
|
|
13
28
|
<svg viewBox='0 0 45 28' key={territory} className={territory}>
|
|
14
|
-
<g
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
29
|
+
<g
|
|
30
|
+
{...otherProps}
|
|
31
|
+
strokeLinejoin='round'
|
|
32
|
+
tabIndex={-1}
|
|
33
|
+
onClick={handleShapeClick}
|
|
34
|
+
data-tooltip-id={dataTooltipId}
|
|
35
|
+
data-tooltip-html={dataTooltipHtml}
|
|
36
|
+
>
|
|
37
|
+
<path stroke={stroke} strokeWidth={strokeWidth} d={rectanglePath} {...otherProps} />
|
|
38
|
+
<text
|
|
39
|
+
textAnchor='middle'
|
|
40
|
+
dominantBaseline='middle'
|
|
41
|
+
x='50%'
|
|
42
|
+
y='54%'
|
|
43
|
+
fill={text}
|
|
44
|
+
style={{ stroke: textColor, strokeWidth: 1 }}
|
|
45
|
+
className='territory-text'
|
|
46
|
+
paintOrder='stroke'
|
|
47
|
+
onClick={handleShapeClick}
|
|
48
|
+
data-tooltip-id={dataTooltipId}
|
|
49
|
+
data-tooltip-html={dataTooltipHtml}
|
|
50
|
+
>
|
|
22
51
|
{label}
|
|
23
52
|
</text>
|
|
24
53
|
|
|
@@ -31,18 +60,62 @@ const TerritoryRectangle = ({ label, text, stroke, strokeWidth, textColor, hasPa
|
|
|
31
60
|
|
|
32
61
|
return (
|
|
33
62
|
<>
|
|
34
|
-
{patternData?.pattern === 'waves' &&
|
|
35
|
-
|
|
36
|
-
|
|
63
|
+
{patternData?.pattern === 'waves' && (
|
|
64
|
+
<PatternWaves
|
|
65
|
+
id={`territory-${patternData?.dataKey}--${patternIndex}`}
|
|
66
|
+
height={patternSizes[patternData?.size] ?? 10}
|
|
67
|
+
width={patternSizes[patternData?.size] ?? 10}
|
|
68
|
+
fill={patternColor}
|
|
69
|
+
complement
|
|
70
|
+
/>
|
|
71
|
+
)}
|
|
72
|
+
{patternData?.pattern === 'circles' && (
|
|
73
|
+
<PatternCircles
|
|
74
|
+
id={`territory-${patternData?.dataKey}--${patternIndex}`}
|
|
75
|
+
height={patternSizes[patternData?.size] ?? 10}
|
|
76
|
+
width={patternSizes[patternData?.size] ?? 10}
|
|
77
|
+
fill={patternColor}
|
|
78
|
+
complement
|
|
79
|
+
/>
|
|
80
|
+
)}
|
|
81
|
+
{patternData?.pattern === 'lines' && (
|
|
82
|
+
<PatternLines
|
|
83
|
+
id={`territory-${patternData?.dataKey}--${patternIndex}`}
|
|
84
|
+
height={patternSizes[patternData?.size] ?? 6}
|
|
85
|
+
width={patternSizes[patternData?.size] ?? 6}
|
|
86
|
+
stroke={patternColor}
|
|
87
|
+
strokeWidth={1}
|
|
88
|
+
orientation={['diagonalRightToLeft']}
|
|
89
|
+
/>
|
|
90
|
+
)}
|
|
37
91
|
<path
|
|
38
92
|
stroke={stroke}
|
|
39
93
|
strokeWidth={strokeWidth}
|
|
40
|
-
d=
|
|
94
|
+
d={rectanglePath}
|
|
41
95
|
fill={`url(#territory-${patternData?.dataKey}--${patternIndex})`}
|
|
42
96
|
color={patternData ? 'white' : textColor}
|
|
43
|
-
className={[
|
|
97
|
+
className={[
|
|
98
|
+
`territory-pattern-${patternData.dataKey}`,
|
|
99
|
+
`territory-pattern-${patternData.dataKey}--${patternData.dataValue}`
|
|
100
|
+
].join(' ')}
|
|
44
101
|
/>
|
|
45
|
-
<text
|
|
102
|
+
<text
|
|
103
|
+
textAnchor='middle'
|
|
104
|
+
dominantBaseline='middle'
|
|
105
|
+
x='50%'
|
|
106
|
+
y='54%'
|
|
107
|
+
fill={text}
|
|
108
|
+
style={{
|
|
109
|
+
fill: patternData ? 'white' : 'black',
|
|
110
|
+
stroke: patternData ? 'black' : textColor,
|
|
111
|
+
strokeWidth: patternData ? 6 : 0
|
|
112
|
+
}}
|
|
113
|
+
className='territory-text'
|
|
114
|
+
paint-order='stroke'
|
|
115
|
+
onClick={handleShapeClick}
|
|
116
|
+
data-tooltip-id={dataTooltipId}
|
|
117
|
+
data-tooltip-html={dataTooltipHtml}
|
|
118
|
+
>
|
|
46
119
|
{label}
|
|
47
120
|
</text>
|
|
48
121
|
</>
|
|
@@ -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
|
+
}
|