@cdc/map 4.24.11 → 4.24.12-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 +34008 -33475
- 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-9989.json +229 -0
- package/examples/private/ardi.json +180 -0
- package/examples/private/colors 2.json +416 -0
- package/examples/private/colors.json +416 -0
- package/examples/private/colors.json.zip +0 -0
- package/examples/private/customColors.json +45348 -0
- package/examples/private/default-patterns.json +867 -0
- package/examples/private/test.json +1632 -0
- package/index.html +4 -5
- package/package.json +3 -3
- package/src/CdcMap.tsx +82 -79
- package/src/_stories/{CdcMapLegend.stories.tsx → CdcMap.Legend.Gradient.stories.tsx} +1 -20
- 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/components/CityList.tsx +5 -2
- package/src/components/EditorPanel/components/EditorPanel.tsx +39 -256
- package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +1 -1
- package/src/components/Legend/components/Legend.tsx +22 -6
- package/src/components/Legend/components/index.scss +16 -23
- 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 +1 -1
- package/src/components/UsaMap/components/Territory/Territory.Rectangle.tsx +14 -13
- 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 +58 -60
- 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/filters.scss +0 -3
- package/src/scss/main.scss +0 -1
- package/src/scss/map.scss +11 -59
- package/src/types/MapConfig.ts +7 -1
- package/src/types/MapContext.ts +1 -0
- package/examples/default-patterns.json +0 -579
- package/src/scss/datatable.scss +0 -6
|
@@ -19,11 +19,13 @@ import { patternSizes } from '../helpers/patternSizes'
|
|
|
19
19
|
import Annotation from '../../Annotation'
|
|
20
20
|
|
|
21
21
|
import Territory from './Territory'
|
|
22
|
+
import { cityKeys } from '../../../data/supported-geos'
|
|
22
23
|
|
|
23
24
|
import useMapLayers from '../../../hooks/useMapLayers'
|
|
24
25
|
import ConfigContext from '../../../context'
|
|
25
26
|
import { MapContext } from '../../../types/MapContext'
|
|
26
27
|
import { checkColorContrast, getContrastColor, getColorContrast } from '@cdc/core/helpers/cove/accessibility'
|
|
28
|
+
import { getGeoFillColor, getGeoStrokeColor } from '../../../helpers/colors'
|
|
27
29
|
|
|
28
30
|
const { features: unitedStates } = feature(topoJSON, topoJSON.objects.states)
|
|
29
31
|
const { features: unitedStatesHex } = feature(hexTopoJSON, hexTopoJSON.objects.states)
|
|
@@ -59,7 +61,6 @@ const UsaMap = () => {
|
|
|
59
61
|
data,
|
|
60
62
|
displayGeoName,
|
|
61
63
|
geoClickHandler,
|
|
62
|
-
handleCircleClick,
|
|
63
64
|
handleMapAriaLabels,
|
|
64
65
|
setSharedFilterValue,
|
|
65
66
|
state,
|
|
@@ -67,11 +68,12 @@ const UsaMap = () => {
|
|
|
67
68
|
titleCase,
|
|
68
69
|
tooltipId,
|
|
69
70
|
handleDragStateChange,
|
|
70
|
-
|
|
71
|
-
|
|
71
|
+
mapId,
|
|
72
|
+
logo,
|
|
72
73
|
} = useContext<MapContext>(ConfigContext)
|
|
73
74
|
|
|
74
75
|
let isFilterValueSupported = false
|
|
76
|
+
const { general, columns, feature, tooltips, hexMap, map, annotations } = state
|
|
75
77
|
|
|
76
78
|
if (setSharedFilterValue) {
|
|
77
79
|
Object.keys(supportedStates).forEach(supportedState => {
|
|
@@ -101,16 +103,16 @@ const UsaMap = () => {
|
|
|
101
103
|
useEffect(() => {
|
|
102
104
|
setTranslate([455, 250])
|
|
103
105
|
setExtent(null)
|
|
104
|
-
}, [
|
|
106
|
+
}, [general.geoType])
|
|
105
107
|
|
|
106
|
-
const isHex =
|
|
108
|
+
const isHex = general.displayAsHex
|
|
107
109
|
|
|
108
110
|
const [territoriesData, setTerritoriesData] = useState([])
|
|
109
111
|
|
|
110
112
|
const territoriesKeys = Object.keys(supportedTerritories) // data will have already mapped abbreviated territories to their full names
|
|
111
113
|
|
|
112
114
|
useEffect(() => {
|
|
113
|
-
if (
|
|
115
|
+
if (general.territoriesAlwaysShow) {
|
|
114
116
|
// show all Territories whether in the data or not
|
|
115
117
|
setTerritoriesData(territoriesKeys)
|
|
116
118
|
} else {
|
|
@@ -118,17 +120,10 @@ const UsaMap = () => {
|
|
|
118
120
|
const territoriesList = territoriesKeys.filter(key => data[key])
|
|
119
121
|
setTerritoriesData(territoriesList)
|
|
120
122
|
}
|
|
121
|
-
}, [data,
|
|
123
|
+
}, [data, general.territoriesAlwaysShow])
|
|
122
124
|
|
|
123
|
-
const geoStrokeColor = state
|
|
124
|
-
|
|
125
|
-
const getTerritoriesClasses = () => {
|
|
126
|
-
const screenWidth = window?.visualViewport?.width
|
|
127
|
-
let className = 'territories'
|
|
128
|
-
if (screenWidth < 700) return 'territories--mobile'
|
|
129
|
-
if (screenWidth < 900) return 'territories--tablet'
|
|
130
|
-
return className
|
|
131
|
-
}
|
|
125
|
+
const geoStrokeColor = getGeoStrokeColor(state)
|
|
126
|
+
const geoFillColor = getGeoFillColor(state)
|
|
132
127
|
|
|
133
128
|
const territories = territoriesData.map((territory, territoryIndex) => {
|
|
134
129
|
const Shape = isHex ? Territory.Hexagon : Territory.Rectangle
|
|
@@ -138,13 +133,24 @@ const UsaMap = () => {
|
|
|
138
133
|
let toolTip
|
|
139
134
|
|
|
140
135
|
let styles = {
|
|
141
|
-
fill:
|
|
136
|
+
fill: geoFillColor,
|
|
137
|
+
stroke: geoStrokeColor,
|
|
142
138
|
color: '#202020'
|
|
143
139
|
}
|
|
144
140
|
|
|
145
141
|
const label = supportedTerritories[territory][1]
|
|
146
142
|
|
|
147
|
-
if (!territoryData)
|
|
143
|
+
if (!territoryData)
|
|
144
|
+
return (
|
|
145
|
+
<Shape
|
|
146
|
+
key={label}
|
|
147
|
+
label={label}
|
|
148
|
+
style={styles}
|
|
149
|
+
text={styles.color}
|
|
150
|
+
territoryData={territoryData}
|
|
151
|
+
backgroundColor={styles.fill}
|
|
152
|
+
/>
|
|
153
|
+
)
|
|
148
154
|
|
|
149
155
|
toolTip = applyTooltipsToGeo(displayGeoName(territory), territoryData)
|
|
150
156
|
|
|
@@ -156,10 +162,7 @@ const UsaMap = () => {
|
|
|
156
162
|
let needsPointer = false
|
|
157
163
|
|
|
158
164
|
// If we need to add a pointer cursor
|
|
159
|
-
if (
|
|
160
|
-
(state.columns.navigate && territoryData[state.columns.navigate.name]) ||
|
|
161
|
-
state.tooltips.appearanceType === 'click'
|
|
162
|
-
) {
|
|
165
|
+
if ((columns.navigate && territoryData[columns.navigate.name]) || tooltips.appearanceType === 'click') {
|
|
163
166
|
needsPointer = true
|
|
164
167
|
}
|
|
165
168
|
|
|
@@ -167,15 +170,11 @@ const UsaMap = () => {
|
|
|
167
170
|
color: textColor,
|
|
168
171
|
fill: legendColors[0],
|
|
169
172
|
opacity:
|
|
170
|
-
setSharedFilterValue &&
|
|
171
|
-
isFilterValueSupported &&
|
|
172
|
-
setSharedFilterValue !== territoryData[state.columns.geo.name]
|
|
173
|
+
setSharedFilterValue && isFilterValueSupported && setSharedFilterValue !== territoryData[columns.geo.name]
|
|
173
174
|
? 0.5
|
|
174
175
|
: 1,
|
|
175
176
|
stroke:
|
|
176
|
-
setSharedFilterValue &&
|
|
177
|
-
isFilterValueSupported &&
|
|
178
|
-
setSharedFilterValue === territoryData[state.columns.geo.name]
|
|
177
|
+
setSharedFilterValue && isFilterValueSupported && setSharedFilterValue === territoryData[columns.geo.name]
|
|
179
178
|
? 'rgba(0, 0, 0, 1)'
|
|
180
179
|
: geoStrokeColor,
|
|
181
180
|
cursor: needsPointer ? 'pointer' : 'default',
|
|
@@ -193,7 +192,7 @@ const UsaMap = () => {
|
|
|
193
192
|
label={label}
|
|
194
193
|
style={styles}
|
|
195
194
|
text={styles.color}
|
|
196
|
-
strokeWidth={1
|
|
195
|
+
strokeWidth={1}
|
|
197
196
|
textColor={textColor}
|
|
198
197
|
handleShapeClick={() => geoClickHandler(territory, territoryData)}
|
|
199
198
|
dataTooltipId={`tooltip__${tooltipId}`}
|
|
@@ -201,6 +200,7 @@ const UsaMap = () => {
|
|
|
201
200
|
territory={territory}
|
|
202
201
|
territoryData={territoryData}
|
|
203
202
|
tabIndex={-1}
|
|
203
|
+
backgroundColor={styles.fill}
|
|
204
204
|
/>
|
|
205
205
|
)
|
|
206
206
|
}
|
|
@@ -213,7 +213,7 @@ const UsaMap = () => {
|
|
|
213
213
|
|
|
214
214
|
// Constructs and displays markup for all geos on the map (except territories right now)
|
|
215
215
|
const constructGeoJsx = (geographies, projection) => {
|
|
216
|
-
let showLabel =
|
|
216
|
+
let showLabel = general.displayStateLabels
|
|
217
217
|
|
|
218
218
|
// Order alphabetically. Important for accessibility if ever read out loud.
|
|
219
219
|
geographies.map(state => {
|
|
@@ -239,7 +239,7 @@ const UsaMap = () => {
|
|
|
239
239
|
const key = isHex ? geo.properties.iso + '-hex-group' : geo.properties.iso + '-group'
|
|
240
240
|
|
|
241
241
|
let styles = {
|
|
242
|
-
fill:
|
|
242
|
+
fill: geoFillColor,
|
|
243
243
|
cursor: 'default'
|
|
244
244
|
}
|
|
245
245
|
|
|
@@ -247,8 +247,6 @@ const UsaMap = () => {
|
|
|
247
247
|
let geoKey = geo.properties.iso
|
|
248
248
|
let geoName = geo.properties.name
|
|
249
249
|
|
|
250
|
-
// Manually add Washington D.C. in for Hex maps
|
|
251
|
-
|
|
252
250
|
if (!geoKey) return
|
|
253
251
|
|
|
254
252
|
const geoData = data[geoKey]
|
|
@@ -267,29 +265,26 @@ const UsaMap = () => {
|
|
|
267
265
|
const tooltip = applyTooltipsToGeo(geoDisplayName, geoData)
|
|
268
266
|
|
|
269
267
|
styles = {
|
|
270
|
-
fill: state.general.type !== 'bubble' ? legendColors[0] :
|
|
268
|
+
fill: state.general.type !== 'bubble' ? legendColors[0] : geoFillColor,
|
|
271
269
|
opacity:
|
|
272
|
-
setSharedFilterValue && isFilterValueSupported && setSharedFilterValue !== geoData[
|
|
270
|
+
setSharedFilterValue && isFilterValueSupported && setSharedFilterValue !== geoData[columns.geo.name]
|
|
273
271
|
? 0.5
|
|
274
272
|
: 1,
|
|
275
273
|
stroke:
|
|
276
|
-
setSharedFilterValue && isFilterValueSupported && setSharedFilterValue === geoData[
|
|
274
|
+
setSharedFilterValue && isFilterValueSupported && setSharedFilterValue === geoData[columns.geo.name]
|
|
277
275
|
? 'rgba(0, 0, 0, 1)'
|
|
278
276
|
: geoStrokeColor,
|
|
279
277
|
cursor: 'default',
|
|
280
278
|
'&:hover': {
|
|
281
|
-
fill: state.general.type !== 'bubble' ? legendColors[1] :
|
|
279
|
+
fill: state.general.type !== 'bubble' ? legendColors[1] : geoFillColor
|
|
282
280
|
},
|
|
283
281
|
'&:active': {
|
|
284
|
-
fill: state.general.type !== 'bubble' ? legendColors[2] :
|
|
282
|
+
fill: state.general.type !== 'bubble' ? legendColors[2] : geoFillColor
|
|
285
283
|
}
|
|
286
284
|
}
|
|
287
285
|
|
|
288
286
|
// When to add pointer cursor
|
|
289
|
-
if (
|
|
290
|
-
(state.columns.navigate && geoData[state.columns.navigate.name]) ||
|
|
291
|
-
state.tooltips.appearanceType === 'click'
|
|
292
|
-
) {
|
|
287
|
+
if ((columns.navigate && geoData[columns.navigate.name]) || tooltips.appearanceType === 'click') {
|
|
293
288
|
styles.cursor = 'pointer'
|
|
294
289
|
}
|
|
295
290
|
|
|
@@ -302,7 +297,7 @@ const UsaMap = () => {
|
|
|
302
297
|
|
|
303
298
|
return (
|
|
304
299
|
<>
|
|
305
|
-
{
|
|
300
|
+
{hexMap.shapeGroups.map((group, groupIndex) => {
|
|
306
301
|
return group.items.map((item, itemIndex) => {
|
|
307
302
|
switch (item.operator) {
|
|
308
303
|
case '=':
|
|
@@ -406,13 +401,13 @@ const UsaMap = () => {
|
|
|
406
401
|
tabIndex={-1}
|
|
407
402
|
>
|
|
408
403
|
{/* state path */}
|
|
409
|
-
<path tabIndex={-1} className='single-geo' strokeWidth={1
|
|
404
|
+
<path tabIndex={-1} className='single-geo' strokeWidth={1} d={path} />
|
|
410
405
|
|
|
411
406
|
{/* apply patterns on top of state path*/}
|
|
412
|
-
{
|
|
407
|
+
{map.patterns.map((patternData, patternIndex) => {
|
|
413
408
|
const { pattern, dataKey, size } = patternData
|
|
414
409
|
const currentFill = styles.fill
|
|
415
|
-
const hasMatchingValues = patternData.dataValue === geoData[patternData.dataKey]
|
|
410
|
+
const hasMatchingValues = patternData.dataValue === geoData?.[patternData.dataKey]
|
|
416
411
|
const patternColor = patternData.color || getContrastColor('#000', currentFill)
|
|
417
412
|
|
|
418
413
|
if (!hasMatchingValues) return
|
|
@@ -422,7 +417,7 @@ const UsaMap = () => {
|
|
|
422
417
|
<>
|
|
423
418
|
{pattern === 'waves' && (
|
|
424
419
|
<PatternWaves
|
|
425
|
-
id={`${mapId}--${dataKey}--${geoIndex}`}
|
|
420
|
+
id={`${mapId}--${String(dataKey).replace(' ', '-')}--${geoIndex}`}
|
|
426
421
|
height={patternSizes[size] ?? 10}
|
|
427
422
|
width={patternSizes[size] ?? 10}
|
|
428
423
|
fill={patternColor}
|
|
@@ -430,7 +425,7 @@ const UsaMap = () => {
|
|
|
430
425
|
)}
|
|
431
426
|
{pattern === 'circles' && (
|
|
432
427
|
<PatternCircles
|
|
433
|
-
id={`${mapId}--${dataKey}--${geoIndex}`}
|
|
428
|
+
id={`${mapId}--${String(dataKey).replace(' ', '-')}--${geoIndex}`}
|
|
434
429
|
height={patternSizes[size] ?? 10}
|
|
435
430
|
width={patternSizes[size] ?? 10}
|
|
436
431
|
fill={patternColor}
|
|
@@ -438,7 +433,7 @@ const UsaMap = () => {
|
|
|
438
433
|
)}
|
|
439
434
|
{pattern === 'lines' && (
|
|
440
435
|
<PatternLines
|
|
441
|
-
id={`${mapId}--${dataKey}--${geoIndex}`}
|
|
436
|
+
id={`${mapId}--${String(dataKey).replace(' ', '-')}--${geoIndex}`}
|
|
442
437
|
height={patternSizes[size] ?? 6}
|
|
443
438
|
width={patternSizes[size] ?? 6}
|
|
444
439
|
stroke={patternColor}
|
|
@@ -451,13 +446,13 @@ const UsaMap = () => {
|
|
|
451
446
|
tabIndex={-1}
|
|
452
447
|
stroke='transparent'
|
|
453
448
|
d={path}
|
|
454
|
-
fill={`url(#${mapId}--${dataKey}--${geoIndex})`}
|
|
449
|
+
fill={`url(#${mapId}--${String(dataKey).replace(' ', '-')}--${geoIndex})`}
|
|
455
450
|
/>
|
|
456
451
|
</>
|
|
457
452
|
)
|
|
458
453
|
})}
|
|
459
454
|
{(isHex || showLabel) && geoLabel(geo, legendColors[0], projection)}
|
|
460
|
-
{isHex &&
|
|
455
|
+
{isHex && hexMap.type === 'shapes' && getArrowDirection(geoData, geo, legendColors[0])}
|
|
461
456
|
</g>
|
|
462
457
|
</g>
|
|
463
458
|
)
|
|
@@ -495,7 +490,7 @@ const UsaMap = () => {
|
|
|
495
490
|
)
|
|
496
491
|
|
|
497
492
|
// Bubbles
|
|
498
|
-
if (
|
|
493
|
+
if (general.type === 'bubble') {
|
|
499
494
|
geosJsx.push(
|
|
500
495
|
<BubbleList
|
|
501
496
|
key='bubbles'
|
|
@@ -529,12 +524,12 @@ const UsaMap = () => {
|
|
|
529
524
|
let textColor = getContrastColor('#FFF', bgColor)
|
|
530
525
|
|
|
531
526
|
// always make HI black since it is off to the side
|
|
532
|
-
if (abbr === 'US-HI' && !
|
|
527
|
+
if (abbr === 'US-HI' && !general.displayAsHex) {
|
|
533
528
|
textColor = '#000'
|
|
534
529
|
}
|
|
535
530
|
|
|
536
531
|
let x = 0,
|
|
537
|
-
y =
|
|
532
|
+
y = hexMap.type === 'shapes' && general.displayAsHex ? -10 : 5
|
|
538
533
|
|
|
539
534
|
// used to nudge/move some of the labels for better readability
|
|
540
535
|
if (nudges[abbr] && false === isHex) {
|
|
@@ -581,7 +576,7 @@ const UsaMap = () => {
|
|
|
581
576
|
return (
|
|
582
577
|
<ErrorBoundary component='UsaMap'>
|
|
583
578
|
<svg viewBox='0 0 880 500' role='img' aria-label={handleMapAriaLabels(state)}>
|
|
584
|
-
{
|
|
579
|
+
{general.displayAsHex ? (
|
|
585
580
|
<Mercator data={unitedStatesHex} scale={650} translate={[1600, 775]}>
|
|
586
581
|
{({ features, projection }) => constructGeoJsx(features, projection)}
|
|
587
582
|
</Mercator>
|
|
@@ -590,18 +585,21 @@ const UsaMap = () => {
|
|
|
590
585
|
{({ features, projection }) => constructGeoJsx(features, projection)}
|
|
591
586
|
</AlbersUsa>
|
|
592
587
|
)}
|
|
593
|
-
{
|
|
588
|
+
{annotations.length > 0 && <Annotation.Draggable onDragStateChange={handleDragStateChange} />}
|
|
594
589
|
</svg>
|
|
595
590
|
|
|
596
591
|
{territories.length > 0 && (
|
|
597
592
|
<>
|
|
598
593
|
{/* Temporarily make the max width fit the image width */}
|
|
599
|
-
<div
|
|
600
|
-
<div>
|
|
601
|
-
<
|
|
594
|
+
<div>
|
|
595
|
+
<div className='d-flex mt-2'>
|
|
596
|
+
<h5>{general.territoriesLabel}</h5>
|
|
597
|
+
{'data' === general.type && logo && (
|
|
598
|
+
<img src={logo} alt='' className='map-logo' style={{ maxWidth: '50px' }} />
|
|
599
|
+
)}
|
|
602
600
|
</div>
|
|
603
601
|
<div>
|
|
604
|
-
<span className=
|
|
602
|
+
<span className='mt-1 mb-2 d-flex flex-wrap territories'>{territories}</span>
|
|
605
603
|
</div>
|
|
606
604
|
</div>
|
|
607
605
|
</>
|
|
@@ -11,6 +11,7 @@ import CityList from '../CityList'
|
|
|
11
11
|
import BubbleList from '../BubbleList'
|
|
12
12
|
import ConfigContext from '../../context'
|
|
13
13
|
import ZoomControls from '../ZoomControls'
|
|
14
|
+
import { getGeoFillColor, getGeoStrokeColor } from '../../helpers/colors'
|
|
14
15
|
|
|
15
16
|
const { features: world } = feature(topoJSON, topoJSON.objects.countries)
|
|
16
17
|
|
|
@@ -77,7 +78,12 @@ const WorldMap = () => {
|
|
|
77
78
|
const geosJsx = geographies.map(({ feature: geo, path }, i) => {
|
|
78
79
|
// If the geo.properties.state value is found in the data use that, otherwise fall back to geo.properties.iso
|
|
79
80
|
const dataHasStateName = state.data.some(d => d[state.columns.geo.name] === geo.properties.state)
|
|
80
|
-
const geoKey =
|
|
81
|
+
const geoKey =
|
|
82
|
+
geo.properties.state && data[geo.properties.state]
|
|
83
|
+
? geo.properties.state
|
|
84
|
+
: geo.properties.name
|
|
85
|
+
? geo.properties.name
|
|
86
|
+
: geo.properties.iso
|
|
81
87
|
|
|
82
88
|
const additionalData = {
|
|
83
89
|
name: geo.properties.name
|
|
@@ -98,33 +104,36 @@ const WorldMap = () => {
|
|
|
98
104
|
legendColors = applyLegendToRow(geoData)
|
|
99
105
|
}
|
|
100
106
|
|
|
101
|
-
const geoStrokeColor = state
|
|
107
|
+
const geoStrokeColor = getGeoStrokeColor(state)
|
|
108
|
+
const geoFillColor = getGeoFillColor(state)
|
|
102
109
|
|
|
103
110
|
let styles: Record<string, string | Record<string, string>> = {
|
|
104
|
-
fill:
|
|
111
|
+
fill: geoFillColor,
|
|
105
112
|
cursor: 'default'
|
|
106
113
|
}
|
|
107
114
|
|
|
108
115
|
const strokeWidth = 0.9
|
|
109
116
|
|
|
110
117
|
// If a legend applies, return it with appropriate information.
|
|
118
|
+
const toolTip = applyTooltipsToGeo(geoDisplayName, geoData)
|
|
111
119
|
if (legendColors && legendColors[0] !== '#000000' && state.general.type !== 'bubble') {
|
|
112
|
-
const toolTip = applyTooltipsToGeo(geoDisplayName, geoData)
|
|
113
|
-
|
|
114
120
|
styles = {
|
|
115
121
|
...styles,
|
|
116
|
-
fill: state.general.type !== 'world-geocode' ? legendColors[0] :
|
|
122
|
+
fill: state.general.type !== 'world-geocode' ? legendColors[0] : geoFillColor,
|
|
117
123
|
cursor: 'default',
|
|
118
124
|
'&:hover': {
|
|
119
|
-
fill: state.general.type !== 'world-geocode' ? legendColors[1] :
|
|
125
|
+
fill: state.general.type !== 'world-geocode' ? legendColors[1] : geoFillColor
|
|
120
126
|
},
|
|
121
127
|
'&:active': {
|
|
122
|
-
fill: state.general.type !== 'world-geocode' ? legendColors[2] :
|
|
128
|
+
fill: state.general.type !== 'world-geocode' ? legendColors[2] : geoFillColor
|
|
123
129
|
}
|
|
124
130
|
}
|
|
125
131
|
|
|
126
132
|
// When to add pointer cursor
|
|
127
|
-
if (
|
|
133
|
+
if (
|
|
134
|
+
(state.columns.navigate && geoData[state.columns.navigate.name]) ||
|
|
135
|
+
state.tooltips.appearanceType === 'click'
|
|
136
|
+
) {
|
|
128
137
|
styles.cursor = 'pointer'
|
|
129
138
|
}
|
|
130
139
|
|
|
@@ -147,11 +156,38 @@ const WorldMap = () => {
|
|
|
147
156
|
}
|
|
148
157
|
|
|
149
158
|
// Default return state, just geo with no additional information
|
|
150
|
-
return
|
|
159
|
+
return (
|
|
160
|
+
<Geo
|
|
161
|
+
additionaldata={JSON.stringify(additionalData)}
|
|
162
|
+
geodata={JSON.stringify(geoData)}
|
|
163
|
+
state={state}
|
|
164
|
+
key={i + '-geo'}
|
|
165
|
+
stroke={geoStrokeColor}
|
|
166
|
+
strokeWidth={strokeWidth}
|
|
167
|
+
style={styles}
|
|
168
|
+
styles={styles}
|
|
169
|
+
path={path}
|
|
170
|
+
data-tooltip-id={`tooltip__${tooltipId}`}
|
|
171
|
+
data-tooltip-html={toolTip}
|
|
172
|
+
/>
|
|
173
|
+
)
|
|
151
174
|
})
|
|
152
175
|
|
|
153
176
|
// Cities
|
|
154
|
-
geosJsx.push(
|
|
177
|
+
geosJsx.push(
|
|
178
|
+
<CityList
|
|
179
|
+
applyLegendToRow={applyLegendToRow}
|
|
180
|
+
applyTooltipsToGeo={applyTooltipsToGeo}
|
|
181
|
+
data={data}
|
|
182
|
+
displayGeoName={displayGeoName}
|
|
183
|
+
geoClickHandler={geoClickHandler}
|
|
184
|
+
key='cities'
|
|
185
|
+
projection={projection}
|
|
186
|
+
state={state}
|
|
187
|
+
titleCase={titleCase}
|
|
188
|
+
tooltipId={tooltipId}
|
|
189
|
+
/>
|
|
190
|
+
)
|
|
155
191
|
|
|
156
192
|
// Bubbles
|
|
157
193
|
if (state.general.type === 'bubble') {
|
|
@@ -166,7 +202,9 @@ const WorldMap = () => {
|
|
|
166
202
|
applyTooltipsToGeo={applyTooltipsToGeo}
|
|
167
203
|
displayGeoName={displayGeoName}
|
|
168
204
|
tooltipId={tooltipId}
|
|
169
|
-
handleCircleClick={country =>
|
|
205
|
+
handleCircleClick={country =>
|
|
206
|
+
handleCircleClick(country, state, setState, setRuntimeData, generateRuntimeData)
|
|
207
|
+
}
|
|
170
208
|
/>
|
|
171
209
|
)
|
|
172
210
|
}
|
|
@@ -178,19 +216,42 @@ const WorldMap = () => {
|
|
|
178
216
|
<ErrorBoundary component='WorldMap'>
|
|
179
217
|
{hasZoom ? (
|
|
180
218
|
<svg viewBox='0 0 880 500' role='img' aria-label={handleMapAriaLabels(state)}>
|
|
181
|
-
<rect
|
|
182
|
-
|
|
219
|
+
<rect
|
|
220
|
+
height={500}
|
|
221
|
+
width={880}
|
|
222
|
+
onClick={() => handleReset(state, setState, setRuntimeData, generateRuntimeData)}
|
|
223
|
+
fill='white'
|
|
224
|
+
/>
|
|
225
|
+
<ZoomableGroup
|
|
226
|
+
zoom={position.zoom}
|
|
227
|
+
center={position.coordinates}
|
|
228
|
+
onMoveEnd={handleMoveEnd}
|
|
229
|
+
maxZoom={4}
|
|
230
|
+
projection={projection}
|
|
231
|
+
width={880}
|
|
232
|
+
height={500}
|
|
233
|
+
>
|
|
183
234
|
<Mercator data={world}>{({ features }) => constructGeoJsx(features)}</Mercator>
|
|
184
235
|
</ZoomableGroup>
|
|
185
236
|
</svg>
|
|
186
237
|
) : (
|
|
187
238
|
<svg viewBox='0 0 880 500'>
|
|
188
|
-
<ZoomableGroup
|
|
239
|
+
<ZoomableGroup
|
|
240
|
+
zoom={1}
|
|
241
|
+
center={position.coordinates}
|
|
242
|
+
onMoveEnd={handleMoveEnd}
|
|
243
|
+
maxZoom={0}
|
|
244
|
+
projection={projection}
|
|
245
|
+
width={880}
|
|
246
|
+
height={500}
|
|
247
|
+
>
|
|
189
248
|
<Mercator data={world}>{({ features }) => constructGeoJsx(features)}</Mercator>
|
|
190
249
|
</ZoomableGroup>
|
|
191
250
|
</svg>
|
|
192
251
|
)}
|
|
193
|
-
{(state.general.type === 'data' ||
|
|
252
|
+
{(state.general.type === 'data' ||
|
|
253
|
+
(state.general.type === 'world-geocode' && hasZoom) ||
|
|
254
|
+
(state.general.type === 'bubble' && hasZoom)) && (
|
|
194
255
|
<ZoomControls
|
|
195
256
|
// prettier-ignore
|
|
196
257
|
generateRuntimeData={generateRuntimeData}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import colorPalettes from '@cdc/core/data/colorPalettes'
|
|
2
|
+
import chroma from 'chroma-js'
|
|
3
|
+
import { isOlderVersion } from '@cdc/core/helpers/ver/versionNeedsUpdate'
|
|
4
|
+
import { type ChartConfig } from '@cdc/chart/src/types/ChartConfig'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* applyColorToLegend
|
|
8
|
+
* @param legendIdx legend item index
|
|
9
|
+
* @param config chart config
|
|
10
|
+
* @param result hash of legend items
|
|
11
|
+
* @returns
|
|
12
|
+
*/
|
|
13
|
+
export const applyColorToLegend = (legendIdx: number, config: ChartConfig, result = []) => {
|
|
14
|
+
try {
|
|
15
|
+
if (!config) throw new Error('Config is required')
|
|
16
|
+
|
|
17
|
+
const colorDistributions = {
|
|
18
|
+
1: [1],
|
|
19
|
+
2: [1, 3],
|
|
20
|
+
3: [1, 3, 5],
|
|
21
|
+
4: [0, 2, 4, 6],
|
|
22
|
+
5: [0, 2, 4, 6, 7],
|
|
23
|
+
6: [0, 2, 3, 4, 5, 7],
|
|
24
|
+
7: [0, 2, 3, 4, 5, 6, 7],
|
|
25
|
+
8: [0, 2, 3, 4, 5, 6, 7, 8],
|
|
26
|
+
9: [0, 1, 2, 3, 4, 5, 6, 7, 8],
|
|
27
|
+
10: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const specialClasses = config?.legend?.specialClasses
|
|
31
|
+
const { general } = config || {}
|
|
32
|
+
// Default to "bluegreen" color scheme if the passed color isn't valid
|
|
33
|
+
let mapColorPalette = config.customColors || colorPalettes[config.color] || colorPalettes['bluegreen']
|
|
34
|
+
|
|
35
|
+
// Handle Region Maps need for a 10th color
|
|
36
|
+
if (general.geoType === 'us-region' && mapColorPalette.length < 10 && mapColorPalette.length > 8) {
|
|
37
|
+
if (!general.palette.isReversed) {
|
|
38
|
+
mapColorPalette.push(chroma(mapColorPalette[8]).darken(0.75).hex())
|
|
39
|
+
} else {
|
|
40
|
+
mapColorPalette.unshift(chroma(mapColorPalette[0]).darken(0.75).hex())
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
let colorIdx = legendIdx - specialClasses.length
|
|
45
|
+
|
|
46
|
+
// Special Classes (No Data)
|
|
47
|
+
if (result[legendIdx].special) {
|
|
48
|
+
if (isOlderVersion(config.version, '4.24.11')) {
|
|
49
|
+
const specialClassColors = chroma.scale(['#D4D4D4', '#939393']).colors(specialClasses)
|
|
50
|
+
return specialClassColors[legendIdx]
|
|
51
|
+
} else {
|
|
52
|
+
const specialClassColors = ['#A9AEB1', '#71767A']
|
|
53
|
+
return specialClassColors[legendIdx]
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (config.color.includes('qualitative')) return mapColorPalette[colorIdx]
|
|
58
|
+
|
|
59
|
+
// If the current version is newer than 4.24.10, use the color palette
|
|
60
|
+
if (!isOlderVersion(config.version, '4.24.12')) {
|
|
61
|
+
if (config.customColors) return mapColorPalette[legendIdx - specialClasses.length]
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
let amt = Math.max(result.length - specialClasses.length, 1)
|
|
65
|
+
let distributionArray = colorDistributions[amt]
|
|
66
|
+
let specificColor
|
|
67
|
+
|
|
68
|
+
if (distributionArray) {
|
|
69
|
+
specificColor = distributionArray[legendIdx - specialClasses.length]
|
|
70
|
+
} else if (mapColorPalette[colorIdx]) {
|
|
71
|
+
specificColor = colorIdx
|
|
72
|
+
} else {
|
|
73
|
+
specificColor = mapColorPalette.length - 1
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return mapColorPalette[specificColor]
|
|
77
|
+
} catch (error) {
|
|
78
|
+
console.error('Error in applyColorToLegend', error)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { MapConfig } from '../types/MapConfig'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Returns the stroke color for geographical borders based on the provided map configuration.
|
|
5
|
+
*
|
|
6
|
+
* @param {MapConfig} config - The map configuration object.
|
|
7
|
+
* @returns {string} The stroke color for geographical borders.
|
|
8
|
+
* @see DEV-9726 Map Border Colors and Fills
|
|
9
|
+
*
|
|
10
|
+
*/
|
|
11
|
+
export const getGeoStrokeColor = (config: MapConfig) => {
|
|
12
|
+
const bodyStyles = getComputedStyle(document.body)
|
|
13
|
+
if (config.general.geoBorderColor === 'darkGray') {
|
|
14
|
+
return bodyStyles.getPropertyValue('--cool-gray-90')
|
|
15
|
+
} else {
|
|
16
|
+
return bodyStyles.getPropertyValue('--white')
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const getGeoFillColor = (config: MapConfig) => {
|
|
21
|
+
const bodyStyles = getComputedStyle(document.body)
|
|
22
|
+
return bodyStyles.getPropertyValue('--cool-gray-10')
|
|
23
|
+
}
|
package/src/hooks/useTooltip.ts
CHANGED
|
@@ -14,7 +14,9 @@ const useTooltip = props => {
|
|
|
14
14
|
if (geoType === 'us-county' && type !== 'us-geocode') {
|
|
15
15
|
let stateFipsCode = row[config.columns.geo.name].substring(0, 2)
|
|
16
16
|
const stateName = supportedStatesFipsCodes[stateFipsCode]
|
|
17
|
-
toolTipText += hideGeoColumnInTooltip
|
|
17
|
+
toolTipText += hideGeoColumnInTooltip
|
|
18
|
+
? `<strong>${stateName}</strong><br/>`
|
|
19
|
+
: `<strong>Location: ${stateName}</strong><br/>`
|
|
18
20
|
}
|
|
19
21
|
return toolTipText
|
|
20
22
|
}
|
|
@@ -61,7 +63,7 @@ const useTooltip = props => {
|
|
|
61
63
|
const handleTooltipSpecialClassText = (specialClasses, column, row, value, columnKey) => {
|
|
62
64
|
if (specialClasses && specialClasses.length && typeof specialClasses[0] === 'object') {
|
|
63
65
|
for (const specialClass of specialClasses) {
|
|
64
|
-
if (column.name === specialClass.key && String(row[specialClass.key]) === specialClass.value) {
|
|
66
|
+
if (column.name === specialClass.key && String(row?.[specialClass.key]) === specialClass.value) {
|
|
65
67
|
value = displayDataAsText(specialClass.label, columnKey)
|
|
66
68
|
break
|
|
67
69
|
}
|
|
@@ -73,7 +75,8 @@ const useTooltip = props => {
|
|
|
73
75
|
const handleTooltipPrimaryColumn = (tooltipValue, column) => {
|
|
74
76
|
const { hidePrimaryColumnInTooltip } = config.general as { hidePrimaryColumnInTooltip: boolean }
|
|
75
77
|
let tooltipPrefix = column.label?.length > 0 ? column.label : ''
|
|
76
|
-
if ((column.name === config.columns.primary.name && hidePrimaryColumnInTooltip) || !tooltipPrefix)
|
|
78
|
+
if ((column.name === config.columns.primary.name && hidePrimaryColumnInTooltip) || !tooltipPrefix)
|
|
79
|
+
return `<li class="tooltip-body">${tooltipValue}</li>`
|
|
77
80
|
return `<li class="tooltip-body">${tooltipPrefix}: ${tooltipValue}</li>`
|
|
78
81
|
}
|
|
79
82
|
|
|
@@ -91,7 +94,7 @@ const useTooltip = props => {
|
|
|
91
94
|
legend: { specialClasses }
|
|
92
95
|
} = config
|
|
93
96
|
|
|
94
|
-
if (tooltipEnabledMaps.includes(currentMapType) &&
|
|
97
|
+
if (tooltipEnabledMaps.includes(currentMapType) && (row !== undefined || config.general.geoType === 'world')) {
|
|
95
98
|
toolTipText += `<ul>`
|
|
96
99
|
|
|
97
100
|
// if tooltips are allowed, loop through each column
|
|
@@ -102,7 +105,7 @@ const useTooltip = props => {
|
|
|
102
105
|
let tooltipValue = handleTooltipSpecialClassText(specialClasses, column, row, '', columnKey)
|
|
103
106
|
|
|
104
107
|
if (!tooltipValue) {
|
|
105
|
-
tooltipValue = displayDataAsText(row[column.name], columnKey)
|
|
108
|
+
tooltipValue = row ? displayDataAsText(row[column.name], columnKey) : 'No Data'
|
|
106
109
|
}
|
|
107
110
|
|
|
108
111
|
toolTipText += handleTooltipPrimaryColumn(tooltipValue, column)
|
|
@@ -115,7 +118,7 @@ const useTooltip = props => {
|
|
|
115
118
|
}
|
|
116
119
|
|
|
117
120
|
const buildTooltip = (row, geoName, toolTipText = '') => {
|
|
118
|
-
if (!row) return
|
|
121
|
+
if (!row && config.general.geoType !== 'world') return
|
|
119
122
|
|
|
120
123
|
// Handle County Location Columns
|
|
121
124
|
toolTipText += handleTooltipStateNameColumn(toolTipText, row)
|