@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
@@ -3,6 +3,13 @@ import { describe, expect, it, vi } from 'vitest'
3
3
  import { createFormatLabels } from './createFormatLabels'
4
4
  import { ChartConfig } from '../../../types/ChartConfig'
5
5
  import { Label } from '../../../types/Label'
6
+ import chroma from 'chroma-js'
7
+ import {
8
+ getForecastConfidenceIntervalGroups,
9
+ getForecastConfidenceIntervalKey,
10
+ getForecastLegendKey,
11
+ isForecastLegendSelectionActive
12
+ } from '../../../helpers/forecastLegend'
6
13
 
7
14
  const defaultLabels: Label[] = [
8
15
  { datum: 'Series A', index: 0, text: 'Series A', value: '#aa0000' },
@@ -86,3 +93,222 @@ describe('createFormatLabels small multiples legend colors', () => {
86
93
  expect(labels.map(label => label.value)).toEqual(['#aa0000', '#aa0000'])
87
94
  })
88
95
  })
96
+ describe('createFormatLabels category-colored bar legends', () => {
97
+ it('uses the same consecutive palette colors as category-colored bars', () => {
98
+ const config = buildConfig({
99
+ visualizationType: 'Bar',
100
+ visualizationSubType: 'regular',
101
+ general: {
102
+ palette: {
103
+ name: 'sequential_blue',
104
+ version: '2.0'
105
+ }
106
+ } as any,
107
+ legend: { colorCode: 'Group' } as any,
108
+ smallMultiples: undefined as any,
109
+ series: [{ dataKey: 'value', name: 'Value', type: 'Bar' }] as any
110
+ })
111
+ const tableData = [{ Group: 'Group A' }, { Group: 'Group B' }]
112
+
113
+ const labels = createFormatLabels(config, tableData, tableData, colorScale, vi.fn())(defaultLabels)
114
+
115
+ expect(labels.map(label => [label.text, label.value])).toEqual([
116
+ ['Group A', '#DBE8F7'],
117
+ ['Group B', '#BED5ED']
118
+ ])
119
+ })
120
+ })
121
+
122
+ const buildForecastConfig = (confidenceIntervals: any[]): ChartConfig =>
123
+ buildConfig({
124
+ visualizationType: 'Combo',
125
+ smallMultiples: undefined,
126
+ general: { palette: { name: 'qualitative_standard', version: '2.0' } } as any,
127
+ legend: { expandForecastConfidenceIntervals: true } as any,
128
+ series: [
129
+ {
130
+ dataKey: 'stage',
131
+ type: 'Forecasting',
132
+ confidenceIntervals,
133
+ stages: [
134
+ { key: 'Observed', color: 'sequential-blue' },
135
+ { key: 'Forecast', color: 'sequential-green' }
136
+ ]
137
+ }
138
+ ] as any,
139
+ runtime: {
140
+ seriesKeys: ['Observed', 'Forecast'],
141
+ forecastingSeriesKeys: [
142
+ {
143
+ confidenceIntervals,
144
+ stages: [
145
+ { key: 'Observed', color: 'sequential-blue' },
146
+ { key: 'Forecast', color: 'sequential-green' }
147
+ ]
148
+ }
149
+ ]
150
+ } as any
151
+ })
152
+
153
+ describe('createFormatLabels forecast confidence interval legends', () => {
154
+ it('orders resolved percentages numerically and assigns an independent identity to each interval and stage', () => {
155
+ const labels = formatLabels(
156
+ buildForecastConfig([
157
+ { low: 'Lower 90%', high: 'Upper 90%' },
158
+ { low: 'Lower 50%', high: 'Upper 50%' }
159
+ ])
160
+ )
161
+
162
+ expect(labels.map(label => [label.text, label.datum])).toEqual([
163
+ ['50% CI Observed', getForecastLegendKey('Observed', 'percentage:50')],
164
+ ['50% CI Forecast', getForecastLegendKey('Forecast', 'percentage:50')],
165
+ ['90% CI Observed', getForecastLegendKey('Observed', 'percentage:90')],
166
+ ['90% CI Forecast', getForecastLegendKey('Forecast', 'percentage:90')]
167
+ ])
168
+ expect(chroma(labels[0].value).alpha()).toBeCloseTo(0.75)
169
+ expect(chroma(labels[2].value).alpha()).toBeCloseTo(0.5)
170
+ })
171
+
172
+ it('collapses duplicate percentages while retaining their effective shade', () => {
173
+ const labels = formatLabels(
174
+ buildForecastConfig([
175
+ { low: 'Lower 90%', high: 'Upper 90%' },
176
+ { low: 'Low 90', high: 'High 90' }
177
+ ])
178
+ )
179
+
180
+ expect(labels.map(label => label.text)).toEqual(['90% CI Observed', '90% CI Forecast'])
181
+ expect(chroma(labels[0].value).alpha()).toBeCloseTo(0.75)
182
+ })
183
+
184
+ it('uses stable fallbacks and omits incomplete intervals', () => {
185
+ const labels = formatLabels(
186
+ buildForecastConfig([
187
+ { low: 'lower', high: 'upper' },
188
+ { low: '', high: 'Upper 80%' },
189
+ { low: 'minimum', high: 'maximum' }
190
+ ])
191
+ )
192
+
193
+ expect(labels.map(label => label.text)).toEqual([
194
+ 'CI 1 Observed',
195
+ 'CI 1 Forecast',
196
+ 'CI 2 Observed',
197
+ 'CI 2 Forecast'
198
+ ])
199
+ })
200
+
201
+ it('falls back when bounds do not contain the same single percentage', () => {
202
+ const labels = formatLabels(
203
+ buildForecastConfig([
204
+ { low: 'Lower 50%', high: 'Upper 95%' },
205
+ { low: 'Lower 50 to 90%', high: 'Upper 50 to 90%' }
206
+ ])
207
+ )
208
+
209
+ expect(labels.map(label => label.text)).toEqual([
210
+ 'CI 1 Observed',
211
+ 'CI 1 Forecast',
212
+ 'CI 2 Observed',
213
+ 'CI 2 Forecast'
214
+ ])
215
+ })
216
+
217
+ it('keeps a single complete interval stage-only', () => {
218
+ const labels = formatLabels(
219
+ buildForecastConfig([
220
+ { low: 'Lower 50%', high: 'Upper 50%' },
221
+ { low: '', high: 'Upper 90%' }
222
+ ])
223
+ )
224
+
225
+ expect(labels.map(label => [label.text, label.datum])).toEqual([
226
+ ['Observed', 'Observed'],
227
+ ['Forecast', 'Forecast']
228
+ ])
229
+ expect(labels.every(label => chroma(label.value).alpha() === 1)).toBe(true)
230
+ })
231
+
232
+ it.each([undefined, false])('keeps multi-interval legends stage-only when expansion is %s', expansionValue => {
233
+ const config = buildForecastConfig([
234
+ { low: 'Lower 50%', high: 'Upper 50%' },
235
+ { low: 'Lower 90%', high: 'Upper 90%' }
236
+ ])
237
+ config.legend.expandForecastConfidenceIntervals = expansionValue
238
+
239
+ expect(formatLabels(config).map(label => [label.text, label.datum])).toEqual([
240
+ ['Observed', 'Observed'],
241
+ ['Forecast', 'Forecast']
242
+ ])
243
+ })
244
+
245
+ it('does not infer percentages outside the valid percentage range', () => {
246
+ const labels = formatLabels(
247
+ buildForecastConfig([
248
+ { low: 'lower_2024', high: 'upper_2024' },
249
+ { low: 'Lower 50%', high: 'Upper 50%' }
250
+ ])
251
+ )
252
+
253
+ expect(labels.map(label => label.text)).toEqual([
254
+ '50% CI Observed',
255
+ '50% CI Forecast',
256
+ 'CI 1 Observed',
257
+ 'CI 1 Forecast'
258
+ ])
259
+ })
260
+
261
+ it('preserves interval-major numeric ordering when legacy legend ordering options are enabled', () => {
262
+ const config = buildForecastConfig([
263
+ { low: 'Lower 90%', high: 'Upper 90%' },
264
+ { low: 'Lower 50%', high: 'Upper 50%' }
265
+ ])
266
+ config.legend.reverseLabelOrder = true
267
+ config.legend.verticalSorted = true
268
+
269
+ expect(formatLabels(config).map(label => label.text)).toEqual([
270
+ '50% CI Observed',
271
+ '50% CI Forecast',
272
+ '90% CI Observed',
273
+ '90% CI Forecast'
274
+ ])
275
+ })
276
+ })
277
+
278
+ describe('forecast confidence interval interaction identity', () => {
279
+ const confidenceIntervals = [
280
+ { low: 'Lower 90%', high: 'Upper 90%' },
281
+ { low: 'Lower 50%', high: 'Upper 50%' }
282
+ ]
283
+
284
+ it('maps configured intervals to their normalized legend keys', () => {
285
+ expect(getForecastConfidenceIntervalKey(confidenceIntervals, 0)).toBe('percentage:90')
286
+ expect(getForecastConfidenceIntervalKey(confidenceIntervals, 1)).toBe('percentage:50')
287
+ expect(getForecastLegendKey('Forecast: west', 'percentage:50')).toBe(
288
+ 'forecast-ci:Forecast%3A%20west:percentage%3A50'
289
+ )
290
+ })
291
+
292
+ it('activates only a matching composite row while accepting legacy stage selections', () => {
293
+ const selectedKey = getForecastLegendKey('Forecast', 'percentage:50')
294
+
295
+ expect(isForecastLegendSelectionActive([selectedKey], 'Forecast', 'percentage:50')).toBe(true)
296
+ expect(isForecastLegendSelectionActive([selectedKey], 'Forecast', 'percentage:90')).toBe(false)
297
+ expect(isForecastLegendSelectionActive([selectedKey], 'Observed', 'percentage:50')).toBe(false)
298
+ expect(isForecastLegendSelectionActive(['Forecast'], 'Forecast', 'percentage:50')).toBe(true)
299
+ expect(isForecastLegendSelectionActive(['Forecast'], 'Forecast', 'percentage:90')).toBe(true)
300
+ })
301
+
302
+ it('shares one composite identity across duplicate resolved percentages', () => {
303
+ const duplicateIntervals = [
304
+ { low: 'Lower 90%', high: 'Upper 90%' },
305
+ { low: 'Low 90', high: 'High 90' }
306
+ ]
307
+ const groups = getForecastConfidenceIntervalGroups(duplicateIntervals)
308
+
309
+ expect(groups).toHaveLength(1)
310
+ expect(groups[0].intervalIndexes).toEqual([0, 1])
311
+ expect(getForecastConfidenceIntervalKey(duplicateIntervals, 0)).toBe('percentage:90')
312
+ expect(getForecastConfidenceIntervalKey(duplicateIntervals, 1)).toBe('percentage:90')
313
+ })
314
+ })
@@ -7,13 +7,13 @@ import {
7
7
  import { getCurrentPaletteName, getFallbackColorPalette, migratePaletteWithMap } from '@cdc/core/helpers/palettes/utils'
8
8
  import { chartPaletteMigrationMap, paletteMigrationMap } from '@cdc/core/helpers/palettes/migratePaletteName'
9
9
  import { getPaletteAccessor } from '@cdc/core/helpers/getPaletteAccessor'
10
- import { getColorPaletteVersion } from '@cdc/core/helpers/getColorPaletteVersion'
10
+ import { getColorPaletteMajorVersion } from '@cdc/core/helpers/getColorPaletteMajorVersion'
11
11
  import { isV1Palette } from '@cdc/core/helpers/palettes/utils'
12
- import { v2ColorDistribution } from '@cdc/core/helpers/palettes/colorDistributions'
13
12
  import { updatePaletteNames } from '@cdc/core/helpers/updatePaletteNames'
14
13
  import { buildForecastPaletteMappings } from '../../../helpers/buildForecastPaletteMappings'
15
14
  import { getFullColorPalette } from '../../../helpers/smallMultiplesHelpers'
16
15
  import { hasSeriesColorAssignmentOverrides } from '../../../helpers/colorAssignmentHelpers'
16
+ import { getColorCodeCategoryColorMap } from '../../../helpers/getColorCodeCategoryColorMap'
17
17
  import { FaStar } from 'react-icons/fa'
18
18
  import { Label } from '../../../types/Label'
19
19
  import { ColorScale, TransformedData } from '../../../types/ChartContext'
@@ -23,6 +23,8 @@ import sortBy from 'lodash/sortBy'
23
23
  import { scaleSequential } from 'd3-scale'
24
24
  import { interpolateRgbBasis } from 'd3-interpolate'
25
25
  import { filterChartColorPalettes } from '@cdc/core/helpers/filterColorPalettes'
26
+ import chroma from 'chroma-js'
27
+ import { getForecastConfidenceIntervalGroups, getForecastLegendKey } from '../../../helpers/forecastLegend'
26
28
 
27
29
  export const createFormatLabels =
28
30
  (
@@ -239,46 +241,13 @@ export const createFormatLabels =
239
241
  return reverseLabels([labelBelow, labelAbove])
240
242
  }
241
243
  if (visualizationType === 'Bar' && visualizationSubType === 'regular' && colorCode && series?.length === 1) {
242
- const currentPaletteName = getCurrentPaletteName(config) || getFallbackColorPalette(config)
243
- const paletteName = migratePaletteWithMap(currentPaletteName, chartPaletteMigrationMap, true)
244
- let palette = getPaletteAccessor(colorPalettes, config, paletteName)
245
-
246
- const numberOfKeys = data.length
247
-
248
- // Check if we should use v2 distribution logic for better contrast
249
- const version = getColorPaletteVersion(config)
250
- const isSequentialOrDivergent =
251
- paletteName && (paletteName.includes('sequential') || paletteName.includes('divergent'))
252
- const isPairedBarOrDeviation = ['Paired Bar', 'Deviation Bar'].includes(config.visualizationType)
253
- const useV2Distribution =
254
- version === 2 && isSequentialOrDivergent && palette.length === 9 && numberOfKeys <= 9 && !isPairedBarOrDeviation
255
-
256
- if (useV2Distribution && v2ColorDistribution[numberOfKeys]) {
257
- // Use strategic color distribution for v2 sequential palettes
258
- const distributionIndices = v2ColorDistribution[numberOfKeys]
259
- palette = distributionIndices.map(index => palette[index])
260
- } else {
261
- // Use existing logic for v1 palettes and other cases
262
- while (tableData.length > palette?.length) {
263
- palette = palette.concat(palette)
264
- }
265
- palette = palette?.slice(0, data.length)
266
- }
267
- //store unique values to Set by colorCode
268
- const set = new Set()
269
-
270
- tableData.forEach(d => set.add(d[colorCode]))
271
-
272
- // create labels with unique values
273
- const uniqueLabels = Array.from(set).map((val, i) => {
274
- const newLabel = {
275
- datum: val,
276
- index: i,
277
- text: val,
278
- value: palette?.[i]
279
- }
280
- return newLabel
281
- })
244
+ const categoryColors = getColorCodeCategoryColorMap(config, tableData, filterChartColorPalettes(config))
245
+ const uniqueLabels = Array.from(categoryColors, ([category, color], index) => ({
246
+ datum: category,
247
+ index,
248
+ text: String(category),
249
+ value: color
250
+ }))
282
251
 
283
252
  return reverseLabels(uniqueLabels)
284
253
  }
@@ -286,10 +255,11 @@ export const createFormatLabels =
286
255
  // get forecasting items inside of combo
287
256
  if (runtime?.forecastingSeriesKeys?.length > 0) {
288
257
  let seriesLabels = []
258
+ let hasExpandedForecastIntervals = false
289
259
 
290
260
  // Create palette lookup map - use version-specific palettes
291
261
  // Forecasting charts use sequentialPalettes for v1, sequential-only palettes for v2
292
- const paletteVersion = getColorPaletteVersion(config)
262
+ const paletteVersion = getColorPaletteMajorVersion(config)
293
263
 
294
264
  let forecastPalettes
295
265
  if (paletteVersion === 1) {
@@ -311,19 +281,43 @@ export const createFormatLabels =
311
281
 
312
282
  //store unique values to Set by colorCode
313
283
  // loop through each stage/group/area on the chart and create a label
314
- config.runtime?.forecastingSeriesKeys?.map((outerGroup, index) => {
315
- return outerGroup?.stages?.map((stage, index) => {
284
+ config.runtime?.forecastingSeriesKeys?.map(outerGroup => {
285
+ const confidenceIntervalLabels =
286
+ legend.expandForecastConfidenceIntervals === true
287
+ ? getForecastConfidenceIntervalGroups(outerGroup?.confidenceIntervals)
288
+ : []
289
+
290
+ const stages = outerGroup?.stages || []
291
+
292
+ if (confidenceIntervalLabels.length) {
293
+ hasExpandedForecastIntervals = true
294
+ confidenceIntervalLabels.forEach(confidenceInterval => {
295
+ stages.forEach(stage => {
296
+ const palette = forecastingPalettes[stage.color] || false
297
+ const colorValue = palette?.[2] || '#ccc'
298
+
299
+ seriesLabels.push({
300
+ datum: getForecastLegendKey(stage.key, confidenceInterval.key),
301
+ index: seriesLabels.length,
302
+ text: `${confidenceInterval.label} ${stage.key}`,
303
+ value: chroma(colorValue).alpha(confidenceInterval.opacity).css()
304
+ })
305
+ })
306
+ })
307
+
308
+ return
309
+ }
310
+
311
+ return stages.map(stage => {
316
312
  const palette = forecastingPalettes[stage.color] || false
317
313
  let colorValue = palette?.[2] || '#ccc'
318
314
 
319
- const newLabel = {
315
+ seriesLabels.push({
320
316
  datum: stage.key,
321
- index: index,
317
+ index: seriesLabels.length,
322
318
  text: stage.key,
323
319
  value: colorValue
324
- }
325
-
326
- seriesLabels.push(newLabel)
320
+ })
327
321
  })
328
322
  })
329
323
 
@@ -345,7 +339,7 @@ export const createFormatLabels =
345
339
  seriesLabels.push(newLabel)
346
340
  })
347
341
 
348
- return reverseLabels(seriesLabels)
342
+ return hasExpandedForecastIntervals ? seriesLabels : reverseLabels(seriesLabels)
349
343
  }
350
344
 
351
345
  // For Radar charts, use the entity names from runtime.seriesKeys (set from xAxis.dataKey values)
@@ -16,6 +16,7 @@ type LineChartCircleProps = {
16
16
  colorScale: any
17
17
  parseDate: any
18
18
  seriesAxis: string
19
+ seriesColor: string
19
20
  dataIndex: number
20
21
  seriesIndex: number
21
22
  mode: 'ISOLATED_POINTS' | 'HOVER_POINTS' | 'ALWAYS_SHOW_POINTS' | 'TOOLTIP_POINTS'
@@ -49,6 +50,7 @@ const LineChartCircle = (props: LineChartCircleProps) => {
49
50
  d: pointData,
50
51
  displayArea,
51
52
  seriesKey,
53
+ seriesColor,
52
54
  xScale,
53
55
  yScale,
54
56
  colorScale,
@@ -136,15 +138,13 @@ const LineChartCircle = (props: LineChartCircleProps) => {
136
138
  : dataIndex
137
139
 
138
140
  if (drawIsolatedPoints(_dataIndex, seriesKey)) {
139
- const color = colorScale(config.runtime.seriesLabelsAll[seriesIndex])
140
-
141
141
  return (
142
142
  <g
143
143
  transform={transformShape(pointData[config.xAxis?.dataKey], pointData[filtered?.dataKey])}
144
144
  className={`visx-glyph-group${displayArea ? '' : '-hidden'}`}
145
145
  data-seriesindex={seriesIndex}
146
146
  >
147
- <Shape size={dotSize} stroke={color} fill={color} />
147
+ <Shape size={dotSize} stroke={seriesColor} fill={seriesColor} />
148
148
  </g>
149
149
  )
150
150
  }