@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
@@ -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
- data,
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
- setWorld(feature(topoJSON, topoJSON.objects.countries).features)
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
- const handleReset = () => {
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('map_reset_zoom_level', 'click', interactionLabel, 'map')
71
- dispatch({ type: 'SET_POSITION', payload: { coordinates: [0, 30], zoom: 1 } })
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
- setRuntimeData(newRuntimeData)
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
- 'map_zoomed_in',
80
- 'click',
81
- `${interactionLabel}|zoom_level_${Math.floor(position.zoom * 1.5)}|${position.coordinates}`,
82
- 'map'
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
- 'map_zoomed_out',
91
- 'click',
92
- `${interactionLabel}|zoom_level_${Math.floor(position.zoom / 1.5)}|${position.coordinates}`,
93
- 'map'
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('map_panned', 'drag', interactionLabel, 'map')
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 && data[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 = data[geoKey]
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
- const strokeWidth = 0.9
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={handleReset} fill='white' />
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={world}>{({ features }) => constructGeoJsx(features)}</Mercator>
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={world}>{({ features }) => constructGeoJsx(features)}</Mercator>
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} handleReset={handleReset} />
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
- handleReset: (setRuntimeData: Function) => void
10
+ handleZoomReset: (setRuntimeData: Function) => void
11
11
  }
12
12
 
13
- const ZoomControls: React.FC<ZoomControlsProps> = ({ handleZoomIn, handleZoomOut, handleReset }) => {
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 === 'bubble' && (
30
- <button onClick={() => handleReset(setRuntimeData)} className='reset' aria-label='Reset Zoom and Map Filters'>
31
- Reset Filters
32
- </button>
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 { MapConfig } from './types/MapConfig'
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)