@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,311 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite'
2
+ import { expect } from 'storybook/test'
3
+ import DynamicSeriesBarConfig from './_mock/dynamic_series_bar_config.json'
4
+ import SuppressedBarConfig from './_mock/bar-chart-suppressed.json'
5
+ import { assertVisualizationRendered, waitForPresence } from '@cdc/core/helpers/testing'
6
+
7
+ import Chart from '../CdcChart'
8
+ import MinimalExampleConfig from '../../examples/minimal-example.json'
9
+
10
+ const meta: Meta<typeof Chart> = {
11
+ title: 'Components/Templates/Chart/Percentage Values',
12
+ component: Chart
13
+ }
14
+
15
+ export default meta
16
+ type Story = StoryObj<typeof Chart>
17
+
18
+ const percentageValueConfig = {
19
+ ...MinimalExampleConfig,
20
+ title: 'Mixed Percentage and Numeric Values',
21
+ dataFormat: {
22
+ ...MinimalExampleConfig.dataFormat,
23
+ roundTo: 1
24
+ },
25
+ data: [
26
+ { category: 'Percentage', value: '12.5%' },
27
+ { category: 'Count', value: 20 }
28
+ ]
29
+ }
30
+
31
+ const horizontalPercentageValueConfig = {
32
+ ...percentageValueConfig,
33
+ title: 'Horizontal Percentage Value',
34
+ orientation: 'horizontal'
35
+ }
36
+
37
+ const findTooltipPath = (canvasElement: HTMLElement, ...requiredText: string[]) =>
38
+ Array.from(canvasElement.querySelectorAll<SVGPathElement>('path[data-tooltip-html]')).find(path => {
39
+ const tooltip = path.getAttribute('data-tooltip-html') || ''
40
+
41
+ return requiredText.every(text => tooltip.includes(text))
42
+ })
43
+
44
+ const getPercentageBar = (canvasElement: HTMLElement) => findTooltipPath(canvasElement, 'Percentage')
45
+
46
+ const stackedPercentageValueConfig = {
47
+ ...MinimalExampleConfig,
48
+ title: 'Stacked Percentage Value',
49
+ visualizationSubType: 'stacked',
50
+ orientation: 'vertical',
51
+ dataFormat: {
52
+ ...MinimalExampleConfig.dataFormat,
53
+ roundTo: 1
54
+ },
55
+ data: [
56
+ { category: 'Stack A', rate: '12.5%', count: 20 },
57
+ { category: 'Stack B', rate: 5, count: 10 }
58
+ ],
59
+ series: [
60
+ { dataKey: 'rate', name: 'Rate', type: 'Bar', axis: 'Left', tooltip: true },
61
+ { dataKey: 'count', name: 'Count', type: 'Bar', axis: 'Left', tooltip: true }
62
+ ]
63
+ }
64
+
65
+ const stackedHorizontalPercentageValueConfig = {
66
+ ...stackedPercentageValueConfig,
67
+ title: 'Stacked Horizontal Percentage Value',
68
+ orientation: 'horizontal'
69
+ }
70
+
71
+ const dynamicPercentageData = DynamicSeriesBarConfig.data.map(row =>
72
+ row.YearStart === 2015 && row.Stratification1 === '65 years or older' ? { ...row, Data_Value: '56.4%' } : row
73
+ )
74
+
75
+ const dynamicPercentageValueConfig = {
76
+ ...DynamicSeriesBarConfig,
77
+ title: 'Dynamic Series Percentage Value',
78
+ dataFormat: {
79
+ ...DynamicSeriesBarConfig.dataFormat,
80
+ roundTo: 1
81
+ },
82
+ data: dynamicPercentageData,
83
+ formattedData: dynamicPercentageData
84
+ }
85
+
86
+ const suppressionPercentageValueConfig = {
87
+ ...SuppressedBarConfig,
88
+ data: SuppressedBarConfig.data.map((row, index) => (index === 1 ? { ...row, 'Data 2': '110%' } : row))
89
+ }
90
+
91
+ const suppressionConfidenceData = [
92
+ { Race_Ethnicity: 'American Indian or Alaska Native', Data_Value: '73.1', CI_Lower: '70.4', CI_Upper: '75.8' },
93
+ { Race_Ethnicity: 'Asian', Data_Value: '77.2', CI_Lower: '75.3', CI_Upper: '79.1' },
94
+ { Race_Ethnicity: 'Black or African American', Data_Value: '68.4', CI_Lower: '66.7', CI_Upper: '70.1' },
95
+ { Race_Ethnicity: 'Hispanic or Latino', Data_Value: '71.6', CI_Lower: '70.2', CI_Upper: '73.0' },
96
+ { Race_Ethnicity: 'More than one race', Data_Value: '72.8', CI_Lower: '70.5', CI_Upper: '75.1' },
97
+ { Race_Ethnicity: 'White', Data_Value: '76.4', CI_Lower: '75.8', CI_Upper: '77.0' },
98
+ {
99
+ Race_Ethnicity: 'NHOPI',
100
+ Data_Value: 'Suppressed',
101
+ CI_Lower: 'Suppressed',
102
+ CI_Upper: 'Suppressed'
103
+ }
104
+ ]
105
+
106
+ const wildcardSuppressionConfidenceConfig = {
107
+ ...SuppressedBarConfig,
108
+ title: 'Wildcard Suppression with Confidence Intervals',
109
+ general: {
110
+ ...SuppressedBarConfig.general,
111
+ showMissingDataLabel: false,
112
+ showSuppressedSymbol: true,
113
+ showZeroValueData: false
114
+ },
115
+ xAxis: {
116
+ ...SuppressedBarConfig.xAxis,
117
+ dataKey: 'Race_Ethnicity',
118
+ type: 'categorical'
119
+ },
120
+ confidenceKeys: {
121
+ lower: 'CI_Lower',
122
+ upper: 'CI_Upper'
123
+ },
124
+ preliminaryData: [
125
+ {
126
+ ...SuppressedBarConfig.preliminaryData[0],
127
+ column: '',
128
+ value: 'Suppressed',
129
+ hideBarSymbol: true
130
+ }
131
+ ],
132
+ series: [{ ...SuppressedBarConfig.series[0], dataKey: 'Data_Value' }],
133
+ data: suppressionConfidenceData
134
+ }
135
+
136
+ const dynamicSuppressionCategory = '65 years or older'
137
+ const dynamicSuppressionData = DynamicSeriesBarConfig.data.map(row =>
138
+ row.YearStart === 2015 && row.Stratification1 === dynamicSuppressionCategory ? { ...row, Data_Value: 'ABC' } : row
139
+ )
140
+ const dynamicSuppressionConfig = {
141
+ ...DynamicSeriesBarConfig,
142
+ data: dynamicSuppressionData,
143
+ formattedData: dynamicSuppressionData,
144
+ preliminaryData: [
145
+ {
146
+ ...SuppressedBarConfig.preliminaryData[0],
147
+ column: dynamicSuppressionCategory,
148
+ value: 'ABC'
149
+ }
150
+ ]
151
+ }
152
+
153
+ export const Percent_Decorated_Value_Renders_As_Bar_And_Tooltip: Story = {
154
+ args: {
155
+ config: percentageValueConfig
156
+ },
157
+ play: async ({ canvasElement }) => {
158
+ await assertVisualizationRendered(canvasElement)
159
+ await waitForPresence('path[data-tooltip-html]', canvasElement)
160
+
161
+ const percentageBar = getPercentageBar(canvasElement)
162
+
163
+ expect(percentageBar).toBeDefined()
164
+ expect(percentageBar?.closest('g[display]')).toHaveAttribute('display', 'block')
165
+ expect(percentageBar?.getBBox().height).toBeGreaterThan(3)
166
+ expect(percentageBar?.getAttribute('data-tooltip-html')).toContain('12.5%')
167
+ }
168
+ }
169
+
170
+ export const Dynamic_Percent_Decoration_Is_Preserved_In_Tooltip: Story = {
171
+ args: {
172
+ config: dynamicPercentageValueConfig
173
+ },
174
+ play: async ({ canvasElement }) => {
175
+ await assertVisualizationRendered(canvasElement)
176
+ await waitForPresence('path[data-tooltip-html]', canvasElement)
177
+
178
+ const dynamicPercentageBar = findTooltipPath(canvasElement, '2015', '65 years or older')
179
+
180
+ expect(dynamicPercentageBar).toBeDefined()
181
+ expect(dynamicPercentageBar?.closest('g[display]')).toHaveAttribute('display', 'block')
182
+ expect(dynamicPercentageBar?.getAttribute('data-tooltip-html')).toContain('56.4%')
183
+ }
184
+ }
185
+
186
+ export const Suppression_Config_Does_Not_Hide_Percentage_Bars: Story = {
187
+ args: {
188
+ config: suppressionPercentageValueConfig
189
+ },
190
+ play: async ({ canvasElement }) => {
191
+ await assertVisualizationRendered(canvasElement)
192
+ await waitForPresence('path[data-tooltip-html]', canvasElement)
193
+
194
+ const percentageBar = findTooltipPath(canvasElement, '110%')
195
+ const suppressedBarGroup = Array.from(canvasElement.querySelectorAll<SVGGElement>('g[display="block"]')).find(
196
+ group => group.textContent?.includes('†') && group.querySelector('path[data-tooltip-html]')
197
+ )
198
+
199
+ expect(percentageBar).toBeDefined()
200
+ expect(percentageBar?.closest('g[display]')).toHaveAttribute('display', 'block')
201
+ expect(percentageBar?.getBBox().height).toBeGreaterThan(3)
202
+ expect(suppressedBarGroup).toBeDefined()
203
+ }
204
+ }
205
+
206
+ export const Wildcard_Suppression_Does_Not_Corrupt_Confidence_Domain: Story = {
207
+ args: {
208
+ config: wildcardSuppressionConfidenceConfig
209
+ },
210
+ play: async ({ canvasElement }) => {
211
+ await assertVisualizationRendered(canvasElement)
212
+ await waitForPresence('path[data-tooltip-html]', canvasElement)
213
+ await waitForPresence('path[stroke-width="px"]', canvasElement)
214
+
215
+ const visibleNumericBars = Array.from(
216
+ canvasElement.querySelectorAll<SVGPathElement>('path[data-tooltip-html]')
217
+ ).filter(path => path.closest('g[display]')?.getAttribute('display') === 'block')
218
+ const visibleConfidenceIntervals = Array.from(
219
+ canvasElement.querySelectorAll<SVGPathElement>('path[stroke-width="px"]')
220
+ ).filter(path => path.closest('g[display]')?.getAttribute('display') === 'block')
221
+ const suppressedBar = findTooltipPath(canvasElement, 'NHOPI')
222
+ const suppressedBarGroup = suppressedBar?.closest('g[display]')
223
+
224
+ expect(visibleNumericBars).toHaveLength(6)
225
+ visibleNumericBars.forEach(bar => {
226
+ expect(bar.getAttribute('d')).not.toContain('NaN')
227
+ expect(bar.getBBox().height).toBeGreaterThan(3)
228
+ })
229
+
230
+ expect(visibleConfidenceIntervals).toHaveLength(6)
231
+ visibleConfidenceIntervals.forEach(path => {
232
+ const pathData = path.getAttribute('d') || ''
233
+ const coordinates = pathData.match(/-?\d+(?:\.\d+)?/g)?.map(Number)
234
+
235
+ expect(pathData).not.toMatch(/NaN|Infinity/)
236
+ expect(coordinates?.length).toBeGreaterThan(0)
237
+ expect(coordinates?.every(Number.isFinite)).toBe(true)
238
+ })
239
+
240
+ expect(suppressedBar).toBeDefined()
241
+ expect(suppressedBarGroup).toHaveAttribute('display', 'none')
242
+ expect(suppressedBarGroup?.querySelector('rect[data-tooltip-html]')).toBeTruthy()
243
+ expect(suppressedBarGroup).not.toHaveTextContent('†')
244
+ }
245
+ }
246
+
247
+ export const Dynamic_Suppression_Target_Is_Preserved: Story = {
248
+ args: {
249
+ config: dynamicSuppressionConfig
250
+ },
251
+ play: async ({ canvasElement }) => {
252
+ await assertVisualizationRendered(canvasElement)
253
+ await waitForPresence('path[data-tooltip-html]', canvasElement)
254
+
255
+ const suppressedBar = findTooltipPath(canvasElement, '2015', dynamicSuppressionCategory)
256
+ const suppressedBarGroup = suppressedBar?.closest('g[display]')
257
+
258
+ expect(suppressedBar).toBeDefined()
259
+ expect(suppressedBarGroup).toHaveAttribute('display', 'block')
260
+ expect(suppressedBarGroup).toHaveTextContent('†')
261
+ }
262
+ }
263
+
264
+ export const Horizontal_Percent_Decoration_Is_Preserved_In_Tooltip: Story = {
265
+ args: {
266
+ config: horizontalPercentageValueConfig
267
+ },
268
+ play: async ({ canvasElement }) => {
269
+ await assertVisualizationRendered(canvasElement)
270
+ await waitForPresence('path[data-tooltip-html]', canvasElement)
271
+
272
+ const percentageBar = getPercentageBar(canvasElement)
273
+
274
+ expect(percentageBar).toBeDefined()
275
+ expect(percentageBar?.closest('g[display]')).toHaveAttribute('display', 'block')
276
+ expect(percentageBar?.getBBox().width).toBeGreaterThanOrEqual(3)
277
+ expect(percentageBar?.getAttribute('data-tooltip-html')).toContain('12.5%')
278
+ }
279
+ }
280
+
281
+ export const Stacked_Vertical_Percent_Decoration_Is_Preserved_In_Tooltip: Story = {
282
+ args: {
283
+ config: stackedPercentageValueConfig
284
+ },
285
+ play: async ({ canvasElement }) => {
286
+ await assertVisualizationRendered(canvasElement)
287
+ await waitForPresence('path[data-tooltip-html]', canvasElement)
288
+
289
+ const percentageSegment = findTooltipPath(canvasElement, 'Stack A', 'Rate:')
290
+
291
+ expect(percentageSegment).toBeDefined()
292
+ expect(percentageSegment).toHaveStyle({ display: 'block' })
293
+ expect(percentageSegment?.getAttribute('data-tooltip-html')).toContain('12.5%')
294
+ }
295
+ }
296
+
297
+ export const Stacked_Horizontal_Percent_Decoration_Is_Preserved_In_Tooltip: Story = {
298
+ args: {
299
+ config: stackedHorizontalPercentageValueConfig
300
+ },
301
+ play: async ({ canvasElement }) => {
302
+ await assertVisualizationRendered(canvasElement)
303
+ await waitForPresence('path[data-tooltip-html]', canvasElement)
304
+
305
+ const percentageSegment = findTooltipPath(canvasElement, 'Stack A', 'Rate:')
306
+
307
+ expect(percentageSegment).toBeDefined()
308
+ expect(percentageSegment).toHaveStyle({ display: 'block' })
309
+ expect(percentageSegment?.getAttribute('data-tooltip-html')).toContain('12.5%')
310
+ }
311
+ }
@@ -0,0 +1,49 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite'
2
+ import { expect, within } from 'storybook/test'
3
+ import Chart from '../CdcChart'
4
+ import pieConfig from './_mock/pie_with_data.json'
5
+ import { editConfigKeys } from '@cdc/core/helpers/configHelpers'
6
+ import { assertVisualizationRendered, waitForEditor, waitForPresence } from '@cdc/core/helpers/testing'
7
+
8
+ const meta: Meta<typeof Chart> = {
9
+ title: 'Components/Templates/Chart/Pie Labels',
10
+ component: Chart
11
+ }
12
+
13
+ type Story = StoryObj<typeof Chart>
14
+
15
+ export const CategoryPercentageLabels: Story = {
16
+ args: {
17
+ config: editConfigKeys(pieConfig, [
18
+ { path: ['labels'], value: true },
19
+ { path: ['legend', 'hide'], value: true }
20
+ ]),
21
+ isEditor: true
22
+ },
23
+ play: async ({ canvasElement }) => {
24
+ await waitForEditor(within(canvasElement))
25
+ await assertVisualizationRendered(canvasElement)
26
+ await waitForPresence('.pie-label', canvasElement)
27
+ expect(canvasElement.querySelector('line')).toBeNull()
28
+ }
29
+ }
30
+
31
+ export const DonutCategoryPercentageLabels: Story = {
32
+ args: {
33
+ config: editConfigKeys(pieConfig, [
34
+ { path: ['labels'], value: true },
35
+ { path: ['legend', 'hide'], value: true },
36
+ { path: ['pieType'], value: 'Donut' }
37
+ ]),
38
+ isEditor: true
39
+ },
40
+ play: async ({ canvasElement }) => {
41
+ await waitForEditor(within(canvasElement))
42
+ await assertVisualizationRendered(canvasElement)
43
+ await waitForPresence('.pie-label--outside', canvasElement)
44
+ expect(canvasElement.querySelector('.pie-label--inside')).toBeNull()
45
+ expect(canvasElement.querySelector('line')).toBeNull()
46
+ }
47
+ }
48
+
49
+ export default meta
@@ -0,0 +1,206 @@
1
+ import { useEffect, useMemo, useState } 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 { ChartConfig } from '../types/ChartConfig'
8
+
9
+ const meta: Meta<typeof RankByValueMutationHarness> = {
10
+ title: 'Components/Templates/Chart/Rank By Value Mutation Regression',
11
+ component: RankByValueMutationHarness,
12
+ parameters: {
13
+ layout: 'fullscreen'
14
+ }
15
+ }
16
+
17
+ export default meta
18
+ type Story = StoryObj<typeof RankByValueMutationHarness>
19
+
20
+ type RankRow = {
21
+ category: string
22
+ value: number
23
+ }
24
+
25
+ const sourceOrder = 'Alpha, Charlie, Bravo'
26
+ const rankedOrder = 'Charlie, Bravo, Alpha'
27
+ const rankedExcludingAlphaOrder = 'Charlie, Bravo'
28
+
29
+ const getOrder = (rows: RankRow[]) => rows.map(row => row.category).join(', ')
30
+
31
+ const getRenderedCategoryOrder = (panel: Element) => {
32
+ const categoryNames = sourceOrder.split(', ')
33
+ return Array.from(panel.querySelectorAll('svg text'))
34
+ .filter(textElement => categoryNames.includes(textElement.textContent?.trim() || ''))
35
+ .sort((a, b) => a.getBoundingClientRect().left - b.getBoundingClientRect().left)
36
+ .map(textElement => textElement.textContent?.trim())
37
+ .join(', ')
38
+ }
39
+
40
+ const getConfig = (
41
+ title: string,
42
+ data: RankRow[],
43
+ rankByValue?: 'asc' | 'desc',
44
+ excludedCategories: string[] = []
45
+ ): ChartConfig =>
46
+ ({
47
+ version: '4.26.8',
48
+ type: 'chart',
49
+ visualizationType: 'Bar',
50
+ title,
51
+ rankByValue,
52
+ data,
53
+ animate: false,
54
+ legend: {
55
+ hide: true
56
+ },
57
+ table: {
58
+ show: false
59
+ },
60
+ xAxis: {
61
+ dataKey: 'category',
62
+ type: 'categorical',
63
+ label: 'Category'
64
+ },
65
+ yAxis: {
66
+ label: 'Value',
67
+ hideAxis: false,
68
+ hideTicks: false,
69
+ gridLines: true,
70
+ numTicks: 4
71
+ },
72
+ series: [
73
+ {
74
+ dataKey: 'value',
75
+ name: 'Value',
76
+ type: 'Bar'
77
+ }
78
+ ],
79
+ dataFormat: {
80
+ roundTo: 0,
81
+ commas: false
82
+ },
83
+ exclusions: {
84
+ active: excludedCategories.length > 0,
85
+ keys: excludedCategories
86
+ }
87
+ } as ChartConfig)
88
+
89
+ const panelStyle = {
90
+ background: '#fff',
91
+ border: '1px solid #d9dfe7',
92
+ borderRadius: 6,
93
+ minWidth: 0,
94
+ padding: 16
95
+ }
96
+
97
+ const labelStyle = {
98
+ color: '#4b5563',
99
+ fontSize: 12,
100
+ fontWeight: 700,
101
+ margin: '0 0 8px',
102
+ textTransform: 'uppercase' as const
103
+ }
104
+
105
+ const diagnosticStyle = {
106
+ background: '#f7f8fa',
107
+ border: '1px solid #d9dfe7',
108
+ borderRadius: 6,
109
+ display: 'grid',
110
+ gap: 8,
111
+ gridColumn: '1 / -1',
112
+ padding: 12
113
+ }
114
+
115
+ function RankByValueMutationHarness() {
116
+ const sharedRows = useMemo<RankRow[]>(
117
+ () => [
118
+ { category: 'Alpha', value: 30 },
119
+ { category: 'Charlie', value: 10 },
120
+ { category: 'Bravo', value: 20 }
121
+ ],
122
+ []
123
+ )
124
+
125
+ const [observedOrder, setObservedOrder] = useState<string>('Checking shared data order...')
126
+
127
+ useEffect(() => {
128
+ const timeoutId = window.setTimeout(() => {
129
+ setObservedOrder(getOrder(sharedRows))
130
+ }, 100)
131
+
132
+ return () => window.clearTimeout(timeoutId)
133
+ }, [sharedRows])
134
+
135
+ const rankedConfig = useMemo(() => getConfig('Ranked by value ascending', sharedRows, 'asc'), [sharedRows])
136
+ const rankedExclusionConfig = useMemo(
137
+ () => getConfig('Ranked by value ascending with Alpha excluded', sharedRows, 'asc', ['Alpha']),
138
+ [sharedRows]
139
+ )
140
+ const sourceOrderConfig = useMemo(() => getConfig('Should preserve source order', sharedRows), [sharedRows])
141
+ const isChecking = observedOrder === 'Checking shared data order...'
142
+ const hasMutated = !isChecking && observedOrder !== sourceOrder
143
+
144
+ return (
145
+ <div
146
+ style={{
147
+ background: '#eef2f7',
148
+ display: 'grid',
149
+ gap: 16,
150
+ gridTemplateColumns: 'repeat(auto-fit, minmax(360px, 1fr))',
151
+ padding: 16
152
+ }}
153
+ >
154
+ <section style={panelStyle} data-rank-by-value-panel='ranked'>
155
+ <p style={labelStyle}>Ranked chart</p>
156
+ <Chart config={rankedConfig} isEditor={false} interactionLabel='' />
157
+ </section>
158
+
159
+ <section style={panelStyle} data-rank-by-value-panel='source-order'>
160
+ <p style={labelStyle}>Source-order chart sharing the same data</p>
161
+ <Chart config={sourceOrderConfig} isEditor={false} interactionLabel='' />
162
+ </section>
163
+
164
+ <section style={panelStyle} data-rank-by-value-panel='ranked-exclusion'>
165
+ <p style={labelStyle}>Ranked chart with Alpha excluded</p>
166
+ <Chart config={rankedExclusionConfig} isEditor={false} interactionLabel='' />
167
+ </section>
168
+
169
+ <section style={diagnosticStyle} aria-label='Shared data order diagnostic'>
170
+ <div>
171
+ <strong>Expected shared data order:</strong> <span>{sourceOrder}</span>
172
+ </div>
173
+ <div>
174
+ <strong>Observed shared data order:</strong>{' '}
175
+ <span data-testid='rank-by-value-observed-order'>{observedOrder}</span>
176
+ </div>
177
+ <div data-testid='rank-by-value-mutation-status'>
178
+ <strong>Status:</strong> {hasMutated ? 'Shared data was mutated' : 'Shared data order is preserved'}
179
+ </div>
180
+ </section>
181
+ </div>
182
+ )
183
+ }
184
+
185
+ export const Shared_Data_Order_Is_Preserved: Story = {
186
+ play: async ({ canvasElement }) => {
187
+ await assertVisualizationRendered(canvasElement)
188
+
189
+ await waitFor(() => {
190
+ const rankedPanel = canvasElement.querySelector('[data-rank-by-value-panel="ranked"]')
191
+ const sourceOrderPanel = canvasElement.querySelector('[data-rank-by-value-panel="source-order"]')
192
+ const rankedExclusionPanel = canvasElement.querySelector('[data-rank-by-value-panel="ranked-exclusion"]')
193
+
194
+ expect(rankedPanel?.querySelector('svg')).toBeTruthy()
195
+ expect(sourceOrderPanel?.querySelector('svg')).toBeTruthy()
196
+ expect(rankedExclusionPanel?.querySelector('svg')).toBeTruthy()
197
+ expect(rankedPanel ? getRenderedCategoryOrder(rankedPanel) : '').toBe(rankedOrder)
198
+ expect(sourceOrderPanel ? getRenderedCategoryOrder(sourceOrderPanel) : '').toBe(sourceOrder)
199
+ expect(rankedExclusionPanel ? getRenderedCategoryOrder(rankedExclusionPanel) : '').toBe(rankedExcludingAlphaOrder)
200
+ expect(canvasElement.querySelector('[data-testid="rank-by-value-observed-order"]')?.textContent).toBe(sourceOrder)
201
+ expect(canvasElement.querySelector('[data-testid="rank-by-value-mutation-status"]')?.textContent).toContain(
202
+ 'Shared data order is preserved'
203
+ )
204
+ })
205
+ }
206
+ }