@cdc/map 4.25.8 → 4.25.11

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 (137) hide show
  1. package/.claude/agents/typescript-organizer.md +118 -0
  2. package/.claude/settings.local.json +30 -0
  3. package/dist/{cdcmap-fce76882.es.js → cdcmap-BnB1QM5d.es.js} +6 -13
  4. package/dist/{cdcmap-c55ac1ea.es.js → cdcmap-D6CG2-Hb.es.js} +5 -12
  5. package/dist/{cdcmap-31a33da1.es.js → cdcmap-MXgURbdZ.es.js} +6 -13
  6. package/dist/{cdcmap-1a1724a1.es.js → cdcmap-dgT_1dIT.es.js} +136 -151
  7. package/dist/cdcmap.js +56991 -53706
  8. package/examples/example-city-state.json +9 -1
  9. package/examples/multi-country-centering.json +45 -0
  10. package/examples/private/c.json +290 -0
  11. package/examples/private/canvas-city-hover.json +787 -0
  12. package/examples/private/colors-2.json +221 -0
  13. package/examples/private/colors.json +221 -0
  14. package/examples/private/d.json +345 -0
  15. package/examples/private/g.json +1 -0
  16. package/examples/private/h.json +105911 -0
  17. package/examples/private/measles-data.json +378 -0
  18. package/examples/private/measles.json +211 -0
  19. package/examples/private/north-dakota.json +1132 -0
  20. package/examples/private/state-with-pattern.json +883 -0
  21. package/index.html +36 -34
  22. package/package.json +26 -5
  23. package/src/CdcMap.tsx +23 -8
  24. package/src/CdcMapComponent.tsx +238 -308
  25. package/src/_stories/CdcMap.ColumnWrap.stories.tsx +31 -0
  26. package/src/_stories/CdcMap.DistrictOfColumbia.stories.tsx +320 -0
  27. package/src/_stories/CdcMap.Editor.stories.tsx +3371 -0
  28. package/src/_stories/CdcMap.Filters.stories.tsx +2 -2
  29. package/src/_stories/CdcMap.Legend.Gradient.stories.tsx +3 -3
  30. package/src/_stories/CdcMap.Legend.stories.tsx +7 -4
  31. package/src/_stories/CdcMap.Patterns.stories.tsx +2 -2
  32. package/src/_stories/CdcMap.SmallMultiples.stories.tsx +35 -0
  33. package/src/_stories/CdcMap.Table.stories.tsx +2 -2
  34. package/src/_stories/CdcMap.stories.tsx +37 -9
  35. package/src/_stories/GoogleMap.stories.tsx +2 -2
  36. package/src/_stories/UsaMap.NoData.stories.tsx +2 -2
  37. package/src/_stories/_mock/column-wrap-test.json +265 -0
  38. package/src/_stories/_mock/equal-number.json +1109 -0
  39. package/src/_stories/_mock/multi-country-hide.json +78 -0
  40. package/src/_stories/_mock/multi-country.json +95 -0
  41. package/src/_stories/_mock/multi-state.json +887 -20403
  42. package/src/_stories/_mock/small_multiples/multi-state-small-multiples.json +8399 -0
  43. package/src/_stories/_mock/small_multiples/region-small-multiples.json +657 -0
  44. package/src/_stories/_mock/small_multiples/wastewater-map-small-multiples.json +221 -0
  45. package/src/_stories/_mock/us-bubble-cities.json +306 -0
  46. package/src/_stories/_mock/usa-state-gradient.json +2 -4
  47. package/src/components/BubbleList.tsx +17 -13
  48. package/src/components/CityList.tsx +85 -107
  49. package/src/components/EditorPanel/components/EditorPanel.tsx +787 -709
  50. package/src/components/EditorPanel/components/HexShapeSettings.tsx +58 -95
  51. package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +34 -42
  52. package/src/components/EditorPanel/components/Panels/Panel.SmallMultiples.tsx +354 -0
  53. package/src/components/EditorPanel/components/Panels/index.tsx +3 -1
  54. package/src/components/Geo.tsx +22 -3
  55. package/src/components/Legend/components/Legend.tsx +76 -40
  56. package/src/components/Legend/components/LegendGroup/Legend.Group.tsx +10 -7
  57. package/src/components/Legend/components/index.scss +1 -1
  58. package/src/components/MapContainer.tsx +52 -0
  59. package/src/components/MapControls.tsx +44 -0
  60. package/src/components/NavigationMenu.tsx +27 -15
  61. package/src/components/SmallMultiples/SmallMultipleTile.tsx +163 -0
  62. package/src/components/SmallMultiples/SmallMultiples.css +32 -0
  63. package/src/components/SmallMultiples/SmallMultiples.tsx +150 -0
  64. package/src/components/SmallMultiples/SynchronizedTooltip.tsx +105 -0
  65. package/src/components/SmallMultiples/index.tsx +3 -0
  66. package/src/components/UsaMap/components/SingleState/SingleState.CountyOutput.tsx +36 -4
  67. package/src/components/UsaMap/components/TerritoriesSection.tsx +26 -12
  68. package/src/components/UsaMap/components/Territory/Territory.Hexagon.tsx +30 -4
  69. package/src/components/UsaMap/components/Territory/Territory.Rectangle.tsx +23 -4
  70. package/src/components/UsaMap/components/Territory/TerritoryShape.ts +6 -0
  71. package/src/components/UsaMap/components/UsaMap.County.tsx +123 -37
  72. package/src/components/UsaMap/components/UsaMap.Region.tsx +36 -5
  73. package/src/components/UsaMap/components/UsaMap.SingleState.tsx +30 -10
  74. package/src/components/UsaMap/components/UsaMap.State.tsx +53 -12
  75. package/src/components/UsaMap/helpers/map.ts +4 -4
  76. package/src/components/UsaMap/helpers/shapes.ts +9 -6
  77. package/src/components/WorldMap/WorldMap.tsx +193 -35
  78. package/src/components/ZoomControls.tsx +6 -9
  79. package/src/context/LegendMemoContext.tsx +30 -0
  80. package/src/context.ts +1 -40
  81. package/src/data/initial-state.js +153 -130
  82. package/src/data/supported-geos.js +25 -78
  83. package/src/helpers/addUIDs.ts +13 -2
  84. package/src/helpers/applyColorToLegend.ts +140 -20
  85. package/src/helpers/applyLegendToRow.ts +10 -6
  86. package/src/helpers/componentHelpers.ts +8 -0
  87. package/src/helpers/constants.ts +12 -14
  88. package/src/helpers/dataTableHelpers.ts +6 -0
  89. package/src/helpers/displayGeoName.ts +18 -3
  90. package/src/helpers/generateRuntimeLegend.ts +44 -10
  91. package/src/helpers/generateRuntimeLegendHash.ts +4 -2
  92. package/src/helpers/getColumnNames.ts +1 -1
  93. package/src/helpers/getCountriesPicked.ts +103 -0
  94. package/src/helpers/getMapContainerClasses.ts +7 -0
  95. package/src/helpers/getPatternForRow.ts +33 -0
  96. package/src/helpers/getStatesPicked.ts +8 -5
  97. package/src/helpers/index.ts +3 -3
  98. package/src/helpers/isLegendItemDisabled.ts +16 -0
  99. package/src/helpers/mapObserverHelpers.ts +40 -0
  100. package/src/helpers/resetLegendToggles.ts +3 -2
  101. package/src/helpers/smallMultiplesHelpers.ts +359 -0
  102. package/src/helpers/tests/titleCase.test.ts +76 -0
  103. package/src/helpers/titleCase.ts +13 -13
  104. package/src/helpers/toggleLegendActive.ts +6 -11
  105. package/src/helpers/urlDataHelpers.ts +70 -0
  106. package/src/hooks/useCountryZoom.tsx +241 -0
  107. package/src/hooks/useGeoClickHandler.ts +36 -2
  108. package/src/hooks/useLegendMemo.ts +17 -0
  109. package/src/hooks/useMapLayers.tsx +5 -4
  110. package/src/hooks/useProgrammaticMapTooltip.ts +110 -0
  111. package/src/hooks/useResizeObserver.ts +5 -2
  112. package/src/hooks/useStateZoom.tsx +30 -8
  113. package/src/hooks/useSynchronizedGeographies.ts +56 -0
  114. package/src/hooks/useTooltip.ts +1 -2
  115. package/src/index.jsx +1 -2
  116. package/src/scss/editor-panel.scss +4 -440
  117. package/src/scss/main.scss +1 -1
  118. package/src/scss/map.scss +12 -15
  119. package/src/store/map.actions.ts +7 -7
  120. package/src/store/map.reducer.ts +17 -6
  121. package/src/test/CdcMap.test.jsx +11 -0
  122. package/src/types/MapConfig.ts +46 -18
  123. package/src/types/MapContext.ts +6 -7
  124. package/src/types/runtimeLegend.ts +17 -1
  125. package/vite.config.js +2 -7
  126. package/vitest.config.ts +16 -0
  127. package/src/components/DataTable.tsx +0 -385
  128. package/src/components/EditorPanel/components/Inputs.tsx +0 -59
  129. package/src/coreStyles_map.scss +0 -3
  130. package/src/helpers/colorDistributions.ts +0 -12
  131. package/src/helpers/generateColorsArray.ts +0 -14
  132. package/src/helpers/tests/generateColorsArray.test.ts +0 -18
  133. package/src/helpers/tests/generateRuntimeLegendHash.test.ts +0 -11
  134. package/src/hooks/useActiveElement.ts +0 -19
  135. package/src/scss/mixins.scss +0 -47
  136. package/src/types/Annotations.ts +0 -24
  137. /package/dist/{cdcmap-548642e6.es.js → cdcmap-Ct2SB0vL.es.js} +0 -0
@@ -1,135 +1,158 @@
1
- export default {
2
- annotations: [],
3
- general: {
4
- navigationTarget: '_self',
5
- noStateFoundMessage: 'Map Unavailable',
6
- annotationDropdownText: 'Annotations',
7
- geoBorderColor: 'darkGray',
8
- headerColor: 'theme-blue',
9
- title: '',
10
- showTitle: true,
11
- showSidebar: true,
12
- showDownloadMediaButton: false,
13
- displayAsHex: false,
14
- displayStateLabels: true,
15
- territoriesAlwaysShow: false,
16
- language: 'en',
17
- geoType: 'single-state',
18
- geoLabelOverride: '',
19
- hasRegions: false,
20
- fullBorder: false,
21
- type: 'data',
22
- convertFipsCodes: true,
23
- palette: {
24
- isReversed: false
25
- },
26
- allowMapZoom: true,
27
- hideGeoColumnInTooltip: false,
28
- hidePrimaryColumnInTooltip: false,
29
- statesPicked: [
30
- {
31
- fipsCode: '01',
32
- stateName: 'Alabama'
1
+ import { USE_V2_MIGRATION } from '@cdc/core/helpers/constants'
2
+
3
+ // Dynamic initial state based on migration flag
4
+ const createInitialState = () => {
5
+ const paletteDefaults = USE_V2_MIGRATION
6
+ ? {
7
+ isReversed: true,
8
+ name: 'sequential_bluereverse',
9
+ version: '2.0'
10
+ }
11
+ : {
12
+ isReversed: true,
13
+ name: 'bluegreen',
14
+ version: '1.0'
33
15
  }
34
- ]
35
- },
36
- type: 'map',
37
- color: 'pinkpurple',
38
- columns: {
39
- geo: {
40
- name: 'FIPS Codes',
41
- label: 'Location',
42
- tooltip: false,
43
- dataTable: true
16
+
17
+ return {
18
+ annotations: [],
19
+ general: {
20
+ navigationTarget: '_self',
21
+ noDataMessage: 'No State Selected',
22
+ annotationDropdownText: 'Annotations',
23
+ geoBorderColor: 'darkGray',
24
+ headerColor: 'theme-blue',
25
+ title: '',
26
+ showTitle: true,
27
+ showSidebar: true,
28
+ showDownloadMediaButton: false,
29
+ displayAsHex: false,
30
+ displayStateLabels: true,
31
+ territoriesAlwaysShow: false,
32
+ language: 'en',
33
+ geoType: 'single-state',
34
+ geoLabelOverride: '',
35
+ hasRegions: false,
36
+ fullBorder: false,
37
+ type: 'data',
38
+ convertFipsCodes: true,
39
+ palette: paletteDefaults,
40
+ allowMapZoom: true,
41
+ hideGeoColumnInTooltip: false,
42
+ hidePrimaryColumnInTooltip: false,
43
+ statesPicked: []
44
44
  },
45
- primary: {
46
- dataTable: true,
47
- tooltip: true,
48
- prefix: '',
49
- suffix: '',
50
- name: '',
51
- label: '',
52
- roundToPlace: 0
45
+ type: 'map',
46
+ columns: {
47
+ geo: {
48
+ name: 'FIPS Codes',
49
+ label: 'Location',
50
+ tooltip: false,
51
+ dataTable: true
52
+ },
53
+ primary: {
54
+ dataTable: true,
55
+ tooltip: true,
56
+ prefix: '',
57
+ suffix: '',
58
+ name: '',
59
+ label: '',
60
+ roundToPlace: 0
61
+ },
62
+ navigate: {
63
+ name: ''
64
+ },
65
+ latitude: { name: '' },
66
+ longitude: { name: '' }
53
67
  },
54
- navigate: {
55
- name: ''
68
+ legend: {
69
+ descriptions: {},
70
+ specialClasses: [],
71
+ unified: false,
72
+ singleColumn: false,
73
+ singleRow: false,
74
+ verticalSorted: false,
75
+ showSpecialClassesLast: false,
76
+ dynamicDescription: false,
77
+ type: 'equalnumber',
78
+ numberOfItems: 3,
79
+ position: 'side',
80
+ title: '',
81
+ style: 'circles',
82
+ subStyle: 'linear blocks',
83
+ tickRotation: '',
84
+ singleColumnLegend: false,
85
+ hideBorder: false,
86
+ groupBy: ''
56
87
  },
57
- latitude: { name: '' },
58
- longitude: { name: '' }
59
- },
60
- legend: {
61
- descriptions: {},
62
- specialClasses: [],
63
- unified: false,
64
- singleColumn: false,
65
- singleRow: false,
66
- verticalSorted: false,
67
- showSpecialClassesLast: false,
68
- dynamicDescription: false,
69
- type: 'equalnumber',
70
- numberOfItems: 3,
71
- position: 'side',
72
- title: '',
73
- style: 'circles',
74
- subStyle: 'linear blocks',
75
- tickRotation: '',
76
- singleColumnLegend: false,
77
- hideBorder: false,
78
- groupBy: ''
79
- },
80
- filters: [],
81
- data: [],
82
- table: {
83
- wrapColumns: false,
84
- label: 'Data Table',
85
- expanded: false,
86
- limitHeight: false,
87
- height: '',
88
- caption: '',
89
- showDownloadUrl: false,
90
- showDataTableLink: true,
91
- showDownloadLinkBelow: true,
92
- showFullGeoNameInCSV: false,
93
- forceDisplay: true,
94
- download: false,
95
- indexLabel: '',
96
- cellMinWidth: '0',
97
- collapsible: true
98
- },
99
- tooltips: {
100
- appearanceType: 'hover',
101
- linkLabel: 'Learn More',
102
- opacity: 90
103
- },
104
- runtime: {
105
- editorErrorMessage: []
106
- },
107
- visual: {
108
- minBubbleSize: 1,
109
- maxBubbleSize: 20,
110
- extraBubbleBorder: false,
111
- cityStyle: 'circle',
112
- cityStyleLabel: '',
113
- showBubbleZeros: false,
114
- additionalCityStyles: [],
115
- geoCodeCircleSize: 8,
116
- showBubbleZeros: false
117
- },
118
- mapPosition: { coordinates: [0, 30], zoom: 1 },
119
- map: {
120
- layers: [],
121
- patterns: []
122
- },
123
- hexMap: {
124
- type: '',
125
- shapeGroups: [
126
- {
127
- legendTitle: '',
128
- legendDescription: '',
129
- items: [{ key: '', shape: 'Arrow Up', column: '', operator: '=', value: '' }]
130
- }
131
- ]
132
- },
133
- filterBehavior: 'Filter Change',
134
- filterIntro: ''
88
+ filters: [],
89
+ data: [],
90
+ table: {
91
+ wrapColumns: false,
92
+ label: 'Data Table',
93
+ expanded: false,
94
+ limitHeight: false,
95
+ height: '',
96
+ caption: '',
97
+ showDownloadUrl: false,
98
+ showDataTableLink: true,
99
+ showDownloadLinkBelow: true,
100
+ showFullGeoNameInCSV: false,
101
+ forceDisplay: true,
102
+ download: false,
103
+ indexLabel: '',
104
+ cellMinWidth: '0',
105
+ collapsible: true
106
+ },
107
+ tooltips: {
108
+ appearanceType: 'hover',
109
+ linkLabel: 'Learn More',
110
+ opacity: 90
111
+ },
112
+ runtime: {
113
+ editorErrorMessage: []
114
+ },
115
+ visual: {
116
+ minBubbleSize: 1,
117
+ maxBubbleSize: 20,
118
+ extraBubbleBorder: false,
119
+ cityStyle: 'circle',
120
+ cityStyleLabel: '',
121
+ showBubbleZeros: false,
122
+ additionalCityStyles: [],
123
+ geoCodeCircleSize: 8,
124
+ showBubbleZeros: false
125
+ },
126
+ mapPosition: { coordinates: [0, 30], zoom: 1 },
127
+ map: {
128
+ layers: [],
129
+ patterns: []
130
+ },
131
+ hexMap: {
132
+ type: '',
133
+ shapeGroups: [
134
+ {
135
+ legendTitle: '',
136
+ legendDescription: '',
137
+ items: [{ key: '', shape: 'Arrow Up', column: '', operator: '=', value: '' }]
138
+ }
139
+ ]
140
+ },
141
+ filterBehavior: 'Filter Change',
142
+ filterIntro: '',
143
+ smallMultiples: {
144
+ mode: '',
145
+ tileColumn: '',
146
+ tilesPerRowDesktop: 2,
147
+ tilesPerRowMobile: 1,
148
+ tileOrderType: 'asc',
149
+ tileOrder: [],
150
+ tileTitles: {},
151
+ synchronizedTooltips: true
152
+ },
153
+ markupVariables: [],
154
+ enableMarkupVariables: false
155
+ }
135
156
  }
157
+
158
+ export default createInitialState()
@@ -98,74 +98,6 @@ export const supportedRegions = {
98
98
  'region 10': ['REGION 10', 'R10']
99
99
  }
100
100
 
101
- /**
102
- * State Name to ISO Code Mapping
103
- *
104
- * Maps proper case state names to their corresponding ISO 3166-2 codes.
105
- * Provides reverse lookup capability for the supportedStates table.
106
- *
107
- * Structure: { 'State Name': 'US-XX' }
108
- * - Key: Proper case state name (e.g., 'California')
109
- * - Value: ISO 3166-2 state code (e.g., 'US-CA')
110
- *
111
- * Used in:
112
- * - Data processing when state names need to be converted to ISO codes
113
- * - Validation and normalization of state data
114
- */
115
- export const stateToIso = {
116
- // States
117
- Alabama: 'US-AL',
118
- Alaska: 'US-AK',
119
- Arizona: 'US-AZ',
120
- Arkansas: 'US-AR',
121
- California: 'US-CA',
122
- Colorado: 'US-CO',
123
- Connecticut: 'US-CT',
124
- Delaware: 'US-DE',
125
- Florida: 'US-FL',
126
- Georgia: 'US-GA',
127
- Hawaii: 'US-HI',
128
- Idaho: 'US-ID',
129
- Illinois: 'US-IL',
130
- Indiana: 'US-IN',
131
- Iowa: 'US-IA',
132
- Kansas: 'US-KS',
133
- Kentucky: 'US-KY',
134
- Louisiana: 'US-LA',
135
- Maine: 'US-ME',
136
- Maryland: 'US-MD',
137
- Massachusetts: 'US-MA',
138
- Michigan: 'US-MI',
139
- Minnesota: 'US-MN',
140
- Mississippi: 'US-MS',
141
- Missouri: 'US-MO',
142
- Montana: 'US-MT',
143
- Nebraska: 'US-NE',
144
- Nevada: 'US-NV',
145
- 'New Hampshire': 'US-NH',
146
- 'New Jersey': 'US-NJ',
147
- 'New Mexico': 'US-NM',
148
- 'New York': 'US-NY',
149
- 'North Carolina': 'US-NC',
150
- 'North Dakota': 'US-ND',
151
- Ohio: 'US-OH',
152
- Oklahoma: 'US-OK',
153
- Oregon: 'US-OR',
154
- Pennsylvania: 'US-PA',
155
- 'Rhode Island': 'US-RI',
156
- 'South Carolina': 'US-SC',
157
- 'South Dakota': 'US-SD',
158
- Tennessee: 'US-TN',
159
- Texas: 'US-TX',
160
- Utah: 'US-UT',
161
- Vermont: 'US-VT',
162
- Virginia: 'US-VA',
163
- Washington: 'US-WA',
164
- 'West Virginia': 'US-WV',
165
- Wisconsin: 'US-WI',
166
- Wyoming: 'US-WY'
167
- }
168
-
169
101
  /**
170
102
  * State FIPS Code to Two-Letter Abbreviation Mapping
171
103
  *
@@ -512,7 +444,14 @@ export const supportedCountries = {
512
444
  NIU: ['Niue', 'Niue (New Zealand)'],
513
445
  NFK: ['Norfolk Island', 'Norfolk Island (Australia)'],
514
446
  MKD: ['North Macedonia', 'Republic of North Macedonia', 'Macedonia'],
515
- MNP: ['Northern Mariana Islands', 'N. Mariana Is.', 'Northern Mariana Islands (U.S.)'],
447
+ MNP: [
448
+ 'Northern Mariana Islands',
449
+ 'N. Mariana Is.',
450
+ 'Northern Mariana Islands (U.S.)',
451
+ 'Commonwealth of Northern Mariana Islands',
452
+ 'Commonwealth of the Northern Mariana Islands',
453
+ 'Commonwealth of the Northern Mariana Islands (CNMI)'
454
+ ],
516
455
  NOR: ['Norway'],
517
456
  OMN: ['Oman'],
518
457
  PAK: ['Pakistan'],
@@ -643,7 +582,15 @@ export const supportedTerritories = {
643
582
  'US-GU': ['GUAM', 'GU'],
644
583
  'US-PR': ['PUERTO RICO', 'PR'],
645
584
  'US-VI': ['U.S. VIRGIN ISLANDS', 'VI', 'US VIRGIN ISLANDS', 'VIRGIN ISLANDS'],
646
- 'US-MP': ['NORTHERN MARIANAS', 'MP', 'CNMI'],
585
+ 'US-MP': [
586
+ 'NORTHERN MARIANA ISLANDS',
587
+ 'MP',
588
+ 'CNMI',
589
+ 'NORTHERN MARIANAS',
590
+ 'COMMONWEALTH OF NORTHERN MARIANA ISLANDS',
591
+ 'COMMONWEALTH OF THE NORTHERN MARIANA ISLANDS',
592
+ 'COMMONWEALTH OF THE NORTHERN MARIANA ISLANDS (CNMI)'
593
+ ],
647
594
  'US-FM': ['MICRONESIA', 'FM', 'Federated States of Micronesia'], // Note: Key is not an official ISO code
648
595
  'US-PW': ['PALAU', 'PW'], // Note: Key is not an official ISO code
649
596
  'US-MH': ['MARSHALL ISLANDS', 'MH', 'RMI'] // Note: Key is not an official ISO code
@@ -735,7 +682,7 @@ export const supportedCities = {
735
682
  'GREAT PLAINS TRIBAL LEADERS HEALTH BOARD': [-103.22444, 44.083054],
736
683
  'GREENSBORO': [-79.791977, 36.072636],
737
684
  'HENDERSON': [-114.981720, 36.039524],
738
- 'HERSHEY': [-76.6779444, 40.2849997 ],
685
+ 'HERSHEY': [-76.6779444, 40.2849997],
739
686
  'HIALEAH': [-80.278107, 25.857595],
740
687
  'HONOLULU': [-157.858337, 21.306944],
741
688
  'HOPI TRIBE': [-110.5035, 35.7833],
@@ -768,7 +715,7 @@ export const supportedCities = {
768
715
  'LUBBOCK': [-101.855164, 33.577862],
769
716
  'MADISON': [-89.401230, 43.073051],
770
717
  'MARION COUNTY, INDIANA': [-86.136543, 39.781029],
771
- 'MARION':[-88.9330556,37.7305556],
718
+ 'MARION': [-88.9330556, 37.7305556],
772
719
  'MEMPHIS': [-90.048981, 35.149532],
773
720
  'MESA': [-111.831474, 33.415184],
774
721
  'MIAMI': [-80.191788, 25.761681],
@@ -793,7 +740,7 @@ export const supportedCities = {
793
740
  'OLYMPIA': [-122.9382403, 47.0394791],
794
741
  'OMAHA': [-95.934502, 41.256538],
795
742
  'ORLANDO': [-81.379234, 28.538336],
796
- 'PASADENA':[-95.209099,29.691063],
743
+ 'PASADENA': [-95.209099, 29.691063],
797
744
  'PHILADELPHIA': [-75.165222, 39.952583],
798
745
  'PHOENIX': [-112.074036, 33.448376],
799
746
  'PITTSBURGH': [-79.995888, 40.440624],
@@ -813,9 +760,9 @@ export const supportedCities = {
813
760
  'SACRAMENTO': [-121.494400, 38.581573],
814
761
  'SAINT PAUL': [-93.089958, 44.953705],
815
762
  'SALEM, ALABAMA': [-85.2386, 32.5968],
816
- 'SALEM, CONNECTICUT': [-72.2754,41.4904],
763
+ 'SALEM, CONNECTICUT': [-72.2754, 41.4904],
817
764
  'SALEM, FLORIDA': [-83.4129, 29.8869],
818
- 'SALEM, ILLINOIS':[-88.945618,38.626991],
765
+ 'SALEM, ILLINOIS': [-88.945618, 38.626991],
819
766
  'SALEM, MASSACHUSETTS': [-70.8955, 42.5197],
820
767
  'SALEM, OR': [-123.0351, 44.9429],
821
768
  'SALEM, OREGON': [-123.0351, 44.9429],
@@ -824,19 +771,19 @@ export const supportedCities = {
824
771
  'SALUDA, VIRGINIA': [-76.5950, 37.6064],
825
772
  'SAN ANTONIO': [-98.493629, 29.424122],
826
773
  'SAN BENITO': [-97.6311, 26.1326],
827
- 'SAN BERNARDINO':[-117.302399,34.115784],
774
+ 'SAN BERNARDINO': [-117.302399, 34.115784],
828
775
  'SAN DIEGO': [-117.161087, 32.715736],
829
776
  'SAN FRANCISCO': [-122.419418, 37.774929],
830
777
  'SAN JOSE': [-121.886330, 37.338207],
831
778
  'SANTA ANA': [-117.867653, 33.745472],
832
- 'SANTA CLARA':[-121.955238,37.354107],
779
+ 'SANTA CLARA': [-121.955238, 37.354107],
833
780
  'SCOTTSDALE': [-111.926048, 33.494171],
834
781
  'SEATTLE': [-122.332069, 47.606209],
835
782
  'SOUTH PUGET INTERTRIBAL PLANNING AGENCY': [-123.0832, 47.1241],
836
783
  'SOUTHCENTRAL FOUNDATION': [-149.7971, 61.1821],
837
784
  'SOUTHEAST ALASKA REGIONAL HEALTH CONSORTIUM': [-135.3369, 57.05479],
838
785
  'SPOKANE': [-117.426048, 47.658779],
839
- 'ST PAUL': [ -93.089958, 44.953705],
786
+ 'ST PAUL': [-93.089958, 44.953705],
840
787
  'ST. LOUIS': [-90.199402, 38.627003],
841
788
  'ST. PETERSBURG': [-82.640289, 27.767601],
842
789
  'STOCKTON': [-121.290779, 37.957703],
@@ -25,8 +25,16 @@ const geoLookups: Record<string, GeoLookup> = {
25
25
  country: { keys: countryKeys, data: supportedCountries }
26
26
  }
27
27
 
28
- const memoizedFindUID = (geoName: string, type: keyof typeof geoLookups): string | undefined => {
28
+ const memoizedFindUID = (
29
+ geoName: string,
30
+ type: keyof typeof geoLookups,
31
+ caseInsensitive = false
32
+ ): string | undefined => {
29
33
  const lookup = geoLookups[type]
34
+ if (caseInsensitive) {
35
+ const lowerGeoName = geoName.toLowerCase()
36
+ return lookup.keys.find(key => lookup.data[key].some(name => name.toLowerCase() === lowerGeoName))
37
+ }
30
38
  return lookup.keys.find(key => lookup.data[key].includes(geoName))
31
39
  }
32
40
 
@@ -72,7 +80,10 @@ const handleUSLocation = (row: DataRow, geoColumn: string, displayAsHex: boolean
72
80
 
73
81
  const handleWorldLocation = (row: DataRow, geoColumn: string, isWorldGeocodeType: boolean): string | null => {
74
82
  const geoName = row[geoColumn]
75
- let uid = memoizedFindUID(geoName, 'country')
83
+ if (!geoName) return null
84
+
85
+ // Use case-insensitive matching for world countries to handle various input formats
86
+ let uid = memoizedFindUID(geoName, 'country', true)
76
87
  if (!uid && (isWorldGeocodeType || geoName)) {
77
88
  uid = findCityUID(geoName)
78
89
  }
@@ -1,7 +1,44 @@
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
@@ -17,39 +54,122 @@ type LegendItem = {
17
54
  export const applyColorToLegend = (legendIdx: number, config: MapConfig, result: LegendItem[] = []): string => {
18
55
  if (!config) throw new Error('Config is required')
19
56
 
20
- const { legend, customColors, general, color } = config
21
- const { geoType, palette } = general
22
- const specialClasses = legend?.specialClasses ?? []
23
- const mapColorPalette = 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'
62
+
63
+ // Apply palette migration if needed (v1 name -> v2 name)
64
+ if (mapPaletteNameMigrations[color]) {
65
+ color = mapPaletteNameMigrations[color]
66
+ }
67
+
68
+ // Check for customColorsOrdered first (direct 1-to-1 mapping, no distribution)
69
+ if (general?.palette?.customColorsOrdered && Array.isArray(general.palette.customColorsOrdered)) {
70
+ const customColorsOrdered = general.palette.customColorsOrdered
71
+
72
+ // Count actual special classes in the result array
73
+ const actualSpecialClassCount = result.filter(item => item.special).length
74
+ const colorIdx = legendIdx - actualSpecialClassCount
75
+
76
+ // Handle special classes coloring
77
+ if (result[legendIdx]?.special) {
78
+ const specialClassColors = chroma.scale(['#D4D4D4', '#939393']).colors(actualSpecialClassCount)
79
+ const specialClassIdx = result.slice(0, legendIdx + 1).filter(item => item.special).length - 1
80
+ return specialClassColors[specialClassIdx]
81
+ }
82
+
83
+ // Direct 1-to-1 mapping with customColorsOrdered (no distribution array)
84
+ if (colorIdx >= 0 && colorIdx < customColorsOrdered.length) {
85
+ return customColorsOrdered[colorIdx]
86
+ }
87
+
88
+ // Fallback to last color if index out of bounds
89
+ return customColorsOrdered[customColorsOrdered.length - 1] || '#d3d3d3'
90
+ }
91
+
92
+ // Try multiple approaches to find the palette
93
+ let mapColorPalette = general?.palette?.customColors
94
+
95
+ if (!mapColorPalette) {
96
+ // Try the detected version first
97
+ mapColorPalette = colorPalettes?.[`v${version}`]?.[color]
98
+ }
99
+
100
+ if (!mapColorPalette) {
101
+ // Try v2 explicitly
102
+ mapColorPalette = colorPalettes?.v2?.[color]
103
+ }
104
+
105
+ if (!mapColorPalette) {
106
+ // Try v1 with original color name
107
+ const originalColor = general?.palette?.name || config.color || palette.name || 'bluegreen'
108
+ mapColorPalette = colorPalettes?.v1?.[originalColor]
109
+ }
110
+
111
+ if (!mapColorPalette) {
112
+ // Final fallback
113
+ mapColorPalette = colorPalettes.v1['bluegreen']
114
+ }
24
115
 
25
116
  // Handle Region Maps need for a 10th color
26
117
  if (geoType === 'us-region' && mapColorPalette.length < 10 && mapColorPalette.length > 8) {
27
- const newColor = chroma(mapColorPalette[palette.isReversed ? 0 : 8])
118
+ const newColor = chroma(mapColorPalette[config.general.palette.isReversed ? 0 : 8])
28
119
  .darken(0.75)
29
120
  .hex()
30
- palette.isReversed ? mapColorPalette.unshift(newColor) : mapColorPalette.push(newColor)
121
+ config.general.palette.isReversed ? mapColorPalette.unshift(newColor) : mapColorPalette.push(newColor)
31
122
  }
32
123
 
33
- const colorIdx = legendIdx - specialClasses.length
124
+ // Count actual special classes in the result array
125
+ const actualSpecialClassCount = result.filter(item => item.special).length
126
+ const colorIdx = legendIdx - actualSpecialClassCount
34
127
 
35
128
  // Handle special classes coloring
36
129
  if (result[legendIdx]?.special) {
37
- const specialClassColors = chroma.scale(['#D4D4D4', '#939393']).colors(specialClasses.length)
38
- return specialClassColors[legendIdx]
130
+ const specialClassColors = chroma.scale(['#D4D4D4', '#939393']).colors(actualSpecialClassCount)
131
+ const specialClassIdx = result.slice(0, legendIdx + 1).filter(item => item.special).length - 1
132
+ return specialClassColors[specialClassIdx]
39
133
  }
40
134
 
41
135
  // Use qualitative color palettes directly
42
- if (color.includes('qualitative')) return mapColorPalette[colorIdx]
136
+ if (color.includes('qualitative')) {
137
+ return mapColorPalette[colorIdx]
138
+ }
139
+
140
+ // Determine color distribution based on non-special items
141
+ // For numeric legends, use the configured numberOfItems for consistent color distribution
142
+ // For category legends, use the actual result length
143
+ const isNumericLegend = legend && ['equalnumber', 'equalinterval'].includes(legend.type)
144
+ const nonSpecialItemCount = isNumericLegend
145
+ ? legend.numberOfItems || result.length
146
+ : result.length - actualSpecialClassCount
43
147
 
44
- // Determine color distribution
45
148
  const amt =
46
- Math.max(result.length - specialClasses.length, 1) < 10
47
- ? Math.max(result.length - specialClasses.length, 1)
48
- : Object.keys(colorDistributions).length
49
- const distributionArray = colorDistributions[amt] ?? []
149
+ Math.max(nonSpecialItemCount, 1) < 10
150
+ ? Math.max(nonSpecialItemCount, 1)
151
+ : Object.keys(mapV1ColorDistribution).length
152
+ const distributionArray = mapV1ColorDistribution[amt] ?? []
153
+
154
+ // Safety check to ensure mapColorPalette exists and is an array
155
+ if (!mapColorPalette || !Array.isArray(mapColorPalette) || mapColorPalette.length === 0) {
156
+ console.warn('No valid color palette found, returning gray fallback')
157
+ return '#d3d3d3'
158
+ }
50
159
 
51
160
  const specificColor =
52
- distributionArray[legendIdx - specialClasses.length] ?? mapColorPalette[colorIdx] ?? mapColorPalette.at(-1)
161
+ distributionArray[colorIdx] ?? mapColorPalette[colorIdx] ?? mapColorPalette[mapColorPalette.length - 1]
162
+
163
+ if (typeof specificColor === 'number') {
164
+ return specificColor < mapColorPalette.length
165
+ ? mapColorPalette[specificColor]
166
+ : mapColorPalette[mapColorPalette.length - 1]
167
+ }
168
+
169
+ if (typeof specificColor === 'string') {
170
+ return specificColor
171
+ }
53
172
 
54
- return mapColorPalette[specificColor]
173
+ // Final fallback
174
+ return mapColorPalette[0] || '#d3d3d3'
55
175
  }