@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
@@ -1,8 +1,11 @@
1
1
  import React from 'react'
2
- import { act, renderHook } from '@testing-library/react'
2
+ import { act, render, renderHook, screen } from '@testing-library/react'
3
3
  import { beforeEach, describe, expect, it, vi } from 'vitest'
4
4
  import ConfigContext from '../../ConfigContext'
5
5
  import { createMockChartContext, createMockConfig } from '../../components/LinearChart/tests/mockConfigContext'
6
+ import { type TooltipRow } from '../../helpers/tooltipHelpers'
7
+ import { type ChartConfig } from '../../types/ChartConfig'
8
+ import { type ChartContext } from '../../types/ChartContext'
6
9
  import { useTooltip } from '../useTooltip'
7
10
 
8
11
  vi.mock('@visx/event', () => ({
@@ -106,6 +109,36 @@ describe('useTooltip', () => {
106
109
  hideTooltip.mockClear()
107
110
  })
108
111
 
112
+ const renderTooltipListItem = (
113
+ tooltipConfig: ChartConfig,
114
+ tooltipRow: TooltipRow,
115
+ {
116
+ index = 1,
117
+ useMarkerColumn = false,
118
+ contextOverrides = {}
119
+ }: { index?: number; useMarkerColumn?: boolean; contextOverrides?: Partial<ChartContext> } = {}
120
+ ) => {
121
+ const tooltipWrapper = ({ children }: React.PropsWithChildren) => (
122
+ <ConfigContext.Provider value={createMockChartContext(tooltipConfig, { tableData: [row], ...contextOverrides })}>
123
+ {children}
124
+ </ConfigContext.Provider>
125
+ )
126
+ const { result } = renderHook(
127
+ () =>
128
+ useTooltip({
129
+ xScale,
130
+ yScale,
131
+ showTooltip,
132
+ hideTooltip,
133
+ yAxisWidth: 0
134
+ }),
135
+ { wrapper: tooltipWrapper }
136
+ )
137
+ const TooltipListItem = result.current.TooltipListItem
138
+
139
+ return render(<TooltipListItem row={tooltipRow} index={index} useMarkerColumn={useMarkerColumn} />)
140
+ }
141
+
109
142
  it('does not apply the left-axis suffix to an additional tooltip column', () => {
110
143
  const { result } = renderHook(
111
144
  () =>
@@ -129,4 +162,323 @@ describe('useTooltip', () => {
129
162
  ])
130
163
  )
131
164
  })
165
+
166
+ it.each([
167
+ {
168
+ caseName: 'customized Column Label over an authored Series Name',
169
+ visualizationType: 'Bar',
170
+ seriesName: 'Rate Series',
171
+ columnLabel: 'Rate Column',
172
+ expected: 'Rate Column: 22.0%'
173
+ },
174
+ {
175
+ caseName: 'inferred Series Name when the Column Label matches the data key',
176
+ visualizationType: 'Bar',
177
+ seriesName: undefined,
178
+ columnLabel: 'Percentage',
179
+ expected: 'Percentage: 22.0%'
180
+ },
181
+ {
182
+ caseName: 'authored Series Name when the Column Label matches the data key',
183
+ visualizationType: 'Bar',
184
+ seriesName: 'Rate Series',
185
+ columnLabel: 'Percentage',
186
+ expected: 'Rate Series: 22.0%'
187
+ },
188
+ {
189
+ caseName: 'authored Series Name when the Column Label is cleared',
190
+ visualizationType: 'Bar',
191
+ seriesName: 'Rate Series',
192
+ columnLabel: '',
193
+ expected: 'Rate Series: 22.0%'
194
+ },
195
+ {
196
+ caseName: 'customized Column Label for a Forest Plot',
197
+ visualizationType: 'Forest Plot',
198
+ seriesName: 'Rate Series',
199
+ columnLabel: 'Rate Column',
200
+ expected: 'Rate Column: 22.0%'
201
+ }
202
+ ])('uses the $caseName', ({ visualizationType, seriesName, columnLabel, expected }) => {
203
+ const series = {
204
+ dataKey: 'Percentage',
205
+ ...(seriesName ? { name: seriesName } : {}),
206
+ type: 'Bar',
207
+ axis: 'Left',
208
+ tooltip: true
209
+ }
210
+ const tooltipConfig = createMockConfig({
211
+ ...config,
212
+ visualizationType: visualizationType as any,
213
+ series: [series] as any,
214
+ columns: {
215
+ ...config.columns,
216
+ Percentage: { name: 'Percentage', label: columnLabel }
217
+ } as any,
218
+ runtime: {
219
+ ...config.runtime,
220
+ series: [series],
221
+ seriesLabels: { Percentage: seriesName || 'Percentage' }
222
+ } as any
223
+ })
224
+ const tooltipWrapper = ({ children }: React.PropsWithChildren) => (
225
+ <ConfigContext.Provider value={createMockChartContext(tooltipConfig, { tableData: [row] })}>
226
+ {children}
227
+ </ConfigContext.Provider>
228
+ )
229
+ const { result } = renderHook(
230
+ () =>
231
+ useTooltip({
232
+ xScale,
233
+ yScale,
234
+ showTooltip,
235
+ hideTooltip,
236
+ yAxisWidth: 0
237
+ }),
238
+ { wrapper: tooltipWrapper }
239
+ )
240
+ const TooltipListItem = result.current.TooltipListItem
241
+
242
+ render(<TooltipListItem row={{ key: 'Percentage', value: '22.0%', kind: 'series' }} index={1} />)
243
+
244
+ expect(screen.getByText(expected)).toBeTruthy()
245
+ })
246
+
247
+ it.each([
248
+ { caseName: 'standard chart', visualizationType: 'Bar' },
249
+ { caseName: 'Forest Plot', visualizationType: 'Forest Plot' }
250
+ ])('preserves an additional column label that matches a series key for a $caseName', ({ visualizationType }) => {
251
+ const series = {
252
+ dataKey: 'Percentage',
253
+ name: 'Rate Series',
254
+ type: 'Bar',
255
+ axis: 'Left',
256
+ tooltip: true
257
+ }
258
+ const tooltipConfig = createMockConfig({
259
+ ...config,
260
+ visualizationType: visualizationType as any,
261
+ series: [series] as any,
262
+ runtime: {
263
+ ...config.runtime,
264
+ series: [series],
265
+ seriesLabels: { Percentage: 'Rate Series' }
266
+ } as any
267
+ })
268
+ const tooltipWrapper = ({ children }: React.PropsWithChildren) => (
269
+ <ConfigContext.Provider value={createMockChartContext(tooltipConfig, { tableData: [row] })}>
270
+ {children}
271
+ </ConfigContext.Provider>
272
+ )
273
+ const { result } = renderHook(
274
+ () =>
275
+ useTooltip({
276
+ xScale,
277
+ yScale,
278
+ showTooltip,
279
+ hideTooltip,
280
+ yAxisWidth: 0
281
+ }),
282
+ { wrapper: tooltipWrapper }
283
+ )
284
+ const TooltipListItem = result.current.TooltipListItem
285
+
286
+ render(<TooltipListItem row={{ key: 'Percentage', value: '146', kind: 'extra' }} index={2} />)
287
+
288
+ expect(screen.getByText('Percentage: 146')).toBeTruthy()
289
+ expect(screen.queryByText('Rate Series: 146')).toBeNull()
290
+ })
291
+
292
+ it('preserves standard x-axis heading rendering', () => {
293
+ renderTooltipListItem(config, {
294
+ key: 'Cause of death',
295
+ value: 'Hypertensive disorders of pregnancy',
296
+ kind: 'heading'
297
+ })
298
+
299
+ expect(screen.getByText(/Hypertensive disorders of pregnancy/)).toHaveClass('tooltip-heading')
300
+ })
301
+
302
+ it('preserves horizontal Bar heading rendering', () => {
303
+ const horizontalBarConfig = createMockConfig({
304
+ ...config,
305
+ orientation: 'horizontal',
306
+ runtime: {
307
+ ...config.runtime,
308
+ yAxis: { ...config.runtime.yAxis, label: 'Cause' }
309
+ } as any
310
+ })
311
+
312
+ renderTooltipListItem(horizontalBarConfig, {
313
+ key: 'Cause of death',
314
+ value: 'Hypertensive disorders of pregnancy',
315
+ kind: 'heading'
316
+ })
317
+
318
+ expect(screen.getByText(/Cause:.*Hypertensive disorders of pregnancy/)).toHaveClass('tooltip-heading')
319
+ })
320
+
321
+ it('preserves Pie calculated-area heading rendering', () => {
322
+ const pieConfig = createMockConfig({
323
+ ...config,
324
+ visualizationType: 'Pie',
325
+ dataFormat: { ...config.dataFormat, showPiePercent: true } as any
326
+ })
327
+
328
+ renderTooltipListItem(pieConfig, {
329
+ key: 'Percentage',
330
+ value: 'Calculated Area',
331
+ kind: 'heading'
332
+ })
333
+
334
+ expect(screen.getByText(/Calculated Area/)).toHaveClass('tooltip-heading')
335
+ })
336
+
337
+ it('preserves Forest Plot heading rendering', () => {
338
+ const forestPlotConfig = createMockConfig({
339
+ ...config,
340
+ visualizationType: 'Forest Plot'
341
+ })
342
+
343
+ renderTooltipListItem(forestPlotConfig, {
344
+ key: 'Cause of death',
345
+ value: 'Hypertensive disorders of pregnancy',
346
+ kind: 'heading'
347
+ })
348
+
349
+ expect(screen.getByText(/Cause of death:.*Hypertensive disorders of pregnancy/)).toHaveClass('tooltip-heading')
350
+ })
351
+
352
+ it.each([
353
+ { caseName: 'standard', orientation: 'vertical' as const },
354
+ { caseName: 'horizontal Bar', orientation: 'horizontal' as const }
355
+ ])('preserves custom date formatting for a $caseName heading', ({ orientation }) => {
356
+ const dateConfig = createMockConfig({
357
+ ...config,
358
+ orientation,
359
+ xAxis: { ...config.xAxis, type: 'date', dataKey: 'Date' },
360
+ tooltips: { ...config.tooltips, dateDisplayFormat: '%Y' },
361
+ runtime: {
362
+ ...config.runtime,
363
+ xAxis: { ...config.runtime.xAxis, type: 'date', dataKey: 'Date', label: 'Date' },
364
+ yAxis: { ...config.runtime.yAxis, label: 'Date' }
365
+ } as any
366
+ })
367
+
368
+ renderTooltipListItem(
369
+ dateConfig,
370
+ { key: 'Date', value: '2025-01-02', kind: 'heading' },
371
+ {
372
+ contextOverrides: {
373
+ parseDate: value => `parsed-${value}`,
374
+ formatDate: value => `date-${value}`,
375
+ formatTooltipsDate: value => `tooltip-${value}`
376
+ }
377
+ }
378
+ )
379
+
380
+ expect(screen.getByText('Date: tooltip-parsed-2025-01-02')).toHaveClass('tooltip-heading')
381
+ })
382
+
383
+ it('hides a matching suppression row when suppressed symbols are disabled', () => {
384
+ const suppressionConfig = createMockConfig({
385
+ ...config,
386
+ general: { ...config.general, showSuppressedSymbol: false } as any,
387
+ preliminaryData: [
388
+ {
389
+ label: 'Suppressed',
390
+ type: 'suppression',
391
+ displayTooltip: true,
392
+ value: '22.0%',
393
+ column: 'Percentage'
394
+ }
395
+ ] as any
396
+ })
397
+
398
+ const { container } = renderTooltipListItem(suppressionConfig, {
399
+ key: 'Percentage',
400
+ value: '22.0%',
401
+ kind: 'series'
402
+ })
403
+
404
+ expect(container.querySelector('li')).toBeNull()
405
+ })
406
+
407
+ it('preserves suppression replacement text and gray styling when symbols are shown', () => {
408
+ const suppressionConfig = createMockConfig({
409
+ ...config,
410
+ general: { ...config.general, showSuppressedSymbol: true } as any,
411
+ preliminaryData: [
412
+ {
413
+ label: 'Suppressed',
414
+ type: 'suppression',
415
+ displayTooltip: true,
416
+ value: '22.0%',
417
+ column: 'Percentage',
418
+ displayGray: true
419
+ }
420
+ ] as any
421
+ })
422
+
423
+ renderTooltipListItem(suppressionConfig, {
424
+ key: 'Percentage',
425
+ value: '22.0%',
426
+ kind: 'series'
427
+ })
428
+
429
+ expect(screen.getByText('Percentage: Suppressed')).toHaveStyle({ color: '#8b8b8a' })
430
+ })
431
+
432
+ it('leaves an unmatched suppression row unchanged', () => {
433
+ const suppressionConfig = createMockConfig({
434
+ ...config,
435
+ general: { ...config.general, showSuppressedSymbol: false } as any,
436
+ preliminaryData: [
437
+ {
438
+ label: 'Suppressed',
439
+ type: 'suppression',
440
+ displayTooltip: true,
441
+ value: 'Different value',
442
+ column: 'Percentage',
443
+ displayGray: true
444
+ }
445
+ ] as any
446
+ })
447
+
448
+ renderTooltipListItem(suppressionConfig, {
449
+ key: 'Percentage',
450
+ value: '22.0%',
451
+ kind: 'series'
452
+ })
453
+
454
+ expect(screen.getByText('Percentage: 22.0%')).not.toHaveStyle({ color: '#8b8b8a' })
455
+ })
456
+
457
+ it.each([
458
+ { markerColor: '#123456', markerShape: 'square' as const, expectedShape: 'square', expectedSwatches: 1 },
459
+ { markerColor: undefined, markerShape: 'square' as const, expectedShape: 'square', expectedSwatches: 0 },
460
+ { markerColor: '#654321', markerShape: undefined, expectedShape: 'circle', expectedSwatches: 1 }
461
+ ])(
462
+ 'preserves the marker column when markerColor is $markerColor and markerShape is $markerShape',
463
+ ({ markerColor, markerShape, expectedShape, expectedSwatches }) => {
464
+ const { container } = renderTooltipListItem(
465
+ config,
466
+ {
467
+ key: 'Percentage',
468
+ value: '22.0%',
469
+ kind: 'series',
470
+ markerColor,
471
+ markerShape
472
+ },
473
+ { useMarkerColumn: true }
474
+ )
475
+
476
+ expect(container.querySelector('.tooltip-body')).toHaveClass('tooltip-body--marker-layout')
477
+ expect(container.querySelector('.tooltip-marker-slot')).toBeTruthy()
478
+ const swatches = container.querySelectorAll(`.tooltip-marker-swatch--${expectedShape}`)
479
+ expect(swatches).toHaveLength(expectedSwatches)
480
+ if (markerColor) expect(swatches[0]).toHaveStyle({ backgroundColor: markerColor })
481
+ expect(container.querySelector('.tooltip-body-content')).toHaveTextContent('Percentage: 22.0%')
482
+ }
483
+ )
132
484
  })
@@ -0,0 +1,22 @@
1
+ import { useEffect, useState } from 'react'
2
+
3
+ const REDUCED_MOTION_QUERY = '(prefers-reduced-motion: reduce)'
4
+
5
+ const usePrefersReducedMotion = () => {
6
+ const [prefersReducedMotion, setPrefersReducedMotion] = useState(
7
+ () => typeof window !== 'undefined' && Boolean(window.matchMedia?.(REDUCED_MOTION_QUERY).matches)
8
+ )
9
+
10
+ useEffect(() => {
11
+ if (typeof window === 'undefined' || !window.matchMedia) return
12
+ const mediaQuery = window.matchMedia(REDUCED_MOTION_QUERY)
13
+ const updatePreference = () => setPrefersReducedMotion(mediaQuery.matches)
14
+ updatePreference()
15
+ mediaQuery.addEventListener?.('change', updatePreference)
16
+ return () => mediaQuery.removeEventListener?.('change', updatePreference)
17
+ }, [])
18
+
19
+ return prefersReducedMotion
20
+ }
21
+
22
+ export default usePrefersReducedMotion
@@ -1,5 +1,6 @@
1
1
  import isNumber from '@cdc/core/helpers/isNumber'
2
2
  import { useMemo } from 'react'
3
+ import getStackedSeriesMax from '../helpers/getStackedSeriesMax'
3
4
 
4
5
  function useReduceData(config, data) {
5
6
  return useMemo(() => {
@@ -30,13 +31,11 @@ function useReduceData(config, data) {
30
31
  let minValue = Infinity
31
32
  let maxValue = -Infinity
32
33
  let existPositiveValue = false
33
- const stackedTotals = []
34
34
 
35
35
  for (let i = 0; i < data.length; i++) {
36
36
  const row = data[i]
37
37
  let rowMax = -Infinity
38
38
  let rowMin = Infinity
39
- let stackedSum = 0
40
39
 
41
40
  for (const key of config.runtime.seriesKeys) {
42
41
  const seriesKey = seriesKeysMap.get(key)
@@ -49,26 +48,22 @@ function useReduceData(config, data) {
49
48
  if (numValue < rowMin) rowMin = numValue
50
49
 
51
50
  if (numValue >= 0) existPositiveValue = true
52
-
53
- if (!isNaN(numValue)) stackedSum += numValue
54
51
  }
55
52
  }
56
53
 
57
54
  if (rowMax > maxValue) maxValue = rowMax
58
55
  if (rowMin < minValue) minValue = rowMin
59
-
60
- if (!isNaN(stackedSum)) stackedTotals.push(stackedSum)
61
56
  }
62
57
 
63
58
  if (
64
59
  (config.visualizationType === 'Bar' || (config.visualizationType === 'Combo' && isBar)) &&
65
60
  config.visualizationSubType === 'stacked'
66
61
  ) {
67
- maxValue = Math.max(...stackedTotals)
62
+ maxValue = getStackedSeriesMax(data, config.runtime.series)
68
63
  }
69
64
 
70
65
  if (config.visualizationSubType === 'stacked' && config.visualizationType === 'Area Chart') {
71
- maxValue = Math.max(...stackedTotals)
66
+ maxValue = getStackedSeriesMax(data, config.runtime.series)
72
67
  }
73
68
 
74
69
  if (
@@ -89,34 +84,7 @@ function useReduceData(config, data) {
89
84
 
90
85
  if (config.visualizationType === 'Combo' && config.visualizationSubType === 'stacked' && !isBar) {
91
86
  if (config.runtime.barSeriesKeys && config.runtime.lineSeriesKeys) {
92
- let barMax = -Infinity
93
- let lineMax = -Infinity
94
-
95
- for (const row of data) {
96
- let barSum = 0
97
- let rowLineMax = -Infinity
98
-
99
- for (const key of config.runtime.barSeriesKeys) {
100
- const cleanValue = cleanChars(row[key])
101
- if (isNumber(cleanValue)) {
102
- const numValue = Number(cleanValue)
103
- if (!isNaN(numValue)) barSum += numValue
104
- }
105
- }
106
-
107
- for (const key of config.runtime.lineSeriesKeys) {
108
- const cleanValue = cleanChars(row[key])
109
- if (isNumber(cleanValue)) {
110
- const numValue = Number(cleanValue)
111
- if (numValue > rowLineMax) rowLineMax = numValue
112
- }
113
- }
114
-
115
- if (barSum > barMax) barMax = barSum
116
- if (rowLineMax > lineMax) lineMax = rowLineMax
117
- }
118
-
119
- maxValue = Math.max(barMax, lineMax)
87
+ maxValue = Math.max(maxValue, getStackedSeriesMax(data, config.runtime.barSeriesKeys))
120
88
  }
121
89
  }
122
90
 
@@ -18,6 +18,7 @@ import { countNumOfTicks } from '../helpers/countNumOfTicks'
18
18
  import { getFinalTopTickMax } from '../helpers/getCleanTopTickMax'
19
19
  import { getYAxisFinalizationEligibility } from '../helpers/getYAxisFinalizationEligibility'
20
20
  import { getAxisMaxOverride } from '../helpers/getAxisMaxOverride'
21
+ import { getDataDrivenYAxisCategories } from '../helpers/dataDrivenYAxisCategories'
21
22
 
22
23
  const scaleTypes = {
23
24
  TIME: 'time',
@@ -67,6 +68,20 @@ const useScales = (properties: useScaleProps) => {
67
68
  const context = useContext<ChartContext>(ConfigContext)
68
69
  const { convertLineToBarGraph = false } = context
69
70
 
71
+ const dataDrivenYAxisCategories = getDataDrivenYAxisCategories(config, data)
72
+ if (dataDrivenYAxisCategories?.axisMax !== null && dataDrivenYAxisCategories?.axisMax !== undefined) {
73
+ config = {
74
+ ...config,
75
+ runtime: {
76
+ ...config.runtime,
77
+ yAxis: {
78
+ ...config.runtime.yAxis,
79
+ max: String(dataDrivenYAxisCategories.axisMax)
80
+ }
81
+ }
82
+ }
83
+ }
84
+
70
85
  const isHorizontal = config.orientation === 'horizontal'
71
86
  const { visualizationType, xAxis, forestPlot, runtime } = config
72
87
  const isForestPlot = visualizationType === 'Forest Plot'