@cdc/map 4.25.7 → 4.25.10

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 (99) hide show
  1. package/.claude/settings.local.json +30 -0
  2. package/CLAUDE.local.md +0 -0
  3. package/dist/cdcmap.js +54785 -53159
  4. package/examples/private/c.json +290 -0
  5. package/examples/private/canvas-city-hover.json +787 -0
  6. package/examples/private/d.json +345 -0
  7. package/examples/private/filter-map.json +909 -0
  8. package/examples/private/g.json +1 -0
  9. package/examples/private/h.json +105911 -0
  10. package/examples/private/measles-data.json +378 -0
  11. package/examples/private/measles.json +211 -0
  12. package/examples/private/north-dakota.json +1132 -0
  13. package/examples/private/rsv-data.json +532 -0
  14. package/examples/private/state-with-pattern.json +883 -0
  15. package/examples/private/test.json +222 -640
  16. package/index.html +1 -1
  17. package/package.json +26 -5
  18. package/src/CdcMap.tsx +28 -8
  19. package/src/CdcMapComponent.tsx +230 -306
  20. package/src/_stories/CdcMap.Filters.stories.tsx +2 -2
  21. package/src/_stories/CdcMap.Legend.Gradient.stories.tsx +3 -3
  22. package/src/_stories/CdcMap.Legend.stories.tsx +7 -4
  23. package/src/_stories/CdcMap.Patterns.stories.tsx +2 -2
  24. package/src/_stories/CdcMap.Table.stories.tsx +2 -2
  25. package/src/_stories/CdcMap.stories.tsx +18 -11
  26. package/src/_stories/GoogleMap.stories.tsx +2 -2
  27. package/src/_stories/UsaMap.NoData.stories.tsx +2 -2
  28. package/src/_stories/_mock/equal-number.json +1109 -0
  29. package/src/_stories/_mock/multi-state.json +21389 -0
  30. package/src/_stories/_mock/us-bubble-cities.json +306 -0
  31. package/src/components/BubbleList.tsx +16 -12
  32. package/src/components/CityList.tsx +88 -110
  33. package/src/components/DataTable.tsx +44 -12
  34. package/src/components/EditorPanel/components/EditorPanel.tsx +201 -203
  35. package/src/components/EditorPanel/components/HexShapeSettings.tsx +3 -2
  36. package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +7 -5
  37. package/src/components/Geo.tsx +2 -0
  38. package/src/components/Legend/components/Legend.tsx +117 -93
  39. package/src/components/Legend/components/LegendGroup/Legend.Group.tsx +10 -7
  40. package/src/components/MapContainer.tsx +52 -0
  41. package/src/components/MapControls.tsx +44 -0
  42. package/src/components/Modal.tsx +2 -8
  43. package/src/components/NavigationMenu.tsx +13 -1
  44. package/src/components/UsaMap/components/SingleState/SingleState.CountyOutput.tsx +24 -7
  45. package/src/components/UsaMap/components/SingleState/SingleState.StateOutput.tsx +21 -15
  46. package/src/components/UsaMap/components/TerritoriesSection.tsx +2 -2
  47. package/src/components/UsaMap/components/UsaMap.County.tsx +112 -33
  48. package/src/components/UsaMap/components/UsaMap.Region.tsx +23 -5
  49. package/src/components/UsaMap/components/UsaMap.SingleState.tsx +38 -26
  50. package/src/components/UsaMap/components/UsaMap.State.tsx +28 -10
  51. package/src/components/UsaMap/helpers/map.ts +16 -8
  52. package/src/components/WorldMap/WorldMap.tsx +116 -11
  53. package/src/components/ZoomControls.tsx +6 -9
  54. package/src/context/LegendMemoContext.tsx +30 -0
  55. package/src/context.ts +1 -39
  56. package/src/data/initial-state.js +143 -128
  57. package/src/data/supported-geos.js +202 -4
  58. package/src/helpers/addUIDs.ts +8 -8
  59. package/src/helpers/applyColorToLegend.ts +122 -45
  60. package/src/helpers/applyLegendToRow.ts +15 -13
  61. package/src/helpers/componentHelpers.ts +8 -0
  62. package/src/helpers/constants.ts +12 -0
  63. package/src/helpers/dataTableHelpers.ts +6 -0
  64. package/src/helpers/displayGeoName.ts +12 -7
  65. package/src/helpers/formatLegendLocation.ts +1 -3
  66. package/src/helpers/generateRuntimeLegend.ts +192 -340
  67. package/src/helpers/generateRuntimeLegendHash.ts +4 -2
  68. package/src/helpers/getColumnNames.ts +1 -1
  69. package/src/helpers/getPatternForRow.ts +36 -0
  70. package/src/helpers/getStatesPicked.ts +14 -0
  71. package/src/helpers/handleMapAriaLabels.ts +2 -2
  72. package/src/helpers/index.ts +11 -3
  73. package/src/helpers/isLegendItemDisabled.ts +16 -0
  74. package/src/helpers/mapObserverHelpers.ts +40 -0
  75. package/src/helpers/resetLegendToggles.ts +3 -2
  76. package/src/helpers/toggleLegendActive.ts +6 -11
  77. package/src/helpers/urlDataHelpers.ts +70 -0
  78. package/src/hooks/useGeoClickHandler.ts +35 -1
  79. package/src/hooks/useLegendMemo.ts +17 -0
  80. package/src/hooks/useMapLayers.tsx +5 -4
  81. package/src/hooks/useStateZoom.tsx +137 -88
  82. package/src/hooks/useTooltip.ts +1 -2
  83. package/src/index.jsx +6 -3
  84. package/src/scss/main.scss +23 -12
  85. package/src/store/map.actions.ts +2 -2
  86. package/src/store/map.reducer.ts +21 -10
  87. package/src/test/CdcMap.test.jsx +11 -0
  88. package/src/types/MapConfig.ts +25 -17
  89. package/src/types/MapContext.ts +2 -8
  90. package/src/types/runtimeLegend.ts +12 -10
  91. package/vite.config.js +2 -7
  92. package/vitest.config.ts +16 -0
  93. package/src/_stories/_mock/floating-point.json +0 -427
  94. package/src/coreStyles_map.scss +0 -3
  95. package/src/helpers/colorDistributions.ts +0 -12
  96. package/src/helpers/generateColorsArray.ts +0 -14
  97. package/src/helpers/getStatePicked.ts +0 -8
  98. package/src/helpers/tests/generateColorsArray.test.ts +0 -18
  99. package/src/helpers/tests/generateRuntimeLegendHash.test.ts +0 -11
@@ -1,74 +1,151 @@
1
- import colorPalettes from '@cdc/core/data/colorPalettes'
1
+ import { mapColorPalettes as colorPalettes } from '@cdc/core/data/colorPalettes'
2
2
  import chroma from 'chroma-js'
3
- import { type MapConfig } from '@cdc/map/src/types/MapConfig'
4
- import { colorDistributions } from './colorDistributions'
3
+ import { type MapConfig } from '../types/MapConfig'
4
+ import { mapV1ColorDistribution } from '@cdc/core/helpers/palettes/colorDistributions'
5
+ import { getColorPaletteVersion } from '@cdc/core/helpers/getColorPaletteVersion'
6
+
7
+ // Palette name migrations from v1 to v2
8
+ const mapPaletteNameMigrations = {
9
+ yelloworangered: 'sequential_yellow_orange_red',
10
+ yelloworangebrown: 'sequential_yellow_orange_brown',
11
+ pinkpurple: 'sequential_pink_purple',
12
+ pinkpurplereverse: 'sequential_pink_purplereverse',
13
+ bluegreen: 'sequential_blue_green',
14
+ bluegreenreverse: 'sequential_blue_greenreverse',
15
+ orangered: 'sequential_orange_red',
16
+ orangeredreverse: 'sequential_orange_redreverse',
17
+ red: 'sequential_red',
18
+ redreverse: 'sequential_redreverse',
19
+ greenblue: 'sequential_green_blue',
20
+ greenbluereverse: 'sequential_green_bluereverse',
21
+ yelloworangeredreverse: 'sequential_yellow_orange_redreverse',
22
+ yelloworangebrownreverse: 'sequential_yellow_orange_brownreverse',
23
+ yellowpurple: 'divergent_yellow_purple',
24
+ yellowpurplereverse: 'divergent_yellow_purplereverse',
25
+ qualitative1: 'qualitative1',
26
+ qualitative2: 'qualitative2',
27
+ qualitative3: 'qualitative3',
28
+ qualitative4: 'qualitative4',
29
+ qualitative9: 'qualitative9',
30
+ 'sequential-blue-2(MPX)': 'sequential_blue_extended',
31
+ 'sequential-blue-2(MPX)reverse': 'sequential_blue_extendedreverse',
32
+ 'sequential-orange(MPX)': 'sequential_orange_extended',
33
+ 'sequential-orange(MPX)reverse': 'sequential_orange_extendedreverse',
34
+ colorblindsafe: 'colorblindsafe',
35
+ qualitative1reverse: 'qualitative1reverse',
36
+ qualitative2reverse: 'qualitative2reverse',
37
+ qualitative3reverse: 'qualitative3reverse',
38
+ qualitative4reverse: 'qualitative4reverse',
39
+ qualitative9reverse: 'qualitative9reverse',
40
+ colorblindsafereverse: 'colorblindsafereverse'
41
+ }
5
42
 
6
43
  type LegendItem = {
7
44
  special: boolean
8
45
  }
9
46
 
10
- // Applies color to a legend item based on its index and special classes.
11
- export const applyColorToLegend = (legendItemIndex: number, config: MapConfig, result: LegendItem[] = []): string => {
47
+ /**
48
+ * applyColorToLegend
49
+ * @param legendIdx legend item index
50
+ * @param config chart config
51
+ * @param result hash of legend items
52
+ * @returns string - the corresponding color for the legend item
53
+ */
54
+ export const applyColorToLegend = (legendIdx: number, config: MapConfig, result: LegendItem[] = []): string => {
12
55
  if (!config) throw new Error('Config is required')
13
56
 
14
- const { legend, customColors, general, color } = config
15
- const { geoType, palette } = general
16
- const specialClasses = legend?.specialClasses ?? []
17
- const colorPalette = customColors ?? colorPalettes[color] ?? colorPalettes['bluegreen']
57
+ const { legend, general } = config
58
+ const { geoType, palette = { name: 'bluegreen', isReversed: false } } = general
59
+ // Support both migrated (general.palette.name) and legacy (config.color) palette locations
60
+ const version = getColorPaletteVersion(config)
61
+ let color = general?.palette?.name || config.color || palette.name || 'bluegreen'
18
62
 
19
- // Handle Region Maps need for a 10th color
20
- if (geoType === 'us-region' && colorPalette.length < 10 && colorPalette.length > 8) {
21
- const darkenedColor = chroma(colorPalette[palette.isReversed ? 0 : 8])
22
- .darken(0.75)
23
- .hex()
24
- palette.isReversed ? colorPalette.unshift(darkenedColor) : colorPalette.push(darkenedColor)
63
+ // Apply palette migration if needed (v1 name -> v2 name)
64
+ if (mapPaletteNameMigrations[color]) {
65
+ color = mapPaletteNameMigrations[color]
25
66
  }
26
67
 
27
- // Check if there are actually any special classes in the current result
28
- const actualSpecialClassesCount = result.filter(item => item.special).length
68
+ // Try multiple approaches to find the palette
69
+ let mapColorPalette = general?.palette?.customColors
29
70
 
30
- const regularItemColorIndex = legendItemIndex - actualSpecialClassesCount
71
+ if (!mapColorPalette) {
72
+ // Try the detected version first
73
+ mapColorPalette = colorPalettes?.[`v${version}`]?.[color]
74
+ }
31
75
 
32
- // Handle special classes coloring
33
- if (result[legendItemIndex]?.special) {
34
- const specialClassColors = chroma.scale(['#D4D4D4', '#939393']).colors(specialClasses.length)
35
- return specialClassColors[legendItemIndex]
76
+ if (!mapColorPalette) {
77
+ // Try v2 explicitly
78
+ mapColorPalette = colorPalettes?.v2?.[color]
79
+ }
80
+
81
+ if (!mapColorPalette) {
82
+ // Try v1 with original color name
83
+ const originalColor = general?.palette?.name || config.color || palette.name || 'bluegreen'
84
+ mapColorPalette = colorPalettes?.v1?.[originalColor]
85
+ }
86
+
87
+ if (!mapColorPalette) {
88
+ // Final fallback
89
+ mapColorPalette = colorPalettes.v1['bluegreen']
36
90
  }
37
91
 
38
- // For categorical maps with custom colors, use color distribution logic
39
- if (config.legend?.type === 'category' && customColors) {
40
- const amt = config.legend.additionalCategories?.length ?? 10
41
- const distributionArray = colorDistributions[amt] ?? []
92
+ // Handle Region Maps need for a 10th color
93
+ if (geoType === 'us-region' && mapColorPalette.length < 10 && mapColorPalette.length > 8) {
94
+ const newColor = chroma(mapColorPalette[config.general.palette.isReversed ? 0 : 8])
95
+ .darken(0.75)
96
+ .hex()
97
+ config.general.palette.isReversed ? mapColorPalette.unshift(newColor) : mapColorPalette.push(newColor)
98
+ }
42
99
 
43
- const specificColor = distributionArray[legendItemIndex - specialClasses.length] ?? colorPalette.at(-1)
100
+ // Count actual special classes in the result array
101
+ const actualSpecialClassCount = result.filter(item => item.special).length
102
+ const colorIdx = legendIdx - actualSpecialClassCount
44
103
 
45
- // If specificColor is a number, use it as an index; otherwise return the color directly
46
- return colorPalette[specificColor] ?? '#fff'
104
+ // Handle special classes coloring
105
+ if (result[legendIdx]?.special) {
106
+ const specialClassColors = chroma.scale(['#D4D4D4', '#939393']).colors(actualSpecialClassCount)
107
+ const specialClassIdx = result.slice(0, legendIdx + 1).filter(item => item.special).length - 1
108
+ return specialClassColors[specialClassIdx]
47
109
  }
48
110
 
49
111
  // Use qualitative color palettes directly
50
112
  if (color.includes('qualitative')) {
51
- const safeIndex = Math.max(0, Math.min(regularItemColorIndex, colorPalette.length - 1))
52
- return colorPalette[safeIndex]
113
+ return mapColorPalette[colorIdx]
53
114
  }
54
115
 
55
- // Determine color distribution - use actual special classes count for consistent coloring
56
- const legendItemCount =
57
- Math.max(result.length - actualSpecialClassesCount, 1) < 10
58
- ? Math.max(result.length - actualSpecialClassesCount, 1)
59
- : Object.keys(colorDistributions).length
60
-
61
- const colorDistributionArray = colorDistributions[legendItemCount] ?? []
116
+ // Determine color distribution based on non-special items
117
+ // For numeric legends, use the configured numberOfItems for consistent color distribution
118
+ // For category legends, use the actual result length
119
+ const isNumericLegend = legend && ['equalnumber', 'equalinterval'].includes(legend.type)
120
+ const nonSpecialItemCount = isNumericLegend
121
+ ? (legend.numberOfItems || result.length)
122
+ : result.length - actualSpecialClassCount
123
+
124
+ const amt =
125
+ Math.max(nonSpecialItemCount, 1) < 10
126
+ ? Math.max(nonSpecialItemCount, 1)
127
+ : Object.keys(mapV1ColorDistribution).length
128
+ const distributionArray = mapV1ColorDistribution[amt] ?? []
129
+
130
+ // Safety check to ensure mapColorPalette exists and is an array
131
+ if (!mapColorPalette || !Array.isArray(mapColorPalette) || mapColorPalette.length === 0) {
132
+ console.warn('No valid color palette found, returning gray fallback')
133
+ return '#d3d3d3'
134
+ }
62
135
 
63
- const rowDistributionIndex = colorDistributionArray[legendItemIndex - actualSpecialClassesCount]
136
+ const specificColor =
137
+ distributionArray[colorIdx] ?? mapColorPalette[colorIdx] ?? mapColorPalette[mapColorPalette.length - 1]
64
138
 
65
- const colorValue = rowDistributionIndex ?? colorPalette[regularItemColorIndex] ?? colorPalette.at(-1)
139
+ if (typeof specificColor === 'number') {
140
+ return specificColor < mapColorPalette.length
141
+ ? mapColorPalette[specificColor]
142
+ : mapColorPalette[mapColorPalette.length - 1]
143
+ }
66
144
 
67
- // Check if specificColor is a string(e.g., a valid color code)
68
- if (typeof colorValue === 'string' && config.legend?.type === 'category' && customColors) {
69
- return colorValue
145
+ if (typeof specificColor === 'string') {
146
+ return specificColor
70
147
  }
71
148
 
72
- // Otherwise, use specificColor as an index for mapColorPalette
73
- return colorPalette[colorValue]
149
+ // Final fallback
150
+ return mapColorPalette[0] || '#d3d3d3'
74
151
  }
@@ -1,7 +1,9 @@
1
- import { generateColorsArray, hashObj } from '../helpers'
2
- import colorPalettes from '@cdc/core/data/colorPalettes'
1
+ import { generateColorsArray } from '@cdc/core/helpers/generateColorsArray'
2
+ import { hashObj, DEFAULT_MAP_BACKGROUND } from '../helpers'
3
+ import { mapColorPalettes as colorPalettes } from '@cdc/core/data/colorPalettes'
3
4
  import { MapConfig } from '../types/MapConfig'
4
5
  import { type RuntimeLegend } from '../types/runtimeLegend'
6
+ import { getColorPaletteVersion } from '@cdc/core/helpers/getColorPaletteVersion'
5
7
 
6
8
  type Memo<T> = { current: Map<string, T> }
7
9
 
@@ -14,8 +16,9 @@ export const applyLegendToRow = (
14
16
  ): string[] => {
15
17
  if (!config) return null
16
18
 
17
- const { general, color, legend } = config
19
+ const { general, legend } = config
18
20
  const { type } = general
21
+ const color = general.palette?.name ?? 'bluegreenreverse'
19
22
  const { showSpecialClassesLast } = legend
20
23
 
21
24
  try {
@@ -25,27 +28,26 @@ export const applyLegendToRow = (
25
28
  }
26
29
 
27
30
  if (type === 'navigation') {
28
- const mapColorPalette = colorPalettes[color] ?? colorPalettes['bluegreenreverse']
31
+ const mapColorPalette =
32
+ colorPalettes[`v${getColorPaletteVersion(config)}`]?.[color] ?? colorPalettes.v1['bluegreenreverse']
29
33
  return generateColorsArray(mapColorPalette[3])
30
34
  }
31
35
 
32
- const hash = String(hashObj(rowObj))
36
+ const hash = hashObj(rowObj)
33
37
 
34
38
  if (!legendMemo.current.has(hash)) {
35
- return generateColorsArray()
39
+ return generateColorsArray(DEFAULT_MAP_BACKGROUND)
36
40
  }
37
41
 
38
42
  const idx = legendMemo.current.get(hash)!
43
+ const disabledIdx = showSpecialClassesLast ? legendSpecialClassLastMemo.current.get(hash) ?? idx : idx
39
44
 
40
- if (runtimeLegend.items?.[idx]?.disabled) {
41
- return generateColorsArray()
45
+ if (runtimeLegend.items?.[disabledIdx]?.disabled) {
46
+ return generateColorsArray(DEFAULT_MAP_BACKGROUND)
42
47
  }
43
48
 
44
- // Use the index from legendMemo which should already be updated for reordered items
45
- const legendItem = runtimeLegend.items?.[idx]
46
- const legendBinColor = legendItem?.color
47
-
48
- return generateColorsArray(legendBinColor, legendItem?.special)
49
+ const legendBinColor = runtimeLegend.items.find(o => o.bin === idx)?.color
50
+ return generateColorsArray(legendBinColor, runtimeLegend.items[idx]?.special)
49
51
  } catch (e) {
50
52
  console.error('COVE: ', e)
51
53
  return null
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Builds CSS class names for the main map section container
3
+ */
4
+ export const buildSectionClassNames = (viewport: string, headerColor: string, hasError: boolean): string => {
5
+ const classes = ['cove-component__content', 'cdc-map-inner-container', viewport, headerColor]
6
+ if (hasError) classes.push('type-map--has-error')
7
+ return classes.join(' ')
8
+ }
@@ -42,3 +42,15 @@ export const GEOCODE_TYPES = {
42
42
  } as const
43
43
 
44
44
  export const DEFAULT_MAP_BACKGROUND = '#DFE1E2'
45
+
46
+ // Component constants
47
+ export const LOGO_MAX_WIDTH = '50px'
48
+ export const STORYBOOK_PORT = 6006
49
+
50
+ // CSV Parsing Configuration
51
+ export const CSV_PARSE_CONFIG = {
52
+ header: true,
53
+ dynamicTyping: true,
54
+ skipEmptyLines: true,
55
+ encoding: 'utf-8'
56
+ } as const
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Determines if the data table should be shown based on current state
3
+ */
4
+ export const shouldShowDataTable = (config: any, table: any, general: any, loading: boolean): boolean => {
5
+ return !config?.runtime?.editorErrorMessage.length && table.forceDisplay && general.type !== 'navigation' && !loading
6
+ }
@@ -1,5 +1,14 @@
1
1
  import { titleCase } from './titleCase'
2
- import { supportedStates, supportedTerritories, supportedCountries, supportedCounties } from '../data/supported-geos'
2
+ import {
3
+ supportedStates,
4
+ supportedTerritories,
5
+ supportedCountries,
6
+ supportedCounties,
7
+ stateKeys,
8
+ territoryKeys,
9
+ countryKeys,
10
+ countyKeys
11
+ } from '../data/supported-geos'
3
12
 
4
13
  /**
5
14
  * Converts a geographic key to its display name.
@@ -10,10 +19,6 @@ import { supportedStates, supportedTerritories, supportedCountries, supportedCou
10
19
  */
11
20
  export const displayGeoName = (key: string, convertFipsCodes = true): string => {
12
21
  if (!convertFipsCodes) return key
13
- const stateKeys = Object.keys(supportedStates)
14
- const territoryKeys = Object.keys(supportedTerritories)
15
- const countryKeys = Object.keys(supportedCountries)
16
- const countyKeys = Object.keys(supportedCounties)
17
22
  let value = key
18
23
 
19
24
  // Map to first item in values array which is the preferred label
@@ -44,13 +49,13 @@ export const displayGeoName = (key: string, convertFipsCodes = true): string =>
44
49
  Congo: 'Republic of the Congo'
45
50
  }
46
51
 
47
- if (true === Object.keys(dict).includes(value)) {
52
+ if (Object.keys(dict).includes(value)) {
48
53
  value = dict[value]
49
54
  }
50
55
  // if you get here and it's 2 letters then DONT titleCase state abbreviations like "AL"
51
56
  if (value?.length === 2 || value === 'U.S. Virgin Islands') {
52
57
  return value
53
58
  } else {
54
- return titleCase(value)
59
+ return value
55
60
  }
56
61
  }
@@ -1,8 +1,6 @@
1
- import { stateFipsToTwoDigit, supportedCounties } from '../data/supported-geos'
1
+ import { stateFipsToTwoDigit, supportedCounties, countyKeySet } from '../data/supported-geos'
2
2
  import { titleCase } from './titleCase'
3
3
 
4
- const countyKeySet = new Set(Object.keys(supportedCounties))
5
-
6
4
  export const formatLegendLocation = (key, runtimeLookup) => {
7
5
  let formattedName = ''
8
6