@cdc/chart 4.26.6 → 4.26.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (276) hide show
  1. package/CONFIG.md +504 -368
  2. package/dist/cdcchart-CYc-Kayd.es.js +22488 -0
  3. package/dist/cdcchart-DCKn8NmI.es.js +20695 -0
  4. package/dist/cdcchart-Dyui9nk_.es.js +45828 -0
  5. package/dist/cdcchart.js +4 -96498
  6. package/examples/__data__/vega-remote-data.json +6 -0
  7. package/examples/dendrogram.json +18 -0
  8. package/examples/feature/bar/bar-chart-race-annual-change.json +68 -0
  9. package/examples/feature/heatmap/average-age-categorical.json +2 -2
  10. package/examples/feature/heatmap/calendar-heatmap.json +2 -2
  11. package/examples/feature/heatmap/cell-values-bottom-axis.json +2 -2
  12. package/examples/feature/heatmap/sparse-aggregation.json +2 -2
  13. package/examples/feature/line/line-chart-race.json +50 -0
  14. package/examples/feature/sankey/sankey-example-data.json +40 -1441
  15. package/examples/feature/sankey/sankey_chart_data.csv +9 -20
  16. package/examples/network.json +31 -0
  17. package/examples/private/box-plot.json +573 -0
  18. package/examples/private/custom-color-issue.json +625 -0
  19. package/examples/private/custom-colors.json +522 -0
  20. package/examples/private/cutoff.json +10750 -0
  21. package/examples/private/flueview.json +22051 -0
  22. package/examples/private/overlay-legend.json +485 -0
  23. package/examples/private/pie-test.json +469 -0
  24. package/examples/vega-remote.json +22 -0
  25. package/package.json +11 -13
  26. package/src/CdcChart.tsx +2 -2
  27. package/src/CdcChartComponent.tsx +444 -217
  28. package/src/_stories/Chart.BarRace.stories.tsx +443 -0
  29. package/src/_stories/Chart.BoxPlotNumericSort.stories.tsx +189 -0
  30. package/src/_stories/Chart.BoxPlotStatsParity.stories.tsx +207 -0
  31. package/src/_stories/Chart.ColorDistribution.stories.tsx +299 -0
  32. package/src/_stories/Chart.DataCutoff.stories.tsx +147 -0
  33. package/src/_stories/Chart.DataTableDownloadFilename.stories.tsx +60 -0
  34. package/src/_stories/Chart.Dendrogram.stories.tsx +373 -0
  35. package/src/_stories/Chart.Forecast.CILegend.stories.tsx +236 -0
  36. package/src/_stories/Chart.HeatMap.stories.tsx +170 -4
  37. package/src/_stories/Chart.Legend.AxisAlign.stories.tsx +119 -0
  38. package/src/_stories/Chart.LineRace.stories.tsx +149 -0
  39. package/src/_stories/Chart.Network.stories.tsx +432 -0
  40. package/src/_stories/Chart.PaletteConversion.Editor.stories.tsx +208 -0
  41. package/src/_stories/Chart.Patterns.smoke.stories.tsx +31 -0
  42. package/src/_stories/Chart.PercentageValue.stories.tsx +311 -0
  43. package/src/_stories/Chart.PieLabels.stories.tsx +49 -0
  44. package/src/_stories/Chart.RankByValueMutation.stories.tsx +206 -0
  45. package/src/_stories/Chart.RequiredFields.stories.tsx +681 -0
  46. package/src/_stories/Chart.Sankey.stories.tsx +417 -0
  47. package/src/_stories/Chart.SeriesColorAssignments.stories.tsx +47 -0
  48. package/src/_stories/Chart.SmallMultiplesNumericTileOrder.stories.tsx +137 -0
  49. package/src/_stories/Chart.TooltipSeriesLabels.stories.tsx +43 -0
  50. package/src/_stories/Chart.VegaRemoteData.stories.tsx +31 -0
  51. package/src/_stories/Chart.smoke.stories.tsx +14 -1
  52. package/src/_stories/ChartAnnotation.Editor.stories.tsx +78 -0
  53. package/src/_stories/ChartAxisTitles.smoke.stories.tsx +48 -0
  54. package/src/_stories/ChartBar.Editor.stories.tsx +499 -57
  55. package/src/_stories/ChartBrush.Editor.stories.tsx +46 -67
  56. package/src/_stories/ChartBrush.Regression.stories.tsx +135 -0
  57. package/src/_stories/ChartEditor.Editor.stories.tsx +33 -3
  58. package/src/_stories/ChartLine.EffectCompatibility.stories.tsx +120 -0
  59. package/src/_stories/ChartPrefixSuffix.smoke.stories.tsx +28 -1
  60. package/src/_stories/_mock/annotation_event-line_mock.json +2 -2
  61. package/src/_stories/_mock/editor-test-configs.json +20 -10
  62. package/src/_stories/_mock/editor-test-datasets.json +38 -42
  63. package/src/_stories/_mock/heatmap-average-age-categorical.json +2 -2
  64. package/src/_stories/_mock/heatmap.json +2 -2
  65. package/src/_stories/_mock/legend-axis-align-stacked-bar.json +125 -0
  66. package/src/_stories/_mock/sankey_mock.json +23 -27
  67. package/src/components/Annotations/components/AnnotationDraggable.styles.css +52 -0
  68. package/src/components/Annotations/components/AnnotationDraggable.tsx +639 -139
  69. package/src/components/Annotations/components/AnnotationDropdown.styles.css +44 -4
  70. package/src/components/Annotations/components/AnnotationDropdown.tsx +10 -32
  71. package/src/components/Annotations/helpers/resolveAnnotationLayout.ts +212 -0
  72. package/src/components/Annotations/tests/AnnotationDraggable.test.tsx +612 -19
  73. package/src/components/Annotations/tests/AnnotationDropdown.test.tsx +50 -0
  74. package/src/components/Annotations/tests/resolveAnnotationLayout.test.ts +203 -0
  75. package/src/components/Axis/BottomAxis.tsx +1 -1
  76. package/src/components/Axis/Categorical.Axis.tsx +3 -3
  77. package/src/components/Axis/RightAxis.tsx +21 -13
  78. package/src/components/BarChart/components/BarChart.Horizontal.tsx +39 -8
  79. package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +13 -4
  80. package/src/components/BarChart/components/BarChart.StackedVertical.test.ts +28 -0
  81. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +30 -26
  82. package/src/components/BarChart/components/BarChart.Vertical.tsx +38 -28
  83. package/src/components/BarChart/components/PortionPatternOverlay.tsx +61 -0
  84. package/src/components/BarChart/components/context.tsx +6 -0
  85. package/src/components/BarChart/helpers/getBarData.ts +28 -28
  86. package/src/components/BarChart/helpers/getBarHeights.ts +2 -2
  87. package/src/components/BarChart/helpers/getPatternUrl.ts +2 -5
  88. package/src/components/BarChart/helpers/index.ts +8 -0
  89. package/src/components/BarChart/helpers/portionPattern.ts +243 -0
  90. package/src/components/BarChart/helpers/tests/addMinimumBarHeights.test.ts +42 -0
  91. package/src/components/BarChart/helpers/tests/getPatternUrl.test.ts +13 -0
  92. package/src/components/BarChart/helpers/tests/portionPattern.test.ts +280 -0
  93. package/src/components/BarChart/helpers/tests/useBarChart.test.tsx +202 -0
  94. package/src/components/BarChart/helpers/useBarChart.ts +60 -60
  95. package/src/components/BarChartRace/BarChartRace.test.tsx +199 -0
  96. package/src/components/BarChartRace/BarChartRace.tsx +230 -0
  97. package/src/components/BarChartRace/BarChartRaceFallback.test.tsx +61 -0
  98. package/src/components/BarChartRace/BarChartRaceFallback.tsx +48 -0
  99. package/src/components/BarChartRace/bar-chart-race.css +160 -0
  100. package/src/components/BarChartRace/helpers.test.ts +114 -0
  101. package/src/components/BarChartRace/helpers.ts +185 -0
  102. package/src/components/BarChartRace/index.ts +3 -0
  103. package/src/components/BoxPlot/BoxPlot.Horizontal.tsx +7 -6
  104. package/src/components/BoxPlot/BoxPlot.Vertical.tsx +9 -7
  105. package/src/components/BoxPlot/helpers/index.ts +13 -83
  106. package/src/components/Brush/BrushSelector.tsx +10 -24
  107. package/src/components/Brush/MiniChartPreview.tsx +33 -1
  108. package/src/components/Dendrogram/components/Dendrogram.tsx +374 -0
  109. package/src/components/Dendrogram/dendrogram.css +113 -0
  110. package/src/components/Dendrogram/helpers/layoutDendrogram.test.ts +95 -0
  111. package/src/components/Dendrogram/helpers/layoutDendrogram.ts +56 -0
  112. package/src/components/Dendrogram/helpers/prepareDendrogramData.test.ts +135 -0
  113. package/src/components/Dendrogram/helpers/prepareDendrogramData.ts +166 -0
  114. package/src/components/Dendrogram/helpers/resolveDendrogramLabels.test.ts +196 -0
  115. package/src/components/Dendrogram/helpers/resolveDendrogramLabels.ts +418 -0
  116. package/src/components/Dendrogram/index.tsx +1 -0
  117. package/src/components/Dendrogram/types/index.ts +66 -0
  118. package/src/components/EditorPanel/EditorPanel.tsx +1111 -805
  119. package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +50 -5
  120. package/src/components/EditorPanel/components/Panels/Panel.Dendrogram.css +10 -0
  121. package/src/components/EditorPanel/components/Panels/Panel.Dendrogram.tsx +211 -0
  122. package/src/components/EditorPanel/components/Panels/Panel.ForestPlotSettings.tsx +19 -8
  123. package/src/components/EditorPanel/components/Panels/Panel.General.tsx +104 -61
  124. package/src/components/EditorPanel/components/Panels/Panel.Network.css +9 -0
  125. package/src/components/EditorPanel/components/Panels/Panel.Network.tsx +202 -0
  126. package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +236 -36
  127. package/src/components/EditorPanel/components/Panels/Panel.Radar.tsx +6 -1
  128. package/src/components/EditorPanel/components/Panels/Panel.Sankey.tsx +89 -105
  129. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +21 -6
  130. package/src/components/EditorPanel/components/Panels/Panel.SmallMultiples.tsx +1 -1
  131. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +8 -66
  132. package/src/components/EditorPanel/components/Panels/RacingControls.test.tsx +131 -0
  133. package/src/components/EditorPanel/components/Panels/RacingControls.tsx +185 -0
  134. package/src/components/EditorPanel/components/Panels/SeriesColorAssignments.tsx +2 -2
  135. package/src/components/EditorPanel/components/Panels/index.tsx +4 -0
  136. package/src/components/EditorPanel/helpers/getVisualizationTypeConfigUpdate.ts +20 -7
  137. package/src/components/EditorPanel/helpers/tests/getVisualizationTypeConfigUpdate.test.ts +25 -2
  138. package/src/components/EditorPanel/helpers/updateFieldRankByValue.ts +5 -4
  139. package/src/components/EditorPanel/useEditorPermissions.test.tsx +94 -0
  140. package/src/components/EditorPanel/useEditorPermissions.ts +75 -25
  141. package/src/components/Forecasting/Forecasting.tsx +16 -7
  142. package/src/components/HeatMap/components/HeatMap.tsx +511 -178
  143. package/src/components/HeatMap/heatmap.constants.ts +7 -3
  144. package/src/components/HeatMap/heatmap.css +101 -2
  145. package/src/components/HeatMap/helpers/layout.ts +80 -0
  146. package/src/components/HeatMap/tests/HeatMap.test.tsx +339 -16
  147. package/src/components/HeatMap/tests/helpers.test.ts +79 -0
  148. package/src/components/Legend/Legend.Component.tsx +163 -148
  149. package/src/components/Legend/Legend.Suppression.tsx +3 -3
  150. package/src/components/Legend/LegendGroup/LegendGroup.styles.css +7 -0
  151. package/src/components/Legend/LegendGroup/LegendGroup.tsx +47 -35
  152. package/src/components/Legend/helpers/createFormatLabels.test.ts +226 -0
  153. package/src/components/Legend/helpers/createFormatLabels.tsx +45 -51
  154. package/src/components/LineChart/components/LineChart.Circle.tsx +3 -3
  155. package/src/components/LineChart/helpers.test.ts +126 -38
  156. package/src/components/LineChart/helpers.ts +9 -6
  157. package/src/components/LineChart/index.tsx +71 -27
  158. package/src/components/LineChart/tests/LineChart.test.tsx +630 -0
  159. package/src/components/LineChartRace/LineChartRace.test.tsx +275 -0
  160. package/src/components/LineChartRace/LineChartRace.tsx +134 -0
  161. package/src/components/LineChartRace/LineChartRaceFallback.tsx +28 -0
  162. package/src/components/LineChartRace/helpers.test.ts +88 -0
  163. package/src/components/LineChartRace/helpers.ts +157 -0
  164. package/src/components/LineChartRace/index.ts +3 -0
  165. package/src/components/LineChartRace/line-chart-race.css +16 -0
  166. package/src/components/LinearChart/ValueAxisAnchors.tsx +58 -0
  167. package/src/components/LinearChart/VisualizationRenderer.tsx +18 -23
  168. package/src/components/LinearChart/tests/LinearChart.test.tsx +412 -13
  169. package/src/components/LinearChart/tests/VisualizationRenderer.test.tsx +141 -0
  170. package/src/components/LinearChart/tests/mockConfigContext.ts +2 -1
  171. package/src/components/LinearChart.tsx +85 -54
  172. package/src/components/Network/components/Network.tsx +372 -0
  173. package/src/components/Network/helpers/layoutNetwork.test.ts +68 -0
  174. package/src/components/Network/helpers/layoutNetwork.ts +87 -0
  175. package/src/components/Network/helpers/prepareNetworkData.test.ts +116 -0
  176. package/src/components/Network/helpers/prepareNetworkData.ts +101 -0
  177. package/src/components/Network/helpers/resolveNetworkLabels.test.ts +112 -0
  178. package/src/components/Network/helpers/resolveNetworkLabels.ts +206 -0
  179. package/src/components/Network/index.tsx +3 -0
  180. package/src/components/Network/network.css +107 -0
  181. package/src/components/Network/types/index.ts +56 -0
  182. package/src/components/PairedBarChart.jsx +5 -10
  183. package/src/components/PairedBarChart.test.tsx +71 -0
  184. package/src/components/PieChart/PieChart.tsx +121 -13
  185. package/src/components/PieChart/helpers/labelPlacement.test.ts +88 -0
  186. package/src/components/PieChart/helpers/labelPlacement.ts +95 -0
  187. package/src/components/RacingChartRenderer.tsx +44 -0
  188. package/src/components/Regions/components/Regions.test.tsx +179 -0
  189. package/src/components/Regions/components/Regions.tsx +104 -69
  190. package/src/components/Regions/index.tsx +2 -3
  191. package/src/components/Sankey/components/Sankey.tsx +264 -437
  192. package/src/components/Sankey/helpers/getSankeyTooltip.ts +12 -0
  193. package/src/components/Sankey/helpers/prepareSankeyData.test.ts +71 -0
  194. package/src/components/Sankey/helpers/prepareSankeyData.ts +90 -0
  195. package/src/components/Sankey/sankey.scss +57 -116
  196. package/src/components/Sankey/types/index.ts +41 -4
  197. package/src/components/ScatterPlot/ScatterPlot.jsx +28 -12
  198. package/src/components/ScatterPlot/ScatterPlot.test.tsx +60 -0
  199. package/src/components/SmallMultiples/SmallMultiples.tsx +12 -12
  200. package/src/components/SmallMultiples/tests/SmallMultipleTile.test.tsx +7 -5
  201. package/src/components/raceTiming.ts +18 -0
  202. package/src/data/initial-state.js +79 -31
  203. package/src/helpers/applyChartTypeContract.ts +71 -0
  204. package/src/helpers/axisLabelFontSize.ts +2 -2
  205. package/src/helpers/boxPlotStats.ts +60 -0
  206. package/src/helpers/bumpChartTooltip.ts +2 -1
  207. package/src/helpers/calculateHorizontalBarCategoryLabelWidth.test.ts +37 -0
  208. package/src/helpers/calculateHorizontalBarCategoryLabelWidth.ts +3 -2
  209. package/src/helpers/calculateRightYAxisWidth.ts +60 -0
  210. package/src/helpers/categoryOrder.ts +60 -0
  211. package/src/helpers/colorAssignmentHelpers.ts +2 -2
  212. package/src/helpers/createNewChartConfig.ts +56 -0
  213. package/src/helpers/dataDrivenYAxisCategories.ts +18 -0
  214. package/src/helpers/forecastLegend.ts +88 -0
  215. package/src/helpers/getBoxPlotConfig.ts +26 -38
  216. package/src/helpers/getChartTypeDefaultPalette.ts +29 -0
  217. package/src/helpers/getColorCodeCategoryColorMap.ts +26 -0
  218. package/src/helpers/getColorScale.ts +31 -22
  219. package/src/helpers/getComboChartConfig.ts +0 -1
  220. package/src/helpers/getDynamicYAxisCategories.ts +69 -0
  221. package/src/helpers/getExcludedData.ts +0 -1
  222. package/src/helpers/getHasBoundarySuppression.ts +4 -3
  223. package/src/helpers/getMinMax.ts +11 -8
  224. package/src/helpers/getNewRuntime.ts +4 -3
  225. package/src/helpers/getPiePercent.ts +3 -3
  226. package/src/helpers/getStackedSeriesMax.ts +51 -0
  227. package/src/helpers/getV21ChartDistributionColors.ts +57 -0
  228. package/src/helpers/handleRankByValue.ts +15 -15
  229. package/src/helpers/isConvertLineToBarGraph.ts +2 -2
  230. package/src/helpers/seriesColumnSettings.ts +31 -3
  231. package/src/helpers/sizeHelpers.ts +4 -5
  232. package/src/helpers/smallMultiplesHelpers.ts +10 -13
  233. package/src/helpers/tests/applyChartTypeContract.test.ts +37 -0
  234. package/src/helpers/tests/boxPlotStats.test.ts +31 -0
  235. package/src/helpers/tests/bumpChartTooltip.test.ts +29 -0
  236. package/src/helpers/tests/calculateRightYAxisWidth.test.ts +105 -0
  237. package/src/helpers/tests/categoryOrder.test.ts +57 -0
  238. package/src/helpers/tests/countNumOfTicks.test.ts +11 -0
  239. package/src/helpers/tests/createNewChartConfig.test.ts +182 -0
  240. package/src/helpers/tests/getBoxPlotConfig.test.ts +70 -0
  241. package/src/helpers/tests/getColorCodeCategoryColorMap.test.ts +113 -0
  242. package/src/helpers/tests/getColorScale.test.ts +226 -1
  243. package/src/helpers/tests/getDynamicYAxisCategories.test.ts +61 -0
  244. package/src/helpers/tests/getMinMax.test.ts +41 -0
  245. package/src/helpers/tests/getStackedSeriesMax.test.ts +26 -0
  246. package/src/helpers/tests/handleRankByValue.test.ts +55 -37
  247. package/src/helpers/tests/seriesColumnSettings.test.ts +83 -0
  248. package/src/helpers/tests/smallMultiplesHelpers.test.ts +37 -1
  249. package/src/helpers/tests/tooltipHelpers.test.ts +80 -0
  250. package/src/helpers/tooltipHelpers.ts +16 -0
  251. package/src/hooks/tests/useRightAxis.test.tsx +143 -2
  252. package/src/hooks/tests/useScales.test.tsx +3 -6
  253. package/src/hooks/tests/useTooltip.test.tsx +484 -0
  254. package/src/hooks/usePrefersReducedMotion.ts +22 -0
  255. package/src/hooks/useReduceData.ts +4 -36
  256. package/src/hooks/useRightAxis.ts +19 -26
  257. package/src/hooks/useScales.ts +15 -0
  258. package/src/hooks/useTooltip.tsx +106 -71
  259. package/src/scss/main.scss +74 -2
  260. package/src/store/chart.reducer.ts +4 -4
  261. package/src/test/CdcChart.dataTable.test.tsx +582 -14
  262. package/src/test/CdcChart.dendrogram.test.tsx +277 -0
  263. package/src/test/CdcChart.finalization.test.tsx +143 -0
  264. package/src/test/CdcChart.network.test.tsx +271 -0
  265. package/src/types/ChartConfig.ts +91 -54
  266. package/src/types/ChartContext.ts +5 -0
  267. package/src/types/Horizon.ts +1 -1
  268. package/LICENSE +0 -201
  269. package/dist/cdcchart-BWVIUcAF.es.js +0 -4802
  270. package/src/_stories/Chart.Sankey.smoke.stories.tsx +0 -62
  271. package/src/components/Sankey/components/ColumnList.tsx +0 -19
  272. package/src/components/Sankey/helpers/getSankeyTooltip.tsx +0 -34
  273. package/src/components/Sankey/useSankeyAlert.tsx +0 -60
  274. package/src/data/legacy-defaults.ts +0 -18
  275. package/src/helpers/ensureSpecialChartAxisTypes.ts +0 -11
  276. package/src/helpers/tests/ensureSpecialChartAxisTypes.test.ts +0 -30
@@ -0,0 +1,179 @@
1
+ import React from 'react'
2
+ import { render } from '@testing-library/react'
3
+ import { scaleBand, scaleTime } from '@visx/scale'
4
+ import { describe, expect, it } from 'vitest'
5
+ import ConfigContext from '../../../ConfigContext'
6
+ import { createMockChartContext } from '../../LinearChart/tests/mockConfigContext'
7
+ import { RegionFills, useRegionLayouts, type RegionBoundaryMode } from './Regions'
8
+
9
+ const regions = [
10
+ {
11
+ from: 'A',
12
+ to: 'B',
13
+ label: 'Region',
14
+ background: '#005ea8',
15
+ color: '#1b1b1b'
16
+ }
17
+ ]
18
+
19
+ const RegionHarness = ({ xScale, seriesScale, boundaryMode, xMax = 300 }) => {
20
+ const regionLayout = useRegionLayouts({ xScale, seriesScale, boundaryMode, xMax })
21
+ return (
22
+ <svg>
23
+ <RegionFills layouts={regionLayout.layouts} yMax={200} />
24
+ </svg>
25
+ )
26
+ }
27
+
28
+ const renderRegion = ({
29
+ boundaryMode,
30
+ xScale,
31
+ seriesScale,
32
+ xMax = 300,
33
+ configOverrides = {}
34
+ }: {
35
+ boundaryMode: RegionBoundaryMode
36
+ xScale: any
37
+ seriesScale?: any
38
+ xMax?: number
39
+ configOverrides?: any
40
+ }) => {
41
+ const context = createMockChartContext({
42
+ regions,
43
+ orientation: 'vertical',
44
+ xAxis: {
45
+ ...createMockChartContext().config.xAxis,
46
+ type: 'categorical'
47
+ },
48
+ ...configOverrides
49
+ })
50
+ const result = render(
51
+ <ConfigContext.Provider value={context}>
52
+ <RegionHarness xScale={xScale} seriesScale={seriesScale} boundaryMode={boundaryMode} xMax={xMax} />
53
+ </ConfigContext.Provider>
54
+ )
55
+ return result.container.querySelector('.region-fill')
56
+ }
57
+
58
+ describe('scale-owned region layouts', () => {
59
+ it('uses the complete outer category allocation for band-boundary regions', () => {
60
+ const xScale = scaleBand({ domain: ['A', 'B', 'C'], range: [0, 300], padding: 0 })
61
+ const fill = renderRegion({
62
+ boundaryMode: 'band',
63
+ xScale,
64
+ seriesScale: scaleBand({ domain: ['one', 'two'], range: [0, 100] })
65
+ })
66
+
67
+ expect(Number(fill?.getAttribute('x'))).toBe(0)
68
+ expect(Number(fill?.getAttribute('width'))).toBe(200)
69
+ })
70
+
71
+ it('uses category centers for point-mark regions', () => {
72
+ const xScale = scaleBand({ domain: ['A', 'B', 'C'], range: [0, 300], padding: 0 })
73
+ const fill = renderRegion({ boundaryMode: 'point', xScale })
74
+
75
+ expect(Number(fill?.getAttribute('x'))).toBe(50)
76
+ expect(Number(fill?.getAttribute('width'))).toBe(100)
77
+ })
78
+
79
+ it('uses the centered series-scale range for date-time bar groups', () => {
80
+ const first = new Date('2024-01-01').getTime()
81
+ const second = new Date('2024-01-08').getTime()
82
+ const xScale = scaleTime({ domain: [first, second], range: [50, 250] })
83
+ const seriesScale = scaleBand({ domain: ['one', 'two'], range: [0, 40], padding: 0 })
84
+ const pointFill = renderRegion({
85
+ boundaryMode: 'point',
86
+ xScale,
87
+ configOverrides: {
88
+ regions: [{ ...regions[0], from: '2024-01-01', to: '2024-01-08' }],
89
+ xAxis: {
90
+ ...createMockChartContext().config.xAxis,
91
+ type: 'date-time',
92
+ dateParseFormat: '%Y-%m-%d'
93
+ }
94
+ }
95
+ })
96
+ const bandFill = renderRegion({
97
+ boundaryMode: 'band',
98
+ xScale,
99
+ seriesScale,
100
+ configOverrides: {
101
+ regions: [{ ...regions[0], from: '2024-01-01', to: '2024-01-08' }],
102
+ xAxis: {
103
+ ...createMockChartContext().config.xAxis,
104
+ type: 'date-time',
105
+ dateParseFormat: '%Y-%m-%d'
106
+ }
107
+ }
108
+ })
109
+
110
+ expect(Number(bandFill?.getAttribute('x'))).toBe(Number(pointFill?.getAttribute('x')) - 20)
111
+ expect(Number(bandFill?.getAttribute('width'))).toBe(Number(pointFill?.getAttribute('width')) + 40)
112
+ })
113
+
114
+ it('ends date-time Last Date bar regions at the final bar group edge', () => {
115
+ const first = new Date('2024-01-01').getTime()
116
+ const second = new Date('2024-01-08').getTime()
117
+ const xScale = scaleTime({ domain: [first, second], range: [50, 250] })
118
+ const seriesScale = scaleBand({ domain: ['one', 'two'], range: [0, 40], padding: 0 })
119
+ const fill = renderRegion({
120
+ boundaryMode: 'band',
121
+ xScale,
122
+ seriesScale,
123
+ configOverrides: {
124
+ regions: [
125
+ {
126
+ ...regions[0],
127
+ from: '2024-01-01T00:00:00',
128
+ to: '',
129
+ toType: 'Last Date'
130
+ }
131
+ ],
132
+ xAxis: {
133
+ ...createMockChartContext().config.xAxis,
134
+ type: 'date-time',
135
+ dateParseFormat: '%Y-%m-%d'
136
+ }
137
+ }
138
+ })
139
+
140
+ const expectedFrom = Number(xScale(first)) - 20
141
+ const expectedTo = Number(xScale(second)) + 20
142
+
143
+ expect(Number(fill?.getAttribute('x'))).toBe(expectedFrom)
144
+ expect(Number(fill?.getAttribute('width'))).toBe(expectedTo - expectedFrom)
145
+ })
146
+
147
+ it('ends Last Date regions at the final date band edge instead of the plot edge', () => {
148
+ const first = new Date('2024-01-01').getTime()
149
+ const second = new Date('2024-01-08').getTime()
150
+ const third = new Date('2024-01-15').getTime()
151
+ const xScale = scaleBand({ domain: [first, second, third], range: [0, 300], padding: 0.2 })
152
+ const fill = renderRegion({
153
+ boundaryMode: 'band',
154
+ xScale,
155
+ configOverrides: {
156
+ regions: [
157
+ {
158
+ ...regions[0],
159
+ from: '2024-01-01',
160
+ to: '',
161
+ toType: 'Last Date'
162
+ }
163
+ ],
164
+ xAxis: {
165
+ ...createMockChartContext().config.xAxis,
166
+ type: 'date',
167
+ dateParseFormat: '%Y-%m-%d'
168
+ }
169
+ }
170
+ })
171
+
172
+ const expectedFrom = Number(xScale(first))
173
+ const expectedTo = Number(xScale(third)) + xScale.bandwidth()
174
+
175
+ expect(Number(fill?.getAttribute('x'))).toBe(expectedFrom)
176
+ expect(Number(fill?.getAttribute('width'))).toBeCloseTo(expectedTo - expectedFrom)
177
+ expect(expectedTo).toBeLessThan(300)
178
+ })
179
+ })
@@ -7,15 +7,6 @@ import { formatDate } from '@cdc/core/helpers/cove/date.js'
7
7
  import { APP_FONT_COLOR } from '@cdc/core/helpers/constants'
8
8
  import { isMobileFontViewport } from '@cdc/core/helpers/viewports'
9
9
 
10
- // Constants for visualization types
11
- const VIZ_TYPES = {
12
- BAR: 'Bar',
13
- LINE: 'Line',
14
- AREA: 'Area Chart',
15
- COMBO: 'Combo',
16
- HORIZON: 'Horizon Chart'
17
- } as const
18
-
19
10
  const DEFAULT_REGION_BACKGROUND = 'var(--cool-gray-50, #71767a)'
20
11
  const TICK_LABEL_FONT_SIZE = 16
21
12
  const TICK_LABEL_FONT_SIZE_SMALL = 13
@@ -25,6 +16,7 @@ const REGION_LABEL_MAX_PLOT_WIDTH_RATIO = 1 / 3
25
16
  const REGION_LABEL_COMFORTABLE_MIN_WIDTH = 120
26
17
  const REGION_LABEL_COMFORTABLE_MIN_WIDTH_SMALL = 96
27
18
  const REGION_LABEL_FONT_FAMILY = 'Nunito, sans-serif'
19
+ const SUPPORTED_REGION_VISUALIZATION_TYPES: readonly string[] = ['Line', 'Area Chart', 'Horizon Chart', 'Bar', 'Combo']
28
20
 
29
21
  type Region = {
30
22
  from: string
@@ -42,14 +34,30 @@ type XScale = {
42
34
  bandwidth?: () => number
43
35
  }
44
36
 
45
- type RegionsProps = {
37
+ type RegionScale = {
38
+ range: () => number[]
39
+ }
40
+
41
+ export type RegionBoundaryMode = 'point' | 'band'
42
+
43
+ type RegionLayoutProps = {
46
44
  xScale: XScale
47
- yMax: number
48
- barWidth?: number
49
- totalBarsInGroup?: number
45
+ seriesScale?: RegionScale
46
+ boundaryMode: RegionBoundaryMode
50
47
  xMax?: number
51
48
  }
52
49
 
50
+ type RegionFillProps = {
51
+ layouts: RegionLayout[]
52
+ yMax: number
53
+ }
54
+
55
+ type RegionLabelsProps = {
56
+ layouts: RegionLayout[]
57
+ regionLabelFontSize: number
58
+ isMobileViewport: boolean
59
+ }
60
+
53
61
  type HighlightedAreaProps = {
54
62
  x: number
55
63
  width: number
@@ -62,6 +70,13 @@ type RegionLabelLayout = {
62
70
  width: number
63
71
  }
64
72
 
73
+ export type RegionLayout = {
74
+ region: Region
75
+ clippedFrom: number
76
+ width: number
77
+ chartEnd: number
78
+ }
79
+
65
80
  type RegionLabelProps = {
66
81
  label: string
67
82
  color: string
@@ -73,7 +88,15 @@ type RegionLabelProps = {
73
88
  }
74
89
 
75
90
  const HighlightedArea: React.FC<HighlightedAreaProps> = ({ x, width, yMax, background }) => (
76
- <rect x={x} y={0} width={width} height={yMax} fill={background || DEFAULT_REGION_BACKGROUND} opacity={0.3} />
91
+ <rect
92
+ className='region-fill'
93
+ x={x}
94
+ y={0}
95
+ width={width}
96
+ height={yMax}
97
+ fill={background || DEFAULT_REGION_BACKGROUND}
98
+ opacity={0.3}
99
+ />
77
100
  )
78
101
 
79
102
  const clamp = (value: number, min: number, max: number): number => Math.min(Math.max(value, min), max)
@@ -147,6 +170,7 @@ const RegionLabel: React.FC<RegionLabelProps> = ({
147
170
 
148
171
  return (
149
172
  <Text
173
+ className='region-label'
150
174
  x={labelLayout.x}
151
175
  y={getRegionLabelY(fontSize)}
152
176
  width={labelLayout.width}
@@ -177,22 +201,20 @@ const findClosestDate = <T,>(targetTime: number, domain: T[], getTime: (d: T) =>
177
201
  return closest
178
202
  }
179
203
 
180
- /** Check if visualization type is line-like (Line, Area Chart, or Horizon Chart) */
181
- const isLineLike = (type: string): boolean =>
182
- type === VIZ_TYPES.LINE || type === VIZ_TYPES.AREA || type === VIZ_TYPES.HORIZON
183
-
184
- /** Check if visualization type is bar-like (Bar or Combo) */
185
- const isBarLike = (type: string): boolean => type === VIZ_TYPES.BAR || type === VIZ_TYPES.COMBO
186
-
187
204
  // TODO: should regions be removed on categorical axis?
188
- const Regions: React.FC<RegionsProps> = ({ xScale, barWidth = 0, totalBarsInGroup = 1, yMax, xMax }) => {
205
+ export const useRegionLayouts = ({ xScale, seriesScale, boundaryMode, xMax }: RegionLayoutProps) => {
189
206
  const { parseDate, config, tableData, vizViewport } = useContext<ChartContext>(ConfigContext)
190
207
 
191
- const { regions, visualizationType, orientation, xAxis } = config
208
+ const { regions, orientation, xAxis } = config
192
209
  const isMobileViewport = isMobileFontViewport(vizViewport || 'lg')
193
210
  const regionLabelFontSize = isMobileViewport ? TICK_LABEL_FONT_SIZE_SMALL : TICK_LABEL_FONT_SIZE
194
211
 
195
- const getBarOffset = (): number => (barWidth * totalBarsInGroup) / 2
212
+ const getBandWidth = (): number => xScale.bandwidth?.() || 0
213
+ const getDateTimeBandOffset = (): number => {
214
+ const range = seriesScale?.range?.() || []
215
+ if (range.length < 2) return 0
216
+ return Math.abs(Number(range[range.length - 1]) - Number(range[0])) / 2
217
+ }
196
218
 
197
219
  // ============================================
198
220
  // HELPER FUNCTIONS FOR PREVIOUS DAYS
@@ -374,7 +396,7 @@ const Regions: React.FC<RegionsProps> = ({ xScale, barWidth = 0, totalBarsInGrou
374
396
  return calculateBarLastDatePosition_Categorical()
375
397
  }
376
398
  let to = xScale(region.to)
377
- return to + barWidth * totalBarsInGroup
399
+ return to + getBandWidth()
378
400
  }
379
401
 
380
402
  const getBarFromValue_Date = (region: Region): number => {
@@ -403,7 +425,7 @@ const Regions: React.FC<RegionsProps> = ({ xScale, barWidth = 0, totalBarsInGrou
403
425
  const closestDate = findClosestDate(parsedDate, domain, d => d)
404
426
  let to = xScale(closestDate)
405
427
 
406
- return to + barWidth * totalBarsInGroup
428
+ return to + getBandWidth()
407
429
  }
408
430
 
409
431
  const getBarFromValue_DateTime = (region: Region): number => {
@@ -415,7 +437,7 @@ const Regions: React.FC<RegionsProps> = ({ xScale, barWidth = 0, totalBarsInGrou
415
437
  const parsedDate = parseDate(formatDate(config.xAxis.dateParseFormat, date, config.locale)).getTime()
416
438
  from = xScale(parsedDate)
417
439
  }
418
- return from - getBarOffset()
440
+ return from - getDateTimeBandOffset()
419
441
  }
420
442
 
421
443
  const getBarToValue_DateTime = (region: Region): number => {
@@ -423,14 +445,10 @@ const Regions: React.FC<RegionsProps> = ({ xScale, barWidth = 0, totalBarsInGrou
423
445
  return calculateBarLastDatePosition_DateTime()
424
446
  }
425
447
  let to = xScale(parseDate(region.to).getTime())
426
- return to - getBarOffset()
448
+ return to + getDateTimeBandOffset()
427
449
  }
428
450
 
429
451
  const calculateBarLastDatePosition_Categorical = (): number => {
430
- const domain = xScale.domain()
431
- const lastDate = domain[domain.length - 1]
432
- const lastDatePosition = xScale(lastDate)
433
- const bandwidth = xScale.bandwidth ? xScale.bandwidth() : 0
434
452
  // For categorical bars, extend to the end of the last bar
435
453
  // Don't add chartStart - xScale already returns positions in the chart coordinate space
436
454
  return xMax
@@ -440,18 +458,14 @@ const Regions: React.FC<RegionsProps> = ({ xScale, barWidth = 0, totalBarsInGrou
440
458
  const domain = xScale.domain()
441
459
  const lastDate = domain[domain.length - 1]
442
460
  const lastDatePosition = xScale(lastDate)
443
- const offset = barWidth * totalBarsInGroup
444
- // Don't add chartStart - xScale already returns positions in chart coordinate space
445
- return Number(lastDatePosition + offset)
461
+ return Number(lastDatePosition) + getBandWidth()
446
462
  }
447
463
 
448
464
  const calculateBarLastDatePosition_DateTime = (): number => {
449
465
  const domain = xScale.domain()
450
466
  const lastDate = domain[domain.length - 1]
451
467
  const lastDatePosition = xScale(lastDate)
452
- // For date-time bars, don't add chartStart - xScale returns positions in chart coordinate space
453
- // Also don't subtract barOffset since we want to extend to the edge
454
- return Number(lastDatePosition)
468
+ return Number(lastDatePosition) + getDateTimeBandOffset()
455
469
  }
456
470
 
457
471
  const isRegionInVisibleDomain = (region: Region): boolean => {
@@ -484,11 +498,7 @@ const Regions: React.FC<RegionsProps> = ({ xScale, barWidth = 0, totalBarsInGrou
484
498
  // ============================================
485
499
 
486
500
  const getFromValue = (region: Region): number => {
487
- const isLine = isLineLike(visualizationType)
488
- const isBar = isBarLike(visualizationType)
489
-
490
- // LINE/AREA CHARTS
491
- if (isLine) {
501
+ if (boundaryMode === 'point') {
492
502
  if (xAxis.type === 'categorical') {
493
503
  return getLineFromValue_Categorical(region)
494
504
  } else if (xAxis.type === 'date') {
@@ -498,8 +508,7 @@ const Regions: React.FC<RegionsProps> = ({ xScale, barWidth = 0, totalBarsInGrou
498
508
  }
499
509
  }
500
510
 
501
- // BAR CHARTS
502
- if (isBar) {
511
+ if (boundaryMode === 'band') {
503
512
  if (xAxis.type === 'categorical') {
504
513
  return getBarFromValue_Categorical(region)
505
514
  } else if (xAxis.type === 'date') {
@@ -513,11 +522,7 @@ const Regions: React.FC<RegionsProps> = ({ xScale, barWidth = 0, totalBarsInGrou
513
522
  }
514
523
 
515
524
  const getToValue = (region: Region): number => {
516
- const isLine = isLineLike(visualizationType)
517
- const isBar = isBarLike(visualizationType)
518
-
519
- // LINE/AREA CHARTS
520
- if (isLine) {
525
+ if (boundaryMode === 'point') {
521
526
  if (xAxis.type === 'categorical') {
522
527
  return getLineToValue_Categorical(region)
523
528
  } else if (xAxis.type === 'date') {
@@ -527,8 +532,7 @@ const Regions: React.FC<RegionsProps> = ({ xScale, barWidth = 0, totalBarsInGrou
527
532
  }
528
533
  }
529
534
 
530
- // BAR CHARTS
531
- if (isBar) {
535
+ if (boundaryMode === 'band') {
532
536
  if (xAxis.type === 'categorical') {
533
537
  return getBarToValue_Categorical(region)
534
538
  } else if (xAxis.type === 'date') {
@@ -543,12 +547,18 @@ const Regions: React.FC<RegionsProps> = ({ xScale, barWidth = 0, totalBarsInGrou
543
547
 
544
548
  const getWidth = (to: number, from: number): number => Math.max(0, to - from)
545
549
 
546
- if (!regions || orientation !== 'vertical') return null
550
+ if (
551
+ !SUPPORTED_REGION_VISUALIZATION_TYPES.includes(config.visualizationType) ||
552
+ !regions ||
553
+ orientation !== 'vertical'
554
+ ) {
555
+ return { layouts: [], regionLabelFontSize, isMobileViewport }
556
+ }
547
557
 
548
558
  const chartStart = 0
549
559
  const chartEnd = xMax !== undefined ? xMax : 1000
550
560
 
551
- return regions.map((region: Region) => {
561
+ const layouts = regions.map((region: Region): RegionLayout | null => {
552
562
  if (!isRegionInVisibleDomain(region)) return null
553
563
 
554
564
  const from = getFromValue(region)
@@ -566,21 +576,46 @@ const Regions: React.FC<RegionsProps> = ({ xScale, barWidth = 0, totalBarsInGrou
566
576
 
567
577
  if (width <= 0) return null
568
578
 
569
- return (
570
- <Group height={100} className='regions regions-group--line' key={region.label} pointerEvents='none'>
571
- <HighlightedArea x={clippedFrom} width={width} yMax={yMax} background={region.background} />
572
- <RegionLabel
573
- label={region.label}
574
- color={region.color}
575
- clippedFrom={clippedFrom}
576
- regionWidth={width}
577
- plotWidth={chartEnd}
578
- fontSize={regionLabelFontSize}
579
- isMobileViewport={isMobileViewport}
580
- />
581
- </Group>
582
- )
579
+ return { region, clippedFrom, width, chartEnd }
583
580
  })
581
+
582
+ return {
583
+ layouts: layouts.filter((layout): layout is RegionLayout => layout !== null),
584
+ regionLabelFontSize,
585
+ isMobileViewport
586
+ }
587
+ }
588
+
589
+ export const RegionFills: React.FC<RegionFillProps> = ({ layouts, yMax }) => {
590
+ return layouts.map(({ region, clippedFrom, width }, index) => (
591
+ <Group
592
+ height={100}
593
+ className='regions regions-group--line regions-layer--fill'
594
+ key={`${region.label}-${index}`}
595
+ pointerEvents='none'
596
+ >
597
+ <HighlightedArea x={clippedFrom} width={width} yMax={yMax} background={region.background} />
598
+ </Group>
599
+ ))
584
600
  }
585
601
 
586
- export default Regions
602
+ export const RegionLabels: React.FC<RegionLabelsProps> = ({ layouts, regionLabelFontSize, isMobileViewport }) => {
603
+ return layouts.map(({ region, clippedFrom, width, chartEnd }, index) => (
604
+ <Group
605
+ height={100}
606
+ className='regions regions-group--line regions-layer--label'
607
+ key={`${region.label}-${index}`}
608
+ pointerEvents='none'
609
+ >
610
+ <RegionLabel
611
+ label={region.label}
612
+ color={region.color}
613
+ clippedFrom={clippedFrom}
614
+ regionWidth={width}
615
+ plotWidth={chartEnd}
616
+ fontSize={regionLabelFontSize}
617
+ isMobileViewport={isMobileViewport}
618
+ />
619
+ </Group>
620
+ ))
621
+ }
@@ -1,3 +1,2 @@
1
- import Regions from './components/Regions'
2
-
3
- export default Regions
1
+ export { RegionFills, RegionLabels, useRegionLayouts } from './components/Regions'
2
+ export type { RegionBoundaryMode, RegionLayout } from './components/Regions'