@cdc/map 4.26.2 → 4.26.3
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/LICENSE +201 -0
- package/dist/cdcmap-vr9HZwRt.es.js +6 -0
- package/dist/cdcmap.js +26781 -24615
- package/examples/private/annotation-bug.json +642 -0
- package/package.json +3 -3
- package/src/CdcMap.tsx +3 -14
- package/src/CdcMapComponent.tsx +214 -159
- package/src/_stories/CdcMap.Defaults.stories.tsx +76 -0
- package/src/_stories/CdcMap.Editor.stories.tsx +187 -14
- package/src/_stories/CdcMap.stories.tsx +11 -1
- package/src/_stories/Map.HTMLInDataTable.stories.tsx +385 -0
- package/src/_stories/_mock/multi-state-show-unselected.json +82 -0
- package/src/cdcMapComponent.styles.css +2 -2
- package/src/components/Annotation/Annotation.Draggable.styles.css +4 -4
- package/src/components/Annotation/AnnotationDropdown.styles.css +1 -1
- package/src/components/Annotation/AnnotationList.styles.css +13 -13
- package/src/components/EditorPanel/components/EditorPanel.tsx +426 -58
- package/src/components/EditorPanel/components/Panels/Panel.PatternSettings-style.css +1 -1
- package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +5 -2
- package/src/components/EditorPanel/components/editorPanel.styles.css +34 -24
- package/src/components/Legend/components/Legend.tsx +9 -4
- package/src/components/Legend/components/LegendGroup/legend.group.css +5 -5
- package/src/components/Legend/components/index.scss +2 -3
- package/src/components/NavigationMenu.tsx +2 -1
- package/src/components/SmallMultiples/SmallMultiples.css +5 -5
- package/src/components/UsaMap/components/SingleState/SingleState.StateOutput.tsx +32 -17
- package/src/components/UsaMap/components/TerritoriesSection.tsx +3 -2
- package/src/components/UsaMap/components/Territory/Territory.Rectangle.tsx +13 -8
- package/src/components/UsaMap/components/UsaMap.County.tsx +410 -183
- package/src/components/UsaMap/components/UsaMap.Region.styles.css +1 -1
- package/src/components/UsaMap/components/UsaMap.SingleState.styles.css +2 -2
- package/src/components/UsaMap/components/UsaMap.State.tsx +13 -8
- package/src/components/WorldMap/WorldMap.tsx +10 -13
- package/src/components/WorldMap/data/world-topo-updated.json +1 -0
- package/src/components/WorldMap/data/world-topo.json +1 -1
- package/src/components/WorldMap/worldMap.styles.css +1 -1
- package/src/components/ZoomControls.tsx +49 -18
- package/src/components/zoomControls.styles.css +27 -11
- package/src/data/initial-state.js +14 -5
- package/src/data/legacy-defaults.ts +8 -0
- package/src/data/supported-geos.js +19 -0
- package/src/helpers/colors.ts +2 -1
- package/src/helpers/dataTableHelpers.ts +56 -0
- package/src/helpers/displayGeoName.ts +19 -11
- package/src/helpers/getMapContainerClasses.ts +8 -2
- package/src/helpers/getMatchingPatternForRow.ts +67 -0
- package/src/helpers/getPatternForRow.ts +11 -18
- package/src/helpers/tests/dataTableHelpers.test.ts +78 -0
- package/src/helpers/tests/displayGeoName.test.ts +17 -0
- package/src/helpers/tests/getMatchingPatternForRow.test.ts +150 -0
- package/src/helpers/tests/getPatternForRow.test.ts +140 -2
- package/src/helpers/urlDataHelpers.ts +7 -1
- package/src/hooks/useResizeObserver.ts +36 -22
- package/src/hooks/useTooltip.test.tsx +64 -0
- package/src/hooks/useTooltip.ts +28 -8
- package/src/scss/editor-panel.scss +1 -1
- package/src/scss/main.scss +140 -6
- package/src/scss/map.scss +9 -4
- package/src/store/map.actions.ts +2 -0
- package/src/store/map.reducer.ts +4 -0
- package/src/test/CdcMap.test.jsx +2 -2
- package/src/types/MapConfig.ts +22 -4
- package/src/types/MapContext.ts +3 -1
- package/dist/cdcmap-Cf9_fbQf.es.js +0 -6
- package/src/helpers/componentHelpers.ts +0 -8
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { getMatchingPatternForRow } from '../getMatchingPatternForRow'
|
|
3
|
+
import { PatternSelection } from '../../types/MapConfig'
|
|
4
|
+
|
|
5
|
+
describe('getMatchingPatternForRow', () => {
|
|
6
|
+
it('uses the last matching specific pattern when multiple specific patterns match', () => {
|
|
7
|
+
const row = { Rate: 55 }
|
|
8
|
+
const patterns: PatternSelection[] = [
|
|
9
|
+
{
|
|
10
|
+
dataKey: 'Rate',
|
|
11
|
+
dataValue: '55',
|
|
12
|
+
pattern: 'lines',
|
|
13
|
+
size: 'small',
|
|
14
|
+
color: '#111',
|
|
15
|
+
label: '',
|
|
16
|
+
contrastCheck: true
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
dataKey: 'Rate',
|
|
20
|
+
dataValue: '55',
|
|
21
|
+
pattern: 'circles',
|
|
22
|
+
size: 'medium',
|
|
23
|
+
color: '#000',
|
|
24
|
+
label: '',
|
|
25
|
+
contrastCheck: true
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
const result = getMatchingPatternForRow(row, patterns)
|
|
30
|
+
|
|
31
|
+
expect(result?.patternIndex).toBe(1)
|
|
32
|
+
expect(result?.pattern.pattern).toBe('circles')
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('matches specific dataKey patterns first', () => {
|
|
36
|
+
const row = { Rate: 55, Status: '55' }
|
|
37
|
+
const patterns: PatternSelection[] = [
|
|
38
|
+
{
|
|
39
|
+
dataKey: '',
|
|
40
|
+
dataValue: '55',
|
|
41
|
+
pattern: 'lines',
|
|
42
|
+
size: 'small',
|
|
43
|
+
color: '#111',
|
|
44
|
+
label: '',
|
|
45
|
+
contrastCheck: true
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
dataKey: 'Rate',
|
|
49
|
+
dataValue: '55',
|
|
50
|
+
pattern: 'circles',
|
|
51
|
+
size: 'medium',
|
|
52
|
+
color: '#000',
|
|
53
|
+
label: '',
|
|
54
|
+
contrastCheck: true
|
|
55
|
+
}
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
const result = getMatchingPatternForRow(row, patterns)
|
|
59
|
+
|
|
60
|
+
expect(result?.patternIndex).toBe(1)
|
|
61
|
+
expect(result?.matchedDataKey).toBe('Rate')
|
|
62
|
+
expect(result?.pattern.pattern).toBe('circles')
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('falls back to broad matching when dataKey is blank', () => {
|
|
66
|
+
const row = { State: 'Colorado', Rate: 55 }
|
|
67
|
+
const patterns: PatternSelection[] = [
|
|
68
|
+
{
|
|
69
|
+
dataKey: '',
|
|
70
|
+
dataValue: 'Colorado',
|
|
71
|
+
pattern: 'waves',
|
|
72
|
+
size: 'large',
|
|
73
|
+
color: '#333',
|
|
74
|
+
label: '',
|
|
75
|
+
contrastCheck: true
|
|
76
|
+
}
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
const result = getMatchingPatternForRow(row, patterns)
|
|
80
|
+
|
|
81
|
+
expect(result?.patternIndex).toBe(0)
|
|
82
|
+
expect(result?.matchedDataKey).toBe('State')
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
it('does not match when dataValue is empty', () => {
|
|
86
|
+
const row = { State: 'Colorado', Rate: 55 }
|
|
87
|
+
const patterns: PatternSelection[] = [
|
|
88
|
+
{
|
|
89
|
+
dataKey: '',
|
|
90
|
+
dataValue: ' ',
|
|
91
|
+
pattern: 'waves',
|
|
92
|
+
size: 'large',
|
|
93
|
+
color: '#333',
|
|
94
|
+
label: '',
|
|
95
|
+
contrastCheck: true
|
|
96
|
+
}
|
|
97
|
+
]
|
|
98
|
+
|
|
99
|
+
expect(getMatchingPatternForRow(row, patterns)).toBeNull()
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
it('supports numeric string and number matching for broad patterns', () => {
|
|
103
|
+
const row = { Rate: 55 }
|
|
104
|
+
const patterns: PatternSelection[] = [
|
|
105
|
+
{
|
|
106
|
+
dataKey: '',
|
|
107
|
+
dataValue: '55',
|
|
108
|
+
pattern: 'waves',
|
|
109
|
+
size: 'large',
|
|
110
|
+
color: '#333',
|
|
111
|
+
label: '',
|
|
112
|
+
contrastCheck: true
|
|
113
|
+
}
|
|
114
|
+
]
|
|
115
|
+
|
|
116
|
+
const result = getMatchingPatternForRow(row, patterns)
|
|
117
|
+
|
|
118
|
+
expect(result?.matchedDataKey).toBe('Rate')
|
|
119
|
+
expect(result?.patternIndex).toBe(0)
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
it('uses the last matching broad pattern when multiple broad patterns match', () => {
|
|
123
|
+
const row = { Category: 'Target' }
|
|
124
|
+
const patterns: PatternSelection[] = [
|
|
125
|
+
{
|
|
126
|
+
dataKey: '',
|
|
127
|
+
dataValue: 'Target',
|
|
128
|
+
pattern: 'lines',
|
|
129
|
+
size: 'small',
|
|
130
|
+
color: '#111',
|
|
131
|
+
label: '',
|
|
132
|
+
contrastCheck: true
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
dataKey: '',
|
|
136
|
+
dataValue: 'Target',
|
|
137
|
+
pattern: 'waves',
|
|
138
|
+
size: 'large',
|
|
139
|
+
color: '#333',
|
|
140
|
+
label: '',
|
|
141
|
+
contrastCheck: true
|
|
142
|
+
}
|
|
143
|
+
]
|
|
144
|
+
|
|
145
|
+
const result = getMatchingPatternForRow(row, patterns)
|
|
146
|
+
|
|
147
|
+
expect(result?.patternIndex).toBe(1)
|
|
148
|
+
expect(result?.pattern.pattern).toBe('waves')
|
|
149
|
+
})
|
|
150
|
+
})
|
|
@@ -4,8 +4,24 @@ import { getPatternForRow } from '../getPatternForRow'
|
|
|
4
4
|
const baseConfig: any = {
|
|
5
5
|
map: {
|
|
6
6
|
patterns: [
|
|
7
|
-
{
|
|
8
|
-
|
|
7
|
+
{
|
|
8
|
+
dataKey: 'Percent Vaccinated',
|
|
9
|
+
dataValue: '99.99',
|
|
10
|
+
pattern: 'circles',
|
|
11
|
+
size: 'medium',
|
|
12
|
+
color: '#000',
|
|
13
|
+
label: '',
|
|
14
|
+
contrastCheck: true
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
dataKey: 'Status',
|
|
18
|
+
dataValue: 'NA',
|
|
19
|
+
pattern: 'lines',
|
|
20
|
+
size: 'small',
|
|
21
|
+
color: '#111',
|
|
22
|
+
label: '',
|
|
23
|
+
contrastCheck: true
|
|
24
|
+
}
|
|
9
25
|
]
|
|
10
26
|
}
|
|
11
27
|
}
|
|
@@ -36,4 +52,126 @@ describe('getPatternForRow', () => {
|
|
|
36
52
|
const row = { 'Percent Vaccinated': 75.06, Status: '*' }
|
|
37
53
|
expect(getPatternForRow(row, baseConfig)).toBeNull()
|
|
38
54
|
})
|
|
55
|
+
|
|
56
|
+
it('matches blank dataKey patterns against any row value', () => {
|
|
57
|
+
const config = {
|
|
58
|
+
map: {
|
|
59
|
+
patterns: [
|
|
60
|
+
{
|
|
61
|
+
dataKey: '',
|
|
62
|
+
dataValue: 'Colorado',
|
|
63
|
+
pattern: 'waves',
|
|
64
|
+
size: 'large',
|
|
65
|
+
color: '#222',
|
|
66
|
+
label: '',
|
|
67
|
+
contrastCheck: true
|
|
68
|
+
}
|
|
69
|
+
]
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const row = { STATE: 'Colorado', Rate: 55 }
|
|
73
|
+
const result = getPatternForRow(row, config)
|
|
74
|
+
|
|
75
|
+
expect(result).toEqual({
|
|
76
|
+
pattern: 'waves',
|
|
77
|
+
dataKey: 'STATE',
|
|
78
|
+
size: 'large',
|
|
79
|
+
patternIndex: 0,
|
|
80
|
+
color: '#222'
|
|
81
|
+
})
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('prioritizes specific dataKey matches over broad matches', () => {
|
|
85
|
+
const config = {
|
|
86
|
+
map: {
|
|
87
|
+
patterns: [
|
|
88
|
+
{
|
|
89
|
+
dataKey: '',
|
|
90
|
+
dataValue: '55',
|
|
91
|
+
pattern: 'lines',
|
|
92
|
+
size: 'small',
|
|
93
|
+
color: '#111',
|
|
94
|
+
label: '',
|
|
95
|
+
contrastCheck: true
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
dataKey: 'Rate',
|
|
99
|
+
dataValue: '55',
|
|
100
|
+
pattern: 'circles',
|
|
101
|
+
size: 'medium',
|
|
102
|
+
color: '#000',
|
|
103
|
+
label: '',
|
|
104
|
+
contrastCheck: true
|
|
105
|
+
}
|
|
106
|
+
]
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
const row = { Rate: 55, Category: '55' }
|
|
110
|
+
const result = getPatternForRow(row, config)
|
|
111
|
+
|
|
112
|
+
expect(result).toEqual({
|
|
113
|
+
pattern: 'circles',
|
|
114
|
+
dataKey: 'Rate',
|
|
115
|
+
size: 'medium',
|
|
116
|
+
patternIndex: 1,
|
|
117
|
+
color: '#000'
|
|
118
|
+
})
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
it('does not match blank dataKey patterns with empty values', () => {
|
|
122
|
+
const config = {
|
|
123
|
+
map: {
|
|
124
|
+
patterns: [
|
|
125
|
+
{
|
|
126
|
+
dataKey: '',
|
|
127
|
+
dataValue: ' ',
|
|
128
|
+
pattern: 'lines',
|
|
129
|
+
size: 'small',
|
|
130
|
+
color: '#111',
|
|
131
|
+
label: '',
|
|
132
|
+
contrastCheck: true
|
|
133
|
+
}
|
|
134
|
+
]
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
const row = { STATE: 'Colorado' }
|
|
138
|
+
expect(getPatternForRow(row, config)).toBeNull()
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
it('returns the last matching specific pattern to preserve dominant visual overlay behavior', () => {
|
|
142
|
+
const config = {
|
|
143
|
+
map: {
|
|
144
|
+
patterns: [
|
|
145
|
+
{
|
|
146
|
+
dataKey: 'Rate',
|
|
147
|
+
dataValue: '55',
|
|
148
|
+
pattern: 'lines',
|
|
149
|
+
size: 'small',
|
|
150
|
+
color: '#111',
|
|
151
|
+
label: '',
|
|
152
|
+
contrastCheck: true
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
dataKey: 'Rate',
|
|
156
|
+
dataValue: '55',
|
|
157
|
+
pattern: 'waves',
|
|
158
|
+
size: 'large',
|
|
159
|
+
color: '#222',
|
|
160
|
+
label: '',
|
|
161
|
+
contrastCheck: true
|
|
162
|
+
}
|
|
163
|
+
]
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
const row = { Rate: 55 }
|
|
167
|
+
const result = getPatternForRow(row, config)
|
|
168
|
+
|
|
169
|
+
expect(result).toEqual({
|
|
170
|
+
pattern: 'waves',
|
|
171
|
+
dataKey: 'Rate',
|
|
172
|
+
size: 'large',
|
|
173
|
+
patternIndex: 1,
|
|
174
|
+
color: '#222'
|
|
175
|
+
})
|
|
176
|
+
})
|
|
39
177
|
})
|
|
@@ -5,6 +5,7 @@ import { isSolrCsv, isSolrJson } from '@cdc/core/helpers/isSolr'
|
|
|
5
5
|
import { MapConfig } from '../types/MapConfig'
|
|
6
6
|
import { CSV_PARSE_CONFIG } from './constants'
|
|
7
7
|
import { cloneConfig } from '@cdc/core/helpers/cloneConfig'
|
|
8
|
+
import { extractDataAndMetadata } from '@cdc/core/helpers/extractDataAndMetadata'
|
|
8
9
|
|
|
9
10
|
const buildQueryString = (params: Record<string, string>): string =>
|
|
10
11
|
Object.keys(params)
|
|
@@ -34,6 +35,7 @@ export const reloadURLData = async (config: MapConfig, setConfig: (config: MapCo
|
|
|
34
35
|
|
|
35
36
|
let dataUrlFinal = `${dataUrl.origin}${dataUrl.pathname}${buildQueryString(qsParams)}`
|
|
36
37
|
let data
|
|
38
|
+
let dataMetadata: Record<string, string> = {}
|
|
37
39
|
|
|
38
40
|
try {
|
|
39
41
|
const regex = /(?:\.([^.]+))?$/
|
|
@@ -47,7 +49,10 @@ export const reloadURLData = async (config: MapConfig, setConfig: (config: MapCo
|
|
|
47
49
|
return parsedCsv.data
|
|
48
50
|
})
|
|
49
51
|
} else if ('json' === ext || isSolrJson(dataUrlFinal)) {
|
|
50
|
-
|
|
52
|
+
const json = await fetch(dataUrlFinal).then(response => response.json())
|
|
53
|
+
const extracted = extractDataAndMetadata(json)
|
|
54
|
+
data = extracted.data
|
|
55
|
+
dataMetadata = extracted.dataMetadata
|
|
51
56
|
} else {
|
|
52
57
|
data = []
|
|
53
58
|
}
|
|
@@ -64,6 +69,7 @@ export const reloadURLData = async (config: MapConfig, setConfig: (config: MapCo
|
|
|
64
69
|
|
|
65
70
|
const newConfig = cloneConfig(config)
|
|
66
71
|
newConfig.data = data
|
|
72
|
+
newConfig.dataMetadata = dataMetadata
|
|
67
73
|
newConfig.runtimeDataUrl = dataUrlFinal
|
|
68
74
|
|
|
69
75
|
setConfig(newConfig)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useState, useCallback,
|
|
1
|
+
import { useState, useCallback, useRef } from 'react'
|
|
2
2
|
import { type ViewPort } from '@cdc/core/types/ViewPort'
|
|
3
3
|
import { type DimensionsType } from '@cdc/core/types/Dimensions'
|
|
4
4
|
import { EDITOR_WIDTH } from '@cdc/core/helpers/constants'
|
|
@@ -11,37 +11,51 @@ export const useResizeObserver = (isEditor: boolean) => {
|
|
|
11
11
|
const [vizViewport, setVizViewport] = useState<ViewPort>(null)
|
|
12
12
|
const [container, setContainer] = useState<HTMLElement | null>(null)
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
// Keep isEditor fresh in the observer callback without recreating the observer
|
|
15
|
+
const isEditorRef = useRef(isEditor)
|
|
16
|
+
isEditorRef.current = isEditor
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
width = editorIsOpen ? width - EDITOR_WIDTH : width
|
|
18
|
+
const resizeObserverRef = useRef<ResizeObserver | null>(null)
|
|
20
19
|
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
const outerContainerRef = useCallback(node => {
|
|
21
|
+
// Tear down any previous observer (node swap or unmount)
|
|
22
|
+
if (resizeObserverRef.current) {
|
|
23
|
+
resizeObserverRef.current.disconnect()
|
|
24
|
+
resizeObserverRef.current = null
|
|
25
|
+
}
|
|
23
26
|
|
|
24
|
-
|
|
27
|
+
if (node !== null) {
|
|
28
|
+
resizeObserverRef.current = new ResizeObserver(entries => {
|
|
29
|
+
for (let entry of entries) {
|
|
30
|
+
let { width, height } = entry.contentRect
|
|
25
31
|
|
|
26
|
-
|
|
27
|
-
|
|
32
|
+
const editorIsOpen = isEditorRef.current && !!document.querySelector('.editor-panel:not(.hidden)')
|
|
33
|
+
width = editorIsOpen ? width - EDITOR_WIDTH : width
|
|
28
34
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
})
|
|
35
|
+
// Account for 1rem padding in editor mode
|
|
36
|
+
width = width - (isEditorRef.current ? 36 : 0)
|
|
32
37
|
|
|
33
|
-
|
|
34
|
-
if (node !== null) {
|
|
35
|
-
resizeObserver.observe(node)
|
|
36
|
-
}
|
|
37
|
-
setContainer(node)
|
|
38
|
+
const newViewport = getViewport(width)
|
|
38
39
|
|
|
39
|
-
|
|
40
|
-
|
|
40
|
+
setCurrentViewport(newViewport)
|
|
41
|
+
setVizViewport(newViewport)
|
|
42
|
+
setDimensions([width, height])
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
resizeObserverRef.current.observe(node)
|
|
41
46
|
}
|
|
47
|
+
|
|
48
|
+
setContainer(node)
|
|
42
49
|
}, [])
|
|
43
50
|
|
|
44
|
-
return {
|
|
51
|
+
return {
|
|
52
|
+
resizeObserver: resizeObserverRef.current,
|
|
53
|
+
dimensions,
|
|
54
|
+
currentViewport,
|
|
55
|
+
vizViewport,
|
|
56
|
+
outerContainerRef,
|
|
57
|
+
container
|
|
58
|
+
}
|
|
45
59
|
}
|
|
46
60
|
|
|
47
61
|
export default useResizeObserver
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { renderHook } from '@testing-library/react'
|
|
2
|
+
import useTooltip from './useTooltip'
|
|
3
|
+
|
|
4
|
+
const supportedStatesFipsCodes = {
|
|
5
|
+
'01': 'Alabama'
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const createConfig = (hideGeoColumnInTooltip: boolean) => ({
|
|
9
|
+
general: {
|
|
10
|
+
geoType: 'world',
|
|
11
|
+
type: 'map',
|
|
12
|
+
hideGeoColumnInTooltip,
|
|
13
|
+
hidePrimaryColumnInTooltip: false,
|
|
14
|
+
geoLabelOverride: ''
|
|
15
|
+
},
|
|
16
|
+
columns: {
|
|
17
|
+
geo: {
|
|
18
|
+
name: 'Country',
|
|
19
|
+
label: 'Location',
|
|
20
|
+
tooltip: true,
|
|
21
|
+
displayColumn: ''
|
|
22
|
+
},
|
|
23
|
+
primary: {
|
|
24
|
+
name: 'Value',
|
|
25
|
+
label: 'Value',
|
|
26
|
+
tooltip: true
|
|
27
|
+
},
|
|
28
|
+
navigate: {
|
|
29
|
+
name: ''
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
legend: {
|
|
33
|
+
specialClasses: []
|
|
34
|
+
},
|
|
35
|
+
tooltips: {
|
|
36
|
+
noDataLabel: 'No Data'
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
describe('useTooltip', () => {
|
|
41
|
+
it('hides the geography column label in the tooltip body when configured', () => {
|
|
42
|
+
const row = { Country: 'ssd', Value: 10 }
|
|
43
|
+
|
|
44
|
+
const { result: visibleResult } = renderHook(() =>
|
|
45
|
+
useTooltip({
|
|
46
|
+
config: createConfig(false),
|
|
47
|
+
supportedStatesFipsCodes
|
|
48
|
+
})
|
|
49
|
+
)
|
|
50
|
+
const { result: hiddenResult } = renderHook(() =>
|
|
51
|
+
useTooltip({
|
|
52
|
+
config: createConfig(true),
|
|
53
|
+
supportedStatesFipsCodes
|
|
54
|
+
})
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
const visibleTooltip = visibleResult.current.buildTooltip(row, 'South Sudan')
|
|
58
|
+
const hiddenTooltip = hiddenResult.current.buildTooltip(row, 'South Sudan')
|
|
59
|
+
|
|
60
|
+
expect(visibleTooltip).toContain('Location: South Sudan')
|
|
61
|
+
expect(hiddenTooltip).not.toContain('Location: South Sudan')
|
|
62
|
+
expect(hiddenTooltip).toContain('<li class="tooltip-body">South Sudan</li>')
|
|
63
|
+
})
|
|
64
|
+
})
|
package/src/hooks/useTooltip.ts
CHANGED
|
@@ -27,8 +27,9 @@ const useTooltip = props => {
|
|
|
27
27
|
* @param {String} geoName - feature name
|
|
28
28
|
* @returns {String} text to be appended to toolTipText
|
|
29
29
|
*/
|
|
30
|
-
const handleTooltipGeoColumn = (geoName: string) => {
|
|
30
|
+
const handleTooltipGeoColumn = (geoName: string, row?) => {
|
|
31
31
|
const { hideGeoColumnInTooltip } = config.general as { hideGeoColumnInTooltip: boolean }
|
|
32
|
+
const displayOverride = row?.[config.columns.geo?.displayColumn]
|
|
32
33
|
|
|
33
34
|
const handleTooltipPrefix = (toolTipText: string) => {
|
|
34
35
|
const { geoType, geoLabelOverride } = config.general
|
|
@@ -54,8 +55,11 @@ const useTooltip = props => {
|
|
|
54
55
|
|
|
55
56
|
const prefix = handleTooltipPrefix('')
|
|
56
57
|
|
|
57
|
-
if (hideGeoColumnInTooltip) return `<strong>${displayGeoName(geoName)}</strong>`
|
|
58
|
-
return `<p class="tooltip-heading" style="text-transform: none;">${prefix}${displayGeoName(
|
|
58
|
+
if (hideGeoColumnInTooltip) return `<strong>${displayGeoName(geoName, displayOverride)}</strong>`
|
|
59
|
+
return `<p class="tooltip-heading" style="text-transform: none;">${prefix}${displayGeoName(
|
|
60
|
+
geoName,
|
|
61
|
+
displayOverride
|
|
62
|
+
)}</p>`
|
|
59
63
|
}
|
|
60
64
|
|
|
61
65
|
/**
|
|
@@ -74,13 +78,29 @@ const useTooltip = props => {
|
|
|
74
78
|
}
|
|
75
79
|
|
|
76
80
|
const handleTooltipPrimaryColumn = (tooltipValue, column) => {
|
|
77
|
-
const { hidePrimaryColumnInTooltip } = config.general as {
|
|
81
|
+
const { hidePrimaryColumnInTooltip, hideGeoColumnInTooltip } = config.general as {
|
|
82
|
+
hidePrimaryColumnInTooltip: boolean
|
|
83
|
+
hideGeoColumnInTooltip: boolean
|
|
84
|
+
}
|
|
78
85
|
let tooltipPrefix = column.label?.length > 0 ? column.label : ''
|
|
79
|
-
|
|
80
|
-
|
|
86
|
+
const hidePrimaryLabel = column.name === config.columns.primary.name && hidePrimaryColumnInTooltip
|
|
87
|
+
const hideGeoLabel = column.name === config.columns.geo.name && hideGeoColumnInTooltip
|
|
88
|
+
|
|
89
|
+
if (hidePrimaryLabel || hideGeoLabel || !tooltipPrefix) return `<li class="tooltip-body">${tooltipValue}</li>`
|
|
81
90
|
return `<li class="tooltip-body">${tooltipPrefix}: ${tooltipValue}</li>`
|
|
82
91
|
}
|
|
83
92
|
|
|
93
|
+
const formatTooltipColumnValue = (row, column, columnKey) => {
|
|
94
|
+
if (!row) return config.tooltips?.noDataLabel || 'No Data'
|
|
95
|
+
|
|
96
|
+
if (column.name === config.columns.geo.name) {
|
|
97
|
+
const displayOverride = row?.[config.columns.geo?.displayColumn]
|
|
98
|
+
return displayGeoName(row[column.name], displayOverride)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return displayDataAsText(row[column.name], columnKey, config)
|
|
102
|
+
}
|
|
103
|
+
|
|
84
104
|
/**
|
|
85
105
|
*
|
|
86
106
|
* @param {String} toolTipText - previous tooltipText to build upon
|
|
@@ -106,7 +126,7 @@ const useTooltip = props => {
|
|
|
106
126
|
let tooltipValue = handleTooltipSpecialClassText(specialClasses, column, row, '', columnKey)
|
|
107
127
|
|
|
108
128
|
if (!tooltipValue) {
|
|
109
|
-
tooltipValue = row
|
|
129
|
+
tooltipValue = formatTooltipColumnValue(row, column, columnKey)
|
|
110
130
|
}
|
|
111
131
|
|
|
112
132
|
toolTipText += handleTooltipPrimaryColumn(tooltipValue, column)
|
|
@@ -125,7 +145,7 @@ const useTooltip = props => {
|
|
|
125
145
|
toolTipText += handleTooltipStateNameColumn(toolTipText, row)
|
|
126
146
|
|
|
127
147
|
// Handle Columns > Data Column In tooltips
|
|
128
|
-
toolTipText += handleTooltipGeoColumn(geoName)
|
|
148
|
+
toolTipText += handleTooltipGeoColumn(geoName, row)
|
|
129
149
|
|
|
130
150
|
// Handle Columns > Geography Column In tooltips
|
|
131
151
|
toolTipText = handleTooltipColumns(toolTipText, row)
|