@cdc/map 4.26.4 → 4.26.5
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/CONFIG.md +70 -37
- package/LICENSE +201 -0
- package/README.md +6 -2
- package/dist/cdcmap.js +23502 -22964
- package/examples/default-county.json +3 -0
- package/examples/minimal-example.json +6 -2
- package/package.json +3 -3
- package/src/CdcMapComponent.tsx +13 -3
- package/src/_stories/CdcMap.AltText.stories.tsx +122 -0
- package/src/_stories/CdcMap.Editor.ColumnsSectionTests.stories.tsx +15 -16
- package/src/_stories/CdcMap.FocusVisibility.stories.tsx +87 -0
- package/src/_stories/CdcMap.HiddenMount.stories.tsx +69 -0
- package/src/_stories/CdcMap.ResetBehavior.stories.tsx +32 -0
- package/src/_stories/CdcMap.Zoom.stories.tsx +111 -0
- package/src/_stories/CdcMap.smoke.stories.tsx +48 -0
- package/src/_stories/_mock/alt_text_metadata.json +65 -0
- package/src/_stories/_mock/world-bubble-reset.json +138 -0
- package/src/_stories/_mock/world-data-zoom-filters.json +166 -0
- package/src/components/BubbleList.tsx +13 -0
- package/src/components/EditorPanel/components/EditorPanel.tsx +134 -0
- package/src/components/FilterControls.tsx +21 -0
- package/src/components/SmallMultiples/SmallMultiples.tsx +2 -2
- package/src/components/UsaMap/components/UsaMap.County.tsx +39 -9
- package/src/components/UsaMap/components/UsaMap.Region.tsx +5 -2
- package/src/components/UsaMap/components/UsaMap.SingleState.tsx +33 -10
- package/src/components/UsaMap/components/UsaMap.State.tsx +9 -2
- package/src/components/WorldMap/WorldMap.tsx +37 -4
- package/src/components/ZoomableGroup.tsx +23 -3
- package/src/components/filterControls.styles.css +6 -0
- package/src/data/initial-state.js +2 -0
- package/src/helpers/countyTerritories.ts +1 -1
- package/src/helpers/generateRuntimeFilters.ts +2 -1
- package/src/helpers/handleMapAriaLabels.ts +45 -30
- package/src/helpers/shouldAutoResetSingleStateZoom.ts +22 -0
- package/src/helpers/tests/handleMapAriaLabels.test.ts +71 -0
- package/src/helpers/tests/shouldAutoResetSingleStateZoom.test.ts +71 -0
- package/src/hooks/useGeoClickHandler.ts +13 -1
- package/src/hooks/useStateZoom.tsx +39 -20
- package/src/hooks/useTooltip.test.tsx +2 -16
- package/src/index.jsx +5 -2
- package/src/scss/main.scss +6 -21
- package/src/scss/map.scss +20 -0
- package/src/types/MapConfig.ts +5 -0
- package/src/types/MapContext.ts +3 -0
|
@@ -12,8 +12,10 @@ import MultiCountryHide from './_mock/multi-country-hide.json'
|
|
|
12
12
|
import SingleStateWithFilters from './_mock/DEV-8942.json'
|
|
13
13
|
import exampleCityState from './_mock/example-city-state.json'
|
|
14
14
|
import USBubbleCities from './_mock/us-bubble-cities.json'
|
|
15
|
+
import worldBubbleReset from './_mock/world-bubble-reset.json'
|
|
15
16
|
import { editConfigKeys } from '@cdc/core/helpers/configHelpers'
|
|
16
17
|
import exampleLegendBins from './_mock/legend-bins.json'
|
|
18
|
+
import { performAndAssert, waitForPresence } from '@cdc/core/helpers/testing'
|
|
17
19
|
|
|
18
20
|
// Fallback step function for test descriptions
|
|
19
21
|
const step = async (description: string, fn: () => Promise<void> | void) => {
|
|
@@ -198,6 +200,52 @@ export const Bubble_Map: Story = {
|
|
|
198
200
|
}
|
|
199
201
|
}
|
|
200
202
|
|
|
203
|
+
export const World_Bubble_Reset_Restores_All_Bubbles: Story = {
|
|
204
|
+
args: {
|
|
205
|
+
config: worldBubbleReset,
|
|
206
|
+
isEditor: true
|
|
207
|
+
},
|
|
208
|
+
play: async ({ canvasElement }) => {
|
|
209
|
+
await assertVisualizationRendered(canvasElement)
|
|
210
|
+
await waitForPresence('circle.bubble.country--France', canvasElement)
|
|
211
|
+
|
|
212
|
+
const getBubbleState = () => ({
|
|
213
|
+
bubbleCount: canvasElement.querySelectorAll('circle.bubble[data-tooltip-id]').length
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
const dispatchBubblePointerClick = (bubble: Element) => {
|
|
217
|
+
const rect = bubble.getBoundingClientRect()
|
|
218
|
+
const clientX = rect.left + rect.width / 2
|
|
219
|
+
const clientY = rect.top + rect.height / 2
|
|
220
|
+
|
|
221
|
+
bubble.dispatchEvent(new PointerEvent('pointerdown', { bubbles: true, clientX, clientY }))
|
|
222
|
+
bubble.dispatchEvent(new PointerEvent('pointerup', { bubbles: true, clientX, clientY }))
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
await performAndAssert(
|
|
226
|
+
'World bubble click narrows the rendered bubble set',
|
|
227
|
+
getBubbleState,
|
|
228
|
+
async () => {
|
|
229
|
+
const franceBubble = canvasElement.querySelector('circle.bubble.country--France')
|
|
230
|
+
expect(franceBubble).toBeTruthy()
|
|
231
|
+
dispatchBubblePointerClick(franceBubble as Element)
|
|
232
|
+
},
|
|
233
|
+
(before, after) => after.bubbleCount > 0 && after.bubbleCount < before.bubbleCount
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
await performAndAssert(
|
|
237
|
+
'Reset Zoom restores all world bubbles',
|
|
238
|
+
getBubbleState,
|
|
239
|
+
async () => {
|
|
240
|
+
const resetButton = canvasElement.querySelector('button[aria-label="Reset Zoom"]') as HTMLButtonElement | null
|
|
241
|
+
expect(resetButton).toBeTruthy()
|
|
242
|
+
resetButton?.click()
|
|
243
|
+
},
|
|
244
|
+
(_before, after) => after.bubbleCount === worldBubbleReset.data.length
|
|
245
|
+
)
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
201
249
|
export const HHS_Region_Map: Story = {
|
|
202
250
|
args: {
|
|
203
251
|
configUrl: 'https://www.cdc.gov/wcms/4.0/cdc-wp/data-presentation/examples/example-hhs-regions-data.json'
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "4.26.4",
|
|
3
|
+
"color": "pinkpurple",
|
|
4
|
+
"general": {
|
|
5
|
+
"title": "COVID-19 Case Rates by State",
|
|
6
|
+
"geoType": "us",
|
|
7
|
+
"type": "data",
|
|
8
|
+
"showTitle": true
|
|
9
|
+
},
|
|
10
|
+
"type": "map",
|
|
11
|
+
"dataMetadata": {
|
|
12
|
+
"altDescription": "Choropleth map of the United States showing COVID-19 case rates per 100,000 population. Rates are highest in the Southeast, with Alabama, Mississippi, and Louisiana exceeding 50 per 100,000.",
|
|
13
|
+
"lastUpdated": "2024-09-21",
|
|
14
|
+
"source": "CDC COVID Data Tracker"
|
|
15
|
+
},
|
|
16
|
+
"columns": {
|
|
17
|
+
"geo": {
|
|
18
|
+
"name": "FIPS Codes",
|
|
19
|
+
"label": "Location",
|
|
20
|
+
"tooltip": false,
|
|
21
|
+
"dataTable": true
|
|
22
|
+
},
|
|
23
|
+
"primary": {
|
|
24
|
+
"name": "Rate",
|
|
25
|
+
"label": "Rate per 100k",
|
|
26
|
+
"prefix": "",
|
|
27
|
+
"suffix": "",
|
|
28
|
+
"tooltip": true,
|
|
29
|
+
"dataTable": true
|
|
30
|
+
},
|
|
31
|
+
"navigate": { "name": "" },
|
|
32
|
+
"latitude": { "name": "" },
|
|
33
|
+
"longitude": { "name": "" }
|
|
34
|
+
},
|
|
35
|
+
"legend": {
|
|
36
|
+
"type": "equalnumber",
|
|
37
|
+
"numberOfItems": 3,
|
|
38
|
+
"position": "side",
|
|
39
|
+
"style": "circles",
|
|
40
|
+
"title": "Rate per 100k",
|
|
41
|
+
"description": "",
|
|
42
|
+
"descriptions": {},
|
|
43
|
+
"specialClasses": [],
|
|
44
|
+
"unified": false,
|
|
45
|
+
"singleColumn": false,
|
|
46
|
+
"singleRow": false,
|
|
47
|
+
"verticalSorted": false,
|
|
48
|
+
"showSpecialClassesLast": false,
|
|
49
|
+
"dynamicDescription": false,
|
|
50
|
+
"hideBorder": false
|
|
51
|
+
},
|
|
52
|
+
"filters": [],
|
|
53
|
+
"filterBehavior": "Filter Change",
|
|
54
|
+
"data": [
|
|
55
|
+
{ "FIPS Codes": "01", "Rate": 55 },
|
|
56
|
+
{ "FIPS Codes": "04", "Rate": 22 },
|
|
57
|
+
{ "FIPS Codes": "06", "Rate": 31 },
|
|
58
|
+
{ "FIPS Codes": "12", "Rate": 44 },
|
|
59
|
+
{ "FIPS Codes": "13", "Rate": 48 },
|
|
60
|
+
{ "FIPS Codes": "22", "Rate": 52 },
|
|
61
|
+
{ "FIPS Codes": "28", "Rate": 58 },
|
|
62
|
+
{ "FIPS Codes": "36", "Rate": 27 },
|
|
63
|
+
{ "FIPS Codes": "48", "Rate": 38 }
|
|
64
|
+
]
|
|
65
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
{
|
|
2
|
+
"annotations": [],
|
|
3
|
+
"general": {
|
|
4
|
+
"title": "World Bubble Reset Demo",
|
|
5
|
+
"subtext": "Click a bubble to zoom, then use Reset Zoom to restore all bubbles.",
|
|
6
|
+
"type": "bubble",
|
|
7
|
+
"geoType": "world",
|
|
8
|
+
"headerColor": "theme-blue",
|
|
9
|
+
"showSidebar": true,
|
|
10
|
+
"showTitle": true,
|
|
11
|
+
"showDownloadButton": true,
|
|
12
|
+
"expandDataTable": false,
|
|
13
|
+
"backgroundColor": "#ffffff",
|
|
14
|
+
"geoBorderColor": "darkGray",
|
|
15
|
+
"territoriesLabel": "Territories",
|
|
16
|
+
"language": "en",
|
|
17
|
+
"hasRegions": false,
|
|
18
|
+
"showDownloadMediaButton": false,
|
|
19
|
+
"fullBorder": false,
|
|
20
|
+
"palette": {
|
|
21
|
+
"isReversed": false
|
|
22
|
+
},
|
|
23
|
+
"allowMapZoom": true,
|
|
24
|
+
"hideGeoColumnInTooltip": false,
|
|
25
|
+
"hidePrimaryColumnInTooltip": false,
|
|
26
|
+
"geoLabelOverride": "",
|
|
27
|
+
"noDataFoundMessage": "No data found"
|
|
28
|
+
},
|
|
29
|
+
"type": "map",
|
|
30
|
+
"color": "bluegreen",
|
|
31
|
+
"columns": {
|
|
32
|
+
"geo": {
|
|
33
|
+
"name": "country",
|
|
34
|
+
"label": "Country",
|
|
35
|
+
"tooltip": true,
|
|
36
|
+
"dataTable": true
|
|
37
|
+
},
|
|
38
|
+
"primary": {
|
|
39
|
+
"dataTable": true,
|
|
40
|
+
"tooltip": true,
|
|
41
|
+
"prefix": "",
|
|
42
|
+
"suffix": "",
|
|
43
|
+
"name": "cases",
|
|
44
|
+
"label": "Cases"
|
|
45
|
+
},
|
|
46
|
+
"navigate": {
|
|
47
|
+
"name": ""
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
"legend": {
|
|
51
|
+
"descriptions": {},
|
|
52
|
+
"specialClasses": [],
|
|
53
|
+
"unified": false,
|
|
54
|
+
"singleColumn": true,
|
|
55
|
+
"dynamicDescription": false,
|
|
56
|
+
"type": "equalinterval",
|
|
57
|
+
"numberOfItems": 5,
|
|
58
|
+
"position": "side",
|
|
59
|
+
"title": "Cases",
|
|
60
|
+
"showTitle": true,
|
|
61
|
+
"reversedOrder": false,
|
|
62
|
+
"singleColumnLegend": false,
|
|
63
|
+
"hideBorder": false
|
|
64
|
+
},
|
|
65
|
+
"filters": [],
|
|
66
|
+
"table": {
|
|
67
|
+
"label": "Data Table",
|
|
68
|
+
"expanded": false,
|
|
69
|
+
"limitHeight": false,
|
|
70
|
+
"height": "",
|
|
71
|
+
"caption": "",
|
|
72
|
+
"showDownloadUrl": false,
|
|
73
|
+
"showDataTableLink": true,
|
|
74
|
+
"showFullGeoNameInCSV": false,
|
|
75
|
+
"forceDisplay": true,
|
|
76
|
+
"download": true,
|
|
77
|
+
"indexLabel": "",
|
|
78
|
+
"wrapColumns": false,
|
|
79
|
+
"showDownloadLinkBelow": true
|
|
80
|
+
},
|
|
81
|
+
"tooltips": {
|
|
82
|
+
"appearanceType": "hover",
|
|
83
|
+
"linkLabel": "Learn More",
|
|
84
|
+
"capitalizeLabels": true,
|
|
85
|
+
"opacity": 90
|
|
86
|
+
},
|
|
87
|
+
"runtime": {
|
|
88
|
+
"editorErrorMessage": []
|
|
89
|
+
},
|
|
90
|
+
"visual": {
|
|
91
|
+
"minBubbleSize": 10,
|
|
92
|
+
"maxBubbleSize": 28,
|
|
93
|
+
"extraBubbleBorder": false,
|
|
94
|
+
"cityStyle": "circle",
|
|
95
|
+
"geoCodeCircleSize": 12,
|
|
96
|
+
"showBubbleZeros": true,
|
|
97
|
+
"cityStyleLabel": "Bubble",
|
|
98
|
+
"additionalCityStyles": []
|
|
99
|
+
},
|
|
100
|
+
"mapPosition": {
|
|
101
|
+
"coordinates": [0, 30],
|
|
102
|
+
"zoom": 1
|
|
103
|
+
},
|
|
104
|
+
"map": {
|
|
105
|
+
"layers": [],
|
|
106
|
+
"patterns": []
|
|
107
|
+
},
|
|
108
|
+
"hexMap": {
|
|
109
|
+
"type": "",
|
|
110
|
+
"shapeGroups": []
|
|
111
|
+
},
|
|
112
|
+
"data": [
|
|
113
|
+
{
|
|
114
|
+
"country": "France",
|
|
115
|
+
"cases": 120
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
"country": "Brazil",
|
|
119
|
+
"cases": 340
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
"country": "Japan",
|
|
123
|
+
"cases": 280
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
"country": "Australia",
|
|
127
|
+
"cases": 200
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
"country": "South Africa",
|
|
131
|
+
"cases": 160
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
"country": "Canada",
|
|
135
|
+
"cases": 240
|
|
136
|
+
}
|
|
137
|
+
]
|
|
138
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
{
|
|
2
|
+
"annotations": [],
|
|
3
|
+
"general": {
|
|
4
|
+
"title": "World Data Map Zoom With Filters",
|
|
5
|
+
"subtext": "Use the region filter, zoom into countries, and test reset behavior on a world data map.",
|
|
6
|
+
"type": "data",
|
|
7
|
+
"geoType": "world",
|
|
8
|
+
"headerColor": "theme-blue",
|
|
9
|
+
"showSidebar": true,
|
|
10
|
+
"showTitle": true,
|
|
11
|
+
"showDownloadButton": true,
|
|
12
|
+
"expandDataTable": false,
|
|
13
|
+
"backgroundColor": "#ffffff",
|
|
14
|
+
"geoBorderColor": "darkGray",
|
|
15
|
+
"territoriesLabel": "Territories",
|
|
16
|
+
"language": "en",
|
|
17
|
+
"hasRegions": false,
|
|
18
|
+
"showDownloadMediaButton": false,
|
|
19
|
+
"fullBorder": false,
|
|
20
|
+
"palette": {
|
|
21
|
+
"isReversed": false
|
|
22
|
+
},
|
|
23
|
+
"allowMapZoom": true,
|
|
24
|
+
"hideGeoColumnInTooltip": false,
|
|
25
|
+
"hidePrimaryColumnInTooltip": false,
|
|
26
|
+
"geoLabelOverride": "",
|
|
27
|
+
"noDataFoundMessage": "No data found"
|
|
28
|
+
},
|
|
29
|
+
"type": "map",
|
|
30
|
+
"color": "bluegreen",
|
|
31
|
+
"columns": {
|
|
32
|
+
"geo": {
|
|
33
|
+
"name": "Country",
|
|
34
|
+
"label": "Country",
|
|
35
|
+
"tooltip": true,
|
|
36
|
+
"dataTable": true
|
|
37
|
+
},
|
|
38
|
+
"primary": {
|
|
39
|
+
"name": "Value",
|
|
40
|
+
"label": "Value",
|
|
41
|
+
"prefix": "",
|
|
42
|
+
"suffix": "",
|
|
43
|
+
"tooltip": true,
|
|
44
|
+
"dataTable": true
|
|
45
|
+
},
|
|
46
|
+
"navigate": {
|
|
47
|
+
"name": ""
|
|
48
|
+
},
|
|
49
|
+
"additionalColumn1": {
|
|
50
|
+
"name": "Region",
|
|
51
|
+
"label": "Region",
|
|
52
|
+
"tooltip": true,
|
|
53
|
+
"dataTable": true,
|
|
54
|
+
"prefix": "",
|
|
55
|
+
"suffix": ""
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"legend": {
|
|
59
|
+
"descriptions": {},
|
|
60
|
+
"specialClasses": [],
|
|
61
|
+
"unified": false,
|
|
62
|
+
"singleColumn": true,
|
|
63
|
+
"dynamicDescription": false,
|
|
64
|
+
"type": "equalinterval",
|
|
65
|
+
"numberOfItems": 5,
|
|
66
|
+
"position": "side",
|
|
67
|
+
"title": "Value",
|
|
68
|
+
"showTitle": true,
|
|
69
|
+
"reversedOrder": false,
|
|
70
|
+
"singleColumnLegend": false,
|
|
71
|
+
"hideBorder": false
|
|
72
|
+
},
|
|
73
|
+
"filters": [
|
|
74
|
+
{
|
|
75
|
+
"order": "asc",
|
|
76
|
+
"label": "Region",
|
|
77
|
+
"columnName": "Region",
|
|
78
|
+
"values": ["Europe", "Americas", "Asia-Pacific"],
|
|
79
|
+
"active": "Europe",
|
|
80
|
+
"filterStyle": "dropdown"
|
|
81
|
+
}
|
|
82
|
+
],
|
|
83
|
+
"table": {
|
|
84
|
+
"label": "Data Table",
|
|
85
|
+
"expanded": false,
|
|
86
|
+
"limitHeight": false,
|
|
87
|
+
"height": "",
|
|
88
|
+
"caption": "",
|
|
89
|
+
"showDownloadUrl": false,
|
|
90
|
+
"showDataTableLink": true,
|
|
91
|
+
"showFullGeoNameInCSV": false,
|
|
92
|
+
"forceDisplay": true,
|
|
93
|
+
"download": true,
|
|
94
|
+
"indexLabel": "",
|
|
95
|
+
"wrapColumns": false,
|
|
96
|
+
"showDownloadLinkBelow": true
|
|
97
|
+
},
|
|
98
|
+
"tooltips": {
|
|
99
|
+
"appearanceType": "hover",
|
|
100
|
+
"linkLabel": "Learn More",
|
|
101
|
+
"capitalizeLabels": true,
|
|
102
|
+
"opacity": 90
|
|
103
|
+
},
|
|
104
|
+
"runtime": {
|
|
105
|
+
"editorErrorMessage": []
|
|
106
|
+
},
|
|
107
|
+
"visual": {
|
|
108
|
+
"minBubbleSize": 10,
|
|
109
|
+
"maxBubbleSize": 28,
|
|
110
|
+
"extraBubbleBorder": false,
|
|
111
|
+
"cityStyle": "circle",
|
|
112
|
+
"geoCodeCircleSize": 12,
|
|
113
|
+
"showBubbleZeros": true,
|
|
114
|
+
"cityStyleLabel": "Bubble",
|
|
115
|
+
"additionalCityStyles": []
|
|
116
|
+
},
|
|
117
|
+
"mapPosition": {
|
|
118
|
+
"coordinates": [0, 30],
|
|
119
|
+
"zoom": 1
|
|
120
|
+
},
|
|
121
|
+
"map": {
|
|
122
|
+
"layers": [],
|
|
123
|
+
"patterns": []
|
|
124
|
+
},
|
|
125
|
+
"hexMap": {
|
|
126
|
+
"type": "",
|
|
127
|
+
"shapeGroups": []
|
|
128
|
+
},
|
|
129
|
+
"data": [
|
|
130
|
+
{
|
|
131
|
+
"Country": "France",
|
|
132
|
+
"Value": 120,
|
|
133
|
+
"Region": "Europe"
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
"Country": "Germany",
|
|
137
|
+
"Value": 150,
|
|
138
|
+
"Region": "Europe"
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
"Country": "Italy",
|
|
142
|
+
"Value": 105,
|
|
143
|
+
"Region": "Europe"
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
"Country": "Brazil",
|
|
147
|
+
"Value": 210,
|
|
148
|
+
"Region": "Americas"
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
"Country": "Canada",
|
|
152
|
+
"Value": 175,
|
|
153
|
+
"Region": "Americas"
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
"Country": "Japan",
|
|
157
|
+
"Value": 190,
|
|
158
|
+
"Region": "Asia-Pacific"
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
"Country": "Australia",
|
|
162
|
+
"Value": 165,
|
|
163
|
+
"Region": "Asia-Pacific"
|
|
164
|
+
}
|
|
165
|
+
]
|
|
166
|
+
}
|
|
@@ -64,11 +64,16 @@ const BubbleList: React.FC<BubbleListProps> = ({ customProjection }) => {
|
|
|
64
64
|
|
|
65
65
|
// Zoom the map in...
|
|
66
66
|
dispatch({ type: 'SET_POSITION', payload: { coordinates: reversedCoordinates, zoom: 3 } })
|
|
67
|
+
dispatch({ type: 'SET_FILTERED_COUNTRY_CODE', payload: _filteredCountryCode })
|
|
67
68
|
|
|
68
69
|
// ...and show the data for the clicked country
|
|
69
70
|
dispatch({ type: 'SET_RUNTIME_DATA', payload: _tempRuntimeData })
|
|
70
71
|
}
|
|
71
72
|
|
|
73
|
+
const handleBubblePointerDown = (e: React.PointerEvent<SVGCircleElement> | React.MouseEvent<SVGCircleElement>) => {
|
|
74
|
+
e.preventDefault()
|
|
75
|
+
}
|
|
76
|
+
|
|
72
77
|
const sortedRuntimeData: DataRow = Object.values(runtimeData).sort((a, b) =>
|
|
73
78
|
a[primaryColumnName] < b[primaryColumnName] ? 1 : -1
|
|
74
79
|
)
|
|
@@ -112,7 +117,9 @@ const BubbleList: React.FC<BubbleListProps> = ({ customProjection }) => {
|
|
|
112
117
|
strokeWidth={1.25}
|
|
113
118
|
fillOpacity={0.4}
|
|
114
119
|
onMouseEnter={() => {}}
|
|
120
|
+
onMouseDown={handleBubblePointerDown}
|
|
115
121
|
onPointerDown={e => {
|
|
122
|
+
handleBubblePointerDown(e)
|
|
116
123
|
pointerX = e.clientX
|
|
117
124
|
pointerY = e.clientY
|
|
118
125
|
}}
|
|
@@ -148,7 +155,9 @@ const BubbleList: React.FC<BubbleListProps> = ({ customProjection }) => {
|
|
|
148
155
|
stroke={'white'}
|
|
149
156
|
strokeWidth={0.5}
|
|
150
157
|
onMouseEnter={() => {}}
|
|
158
|
+
onMouseDown={handleBubblePointerDown}
|
|
151
159
|
onPointerDown={e => {
|
|
160
|
+
handleBubblePointerDown(e)
|
|
152
161
|
pointerX = e.clientX
|
|
153
162
|
pointerY = e.clientY
|
|
154
163
|
}}
|
|
@@ -226,7 +235,9 @@ const BubbleList: React.FC<BubbleListProps> = ({ customProjection }) => {
|
|
|
226
235
|
strokeWidth={1.25}
|
|
227
236
|
fillOpacity={0.4}
|
|
228
237
|
onMouseEnter={() => {}}
|
|
238
|
+
onMouseDown={handleBubblePointerDown}
|
|
229
239
|
onPointerDown={e => {
|
|
240
|
+
handleBubblePointerDown(e)
|
|
230
241
|
pointerX = e.clientX
|
|
231
242
|
pointerY = e.clientY
|
|
232
243
|
}}
|
|
@@ -262,7 +273,9 @@ const BubbleList: React.FC<BubbleListProps> = ({ customProjection }) => {
|
|
|
262
273
|
strokeWidth={0.5}
|
|
263
274
|
fillOpacity={0.4}
|
|
264
275
|
onMouseEnter={() => {}}
|
|
276
|
+
onMouseDown={handleBubblePointerDown}
|
|
265
277
|
onPointerDown={e => {
|
|
278
|
+
handleBubblePointerDown(e)
|
|
266
279
|
pointerX = e.clientX
|
|
267
280
|
pointerY = e.clientY
|
|
268
281
|
}}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React, { useContext, useEffect, useState, useMemo, useRef } from 'react'
|
|
2
2
|
import { filterColorPalettes } from '@cdc/core/helpers/filterColorPalettes'
|
|
3
3
|
import { cloneConfig } from '@cdc/core/helpers/cloneConfig'
|
|
4
|
+
import { resolveAltTextDescription } from '@cdc/core/helpers/resolveAltTextDescription'
|
|
4
5
|
|
|
5
6
|
// Third Party
|
|
6
7
|
import {
|
|
@@ -1831,6 +1832,105 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
|
|
|
1831
1832
|
</Tooltip>
|
|
1832
1833
|
}
|
|
1833
1834
|
/>
|
|
1835
|
+
|
|
1836
|
+
{/* Accessible Alt Text Description */}
|
|
1837
|
+
{(() => {
|
|
1838
|
+
const metadataKeys = Object.keys(config.dataMetadata || {})
|
|
1839
|
+
const hasMetadata = metadataKeys.length > 0
|
|
1840
|
+
const descType = config.altText?.type || ''
|
|
1841
|
+
const resolvedDescription = resolveAltTextDescription(config.altText, config.dataMetadata)
|
|
1842
|
+
return (
|
|
1843
|
+
<>
|
|
1844
|
+
<Select
|
|
1845
|
+
value={descType}
|
|
1846
|
+
fieldName='altTextType'
|
|
1847
|
+
label='Alt Text Description'
|
|
1848
|
+
options={[
|
|
1849
|
+
{ value: '', label: 'None' },
|
|
1850
|
+
{ value: 'static', label: 'Static (manual text)' },
|
|
1851
|
+
{ value: 'metadata', label: 'Data File Metadata' }
|
|
1852
|
+
]}
|
|
1853
|
+
updateField={(_section, _subsection, _fieldName, value) => {
|
|
1854
|
+
if (value === '') {
|
|
1855
|
+
updateField(null, null, 'altText', undefined)
|
|
1856
|
+
} else {
|
|
1857
|
+
updateField(null, null, 'altText', { type: value as 'static' | 'metadata' })
|
|
1858
|
+
}
|
|
1859
|
+
}}
|
|
1860
|
+
tooltip={
|
|
1861
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
1862
|
+
<Tooltip.Target>
|
|
1863
|
+
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
1864
|
+
</Tooltip.Target>
|
|
1865
|
+
<Tooltip.Content>
|
|
1866
|
+
<p>
|
|
1867
|
+
Add a longer description for screen readers. The map title is always auto-generated.
|
|
1868
|
+
Use "Static" for manually written text, or "Data File Metadata" to pull it from a
|
|
1869
|
+
key in your data file.
|
|
1870
|
+
</p>
|
|
1871
|
+
</Tooltip.Content>
|
|
1872
|
+
</Tooltip>
|
|
1873
|
+
}
|
|
1874
|
+
/>
|
|
1875
|
+
{descType === 'static' && (
|
|
1876
|
+
<TextField
|
|
1877
|
+
value={config.altText?.value || ''}
|
|
1878
|
+
fieldName='altTextValue'
|
|
1879
|
+
type='textarea'
|
|
1880
|
+
label='Description Text'
|
|
1881
|
+
placeholder='Longer interpretive description of map insights...'
|
|
1882
|
+
updateField={(_section, _subsection, _fieldName, value) => {
|
|
1883
|
+
updateField(null, null, 'altText', { ...config.altText, value })
|
|
1884
|
+
}}
|
|
1885
|
+
/>
|
|
1886
|
+
)}
|
|
1887
|
+
{descType === 'metadata' && (
|
|
1888
|
+
<>
|
|
1889
|
+
{hasMetadata ? (
|
|
1890
|
+
<Select
|
|
1891
|
+
value={config.altText?.metadataKey || ''}
|
|
1892
|
+
fieldName='altTextMetadataKey'
|
|
1893
|
+
label='Description Metadata Field'
|
|
1894
|
+
options={[
|
|
1895
|
+
{ value: '', label: 'Select Metadata Field...' },
|
|
1896
|
+
...metadataKeys.map(key => ({
|
|
1897
|
+
value: key,
|
|
1898
|
+
label: `${key}: ${config.dataMetadata[key]}`
|
|
1899
|
+
}))
|
|
1900
|
+
]}
|
|
1901
|
+
updateField={(_section, _subsection, _fieldName, value) => {
|
|
1902
|
+
updateField(null, null, 'altText', { ...config.altText, metadataKey: value })
|
|
1903
|
+
}}
|
|
1904
|
+
/>
|
|
1905
|
+
) : (
|
|
1906
|
+
<span className='subtext'>
|
|
1907
|
+
No metadata fields are available. Your data file must be a JSON object with a{' '}
|
|
1908
|
+
<code>data</code> array and sibling key-value pairs, for example:{' '}
|
|
1909
|
+
<code>{`{ "altDescription": "...", "data": [...] }`}</code>
|
|
1910
|
+
</span>
|
|
1911
|
+
)}
|
|
1912
|
+
</>
|
|
1913
|
+
)}
|
|
1914
|
+
{resolvedDescription && (
|
|
1915
|
+
<div
|
|
1916
|
+
style={{
|
|
1917
|
+
marginTop: '1em',
|
|
1918
|
+
padding: '0.75em',
|
|
1919
|
+
background: '#f5f5f5',
|
|
1920
|
+
borderRadius: '4px',
|
|
1921
|
+
fontSize: '0.8em',
|
|
1922
|
+
textTransform: 'none'
|
|
1923
|
+
}}
|
|
1924
|
+
>
|
|
1925
|
+
<strong style={{ display: 'block', marginBottom: '0.25em' }}>Preview:</strong>
|
|
1926
|
+
<p data-testid='alt-text-desc-preview' style={{ margin: 0, fontStyle: 'italic' }}>
|
|
1927
|
+
{resolvedDescription}
|
|
1928
|
+
</p>
|
|
1929
|
+
</div>
|
|
1930
|
+
)}
|
|
1931
|
+
</>
|
|
1932
|
+
)
|
|
1933
|
+
})()}
|
|
1834
1934
|
</AccordionItemPanel>
|
|
1835
1935
|
</AccordionItem>
|
|
1836
1936
|
<AccordionItem>
|
|
@@ -3220,6 +3320,26 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
|
|
|
3220
3320
|
label='Show collapse below table'
|
|
3221
3321
|
updateField={updateField}
|
|
3222
3322
|
/>
|
|
3323
|
+
<CheckBox
|
|
3324
|
+
value={config.table.search ?? false}
|
|
3325
|
+
section='table'
|
|
3326
|
+
subsection={null}
|
|
3327
|
+
fieldName='search'
|
|
3328
|
+
label='Enable Search'
|
|
3329
|
+
updateField={updateField}
|
|
3330
|
+
/>
|
|
3331
|
+
{config.table.search && (
|
|
3332
|
+
<div className='ms-4 mt-2' style={{ maxWidth: 'calc(100% - 1.5rem)' }}>
|
|
3333
|
+
<TextField
|
|
3334
|
+
value={config.table.searchPlaceholder || ''}
|
|
3335
|
+
section='table'
|
|
3336
|
+
fieldName='searchPlaceholder'
|
|
3337
|
+
label='Search Placeholder Text'
|
|
3338
|
+
placeholder='Filter...'
|
|
3339
|
+
updateField={updateField}
|
|
3340
|
+
/>
|
|
3341
|
+
</div>
|
|
3342
|
+
)}
|
|
3223
3343
|
<Select
|
|
3224
3344
|
value={config.table.defaultSort?.column || ''}
|
|
3225
3345
|
fieldName='column'
|
|
@@ -3755,6 +3875,20 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
|
|
|
3755
3875
|
<span className='edit-label'>Allow Map Zooming</span>
|
|
3756
3876
|
</label>
|
|
3757
3877
|
)}
|
|
3878
|
+
{config.general.geoType === 'us' && (
|
|
3879
|
+
<label className='checkbox'>
|
|
3880
|
+
<input
|
|
3881
|
+
type='checkbox'
|
|
3882
|
+
checked={config.general.showClearSelectionButton !== false}
|
|
3883
|
+
onChange={event => {
|
|
3884
|
+
const _newConfig = cloneConfig(config)
|
|
3885
|
+
_newConfig.general.showClearSelectionButton = event.target.checked
|
|
3886
|
+
setConfig(_newConfig)
|
|
3887
|
+
}}
|
|
3888
|
+
/>
|
|
3889
|
+
<span className='edit-label'>Show Clear Selection Button</span>
|
|
3890
|
+
</label>
|
|
3891
|
+
)}
|
|
3758
3892
|
{config.general.type === 'bubble' && (
|
|
3759
3893
|
<label className='checkbox'>
|
|
3760
3894
|
<input
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import React, { useContext } from 'react'
|
|
2
|
+
import ConfigContext from '../context'
|
|
3
|
+
import { MapContext } from '../types/MapContext'
|
|
4
|
+
import './filterControls.styles.css'
|
|
5
|
+
|
|
6
|
+
const FilterControls: React.FC = () => {
|
|
7
|
+
const { config, clearSharedFilter, hasActiveSharedFilter } = useContext<MapContext>(ConfigContext)
|
|
8
|
+
|
|
9
|
+
if (config.general.showClearSelectionButton === false) return null
|
|
10
|
+
if (!hasActiveSharedFilter || !clearSharedFilter) return null
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<div className='filter-controls' data-html2canvas-ignore='true'>
|
|
14
|
+
<button className='cove-button' onClick={() => clearSharedFilter(config.uid)} aria-label='Clear Selection'>
|
|
15
|
+
Clear Selection
|
|
16
|
+
</button>
|
|
17
|
+
</div>
|
|
18
|
+
)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export default FilterControls
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useContext, useMemo, useRef,
|
|
1
|
+
import React, { useContext, useMemo, useRef, useCallback, useLayoutEffect } from 'react'
|
|
2
2
|
import SmallMultipleTile from './SmallMultipleTile'
|
|
3
3
|
import ConfigContext from '../../context'
|
|
4
4
|
import { MapContext } from '../../types/MapContext'
|
|
@@ -71,7 +71,7 @@ const SmallMultiples: React.FC<SmallMultiplesProps> = () => {
|
|
|
71
71
|
)
|
|
72
72
|
|
|
73
73
|
// Align tile header heights per row
|
|
74
|
-
|
|
74
|
+
useLayoutEffect(() => {
|
|
75
75
|
const headerEntries = Object.entries(headerRefs.current).filter(([_, ref]) => ref) as TileHeaderEntries
|
|
76
76
|
if (headerEntries.length === 0) return
|
|
77
77
|
|