@cdc/chart 4.26.7 → 4.26.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (255) hide show
  1. package/CONFIG.md +502 -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 -97406
  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/overlay-legend.json +485 -0
  22. package/examples/private/pie-test.json +469 -0
  23. package/examples/vega-remote.json +22 -0
  24. package/package.json +11 -5
  25. package/src/CdcChartComponent.tsx +405 -208
  26. package/src/_stories/Chart.BarRace.stories.tsx +443 -0
  27. package/src/_stories/Chart.BoxPlotNumericSort.stories.tsx +189 -0
  28. package/src/_stories/Chart.BoxPlotStatsParity.stories.tsx +207 -0
  29. package/src/_stories/Chart.ColorDistribution.stories.tsx +299 -0
  30. package/src/_stories/Chart.DataCutoff.stories.tsx +147 -0
  31. package/src/_stories/Chart.DataTableDownloadFilename.stories.tsx +60 -0
  32. package/src/_stories/Chart.Dendrogram.stories.tsx +373 -0
  33. package/src/_stories/Chart.Forecast.CILegend.stories.tsx +236 -0
  34. package/src/_stories/Chart.HeatMap.stories.tsx +170 -4
  35. package/src/_stories/Chart.Legend.AxisAlign.stories.tsx +119 -0
  36. package/src/_stories/Chart.LineRace.stories.tsx +149 -0
  37. package/src/_stories/Chart.Network.stories.tsx +432 -0
  38. package/src/_stories/Chart.PaletteConversion.Editor.stories.tsx +208 -0
  39. package/src/_stories/Chart.Patterns.smoke.stories.tsx +31 -0
  40. package/src/_stories/Chart.PercentageValue.stories.tsx +311 -0
  41. package/src/_stories/Chart.PieLabels.stories.tsx +4 -2
  42. package/src/_stories/Chart.RankByValueMutation.stories.tsx +206 -0
  43. package/src/_stories/Chart.RequiredFields.stories.tsx +681 -0
  44. package/src/_stories/Chart.Sankey.stories.tsx +417 -0
  45. package/src/_stories/Chart.SeriesColorAssignments.stories.tsx +47 -0
  46. package/src/_stories/Chart.SmallMultiplesNumericTileOrder.stories.tsx +137 -0
  47. package/src/_stories/Chart.TooltipSeriesLabels.stories.tsx +43 -0
  48. package/src/_stories/Chart.VegaRemoteData.stories.tsx +31 -0
  49. package/src/_stories/ChartAnnotation.Editor.stories.tsx +78 -0
  50. package/src/_stories/ChartAxisTitles.smoke.stories.tsx +48 -0
  51. package/src/_stories/ChartBar.Editor.stories.tsx +499 -57
  52. package/src/_stories/ChartBrush.Editor.stories.tsx +46 -67
  53. package/src/_stories/ChartBrush.Regression.stories.tsx +135 -0
  54. package/src/_stories/ChartEditor.Editor.stories.tsx +33 -3
  55. package/src/_stories/ChartLine.EffectCompatibility.stories.tsx +120 -0
  56. package/src/_stories/_mock/annotation_event-line_mock.json +2 -2
  57. package/src/_stories/_mock/editor-test-configs.json +20 -10
  58. package/src/_stories/_mock/editor-test-datasets.json +38 -42
  59. package/src/_stories/_mock/heatmap-average-age-categorical.json +2 -2
  60. package/src/_stories/_mock/heatmap.json +2 -2
  61. package/src/_stories/_mock/legend-axis-align-stacked-bar.json +125 -0
  62. package/src/_stories/_mock/sankey_mock.json +23 -27
  63. package/src/components/Annotations/components/AnnotationDraggable.styles.css +52 -0
  64. package/src/components/Annotations/components/AnnotationDraggable.tsx +639 -139
  65. package/src/components/Annotations/components/AnnotationDropdown.styles.css +44 -4
  66. package/src/components/Annotations/components/AnnotationDropdown.tsx +10 -32
  67. package/src/components/Annotations/helpers/resolveAnnotationLayout.ts +212 -0
  68. package/src/components/Annotations/tests/AnnotationDraggable.test.tsx +612 -19
  69. package/src/components/Annotations/tests/AnnotationDropdown.test.tsx +50 -0
  70. package/src/components/Annotations/tests/resolveAnnotationLayout.test.ts +203 -0
  71. package/src/components/Axis/BottomAxis.tsx +1 -1
  72. package/src/components/Axis/Categorical.Axis.tsx +3 -3
  73. package/src/components/Axis/RightAxis.tsx +21 -13
  74. package/src/components/BarChart/components/BarChart.Horizontal.tsx +38 -6
  75. package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +12 -3
  76. package/src/components/BarChart/components/BarChart.StackedVertical.test.ts +28 -0
  77. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +29 -25
  78. package/src/components/BarChart/components/BarChart.Vertical.tsx +37 -26
  79. package/src/components/BarChart/components/PortionPatternOverlay.tsx +61 -0
  80. package/src/components/BarChart/components/context.tsx +6 -0
  81. package/src/components/BarChart/helpers/getPatternUrl.ts +2 -5
  82. package/src/components/BarChart/helpers/index.ts +8 -0
  83. package/src/components/BarChart/helpers/portionPattern.ts +243 -0
  84. package/src/components/BarChart/helpers/tests/addMinimumBarHeights.test.ts +42 -0
  85. package/src/components/BarChart/helpers/tests/getPatternUrl.test.ts +13 -0
  86. package/src/components/BarChart/helpers/tests/portionPattern.test.ts +280 -0
  87. package/src/components/BarChart/helpers/tests/useBarChart.test.tsx +104 -0
  88. package/src/components/BarChart/helpers/useBarChart.ts +60 -59
  89. package/src/components/BarChartRace/BarChartRace.test.tsx +199 -0
  90. package/src/components/BarChartRace/BarChartRace.tsx +230 -0
  91. package/src/components/BarChartRace/BarChartRaceFallback.test.tsx +61 -0
  92. package/src/components/BarChartRace/BarChartRaceFallback.tsx +48 -0
  93. package/src/components/BarChartRace/bar-chart-race.css +160 -0
  94. package/src/components/BarChartRace/helpers.test.ts +114 -0
  95. package/src/components/BarChartRace/helpers.ts +185 -0
  96. package/src/components/BarChartRace/index.ts +3 -0
  97. package/src/components/BoxPlot/helpers/index.ts +11 -82
  98. package/src/components/Brush/BrushSelector.tsx +9 -23
  99. package/src/components/Brush/MiniChartPreview.tsx +33 -1
  100. package/src/components/Dendrogram/components/Dendrogram.tsx +374 -0
  101. package/src/components/Dendrogram/dendrogram.css +113 -0
  102. package/src/components/Dendrogram/helpers/layoutDendrogram.test.ts +95 -0
  103. package/src/components/Dendrogram/helpers/layoutDendrogram.ts +56 -0
  104. package/src/components/Dendrogram/helpers/prepareDendrogramData.test.ts +135 -0
  105. package/src/components/Dendrogram/helpers/prepareDendrogramData.ts +166 -0
  106. package/src/components/Dendrogram/helpers/resolveDendrogramLabels.test.ts +196 -0
  107. package/src/components/Dendrogram/helpers/resolveDendrogramLabels.ts +418 -0
  108. package/src/components/Dendrogram/index.tsx +1 -0
  109. package/src/components/Dendrogram/types/index.ts +66 -0
  110. package/src/components/EditorPanel/EditorPanel.tsx +1005 -760
  111. package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +50 -5
  112. package/src/components/EditorPanel/components/Panels/Panel.Dendrogram.css +10 -0
  113. package/src/components/EditorPanel/components/Panels/Panel.Dendrogram.tsx +211 -0
  114. package/src/components/EditorPanel/components/Panels/Panel.ForestPlotSettings.tsx +19 -8
  115. package/src/components/EditorPanel/components/Panels/Panel.General.tsx +88 -54
  116. package/src/components/EditorPanel/components/Panels/Panel.Network.css +9 -0
  117. package/src/components/EditorPanel/components/Panels/Panel.Network.tsx +202 -0
  118. package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +233 -33
  119. package/src/components/EditorPanel/components/Panels/Panel.Radar.tsx +6 -1
  120. package/src/components/EditorPanel/components/Panels/Panel.Sankey.tsx +89 -105
  121. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +2 -2
  122. package/src/components/EditorPanel/components/Panels/Panel.SmallMultiples.tsx +1 -1
  123. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +8 -66
  124. package/src/components/EditorPanel/components/Panels/RacingControls.test.tsx +131 -0
  125. package/src/components/EditorPanel/components/Panels/RacingControls.tsx +185 -0
  126. package/src/components/EditorPanel/components/Panels/SeriesColorAssignments.tsx +2 -2
  127. package/src/components/EditorPanel/components/Panels/index.tsx +4 -0
  128. package/src/components/EditorPanel/helpers/getVisualizationTypeConfigUpdate.ts +20 -7
  129. package/src/components/EditorPanel/helpers/tests/getVisualizationTypeConfigUpdate.test.ts +25 -2
  130. package/src/components/EditorPanel/helpers/updateFieldRankByValue.ts +2 -1
  131. package/src/components/EditorPanel/useEditorPermissions.test.tsx +62 -0
  132. package/src/components/EditorPanel/useEditorPermissions.ts +70 -24
  133. package/src/components/Forecasting/Forecasting.tsx +15 -6
  134. package/src/components/HeatMap/components/HeatMap.tsx +511 -178
  135. package/src/components/HeatMap/heatmap.constants.ts +7 -3
  136. package/src/components/HeatMap/heatmap.css +101 -2
  137. package/src/components/HeatMap/helpers/layout.ts +80 -0
  138. package/src/components/HeatMap/tests/HeatMap.test.tsx +339 -16
  139. package/src/components/HeatMap/tests/helpers.test.ts +79 -0
  140. package/src/components/Legend/Legend.Component.tsx +110 -90
  141. package/src/components/Legend/LegendGroup/LegendGroup.styles.css +7 -0
  142. package/src/components/Legend/LegendGroup/LegendGroup.tsx +47 -34
  143. package/src/components/Legend/helpers/createFormatLabels.test.ts +226 -0
  144. package/src/components/Legend/helpers/createFormatLabels.tsx +45 -51
  145. package/src/components/LineChart/components/LineChart.Circle.tsx +3 -3
  146. package/src/components/LineChart/helpers.test.ts +126 -38
  147. package/src/components/LineChart/helpers.ts +9 -4
  148. package/src/components/LineChart/index.tsx +69 -24
  149. package/src/components/LineChart/tests/LineChart.test.tsx +630 -0
  150. package/src/components/LineChartRace/LineChartRace.test.tsx +275 -0
  151. package/src/components/LineChartRace/LineChartRace.tsx +134 -0
  152. package/src/components/LineChartRace/LineChartRaceFallback.tsx +28 -0
  153. package/src/components/LineChartRace/helpers.test.ts +88 -0
  154. package/src/components/LineChartRace/helpers.ts +157 -0
  155. package/src/components/LineChartRace/index.ts +3 -0
  156. package/src/components/LineChartRace/line-chart-race.css +16 -0
  157. package/src/components/LinearChart/ValueAxisAnchors.tsx +58 -0
  158. package/src/components/LinearChart/VisualizationRenderer.tsx +18 -23
  159. package/src/components/LinearChart/tests/LinearChart.test.tsx +412 -13
  160. package/src/components/LinearChart/tests/VisualizationRenderer.test.tsx +141 -0
  161. package/src/components/LinearChart/tests/mockConfigContext.ts +2 -1
  162. package/src/components/LinearChart.tsx +81 -51
  163. package/src/components/Network/components/Network.tsx +372 -0
  164. package/src/components/Network/helpers/layoutNetwork.test.ts +68 -0
  165. package/src/components/Network/helpers/layoutNetwork.ts +87 -0
  166. package/src/components/Network/helpers/prepareNetworkData.test.ts +116 -0
  167. package/src/components/Network/helpers/prepareNetworkData.ts +101 -0
  168. package/src/components/Network/helpers/resolveNetworkLabels.test.ts +112 -0
  169. package/src/components/Network/helpers/resolveNetworkLabels.ts +206 -0
  170. package/src/components/Network/index.tsx +3 -0
  171. package/src/components/Network/network.css +107 -0
  172. package/src/components/Network/types/index.ts +56 -0
  173. package/src/components/PairedBarChart.jsx +5 -10
  174. package/src/components/PairedBarChart.test.tsx +71 -0
  175. package/src/components/PieChart/PieChart.tsx +9 -8
  176. package/src/components/RacingChartRenderer.tsx +44 -0
  177. package/src/components/Regions/components/Regions.test.tsx +179 -0
  178. package/src/components/Regions/components/Regions.tsx +104 -69
  179. package/src/components/Regions/index.tsx +2 -3
  180. package/src/components/Sankey/components/Sankey.tsx +264 -437
  181. package/src/components/Sankey/helpers/getSankeyTooltip.ts +12 -0
  182. package/src/components/Sankey/helpers/prepareSankeyData.test.ts +71 -0
  183. package/src/components/Sankey/helpers/prepareSankeyData.ts +90 -0
  184. package/src/components/Sankey/sankey.scss +57 -116
  185. package/src/components/Sankey/types/index.ts +41 -4
  186. package/src/components/ScatterPlot/ScatterPlot.jsx +28 -4
  187. package/src/components/ScatterPlot/ScatterPlot.test.tsx +60 -0
  188. package/src/components/SmallMultiples/SmallMultiples.tsx +12 -12
  189. package/src/components/SmallMultiples/tests/SmallMultipleTile.test.tsx +7 -5
  190. package/src/components/raceTiming.ts +18 -0
  191. package/src/data/initial-state.js +77 -31
  192. package/src/helpers/applyChartTypeContract.ts +71 -0
  193. package/src/helpers/axisLabelFontSize.ts +2 -2
  194. package/src/helpers/boxPlotStats.ts +60 -0
  195. package/src/helpers/bumpChartTooltip.ts +2 -1
  196. package/src/helpers/calculateHorizontalBarCategoryLabelWidth.test.ts +37 -0
  197. package/src/helpers/calculateHorizontalBarCategoryLabelWidth.ts +3 -2
  198. package/src/helpers/calculateRightYAxisWidth.ts +60 -0
  199. package/src/helpers/colorAssignmentHelpers.ts +2 -2
  200. package/src/helpers/createNewChartConfig.ts +56 -0
  201. package/src/helpers/dataDrivenYAxisCategories.ts +18 -0
  202. package/src/helpers/forecastLegend.ts +88 -0
  203. package/src/helpers/getBoxPlotConfig.ts +18 -37
  204. package/src/helpers/getChartTypeDefaultPalette.ts +29 -0
  205. package/src/helpers/getColorCodeCategoryColorMap.ts +26 -0
  206. package/src/helpers/getColorScale.ts +31 -22
  207. package/src/helpers/getDynamicYAxisCategories.ts +69 -0
  208. package/src/helpers/getMinMax.ts +9 -7
  209. package/src/helpers/getNewRuntime.ts +5 -2
  210. package/src/helpers/getStackedSeriesMax.ts +51 -0
  211. package/src/helpers/getV21ChartDistributionColors.ts +57 -0
  212. package/src/helpers/handleRankByValue.ts +15 -15
  213. package/src/helpers/seriesColumnSettings.ts +10 -0
  214. package/src/helpers/sizeHelpers.ts +4 -3
  215. package/src/helpers/smallMultiplesHelpers.ts +10 -13
  216. package/src/helpers/tests/applyChartTypeContract.test.ts +37 -0
  217. package/src/helpers/tests/boxPlotStats.test.ts +31 -0
  218. package/src/helpers/tests/bumpChartTooltip.test.ts +29 -0
  219. package/src/helpers/tests/calculateRightYAxisWidth.test.ts +105 -0
  220. package/src/helpers/tests/countNumOfTicks.test.ts +11 -0
  221. package/src/helpers/tests/createNewChartConfig.test.ts +182 -0
  222. package/src/helpers/tests/getBoxPlotConfig.test.ts +70 -0
  223. package/src/helpers/tests/getColorCodeCategoryColorMap.test.ts +113 -0
  224. package/src/helpers/tests/getColorScale.test.ts +226 -1
  225. package/src/helpers/tests/getDynamicYAxisCategories.test.ts +61 -0
  226. package/src/helpers/tests/getMinMax.test.ts +41 -0
  227. package/src/helpers/tests/getStackedSeriesMax.test.ts +26 -0
  228. package/src/helpers/tests/handleRankByValue.test.ts +55 -37
  229. package/src/helpers/tests/seriesColumnSettings.test.ts +23 -0
  230. package/src/helpers/tests/smallMultiplesHelpers.test.ts +37 -1
  231. package/src/helpers/tests/tooltipHelpers.test.ts +80 -0
  232. package/src/helpers/tooltipHelpers.ts +16 -0
  233. package/src/hooks/tests/useTooltip.test.tsx +353 -1
  234. package/src/hooks/usePrefersReducedMotion.ts +22 -0
  235. package/src/hooks/useReduceData.ts +4 -36
  236. package/src/hooks/useScales.ts +15 -0
  237. package/src/hooks/useTooltip.tsx +95 -64
  238. package/src/scss/main.scss +74 -2
  239. package/src/store/chart.reducer.ts +4 -3
  240. package/src/test/CdcChart.dataTable.test.tsx +490 -16
  241. package/src/test/CdcChart.dendrogram.test.tsx +277 -0
  242. package/src/test/CdcChart.finalization.test.tsx +143 -0
  243. package/src/test/CdcChart.network.test.tsx +271 -0
  244. package/src/types/ChartConfig.ts +91 -54
  245. package/src/types/ChartContext.ts +5 -0
  246. package/src/types/Horizon.ts +1 -1
  247. package/LICENSE +0 -201
  248. package/dist/cdcchart-BWVIUcAF.es.js +0 -4802
  249. package/src/_stories/Chart.Sankey.smoke.stories.tsx +0 -62
  250. package/src/components/Sankey/components/ColumnList.tsx +0 -19
  251. package/src/components/Sankey/helpers/getSankeyTooltip.tsx +0 -34
  252. package/src/components/Sankey/useSankeyAlert.tsx +0 -60
  253. package/src/data/legacy-defaults.ts +0 -18
  254. package/src/helpers/ensureSpecialChartAxisTypes.ts +0 -11
  255. package/src/helpers/tests/ensureSpecialChartAxisTypes.test.ts +0 -30
@@ -5,7 +5,7 @@ import heatMapAverageAgeCategoricalConfig from './_mock/heatmap-average-age-cate
5
5
  import heatMapSparseAggregationConfig from '../../examples/feature/heatmap/sparse-aggregation.json'
6
6
  import heatMapCellValuesBottomAxisConfig from '../../examples/feature/heatmap/cell-values-bottom-axis.json'
7
7
  import heatMapCalendarConfig from '../../examples/feature/heatmap/calendar-heatmap.json'
8
- import { assertVisualizationRendered, waitForPresence } from '@cdc/core/helpers/testing'
8
+ import { assertVisualizationRendered, performAndAssert, waitForPresence } from '@cdc/core/helpers/testing'
9
9
 
10
10
  const meta: Meta<typeof Chart> = {
11
11
  title: 'Components/Templates/Chart/HeatMap',
@@ -22,6 +22,40 @@ const heatMapTopYAxisLabelConfig = {
22
22
  }
23
23
  }
24
24
 
25
+ const heatMapHorizontalScrollConfig = {
26
+ ...heatMapCalendarConfig,
27
+ title: 'Scrollable Calendar HeatMap',
28
+ heatmap: {
29
+ ...heatMapCalendarConfig.heatmap,
30
+ horizontalScroll: true,
31
+ minColumnWidth: 44
32
+ }
33
+ }
34
+
35
+ const heatMapHorizontalScrollBottomAxisConfig = {
36
+ ...heatMapAverageAgeCategoricalConfig,
37
+ title: 'Scrollable Bottom Axis HeatMap',
38
+ data: heatMapAverageAgeCategoricalConfig.data.map((row, index) =>
39
+ index === 0 ? { ...row, communityType: 'Extremely Long Urban Community Classification' } : row
40
+ ),
41
+ xAxis: {
42
+ ...heatMapAverageAgeCategoricalConfig.xAxis,
43
+ tickRotation: 45
44
+ },
45
+ yAxis: {
46
+ ...heatMapAverageAgeCategoricalConfig.yAxis,
47
+ label: 'Geographic region category',
48
+ titlePlacement: 'top'
49
+ },
50
+ heatmap: {
51
+ ...heatMapAverageAgeCategoricalConfig.heatmap,
52
+ horizontalScroll: true,
53
+ minColumnWidth: 220,
54
+ rowLabelGap: 18,
55
+ xAxisPosition: 'bottom'
56
+ }
57
+ }
58
+
25
59
  const getTooltipHtmlValues = (canvasElement: HTMLElement) =>
26
60
  Array.from(canvasElement.querySelectorAll('[data-tooltip-html]')).map(
27
61
  element => element.getAttribute('data-tooltip-html') || ''
@@ -93,18 +127,17 @@ export const HeatMap_Average_Age_Categorical_Demo: Story = {
93
127
  const cityLabelBox = getSvgTextBox(chartSvg, 'Atlanta')
94
128
  const rowAxisTitle = getSvgText(chartSvg, 'City')
95
129
  const columnLabelBox = getSvgTextBox(chartSvg, 'Urban Core')
130
+ const xAxisLine = chartSvg.querySelector('.cdc-heatmap__x-axis-line')
96
131
  const svgBox = chartSvg.getBoundingClientRect()
97
132
  const blockLeft = Math.min(cityLabelBox?.left || firstCellBox.left, firstCellBox.left)
98
133
  const blockRight = lastCellBox.right
99
134
  const blockCenterDelta = Math.abs((blockLeft + blockRight) / 2 - (svgBox.left + svgBox.width / 2))
100
- const rowLabelGap = cityLabelBox ? firstCellBox.left - cityLabelBox.right : 0
101
135
  const columnLabelGap = columnLabelBox ? firstCellBox.top - columnLabelBox.bottom : 0
102
136
 
103
137
  expect(cells.length).toBe(60)
104
138
  expect(firstCellAriaLabel).toContain('Community Type: Urban Core')
105
139
  expect(firstCellAriaLabel).toContain('City: Atlanta')
106
- expect(rowLabelGap).toBeGreaterThan(16)
107
- expect(rowLabelGap).toBeLessThan(80)
140
+ expect(Number(firstCell.getAttribute('x'))).toBe(Number(xAxisLine?.getAttribute('x1')))
108
141
  expect(columnLabelGap).toBeGreaterThan(0)
109
142
  expect(rowAxisTitle?.getAttribute('transform')).toContain('rotate(-90)')
110
143
  expect(columnLabelBox?.top).toBeGreaterThanOrEqual(svgBox.top - 1)
@@ -184,4 +217,137 @@ export const HeatMap_Calendar_Demo: Story = {
184
217
  }
185
218
  }
186
219
 
220
+ export const HeatMap_Horizontal_Scroll_Demo: Story = {
221
+ args: {
222
+ config: heatMapHorizontalScrollConfig,
223
+ isEditor: false
224
+ },
225
+ play: async ({ canvasElement }) => {
226
+ await assertVisualizationRendered(canvasElement)
227
+ await waitForPresence('.cdc-heatmap__scroll-area', canvasElement)
228
+
229
+ const scrollArea = canvasElement.querySelector('.cdc-heatmap__scroll-area') as HTMLElement
230
+ const fixedAxis = canvasElement.querySelector('.cdc-heatmap__fixed-axis-svg')
231
+ const fixedXAxisTitle = canvasElement.querySelector('.cdc-heatmap__fixed-x-title text') as SVGTextElement
232
+ const firstCell = scrollArea.querySelector('.visx-heatmap-rect') as SVGRectElement
233
+ const leftScrollCue = canvasElement.querySelector('.cdc-heatmap__scroll-cue--left') as HTMLElement
234
+ const rightScrollCue = canvasElement.querySelector('.cdc-heatmap__scroll-cue--right') as HTMLElement
235
+ const scrollInstructionsId = scrollArea.getAttribute('aria-describedby') || ''
236
+
237
+ expect(scrollArea).toHaveAttribute('role', 'region')
238
+ expect(scrollArea).toHaveAttribute('tabindex', '0')
239
+ expect(scrollArea.scrollLeft).toBe(0)
240
+ expect(scrollArea.scrollWidth).toBeGreaterThan(scrollArea.clientWidth)
241
+ expect(fixedAxis).toBeTruthy()
242
+ expect(scrollArea.contains(fixedAxis)).toBe(false)
243
+ expect(leftScrollCue).toHaveAttribute('aria-hidden', 'true')
244
+ expect(rightScrollCue).toHaveAttribute('aria-hidden', 'true')
245
+ expect(leftScrollCue).not.toHaveClass('is-visible')
246
+ expect(rightScrollCue).toHaveClass('is-visible')
247
+ expect(canvasElement.querySelector(`#${scrollInstructionsId}`)).toHaveTextContent(
248
+ 'Scroll horizontally to view additional columns.'
249
+ )
250
+
251
+ await performAndAssert(
252
+ 'HeatMap horizontal scrolling',
253
+ () => ({
254
+ scrollLeft: scrollArea.scrollLeft,
255
+ cellLeft: firstCell.getBoundingClientRect().left,
256
+ fixedAxisLeft: fixedAxis?.getBoundingClientRect().left || 0,
257
+ fixedTitleLeft: fixedXAxisTitle.getBoundingClientRect().left,
258
+ canScrollLeft: leftScrollCue.classList.contains('is-visible'),
259
+ canScrollRight: rightScrollCue.classList.contains('is-visible')
260
+ }),
261
+ () => {
262
+ scrollArea.scrollLeft = Math.min(200, scrollArea.scrollWidth - scrollArea.clientWidth)
263
+ scrollArea.dispatchEvent(new Event('scroll'))
264
+ },
265
+ (before, after) =>
266
+ after.scrollLeft > before.scrollLeft &&
267
+ after.cellLeft < before.cellLeft &&
268
+ Math.abs(after.fixedAxisLeft - before.fixedAxisLeft) < 1 &&
269
+ Math.abs(after.fixedTitleLeft - before.fixedTitleLeft) < 1 &&
270
+ after.canScrollLeft &&
271
+ after.canScrollRight
272
+ )
273
+
274
+ await performAndAssert(
275
+ 'HeatMap horizontal scroll end cue',
276
+ () => ({
277
+ canScrollLeft: leftScrollCue.classList.contains('is-visible'),
278
+ canScrollRight: rightScrollCue.classList.contains('is-visible')
279
+ }),
280
+ () => {
281
+ scrollArea.scrollLeft = scrollArea.scrollWidth - scrollArea.clientWidth
282
+ scrollArea.dispatchEvent(new Event('scroll'))
283
+ },
284
+ (before, after) => before.canScrollRight && after.canScrollLeft && !after.canScrollRight
285
+ )
286
+ }
287
+ }
288
+
289
+ export const HeatMap_Horizontal_Scroll_Bottom_Axis_Demo: Story = {
290
+ args: {
291
+ config: heatMapHorizontalScrollBottomAxisConfig,
292
+ isEditor: false
293
+ },
294
+ play: async ({ canvasElement }) => {
295
+ await assertVisualizationRendered(canvasElement)
296
+ await waitForPresence('.cdc-heatmap__scroll-area', canvasElement)
297
+
298
+ const scrollArea = canvasElement.querySelector('.cdc-heatmap__scroll-area') as HTMLElement
299
+ const firstCell = scrollArea.querySelector('.visx-heatmap-rect') as SVGRectElement
300
+ const firstTickLabel = scrollArea.querySelector('.visx-axis-bottom .visx-axis-tick text') as SVGTextElement
301
+ const xAxisLine = scrollArea.querySelector('.cdc-heatmap__x-axis-line')
302
+ const fixedAxis = canvasElement.querySelector('.cdc-heatmap__fixed-axis-svg') as SVGSVGElement
303
+ const scrollAreaBox = scrollArea.getBoundingClientRect()
304
+ const firstCellBox = firstCell.getBoundingClientRect()
305
+ const firstTickLabelBox = firstTickLabel.getBoundingClientRect()
306
+
307
+ expect(canvasElement.querySelector('.visx-axis-bottom')).toBeTruthy()
308
+ expect(Number(firstCell.getAttribute('x'))).toBeGreaterThan(18)
309
+ expect(firstCellBox.left).toBeLessThan(scrollAreaBox.right)
310
+ expect(firstTickLabelBox.left).toBeGreaterThanOrEqual(scrollAreaBox.left - 1)
311
+ expect(xAxisLine).toHaveAttribute('x1', '0')
312
+ expect(getComputedStyle(fixedAxis).overflow).toBe('visible')
313
+ }
314
+ }
315
+
316
+ export const HeatMap_QA_Spacing_And_Cell_Values: Story = {
317
+ args: {
318
+ config: {
319
+ ...heatMapAverageAgeCategoricalConfig,
320
+ heights: { ...heatMapAverageAgeCategoricalConfig.heights, vertical: 600 },
321
+ xAxis: { ...heatMapAverageAgeCategoricalConfig.xAxis, size: 0, tickRotation: 0, maxTickRotation: 0 },
322
+ heatmap: {
323
+ ...heatMapAverageAgeCategoricalConfig.heatmap,
324
+ rowLabelGap: 18,
325
+ columnLabelGap: 20,
326
+ xAxisPosition: 'bottom',
327
+ showCellValues: true
328
+ }
329
+ },
330
+ isEditor: false
331
+ },
332
+ play: async ({ canvasElement }) => {
333
+ await assertVisualizationRendered(canvasElement)
334
+ await waitForPresence('.cdc-heatmap__cell-value', canvasElement)
335
+ const cell = canvasElement.querySelector('.visx-heatmap-rect') as SVGRectElement
336
+ const svg = cell.ownerSVGElement as SVGSVGElement
337
+ const rowLabel = svg.querySelector('.visx-axis-left text') as SVGTextElement
338
+ const title = getSvgText(svg, 'Community Type') as SVGTextElement
339
+ const values = Array.from(svg.querySelectorAll<SVGTextElement>('.cdc-heatmap__cell-value'))
340
+
341
+ expect(Number(cell.getAttribute('x'))).toBe(0)
342
+ expect(Number(rowLabel.getAttribute('x'))).toBe(-26)
343
+ expect(svg.getBoundingClientRect().bottom - title.getBoundingClientRect().bottom).toBeLessThan(16)
344
+ expect(title.getBoundingClientRect().bottom).toBeLessThan(svg.getBoundingClientRect().bottom)
345
+ expect(values.length).toBeGreaterThan(0)
346
+ for (const value of values) {
347
+ expect(Number(value.getAttribute('font-size'))).toBe(16)
348
+ expect(value.getBoundingClientRect().width).toBeLessThan(cell.getBoundingClientRect().width)
349
+ }
350
+ }
351
+ }
352
+
187
353
  export default meta
@@ -0,0 +1,119 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite'
2
+ import Chart from '../CdcChartComponent'
3
+ import axisAlignStackedBar from './_mock/legend-axis-align-stacked-bar.json'
4
+ import { editConfigKeys } from '@cdc/core/helpers/configHelpers'
5
+ import { assertVisualizationRendered, openAccordion, waitForEditor } from '@cdc/core/helpers/testing'
6
+ import { expect, waitFor, within } from 'storybook/test'
7
+
8
+ const meta: Meta<typeof Chart> = {
9
+ title: 'Components/Templates/Chart/Legend/Axis Align',
10
+ component: Chart,
11
+ parameters: {
12
+ layout: 'fullscreen'
13
+ }
14
+ }
15
+
16
+ export default meta
17
+ type Story = StoryObj<typeof Chart>
18
+
19
+ const isolatedSeries = 'Sub-1 Rate'
20
+
21
+ const getConfig = (axisAlign: boolean) =>
22
+ editConfigKeys(axisAlignStackedBar, [
23
+ { path: ['title'], value: axisAlign ? 'Sub-1 values start at zero' : 'Sub-1 values keep stack offset' },
24
+ { path: ['legend', 'axisAlign'], value: axisAlign },
25
+ { path: ['legend', 'seriesHighlight'], value: [isolatedSeries] }
26
+ ])
27
+
28
+ const getPathYValues = (path: SVGPathElement) => {
29
+ const values = [...(path.getAttribute('d') || '').matchAll(/-?(?:\d+\.?\d*|\.\d+)(?:e[-+]?\d+)?/gi)].map(match =>
30
+ Number(match[0])
31
+ )
32
+
33
+ return values.filter((_value, index) => index % 2 === 1).filter(Number.isFinite)
34
+ }
35
+
36
+ const getVisibleBarBounds = (panel: Element) => {
37
+ const bars = Array.from(panel.querySelectorAll<SVGPathElement>('.stack.vertical path')).filter(
38
+ bar => getComputedStyle(bar).display !== 'none'
39
+ )
40
+ const bounds = bars
41
+ .map(getPathYValues)
42
+ .filter(yValues => yValues.length)
43
+ .map(yValues => ({
44
+ top: Math.min(...yValues),
45
+ bottom: Math.max(...yValues)
46
+ }))
47
+
48
+ return {
49
+ count: bounds.length,
50
+ top: Math.min(...bounds.map(bound => bound.top)),
51
+ bottom: Math.max(...bounds.map(bound => bound.bottom)),
52
+ height: Math.max(...bounds.map(bound => bound.bottom)) - Math.min(...bounds.map(bound => bound.top))
53
+ }
54
+ }
55
+
56
+ const panelStyle = {
57
+ border: '1px solid #d9dfe7',
58
+ borderRadius: 6,
59
+ padding: 16,
60
+ minWidth: 0,
61
+ background: '#fff'
62
+ }
63
+
64
+ export const Stacked_Bar_Isolate_Comparison: Story = {
65
+ render: () => (
66
+ <div
67
+ style={{
68
+ display: 'grid',
69
+ gridTemplateColumns: 'repeat(auto-fit, minmax(280px, 1fr))',
70
+ gap: 16,
71
+ padding: 16,
72
+ background: '#f7f8fa'
73
+ }}
74
+ >
75
+ <section style={panelStyle} data-axis-align='true'>
76
+ <Chart config={getConfig(true)} isEditor={false} />
77
+ </section>
78
+ <section style={panelStyle} data-axis-align='false'>
79
+ <Chart config={getConfig(false)} isEditor={false} />
80
+ </section>
81
+ </div>
82
+ ),
83
+ play: async ({ canvasElement }) => {
84
+ await assertVisualizationRendered(canvasElement)
85
+
86
+ await waitFor(() => {
87
+ const alignedPanel = canvasElement.querySelector('[data-axis-align="true"]')
88
+ const offsetPanel = canvasElement.querySelector('[data-axis-align="false"]')
89
+
90
+ expect(alignedPanel).toBeTruthy()
91
+ expect(offsetPanel).toBeTruthy()
92
+
93
+ const alignedBarBounds = getVisibleBarBounds(alignedPanel!)
94
+ const offsetBarBounds = getVisibleBarBounds(offsetPanel!)
95
+
96
+ expect(alignedBarBounds.count).toBeGreaterThan(0)
97
+ expect(offsetBarBounds.count).toBeGreaterThan(0)
98
+ expect(alignedBarBounds.bottom).toBeGreaterThan(offsetBarBounds.bottom)
99
+ expect(alignedBarBounds.height).toBeGreaterThanOrEqual(2.5)
100
+ })
101
+ }
102
+ }
103
+
104
+ export const Sub_One_Left_Axis_100_Editor: Story = {
105
+ args: {
106
+ config: getConfig(true),
107
+ isEditor: true
108
+ },
109
+ play: async ({ canvasElement }) => {
110
+ const canvas = within(canvasElement)
111
+
112
+ await waitForEditor(canvas)
113
+ await openAccordion(canvas, 'Legend')
114
+ await assertVisualizationRendered(canvasElement)
115
+
116
+ const axisAlignCheckbox = canvas.getByLabelText(/align to axis on isolate/i) as HTMLInputElement
117
+ expect(axisAlignCheckbox).toBeChecked()
118
+ }
119
+ }
@@ -0,0 +1,149 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite'
2
+ import { expect, fireEvent, userEvent, within } from 'storybook/test'
3
+ import { assertVisualizationRendered, openAccordion, performAndAssert, waitForEditor } from '@cdc/core/helpers/testing'
4
+ import Chart from '../CdcChartComponent'
5
+ import lineRaceExample from '../../examples/feature/line/line-chart-race.json'
6
+
7
+ const lineRaceStyledExample = {
8
+ ...lineRaceExample,
9
+ series: lineRaceExample.series.map((series, index) =>
10
+ index === 0 ? { ...series, type: 'dashed-md', weight: 7 } : series
11
+ )
12
+ }
13
+
14
+ const newestFirstLineRaceExample = {
15
+ ...lineRaceExample,
16
+ title: 'Newest-first population trends',
17
+ xAxis: {
18
+ ...lineRaceExample.xAxis,
19
+ type: 'date',
20
+ dataKey: 'Date',
21
+ dateParseFormat: '%Y-%m-%d',
22
+ dateDisplayFormat: '%Y-%m-%d',
23
+ sortByRecentDate: true
24
+ },
25
+ data: lineRaceExample.data.map(({ Year, ...row }) => ({ Date: `${Year}-01-01`, ...row }))
26
+ }
27
+
28
+ const meta: Meta<typeof Chart> = {
29
+ title: 'Components/Templates/Chart/Line Race',
30
+ component: Chart,
31
+ parameters: { layout: 'fullscreen' }
32
+ }
33
+
34
+ export default meta
35
+ type Story = StoryObj<typeof Chart>
36
+
37
+ export const ChangesOverTime: Story = {
38
+ name: 'Changes Over Time',
39
+ args: {
40
+ config: lineRaceStyledExample as any,
41
+ isEditor: false,
42
+ interactionLabel: 'Population trends Line race'
43
+ },
44
+ play: async ({ canvasElement }) => {
45
+ const canvas = within(canvasElement)
46
+ await assertVisualizationRendered(canvasElement)
47
+ expect(canvasElement.querySelector('.line-chart-race__frame')).toHaveTextContent('1950')
48
+ expect(canvasElement.querySelector('.bottom-axis')).toHaveTextContent('1950')
49
+ expect(canvasElement.querySelector('.component--has-accent')).not.toBeInTheDocument()
50
+ expect(canvas.getByRole('button', { name: 'Play' })).toBeInTheDocument()
51
+ expect(canvasElement.querySelector('[data-line-race-series-path]')).toHaveAttribute('stroke-dasharray', '10 5')
52
+ expect(canvasElement.querySelector('[data-line-race-series-path]')).toHaveAttribute('stroke-width', '7')
53
+ expect(canvasElement.querySelector('[data-line-race-series-path]')).not.toHaveAttribute('pathLength')
54
+ expect(canvasElement.querySelector('[data-line-race-reveal-path]')).toHaveAttribute('pathLength', '1')
55
+
56
+ await performAndAssert(
57
+ 'Draw the styled Line path forward without pausing playback',
58
+ () => ({
59
+ frame: canvasElement.querySelector('.line-chart-race__frame')?.textContent?.trim(),
60
+ revealDash: canvasElement.querySelector('[data-line-race-reveal-path]')?.getAttribute('stroke-dasharray'),
61
+ seriesDash: canvasElement.querySelector('[data-line-race-series-path]')?.getAttribute('stroke-dasharray'),
62
+ seriesWeight: canvasElement.querySelector('[data-line-race-series-path]')?.getAttribute('stroke-width'),
63
+ axis: canvasElement.querySelector('.bottom-axis')?.textContent
64
+ }),
65
+ async () => userEvent.click(canvas.getByRole('button', { name: 'Play' })),
66
+ (before, after) =>
67
+ before.frame === '1950' &&
68
+ after.frame === '1951' &&
69
+ after.revealDash !== before.revealDash &&
70
+ after.seriesDash === before.seriesDash &&
71
+ after.seriesDash === '10 5' &&
72
+ after.seriesWeight === '7' &&
73
+ after.axis === before.axis
74
+ )
75
+ }
76
+ }
77
+
78
+ export const NewestFirstDates: Story = {
79
+ name: 'Newest-first dates',
80
+ args: {
81
+ config: newestFirstLineRaceExample as any,
82
+ isEditor: false,
83
+ interactionLabel: 'Newest-first population trends Line race'
84
+ },
85
+ play: async ({ canvasElement }) => {
86
+ const canvas = within(canvasElement)
87
+ await assertVisualizationRendered(canvasElement)
88
+ expect(canvasElement.querySelector('.line-chart-race__frame')).toHaveTextContent('1955-01-01')
89
+
90
+ const getRaceState = () => {
91
+ const path = canvasElement.querySelector('[data-line-race-series-path]') as SVGPathElement | null
92
+ expect(path).not.toBeNull()
93
+ if (!path) throw new Error('Expected the Line race series path to render')
94
+ const length = path.getAttribute('d') ? path.getTotalLength() : 0
95
+ return {
96
+ frame: canvasElement.querySelector('.line-chart-race__frame')?.textContent?.trim(),
97
+ startsLeft: length > 0 && path.getPointAtLength(0).x < path.getPointAtLength(length).x
98
+ }
99
+ }
100
+
101
+ await performAndAssert(
102
+ 'Advance newest-first playback while drawing left to right',
103
+ getRaceState,
104
+ async () => userEvent.click(canvas.getByRole('button', { name: 'Play' })),
105
+ (before, after) =>
106
+ before.frame === '1955-01-01' && after.frame === '1954-01-01' && before.startsLeft && after.startsLeft
107
+ )
108
+ }
109
+ }
110
+
111
+ export const GeneralSectionTests: Story = {
112
+ name: 'Editor: General Section',
113
+ parameters: { test: { timeout: 30000 } },
114
+ args: {
115
+ config: { ...lineRaceExample, visualizationSubType: 'regular' } as any,
116
+ isEditor: true,
117
+ interactionLabel: 'Line race editor story'
118
+ },
119
+ play: async ({ canvasElement }) => {
120
+ const canvas = within(canvasElement)
121
+ await waitForEditor(canvas)
122
+ await openAccordion(canvas, 'General')
123
+ expect(canvasElement.querySelector('.line-chart-race')).not.toBeInTheDocument()
124
+ const subtype = canvas.getByLabelText(/chart subtype/i) as HTMLSelectElement
125
+ expect(Array.from(subtype.options).map(option => option.value)).toEqual(['regular', 'racing'])
126
+
127
+ await performAndAssert(
128
+ 'Enable Line racing mode',
129
+ () => Boolean(canvasElement.querySelector('.line-chart-race')),
130
+ async () => userEvent.selectOptions(subtype, 'racing'),
131
+ (_before, after) => after
132
+ )
133
+ expect(canvasElement.querySelector('.line-chart-race__frame')).toHaveTextContent('1950')
134
+ const timing = canvas.getByRole('slider', { name: /seconds per time step/i }) as HTMLInputElement
135
+ expect(timing.valueAsNumber).toBe(0.5)
136
+ expect(timing).toHaveAttribute('min', '0')
137
+ expect(timing).toHaveAttribute('max', '1.5')
138
+ expect(timing).toHaveAttribute('step', '0.5')
139
+
140
+ await fireEvent.change(timing, { target: { value: '0' } })
141
+ await performAndAssert(
142
+ 'Apply instant playback timing',
143
+ () => canvasElement.querySelector('.line-chart-race__frame')?.textContent?.trim(),
144
+ async () => userEvent.click(canvas.getByRole('button', { name: 'Play' })),
145
+ (before, after) => before === '1950' && after === '1955'
146
+ )
147
+ expect(canvas.getByRole('button', { name: 'Replay' })).toBeInTheDocument()
148
+ }
149
+ }