@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
@@ -0,0 +1,71 @@
1
+ import React from 'react'
2
+ import { render } from '@testing-library/react'
3
+ import { describe, expect, it, vi } from 'vitest'
4
+ import ConfigContext from '../ConfigContext'
5
+ import PairedBarChart from './PairedBarChart'
6
+ import { createMockChartContext, createMockConfig } from './LinearChart/tests/mockConfigContext'
7
+
8
+ vi.mock('@cdc/core/helpers/cove/accessibility', () => ({
9
+ getContrastColor: () => '#000'
10
+ }))
11
+
12
+ vi.mock('@cdc/core/helpers/getTextWidth', () => ({
13
+ getTextWidth: () => 10
14
+ }))
15
+
16
+ const renderPairedBar = (casesLabel: string) => {
17
+ const baseConfig = createMockConfig()
18
+ const data = [{ Year: '2024', Cases: 10, Deaths: 2 }]
19
+ const config = createMockConfig({
20
+ visualizationType: 'Paired Bar',
21
+ orientation: 'horizontal',
22
+ barHasBorder: 'false',
23
+ barHeight: 25,
24
+ barSpace: 15,
25
+ xAxis: { ...baseConfig.xAxis, dataKey: 'Year', size: '50', type: 'categorical' },
26
+ yAxis: { ...baseConfig.yAxis, displayNumbersOnBar: false },
27
+ series: [
28
+ { dataKey: 'Cases', name: 'Reported Cases', type: 'Paired Bar', axis: 'Left', tooltip: true },
29
+ { dataKey: 'Deaths', name: 'Reported Deaths', type: 'Paired Bar', axis: 'Left', tooltip: true }
30
+ ] as any,
31
+ columns: {
32
+ Cases: { name: 'Cases', label: casesLabel },
33
+ Deaths: { name: 'Deaths', label: 'Death Rate' }
34
+ } as any,
35
+ runtime: {
36
+ ...baseConfig.runtime,
37
+ seriesLabels: { Cases: 'Reported Cases', Deaths: 'Reported Deaths' }
38
+ } as any
39
+ })
40
+ const context = createMockChartContext(config, {
41
+ transformedData: data,
42
+ colorScale: () => '#005ea8',
43
+ formatNumber: value => String(value)
44
+ })
45
+
46
+ return render(
47
+ <ConfigContext.Provider value={context}>
48
+ <PairedBarChart width={400} height={100} originalWidth={450} yAxisWidth={50} />
49
+ </ConfigContext.Provider>
50
+ )
51
+ }
52
+
53
+ describe('PairedBarChart tooltips', () => {
54
+ it('uses the customized series column label without rendering an undefined heading', () => {
55
+ const { container } = renderPairedBar('Case Rate')
56
+ const tooltip = container.querySelector('.bar.group-1')?.getAttribute('data-tooltip-html') || ''
57
+
58
+ expect(tooltip).toContain('Year: 2024')
59
+ expect(tooltip).toContain('Case Rate: 10')
60
+ expect(tooltip).not.toContain('Reported Cases')
61
+ expect(tooltip).not.toContain('undefined:')
62
+ })
63
+
64
+ it('falls back to the Series Name when the Label matches the data key', () => {
65
+ const { container } = renderPairedBar('Cases')
66
+ const tooltip = container.querySelector('.bar.group-1')?.getAttribute('data-tooltip-html') || ''
67
+
68
+ expect(tooltip).toContain('Reported Cases: 10')
69
+ expect(tooltip).not.toContain('undefined:')
70
+ })
71
+ })
@@ -7,7 +7,7 @@ import { Group } from '@visx/group'
7
7
  import { useTooltip, TooltipWithBounds } from '@visx/tooltip'
8
8
  import { colorPalettesChart as colorPalettes } from '@cdc/core/data/colorPalettes'
9
9
  import { getPaletteColors } from '@cdc/core/helpers/palettes/utils'
10
- import { getColorPaletteVersion } from '@cdc/core/helpers/getColorPaletteVersion'
10
+ import { getColorPaletteMajorVersion } from '@cdc/core/helpers/getColorPaletteMajorVersion'
11
11
  import {
12
12
  v2ColorDistribution,
13
13
  divergentColorDistribution,
@@ -26,6 +26,7 @@ import { getContrastColor } from '@cdc/core/helpers/cove/accessibility'
26
26
  import { type TooltipDisplayData } from '../../helpers/tooltipHelpers'
27
27
  import { getTextWidth } from '@cdc/core/helpers/getTextWidth'
28
28
  import { getPieLabelPosition } from './helpers/labelPlacement'
29
+ import { getV21ChartDistributionColors } from '../../helpers/getV21ChartDistributionColors'
29
30
 
30
31
  type TooltipData = TooltipDisplayData
31
32
 
@@ -57,8 +58,7 @@ const measurePieLabelText = (text: string) => {
57
58
  const measured =
58
59
  typeof document === 'undefined'
59
60
  ? Math.ceil(text.length * ENHANCED_PIE_LABEL_FONT_SIZE * 0.6)
60
- : (getTextWidth(text, ENHANCED_PIE_LABEL_FONT) ??
61
- Math.ceil(text.length * ENHANCED_PIE_LABEL_FONT_SIZE * 0.6))
61
+ : getTextWidth(text, ENHANCED_PIE_LABEL_FONT) ?? Math.ceil(text.length * ENHANCED_PIE_LABEL_FONT_SIZE * 0.6)
62
62
 
63
63
  // Avoid unbounded growth in long-lived sessions with many unique labels.
64
64
  if (pieLabelTextWidthCache.size >= MAX_PIE_LABEL_TEXT_CACHE) pieLabelTextWidthCache.clear()
@@ -158,7 +158,10 @@ const PieChart = React.forwardRef<SVGSVGElement, PieChartProps>((props, ref) =>
158
158
 
159
159
  // Helper function to determine enhanced distribution type and apply it
160
160
  const applyEnhancedColorDistribution = (config, palette, numberOfKeys) => {
161
- const version = getColorPaletteVersion(config)
161
+ const v21DistributionColors = getV21ChartDistributionColors(config, palette, numberOfKeys)
162
+ if (v21DistributionColors) return v21DistributionColors
163
+
164
+ const version = getColorPaletteMajorVersion(config)
162
165
  const configPalette = config.general?.palette?.name || config.palette
163
166
 
164
167
  // Skip enhanced distribution if not v2, too many keys, or wrong palette length
@@ -170,7 +173,6 @@ const PieChart = React.forwardRef<SVGSVGElement, PieChartProps>((props, ref) =>
170
173
  const isDivergent = configPalette && configPalette.includes('divergent')
171
174
  const isColorblindSafe =
172
175
  configPalette && (configPalette.includes('colorblindsafe') || configPalette.includes('qualitative_standard'))
173
-
174
176
  // Determine which distribution to use based on palette type
175
177
  let distributionMap = null
176
178
  if (isDivergent) {
@@ -251,6 +253,7 @@ const PieChart = React.forwardRef<SVGSVGElement, PieChartProps>((props, ref) =>
251
253
  config.xAxis.dataKey,
252
254
  config.general?.palette?.name,
253
255
  config.general?.palette?.isReversed,
256
+ config.general?.palette?.version,
254
257
  config.general?.palette?.customColors,
255
258
  config.general?.palette?.customColorsOrdered,
256
259
  config.palette
@@ -438,9 +441,7 @@ const PieChart = React.forwardRef<SVGSVGElement, PieChartProps>((props, ref) =>
438
441
  ? Math.min(width - labelGutter * 2, height - VERTICAL_LABEL_GUTTER * 2)
439
442
  : Math.min(width, height)
440
443
 
441
- const radius = showCategoryPercentageLabels
442
- ? Math.max(0, availableDiameter / 2)
443
- : Math.min(width, height) / 2
444
+ const radius = showCategoryPercentageLabels ? Math.max(0, availableDiameter / 2) : Math.min(width, height) / 2
444
445
  const svgWidth = showCategoryPercentageLabels ? width : radius * 2
445
446
  const svgLeftOffset = Math.max((props.parentWidth - svgWidth) / 2, 0)
446
447
  const centerY = height / 2
@@ -0,0 +1,44 @@
1
+ import { type ReactNode, type RefObject } from 'react'
2
+ import ParentSize from '@visx/responsive/lib/components/ParentSize'
3
+ import BarChartRace, { BarChartRaceFallback, type BarRaceEligibility } from './BarChartRace'
4
+ import LineChartRace, { LineChartRaceFallback, type LineRaceEligibility } from './LineChartRace'
5
+
6
+ type Props = {
7
+ barRace: BarRaceEligibility
8
+ family: 'Bar' | 'Line'
9
+ lineRace: LineRaceEligibility
10
+ parentRef: RefObject<HTMLDivElement>
11
+ renderTopYAxisTitles: () => ReactNode
12
+ svgRef: RefObject<SVGAElement>
13
+ }
14
+
15
+ const RacingChartRenderer = ({ barRace, family, lineRace, parentRef, renderTopYAxisTitles, svgRef }: Props) => {
16
+ const eligibility = family === 'Bar' ? barRace : lineRace
17
+
18
+ return (
19
+ <>
20
+ {(family === 'Line' || !eligibility.eligible) && renderTopYAxisTitles()}
21
+ <div ref={parentRef} style={{ width: '100%' }}>
22
+ <ParentSize>
23
+ {parent => {
24
+ if (family === 'Bar') {
25
+ return barRace.eligible ? (
26
+ <BarChartRace parentWidth={parent.width} race={barRace} />
27
+ ) : (
28
+ <BarChartRaceFallback ref={svgRef} parentWidth={parent.width} parentHeight={parent.height} />
29
+ )
30
+ }
31
+
32
+ return lineRace.eligible ? (
33
+ <LineChartRace ref={svgRef} parentWidth={parent.width} parentHeight={parent.height} race={lineRace} />
34
+ ) : (
35
+ <LineChartRaceFallback ref={svgRef} parentWidth={parent.width} parentHeight={parent.height} />
36
+ )
37
+ }}
38
+ </ParentSize>
39
+ </div>
40
+ </>
41
+ )
42
+ }
43
+
44
+ export default RacingChartRenderer
@@ -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'