@cdc/core 4.25.11 → 4.26.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/agents/qa-test-developer.md +126 -0
- package/CLAUDE.local.md +67 -0
- package/_stories/Gallery.Charts.stories.tsx +300 -0
- package/_stories/Gallery.DataBite.stories.tsx +79 -0
- package/_stories/Gallery.Maps.stories.tsx +239 -0
- package/_stories/Gallery.WaffleChart.stories.tsx +187 -0
- package/_stories/PageART.stories.tsx +193 -0
- package/_stories/PageBRFSS.stories.tsx +294 -0
- package/_stories/PageCancerRegistries.stories.tsx +199 -0
- package/_stories/PageEasternEquineEncephalitis.stories.tsx +216 -0
- package/_stories/PageExcessiveAlcoholUse.stories.tsx +201 -0
- package/_stories/PageMaternalMortality.stories.tsx +193 -0
- package/_stories/PageOralHealth.stories.tsx +201 -0
- package/_stories/PageRespiratory.stories.tsx +332 -0
- package/_stories/PageSmokingTobacco.stories.tsx +200 -0
- package/_stories/PageStateDiabetesProfiles.stories.tsx +201 -0
- package/_stories/PageWastewater.stories.tsx +477 -0
- package/_stories/VegaImport.stories.tsx +401 -0
- package/_stories/vega-fixtures/bars-with-line.json +444 -0
- package/_stories/vega-fixtures/bars.json +58 -0
- package/_stories/vega-fixtures/combo-bar-rolling-mean.json +88 -0
- package/_stories/vega-fixtures/combo.json +68 -0
- package/_stories/vega-fixtures/grouped-horizontal-bars.json +83 -0
- package/_stories/vega-fixtures/grouped-horizontal-bars2.json +231 -0
- package/_stories/vega-fixtures/horizontal-bar.json +427 -0
- package/_stories/vega-fixtures/horizontal-bars-with-bad-colors.json +197 -0
- package/_stories/vega-fixtures/horizontal-bars2.json +58 -0
- package/_stories/vega-fixtures/lines.json +227 -0
- package/_stories/vega-fixtures/measles-bars.json +348 -0
- package/_stories/vega-fixtures/measles-map.json +11101 -0
- package/_stories/vega-fixtures/measles-stacked-bars.json +2147 -0
- package/_stories/vega-fixtures/multi-dataset.json +255 -0
- package/_stories/vega-fixtures/no-data.json +14 -0
- package/_stories/vega-fixtures/pie-chart.json +94 -0
- package/_stories/vega-fixtures/repeat-spec.json +47 -0
- package/_stories/vega-fixtures/stacked-area.json +222 -0
- package/_stories/vega-fixtures/stacked-bar-with-rect.json +3412 -0
- package/_stories/vega-fixtures/stacked-bars-with-line.json +364 -0
- package/_stories/vega-fixtures/stacked-bars.json +212 -0
- package/_stories/vega-fixtures/stacked-horizontal-bars.json +140 -0
- package/_stories/vega-fixtures/warning-combo.json +59 -0
- package/_stories/vega-fixtures/warning-scatter-and-line.json +1182 -0
- package/assets/icon-chart-area.svg +1 -0
- package/assets/icon-chart-radar.svg +23 -0
- package/assets/icon-magnifying-glass.svg +5 -0
- package/assets/icon-warming-stripes.svg +13 -0
- package/assets/logo2.svg +31 -0
- package/components/AdvancedEditor/AdvancedEditor.tsx +4 -0
- package/components/AdvancedEditor/EmbedEditor.tsx +513 -0
- package/components/ComboBox/ComboBox.tsx +345 -0
- package/components/ComboBox/combobox.styles.css +185 -0
- package/components/ComboBox/index.ts +1 -0
- package/components/CustomColorsEditor/CustomColorsEditor.tsx +3 -10
- package/components/DataTable/DataTable.tsx +132 -58
- package/components/DataTable/data-table.css +216 -215
- package/components/DataTable/helpers/getSeriesName.ts +6 -0
- package/components/DataTable/helpers/mapCellMatrix.tsx +14 -6
- package/components/EditorPanel/ColumnsEditor.tsx +37 -19
- package/components/EditorPanel/DataTableEditor.tsx +51 -25
- package/components/EditorPanel/EditorPanel.styles.css +16 -0
- package/components/EditorPanel/EditorPanel.tsx +144 -0
- package/components/EditorPanel/EditorPanelDispatch.tsx +75 -0
- package/components/EditorPanel/FieldSetWrapper.tsx +66 -23
- package/components/EditorPanel/Inputs.tsx +33 -7
- package/components/EditorPanel/VizFilterEditor/NestedDropdownEditor.tsx +14 -6
- package/components/EditorPanel/VizFilterEditor/VizFilterEditor.tsx +240 -175
- package/components/EditorPanel/VizFilterEditor/components/FilterOrder.tsx +33 -29
- package/components/EditorPanel/sections/VisualSection.tsx +169 -0
- package/components/Filters/Filters.tsx +31 -5
- package/components/Filters/helpers/getNestedOptions.ts +2 -1
- package/components/Filters/helpers/handleSorting.ts +1 -1
- package/components/Layout/components/Sidebar/components/sidebar.styles.scss +84 -2
- package/components/Layout/components/Visualization/index.tsx +27 -1
- package/components/Layout/components/Visualization/visualizations.scss +7 -0
- package/components/Legend/Legend.Gradient.tsx +1 -1
- package/components/MediaControls.tsx +53 -28
- package/components/_stories/CustomColorsEditor.stories.tsx +37 -0
- package/components/_stories/DataTable.stories.tsx +1 -0
- package/components/ui/Icon.tsx +3 -1
- package/components/ui/Title/index.tsx +30 -2
- package/components/ui/Title/title.styles.css +42 -0
- package/data/colorPalettes.ts +18 -5
- package/data/mapColorPalettes.ts +10 -0
- package/devTemplate/dev.js +235 -0
- package/devTemplate/index.html +30 -0
- package/devTemplate/preview.html +1503 -0
- package/devTemplate/sidebar.css +151 -0
- package/dist/cove-main.css +2803 -4448
- package/dist/cove-main.css.map +1 -1
- package/generateViteConfig.js +118 -2
- package/helpers/DataTransform.ts +1 -5
- package/helpers/addValuesToFilters.ts +6 -1
- package/helpers/cove/date.ts +33 -1
- package/helpers/cove/string.ts +29 -0
- package/helpers/coveUpdateWorker.ts +21 -12
- package/helpers/embed/embedCodeGenerator.ts +80 -0
- package/helpers/embed/embedHelper.js +158 -0
- package/helpers/embed/filterUtils.ts +121 -0
- package/helpers/embed/index.ts +21 -0
- package/helpers/embed/urlValidation.ts +119 -0
- package/helpers/filterVizData.ts +6 -1
- package/helpers/getFileExtension.ts +0 -6
- package/helpers/getUniqueValues.ts +19 -0
- package/helpers/hashObj.ts +25 -0
- package/helpers/isRightAlignedTableValue.js +5 -0
- package/helpers/metrics/helpers.ts +1 -0
- package/helpers/metrics/types.ts +3 -0
- package/helpers/palettes/colorDistributions.ts +1 -1
- package/helpers/palettes/utils.ts +12 -12
- package/helpers/parseCsvWithQuotes.ts +15 -14
- package/helpers/pivotData.ts +2 -2
- package/helpers/prepareScreenshot.ts +288 -0
- package/helpers/queryStringUtils.ts +29 -0
- package/helpers/testing.ts +44 -0
- package/helpers/tests/DataTransform.test.ts +125 -0
- package/helpers/tests/date.test.ts +64 -0
- package/helpers/tests/prepareScreenshot.test.ts +414 -0
- package/helpers/tests/queryStringUtils.test.ts +381 -0
- package/helpers/tests/testStandaloneBuild.ts +23 -5
- package/helpers/useDataVizClasses.ts +0 -1
- package/helpers/vegaConfig.ts +1 -1
- package/helpers/vegaConfigImport.ts +160 -0
- package/helpers/ver/4.26.1.ts +80 -0
- package/helpers/ver/4.26.2.ts +84 -0
- package/helpers/ver/tests/4.26.1.test.ts +105 -0
- package/helpers/ver/tests/4.26.2.test.ts +298 -0
- package/helpers/viewports.ts +2 -0
- package/hooks/useDataColumns.ts +63 -0
- package/hooks/useFilterManagement.ts +94 -0
- package/hooks/useLegendSeparators.ts +26 -0
- package/hooks/useListManagement.ts +192 -0
- package/package.json +29 -33
- package/styles/_button-section.scss +0 -3
- package/styles/v2/components/editor.scss +9 -9
- package/styles/v2/utils/_grid.scss +8 -3
- package/types/Annotation.ts +10 -11
- package/types/Axis.ts +1 -0
- package/types/ForecastingSeriesKey.ts +1 -0
- package/types/General.ts +2 -0
- package/types/MarkupInclude.ts +1 -0
- package/types/Palette.ts +21 -0
- package/types/Series.ts +3 -0
- package/types/Table.ts +1 -0
- package/types/Visualization.ts +7 -0
- package/types/VizFilter.ts +1 -0
- package/LICENSE +0 -201
- package/_stories/StoryRenderingTests.stories.tsx +0 -164
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
+
import { within, expect } from 'storybook/test'
|
|
3
|
+
import Dashboard from '@cdc/dashboard'
|
|
4
|
+
import { useEffect, useState } from 'react'
|
|
5
|
+
|
|
6
|
+
// Fallback step function for test descriptions
|
|
7
|
+
const step = async (description: string, fn: () => Promise<void> | void) => {
|
|
8
|
+
console.log(`▶ ${description}`)
|
|
9
|
+
await fn()
|
|
10
|
+
console.log(`✓ ${description}`)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const meta: Meta = {
|
|
14
|
+
title: 'Regression Tests/Pages/Oral Health',
|
|
15
|
+
parameters: {
|
|
16
|
+
layout: 'fullscreen',
|
|
17
|
+
docs: {
|
|
18
|
+
description: {
|
|
19
|
+
component:
|
|
20
|
+
'Stories for visualizations from the CDC Oral Health Data page (https://www.cdc.gov/oral-health-data-systems/oral_health_data/oral-health-dashboard.html)'
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
tags: ['autodocs']
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default meta
|
|
28
|
+
|
|
29
|
+
// Config URL from the oral health data page
|
|
30
|
+
const CONFIG_URLS = {
|
|
31
|
+
oralHealthData: 'https://www.cdc.gov/oral-health-data-systems/oral_health_data/modules/oral-health-data.json'
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Helper to fetch config and update data URLs to use absolute cdc.gov paths
|
|
35
|
+
const useConfigWithAbsoluteDataUrl = (configUrl: string) => {
|
|
36
|
+
const [config, setConfig] = useState(null)
|
|
37
|
+
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
fetch(configUrl)
|
|
40
|
+
.then(res => res.json())
|
|
41
|
+
.then(data => {
|
|
42
|
+
// Convert relative data URLs to absolute cdc.gov URLs
|
|
43
|
+
if (data.dataUrl) {
|
|
44
|
+
// Handle different relative path formats (../../path or /path)
|
|
45
|
+
const dataUrl = data.dataUrl.replace(/^(\.\.\/)+/, '').replace(/^\//, '')
|
|
46
|
+
data.dataUrl = `https://www.cdc.gov/${dataUrl}`
|
|
47
|
+
}
|
|
48
|
+
if (data.dataFileName) {
|
|
49
|
+
const dataFileName = data.dataFileName.replace(/^(\.\.\/)+/, '').replace(/^\//, '')
|
|
50
|
+
data.dataFileName = `https://www.cdc.gov/${dataFileName}`
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// For dashboard configs with multiDashboards, convert dataKey references in visualizations
|
|
54
|
+
if (data.multiDashboards) {
|
|
55
|
+
data.multiDashboards.forEach((dashboard: any) => {
|
|
56
|
+
if (dashboard.visualizations) {
|
|
57
|
+
Object.values(dashboard.visualizations).forEach((viz: any) => {
|
|
58
|
+
// Only convert dataKey if it's a URL path (starts with / or ../)
|
|
59
|
+
if (viz.dataKey && (viz.dataKey.startsWith('/') || viz.dataKey.startsWith('../'))) {
|
|
60
|
+
const dataKey = viz.dataKey.replace(/^(\.\.\/)+/, '').replace(/^\//, '')
|
|
61
|
+
viz.dataKey = `https://www.cdc.gov/${dataKey}`
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
})
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// For dashboard configs, convert dataKey references in visualizations
|
|
69
|
+
if (data.visualizations) {
|
|
70
|
+
Object.values(data.visualizations).forEach((viz: any) => {
|
|
71
|
+
// Only convert dataKey if it's a URL path (starts with / or ../)
|
|
72
|
+
if (viz.dataKey && (viz.dataKey.startsWith('/') || viz.dataKey.startsWith('../'))) {
|
|
73
|
+
const dataKey = viz.dataKey.replace(/^(\.\.\/)+/, '').replace(/^\//, '')
|
|
74
|
+
viz.dataKey = `https://www.cdc.gov/${dataKey}`
|
|
75
|
+
}
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// For dashboard configs, convert datasets only if they reference external files
|
|
80
|
+
if (data.datasets) {
|
|
81
|
+
const newDatasets = {}
|
|
82
|
+
Object.entries(data.datasets).forEach(([key, dataset]: [string, any]) => {
|
|
83
|
+
// Check if dataset has embedded data
|
|
84
|
+
const hasEmbeddedData = (dataset as any).data && Array.isArray((dataset as any).data)
|
|
85
|
+
|
|
86
|
+
// If data is embedded, keep the original key
|
|
87
|
+
if (hasEmbeddedData) {
|
|
88
|
+
newDatasets[key] = dataset
|
|
89
|
+
} else {
|
|
90
|
+
// Otherwise, convert paths to absolute URLs (but keep absolute URLs as-is)
|
|
91
|
+
const newKey = key.replace(/^(\.\.\/)+/, '').replace(/^\//, '')
|
|
92
|
+
const absoluteKey = key.startsWith('http') ? key : `https://www.cdc.gov/${newKey}`
|
|
93
|
+
|
|
94
|
+
newDatasets[absoluteKey] = {
|
|
95
|
+
...dataset,
|
|
96
|
+
dataFileName:
|
|
97
|
+
(dataset as any).dataFileName && !(dataset as any).dataFileName.startsWith('http')
|
|
98
|
+
? `https://www.cdc.gov/${(dataset as any).dataFileName
|
|
99
|
+
.replace(/^(\.\.\/)+/, '')
|
|
100
|
+
.replace(/^\//, '')}`
|
|
101
|
+
: (dataset as any).dataFileName,
|
|
102
|
+
dataUrl:
|
|
103
|
+
(dataset as any).dataUrl && !(dataset as any).dataUrl.startsWith('http')
|
|
104
|
+
? `https://www.cdc.gov/${(dataset as any).dataUrl.replace(/^(\.\.\/)+/, '').replace(/^\//, '')}`
|
|
105
|
+
: (dataset as any).dataUrl
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
})
|
|
109
|
+
data.datasets = newDatasets
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Set activeDashboard to 0 if it's null and multiDashboards exist
|
|
113
|
+
if (data.multiDashboards && data.multiDashboards.length > 0 && data.activeDashboard === null) {
|
|
114
|
+
data.activeDashboard = 0
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Log config info for debugging
|
|
118
|
+
console.log('✓ Config loaded:', {
|
|
119
|
+
type: data.type,
|
|
120
|
+
hasMultiDashboards: !!data.multiDashboards,
|
|
121
|
+
dashboardCount: data.multiDashboards?.length || 0,
|
|
122
|
+
activeDashboard: data.activeDashboard,
|
|
123
|
+
datasetCount: Object.keys(data.datasets || {}).length,
|
|
124
|
+
dashboardLabels: data.multiDashboards?.map((d: any) => d.label)
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
setConfig(data)
|
|
128
|
+
})
|
|
129
|
+
.catch(err => {
|
|
130
|
+
console.error('Failed to fetch config:', configUrl, err)
|
|
131
|
+
})
|
|
132
|
+
}, [configUrl])
|
|
133
|
+
|
|
134
|
+
return config
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
type DashboardStory = StoryObj<typeof Dashboard>
|
|
138
|
+
|
|
139
|
+
// Helper function to test dashboard rendering
|
|
140
|
+
const testDashboardRendering = async (canvasElement: HTMLElement, storyName: string) => {
|
|
141
|
+
await step('Wait for dashboard to render', async () => {
|
|
142
|
+
await new Promise<void>((resolve, reject) => {
|
|
143
|
+
const startTime = Date.now()
|
|
144
|
+
const timeout = 30000 // Longer timeout for external data loading
|
|
145
|
+
|
|
146
|
+
const checkDashboard = () => {
|
|
147
|
+
const dashboardElement = canvasElement.querySelector('.type-dashboard')
|
|
148
|
+
const loadingDiv = canvasElement.querySelector('div')
|
|
149
|
+
|
|
150
|
+
// Log current state for debugging
|
|
151
|
+
if (!dashboardElement && loadingDiv?.textContent?.includes('Loading')) {
|
|
152
|
+
console.log('Still loading config...')
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (dashboardElement) {
|
|
156
|
+
resolve()
|
|
157
|
+
} else if (Date.now() - startTime > timeout) {
|
|
158
|
+
reject(new Error(`Timeout: Dashboard element not found after ${timeout}ms`))
|
|
159
|
+
} else {
|
|
160
|
+
setTimeout(checkDashboard, 100)
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
checkDashboard()
|
|
164
|
+
})
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
await step('Verify dashboard wrapper is present', async () => {
|
|
168
|
+
const dashboard = canvasElement.querySelector('.type-dashboard')
|
|
169
|
+
expect(dashboard).toBeInTheDocument()
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
await step('Verify at least one visualization rendered', async () => {
|
|
173
|
+
const coveModules = canvasElement.querySelectorAll('.cdc-open-viz-module')
|
|
174
|
+
expect(coveModules.length).toBeGreaterThan(0)
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
console.log(` ${storyName} dashboard rendered successfully`)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Oral Health Data Dashboard
|
|
182
|
+
*
|
|
183
|
+
* Interactive dashboard showing oral health surveillance data from the National Oral Health
|
|
184
|
+
* Surveillance System (NOHSS). The dashboard includes 4 views:
|
|
185
|
+
* - All States: State-level oral health data across all U.S. states
|
|
186
|
+
* - Single State: Detailed data for a selected state
|
|
187
|
+
* - All Counties: County-level oral health data
|
|
188
|
+
* - Single County: Detailed data for a selected county
|
|
189
|
+
*
|
|
190
|
+
* Data includes oral health indicators, intervention strategies, and public health programs.
|
|
191
|
+
*/
|
|
192
|
+
export const Oral_Health_Dashboard: DashboardStory = {
|
|
193
|
+
render: () => {
|
|
194
|
+
const config = useConfigWithAbsoluteDataUrl(CONFIG_URLS.oralHealthData)
|
|
195
|
+
if (!config) return <div>Loading...</div>
|
|
196
|
+
return <Dashboard config={config} />
|
|
197
|
+
},
|
|
198
|
+
play: async ({ canvasElement }) => {
|
|
199
|
+
await testDashboardRendering(canvasElement, 'Oral Health Dashboard')
|
|
200
|
+
}
|
|
201
|
+
}
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
+
import { within, expect } from 'storybook/test'
|
|
3
|
+
import Chart from '@cdc/chart'
|
|
4
|
+
import CdcMap from '@cdc/map'
|
|
5
|
+
import { useEffect, useState } from 'react'
|
|
6
|
+
|
|
7
|
+
// Fallback step function for test descriptions
|
|
8
|
+
const step = async (description: string, fn: () => Promise<void> | void) => {
|
|
9
|
+
console.log(`▶ ${description}`)
|
|
10
|
+
await fn()
|
|
11
|
+
console.log(`✓ ${description}`)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const meta: Meta = {
|
|
15
|
+
title: 'Regression Tests/Pages/Respiratory',
|
|
16
|
+
parameters: {
|
|
17
|
+
layout: 'fullscreen',
|
|
18
|
+
docs: {
|
|
19
|
+
description: {
|
|
20
|
+
component: 'Stories for all visualizations from the CDC Respiratory Viruses Activity Levels page (https://www.cdc.gov/respiratory-viruses/data/activity-levels.html)'
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
tags: ['autodocs']
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default meta
|
|
28
|
+
|
|
29
|
+
// Config URLs from the respiratory viruses activity levels page
|
|
30
|
+
const CONFIG_URLS = {
|
|
31
|
+
ariMap: 'https://www.cdc.gov/respiratory-viruses/modules/respiratory-virus-activity/ARI_Map_Viz.json',
|
|
32
|
+
cfaMap: 'https://www.cdc.gov/respiratory-viruses/modules/respiratory-virus-activity/CFA_Map_Viz.json',
|
|
33
|
+
wastewaterMap: 'https://www.cdc.gov/respiratory-viruses/modules/respiratory-virus-activity/wastewatermap.json',
|
|
34
|
+
testPositivity: 'https://www.cdc.gov/respiratory-viruses/modules/test-in-percent-test-positivity-in-usa.json'
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Helper to fetch config and update data URLs to use absolute cdc.gov paths
|
|
38
|
+
const useConfigWithAbsoluteDataUrl = (configUrl: string) => {
|
|
39
|
+
const [config, setConfig] = useState(null)
|
|
40
|
+
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
fetch(configUrl)
|
|
43
|
+
.then(res => res.json())
|
|
44
|
+
.then(data => {
|
|
45
|
+
// Convert relative data URLs to absolute cdc.gov URLs
|
|
46
|
+
if (data.dataUrl) {
|
|
47
|
+
// Handle different relative path formats (../../path or /path)
|
|
48
|
+
const dataUrl = data.dataUrl.replace(/^(\.\.\/)+/, '').replace(/^\//, '')
|
|
49
|
+
data.dataUrl = `https://www.cdc.gov/${dataUrl}`
|
|
50
|
+
}
|
|
51
|
+
if (data.dataFileName) {
|
|
52
|
+
const dataFileName = data.dataFileName.replace(/^(\.\.\/)+/, '').replace(/^\//, '')
|
|
53
|
+
data.dataFileName = `https://www.cdc.gov/${dataFileName}`
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Validate that color configuration exists
|
|
57
|
+
if (!data.customColors || data.customColors.length === 0) {
|
|
58
|
+
console.warn('⚠️ No customColors found in config:', configUrl)
|
|
59
|
+
} else {
|
|
60
|
+
console.log(`✓ Config has ${data.customColors.length} custom colors`, data.customColors)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Validate legend configuration
|
|
64
|
+
if (data.legend) {
|
|
65
|
+
console.log('✓ Legend config:', {
|
|
66
|
+
type: data.legend.type,
|
|
67
|
+
categories: data.legend.categoryValuesOrder || data.legend.additionalCategories,
|
|
68
|
+
style: data.legend.style
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
setConfig(data)
|
|
73
|
+
})
|
|
74
|
+
.catch(err => {
|
|
75
|
+
console.error('Failed to fetch config:', configUrl, err)
|
|
76
|
+
})
|
|
77
|
+
}, [configUrl])
|
|
78
|
+
|
|
79
|
+
return config
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
type MapStory = StoryObj<typeof CdcMap>
|
|
83
|
+
type ChartStory = StoryObj<typeof Chart>
|
|
84
|
+
|
|
85
|
+
// Helper to verify colors in visualizations (Playwright assertions)
|
|
86
|
+
const verifyColors = (canvasElement: HTMLElement, storyName: string) => {
|
|
87
|
+
// Check for colored paths (maps)
|
|
88
|
+
const mapPaths = canvasElement.querySelectorAll('svg path[fill]')
|
|
89
|
+
let coloredPaths = 0
|
|
90
|
+
mapPaths.forEach(path => {
|
|
91
|
+
const fill = path.getAttribute('fill')
|
|
92
|
+
if (fill && fill !== 'none' && fill !== '#cccccc' && fill !== '#e0e0e0' && !fill.toLowerCase().includes('gray')) {
|
|
93
|
+
coloredPaths++
|
|
94
|
+
}
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
// Check for colored chart elements (lines, strokes)
|
|
98
|
+
const chartElements = canvasElement.querySelectorAll('svg path[stroke], svg line[stroke]')
|
|
99
|
+
let coloredElements = 0
|
|
100
|
+
chartElements.forEach(element => {
|
|
101
|
+
const stroke = element.getAttribute('stroke')
|
|
102
|
+
if (stroke && stroke !== 'none' && !stroke.toLowerCase().includes('gray')) {
|
|
103
|
+
coloredElements++
|
|
104
|
+
}
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
const totalColored = coloredPaths + coloredElements
|
|
108
|
+
|
|
109
|
+
// Assert that colored elements exist (will fail Playwright test if not)
|
|
110
|
+
expect(totalColored).toBeGreaterThan(0)
|
|
111
|
+
|
|
112
|
+
console.log(`✓ ${storyName}: ${totalColored} colored elements verified (${coloredPaths} paths, ${coloredElements} strokes)`)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Helper function to test map rendering
|
|
116
|
+
const testMapRendering = async (canvasElement: HTMLElement, storyName: string) => {
|
|
117
|
+
const canvas = within(canvasElement)
|
|
118
|
+
|
|
119
|
+
await step('Wait for map to render', async () => {
|
|
120
|
+
const mapElement = await canvas.findByRole('img', { hidden: true }, { timeout: 10000 })
|
|
121
|
+
expect(mapElement).toBeInTheDocument()
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
await step('Verify SVG element is present', async () => {
|
|
125
|
+
const svgElement = canvasElement.querySelector('svg')
|
|
126
|
+
expect(svgElement).toBeInTheDocument()
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
await step('Verify COVE module wrapper is present', async () => {
|
|
130
|
+
const coveModule = canvasElement.querySelector('.cdc-open-viz-module')
|
|
131
|
+
expect(coveModule).toBeInTheDocument()
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
await step('Verify colors are applied to map regions', async () => {
|
|
135
|
+
verifyColors(canvasElement, storyName)
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
console.log(` ${storyName} map rendered successfully`)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Helper function to test chart rendering
|
|
142
|
+
// Helper function to test chart rendering
|
|
143
|
+
const testChartRendering = async (canvasElement: HTMLElement, storyName: string) => {
|
|
144
|
+
const canvas = within(canvasElement)
|
|
145
|
+
|
|
146
|
+
await step('Wait for chart to render', async () => {
|
|
147
|
+
const svgElement = await canvas.findByRole('img', { hidden: true }, { timeout: 10000 })
|
|
148
|
+
expect(svgElement).toBeInTheDocument()
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
await step('Verify chart SVG is present', async () => {
|
|
152
|
+
const chartSvg = canvasElement.querySelector('svg')
|
|
153
|
+
expect(chartSvg).toBeInTheDocument()
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
await step('Verify COVE module wrapper is present', async () => {
|
|
157
|
+
const coveModule = canvasElement.querySelector('.cdc-open-viz-module')
|
|
158
|
+
expect(coveModule).toBeInTheDocument()
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
await step('Verify colors are applied to chart elements', async () => {
|
|
162
|
+
verifyColors(canvasElement, storyName)
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
console.log(` ${storyName} chart rendered successfully`)
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Level of Respiratory Illness Activity Map
|
|
169
|
+
*
|
|
170
|
+
* Displays respiratory illness activity monitored using the acute respiratory
|
|
171
|
+
* illness (ARI) metric. ARI captures a broad range of diagnoses from emergency
|
|
172
|
+
* department visits for respiratory illnesses.
|
|
173
|
+
*/
|
|
174
|
+
export const ARI_Activity_Map: MapStory = {
|
|
175
|
+
render: () => {
|
|
176
|
+
const config = useConfigWithAbsoluteDataUrl(CONFIG_URLS.ariMap)
|
|
177
|
+
if (!config) return <div>Loading...</div>
|
|
178
|
+
return <CdcMap config={config} />
|
|
179
|
+
},
|
|
180
|
+
play: async ({ canvasElement }) => {
|
|
181
|
+
await testMapRendering(canvasElement, 'ARI Activity Map')
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Epidemic Trends Map
|
|
187
|
+
*
|
|
188
|
+
* CDC uses data from emergency department visits to model epidemic trends.
|
|
189
|
+
* This model helps tell whether the number of new respiratory infections
|
|
190
|
+
* is growing or declining in your state.
|
|
191
|
+
*/
|
|
192
|
+
export const Epidemic_Trends_Map: MapStory = {
|
|
193
|
+
render: () => {
|
|
194
|
+
const config = useConfigWithAbsoluteDataUrl(CONFIG_URLS.cfaMap)
|
|
195
|
+
if (!config) return <div>Loading...</div>
|
|
196
|
+
return <CdcMap config={config} />
|
|
197
|
+
},
|
|
198
|
+
play: async ({ canvasElement }) => {
|
|
199
|
+
await testMapRendering(canvasElement, 'Epidemic Trends Map')
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Wastewater Surveillance Map
|
|
205
|
+
*
|
|
206
|
+
* Wastewater surveillance for COVID-19, influenza, and RSV by state/territory.
|
|
207
|
+
* Wastewater data can detect infections before clinical symptoms appear.
|
|
208
|
+
*/
|
|
209
|
+
export const Wastewater_Surveillance_Map: MapStory = {
|
|
210
|
+
render: () => {
|
|
211
|
+
const config = useConfigWithAbsoluteDataUrl(CONFIG_URLS.wastewaterMap)
|
|
212
|
+
if (!config) return <div>Loading...</div>
|
|
213
|
+
return <CdcMap config={config} />
|
|
214
|
+
},
|
|
215
|
+
play: async ({ canvasElement }) => {
|
|
216
|
+
await testMapRendering(canvasElement, 'Wastewater Surveillance Map')
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Percent of Tests Positive for Respiratory Viruses
|
|
222
|
+
*
|
|
223
|
+
* Weekly percent of tests positive for the viruses that cause COVID-19,
|
|
224
|
+
* influenza, and RSV at the national level.
|
|
225
|
+
*/
|
|
226
|
+
export const Test_Positivity_Chart: ChartStory = {
|
|
227
|
+
render: () => {
|
|
228
|
+
const config = useConfigWithAbsoluteDataUrl(CONFIG_URLS.testPositivity)
|
|
229
|
+
if (!config) return <div>Loading...</div>
|
|
230
|
+
return <Chart config={config} />
|
|
231
|
+
},
|
|
232
|
+
play: async ({ canvasElement }) => {
|
|
233
|
+
await testChartRendering(canvasElement, 'Test Positivity Chart')
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* All Visualizations - Combined Test
|
|
239
|
+
*
|
|
240
|
+
* Tests all four visualizations from the respiratory viruses page to ensure
|
|
241
|
+
* they all render correctly together.
|
|
242
|
+
*/
|
|
243
|
+
export const All_Visualizations: StoryObj = {
|
|
244
|
+
render: () => {
|
|
245
|
+
const ariConfig = useConfigWithAbsoluteDataUrl(CONFIG_URLS.ariMap)
|
|
246
|
+
const cfaConfig = useConfigWithAbsoluteDataUrl(CONFIG_URLS.cfaMap)
|
|
247
|
+
const wastewaterConfig = useConfigWithAbsoluteDataUrl(CONFIG_URLS.wastewaterMap)
|
|
248
|
+
const testPositivityConfig = useConfigWithAbsoluteDataUrl(CONFIG_URLS.testPositivity)
|
|
249
|
+
|
|
250
|
+
if (!ariConfig || !cfaConfig || !wastewaterConfig || !testPositivityConfig) {
|
|
251
|
+
return <div>Loading...</div>
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return (
|
|
255
|
+
<div className="container-fluid p-4">
|
|
256
|
+
<h1 className="mb-4">Respiratory Viruses Activity Levels - All Visualizations</h1>
|
|
257
|
+
|
|
258
|
+
<section className="mb-5">
|
|
259
|
+
<h2>Level of Respiratory Illness Activity</h2>
|
|
260
|
+
<CdcMap config={ariConfig} />
|
|
261
|
+
</section>
|
|
262
|
+
|
|
263
|
+
<section className="mb-5">
|
|
264
|
+
<h2>Epidemic Trends</h2>
|
|
265
|
+
<CdcMap config={cfaConfig} />
|
|
266
|
+
</section>
|
|
267
|
+
|
|
268
|
+
<section className="mb-5">
|
|
269
|
+
<h2>Wastewater Surveillance</h2>
|
|
270
|
+
<CdcMap config={wastewaterConfig} />
|
|
271
|
+
</section>
|
|
272
|
+
|
|
273
|
+
<section className="mb-5">
|
|
274
|
+
<h2>Percent of Tests Positive for Respiratory Viruses</h2>
|
|
275
|
+
<Chart config={testPositivityConfig} />
|
|
276
|
+
</section>
|
|
277
|
+
</div>
|
|
278
|
+
)
|
|
279
|
+
},
|
|
280
|
+
play: async ({ canvasElement }) => {
|
|
281
|
+
const canvas = within(canvasElement)
|
|
282
|
+
|
|
283
|
+
await step('Wait for all configs to load', async () => {
|
|
284
|
+
await new Promise<void>(resolve => {
|
|
285
|
+
const checkLoading = () => {
|
|
286
|
+
const loadingDiv = canvasElement.querySelector('div:not(.container-fluid)')
|
|
287
|
+
if (!loadingDiv || !loadingDiv.textContent?.includes('Loading')) {
|
|
288
|
+
resolve()
|
|
289
|
+
} else {
|
|
290
|
+
setTimeout(checkLoading, 100)
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
checkLoading()
|
|
294
|
+
})
|
|
295
|
+
})
|
|
296
|
+
|
|
297
|
+
await step('Wait for visualizations to start rendering', async () => {
|
|
298
|
+
await new Promise<void>(resolve => setTimeout(resolve, 2000))
|
|
299
|
+
})
|
|
300
|
+
|
|
301
|
+
await step('Wait for all 4 COVE modules to render', async () => {
|
|
302
|
+
await new Promise<void>((resolve, reject) => {
|
|
303
|
+
const startTime = Date.now()
|
|
304
|
+
const timeout = 20000
|
|
305
|
+
|
|
306
|
+
const checkModules = () => {
|
|
307
|
+
const coveModules = canvasElement.querySelectorAll('.cdc-open-viz-module')
|
|
308
|
+
if (coveModules.length >= 4) {
|
|
309
|
+
resolve()
|
|
310
|
+
} else if (Date.now() - startTime > timeout) {
|
|
311
|
+
reject(new Error(`Timeout: Only ${coveModules.length}/4 COVE modules found after ${timeout}ms`))
|
|
312
|
+
} else {
|
|
313
|
+
setTimeout(checkModules, 200)
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
checkModules()
|
|
317
|
+
})
|
|
318
|
+
})
|
|
319
|
+
|
|
320
|
+
await step('Verify all 4 SVG visualizations are present', async () => {
|
|
321
|
+
const allSvgs = await canvas.findAllByRole('img', { hidden: true }, { timeout: 5000 })
|
|
322
|
+
expect(allSvgs.length).toBeGreaterThanOrEqual(4)
|
|
323
|
+
})
|
|
324
|
+
|
|
325
|
+
await step('Verify exactly 4 COVE modules are present', async () => {
|
|
326
|
+
const coveModules = canvasElement.querySelectorAll('.cdc-open-viz-module')
|
|
327
|
+
expect(coveModules.length).toBe(4)
|
|
328
|
+
})
|
|
329
|
+
|
|
330
|
+
console.log(` All 4 visualizations rendered successfully`)
|
|
331
|
+
}
|
|
332
|
+
}
|