@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
@@ -1,25 +1,60 @@
1
1
  import React from 'react'
2
- import { render, screen, waitFor } from '@testing-library/react'
2
+ import { act, render, screen } from '@testing-library/react'
3
3
  import { beforeEach, describe, expect, it, vi } from 'vitest'
4
4
  import CdcChart from '../CdcChartComponent'
5
+ import fetchRemoteData from '@cdc/core/helpers/fetchRemoteData'
5
6
 
6
- const dataTableProps = vi.hoisted(() => {
7
+ vi.hoisted(() => {
7
8
  Object.defineProperty((globalThis as any).HTMLCanvasElement.prototype, 'getContext', {
8
9
  configurable: true,
9
10
  value: () => ({
10
11
  measureText: (text = '') => ({ width: String(text).length * 8 })
11
12
  })
12
13
  })
13
-
14
- return [] as any[]
15
14
  })
16
15
 
16
+ const chartRender = vi.hoisted(() => ({ onRender: undefined as undefined | ((config: any) => void) }))
17
+ const dataTableRender = vi.hoisted(() => ({ onRender: undefined as undefined | ((props: any) => void) }))
18
+ const chartResize = vi.hoisted(() => ({ resize: undefined as undefined | ((width: number) => void) }))
19
+
20
+ vi.mock('resize-observer-polyfill', () => ({
21
+ default: class {
22
+ constructor(private callback: ResizeObserverCallback) {}
23
+ observe(target: HTMLElement) {
24
+ if (target.classList.contains('cove-visualization')) {
25
+ chartResize.resize = width =>
26
+ this.callback(
27
+ [{ target, contentRect: { width, height: 360 } } as unknown as ResizeObserverEntry],
28
+ this as unknown as ResizeObserver
29
+ )
30
+ }
31
+ }
32
+ unobserve() {}
33
+ disconnect() {}
34
+ }
35
+ }))
36
+
37
+ const nextRender = (signal: { onRender?: (value: any) => void }, isReady: (value: any) => boolean = () => true) =>
38
+ new Promise<any>(resolve => {
39
+ signal.onRender = value => {
40
+ if (!isReady(value)) return
41
+ signal.onRender = undefined
42
+ resolve(value)
43
+ }
44
+ })
45
+
46
+ vi.mock('@cdc/core/components/ui/Icon', () => ({
47
+ default: ({ display }) => React.createElement('span', { 'data-icon': display })
48
+ }))
49
+
50
+ vi.mock('@cdc/core/helpers/fetchRemoteData', () => ({ default: vi.fn() }))
51
+
17
52
  vi.mock('@cdc/core/components/DataTable', async () => {
18
53
  const React = await vi.importActual<typeof import('react')>('react')
19
54
 
20
55
  return {
21
56
  default: props => {
22
- dataTableProps.push(props)
57
+ dataTableRender.onRender?.(props)
23
58
  return React.createElement('div', { 'data-testid': 'data-table-probe' })
24
59
  }
25
60
  }
@@ -31,15 +66,118 @@ vi.mock('@visx/responsive/lib/components/ParentSize', () => ({
31
66
 
32
67
  vi.mock('../components/LinearChart', async () => {
33
68
  const React = await vi.importActual<typeof import('react')>('react')
69
+ const { default: ConfigContext } = await vi.importActual<typeof import('../ConfigContext')>('../ConfigContext')
34
70
 
35
71
  return {
36
- default: React.forwardRef(() => React.createElement('div', { 'data-testid': 'mock-linear-chart' }))
72
+ default: React.forwardRef(() => {
73
+ const { config } = React.useContext(ConfigContext)
74
+ chartRender.onRender?.(config)
75
+ return React.createElement('div', { 'data-testid': 'mock-linear-chart' })
76
+ })
37
77
  }
38
78
  })
39
79
 
40
- describe('CdcChart data table dataset wiring', () => {
80
+ describe('CdcChart config hydration and data table wiring', () => {
41
81
  beforeEach(() => {
42
- dataTableProps.length = 0
82
+ chartResize.resize = undefined
83
+ chartRender.onRender = undefined
84
+ dataTableRender.onRender = undefined
85
+ vi.mocked(fetchRemoteData).mockReset()
86
+ })
87
+
88
+ it('processes remote Vega data before rendering an imported chart', async () => {
89
+ vi.mocked(fetchRemoteData).mockResolvedValue({
90
+ data: { source: [{ category: 'Remote', value: 42 }] } as any,
91
+ dataMetadata: {}
92
+ })
93
+ const chartWithData = nextRender(chartRender, config => config.data?.length > 0)
94
+
95
+ render(
96
+ <CdcChart
97
+ config={
98
+ {
99
+ type: 'chart',
100
+ visualizationType: 'Bar',
101
+ dataUrl: '/vega-data.json',
102
+ data: [],
103
+ xAxis: { dataKey: 'category' },
104
+ series: [{ dataKey: 'value' }],
105
+ vegaConfig: {
106
+ data: [{ name: 'source', values: [{ category: 'Stale', value: 1 }] }],
107
+ marks: [{ type: 'rect', from: { data: 'source' } }],
108
+ scales: []
109
+ }
110
+ } as any
111
+ }
112
+ interactionLabel='remote-vega-chart-test'
113
+ />
114
+ )
115
+
116
+ expect((await chartWithData).data[0]).toMatchObject({ category: 'Remote', value: 42 })
117
+ })
118
+
119
+ it('loads palette configurations sequentially without mutating shared defaults', async () => {
120
+ const baseConfig = {
121
+ type: 'chart',
122
+ data: [{ category: 'A', value: 1 }],
123
+ xAxis: { dataKey: 'category' },
124
+ series: [{ dataKey: 'value' }]
125
+ }
126
+ const firstChart = nextRender(chartRender, config => config.general?.palette?.name === 'divergent_blue_cyan')
127
+ const first = render(
128
+ <CdcChart
129
+ config={{ ...baseConfig, visualizationType: 'Line', color: 'sequential-orange' } as any}
130
+ interactionLabel='first-palette-load'
131
+ />
132
+ )
133
+
134
+ const firstConfig = await firstChart
135
+ expect(firstConfig.general.palette).toMatchObject({
136
+ name: 'divergent_blue_cyan',
137
+ version: '2.0',
138
+ isReversed: false
139
+ })
140
+ expect(firstConfig.general.palette.backups).toBeUndefined()
141
+ expect(firstConfig.migrations?.paletteFallbackFrozen).toBeUndefined()
142
+ first.unmount()
143
+
144
+ const secondChart = nextRender(chartRender, config => config.general?.palette?.name === 'sequential_bluereverse')
145
+ render(
146
+ <CdcChart config={{ ...baseConfig, visualizationType: 'Bar' } as any} interactionLabel='second-palette-load' />
147
+ )
148
+
149
+ const secondConfig = await secondChart
150
+ expect(secondConfig.general.palette).toMatchObject({
151
+ name: 'sequential_bluereverse',
152
+ version: '2.0',
153
+ isReversed: true
154
+ })
155
+ expect(secondConfig.migrations?.paletteFallbackFrozen).toBeUndefined()
156
+ })
157
+
158
+ it.each([
159
+ ['the historical thickness for a versionless omission', {}, 0.35],
160
+ ['the current thickness for a current-version omission', { version: '4.26.8' }, 0.8],
161
+ ['an explicitly authored thickness', { barThickness: 0.8 }, 0.8]
162
+ ])('uses %s', async (_label, configOverrides, expectedBarThickness) => {
163
+ const chart = nextRender(chartRender, config => config.barThickness !== undefined)
164
+ render(
165
+ <CdcChart
166
+ config={
167
+ {
168
+ type: 'chart',
169
+ visualizationType: 'Bar',
170
+ data: [{ category: 'A', value: 1 }],
171
+ xAxis: { dataKey: 'category' },
172
+ series: [{ dataKey: 'value' }],
173
+ ...configOverrides
174
+ } as any
175
+ }
176
+ interactionLabel='chart-thickness-test'
177
+ />
178
+ )
179
+
180
+ expect((await chart).barThickness).toBe(expectedBarThickness)
43
181
  })
44
182
 
45
183
  it('passes the selected dashboard dataset metadata to DataTable', async () => {
@@ -51,6 +189,7 @@ describe('CdcChart data table dataset wiring', () => {
51
189
  runtimeDataUrl: '/wcms/vizdata/chart-runtime.json'
52
190
  }
53
191
 
192
+ const table = nextRender(dataTableRender, props => props.dataConfig?.runtimeDataUrl !== undefined)
54
193
  render(
55
194
  <CdcChart
56
195
  config={
@@ -77,14 +216,150 @@ describe('CdcChart data table dataset wiring', () => {
77
216
  />
78
217
  )
79
218
 
80
- await waitFor(() => expect(dataTableProps.length).toBeGreaterThan(0))
81
-
82
- expect(dataTableProps.at(-1).dataConfig).toMatchObject({
219
+ expect((await table).dataConfig).toMatchObject({
83
220
  runtimeDataUrl: '/wcms/vizdata/chart-runtime.json'
84
221
  })
85
222
  })
86
223
 
224
+ it('keeps the download area mounted when the data table is hidden', async () => {
225
+ const table = nextRender(dataTableRender)
226
+ render(
227
+ <CdcChart
228
+ config={
229
+ {
230
+ type: 'chart',
231
+ visualizationType: 'Bar',
232
+ title: 'Download-only Chart',
233
+ data: [{ category: 'A', value: 1 }],
234
+ xAxis: { dataKey: 'category' },
235
+ series: [{ dataKey: 'value' }],
236
+ table: {
237
+ show: false,
238
+ expanded: false,
239
+ download: true,
240
+ label: 'Data Table',
241
+ indexLabel: ''
242
+ }
243
+ } as any
244
+ }
245
+ interactionLabel='chart-download-only-test'
246
+ />
247
+ )
248
+
249
+ expect((await table).showTable).toBe(false)
250
+ })
251
+
252
+ it('renders the annotation table between chart subtext and the data-table area', async () => {
253
+ render(
254
+ <CdcChart
255
+ config={
256
+ {
257
+ type: 'chart',
258
+ visualizationType: 'Bar',
259
+ title: 'Annotated Chart',
260
+ description: 'Chart subtext',
261
+ dataKey: 'datasetA',
262
+ data: [{ category: 'A', value: 1 }],
263
+ xAxis: { dataKey: 'category' },
264
+ series: [{ dataKey: 'value' }],
265
+ annotations: [
266
+ {
267
+ text: 'Important annotation',
268
+ anchorMode: 'fixed',
269
+ x: 50,
270
+ y: 50,
271
+ dx: 20,
272
+ dy: -20
273
+ }
274
+ ],
275
+ general: {
276
+ annotationDropdownText: '',
277
+ showAnnotationDropdown: true
278
+ },
279
+ table: {
280
+ show: true,
281
+ expanded: false,
282
+ download: false,
283
+ label: 'Data Table',
284
+ indexLabel: '',
285
+ showDataTableLink: true
286
+ }
287
+ } as any
288
+ }
289
+ isDashboard={true}
290
+ interactionLabel='annotation-table-order-test'
291
+ />
292
+ )
293
+
294
+ const subtext = await screen.findByText('Chart subtext')
295
+ const annotations = await screen.findByRole('button', { name: 'Annotations' })
296
+ const tableLink = await screen.findByRole('link', { name: 'datasetA (Go to Table)' })
297
+ const dataTable = await screen.findByTestId('data-table-probe')
298
+ const bodyWrap = annotations.closest('.cove-visualization__body-wrap')
299
+ const annotationsSection = annotations.closest('.cove-visualization__body-annotations-section')
300
+ const footerSection = dataTable.closest('.cove-visualization__body-footer-section')
301
+
302
+ expect(subtext.compareDocumentPosition(annotations) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy()
303
+ expect(annotations.compareDocumentPosition(tableLink) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy()
304
+ expect(tableLink.compareDocumentPosition(dataTable) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy()
305
+ expect(annotationsSection?.parentElement).toBe(bodyWrap)
306
+ expect(footerSection?.parentElement).toBe(bodyWrap)
307
+ expect(annotationsSection?.nextElementSibling).toBe(footerSection)
308
+ })
309
+
310
+ it.each([
311
+ {
312
+ mobileAnnotationDisplay: undefined,
313
+ showAnnotationDropdown: undefined,
314
+ mobileVisible: true,
315
+ desktopVisible: false
316
+ },
317
+ { mobileAnnotationDisplay: 'symbol', showAnnotationDropdown: false, mobileVisible: true, desktopVisible: false },
318
+ { mobileAnnotationDisplay: 'text', showAnnotationDropdown: false, mobileVisible: false, desktopVisible: false },
319
+ { mobileAnnotationDisplay: 'text', showAnnotationDropdown: true, mobileVisible: true, desktopVisible: true },
320
+ { mobileAnnotationDisplay: 'symbol', showAnnotationDropdown: true, mobileVisible: true, desktopVisible: true }
321
+ ])(
322
+ 'mounts the annotation section only when visible across resizes: %j',
323
+ async ({ mobileAnnotationDisplay, showAnnotationDropdown, mobileVisible, desktopVisible }) => {
324
+ const { container } = render(
325
+ <CdcChart
326
+ config={
327
+ {
328
+ type: 'chart',
329
+ visualizationType: 'Bar',
330
+ data: [{ category: 'A', value: 1 }],
331
+ xAxis: { dataKey: 'category' },
332
+ series: [{ dataKey: 'value' }],
333
+ annotations: [{ text: 'Important annotation', anchorMode: 'fixed', x: 50, y: 50, dx: 20, dy: -20 }],
334
+ general: { annotationDropdownText: '', mobileAnnotationDisplay, showAnnotationDropdown },
335
+ table: { show: true, download: false }
336
+ } as any
337
+ }
338
+ interactionLabel='annotation-visibility-test'
339
+ />
340
+ )
341
+
342
+ await screen.findByTestId('data-table-probe')
343
+ for (const [width, visible] of [
344
+ [1300, desktopVisible],
345
+ [400, mobileVisible],
346
+ [1300, desktopVisible]
347
+ ] as const) {
348
+ await act(async () => chartResize.resize!(width))
349
+ const section = container.querySelector('.cove-visualization__body-annotations-section')
350
+ if (visible) {
351
+ expect(section).toBeInTheDocument()
352
+ expect(screen.getByRole('button', { name: 'Annotations' })).toBeVisible()
353
+ } else {
354
+ expect(section).not.toBeInTheDocument()
355
+ expect(screen.queryByRole('button', { name: 'Annotations' })).not.toBeInTheDocument()
356
+ }
357
+ }
358
+ }
359
+ )
360
+
87
361
  it('keeps chart footnotes visible when the data table is collapsed by default', async () => {
362
+ const table = nextRender(dataTableRender, props => props.expandDataTable === false)
88
363
  render(
89
364
  <CdcChart
90
365
  config={
@@ -112,11 +387,304 @@ describe('CdcChart data table dataset wiring', () => {
112
387
  />
113
388
  )
114
389
 
115
- await waitFor(() => {
116
- expect(dataTableProps.at(-1)?.expandDataTable).toBe(false)
117
- })
390
+ expect((await table).expandDataTable).toBe(false)
118
391
 
119
392
  expect(await screen.findByText('Legacy chart footnote')).toBeInTheDocument()
120
393
  expect(await screen.findByText('Structured chart footnote')).toBeInTheDocument()
121
394
  })
395
+
396
+ it('passes custom category order to the chart-owned data table runtime data', async () => {
397
+ const table = nextRender(dataTableRender, props => props.runtimeData?.length === 4)
398
+ render(
399
+ <CdcChart
400
+ config={
401
+ {
402
+ type: 'chart',
403
+ visualizationType: 'Bar',
404
+ title: 'Custom Category Order Chart',
405
+ data: [
406
+ { category: 'A', value: 1 },
407
+ { category: 'D', value: 4 },
408
+ { category: 'C', value: 3 },
409
+ { category: 'L', value: 12 }
410
+ ],
411
+ xAxis: {
412
+ type: 'categorical',
413
+ dataKey: 'category',
414
+ categoryOrderType: 'custom',
415
+ categoryOrder: ['A', 'C', 'D', 'L']
416
+ },
417
+ series: [{ dataKey: 'value' }],
418
+ table: {
419
+ show: true,
420
+ expanded: true,
421
+ download: true,
422
+ label: 'Data Table',
423
+ indexLabel: ''
424
+ }
425
+ } as any
426
+ }
427
+ interactionLabel='chart-category-order-table-test'
428
+ />
429
+ )
430
+
431
+ expect((await table).runtimeData.map(row => row.category)).toEqual(['A', 'C', 'D', 'L'])
432
+ })
433
+
434
+ it('derives Box Plot table rows from prepared data before rendering the DataTable', async () => {
435
+ const formattedData = [
436
+ { Group: 'Group A', Score: '8' },
437
+ { Group: 'Group A', Score: '24' },
438
+ { Group: 'Group A', Score: '32' }
439
+ ]
440
+
441
+ const table = nextRender(dataTableRender, props => props.config?.boxplot?.plots?.length === 1)
442
+ render(
443
+ <CdcChart
444
+ config={
445
+ {
446
+ type: 'chart',
447
+ visualizationType: 'Box Plot',
448
+ title: 'Prepared Box Plot',
449
+ formattedData,
450
+ xAxis: { type: 'categorical', dataKey: 'Group' },
451
+ yAxis: { dataKey: 'Score' },
452
+ series: [{ dataKey: 'Score' }],
453
+ table: {
454
+ show: true,
455
+ expanded: true,
456
+ download: true,
457
+ label: 'Data Table',
458
+ indexLabel: ''
459
+ }
460
+ } as any
461
+ }
462
+ interactionLabel='chart-box-plot-table-test'
463
+ />
464
+ )
465
+
466
+ const tableProps = await table
467
+ expect(tableProps.config.boxplot.plots).toHaveLength(1)
468
+
469
+ expect(tableProps.config.boxplot.categories).toEqual(['Group A'])
470
+ expect(tableProps.config.boxplot.plots[0]).toMatchObject({
471
+ columnCategory: 'Group A',
472
+ columnCount: 3,
473
+ columnMax: 32,
474
+ columnMin: 8
475
+ })
476
+ })
477
+
478
+ it('updates metadata-backed chart title and text when dataMetadata changes and data does not', async () => {
479
+ const data = [{ category: 'A', value: 1 }]
480
+ const config = {
481
+ type: 'chart',
482
+ visualizationType: 'Bar',
483
+ title: 'Chart {{source}}',
484
+ introText: 'Intro {{source}}',
485
+ legend: {
486
+ label: 'Legend <strong>{{source}}</strong>',
487
+ description: 'Legend description <strong>{{source}}</strong>'
488
+ },
489
+ data,
490
+ dataMetadata: {},
491
+ enableMarkupVariables: true,
492
+ markupVariables: [
493
+ {
494
+ sourceType: 'metadata',
495
+ name: 'Source',
496
+ tag: '{{source}}',
497
+ metadataKey: 'source',
498
+ conditions: [],
499
+ addCommas: false
500
+ }
501
+ ],
502
+ xAxis: { dataKey: 'category' },
503
+ series: [{ dataKey: 'value' }],
504
+ table: {
505
+ show: false,
506
+ expanded: false,
507
+ label: 'Data Table',
508
+ indexLabel: ''
509
+ }
510
+ } as any
511
+
512
+ const { container, rerender } = render(<CdcChart config={config} interactionLabel='chart-metadata-test' />)
513
+
514
+ expect(await screen.findByText('Chart')).toBeInTheDocument()
515
+
516
+ rerender(
517
+ <CdcChart
518
+ config={{
519
+ ...config,
520
+ dataMetadata: { source: 'June file' }
521
+ }}
522
+ interactionLabel='chart-metadata-test'
523
+ />
524
+ )
525
+
526
+ expect(await screen.findByText('Chart June file')).toBeInTheDocument()
527
+ expect(screen.getByText('Intro June file')).toBeInTheDocument()
528
+ expect(container.textContent).toContain('Legend June file')
529
+ expect(container.textContent).toContain('Legend description June file')
530
+ expect(screen.getAllByText('June file', { selector: 'strong' }).length).toBeGreaterThanOrEqual(2)
531
+ })
532
+
533
+ it('parses inline markup in top y-axis titles', async () => {
534
+ const { container } = render(
535
+ <CdcChart
536
+ config={
537
+ {
538
+ type: 'chart',
539
+ visualizationType: 'Bar',
540
+ data: [{ category: 'A', value: 1 }],
541
+ xAxis: { dataKey: 'category' },
542
+ yAxis: {
543
+ titlePlacement: 'top',
544
+ label:
545
+ '<svg width="12" height="12" viewBox="0 0 12 12" aria-hidden="true"><rect width="12" height="12" fill="#56b4e9"></rect></svg> Number of measles cases'
546
+ },
547
+ series: [{ dataKey: 'value' }],
548
+ table: {
549
+ show: false,
550
+ expanded: false,
551
+ label: 'Data Table',
552
+ indexLabel: ''
553
+ }
554
+ } as any
555
+ }
556
+ interactionLabel='chart-top-y-axis-title-test'
557
+ />
558
+ )
559
+
560
+ const topTitle = await screen.findByText('Number of measles cases')
561
+
562
+ expect(topTitle.closest('.y-axis-top-title')).toBeTruthy()
563
+ expect(container.querySelector('.y-axis-top-title svg rect')?.getAttribute('fill')).toBe('#56b4e9')
564
+ expect(container.querySelector('.y-axis-top-title')?.textContent).not.toContain('<svg')
565
+ })
566
+
567
+ it('renders a right-only top y-axis title above the chart and parses inline markup', async () => {
568
+ const { container } = render(
569
+ <CdcChart
570
+ config={
571
+ {
572
+ type: 'chart',
573
+ visualizationType: 'Combo',
574
+ data: [{ category: 'A', left: 1, right: 2 }],
575
+ xAxis: { dataKey: 'category' },
576
+ yAxis: {
577
+ titlePlacement: 'side',
578
+ label: 'Left title',
579
+ rightTitlePlacement: 'top',
580
+ rightLabel:
581
+ '<svg width="12" height="12" viewBox="0 0 12 12" aria-hidden="true"><rect width="12" height="12" fill="#d55e00"></rect></svg> Right rate'
582
+ },
583
+ series: [
584
+ { dataKey: 'left', axis: 'Left', type: 'Bar' },
585
+ { dataKey: 'right', axis: 'Right', type: 'Line' }
586
+ ],
587
+ table: {
588
+ show: false,
589
+ expanded: false,
590
+ label: 'Data Table',
591
+ indexLabel: ''
592
+ }
593
+ } as any
594
+ }
595
+ interactionLabel='chart-top-right-y-axis-title-test'
596
+ />
597
+ )
598
+
599
+ const topTitle = await screen.findByText('Right rate')
600
+
601
+ expect(topTitle.closest('.y-axis-top-title--right')).toBeTruthy()
602
+ expect(container.querySelector('.y-axis-top-title--right svg rect')?.getAttribute('fill')).toBe('#d55e00')
603
+ expect(container.querySelector('.y-axis-top-title-row--split')).toBeFalsy()
604
+ })
605
+
606
+ it('processes markup variables in right top y-axis titles', async () => {
607
+ render(
608
+ <CdcChart
609
+ config={
610
+ {
611
+ type: 'chart',
612
+ visualizationType: 'Combo',
613
+ data: [{ category: 'A', left: 1, right: 2 }],
614
+ dataMetadata: { source: 'June file' },
615
+ enableMarkupVariables: true,
616
+ markupVariables: [
617
+ {
618
+ sourceType: 'metadata',
619
+ name: 'Source',
620
+ tag: '{{source}}',
621
+ metadataKey: 'source',
622
+ conditions: [],
623
+ addCommas: false
624
+ }
625
+ ],
626
+ xAxis: { dataKey: 'category' },
627
+ yAxis: {
628
+ titlePlacement: 'side',
629
+ label: 'Left title',
630
+ rightTitlePlacement: 'top',
631
+ rightLabel: 'Right {{source}} rate'
632
+ },
633
+ series: [
634
+ { dataKey: 'left', axis: 'Left', type: 'Bar' },
635
+ { dataKey: 'right', axis: 'Right', type: 'Line' }
636
+ ],
637
+ table: {
638
+ show: false,
639
+ expanded: false,
640
+ label: 'Data Table',
641
+ indexLabel: ''
642
+ }
643
+ } as any
644
+ }
645
+ interactionLabel='chart-top-right-y-axis-markup-variable-test'
646
+ />
647
+ )
648
+
649
+ expect(await screen.findByText('Right June file rate')).toBeInTheDocument()
650
+ })
651
+
652
+ it('renders left and right top y-axis titles in a shared split row', async () => {
653
+ const { container } = render(
654
+ <CdcChart
655
+ config={
656
+ {
657
+ type: 'chart',
658
+ visualizationType: 'Combo',
659
+ data: [{ category: 'A', left: 1, right: 2 }],
660
+ xAxis: { dataKey: 'category' },
661
+ yAxis: {
662
+ titlePlacement: 'top',
663
+ label: 'Left cases',
664
+ rightTitlePlacement: 'top',
665
+ rightLabel: 'Right rate'
666
+ },
667
+ series: [
668
+ { dataKey: 'left', axis: 'Left', type: 'Bar' },
669
+ { dataKey: 'right', axis: 'Right', type: 'Line' }
670
+ ],
671
+ table: {
672
+ show: false,
673
+ expanded: false,
674
+ label: 'Data Table',
675
+ indexLabel: ''
676
+ }
677
+ } as any
678
+ }
679
+ interactionLabel='chart-both-top-y-axis-title-test'
680
+ />
681
+ )
682
+
683
+ await screen.findByText('Left cases')
684
+ await screen.findByText('Right rate')
685
+
686
+ const titleRow = container.querySelector('.y-axis-top-title-row')
687
+ expect(titleRow?.classList.contains('y-axis-top-title-row--split')).toBe(true)
688
+ expect(titleRow?.querySelectorAll('.y-axis-top-title')).toHaveLength(2)
689
+ })
122
690
  })