@cdc/map 4.24.7 → 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.
Files changed (46) hide show
  1. package/dist/cdcmap.js +40720 -38422
  2. package/examples/county-year.csv +10 -0
  3. package/examples/default-geocode.json +44 -10
  4. package/examples/default-patterns.json +0 -2
  5. package/examples/default-single-state.json +279 -108
  6. package/examples/map-issue-3.json +646 -0
  7. package/examples/single-state-filter.json +153 -0
  8. package/index.html +9 -6
  9. package/package.json +3 -3
  10. package/src/CdcMap.tsx +322 -126
  11. package/src/_stories/CdcMap.stories.tsx +7 -0
  12. package/src/_stories/_mock/DEV-8942.json +270 -0
  13. package/src/components/Annotation/AnnotationDropdown.tsx +1 -0
  14. package/src/components/{BubbleList.jsx → BubbleList.tsx} +1 -1
  15. package/src/components/{CityList.jsx → CityList.tsx} +28 -2
  16. package/src/components/{DataTable.jsx → DataTable.tsx} +2 -2
  17. package/src/components/EditorPanel/components/EditorPanel.tsx +647 -127
  18. package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +0 -22
  19. package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +61 -11
  20. package/src/components/Legend/components/Legend.tsx +125 -36
  21. package/src/components/Legend/components/index.scss +42 -42
  22. package/src/components/Modal.tsx +25 -0
  23. package/src/components/UsaMap/components/SingleState/SingleState.CountyOutput.tsx +74 -0
  24. package/src/components/UsaMap/components/SingleState/SingleState.StateOutput.tsx +29 -0
  25. package/src/components/UsaMap/components/SingleState/index.tsx +9 -0
  26. package/src/components/UsaMap/components/UsaMap.County.tsx +84 -33
  27. package/src/components/UsaMap/components/UsaMap.SingleState.tsx +173 -206
  28. package/src/components/UsaMap/components/UsaMap.State.tsx +161 -26
  29. package/src/components/UsaMap/data/us-extended-geography.json +1 -0
  30. package/src/components/UsaMap/helpers/map.ts +111 -0
  31. package/src/components/WorldMap/WorldMap.tsx +17 -32
  32. package/src/components/ZoomControls.tsx +41 -0
  33. package/src/data/initial-state.js +7 -1
  34. package/src/data/supported-geos.js +15 -4
  35. package/src/helpers/generateRuntimeLegendHash.ts +2 -2
  36. package/src/hooks/useStateZoom.tsx +157 -0
  37. package/src/hooks/{useZoomPan.js → useZoomPan.ts} +6 -5
  38. package/src/scss/editor-panel.scss +0 -4
  39. package/src/scss/main.scss +23 -1
  40. package/src/scss/map.scss +8 -0
  41. package/src/types/MapConfig.ts +9 -1
  42. package/src/types/MapContext.ts +14 -2
  43. package/src/components/Modal.jsx +0 -22
  44. /package/src/components/{Geo.jsx → Geo.tsx} +0 -0
  45. /package/src/components/{NavigationMenu.jsx → NavigationMenu.tsx} +0 -0
  46. /package/src/components/{ZoomableGroup.jsx → ZoomableGroup.tsx} +0 -0
@@ -23,7 +23,7 @@ import Territory from './Territory'
23
23
  import useMapLayers from '../../../hooks/useMapLayers'
24
24
  import ConfigContext from '../../../context'
25
25
  import { MapContext } from '../../../types/MapContext'
26
- import { getContrastColor } from '@cdc/core/helpers/cove/accessibility'
26
+ import { checkColorContrast, getContrastColor, getColorContrast } from '@cdc/core/helpers/cove/accessibility'
27
27
 
28
28
  const { features: unitedStates } = feature(topoJSON, topoJSON.objects.states)
29
29
  const { features: unitedStatesHex } = feature(hexTopoJSON, hexTopoJSON.objects.states)
@@ -66,7 +66,9 @@ const UsaMap = () => {
66
66
  supportedTerritories,
67
67
  titleCase,
68
68
  tooltipId,
69
- handleDragStateChange
69
+ handleDragStateChange,
70
+ setState,
71
+ mapId
70
72
  } = useContext<MapContext>(ConfigContext)
71
73
 
72
74
  let isFilterValueSupported = false
@@ -154,15 +156,28 @@ const UsaMap = () => {
154
156
  let needsPointer = false
155
157
 
156
158
  // If we need to add a pointer cursor
157
- if ((state.columns.navigate && territoryData[state.columns.navigate.name]) || state.tooltips.appearanceType === 'click') {
159
+ if (
160
+ (state.columns.navigate && territoryData[state.columns.navigate.name]) ||
161
+ state.tooltips.appearanceType === 'click'
162
+ ) {
158
163
  needsPointer = true
159
164
  }
160
165
 
161
166
  styles = {
162
167
  color: textColor,
163
168
  fill: legendColors[0],
164
- opacity: setSharedFilterValue && isFilterValueSupported && setSharedFilterValue !== territoryData[state.columns.geo.name] ? 0.5 : 1,
165
- stroke: setSharedFilterValue && isFilterValueSupported && setSharedFilterValue === territoryData[state.columns.geo.name] ? 'rgba(0, 0, 0, 1)' : geoStrokeColor,
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,
166
181
  cursor: needsPointer ? 'pointer' : 'default',
167
182
  '&:hover': {
168
183
  fill: legendColors[1]
@@ -253,8 +268,14 @@ const UsaMap = () => {
253
268
 
254
269
  styles = {
255
270
  fill: state.general.type !== 'bubble' ? legendColors[0] : '#E6E6E6',
256
- opacity: setSharedFilterValue && isFilterValueSupported && setSharedFilterValue !== geoData[state.columns.geo.name] ? 0.5 : 1,
257
- stroke: setSharedFilterValue && isFilterValueSupported && setSharedFilterValue === geoData[state.columns.geo.name] ? 'rgba(0, 0, 0, 1)' : geoStrokeColor,
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,
258
279
  cursor: 'default',
259
280
  '&:hover': {
260
281
  fill: state.general.type !== 'bubble' ? legendColors[1] : '#e6e6e6'
@@ -265,7 +286,10 @@ const UsaMap = () => {
265
286
  }
266
287
 
267
288
  // When to add pointer cursor
268
- if ((state.columns.navigate && geoData[state.columns.navigate.name]) || state.tooltips.appearanceType === 'click') {
289
+ if (
290
+ (state.columns.navigate && geoData[state.columns.navigate.name]) ||
291
+ state.tooltips.appearanceType === 'click'
292
+ ) {
269
293
  styles.cursor = 'pointer'
270
294
  }
271
295
 
@@ -283,33 +307,81 @@ const UsaMap = () => {
283
307
  switch (item.operator) {
284
308
  case '=':
285
309
  if (geoData[item.key] === item.value || Number(geoData[item.key]) === Number(item.value)) {
286
- return <HexIcon textColor={textColor} item={item} index={itemIndex} centroid={centroid} iconSize={iconSize} />
310
+ return (
311
+ <HexIcon
312
+ textColor={textColor}
313
+ item={item}
314
+ index={itemIndex}
315
+ centroid={centroid}
316
+ iconSize={iconSize}
317
+ />
318
+ )
287
319
  }
288
320
  break
289
321
  case '≠':
290
322
  if (geoData[item.key] !== item.value && Number(geoData[item.key]) !== Number(item.value)) {
291
- return <HexIcon textColor={textColor} item={item} index={itemIndex} centroid={centroid} iconSize={iconSize} />
323
+ return (
324
+ <HexIcon
325
+ textColor={textColor}
326
+ item={item}
327
+ index={itemIndex}
328
+ centroid={centroid}
329
+ iconSize={iconSize}
330
+ />
331
+ )
292
332
  }
293
333
  break
294
334
  case '<':
295
335
  if (Number(geoData[item.key]) < Number(item.value)) {
296
- return <HexIcon textColor={textColor} item={item} index={itemIndex} centroid={centroid} iconSize={iconSize} />
336
+ return (
337
+ <HexIcon
338
+ textColor={textColor}
339
+ item={item}
340
+ index={itemIndex}
341
+ centroid={centroid}
342
+ iconSize={iconSize}
343
+ />
344
+ )
297
345
  }
298
346
  break
299
347
  case '>':
300
348
  if (Number(geoData[item.key]) > Number(item.value)) {
301
- return <HexIcon textColor={textColor} item={item} index={itemIndex} centroid={centroid} iconSize={iconSize} />
349
+ return (
350
+ <HexIcon
351
+ textColor={textColor}
352
+ item={item}
353
+ index={itemIndex}
354
+ centroid={centroid}
355
+ iconSize={iconSize}
356
+ />
357
+ )
302
358
  }
303
359
  break
304
360
  case '<=':
305
361
  if (Number(geoData[item.key]) <= Number(item.value)) {
306
- return <HexIcon textColor={textColor} item={item} index={itemIndex} centroid={centroid} iconSize={iconSize} />
362
+ return (
363
+ <HexIcon
364
+ textColor={textColor}
365
+ item={item}
366
+ index={itemIndex}
367
+ centroid={centroid}
368
+ iconSize={iconSize}
369
+ />
370
+ )
307
371
  }
308
372
  break
309
373
  case '>=':
310
374
  if (item.operator === '>=') {
311
375
  if (Number(geoData[item.key]) >= Number(item.value)) {
312
- return <HexIcon textColor={textColor} item={item} index={itemIndex} centroid={centroid} iconSize={iconSize} />
376
+ return (
377
+ <HexIcon
378
+ textColor={textColor}
379
+ item={item}
380
+ index={itemIndex}
381
+ centroid={centroid}
382
+ iconSize={iconSize}
383
+ />
384
+ )
313
385
  }
314
386
  }
315
387
  break
@@ -324,7 +396,15 @@ const UsaMap = () => {
324
396
 
325
397
  return (
326
398
  <g data-name={geoName} key={key} tabIndex={-1}>
327
- <g className='geo-group' style={styles} onClick={() => geoClickHandler(geoDisplayName, geoData)} id={geoName} data-tooltip-id={`tooltip__${tooltipId}`} data-tooltip-html={tooltip} tabIndex={-1}>
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
+ >
328
408
  {/* state path */}
329
409
  <path tabIndex={-1} className='single-geo' strokeWidth={1.3} d={path} />
330
410
 
@@ -335,15 +415,45 @@ const UsaMap = () => {
335
415
  const hasMatchingValues = patternData.dataValue === geoData[patternData.dataKey]
336
416
  const patternColor = patternData.color || getContrastColor('#000', currentFill)
337
417
 
418
+ if (!hasMatchingValues) return
419
+ checkColorContrast(currentFill, patternColor)
420
+
338
421
  return (
339
- hasMatchingValues && (
340
- <>
341
- {pattern === 'waves' && <PatternWaves id={`${dataKey}--${geoIndex}`} height={patternSizes[size] ?? 10} width={patternSizes[size] ?? 10} fill={patternColor} />}
342
- {pattern === 'circles' && <PatternCircles id={`${dataKey}--${geoIndex}`} height={patternSizes[size] ?? 10} width={patternSizes[size] ?? 10} fill={patternColor} />}
343
- {pattern === 'lines' && <PatternLines id={`${dataKey}--${geoIndex}`} height={patternSizes[size] ?? 6} width={patternSizes[size] ?? 6} stroke={patternColor} strokeWidth={1} orientation={['diagonalRightToLeft']} />}
344
- <path className={`pattern-geoKey--${dataKey}`} tabIndex={-1} stroke='transparent' d={path} fill={`url(#${dataKey}--${geoIndex})`} />
345
- </>
346
- )
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
+ </>
347
457
  )
348
458
  })}
349
459
  {(isHex || showLabel) && geoLabel(geo, legendColors[0], projection)}
@@ -386,7 +496,18 @@ const UsaMap = () => {
386
496
 
387
497
  // Bubbles
388
498
  if (state.general.type === 'bubble') {
389
- geosJsx.push(<BubbleList key='bubbles' data={state.data} runtimeData={data} state={state} projection={projection} applyLegendToRow={applyLegendToRow} applyTooltipsToGeo={applyTooltipsToGeo} displayGeoName={displayGeoName} />)
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
+ )
390
511
  }
391
512
 
392
513
  // })
@@ -435,8 +556,22 @@ const UsaMap = () => {
435
556
 
436
557
  return (
437
558
  <g tabIndex={-1}>
438
- <line x1={centroid[0]} y1={centroid[1]} x2={centroid[0] + dx} y2={centroid[1] + dy} stroke='rgba(0,0,0,.5)' strokeWidth={1} />
439
- <text x={4} strokeWidth='0' fontSize={13} style={{ fill: '#202020' }} alignmentBaseline='middle' transform={`translate(${centroid[0] + dx}, ${centroid[1] + dy})`}>
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
+ >
440
575
  {abbr.substring(3)}
441
576
  </text>
442
577
  </g>
@@ -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 additionalData={additionalData} geoData={geoData} state={state} key={i + '-geo'} stroke={geoStrokeColor} strokeWidth={strokeWidth} style={styles} path={path} />
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 position={position} setPosition={setPosition} setRuntimeData={setRuntimeData} state={state} setState={setState} generateRuntimeData={generateRuntimeData} />
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,6 +1,7 @@
1
1
  export default {
2
2
  annotations: [],
3
3
  general: {
4
+ noStateFoundMessage: 'Map Unavailable',
4
5
  annotationDropdownText: 'Annotations',
5
6
  geoBorderColor: 'darkGray',
6
7
  headerColor: 'theme-blue',
@@ -67,7 +68,12 @@ export default {
67
68
  type: 'equalnumber',
68
69
  numberOfItems: 3,
69
70
  position: 'side',
70
- title: 'Legend'
71
+ title: '',
72
+ style: 'circles',
73
+ subStyle: 'linear blocks',
74
+ tickRotation: '',
75
+ singleColumnLegend: false,
76
+ hideBorder: false
71
77
  },
72
78
  filters: [],
73
79
  table: {
@@ -190,6 +190,7 @@ export const supportedStatesFipsCodes = {
190
190
  '08': 'Colorado',
191
191
  '09': 'Connecticut',
192
192
  10: 'Delaware',
193
+ 11: 'District of Columbia',
193
194
  12: 'Florida',
194
195
  13: 'Georgia',
195
196
  15: 'Hawaii',
@@ -234,9 +235,9 @@ export const supportedStatesFipsCodes = {
234
235
  56: 'Wyoming',
235
236
  60: 'American Samoa',
236
237
  66: 'Guam',
237
- 69: 'Northern Mariana Islands',
238
+ 69: 'Commonwealth of the Northern Mariana Islands',
238
239
  72: 'Puerto Rico',
239
- 78: 'Virgin Islands'
240
+ 78: 'United States Virgin Islands'
240
241
  }
241
242
 
242
243
  export const supportedCountries = {
@@ -289,7 +290,12 @@ export const supportedCountries = {
289
290
  COL: ['Colombia'],
290
291
  COM: ['Comoros'],
291
292
  COG: ['Congo', 'Congo, Republic of the', 'Republic of the Congo'],
292
- COD: ['Democratic Republic of the Congo', 'Congo, Democratic Republic of the', 'Congo, the Democratic Republic of the', 'Dem. Rep. Congo'],
293
+ COD: [
294
+ 'Democratic Republic of the Congo',
295
+ 'Congo, Democratic Republic of the',
296
+ 'Congo, the Democratic Republic of the',
297
+ 'Dem. Rep. Congo'
298
+ ],
293
299
  COK: ['Cook Islands', 'Cook Is.', 'Cook Islands (New Zealand)'],
294
300
  CRI: ['Costa Rica'],
295
301
  CIV: ["Côte d'Ivoire"],
@@ -382,7 +388,12 @@ export const supportedCountries = {
382
388
  MUS: ['Mauritius'],
383
389
  MYT: ['Mayotte', 'Mayotte (France)'],
384
390
  MEX: ['Mexico'],
385
- FSM: ['Micronesia', 'Federated States of Micronesia', 'Micronesia (Federated States of)', 'Micronesia, Federated States of'],
391
+ FSM: [
392
+ 'Micronesia',
393
+ 'Federated States of Micronesia',
394
+ 'Micronesia (Federated States of)',
395
+ 'Micronesia, Federated States of'
396
+ ],
386
397
  MDA: ['Moldova', 'Moldova, Republic of'],
387
398
  MCO: ['Monaco'],
388
399
  MNG: ['Mongolia'],
@@ -15,9 +15,9 @@ export const generateRuntimeLegendHash = (state, runtimeFilters) => {
15
15
  specialClasses: state.legend.specialClasses,
16
16
  geoType: state.general.geoType,
17
17
  data: state.data,
18
- ...runtimeFilters,
19
18
  filters: {
20
19
  ...state.filters
21
- }
20
+ },
21
+ ...runtimeFilters
22
22
  })
23
23
  }