@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
@@ -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
  }
@@ -20,6 +20,7 @@ describe('LineChart helpers', () => {
20
20
  const basePreliminaryData: PreliminaryDataItem[] = [
21
21
  {
22
22
  type: 'effect',
23
+ lineEffectScope: 'outgoing',
23
24
  seriesKeys: ['COVID-19'],
24
25
  column: 'Attribute',
25
26
  value: 'Dotted',
@@ -38,6 +39,7 @@ describe('LineChart helpers', () => {
38
39
  },
39
40
  {
40
41
  type: 'effect',
42
+ lineEffectScope: 'outgoing',
41
43
  seriesKeys: ['Influenza'],
42
44
  column: 'Attribute',
43
45
  value: 'Dotted',
@@ -56,14 +58,54 @@ describe('LineChart helpers', () => {
56
58
  }
57
59
  ]
58
60
 
61
+ describe('existing effect rules without a scope marker', () => {
62
+ it.each(['', 'Category'])('preserves adjacent segments for %s category data', dynamicCategory => {
63
+ const data = [
64
+ { Date: 'Oct.–Dec. 2024', Category: 'COVID-19', 'COVID-19': 5.2, Value: 5.2, Attribute: '' },
65
+ { Date: 'Jan.–Mar. 2025', Category: 'COVID-19', 'COVID-19': 5.7, Value: 5.7, Attribute: 'Dotted' },
66
+ { Date: 'Apr.–Jun. 2025', Category: 'COVID-19', 'COVID-19': 5.4, Value: 5.4, Attribute: 'Dotted' }
67
+ ]
68
+ const styles = createStyles({
69
+ preliminaryData: [{ ...basePreliminaryData[0], lineEffectScope: undefined, weight: 4 }],
70
+ data,
71
+ stroke: '#000',
72
+ strokeWidth: 2,
73
+ handleLineType: mockHandleLineType,
74
+ lineType: 'solid-line',
75
+ seriesKey: 'COVID-19',
76
+ dynamicCategory,
77
+ originalSeriesKey: 'Value'
78
+ })
79
+
80
+ expect(styles.map(style => style.strokeDasharray)).toEqual(['5 5', '5 5', '5 5'])
81
+ expect(styles.map(style => style.strokeWidth)).toEqual([4, 4, 4])
82
+ })
83
+
84
+ it('keeps an incoming effect when only the latest point matches', () => {
85
+ const styles = createStyles({
86
+ preliminaryData: basePreliminaryData.map(rule => ({ ...rule, lineEffectScope: undefined })),
87
+ data: [{ Attribute: '' }, { Attribute: 'Dotted' }],
88
+ stroke: '#000',
89
+ strokeWidth: 2,
90
+ handleLineType: mockHandleLineType,
91
+ lineType: 'solid-line',
92
+ seriesKey: 'COVID-19',
93
+ dynamicCategory: '',
94
+ originalSeriesKey: ''
95
+ })
96
+
97
+ expect(styles.map(style => style.strokeDasharray)).toEqual(['5 5', '5 5'])
98
+ })
99
+ })
100
+
59
101
  describe('with dynamic category data (single-row/tall format)', () => {
60
102
  const dynamicCategoryData = [
61
- { Date: '10/5/2025', Category: 'COVID-19', Value: '43.6', Attribute: 'Dotted' },
62
- { Date: '10/12/2025', Category: 'COVID-19', Value: '40.7', Attribute: '' },
63
- { Date: '10/19/2025', Category: 'COVID-19', Value: '42.6', Attribute: 'Dotted' }
103
+ { Date: '10/5/2025', Category: 'COVID-19', Value: '43.6', Attribute: '' },
104
+ { Date: '10/12/2025', Category: 'COVID-19', Value: '40.7', Attribute: 'Dotted' },
105
+ { Date: '10/19/2025', Category: 'COVID-19', Value: '42.6', Attribute: '' }
64
106
  ]
65
107
 
66
- it('should apply dashed style when Attribute matches effect value', () => {
108
+ it('should apply an isolated effect only to the segment leaving the matching point', () => {
67
109
  const styles = createStyles({
68
110
  preliminaryData: basePreliminaryData,
69
111
  data: dynamicCategoryData,
@@ -77,14 +119,51 @@ describe('LineChart helpers', () => {
77
119
  })
78
120
 
79
121
  expect(styles).toHaveLength(3)
80
- // First point has Attribute="Dotted" - should be dashed
81
- expect(styles[0].strokeDasharray).toBe('5 5')
82
- // Second point has Attribute="" - but previous point was dashed, so it inherits
83
- // Actually, the logic applies dashed to current AND previous point when effect is found
84
- // Let's check what happens
85
- expect(styles[1].strokeDasharray).toBe('5 5') // Inherits from next point's effect
86
- // Third point has Attribute="Dotted" - should be dashed
87
- expect(styles[2].strokeDasharray).toBe('5 5')
122
+ expect(styles.map(style => style.strokeDasharray)).toEqual([0, '5 5', 0])
123
+ })
124
+
125
+ it('should create a continuous effect range from consecutive matching points', () => {
126
+ const data = [
127
+ { Date: '10/5/2025', Category: 'COVID-19', Value: '43.6', Attribute: '' },
128
+ { Date: '10/12/2025', Category: 'COVID-19', Value: '40.7', Attribute: 'Dotted' },
129
+ { Date: '10/19/2025', Category: 'COVID-19', Value: '42.6', Attribute: 'Dotted' },
130
+ { Date: '10/26/2025', Category: 'COVID-19', Value: '44.1', Attribute: '' }
131
+ ]
132
+
133
+ const styles = createStyles({
134
+ preliminaryData: basePreliminaryData,
135
+ data,
136
+ stroke: '#000',
137
+ strokeWidth: 2,
138
+ handleLineType: mockHandleLineType,
139
+ lineType: 'solid-line',
140
+ seriesKey: 'COVID-19',
141
+ dynamicCategory: 'Category',
142
+ originalSeriesKey: 'Value'
143
+ })
144
+
145
+ expect(styles.map(style => style.strokeDasharray)).toEqual([0, '5 5', '5 5', 0])
146
+ })
147
+
148
+ it('should leave the incoming segment normal when only the final point matches', () => {
149
+ const data = [
150
+ { Date: '10/5/2025', Category: 'COVID-19', Value: '43.6', Attribute: '' },
151
+ { Date: '10/12/2025', Category: 'COVID-19', Value: '40.7', Attribute: 'Dotted' }
152
+ ]
153
+
154
+ const styles = createStyles({
155
+ preliminaryData: basePreliminaryData,
156
+ data,
157
+ stroke: '#000',
158
+ strokeWidth: 2,
159
+ handleLineType: mockHandleLineType,
160
+ lineType: 'solid-line',
161
+ seriesKey: 'COVID-19',
162
+ dynamicCategory: 'Category',
163
+ originalSeriesKey: 'Value'
164
+ })
165
+
166
+ expect(styles.map(style => style.strokeDasharray)).toEqual([0, 0])
88
167
  })
89
168
 
90
169
  it('should apply solid style when no effect matches', () => {
@@ -111,7 +190,7 @@ describe('LineChart helpers', () => {
111
190
  })
112
191
 
113
192
  it('should only match effects for the correct series', () => {
114
- // Using Influenza series key with COVID data should not match
193
+ // Using an unconfigured series key with COVID data should not match
115
194
  const styles = createStyles({
116
195
  preliminaryData: basePreliminaryData,
117
196
  data: dynamicCategoryData,
@@ -119,14 +198,12 @@ describe('LineChart helpers', () => {
119
198
  strokeWidth: 2,
120
199
  handleLineType: mockHandleLineType,
121
200
  lineType: 'solid-line',
122
- seriesKey: 'Influenza', // Different series
201
+ seriesKey: 'RSV', // Series not configured for an effect
123
202
  dynamicCategory: 'Category',
124
203
  originalSeriesKey: 'Value'
125
204
  })
126
205
 
127
- // No effect should match since seriesKeys is ['COVID-19'] not ['Influenza']
128
- // (assuming we use the COVID effect config)
129
- expect(styles.every(s => s.strokeDasharray === 0 || s.strokeDasharray === '10 5')).toBe(true)
206
+ expect(styles.every(style => style.strokeDasharray === 0)).toBe(true)
130
207
  })
131
208
  })
132
209
 
@@ -134,6 +211,7 @@ describe('LineChart helpers', () => {
134
211
  const multiColumnPreliminaryData: PreliminaryDataItem[] = [
135
212
  {
136
213
  type: 'effect',
214
+ lineEffectScope: 'outgoing',
137
215
  seriesKeys: ['COVID-19'],
138
216
  column: 'COVID-19-Attribute',
139
217
  value: 'Dotted',
@@ -153,12 +231,12 @@ describe('LineChart helpers', () => {
153
231
  ]
154
232
 
155
233
  const multiColumnData = [
156
- { Date: '10/5/2025', 'COVID-19': '43.6', 'COVID-19-Attribute': 'Dotted' },
157
- { Date: '10/12/2025', 'COVID-19': '40.7', 'COVID-19-Attribute': '' },
158
- { Date: '10/19/2025', 'COVID-19': '42.6', 'COVID-19-Attribute': 'Dotted' }
234
+ { Date: '10/5/2025', 'COVID-19': '43.6', 'COVID-19-Attribute': '' },
235
+ { Date: '10/12/2025', 'COVID-19': '40.7', 'COVID-19-Attribute': 'Dotted' },
236
+ { Date: '10/19/2025', 'COVID-19': '42.6', 'COVID-19-Attribute': '' }
159
237
  ]
160
238
 
161
- it('should apply dashed style when attribute column matches', () => {
239
+ it('should apply an isolated effect only to the segment leaving the matching point', () => {
162
240
  const styles = createStyles({
163
241
  preliminaryData: multiColumnPreliminaryData,
164
242
  data: multiColumnData,
@@ -172,12 +250,7 @@ describe('LineChart helpers', () => {
172
250
  })
173
251
 
174
252
  expect(styles).toHaveLength(3)
175
- // First point has attribute - dashed
176
- expect(styles[0].strokeDasharray).toBe('5 5')
177
- // Second point inherits from third point's effect
178
- expect(styles[1].strokeDasharray).toBe('5 5')
179
- // Third point has attribute - dashed
180
- expect(styles[2].strokeDasharray).toBe('5 5')
253
+ expect(styles.map(style => style.strokeDasharray)).toEqual([0, '5 5', 0])
181
254
  })
182
255
  })
183
256
 
@@ -186,6 +259,7 @@ describe('LineChart helpers', () => {
186
259
  const preliminaryDataWithWeight: PreliminaryDataItem[] = [
187
260
  {
188
261
  type: 'effect',
262
+ lineEffectScope: 'outgoing',
189
263
  seriesKeys: ['COVID-19'],
190
264
  column: 'Attribute',
191
265
  value: 'Dotted',
@@ -206,9 +280,9 @@ describe('LineChart helpers', () => {
206
280
  ]
207
281
 
208
282
  const data = [
209
- { Date: '10/5/2025', Category: 'COVID-19', Value: '43.6', Attribute: 'Dotted' },
210
- { Date: '10/12/2025', Category: 'COVID-19', Value: '40.7', Attribute: '' },
211
- { Date: '10/19/2025', Category: 'COVID-19', Value: '42.6', Attribute: 'Dotted' }
283
+ { Date: '10/5/2025', Category: 'COVID-19', Value: '43.6', Attribute: '' },
284
+ { Date: '10/12/2025', Category: 'COVID-19', Value: '40.7', Attribute: 'Dotted' },
285
+ { Date: '10/19/2025', Category: 'COVID-19', Value: '42.6', Attribute: '' }
212
286
  ]
213
287
 
214
288
  const styles = createStyles({
@@ -224,18 +298,15 @@ describe('LineChart helpers', () => {
224
298
  })
225
299
 
226
300
  expect(styles).toHaveLength(3)
227
- // First point has effect with custom weight
228
- expect(styles[0].strokeWidth).toBe(5)
229
- // Second point inherits custom weight from next point's effect
230
- expect(styles[1].strokeWidth).toBe(5)
231
- // Third point has effect with custom weight
232
- expect(styles[2].strokeWidth).toBe(5)
301
+ expect(styles.map(style => style.strokeWidth)).toEqual([2, 5, 2])
302
+ expect(styles.map(style => style.strokeDasharray)).toEqual([0, '5 5', 0])
233
303
  })
234
304
 
235
305
  it('should use default strokeWidth when preliminaryData has no weight', () => {
236
306
  const preliminaryDataNoWeight: PreliminaryDataItem[] = [
237
307
  {
238
308
  type: 'effect',
309
+ lineEffectScope: 'outgoing',
239
310
  seriesKeys: ['COVID-19'],
240
311
  column: 'Attribute',
241
312
  value: 'Dotted',
@@ -287,6 +358,7 @@ describe('LineChart helpers', () => {
287
358
  const preliminaryData: PreliminaryDataItem[] = [
288
359
  {
289
360
  type: 'effect',
361
+ lineEffectScope: 'outgoing',
290
362
  seriesKeys: ['COVID-19'],
291
363
  column: 'Attribute',
292
364
  value: 'Dotted',
@@ -306,7 +378,10 @@ describe('LineChart helpers', () => {
306
378
  }
307
379
  ]
308
380
 
309
- const data = [{ Date: '10/5/2025', Category: 'COVID-19', Value: '43.6', Attribute: 'Dotted' }]
381
+ const data = [
382
+ { Date: '10/5/2025', Category: 'COVID-19', Value: '43.6', Attribute: 'Dotted' },
383
+ { Date: '10/12/2025', Category: 'COVID-19', Value: '40.7', Attribute: '' }
384
+ ]
310
385
 
311
386
  const styles = createStyles({
312
387
  preliminaryData,
@@ -320,7 +395,7 @@ describe('LineChart helpers', () => {
320
395
  originalSeriesKey: 'Value'
321
396
  })
322
397
 
323
- expect(styles).toHaveLength(1)
398
+ expect(styles).toHaveLength(2)
324
399
  expect(styles[0].strokeWidth).toBe(expected)
325
400
  })
326
401
  })
@@ -570,5 +645,18 @@ describe('LineChart helpers', () => {
570
645
  expect(suppressedSegments.length).toBeGreaterThan(0)
571
646
  expect(suppressedSegments.every(segment => segment.weight === undefined)).toBe(true)
572
647
  })
648
+
649
+ it('returns no segments when a brushed dynamic-category series has no rows', () => {
650
+ const segments = createDataSegments({
651
+ data: [],
652
+ seriesKey: 'Influenza',
653
+ preliminaryData: [makeSuppression({ seriesKeys: ['Influenza'] })],
654
+ dynamicCategory: 'Category',
655
+ originalSeriesKey: 'Value',
656
+ colorScale: mockColorScale
657
+ })
658
+
659
+ expect(segments).toEqual([])
660
+ })
573
661
  })
574
662
  })
@@ -55,16 +55,19 @@ export const createStyles = (props: StyleProps): Style[] => {
55
55
  }
56
56
 
57
57
  data.forEach((d, index) => {
58
- const matchingPd: PreliminaryDataItem = getMatchingPd(d)
58
+ const matchingPd = getMatchingPd(d)
59
+ const preserveAdjacentSegments =
60
+ matchingPd && !matchingPd.style.includes('Circles') && matchingPd.lineEffectScope !== 'outgoing'
61
+ const hasStyledSegment = matchingPd && (preserveAdjacentSegments || index < data.length - 1)
59
62
 
60
- let style: Style = matchingPd
63
+ const style: Style = hasStyledSegment
61
64
  ? createStyle(handleLineType(matchingPd.style), matchingPd.weight)
62
65
  : createStyle(handleLineType(lineType))
63
66
 
64
67
  styles.push(style)
65
68
 
66
- // If matchingPd exists, update the previous style if there is a previous element
67
- if (matchingPd && index > 0) {
69
+ // Existing rules also style the incoming segment, including a preliminary final point.
70
+ if (preserveAdjacentSegments && index > 0) {
68
71
  styles[index - 1] = createStyle(handleLineType(matchingPd.style), matchingPd.weight)
69
72
  }
70
73
  })
@@ -297,6 +300,8 @@ export const createDataSegments = props => {
297
300
 
298
301
  return (props.preliminaryData ?? []).some(pd => d?.[props.dynamicCategory] === props?.seriesKey)
299
302
  })
303
+ if (dynamicData.length === 0) return []
304
+
300
305
  const isSuppressed = (pd, dataItem) => {
301
306
  if (pd.type === 'effect' || pd.hideLineStyle) return false
302
307