@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.
- package/.claude/agents/typescript-organizer.md +118 -0
- package/.claude/settings.local.json +30 -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 +56991 -53706
- package/examples/example-city-state.json +9 -1
- package/examples/multi-country-centering.json +45 -0
- package/examples/private/c.json +290 -0
- package/examples/private/canvas-city-hover.json +787 -0
- package/examples/private/colors-2.json +221 -0
- package/examples/private/colors.json +221 -0
- package/examples/private/d.json +345 -0
- package/examples/private/g.json +1 -0
- package/examples/private/h.json +105911 -0
- package/examples/private/measles-data.json +378 -0
- package/examples/private/measles.json +211 -0
- package/examples/private/north-dakota.json +1132 -0
- package/examples/private/state-with-pattern.json +883 -0
- package/index.html +36 -34
- package/package.json +26 -5
- package/src/CdcMap.tsx +23 -8
- package/src/CdcMapComponent.tsx +238 -308
- 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 +3371 -0
- package/src/_stories/CdcMap.Filters.stories.tsx +2 -2
- package/src/_stories/CdcMap.Legend.Gradient.stories.tsx +3 -3
- package/src/_stories/CdcMap.Legend.stories.tsx +7 -4
- package/src/_stories/CdcMap.Patterns.stories.tsx +2 -2
- package/src/_stories/CdcMap.SmallMultiples.stories.tsx +35 -0
- package/src/_stories/CdcMap.Table.stories.tsx +2 -2
- package/src/_stories/CdcMap.stories.tsx +37 -9
- package/src/_stories/GoogleMap.stories.tsx +2 -2
- package/src/_stories/UsaMap.NoData.stories.tsx +2 -2
- package/src/_stories/_mock/column-wrap-test.json +265 -0
- package/src/_stories/_mock/equal-number.json +1109 -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/us-bubble-cities.json +306 -0
- package/src/_stories/_mock/usa-state-gradient.json +2 -4
- package/src/components/BubbleList.tsx +17 -13
- package/src/components/CityList.tsx +85 -107
- package/src/components/EditorPanel/components/EditorPanel.tsx +787 -709
- package/src/components/EditorPanel/components/HexShapeSettings.tsx +58 -95
- package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +34 -42
- package/src/components/EditorPanel/components/Panels/Panel.SmallMultiples.tsx +354 -0
- package/src/components/EditorPanel/components/Panels/index.tsx +3 -1
- package/src/components/Geo.tsx +22 -3
- package/src/components/Legend/components/Legend.tsx +76 -40
- package/src/components/Legend/components/LegendGroup/Legend.Group.tsx +10 -7
- package/src/components/Legend/components/index.scss +1 -1
- package/src/components/MapContainer.tsx +52 -0
- package/src/components/MapControls.tsx +44 -0
- package/src/components/NavigationMenu.tsx +27 -15
- 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 +36 -4
- 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 +23 -4
- package/src/components/UsaMap/components/Territory/TerritoryShape.ts +6 -0
- package/src/components/UsaMap/components/UsaMap.County.tsx +123 -37
- package/src/components/UsaMap/components/UsaMap.Region.tsx +36 -5
- package/src/components/UsaMap/components/UsaMap.SingleState.tsx +30 -10
- package/src/components/UsaMap/components/UsaMap.State.tsx +53 -12
- package/src/components/UsaMap/helpers/map.ts +4 -4
- package/src/components/UsaMap/helpers/shapes.ts +9 -6
- package/src/components/WorldMap/WorldMap.tsx +193 -35
- package/src/components/ZoomControls.tsx +6 -9
- package/src/context/LegendMemoContext.tsx +30 -0
- package/src/context.ts +1 -40
- package/src/data/initial-state.js +153 -130
- package/src/data/supported-geos.js +25 -78
- package/src/helpers/addUIDs.ts +13 -2
- package/src/helpers/applyColorToLegend.ts +140 -20
- package/src/helpers/applyLegendToRow.ts +10 -6
- package/src/helpers/componentHelpers.ts +8 -0
- package/src/helpers/constants.ts +12 -14
- package/src/helpers/dataTableHelpers.ts +6 -0
- package/src/helpers/displayGeoName.ts +18 -3
- package/src/helpers/generateRuntimeLegend.ts +44 -10
- package/src/helpers/generateRuntimeLegendHash.ts +4 -2
- package/src/helpers/getColumnNames.ts +1 -1
- package/src/helpers/getCountriesPicked.ts +103 -0
- package/src/helpers/getMapContainerClasses.ts +7 -0
- package/src/helpers/getPatternForRow.ts +33 -0
- package/src/helpers/getStatesPicked.ts +8 -5
- package/src/helpers/index.ts +3 -3
- package/src/helpers/isLegendItemDisabled.ts +16 -0
- package/src/helpers/mapObserverHelpers.ts +40 -0
- package/src/helpers/resetLegendToggles.ts +3 -2
- package/src/helpers/smallMultiplesHelpers.ts +359 -0
- package/src/helpers/tests/titleCase.test.ts +76 -0
- package/src/helpers/titleCase.ts +13 -13
- package/src/helpers/toggleLegendActive.ts +6 -11
- package/src/helpers/urlDataHelpers.ts +70 -0
- package/src/hooks/useCountryZoom.tsx +241 -0
- package/src/hooks/useGeoClickHandler.ts +36 -2
- package/src/hooks/useLegendMemo.ts +17 -0
- package/src/hooks/useMapLayers.tsx +5 -4
- package/src/hooks/useProgrammaticMapTooltip.ts +110 -0
- package/src/hooks/useResizeObserver.ts +5 -2
- package/src/hooks/useStateZoom.tsx +30 -8
- package/src/hooks/useSynchronizedGeographies.ts +56 -0
- package/src/hooks/useTooltip.ts +1 -2
- package/src/index.jsx +1 -2
- 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/store/map.reducer.ts +17 -6
- package/src/test/CdcMap.test.jsx +11 -0
- package/src/types/MapConfig.ts +46 -18
- package/src/types/MapContext.ts +6 -7
- package/src/types/runtimeLegend.ts +17 -1
- package/vite.config.js +2 -7
- package/vitest.config.ts +16 -0
- package/src/components/DataTable.tsx +0 -385
- package/src/components/EditorPanel/components/Inputs.tsx +0 -59
- package/src/coreStyles_map.scss +0 -3
- package/src/helpers/colorDistributions.ts +0 -12
- package/src/helpers/generateColorsArray.ts +0 -14
- package/src/helpers/tests/generateColorsArray.test.ts +0 -18
- package/src/helpers/tests/generateRuntimeLegendHash.test.ts +0 -11
- package/src/hooks/useActiveElement.ts +0 -19
- 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
|
@@ -3,6 +3,7 @@ import { geoMercator } from 'd3-geo'
|
|
|
3
3
|
import { Mercator } from '@visx/geo'
|
|
4
4
|
import { feature } from 'topojson-client'
|
|
5
5
|
import ConfigContext, { MapDispatchContext } from '../../context'
|
|
6
|
+
import { useLegendMemoContext } from '../../context/LegendMemoContext'
|
|
6
7
|
import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
7
8
|
import ZoomableGroup from '../ZoomableGroup'
|
|
8
9
|
import Geo from '../Geo'
|
|
@@ -10,6 +11,7 @@ import CityList from '../CityList'
|
|
|
10
11
|
import BubbleList from '../BubbleList'
|
|
11
12
|
import ZoomControls from '../ZoomControls'
|
|
12
13
|
import { supportedCountries } from '../../data/supported-geos'
|
|
14
|
+
import { getCountriesPicked } from '../../helpers/getCountriesPicked'
|
|
13
15
|
import {
|
|
14
16
|
getGeoFillColor,
|
|
15
17
|
getGeoStrokeColor,
|
|
@@ -23,39 +25,51 @@ import {
|
|
|
23
25
|
} from '../../helpers'
|
|
24
26
|
import useGeoClickHandler from '../../hooks/useGeoClickHandler'
|
|
25
27
|
import useApplyTooltipsToGeo from '../../hooks/useApplyTooltipsToGeo'
|
|
28
|
+
import useCountryZoom from '../../hooks/useCountryZoom'
|
|
26
29
|
import generateRuntimeData from '../../helpers/generateRuntimeData'
|
|
27
30
|
import { applyLegendToRow } from '../../helpers/applyLegendToRow'
|
|
28
31
|
|
|
29
32
|
import './worldMap.styles.css'
|
|
30
33
|
import { publishAnalyticsEvent } from '@cdc/core/helpers/metrics/helpers'
|
|
34
|
+
import { getVizTitle, getVizSubType } from '@cdc/core/helpers/metrics/utils'
|
|
31
35
|
|
|
32
36
|
let projection = geoMercator()
|
|
33
37
|
|
|
38
|
+
const GRAYED_OUT_COLOR = '#d3d3d3'
|
|
39
|
+
|
|
40
|
+
type MapPosition = { coordinates: number[]; zoom: number }
|
|
41
|
+
|
|
34
42
|
const WorldMap = () => {
|
|
35
43
|
// prettier-ignore
|
|
36
44
|
const {
|
|
37
|
-
|
|
38
|
-
position,
|
|
39
|
-
setRuntimeData,
|
|
45
|
+
runtimeData,
|
|
46
|
+
position: mapPosition,
|
|
40
47
|
config,
|
|
41
48
|
tooltipId,
|
|
42
49
|
runtimeLegend,
|
|
43
|
-
legendMemo,
|
|
44
|
-
legendSpecialClassLastMemo,
|
|
45
50
|
interactionLabel
|
|
46
51
|
} = useContext(ConfigContext)
|
|
47
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
|
+
|
|
56
|
+
const { legendMemo, legendSpecialClassLastMemo } = useLegendMemoContext()
|
|
57
|
+
|
|
48
58
|
const { type, allowMapZoom } = config.general
|
|
49
59
|
|
|
50
60
|
const [world, setWorld] = useState(null)
|
|
51
61
|
const { geoClickHandler } = useGeoClickHandler()
|
|
52
62
|
const { applyTooltipsToGeo } = useApplyTooltipsToGeo()
|
|
63
|
+
|
|
64
|
+
const { centerOnCountries } = useCountryZoom(world)
|
|
65
|
+
|
|
53
66
|
const dispatch = useContext(MapDispatchContext)
|
|
54
67
|
|
|
55
68
|
useEffect(() => {
|
|
56
69
|
const fetchData = async () => {
|
|
57
70
|
import(/* webpackChunkName: "world-topo" */ './data/world-topo.json').then(topoJSON => {
|
|
58
|
-
|
|
71
|
+
const worldFeatures = feature(topoJSON, topoJSON.objects.countries).features
|
|
72
|
+
setWorld(worldFeatures)
|
|
59
73
|
})
|
|
60
74
|
}
|
|
61
75
|
fetchData()
|
|
@@ -65,38 +79,118 @@ const WorldMap = () => {
|
|
|
65
79
|
return <></>
|
|
66
80
|
}
|
|
67
81
|
|
|
68
|
-
|
|
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
|
+
|
|
95
|
+
const handleFiltersReset = () => {
|
|
69
96
|
const newRuntimeData = generateRuntimeData(config)
|
|
70
|
-
publishAnalyticsEvent(
|
|
71
|
-
|
|
97
|
+
publishAnalyticsEvent({
|
|
98
|
+
vizType: config.type,
|
|
99
|
+
vizSubType: getVizSubType(config),
|
|
100
|
+
eventType: 'map_filter_reset',
|
|
101
|
+
eventAction: 'click',
|
|
102
|
+
eventLabel: interactionLabel,
|
|
103
|
+
vizTitle: getVizTitle(config)
|
|
104
|
+
})
|
|
72
105
|
dispatch({ type: 'SET_FILTERED_COUNTRY_CODE', payload: '' })
|
|
73
|
-
|
|
106
|
+
dispatch({ type: 'SET_RUNTIME_DATA', payload: newRuntimeData })
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const handleZoomReset = _setRuntimeData => {
|
|
110
|
+
publishAnalyticsEvent({
|
|
111
|
+
vizType: config.type,
|
|
112
|
+
vizSubType: getVizSubType(config),
|
|
113
|
+
eventType: 'map_reset_zoom_level',
|
|
114
|
+
eventAction: 'click',
|
|
115
|
+
eventLabel: interactionLabel,
|
|
116
|
+
vizTitle: getVizTitle(config)
|
|
117
|
+
})
|
|
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
|
+
}
|
|
74
127
|
}
|
|
75
128
|
|
|
76
129
|
const handleZoomIn = position => {
|
|
77
130
|
if (position.zoom >= 4) return
|
|
78
|
-
publishAnalyticsEvent(
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
'
|
|
83
|
-
|
|
131
|
+
publishAnalyticsEvent({
|
|
132
|
+
vizType: config.type,
|
|
133
|
+
vizSubType: getVizSubType(config),
|
|
134
|
+
eventType: `zoom_in`,
|
|
135
|
+
eventAction: 'click',
|
|
136
|
+
eventLabel: interactionLabel,
|
|
137
|
+
vizTitle: getVizTitle(config),
|
|
138
|
+
specifics: `zoom_level: ${Math.floor(position.zoom * 1.5)}`
|
|
139
|
+
})
|
|
140
|
+
publishAnalyticsEvent({
|
|
141
|
+
vizType: config.type,
|
|
142
|
+
vizSubType: getVizSubType(config),
|
|
143
|
+
eventType: `zoom_in`,
|
|
144
|
+
eventAction: 'click',
|
|
145
|
+
eventLabel: interactionLabel,
|
|
146
|
+
vizTitle: getVizTitle(config),
|
|
147
|
+
specifics: `location: ${position.coordinates}`
|
|
148
|
+
})
|
|
84
149
|
dispatch({ type: 'SET_POSITION', payload: { coordinates: position.coordinates, zoom: position.zoom * 1.5 } })
|
|
85
150
|
}
|
|
86
151
|
|
|
87
152
|
const handleZoomOut = position => {
|
|
88
153
|
if (position.zoom <= 1) return
|
|
89
|
-
publishAnalyticsEvent(
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
'
|
|
94
|
-
|
|
154
|
+
publishAnalyticsEvent({
|
|
155
|
+
vizType: config.type,
|
|
156
|
+
vizSubType: getVizSubType(config),
|
|
157
|
+
eventType: `zoom_out`,
|
|
158
|
+
eventAction: 'click',
|
|
159
|
+
eventLabel: interactionLabel,
|
|
160
|
+
vizTitle: getVizTitle(config),
|
|
161
|
+
specifics: `zoom_level: ${Math.floor(position.zoom / 1.5)}`
|
|
162
|
+
})
|
|
163
|
+
publishAnalyticsEvent({
|
|
164
|
+
vizType: config.type,
|
|
165
|
+
vizSubType: getVizSubType(config),
|
|
166
|
+
eventType: `zoom_out`,
|
|
167
|
+
eventAction: 'click',
|
|
168
|
+
eventLabel: interactionLabel,
|
|
169
|
+
vizTitle: getVizTitle(config),
|
|
170
|
+
specifics: `location: ${position.coordinates}`
|
|
171
|
+
})
|
|
95
172
|
dispatch({ type: 'SET_POSITION', payload: { coordinates: position.coordinates, zoom: position.zoom / 1.5 } })
|
|
96
173
|
}
|
|
97
174
|
|
|
98
175
|
const handleMoveEnd = position => {
|
|
99
|
-
publishAnalyticsEvent(
|
|
176
|
+
publishAnalyticsEvent({
|
|
177
|
+
vizType: config.type,
|
|
178
|
+
vizSubType: getVizSubType(config),
|
|
179
|
+
eventType: 'map_panned',
|
|
180
|
+
eventAction: 'drag',
|
|
181
|
+
eventLabel: interactionLabel,
|
|
182
|
+
vizTitle: getVizTitle(config),
|
|
183
|
+
specifics: `zoom: ${position.zoom}`
|
|
184
|
+
})
|
|
185
|
+
publishAnalyticsEvent({
|
|
186
|
+
vizType: config.type,
|
|
187
|
+
vizSubType: getVizSubType(config),
|
|
188
|
+
eventType: 'map_panned',
|
|
189
|
+
eventAction: 'drag',
|
|
190
|
+
eventLabel: interactionLabel,
|
|
191
|
+
vizTitle: getVizTitle(config),
|
|
192
|
+
specifics: `coordinates: ${position.coordinates}`
|
|
193
|
+
})
|
|
100
194
|
dispatch({ type: 'SET_POSITION', payload: position })
|
|
101
195
|
}
|
|
102
196
|
|
|
@@ -105,7 +199,7 @@ const WorldMap = () => {
|
|
|
105
199
|
// If the geo.properties.config value is found in the data use that, otherwise fall back to geo.properties.iso
|
|
106
200
|
const dataHasStateName = config.data.some(d => d[config.columns.geo.name] === geo.properties.state)
|
|
107
201
|
const geoKey =
|
|
108
|
-
geo.properties.state &&
|
|
202
|
+
geo.properties.state && runtimeData[geo.properties.state]
|
|
109
203
|
? geo.properties.state
|
|
110
204
|
: geo.properties.name
|
|
111
205
|
? geo.properties.name
|
|
@@ -116,7 +210,7 @@ const WorldMap = () => {
|
|
|
116
210
|
}
|
|
117
211
|
if (!geoKey) return null
|
|
118
212
|
|
|
119
|
-
let geoData =
|
|
213
|
+
let geoData = runtimeData[geoKey]
|
|
120
214
|
|
|
121
215
|
const geoDisplayName = displayGeoName(supportedCountries[geoKey]?.[0])
|
|
122
216
|
let legendColors
|
|
@@ -129,25 +223,57 @@ const WorldMap = () => {
|
|
|
129
223
|
const geoStrokeColor = getGeoStrokeColor(config)
|
|
130
224
|
const geoFillColor = getGeoFillColor(config)
|
|
131
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
|
+
|
|
132
253
|
let styles: Record<string, string | Record<string, string>> = {
|
|
133
|
-
fill: geoFillColor,
|
|
134
|
-
cursor: 'default'
|
|
254
|
+
fill: isGreyedOut ? GRAYED_OUT_COLOR : geoFillColor,
|
|
255
|
+
cursor: 'default',
|
|
256
|
+
...(isGreyedOut && { opacity: '0.3' })
|
|
135
257
|
}
|
|
136
258
|
|
|
137
|
-
|
|
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
|
|
138
264
|
|
|
139
265
|
// If a legend applies, return it with appropriate information.
|
|
140
266
|
const toolTip = applyTooltipsToGeo(geoDisplayName, geoData)
|
|
141
267
|
if (legendColors && legendColors[0] !== '#000000' && type !== 'bubble') {
|
|
142
268
|
styles = {
|
|
143
269
|
...styles,
|
|
144
|
-
fill: type !== 'world-geocode' ? legendColors[0] : geoFillColor,
|
|
270
|
+
fill: isGreyedOut ? GRAYED_OUT_COLOR : type !== 'world-geocode' ? legendColors[0] : geoFillColor,
|
|
145
271
|
cursor: 'default',
|
|
146
272
|
'&:hover': {
|
|
147
|
-
fill: type !== 'world-geocode' ? legendColors[1] : geoFillColor
|
|
273
|
+
fill: isGreyedOut ? GRAYED_OUT_COLOR : type !== 'world-geocode' ? legendColors[1] : geoFillColor
|
|
148
274
|
},
|
|
149
275
|
'&:active': {
|
|
150
|
-
fill: type !== 'world-geocode' ? legendColors[2] : geoFillColor
|
|
276
|
+
fill: isGreyedOut ? GRAYED_OUT_COLOR : type !== 'world-geocode' ? legendColors[2] : geoFillColor
|
|
151
277
|
}
|
|
152
278
|
}
|
|
153
279
|
|
|
@@ -168,9 +294,25 @@ const WorldMap = () => {
|
|
|
168
294
|
path={path}
|
|
169
295
|
stroke={geoStrokeColor}
|
|
170
296
|
strokeWidth={strokeWidth}
|
|
297
|
+
className={geoClassName}
|
|
171
298
|
onClick={() => geoClickHandler(geoDisplayName, geoData)}
|
|
299
|
+
onMouseEnter={() => {
|
|
300
|
+
// Track hover analytics event if this is a new location
|
|
301
|
+
const locationName = geoDisplayName.replace(/[^a-zA-Z0-9]/g, '_')
|
|
302
|
+
publishAnalyticsEvent({
|
|
303
|
+
vizType: config.type,
|
|
304
|
+
vizSubType: getVizSubType(config),
|
|
305
|
+
eventType: `map_hover`,
|
|
306
|
+
eventAction: 'hover',
|
|
307
|
+
eventLabel: interactionLabel,
|
|
308
|
+
vizTitle: getVizTitle(config),
|
|
309
|
+
location: geoDisplayName,
|
|
310
|
+
specifics: `location: ${locationName?.toLowerCase()}`
|
|
311
|
+
})
|
|
312
|
+
}}
|
|
172
313
|
data-tooltip-id={`tooltip__${tooltipId}`}
|
|
173
314
|
data-tooltip-html={toolTip}
|
|
315
|
+
data-country-code={geo.properties.iso}
|
|
174
316
|
tabIndex={-1}
|
|
175
317
|
/>
|
|
176
318
|
)
|
|
@@ -186,8 +328,24 @@ const WorldMap = () => {
|
|
|
186
328
|
strokeWidth={strokeWidth}
|
|
187
329
|
styles={styles}
|
|
188
330
|
path={path}
|
|
331
|
+
className={geoClassName}
|
|
332
|
+
onMouseEnter={() => {
|
|
333
|
+
// Track hover analytics event if this is a new location
|
|
334
|
+
const locationName = geoDisplayName.replace(/[^a-zA-Z0-9]/g, '_')
|
|
335
|
+
publishAnalyticsEvent({
|
|
336
|
+
vizType: config.type,
|
|
337
|
+
vizSubType: getVizSubType(config),
|
|
338
|
+
eventType: `map_hover`,
|
|
339
|
+
eventAction: 'hover',
|
|
340
|
+
eventLabel: interactionLabel,
|
|
341
|
+
vizTitle: getVizTitle(config),
|
|
342
|
+
location: geoDisplayName,
|
|
343
|
+
specifics: `location: ${locationName?.toLowerCase()}`
|
|
344
|
+
})
|
|
345
|
+
}}
|
|
189
346
|
data-tooltip-id={`tooltip__${tooltipId}`}
|
|
190
347
|
data-tooltip-html={toolTip}
|
|
348
|
+
data-country-code={geo.properties.iso}
|
|
191
349
|
/>
|
|
192
350
|
)
|
|
193
351
|
})
|
|
@@ -207,7 +365,7 @@ const WorldMap = () => {
|
|
|
207
365
|
<ErrorBoundary component='WorldMap'>
|
|
208
366
|
{allowMapZoom ? (
|
|
209
367
|
<svg viewBox={SVG_VIEWBOX} role='img' aria-label={handleMapAriaLabels(config)}>
|
|
210
|
-
<rect height={SVG_HEIGHT} width={SVG_WIDTH} onClick={
|
|
368
|
+
<rect height={SVG_HEIGHT} width={SVG_WIDTH} onClick={handleFiltersReset} fill='white' />
|
|
211
369
|
<ZoomableGroup
|
|
212
370
|
zoom={position.zoom}
|
|
213
371
|
center={position.coordinates}
|
|
@@ -217,7 +375,7 @@ const WorldMap = () => {
|
|
|
217
375
|
width={SVG_WIDTH}
|
|
218
376
|
height={SVG_HEIGHT}
|
|
219
377
|
>
|
|
220
|
-
<Mercator data={
|
|
378
|
+
<Mercator data={filteredWorld}>{({ features }) => constructGeoJsx(features)}</Mercator>
|
|
221
379
|
</ZoomableGroup>
|
|
222
380
|
</svg>
|
|
223
381
|
) : (
|
|
@@ -231,12 +389,12 @@ const WorldMap = () => {
|
|
|
231
389
|
width={SVG_WIDTH}
|
|
232
390
|
height={SVG_HEIGHT}
|
|
233
391
|
>
|
|
234
|
-
<Mercator data={
|
|
392
|
+
<Mercator data={filteredWorld}>{({ features }) => constructGeoJsx(features)}</Mercator>
|
|
235
393
|
</ZoomableGroup>
|
|
236
394
|
</svg>
|
|
237
395
|
)}
|
|
238
396
|
{(type === 'data' || (type === 'world-geocode' && allowMapZoom) || (type === 'bubble' && allowMapZoom)) && (
|
|
239
|
-
<ZoomControls handleZoomIn={handleZoomIn} handleZoomOut={handleZoomOut}
|
|
397
|
+
<ZoomControls handleZoomIn={handleZoomIn} handleZoomOut={handleZoomOut} handleZoomReset={handleZoomReset} />
|
|
240
398
|
)}
|
|
241
399
|
</ErrorBoundary>
|
|
242
400
|
)
|
|
@@ -7,10 +7,10 @@ import './zoomControls.styles.css'
|
|
|
7
7
|
type ZoomControlsProps = {
|
|
8
8
|
handleZoomIn: (coordinates: [Number, Number]) => void
|
|
9
9
|
handleZoomOut: (coordinates: [Number, Number]) => void
|
|
10
|
-
|
|
10
|
+
handleZoomReset: (setRuntimeData: Function) => void
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
const ZoomControls: React.FC<ZoomControlsProps> = ({ handleZoomIn, handleZoomOut,
|
|
13
|
+
const ZoomControls: React.FC<ZoomControlsProps> = ({ handleZoomIn, handleZoomOut, handleZoomReset }) => {
|
|
14
14
|
const { config, setRuntimeData, position } = useContext<MapContext>(ConfigContext)
|
|
15
15
|
if (!config.general.allowMapZoom) return
|
|
16
16
|
return (
|
|
@@ -26,13 +26,10 @@ const ZoomControls: React.FC<ZoomControlsProps> = ({ handleZoomIn, handleZoomOut
|
|
|
26
26
|
<line x1='5' y1='12' x2='19' y2='12' />
|
|
27
27
|
</svg>
|
|
28
28
|
</button>
|
|
29
|
-
{config.general.type === '
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
)}
|
|
34
|
-
{(config.general.type === 'world-geocode' || config.general.geoType === 'single-state') && (
|
|
35
|
-
<button onClick={() => handleReset(setRuntimeData)} className='reset' aria-label='Reset Zoom'>
|
|
29
|
+
{(config.general.type === 'world-geocode' ||
|
|
30
|
+
config.general.geoType === 'single-state' ||
|
|
31
|
+
config.general.type === 'bubble') && (
|
|
32
|
+
<button onClick={() => handleZoomReset(setRuntimeData)} className='reset' aria-label='Reset Zoom'>
|
|
36
33
|
Reset Zoom
|
|
37
34
|
</button>
|
|
38
35
|
)}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import React, { createContext, useContext } from 'react'
|
|
2
|
+
|
|
3
|
+
interface LegendMemoContextType {
|
|
4
|
+
legendMemo: React.RefObject<Map<any, any>>
|
|
5
|
+
legendSpecialClassLastMemo: React.RefObject<Map<any, any>>
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const LegendMemoContext = createContext<LegendMemoContextType | null>(null)
|
|
9
|
+
|
|
10
|
+
export const LegendMemoProvider: React.FC<{
|
|
11
|
+
children: React.ReactNode
|
|
12
|
+
legendMemo: React.RefObject<Map<any, any>>
|
|
13
|
+
legendSpecialClassLastMemo: React.RefObject<Map<any, any>>
|
|
14
|
+
}> = ({ children, legendMemo, legendSpecialClassLastMemo }) => {
|
|
15
|
+
return (
|
|
16
|
+
<LegendMemoContext.Provider value={{ legendMemo, legendSpecialClassLastMemo }}>
|
|
17
|
+
{children}
|
|
18
|
+
</LegendMemoContext.Provider>
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const useLegendMemoContext = () => {
|
|
23
|
+
const context = useContext(LegendMemoContext)
|
|
24
|
+
if (!context) {
|
|
25
|
+
throw new Error('useLegendMemoContext must be used within a LegendMemoProvider')
|
|
26
|
+
}
|
|
27
|
+
return context
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export default LegendMemoContext
|
package/src/context.ts
CHANGED
|
@@ -1,46 +1,7 @@
|
|
|
1
1
|
import { createContext, Dispatch } from 'react'
|
|
2
|
-
import {
|
|
2
|
+
import { MapContext } from './types/MapContext'
|
|
3
3
|
import MapActions from './store/map.actions'
|
|
4
4
|
|
|
5
|
-
type MapContext = {
|
|
6
|
-
container
|
|
7
|
-
setSharedFilter
|
|
8
|
-
customNavigationHandler
|
|
9
|
-
tooltipRef
|
|
10
|
-
containerEl
|
|
11
|
-
applyLegendToRow
|
|
12
|
-
data
|
|
13
|
-
displayGeoName
|
|
14
|
-
filteredCountryCode
|
|
15
|
-
generateColorsArray
|
|
16
|
-
generateRuntimeData
|
|
17
|
-
geoClickHandler
|
|
18
|
-
handleCircleClick: Function
|
|
19
|
-
innerContainerRef
|
|
20
|
-
isDashboard
|
|
21
|
-
isEditor
|
|
22
|
-
mapId: string
|
|
23
|
-
loadConfig
|
|
24
|
-
position
|
|
25
|
-
resetLegendToggles
|
|
26
|
-
runtimeFilters
|
|
27
|
-
runtimeLegend
|
|
28
|
-
setParentConfig
|
|
29
|
-
setRuntimeData
|
|
30
|
-
setRuntimeFilters
|
|
31
|
-
setRuntimeLegend
|
|
32
|
-
setSharedFilterValue
|
|
33
|
-
setConfig: Function
|
|
34
|
-
config: MapConfig
|
|
35
|
-
tooltipId: string
|
|
36
|
-
legendMemo
|
|
37
|
-
legendSpecialClassLastMemo
|
|
38
|
-
translate
|
|
39
|
-
scale
|
|
40
|
-
annotations
|
|
41
|
-
interactionLabel?: string
|
|
42
|
-
}
|
|
43
|
-
|
|
44
5
|
export const MapDispatchContext = createContext<Dispatch<MapActions>>(() => {})
|
|
45
6
|
|
|
46
7
|
const ConfigContext = createContext({} as MapContext)
|