@cdc/map 4.24.2 → 4.24.4
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 +33089 -32197
- package/examples/508.json +548 -0
- package/examples/default-county.json +0 -28
- package/examples/default-hex.json +110 -13
- package/examples/default-usa.json +69 -28
- package/examples/example-city-state.json +51 -17
- package/examples/hex-colors.json +507 -0
- package/examples/usa-special-class-legend.json +501 -0
- package/index.html +10 -9
- package/package.json +3 -3
- package/src/CdcMap.tsx +200 -125
- package/src/components/BubbleList.jsx +16 -6
- package/src/components/CityList.jsx +64 -12
- package/src/components/DataTable.jsx +7 -7
- package/src/components/EditorPanel/components/EditorPanel.tsx +1457 -1367
- package/src/components/EditorPanel/components/Error.tsx +12 -0
- package/src/components/EditorPanel/components/HexShapeSettings.tsx +16 -1
- package/src/components/EditorPanel/components/Panel.PatternSettings-style.css +5 -0
- package/src/components/EditorPanel/components/Panel.PatternSettings.tsx +18 -1
- package/src/components/Legend/components/Legend.tsx +80 -15
- package/src/components/Legend/components/LegendItem.Hex.tsx +1 -1
- package/src/components/Legend/components/index.scss +31 -5
- package/src/components/UsaMap/components/HexIcon.tsx +41 -0
- package/src/components/UsaMap/components/Territory/Territory.Hexagon.tsx +38 -19
- package/src/components/UsaMap/components/Territory/Territory.Rectangle.tsx +11 -22
- package/src/components/UsaMap/components/UsaMap.County.tsx +7 -4
- package/src/components/UsaMap/components/UsaMap.Region.tsx +13 -38
- package/src/components/UsaMap/components/UsaMap.SingleState.tsx +5 -3
- package/src/components/UsaMap/components/UsaMap.State.tsx +77 -60
- package/src/components/UsaMap/helpers/patternSizes.tsx +5 -0
- package/src/components/WorldMap/components/WorldMap.jsx +20 -3
- package/src/data/initial-state.js +3 -0
- package/src/data/supported-geos.js +2 -1
- package/src/hooks/useMapLayers.tsx +2 -2
- package/src/scss/editor-panel.scss +4 -12
- package/src/scss/main.scss +3 -1
- package/src/scss/map.scss +1 -1
- package/src/types/MapConfig.ts +7 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
const Error = ({ state }) => {
|
|
2
|
+
return (
|
|
3
|
+
<section className='waiting'>
|
|
4
|
+
<section className='waiting-container'>
|
|
5
|
+
<h3>Error With Configuration</h3>
|
|
6
|
+
<p>{state.runtime.editorErrorMessage}</p>
|
|
7
|
+
</section>
|
|
8
|
+
</section>
|
|
9
|
+
)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default Error
|
|
@@ -4,7 +4,7 @@ import parse from 'html-react-parser'
|
|
|
4
4
|
import { AiOutlineArrowUp, AiOutlineArrowDown, AiOutlineArrowRight } from 'react-icons/ai'
|
|
5
5
|
import useDataVizClasses from '@cdc/core/helpers/useDataVizClasses'
|
|
6
6
|
|
|
7
|
-
const shapeOptions = ['Arrow Up', 'Arrow Down', 'Arrow Right', 'None']
|
|
7
|
+
const shapeOptions = ['Arrow Up', 'Arrow Down', 'Arrow Right', 'Arrow Left', 'None']
|
|
8
8
|
|
|
9
9
|
// todo: Move duplicated operators to CORE
|
|
10
10
|
export const DATA_OPERATOR_LESS = '<'
|
|
@@ -221,6 +221,21 @@ const HexSettingShapeColumns = props => {
|
|
|
221
221
|
{[DATA_OPERATOR_EQUAL].map(option => {
|
|
222
222
|
return <option value={option}>{option}</option>
|
|
223
223
|
})}
|
|
224
|
+
{[DATA_OPERATOR_NOTEQUAL].map(option => {
|
|
225
|
+
return <option value={option}>{option}</option>
|
|
226
|
+
})}
|
|
227
|
+
{[DATA_OPERATOR_LESS].map(option => {
|
|
228
|
+
return <option value={option}>{option}</option>
|
|
229
|
+
})}
|
|
230
|
+
{[DATA_OPERATOR_GREATER].map(option => {
|
|
231
|
+
return <option value={option}>{option}</option>
|
|
232
|
+
})}
|
|
233
|
+
{[DATA_OPERATOR_LESSEQUAL].map(option => {
|
|
234
|
+
return <option value={option}>{option}</option>
|
|
235
|
+
})}
|
|
236
|
+
{[DATA_OPERATOR_GREATEREQUAL].map(option => {
|
|
237
|
+
return <option value={option}>{option}</option>
|
|
238
|
+
})}
|
|
224
239
|
</select>
|
|
225
240
|
</div>
|
|
226
241
|
<div className='cove-accordion__panel-col cove-input'>
|
|
@@ -3,6 +3,9 @@ import { Accordion, AccordionItem, AccordionItemHeading, AccordionItemPanel, Acc
|
|
|
3
3
|
import ConfigContext from '../../../context'
|
|
4
4
|
import { type MapContext } from '../../../types/MapContext'
|
|
5
5
|
import Button from '@cdc/core/components/elements/Button'
|
|
6
|
+
import Tooltip from '@cdc/core/components/ui/Tooltip'
|
|
7
|
+
import Icon from '@cdc/core/components/ui/Icon'
|
|
8
|
+
import './Panel.PatternSettings-style.css'
|
|
6
9
|
|
|
7
10
|
type PanelProps = {
|
|
8
11
|
name: string
|
|
@@ -32,7 +35,7 @@ const PatternSettings = ({ name }: PanelProps) => {
|
|
|
32
35
|
}
|
|
33
36
|
|
|
34
37
|
/** Updates the map pattern at a given index */
|
|
35
|
-
const handleUpdateGeoPattern = (value: string, index: number, keyToUpdate: 'dataKey' | 'pattern' | 'dataValue' | 'size' | 'label') => {
|
|
38
|
+
const handleUpdateGeoPattern = (value: string, index: number, keyToUpdate: 'dataKey' | 'pattern' | 'dataValue' | 'size' | 'label' | 'color') => {
|
|
36
39
|
const updatedPatterns = [...state.map.patterns]
|
|
37
40
|
updatedPatterns[index] = { ...updatedPatterns[index], [keyToUpdate]: value }
|
|
38
41
|
|
|
@@ -120,6 +123,20 @@ const PatternSettings = ({ name }: PanelProps) => {
|
|
|
120
123
|
</option>
|
|
121
124
|
))}
|
|
122
125
|
</select>
|
|
126
|
+
<div className='pattern-input__color'>
|
|
127
|
+
<label htmlFor='patternColor'>
|
|
128
|
+
Pattern Color
|
|
129
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
130
|
+
<Tooltip.Target>
|
|
131
|
+
<Icon display='question' style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }} />
|
|
132
|
+
</Tooltip.Target>
|
|
133
|
+
<Tooltip.Content>
|
|
134
|
+
<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>
|
|
135
|
+
</Tooltip.Content>
|
|
136
|
+
</Tooltip>
|
|
137
|
+
<input type='text' value={pattern.color || ''} id='patternColor' name='patternColor' onChange={e => handleUpdateGeoPattern(e.target.value, patternIndex, 'color')} />
|
|
138
|
+
</label>
|
|
139
|
+
</div>
|
|
123
140
|
<Button onClick={e => handleRemovePattern(patternIndex)} className='btn btn--remove warn'>
|
|
124
141
|
Remove Pattern
|
|
125
142
|
</Button>
|
|
@@ -1,18 +1,26 @@
|
|
|
1
1
|
//TODO: Move legends to core
|
|
2
|
-
import { useContext } from 'react'
|
|
2
|
+
import { forwardRef, useContext } from 'react'
|
|
3
3
|
import parse from 'html-react-parser'
|
|
4
|
-
import chroma from 'chroma-js'
|
|
5
4
|
|
|
6
5
|
import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
7
6
|
import LegendCircle from '@cdc/core/components/LegendCircle'
|
|
8
7
|
import LegendItemHex from './LegendItem.Hex'
|
|
8
|
+
import Button from '@cdc/core/components/elements/Button'
|
|
9
9
|
|
|
10
10
|
import useDataVizClasses from '@cdc/core/helpers/useDataVizClasses'
|
|
11
11
|
import ConfigContext from '../../../context'
|
|
12
12
|
import { PatternLines, PatternCircles, PatternWaves } from '@visx/pattern'
|
|
13
|
+
import { GlyphStar, GlyphTriangle, GlyphDiamond, GlyphSquare, GlyphCircle } from '@visx/glyph'
|
|
14
|
+
import { Group } from '@visx/group'
|
|
13
15
|
import './index.scss'
|
|
14
16
|
|
|
15
|
-
|
|
17
|
+
type LegendProps = {
|
|
18
|
+
skipId: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const Legend = forwardRef((props, ref) => {
|
|
22
|
+
const { skipId } = props
|
|
23
|
+
|
|
16
24
|
// prettier-ignore
|
|
17
25
|
const {
|
|
18
26
|
displayDataAsText,
|
|
@@ -26,6 +34,7 @@ const Legend = () => {
|
|
|
26
34
|
} = useContext(ConfigContext)
|
|
27
35
|
|
|
28
36
|
const { legend } = state
|
|
37
|
+
const fontSize = ['sm', 'xs', 'xxs'].includes(viewport) ? { fontSize: '11px' } : null
|
|
29
38
|
|
|
30
39
|
// Toggles if a legend is active and being applied to the map and data table.
|
|
31
40
|
const toggleLegendActive = (i, legendLabel) => {
|
|
@@ -87,14 +96,22 @@ const Legend = () => {
|
|
|
87
96
|
return (
|
|
88
97
|
// eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-noninteractive-element-interactions
|
|
89
98
|
<li
|
|
99
|
+
style={fontSize}
|
|
90
100
|
className={handleListItemClass().join(' ')}
|
|
91
101
|
key={idx}
|
|
92
102
|
title={`Legend item ${legendLabel} - Click to disable`}
|
|
93
103
|
onClick={() => {
|
|
94
104
|
toggleLegendActive(idx, legendLabel)
|
|
95
105
|
}}
|
|
106
|
+
onKeyDown={e => {
|
|
107
|
+
if (e.key === 'Enter') {
|
|
108
|
+
e.preventDefault()
|
|
109
|
+
toggleLegendActive(idx, legendLabel)
|
|
110
|
+
}
|
|
111
|
+
}}
|
|
112
|
+
tabIndex={0}
|
|
96
113
|
>
|
|
97
|
-
<LegendCircle fill={entry.color} /> <span
|
|
114
|
+
<LegendCircle viewport={viewport} fill={entry.color} /> <span>{legendLabel}</span>
|
|
98
115
|
</li>
|
|
99
116
|
)
|
|
100
117
|
})
|
|
@@ -114,7 +131,7 @@ const Legend = () => {
|
|
|
114
131
|
|
|
115
132
|
legendItems.push(
|
|
116
133
|
<>
|
|
117
|
-
<li className={`legend-container__li legend-container__li--geo-pattern`}>
|
|
134
|
+
<li className={`legend-container__li legend-container__li--geo-pattern`} aria-label='You are on a pattern button. We dont support toggling patterns on this legend at the moment, but provide the area as being focusable for congruity.' tabIndex={0}>
|
|
118
135
|
<span className='legend-item' style={{ border: 'unset' }}>
|
|
119
136
|
<svg width={legendSize} height={legendSize}>
|
|
120
137
|
{pattern === 'waves' && <PatternWaves id={`${dataKey}--${patternDataIndex}`} height={sizes[size] ?? 10} width={sizes[size] ?? 10} fill={defaultPatternColor} />}
|
|
@@ -136,23 +153,39 @@ const Legend = () => {
|
|
|
136
153
|
const { legendClasses } = useDataVizClasses(state, viewport)
|
|
137
154
|
|
|
138
155
|
const handleReset = e => {
|
|
139
|
-
|
|
156
|
+
const legend = ref.current
|
|
157
|
+
if (e) {
|
|
158
|
+
e.preventDefault()
|
|
159
|
+
}
|
|
140
160
|
resetLegendToggles()
|
|
141
161
|
setAccessibleStatus('Legend has been reset, please reference the data table to see updated values.')
|
|
162
|
+
if (legend) {
|
|
163
|
+
legend.focus()
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const pin = <path className='marker' d='M0,0l-8.8-17.7C-12.1-24.3-7.4-32,0-32h0c7.4,0,12.1,7.7,8.8,14.3L0,0z' strokeWidth={2} stroke={'black'} transform={`scale(0.5)`} />
|
|
168
|
+
|
|
169
|
+
const cityStyleShapes = {
|
|
170
|
+
pin: pin,
|
|
171
|
+
circle: <GlyphCircle color='#000' size={150} />,
|
|
172
|
+
square: <GlyphSquare color='#000' size={150} />,
|
|
173
|
+
diamond: <GlyphDiamond color='#000' size={150} />,
|
|
174
|
+
star: <GlyphStar color='#000' size={150} />,
|
|
175
|
+
triangle: <GlyphTriangle color='#000' size={150} />
|
|
142
176
|
}
|
|
143
177
|
|
|
144
178
|
return (
|
|
145
179
|
<ErrorBoundary component='Sidebar'>
|
|
146
180
|
<div className='legends'>
|
|
147
|
-
<aside id='legend' className={legendClasses.aside.join(' ') || ''} role='region' aria-label='Legend'>
|
|
181
|
+
<aside id={skipId || 'legend'} className={legendClasses.aside.join(' ') || ''} role='region' aria-label='Legend' tabIndex={0} ref={ref}>
|
|
148
182
|
<section className={legendClasses.section.join(' ') || ''} aria-label='Map Legend'>
|
|
149
|
-
{
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
183
|
+
{legend.title && <h3 className={legendClasses.title.join(' ') || ''}>{parse(legend.title)}</h3>}
|
|
184
|
+
{legend.dynamicDescription === false && legend.description && (
|
|
185
|
+
<p style={fontSize} className={legendClasses.description.join(' ') || ''}>
|
|
186
|
+
{parse(legend.description)}
|
|
187
|
+
</p>
|
|
153
188
|
)}
|
|
154
|
-
{legend.title && <span className={legendClasses.title.join(' ') || ''}>{parse(legend.title)}</span>}
|
|
155
|
-
{legend.dynamicDescription === false && legend.description && <p className={legendClasses.description.join(' ') || ''}>{parse(legend.description)}</p>}
|
|
156
189
|
{legend.dynamicDescription === true &&
|
|
157
190
|
runtimeFilters.map((filter, idx) => {
|
|
158
191
|
const lookupStr = `${idx},${filter.values.indexOf(String(filter.active))}`
|
|
@@ -162,7 +195,7 @@ const Legend = () => {
|
|
|
162
195
|
|
|
163
196
|
if (desc.length > 0) {
|
|
164
197
|
return (
|
|
165
|
-
<p key={`dynamic-description-${lookupStr}`} className={`dynamic-legend-description-${lookupStr}`}>
|
|
198
|
+
<p style={fontSize} key={`dynamic-description-${lookupStr}`} className={`dynamic-legend-description-${lookupStr}`}>
|
|
166
199
|
{desc}
|
|
167
200
|
</p>
|
|
168
201
|
)
|
|
@@ -172,12 +205,44 @@ const Legend = () => {
|
|
|
172
205
|
<ul className={legendClasses.ul.join(' ') || ''} aria-label='Legend items'>
|
|
173
206
|
{legendList()}
|
|
174
207
|
</ul>
|
|
208
|
+
{(state.visual.additionalCityStyles.some(c => c.label) || state.visual.cityStyleLabel) && (
|
|
209
|
+
<>
|
|
210
|
+
<hr />
|
|
211
|
+
<div className={legendClasses.div.join(' ') || ''}>
|
|
212
|
+
{state.visual.cityStyleLabel && (
|
|
213
|
+
<div>
|
|
214
|
+
<svg>
|
|
215
|
+
<Group top={state.visual.cityStyle === 'pin' ? 19 : state.visual.cityStyle === 'triangle' ? 13 : 11} left={10}>
|
|
216
|
+
{cityStyleShapes[state.visual.cityStyle.toLowerCase()]}
|
|
217
|
+
</Group>
|
|
218
|
+
</svg>
|
|
219
|
+
<p>{state.visual.cityStyleLabel}</p>
|
|
220
|
+
</div>
|
|
221
|
+
)}
|
|
222
|
+
|
|
223
|
+
{state.visual.additionalCityStyles.map(
|
|
224
|
+
({ shape, label }) =>
|
|
225
|
+
label && (
|
|
226
|
+
<div>
|
|
227
|
+
<svg>
|
|
228
|
+
<Group top={shape === 'Pin' ? 19 : shape === 'Triangle' ? 13 : 11} left={10}>
|
|
229
|
+
{cityStyleShapes[shape.toLowerCase()]}
|
|
230
|
+
</Group>
|
|
231
|
+
</svg>
|
|
232
|
+
<p style={fontSize}>{label}</p>
|
|
233
|
+
</div>
|
|
234
|
+
)
|
|
235
|
+
)}
|
|
236
|
+
</div>
|
|
237
|
+
</>
|
|
238
|
+
)}
|
|
239
|
+
{runtimeLegend.disabledAmt > 0 && <Button onClick={handleReset}>Reset</Button>}
|
|
175
240
|
</section>
|
|
176
241
|
</aside>
|
|
177
242
|
{state.hexMap.shapeGroups?.length > 0 && state.hexMap.type === 'shapes' && state.general.displayAsHex && <LegendItemHex state={state} runtimeLegend={runtimeLegend} viewport={viewport} />}
|
|
178
243
|
</div>
|
|
179
244
|
</ErrorBoundary>
|
|
180
245
|
)
|
|
181
|
-
}
|
|
246
|
+
})
|
|
182
247
|
|
|
183
248
|
export default Legend
|
|
@@ -27,7 +27,7 @@ const LegendItemHex = props => {
|
|
|
27
27
|
return (
|
|
28
28
|
<aside id='legend' className={legendClasses.aside.join(' ')} role='region' aria-label='Legend' tabIndex={0}>
|
|
29
29
|
<section className={legendClasses.section.join(' ')} aria-label='Map Legend'>
|
|
30
|
-
{shapeGroup.legendTitle && <
|
|
30
|
+
{shapeGroup.legendTitle && <h3 className={legendClasses.title.join(' ')}>{parse(shapeGroup.legendTitle)}</h3>}
|
|
31
31
|
{shapeGroup.legendDescription && <p className={legendClasses.description.join(' ')}>{parse(shapeGroup.legendDescription)}</p>}
|
|
32
32
|
|
|
33
33
|
<ul className={legendClasses.ul.join(' ')} aria-label='Legend items' style={{ listStyle: 'none' }}>
|
|
@@ -89,17 +89,14 @@
|
|
|
89
89
|
}
|
|
90
90
|
.legend-container__reset-button {
|
|
91
91
|
font-size: 0.75em;
|
|
92
|
-
color: rgba(0, 0, 0, 0.6);
|
|
93
|
-
position: absolute;
|
|
94
92
|
right: 1em;
|
|
95
|
-
background: rgba(0, 0, 0, 0.1);
|
|
96
93
|
text-transform: uppercase;
|
|
97
94
|
transition: 0.2s all;
|
|
98
95
|
padding: 0.2em 0.5em;
|
|
99
96
|
border: rgba(0, 0, 0, 0.2) 1px solid;
|
|
97
|
+
padding: 0.375rem;
|
|
100
98
|
&:hover {
|
|
101
99
|
text-decoration: none;
|
|
102
|
-
background: rgba(0, 0, 0, 0.15);
|
|
103
100
|
transition: 0.2s all;
|
|
104
101
|
}
|
|
105
102
|
}
|
|
@@ -118,7 +115,7 @@
|
|
|
118
115
|
flex-direction: column;
|
|
119
116
|
}
|
|
120
117
|
|
|
121
|
-
&:not(.vertical-sorted, .legend-container__ul--single-column) {
|
|
118
|
+
&:not(.vertical-sorted, .legend-container__ul--single-column, .single-row) {
|
|
122
119
|
width: 100%;
|
|
123
120
|
@include breakpoint(sm) {
|
|
124
121
|
.legend-container__li {
|
|
@@ -231,5 +228,34 @@
|
|
|
231
228
|
}
|
|
232
229
|
}
|
|
233
230
|
}
|
|
231
|
+
& .shape-container {
|
|
232
|
+
&.single-row {
|
|
233
|
+
display: flex;
|
|
234
|
+
flex-direction: row;
|
|
235
|
+
align-items: center;
|
|
236
|
+
justify-content: flex-start;
|
|
237
|
+
flex-wrap: wrap;
|
|
238
|
+
|
|
239
|
+
& > div {
|
|
240
|
+
margin-right: 2em;
|
|
241
|
+
}
|
|
242
|
+
& > div:last-child {
|
|
243
|
+
margin-right: 0;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
& > div {
|
|
248
|
+
display: flex;
|
|
249
|
+
flex-direction: row;
|
|
250
|
+
}
|
|
251
|
+
& div > svg {
|
|
252
|
+
width: 25px;
|
|
253
|
+
height: 32px;
|
|
254
|
+
}
|
|
255
|
+
& div > p {
|
|
256
|
+
white-space: nowrap;
|
|
257
|
+
margin-top: 1px;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
234
260
|
}
|
|
235
261
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { AiOutlineArrowUp, AiOutlineArrowDown, AiOutlineArrowRight, AiOutlineArrowLeft } from 'react-icons/ai'
|
|
2
|
+
import { Group } from '@visx/group'
|
|
3
|
+
|
|
4
|
+
type HexIconProps = {
|
|
5
|
+
item: {
|
|
6
|
+
operator: '=' | '≠' | '<' | '>' | '<=' | '>='
|
|
7
|
+
shape: 'Arrow Up' | 'Arrow Down' | 'Arrow Right' | 'Arrow Left'
|
|
8
|
+
key: string
|
|
9
|
+
value: string
|
|
10
|
+
}
|
|
11
|
+
index: number
|
|
12
|
+
centroid: [number, number]
|
|
13
|
+
iconSize: number
|
|
14
|
+
textColor: string
|
|
15
|
+
isTerritory: boolean
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const HexIcon: React.FC<HexIconProps> = props => {
|
|
19
|
+
const { item, index, centroid, iconSize, textColor = '#000', isTerritory } = props
|
|
20
|
+
if (!centroid) return
|
|
21
|
+
|
|
22
|
+
if (isTerritory) {
|
|
23
|
+
return (
|
|
24
|
+
<Group style={{ transform: `translate(36%, 50%)` }} key={`territory-hex--${index}`}>
|
|
25
|
+
{item.shape === 'Arrow Down' && <AiOutlineArrowDown size={12} stroke='none' fontWeight={100} />}
|
|
26
|
+
{item.shape === 'Arrow Up' && <AiOutlineArrowUp size={12} stroke='none' fontWeight={100} />}
|
|
27
|
+
{item.shape === 'Arrow Right' && <AiOutlineArrowRight size={12} stroke='none' fontWeight={100} />}
|
|
28
|
+
{item.shape === 'Arrow Left' && <AiOutlineArrowLeft size={12} stroke='none' fontWeight={100} />}
|
|
29
|
+
</Group>
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
return (
|
|
33
|
+
<Group top={centroid[1] - 5} left={centroid[0] - iconSize} color={textColor} textAnchor='start' key={`hex--${item.key}-${item.value}-${index}`}>
|
|
34
|
+
{item.shape === 'Arrow Down' && <AiOutlineArrowDown />}
|
|
35
|
+
{item.shape === 'Arrow Up' && <AiOutlineArrowUp />}
|
|
36
|
+
{item.shape === 'Arrow Right' && <AiOutlineArrowRight />}
|
|
37
|
+
{item.shape === 'Arrow Left' && <AiOutlineArrowLeft />}
|
|
38
|
+
</Group>
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
export default HexIcon
|
|
@@ -2,10 +2,9 @@ import { useContext } from 'react'
|
|
|
2
2
|
import { geoCentroid, geoPath } from 'd3-geo'
|
|
3
3
|
import ConfigContext from './../../../../context'
|
|
4
4
|
import { MapContext } from './../../../../types/MapContext'
|
|
5
|
-
import
|
|
6
|
-
import { Group } from '@visx/group'
|
|
5
|
+
import HexIcon from '../HexIcon'
|
|
7
6
|
import { Text } from '@visx/text'
|
|
8
|
-
import
|
|
7
|
+
import { getContrastColor } from '@cdc/core/helpers/cove/accessibility'
|
|
9
8
|
|
|
10
9
|
const offsets = {
|
|
11
10
|
'US-VT': [50, -8],
|
|
@@ -52,16 +51,41 @@ const TerritoryHexagon = ({ label, text, stroke, strokeWidth, textColor, territo
|
|
|
52
51
|
<>
|
|
53
52
|
{state.hexMap.shapeGroups.map((group, groupIndex) => {
|
|
54
53
|
return group.items.map((item, itemIndex) => {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
<
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
54
|
+
switch (item.operator) {
|
|
55
|
+
case '=':
|
|
56
|
+
if (geoData[item.key] === item.value || Number(geoData[item.key]) === Number(item.value)) {
|
|
57
|
+
return <HexIcon item={item} index={itemIndex} centroid={centroid} isTerritory />
|
|
58
|
+
}
|
|
59
|
+
break
|
|
60
|
+
case '≠':
|
|
61
|
+
if (geoData[item.key] !== item.value && Number(geoData[item.key]) !== Number(item.value)) {
|
|
62
|
+
return <HexIcon item={item} index={itemIndex} centroid={centroid} isTerritory />
|
|
63
|
+
}
|
|
64
|
+
break
|
|
65
|
+
case '<':
|
|
66
|
+
if (Number(geoData[item.key]) < Number(item.value)) {
|
|
67
|
+
return <HexIcon item={item} index={itemIndex} centroid={centroid} isTerritory />
|
|
68
|
+
}
|
|
69
|
+
break
|
|
70
|
+
case '>':
|
|
71
|
+
if (Number(geoData[item.key]) > Number(item.value)) {
|
|
72
|
+
return <HexIcon item={item} index={itemIndex} centroid={centroid} isTerritory />
|
|
73
|
+
}
|
|
74
|
+
break
|
|
75
|
+
case '<=':
|
|
76
|
+
if (Number(geoData[item.key]) <= Number(item.value)) {
|
|
77
|
+
return <HexIcon item={item} index={itemIndex} centroid={centroid} isTerritory />
|
|
78
|
+
}
|
|
79
|
+
break
|
|
80
|
+
case '>=':
|
|
81
|
+
if (item.operator === '>=') {
|
|
82
|
+
if (Number(geoData[item.key]) >= Number(item.value)) {
|
|
83
|
+
return <HexIcon item={item} index={itemIndex} centroid={centroid} isTerritory />
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
break
|
|
87
|
+
default:
|
|
88
|
+
break
|
|
65
89
|
}
|
|
66
90
|
})
|
|
67
91
|
})}
|
|
@@ -71,12 +95,7 @@ const TerritoryHexagon = ({ label, text, stroke, strokeWidth, textColor, territo
|
|
|
71
95
|
|
|
72
96
|
if (undefined === abbr) return null
|
|
73
97
|
|
|
74
|
-
let textColor = '#FFF'
|
|
75
|
-
|
|
76
|
-
// Dynamic text color
|
|
77
|
-
if (chroma.contrast(textColor, bgColor) < 3.5) {
|
|
78
|
-
textColor = '#202020' // dark gray
|
|
79
|
-
}
|
|
98
|
+
let textColor = getContrastColor('#FFF', bgColor)
|
|
80
99
|
|
|
81
100
|
// always make HI black since it is off to the side
|
|
82
101
|
if (abbr === 'US-HI') {
|
|
@@ -1,58 +1,47 @@
|
|
|
1
1
|
import { useContext } from 'react'
|
|
2
2
|
import { PatternLines, PatternCircles, PatternWaves } from '@visx/pattern'
|
|
3
|
-
import chroma from 'chroma-js'
|
|
4
3
|
import ConfigContext from './../../../../context'
|
|
5
4
|
import { type MapContext } from '../../../../types/MapContext'
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
small: '8',
|
|
9
|
-
medium: '10',
|
|
10
|
-
large: '12'
|
|
11
|
-
}
|
|
5
|
+
import { patternSizes } from './../../helpers/patternSizes'
|
|
6
|
+
import { getContrastColor } from '@cdc/core/helpers/cove/accessibility'
|
|
12
7
|
|
|
13
8
|
const TerritoryRectangle = ({ label, text, stroke, strokeWidth, textColor, hasPattern, territory, ...props }) => {
|
|
14
9
|
const { state, supportedTerritories } = useContext<MapContext>(ConfigContext)
|
|
15
10
|
|
|
16
11
|
return (
|
|
17
12
|
<svg viewBox='0 0 45 28'>
|
|
18
|
-
<g {...props} strokeLinejoin='round'>
|
|
13
|
+
<g {...props} strokeLinejoin='round' tabIndex={-1}>
|
|
19
14
|
<path
|
|
20
15
|
stroke={stroke}
|
|
21
16
|
strokeWidth={strokeWidth}
|
|
22
17
|
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
18
|
{...props}
|
|
24
19
|
/>
|
|
25
|
-
<text textAnchor='middle' dominantBaseline='middle' x='50%' y='54%' fill={text} style={{ stroke: textColor, strokeWidth: 1 }} className='territory-text'
|
|
20
|
+
<text textAnchor='middle' dominantBaseline='middle' x='50%' y='54%' fill={text} style={{ stroke: textColor, strokeWidth: 1 }} className='territory-text' paintOrder='stroke'>
|
|
26
21
|
{label}
|
|
27
22
|
</text>
|
|
28
23
|
|
|
29
24
|
{state.map.patterns.map((patternData, patternIndex) => {
|
|
30
|
-
|
|
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
|
-
}
|
|
25
|
+
const patternColor = getContrastColor('#FFF', props.style.fill)
|
|
26
|
+
const hasMatchingValues = supportedTerritories[territory]?.includes(patternData?.dataValue)
|
|
38
27
|
|
|
39
28
|
if (!hasMatchingValues) return null
|
|
40
29
|
if (!patternData.pattern) return null
|
|
41
30
|
|
|
42
31
|
return (
|
|
43
32
|
<>
|
|
44
|
-
{patternData?.pattern === 'waves' && <PatternWaves id={`territory-${patternData?.dataKey}--${patternIndex}`} height={
|
|
45
|
-
{patternData?.pattern === 'circles' && <PatternCircles id={`territory-${patternData?.dataKey}--${patternIndex}`} height={
|
|
46
|
-
{patternData?.pattern === 'lines' && <PatternLines id={`territory-${patternData?.dataKey}--${patternIndex}`} height={
|
|
33
|
+
{patternData?.pattern === 'waves' && <PatternWaves id={`territory-${patternData?.dataKey}--${patternIndex}`} height={patternSizes[patternData?.size] ?? 10} width={patternSizes[patternData?.size] ?? 10} fill={patternColor} complement />}
|
|
34
|
+
{patternData?.pattern === 'circles' && <PatternCircles id={`territory-${patternData?.dataKey}--${patternIndex}`} height={patternSizes[patternData?.size] ?? 10} width={patternSizes[patternData?.size] ?? 10} fill={patternColor} complement />}
|
|
35
|
+
{patternData?.pattern === 'lines' && <PatternLines id={`territory-${patternData?.dataKey}--${patternIndex}`} height={patternSizes[patternData?.size] ?? 6} width={patternSizes[patternData?.size] ?? 6} stroke={patternColor} strokeWidth={1} orientation={['diagonalRightToLeft']} />}
|
|
47
36
|
<path
|
|
48
37
|
stroke={stroke}
|
|
49
38
|
strokeWidth={strokeWidth}
|
|
50
39
|
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
40
|
fill={`url(#territory-${patternData?.dataKey}--${patternIndex})`}
|
|
52
|
-
color='white'
|
|
41
|
+
color={patternData ? 'white' : textColor}
|
|
53
42
|
className={[`territory-pattern-${patternData.dataKey}`, `territory-pattern-${patternData.dataKey}--${patternData.dataValue}`].join(' ')}
|
|
54
43
|
/>
|
|
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'>
|
|
44
|
+
<text textAnchor='middle' dominantBaseline='middle' x='50%' y='54%' fill={text} style={{ fill: patternData ? 'white' : 'black', stroke: patternData ? 'black' : textColor, strokeWidth: patternData ? 6 : 0 }} className='territory-text' paint-order='stroke'>
|
|
56
45
|
{label}
|
|
57
46
|
</text>
|
|
58
47
|
</>
|
|
@@ -127,7 +127,9 @@ const CountyMap = props => {
|
|
|
127
127
|
handleMapAriaLabels,
|
|
128
128
|
runtimeLegend,
|
|
129
129
|
state,
|
|
130
|
-
runtimeFilters
|
|
130
|
+
runtimeFilters,
|
|
131
|
+
tooltipId,
|
|
132
|
+
isEditor
|
|
131
133
|
} = useContext(ConfigContext)
|
|
132
134
|
|
|
133
135
|
// CREATE STATE LINES
|
|
@@ -138,7 +140,7 @@ const CountyMap = props => {
|
|
|
138
140
|
|
|
139
141
|
const pathGenerator = geoPath().projection(geoAlbersUsaTerritories())
|
|
140
142
|
|
|
141
|
-
const { featureArray } = useMapLayers(state, '', pathGenerator,
|
|
143
|
+
const { featureArray } = useMapLayers(state, '', pathGenerator, tooltipId)
|
|
142
144
|
|
|
143
145
|
useEffect(() => {
|
|
144
146
|
if (containerEl) {
|
|
@@ -322,7 +324,7 @@ const CountyMap = props => {
|
|
|
322
324
|
|
|
323
325
|
tooltipRef.current.style.display = 'block'
|
|
324
326
|
tooltipRef.current.style.top = e.clientY + 'px'
|
|
325
|
-
tooltipRef.current.style.left = e.clientX + 'px'
|
|
327
|
+
tooltipRef.current.style.left = isEditor ? Number(e.clientX - 350) + 'px' : e.clientX + 'px'
|
|
326
328
|
tooltipRef.current.innerHTML = applyTooltipsToGeo(displayGeoName(county.id), data[county.id])
|
|
327
329
|
tooltipRef.current.setAttribute('data-index', countyIndex)
|
|
328
330
|
} else {
|
|
@@ -520,8 +522,9 @@ const CountyMap = props => {
|
|
|
520
522
|
tooltipRef.current.setAttribute('data-index', null)
|
|
521
523
|
}}
|
|
522
524
|
onClick={canvasClick}
|
|
525
|
+
className='county-map-canvas'
|
|
523
526
|
></canvas>
|
|
524
|
-
<div ref={tooltipRef} id=
|
|
527
|
+
<div ref={tooltipRef} id={`tooltip__${tooltipId}`} className='tooltip' style={{ background: `rgba(255,255,255,${state.tooltips.opacity / 100})` }}></div>
|
|
525
528
|
<button className={`btn btn--reset`} onClick={onReset} ref={resetButton} tabIndex='0'>
|
|
526
529
|
Reset Zoom
|
|
527
530
|
</button>
|