@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.
Files changed (49) hide show
  1. package/dist/cdcmap.js +34008 -33475
  2. package/examples/default-geocode.json +13 -4
  3. package/examples/default-usa-regions.json +267 -117
  4. package/examples/example-city-state.json +6 -3
  5. package/examples/pattern.json +861 -0
  6. package/examples/private/DEV-9989.json +229 -0
  7. package/examples/private/ardi.json +180 -0
  8. package/examples/private/colors 2.json +416 -0
  9. package/examples/private/colors.json +416 -0
  10. package/examples/private/colors.json.zip +0 -0
  11. package/examples/private/customColors.json +45348 -0
  12. package/examples/private/default-patterns.json +867 -0
  13. package/examples/private/test.json +1632 -0
  14. package/index.html +4 -5
  15. package/package.json +3 -3
  16. package/src/CdcMap.tsx +82 -79
  17. package/src/_stories/{CdcMapLegend.stories.tsx → CdcMap.Legend.Gradient.stories.tsx} +1 -20
  18. package/src/_stories/CdcMap.Legend.stories.tsx +40 -0
  19. package/src/_stories/CdcMap.Patterns.stories.tsx +29 -0
  20. package/src/_stories/CdcMap.stories.tsx +59 -0
  21. package/src/_stories/UsaMap.NoData.stories.tsx +19 -0
  22. package/src/_stories/_mock/custom-layer-map.json +1117 -0
  23. package/src/_stories/_mock/default-patterns.json +865 -0
  24. package/src/_stories/_mock/example-city-state.json +858 -0
  25. package/src/components/CityList.tsx +5 -2
  26. package/src/components/EditorPanel/components/EditorPanel.tsx +39 -256
  27. package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +1 -1
  28. package/src/components/Legend/components/Legend.tsx +22 -6
  29. package/src/components/Legend/components/index.scss +16 -23
  30. package/src/components/UsaMap/components/SingleState/SingleState.CountyOutput.tsx +40 -6
  31. package/src/components/UsaMap/components/SingleState/SingleState.StateOutput.tsx +10 -2
  32. package/src/components/UsaMap/components/Territory/Territory.Hexagon.tsx +1 -1
  33. package/src/components/UsaMap/components/Territory/Territory.Rectangle.tsx +14 -13
  34. package/src/components/UsaMap/components/UsaMap.County.tsx +11 -13
  35. package/src/components/UsaMap/components/UsaMap.Region.tsx +59 -16
  36. package/src/components/UsaMap/components/UsaMap.SingleState.tsx +2 -1
  37. package/src/components/UsaMap/components/UsaMap.State.tsx +58 -60
  38. package/src/components/WorldMap/WorldMap.tsx +77 -16
  39. package/src/data/initial-state.js +2 -1
  40. package/src/helpers/applyColorToLegend.ts +80 -0
  41. package/src/helpers/colors.ts +23 -0
  42. package/src/hooks/useTooltip.ts +9 -6
  43. package/src/scss/filters.scss +0 -3
  44. package/src/scss/main.scss +0 -1
  45. package/src/scss/map.scss +11 -59
  46. package/src/types/MapConfig.ts +7 -1
  47. package/src/types/MapContext.ts +1 -0
  48. package/examples/default-patterns.json +0 -579
  49. 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
- setState,
71
- mapId
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
- }, [state.general.geoType])
106
+ }, [general.geoType])
105
107
 
106
- const isHex = state.general.displayAsHex
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 (state.general.territoriesAlwaysShow) {
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, state.general.territoriesAlwaysShow])
123
+ }, [data, general.territoriesAlwaysShow])
122
124
 
123
- const geoStrokeColor = state.general.geoBorderColor === 'darkGray' ? 'rgba(0, 0, 0, 0.2)' : 'rgba(255,255,255,0.7)'
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: '#E6E6E6',
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) return <Shape key={label} label={label} style={styles} text={styles.color} />
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.5}
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 = state.general.displayStateLabels
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: '#E6E6E6',
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] : '#E6E6E6',
268
+ fill: state.general.type !== 'bubble' ? legendColors[0] : geoFillColor,
271
269
  opacity:
272
- setSharedFilterValue && isFilterValueSupported && setSharedFilterValue !== geoData[state.columns.geo.name]
270
+ setSharedFilterValue && isFilterValueSupported && setSharedFilterValue !== geoData[columns.geo.name]
273
271
  ? 0.5
274
272
  : 1,
275
273
  stroke:
276
- setSharedFilterValue && isFilterValueSupported && setSharedFilterValue === geoData[state.columns.geo.name]
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] : '#e6e6e6'
279
+ fill: state.general.type !== 'bubble' ? legendColors[1] : geoFillColor
282
280
  },
283
281
  '&:active': {
284
- fill: state.general.type !== 'bubble' ? legendColors[2] : '#e6e6e6'
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
- {state.hexMap.shapeGroups.map((group, groupIndex) => {
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.3} d={path} />
404
+ <path tabIndex={-1} className='single-geo' strokeWidth={1} d={path} />
410
405
 
411
406
  {/* apply patterns on top of state path*/}
412
- {state.map.patterns.map((patternData, patternIndex) => {
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 && state.hexMap.type === 'shapes' && getArrowDirection(geoData, geo, legendColors[0])}
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 (state.general.type === 'bubble') {
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' && !state.general.displayAsHex) {
527
+ if (abbr === 'US-HI' && !general.displayAsHex) {
533
528
  textColor = '#000'
534
529
  }
535
530
 
536
531
  let x = 0,
537
- y = state.hexMap.type === 'shapes' && state.general.displayAsHex ? -10 : 5
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
- {state.general.displayAsHex ? (
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
- {state.annotations.length > 0 && <Annotation.Draggable onDragStateChange={handleDragStateChange} />}
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 className='two-col' style={{ maxWidth: 'calc(100% - 75px)' }}>
600
- <div>
601
- <span className='territories-label label'>{state.general.territoriesLabel}</span>
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={getTerritoriesClasses()}>{territories}</span>
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 = geo.properties.state && data[geo.properties.state] ? geo.properties.state : geo.properties.name ? geo.properties.name : geo.properties.iso
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.general.geoBorderColor === 'darkGray' ? 'rgba(0, 0, 0, 0.2)' : 'rgba(255,255,255,0.7)'
107
+ const geoStrokeColor = getGeoStrokeColor(state)
108
+ const geoFillColor = getGeoFillColor(state)
102
109
 
103
110
  let styles: Record<string, string | Record<string, string>> = {
104
- fill: '#E6E6E6',
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] : '#E6E6E6',
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] : '#E6E6E6'
125
+ fill: state.general.type !== 'world-geocode' ? legendColors[1] : geoFillColor
120
126
  },
121
127
  '&:active': {
122
- fill: state.general.type !== 'world-geocode' ? legendColors[2] : '#E6E6E6'
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 ((state.columns.navigate && geoData[state.columns.navigate.name]) || state.tooltips.appearanceType === 'click') {
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 <Geo additionaldata={JSON.stringify(additionalData)} geodata={JSON.stringify(geoData)} state={state} key={i + '-geo'} stroke={geoStrokeColor} strokeWidth={strokeWidth} style={styles} path={path} />
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(<CityList applyLegendToRow={applyLegendToRow} applyTooltipsToGeo={applyTooltipsToGeo} data={data} displayGeoName={displayGeoName} geoClickHandler={geoClickHandler} key='cities' projection={projection} state={state} titleCase={titleCase} tooltipId={tooltipId} />)
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 => handleCircleClick(country, state, setState, setRuntimeData, generateRuntimeData)}
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 height={500} width={880} onClick={() => handleReset(state, setState, setRuntimeData, generateRuntimeData)} fill='white' />
182
- <ZoomableGroup zoom={position.zoom} center={position.coordinates} onMoveEnd={handleMoveEnd} maxZoom={4} projection={projection} width={880} height={500}>
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 zoom={1} center={position.coordinates} onMoveEnd={handleMoveEnd} maxZoom={0} projection={projection} width={880} height={500}>
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' || (state.general.type === 'world-geocode' && hasZoom) || (state.general.type === 'bubble' && hasZoom)) && (
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}
@@ -126,5 +126,6 @@ export default {
126
126
  }
127
127
  ]
128
128
  },
129
- filterBehavior: 'Filter Change'
129
+ filterBehavior: 'Filter Change',
130
+ filterIntro: ''
130
131
  }
@@ -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
+ }
@@ -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 ? `<strong>${stateName}</strong><br/>` : `<strong>Location: ${stateName}</strong><br/>`
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) return `<li class="tooltip-body">${tooltipValue}</li>`
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) && undefined !== row) {
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)