@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.
Files changed (276) hide show
  1. package/CONFIG.md +504 -368
  2. package/dist/cdcchart-CYc-Kayd.es.js +22488 -0
  3. package/dist/cdcchart-DCKn8NmI.es.js +20695 -0
  4. package/dist/cdcchart-Dyui9nk_.es.js +45828 -0
  5. package/dist/cdcchart.js +4 -96498
  6. package/examples/__data__/vega-remote-data.json +6 -0
  7. package/examples/dendrogram.json +18 -0
  8. package/examples/feature/bar/bar-chart-race-annual-change.json +68 -0
  9. package/examples/feature/heatmap/average-age-categorical.json +2 -2
  10. package/examples/feature/heatmap/calendar-heatmap.json +2 -2
  11. package/examples/feature/heatmap/cell-values-bottom-axis.json +2 -2
  12. package/examples/feature/heatmap/sparse-aggregation.json +2 -2
  13. package/examples/feature/line/line-chart-race.json +50 -0
  14. package/examples/feature/sankey/sankey-example-data.json +40 -1441
  15. package/examples/feature/sankey/sankey_chart_data.csv +9 -20
  16. package/examples/network.json +31 -0
  17. package/examples/private/box-plot.json +573 -0
  18. package/examples/private/custom-color-issue.json +625 -0
  19. package/examples/private/custom-colors.json +522 -0
  20. package/examples/private/cutoff.json +10750 -0
  21. package/examples/private/flueview.json +22051 -0
  22. package/examples/private/overlay-legend.json +485 -0
  23. package/examples/private/pie-test.json +469 -0
  24. package/examples/vega-remote.json +22 -0
  25. package/package.json +11 -13
  26. package/src/CdcChart.tsx +2 -2
  27. package/src/CdcChartComponent.tsx +444 -217
  28. package/src/_stories/Chart.BarRace.stories.tsx +443 -0
  29. package/src/_stories/Chart.BoxPlotNumericSort.stories.tsx +189 -0
  30. package/src/_stories/Chart.BoxPlotStatsParity.stories.tsx +207 -0
  31. package/src/_stories/Chart.ColorDistribution.stories.tsx +299 -0
  32. package/src/_stories/Chart.DataCutoff.stories.tsx +147 -0
  33. package/src/_stories/Chart.DataTableDownloadFilename.stories.tsx +60 -0
  34. package/src/_stories/Chart.Dendrogram.stories.tsx +373 -0
  35. package/src/_stories/Chart.Forecast.CILegend.stories.tsx +236 -0
  36. package/src/_stories/Chart.HeatMap.stories.tsx +170 -4
  37. package/src/_stories/Chart.Legend.AxisAlign.stories.tsx +119 -0
  38. package/src/_stories/Chart.LineRace.stories.tsx +149 -0
  39. package/src/_stories/Chart.Network.stories.tsx +432 -0
  40. package/src/_stories/Chart.PaletteConversion.Editor.stories.tsx +208 -0
  41. package/src/_stories/Chart.Patterns.smoke.stories.tsx +31 -0
  42. package/src/_stories/Chart.PercentageValue.stories.tsx +311 -0
  43. package/src/_stories/Chart.PieLabels.stories.tsx +49 -0
  44. package/src/_stories/Chart.RankByValueMutation.stories.tsx +206 -0
  45. package/src/_stories/Chart.RequiredFields.stories.tsx +681 -0
  46. package/src/_stories/Chart.Sankey.stories.tsx +417 -0
  47. package/src/_stories/Chart.SeriesColorAssignments.stories.tsx +47 -0
  48. package/src/_stories/Chart.SmallMultiplesNumericTileOrder.stories.tsx +137 -0
  49. package/src/_stories/Chart.TooltipSeriesLabels.stories.tsx +43 -0
  50. package/src/_stories/Chart.VegaRemoteData.stories.tsx +31 -0
  51. package/src/_stories/Chart.smoke.stories.tsx +14 -1
  52. package/src/_stories/ChartAnnotation.Editor.stories.tsx +78 -0
  53. package/src/_stories/ChartAxisTitles.smoke.stories.tsx +48 -0
  54. package/src/_stories/ChartBar.Editor.stories.tsx +499 -57
  55. package/src/_stories/ChartBrush.Editor.stories.tsx +46 -67
  56. package/src/_stories/ChartBrush.Regression.stories.tsx +135 -0
  57. package/src/_stories/ChartEditor.Editor.stories.tsx +33 -3
  58. package/src/_stories/ChartLine.EffectCompatibility.stories.tsx +120 -0
  59. package/src/_stories/ChartPrefixSuffix.smoke.stories.tsx +28 -1
  60. package/src/_stories/_mock/annotation_event-line_mock.json +2 -2
  61. package/src/_stories/_mock/editor-test-configs.json +20 -10
  62. package/src/_stories/_mock/editor-test-datasets.json +38 -42
  63. package/src/_stories/_mock/heatmap-average-age-categorical.json +2 -2
  64. package/src/_stories/_mock/heatmap.json +2 -2
  65. package/src/_stories/_mock/legend-axis-align-stacked-bar.json +125 -0
  66. package/src/_stories/_mock/sankey_mock.json +23 -27
  67. package/src/components/Annotations/components/AnnotationDraggable.styles.css +52 -0
  68. package/src/components/Annotations/components/AnnotationDraggable.tsx +639 -139
  69. package/src/components/Annotations/components/AnnotationDropdown.styles.css +44 -4
  70. package/src/components/Annotations/components/AnnotationDropdown.tsx +10 -32
  71. package/src/components/Annotations/helpers/resolveAnnotationLayout.ts +212 -0
  72. package/src/components/Annotations/tests/AnnotationDraggable.test.tsx +612 -19
  73. package/src/components/Annotations/tests/AnnotationDropdown.test.tsx +50 -0
  74. package/src/components/Annotations/tests/resolveAnnotationLayout.test.ts +203 -0
  75. package/src/components/Axis/BottomAxis.tsx +1 -1
  76. package/src/components/Axis/Categorical.Axis.tsx +3 -3
  77. package/src/components/Axis/RightAxis.tsx +21 -13
  78. package/src/components/BarChart/components/BarChart.Horizontal.tsx +39 -8
  79. package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +13 -4
  80. package/src/components/BarChart/components/BarChart.StackedVertical.test.ts +28 -0
  81. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +30 -26
  82. package/src/components/BarChart/components/BarChart.Vertical.tsx +38 -28
  83. package/src/components/BarChart/components/PortionPatternOverlay.tsx +61 -0
  84. package/src/components/BarChart/components/context.tsx +6 -0
  85. package/src/components/BarChart/helpers/getBarData.ts +28 -28
  86. package/src/components/BarChart/helpers/getBarHeights.ts +2 -2
  87. package/src/components/BarChart/helpers/getPatternUrl.ts +2 -5
  88. package/src/components/BarChart/helpers/index.ts +8 -0
  89. package/src/components/BarChart/helpers/portionPattern.ts +243 -0
  90. package/src/components/BarChart/helpers/tests/addMinimumBarHeights.test.ts +42 -0
  91. package/src/components/BarChart/helpers/tests/getPatternUrl.test.ts +13 -0
  92. package/src/components/BarChart/helpers/tests/portionPattern.test.ts +280 -0
  93. package/src/components/BarChart/helpers/tests/useBarChart.test.tsx +202 -0
  94. package/src/components/BarChart/helpers/useBarChart.ts +60 -60
  95. package/src/components/BarChartRace/BarChartRace.test.tsx +199 -0
  96. package/src/components/BarChartRace/BarChartRace.tsx +230 -0
  97. package/src/components/BarChartRace/BarChartRaceFallback.test.tsx +61 -0
  98. package/src/components/BarChartRace/BarChartRaceFallback.tsx +48 -0
  99. package/src/components/BarChartRace/bar-chart-race.css +160 -0
  100. package/src/components/BarChartRace/helpers.test.ts +114 -0
  101. package/src/components/BarChartRace/helpers.ts +185 -0
  102. package/src/components/BarChartRace/index.ts +3 -0
  103. package/src/components/BoxPlot/BoxPlot.Horizontal.tsx +7 -6
  104. package/src/components/BoxPlot/BoxPlot.Vertical.tsx +9 -7
  105. package/src/components/BoxPlot/helpers/index.ts +13 -83
  106. package/src/components/Brush/BrushSelector.tsx +10 -24
  107. package/src/components/Brush/MiniChartPreview.tsx +33 -1
  108. package/src/components/Dendrogram/components/Dendrogram.tsx +374 -0
  109. package/src/components/Dendrogram/dendrogram.css +113 -0
  110. package/src/components/Dendrogram/helpers/layoutDendrogram.test.ts +95 -0
  111. package/src/components/Dendrogram/helpers/layoutDendrogram.ts +56 -0
  112. package/src/components/Dendrogram/helpers/prepareDendrogramData.test.ts +135 -0
  113. package/src/components/Dendrogram/helpers/prepareDendrogramData.ts +166 -0
  114. package/src/components/Dendrogram/helpers/resolveDendrogramLabels.test.ts +196 -0
  115. package/src/components/Dendrogram/helpers/resolveDendrogramLabels.ts +418 -0
  116. package/src/components/Dendrogram/index.tsx +1 -0
  117. package/src/components/Dendrogram/types/index.ts +66 -0
  118. package/src/components/EditorPanel/EditorPanel.tsx +1111 -805
  119. package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +50 -5
  120. package/src/components/EditorPanel/components/Panels/Panel.Dendrogram.css +10 -0
  121. package/src/components/EditorPanel/components/Panels/Panel.Dendrogram.tsx +211 -0
  122. package/src/components/EditorPanel/components/Panels/Panel.ForestPlotSettings.tsx +19 -8
  123. package/src/components/EditorPanel/components/Panels/Panel.General.tsx +104 -61
  124. package/src/components/EditorPanel/components/Panels/Panel.Network.css +9 -0
  125. package/src/components/EditorPanel/components/Panels/Panel.Network.tsx +202 -0
  126. package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +236 -36
  127. package/src/components/EditorPanel/components/Panels/Panel.Radar.tsx +6 -1
  128. package/src/components/EditorPanel/components/Panels/Panel.Sankey.tsx +89 -105
  129. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +21 -6
  130. package/src/components/EditorPanel/components/Panels/Panel.SmallMultiples.tsx +1 -1
  131. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +8 -66
  132. package/src/components/EditorPanel/components/Panels/RacingControls.test.tsx +131 -0
  133. package/src/components/EditorPanel/components/Panels/RacingControls.tsx +185 -0
  134. package/src/components/EditorPanel/components/Panels/SeriesColorAssignments.tsx +2 -2
  135. package/src/components/EditorPanel/components/Panels/index.tsx +4 -0
  136. package/src/components/EditorPanel/helpers/getVisualizationTypeConfigUpdate.ts +20 -7
  137. package/src/components/EditorPanel/helpers/tests/getVisualizationTypeConfigUpdate.test.ts +25 -2
  138. package/src/components/EditorPanel/helpers/updateFieldRankByValue.ts +5 -4
  139. package/src/components/EditorPanel/useEditorPermissions.test.tsx +94 -0
  140. package/src/components/EditorPanel/useEditorPermissions.ts +75 -25
  141. package/src/components/Forecasting/Forecasting.tsx +16 -7
  142. package/src/components/HeatMap/components/HeatMap.tsx +511 -178
  143. package/src/components/HeatMap/heatmap.constants.ts +7 -3
  144. package/src/components/HeatMap/heatmap.css +101 -2
  145. package/src/components/HeatMap/helpers/layout.ts +80 -0
  146. package/src/components/HeatMap/tests/HeatMap.test.tsx +339 -16
  147. package/src/components/HeatMap/tests/helpers.test.ts +79 -0
  148. package/src/components/Legend/Legend.Component.tsx +163 -148
  149. package/src/components/Legend/Legend.Suppression.tsx +3 -3
  150. package/src/components/Legend/LegendGroup/LegendGroup.styles.css +7 -0
  151. package/src/components/Legend/LegendGroup/LegendGroup.tsx +47 -35
  152. package/src/components/Legend/helpers/createFormatLabels.test.ts +226 -0
  153. package/src/components/Legend/helpers/createFormatLabels.tsx +45 -51
  154. package/src/components/LineChart/components/LineChart.Circle.tsx +3 -3
  155. package/src/components/LineChart/helpers.test.ts +126 -38
  156. package/src/components/LineChart/helpers.ts +9 -6
  157. package/src/components/LineChart/index.tsx +71 -27
  158. package/src/components/LineChart/tests/LineChart.test.tsx +630 -0
  159. package/src/components/LineChartRace/LineChartRace.test.tsx +275 -0
  160. package/src/components/LineChartRace/LineChartRace.tsx +134 -0
  161. package/src/components/LineChartRace/LineChartRaceFallback.tsx +28 -0
  162. package/src/components/LineChartRace/helpers.test.ts +88 -0
  163. package/src/components/LineChartRace/helpers.ts +157 -0
  164. package/src/components/LineChartRace/index.ts +3 -0
  165. package/src/components/LineChartRace/line-chart-race.css +16 -0
  166. package/src/components/LinearChart/ValueAxisAnchors.tsx +58 -0
  167. package/src/components/LinearChart/VisualizationRenderer.tsx +18 -23
  168. package/src/components/LinearChart/tests/LinearChart.test.tsx +412 -13
  169. package/src/components/LinearChart/tests/VisualizationRenderer.test.tsx +141 -0
  170. package/src/components/LinearChart/tests/mockConfigContext.ts +2 -1
  171. package/src/components/LinearChart.tsx +85 -54
  172. package/src/components/Network/components/Network.tsx +372 -0
  173. package/src/components/Network/helpers/layoutNetwork.test.ts +68 -0
  174. package/src/components/Network/helpers/layoutNetwork.ts +87 -0
  175. package/src/components/Network/helpers/prepareNetworkData.test.ts +116 -0
  176. package/src/components/Network/helpers/prepareNetworkData.ts +101 -0
  177. package/src/components/Network/helpers/resolveNetworkLabels.test.ts +112 -0
  178. package/src/components/Network/helpers/resolveNetworkLabels.ts +206 -0
  179. package/src/components/Network/index.tsx +3 -0
  180. package/src/components/Network/network.css +107 -0
  181. package/src/components/Network/types/index.ts +56 -0
  182. package/src/components/PairedBarChart.jsx +5 -10
  183. package/src/components/PairedBarChart.test.tsx +71 -0
  184. package/src/components/PieChart/PieChart.tsx +121 -13
  185. package/src/components/PieChart/helpers/labelPlacement.test.ts +88 -0
  186. package/src/components/PieChart/helpers/labelPlacement.ts +95 -0
  187. package/src/components/RacingChartRenderer.tsx +44 -0
  188. package/src/components/Regions/components/Regions.test.tsx +179 -0
  189. package/src/components/Regions/components/Regions.tsx +104 -69
  190. package/src/components/Regions/index.tsx +2 -3
  191. package/src/components/Sankey/components/Sankey.tsx +264 -437
  192. package/src/components/Sankey/helpers/getSankeyTooltip.ts +12 -0
  193. package/src/components/Sankey/helpers/prepareSankeyData.test.ts +71 -0
  194. package/src/components/Sankey/helpers/prepareSankeyData.ts +90 -0
  195. package/src/components/Sankey/sankey.scss +57 -116
  196. package/src/components/Sankey/types/index.ts +41 -4
  197. package/src/components/ScatterPlot/ScatterPlot.jsx +28 -12
  198. package/src/components/ScatterPlot/ScatterPlot.test.tsx +60 -0
  199. package/src/components/SmallMultiples/SmallMultiples.tsx +12 -12
  200. package/src/components/SmallMultiples/tests/SmallMultipleTile.test.tsx +7 -5
  201. package/src/components/raceTiming.ts +18 -0
  202. package/src/data/initial-state.js +79 -31
  203. package/src/helpers/applyChartTypeContract.ts +71 -0
  204. package/src/helpers/axisLabelFontSize.ts +2 -2
  205. package/src/helpers/boxPlotStats.ts +60 -0
  206. package/src/helpers/bumpChartTooltip.ts +2 -1
  207. package/src/helpers/calculateHorizontalBarCategoryLabelWidth.test.ts +37 -0
  208. package/src/helpers/calculateHorizontalBarCategoryLabelWidth.ts +3 -2
  209. package/src/helpers/calculateRightYAxisWidth.ts +60 -0
  210. package/src/helpers/categoryOrder.ts +60 -0
  211. package/src/helpers/colorAssignmentHelpers.ts +2 -2
  212. package/src/helpers/createNewChartConfig.ts +56 -0
  213. package/src/helpers/dataDrivenYAxisCategories.ts +18 -0
  214. package/src/helpers/forecastLegend.ts +88 -0
  215. package/src/helpers/getBoxPlotConfig.ts +26 -38
  216. package/src/helpers/getChartTypeDefaultPalette.ts +29 -0
  217. package/src/helpers/getColorCodeCategoryColorMap.ts +26 -0
  218. package/src/helpers/getColorScale.ts +31 -22
  219. package/src/helpers/getComboChartConfig.ts +0 -1
  220. package/src/helpers/getDynamicYAxisCategories.ts +69 -0
  221. package/src/helpers/getExcludedData.ts +0 -1
  222. package/src/helpers/getHasBoundarySuppression.ts +4 -3
  223. package/src/helpers/getMinMax.ts +11 -8
  224. package/src/helpers/getNewRuntime.ts +4 -3
  225. package/src/helpers/getPiePercent.ts +3 -3
  226. package/src/helpers/getStackedSeriesMax.ts +51 -0
  227. package/src/helpers/getV21ChartDistributionColors.ts +57 -0
  228. package/src/helpers/handleRankByValue.ts +15 -15
  229. package/src/helpers/isConvertLineToBarGraph.ts +2 -2
  230. package/src/helpers/seriesColumnSettings.ts +31 -3
  231. package/src/helpers/sizeHelpers.ts +4 -5
  232. package/src/helpers/smallMultiplesHelpers.ts +10 -13
  233. package/src/helpers/tests/applyChartTypeContract.test.ts +37 -0
  234. package/src/helpers/tests/boxPlotStats.test.ts +31 -0
  235. package/src/helpers/tests/bumpChartTooltip.test.ts +29 -0
  236. package/src/helpers/tests/calculateRightYAxisWidth.test.ts +105 -0
  237. package/src/helpers/tests/categoryOrder.test.ts +57 -0
  238. package/src/helpers/tests/countNumOfTicks.test.ts +11 -0
  239. package/src/helpers/tests/createNewChartConfig.test.ts +182 -0
  240. package/src/helpers/tests/getBoxPlotConfig.test.ts +70 -0
  241. package/src/helpers/tests/getColorCodeCategoryColorMap.test.ts +113 -0
  242. package/src/helpers/tests/getColorScale.test.ts +226 -1
  243. package/src/helpers/tests/getDynamicYAxisCategories.test.ts +61 -0
  244. package/src/helpers/tests/getMinMax.test.ts +41 -0
  245. package/src/helpers/tests/getStackedSeriesMax.test.ts +26 -0
  246. package/src/helpers/tests/handleRankByValue.test.ts +55 -37
  247. package/src/helpers/tests/seriesColumnSettings.test.ts +83 -0
  248. package/src/helpers/tests/smallMultiplesHelpers.test.ts +37 -1
  249. package/src/helpers/tests/tooltipHelpers.test.ts +80 -0
  250. package/src/helpers/tooltipHelpers.ts +16 -0
  251. package/src/hooks/tests/useRightAxis.test.tsx +143 -2
  252. package/src/hooks/tests/useScales.test.tsx +3 -6
  253. package/src/hooks/tests/useTooltip.test.tsx +484 -0
  254. package/src/hooks/usePrefersReducedMotion.ts +22 -0
  255. package/src/hooks/useReduceData.ts +4 -36
  256. package/src/hooks/useRightAxis.ts +19 -26
  257. package/src/hooks/useScales.ts +15 -0
  258. package/src/hooks/useTooltip.tsx +106 -71
  259. package/src/scss/main.scss +74 -2
  260. package/src/store/chart.reducer.ts +4 -4
  261. package/src/test/CdcChart.dataTable.test.tsx +582 -14
  262. package/src/test/CdcChart.dendrogram.test.tsx +277 -0
  263. package/src/test/CdcChart.finalization.test.tsx +143 -0
  264. package/src/test/CdcChart.network.test.tsx +271 -0
  265. package/src/types/ChartConfig.ts +91 -54
  266. package/src/types/ChartContext.ts +5 -0
  267. package/src/types/Horizon.ts +1 -1
  268. package/LICENSE +0 -201
  269. package/dist/cdcchart-BWVIUcAF.es.js +0 -4802
  270. package/src/_stories/Chart.Sankey.smoke.stories.tsx +0 -62
  271. package/src/components/Sankey/components/ColumnList.tsx +0 -19
  272. package/src/components/Sankey/helpers/getSankeyTooltip.tsx +0 -34
  273. package/src/components/Sankey/useSankeyAlert.tsx +0 -60
  274. package/src/data/legacy-defaults.ts +0 -18
  275. package/src/helpers/ensureSpecialChartAxisTypes.ts +0 -11
  276. package/src/helpers/tests/ensureSpecialChartAxisTypes.test.ts +0 -30
@@ -0,0 +1,443 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite'
2
+ import { expect, fireEvent, userEvent, within } from 'storybook/test'
3
+ import {
4
+ assertVisualizationRendered,
5
+ openAccordion,
6
+ performAndAssert,
7
+ waitForEditor,
8
+ waitForPresence
9
+ } from '@cdc/core/helpers/testing'
10
+ import Chart from '../CdcChartComponent'
11
+ import annualChangeConfig from '../../examples/feature/bar/bar-chart-race-annual-change.json'
12
+
13
+ const meta: Meta<typeof Chart> = {
14
+ title: 'Components/Templates/Chart/Bar Race',
15
+ component: Chart,
16
+ parameters: { layout: 'fullscreen' }
17
+ }
18
+
19
+ export default meta
20
+ type Story = StoryObj<typeof Chart>
21
+
22
+ export const AnnualChangesOverTime: Story = {
23
+ name: 'Annual Changes Over Time',
24
+ args: {
25
+ config: annualChangeConfig as any,
26
+ isEditor: false,
27
+ interactionLabel: 'Annual program reach bar race'
28
+ },
29
+ play: async ({ canvasElement }) => {
30
+ const canvas = within(canvasElement)
31
+ await assertVisualizationRendered(canvasElement)
32
+
33
+ expect(canvasElement.querySelector('.bar-chart-race__frame')).toHaveTextContent('2018')
34
+ expect(canvasElement.querySelectorAll('.bar-chart-race__row')).toHaveLength(8)
35
+ const frameAxis = canvas.getByRole('group', { name: 'Year axis' })
36
+ expect(frameAxis).toHaveTextContent('2018')
37
+ expect(frameAxis).toHaveTextContent('2024')
38
+ expect(frameAxis.querySelectorAll('.bar-chart-race__frame-axis-tick')).toHaveLength(7)
39
+ expect(frameAxis.querySelector('[aria-current="step"]')).toHaveTextContent('2018')
40
+ expect(canvasElement.querySelector('.component--has-accent')).not.toBeInTheDocument()
41
+ expect(canvas.getByRole('button', { name: 'Play' })).toBeInTheDocument()
42
+ expect(canvasElement.querySelector('[data-category="Northeast"] .bar-chart-race__value')).toHaveTextContent('74')
43
+ expect(canvasElement.querySelector('[data-category="Mountain"] .bar-chart-race__value')).toHaveTextContent('61')
44
+
45
+ await performAndAssert(
46
+ 'Advance the annual time series',
47
+ () => canvasElement.querySelector('.bar-chart-race__frame')?.textContent?.trim(),
48
+ async () => userEvent.click(canvas.getByRole('button', { name: 'Play' })),
49
+ (before, after) => before === '2018' && after === '2019'
50
+ )
51
+
52
+ expect(frameAxis.querySelector('[aria-current="step"]')).toHaveTextContent('2019')
53
+ expect(canvasElement.querySelector('[data-category="Northeast"] .bar-chart-race__value')).toHaveTextContent('73')
54
+ expect(canvasElement.querySelector('[data-category="Mountain"] .bar-chart-race__value')).toHaveTextContent('67')
55
+ }
56
+ }
57
+
58
+ export const GeneralSectionTests: Story = {
59
+ name: 'Editor: General Section',
60
+ parameters: { test: { timeout: 30000 } },
61
+ beforeEach: () => {
62
+ const originalUrl = window.location.href
63
+ const originalState = window.history.state
64
+ const developerUrl = new URL(originalUrl)
65
+ developerUrl.searchParams.set('isCoveDeveloper', 'true')
66
+ window.history.replaceState(originalState, '', developerUrl)
67
+
68
+ return () => window.history.replaceState(originalState, '', originalUrl)
69
+ },
70
+ args: {
71
+ config: {
72
+ ...annualChangeConfig,
73
+ visualizationSubType: 'regular',
74
+ orientation: 'vertical'
75
+ } as any,
76
+ isEditor: true,
77
+ interactionLabel: 'Bar race editor story'
78
+ },
79
+ play: async ({ canvasElement }) => {
80
+ const canvas = within(canvasElement)
81
+ await waitForEditor(canvas)
82
+ await openAccordion(canvas, 'General')
83
+
84
+ expect(canvasElement.querySelector('.bar-chart-race')).not.toBeInTheDocument()
85
+ const subtype = canvas.getByLabelText(/chart subtype/i) as HTMLSelectElement
86
+ expect(Array.from(subtype.options).map(option => option.value)).toEqual(['regular', 'stacked', 'racing'])
87
+ await performAndAssert(
88
+ 'Enable racing mode',
89
+ () => Boolean(canvasElement.querySelector('.bar-chart-race')),
90
+ async () => userEvent.selectOptions(subtype, 'racing'),
91
+ (_before, after) => after
92
+ )
93
+
94
+ await waitForPresence('.bar-chart-race', canvasElement)
95
+ const labelPlacement = canvasElement.querySelector('select[name="labelPlacement"]') as HTMLSelectElement
96
+ expect(labelPlacement).toBeInTheDocument()
97
+ expect(Array.from(labelPlacement.options).map(option => option.value)).toEqual([
98
+ 'Below Bar',
99
+ 'On Date/Category Axis'
100
+ ])
101
+ await performAndAssert(
102
+ 'Move race labels below their bars',
103
+ () => {
104
+ const race = canvasElement.querySelector('.bar-chart-race')
105
+ return {
106
+ labelsBelowBar: race?.classList.contains('bar-chart-race--labels-below-bar') ?? false,
107
+ labelsOnAxis: race?.classList.contains('bar-chart-race--labels-on-axis') ?? false,
108
+ plotHeight: Number.parseFloat(
109
+ (canvasElement.querySelector('.bar-chart-race__plot') as HTMLElement | null)?.style.height || '0'
110
+ )
111
+ }
112
+ },
113
+ async () => userEvent.selectOptions(labelPlacement, 'Below Bar'),
114
+ (before, after) =>
115
+ before.labelsOnAxis &&
116
+ !before.labelsBelowBar &&
117
+ after.labelsBelowBar &&
118
+ !after.labelsOnAxis &&
119
+ after.plotHeight > before.plotHeight
120
+ )
121
+
122
+ const displayNumbersOnBar = canvas.getByLabelText(/display numbers on bar/i) as HTMLInputElement
123
+ await performAndAssert(
124
+ 'Move race values after their bars',
125
+ () => ({
126
+ inside: canvasElement.querySelectorAll('.bar-chart-race__value--inside').length,
127
+ after: canvasElement.querySelectorAll('.bar-chart-race__value--after').length
128
+ }),
129
+ async () => userEvent.click(displayNumbersOnBar),
130
+ (before, after) => before.inside > 0 && before.after === 0 && after.inside === 0 && after.after > 0
131
+ )
132
+ await performAndAssert(
133
+ 'Show race value labels on fitting bars',
134
+ () => ({
135
+ inside: canvasElement.querySelectorAll('.bar-chart-race__value--inside').length,
136
+ after: canvasElement.querySelectorAll('.bar-chart-race__value--after').length
137
+ }),
138
+ async () => userEvent.click(displayNumbersOnBar),
139
+ (_before, after) => after.inside > 0
140
+ )
141
+
142
+ const timing = canvas.getByRole('slider', { name: /seconds per time step/i }) as HTMLInputElement
143
+ expect(timing.valueAsNumber).toBe(0.5)
144
+ expect(timing).toHaveAttribute('min', '0')
145
+ expect(timing).toHaveAttribute('max', '1.5')
146
+ expect(timing).toHaveAttribute('step', '0.5')
147
+
148
+ const maximumBars = canvas.getByLabelText(/maximum bars/i) as HTMLInputElement
149
+ await performAndAssert(
150
+ 'Limit visible bars',
151
+ () => canvasElement.querySelectorAll('.bar-chart-race__row').length,
152
+ async () => {
153
+ await userEvent.clear(maximumBars)
154
+ await userEvent.type(maximumBars, '2')
155
+ await userEvent.tab()
156
+ },
157
+ (_before, after) => after === 2
158
+ )
159
+
160
+ await fireEvent.change(timing, { target: { value: '0' } })
161
+ await performAndAssert(
162
+ 'Apply instant playback timing',
163
+ () => canvasElement.querySelector('.bar-chart-race__frame')?.textContent?.trim(),
164
+ async () => userEvent.click(canvas.getByRole('button', { name: 'Play' })),
165
+ (before, after) => before === '2018' && after === '2024'
166
+ )
167
+ expect(canvas.getByRole('button', { name: 'Replay' })).toBeInTheDocument()
168
+
169
+ await performAndAssert(
170
+ 'Switch to stacked bars without playback',
171
+ () => Boolean(canvasElement.querySelector('.bar-chart-race')),
172
+ async () => userEvent.selectOptions(subtype, 'stacked'),
173
+ (before, after) => before && !after
174
+ )
175
+ }
176
+ }
177
+
178
+ export const ExistingRaceSubtypeTests: Story = {
179
+ name: 'Editor: Existing Race Subtype',
180
+ parameters: { test: { timeout: 30000 } },
181
+ args: {
182
+ config: annualChangeConfig as any,
183
+ isEditor: true,
184
+ interactionLabel: 'Existing bar race subtype story'
185
+ },
186
+ play: async ({ canvasElement }) => {
187
+ const canvas = within(canvasElement)
188
+ await waitForEditor(canvas)
189
+ await openAccordion(canvas, 'General')
190
+
191
+ const subtype = canvas.getByLabelText(/chart subtype/i) as HTMLSelectElement
192
+ expect(Array.from(subtype.options).map(option => option.value)).toContain('racing')
193
+ await performAndAssert(
194
+ 'Switch an existing race to Standard without losing Racing',
195
+ () => ({
196
+ options: Array.from((canvas.getByLabelText(/chart subtype/i) as HTMLSelectElement).options).map(
197
+ option => option.value
198
+ ),
199
+ raceRendered: Boolean(canvasElement.querySelector('.bar-chart-race')),
200
+ subtype: (canvas.getByLabelText(/chart subtype/i) as HTMLSelectElement).value
201
+ }),
202
+ async () => userEvent.selectOptions(subtype, 'regular'),
203
+ (before, after) =>
204
+ before.subtype === 'racing' &&
205
+ before.raceRendered &&
206
+ after.subtype === 'regular' &&
207
+ !after.raceRendered &&
208
+ after.options.includes('racing')
209
+ )
210
+ await performAndAssert(
211
+ 'Switch the same chart back to Racing',
212
+ () => Boolean(canvasElement.querySelector('.bar-chart-race')),
213
+ async () => userEvent.selectOptions(canvas.getByLabelText(/chart subtype/i), 'racing'),
214
+ (before, after) => !before && after
215
+ )
216
+ }
217
+ }
218
+
219
+ export const ValueAxisTests: Story = {
220
+ name: 'Editor: Race Value Axis',
221
+ parameters: { test: { timeout: 30000 } },
222
+ args: {
223
+ config: annualChangeConfig as any,
224
+ isEditor: true,
225
+ interactionLabel: 'Bar race value axis story'
226
+ },
227
+ play: async ({ canvasElement }) => {
228
+ const canvas = within(canvasElement)
229
+ await waitForEditor(canvas)
230
+ const valueAxisButton = canvas.getByRole('button', { name: 'Value Axis' })
231
+ await performAndAssert(
232
+ 'Open the race Value Axis panel',
233
+ () => valueAxisButton.getAttribute('aria-expanded'),
234
+ async () => userEvent.click(valueAxisButton),
235
+ (before, after) => before === 'false' && after === 'true'
236
+ )
237
+
238
+ expect(canvas.queryByLabelText('Axis Type')).not.toBeInTheDocument()
239
+ expect(canvas.queryByLabelText('Hide Axis')).not.toBeInTheDocument()
240
+ expect(canvas.queryByLabelText('Hide Tick Labels')).not.toBeInTheDocument()
241
+ expect(canvasElement.querySelector('#checkbox-dataFormat-none-commas')).toBeInTheDocument()
242
+ expect(canvasElement.querySelector('#input-dataFormat-none-roundTo')).toBeInTheDocument()
243
+ const prefix = canvasElement.querySelector('#input-dataFormat-none-prefix') as HTMLInputElement
244
+ await performAndAssert(
245
+ 'Apply value-axis number formatting to race labels',
246
+ () => canvasElement.querySelector('[data-category="Northeast"] .bar-chart-race__value')?.textContent,
247
+ async () => {
248
+ await userEvent.type(prefix, '$')
249
+ await userEvent.tab()
250
+ },
251
+ (before, after) => before === '74K' && after === '$74K'
252
+ )
253
+ }
254
+ }
255
+
256
+ export const DateCategoryAxisTests: Story = {
257
+ name: 'Editor: Race Date Category Axis',
258
+ parameters: { test: { timeout: 30000 } },
259
+ args: {
260
+ config: {
261
+ ...annualChangeConfig,
262
+ exclusions: { active: false, keys: [] }
263
+ } as any,
264
+ isEditor: true,
265
+ interactionLabel: 'Bar race date category axis story'
266
+ },
267
+ play: async ({ canvasElement }) => {
268
+ const canvas = within(canvasElement)
269
+ await waitForEditor(canvas)
270
+ const dateCategoryAxisButton = canvas.getByRole('button', { name: 'Date/Category Axis' })
271
+ await performAndAssert(
272
+ 'Open the race Date/Category Axis panel',
273
+ () => dateCategoryAxisButton.getAttribute('aria-expanded'),
274
+ async () => userEvent.click(dateCategoryAxisButton),
275
+ (before, after) => before === 'false' && after === 'true'
276
+ )
277
+
278
+ expect(canvas.queryByLabelText('Data Scaling Type')).not.toBeInTheDocument()
279
+ expect(canvas.queryByLabelText('Manual Ticks')).not.toBeInTheDocument()
280
+ expect(canvas.queryByLabelText('Number of Ticks')).not.toBeInTheDocument()
281
+ expect(canvas.queryByLabelText(/tick rotation/i)).not.toBeInTheDocument()
282
+ expect(canvas.queryByLabelText('Hide Axis')).not.toBeInTheDocument()
283
+ expect(canvas.queryByLabelText('Hide Tick Labels')).not.toBeInTheDocument()
284
+ expect(canvas.queryByLabelText(/show years once/i)).not.toBeInTheDocument()
285
+ expect(canvas.getByLabelText(/data key/i)).toBeInTheDocument()
286
+
287
+ const exclusionsToggle = canvas.getByLabelText(/exclude one or more values/i)
288
+ await userEvent.click(exclusionsToggle)
289
+ const getAddExclusion = () =>
290
+ canvasElement.querySelector('select option[value="2018"]')?.parentElement as HTMLSelectElement | null
291
+ const addExclusion = getAddExclusion() as HTMLSelectElement
292
+ await performAndAssert(
293
+ 'Exclude the first race frame',
294
+ () => canvasElement.querySelector('.bar-chart-race__frame')?.textContent?.trim(),
295
+ async () => userEvent.selectOptions(addExclusion, '2018'),
296
+ (before, after) => before === '2018' && after === '2019'
297
+ )
298
+ await performAndAssert(
299
+ 'Remove the final exclusion without corrupting its config',
300
+ () => ({
301
+ addExclusionVisible: Boolean(getAddExclusion()),
302
+ frame: canvasElement.querySelector('.bar-chart-race__frame')?.textContent?.trim(),
303
+ removeButtonVisible: Boolean(canvasElement.querySelector('.series-list__remove'))
304
+ }),
305
+ async () => userEvent.click(canvasElement.querySelector('.series-list__remove') as HTMLButtonElement),
306
+ (before, after) =>
307
+ before.frame === '2019' &&
308
+ before.removeButtonVisible &&
309
+ after.frame === '2018' &&
310
+ !after.removeButtonVisible &&
311
+ after.addExclusionVisible
312
+ )
313
+ expect(exclusionsToggle).toBeChecked()
314
+ }
315
+ }
316
+
317
+ export const UnsupportedPanelsTests: Story = {
318
+ name: 'Editor: Race Unsupported Panels',
319
+ args: {
320
+ config: {
321
+ ...annualChangeConfig,
322
+ filters: [
323
+ {
324
+ active: '2018',
325
+ columnName: 'Year',
326
+ filterStyle: 'dropdown',
327
+ values: ['2018', '2019', '2020', '2021', '2022', '2023', '2024']
328
+ }
329
+ ]
330
+ } as any,
331
+ isEditor: true,
332
+ interactionLabel: 'Bar race unsupported panels story'
333
+ },
334
+ play: async ({ canvasElement }) => {
335
+ const canvas = within(canvasElement)
336
+ await waitForEditor(canvas)
337
+
338
+ expect(canvasElement.querySelector('.bar-chart-race')).toBeInTheDocument()
339
+ expect(canvasElement.querySelectorAll('.bar-chart-race__frame-axis-tick')).toHaveLength(7)
340
+ for (const name of ['Regions', 'Legend', 'Filters', 'PatternSettings', 'Text Annotations']) {
341
+ expect(canvas.queryByRole('button', { name })).not.toBeInTheDocument()
342
+ }
343
+ }
344
+ }
345
+
346
+ export const VisualSectionTests: Story = {
347
+ name: 'Editor: Race Visual Section',
348
+ args: {
349
+ config: annualChangeConfig as any,
350
+ isEditor: true,
351
+ interactionLabel: 'Bar race visual section story'
352
+ },
353
+ play: async ({ canvasElement }) => {
354
+ const canvas = within(canvasElement)
355
+ await waitForEditor(canvas)
356
+ await openAccordion(canvas, '^Visual$')
357
+
358
+ expect(canvas.queryByLabelText('Bar Borders')).not.toBeInTheDocument()
359
+ }
360
+ }
361
+
362
+ export const IneligibleGeneralSectionTests: Story = {
363
+ name: 'Editor: Ineligible General Section',
364
+ parameters: { test: { timeout: 30000 } },
365
+ beforeEach: () => {
366
+ const originalUrl = window.location.href
367
+ const originalState = window.history.state
368
+ const developerUrl = new URL(originalUrl)
369
+ developerUrl.searchParams.set('isCoveDeveloper', 'true')
370
+ window.history.replaceState(originalState, '', developerUrl)
371
+
372
+ return () => window.history.replaceState(originalState, '', originalUrl)
373
+ },
374
+ args: {
375
+ config: {
376
+ ...annualChangeConfig,
377
+ visualizationSubType: 'regular',
378
+ orientation: 'horizontal',
379
+ barStyle: 'rounded',
380
+ series: annualChangeConfig.series.slice(0, 1)
381
+ } as any,
382
+ isEditor: true,
383
+ interactionLabel: 'Ineligible bar race editor story'
384
+ },
385
+ play: async ({ canvasElement }) => {
386
+ const canvas = within(canvasElement)
387
+ await waitForEditor(canvas)
388
+ await openAccordion(canvas, 'General')
389
+
390
+ const subtype = canvas.getByLabelText(/chart subtype/i) as HTMLSelectElement
391
+ await performAndAssert(
392
+ 'Keep ineligible racing mode selected with the regular Bar fallback',
393
+ () => {
394
+ const fallbackBars = Array.from(canvasElement.querySelectorAll('svg .horizontal path[id^="barGroup"]'))
395
+ return {
396
+ fallbackRendered: Boolean(canvasElement.querySelector('svg .horizontal')),
397
+ flatBarsRendered:
398
+ fallbackBars.length > 0 && fallbackBars.every(path => !path.getAttribute('d')?.includes('Q')),
399
+ guidanceInPreviewAlerts: Boolean(
400
+ Array.from(canvasElement.querySelectorAll('.chart-required-fields-alerts .alert-info')).find(alert =>
401
+ /requires at least two ordinary data series/i.test(alert.textContent || '')
402
+ )
403
+ ),
404
+ guidanceInEditorPanel: /requires at least two ordinary data series/i.test(
405
+ canvasElement.querySelector('.editor-panel')?.textContent || ''
406
+ ),
407
+ lollipopRendered: Boolean(canvasElement.querySelector('svg .horizontal circle[data-tooltip-html]')),
408
+ raceRendered: Boolean(canvasElement.querySelector('.bar-chart-race')),
409
+ subtype: (canvas.getByLabelText(/chart subtype/i) as HTMLSelectElement).value
410
+ }
411
+ },
412
+ async () => userEvent.selectOptions(subtype, 'racing'),
413
+ (_before, after) =>
414
+ after.subtype === 'racing' &&
415
+ after.guidanceInPreviewAlerts &&
416
+ !after.guidanceInEditorPanel &&
417
+ after.fallbackRendered &&
418
+ after.flatBarsRendered &&
419
+ !after.lollipopRendered &&
420
+ !after.raceRendered
421
+ )
422
+
423
+ await performAndAssert(
424
+ 'Exit an ineligible race through the same subtype control',
425
+ () => ({
426
+ fallbackRendered: Boolean(canvasElement.querySelector('svg .horizontal')),
427
+ guidanceInPreviewAlerts: Boolean(
428
+ Array.from(canvasElement.querySelectorAll('.chart-required-fields-alerts .alert-info')).find(alert =>
429
+ /requires at least two ordinary data series/i.test(alert.textContent || '')
430
+ )
431
+ ),
432
+ subtype: (canvas.getByLabelText(/chart subtype/i) as HTMLSelectElement).value
433
+ }),
434
+ async () => userEvent.selectOptions(canvas.getByLabelText(/chart subtype/i), 'regular'),
435
+ (before, after) =>
436
+ before.subtype === 'racing' &&
437
+ before.guidanceInPreviewAlerts &&
438
+ after.subtype === 'regular' &&
439
+ !after.guidanceInPreviewAlerts &&
440
+ after.fallbackRendered
441
+ )
442
+ }
443
+ }
@@ -0,0 +1,189 @@
1
+ import { useMemo } from 'react'
2
+ import type { Meta, StoryObj } from '@storybook/react-vite'
3
+ import { expect, waitFor } from 'storybook/test'
4
+
5
+ import { assertVisualizationRendered } from '@cdc/core/helpers/testing'
6
+ import Chart from '../CdcChartComponent'
7
+ import { getBoxPlotConfig } from '../helpers/getBoxPlotConfig'
8
+ import { ChartConfig } from '../types/ChartConfig'
9
+
10
+ const expectedNumericValueOrder = '1,2,3,10,20,100'
11
+ const expectedNumericNonOutlierOrder = '1,2,3,10,20'
12
+
13
+ const boxPlotRows = [
14
+ { Group: 'Group A', Score: '1' },
15
+ { Group: 'Group A', Score: '2' },
16
+ { Group: 'Group A', Score: '3' },
17
+ { Group: 'Group A', Score: '10' },
18
+ { Group: 'Group A', Score: '20' },
19
+ { Group: 'Group A', Score: '100' }
20
+ ]
21
+
22
+ const boxPlotLabels = {
23
+ maximum: 'Maximum',
24
+ q3: 'Upper Quartile',
25
+ median: 'Median',
26
+ q1: 'Lower Quartile',
27
+ minimum: 'Minimum',
28
+ count: 'Count',
29
+ mean: 'Mean',
30
+ iqr: 'Interquartile Range',
31
+ outliers: 'Outliers',
32
+ values: 'Values',
33
+ lowerBounds: 'Lower Bounds',
34
+ upperBounds: 'Upper Bounds'
35
+ }
36
+
37
+ const getConfig = (): ChartConfig =>
38
+ ({
39
+ version: '4.26.8',
40
+ type: 'chart',
41
+ visualizationType: 'Box Plot',
42
+ title: 'Box Plot Numeric Sort Regression',
43
+ orientation: 'vertical',
44
+ animate: false,
45
+ data: boxPlotRows,
46
+ table: {
47
+ show: true,
48
+ expanded: true,
49
+ download: false,
50
+ label: 'Data Table',
51
+ indexLabel: ''
52
+ },
53
+ xAxis: {
54
+ dataKey: 'Group',
55
+ type: 'categorical',
56
+ label: 'Group'
57
+ },
58
+ yAxis: {
59
+ dataKey: 'Score',
60
+ label: 'Score',
61
+ hideAxis: false,
62
+ hideTicks: false,
63
+ gridLines: true,
64
+ numTicks: 5
65
+ },
66
+ series: [
67
+ {
68
+ dataKey: 'Score',
69
+ name: 'Score'
70
+ }
71
+ ],
72
+ boxplot: {
73
+ plots: [],
74
+ categories: [],
75
+ borders: 'true',
76
+ plotOutlierValues: false,
77
+ plotNonOutlierValues: false,
78
+ labels: boxPlotLabels
79
+ },
80
+ legend: {
81
+ hide: true
82
+ },
83
+ dataFormat: {
84
+ roundTo: 0,
85
+ commas: false
86
+ }
87
+ } as ChartConfig)
88
+
89
+ function BoxPlotNumericSortRegression() {
90
+ const config = useMemo(() => getConfig(), [])
91
+ const [plots] = getBoxPlotConfig(config, config.data)
92
+ const currentValues = plots[0]?.values?.join(',') || ''
93
+ const currentNonOutliers = plots[0]?.columnNonOutliers?.join(',') || ''
94
+ const preservesNumericOrder =
95
+ currentValues === expectedNumericValueOrder && currentNonOutliers === expectedNumericNonOutlierOrder
96
+
97
+ return (
98
+ <div
99
+ style={{
100
+ background: '#eef2f7',
101
+ display: 'grid',
102
+ gap: 16,
103
+ padding: 16
104
+ }}
105
+ >
106
+ <section
107
+ style={{
108
+ background: '#fff',
109
+ border: '1px solid #d9dfe7',
110
+ borderRadius: 6,
111
+ padding: 16
112
+ }}
113
+ >
114
+ <Chart config={config} isEditor={false} interactionLabel='boxplot-numeric-sort-regression' />
115
+ </section>
116
+
117
+ <section
118
+ aria-label='Box plot numeric sort diagnostic'
119
+ style={{
120
+ background: '#f7f8fa',
121
+ border: '1px solid #d9dfe7',
122
+ borderRadius: 6,
123
+ display: 'grid',
124
+ gap: 8,
125
+ padding: 12
126
+ }}
127
+ >
128
+ <div>
129
+ <strong>Expected numeric values:</strong> <span>{expectedNumericValueOrder}</span>
130
+ </div>
131
+ <div>
132
+ <strong>Generated boxplot values:</strong> <span data-testid='boxplot-current-values'>{currentValues}</span>
133
+ </div>
134
+ <div>
135
+ <strong>Generated non-outliers:</strong>{' '}
136
+ <span data-testid='boxplot-current-non-outliers'>{currentNonOutliers}</span>
137
+ </div>
138
+ <div data-testid='boxplot-numeric-sort-status'>
139
+ <strong>Status:</strong> {preservesNumericOrder ? 'Numeric order preserved' : 'Issue reproduced'}
140
+ </div>
141
+ </section>
142
+ </div>
143
+ )
144
+ }
145
+
146
+ const getBoxPlotTableValue = (canvasElement: HTMLElement, rowLabel: string) => {
147
+ const row = Array.from(canvasElement.querySelectorAll('tr')).find(tableRow => {
148
+ const firstCell = tableRow.querySelector('th, td')
149
+ return firstCell?.textContent?.trim() === rowLabel
150
+ })
151
+
152
+ return Array.from(row?.querySelectorAll('th, td') || [])
153
+ .slice(1)
154
+ .map(cell => cell.textContent?.trim())
155
+ .join('')
156
+ }
157
+
158
+ const meta: Meta<typeof BoxPlotNumericSortRegression> = {
159
+ title: 'Components/Templates/Chart/Box Plot Numeric Sort Regression',
160
+ component: BoxPlotNumericSortRegression,
161
+ parameters: {
162
+ layout: 'fullscreen'
163
+ }
164
+ }
165
+
166
+ export default meta
167
+ type Story = StoryObj<typeof BoxPlotNumericSortRegression>
168
+
169
+ export const Generated_Values_Use_Numeric_Order: Story = {
170
+ play: async ({ canvasElement }) => {
171
+ await assertVisualizationRendered(canvasElement)
172
+
173
+ await waitFor(() => {
174
+ expect(canvasElement.querySelector('[data-testid="boxplot-current-values"]')?.textContent).toBe(
175
+ expectedNumericValueOrder
176
+ )
177
+ expect(canvasElement.querySelector('[data-testid="boxplot-current-non-outliers"]')?.textContent).toBe(
178
+ expectedNumericNonOutlierOrder
179
+ )
180
+ expect(canvasElement.querySelector('[data-testid="boxplot-numeric-sort-status"]')?.textContent).toContain(
181
+ 'Numeric order preserved'
182
+ )
183
+ expect(getBoxPlotTableValue(canvasElement, 'Values')).toBe(expectedNumericValueOrder)
184
+ expect(getBoxPlotTableValue(canvasElement, 'Non Outliers')).toBe(expectedNumericNonOutlierOrder)
185
+ expect(canvasElement.textContent).toContain(expectedNumericValueOrder)
186
+ expect(canvasElement.textContent).toContain(expectedNumericNonOutlierOrder)
187
+ })
188
+ }
189
+ }