@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,4 +1,4 @@
1
- import React, { useContext, useState } from 'react'
1
+ import React, { useContext } from 'react'
2
2
  // Local contexts
3
3
  import ConfigContext from '../../../ConfigContext'
4
4
  import BarChartContext, { type BarChartContextValues } from './context'
@@ -10,27 +10,29 @@ import { Group } from '@visx/group'
10
10
  import { Text } from '@visx/text'
11
11
  import { BarGroup } from '@visx/shape'
12
12
  import { PatternLines, PatternCircles, PatternWaves } from '@visx/pattern'
13
- // Local components
14
- import Regions from '../../Regions'
15
13
  // CDC core components and helpers
16
14
  import { isDateScale } from '@cdc/core/helpers/cove/date'
17
15
  import isNumber from '@cdc/core/helpers/isNumber'
18
16
  import createBarElement from '@cdc/core/components/createBarElement'
19
17
  import { APP_FONT_COLOR } from '@cdc/core/helpers/constants'
18
+ import { getSeriesValueLabel } from '@cdc/core/helpers/getSeriesName'
20
19
  // Types
21
20
  import { type ChartContext } from '../../../types/ChartContext'
22
21
  import { getBarData } from '../helpers/getBarData'
23
22
  import { getPatternUrl as getPatternUrlForBar } from '../helpers/getPatternUrl'
23
+ import { getPortionPatternRenderData } from '../helpers/portionPattern'
24
24
  import { getChartPatternId } from '../../../helpers/getChartPatternId'
25
25
  import { buildSeriesTooltipListHtml } from '../../../helpers/tooltipHelpers'
26
+ import { BarPortionPatternOverlay } from './PortionPatternOverlay'
26
27
 
27
28
  const BarChartVertical = () => {
28
- const { xScale, yScale, xMax, yMax, seriesScale, convertLineToBarGraph, barChart } =
29
+ const { xScale, yScale, yMax, seriesScale, convertLineToBarGraph, barChart } =
29
30
  useContext<BarChartContextValues>(BarChartContext)
30
31
  const {
31
32
  assignColorsToValues,
32
33
  barBorderWidth,
33
34
  getAdditionalColumn,
35
+ formatTooltipValue,
34
36
  getHighlightedBarByValue,
35
37
  getHighlightedBarColorByValue,
36
38
  labelFontSize,
@@ -41,16 +43,12 @@ const BarChartVertical = () => {
41
43
  section
42
44
  } = barChart
43
45
 
44
- const [barWidth, setBarWidth] = useState(0)
45
- const [totalBarsInGroup, setTotalBarsInGroup] = useState(0)
46
-
47
46
  const {
48
47
  colorScale,
49
48
  config,
50
49
  currentViewport,
51
50
  vizViewport,
52
51
  dashboardConfig,
53
- tableData,
54
52
  formatDate,
55
53
  formatNumber,
56
54
  parseDate,
@@ -63,13 +61,7 @@ const BarChartVertical = () => {
63
61
 
64
62
  const root = document.documentElement
65
63
 
66
- let data = transformedData
67
- // check if user add suppression
68
- const isSuppressionActive = config.preliminaryData.some(pd => pd.value && pd.type === 'suppression')
69
- // if suppression active use table data (filtere | excluded) but non cleaned
70
- if (isSuppressionActive) {
71
- data = tableData
72
- }
64
+ const data = transformedData
73
65
 
74
66
  const hasConfidenceInterval =
75
67
  config.confidenceKeys.upper &&
@@ -134,8 +126,9 @@ const BarChartVertical = () => {
134
126
  id={patternId}
135
127
  height={size}
136
128
  width={size}
137
- fill={pattern.color}
138
- strokeWidth={0.25}
129
+ fill='transparent'
130
+ stroke={pattern.color}
131
+ strokeWidth={0.75}
139
132
  />
140
133
  )
141
134
  default:
@@ -215,12 +208,12 @@ const BarChartVertical = () => {
215
208
  bar.x +
216
209
  (config.isLollipopChart ? (barGroupWidth / barGroup.bars.length - lollipopBarWidth) / 2 : 0) -
217
210
  (config.xAxis.type === 'date-time' ? barGroupWidth / 2 : 0)
218
- setBarWidth(barWidth)
219
- setTotalBarsInGroup(barGroup.bars.length)
220
211
  const yAxisValue = formatNumber(/[a-zA-Z]/.test(String(bar.value)) ? '' : bar.value, 'left')
221
212
  const xAxisValue =
222
213
  config.runtime[section].type === 'date' ? formatDate(parseDate(dataValue)) : dataValue
223
214
 
215
+ const tooltipValue = formatTooltipValue(bar.key, dataValue, yAxisValue, barGroup.index)
216
+
224
217
  // create new Index for bars with negative values
225
218
  const newIndex = bar.value < 0 ? -1 : index
226
219
  // tooltips
@@ -228,7 +221,7 @@ const BarChartVertical = () => {
228
221
  let xAxisTooltip = config.runtime.xAxis.label
229
222
  ? `${config.runtime.xAxis.label}: ${xAxisValue}`
230
223
  : xAxisValue
231
- const tooltipBody = `${config.runtime.seriesLabels[bar.key]}: ${yAxisValue}`
224
+ const tooltipBody = `${getSeriesValueLabel(bar.key, config)}: ${tooltipValue}`
232
225
  const tooltip = buildSeriesTooltipListHtml({
233
226
  config,
234
227
  colorScale,
@@ -308,8 +301,7 @@ const BarChartVertical = () => {
308
301
  if (isRegularLollipopColor) _barColor = barColor
309
302
 
310
303
  if (isHighlightedBar) _barColor = 'transparent'
311
- if (config.legend.colorCode)
312
- _barColor = assignColorsToValues(barGroups.length, barGroup.index, barColor)
304
+ if (config.legend.colorCode) _barColor = assignColorsToValues(barGroup.index, barColor)
313
305
  if (isTwoToneLollipopColor) {
314
306
  _barColor = getLollipopStemColor(barColor)
315
307
  }
@@ -320,8 +312,7 @@ const BarChartVertical = () => {
320
312
  if (isTwoToneLollipopColor) {
321
313
  _barColor = getLollipopStemColor(barColor)
322
314
  }
323
- if (config.legend.colorCode)
324
- _barColor = assignColorsToValues(barGroups.length, barGroup.index, barColor)
315
+ if (config.legend.colorCode) _barColor = assignColorsToValues(barGroup.index, barColor)
325
316
 
326
317
  // if we're highlighting a bar make it invisible since it gets a border
327
318
  if (isHighlightedBar) _barColor = 'transparent'
@@ -339,6 +330,15 @@ const BarChartVertical = () => {
339
330
  allowNonSeriesFieldMatch: !config.series || config.series.length <= 1
340
331
  })
341
332
  const baseBackground = getBarBackgroundColor(colorScale(config.runtime.seriesLabels[bar.key]))
333
+ const portionPatternRenderData = getPortionPatternRenderData({
334
+ config,
335
+ orientation: 'vertical',
336
+ bounds: { x: barX, y: barY, width: barWidth, height: barHeight },
337
+ patterns: config.legend?.patterns,
338
+ datum,
339
+ seriesKey: bar.key,
340
+ totalValue: bar.value
341
+ })
342
342
 
343
343
  // Confidence Interval Variables
344
344
  const tickWidth = 5
@@ -419,6 +419,19 @@ const BarChartVertical = () => {
419
419
  }
420
420
  })}
421
421
 
422
+ {portionPatternRenderData && (
423
+ <BarPortionPatternOverlay
424
+ config={config}
425
+ index={newIndex}
426
+ renderData={portionPatternRenderData}
427
+ transformOrigin={`0 ${barY + barHeight}px`}
428
+ style={{
429
+ opacity: transparentBar ? 0.2 : 1,
430
+ display: displayBar ? 'block' : 'none'
431
+ }}
432
+ />
433
+ )}
434
+
422
435
  {(absentDataLabel || isSuppressed) && (
423
436
  <rect
424
437
  x={barX}
@@ -551,8 +564,6 @@ const BarChartVertical = () => {
551
564
  ))
552
565
  }}
553
566
  </BarGroup>
554
-
555
- <Regions xScale={xScale} yMax={yMax} barWidth={barWidth} totalBarsInGroup={totalBarsInGroup} xMax={xMax} />
556
567
  </Group>
557
568
  )
558
569
  )
@@ -0,0 +1,61 @@
1
+ import React from 'react'
2
+
3
+ import createBarElement from '@cdc/core/components/createBarElement'
4
+
5
+ import { type ChartConfig } from '../../../types/ChartConfig'
6
+ import { type PortionPatternRenderData } from '../helpers/portionPattern'
7
+
8
+ type PortionPatternBoundaryProps = {
9
+ renderData: PortionPatternRenderData
10
+ brush?: boolean
11
+ style?: React.CSSProperties
12
+ }
13
+
14
+ type BarPortionPatternOverlayProps = PortionPatternBoundaryProps & {
15
+ config: ChartConfig
16
+ index: number
17
+ transformOrigin: string
18
+ }
19
+
20
+ export const PortionPatternBoundary = ({ renderData, brush = false, style }: PortionPatternBoundaryProps) => {
21
+ if (!renderData.patternColor || !renderData.boundaryGeometry) return null
22
+
23
+ return (
24
+ <line
25
+ className={`pattern-overlay__boundary${brush ? ' pattern-overlay__boundary--brush' : ''}`}
26
+ {...renderData.boundaryGeometry}
27
+ stroke={renderData.patternColor}
28
+ strokeWidth={0.75}
29
+ vectorEffect='non-scaling-stroke'
30
+ pointerEvents='none'
31
+ aria-hidden='true'
32
+ style={style}
33
+ />
34
+ )
35
+ }
36
+
37
+ export const BarPortionPatternOverlay = ({
38
+ config,
39
+ index,
40
+ renderData,
41
+ transformOrigin,
42
+ style
43
+ }: BarPortionPatternOverlayProps) => (
44
+ <>
45
+ {createBarElement({
46
+ config,
47
+ index: renderData.placement === 'end' ? index : undefined,
48
+ className: 'pattern-overlay pattern-overlay--portion',
49
+ background: renderData.patternUrl,
50
+ borderColor: 'transparent',
51
+ borderWidth: '0px',
52
+ ...renderData.geometry,
53
+ styleOverrides: {
54
+ transformOrigin,
55
+ ...style,
56
+ pointerEvents: 'none'
57
+ }
58
+ })}
59
+ <PortionPatternBoundary renderData={renderData} style={style} />
60
+ </>
61
+ )
@@ -14,6 +14,12 @@ export type BarChartContextValues = {
14
14
  assignColorsToValues: Function
15
15
  barBorderWidth: number
16
16
  getAdditionalColumn: Function
17
+ formatTooltipValue: (
18
+ seriesKey: string,
19
+ xAxisValue: unknown,
20
+ displayValue: string | number,
21
+ rowIndex?: number
22
+ ) => string | number
17
23
  getHighlightedBarByValue: Function
18
24
  getHighlightedBarColorByValue: Function
19
25
  lollipopBarWidth: number
@@ -1,9 +1,5 @@
1
1
  import { getChartPatternId } from '../../../helpers/getChartPatternId'
2
-
3
- type LegendPattern = {
4
- dataKey?: string
5
- dataValue?: string | number
6
- }
2
+ import { type LegendPattern } from '../../../types/ChartConfig'
7
3
 
8
4
  type SeriesLabels = Record<string, string> | undefined
9
5
 
@@ -64,6 +60,7 @@ export const getPatternUrl = ({
64
60
  for (const patternKey in patterns) {
65
61
  if (!Object.prototype.hasOwnProperty.call(patterns, patternKey)) continue
66
62
  const pattern = patterns[patternKey]
63
+ if (pattern.application === 'portion') continue
67
64
  const dataKey = normalizeString(pattern.dataKey)
68
65
 
69
66
  if (!hasPatternValue(pattern.dataValue)) {
@@ -125,6 +125,14 @@ export const addMinimumBarHeights = barStacks => {
125
125
  const segmentsNeedingAdjustment = segments.filter(segment => segment.height > 0 && segment.height < MIN_BAR_HEIGHT)
126
126
  const segmentsToShrink = segments.filter(segment => segment.height > MIN_BAR_HEIGHT)
127
127
 
128
+ if (segments.length === 1 && segmentsNeedingAdjustment.length === 1) {
129
+ const segment = segmentsNeedingAdjustment[0]
130
+ const bottom = segment.y + segment.height
131
+ segment.height = MIN_BAR_HEIGHT
132
+ segment.y = bottom - MIN_BAR_HEIGHT
133
+ return
134
+ }
135
+
128
136
  if (segmentsNeedingAdjustment.length > 0 && segmentsToShrink.length > 0) {
129
137
  segmentsNeedingAdjustment.forEach(smallSegment => {
130
138
  const heightToAdd = MIN_BAR_HEIGHT - smallSegment.height
@@ -0,0 +1,243 @@
1
+ import { type Column } from '@cdc/core/types/Column'
2
+
3
+ import { getChartPatternId } from '../../../helpers/getChartPatternId'
4
+ import { type ChartConfig, type LegendPattern } from '../../../types/ChartConfig'
5
+
6
+ type PortionPatternSupportConfig = Pick<
7
+ ChartConfig,
8
+ 'visualizationType' | 'visualizationSubType' | 'isLollipopChart' | 'yAxis'
9
+ >
10
+
11
+ export type PortionPatternOverlay = {
12
+ patternUrl: string
13
+ ratio: number
14
+ placement: 'start' | 'end'
15
+ patternColor?: string
16
+ }
17
+
18
+ export type PortionPatternGeometry = {
19
+ x: number
20
+ y: number
21
+ width: number
22
+ height: number
23
+ }
24
+
25
+ export type PortionPatternBoundaryGeometry = {
26
+ x1: number
27
+ y1: number
28
+ x2: number
29
+ y2: number
30
+ }
31
+
32
+ export type PortionPatternRenderData = PortionPatternOverlay & {
33
+ geometry: PortionPatternGeometry
34
+ boundaryGeometry: PortionPatternBoundaryGeometry | null
35
+ }
36
+
37
+ type GetPortionPatternOverlayArgs = {
38
+ patterns?: Record<string, LegendPattern>
39
+ datum: Record<string, any>
40
+ seriesKey: string
41
+ totalValue: unknown
42
+ }
43
+
44
+ type GetPortionPatternGeometryArgs = {
45
+ orientation: 'vertical' | 'horizontal'
46
+ bounds: PortionPatternGeometry
47
+ overlay: Pick<PortionPatternOverlay, 'ratio' | 'placement'>
48
+ }
49
+
50
+ type GetPortionPatternBoundaryGeometryArgs = {
51
+ orientation: 'vertical' | 'horizontal'
52
+ geometry: PortionPatternGeometry
53
+ overlay: Pick<PortionPatternOverlay, 'ratio' | 'placement'>
54
+ }
55
+
56
+ type GetPortionPatternRenderDataArgs = GetPortionPatternOverlayArgs & {
57
+ config: PortionPatternSupportConfig
58
+ orientation: 'vertical' | 'horizontal'
59
+ bounds: PortionPatternGeometry
60
+ }
61
+
62
+ export type ChartColumns = Record<string, Column>
63
+
64
+ export const findColumnConfigKey = (columns: ChartColumns, columnName: string): string | undefined =>
65
+ Object.entries(columns).find(
66
+ ([columnKey, column]) => column?.name === columnName || (!column?.name && columnKey === columnName)
67
+ )?.[0]
68
+
69
+ export const createPatternColumnConfig = (columnName: string): Column =>
70
+ ({
71
+ name: columnName,
72
+ label: columnName,
73
+ prefix: '',
74
+ suffix: '',
75
+ roundToPlace: 0,
76
+ commas: true,
77
+ dataTable: true,
78
+ tooltips: true
79
+ } as Column)
80
+
81
+ export const ensurePatternColumnConfig = (columns: ChartColumns, columnName?: string): ChartColumns => {
82
+ if (!columnName || findColumnConfigKey(columns, columnName)) return columns
83
+
84
+ return {
85
+ ...columns,
86
+ [columnName]: createPatternColumnConfig(columnName)
87
+ }
88
+ }
89
+
90
+ export const removeUnusedPatternColumnConfig = ({
91
+ columns,
92
+ columnName,
93
+ patterns,
94
+ protectedColumnNames = []
95
+ }: {
96
+ columns: ChartColumns
97
+ columnName?: string
98
+ patterns: Record<string, LegendPattern>
99
+ protectedColumnNames?: Array<string | undefined>
100
+ }): ChartColumns => {
101
+ if (!columnName) return columns
102
+
103
+ const isStillUsedByPattern = Object.values(patterns).some(
104
+ pattern => pattern.application === 'portion' && pattern.patternValueKey === columnName
105
+ )
106
+
107
+ if (isStillUsedByPattern || protectedColumnNames.includes(columnName)) return columns
108
+
109
+ const columnConfigKey = findColumnConfigKey(columns, columnName)
110
+ if (!columnConfigKey) return columns
111
+
112
+ const nextColumns = { ...columns }
113
+ delete nextColumns[columnConfigKey]
114
+ return nextColumns
115
+ }
116
+
117
+ const normalizeString = (value: unknown): string => String(value ?? '').trim()
118
+
119
+ const toPositiveFiniteNumber = (value: unknown): number | null => {
120
+ if (typeof value !== 'number' && typeof value !== 'string') return null
121
+ if (typeof value === 'string' && value.trim() === '') return null
122
+
123
+ const numericValue = Number(value)
124
+ return Number.isFinite(numericValue) && numericValue > 0 ? numericValue : null
125
+ }
126
+
127
+ export const isPortionPatternSupported = (config: PortionPatternSupportConfig): boolean =>
128
+ ['Bar', 'Combo'].includes(config.visualizationType) &&
129
+ config.visualizationSubType !== 'stacked' &&
130
+ !config.isLollipopChart &&
131
+ config.yAxis?.type !== 'logarithmic'
132
+
133
+ /**
134
+ * Resolves the first configured portion pattern for a bar series. The first
135
+ * matching entry wins even when its row values are invalid, so manually
136
+ * authored duplicate targets remain deterministic.
137
+ */
138
+ export const getPortionPatternOverlay = ({
139
+ patterns,
140
+ datum,
141
+ seriesKey,
142
+ totalValue
143
+ }: GetPortionPatternOverlayArgs): PortionPatternOverlay | null => {
144
+ if (!patterns) return null
145
+
146
+ for (const patternKey in patterns) {
147
+ if (!Object.prototype.hasOwnProperty.call(patterns, patternKey)) continue
148
+
149
+ const pattern = patterns[patternKey]
150
+ if (pattern.application !== 'portion' || normalizeString(pattern.dataKey) !== seriesKey) continue
151
+
152
+ const total = toPositiveFiniteNumber(totalValue)
153
+ const portion = toPositiveFiniteNumber(datum?.[normalizeString(pattern.patternValueKey)])
154
+
155
+ if (total === null || portion === null || portion > total) return null
156
+
157
+ const patternColor = normalizeString(pattern.color)
158
+
159
+ return {
160
+ patternUrl: `url(#${getChartPatternId(patternKey)})`,
161
+ ratio: portion / total,
162
+ placement: pattern.placement === 'start' ? 'start' : 'end',
163
+ ...(patternColor ? { patternColor } : {})
164
+ }
165
+ }
166
+
167
+ return null
168
+ }
169
+
170
+ export const getPortionPatternGeometry = ({
171
+ orientation,
172
+ bounds,
173
+ overlay
174
+ }: GetPortionPatternGeometryArgs): PortionPatternGeometry => {
175
+ const { x, y, width, height } = bounds
176
+
177
+ if (orientation === 'horizontal') {
178
+ const overlayWidth = width * overlay.ratio
179
+ return {
180
+ x: overlay.placement === 'end' ? x + width - overlayWidth : x,
181
+ y,
182
+ width: overlayWidth,
183
+ height
184
+ }
185
+ }
186
+
187
+ const overlayHeight = height * overlay.ratio
188
+ return {
189
+ x,
190
+ y: overlay.placement === 'start' ? y + height - overlayHeight : y,
191
+ width,
192
+ height: overlayHeight
193
+ }
194
+ }
195
+
196
+ export const getPortionPatternBoundaryGeometry = ({
197
+ orientation,
198
+ geometry,
199
+ overlay
200
+ }: GetPortionPatternBoundaryGeometryArgs): PortionPatternBoundaryGeometry | null => {
201
+ if (overlay.ratio >= 1) return null
202
+
203
+ if (orientation === 'horizontal') {
204
+ const boundaryX = overlay.placement === 'end' ? geometry.x : geometry.x + geometry.width
205
+ return {
206
+ x1: boundaryX,
207
+ y1: geometry.y,
208
+ x2: boundaryX,
209
+ y2: geometry.y + geometry.height
210
+ }
211
+ }
212
+
213
+ const boundaryY = overlay.placement === 'end' ? geometry.y + geometry.height : geometry.y
214
+ return {
215
+ x1: geometry.x,
216
+ y1: boundaryY,
217
+ x2: geometry.x + geometry.width,
218
+ y2: boundaryY
219
+ }
220
+ }
221
+
222
+ export const getPortionPatternRenderData = ({
223
+ config,
224
+ orientation,
225
+ bounds,
226
+ patterns,
227
+ datum,
228
+ seriesKey,
229
+ totalValue
230
+ }: GetPortionPatternRenderDataArgs): PortionPatternRenderData | null => {
231
+ if (!isPortionPatternSupported(config)) return null
232
+
233
+ const overlay = getPortionPatternOverlay({ patterns, datum, seriesKey, totalValue })
234
+ if (!overlay) return null
235
+
236
+ const geometry = getPortionPatternGeometry({ orientation, bounds, overlay })
237
+
238
+ return {
239
+ ...overlay,
240
+ geometry,
241
+ boundaryGeometry: getPortionPatternBoundaryGeometry({ orientation, geometry, overlay })
242
+ }
243
+ }
@@ -0,0 +1,42 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { addMinimumBarHeights } from '..'
3
+
4
+ const createSegment = ({ key, height, y, value }: { key: string; height: number; y: number; value: unknown }) => ({
5
+ key,
6
+ height,
7
+ y,
8
+ bar: {
9
+ data: {
10
+ [key]: value
11
+ }
12
+ }
13
+ })
14
+
15
+ describe('addMinimumBarHeights', () => {
16
+ it('gives an isolated tiny positive segment a minimum visible height while preserving its baseline', () => {
17
+ const barStacks = [
18
+ {
19
+ bars: [createSegment({ key: 'Tiny', height: 0.02, y: 99.98, value: 0.005 })]
20
+ }
21
+ ]
22
+
23
+ addMinimumBarHeights(barStacks)
24
+
25
+ expect(barStacks[0].bars[0].height).toBe(3)
26
+ expect(barStacks[0].bars[0].y).toBeCloseTo(97)
27
+ expect(barStacks[0].bars[0].y + barStacks[0].bars[0].height).toBeCloseTo(100)
28
+ })
29
+
30
+ it('leaves exact zero segments at zero height', () => {
31
+ const barStacks = [
32
+ {
33
+ bars: [createSegment({ key: 'Zero', height: 0, y: 100, value: 0 })]
34
+ }
35
+ ]
36
+
37
+ addMinimumBarHeights(barStacks)
38
+
39
+ expect(barStacks[0].bars[0].height).toBe(0)
40
+ expect(barStacks[0].bars[0].y).toBe(100)
41
+ })
42
+ })
@@ -131,4 +131,17 @@ describe('getPatternUrl', () => {
131
131
  expect(idA.startsWith('chart-pattern-A-B-')).toBe(true)
132
132
  expect(idB.startsWith('chart-pattern-A-B-')).toBe(true)
133
133
  })
134
+
135
+ it('does not treat portion patterns as exact-value patterns', () => {
136
+ expect(
137
+ getPatternUrl({
138
+ patterns: {
139
+ Pattern1: { application: 'portion', dataKey: 'y1', dataValue: 100, patternValueKey: 'portion' }
140
+ },
141
+ datum: { y1: 100, portion: 25 },
142
+ seriesKey: 'y1',
143
+ seriesValue: 100
144
+ })
145
+ ).toBeNull()
146
+ })
134
147
  })