@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
@@ -3,7 +3,13 @@ import { within, userEvent, expect } from 'storybook/test'
3
3
  import Chart from '../CdcChartComponent'
4
4
 
5
5
  // Import testing helpers following best practices document
6
- import { openAccordion, performAndAssert, waitForEditor } from '@cdc/core/helpers/testing'
6
+ import {
7
+ assertVisualizationRendered,
8
+ openAccordion,
9
+ performAndAssert,
10
+ waitForEditor,
11
+ waitForPresence
12
+ } from '@cdc/core/helpers/testing'
7
13
 
8
14
  // Import working configuration (same as other successful tests)
9
15
  import mockScatterPlot from './_mock/scatterplot_mock.json'
@@ -216,7 +222,14 @@ export const BarGeneralTests: Story = {
216
222
 
217
223
  // Additional validation
218
224
  hasHorizontalStructure: horizontalBars.length > 0,
219
- hasVerticalStructure: verticalBarContainers.length > 0
225
+ hasVerticalStructure: verticalBarContainers.length > 0,
226
+
227
+ // Bottom numeric axis defaults
228
+ bottomAxisLineCount: svg?.querySelectorAll('.bottom-axis > line').length || 0,
229
+ bottomAxisTickCount: svg?.querySelectorAll('.bottom-axis .vx-axis-tick > line').length || 0,
230
+
231
+ // Left date/category axis default
232
+ leftAxisLineCount: svg?.querySelectorAll('.left-axis > line').length || 0
220
233
  }
221
234
  }
222
235
 
@@ -242,6 +255,9 @@ export const BarGeneralTests: Story = {
242
255
  expect(after.hasHorizontalStructure).toBe(true)
243
256
  expect(after.horizontalBarCount).toBeGreaterThan(0)
244
257
  expect(after.isPredominantlyHorizontal).toBe(true)
258
+ expect(after.bottomAxisLineCount).toBe(0)
259
+ expect(after.bottomAxisTickCount).toBe(0)
260
+ expect(after.leftAxisLineCount).toBeGreaterThan(0)
245
261
 
246
262
  return true
247
263
  }
@@ -260,83 +276,74 @@ export const BarGeneralTests: Story = {
260
276
  expect(after.hasVerticalStructure).toBe(true)
261
277
  expect(after.verticalBarCount).toBeGreaterThan(0)
262
278
  expect(after.isPredominantlyVertical).toBe(true)
279
+ expect(after.bottomAxisLineCount).toBeGreaterThan(0)
280
+ expect(after.bottomAxisTickCount).toBeGreaterThan(0)
281
+ expect(after.leftAxisLineCount).toBe(0)
263
282
 
264
283
  return true
265
284
  }
266
285
  )
286
+ }
287
+ }
267
288
 
268
- // ============================================================================
269
- // TEST: Bar Style Dropdown
270
- // Tests visualization output changes when switching between flat, rounded, and lollipop bar styles
271
- // Per testing document: Test visualization output, not control state
272
- // ============================================================================
289
+ export const BarLollipopStyleTests: Story = {
290
+ name: 'Lollipop Bar Style Tests',
291
+ args: {
292
+ config: {
293
+ ...mockScatterPlot,
294
+ visualizationType: 'Bar',
295
+ visualizationSubType: 'regular',
296
+ orientation: 'vertical',
297
+ xAxis: {
298
+ ...mockScatterPlot.xAxis,
299
+ type: 'categorical',
300
+ dataKey: 'category'
301
+ },
302
+ series: [{ ...mockScatterPlot.series[0], type: 'Bar' }],
303
+ data: [
304
+ { category: 'Q1', y1: 19000 },
305
+ { category: 'Q2', y1: 18000 },
306
+ { category: 'Q3', y1: 7000 },
307
+ { category: 'Q4', y1: 15000 }
308
+ ]
309
+ },
310
+ isEditor: true
311
+ },
312
+ play: async ({ canvasElement }) => {
313
+ const canvas = within(canvasElement)
314
+ await waitForEditor(canvas)
315
+ await openAccordion(canvas, 'General')
273
316
 
274
317
  const getBarStyleVisualization = () => {
275
- // Target the chart visualization SVG specifically, not editor UI icons
276
318
  const chartContainer = canvasElement.querySelector('.cove-visualization__body, .chart-container, .visualization')
277
319
  const svg = chartContainer?.querySelector('svg') || canvasElement.querySelector('svg:not(.icon)')
320
+ const lollipopCircles = svg?.querySelectorAll('circle[cx][cy][r]').length || 0
321
+ const lollipopSquares = svg?.querySelectorAll('rect[data-tooltip-html]').length || 0
278
322
 
279
323
  return {
280
- // Flat/Rounded bars: Look for path elements (both use paths but different shapes)
281
- pathElements: svg?.querySelectorAll('path[fill]').length || 0,
282
-
283
- // Lollipop-specific: Look for circle "heads"
284
- lollipopCircles: svg?.querySelectorAll('circle[cx][cy][r]').length || 0,
285
-
286
- // Lollipop-specific: Look for square "heads" (alternative shape)
287
- lollipopSquares: svg?.querySelectorAll('rect[data-tooltip-html]').length || 0,
288
-
289
- // Specific style indicators
290
- hasLollipopElements: (svg?.querySelectorAll('circle[cx][cy][r], rect[data-tooltip-html]').length || 0) > 0,
324
+ lollipopCircles,
325
+ lollipopSquares,
326
+ hasLollipopElements: lollipopCircles + lollipopSquares > 0,
291
327
  hasRegularBarElements: (svg?.querySelectorAll('path[fill]').length || 0) > 0
292
328
  }
293
329
  }
294
330
 
295
- // First ensure we're in regular subtype so lollipop option is available
296
- await userEvent.selectOptions(chartSubtypeDropdown, 'regular')
297
- expect(chartSubtypeDropdown.value).toBe('regular')
298
-
299
- // Find Bar Style dropdown
300
331
  const barStyleDropdown = canvas.getByLabelText(/bar style/i) as HTMLSelectElement
332
+ const barStyleOptions = Array.from(barStyleDropdown.options).map(option => option.value)
333
+ expect(barStyleOptions).toEqual(expect.arrayContaining(['flat', 'rounded', 'lollipop']))
301
334
 
302
- // Verify dropdown has expected options for regular Bar charts
303
- const barStyleOptions = Array.from(barStyleDropdown.options).map(opt => opt.value)
304
- expect(barStyleOptions).toContain('flat')
305
- expect(barStyleOptions).toContain('rounded')
306
- expect(barStyleOptions).toContain('lollipop')
307
-
308
- // Test Bar Style: Flat → Lollipop (most dramatic visual change)
309
335
  await performAndAssert(
310
336
  'Switch Bar Style to Lollipop',
311
337
  getBarStyleVisualization,
312
338
  async () => await userEvent.selectOptions(barStyleDropdown, 'lollipop'),
313
- (before, after) => {
314
- // Control state changed
315
- expect(barStyleDropdown.value).toBe('lollipop')
316
-
317
- // Lollipop elements (circles or squares) should appear
318
- expect(after.hasLollipopElements).toBe(true)
319
- expect(after.lollipopCircles + after.lollipopSquares).toBeGreaterThan(0)
320
-
321
- return true
322
- }
339
+ (_before, after) => after.hasLollipopElements && after.lollipopCircles + after.lollipopSquares > 0
323
340
  )
324
341
 
325
- // Test switching back to flat
326
342
  await performAndAssert(
327
343
  'Switch Bar Style back to Flat',
328
344
  getBarStyleVisualization,
329
345
  async () => await userEvent.selectOptions(barStyleDropdown, 'flat'),
330
- (before, after) => {
331
- // Control state changed back
332
- expect(barStyleDropdown.value).toBe('flat')
333
-
334
- // Lollipop elements should disappear, regular bar elements should remain
335
- expect(after.hasLollipopElements).toBe(false)
336
- expect(after.hasRegularBarElements).toBe(true)
337
-
338
- return true
339
- }
346
+ (_before, after) => !after.hasLollipopElements && after.hasRegularBarElements
340
347
  )
341
348
  }
342
349
  }
@@ -1204,6 +1211,87 @@ export const BarLeftValueAxisTests: Story = {
1204
1211
  // - Use performAndAssert pattern for consistent testing
1205
1212
  // - Focus on user-visible changes in the SVG rendering
1206
1213
  // ============================================================================
1214
+ export const HorizontalDateCategoryAxisTitlePlacementTests: StoryObj<typeof Chart> = {
1215
+ name: 'Horizontal Date/Category Axis Title Placement Tests',
1216
+ args: {
1217
+ config: {
1218
+ ...barChartEditorTest,
1219
+ title: 'Horizontal Bar Chart Date/Category Axis Title Placement Test',
1220
+ orientation: 'horizontal',
1221
+ xAxis: {
1222
+ ...barChartEditorTest.xAxis,
1223
+ label: 'Year'
1224
+ },
1225
+ yAxis: {
1226
+ ...barChartEditorTest.yAxis,
1227
+ titlePlacement: 'side'
1228
+ }
1229
+ },
1230
+ isEditor: true
1231
+ },
1232
+ play: async ({ canvasElement }) => {
1233
+ const canvas = within(canvasElement)
1234
+ await waitForEditor(canvas)
1235
+ await openAccordion(canvas, 'Date/Category Axis')
1236
+
1237
+ const titlePlacementSelect = canvasElement.querySelector('select[name="titlePlacement"]') as HTMLSelectElement
1238
+ expect(titlePlacementSelect).toBeTruthy()
1239
+ const accordionItem = titlePlacementSelect.closest('.accordion__item') as HTMLElement
1240
+ const categoryLabelInput = accordionItem.querySelector('input[name="xAxis-null-label"]') as HTMLInputElement
1241
+
1242
+ expect(within(accordionItem).getByRole('button', { name: /date\/category axis/i })).toBeVisible()
1243
+ expect(categoryLabelInput).toBeTruthy()
1244
+ expect(categoryLabelInput.getAttribute('maxlength')).toBe('35')
1245
+
1246
+ const getAxisTitlePlacementVisualization = () => {
1247
+ const chartContainer = canvasElement.querySelector('.cove-visualization__body, .chart-container, .visualization')
1248
+ const svg = chartContainer?.querySelector('svg') || canvasElement.querySelector('svg:not(.icon)')
1249
+ const sideLabel = Array.from(svg?.querySelectorAll('text.y-label') || []).find(
1250
+ element => element.textContent?.trim() === 'Year'
1251
+ )
1252
+ const topLabel = chartContainer?.querySelector('.y-axis-top-title')
1253
+
1254
+ return {
1255
+ hasSideLabel: Boolean(sideLabel),
1256
+ sideLabelTransform: sideLabel?.getAttribute('transform') || '',
1257
+ topLabelText: topLabel?.textContent?.trim() || ''
1258
+ }
1259
+ }
1260
+
1261
+ await performAndAssert(
1262
+ 'Move horizontal category-axis title to top',
1263
+ getAxisTitlePlacementVisualization,
1264
+ async () => {
1265
+ await userEvent.selectOptions(titlePlacementSelect, 'top')
1266
+ },
1267
+ (before, after) => {
1268
+ expect(before.hasSideLabel).toBe(true)
1269
+ expect(before.sideLabelTransform).toContain('rotate(-90)')
1270
+ expect(after.hasSideLabel).toBe(false)
1271
+ expect(after.topLabelText).toBe('Year')
1272
+ expect(categoryLabelInput.getAttribute('maxlength')).toBeNull()
1273
+ return true
1274
+ }
1275
+ )
1276
+
1277
+ await performAndAssert(
1278
+ 'Move horizontal category-axis title back to side',
1279
+ getAxisTitlePlacementVisualization,
1280
+ async () => {
1281
+ await userEvent.selectOptions(titlePlacementSelect, 'side')
1282
+ },
1283
+ (before, after) => {
1284
+ expect(before.topLabelText).toBe('Year')
1285
+ expect(after.hasSideLabel).toBe(true)
1286
+ expect(after.sideLabelTransform).toContain('rotate(-90)')
1287
+ expect(after.topLabelText).toBe('')
1288
+ expect(categoryLabelInput.getAttribute('maxlength')).toBe('35')
1289
+ return true
1290
+ }
1291
+ )
1292
+ }
1293
+ }
1294
+
1207
1295
  export const DateCategoryAxisSectionTests: StoryObj<typeof Chart> = {
1208
1296
  name: 'Date/Category Axis Section Tests',
1209
1297
  args: {
@@ -2969,10 +3057,10 @@ export const BarPatternSettingsTests: Story = {
2969
3057
  series: [{ dataKey: 'y1' }, { dataKey: 'y2' }, { dataKey: 'y3' }, { dataKey: 'y4' }],
2970
3058
  // Override with data suitable for pattern testing
2971
3059
  data: [
2972
- { category: 'Q1', y1: 19000, y2: 47000, y3: 59000, y4: 91000 },
2973
- { category: 'Q2', y1: 18000, y2: 32000, y3: 19000, y4: 89000 },
2974
- { category: 'Q3', y1: 7000, y2: 38000, y3: 74000, y4: 89000 },
2975
- { category: 'Q4', y1: 15000, y2: 41000, y3: 67000, y4: 95000 }
3060
+ { category: 'Q1', y1: 19000, y2: 47000, y2Portion: 23500, y3: 59000, y4: 91000 },
3061
+ { category: 'Q2', y1: 18000, y2: 32000, y2Portion: 16000, y3: 19000, y4: 89000 },
3062
+ { category: 'Q3', y1: 7000, y2: 38000, y2Portion: 19000, y3: 74000, y4: 89000 },
3063
+ { category: 'Q4', y1: 15000, y2: 41000, y2Portion: 20500, y3: 67000, y4: 95000 }
2976
3064
  ],
2977
3065
  patterns: [] // Start with no patterns
2978
3066
  },
@@ -3039,7 +3127,9 @@ export const BarPatternSettingsTests: Story = {
3039
3127
  // Extract pattern IDs for validation
3040
3128
  patternIds: Array.from(patternElements).map(pattern => pattern.getAttribute('id')),
3041
3129
  // Extract overlay classes for validation
3042
- overlayClasses: Array.from(patternOverlays).map(overlay => overlay.getAttribute('class'))
3130
+ overlayClasses: Array.from(patternOverlays).map(overlay => overlay.getAttribute('class')),
3131
+ patternTypeValue: (canvasElement.querySelector('select[id*="pattern-type-"]') as HTMLSelectElement)?.value,
3132
+ patternColorValue: (canvasElement.querySelector('input[id*="pattern-color-"]') as HTMLInputElement)?.value
3043
3133
  }
3044
3134
  }
3045
3135
 
@@ -3086,6 +3176,8 @@ export const BarPatternSettingsTests: Story = {
3086
3176
  // Pattern configuration should still be there and chart functional
3087
3177
  expect(after.hasPatternConfigSections).toBe(true)
3088
3178
  expect(after.hasChartSvg).toBe(true)
3179
+ expect(after.patternTypeValue).toBe('diagonalLines')
3180
+ expect(after.patternColorValue).toBe('#1c1d1f')
3089
3181
 
3090
3182
  return true
3091
3183
  }
@@ -3448,6 +3540,356 @@ export const BarPatternSettingsTests: Story = {
3448
3540
  return true
3449
3541
  }
3450
3542
  )
3543
+
3544
+ await performAndAssert(
3545
+ 'Switch to Portion Application - Portion controls replace exact-value controls',
3546
+ () => ({
3547
+ hasTargetControl: !!canvasElement.querySelector('select[id*="pattern-target-series-Pattern2"]'),
3548
+ hasValueColumnControl: !!canvasElement.querySelector('select[id*="pattern-value-key-Pattern2"]'),
3549
+ hasValueColumnTooltip: !!canvasElement
3550
+ .querySelector('select[id*="pattern-value-key-Pattern2"]')
3551
+ ?.closest('label')
3552
+ ?.querySelector('.cove-tooltip'),
3553
+ hasExactValueControl: !!canvasElement.querySelector('input[id*="pattern-datavalue-Pattern2"]'),
3554
+ hasStandalonePortionGuidance:
3555
+ canvas.queryByText(
3556
+ 'Portion values must be greater than zero and no greater than the corresponding total.'
3557
+ ) !== null,
3558
+ legendBackgroundFill: canvasElement
3559
+ .querySelector(
3560
+ '.legend-item--pattern .legend-shape-svg > circle, .legend-item--pattern .legend-shape-svg > rect'
3561
+ )
3562
+ ?.getAttribute('fill')
3563
+ }),
3564
+ async () => {
3565
+ const applicationSelect = canvasElement.querySelector(
3566
+ 'select[id*="pattern-application-Pattern2"]'
3567
+ ) as HTMLSelectElement
3568
+ expect(applicationSelect).toBeTruthy()
3569
+ await userEvent.selectOptions(applicationSelect, 'portion')
3570
+ },
3571
+ (before, after) =>
3572
+ before.legendBackgroundFill === 'white' &&
3573
+ after.hasTargetControl &&
3574
+ after.hasValueColumnControl &&
3575
+ after.hasValueColumnTooltip &&
3576
+ !after.hasStandalonePortionGuidance &&
3577
+ !after.hasExactValueControl
3578
+ )
3579
+
3580
+ await performAndAssert(
3581
+ 'Configure Portion Value - Half-height overlays render at the bar end',
3582
+ getPatternVisualizationState,
3583
+ async () => {
3584
+ const valueColumnSelect = canvasElement.querySelector(
3585
+ 'select[id*="pattern-value-key-Pattern2"]'
3586
+ ) as HTMLSelectElement
3587
+ expect(valueColumnSelect).toBeTruthy()
3588
+ await userEvent.selectOptions(valueColumnSelect, 'y2Portion')
3589
+ },
3590
+ (_before, after) => after.patternOverlaysCount === 4,
3591
+ after => {
3592
+ expect(after.patternDefinitionsCount).toBe(1)
3593
+ expect(canvasElement.querySelectorAll('.legend-item--pattern')).toHaveLength(1)
3594
+
3595
+ const overlay = canvasElement.querySelector('.pattern-overlay--portion') as SVGGraphicsElement
3596
+ const baseBar = overlay?.parentElement?.querySelector('path[id^="barGroup"]') as SVGGraphicsElement
3597
+ const boundary = overlay?.parentElement?.querySelector('.pattern-overlay__boundary') as SVGLineElement
3598
+ const legendBackground = canvasElement.querySelector(
3599
+ '.legend-item--pattern .legend-shape-svg > circle, .legend-item--pattern .legend-shape-svg > rect'
3600
+ ) as SVGGraphicsElement
3601
+ expect(overlay).toBeTruthy()
3602
+ expect(baseBar).toBeTruthy()
3603
+ expect(legendBackground).toBeTruthy()
3604
+ expect(canvasElement.querySelectorAll('.pattern-overlay__boundary')).toHaveLength(4)
3605
+ expect(legendBackground.getAttribute('fill')).toBe(baseBar.getAttribute('fill'))
3606
+ expect(overlay.getBBox().height / baseBar.getBBox().height).toBeCloseTo(0.5, 2)
3607
+ expect(overlay.getBBox().y).toBeCloseTo(baseBar.getBBox().y, 2)
3608
+ expect(Number(boundary.getAttribute('y1'))).toBeCloseTo(overlay.getBBox().y + overlay.getBBox().height, 2)
3609
+ expect(boundary.getAttribute('stroke-width')).toBe('0.75')
3610
+ expect(canvas.getByTestId('portion-pattern-column-note')).toBeVisible()
3611
+ }
3612
+ )
3613
+
3614
+ await performAndAssert(
3615
+ 'Beginning Placement - Portion overlay moves to the bar baseline',
3616
+ () => {
3617
+ const overlay = canvasElement.querySelector('.pattern-overlay--portion') as SVGGraphicsElement
3618
+ const baseBar = overlay?.parentElement?.querySelector('path[id^="barGroup"]') as SVGGraphicsElement
3619
+ const boundary = overlay?.parentElement?.querySelector('.pattern-overlay__boundary') as SVGLineElement
3620
+ return overlay && baseBar && boundary
3621
+ ? {
3622
+ overlayBottom: overlay.getBBox().y + overlay.getBBox().height,
3623
+ baseBottom: baseBar.getBBox().y + baseBar.getBBox().height,
3624
+ overlayTop: overlay.getBBox().y,
3625
+ boundaryY: Number(boundary.getAttribute('y1'))
3626
+ }
3627
+ : { overlayBottom: 0, baseBottom: 1, overlayTop: 0, boundaryY: 1 }
3628
+ },
3629
+ async () => {
3630
+ const placementSelect = canvasElement.querySelector(
3631
+ 'select[id*="pattern-placement-Pattern2"]'
3632
+ ) as HTMLSelectElement
3633
+ expect(placementSelect).toBeTruthy()
3634
+ await userEvent.selectOptions(placementSelect, 'start')
3635
+ },
3636
+ (_before, after) =>
3637
+ Math.abs(after.overlayBottom - after.baseBottom) < 0.01 && Math.abs(after.boundaryY - after.overlayTop) < 0.01
3638
+ )
3639
+
3640
+ await performAndAssert(
3641
+ 'Duplicate Portion Target - Used series is unavailable to later patterns',
3642
+ () => ({ patternCount: patternSettingsPanel?.querySelectorAll('.series-item--chart').length || 0 }),
3643
+ async () => {
3644
+ await userEvent.click(canvas.getByRole('button', { name: /add pattern/i }))
3645
+ const applicationSelect = canvasElement.querySelector(
3646
+ 'select[id*="pattern-application-Pattern2"]'
3647
+ ) as HTMLSelectElement
3648
+ const applicationSelects = canvasElement.querySelectorAll('select[id*="pattern-application-"]')
3649
+ const newApplicationSelect = applicationSelects[applicationSelects.length - 1] as HTMLSelectElement
3650
+ expect(applicationSelect).toBeTruthy()
3651
+ expect(newApplicationSelect).toBeTruthy()
3652
+ await userEvent.selectOptions(newApplicationSelect, 'portion')
3653
+
3654
+ const targetSelects = canvasElement.querySelectorAll('select[id*="pattern-target-series-"]')
3655
+ const newTargetSelect = targetSelects[targetSelects.length - 1] as HTMLSelectElement
3656
+ expect(Array.from(newTargetSelect.options).some(option => option.value === 'y2')).toBe(false)
3657
+ },
3658
+ (before, after) => after.patternCount === before.patternCount + 1
3659
+ )
3660
+ }
3661
+ }
3662
+
3663
+ const portionPatternLegend = {
3664
+ ...mockScatterPlot.legend,
3665
+ patterns: {
3666
+ Portion: {
3667
+ application: 'portion' as const,
3668
+ dataKey: 'y1',
3669
+ patternValueKey: 'portion',
3670
+ placement: 'end' as const,
3671
+ label: 'Portion',
3672
+ color: '#000000',
3673
+ shape: 'diagonalLines',
3674
+ patternSize: 10
3675
+ }
3676
+ }
3677
+ }
3678
+
3679
+ const portionPatternData = [
3680
+ { category: 'Q1', y1: 100, y2: 80, portion: 25 },
3681
+ { category: 'Q2', y1: 80, y2: 60, portion: 20 }
3682
+ ]
3683
+
3684
+ export const StackedPortionPatternEditorWarningTests: Story = {
3685
+ name: 'Stacked Portion Pattern Editor Warning Tests',
3686
+ args: {
3687
+ config: {
3688
+ ...mockScatterPlot,
3689
+ visualizationType: 'Bar',
3690
+ visualizationSubType: 'stacked',
3691
+ orientation: 'vertical',
3692
+ xAxis: { ...mockScatterPlot.xAxis, type: 'categorical', dataKey: 'category' },
3693
+ yAxis: { ...mockScatterPlot.yAxis, type: 'continuous', dataKey: 'y1' },
3694
+ series: [
3695
+ { ...mockScatterPlot.series[0], dataKey: 'y1', type: 'Bar' },
3696
+ { ...mockScatterPlot.series[1], dataKey: 'y2', type: 'Bar' }
3697
+ ],
3698
+ data: portionPatternData,
3699
+ legend: {
3700
+ ...mockScatterPlot.legend,
3701
+ patterns: {
3702
+ WarningPattern: {
3703
+ application: 'value',
3704
+ dataKey: 'category',
3705
+ dataValue: 'Q1',
3706
+ label: 'Warning Pattern',
3707
+ color: '#000000',
3708
+ shape: 'diagonalLines',
3709
+ patternSize: 10
3710
+ }
3711
+ }
3712
+ }
3713
+ },
3714
+ isEditor: true
3715
+ },
3716
+ play: async ({ canvasElement }) => {
3717
+ const canvas = within(canvasElement)
3718
+ await waitForEditor(canvas)
3719
+ await openAccordion(canvas, 'Pattern Settings')
3720
+
3721
+ await performAndAssert(
3722
+ 'Select Portion Application - Unsupported stacked charts show an inline warning',
3723
+ () => ({
3724
+ application: (
3725
+ canvasElement.querySelector('select[id*="pattern-application-WarningPattern"]') as HTMLSelectElement
3726
+ )?.value,
3727
+ warningVisible:
3728
+ canvas.queryByText(
3729
+ /portion patterns are supported only for regular unstacked bar charts and combo charts/i
3730
+ ) !== null,
3731
+ targetOptionCount:
3732
+ (canvasElement.querySelector('select[id*="pattern-target-series-WarningPattern"]') as HTMLSelectElement)
3733
+ ?.options.length || 0
3734
+ }),
3735
+ async () => {
3736
+ const applicationSelect = canvasElement.querySelector(
3737
+ 'select[id*="pattern-application-WarningPattern"]'
3738
+ ) as HTMLSelectElement
3739
+ expect(applicationSelect).toBeTruthy()
3740
+ await userEvent.selectOptions(applicationSelect, 'portion')
3741
+ },
3742
+ (_before, after) => after.application === 'portion' && after.warningVisible && after.targetOptionCount === 1
3743
+ )
3744
+ }
3745
+ }
3746
+
3747
+ export const HorizontalPortionPatternRenderingTests: Story = {
3748
+ name: 'Horizontal Portion Pattern Rendering Tests',
3749
+ args: {
3750
+ config: {
3751
+ ...mockScatterPlot,
3752
+ visualizationType: 'Bar',
3753
+ visualizationSubType: 'regular',
3754
+ orientation: 'horizontal',
3755
+ xAxis: { ...mockScatterPlot.xAxis, type: 'categorical', dataKey: 'category' },
3756
+ yAxis: { ...mockScatterPlot.yAxis, type: 'continuous', dataKey: 'y1' },
3757
+ series: [{ ...mockScatterPlot.series[0], dataKey: 'y1', type: 'Bar' }],
3758
+ data: portionPatternData,
3759
+ legend: portionPatternLegend
3760
+ },
3761
+ isEditor: false
3762
+ },
3763
+ play: async ({ canvasElement }) => {
3764
+ await assertVisualizationRendered(canvasElement)
3765
+
3766
+ const overlay = canvasElement.querySelector('.pattern-overlay--portion') as SVGGraphicsElement
3767
+ const baseBar = overlay?.parentElement?.querySelector('path[id^="barGroup"]') as SVGGraphicsElement
3768
+ const boundary = overlay?.parentElement?.querySelector('.pattern-overlay__boundary') as SVGLineElement
3769
+ expect(overlay).toBeTruthy()
3770
+ expect(baseBar).toBeTruthy()
3771
+ expect(boundary).toBeTruthy()
3772
+ expect(overlay.getBBox().width / baseBar.getBBox().width).toBeCloseTo(0.25, 2)
3773
+ expect(overlay.getBBox().x + overlay.getBBox().width).toBeCloseTo(baseBar.getBBox().x + baseBar.getBBox().width, 2)
3774
+ expect(Number(boundary.getAttribute('x1'))).toBeCloseTo(overlay.getBBox().x, 2)
3775
+ expect(boundary.getAttribute('stroke')).toBe('#000000')
3776
+ }
3777
+ }
3778
+
3779
+ export const ComboPortionPatternRenderingTests: Story = {
3780
+ name: 'Combo Portion Pattern Rendering Tests',
3781
+ args: {
3782
+ config: {
3783
+ ...mockScatterPlot,
3784
+ visualizationType: 'Combo',
3785
+ visualizationSubType: 'regular',
3786
+ orientation: 'vertical',
3787
+ xAxis: { ...mockScatterPlot.xAxis, type: 'categorical', dataKey: 'category' },
3788
+ yAxis: { ...mockScatterPlot.yAxis, type: 'continuous', dataKey: 'y1' },
3789
+ series: [
3790
+ { ...mockScatterPlot.series[0], dataKey: 'y1', type: 'Bar' },
3791
+ { ...mockScatterPlot.series[1], dataKey: 'y2', type: 'Line' }
3792
+ ],
3793
+ data: portionPatternData,
3794
+ legend: portionPatternLegend
3795
+ },
3796
+ isEditor: false
3797
+ },
3798
+ play: async ({ canvasElement }) => {
3799
+ await assertVisualizationRendered(canvasElement)
3800
+ expect(canvasElement.querySelectorAll('.pattern-overlay--portion')).toHaveLength(2)
3801
+ expect(canvasElement.querySelectorAll('.pattern-overlay__boundary')).toHaveLength(2)
3802
+ expect(canvasElement.querySelector('path[id^="barGroup"]')).toBeTruthy()
3803
+ }
3804
+ }
3805
+
3806
+ export const InvalidPortionPatternRenderingTests: Story = {
3807
+ name: 'Invalid Portion Pattern Rendering Tests',
3808
+ args: {
3809
+ config: {
3810
+ ...mockScatterPlot,
3811
+ visualizationType: 'Bar',
3812
+ visualizationSubType: 'regular',
3813
+ orientation: 'vertical',
3814
+ xAxis: { ...mockScatterPlot.xAxis, type: 'categorical', dataKey: 'category' },
3815
+ yAxis: { ...mockScatterPlot.yAxis, type: 'continuous', dataKey: 'y1' },
3816
+ series: [{ ...mockScatterPlot.series[0], dataKey: 'y1', type: 'Bar' }],
3817
+ data: [
3818
+ { category: 'Missing', y1: 100 },
3819
+ { category: 'Nonnumeric', y1: 100, portion: 'invalid' },
3820
+ { category: 'Zero', y1: 100, portion: 0 },
3821
+ { category: 'Negative', y1: 100, portion: -10 },
3822
+ { category: 'Greater', y1: 100, portion: 101 },
3823
+ { category: 'Negative total', y1: -100, portion: 10 }
3824
+ ],
3825
+ legend: portionPatternLegend
3826
+ },
3827
+ isEditor: false
3828
+ },
3829
+ play: async ({ canvasElement }) => {
3830
+ await assertVisualizationRendered(canvasElement)
3831
+ expect(canvasElement.querySelectorAll('.pattern-overlay--portion')).toHaveLength(0)
3832
+ expect(canvasElement.querySelectorAll('.pattern-overlay__boundary')).toHaveLength(0)
3833
+ expect(canvasElement.querySelectorAll('path[id^="barGroup"]')).toHaveLength(6)
3834
+ }
3835
+ }
3836
+
3837
+ export const BrushPortionPatternRenderingTests: Story = {
3838
+ name: 'Brush Portion Pattern Rendering Tests',
3839
+ args: {
3840
+ config: {
3841
+ ...mockScatterPlot,
3842
+ visualizationType: 'Bar',
3843
+ visualizationSubType: 'regular',
3844
+ orientation: 'vertical',
3845
+ xAxis: {
3846
+ ...mockScatterPlot.xAxis,
3847
+ type: 'date-time',
3848
+ dataKey: 'date',
3849
+ dateParseFormat: '%m/%d/%Y',
3850
+ dateDisplayFormat: '%m/%d/%Y',
3851
+ brushActive: true
3852
+ },
3853
+ yAxis: { ...mockScatterPlot.yAxis, type: 'continuous', dataKey: 'y1' },
3854
+ series: [{ ...mockScatterPlot.series[0], dataKey: 'y1', type: 'Bar' }],
3855
+ data: [
3856
+ { date: '01/01/2025', y1: 100, portion: 50 },
3857
+ { date: '02/01/2025', y1: 80, portion: 40 },
3858
+ { date: '03/01/2025', y1: 60, portion: 30 }
3859
+ ],
3860
+ legend: portionPatternLegend
3861
+ },
3862
+ isEditor: false
3863
+ },
3864
+ play: async ({ canvasElement }) => {
3865
+ await assertVisualizationRendered(canvasElement)
3866
+
3867
+ await Promise.all([
3868
+ waitForPresence('.pattern-overlay--portion:not(.pattern-overlay--brush)', canvasElement),
3869
+ waitForPresence('.pattern-overlay--brush', canvasElement),
3870
+ waitForPresence('.pattern-overlay__boundary:not(.pattern-overlay__boundary--brush)', canvasElement),
3871
+ waitForPresence('.pattern-overlay__boundary--brush', canvasElement)
3872
+ ])
3873
+
3874
+ const mainOverlay = canvasElement.querySelector(
3875
+ '.pattern-overlay--portion:not(.pattern-overlay--brush)'
3876
+ ) as SVGGraphicsElement
3877
+ const brushOverlay = canvasElement.querySelector('.pattern-overlay--brush') as SVGGraphicsElement
3878
+ const mainBoundary = canvasElement.querySelector(
3879
+ '.pattern-overlay__boundary:not(.pattern-overlay__boundary--brush)'
3880
+ ) as SVGLineElement
3881
+ const brushBoundary = canvasElement.querySelector('.pattern-overlay__boundary--brush') as SVGLineElement
3882
+ expect(mainOverlay).toBeTruthy()
3883
+ expect(brushOverlay).toBeTruthy()
3884
+ expect(mainBoundary).toBeTruthy()
3885
+ expect(brushBoundary).toBeTruthy()
3886
+
3887
+ const mainBase = mainOverlay.parentElement?.querySelector('path[id^="barGroup"]') as SVGGraphicsElement
3888
+ const brushBase = brushOverlay.parentElement?.querySelector('rect:not(.pattern-overlay)') as SVGGraphicsElement
3889
+ expect(mainOverlay.getBBox().height / mainBase.getBBox().height).toBeCloseTo(0.5, 2)
3890
+ expect(brushOverlay.getBBox().height / brushBase.getBBox().height).toBeCloseTo(0.5, 2)
3891
+ expect(mainBoundary.getAttribute('stroke-width')).toBe('0.75')
3892
+ expect(brushBoundary.getAttribute('stroke-width')).toBe('0.75')
3451
3893
  }
3452
3894
  }
3453
3895