@cdc/map 4.25.10 → 4.26.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/agents/typescript-organizer.md +118 -0
- package/dist/{cdcmap-fce76882.es.js → cdcmap-BnB1QM5d.es.js} +6 -13
- package/dist/{cdcmap-c55ac1ea.es.js → cdcmap-D6CG2-Hb.es.js} +5 -12
- package/dist/{cdcmap-31a33da1.es.js → cdcmap-MXgURbdZ.es.js} +6 -13
- package/dist/{cdcmap-1a1724a1.es.js → cdcmap-dgT_1dIT.es.js} +136 -151
- package/dist/cdcmap.js +58397 -55987
- package/examples/example-city-state.json +9 -1
- package/examples/multi-country-centering.json +45 -0
- package/examples/private/city_styles_variable.json +877 -0
- package/examples/private/colors-2.json +221 -0
- package/examples/private/colors.json +221 -0
- package/examples/private/map-filter-issue.json +2260 -0
- package/examples/private/map-legend.json +5303 -0
- package/index.html +27 -36
- package/package.json +6 -5
- package/src/CdcMapComponent.tsx +86 -26
- package/src/_stories/CdcMap.ColumnWrap.stories.tsx +31 -0
- package/src/_stories/CdcMap.DistrictOfColumbia.stories.tsx +320 -0
- package/src/_stories/CdcMap.Editor.stories.tsx +3426 -0
- package/src/_stories/CdcMap.SmallMultiples.stories.tsx +35 -0
- package/src/_stories/CdcMap.stories.tsx +116 -4
- package/src/_stories/_mock/column-wrap-test.json +265 -0
- package/src/_stories/_mock/multi-country-hide.json +78 -0
- package/src/_stories/_mock/multi-country.json +95 -0
- package/src/_stories/_mock/multi-state.json +887 -20403
- package/src/_stories/_mock/small_multiples/multi-state-small-multiples.json +8399 -0
- package/src/_stories/_mock/small_multiples/region-small-multiples.json +657 -0
- package/src/_stories/_mock/small_multiples/wastewater-map-small-multiples.json +221 -0
- package/src/_stories/_mock/usa-state-gradient.json +3 -4
- package/src/components/BubbleList.tsx +1 -1
- package/src/components/CityList.tsx +24 -18
- package/src/components/EditorPanel/components/EditorPanel.tsx +2380 -2206
- package/src/components/EditorPanel/components/HexShapeSettings.tsx +55 -93
- package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +0 -19
- package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +27 -37
- package/src/components/EditorPanel/components/Panels/Panel.SmallMultiples.tsx +351 -0
- package/src/components/EditorPanel/components/Panels/index.tsx +3 -1
- package/src/components/Geo.tsx +20 -3
- package/src/components/Legend/components/Legend.tsx +58 -75
- package/src/components/Legend/components/LegendGroup/Legend.Group.tsx +1 -1
- package/src/components/Legend/components/index.scss +23 -6
- package/src/components/NavigationMenu.tsx +16 -13
- package/src/components/SmallMultiples/SmallMultipleTile.tsx +163 -0
- package/src/components/SmallMultiples/SmallMultiples.css +32 -0
- package/src/components/SmallMultiples/SmallMultiples.tsx +150 -0
- package/src/components/SmallMultiples/SynchronizedTooltip.tsx +105 -0
- package/src/components/SmallMultiples/index.tsx +3 -0
- package/src/components/UsaMap/components/SingleState/SingleState.CountyOutput.tsx +18 -3
- package/src/components/UsaMap/components/TerritoriesSection.tsx +26 -12
- package/src/components/UsaMap/components/Territory/Territory.Hexagon.tsx +30 -4
- package/src/components/UsaMap/components/Territory/Territory.Rectangle.tsx +29 -9
- package/src/components/UsaMap/components/Territory/TerritoryShape.ts +7 -0
- package/src/components/UsaMap/components/UsaMap.County.tsx +16 -4
- package/src/components/UsaMap/components/UsaMap.Region.tsx +14 -1
- package/src/components/UsaMap/components/UsaMap.SingleState.tsx +29 -12
- package/src/components/UsaMap/components/UsaMap.State.tsx +30 -5
- package/src/components/UsaMap/helpers/map.ts +2 -2
- package/src/components/UsaMap/helpers/shapes.ts +9 -6
- package/src/components/WorldMap/WorldMap.tsx +81 -11
- package/src/data/initial-state.js +11 -0
- package/src/data/supported-geos.js +8 -76
- package/src/helpers/addUIDs.ts +13 -2
- package/src/helpers/applyColorToLegend.ts +25 -1
- package/src/helpers/applyLegendToRow.ts +5 -3
- package/src/helpers/constants.ts +3 -15
- package/src/helpers/displayGeoName.ts +22 -4
- package/src/helpers/generateRuntimeFilters.ts +1 -1
- package/src/helpers/generateRuntimeLegend.ts +1 -3
- package/src/helpers/generateRuntimeLegendHash.ts +1 -1
- package/src/helpers/getCountriesPicked.ts +103 -0
- package/src/helpers/getMapContainerClasses.ts +7 -0
- package/src/helpers/getPatternForRow.ts +2 -5
- package/src/helpers/index.ts +2 -4
- package/src/helpers/isLegendItemDisabled.ts +2 -2
- package/src/helpers/resetLegendToggles.ts +1 -0
- package/src/helpers/smallMultiplesHelpers.ts +359 -0
- package/src/helpers/tests/hashObj.test.ts +1 -1
- package/src/helpers/tests/titleCase.test.ts +76 -0
- package/src/helpers/titleCase.ts +13 -13
- package/src/helpers/toggleLegendActive.ts +76 -8
- package/src/helpers/urlDataHelpers.ts +1 -1
- package/src/hooks/useCountryZoom.tsx +241 -0
- package/src/hooks/useGeoClickHandler.ts +1 -1
- package/src/hooks/useProgrammaticMapTooltip.ts +110 -0
- package/src/hooks/useResizeObserver.ts +8 -2
- package/src/hooks/useStateZoom.tsx +7 -4
- package/src/hooks/useSynchronizedGeographies.ts +56 -0
- package/src/index.jsx +1 -0
- package/src/scss/editor-panel.scss +4 -440
- package/src/scss/main.scss +1 -1
- package/src/scss/map.scss +12 -15
- package/src/store/map.actions.ts +7 -7
- package/src/test/CdcMap.test.jsx +1 -1
- package/src/types/MapConfig.ts +32 -11
- package/src/types/MapContext.ts +6 -0
- package/src/types/runtimeLegend.ts +2 -1
- package/LICENSE +0 -201
- package/src/components/DataTable.tsx +0 -413
- package/src/components/EditorPanel/components/Inputs.tsx +0 -59
- package/src/components/MapControls.tsx +0 -44
- package/src/helpers/getUniqueValues.ts +0 -19
- package/src/helpers/hashObj.ts +0 -25
- package/src/hooks/useActiveElement.ts +0 -19
- package/src/hooks/useLegendSeparators.ts +0 -26
- package/src/scss/mixins.scss +0 -47
- package/src/types/Annotations.ts +0 -24
- /package/dist/{cdcmap-548642e6.es.js → cdcmap-Ct2SB0vL.es.js} +0 -0
|
@@ -26,6 +26,8 @@ import { useLegendMemoContext } from '../../../context/LegendMemoContext'
|
|
|
26
26
|
import { MapContext } from '../../../types/MapContext'
|
|
27
27
|
import { checkColorContrast, getContrastColor, outlinedTextColor } from '@cdc/core/helpers/cove/accessibility'
|
|
28
28
|
import TerritoriesSection from './TerritoriesSection'
|
|
29
|
+
import SmallMultiples from '../../SmallMultiples'
|
|
30
|
+
import { useSynchronizedGeographies } from '../../../hooks/useSynchronizedGeographies'
|
|
29
31
|
|
|
30
32
|
import { isMobileStateLabelViewport } from '@cdc/core/helpers/viewports'
|
|
31
33
|
import { APP_FONT_COLOR } from '@cdc/core/helpers/constants'
|
|
@@ -42,9 +44,9 @@ import {
|
|
|
42
44
|
displayGeoName,
|
|
43
45
|
SVG_HEIGHT,
|
|
44
46
|
SVG_VIEWBOX,
|
|
45
|
-
SVG_WIDTH
|
|
46
|
-
hashObj
|
|
47
|
+
SVG_WIDTH
|
|
47
48
|
} from '../../../helpers'
|
|
49
|
+
import { hashObj } from '@cdc/core/helpers/hashObj'
|
|
48
50
|
const { features: unitedStatesHex } = topoFeature(hexTopoJSON, hexTopoJSON.objects.states)
|
|
49
51
|
|
|
50
52
|
const offsets = {
|
|
@@ -80,6 +82,8 @@ const UsaMap = () => {
|
|
|
80
82
|
mapId,
|
|
81
83
|
logo,
|
|
82
84
|
currentViewport,
|
|
85
|
+
vizViewport,
|
|
86
|
+
dimensions,
|
|
83
87
|
translate,
|
|
84
88
|
runtimeLegend,
|
|
85
89
|
interactionLabel
|
|
@@ -87,6 +91,8 @@ const UsaMap = () => {
|
|
|
87
91
|
|
|
88
92
|
const { legendMemo, legendSpecialClassLastMemo } = useLegendMemoContext()
|
|
89
93
|
|
|
94
|
+
const { getSyncProps, syncHandlers } = useSynchronizedGeographies()
|
|
95
|
+
|
|
90
96
|
let isFilterValueSupported = false
|
|
91
97
|
const { general, columns, tooltips, hexMap, map, annotations } = config
|
|
92
98
|
const { displayAsHex } = general
|
|
@@ -165,6 +171,10 @@ const UsaMap = () => {
|
|
|
165
171
|
const geoStrokeColor = getGeoStrokeColor(config)
|
|
166
172
|
const geoFillColor = getGeoFillColor(config)
|
|
167
173
|
|
|
174
|
+
// Chrome needs wider stroke for small maps or it doesn't render the pattern
|
|
175
|
+
const mapWidth = dimensions?.[0] || 880
|
|
176
|
+
const patternLinesStrokeWidth = mapWidth < 200 ? 1.75 : mapWidth < 375 ? 1.25 : 0.75
|
|
177
|
+
|
|
168
178
|
const territories = territoriesData.map((territory, territoryIndex) => {
|
|
169
179
|
const Shape = displayAsHex ? Territory.Hexagon : Territory.Rectangle
|
|
170
180
|
|
|
@@ -190,6 +200,9 @@ const UsaMap = () => {
|
|
|
190
200
|
strokeColor='#fff'
|
|
191
201
|
territoryData={territoryData}
|
|
192
202
|
backgroundColor={styles.fill}
|
|
203
|
+
mapId={mapId}
|
|
204
|
+
getSyncProps={getSyncProps}
|
|
205
|
+
syncHandlers={syncHandlers}
|
|
193
206
|
/>
|
|
194
207
|
)
|
|
195
208
|
|
|
@@ -242,6 +255,9 @@ const UsaMap = () => {
|
|
|
242
255
|
territoryData={territoryData}
|
|
243
256
|
tabIndex={-1}
|
|
244
257
|
backgroundColor={styles.fill}
|
|
258
|
+
mapId={mapId}
|
|
259
|
+
getSyncProps={getSyncProps}
|
|
260
|
+
syncHandlers={syncHandlers}
|
|
245
261
|
/>
|
|
246
262
|
)
|
|
247
263
|
}
|
|
@@ -256,6 +272,10 @@ const UsaMap = () => {
|
|
|
256
272
|
return <></>
|
|
257
273
|
}
|
|
258
274
|
|
|
275
|
+
if (config.smallMultiples?.mode) {
|
|
276
|
+
return <SmallMultiples />
|
|
277
|
+
}
|
|
278
|
+
|
|
259
279
|
// Constructs and displays markup for all geos on the map (except territories right now)
|
|
260
280
|
const constructGeoJsx = (geographies, projection) => {
|
|
261
281
|
let showLabel = general.displayStateLabels
|
|
@@ -437,6 +457,7 @@ const UsaMap = () => {
|
|
|
437
457
|
return (
|
|
438
458
|
<g data-name={geoName} key={key} tabIndex={-1}>
|
|
439
459
|
<g
|
|
460
|
+
{...getSyncProps(geoKey)}
|
|
440
461
|
className='geo-group'
|
|
441
462
|
style={styles}
|
|
442
463
|
onClick={() => geoClickHandler(geoDisplayName, geoData)}
|
|
@@ -444,7 +465,7 @@ const UsaMap = () => {
|
|
|
444
465
|
data-tooltip-id={`tooltip__${tooltipId}`}
|
|
445
466
|
data-tooltip-html={tooltip}
|
|
446
467
|
tabIndex={-1}
|
|
447
|
-
onMouseEnter={
|
|
468
|
+
onMouseEnter={e => {
|
|
448
469
|
// Track hover analytics event if this is a new location
|
|
449
470
|
const locationName = geoDisplayName.replace(/[^a-zA-Z0-9]/g, '_')
|
|
450
471
|
publishAnalyticsEvent({
|
|
@@ -457,6 +478,10 @@ const UsaMap = () => {
|
|
|
457
478
|
location: geoDisplayName,
|
|
458
479
|
specifics: `location: ${locationName?.toLowerCase()}`
|
|
459
480
|
})
|
|
481
|
+
syncHandlers.onMouseEnter(geoKey, e.clientY)
|
|
482
|
+
}}
|
|
483
|
+
onMouseLeave={() => {
|
|
484
|
+
syncHandlers.onMouseLeave()
|
|
460
485
|
}}
|
|
461
486
|
>
|
|
462
487
|
{/* state path */}
|
|
@@ -499,7 +524,7 @@ const UsaMap = () => {
|
|
|
499
524
|
height={patternSizes[size] ?? 6}
|
|
500
525
|
width={patternSizes[size] ?? 6}
|
|
501
526
|
stroke={patternColor}
|
|
502
|
-
strokeWidth={
|
|
527
|
+
strokeWidth={patternLinesStrokeWidth}
|
|
503
528
|
orientation={['diagonalRightToLeft']}
|
|
504
529
|
/>
|
|
505
530
|
)}
|
|
@@ -590,7 +615,7 @@ const UsaMap = () => {
|
|
|
590
615
|
<text
|
|
591
616
|
x={x}
|
|
592
617
|
y={y}
|
|
593
|
-
fontSize={isMobileStateLabelViewport(
|
|
618
|
+
fontSize={isMobileStateLabelViewport(vizViewport) ? 16 : 13}
|
|
594
619
|
fontWeight={900}
|
|
595
620
|
strokeWidth='1'
|
|
596
621
|
paintOrder='stroke'
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { feature } from 'topojson-client'
|
|
2
2
|
import usExtendedGeography from './../data/us-extended-geography.json'
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
const getCountyTopoURL = year => {
|
|
5
5
|
return `https://www.cdc.gov/TemplatePackage/contrib/data/county-topography/cb_${year}_us_county_20m.json`
|
|
6
6
|
}
|
|
7
7
|
|
|
@@ -85,7 +85,7 @@ export const isTopoReady = (topoData, state, runtimeFilters) => {
|
|
|
85
85
|
return topoData?.year && (!currentYear || currentYear === topoData?.year)
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
|
|
88
|
+
const hasMoreThanFromHash = (data: { [key: string]: any }): boolean => {
|
|
89
89
|
// Get all keys of the data object
|
|
90
90
|
const keys = Object.keys(data)
|
|
91
91
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
const drawCircle = (circle, context, state) => {
|
|
2
2
|
const percentOfOriginalSize = 0.75
|
|
3
3
|
const scaleVal = 1
|
|
4
4
|
const adjustedGeoRadius =
|
|
@@ -12,7 +12,8 @@ export const drawCircle = (circle, context, state) => {
|
|
|
12
12
|
context.fill()
|
|
13
13
|
context.stroke()
|
|
14
14
|
}
|
|
15
|
-
|
|
15
|
+
|
|
16
|
+
const drawSquare = (square, context, state) => {
|
|
16
17
|
const percentOfOriginalSize = 0.75
|
|
17
18
|
const scaleVal = 1.75
|
|
18
19
|
const sideLength = square.size * scaleVal
|
|
@@ -32,7 +33,7 @@ export const drawSquare = (square, context, state) => {
|
|
|
32
33
|
context.stroke()
|
|
33
34
|
}
|
|
34
35
|
|
|
35
|
-
|
|
36
|
+
const drawDiamond = (diamond, context, state) => {
|
|
36
37
|
const percentOfOriginalSize = 0.75
|
|
37
38
|
const scaleVal = 2.2
|
|
38
39
|
const fullSize = diamond.size * scaleVal
|
|
@@ -69,7 +70,8 @@ export const drawDiamond = (diamond, context, state) => {
|
|
|
69
70
|
context.fill()
|
|
70
71
|
context.stroke()
|
|
71
72
|
}
|
|
72
|
-
|
|
73
|
+
|
|
74
|
+
const drawTriangle = (triangle, context, state) => {
|
|
73
75
|
const percentOfOriginalSize = 0.75
|
|
74
76
|
const scaleVal = 2.2
|
|
75
77
|
const baseLength = triangle.size * scaleVal
|
|
@@ -102,7 +104,8 @@ export const drawTriangle = (triangle, context, state) => {
|
|
|
102
104
|
context.fill()
|
|
103
105
|
context.stroke()
|
|
104
106
|
}
|
|
105
|
-
|
|
107
|
+
|
|
108
|
+
const drawStar = (star, context, state) => {
|
|
106
109
|
const percentOfOriginalSize = 0.75
|
|
107
110
|
const scaleVal = 2.2
|
|
108
111
|
const spikes = star.spikes
|
|
@@ -155,7 +158,7 @@ export const drawStar = (star, context, state) => {
|
|
|
155
158
|
context.stroke()
|
|
156
159
|
}
|
|
157
160
|
|
|
158
|
-
|
|
161
|
+
const drawPin = (pin, ctx, state) => {
|
|
159
162
|
const scaleVal = 10
|
|
160
163
|
const percentOfOriginalSize = 0.75
|
|
161
164
|
const baseSize = pin.size * scaleVal
|
|
@@ -11,6 +11,7 @@ import CityList from '../CityList'
|
|
|
11
11
|
import BubbleList from '../BubbleList'
|
|
12
12
|
import ZoomControls from '../ZoomControls'
|
|
13
13
|
import { supportedCountries } from '../../data/supported-geos'
|
|
14
|
+
import { getCountriesPicked } from '../../helpers/getCountriesPicked'
|
|
14
15
|
import {
|
|
15
16
|
getGeoFillColor,
|
|
16
17
|
getGeoStrokeColor,
|
|
@@ -24,6 +25,7 @@ import {
|
|
|
24
25
|
} from '../../helpers'
|
|
25
26
|
import useGeoClickHandler from '../../hooks/useGeoClickHandler'
|
|
26
27
|
import useApplyTooltipsToGeo from '../../hooks/useApplyTooltipsToGeo'
|
|
28
|
+
import useCountryZoom from '../../hooks/useCountryZoom'
|
|
27
29
|
import generateRuntimeData from '../../helpers/generateRuntimeData'
|
|
28
30
|
import { applyLegendToRow } from '../../helpers/applyLegendToRow'
|
|
29
31
|
|
|
@@ -33,17 +35,24 @@ import { getVizTitle, getVizSubType } from '@cdc/core/helpers/metrics/utils'
|
|
|
33
35
|
|
|
34
36
|
let projection = geoMercator()
|
|
35
37
|
|
|
38
|
+
const GRAYED_OUT_COLOR = '#d3d3d3'
|
|
39
|
+
|
|
40
|
+
type MapPosition = { coordinates: number[]; zoom: number }
|
|
41
|
+
|
|
36
42
|
const WorldMap = () => {
|
|
37
43
|
// prettier-ignore
|
|
38
44
|
const {
|
|
39
45
|
runtimeData,
|
|
40
|
-
position,
|
|
46
|
+
position: mapPosition,
|
|
41
47
|
config,
|
|
42
48
|
tooltipId,
|
|
43
49
|
runtimeLegend,
|
|
44
50
|
interactionLabel
|
|
45
51
|
} = useContext(ConfigContext)
|
|
46
52
|
|
|
53
|
+
// Type assertion: position from context is actually the map viewport position, not legend position
|
|
54
|
+
const position = mapPosition as unknown as MapPosition
|
|
55
|
+
|
|
47
56
|
const { legendMemo, legendSpecialClassLastMemo } = useLegendMemoContext()
|
|
48
57
|
|
|
49
58
|
const { type, allowMapZoom } = config.general
|
|
@@ -51,12 +60,16 @@ const WorldMap = () => {
|
|
|
51
60
|
const [world, setWorld] = useState(null)
|
|
52
61
|
const { geoClickHandler } = useGeoClickHandler()
|
|
53
62
|
const { applyTooltipsToGeo } = useApplyTooltipsToGeo()
|
|
63
|
+
|
|
64
|
+
const { centerOnCountries } = useCountryZoom(world)
|
|
65
|
+
|
|
54
66
|
const dispatch = useContext(MapDispatchContext)
|
|
55
67
|
|
|
56
68
|
useEffect(() => {
|
|
57
69
|
const fetchData = async () => {
|
|
58
70
|
import(/* webpackChunkName: "world-topo" */ './data/world-topo.json').then(topoJSON => {
|
|
59
|
-
|
|
71
|
+
const worldFeatures = feature(topoJSON, topoJSON.objects.countries).features
|
|
72
|
+
setWorld(worldFeatures)
|
|
60
73
|
})
|
|
61
74
|
}
|
|
62
75
|
fetchData()
|
|
@@ -66,6 +79,19 @@ const WorldMap = () => {
|
|
|
66
79
|
return <></>
|
|
67
80
|
}
|
|
68
81
|
|
|
82
|
+
// Filter countries based on selection
|
|
83
|
+
const getFilteredWorld = () => {
|
|
84
|
+
if (!config.general.countriesPicked || config.general.countriesPicked.length === 0) {
|
|
85
|
+
return world // Show all countries if none selected
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Always show all countries when multi-country mode is active
|
|
89
|
+
// Individual country styling will handle hide/grayed-out states
|
|
90
|
+
return world
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const filteredWorld = getFilteredWorld()
|
|
94
|
+
|
|
69
95
|
const handleFiltersReset = () => {
|
|
70
96
|
const newRuntimeData = generateRuntimeData(config)
|
|
71
97
|
publishAnalyticsEvent({
|
|
@@ -89,7 +115,15 @@ const WorldMap = () => {
|
|
|
89
115
|
eventLabel: interactionLabel,
|
|
90
116
|
vizTitle: getVizTitle(config)
|
|
91
117
|
})
|
|
92
|
-
|
|
118
|
+
|
|
119
|
+
// If countries are selected, center on them; otherwise, use default world position
|
|
120
|
+
const countriesPicked = getCountriesPicked(config)
|
|
121
|
+
|
|
122
|
+
if (countriesPicked && countriesPicked.length > 0) {
|
|
123
|
+
centerOnCountries('reset')
|
|
124
|
+
} else {
|
|
125
|
+
dispatch({ type: 'SET_POSITION', payload: { coordinates: [0, 30], zoom: 1 } })
|
|
126
|
+
}
|
|
93
127
|
}
|
|
94
128
|
|
|
95
129
|
const handleZoomIn = position => {
|
|
@@ -189,25 +223,57 @@ const WorldMap = () => {
|
|
|
189
223
|
const geoStrokeColor = getGeoStrokeColor(config)
|
|
190
224
|
const geoFillColor = getGeoFillColor(config)
|
|
191
225
|
|
|
226
|
+
// Check if this country should be greyed out for multi-country selection
|
|
227
|
+
const countriesPicked = getCountriesPicked(config)
|
|
228
|
+
|
|
229
|
+
const isGreyedOut = Boolean(
|
|
230
|
+
countriesPicked.length > 0 &&
|
|
231
|
+
config.general.hideUnselectedCountries !== true &&
|
|
232
|
+
!countriesPicked.some(country => country.iso === geo.properties.iso || country.name === geoDisplayName)
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
// Determine visual state for TDD tests
|
|
236
|
+
const isSelected = countriesPicked.some(
|
|
237
|
+
country => country.iso === geo.properties.iso || country.name === geoDisplayName
|
|
238
|
+
)
|
|
239
|
+
const isHidden = countriesPicked.length > 0 && config.general.hideUnselectedCountries === true && !isSelected
|
|
240
|
+
|
|
241
|
+
// Build CSS class names for TDD tests
|
|
242
|
+
let geoClassName = ''
|
|
243
|
+
if (countriesPicked.length > 0) {
|
|
244
|
+
if (isSelected) {
|
|
245
|
+
geoClassName = 'selected'
|
|
246
|
+
} else if (isGreyedOut) {
|
|
247
|
+
geoClassName = 'grayed-out'
|
|
248
|
+
} else if (isHidden) {
|
|
249
|
+
geoClassName = 'hidden'
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
192
253
|
let styles: Record<string, string | Record<string, string>> = {
|
|
193
|
-
fill: geoFillColor,
|
|
194
|
-
cursor: 'default'
|
|
254
|
+
fill: isGreyedOut ? GRAYED_OUT_COLOR : geoFillColor,
|
|
255
|
+
cursor: 'default',
|
|
256
|
+
...(isGreyedOut && { opacity: '0.3' })
|
|
195
257
|
}
|
|
196
258
|
|
|
197
|
-
|
|
259
|
+
// Scale stroke width inversely with zoom level to maintain consistent visual thickness
|
|
260
|
+
// At zoom=1, use base width of 0.9; at zoom=4, use 0.225; etc.
|
|
261
|
+
const baseStrokeWidth = 0.9
|
|
262
|
+
const currentZoom = position?.zoom || 1
|
|
263
|
+
const strokeWidth = baseStrokeWidth / currentZoom
|
|
198
264
|
|
|
199
265
|
// If a legend applies, return it with appropriate information.
|
|
200
266
|
const toolTip = applyTooltipsToGeo(geoDisplayName, geoData)
|
|
201
267
|
if (legendColors && legendColors[0] !== '#000000' && type !== 'bubble') {
|
|
202
268
|
styles = {
|
|
203
269
|
...styles,
|
|
204
|
-
fill: type !== 'world-geocode' ? legendColors[0] : geoFillColor,
|
|
270
|
+
fill: isGreyedOut ? GRAYED_OUT_COLOR : type !== 'world-geocode' ? legendColors[0] : geoFillColor,
|
|
205
271
|
cursor: 'default',
|
|
206
272
|
'&:hover': {
|
|
207
|
-
fill: type !== 'world-geocode' ? legendColors[1] : geoFillColor
|
|
273
|
+
fill: isGreyedOut ? GRAYED_OUT_COLOR : type !== 'world-geocode' ? legendColors[1] : geoFillColor
|
|
208
274
|
},
|
|
209
275
|
'&:active': {
|
|
210
|
-
fill: type !== 'world-geocode' ? legendColors[2] : geoFillColor
|
|
276
|
+
fill: isGreyedOut ? GRAYED_OUT_COLOR : type !== 'world-geocode' ? legendColors[2] : geoFillColor
|
|
211
277
|
}
|
|
212
278
|
}
|
|
213
279
|
|
|
@@ -228,6 +294,7 @@ const WorldMap = () => {
|
|
|
228
294
|
path={path}
|
|
229
295
|
stroke={geoStrokeColor}
|
|
230
296
|
strokeWidth={strokeWidth}
|
|
297
|
+
className={geoClassName}
|
|
231
298
|
onClick={() => geoClickHandler(geoDisplayName, geoData)}
|
|
232
299
|
onMouseEnter={() => {
|
|
233
300
|
// Track hover analytics event if this is a new location
|
|
@@ -245,6 +312,7 @@ const WorldMap = () => {
|
|
|
245
312
|
}}
|
|
246
313
|
data-tooltip-id={`tooltip__${tooltipId}`}
|
|
247
314
|
data-tooltip-html={toolTip}
|
|
315
|
+
data-country-code={geo.properties.iso}
|
|
248
316
|
tabIndex={-1}
|
|
249
317
|
/>
|
|
250
318
|
)
|
|
@@ -260,6 +328,7 @@ const WorldMap = () => {
|
|
|
260
328
|
strokeWidth={strokeWidth}
|
|
261
329
|
styles={styles}
|
|
262
330
|
path={path}
|
|
331
|
+
className={geoClassName}
|
|
263
332
|
onMouseEnter={() => {
|
|
264
333
|
// Track hover analytics event if this is a new location
|
|
265
334
|
const locationName = geoDisplayName.replace(/[^a-zA-Z0-9]/g, '_')
|
|
@@ -276,6 +345,7 @@ const WorldMap = () => {
|
|
|
276
345
|
}}
|
|
277
346
|
data-tooltip-id={`tooltip__${tooltipId}`}
|
|
278
347
|
data-tooltip-html={toolTip}
|
|
348
|
+
data-country-code={geo.properties.iso}
|
|
279
349
|
/>
|
|
280
350
|
)
|
|
281
351
|
})
|
|
@@ -305,7 +375,7 @@ const WorldMap = () => {
|
|
|
305
375
|
width={SVG_WIDTH}
|
|
306
376
|
height={SVG_HEIGHT}
|
|
307
377
|
>
|
|
308
|
-
<Mercator data={
|
|
378
|
+
<Mercator data={filteredWorld}>{({ features }) => constructGeoJsx(features)}</Mercator>
|
|
309
379
|
</ZoomableGroup>
|
|
310
380
|
</svg>
|
|
311
381
|
) : (
|
|
@@ -319,7 +389,7 @@ const WorldMap = () => {
|
|
|
319
389
|
width={SVG_WIDTH}
|
|
320
390
|
height={SVG_HEIGHT}
|
|
321
391
|
>
|
|
322
|
-
<Mercator data={
|
|
392
|
+
<Mercator data={filteredWorld}>{({ features }) => constructGeoJsx(features)}</Mercator>
|
|
323
393
|
</ZoomableGroup>
|
|
324
394
|
</svg>
|
|
325
395
|
)}
|
|
@@ -24,6 +24,7 @@ const createInitialState = () => {
|
|
|
24
24
|
headerColor: 'theme-blue',
|
|
25
25
|
title: '',
|
|
26
26
|
showTitle: true,
|
|
27
|
+
titleStyle: 'small',
|
|
27
28
|
showSidebar: true,
|
|
28
29
|
showDownloadMediaButton: false,
|
|
29
30
|
displayAsHex: false,
|
|
@@ -140,6 +141,16 @@ const createInitialState = () => {
|
|
|
140
141
|
},
|
|
141
142
|
filterBehavior: 'Filter Change',
|
|
142
143
|
filterIntro: '',
|
|
144
|
+
smallMultiples: {
|
|
145
|
+
mode: '',
|
|
146
|
+
tileColumn: '',
|
|
147
|
+
tilesPerRowDesktop: 2,
|
|
148
|
+
tilesPerRowMobile: 1,
|
|
149
|
+
tileOrderType: 'asc',
|
|
150
|
+
tileOrder: [],
|
|
151
|
+
tileTitles: {},
|
|
152
|
+
synchronizedTooltips: true
|
|
153
|
+
},
|
|
143
154
|
markupVariables: [],
|
|
144
155
|
enableMarkupVariables: false
|
|
145
156
|
}
|
|
@@ -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
|
*
|
|
@@ -750,7 +682,7 @@ export const supportedCities = {
|
|
|
750
682
|
'GREAT PLAINS TRIBAL LEADERS HEALTH BOARD': [-103.22444, 44.083054],
|
|
751
683
|
'GREENSBORO': [-79.791977, 36.072636],
|
|
752
684
|
'HENDERSON': [-114.981720, 36.039524],
|
|
753
|
-
'HERSHEY': [-76.6779444, 40.2849997
|
|
685
|
+
'HERSHEY': [-76.6779444, 40.2849997],
|
|
754
686
|
'HIALEAH': [-80.278107, 25.857595],
|
|
755
687
|
'HONOLULU': [-157.858337, 21.306944],
|
|
756
688
|
'HOPI TRIBE': [-110.5035, 35.7833],
|
|
@@ -783,7 +715,7 @@ export const supportedCities = {
|
|
|
783
715
|
'LUBBOCK': [-101.855164, 33.577862],
|
|
784
716
|
'MADISON': [-89.401230, 43.073051],
|
|
785
717
|
'MARION COUNTY, INDIANA': [-86.136543, 39.781029],
|
|
786
|
-
'MARION':[-88.9330556,37.7305556],
|
|
718
|
+
'MARION': [-88.9330556, 37.7305556],
|
|
787
719
|
'MEMPHIS': [-90.048981, 35.149532],
|
|
788
720
|
'MESA': [-111.831474, 33.415184],
|
|
789
721
|
'MIAMI': [-80.191788, 25.761681],
|
|
@@ -808,7 +740,7 @@ export const supportedCities = {
|
|
|
808
740
|
'OLYMPIA': [-122.9382403, 47.0394791],
|
|
809
741
|
'OMAHA': [-95.934502, 41.256538],
|
|
810
742
|
'ORLANDO': [-81.379234, 28.538336],
|
|
811
|
-
'PASADENA':[-95.209099,29.691063],
|
|
743
|
+
'PASADENA': [-95.209099, 29.691063],
|
|
812
744
|
'PHILADELPHIA': [-75.165222, 39.952583],
|
|
813
745
|
'PHOENIX': [-112.074036, 33.448376],
|
|
814
746
|
'PITTSBURGH': [-79.995888, 40.440624],
|
|
@@ -828,9 +760,9 @@ export const supportedCities = {
|
|
|
828
760
|
'SACRAMENTO': [-121.494400, 38.581573],
|
|
829
761
|
'SAINT PAUL': [-93.089958, 44.953705],
|
|
830
762
|
'SALEM, ALABAMA': [-85.2386, 32.5968],
|
|
831
|
-
'SALEM, CONNECTICUT': [-72.2754,41.4904],
|
|
763
|
+
'SALEM, CONNECTICUT': [-72.2754, 41.4904],
|
|
832
764
|
'SALEM, FLORIDA': [-83.4129, 29.8869],
|
|
833
|
-
'SALEM, ILLINOIS':[-88.945618,38.626991],
|
|
765
|
+
'SALEM, ILLINOIS': [-88.945618, 38.626991],
|
|
834
766
|
'SALEM, MASSACHUSETTS': [-70.8955, 42.5197],
|
|
835
767
|
'SALEM, OR': [-123.0351, 44.9429],
|
|
836
768
|
'SALEM, OREGON': [-123.0351, 44.9429],
|
|
@@ -839,19 +771,19 @@ export const supportedCities = {
|
|
|
839
771
|
'SALUDA, VIRGINIA': [-76.5950, 37.6064],
|
|
840
772
|
'SAN ANTONIO': [-98.493629, 29.424122],
|
|
841
773
|
'SAN BENITO': [-97.6311, 26.1326],
|
|
842
|
-
'SAN BERNARDINO':[-117.302399,34.115784],
|
|
774
|
+
'SAN BERNARDINO': [-117.302399, 34.115784],
|
|
843
775
|
'SAN DIEGO': [-117.161087, 32.715736],
|
|
844
776
|
'SAN FRANCISCO': [-122.419418, 37.774929],
|
|
845
777
|
'SAN JOSE': [-121.886330, 37.338207],
|
|
846
778
|
'SANTA ANA': [-117.867653, 33.745472],
|
|
847
|
-
'SANTA CLARA':[-121.955238,37.354107],
|
|
779
|
+
'SANTA CLARA': [-121.955238, 37.354107],
|
|
848
780
|
'SCOTTSDALE': [-111.926048, 33.494171],
|
|
849
781
|
'SEATTLE': [-122.332069, 47.606209],
|
|
850
782
|
'SOUTH PUGET INTERTRIBAL PLANNING AGENCY': [-123.0832, 47.1241],
|
|
851
783
|
'SOUTHCENTRAL FOUNDATION': [-149.7971, 61.1821],
|
|
852
784
|
'SOUTHEAST ALASKA REGIONAL HEALTH CONSORTIUM': [-135.3369, 57.05479],
|
|
853
785
|
'SPOKANE': [-117.426048, 47.658779],
|
|
854
|
-
'ST PAUL': [
|
|
786
|
+
'ST PAUL': [-93.089958, 44.953705],
|
|
855
787
|
'ST. LOUIS': [-90.199402, 38.627003],
|
|
856
788
|
'ST. PETERSBURG': [-82.640289, 27.767601],
|
|
857
789
|
'STOCKTON': [-121.290779, 37.957703],
|
package/src/helpers/addUIDs.ts
CHANGED
|
@@ -25,8 +25,16 @@ const geoLookups: Record<string, GeoLookup> = {
|
|
|
25
25
|
country: { keys: countryKeys, data: supportedCountries }
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
const memoizedFindUID = (
|
|
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
|
-
|
|
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
|
}
|
|
@@ -65,6 +65,30 @@ export const applyColorToLegend = (legendIdx: number, config: MapConfig, result:
|
|
|
65
65
|
color = mapPaletteNameMigrations[color]
|
|
66
66
|
}
|
|
67
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
|
+
|
|
68
92
|
// Try multiple approaches to find the palette
|
|
69
93
|
let mapColorPalette = general?.palette?.customColors
|
|
70
94
|
|
|
@@ -118,7 +142,7 @@ export const applyColorToLegend = (legendIdx: number, config: MapConfig, result:
|
|
|
118
142
|
// For category legends, use the actual result length
|
|
119
143
|
const isNumericLegend = legend && ['equalnumber', 'equalinterval'].includes(legend.type)
|
|
120
144
|
const nonSpecialItemCount = isNumericLegend
|
|
121
|
-
?
|
|
145
|
+
? legend.numberOfItems || result.length
|
|
122
146
|
: result.length - actualSpecialClassCount
|
|
123
147
|
|
|
124
148
|
const amt =
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { generateColorsArray } from '@cdc/core/helpers/generateColorsArray'
|
|
2
|
-
import { hashObj
|
|
2
|
+
import { hashObj } from '@cdc/core/helpers/hashObj'
|
|
3
|
+
import { DEFAULT_MAP_BACKGROUND, DISABLED_MAP_COLOR } from '../helpers'
|
|
3
4
|
import { mapColorPalettes as colorPalettes } from '@cdc/core/data/colorPalettes'
|
|
4
5
|
import { MapConfig } from '../types/MapConfig'
|
|
5
6
|
import { type RuntimeLegend } from '../types/runtimeLegend'
|
|
@@ -42,8 +43,9 @@ export const applyLegendToRow = (
|
|
|
42
43
|
const idx = legendMemo.current.get(hash)!
|
|
43
44
|
const disabledIdx = showSpecialClassesLast ? legendSpecialClassLastMemo.current.get(hash) ?? idx : idx
|
|
44
45
|
|
|
45
|
-
|
|
46
|
-
|
|
46
|
+
// Note: DISABLED_MAP_COLOR is used in UsaMap.County.tsx to check for hidden bubbles. Should be refactored to use the hidden value when that is implemented.
|
|
47
|
+
if (runtimeLegend.items?.[disabledIdx]?.disabled || runtimeLegend.items?.[disabledIdx]?.hidden) {
|
|
48
|
+
return generateColorsArray(DISABLED_MAP_COLOR)
|
|
47
49
|
}
|
|
48
50
|
|
|
49
51
|
const legendBinColor = runtimeLegend.items.find(o => o.bin === idx)?.color
|