@cdc/map 4.24.5 → 4.24.9
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 +71853 -64936
- package/examples/annotation/index.json +552 -0
- package/examples/annotation/usa-map.json +900 -0
- package/examples/county-year.csv +10 -0
- package/examples/default-geocode.json +44 -10
- package/examples/default-patterns.json +0 -2
- package/examples/default-single-state.json +279 -108
- package/examples/map-issue-3.json +646 -0
- package/examples/single-state-filter.json +153 -0
- package/index.html +10 -6
- package/package.json +6 -5
- package/src/CdcMap.tsx +367 -199
- package/src/_stories/CdcMap.stories.tsx +14 -0
- package/src/_stories/_mock/DEV-7286.json +165 -0
- package/src/_stories/_mock/DEV-8942.json +270 -0
- package/src/components/Annotation/Annotation.Draggable.styles.css +18 -0
- package/src/components/Annotation/Annotation.Draggable.tsx +152 -0
- package/src/components/Annotation/AnnotationDropdown.styles.css +14 -0
- package/src/components/Annotation/AnnotationDropdown.tsx +70 -0
- package/src/components/Annotation/AnnotationList.styles.css +45 -0
- package/src/components/Annotation/AnnotationList.tsx +42 -0
- package/src/components/Annotation/index.tsx +11 -0
- package/src/components/{BubbleList.jsx → BubbleList.tsx} +1 -1
- package/src/components/{CityList.jsx → CityList.tsx} +28 -2
- package/src/components/{DataTable.jsx → DataTable.tsx} +2 -2
- package/src/components/EditorPanel/components/EditorPanel.tsx +650 -129
- package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +336 -0
- package/src/components/EditorPanel/components/{Panel.PatternSettings.tsx → Panels/Panel.PatternSettings.tsx} +63 -13
- package/src/components/EditorPanel/components/{Panels.tsx → Panels/index.tsx} +3 -0
- package/src/components/Legend/components/Legend.tsx +125 -42
- package/src/components/Legend/components/index.scss +42 -42
- package/src/components/Modal.tsx +25 -0
- package/src/components/UsaMap/components/SingleState/SingleState.CountyOutput.tsx +74 -0
- package/src/components/UsaMap/components/SingleState/SingleState.StateOutput.tsx +29 -0
- package/src/components/UsaMap/components/SingleState/index.tsx +9 -0
- package/src/components/UsaMap/components/Territory/Territory.Rectangle.tsx +4 -3
- package/src/components/UsaMap/components/UsaMap.County.tsx +114 -36
- package/src/components/UsaMap/components/UsaMap.Region.tsx +2 -0
- package/src/components/UsaMap/components/UsaMap.SingleState.tsx +175 -206
- package/src/components/UsaMap/components/UsaMap.State.tsx +188 -44
- package/src/components/UsaMap/data/us-extended-geography.json +1 -0
- package/src/components/UsaMap/helpers/map.ts +111 -0
- package/src/components/WorldMap/WorldMap.tsx +17 -32
- package/src/components/ZoomControls.tsx +41 -0
- package/src/data/initial-state.js +11 -2
- package/src/data/supported-geos.js +15 -4
- package/src/helpers/generateColorsArray.ts +13 -0
- package/src/helpers/generateRuntimeLegendHash.ts +23 -0
- package/src/helpers/getUniqueValues.ts +19 -0
- package/src/helpers/hashObj.ts +25 -0
- package/src/helpers/tests/generateColorsArray.test.ts +18 -0
- package/src/helpers/tests/generateRuntimeLegendHash.test.ts +11 -0
- package/src/helpers/tests/hashObj.test.ts +10 -0
- package/src/hooks/useStateZoom.tsx +157 -0
- package/src/hooks/{useZoomPan.js → useZoomPan.ts} +6 -5
- package/src/scss/editor-panel.scss +0 -4
- package/src/scss/main.scss +23 -1
- package/src/scss/map.scss +14 -3
- package/src/types/MapConfig.ts +9 -1
- package/src/types/MapContext.ts +16 -2
- package/LICENSE +0 -201
- package/src/components/Modal.jsx +0 -22
- package/src/test/CdcMap.test.jsx +0 -19
- /package/src/components/EditorPanel/components/{Panel.PatternSettings-style.css → Panels/Panel.PatternSettings-style.css} +0 -0
- /package/src/components/{Geo.jsx → Geo.tsx} +0 -0
- /package/src/components/{NavigationMenu.jsx → NavigationMenu.tsx} +0 -0
- /package/src/components/{ZoomableGroup.jsx → ZoomableGroup.tsx} +0 -0
|
@@ -16,13 +16,14 @@ import { geoAlbersUsa } from 'd3-composite-projections'
|
|
|
16
16
|
import { PatternLines, PatternCircles, PatternWaves } from '@visx/pattern'
|
|
17
17
|
import HexIcon from './HexIcon'
|
|
18
18
|
import { patternSizes } from '../helpers/patternSizes'
|
|
19
|
+
import Annotation from '../../Annotation'
|
|
19
20
|
|
|
20
21
|
import Territory from './Territory'
|
|
21
22
|
|
|
22
23
|
import useMapLayers from '../../../hooks/useMapLayers'
|
|
23
24
|
import ConfigContext from '../../../context'
|
|
24
25
|
import { MapContext } from '../../../types/MapContext'
|
|
25
|
-
import { getContrastColor } from '@cdc/core/helpers/cove/accessibility'
|
|
26
|
+
import { checkColorContrast, getContrastColor, getColorContrast } from '@cdc/core/helpers/cove/accessibility'
|
|
26
27
|
|
|
27
28
|
const { features: unitedStates } = feature(topoJSON, topoJSON.objects.states)
|
|
28
29
|
const { features: unitedStatesHex } = feature(hexTopoJSON, hexTopoJSON.objects.states)
|
|
@@ -64,7 +65,10 @@ const UsaMap = () => {
|
|
|
64
65
|
state,
|
|
65
66
|
supportedTerritories,
|
|
66
67
|
titleCase,
|
|
67
|
-
tooltipId
|
|
68
|
+
tooltipId,
|
|
69
|
+
handleDragStateChange,
|
|
70
|
+
setState,
|
|
71
|
+
mapId
|
|
68
72
|
} = useContext<MapContext>(ConfigContext)
|
|
69
73
|
|
|
70
74
|
let isFilterValueSupported = false
|
|
@@ -118,7 +122,15 @@ const UsaMap = () => {
|
|
|
118
122
|
|
|
119
123
|
const geoStrokeColor = state.general.geoBorderColor === 'darkGray' ? 'rgba(0, 0, 0, 0.2)' : 'rgba(255,255,255,0.7)'
|
|
120
124
|
|
|
121
|
-
const
|
|
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
|
+
}
|
|
132
|
+
|
|
133
|
+
const territories = territoriesData.map((territory, territoryIndex) => {
|
|
122
134
|
const Shape = isHex ? Territory.Hexagon : Territory.Rectangle
|
|
123
135
|
|
|
124
136
|
const territoryData = data[territory]
|
|
@@ -144,15 +156,28 @@ const UsaMap = () => {
|
|
|
144
156
|
let needsPointer = false
|
|
145
157
|
|
|
146
158
|
// If we need to add a pointer cursor
|
|
147
|
-
if (
|
|
159
|
+
if (
|
|
160
|
+
(state.columns.navigate && territoryData[state.columns.navigate.name]) ||
|
|
161
|
+
state.tooltips.appearanceType === 'click'
|
|
162
|
+
) {
|
|
148
163
|
needsPointer = true
|
|
149
164
|
}
|
|
150
165
|
|
|
151
166
|
styles = {
|
|
152
167
|
color: textColor,
|
|
153
168
|
fill: legendColors[0],
|
|
154
|
-
opacity:
|
|
155
|
-
|
|
169
|
+
opacity:
|
|
170
|
+
setSharedFilterValue &&
|
|
171
|
+
isFilterValueSupported &&
|
|
172
|
+
setSharedFilterValue !== territoryData[state.columns.geo.name]
|
|
173
|
+
? 0.5
|
|
174
|
+
: 1,
|
|
175
|
+
stroke:
|
|
176
|
+
setSharedFilterValue &&
|
|
177
|
+
isFilterValueSupported &&
|
|
178
|
+
setSharedFilterValue === territoryData[state.columns.geo.name]
|
|
179
|
+
? 'rgba(0, 0, 0, 1)'
|
|
180
|
+
: geoStrokeColor,
|
|
156
181
|
cursor: needsPointer ? 'pointer' : 'default',
|
|
157
182
|
'&:hover': {
|
|
158
183
|
fill: legendColors[1]
|
|
@@ -163,22 +188,20 @@ const UsaMap = () => {
|
|
|
163
188
|
}
|
|
164
189
|
|
|
165
190
|
return (
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
/>
|
|
181
|
-
</>
|
|
191
|
+
<Shape
|
|
192
|
+
key={`label__${territoryIndex}`}
|
|
193
|
+
label={label}
|
|
194
|
+
style={styles}
|
|
195
|
+
text={styles.color}
|
|
196
|
+
strokeWidth={1.5}
|
|
197
|
+
textColor={textColor}
|
|
198
|
+
onClick={() => geoClickHandler(territory, territoryData)}
|
|
199
|
+
data-tooltip-id={`tooltip__${tooltipId}`}
|
|
200
|
+
data-tooltip-html={toolTip}
|
|
201
|
+
territory={territory}
|
|
202
|
+
territoryData={territoryData}
|
|
203
|
+
tabIndex={-1}
|
|
204
|
+
/>
|
|
182
205
|
)
|
|
183
206
|
}
|
|
184
207
|
})
|
|
@@ -245,8 +268,14 @@ const UsaMap = () => {
|
|
|
245
268
|
|
|
246
269
|
styles = {
|
|
247
270
|
fill: state.general.type !== 'bubble' ? legendColors[0] : '#E6E6E6',
|
|
248
|
-
opacity:
|
|
249
|
-
|
|
271
|
+
opacity:
|
|
272
|
+
setSharedFilterValue && isFilterValueSupported && setSharedFilterValue !== geoData[state.columns.geo.name]
|
|
273
|
+
? 0.5
|
|
274
|
+
: 1,
|
|
275
|
+
stroke:
|
|
276
|
+
setSharedFilterValue && isFilterValueSupported && setSharedFilterValue === geoData[state.columns.geo.name]
|
|
277
|
+
? 'rgba(0, 0, 0, 1)'
|
|
278
|
+
: geoStrokeColor,
|
|
250
279
|
cursor: 'default',
|
|
251
280
|
'&:hover': {
|
|
252
281
|
fill: state.general.type !== 'bubble' ? legendColors[1] : '#e6e6e6'
|
|
@@ -257,7 +286,10 @@ const UsaMap = () => {
|
|
|
257
286
|
}
|
|
258
287
|
|
|
259
288
|
// When to add pointer cursor
|
|
260
|
-
if (
|
|
289
|
+
if (
|
|
290
|
+
(state.columns.navigate && geoData[state.columns.navigate.name]) ||
|
|
291
|
+
state.tooltips.appearanceType === 'click'
|
|
292
|
+
) {
|
|
261
293
|
styles.cursor = 'pointer'
|
|
262
294
|
}
|
|
263
295
|
|
|
@@ -275,33 +307,81 @@ const UsaMap = () => {
|
|
|
275
307
|
switch (item.operator) {
|
|
276
308
|
case '=':
|
|
277
309
|
if (geoData[item.key] === item.value || Number(geoData[item.key]) === Number(item.value)) {
|
|
278
|
-
return
|
|
310
|
+
return (
|
|
311
|
+
<HexIcon
|
|
312
|
+
textColor={textColor}
|
|
313
|
+
item={item}
|
|
314
|
+
index={itemIndex}
|
|
315
|
+
centroid={centroid}
|
|
316
|
+
iconSize={iconSize}
|
|
317
|
+
/>
|
|
318
|
+
)
|
|
279
319
|
}
|
|
280
320
|
break
|
|
281
321
|
case '≠':
|
|
282
322
|
if (geoData[item.key] !== item.value && Number(geoData[item.key]) !== Number(item.value)) {
|
|
283
|
-
return
|
|
323
|
+
return (
|
|
324
|
+
<HexIcon
|
|
325
|
+
textColor={textColor}
|
|
326
|
+
item={item}
|
|
327
|
+
index={itemIndex}
|
|
328
|
+
centroid={centroid}
|
|
329
|
+
iconSize={iconSize}
|
|
330
|
+
/>
|
|
331
|
+
)
|
|
284
332
|
}
|
|
285
333
|
break
|
|
286
334
|
case '<':
|
|
287
335
|
if (Number(geoData[item.key]) < Number(item.value)) {
|
|
288
|
-
return
|
|
336
|
+
return (
|
|
337
|
+
<HexIcon
|
|
338
|
+
textColor={textColor}
|
|
339
|
+
item={item}
|
|
340
|
+
index={itemIndex}
|
|
341
|
+
centroid={centroid}
|
|
342
|
+
iconSize={iconSize}
|
|
343
|
+
/>
|
|
344
|
+
)
|
|
289
345
|
}
|
|
290
346
|
break
|
|
291
347
|
case '>':
|
|
292
348
|
if (Number(geoData[item.key]) > Number(item.value)) {
|
|
293
|
-
return
|
|
349
|
+
return (
|
|
350
|
+
<HexIcon
|
|
351
|
+
textColor={textColor}
|
|
352
|
+
item={item}
|
|
353
|
+
index={itemIndex}
|
|
354
|
+
centroid={centroid}
|
|
355
|
+
iconSize={iconSize}
|
|
356
|
+
/>
|
|
357
|
+
)
|
|
294
358
|
}
|
|
295
359
|
break
|
|
296
360
|
case '<=':
|
|
297
361
|
if (Number(geoData[item.key]) <= Number(item.value)) {
|
|
298
|
-
return
|
|
362
|
+
return (
|
|
363
|
+
<HexIcon
|
|
364
|
+
textColor={textColor}
|
|
365
|
+
item={item}
|
|
366
|
+
index={itemIndex}
|
|
367
|
+
centroid={centroid}
|
|
368
|
+
iconSize={iconSize}
|
|
369
|
+
/>
|
|
370
|
+
)
|
|
299
371
|
}
|
|
300
372
|
break
|
|
301
373
|
case '>=':
|
|
302
374
|
if (item.operator === '>=') {
|
|
303
375
|
if (Number(geoData[item.key]) >= Number(item.value)) {
|
|
304
|
-
return
|
|
376
|
+
return (
|
|
377
|
+
<HexIcon
|
|
378
|
+
textColor={textColor}
|
|
379
|
+
item={item}
|
|
380
|
+
index={itemIndex}
|
|
381
|
+
centroid={centroid}
|
|
382
|
+
iconSize={iconSize}
|
|
383
|
+
/>
|
|
384
|
+
)
|
|
305
385
|
}
|
|
306
386
|
}
|
|
307
387
|
break
|
|
@@ -316,7 +396,15 @@ const UsaMap = () => {
|
|
|
316
396
|
|
|
317
397
|
return (
|
|
318
398
|
<g data-name={geoName} key={key} tabIndex={-1}>
|
|
319
|
-
<g
|
|
399
|
+
<g
|
|
400
|
+
className='geo-group'
|
|
401
|
+
style={styles}
|
|
402
|
+
onClick={() => geoClickHandler(geoDisplayName, geoData)}
|
|
403
|
+
id={geoName}
|
|
404
|
+
data-tooltip-id={`tooltip__${tooltipId}`}
|
|
405
|
+
data-tooltip-html={tooltip}
|
|
406
|
+
tabIndex={-1}
|
|
407
|
+
>
|
|
320
408
|
{/* state path */}
|
|
321
409
|
<path tabIndex={-1} className='single-geo' strokeWidth={1.3} d={path} />
|
|
322
410
|
|
|
@@ -327,15 +415,45 @@ const UsaMap = () => {
|
|
|
327
415
|
const hasMatchingValues = patternData.dataValue === geoData[patternData.dataKey]
|
|
328
416
|
const patternColor = patternData.color || getContrastColor('#000', currentFill)
|
|
329
417
|
|
|
418
|
+
if (!hasMatchingValues) return
|
|
419
|
+
checkColorContrast(currentFill, patternColor)
|
|
420
|
+
|
|
330
421
|
return (
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
422
|
+
<>
|
|
423
|
+
{pattern === 'waves' && (
|
|
424
|
+
<PatternWaves
|
|
425
|
+
id={`${mapId}--${dataKey}--${geoIndex}`}
|
|
426
|
+
height={patternSizes[size] ?? 10}
|
|
427
|
+
width={patternSizes[size] ?? 10}
|
|
428
|
+
fill={patternColor}
|
|
429
|
+
/>
|
|
430
|
+
)}
|
|
431
|
+
{pattern === 'circles' && (
|
|
432
|
+
<PatternCircles
|
|
433
|
+
id={`${mapId}--${dataKey}--${geoIndex}`}
|
|
434
|
+
height={patternSizes[size] ?? 10}
|
|
435
|
+
width={patternSizes[size] ?? 10}
|
|
436
|
+
fill={patternColor}
|
|
437
|
+
/>
|
|
438
|
+
)}
|
|
439
|
+
{pattern === 'lines' && (
|
|
440
|
+
<PatternLines
|
|
441
|
+
id={`${mapId}--${dataKey}--${geoIndex}`}
|
|
442
|
+
height={patternSizes[size] ?? 6}
|
|
443
|
+
width={patternSizes[size] ?? 6}
|
|
444
|
+
stroke={patternColor}
|
|
445
|
+
strokeWidth={1}
|
|
446
|
+
orientation={['diagonalRightToLeft']}
|
|
447
|
+
/>
|
|
448
|
+
)}
|
|
449
|
+
<path
|
|
450
|
+
className={`pattern-geoKey--${dataKey}`}
|
|
451
|
+
tabIndex={-1}
|
|
452
|
+
stroke='transparent'
|
|
453
|
+
d={path}
|
|
454
|
+
fill={`url(#${mapId}--${dataKey}--${geoIndex})`}
|
|
455
|
+
/>
|
|
456
|
+
</>
|
|
339
457
|
)
|
|
340
458
|
})}
|
|
341
459
|
{(isHex || showLabel) && geoLabel(geo, legendColors[0], projection)}
|
|
@@ -378,7 +496,18 @@ const UsaMap = () => {
|
|
|
378
496
|
|
|
379
497
|
// Bubbles
|
|
380
498
|
if (state.general.type === 'bubble') {
|
|
381
|
-
geosJsx.push(
|
|
499
|
+
geosJsx.push(
|
|
500
|
+
<BubbleList
|
|
501
|
+
key='bubbles'
|
|
502
|
+
data={state.data}
|
|
503
|
+
runtimeData={data}
|
|
504
|
+
state={state}
|
|
505
|
+
projection={projection}
|
|
506
|
+
applyLegendToRow={applyLegendToRow}
|
|
507
|
+
applyTooltipsToGeo={applyTooltipsToGeo}
|
|
508
|
+
displayGeoName={displayGeoName}
|
|
509
|
+
/>
|
|
510
|
+
)
|
|
382
511
|
}
|
|
383
512
|
|
|
384
513
|
// })
|
|
@@ -427,8 +556,22 @@ const UsaMap = () => {
|
|
|
427
556
|
|
|
428
557
|
return (
|
|
429
558
|
<g tabIndex={-1}>
|
|
430
|
-
<line
|
|
431
|
-
|
|
559
|
+
<line
|
|
560
|
+
x1={centroid[0]}
|
|
561
|
+
y1={centroid[1]}
|
|
562
|
+
x2={centroid[0] + dx}
|
|
563
|
+
y2={centroid[1] + dy}
|
|
564
|
+
stroke='rgba(0,0,0,.5)'
|
|
565
|
+
strokeWidth={1}
|
|
566
|
+
/>
|
|
567
|
+
<text
|
|
568
|
+
x={4}
|
|
569
|
+
strokeWidth='0'
|
|
570
|
+
fontSize={13}
|
|
571
|
+
style={{ fill: '#202020' }}
|
|
572
|
+
alignmentBaseline='middle'
|
|
573
|
+
transform={`translate(${centroid[0] + dx}, ${centroid[1] + dy})`}
|
|
574
|
+
>
|
|
432
575
|
{abbr.substring(3)}
|
|
433
576
|
</text>
|
|
434
577
|
</g>
|
|
@@ -447,6 +590,7 @@ const UsaMap = () => {
|
|
|
447
590
|
{({ features, projection }) => constructGeoJsx(features, projection)}
|
|
448
591
|
</AlbersUsa>
|
|
449
592
|
)}
|
|
593
|
+
{state.annotations.length > 0 && <Annotation.Draggable onDragStateChange={handleDragStateChange} />}
|
|
450
594
|
</svg>
|
|
451
595
|
|
|
452
596
|
{territories.length > 0 && (
|
|
@@ -456,7 +600,7 @@ const UsaMap = () => {
|
|
|
456
600
|
<span className='territories-label label'>{state.general.territoriesLabel}</span>
|
|
457
601
|
</div>
|
|
458
602
|
<div>
|
|
459
|
-
<span className={
|
|
603
|
+
<span className={getTerritoriesClasses()}>{territories}</span>
|
|
460
604
|
</div>
|
|
461
605
|
</div>
|
|
462
606
|
</>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type":"Topology","arcs":[[[128628,58684],[30,57],[-17,63],[30,47],[44,-21],[35,35],[74,5],[26,-46],[170,8],[9,-28],[-140,-76],[-178,-47],[-83,3]],[[1608,280],[113,17],[5,-80],[-39,13],[-35,-44],[-44,94]],[[1408,384],[91,12],[-17,-33],[-57,-19],[-17,40]],[[122,156],[48,-77]],[[170,79],[-65,-79],[-48,74],[-57,25],[57,55],[65,2]],[[128706,59939],[0,-30]],[[128706,59909],[26,-69],[-69,-17],[-66,36],[-30,-32],[-52,78],[-105,-23],[30,85],[44,-36],[52,41],[83,-33],[87,0]],[[384328,54018],[17,44],[9,159],[39,-19],[87,105],[22,-34],[-61,-126],[17,-99],[-56,11],[0,-115],[-53,28],[-21,46]],[[128759,59886],[56,61],[48,-6],[66,-44],[-57,-7],[4,-77],[-39,32],[-57,-8],[-21,49]],[[128706,59939],[57,10],[-9,-37],[-48,-3]],[[383033,50942],[22,23],[148,25],[92,216],[26,102],[30,-17],[31,-68],[61,-17],[-44,-139],[-139,-171],[-39,-69],[-9,-175],[-52,-88],[-79,47],[-30,159],[35,79],[-53,93]],[[384201,53806],[18,70],[56,85],[22,-85],[-17,-37],[30,-63],[-9,-96],[-39,-46],[-61,172]],[[384746,55614],[44,68],[17,-25],[-35,-71],[-26,28]],[[384485,58543],[21,31],[5,-77],[-26,46]],[[384354,59393],[61,84],[0,93],[48,0],[0,-104],[-40,-21],[-43,-86],[-26,34]],[[384267,60694],[26,61],[48,-44],[-5,-80],[-35,-36],[-30,39],[-4,60]],[[384262,56279],[87,4],[13,-57],[-82,-13],[-18,66]],[[122,156],[87,108],[18,-31],[117,3],[18,-69],[-31,20],[-126,-47],[-35,-61]],[[383639,52173],[48,80],[92,55],[65,-14],[-9,-55],[-52,-20],[-35,-62],[-48,-9],[-22,36],[-39,-11]],[[113802,97593],[91,110],[48,-53],[113,-133],[-156,-185],[-5,88],[5,58],[-96,115]],[[126645,59224],[-48,-26],[-61,38],[-52,-30],[-31,21],[-74,37],[-96,-88],[-17,10],[-9,-2],[-65,-12],[-18,-38],[-65,47],[-4,27],[-92,12],[-61,-52],[-70,32],[-52,-19],[4,83],[44,49],[-48,19],[13,102],[22,24],[4,118],[31,49],[-40,132],[-47,55],[-57,119],[39,24],[96,74],[-9,115],[31,50],[48,16],[100,-7],[78,-39],[66,-9],[78,6],[87,-6],[70,-22],[105,40],[43,-17],[66,-6],[78,-24],[35,32],[109,0],[43,-21],[53,10],[87,-25],[48,-24],[39,35],[109,-41],[57,14],[104,-13],[92,-50],[43,-2],[53,-69],[100,-39],[52,44],[-9,-72],[14,-109],[56,-71],[-30,-67],[-48,29],[13,-44],[-39,4],[-35,-51],[-35,21],[-26,-32],[-44,-80],[-26,-110],[-39,-30],[-26,-75],[-74,-57],[-79,-24],[-52,19],[-31,-42],[-30,20],[-105,-67],[-43,14],[-31,-40],[-30,-3],[-27,57],[-91,58],[-57,-69],[-78,82],[-48,12],[-61,-26]],[[128105,59906],[48,-33],[6,2],[46,18],[39,-56],[-74,-57],[-11,7],[-41,23],[-13,96]],[[124954,59472],[87,19],[22,-68],[-65,-60],[-53,51],[9,58]],[[127817,59480],[214,85],[152,-51],[-78,-40],[-92,-11],[-48,-38],[-34,19],[-75,-27],[-39,63]],[[127795,59986],[31,-22],[0,-69],[-31,91]]],"transform":{"scale":[0.000823601132486181,0.000546229125192084],"translate":[-170.84530299432993,-14.373864584355845]},"objects":{"counties":{"type":"GeometryCollection","geometries":[{"arcs":[[[1]],[[2]]],"type":"MultiPolygon","properties":{"name":"Manu'a"},"id":"60020"},{"arcs":[[3,4]],"type":"Polygon","properties":{"name":"Western"},"id":"60050"},{"arcs":[[5,6]],"type":"Polygon","properties":{"name":"St. Thomas"},"id":"78030"},{"arcs":[[7]],"type":"Polygon","properties":{"name":"Saipan"},"id":"69110"},{"arcs":[[[8]],[[9,-6]]],"type":"MultiPolygon","properties":{"name":"St. John"},"id":"78020"},{"arcs":[[10]],"type":"Polygon","properties":{"name":"Guam"},"id":"66010"},{"arcs":[[11]],"type":"Polygon","properties":{"name":"Tinian"},"id":"69120"},{"arcs":[[[12]],[[13]],[[14]],[[15]],[[16]]],"type":"MultiPolygon","properties":{"name":"Northern Islands"},"id":"69085"},{"arcs":[[-4,17]],"type":"Polygon","properties":{"name":"Eastern"},"id":"60010"},{"arcs":[[18]],"type":"Polygon","properties":{"name":"Rota"},"id":"69100"},{"arcs":[[0]],"type":"Polygon","properties":{"name":"St. Croix"},"id":"78010"},{"arcs":[[0]],"type":"Polygon","properties":{"name":"District of Columbia"},"id":"11"}]},"states":{"type":"GeometryCollection","geometries":[{"arcs":[[[20]],[[21]],[[22]],[[23]],[[24]]],"type":"MultiPolygon","properties":{"name":"Puerto Rico"},"id":"72"},{"arcs":[[[1]],[[2]],[[4,17]]],"type":"MultiPolygon","properties":{"name":"American Samoa"},"id":"60"},{"arcs":[[[6,9]],[[0]],[[8]]],"type":"MultiPolygon","properties":{"name":"United States Virgin Islands"},"id":"78"},{"arcs":[[[7]],[[11]],[[12]],[[13]],[[14]],[[15]],[[16]],[[18]]],"type":"MultiPolygon","properties":{"name":"Commonwealth of the Northern Mariana Islands"},"id":"69"},{"arcs":[[10]],"type":"Polygon","properties":{"name":"Guam"},"id":"66"}]}}}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { feature } from 'topojson-client'
|
|
2
|
+
import usExtendedGeography from './../data/us-extended-geography.json'
|
|
3
|
+
|
|
4
|
+
export const getCountyTopoURL = year => {
|
|
5
|
+
return `https://www.cdc.gov/TemplatePackage/contrib/data/county-topography/cb_${year}_us_county_20m.json`
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const getTopoData = year => {
|
|
9
|
+
return new Promise((resolve, reject) => {
|
|
10
|
+
const resolveWithTopo = async response => {
|
|
11
|
+
if (response.status !== 200) {
|
|
12
|
+
response = await import('./../data/cb_2019_us_county_20m.json')
|
|
13
|
+
} else {
|
|
14
|
+
response = await response.json()
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const counties = [response, usExtendedGeography].flatMap(topo => feature(topo, topo.objects.counties).features)
|
|
18
|
+
const states = [response, usExtendedGeography].flatMap(topo => feature(topo, topo.objects.states).features)
|
|
19
|
+
|
|
20
|
+
const topoData = {
|
|
21
|
+
year: year || 'default',
|
|
22
|
+
fulljson: response,
|
|
23
|
+
counties,
|
|
24
|
+
states
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
resolve(topoData)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const numericYear = parseInt(year)
|
|
31
|
+
|
|
32
|
+
if (isNaN(numericYear)) {
|
|
33
|
+
fetch(getCountyTopoURL(2019)).then(resolveWithTopo)
|
|
34
|
+
} else if (numericYear > 2022) {
|
|
35
|
+
fetch(getCountyTopoURL(2022)).then(resolveWithTopo)
|
|
36
|
+
} else if (numericYear < 2013) {
|
|
37
|
+
fetch(getCountyTopoURL(2013)).then(resolveWithTopo)
|
|
38
|
+
} else {
|
|
39
|
+
switch (numericYear) {
|
|
40
|
+
case 2022:
|
|
41
|
+
fetch(getCountyTopoURL(2022)).then(resolveWithTopo)
|
|
42
|
+
break
|
|
43
|
+
case 2021:
|
|
44
|
+
fetch(getCountyTopoURL(2021)).then(resolveWithTopo)
|
|
45
|
+
break
|
|
46
|
+
case 2020:
|
|
47
|
+
fetch(getCountyTopoURL(2020)).then(resolveWithTopo)
|
|
48
|
+
break
|
|
49
|
+
case 2018:
|
|
50
|
+
case 2017:
|
|
51
|
+
case 2016:
|
|
52
|
+
case 2015:
|
|
53
|
+
fetch(getCountyTopoURL(2015)).then(resolveWithTopo)
|
|
54
|
+
break
|
|
55
|
+
case 2014:
|
|
56
|
+
fetch(getCountyTopoURL(2014)).then(resolveWithTopo)
|
|
57
|
+
break
|
|
58
|
+
case 2013:
|
|
59
|
+
fetch(getCountyTopoURL(2013)).then(resolveWithTopo)
|
|
60
|
+
break
|
|
61
|
+
default:
|
|
62
|
+
fetch(getCountyTopoURL(2019)).then(resolveWithTopo)
|
|
63
|
+
break
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export const getCurrentTopoYear = (state, runtimeFilters) => {
|
|
70
|
+
let currentYear = state.general.countyCensusYear
|
|
71
|
+
|
|
72
|
+
if (state.general.filterControlsCountyYear && runtimeFilters && runtimeFilters.length > 0) {
|
|
73
|
+
let yearFilter = runtimeFilters.filter(filter => filter.columnName === state.general.filterControlsCountyYear)
|
|
74
|
+
if (yearFilter.length > 0 && yearFilter[0].active) {
|
|
75
|
+
currentYear = yearFilter[0].active
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return currentYear || 'default'
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export const isTopoReady = (topoData, state, runtimeFilters) => {
|
|
83
|
+
let currentYear = getCurrentTopoYear(state, runtimeFilters)
|
|
84
|
+
|
|
85
|
+
return topoData.year && (!currentYear || currentYear === topoData.year)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export const hasMoreThanFromHash = (data: { [key: string]: any }): boolean => {
|
|
89
|
+
// Get all keys of the data object
|
|
90
|
+
const keys = Object.keys(data)
|
|
91
|
+
|
|
92
|
+
// Filter out the 'fromHash' key
|
|
93
|
+
const otherKeys = keys.filter(key => key !== 'fromHash')
|
|
94
|
+
|
|
95
|
+
// Check if there are any other keys left
|
|
96
|
+
return otherKeys.length > 0
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export const getFilterControllingStatePicked = (state, runtimeData) => {
|
|
100
|
+
if (!state.general.filterControlsStatePicked || !runtimeData) {
|
|
101
|
+
const statePicked = state?.general?.statePicked?.stateName
|
|
102
|
+
return statePicked
|
|
103
|
+
} else {
|
|
104
|
+
if (hasMoreThanFromHash(runtimeData)) {
|
|
105
|
+
let statePickedFromFilter = Object.values(runtimeData)?.map(s => s[state.general.filterControlsStatePicked])?.[0]
|
|
106
|
+
const statePicked = statePickedFromFilter || state.general.statePicked.stateName || 'Alabama'
|
|
107
|
+
return statePicked
|
|
108
|
+
}
|
|
109
|
+
return null
|
|
110
|
+
}
|
|
111
|
+
}
|
|
@@ -10,6 +10,7 @@ import Geo from '../Geo'
|
|
|
10
10
|
import CityList from '../CityList'
|
|
11
11
|
import BubbleList from '../BubbleList'
|
|
12
12
|
import ConfigContext from '../../context'
|
|
13
|
+
import ZoomControls from '../ZoomControls'
|
|
13
14
|
|
|
14
15
|
const { features: world } = feature(topoJSON, topoJSON.objects.countries)
|
|
15
16
|
|
|
@@ -34,7 +35,9 @@ const WorldMap = () => {
|
|
|
34
35
|
state,
|
|
35
36
|
supportedCountries,
|
|
36
37
|
titleCase,
|
|
37
|
-
tooltipId
|
|
38
|
+
tooltipId,
|
|
39
|
+
setScale,
|
|
40
|
+
setTranslate
|
|
38
41
|
} = useContext(ConfigContext)
|
|
39
42
|
|
|
40
43
|
// TODO Refactor - state should be set together here to avoid rerenders
|
|
@@ -59,35 +62,6 @@ const WorldMap = () => {
|
|
|
59
62
|
setPosition(pos => ({ ...pos, zoom: pos.zoom / 1.5 }))
|
|
60
63
|
}
|
|
61
64
|
|
|
62
|
-
const ZoomControls = ({ position, setPosition, state, setState, setRuntimeData, generateRuntimeData }) => {
|
|
63
|
-
if (!state.general.allowMapZoom) return
|
|
64
|
-
return (
|
|
65
|
-
<div className='zoom-controls' data-html2canvas-ignore>
|
|
66
|
-
<button onClick={() => handleZoomIn(position, setPosition)} aria-label='Zoom In'>
|
|
67
|
-
<svg viewBox='0 0 24 24' stroke='currentColor' strokeWidth='3'>
|
|
68
|
-
<line x1='12' y1='5' x2='12' y2='19' />
|
|
69
|
-
<line x1='5' y1='12' x2='19' y2='12' />
|
|
70
|
-
</svg>
|
|
71
|
-
</button>
|
|
72
|
-
<button onClick={() => handleZoomOut(position, setPosition)} aria-label='Zoom Out'>
|
|
73
|
-
<svg viewBox='0 0 24 24' stroke='currentColor' strokeWidth='3'>
|
|
74
|
-
<line x1='5' y1='12' x2='19' y2='12' />
|
|
75
|
-
</svg>
|
|
76
|
-
</button>
|
|
77
|
-
{state.general.type === 'bubble' && (
|
|
78
|
-
<button onClick={() => handleReset(state, setState, setRuntimeData, generateRuntimeData)} className='reset' aria-label='Reset Zoom and Map Filters'>
|
|
79
|
-
Reset Filters
|
|
80
|
-
</button>
|
|
81
|
-
)}
|
|
82
|
-
{state.general.type === 'world-geocode' && (
|
|
83
|
-
<button onClick={() => handleReset(state, setState, setRuntimeData, generateRuntimeData)} className='reset' aria-label='Reset Zoom'>
|
|
84
|
-
Reset Zoom
|
|
85
|
-
</button>
|
|
86
|
-
)}
|
|
87
|
-
</div>
|
|
88
|
-
)
|
|
89
|
-
}
|
|
90
|
-
|
|
91
65
|
// TODO Refactor - state should be set together here to avoid rerenders
|
|
92
66
|
const handleCircleClick = (country, state, setState, setRuntimeData, generateRuntimeData) => {
|
|
93
67
|
if (!state.general.allowMapZoom) return
|
|
@@ -173,7 +147,7 @@ const WorldMap = () => {
|
|
|
173
147
|
}
|
|
174
148
|
|
|
175
149
|
// Default return state, just geo with no additional information
|
|
176
|
-
return <Geo
|
|
150
|
+
return <Geo additionaldata={JSON.stringify(additionalData)} geodata={JSON.stringify(geoData)} state={state} key={i + '-geo'} stroke={geoStrokeColor} strokeWidth={strokeWidth} style={styles} path={path} />
|
|
177
151
|
})
|
|
178
152
|
|
|
179
153
|
// Cities
|
|
@@ -217,7 +191,18 @@ const WorldMap = () => {
|
|
|
217
191
|
</svg>
|
|
218
192
|
)}
|
|
219
193
|
{(state.general.type === 'data' || (state.general.type === 'world-geocode' && hasZoom) || (state.general.type === 'bubble' && hasZoom)) && (
|
|
220
|
-
<ZoomControls
|
|
194
|
+
<ZoomControls
|
|
195
|
+
// prettier-ignore
|
|
196
|
+
generateRuntimeData={generateRuntimeData}
|
|
197
|
+
handleZoomIn={handleZoomIn}
|
|
198
|
+
handleZoomOut={handleZoomOut}
|
|
199
|
+
position={position}
|
|
200
|
+
setPosition={setPosition}
|
|
201
|
+
setRuntimeData={setRuntimeData}
|
|
202
|
+
setState={setState}
|
|
203
|
+
state={state}
|
|
204
|
+
handleReset={handleReset}
|
|
205
|
+
/>
|
|
221
206
|
)}
|
|
222
207
|
</ErrorBoundary>
|
|
223
208
|
)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import React, { useContext } from 'react'
|
|
2
|
+
import { MapConfig } from '../types/MapConfig'
|
|
3
|
+
import ConfigContext from '../context'
|
|
4
|
+
|
|
5
|
+
type ZoomControlsProps = {
|
|
6
|
+
handleZoomIn: (coordinates: [Number, Number], setPosition: Function) => void
|
|
7
|
+
handleZoomOut: (coordinates: [Number, Number], setPosition: Function) => void
|
|
8
|
+
handleReset: (coordinates: [Number, Number], setPosition: Function) => void
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const ZoomControls: React.FC<ZoomControlsProps> = ({ handleZoomIn, handleZoomOut, handleReset }) => {
|
|
12
|
+
const { state, setState, setRuntimeData, setPosition, position, generateRuntimeData } = useContext<MapContext>(ConfigContext)
|
|
13
|
+
if (!state.general.allowMapZoom) return
|
|
14
|
+
return (
|
|
15
|
+
<div className='zoom-controls' data-html2canvas-ignore>
|
|
16
|
+
<button onClick={() => handleZoomIn(position, setPosition)} aria-label='Zoom In'>
|
|
17
|
+
<svg viewBox='0 0 24 24' stroke='currentColor' strokeWidth='3'>
|
|
18
|
+
<line x1='12' y1='5' x2='12' y2='19' />
|
|
19
|
+
<line x1='5' y1='12' x2='19' y2='12' />
|
|
20
|
+
</svg>
|
|
21
|
+
</button>
|
|
22
|
+
<button onClick={() => handleZoomOut(position, setPosition)} aria-label='Zoom Out'>
|
|
23
|
+
<svg viewBox='0 0 24 24' stroke='currentColor' strokeWidth='3'>
|
|
24
|
+
<line x1='5' y1='12' x2='19' y2='12' />
|
|
25
|
+
</svg>
|
|
26
|
+
</button>
|
|
27
|
+
{state.general.type === 'bubble' && (
|
|
28
|
+
<button onClick={() => handleReset(state, setState, setRuntimeData, generateRuntimeData)} className='reset' aria-label='Reset Zoom and Map Filters'>
|
|
29
|
+
Reset Filters
|
|
30
|
+
</button>
|
|
31
|
+
)}
|
|
32
|
+
{(state.general.type === 'world-geocode' || state.general.geoType === 'single-state') && (
|
|
33
|
+
<button onClick={() => handleReset(state, setState, setRuntimeData, generateRuntimeData)} className='reset' aria-label='Reset Zoom'>
|
|
34
|
+
Reset Zoom
|
|
35
|
+
</button>
|
|
36
|
+
)}
|
|
37
|
+
</div>
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export default ZoomControls
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
export default {
|
|
2
|
+
annotations: [],
|
|
2
3
|
general: {
|
|
4
|
+
noStateFoundMessage: 'Map Unavailable',
|
|
5
|
+
annotationDropdownText: 'Annotations',
|
|
3
6
|
geoBorderColor: 'darkGray',
|
|
4
7
|
headerColor: 'theme-blue',
|
|
5
8
|
title: '',
|
|
@@ -44,7 +47,8 @@ export default {
|
|
|
44
47
|
prefix: '',
|
|
45
48
|
suffix: '',
|
|
46
49
|
name: '',
|
|
47
|
-
label: ''
|
|
50
|
+
label: '',
|
|
51
|
+
roundToPlace: 0
|
|
48
52
|
},
|
|
49
53
|
navigate: {
|
|
50
54
|
name: ''
|
|
@@ -64,7 +68,12 @@ export default {
|
|
|
64
68
|
type: 'equalnumber',
|
|
65
69
|
numberOfItems: 3,
|
|
66
70
|
position: 'side',
|
|
67
|
-
title: '
|
|
71
|
+
title: '',
|
|
72
|
+
style: 'circles',
|
|
73
|
+
subStyle: 'linear blocks',
|
|
74
|
+
tickRotation: '',
|
|
75
|
+
singleColumnLegend: false,
|
|
76
|
+
hideBorder: false
|
|
68
77
|
},
|
|
69
78
|
filters: [],
|
|
70
79
|
table: {
|