@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,88 @@
1
+ export type ForecastConfidenceInterval = {
2
+ high?: string | number
3
+ low?: string | number
4
+ }
5
+
6
+ export type ForecastConfidenceIntervalGroup = {
7
+ intervalIndexes: number[]
8
+ key: string
9
+ label: string
10
+ opacity: number
11
+ }
12
+
13
+ const FORECAST_CI_KEY_PREFIX = 'forecast-ci:'
14
+
15
+ const getSharedPercentage = ({ high, low }: ForecastConfidenceInterval): number | undefined => {
16
+ const highMatches = String(high).match(/\d+(?:\.\d+)?/g)
17
+ const lowMatches = String(low).match(/\d+(?:\.\d+)?/g)
18
+
19
+ if (highMatches?.length !== 1 || lowMatches?.length !== 1) return
20
+
21
+ const highPercentage = Number(highMatches[0])
22
+ const lowPercentage = Number(lowMatches[0])
23
+
24
+ return highPercentage === lowPercentage && highPercentage >= 0 && highPercentage <= 100 ? highPercentage : undefined
25
+ }
26
+
27
+ export const getForecastConfidenceIntervalGroups = (
28
+ confidenceIntervals: ForecastConfidenceInterval[] = []
29
+ ): ForecastConfidenceIntervalGroup[] => {
30
+ const completeIntervals = confidenceIntervals
31
+ .map((interval, configIndex) => ({ interval, configIndex, percentage: getSharedPercentage(interval) }))
32
+ .filter(({ interval }) => String(interval.high ?? '').trim() && String(interval.low ?? '').trim())
33
+
34
+ if (completeIntervals.length <= 1) return []
35
+
36
+ const orderedIntervals = completeIntervals.sort((a, b) => {
37
+ if (a.percentage !== undefined && b.percentage !== undefined) return a.percentage - b.percentage
38
+ if (a.percentage !== undefined) return -1
39
+ if (b.percentage !== undefined) return 1
40
+ return a.configIndex - b.configIndex
41
+ })
42
+ const groups: ForecastConfidenceIntervalGroup[] = []
43
+ const percentageGroups = new Map<number, ForecastConfidenceIntervalGroup>()
44
+ let fallbackIndex = 0
45
+
46
+ orderedIntervals.forEach((interval, displayIndex) => {
47
+ if (interval.percentage !== undefined) {
48
+ const existingGroup = percentageGroups.get(interval.percentage)
49
+ if (existingGroup) {
50
+ existingGroup.intervalIndexes.push(interval.configIndex)
51
+ return
52
+ }
53
+ }
54
+
55
+ const fallbackNumber = interval.percentage === undefined ? ++fallbackIndex : undefined
56
+ const group = {
57
+ intervalIndexes: [interval.configIndex],
58
+ key: interval.percentage !== undefined ? `percentage:${interval.percentage}` : `interval:${interval.configIndex}`,
59
+ label: interval.percentage !== undefined ? `${interval.percentage}% CI` : `CI ${fallbackNumber}`,
60
+ opacity: 1 - Math.pow(1 - 0.5, completeIntervals.length - displayIndex)
61
+ }
62
+
63
+ groups.push(group)
64
+ if (interval.percentage !== undefined) percentageGroups.set(interval.percentage, group)
65
+ })
66
+
67
+ return groups
68
+ }
69
+
70
+ export const getForecastLegendKey = (stageKey: string, intervalKey: string): string =>
71
+ `${FORECAST_CI_KEY_PREFIX}${encodeURIComponent(stageKey)}:${encodeURIComponent(intervalKey)}`
72
+
73
+ export const getForecastConfidenceIntervalKey = (
74
+ confidenceIntervals: ForecastConfidenceInterval[] = [],
75
+ intervalIndex: number
76
+ ): string | undefined =>
77
+ getForecastConfidenceIntervalGroups(confidenceIntervals).find(group => group.intervalIndexes.includes(intervalIndex))
78
+ ?.key
79
+
80
+ export const isForecastLegendSelectionActive = (
81
+ seriesHighlight: string[],
82
+ stageKey: string,
83
+ intervalKey?: string
84
+ ): boolean => {
85
+ if (!seriesHighlight.length) return true
86
+ if (seriesHighlight.includes(stageKey)) return true
87
+ return intervalKey ? seriesHighlight.includes(getForecastLegendKey(stageKey, intervalKey)) : false
88
+ }
@@ -1,11 +1,15 @@
1
- import _ from 'lodash'
1
+ import capitalize from 'lodash/capitalize'
2
+ import flatMap from 'lodash/flatMap'
3
+ import map from 'lodash/map'
4
+ import round from 'lodash/round'
5
+ import uniq from 'lodash/uniq'
6
+ import { calculateBoxPlotStats } from './boxPlotStats'
2
7
  import { ChartConfig } from '../types/ChartConfig'
3
- import * as d3 from 'd3-array'
4
8
 
5
9
  export const getBoxPlotConfig = (newConfig: ChartConfig, data: object[]) => {
6
10
  const combinedData = data
7
- const groups = _.uniq(_.map(combinedData, newConfig.xAxis.dataKey))
8
- const seriesKeys = _.map(newConfig.series, 'dataKey')
11
+ const groups = uniq(map(combinedData, newConfig.xAxis.dataKey))
12
+ const seriesKeys = map(newConfig.series, 'dataKey')
9
13
  const plots: any[] = []
10
14
 
11
15
  groups.forEach(g => {
@@ -13,44 +17,28 @@ export const getBoxPlotConfig = (newConfig: ChartConfig, data: object[]) => {
13
17
  try {
14
18
  if (!g) throw new Error('No groups resolved in box plots')
15
19
 
16
- // Start handle operations on combinedData
17
- const { count, sortedData } = _.chain(combinedData)
18
- // Filter by xAxis data key
19
- .filter(item => item[newConfig.xAxis.dataKey] === g)
20
- // perform multiple operations on the filtered data
21
- .thru(filteredData => ({
22
- count: filteredData.length,
23
- sortedData: _.map(filteredData, item => Number(item[seriesKey])).sort()
24
- }))
25
- // get the results from the chain
26
- .value()
20
+ const filteredData = combinedData.filter(item => item[newConfig.xAxis.dataKey] === g)
21
+ const stats = calculateBoxPlotStats(map(filteredData, item => item[seriesKey]))
27
22
 
28
- if (!sortedData) throw new Error('boxplots dont have data yet')
23
+ if (!stats) throw new Error('boxplots dont have data yet')
29
24
  if (!plots) throw new Error('boxplots dont have plots yet')
30
25
 
31
- const q1 = d3.quantile(sortedData, 0.25)
32
- const q3 = d3.quantile(sortedData, 0.75)
33
-
34
- const iqr = q3 - q1
35
- const lowerBounds = q1 - 1.5 * iqr
36
- const upperBounds = q3 + 1.5 * iqr
37
- const nonOutliers = sortedData.filter(value => value >= lowerBounds && value <= upperBounds)
38
26
  plots.push({
39
27
  columnCategory: g,
40
- columnMax: d3.max(nonOutliers),
41
- columnThirdQuartile: _.round(q3, newConfig.dataFormat.roundTo),
42
- columnMedian: Number(d3.median(sortedData)).toFixed(newConfig.dataFormat.roundTo),
43
- columnFirstQuartile: _.round(q1, newConfig.dataFormat.roundTo),
44
- columnMin: _.min(nonOutliers),
45
- columnCount: count,
46
- columnSd: Number(d3.deviation(sortedData)).toFixed(newConfig.dataFormat.roundTo),
47
- columnMean: Number(d3.mean(sortedData)).toFixed(newConfig.dataFormat.roundTo),
48
- columnIqr: _.round(iqr, newConfig.dataFormat.roundTo),
49
- values: sortedData,
50
- columnLowerBounds: lowerBounds,
51
- columnUpperBounds: upperBounds,
52
- columnOutliers: _.filter(sortedData, value => value < lowerBounds || value > upperBounds),
53
- columnNonOutliers: _.filter(sortedData, value => value >= lowerBounds && value <= upperBounds)
28
+ columnMax: stats.whiskerMax,
29
+ columnThirdQuartile: round(stats.q3, newConfig.dataFormat.roundTo),
30
+ columnMedian: Number(stats.median).toFixed(newConfig.dataFormat.roundTo),
31
+ columnFirstQuartile: round(stats.q1, newConfig.dataFormat.roundTo),
32
+ columnMin: stats.whiskerMin,
33
+ columnCount: stats.values.length,
34
+ columnSd: Number(stats.deviation).toFixed(newConfig.dataFormat.roundTo),
35
+ columnMean: Number(stats.mean).toFixed(newConfig.dataFormat.roundTo),
36
+ columnIqr: round(stats.iqr, newConfig.dataFormat.roundTo),
37
+ values: stats.values,
38
+ columnLowerBounds: stats.lowerBound,
39
+ columnUpperBounds: stats.upperBound,
40
+ columnOutliers: stats.outliers,
41
+ columnNonOutliers: stats.nonOutliers
54
42
  })
55
43
  } catch (e) {
56
44
  console.error('COVE: ', e.message) // eslint-disable-line
@@ -61,7 +49,7 @@ export const getBoxPlotConfig = (newConfig: ChartConfig, data: object[]) => {
61
49
  // Generate a flat list of categories based on seriesKeys and groups
62
50
  const categories =
63
51
  seriesKeys.length > 1
64
- ? _.flatMap(groups, value => _.map(seriesKeys, key => `${_.capitalize(key)} - ${_.capitalize(value)}`))
52
+ ? flatMap(groups, value => map(seriesKeys, key => `${capitalize(key)} - ${capitalize(value)}`))
65
53
  : groups
66
54
 
67
55
  return [plots, categories]
@@ -0,0 +1,29 @@
1
+ import { Palette } from '@cdc/core/types/Palette'
2
+
3
+ const paletteDefaultsByVisualizationType: Record<string, Palette> = {
4
+ Line: {
5
+ isReversed: false,
6
+ version: '2.1',
7
+ name: 'qualitative_standard'
8
+ },
9
+ 'Horizon Chart': {
10
+ isReversed: false,
11
+ version: '2.1',
12
+ name: 'sequential_blue'
13
+ },
14
+ HeatMap: {
15
+ isReversed: false,
16
+ version: '2.1',
17
+ name: 'sequential_blue'
18
+ },
19
+ Sankey: {
20
+ isReversed: true,
21
+ version: '2.1',
22
+ name: 'sequential_bluereverse'
23
+ }
24
+ }
25
+
26
+ export const getChartTypeDefaultPalette = (visualizationType?: string): Palette | undefined => {
27
+ const palette = visualizationType ? paletteDefaultsByVisualizationType[visualizationType] : undefined
28
+ return palette ? { ...palette } : undefined
29
+ }
@@ -0,0 +1,26 @@
1
+ import { getPaletteColors } from '@cdc/core/helpers/palettes/utils'
2
+ import { ChartConfig } from '../types/ChartConfig'
3
+ import { getV21ChartDistributionColors } from './getV21ChartDistributionColors'
4
+
5
+ export const getColorCodeCategoryColorMap = (
6
+ config: ChartConfig,
7
+ tableData: Record<string, unknown>[],
8
+ colorPalettes: Record<string, string[]>
9
+ ): Map<unknown, string> => {
10
+ const colorCode = config.legend?.colorCode
11
+ if (!colorCode || !Array.isArray(tableData)) return new Map()
12
+
13
+ let palette = getPaletteColors(config, colorPalettes)
14
+ if (!palette.length) return new Map()
15
+
16
+ const categoryColors = new Map<unknown, string>()
17
+
18
+ const categories = Array.from(new Set(tableData.map(row => row[colorCode])))
19
+ palette = getV21ChartDistributionColors(config, palette, categories.length) ?? palette
20
+
21
+ categories.forEach((category, index) => {
22
+ categoryColors.set(category, palette[index % palette.length])
23
+ })
24
+
25
+ return categoryColors
26
+ }
@@ -1,6 +1,6 @@
1
1
  import { twoColorPalette } from '@cdc/core/data/colorPalettes'
2
2
  import { filterChartColorPalettes } from '@cdc/core/helpers/filterColorPalettes'
3
- import { getColorPaletteVersion } from '@cdc/core/helpers/getColorPaletteVersion'
3
+ import { getColorPaletteMajorVersion } from '@cdc/core/helpers/getColorPaletteMajorVersion'
4
4
  import { scaleOrdinal } from '@visx/scale'
5
5
  import { ChartConfig } from '../types/ChartConfig'
6
6
  import { paletteMigrationMap } from '@cdc/core/helpers/palettes/migratePaletteName'
@@ -10,7 +10,17 @@ import {
10
10
  divergentColorDistribution,
11
11
  colorblindColorDistribution
12
12
  } from '@cdc/core/helpers/palettes/colorDistributions'
13
+ import { isValidPaletteColor } from '@cdc/core/helpers/palettes/colorValidation'
13
14
  import { applySeriesColorAssignmentsToRange } from './colorAssignmentHelpers'
15
+ import { getV21ChartDistributionColors } from './getV21ChartDistributionColors'
16
+
17
+ const INVALID_CUSTOM_COLOR_FALLBACK = '#000000'
18
+
19
+ const normalizeCustomColors = (colors: unknown): string[] => {
20
+ if (!Array.isArray(colors) || colors.length === 0) return []
21
+
22
+ return colors.map(color => (isValidPaletteColor(color) ? color.trim() : INVALID_CUSTOM_COLOR_FALLBACK))
23
+ }
14
24
 
15
25
  export const getColorScale = (config: ChartConfig): ((value: string) => string) => {
16
26
  const configPalette = ['Paired Bar', 'Deviation Bar'].includes(config.visualizationType)
@@ -19,7 +29,7 @@ export const getColorScale = (config: ChartConfig): ((value: string) => string)
19
29
  const colorPalettes = filterChartColorPalettes(config)
20
30
 
21
31
  // Get the correct version of two-color palettes
22
- const version = getColorPaletteVersion(config)
32
+ const version = getColorPaletteMajorVersion(config)
23
33
  const versionKey = `v${version}`
24
34
  const versionedTwoColorPalette = twoColorPalette[versionKey] || twoColorPalette.v2
25
35
 
@@ -28,17 +38,24 @@ export const getColorScale = (config: ChartConfig): ((value: string) => string)
28
38
  ? versionedTwoColorPalette
29
39
  : colorPalettes
30
40
 
31
- const allPalettes: Record<string, string[]> = { ...versionedTwoColorPalette, ...colorPalettes }
32
-
33
41
  // Migrate old palette name if needed
34
42
  const migratedPaletteName = configPalette ? configPalette : getFallbackColorPalette(config)
43
+ let namedPalette =
44
+ palettesSource[migratePaletteWithMap(migratedPaletteName, paletteMigrationMap, false)] ||
45
+ palettesSource[configPalette]
46
+
47
+ // Use a visible sentinel when saved palette config cannot resolve.
48
+ if (!namedPalette) {
49
+ console.warn(`Palette "${configPalette}" not found, falling back to black`)
50
+ namedPalette = [INVALID_CUSTOM_COLOR_FALLBACK]
51
+ }
35
52
 
36
53
  const domain = config.runtime.seriesLabelsAll
37
54
 
38
- const customColorsOrdered = config.general?.palette?.customColorsOrdered
55
+ const customColorsOrdered = normalizeCustomColors(config.general?.palette?.customColorsOrdered)
39
56
 
40
57
  // Check for customColorsOrdered first (direct 1-to-1 mapping, no distribution needed)
41
- if (Array.isArray(customColorsOrdered) && customColorsOrdered.length > 0) {
58
+ if (customColorsOrdered.length > 0) {
42
59
  const range = applySeriesColorAssignmentsToRange(config, domain, customColorsOrdered)
43
60
  return scaleOrdinal({
44
61
  domain,
@@ -47,26 +64,19 @@ export const getColorScale = (config: ChartConfig): ((value: string) => string)
47
64
  })
48
65
  }
49
66
 
50
- const customColors = config.general?.palette?.customColors
51
- const isUsingCustomColors = Array.isArray(customColors) && customColors.length > 0
52
- let palette =
53
- (isUsingCustomColors ? customColors : undefined) ||
54
- palettesSource[migratePaletteWithMap(migratedPaletteName, paletteMigrationMap, false)] ||
55
- palettesSource[configPalette]
56
-
57
- // Fallback to a default palette if none found
58
- if (!palette) {
59
- console.warn(`Palette "${configPalette}" not found, falling back to default`)
60
- palette = Object.values(allPalettes)[0] || ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd']
61
- }
67
+ const customColors = normalizeCustomColors(config.general?.palette?.customColors)
68
+ const isUsingCustomColors = customColors.length > 0
69
+ let palette = isUsingCustomColors ? customColors : namedPalette
62
70
 
63
71
  let numberOfKeys = config.runtime.seriesKeys.length
64
72
 
65
73
  // Apply enhanced color distribution (same logic as pie charts)
66
- const paletteVersion = getColorPaletteVersion(config)
74
+ const paletteVersion = getColorPaletteMajorVersion(config)
75
+ const v21DistributionColors = getV21ChartDistributionColors(config, palette, numberOfKeys)
67
76
 
68
- // Skip enhanced distribution if using custom colors, not v2, too many keys, or wrong palette length
69
- if (isUsingCustomColors || paletteVersion !== 2 || numberOfKeys > 9 || palette.length !== 9) {
77
+ if (v21DistributionColors) {
78
+ palette = v21DistributionColors
79
+ } else if (isUsingCustomColors || paletteVersion !== 2 || numberOfKeys > 9 || palette.length !== 9) {
70
80
  // Use existing logic for v1 palettes and other cases
71
81
  while (numberOfKeys > palette.length) {
72
82
  palette = palette.concat(palette)
@@ -78,7 +88,6 @@ export const getColorScale = (config: ChartConfig): ((value: string) => string)
78
88
  const isDivergent = configPalette && configPalette.includes('divergent')
79
89
  const isColorblindSafe =
80
90
  configPalette && (configPalette.includes('colorblindsafe') || configPalette.includes('qualitative_standard'))
81
-
82
91
  // Determine which distribution to use based on palette type
83
92
  let distributionMap = null
84
93
  if (isDivergent) {
@@ -1,4 +1,3 @@
1
- import _ from 'lodash'
2
1
  import { ChartConfig } from '../types/ChartConfig'
3
2
  import * as d3 from 'd3-array'
4
3
 
@@ -0,0 +1,69 @@
1
+ import { Axis } from '@cdc/core/types/Axis'
2
+
3
+ export type DynamicYAxisCategory = {
4
+ label: string
5
+ upperBoundKey?: string
6
+ color?: string
7
+ }
8
+
9
+ export type DynamicYAxisCategoriesConfig = {
10
+ categories: DynamicYAxisCategory[]
11
+ }
12
+
13
+ type DynamicYAxisCategoriesParams = {
14
+ config?: DynamicYAxisCategoriesConfig
15
+ data?: Record<string, any>[]
16
+ }
17
+
18
+ type DynamicYAxisCategoriesResult = {
19
+ categories: NonNullable<Axis['categories']>
20
+ axisMax: number | null
21
+ } | null
22
+
23
+ const toFiniteNumber = value => {
24
+ const number = Number(value)
25
+ return Number.isFinite(number) ? number : null
26
+ }
27
+
28
+ export const getDynamicYAxisCategories = ({
29
+ config,
30
+ data = []
31
+ }: DynamicYAxisCategoriesParams): DynamicYAxisCategoriesResult => {
32
+ if (!config || !data.length || !config.categories?.length) return null
33
+
34
+ const currentRow = data.find(row =>
35
+ config.categories.every(
36
+ (category, index) =>
37
+ (index === config.categories.length - 1 && !category.upperBoundKey) ||
38
+ toFiniteNumber(row?.[category.upperBoundKey]) !== null
39
+ )
40
+ )
41
+ if (!currentRow) return null
42
+
43
+ const upperBounds = config.categories.map(category =>
44
+ category.upperBoundKey ? toFiniteNumber(currentRow[category.upperBoundKey]) : null
45
+ )
46
+ const lastCategory = config.categories[config.categories.length - 1]
47
+ const hasOpenEndedFinalCategory = !lastCategory.upperBoundKey
48
+ const axisMax = hasOpenEndedFinalCategory ? null : upperBounds[upperBounds.length - 1]
49
+ const resolvedUpperBounds = hasOpenEndedFinalCategory ? upperBounds.slice(0, -1) : upperBounds
50
+
51
+ if (resolvedUpperBounds.some(bound => bound === null)) return null
52
+ if (!hasOpenEndedFinalCategory && (axisMax === null || axisMax <= 0)) return null
53
+
54
+ const numericUpperBounds = resolvedUpperBounds as number[]
55
+ if (numericUpperBounds.some((bound, index) => index > 0 && bound <= numericUpperBounds[index - 1])) {
56
+ return null
57
+ }
58
+
59
+ const categories = config.categories.map((category, index) => ({
60
+ label: category.label,
61
+ height:
62
+ hasOpenEndedFinalCategory && index === config.categories.length - 1
63
+ ? ''
64
+ : String(numericUpperBounds[index] - (numericUpperBounds[index - 1] || 0)),
65
+ color: category.color || '#f1f1f1'
66
+ }))
67
+
68
+ return { categories, axisMax }
69
+ }
@@ -1,5 +1,4 @@
1
1
  import { isDateScale } from '@cdc/core/helpers/cove/date'
2
- import _ from 'lodash'
3
2
  import { ChartConfig } from '../types/ChartConfig'
4
3
  export const getExcludedData = (newConfig: ChartConfig, data: object[]) => {
5
4
  if (!Array.isArray(data)) {
@@ -1,4 +1,5 @@
1
- import _ from 'lodash'
1
+ import pick from 'lodash/pick'
2
+ import values from 'lodash/values'
2
3
  import { ChartConfig } from '../types/ChartConfig'
3
4
 
4
5
  type GetHasBoundarySuppressionProps = {
@@ -15,9 +16,9 @@ export const getHasBoundarySuppression = ({ rows, config }: GetHasBoundarySuppre
15
16
 
16
17
  return boundaryRows.some(item =>
17
18
  suppressionRules.some(({ column, value }) => {
18
- const values = _.values(_.pick(item, config.runtime?.seriesKeys))
19
+ const seriesValues = values(pick(item, config.runtime?.seriesKeys))
19
20
  const dynamicCategory = config.series[0]?.dynamicCategory
20
- const match = column ? item[column] === value : values.includes(value)
21
+ const match = column ? item[column] === value : seriesValues.includes(value)
21
22
  const dynamic = dynamicCategory && (item[dynamicCategory] === column || !column)
22
23
 
23
24
  return Boolean(match || dynamic)
@@ -1,6 +1,7 @@
1
1
  import { ChartConfig } from '../types/ChartConfig'
2
2
  import { getCleanTopTickMax } from './getCleanTopTickMax'
3
3
  import { getAxisMaxOverride } from './getAxisMaxOverride'
4
+ import getStackedSeriesMax from './getStackedSeriesMax'
4
5
 
5
6
  type GetMinMaxProps = {
6
7
  /** config - standard chart config */
@@ -120,24 +121,25 @@ const getMinMax = ({
120
121
  let rightAxisSeriesItems = series.filter(s => s.axis === 'Right')
121
122
 
122
123
  const findMaxFromSeriesKeys = (data, seriesData, max, axis = 'left') => {
123
- let stackedBarMax = 0
124
- let axisSeriesKeys = seriesData.map(i => i.dataKey) || []
124
+ const stackedBarSeries =
125
+ config.visualizationSubType === 'stacked' && axis === 'left' ? seriesData.filter(s => s.type === 'Bar') : []
126
+ const stackedBarSeriesKeys = new Set(stackedBarSeries.map(s => s.dataKey))
127
+ let axisSeriesKeys = seriesData.map(i => i.dataKey).filter(key => !stackedBarSeriesKeys.has(key)) || []
125
128
 
126
129
  axisSeriesKeys.forEach(key => {
127
- let _seriesData = seriesData.find(s => s.dataKey === key)
128
130
  let _data = data.map(d => d[key])
129
131
  let seriesMax = Math.max.apply(null, _data)
130
- if (config.visualizationSubType === 'stacked' && axis === 'left' && _seriesData.type === 'Bar') {
131
- stackedBarMax += seriesMax
132
- }
133
132
  if (seriesMax > max) {
134
133
  max = seriesMax
135
134
  }
135
+ })
136
136
 
137
+ if (stackedBarSeries.length) {
138
+ const stackedBarMax = getStackedSeriesMax(data, stackedBarSeries)
137
139
  if (max < stackedBarMax) {
138
140
  max = stackedBarMax
139
141
  }
140
- })
142
+ }
141
143
  return max
142
144
  }
143
145
  leftMax = findMaxFromSeriesKeys(dataForMinMax, leftAxisSeriesItems, leftMax, 'left')
@@ -190,7 +192,8 @@ const getMinMax = ({
190
192
 
191
193
  const isCategorical = config.yAxis.type === 'categorical'
192
194
 
193
- min = enteredMinValue !== '' && isMinValid ? numEnteredMin : hasBoundarySuppression ? 0 : isCategorical ? 0 : minValue
195
+ min =
196
+ enteredMinValue !== '' && isMinValid ? numEnteredMin : hasBoundarySuppression ? 0 : isCategorical ? 0 : minValue
194
197
  }
195
198
 
196
199
  //If data value max wasn't provided, calculate it
@@ -1,4 +1,4 @@
1
- import _ from 'lodash'
1
+ import { getSeriesName } from '@cdc/core/helpers/getSeriesName'
2
2
 
3
3
  export const getNewRuntime = (visualizationConfig, newFilteredData) => {
4
4
  const runtime = visualizationConfig.runtime ? { ...visualizationConfig.runtime } : {}
@@ -25,8 +25,9 @@ export const getNewRuntime = (visualizationConfig, newFilteredData) => {
25
25
 
26
26
  runtime.seriesKeys = runtime.series
27
27
  ? runtime.series.map(series => {
28
- runtime.seriesLabels[series.dataKey] = series.name || series.label || series.dataKey
29
- runtime.seriesLabelsAll.push(series.name || series.dataKey)
28
+ const seriesName = getSeriesName(series.dataKey, { series: [series] })
29
+ runtime.seriesLabels[series.dataKey] = seriesName
30
+ runtime.seriesLabelsAll.push(seriesName)
30
31
  return series.dataKey
31
32
  })
32
33
  : []
@@ -1,13 +1,13 @@
1
- import _ from 'lodash'
1
+ import toNumber from 'lodash/toNumber'
2
2
 
3
3
  export const getPiePercent = (data: Record<string, any>[], seriesKey: string): Record<string, any>[] => {
4
4
  // getonly the numeric values for each seriesKey
5
- const numericValues = data.map(row => _.toNumber(row[seriesKey])).filter(v => !Number.isNaN(v))
5
+ const numericValues = data.map(row => toNumber(row[seriesKey])).filter(v => !Number.isNaN(v))
6
6
 
7
7
  const total = numericValues.reduce((sum, v) => sum + v, 0)
8
8
 
9
9
  return data.map(row => {
10
- const raw = _.toNumber(row[seriesKey])
10
+ const raw = toNumber(row[seriesKey])
11
11
  if (Number.isNaN(raw)) {
12
12
  // skip non-numeric / undefined
13
13
  return row
@@ -0,0 +1,51 @@
1
+ import isNumber from '@cdc/core/helpers/isNumber'
2
+
3
+ type SeriesKeyOrItem =
4
+ | string
5
+ | {
6
+ dataKey?: string
7
+ originalDataKey?: string
8
+ dynamicCategory?: boolean
9
+ }
10
+
11
+ const cleanValue = value => {
12
+ if (value === null || value === '') return ''
13
+ return typeof value === 'string' ? value.replace(/[,$]/g, '') : value
14
+ }
15
+
16
+ const getSeriesDataKey = (seriesKeyOrItem: SeriesKeyOrItem) => {
17
+ if (typeof seriesKeyOrItem === 'string') return seriesKeyOrItem
18
+ return seriesKeyOrItem.dynamicCategory && seriesKeyOrItem.originalDataKey
19
+ ? seriesKeyOrItem.originalDataKey
20
+ : seriesKeyOrItem.dataKey
21
+ }
22
+
23
+ const getStackedSeriesMax = (data: Object[] = [], seriesKeysOrSeriesItems: SeriesKeyOrItem[] = []) => {
24
+ let maxValue = Number.NEGATIVE_INFINITY
25
+ let hasValue = false
26
+
27
+ for (const row of data) {
28
+ let rowSum = 0
29
+ let hasRowValue = false
30
+
31
+ for (const seriesKeyOrItem of seriesKeysOrSeriesItems) {
32
+ const seriesKey = getSeriesDataKey(seriesKeyOrItem)
33
+ if (!seriesKey) continue
34
+
35
+ const value = cleanValue(row[seriesKey])
36
+ if (isNumber(value)) {
37
+ rowSum += Number(value)
38
+ hasRowValue = true
39
+ }
40
+ }
41
+
42
+ if (hasRowValue) {
43
+ maxValue = Math.max(maxValue, rowSum)
44
+ hasValue = true
45
+ }
46
+ }
47
+
48
+ return hasValue ? maxValue : 0
49
+ }
50
+
51
+ export default getStackedSeriesMax
@@ -0,0 +1,57 @@
1
+ import { type ChartConfig, type VisualizationType } from '../types/ChartConfig'
2
+ import {
3
+ chartV21ColorDistribution,
4
+ divergentColorDistribution,
5
+ qualitativeStandardColorDistribution
6
+ } from '@cdc/core/helpers/palettes/colorDistributions'
7
+
8
+ const V21_COLOR_DISTRIBUTION_CHART_TYPES = new Set<VisualizationType>([
9
+ 'Area Chart',
10
+ 'Bar',
11
+ 'Box Plot',
12
+ 'Bump Chart',
13
+ 'Combo',
14
+ 'Line',
15
+ 'Pie',
16
+ 'Radar',
17
+ 'Scatter Plot',
18
+ 'Spark Line'
19
+ ])
20
+
21
+ export const getV21ChartDistributionColors = (
22
+ config: ChartConfig,
23
+ palette: string[],
24
+ itemCount: number
25
+ ): string[] | undefined => {
26
+ const paletteName = config.general?.palette?.name || config.palette || ''
27
+ const hasCustomColors =
28
+ Boolean(config.general?.palette?.customColors?.length) ||
29
+ Boolean(config.general?.palette?.customColorsOrdered?.length)
30
+
31
+ if (
32
+ config.general?.palette?.version !== '2.1' ||
33
+ !V21_COLOR_DISTRIBUTION_CHART_TYPES.has(config.visualizationType) ||
34
+ hasCustomColors ||
35
+ palette.length !== 9
36
+ ) {
37
+ return undefined
38
+ }
39
+
40
+ let distributionMap: Record<number, number[]> | undefined
41
+ if (paletteName.includes('sequential')) {
42
+ distributionMap = chartV21ColorDistribution
43
+ } else if (paletteName.includes('divergent')) {
44
+ distributionMap = divergentColorDistribution
45
+ } else if (paletteName.includes('qualitative_standard')) {
46
+ distributionMap = qualitativeStandardColorDistribution
47
+ }
48
+
49
+ const distributionIndices = distributionMap?.[itemCount]
50
+ if (!distributionIndices) return undefined
51
+
52
+ const orientedIndices = paletteName.endsWith('reverse')
53
+ ? [...distributionIndices].reverse().map(index => palette.length - 1 - index)
54
+ : distributionIndices
55
+
56
+ return orientedIndices.map(index => palette[index])
57
+ }