@cdc/chart 4.26.7 → 4.26.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/CONFIG.md +502 -368
- package/dist/cdcchart-CYc-Kayd.es.js +22488 -0
- package/dist/cdcchart-DCKn8NmI.es.js +20695 -0
- package/dist/cdcchart-Dyui9nk_.es.js +45828 -0
- package/dist/cdcchart.js +4 -97406
- package/examples/__data__/vega-remote-data.json +6 -0
- package/examples/dendrogram.json +18 -0
- package/examples/feature/bar/bar-chart-race-annual-change.json +68 -0
- package/examples/feature/heatmap/average-age-categorical.json +2 -2
- package/examples/feature/heatmap/calendar-heatmap.json +2 -2
- package/examples/feature/heatmap/cell-values-bottom-axis.json +2 -2
- package/examples/feature/heatmap/sparse-aggregation.json +2 -2
- package/examples/feature/line/line-chart-race.json +50 -0
- package/examples/feature/sankey/sankey-example-data.json +40 -1441
- package/examples/feature/sankey/sankey_chart_data.csv +9 -20
- package/examples/network.json +31 -0
- package/examples/private/box-plot.json +573 -0
- package/examples/private/custom-color-issue.json +625 -0
- package/examples/private/custom-colors.json +522 -0
- package/examples/private/cutoff.json +10750 -0
- package/examples/private/overlay-legend.json +485 -0
- package/examples/private/pie-test.json +469 -0
- package/examples/vega-remote.json +22 -0
- package/package.json +11 -5
- package/src/CdcChartComponent.tsx +405 -208
- package/src/_stories/Chart.BarRace.stories.tsx +443 -0
- package/src/_stories/Chart.BoxPlotNumericSort.stories.tsx +189 -0
- package/src/_stories/Chart.BoxPlotStatsParity.stories.tsx +207 -0
- package/src/_stories/Chart.ColorDistribution.stories.tsx +299 -0
- package/src/_stories/Chart.DataCutoff.stories.tsx +147 -0
- package/src/_stories/Chart.DataTableDownloadFilename.stories.tsx +60 -0
- package/src/_stories/Chart.Dendrogram.stories.tsx +373 -0
- package/src/_stories/Chart.Forecast.CILegend.stories.tsx +236 -0
- package/src/_stories/Chart.HeatMap.stories.tsx +170 -4
- package/src/_stories/Chart.Legend.AxisAlign.stories.tsx +119 -0
- package/src/_stories/Chart.LineRace.stories.tsx +149 -0
- package/src/_stories/Chart.Network.stories.tsx +432 -0
- package/src/_stories/Chart.PaletteConversion.Editor.stories.tsx +208 -0
- package/src/_stories/Chart.Patterns.smoke.stories.tsx +31 -0
- package/src/_stories/Chart.PercentageValue.stories.tsx +311 -0
- package/src/_stories/Chart.PieLabels.stories.tsx +4 -2
- package/src/_stories/Chart.RankByValueMutation.stories.tsx +206 -0
- package/src/_stories/Chart.RequiredFields.stories.tsx +681 -0
- package/src/_stories/Chart.Sankey.stories.tsx +417 -0
- package/src/_stories/Chart.SeriesColorAssignments.stories.tsx +47 -0
- package/src/_stories/Chart.SmallMultiplesNumericTileOrder.stories.tsx +137 -0
- package/src/_stories/Chart.TooltipSeriesLabels.stories.tsx +43 -0
- package/src/_stories/Chart.VegaRemoteData.stories.tsx +31 -0
- package/src/_stories/ChartAnnotation.Editor.stories.tsx +78 -0
- package/src/_stories/ChartAxisTitles.smoke.stories.tsx +48 -0
- package/src/_stories/ChartBar.Editor.stories.tsx +499 -57
- package/src/_stories/ChartBrush.Editor.stories.tsx +46 -67
- package/src/_stories/ChartBrush.Regression.stories.tsx +135 -0
- package/src/_stories/ChartEditor.Editor.stories.tsx +33 -3
- package/src/_stories/ChartLine.EffectCompatibility.stories.tsx +120 -0
- package/src/_stories/_mock/annotation_event-line_mock.json +2 -2
- package/src/_stories/_mock/editor-test-configs.json +20 -10
- package/src/_stories/_mock/editor-test-datasets.json +38 -42
- package/src/_stories/_mock/heatmap-average-age-categorical.json +2 -2
- package/src/_stories/_mock/heatmap.json +2 -2
- package/src/_stories/_mock/legend-axis-align-stacked-bar.json +125 -0
- package/src/_stories/_mock/sankey_mock.json +23 -27
- package/src/components/Annotations/components/AnnotationDraggable.styles.css +52 -0
- package/src/components/Annotations/components/AnnotationDraggable.tsx +639 -139
- package/src/components/Annotations/components/AnnotationDropdown.styles.css +44 -4
- package/src/components/Annotations/components/AnnotationDropdown.tsx +10 -32
- package/src/components/Annotations/helpers/resolveAnnotationLayout.ts +212 -0
- package/src/components/Annotations/tests/AnnotationDraggable.test.tsx +612 -19
- package/src/components/Annotations/tests/AnnotationDropdown.test.tsx +50 -0
- package/src/components/Annotations/tests/resolveAnnotationLayout.test.ts +203 -0
- package/src/components/Axis/BottomAxis.tsx +1 -1
- package/src/components/Axis/Categorical.Axis.tsx +3 -3
- package/src/components/Axis/RightAxis.tsx +21 -13
- package/src/components/BarChart/components/BarChart.Horizontal.tsx +38 -6
- package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +12 -3
- package/src/components/BarChart/components/BarChart.StackedVertical.test.ts +28 -0
- package/src/components/BarChart/components/BarChart.StackedVertical.tsx +29 -25
- package/src/components/BarChart/components/BarChart.Vertical.tsx +37 -26
- package/src/components/BarChart/components/PortionPatternOverlay.tsx +61 -0
- package/src/components/BarChart/components/context.tsx +6 -0
- package/src/components/BarChart/helpers/getPatternUrl.ts +2 -5
- package/src/components/BarChart/helpers/index.ts +8 -0
- package/src/components/BarChart/helpers/portionPattern.ts +243 -0
- package/src/components/BarChart/helpers/tests/addMinimumBarHeights.test.ts +42 -0
- package/src/components/BarChart/helpers/tests/getPatternUrl.test.ts +13 -0
- package/src/components/BarChart/helpers/tests/portionPattern.test.ts +280 -0
- package/src/components/BarChart/helpers/tests/useBarChart.test.tsx +104 -0
- package/src/components/BarChart/helpers/useBarChart.ts +60 -59
- package/src/components/BarChartRace/BarChartRace.test.tsx +199 -0
- package/src/components/BarChartRace/BarChartRace.tsx +230 -0
- package/src/components/BarChartRace/BarChartRaceFallback.test.tsx +61 -0
- package/src/components/BarChartRace/BarChartRaceFallback.tsx +48 -0
- package/src/components/BarChartRace/bar-chart-race.css +160 -0
- package/src/components/BarChartRace/helpers.test.ts +114 -0
- package/src/components/BarChartRace/helpers.ts +185 -0
- package/src/components/BarChartRace/index.ts +3 -0
- package/src/components/BoxPlot/helpers/index.ts +11 -82
- package/src/components/Brush/BrushSelector.tsx +9 -23
- package/src/components/Brush/MiniChartPreview.tsx +33 -1
- package/src/components/Dendrogram/components/Dendrogram.tsx +374 -0
- package/src/components/Dendrogram/dendrogram.css +113 -0
- package/src/components/Dendrogram/helpers/layoutDendrogram.test.ts +95 -0
- package/src/components/Dendrogram/helpers/layoutDendrogram.ts +56 -0
- package/src/components/Dendrogram/helpers/prepareDendrogramData.test.ts +135 -0
- package/src/components/Dendrogram/helpers/prepareDendrogramData.ts +166 -0
- package/src/components/Dendrogram/helpers/resolveDendrogramLabels.test.ts +196 -0
- package/src/components/Dendrogram/helpers/resolveDendrogramLabels.ts +418 -0
- package/src/components/Dendrogram/index.tsx +1 -0
- package/src/components/Dendrogram/types/index.ts +66 -0
- package/src/components/EditorPanel/EditorPanel.tsx +1005 -760
- package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +50 -5
- package/src/components/EditorPanel/components/Panels/Panel.Dendrogram.css +10 -0
- package/src/components/EditorPanel/components/Panels/Panel.Dendrogram.tsx +211 -0
- package/src/components/EditorPanel/components/Panels/Panel.ForestPlotSettings.tsx +19 -8
- package/src/components/EditorPanel/components/Panels/Panel.General.tsx +88 -54
- package/src/components/EditorPanel/components/Panels/Panel.Network.css +9 -0
- package/src/components/EditorPanel/components/Panels/Panel.Network.tsx +202 -0
- package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +233 -33
- package/src/components/EditorPanel/components/Panels/Panel.Radar.tsx +6 -1
- package/src/components/EditorPanel/components/Panels/Panel.Sankey.tsx +89 -105
- package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +2 -2
- package/src/components/EditorPanel/components/Panels/Panel.SmallMultiples.tsx +1 -1
- package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +8 -66
- package/src/components/EditorPanel/components/Panels/RacingControls.test.tsx +131 -0
- package/src/components/EditorPanel/components/Panels/RacingControls.tsx +185 -0
- package/src/components/EditorPanel/components/Panels/SeriesColorAssignments.tsx +2 -2
- package/src/components/EditorPanel/components/Panels/index.tsx +4 -0
- package/src/components/EditorPanel/helpers/getVisualizationTypeConfigUpdate.ts +20 -7
- package/src/components/EditorPanel/helpers/tests/getVisualizationTypeConfigUpdate.test.ts +25 -2
- package/src/components/EditorPanel/helpers/updateFieldRankByValue.ts +2 -1
- package/src/components/EditorPanel/useEditorPermissions.test.tsx +62 -0
- package/src/components/EditorPanel/useEditorPermissions.ts +70 -24
- package/src/components/Forecasting/Forecasting.tsx +15 -6
- package/src/components/HeatMap/components/HeatMap.tsx +511 -178
- package/src/components/HeatMap/heatmap.constants.ts +7 -3
- package/src/components/HeatMap/heatmap.css +101 -2
- package/src/components/HeatMap/helpers/layout.ts +80 -0
- package/src/components/HeatMap/tests/HeatMap.test.tsx +339 -16
- package/src/components/HeatMap/tests/helpers.test.ts +79 -0
- package/src/components/Legend/Legend.Component.tsx +110 -90
- package/src/components/Legend/LegendGroup/LegendGroup.styles.css +7 -0
- package/src/components/Legend/LegendGroup/LegendGroup.tsx +47 -34
- package/src/components/Legend/helpers/createFormatLabels.test.ts +226 -0
- package/src/components/Legend/helpers/createFormatLabels.tsx +45 -51
- package/src/components/LineChart/components/LineChart.Circle.tsx +3 -3
- package/src/components/LineChart/helpers.test.ts +126 -38
- package/src/components/LineChart/helpers.ts +9 -4
- package/src/components/LineChart/index.tsx +69 -24
- package/src/components/LineChart/tests/LineChart.test.tsx +630 -0
- package/src/components/LineChartRace/LineChartRace.test.tsx +275 -0
- package/src/components/LineChartRace/LineChartRace.tsx +134 -0
- package/src/components/LineChartRace/LineChartRaceFallback.tsx +28 -0
- package/src/components/LineChartRace/helpers.test.ts +88 -0
- package/src/components/LineChartRace/helpers.ts +157 -0
- package/src/components/LineChartRace/index.ts +3 -0
- package/src/components/LineChartRace/line-chart-race.css +16 -0
- package/src/components/LinearChart/ValueAxisAnchors.tsx +58 -0
- package/src/components/LinearChart/VisualizationRenderer.tsx +18 -23
- package/src/components/LinearChart/tests/LinearChart.test.tsx +412 -13
- package/src/components/LinearChart/tests/VisualizationRenderer.test.tsx +141 -0
- package/src/components/LinearChart/tests/mockConfigContext.ts +2 -1
- package/src/components/LinearChart.tsx +81 -51
- package/src/components/Network/components/Network.tsx +372 -0
- package/src/components/Network/helpers/layoutNetwork.test.ts +68 -0
- package/src/components/Network/helpers/layoutNetwork.ts +87 -0
- package/src/components/Network/helpers/prepareNetworkData.test.ts +116 -0
- package/src/components/Network/helpers/prepareNetworkData.ts +101 -0
- package/src/components/Network/helpers/resolveNetworkLabels.test.ts +112 -0
- package/src/components/Network/helpers/resolveNetworkLabels.ts +206 -0
- package/src/components/Network/index.tsx +3 -0
- package/src/components/Network/network.css +107 -0
- package/src/components/Network/types/index.ts +56 -0
- package/src/components/PairedBarChart.jsx +5 -10
- package/src/components/PairedBarChart.test.tsx +71 -0
- package/src/components/PieChart/PieChart.tsx +9 -8
- package/src/components/RacingChartRenderer.tsx +44 -0
- package/src/components/Regions/components/Regions.test.tsx +179 -0
- package/src/components/Regions/components/Regions.tsx +104 -69
- package/src/components/Regions/index.tsx +2 -3
- package/src/components/Sankey/components/Sankey.tsx +264 -437
- package/src/components/Sankey/helpers/getSankeyTooltip.ts +12 -0
- package/src/components/Sankey/helpers/prepareSankeyData.test.ts +71 -0
- package/src/components/Sankey/helpers/prepareSankeyData.ts +90 -0
- package/src/components/Sankey/sankey.scss +57 -116
- package/src/components/Sankey/types/index.ts +41 -4
- package/src/components/ScatterPlot/ScatterPlot.jsx +28 -4
- package/src/components/ScatterPlot/ScatterPlot.test.tsx +60 -0
- package/src/components/SmallMultiples/SmallMultiples.tsx +12 -12
- package/src/components/SmallMultiples/tests/SmallMultipleTile.test.tsx +7 -5
- package/src/components/raceTiming.ts +18 -0
- package/src/data/initial-state.js +77 -31
- package/src/helpers/applyChartTypeContract.ts +71 -0
- package/src/helpers/axisLabelFontSize.ts +2 -2
- package/src/helpers/boxPlotStats.ts +60 -0
- package/src/helpers/bumpChartTooltip.ts +2 -1
- package/src/helpers/calculateHorizontalBarCategoryLabelWidth.test.ts +37 -0
- package/src/helpers/calculateHorizontalBarCategoryLabelWidth.ts +3 -2
- package/src/helpers/calculateRightYAxisWidth.ts +60 -0
- package/src/helpers/colorAssignmentHelpers.ts +2 -2
- package/src/helpers/createNewChartConfig.ts +56 -0
- package/src/helpers/dataDrivenYAxisCategories.ts +18 -0
- package/src/helpers/forecastLegend.ts +88 -0
- package/src/helpers/getBoxPlotConfig.ts +18 -37
- package/src/helpers/getChartTypeDefaultPalette.ts +29 -0
- package/src/helpers/getColorCodeCategoryColorMap.ts +26 -0
- package/src/helpers/getColorScale.ts +31 -22
- package/src/helpers/getDynamicYAxisCategories.ts +69 -0
- package/src/helpers/getMinMax.ts +9 -7
- package/src/helpers/getNewRuntime.ts +5 -2
- package/src/helpers/getStackedSeriesMax.ts +51 -0
- package/src/helpers/getV21ChartDistributionColors.ts +57 -0
- package/src/helpers/handleRankByValue.ts +15 -15
- package/src/helpers/seriesColumnSettings.ts +10 -0
- package/src/helpers/sizeHelpers.ts +4 -3
- package/src/helpers/smallMultiplesHelpers.ts +10 -13
- package/src/helpers/tests/applyChartTypeContract.test.ts +37 -0
- package/src/helpers/tests/boxPlotStats.test.ts +31 -0
- package/src/helpers/tests/bumpChartTooltip.test.ts +29 -0
- package/src/helpers/tests/calculateRightYAxisWidth.test.ts +105 -0
- package/src/helpers/tests/countNumOfTicks.test.ts +11 -0
- package/src/helpers/tests/createNewChartConfig.test.ts +182 -0
- package/src/helpers/tests/getBoxPlotConfig.test.ts +70 -0
- package/src/helpers/tests/getColorCodeCategoryColorMap.test.ts +113 -0
- package/src/helpers/tests/getColorScale.test.ts +226 -1
- package/src/helpers/tests/getDynamicYAxisCategories.test.ts +61 -0
- package/src/helpers/tests/getMinMax.test.ts +41 -0
- package/src/helpers/tests/getStackedSeriesMax.test.ts +26 -0
- package/src/helpers/tests/handleRankByValue.test.ts +55 -37
- package/src/helpers/tests/seriesColumnSettings.test.ts +23 -0
- package/src/helpers/tests/smallMultiplesHelpers.test.ts +37 -1
- package/src/helpers/tests/tooltipHelpers.test.ts +80 -0
- package/src/helpers/tooltipHelpers.ts +16 -0
- package/src/hooks/tests/useTooltip.test.tsx +353 -1
- package/src/hooks/usePrefersReducedMotion.ts +22 -0
- package/src/hooks/useReduceData.ts +4 -36
- package/src/hooks/useScales.ts +15 -0
- package/src/hooks/useTooltip.tsx +95 -64
- package/src/scss/main.scss +74 -2
- package/src/store/chart.reducer.ts +4 -3
- package/src/test/CdcChart.dataTable.test.tsx +490 -16
- package/src/test/CdcChart.dendrogram.test.tsx +277 -0
- package/src/test/CdcChart.finalization.test.tsx +143 -0
- package/src/test/CdcChart.network.test.tsx +271 -0
- package/src/types/ChartConfig.ts +91 -54
- package/src/types/ChartContext.ts +5 -0
- package/src/types/Horizon.ts +1 -1
- package/LICENSE +0 -201
- package/dist/cdcchart-BWVIUcAF.es.js +0 -4802
- package/src/_stories/Chart.Sankey.smoke.stories.tsx +0 -62
- package/src/components/Sankey/components/ColumnList.tsx +0 -19
- package/src/components/Sankey/helpers/getSankeyTooltip.tsx +0 -34
- package/src/components/Sankey/useSankeyAlert.tsx +0 -60
- package/src/data/legacy-defaults.ts +0 -18
- package/src/helpers/ensureSpecialChartAxisTypes.ts +0 -11
- package/src/helpers/tests/ensureSpecialChartAxisTypes.test.ts +0 -30
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
+
import { expect, userEvent, within } from 'storybook/test'
|
|
3
|
+
import Chart from '../CdcChartComponent'
|
|
4
|
+
import sankeyConfig from './_mock/sankey_mock.json'
|
|
5
|
+
import {
|
|
6
|
+
assertVisualizationRendered,
|
|
7
|
+
openAccordion,
|
|
8
|
+
performAndAssert,
|
|
9
|
+
waitForEditor,
|
|
10
|
+
waitForOptionsToPopulate,
|
|
11
|
+
waitForPresence
|
|
12
|
+
} from '@cdc/core/helpers/testing'
|
|
13
|
+
|
|
14
|
+
const tabularSankeyRows = sankeyConfig.data
|
|
15
|
+
const expectedPaletteColors = {
|
|
16
|
+
node: '#032659',
|
|
17
|
+
link: '#dbe8f7'
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const normalizeColor = (color: string | null) => color?.toLowerCase() || ''
|
|
21
|
+
|
|
22
|
+
const longLabelRows = [
|
|
23
|
+
{
|
|
24
|
+
source: 'Public health program referral source with a long label',
|
|
25
|
+
target: 'Eligibility review and documentation step',
|
|
26
|
+
value: '1,200'
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
source: 'Public health program referral source with a long label',
|
|
30
|
+
target: 'Not eligible after review',
|
|
31
|
+
value: 240
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
source: 'Eligibility review and documentation step',
|
|
35
|
+
target: 'Enrolled in follow up services',
|
|
36
|
+
value: 820
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
source: 'Eligibility review and documentation step',
|
|
40
|
+
target: 'Declined services after review',
|
|
41
|
+
value: 140
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
source: 'Ignored zero value',
|
|
45
|
+
target: 'Should not render',
|
|
46
|
+
value: 0
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
source: 'Ignored invalid value',
|
|
50
|
+
target: 'Should not render',
|
|
51
|
+
value: 'not available'
|
|
52
|
+
}
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
const mappedColumnRows = [
|
|
56
|
+
{ Step: 'Intake', Next: 'Eligible', People: 12 },
|
|
57
|
+
{ Step: 'Eligible', Next: 'Enrolled', People: 9 },
|
|
58
|
+
{ Step: 'Eligible', Next: 'Declined', People: 3 }
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
const assertSankeyRendered = async (
|
|
62
|
+
canvasElement: HTMLElement,
|
|
63
|
+
expectedRejectedRows = 0,
|
|
64
|
+
expectedScrollable = false,
|
|
65
|
+
expectedLabelSides: Record<string, 'left' | 'right'> = {},
|
|
66
|
+
expectedColors?: { node: string; link: string }
|
|
67
|
+
) => {
|
|
68
|
+
await assertVisualizationRendered(canvasElement)
|
|
69
|
+
|
|
70
|
+
const svg = (await waitForPresence('.sankey-chart__diagram', canvasElement)) as SVGElement
|
|
71
|
+
const scrollArea = (await waitForPresence('.sankey-chart__scroll-area', canvasElement)) as HTMLElement
|
|
72
|
+
const nodes = Array.from(svg.querySelectorAll('.sankey-chart__node'))
|
|
73
|
+
const links = Array.from(svg.querySelectorAll('.sankey-chart__link'))
|
|
74
|
+
const labels = Array.from(svg.querySelectorAll('.sankey-chart__label'))
|
|
75
|
+
const svgRect = svg.getBoundingClientRect()
|
|
76
|
+
const scrollAreaRect = scrollArea.getBoundingClientRect()
|
|
77
|
+
|
|
78
|
+
expect(nodes.length).toBeGreaterThan(0)
|
|
79
|
+
expect(links.length).toBeGreaterThan(0)
|
|
80
|
+
expect(labels.length).toBe(nodes.length)
|
|
81
|
+
expect(svgRect.width).toBeGreaterThan(250)
|
|
82
|
+
expect(svgRect.height).toBeGreaterThan(300)
|
|
83
|
+
expect(Number(svg.getAttribute('data-rejected-row-count'))).toBe(expectedRejectedRows)
|
|
84
|
+
|
|
85
|
+
if (expectedScrollable) {
|
|
86
|
+
expect(scrollArea.scrollWidth).toBeGreaterThan(scrollArea.clientWidth)
|
|
87
|
+
expect(svgRect.width).toBeGreaterThan(scrollAreaRect.width)
|
|
88
|
+
} else {
|
|
89
|
+
expect(scrollArea.scrollWidth).toBeLessThanOrEqual(scrollArea.clientWidth + 1)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
labels.forEach(label => {
|
|
93
|
+
const labelRect = label.getBoundingClientRect()
|
|
94
|
+
expect(labelRect.width).toBeGreaterThan(0)
|
|
95
|
+
expect(labelRect.left).toBeGreaterThanOrEqual(svgRect.left - 1)
|
|
96
|
+
expect(labelRect.right).toBeLessThanOrEqual(svgRect.right + 1)
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
Object.entries(expectedLabelSides).forEach(([nodeId, side]) => {
|
|
100
|
+
const label = labels.find(label => label.getAttribute('data-node-id') === nodeId)
|
|
101
|
+
expect(label?.getAttribute('data-label-side')).toBe(side)
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
if (expectedColors) {
|
|
105
|
+
nodes.forEach(node => {
|
|
106
|
+
expect(normalizeColor(node.getAttribute('fill'))).toBe(expectedColors.node)
|
|
107
|
+
})
|
|
108
|
+
links.forEach(link => {
|
|
109
|
+
expect(normalizeColor(link.getAttribute('stroke'))).toBe(expectedColors.link)
|
|
110
|
+
})
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const expectedTabularLabelSides = {
|
|
115
|
+
Screened: 'left',
|
|
116
|
+
Eligible: 'right',
|
|
117
|
+
Enrolled: 'right',
|
|
118
|
+
'Completed Treatment': 'right',
|
|
119
|
+
Ineligible: 'right',
|
|
120
|
+
Declined: 'right',
|
|
121
|
+
'Dropped Out': 'right',
|
|
122
|
+
Recovered: 'right',
|
|
123
|
+
'Ongoing Care': 'right'
|
|
124
|
+
} as const
|
|
125
|
+
|
|
126
|
+
const getSankeyNodeIds = (canvasElement: HTMLElement) =>
|
|
127
|
+
Array.from(canvasElement.querySelectorAll('.sankey-chart__node'))
|
|
128
|
+
.map(node => node.getAttribute('data-node-id') || '')
|
|
129
|
+
.filter(Boolean)
|
|
130
|
+
.sort()
|
|
131
|
+
|
|
132
|
+
const getSankeyTooltipAttributeCount = (canvasElement: HTMLElement) =>
|
|
133
|
+
Array.from(canvasElement.querySelectorAll('.sankey-chart__node, .sankey-chart__link')).filter(element =>
|
|
134
|
+
element.getAttribute('data-tooltip-html')
|
|
135
|
+
).length
|
|
136
|
+
|
|
137
|
+
const getSankeyHighlightState = (canvasElement: HTMLElement) => {
|
|
138
|
+
const nodeOpacity = Object.fromEntries(
|
|
139
|
+
Array.from(canvasElement.querySelectorAll('.sankey-chart__node')).map(node => [
|
|
140
|
+
node.getAttribute('data-node-id') || '',
|
|
141
|
+
node.getAttribute('fill-opacity') || ''
|
|
142
|
+
])
|
|
143
|
+
)
|
|
144
|
+
const linkOpacity = Object.fromEntries(
|
|
145
|
+
Array.from(canvasElement.querySelectorAll('.sankey-chart__link')).map(link => [
|
|
146
|
+
`${link.getAttribute('data-link-source-id')}->${link.getAttribute('data-link-target-id')}`,
|
|
147
|
+
link.getAttribute('stroke-opacity') || ''
|
|
148
|
+
])
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
return { linkOpacity, nodeOpacity }
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const meta: Meta<typeof Chart> = {
|
|
155
|
+
title: 'Components/Templates/Chart/Sankey',
|
|
156
|
+
component: Chart
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export default meta
|
|
160
|
+
type Story = StoryObj<typeof Chart>
|
|
161
|
+
|
|
162
|
+
export const Sankey_TabularImport: Story = {
|
|
163
|
+
args: {
|
|
164
|
+
config: {
|
|
165
|
+
...sankeyConfig,
|
|
166
|
+
title: 'Patient Care Pathway',
|
|
167
|
+
data: tabularSankeyRows
|
|
168
|
+
},
|
|
169
|
+
isEditor: false
|
|
170
|
+
},
|
|
171
|
+
play: async ({ canvasElement }) => {
|
|
172
|
+
await assertSankeyRendered(canvasElement, 0, false, expectedTabularLabelSides, expectedPaletteColors)
|
|
173
|
+
|
|
174
|
+
const screenedEligibleLink = canvasElement.querySelector(
|
|
175
|
+
'[data-link-source-id="Screened"][data-link-target-id="Eligible"]'
|
|
176
|
+
)
|
|
177
|
+
expect(screenedEligibleLink?.getAttribute('data-tooltip-html')).toBe(`<div class="sankey-chart__tooltip">
|
|
178
|
+
<span class="sankey-chart__tooltip--tooltip-header">Screened → Eligible</span>
|
|
179
|
+
<span>850</span>
|
|
180
|
+
</div>`)
|
|
181
|
+
|
|
182
|
+
await performAndAssert(
|
|
183
|
+
'Sankey downstream highlight',
|
|
184
|
+
() => getSankeyHighlightState(canvasElement),
|
|
185
|
+
async () => {
|
|
186
|
+
const enrolledNode = canvasElement.querySelector('[data-node-id="Enrolled"]') as SVGRectElement
|
|
187
|
+
await userEvent.click(enrolledNode)
|
|
188
|
+
},
|
|
189
|
+
(_before, after) =>
|
|
190
|
+
['Enrolled', 'Completed Treatment', 'Dropped Out', 'Recovered', 'Ongoing Care'].every(
|
|
191
|
+
nodeId => after.nodeOpacity[nodeId] === '1'
|
|
192
|
+
) &&
|
|
193
|
+
['Screened', 'Eligible', 'Ineligible', 'Declined'].every(nodeId => after.nodeOpacity[nodeId] === '0.1') &&
|
|
194
|
+
[
|
|
195
|
+
'Enrolled->Completed Treatment',
|
|
196
|
+
'Enrolled->Dropped Out',
|
|
197
|
+
'Completed Treatment->Recovered',
|
|
198
|
+
'Completed Treatment->Ongoing Care'
|
|
199
|
+
].every(linkId => after.linkOpacity[linkId] === '0.6') &&
|
|
200
|
+
['Screened->Eligible', 'Screened->Ineligible', 'Eligible->Enrolled', 'Eligible->Declined'].every(
|
|
201
|
+
linkId => after.linkOpacity[linkId] === '0.1'
|
|
202
|
+
)
|
|
203
|
+
)
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export const Sankey_NarrowViewport: Story = {
|
|
208
|
+
decorators: [
|
|
209
|
+
Story => (
|
|
210
|
+
<div style={{ width: '360px' }}>
|
|
211
|
+
<Story />
|
|
212
|
+
</div>
|
|
213
|
+
)
|
|
214
|
+
],
|
|
215
|
+
args: {
|
|
216
|
+
config: {
|
|
217
|
+
...sankeyConfig,
|
|
218
|
+
title: 'Patient Care Pathway - Narrow',
|
|
219
|
+
data: tabularSankeyRows,
|
|
220
|
+
sankey: {
|
|
221
|
+
...sankeyConfig.sankey,
|
|
222
|
+
horizontalScrollWidth: 900
|
|
223
|
+
}
|
|
224
|
+
},
|
|
225
|
+
isEditor: false
|
|
226
|
+
},
|
|
227
|
+
play: async ({ canvasElement }) => {
|
|
228
|
+
await assertSankeyRendered(canvasElement, 0, true, expectedTabularLabelSides, expectedPaletteColors)
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export const Sankey_MediumViewportFullWidthWhenUnset: Story = {
|
|
233
|
+
decorators: [
|
|
234
|
+
Story => (
|
|
235
|
+
<div style={{ width: '760px' }}>
|
|
236
|
+
<Story />
|
|
237
|
+
</div>
|
|
238
|
+
)
|
|
239
|
+
],
|
|
240
|
+
args: {
|
|
241
|
+
config: {
|
|
242
|
+
...sankeyConfig,
|
|
243
|
+
title: 'Patient Care Pathway - Medium Full Width',
|
|
244
|
+
data: tabularSankeyRows,
|
|
245
|
+
sankey: {
|
|
246
|
+
...sankeyConfig.sankey,
|
|
247
|
+
horizontalScrollWidth: ''
|
|
248
|
+
}
|
|
249
|
+
},
|
|
250
|
+
isEditor: false
|
|
251
|
+
},
|
|
252
|
+
play: async ({ canvasElement }) => {
|
|
253
|
+
await assertSankeyRendered(canvasElement, 0, false, expectedTabularLabelSides, expectedPaletteColors)
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export const Sankey_MediumViewport: Story = {
|
|
258
|
+
decorators: [
|
|
259
|
+
Story => (
|
|
260
|
+
<div style={{ width: '760px' }}>
|
|
261
|
+
<Story />
|
|
262
|
+
</div>
|
|
263
|
+
)
|
|
264
|
+
],
|
|
265
|
+
args: {
|
|
266
|
+
config: {
|
|
267
|
+
...sankeyConfig,
|
|
268
|
+
title: 'Patient Care Pathway - Medium',
|
|
269
|
+
data: tabularSankeyRows,
|
|
270
|
+
sankey: {
|
|
271
|
+
...sankeyConfig.sankey,
|
|
272
|
+
horizontalScrollWidth: 900
|
|
273
|
+
}
|
|
274
|
+
},
|
|
275
|
+
isEditor: false
|
|
276
|
+
},
|
|
277
|
+
play: async ({ canvasElement }) => {
|
|
278
|
+
await assertSankeyRendered(canvasElement, 0, true, expectedTabularLabelSides, expectedPaletteColors)
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export const Sankey_CustomHorizontalScrollWidth: Story = {
|
|
283
|
+
decorators: [
|
|
284
|
+
Story => (
|
|
285
|
+
<div style={{ width: '980px' }}>
|
|
286
|
+
<Story />
|
|
287
|
+
</div>
|
|
288
|
+
)
|
|
289
|
+
],
|
|
290
|
+
args: {
|
|
291
|
+
config: {
|
|
292
|
+
...sankeyConfig,
|
|
293
|
+
title: 'Patient Care Pathway - Custom Scroll Width',
|
|
294
|
+
data: tabularSankeyRows,
|
|
295
|
+
sankey: {
|
|
296
|
+
...sankeyConfig.sankey,
|
|
297
|
+
horizontalScrollWidth: 1100
|
|
298
|
+
}
|
|
299
|
+
},
|
|
300
|
+
isEditor: false
|
|
301
|
+
},
|
|
302
|
+
play: async ({ canvasElement }) => {
|
|
303
|
+
await assertSankeyRendered(canvasElement, 0, true, expectedTabularLabelSides, expectedPaletteColors)
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
export const Sankey_LongLabelsAndInvalidRows: Story = {
|
|
308
|
+
args: {
|
|
309
|
+
config: {
|
|
310
|
+
...sankeyConfig,
|
|
311
|
+
title: 'Sankey with Long Labels and Invalid Rows',
|
|
312
|
+
data: longLabelRows
|
|
313
|
+
},
|
|
314
|
+
isEditor: false
|
|
315
|
+
},
|
|
316
|
+
play: async ({ canvasElement }) => {
|
|
317
|
+
await assertSankeyRendered(canvasElement, 2)
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export const Sankey_TooltipsDisabled: Story = {
|
|
322
|
+
args: {
|
|
323
|
+
config: {
|
|
324
|
+
...sankeyConfig,
|
|
325
|
+
title: 'Sankey - Tooltips Disabled',
|
|
326
|
+
data: tabularSankeyRows,
|
|
327
|
+
enableTooltips: false
|
|
328
|
+
},
|
|
329
|
+
isEditor: false
|
|
330
|
+
},
|
|
331
|
+
play: async ({ canvasElement }) => {
|
|
332
|
+
await assertSankeyRendered(canvasElement)
|
|
333
|
+
expect(getSankeyTooltipAttributeCount(canvasElement)).toBe(0)
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
export const Sankey_DataColumnControls: Story = {
|
|
338
|
+
args: {
|
|
339
|
+
config: {
|
|
340
|
+
...sankeyConfig,
|
|
341
|
+
title: 'Sankey - Data Column Controls',
|
|
342
|
+
data: mappedColumnRows,
|
|
343
|
+
sankey: {
|
|
344
|
+
...sankeyConfig.sankey,
|
|
345
|
+
horizontalScrollWidth: '',
|
|
346
|
+
columns: {
|
|
347
|
+
source: '',
|
|
348
|
+
target: '',
|
|
349
|
+
value: ''
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
},
|
|
353
|
+
isEditor: true
|
|
354
|
+
},
|
|
355
|
+
play: async ({ canvasElement }) => {
|
|
356
|
+
const canvas = within(canvasElement)
|
|
357
|
+
await waitForEditor(canvas)
|
|
358
|
+
await openAccordion(canvas, 'Sankey')
|
|
359
|
+
|
|
360
|
+
const sourceSelect = canvas.getByLabelText('Source Column') as HTMLSelectElement
|
|
361
|
+
await waitForOptionsToPopulate(sourceSelect, 4)
|
|
362
|
+
const scrollWidthInput = canvas.getByLabelText('Horizontal Scroll Width') as HTMLInputElement
|
|
363
|
+
expect(scrollWidthInput.value).toBe('')
|
|
364
|
+
|
|
365
|
+
await userEvent.type(scrollWidthInput, '640')
|
|
366
|
+
expect(scrollWidthInput.value).toBe('640')
|
|
367
|
+
|
|
368
|
+
await performAndAssert(
|
|
369
|
+
'Sankey data column mapping',
|
|
370
|
+
() => getSankeyNodeIds(canvasElement),
|
|
371
|
+
async () => {
|
|
372
|
+
await userEvent.selectOptions(canvas.getByLabelText('Source Column'), 'Step')
|
|
373
|
+
await userEvent.selectOptions(canvas.getByLabelText('Target Column'), 'Next')
|
|
374
|
+
await userEvent.selectOptions(canvas.getByLabelText('Value Column'), 'People')
|
|
375
|
+
},
|
|
376
|
+
(before, after) =>
|
|
377
|
+
before.length === 0 && ['Declined', 'Eligible', 'Enrolled', 'Intake'].every(nodeId => after.includes(nodeId))
|
|
378
|
+
)
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
export const Sankey_TooltipControl: Story = {
|
|
383
|
+
args: {
|
|
384
|
+
config: {
|
|
385
|
+
...sankeyConfig,
|
|
386
|
+
title: 'Sankey - Tooltip Control',
|
|
387
|
+
data: tabularSankeyRows,
|
|
388
|
+
enableTooltips: false
|
|
389
|
+
},
|
|
390
|
+
isEditor: true
|
|
391
|
+
},
|
|
392
|
+
play: async ({ canvasElement }) => {
|
|
393
|
+
const canvas = within(canvasElement)
|
|
394
|
+
await waitForEditor(canvas)
|
|
395
|
+
await openAccordion(canvas, 'Sankey')
|
|
396
|
+
const scrollWidthInput = canvas.getByLabelText('Horizontal Scroll Width') as HTMLInputElement
|
|
397
|
+
|
|
398
|
+
await performAndAssert(
|
|
399
|
+
'Sankey horizontal scroll width input',
|
|
400
|
+
() => scrollWidthInput.value,
|
|
401
|
+
async () => {
|
|
402
|
+
await userEvent.clear(scrollWidthInput)
|
|
403
|
+
await userEvent.type(scrollWidthInput, '640')
|
|
404
|
+
},
|
|
405
|
+
(before, after) => before === '900' && after === '640'
|
|
406
|
+
)
|
|
407
|
+
|
|
408
|
+
await performAndAssert(
|
|
409
|
+
'Sankey tooltip toggle',
|
|
410
|
+
() => getSankeyTooltipAttributeCount(canvasElement),
|
|
411
|
+
async () => {
|
|
412
|
+
await userEvent.click(canvas.getByLabelText('Show Tooltips'))
|
|
413
|
+
},
|
|
414
|
+
(before, after) => before === 0 && after > 0
|
|
415
|
+
)
|
|
416
|
+
}
|
|
417
|
+
}
|
|
@@ -16,6 +16,11 @@ const assignedColors = {
|
|
|
16
16
|
charlie: '#5A8E3F'
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
const comboColors = {
|
|
20
|
+
bars: '#0057B7',
|
|
21
|
+
line: '#E69F00'
|
|
22
|
+
}
|
|
23
|
+
|
|
19
24
|
const normalizeColor = (color: string) => {
|
|
20
25
|
const swatch = document.createElement('div')
|
|
21
26
|
swatch.style.color = color
|
|
@@ -180,4 +185,46 @@ export const FilteredSeriesKeepAssignedColors: Story = {
|
|
|
180
185
|
}
|
|
181
186
|
}
|
|
182
187
|
|
|
188
|
+
const comboWithIsolatedLinePointConfig = {
|
|
189
|
+
...config,
|
|
190
|
+
title: 'Combo Chart Isolated Point Color',
|
|
191
|
+
visualizationType: 'Combo',
|
|
192
|
+
lineDatapointStyle: 'hidden',
|
|
193
|
+
general: {
|
|
194
|
+
...config.general,
|
|
195
|
+
palette: {
|
|
196
|
+
...config.general.palette,
|
|
197
|
+
colorAssignments: [
|
|
198
|
+
{ key: 'bars', color: comboColors.bars },
|
|
199
|
+
{ key: 'line', color: comboColors.line }
|
|
200
|
+
]
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
legend: {
|
|
204
|
+
...config.legend,
|
|
205
|
+
hide: true
|
|
206
|
+
},
|
|
207
|
+
series: [
|
|
208
|
+
{ dataKey: 'bars', name: 'Bars', type: 'Bar', axis: 'Left', tooltip: true },
|
|
209
|
+
{ dataKey: 'line', name: 'Line', type: 'Line', axis: 'Left', tooltip: true }
|
|
210
|
+
],
|
|
211
|
+
filters: [],
|
|
212
|
+
data: [
|
|
213
|
+
{ week: '2024-01-01', bars: 10, line: 20 },
|
|
214
|
+
{ week: '2024-01-08', bars: 12, line: null },
|
|
215
|
+
{ week: '2024-01-15', bars: 14, line: null }
|
|
216
|
+
]
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export const ComboIsolatedPointUsesLineColor: Story = {
|
|
220
|
+
args: {
|
|
221
|
+
config: comboWithIsolatedLinePointConfig,
|
|
222
|
+
isEditor: false
|
|
223
|
+
},
|
|
224
|
+
play: async ({ canvasElement }) => {
|
|
225
|
+
await assertVisualizationRendered(canvasElement)
|
|
226
|
+
await expectVisibleSeriesColors(canvasElement, [['0', comboColors.line]])
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
183
230
|
export default meta
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { useMemo } from 'react'
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
3
|
+
import { expect, waitFor } from 'storybook/test'
|
|
4
|
+
|
|
5
|
+
import { editConfigKeys } from '@cdc/core/helpers/configHelpers'
|
|
6
|
+
import { assertVisualizationRendered } from '@cdc/core/helpers/testing'
|
|
7
|
+
import Chart from '../CdcChartComponent'
|
|
8
|
+
import smallMultiplesBars from './_mock/small_multiples/small_multiples_bars.json'
|
|
9
|
+
import type { ChartConfig } from '../types/ChartConfig'
|
|
10
|
+
|
|
11
|
+
const sourceTileValues = ['10', '2', '1', '20', '3']
|
|
12
|
+
const expectedNumericAwareOrder = ['Week 1', 'Week 2', 'Week 3', 'Week 10', 'Week 20']
|
|
13
|
+
|
|
14
|
+
const numericTileRows = sourceTileValues.map((week, index) => ({
|
|
15
|
+
Week: week,
|
|
16
|
+
Date: 'Current period',
|
|
17
|
+
Cases: String((index + 1) * 10)
|
|
18
|
+
}))
|
|
19
|
+
|
|
20
|
+
const tileTitles = sourceTileValues.reduce<Record<string, string>>((titles, week) => {
|
|
21
|
+
titles[week] = `Week ${week}`
|
|
22
|
+
return titles
|
|
23
|
+
}, {})
|
|
24
|
+
|
|
25
|
+
const getConfig = (): ChartConfig =>
|
|
26
|
+
editConfigKeys(smallMultiplesBars, [
|
|
27
|
+
{ path: ['title'], value: 'Small Multiples Numeric Tile Order' },
|
|
28
|
+
{ path: ['showTitle'], value: true },
|
|
29
|
+
{ path: ['visualizationSubType'], value: 'regular' },
|
|
30
|
+
{ path: ['data'], value: numericTileRows },
|
|
31
|
+
{ path: ['formattedData'], value: numericTileRows },
|
|
32
|
+
{ path: ['dataUrl'], value: '' },
|
|
33
|
+
{ path: ['runtimeDataUrl'], value: '' },
|
|
34
|
+
{ path: ['filters'], value: [] },
|
|
35
|
+
{ path: ['table', 'show'], value: false },
|
|
36
|
+
{ path: ['xAxis', 'dataKey'], value: 'Date' },
|
|
37
|
+
{ path: ['xAxis', 'type'], value: 'categorical' },
|
|
38
|
+
{ path: ['xAxis', 'label'], value: 'Date' },
|
|
39
|
+
{ path: ['yAxis', 'label'], value: 'Cases' },
|
|
40
|
+
{ path: ['yAxis', 'hideAxis'], value: false },
|
|
41
|
+
{ path: ['yAxis', 'hideTicks'], value: false },
|
|
42
|
+
{ path: ['yAxis', 'numTicks'], value: '3' },
|
|
43
|
+
{
|
|
44
|
+
path: ['series'],
|
|
45
|
+
value: [{ dataKey: 'Cases', type: 'Bar', axis: 'Left', tooltip: true }]
|
|
46
|
+
},
|
|
47
|
+
{ path: ['smallMultiples', 'mode'], value: 'by-column' },
|
|
48
|
+
{ path: ['smallMultiples', 'tileColumn'], value: 'Week' },
|
|
49
|
+
{ path: ['smallMultiples', 'tileOrderType'], value: 'asc' },
|
|
50
|
+
{ path: ['smallMultiples', 'tileOrder'], value: [] },
|
|
51
|
+
{ path: ['smallMultiples', 'tileTitles'], value: tileTitles },
|
|
52
|
+
{ path: ['smallMultiples', 'tilesPerRowDesktop'], value: 5 },
|
|
53
|
+
{ path: ['smallMultiples', 'tilesPerRowMobile'], value: 1 }
|
|
54
|
+
]) as ChartConfig
|
|
55
|
+
|
|
56
|
+
const getRenderedTileTitles = (canvasElement: HTMLElement) =>
|
|
57
|
+
Array.from(canvasElement.querySelectorAll('.small-multiple-tile .tile-title')).map(
|
|
58
|
+
title => title.textContent?.trim() || ''
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
function SmallMultiplesNumericTileOrderRegression() {
|
|
62
|
+
const config = useMemo(() => getConfig(), [])
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<div
|
|
66
|
+
style={{
|
|
67
|
+
background: '#f7f8fa',
|
|
68
|
+
display: 'grid',
|
|
69
|
+
gap: 16,
|
|
70
|
+
padding: 16
|
|
71
|
+
}}
|
|
72
|
+
>
|
|
73
|
+
<section
|
|
74
|
+
style={{
|
|
75
|
+
background: '#fff',
|
|
76
|
+
border: '1px solid #d9dfe7',
|
|
77
|
+
borderRadius: 6,
|
|
78
|
+
padding: 16
|
|
79
|
+
}}
|
|
80
|
+
>
|
|
81
|
+
<Chart config={config} isEditor={true} interactionLabel='small-multiples-numeric-tile-order-regression' />
|
|
82
|
+
</section>
|
|
83
|
+
|
|
84
|
+
<section
|
|
85
|
+
aria-label='Small multiples numeric tile order diagnostic'
|
|
86
|
+
style={{
|
|
87
|
+
background: '#fff',
|
|
88
|
+
border: '1px solid #d9dfe7',
|
|
89
|
+
borderRadius: 6,
|
|
90
|
+
display: 'grid',
|
|
91
|
+
gap: 8,
|
|
92
|
+
padding: 12
|
|
93
|
+
}}
|
|
94
|
+
>
|
|
95
|
+
<div>
|
|
96
|
+
<strong>Input tile values:</strong> <span>{sourceTileValues.join(', ')}</span>
|
|
97
|
+
</div>
|
|
98
|
+
<div>
|
|
99
|
+
<strong>Expected numeric-aware order:</strong> <span>{expectedNumericAwareOrder.join(', ')}</span>
|
|
100
|
+
</div>
|
|
101
|
+
<div>
|
|
102
|
+
<strong>Expected rendered order:</strong>{' '}
|
|
103
|
+
<span data-testid='small-multiples-expected-order'>{expectedNumericAwareOrder.join(', ')}</span>
|
|
104
|
+
</div>
|
|
105
|
+
<div data-testid='small-multiples-order-status'>
|
|
106
|
+
<strong>Status:</strong> Numeric order preserved
|
|
107
|
+
</div>
|
|
108
|
+
</section>
|
|
109
|
+
</div>
|
|
110
|
+
)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const meta: Meta<typeof SmallMultiplesNumericTileOrderRegression> = {
|
|
114
|
+
title: 'Components/Templates/Chart/Small Multiples',
|
|
115
|
+
component: SmallMultiplesNumericTileOrderRegression,
|
|
116
|
+
parameters: {
|
|
117
|
+
layout: 'fullscreen'
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export default meta
|
|
122
|
+
type Story = StoryObj<typeof SmallMultiplesNumericTileOrderRegression>
|
|
123
|
+
|
|
124
|
+
export const Numeric_Tile_Values_Render_In_Numeric_Aware_Order: Story = {
|
|
125
|
+
play: async ({ canvasElement }) => {
|
|
126
|
+
await assertVisualizationRendered(canvasElement)
|
|
127
|
+
|
|
128
|
+
await waitFor(() => {
|
|
129
|
+
const renderedTileTitles = getRenderedTileTitles(canvasElement)
|
|
130
|
+
|
|
131
|
+
expect(renderedTileTitles).toEqual(expectedNumericAwareOrder)
|
|
132
|
+
expect(canvasElement.querySelector('[data-testid="small-multiples-order-status"]')?.textContent).toContain(
|
|
133
|
+
'Numeric order preserved'
|
|
134
|
+
)
|
|
135
|
+
})
|
|
136
|
+
}
|
|
137
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
+
import { expect } from 'storybook/test'
|
|
3
|
+
import { assertVisualizationRendered } from '@cdc/core/helpers/testing'
|
|
4
|
+
import Chart from '../CdcChartComponent'
|
|
5
|
+
import pairedBar from './_mock/paired-bar.json'
|
|
6
|
+
|
|
7
|
+
const meta: Meta<typeof Chart> = {
|
|
8
|
+
title: 'Components/Templates/Chart/Tooltip/Series Labels',
|
|
9
|
+
component: Chart
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
type Story = StoryObj<typeof Chart>
|
|
13
|
+
|
|
14
|
+
export const Paired_Bar_Custom_Series_Labels: Story = {
|
|
15
|
+
args: {
|
|
16
|
+
config: {
|
|
17
|
+
...pairedBar,
|
|
18
|
+
title: 'Paired Bar Custom Series Labels',
|
|
19
|
+
series: pairedBar.series.map(series => ({
|
|
20
|
+
...series,
|
|
21
|
+
name: series.dataKey === 'Male' ? 'Reported Males' : 'Reported Females'
|
|
22
|
+
})),
|
|
23
|
+
columns: {
|
|
24
|
+
Male: { name: 'Male', label: 'Male Rate' },
|
|
25
|
+
Female: { name: 'Female', label: 'Female Rate' }
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
play: async ({ canvasElement }) => {
|
|
30
|
+
await assertVisualizationRendered(canvasElement)
|
|
31
|
+
|
|
32
|
+
const firstBarTooltip = canvasElement.querySelector('.bar.group-1')?.getAttribute('data-tooltip-html') || ''
|
|
33
|
+
const secondBarTooltip = canvasElement.querySelector('.bar.group-2')?.getAttribute('data-tooltip-html') || ''
|
|
34
|
+
|
|
35
|
+
expect(firstBarTooltip).toContain('Male Rate:')
|
|
36
|
+
expect(firstBarTooltip).not.toContain('Reported Males:')
|
|
37
|
+
expect(secondBarTooltip).toContain('Female Rate:')
|
|
38
|
+
expect(secondBarTooltip).not.toContain('Reported Females:')
|
|
39
|
+
expect(`${firstBarTooltip}${secondBarTooltip}`).not.toContain('undefined:')
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export default meta
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
+
import { expect, within } from 'storybook/test'
|
|
3
|
+
import { assertVisualizationRendered } from '@cdc/core/helpers/testing'
|
|
4
|
+
import Chart from '../CdcChart'
|
|
5
|
+
import config from '../../examples/vega-remote.json'
|
|
6
|
+
import remoteDataUrl from '../../examples/__data__/vega-remote-data.json?url'
|
|
7
|
+
|
|
8
|
+
const meta: Meta<typeof Chart> = {
|
|
9
|
+
title: 'Components/Templates/Chart/Vega Remote Data',
|
|
10
|
+
component: Chart
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export default meta
|
|
14
|
+
|
|
15
|
+
type Story = StoryObj<typeof Chart>
|
|
16
|
+
|
|
17
|
+
export const ProcessesFetchedData: Story = {
|
|
18
|
+
args: {
|
|
19
|
+
config: { ...config, dataUrl: remoteDataUrl } as any,
|
|
20
|
+
isEditor: false
|
|
21
|
+
},
|
|
22
|
+
play: async ({ canvasElement }) => {
|
|
23
|
+
await assertVisualizationRendered(canvasElement)
|
|
24
|
+
|
|
25
|
+
const table = await within(canvasElement).findByRole('table', { name: /data table showing data for the chart/i })
|
|
26
|
+
expect(within(table).getByRole('columnheader', { name: 'Alpha' })).toBeVisible()
|
|
27
|
+
expect(within(table).getByRole('gridcell', { name: '20' })).toBeVisible()
|
|
28
|
+
expect(within(table).getByRole('columnheader', { name: 'Beta' })).toBeVisible()
|
|
29
|
+
expect(within(table).getByRole('gridcell', { name: '40' })).toBeVisible()
|
|
30
|
+
}
|
|
31
|
+
}
|