@cdc/map 4.25.7 → 4.25.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +30 -0
- package/CLAUDE.local.md +0 -0
- package/dist/cdcmap.js +54785 -53159
- package/examples/private/c.json +290 -0
- package/examples/private/canvas-city-hover.json +787 -0
- package/examples/private/d.json +345 -0
- package/examples/private/filter-map.json +909 -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/rsv-data.json +532 -0
- package/examples/private/state-with-pattern.json +883 -0
- package/examples/private/test.json +222 -640
- package/index.html +1 -1
- package/package.json +26 -5
- package/src/CdcMap.tsx +28 -8
- package/src/CdcMapComponent.tsx +230 -306
- 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.Table.stories.tsx +2 -2
- package/src/_stories/CdcMap.stories.tsx +18 -11
- package/src/_stories/GoogleMap.stories.tsx +2 -2
- package/src/_stories/UsaMap.NoData.stories.tsx +2 -2
- package/src/_stories/_mock/equal-number.json +1109 -0
- package/src/_stories/_mock/multi-state.json +21389 -0
- package/src/_stories/_mock/us-bubble-cities.json +306 -0
- package/src/components/BubbleList.tsx +16 -12
- package/src/components/CityList.tsx +88 -110
- package/src/components/DataTable.tsx +44 -12
- package/src/components/EditorPanel/components/EditorPanel.tsx +201 -203
- package/src/components/EditorPanel/components/HexShapeSettings.tsx +3 -2
- package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +7 -5
- package/src/components/Geo.tsx +2 -0
- package/src/components/Legend/components/Legend.tsx +117 -93
- package/src/components/Legend/components/LegendGroup/Legend.Group.tsx +10 -7
- package/src/components/MapContainer.tsx +52 -0
- package/src/components/MapControls.tsx +44 -0
- package/src/components/Modal.tsx +2 -8
- package/src/components/NavigationMenu.tsx +13 -1
- package/src/components/UsaMap/components/SingleState/SingleState.CountyOutput.tsx +24 -7
- package/src/components/UsaMap/components/SingleState/SingleState.StateOutput.tsx +21 -15
- package/src/components/UsaMap/components/TerritoriesSection.tsx +2 -2
- package/src/components/UsaMap/components/UsaMap.County.tsx +112 -33
- package/src/components/UsaMap/components/UsaMap.Region.tsx +23 -5
- package/src/components/UsaMap/components/UsaMap.SingleState.tsx +38 -26
- package/src/components/UsaMap/components/UsaMap.State.tsx +28 -10
- package/src/components/UsaMap/helpers/map.ts +16 -8
- package/src/components/WorldMap/WorldMap.tsx +116 -11
- package/src/components/ZoomControls.tsx +6 -9
- package/src/context/LegendMemoContext.tsx +30 -0
- package/src/context.ts +1 -39
- package/src/data/initial-state.js +143 -128
- package/src/data/supported-geos.js +202 -4
- package/src/helpers/addUIDs.ts +8 -8
- package/src/helpers/applyColorToLegend.ts +122 -45
- package/src/helpers/applyLegendToRow.ts +15 -13
- package/src/helpers/componentHelpers.ts +8 -0
- package/src/helpers/constants.ts +12 -0
- package/src/helpers/dataTableHelpers.ts +6 -0
- package/src/helpers/displayGeoName.ts +12 -7
- package/src/helpers/formatLegendLocation.ts +1 -3
- package/src/helpers/generateRuntimeLegend.ts +192 -340
- package/src/helpers/generateRuntimeLegendHash.ts +4 -2
- package/src/helpers/getColumnNames.ts +1 -1
- package/src/helpers/getPatternForRow.ts +36 -0
- package/src/helpers/getStatesPicked.ts +14 -0
- package/src/helpers/handleMapAriaLabels.ts +2 -2
- package/src/helpers/index.ts +11 -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/toggleLegendActive.ts +6 -11
- package/src/helpers/urlDataHelpers.ts +70 -0
- package/src/hooks/useGeoClickHandler.ts +35 -1
- package/src/hooks/useLegendMemo.ts +17 -0
- package/src/hooks/useMapLayers.tsx +5 -4
- package/src/hooks/useStateZoom.tsx +137 -88
- package/src/hooks/useTooltip.ts +1 -2
- package/src/index.jsx +6 -3
- package/src/scss/main.scss +23 -12
- package/src/store/map.actions.ts +2 -2
- package/src/store/map.reducer.ts +21 -10
- package/src/test/CdcMap.test.jsx +11 -0
- package/src/types/MapConfig.ts +25 -17
- package/src/types/MapContext.ts +2 -8
- package/src/types/runtimeLegend.ts +12 -10
- package/vite.config.js +2 -7
- package/vitest.config.ts +16 -0
- package/src/_stories/_mock/floating-point.json +0 -427
- package/src/coreStyles_map.scss +0 -3
- package/src/helpers/colorDistributions.ts +0 -12
- package/src/helpers/generateColorsArray.ts +0 -14
- package/src/helpers/getStatePicked.ts +0 -8
- package/src/helpers/tests/generateColorsArray.test.ts +0 -18
- package/src/helpers/tests/generateRuntimeLegendHash.test.ts +0 -11
|
@@ -2,12 +2,13 @@ import { hashObj } from './hashObj'
|
|
|
2
2
|
import { MapConfig } from '../types/MapConfig'
|
|
3
3
|
|
|
4
4
|
export const generateRuntimeLegendHash = (config: MapConfig, runtimeFilters) => {
|
|
5
|
+
const { name: paletteName } = config.general.palette
|
|
5
6
|
return hashObj({
|
|
6
7
|
unified: config.legend.unified ?? false,
|
|
7
8
|
equalNumberOptIn: config.general.equalNumberOptIn ?? false,
|
|
8
9
|
specialClassesLast: config.legend.showSpecialClassesLast ?? false,
|
|
9
|
-
color:
|
|
10
|
-
customColors: config.customColors,
|
|
10
|
+
color: paletteName,
|
|
11
|
+
customColors: config.general?.palette?.customColors,
|
|
11
12
|
numberOfItems: config.legend.numberOfItems,
|
|
12
13
|
type: config.legend.type,
|
|
13
14
|
separateZero: config.legend.separateZero ?? false,
|
|
@@ -16,6 +17,7 @@ export const generateRuntimeLegendHash = (config: MapConfig, runtimeFilters) =>
|
|
|
16
17
|
specialClasses: config.legend.specialClasses,
|
|
17
18
|
geoType: config.general.geoType,
|
|
18
19
|
data: config.data,
|
|
20
|
+
palette: config.general.palette,
|
|
19
21
|
filters: {
|
|
20
22
|
...config.filters
|
|
21
23
|
},
|
|
@@ -8,7 +8,7 @@ type ColumnNames = {
|
|
|
8
8
|
categoricalColumnName: string | null
|
|
9
9
|
} | null
|
|
10
10
|
|
|
11
|
-
export const getColumnNames = (columns?:
|
|
11
|
+
export const getColumnNames = (columns?: MapConfig['columns']): ColumnNames => {
|
|
12
12
|
if (!columns) return null
|
|
13
13
|
const geoColumnName = columns.geo?.name || null
|
|
14
14
|
const primaryColumnName = columns.primary?.name || null
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { MapConfig } from '../types/MapConfig'
|
|
2
|
+
|
|
3
|
+
export interface PatternInfo {
|
|
4
|
+
pattern?: string
|
|
5
|
+
dataKey: string
|
|
6
|
+
size?: string
|
|
7
|
+
patternIndex: number
|
|
8
|
+
color?: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const getPatternForRow = (
|
|
12
|
+
rowObj: Record<string, any>,
|
|
13
|
+
config: MapConfig
|
|
14
|
+
): PatternInfo | null => {
|
|
15
|
+
if (!config.map?.patterns || !rowObj) {
|
|
16
|
+
return null
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Find a pattern that matches this row's data
|
|
20
|
+
for (let i = 0; i < config.map.patterns.length; i++) {
|
|
21
|
+
const patternData = config.map.patterns[i]
|
|
22
|
+
const hasMatchingValues = patternData.dataValue === rowObj[patternData.dataKey]
|
|
23
|
+
|
|
24
|
+
if (hasMatchingValues) {
|
|
25
|
+
return {
|
|
26
|
+
pattern: patternData.pattern,
|
|
27
|
+
dataKey: patternData.dataKey,
|
|
28
|
+
size: patternData.size,
|
|
29
|
+
patternIndex: i,
|
|
30
|
+
color: patternData.color
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return null
|
|
36
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { getFilterControllingStatesPicked } from '../components/UsaMap/helpers/map'
|
|
2
|
+
import { supportedStatesFipsCodes } from '../data/supported-geos'
|
|
3
|
+
|
|
4
|
+
export const getStatesPicked = (config, runtimeData) => {
|
|
5
|
+
const stateNames = getFilterControllingStatesPicked(config, runtimeData)
|
|
6
|
+
return stateNames.map(stateName => {
|
|
7
|
+
const fipsCode = Object.keys(supportedStatesFipsCodes).find(key => supportedStatesFipsCodes[key] === stateName)
|
|
8
|
+
if (!fipsCode) console.error(`State name "${stateName}" not found.`)
|
|
9
|
+
return {
|
|
10
|
+
fipsCode,
|
|
11
|
+
stateName
|
|
12
|
+
}
|
|
13
|
+
})
|
|
14
|
+
}
|
|
@@ -3,7 +3,7 @@ export const handleMapAriaLabels = (state: MapConfig = '', testing = false) => {
|
|
|
3
3
|
try {
|
|
4
4
|
if (!state.general.geoType) throw Error('handleMapAriaLabels: no geoType found in state')
|
|
5
5
|
const {
|
|
6
|
-
general: { title, geoType,
|
|
6
|
+
general: { title, geoType, statesPicked }
|
|
7
7
|
} = state
|
|
8
8
|
let ariaLabel = ''
|
|
9
9
|
switch (geoType) {
|
|
@@ -17,7 +17,7 @@ export const handleMapAriaLabels = (state: MapConfig = '', testing = false) => {
|
|
|
17
17
|
ariaLabel += `United States county map`
|
|
18
18
|
break
|
|
19
19
|
case 'single-state':
|
|
20
|
-
ariaLabel += `${
|
|
20
|
+
ariaLabel += `${statesPicked.map(sp => sp.stateName).join(', ')} county map`
|
|
21
21
|
break
|
|
22
22
|
case 'us-region':
|
|
23
23
|
ariaLabel += `United States HHS Region map`
|
package/src/helpers/index.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
export { addUIDs } from './addUIDs'
|
|
2
2
|
export { applyColorToLegend } from './applyColorToLegend'
|
|
3
|
-
export { colorDistributions } from './colorDistributions'
|
|
4
3
|
export { displayGeoName } from './displayGeoName'
|
|
5
4
|
export { formatLegendLocation } from './formatLegendLocation'
|
|
6
|
-
export { generateColorsArray } from '
|
|
5
|
+
export { generateColorsArray } from '@cdc/core/helpers/generateColorsArray'
|
|
7
6
|
export { generateRuntimeLegendHash } from './generateRuntimeLegendHash'
|
|
8
7
|
export { getGeoStrokeColor, getGeoFillColor } from './colors'
|
|
9
8
|
export { getUniqueValues } from './getUniqueValues'
|
|
@@ -11,6 +10,7 @@ export { handleMapAriaLabels } from './handleMapAriaLabels'
|
|
|
11
10
|
export { handleMapTabbing } from './handleMapTabbing'
|
|
12
11
|
export { hashObj } from './hashObj'
|
|
13
12
|
export { indexOfIgnoreType } from './indexOfIgnoreType'
|
|
13
|
+
export { isLegendItemDisabled } from './isLegendItemDisabled'
|
|
14
14
|
export { navigationHandler } from './navigationHandler'
|
|
15
15
|
export { resetLegendToggles } from './resetLegendToggles'
|
|
16
16
|
export { setBinNumbers } from './setBinNumbers'
|
|
@@ -19,4 +19,12 @@ export { titleCase as toTitleCase } from './toTitleCase'
|
|
|
19
19
|
export { titleCase } from './titleCase'
|
|
20
20
|
export { validateFipsCodeLength } from './validateFipsCodeLength'
|
|
21
21
|
export { getMapContainerClasses } from './getMapContainerClasses'
|
|
22
|
-
export {
|
|
22
|
+
export {
|
|
23
|
+
SVG_HEIGHT,
|
|
24
|
+
SVG_WIDTH,
|
|
25
|
+
SVG_PADDING,
|
|
26
|
+
SVG_VIEWBOX,
|
|
27
|
+
HEADER_COLORS,
|
|
28
|
+
MAX_ZOOM_LEVEL,
|
|
29
|
+
DEFAULT_MAP_BACKGROUND
|
|
30
|
+
} from './constants'
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { hashObj } from './hashObj'
|
|
2
|
+
|
|
3
|
+
export const isLegendItemDisabled = (
|
|
4
|
+
dataForCheck: any,
|
|
5
|
+
runtimeLegend: any,
|
|
6
|
+
legendMemo: React.MutableRefObject<Map<number, number>>,
|
|
7
|
+
legendSpecialClassLastMemo: React.MutableRefObject<Map<number, number>>,
|
|
8
|
+
config: any
|
|
9
|
+
): boolean => {
|
|
10
|
+
if (!dataForCheck || !runtimeLegend?.items) return false
|
|
11
|
+
const hash = hashObj(dataForCheck)
|
|
12
|
+
if (!legendMemo.current.has(hash)) return false
|
|
13
|
+
const idx = legendMemo.current.get(hash)
|
|
14
|
+
const disabledIdx = config.legend.showSpecialClassesLast ? legendSpecialClassLastMemo.current.get(hash) ?? idx : idx
|
|
15
|
+
return runtimeLegend.items[disabledIdx]?.disabled || false
|
|
16
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { publish } from '@cdc/core/helpers/events'
|
|
2
|
+
import { MapConfig } from '../types/MapConfig'
|
|
3
|
+
import MapActions from '../store/map.actions'
|
|
4
|
+
import { Dispatch } from 'react'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Publishes 'cove_loaded' only after the map SVG is rendered in the DOM.
|
|
8
|
+
* Checks immediately, then uses a MutationObserver as a fallback for async rendering.
|
|
9
|
+
* Update the mapSvg ref if the map container changes.
|
|
10
|
+
*/
|
|
11
|
+
export const observeMapSvgLoaded = (
|
|
12
|
+
mapSvgRef: React.RefObject<HTMLElement>,
|
|
13
|
+
config: MapConfig,
|
|
14
|
+
coveLoadedHasRan: boolean,
|
|
15
|
+
dispatch: Dispatch<MapActions>
|
|
16
|
+
): (() => void) => {
|
|
17
|
+
// Immediate check in case SVG is already present
|
|
18
|
+
const svgEl = mapSvgRef.current?.querySelector('svg')
|
|
19
|
+
if (svgEl && svgEl.childNodes.length > 0) {
|
|
20
|
+
publish('cove_loaded', { config })
|
|
21
|
+
dispatch({ type: 'SET_COVE_LOADED_HAS_RAN', payload: true })
|
|
22
|
+
return () => {}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Fallback to observer for async SVG rendering
|
|
26
|
+
const observer = new MutationObserver(() => {
|
|
27
|
+
const svgEl = mapSvgRef.current?.querySelector('svg')
|
|
28
|
+
if (svgEl && svgEl.childNodes.length > 0) {
|
|
29
|
+
publish('cove_loaded', { config })
|
|
30
|
+
dispatch({ type: 'SET_COVE_LOADED_HAS_RAN', payload: true })
|
|
31
|
+
observer.disconnect()
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
if (mapSvgRef.current) {
|
|
36
|
+
observer.observe(mapSvgRef.current, { childList: true, subtree: true })
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return () => observer.disconnect()
|
|
40
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import _ from 'lodash'
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
export const resetLegendToggles = (runtimeLegend, dispatch) => {
|
|
3
4
|
const legendCopy = _.cloneDeep(runtimeLegend)
|
|
4
5
|
|
|
5
6
|
legendCopy.items.forEach(legendItem => {
|
|
@@ -9,5 +10,5 @@ export const resetLegendToggles = (runtimeLegend, setRuntimeLegend) => {
|
|
|
9
10
|
|
|
10
11
|
legendCopy.runtimeDataHash = runtimeLegend.runtimeDataHash
|
|
11
12
|
|
|
12
|
-
|
|
13
|
+
dispatch({ type: 'SET_RUNTIME_LEGEND', payload: legendCopy })
|
|
13
14
|
}
|
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
import _ from 'lodash'
|
|
2
2
|
|
|
3
|
-
export const toggleLegendActive = (
|
|
4
|
-
i: number,
|
|
5
|
-
legendLabel: string,
|
|
6
|
-
runtimeLegend,
|
|
7
|
-
setRuntimeLegend,
|
|
8
|
-
setAccessibleStatus: (message: string) => void
|
|
9
|
-
) => {
|
|
3
|
+
export const toggleLegendActive = (i: number, legendLabel: string, runtimeLegend, dispatch) => {
|
|
10
4
|
const runtimeLegendCopy = _.cloneDeep(runtimeLegend)
|
|
11
5
|
|
|
12
6
|
// Create and toggle the new value
|
|
@@ -17,9 +11,10 @@ export const toggleLegendActive = (
|
|
|
17
11
|
|
|
18
12
|
runtimeLegendCopy['disabledAmt'] = newValue ? disabledAmt + 1 : disabledAmt - 1
|
|
19
13
|
|
|
20
|
-
|
|
14
|
+
dispatch({ type: 'SET_RUNTIME_LEGEND', payload: runtimeLegendCopy })
|
|
21
15
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
16
|
+
dispatch({
|
|
17
|
+
type: 'SET_ACCESSIBLE_STATUS',
|
|
18
|
+
payload: `Disabled legend item ${legendLabel ?? ''}. Please reference the data table to see updated values.`
|
|
19
|
+
})
|
|
25
20
|
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import Papa from 'papaparse'
|
|
2
|
+
import _ from 'lodash'
|
|
3
|
+
import { DataTransform } from '@cdc/core/helpers/DataTransform'
|
|
4
|
+
import { isSolrCsv, isSolrJson } from '@cdc/core/helpers/isSolr'
|
|
5
|
+
import { MapConfig } from '../types/MapConfig'
|
|
6
|
+
import { CSV_PARSE_CONFIG } from './constants'
|
|
7
|
+
import { cloneConfig } from '@cdc/core/helpers/cloneConfig'
|
|
8
|
+
|
|
9
|
+
export const buildQueryString = (params: Record<string, string>): string =>
|
|
10
|
+
Object.keys(params)
|
|
11
|
+
.map((param, i) => {
|
|
12
|
+
let qs = i === 0 ? '?' : '&'
|
|
13
|
+
qs += param + '='
|
|
14
|
+
qs += params[param]
|
|
15
|
+
return qs
|
|
16
|
+
})
|
|
17
|
+
.join('')
|
|
18
|
+
|
|
19
|
+
export const reloadURLData = async (config: MapConfig, setConfig: (config: MapConfig) => void): Promise<void> => {
|
|
20
|
+
if (!config.dataUrl) return
|
|
21
|
+
|
|
22
|
+
const dataUrl = new URL(config.runtimeDataUrl || config.dataUrl, window.location.origin)
|
|
23
|
+
let qsParams = Object.fromEntries(new URLSearchParams(dataUrl.search))
|
|
24
|
+
|
|
25
|
+
let isUpdateNeeded = false
|
|
26
|
+
config.filters.forEach(filter => {
|
|
27
|
+
if (filter.type === 'url' && qsParams[filter.queryParameter] !== decodeURIComponent(filter.active)) {
|
|
28
|
+
qsParams[filter.queryParameter] = filter.active
|
|
29
|
+
isUpdateNeeded = true
|
|
30
|
+
}
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
if (!isUpdateNeeded) return
|
|
34
|
+
|
|
35
|
+
let dataUrlFinal = `${dataUrl.origin}${dataUrl.pathname}${buildQueryString(qsParams)}`
|
|
36
|
+
let data
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const regex = /(?:\.([^.]+))?$/
|
|
40
|
+
const ext = regex.exec(dataUrl.pathname)[1]
|
|
41
|
+
|
|
42
|
+
if ('csv' === ext || isSolrCsv(dataUrlFinal)) {
|
|
43
|
+
data = await fetch(dataUrlFinal)
|
|
44
|
+
.then(response => response.text())
|
|
45
|
+
.then(responseText => {
|
|
46
|
+
const parsedCsv = Papa.parse(responseText, CSV_PARSE_CONFIG)
|
|
47
|
+
return parsedCsv.data
|
|
48
|
+
})
|
|
49
|
+
} else if ('json' === ext || isSolrJson(dataUrlFinal)) {
|
|
50
|
+
data = await fetch(dataUrlFinal).then(response => response.json())
|
|
51
|
+
} else {
|
|
52
|
+
data = []
|
|
53
|
+
}
|
|
54
|
+
} catch (e) {
|
|
55
|
+
console.error(`Cannot parse URL: ${dataUrlFinal}`, e)
|
|
56
|
+
data = []
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (config.dataDescription) {
|
|
60
|
+
const transform = new DataTransform()
|
|
61
|
+
data = transform.autoStandardize(data)
|
|
62
|
+
data = transform.developerStandardize(data, config.dataDescription)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const newConfig = cloneConfig(config)
|
|
66
|
+
newConfig.data = data
|
|
67
|
+
newConfig.runtimeDataUrl = dataUrlFinal
|
|
68
|
+
|
|
69
|
+
setConfig(newConfig)
|
|
70
|
+
}
|
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
import ConfigContext, { MapDispatchContext } from '../context'
|
|
2
2
|
import { navigationHandler } from '../helpers'
|
|
3
3
|
import { useContext } from 'react'
|
|
4
|
+
import { publishAnalyticsEvent } from '@cdc/core/helpers/metrics/helpers'
|
|
5
|
+
import { getVizTitle } from '@cdc/core/helpers/metrics/utils'
|
|
4
6
|
|
|
5
7
|
const useGeoClickHandler = () => {
|
|
6
|
-
const {
|
|
8
|
+
const {
|
|
9
|
+
config: state,
|
|
10
|
+
setConfig,
|
|
11
|
+
setSharedFilter,
|
|
12
|
+
customNavigationHandler,
|
|
13
|
+
interactionLabel
|
|
14
|
+
} = useContext(ConfigContext)
|
|
7
15
|
const dispatch = useContext(MapDispatchContext)
|
|
8
16
|
|
|
9
17
|
const geoClickHandler = (geoDisplayName: string, geoData: object): void => {
|
|
@@ -30,11 +38,37 @@ const useGeoClickHandler = () => {
|
|
|
30
38
|
}
|
|
31
39
|
dispatch({ type: 'SET_MODAL', payload: modalData })
|
|
32
40
|
|
|
41
|
+
// Track modal click analytics event
|
|
42
|
+
if (interactionLabel) {
|
|
43
|
+
const locationName = geoDisplayName.replace(/[^a-zA-Z0-9]/g, '_')
|
|
44
|
+
publishAnalyticsEvent({
|
|
45
|
+
vizType: 'map',
|
|
46
|
+
eventType: `modal_trigger` as any,
|
|
47
|
+
eventAction: 'click',
|
|
48
|
+
eventLabel: interactionLabel,
|
|
49
|
+
vizTitle: getVizTitle(state),
|
|
50
|
+
specifics: `clicked on: ${String(locationName).toLowerCase()}`
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
|
|
33
54
|
return
|
|
34
55
|
}
|
|
35
56
|
|
|
36
57
|
// Otherwise if this item has a link specified for it, do regular navigation.
|
|
37
58
|
if (state.columns.navigate && geoData[state.columns.navigate.name]) {
|
|
59
|
+
// Track navigation click analytics event
|
|
60
|
+
if (interactionLabel) {
|
|
61
|
+
const locationName = geoDisplayName.replace(/[^a-zA-Z0-9]/g, '_')
|
|
62
|
+
publishAnalyticsEvent({
|
|
63
|
+
vizType: 'map',
|
|
64
|
+
eventType: `map_trigger` as any,
|
|
65
|
+
eventAction: 'click',
|
|
66
|
+
eventLabel: interactionLabel,
|
|
67
|
+
vizTitle: getVizTitle(state),
|
|
68
|
+
specifics: `clicked on: ${String(locationName).toLowerCase()}`
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
|
|
38
72
|
navigationHandler(state.general.navigationTarget, geoData[state.columns.navigate.name], customNavigationHandler)
|
|
39
73
|
}
|
|
40
74
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { useRef } from 'react'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Custom hook to manage legend memoization refs
|
|
5
|
+
* Extracted from context to reduce context props and improve performance
|
|
6
|
+
*/
|
|
7
|
+
export const useLegendMemo = () => {
|
|
8
|
+
const legendMemo = useRef(new Map())
|
|
9
|
+
const legendSpecialClassLastMemo = useRef(new Map())
|
|
10
|
+
|
|
11
|
+
return {
|
|
12
|
+
legendMemo,
|
|
13
|
+
legendSpecialClassLastMemo
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default useLegendMemo
|
|
@@ -3,6 +3,7 @@ import { feature } from 'topojson-client'
|
|
|
3
3
|
import { Group } from '@visx/group'
|
|
4
4
|
import { MapConfig } from '../types/MapConfig'
|
|
5
5
|
import _ from 'lodash'
|
|
6
|
+
import { cloneConfig } from '@cdc/core/helpers/cloneConfig'
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* This is the starting structure for adding custom geoJSON shape layers to a projection.
|
|
@@ -46,10 +47,10 @@ export default function useMapLayers(config: MapConfig, setConfig, pathGenerator
|
|
|
46
47
|
|
|
47
48
|
const handleRemoveLayer = (e: MouseEvent<HTMLButtonElement>, index: number) => {
|
|
48
49
|
e.preventDefault()
|
|
49
|
-
const newConfig =
|
|
50
|
+
const newConfig = cloneConfig(config)
|
|
50
51
|
const layers = newConfig.map.layers.filter((_layer, i) => i !== index)
|
|
51
52
|
newConfig.map.layers = layers
|
|
52
|
-
setConfig(newConfig
|
|
53
|
+
setConfig(newConfig)
|
|
53
54
|
}
|
|
54
55
|
|
|
55
56
|
const handleAddLayer = (e: Event) => {
|
|
@@ -58,9 +59,9 @@ export default function useMapLayers(config: MapConfig, setConfig, pathGenerator
|
|
|
58
59
|
name: 'New Custom Layer',
|
|
59
60
|
url: ''
|
|
60
61
|
}
|
|
61
|
-
const newConfig =
|
|
62
|
+
const newConfig = cloneConfig(config)
|
|
62
63
|
newConfig.map.layers.unshift(placeHolderLayer)
|
|
63
|
-
setConfig(
|
|
64
|
+
setConfig(newConfig)
|
|
64
65
|
}
|
|
65
66
|
|
|
66
67
|
const handleMapLayer = (e: ChangeEvent<HTMLInputElement>, index: number, layerKey: string) => {
|