@cdc/chart 4.26.6 → 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 +504 -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 -96498
- 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/flueview.json +22051 -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 -13
- package/src/CdcChart.tsx +2 -2
- package/src/CdcChartComponent.tsx +444 -217
- 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 +49 -0
- 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/Chart.smoke.stories.tsx +14 -1
- 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/ChartPrefixSuffix.smoke.stories.tsx +28 -1
- 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 +39 -8
- package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +13 -4
- package/src/components/BarChart/components/BarChart.StackedVertical.test.ts +28 -0
- package/src/components/BarChart/components/BarChart.StackedVertical.tsx +30 -26
- package/src/components/BarChart/components/BarChart.Vertical.tsx +38 -28
- package/src/components/BarChart/components/PortionPatternOverlay.tsx +61 -0
- package/src/components/BarChart/components/context.tsx +6 -0
- package/src/components/BarChart/helpers/getBarData.ts +28 -28
- package/src/components/BarChart/helpers/getBarHeights.ts +2 -2
- 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 +202 -0
- package/src/components/BarChart/helpers/useBarChart.ts +60 -60
- 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/BoxPlot.Horizontal.tsx +7 -6
- package/src/components/BoxPlot/BoxPlot.Vertical.tsx +9 -7
- package/src/components/BoxPlot/helpers/index.ts +13 -83
- package/src/components/Brush/BrushSelector.tsx +10 -24
- 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 +1111 -805
- 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 +104 -61
- 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 +236 -36
- 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 +21 -6
- 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 +5 -4
- package/src/components/EditorPanel/useEditorPermissions.test.tsx +94 -0
- package/src/components/EditorPanel/useEditorPermissions.ts +75 -25
- package/src/components/Forecasting/Forecasting.tsx +16 -7
- 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 +163 -148
- package/src/components/Legend/Legend.Suppression.tsx +3 -3
- package/src/components/Legend/LegendGroup/LegendGroup.styles.css +7 -0
- package/src/components/Legend/LegendGroup/LegendGroup.tsx +47 -35
- 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 -6
- package/src/components/LineChart/index.tsx +71 -27
- 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 +85 -54
- 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 +121 -13
- package/src/components/PieChart/helpers/labelPlacement.test.ts +88 -0
- package/src/components/PieChart/helpers/labelPlacement.ts +95 -0
- 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 -12
- 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 +79 -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/categoryOrder.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 +26 -38
- package/src/helpers/getChartTypeDefaultPalette.ts +29 -0
- package/src/helpers/getColorCodeCategoryColorMap.ts +26 -0
- package/src/helpers/getColorScale.ts +31 -22
- package/src/helpers/getComboChartConfig.ts +0 -1
- package/src/helpers/getDynamicYAxisCategories.ts +69 -0
- package/src/helpers/getExcludedData.ts +0 -1
- package/src/helpers/getHasBoundarySuppression.ts +4 -3
- package/src/helpers/getMinMax.ts +11 -8
- package/src/helpers/getNewRuntime.ts +4 -3
- package/src/helpers/getPiePercent.ts +3 -3
- package/src/helpers/getStackedSeriesMax.ts +51 -0
- package/src/helpers/getV21ChartDistributionColors.ts +57 -0
- package/src/helpers/handleRankByValue.ts +15 -15
- package/src/helpers/isConvertLineToBarGraph.ts +2 -2
- package/src/helpers/seriesColumnSettings.ts +31 -3
- package/src/helpers/sizeHelpers.ts +4 -5
- 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/categoryOrder.test.ts +57 -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 +83 -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/useRightAxis.test.tsx +143 -2
- package/src/hooks/tests/useScales.test.tsx +3 -6
- package/src/hooks/tests/useTooltip.test.tsx +484 -0
- package/src/hooks/usePrefersReducedMotion.ts +22 -0
- package/src/hooks/useReduceData.ts +4 -36
- package/src/hooks/useRightAxis.ts +19 -26
- package/src/hooks/useScales.ts +15 -0
- package/src/hooks/useTooltip.tsx +106 -71
- package/src/scss/main.scss +74 -2
- package/src/store/chart.reducer.ts +4 -4
- package/src/test/CdcChart.dataTable.test.tsx +582 -14
- 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,432 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
+
import { expect, fireEvent, userEvent, within } from 'storybook/test'
|
|
3
|
+
import Chart from '../CdcChartComponent'
|
|
4
|
+
import { createNewChartConfig } from '../helpers/createNewChartConfig'
|
|
5
|
+
import {
|
|
6
|
+
assertVisualizationRendered,
|
|
7
|
+
openAccordion,
|
|
8
|
+
performAndAssert,
|
|
9
|
+
waitForEditor,
|
|
10
|
+
waitForPresence
|
|
11
|
+
} from '@cdc/core/helpers/testing'
|
|
12
|
+
|
|
13
|
+
const rows = [
|
|
14
|
+
{
|
|
15
|
+
source: 'Clinic',
|
|
16
|
+
target: 'Health Department',
|
|
17
|
+
weight: 8,
|
|
18
|
+
style: 'solid',
|
|
19
|
+
nodeColor: '#005eaa',
|
|
20
|
+
alternateSource: 'A',
|
|
21
|
+
alternateTarget: 'B',
|
|
22
|
+
alternateWeight: 1,
|
|
23
|
+
alternateStyle: 'solid',
|
|
24
|
+
alternateNodeColor: '#111111'
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
source: 'Health Department',
|
|
28
|
+
target: 'Hospital',
|
|
29
|
+
weight: 5,
|
|
30
|
+
style: 'dashed',
|
|
31
|
+
nodeColor: '#4b830d',
|
|
32
|
+
alternateSource: 'B',
|
|
33
|
+
alternateTarget: 'C',
|
|
34
|
+
alternateWeight: 5,
|
|
35
|
+
alternateStyle: 'dashed',
|
|
36
|
+
alternateNodeColor: '#222222'
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
source: 'Clinic',
|
|
40
|
+
target: 'Hospital',
|
|
41
|
+
weight: 3,
|
|
42
|
+
style: 'solid',
|
|
43
|
+
nodeColor: '#005eaa',
|
|
44
|
+
alternateSource: 'A',
|
|
45
|
+
alternateTarget: 'C',
|
|
46
|
+
alternateWeight: 10,
|
|
47
|
+
alternateStyle: 'solid',
|
|
48
|
+
alternateNodeColor: '#111111'
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
source: 'Hospital',
|
|
52
|
+
target: 'Clinic',
|
|
53
|
+
weight: 2,
|
|
54
|
+
style: 'dashed',
|
|
55
|
+
nodeColor: '#712177',
|
|
56
|
+
alternateSource: 'C',
|
|
57
|
+
alternateTarget: 'A',
|
|
58
|
+
alternateWeight: 20,
|
|
59
|
+
alternateStyle: 'dashed',
|
|
60
|
+
alternateNodeColor: '#333333'
|
|
61
|
+
}
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
const networkConfig = createNewChartConfig({
|
|
65
|
+
type: 'chart',
|
|
66
|
+
visualizationType: 'Network',
|
|
67
|
+
title: 'Partner Network',
|
|
68
|
+
showTitle: true,
|
|
69
|
+
enableTooltips: true,
|
|
70
|
+
data: rows,
|
|
71
|
+
network: {
|
|
72
|
+
columns: {
|
|
73
|
+
source: 'source',
|
|
74
|
+
target: 'target',
|
|
75
|
+
weight: 'weight',
|
|
76
|
+
style: 'style',
|
|
77
|
+
nodeColor: 'nodeColor'
|
|
78
|
+
},
|
|
79
|
+
directed: false,
|
|
80
|
+
height: 420,
|
|
81
|
+
nodeRadius: 8,
|
|
82
|
+
nodeColor: '#005eaa',
|
|
83
|
+
linkColor: '#333333',
|
|
84
|
+
linkDistance: 90,
|
|
85
|
+
chargeStrength: -240,
|
|
86
|
+
rotation: 0,
|
|
87
|
+
scale: 1,
|
|
88
|
+
offsetX: 0,
|
|
89
|
+
offsetY: 0
|
|
90
|
+
}
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
const labelOverlapsLink = (label: SVGTextElement, link: SVGLineElement) => {
|
|
94
|
+
const labelBounds = label.getBBox()
|
|
95
|
+
const labelTransform = label.getCTM()
|
|
96
|
+
const linkTransform = link.getCTM()
|
|
97
|
+
if (!labelTransform || !linkTransform) {
|
|
98
|
+
throw new Error('Network collision characterization requires SVG label and link CTMs.')
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const overlapsAtDistance = (distance: number) => {
|
|
102
|
+
const point = link
|
|
103
|
+
.getPointAtLength(distance)
|
|
104
|
+
.matrixTransform(linkTransform)
|
|
105
|
+
.matrixTransform(labelTransform.inverse())
|
|
106
|
+
return (
|
|
107
|
+
point.x >= labelBounds.x &&
|
|
108
|
+
point.x <= labelBounds.x + labelBounds.width &&
|
|
109
|
+
point.y >= labelBounds.y &&
|
|
110
|
+
point.y <= labelBounds.y + labelBounds.height
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const linkLength = link.getTotalLength()
|
|
115
|
+
for (let distance = 0; distance < linkLength; distance += 1) {
|
|
116
|
+
if (overlapsAtDistance(distance)) return true
|
|
117
|
+
}
|
|
118
|
+
return overlapsAtDistance(linkLength)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const expectLabelsWithinSvg = (svg: SVGSVGElement) => {
|
|
122
|
+
const svgBounds = svg.getBoundingClientRect()
|
|
123
|
+
svg.querySelectorAll<SVGTextElement>('.network-chart__label').forEach(label => {
|
|
124
|
+
const labelBounds = label.getBoundingClientRect()
|
|
125
|
+
expect(labelBounds.left).toBeGreaterThanOrEqual(svgBounds.left - 1)
|
|
126
|
+
expect(labelBounds.right).toBeLessThanOrEqual(svgBounds.right + 1)
|
|
127
|
+
expect(labelBounds.top).toBeGreaterThanOrEqual(svgBounds.top - 1)
|
|
128
|
+
expect(labelBounds.bottom).toBeLessThanOrEqual(svgBounds.bottom + 1)
|
|
129
|
+
})
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const meta: Meta<typeof Chart> = {
|
|
133
|
+
title: 'Components/Templates/Chart/Network',
|
|
134
|
+
component: Chart
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export default meta
|
|
138
|
+
type Story = StoryObj<typeof Chart>
|
|
139
|
+
|
|
140
|
+
export const Network_Runtime: Story = {
|
|
141
|
+
args: {
|
|
142
|
+
config: createNewChartConfig({
|
|
143
|
+
...networkConfig,
|
|
144
|
+
network: { ...networkConfig.network, rotation: 30 }
|
|
145
|
+
}),
|
|
146
|
+
isEditor: false
|
|
147
|
+
},
|
|
148
|
+
play: async ({ canvasElement }) => {
|
|
149
|
+
await assertVisualizationRendered(canvasElement)
|
|
150
|
+
const table = await waitForPresence('.data-table', canvasElement)
|
|
151
|
+
const tableHeaders = Array.from(table.querySelectorAll('thead th > span:first-child')).map(header =>
|
|
152
|
+
header.textContent?.trim()
|
|
153
|
+
)
|
|
154
|
+
expect(tableHeaders).toEqual([
|
|
155
|
+
'source',
|
|
156
|
+
'target',
|
|
157
|
+
'alternateSource',
|
|
158
|
+
'alternateTarget',
|
|
159
|
+
'alternateWeight',
|
|
160
|
+
'alternateStyle',
|
|
161
|
+
'alternateNodeColor'
|
|
162
|
+
])
|
|
163
|
+
const svg = await waitForPresence('.network-chart', canvasElement)
|
|
164
|
+
const nodes = svg.querySelectorAll('.network-chart__node')
|
|
165
|
+
const links = svg.querySelectorAll('.network-chart__link')
|
|
166
|
+
const linkGroups = svg.querySelectorAll('.network-chart__link-group')
|
|
167
|
+
|
|
168
|
+
expect(nodes).toHaveLength(3)
|
|
169
|
+
expect(links).toHaveLength(3)
|
|
170
|
+
expect(nodes[0]).toHaveAttribute('tabindex', '0')
|
|
171
|
+
expect(nodes[0]).toHaveAttribute('aria-label')
|
|
172
|
+
expect(linkGroups[0]).toHaveAttribute('data-tooltip-content')
|
|
173
|
+
expect(svg.querySelectorAll('.network-chart__link--dashed')).toHaveLength(2)
|
|
174
|
+
expect(svg.querySelectorAll('.network-chart__link:not(.network-chart__link--dashed)')).toHaveLength(1)
|
|
175
|
+
expect(svg.querySelector('[aria-label^="Clinic,"] circle')).toHaveAttribute('fill', '#005eaa')
|
|
176
|
+
expect(svg.querySelector('[aria-label^="Health Department,"] circle')).toHaveAttribute('fill', '#4b830d')
|
|
177
|
+
expect(svg.querySelector('[aria-label^="Hospital,"] circle')).toHaveAttribute('fill', '#712177')
|
|
178
|
+
expect(window.getComputedStyle(svg.querySelector('.network-chart__label')!).fontSize).toBe('16px')
|
|
179
|
+
const labels = [...svg.querySelectorAll<SVGTextElement>('.network-chart__label')]
|
|
180
|
+
const renderedLinks = [...svg.querySelectorAll<SVGLineElement>('.network-chart__viewport .network-chart__link')]
|
|
181
|
+
expect(labels.some(label => renderedLinks.some(link => labelOverlapsLink(label, link)))).toBe(false)
|
|
182
|
+
const linkStyles = within(canvasElement).getByRole('group', { name: 'Link styles' })
|
|
183
|
+
expect(linkStyles).toHaveTextContent('Solid')
|
|
184
|
+
expect(linkStyles).toHaveTextContent('Dashed')
|
|
185
|
+
|
|
186
|
+
const viewport = svg.querySelector('.network-chart__viewport')
|
|
187
|
+
const initialTransform = viewport?.getAttribute('transform')
|
|
188
|
+
expect(initialTransform).toContain('rotate(30)')
|
|
189
|
+
expectLabelsWithinSvg(svg as SVGSVGElement)
|
|
190
|
+
fireEvent.wheel(svg, { deltaY: -100 })
|
|
191
|
+
expect(viewport?.getAttribute('transform')).toBe(initialTransform)
|
|
192
|
+
|
|
193
|
+
within(canvasElement).getByLabelText('Zoom in').focus()
|
|
194
|
+
await userEvent.keyboard('{Enter}')
|
|
195
|
+
expect(viewport?.getAttribute('transform')).not.toBe(initialTransform)
|
|
196
|
+
expect(within(canvasElement).queryByLabelText('Rotate left')).not.toBeInTheDocument()
|
|
197
|
+
expect(within(canvasElement).queryByLabelText('Rotate right')).not.toBeInTheDocument()
|
|
198
|
+
await userEvent.click(within(canvasElement).getByLabelText('Reset view'))
|
|
199
|
+
expect(viewport?.getAttribute('transform')).toBe(initialTransform)
|
|
200
|
+
expectLabelsWithinSvg(svg as SVGSVGElement)
|
|
201
|
+
await userEvent.hover(svg.querySelector('.network-chart__link-hitbox')!)
|
|
202
|
+
const tooltip = await waitForPresence('.network-chart__tooltip', document.body)
|
|
203
|
+
expect(window.getComputedStyle(tooltip).backgroundColor).toBe('rgb(255, 255, 255)')
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export const Network_NarrowViewport: Story = {
|
|
208
|
+
args: { config: networkConfig, isEditor: false },
|
|
209
|
+
decorators: [
|
|
210
|
+
Story => (
|
|
211
|
+
<div style={{ width: 360 }}>
|
|
212
|
+
<Story />
|
|
213
|
+
</div>
|
|
214
|
+
)
|
|
215
|
+
],
|
|
216
|
+
play: async ({ canvasElement }) => {
|
|
217
|
+
const svg = (await waitForPresence('.network-chart', canvasElement)) as SVGSVGElement
|
|
218
|
+
const [, , width, height] = (svg.getAttribute('viewBox') || '').split(' ').map(Number)
|
|
219
|
+
expect(width).toBeGreaterThanOrEqual(160)
|
|
220
|
+
expect(height).toBe(388)
|
|
221
|
+
expect(svg.querySelectorAll('.network-chart__node')).toHaveLength(3)
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export const Network_MinimumHeightWithLinkKey: Story = {
|
|
226
|
+
args: {
|
|
227
|
+
config: createNewChartConfig({
|
|
228
|
+
...networkConfig,
|
|
229
|
+
network: { ...networkConfig.network, height: 160 }
|
|
230
|
+
}),
|
|
231
|
+
isEditor: false
|
|
232
|
+
},
|
|
233
|
+
play: async ({ canvasElement }) => {
|
|
234
|
+
const svg = (await waitForPresence('.network-chart', canvasElement)) as SVGSVGElement
|
|
235
|
+
const [, , width, height] = (svg.getAttribute('viewBox') || '').split(' ').map(Number)
|
|
236
|
+
expect(width).toBeGreaterThanOrEqual(160)
|
|
237
|
+
expect(height).toBe(128)
|
|
238
|
+
expect(within(canvasElement).getByRole('group', { name: 'Link styles' })).toBeVisible()
|
|
239
|
+
expectLabelsWithinSvg(svg)
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export const Network_Editor: Story = {
|
|
244
|
+
args: {
|
|
245
|
+
config: {
|
|
246
|
+
...networkConfig,
|
|
247
|
+
network: {
|
|
248
|
+
...networkConfig.network,
|
|
249
|
+
columns: { ...networkConfig.network.columns, style: '', nodeColor: '' }
|
|
250
|
+
}
|
|
251
|
+
},
|
|
252
|
+
isEditor: true
|
|
253
|
+
},
|
|
254
|
+
play: async ({ canvasElement }) => {
|
|
255
|
+
const canvas = within(canvasElement)
|
|
256
|
+
await waitForEditor(canvas)
|
|
257
|
+
await openAccordion(canvas, 'Network')
|
|
258
|
+
expect(canvas.getByLabelText('Source Column')).toBeInTheDocument()
|
|
259
|
+
expect(canvas.getByLabelText('Target Column')).toBeInTheDocument()
|
|
260
|
+
expect(canvas.getByLabelText('Weight Column (Optional)')).toBeInTheDocument()
|
|
261
|
+
expect(canvas.getByLabelText('Link Style Column (Optional)')).toBeInTheDocument()
|
|
262
|
+
expect(canvas.getByLabelText('Solid Link Legend Label')).toHaveValue('Solid')
|
|
263
|
+
expect(canvas.getByLabelText('Dashed Link Legend Label')).toHaveValue('Dashed')
|
|
264
|
+
expect(canvas.getByLabelText('Node Color Column (Optional)')).toBeInTheDocument()
|
|
265
|
+
expect(canvas.getByLabelText('Directed Network')).toBeInTheDocument()
|
|
266
|
+
expect(canvas.queryByLabelText('Show Node Labels')).not.toBeInTheDocument()
|
|
267
|
+
expect(canvas.queryByLabelText('Show Tooltips')).not.toBeInTheDocument()
|
|
268
|
+
expect(canvasElement.querySelector('[data-tooltip-content]')).toBeInTheDocument()
|
|
269
|
+
expect(canvas.getByLabelText('Link Distance')).toHaveAttribute('type', 'range')
|
|
270
|
+
expect(canvas.getByLabelText('Repulsion Strength')).toHaveAttribute('type', 'range')
|
|
271
|
+
expect(canvas.queryByLabelText('Initial Rotation (Degrees)')).not.toBeInTheDocument()
|
|
272
|
+
expect(canvas.queryByLabelText('Initial Zoom')).not.toBeInTheDocument()
|
|
273
|
+
expect(canvas.queryByLabelText('Initial Horizontal Offset')).not.toBeInTheDocument()
|
|
274
|
+
expect(canvas.queryByLabelText('Initial Vertical Offset')).not.toBeInTheDocument()
|
|
275
|
+
expect(canvas.queryByLabelText('Default Node Color')).not.toBeInTheDocument()
|
|
276
|
+
expect(canvas.queryByLabelText('Link Color')).not.toBeInTheDocument()
|
|
277
|
+
expect(canvas.queryByRole('button', { name: 'Reset Initial View' })).not.toBeInTheDocument()
|
|
278
|
+
expect(canvas.queryByText('Pattern Settings')).not.toBeInTheDocument()
|
|
279
|
+
expect(canvas.queryByText('Text Annotations')).not.toBeInTheDocument()
|
|
280
|
+
expect(canvas.getByRole('button', { name: 'Columns' })).toBeInTheDocument()
|
|
281
|
+
expect(canvas.getByRole('button', { name: 'Legend' })).toBeInTheDocument()
|
|
282
|
+
|
|
283
|
+
await openAccordion(canvas, 'Columns')
|
|
284
|
+
expect(canvas.getByRole('button', { name: 'Add Configuration' })).toBeInTheDocument()
|
|
285
|
+
await openAccordion(canvas, 'Network')
|
|
286
|
+
|
|
287
|
+
const nodeLabels = () =>
|
|
288
|
+
Array.from(canvasElement.querySelectorAll('.network-chart__label'))
|
|
289
|
+
.map(label => label.textContent)
|
|
290
|
+
.sort()
|
|
291
|
+
.join('|')
|
|
292
|
+
const linkWidths = () =>
|
|
293
|
+
Array.from(canvasElement.querySelectorAll('.network-chart__viewport .network-chart__link'))
|
|
294
|
+
.map(link => link.getAttribute('stroke-width'))
|
|
295
|
+
.join('|')
|
|
296
|
+
const linkCoordinates = () =>
|
|
297
|
+
Array.from(canvasElement.querySelectorAll('.network-chart__viewport .network-chart__link'))
|
|
298
|
+
.map(link => ['x1', 'y1', 'x2', 'y2'].map(attribute => link.getAttribute(attribute)).join(','))
|
|
299
|
+
.join('|')
|
|
300
|
+
const nodeFills = () =>
|
|
301
|
+
Array.from(canvasElement.querySelectorAll('.network-chart__node circle'))
|
|
302
|
+
.map(node => node.getAttribute('fill'))
|
|
303
|
+
.sort()
|
|
304
|
+
.join('|')
|
|
305
|
+
|
|
306
|
+
await performAndAssert(
|
|
307
|
+
'Source mapping changes rendered nodes',
|
|
308
|
+
nodeLabels,
|
|
309
|
+
async () => userEvent.selectOptions(canvas.getByLabelText('Source Column'), 'alternateSource'),
|
|
310
|
+
(before, after) => before !== after && after.includes('A')
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
await performAndAssert(
|
|
314
|
+
'Target mapping changes rendered nodes',
|
|
315
|
+
nodeLabels,
|
|
316
|
+
async () => userEvent.selectOptions(canvas.getByLabelText('Target Column'), 'alternateTarget'),
|
|
317
|
+
(before, after) => before !== after && after === 'A|B|C'
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
await performAndAssert(
|
|
321
|
+
'Weight mapping changes rendered link widths',
|
|
322
|
+
linkWidths,
|
|
323
|
+
async () => userEvent.selectOptions(canvas.getByLabelText('Weight Column (Optional)'), 'alternateWeight'),
|
|
324
|
+
(before, after) => before !== after
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
await performAndAssert(
|
|
328
|
+
'Directed links show arrowheads',
|
|
329
|
+
() =>
|
|
330
|
+
Array.from(canvasElement.querySelectorAll('.network-chart__link')).filter(link =>
|
|
331
|
+
link.hasAttribute('marker-end')
|
|
332
|
+
).length,
|
|
333
|
+
async () => userEvent.click(canvas.getByLabelText('Directed Network')),
|
|
334
|
+
(before, after) => before === 0 && after === 4
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
await performAndAssert(
|
|
338
|
+
'Node color mapping applies source-node data colors',
|
|
339
|
+
nodeFills,
|
|
340
|
+
async () => userEvent.selectOptions(canvas.getByLabelText('Node Color Column (Optional)'), 'alternateNodeColor'),
|
|
341
|
+
(before, after) => before !== after && after.includes('#111111')
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
await performAndAssert(
|
|
345
|
+
'Link style mapping applies styles per row',
|
|
346
|
+
() => canvasElement.querySelectorAll('.network-chart__viewport .network-chart__link--dashed').length,
|
|
347
|
+
async () => userEvent.selectOptions(canvas.getByLabelText('Link Style Column (Optional)'), 'alternateStyle'),
|
|
348
|
+
(before, after) => before === 0 && after === 2
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
await performAndAssert(
|
|
352
|
+
'Solid link legend label updates the rendered key',
|
|
353
|
+
() => canvasElement.querySelector('[aria-label="Link styles"]')?.textContent || '',
|
|
354
|
+
async () =>
|
|
355
|
+
fireEvent.change(canvas.getByLabelText('Solid Link Legend Label'), { target: { value: 'Established' } }),
|
|
356
|
+
(before, after) => before.includes('Solid') && after.includes('Established')
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
await performAndAssert(
|
|
360
|
+
'Dashed link legend label updates the rendered key',
|
|
361
|
+
() => canvasElement.querySelector('[aria-label="Link styles"]')?.textContent || '',
|
|
362
|
+
async () =>
|
|
363
|
+
fireEvent.change(canvas.getByLabelText('Dashed Link Legend Label'), { target: { value: 'Provisional' } }),
|
|
364
|
+
(before, after) => before.includes('Dashed') && after.includes('Provisional')
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
await openAccordion(canvas, 'Legend')
|
|
368
|
+
const legendPanel = within(
|
|
369
|
+
canvas.getByRole('button', { name: 'Legend' }).closest('.accordion__item') as HTMLElement
|
|
370
|
+
)
|
|
371
|
+
expect(legendPanel.getByRole('checkbox', { name: /Hide Legend/ })).toBeInTheDocument()
|
|
372
|
+
expect(legendPanel.queryByLabelText('Position')).not.toBeInTheDocument()
|
|
373
|
+
expect(legendPanel.queryByLabelText('Legend Style')).not.toBeInTheDocument()
|
|
374
|
+
expect(legendPanel.queryByLabelText('Title')).not.toBeInTheDocument()
|
|
375
|
+
expect(legendPanel.queryByLabelText('Legend Description')).not.toBeInTheDocument()
|
|
376
|
+
|
|
377
|
+
await performAndAssert(
|
|
378
|
+
'Hide Legend removes the rendered link-style key',
|
|
379
|
+
() => canvasElement.querySelectorAll('[aria-label="Link styles"]').length,
|
|
380
|
+
async () => userEvent.click(legendPanel.getByRole('checkbox', { name: /Hide Legend/ })),
|
|
381
|
+
(before, after) => before === 1 && after === 0
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
await performAndAssert(
|
|
385
|
+
'Show Legend restores the rendered link-style key',
|
|
386
|
+
() => canvasElement.querySelectorAll('[aria-label="Link styles"]').length,
|
|
387
|
+
async () => userEvent.click(legendPanel.getByRole('checkbox', { name: /Hide Legend/ })),
|
|
388
|
+
(before, after) => before === 0 && after === 1
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
await openAccordion(canvas, 'Network')
|
|
392
|
+
|
|
393
|
+
await performAndAssert(
|
|
394
|
+
'Height changes the rendered SVG viewport',
|
|
395
|
+
() => Number(canvasElement.querySelector('.network-chart')?.getAttribute('viewBox')?.split(' ')[3]),
|
|
396
|
+
async () => {
|
|
397
|
+
const heightInput = canvas.getByLabelText('Height')
|
|
398
|
+
await userEvent.clear(heightInput)
|
|
399
|
+
await userEvent.type(heightInput, '360')
|
|
400
|
+
},
|
|
401
|
+
(before, after) => before !== after && after === 328
|
|
402
|
+
)
|
|
403
|
+
|
|
404
|
+
await performAndAssert(
|
|
405
|
+
'Blank height restores the rendered SVG viewport default',
|
|
406
|
+
() => Number(canvasElement.querySelector('.network-chart')?.getAttribute('viewBox')?.split(' ')[3]),
|
|
407
|
+
async () => userEvent.clear(canvas.getByLabelText('Height')),
|
|
408
|
+
(before, after) => before === 328 && after === 468
|
|
409
|
+
)
|
|
410
|
+
|
|
411
|
+
await performAndAssert(
|
|
412
|
+
'Node radius changes rendered circles',
|
|
413
|
+
() => canvasElement.querySelector('.network-chart__node circle')?.getAttribute('r'),
|
|
414
|
+
async () => fireEvent.change(canvas.getByLabelText('Node Radius'), { target: { value: '12' } }),
|
|
415
|
+
(before, after) => before !== after && after === '12'
|
|
416
|
+
)
|
|
417
|
+
|
|
418
|
+
await performAndAssert(
|
|
419
|
+
'Link distance changes node layout',
|
|
420
|
+
linkCoordinates,
|
|
421
|
+
async () => fireEvent.change(canvas.getByLabelText('Link Distance'), { target: { value: '140' } }),
|
|
422
|
+
(before, after) => before !== after
|
|
423
|
+
)
|
|
424
|
+
|
|
425
|
+
await performAndAssert(
|
|
426
|
+
'Repulsion strength changes node layout',
|
|
427
|
+
linkCoordinates,
|
|
428
|
+
async () => fireEvent.change(canvas.getByLabelText('Repulsion Strength'), { target: { value: '-80' } }),
|
|
429
|
+
(before, after) => before !== after
|
|
430
|
+
)
|
|
431
|
+
}
|
|
432
|
+
}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
+
import { expect, fn, userEvent, waitFor, within } from 'storybook/test'
|
|
3
|
+
import cloneConfig from '@cdc/core/helpers/cloneConfig'
|
|
4
|
+
import { openAccordion, waitForEditor } from '@cdc/core/helpers/testing'
|
|
5
|
+
|
|
6
|
+
import Chart from '../CdcChartComponent'
|
|
7
|
+
import mockScatterPlot from './_mock/scatterplot_mock.json'
|
|
8
|
+
import forecastComboWithGaps from './_mock/forecast_combo_with_gaps.json'
|
|
9
|
+
|
|
10
|
+
const meta: Meta<typeof Chart> = {
|
|
11
|
+
title: 'Components/Templates/Chart/Editor Tests/Palette Conversion',
|
|
12
|
+
component: Chart
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export default meta
|
|
16
|
+
type Story = StoryObj<typeof Chart>
|
|
17
|
+
|
|
18
|
+
const twoColorVersion10Updates = fn()
|
|
19
|
+
const twoColorVersion20Updates = fn()
|
|
20
|
+
const forecastVersion10Updates = fn()
|
|
21
|
+
const forecastVersion20Updates = fn()
|
|
22
|
+
|
|
23
|
+
const buildBarConfig = (version: '1.0' | '2.0' | '2.1', visualizationType: 'Bar' | 'Paired Bar' = 'Bar') => {
|
|
24
|
+
const config = cloneConfig(mockScatterPlot) as any
|
|
25
|
+
config.version = '4.26.8'
|
|
26
|
+
config.visualizationType = visualizationType
|
|
27
|
+
config.xAxis = { ...config.xAxis, dataKey: 'category', type: 'categorical' }
|
|
28
|
+
config.series = config.series.slice(0, 2).map(series => ({ ...series, type: 'Bar' }))
|
|
29
|
+
config.data = [
|
|
30
|
+
{ category: 'Q1', y1: 19, y2: 47 },
|
|
31
|
+
{ category: 'Q2', y1: 18, y2: 32 }
|
|
32
|
+
]
|
|
33
|
+
config.general.palette = {
|
|
34
|
+
isReversed: false,
|
|
35
|
+
name: version === '1.0' ? 'qualitative-bold' : 'qualitative_standard',
|
|
36
|
+
version
|
|
37
|
+
}
|
|
38
|
+
return config
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const selectAlternativePalette = async (canvasElement: HTMLElement) => {
|
|
42
|
+
const button = Array.from(canvasElement.querySelectorAll<HTMLButtonElement>('.color-palette button[title]')).find(
|
|
43
|
+
candidate => !candidate.classList.contains('selected') && !candidate.title.startsWith('theme-')
|
|
44
|
+
) as HTMLButtonElement
|
|
45
|
+
expect(button).toBeTruthy()
|
|
46
|
+
await userEvent.click(button)
|
|
47
|
+
return button
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const expectConversionModal = (canvas: ReturnType<typeof within>) =>
|
|
51
|
+
canvas.getByRole('heading', { name: 'Color Palette Conversion' })
|
|
52
|
+
|
|
53
|
+
export const MainPaletteVersion10: Story = {
|
|
54
|
+
args: { isEditor: true, config: buildBarConfig('1.0') },
|
|
55
|
+
play: async ({ canvasElement }) => {
|
|
56
|
+
const canvas = within(canvasElement)
|
|
57
|
+
await waitForEditor(canvas)
|
|
58
|
+
await openAccordion(canvas, 'Visual')
|
|
59
|
+
|
|
60
|
+
const cancelledPalette = await selectAlternativePalette(canvasElement)
|
|
61
|
+
expect(expectConversionModal(canvas)).toBeInTheDocument()
|
|
62
|
+
expect(canvas.getByText(/uses an outdated color palette/)).toBeInTheDocument()
|
|
63
|
+
await userEvent.click(canvas.getByRole('button', { name: 'Cancel' }))
|
|
64
|
+
expect(cancelledPalette).toHaveClass('selected')
|
|
65
|
+
|
|
66
|
+
await selectAlternativePalette(canvasElement)
|
|
67
|
+
await userEvent.click(canvas.getByRole('button', { name: 'Convert to New Palette' }))
|
|
68
|
+
await selectAlternativePalette(canvasElement)
|
|
69
|
+
expect(canvas.queryByRole('heading', { name: 'Color Palette Conversion' })).not.toBeInTheDocument()
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export const MainPaletteVersion20: Story = {
|
|
74
|
+
args: { isEditor: true, config: buildBarConfig('2.0') },
|
|
75
|
+
play: async ({ canvasElement }) => {
|
|
76
|
+
const canvas = within(canvasElement)
|
|
77
|
+
await waitForEditor(canvas)
|
|
78
|
+
await openAccordion(canvas, 'Visual')
|
|
79
|
+
|
|
80
|
+
const cancelledPalette = await selectAlternativePalette(canvasElement)
|
|
81
|
+
expect(expectConversionModal(canvas)).toBeInTheDocument()
|
|
82
|
+
expect(canvas.getByText(/updated color distribution that improves contrast/)).toBeInTheDocument()
|
|
83
|
+
await userEvent.click(canvas.getByRole('button', { name: 'Cancel' }))
|
|
84
|
+
expect(cancelledPalette).toHaveClass('selected')
|
|
85
|
+
|
|
86
|
+
await selectAlternativePalette(canvasElement)
|
|
87
|
+
await userEvent.click(canvas.getByRole('button', { name: 'Convert to New Palette' }))
|
|
88
|
+
await selectAlternativePalette(canvasElement)
|
|
89
|
+
expect(canvas.queryByRole('heading', { name: 'Color Palette Conversion' })).not.toBeInTheDocument()
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export const MainPaletteVersion21: Story = {
|
|
94
|
+
args: { isEditor: true, config: buildBarConfig('2.1') },
|
|
95
|
+
play: async ({ canvasElement }) => {
|
|
96
|
+
const canvas = within(canvasElement)
|
|
97
|
+
await waitForEditor(canvas)
|
|
98
|
+
await openAccordion(canvas, 'Visual')
|
|
99
|
+
await selectAlternativePalette(canvasElement)
|
|
100
|
+
|
|
101
|
+
expect(canvas.queryByRole('heading', { name: 'Color Palette Conversion' })).not.toBeInTheDocument()
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export const TwoColorVersion10: Story = {
|
|
106
|
+
args: { isEditor: true, config: buildBarConfig('1.0', 'Paired Bar'), setConfig: twoColorVersion10Updates },
|
|
107
|
+
play: async ({ canvasElement }) => {
|
|
108
|
+
twoColorVersion10Updates.mockClear()
|
|
109
|
+
const canvas = within(canvasElement)
|
|
110
|
+
await waitForEditor(canvas)
|
|
111
|
+
await openAccordion(canvas, 'Visual')
|
|
112
|
+
await selectAlternativePalette(canvasElement)
|
|
113
|
+
|
|
114
|
+
expect(expectConversionModal(canvas)).toBeInTheDocument()
|
|
115
|
+
await userEvent.click(canvas.getByRole('button', { name: 'Convert to New Palette' }))
|
|
116
|
+
await waitFor(() => {
|
|
117
|
+
const updatedConfig = twoColorVersion10Updates.mock.calls.at(-1)?.[0]
|
|
118
|
+
expect(updatedConfig.general.palette.version).toBe('2.1')
|
|
119
|
+
expect(updatedConfig.general.palette.backups).toContainEqual(
|
|
120
|
+
expect.objectContaining({ version: '1.0', type: 'twoColor' })
|
|
121
|
+
)
|
|
122
|
+
})
|
|
123
|
+
await selectAlternativePalette(canvasElement)
|
|
124
|
+
expect(canvas.queryByRole('heading', { name: 'Color Palette Conversion' })).not.toBeInTheDocument()
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export const TwoColorVersion20: Story = {
|
|
129
|
+
args: { isEditor: true, config: buildBarConfig('2.0', 'Paired Bar'), setConfig: twoColorVersion20Updates },
|
|
130
|
+
play: async ({ canvasElement }) => {
|
|
131
|
+
twoColorVersion20Updates.mockClear()
|
|
132
|
+
const canvas = within(canvasElement)
|
|
133
|
+
await waitForEditor(canvas)
|
|
134
|
+
await openAccordion(canvas, 'Visual')
|
|
135
|
+
await selectAlternativePalette(canvasElement)
|
|
136
|
+
expect(canvas.queryByRole('heading', { name: 'Color Palette Conversion' })).not.toBeInTheDocument()
|
|
137
|
+
await waitFor(() => expect(twoColorVersion20Updates.mock.calls.at(-1)?.[0].general.palette.version).toBe('2.0'))
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const buildForecastConfig = (version: '1.0' | '2.0') => {
|
|
142
|
+
const config = cloneConfig(forecastComboWithGaps) as any
|
|
143
|
+
config.version = '4.26.8'
|
|
144
|
+
config.general.palette = {
|
|
145
|
+
isReversed: false,
|
|
146
|
+
name: version === '1.0' ? 'qualitative-bold' : 'qualitative_standard',
|
|
147
|
+
version
|
|
148
|
+
}
|
|
149
|
+
config.series[0].stages[0].color = version === '1.0' ? 'Sequential Blue Two' : 'sequential-blue'
|
|
150
|
+
return config
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const changeFirstForecastColor = async (canvas: ReturnType<typeof within>) => {
|
|
154
|
+
const forecastSeries = canvas
|
|
155
|
+
.getAllByRole('button', { name: /Month/ })
|
|
156
|
+
.find(button => button.classList.contains('accordion__button')) as HTMLElement
|
|
157
|
+
expect(forecastSeries).toBeTruthy()
|
|
158
|
+
await userEvent.click(forecastSeries)
|
|
159
|
+
const colorLabel = canvas.getByText('11 Series Color').closest('label')
|
|
160
|
+
const colorSelect = colorLabel?.querySelector('select') as HTMLSelectElement
|
|
161
|
+
expect(colorSelect).toBeTruthy()
|
|
162
|
+
const alternative = Array.from(colorSelect.options).find(option => option.value && option.value !== colorSelect.value)
|
|
163
|
+
expect(alternative).toBeTruthy()
|
|
164
|
+
await userEvent.selectOptions(colorSelect, alternative!.value)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export const ForecastVersion10: Story = {
|
|
168
|
+
args: { isEditor: true, config: buildForecastConfig('1.0'), setConfig: forecastVersion10Updates },
|
|
169
|
+
play: async ({ canvasElement }) => {
|
|
170
|
+
forecastVersion10Updates.mockClear()
|
|
171
|
+
const canvas = within(canvasElement)
|
|
172
|
+
await waitForEditor(canvas)
|
|
173
|
+
await openAccordion(canvas, 'Data Series')
|
|
174
|
+
await changeFirstForecastColor(canvas)
|
|
175
|
+
|
|
176
|
+
expect(expectConversionModal(canvas)).toBeInTheDocument()
|
|
177
|
+
await userEvent.click(canvas.getByRole('button', { name: 'Convert to New Palette' }))
|
|
178
|
+
await waitFor(() => {
|
|
179
|
+
const updatedConfig = forecastVersion10Updates.mock.calls.at(-1)?.[0]
|
|
180
|
+
expect(updatedConfig.general.palette.version).toBe('2.1')
|
|
181
|
+
updatedConfig.series
|
|
182
|
+
.filter(series => series.type === 'Forecasting')
|
|
183
|
+
.flatMap(series => series.stages)
|
|
184
|
+
.forEach(stage => expect(stage.color).toMatch(/^sequential-[a-z]+(?:reverse)?$/))
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
await openAccordion(canvas, 'Visual')
|
|
188
|
+
await selectAlternativePalette(canvasElement)
|
|
189
|
+
expect(canvas.queryByRole('heading', { name: 'Color Palette Conversion' })).not.toBeInTheDocument()
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export const ForecastVersion20: Story = {
|
|
194
|
+
args: { isEditor: true, config: buildForecastConfig('2.0'), setConfig: forecastVersion20Updates },
|
|
195
|
+
play: async ({ canvasElement }) => {
|
|
196
|
+
forecastVersion20Updates.mockClear()
|
|
197
|
+
const canvas = within(canvasElement)
|
|
198
|
+
await waitForEditor(canvas)
|
|
199
|
+
await openAccordion(canvas, 'Data Series')
|
|
200
|
+
await changeFirstForecastColor(canvas)
|
|
201
|
+
expect(canvas.queryByRole('heading', { name: 'Color Palette Conversion' })).not.toBeInTheDocument()
|
|
202
|
+
await waitFor(() => expect(forecastVersion20Updates.mock.calls.at(-1)?.[0].general.palette.version).toBe('2.0'))
|
|
203
|
+
|
|
204
|
+
await openAccordion(canvas, 'Visual')
|
|
205
|
+
await selectAlternativePalette(canvasElement)
|
|
206
|
+
expect(expectConversionModal(canvas)).toBeInTheDocument()
|
|
207
|
+
}
|
|
208
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from '@storybook/react'
|
|
2
|
+
import { expect } from 'storybook/test'
|
|
2
3
|
import StackedPattern from './_mock/stacked-pattern-test.json'
|
|
3
4
|
|
|
4
5
|
import Chart from '../CdcChartComponent'
|
|
@@ -21,4 +22,34 @@ export const Stacked_Bar_Pattern: Story = {
|
|
|
21
22
|
}
|
|
22
23
|
}
|
|
23
24
|
|
|
25
|
+
export const Large_Wave_Pattern: Story = {
|
|
26
|
+
args: {
|
|
27
|
+
config: {
|
|
28
|
+
...StackedPattern,
|
|
29
|
+
legend: {
|
|
30
|
+
...StackedPattern.legend,
|
|
31
|
+
patterns: {
|
|
32
|
+
Provisional: {
|
|
33
|
+
...StackedPattern.legend.patterns.Provisional,
|
|
34
|
+
shape: 'waves',
|
|
35
|
+
patternSize: 16
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
} as any
|
|
40
|
+
},
|
|
41
|
+
play: async ({ canvasElement }) => {
|
|
42
|
+
await assertVisualizationRendered(canvasElement)
|
|
43
|
+
|
|
44
|
+
const wavePaths = Array.from(canvasElement.querySelectorAll('path.visx-pattern-wave'))
|
|
45
|
+
expect(wavePaths.length).toBeGreaterThan(0)
|
|
46
|
+
|
|
47
|
+
wavePaths.forEach(path => {
|
|
48
|
+
expect(path).toHaveAttribute('fill', 'transparent')
|
|
49
|
+
expect(path).toHaveAttribute('stroke', '#000000')
|
|
50
|
+
expect(path).toHaveAttribute('stroke-width', '0.75')
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
24
55
|
export default meta
|