@cdc/chart 4.25.10 → 4.26.1

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 (135) hide show
  1. package/dist/{cdcchart-1a1724a1.es.js → cdcchart-dgT_1dIT.es.js} +136 -151
  2. package/dist/cdcchart.js +44003 -43518
  3. package/examples/feature/__data__/planet-example-data.json +1 -1
  4. package/examples/feature/boxplot/valid-boxplot.csv +38 -17
  5. package/examples/feature/pie/planet-pie-example-config.json +48 -2
  6. package/examples/private/DEV-11825.json +573 -0
  7. package/examples/private/DEV-12100.json +1303 -0
  8. package/examples/private/cat-y.json +1235 -0
  9. package/examples/private/data-points.json +228 -0
  10. package/examples/private/height.json +3915 -0
  11. package/examples/private/links.json +569 -0
  12. package/examples/private/na.json +913 -0
  13. package/examples/private/quadrant.txt +30 -0
  14. package/examples/private/test-data.csv +28 -0
  15. package/examples/private/test-forecast.json +5510 -0
  16. package/examples/private/warming-stripe-test.json +2578 -0
  17. package/examples/private/warming-stripes.json +4763 -0
  18. package/examples/tech-adoption-with-links.json +560 -0
  19. package/index.html +16 -140
  20. package/package.json +6 -5
  21. package/preview.html +1616 -0
  22. package/src/CdcChart.tsx +8 -11
  23. package/src/CdcChartComponent.tsx +329 -124
  24. package/src/_stories/Chart.Combo.stories.tsx +18 -0
  25. package/src/_stories/Chart.Forecast.stories.tsx +36 -0
  26. package/src/_stories/Chart.HTMLInDataTable.stories.tsx +520 -0
  27. package/src/_stories/Chart.Patterns.stories.tsx +2 -1
  28. package/src/_stories/Chart.PreserveDecimals.stories.tsx +220 -0
  29. package/src/_stories/Chart.Regions.Categorical.stories.tsx +148 -0
  30. package/src/_stories/Chart.Regions.DateScale.stories.tsx +197 -0
  31. package/src/_stories/Chart.Regions.DateTimeScale.stories.tsx +297 -0
  32. package/src/_stories/Chart.SmallMultiples.stories.tsx +47 -0
  33. package/src/_stories/Chart.stories.tsx +8 -0
  34. package/src/_stories/ChartAnnotation.stories.tsx +6 -3
  35. package/src/_stories/ChartBar.Editor.stories.tsx +3585 -0
  36. package/src/_stories/ChartBrush.Editor.stories.tsx +295 -0
  37. package/src/_stories/ChartBrush.stories.tsx +50 -0
  38. package/src/_stories/ChartEditor.Editor.stories.tsx +656 -0
  39. package/src/_stories/ChartEditor.stories.tsx +1 -2
  40. package/src/_stories/TechAdoptionWithLinks.stories.tsx +27 -0
  41. package/src/_stories/_mock/brush_enabled.json +326 -0
  42. package/src/_stories/_mock/brush_mock.json +2 -69
  43. package/src/_stories/_mock/combo.json +451 -0
  44. package/src/_stories/_mock/editor-test-configs.json +376 -0
  45. package/src/_stories/_mock/editor-test-datasets.json +477 -0
  46. package/src/_stories/_mock/editor-tests/bar-chart-editor-test.json +255 -0
  47. package/src/_stories/_mock/editor-tests/bar-chart-general-test.json +267 -0
  48. package/src/_stories/_mock/editor-tests/bar-chart-test.json +237 -0
  49. package/src/_stories/_mock/forecast_combo_with_gaps.json +913 -0
  50. package/src/_stories/_mock/horizontal-bars-dynamic-y-axis.json +413 -0
  51. package/src/_stories/_mock/pie_config.json +257 -62
  52. package/src/_stories/_mock/small_multiples/small_multiples_bars.json +1944 -0
  53. package/src/_stories/_mock/small_multiples/small_multiples_big_data_bars.json +1114 -0
  54. package/src/_stories/_mock/small_multiples/small_multiples_lines.json +2646 -0
  55. package/src/_stories/_mock/small_multiples/small_multiples_lines_colors.json +1305 -0
  56. package/src/_stories/_mock/small_multiples/small_multiples_stacked_bars.json +1936 -0
  57. package/src/components/Annotations/components/findNearestDatum.ts +6 -41
  58. package/src/components/AreaChart/components/AreaChart.Stacked.jsx +10 -7
  59. package/src/components/AreaChart/index.tsx +1 -2
  60. package/src/components/Axis/Categorical.Axis.tsx +6 -7
  61. package/src/components/BarChart/components/BarChart.Horizontal.tsx +181 -27
  62. package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +3 -1
  63. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +1 -0
  64. package/src/components/BarChart/components/BarChart.Vertical.tsx +8 -9
  65. package/src/components/BarChart/components/context.tsx +1 -0
  66. package/src/components/BarChart/helpers/useBarChart.ts +14 -2
  67. package/src/components/BoxPlot/helpers/index.ts +3 -3
  68. package/src/components/Brush/BrushSelector.tsx +1258 -0
  69. package/src/components/Brush/MiniChartPreview.tsx +283 -0
  70. package/src/components/DeviationBar.jsx +9 -7
  71. package/src/components/EditorPanel/EditorPanel.tsx +2720 -2586
  72. package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +96 -111
  73. package/src/components/EditorPanel/components/Panels/Panel.ForestPlotSettings.tsx +56 -34
  74. package/src/components/EditorPanel/components/Panels/Panel.General.tsx +76 -31
  75. package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +104 -55
  76. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +54 -49
  77. package/src/components/EditorPanel/components/Panels/Panel.SmallMultiples.tsx +427 -0
  78. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +96 -48
  79. package/src/components/EditorPanel/components/Panels/index.tsx +3 -1
  80. package/src/components/EditorPanel/editor-panel.scss +0 -20
  81. package/src/components/EditorPanel/useEditorPermissions.ts +36 -31
  82. package/src/components/Forecasting/Forecasting.tsx +139 -21
  83. package/src/components/Legend/Legend.Component.tsx +16 -9
  84. package/src/components/Legend/Legend.tsx +3 -2
  85. package/src/components/Legend/helpers/createFormatLabels.tsx +325 -176
  86. package/src/components/Legend/helpers/getLegendClasses.ts +0 -1
  87. package/src/components/Legend/helpers/index.ts +10 -6
  88. package/src/components/LineChart/LineChartProps.ts +0 -3
  89. package/src/components/LineChart/helpers.ts +1 -1
  90. package/src/components/LineChart/index.tsx +36 -13
  91. package/src/components/LinearChart.tsx +559 -499
  92. package/src/components/PairedBarChart.jsx +20 -3
  93. package/src/components/Regions/components/Regions.tsx +366 -144
  94. package/src/components/Sankey/types/index.ts +1 -1
  95. package/src/components/ScatterPlot/ScatterPlot.jsx +2 -2
  96. package/src/components/SmallMultiples/SmallMultipleTile.tsx +202 -0
  97. package/src/components/SmallMultiples/SmallMultiples.css +32 -0
  98. package/src/components/SmallMultiples/SmallMultiples.tsx +271 -0
  99. package/src/components/SmallMultiples/index.ts +2 -0
  100. package/src/components/WarmingStripes/WarmingStripes.tsx +160 -0
  101. package/src/components/WarmingStripes/WarmingStripesGradientLegend.css +35 -0
  102. package/src/components/WarmingStripes/WarmingStripesGradientLegend.tsx +104 -0
  103. package/src/components/WarmingStripes/index.tsx +3 -0
  104. package/src/data/initial-state.js +16 -2
  105. package/src/helpers/buildForecastPaletteOptions.ts +0 -38
  106. package/src/helpers/calculateHorizontalBarCategoryLabelWidth.ts +57 -0
  107. package/src/helpers/getColorScale.ts +10 -0
  108. package/src/{hooks/useMinMax.ts → helpers/getMinMax.ts} +26 -14
  109. package/src/helpers/getYAxisAutoPadding.ts +53 -0
  110. package/src/helpers/sizeHelpers.ts +0 -20
  111. package/src/helpers/smallMultiplesHelpers.ts +529 -0
  112. package/src/hooks/useChartHoverAnalytics.tsx +10 -9
  113. package/src/hooks/useProgrammaticTooltip.ts +96 -0
  114. package/src/hooks/useScales.ts +98 -34
  115. package/src/hooks/useSmallMultipleSynchronization.ts +59 -0
  116. package/src/hooks/useTooltip.tsx +91 -25
  117. package/src/scss/DataTable.scss +0 -4
  118. package/src/scss/main.scss +18 -83
  119. package/src/store/chart.actions.ts +2 -0
  120. package/src/store/chart.reducer.ts +4 -0
  121. package/src/test/CdcChart.test.jsx +1 -1
  122. package/src/types/ChartConfig.ts +27 -6
  123. package/src/types/ChartContext.ts +3 -0
  124. package/src/types/Label.ts +1 -0
  125. package/src/utils/analyticsTracking.ts +19 -0
  126. package/LICENSE +0 -201
  127. package/src/_stories/_mock/pie_data.json +0 -218
  128. package/src/components/AreaChart/components/AreaChart.jsx +0 -109
  129. package/src/components/Brush/BrushChart.tsx +0 -128
  130. package/src/components/Brush/BrushController.tsx +0 -71
  131. package/src/components/Brush/types.tsx +0 -8
  132. package/src/components/BrushChart.tsx +0 -223
  133. package/src/helpers/sort.ts +0 -7
  134. package/src/hooks/useActiveElement.js +0 -19
  135. package/src/hooks/useChartClasses.js +0 -41
@@ -21,21 +21,8 @@ export const useEditorPermissions = () => {
21
21
  'Pie',
22
22
  'Scatter Plot',
23
23
  'Spark Line',
24
- 'Sankey'
25
- ]
26
-
27
- const headerColors = [
28
- 'theme-blue',
29
- 'theme-purple',
30
- 'theme-brown',
31
- 'theme-teal',
32
- 'theme-pink',
33
- 'theme-orange',
34
- 'theme-slate',
35
- 'theme-indigo',
36
- 'theme-cyan',
37
- 'theme-green',
38
- 'theme-amber'
24
+ 'Sankey',
25
+ 'Warming Stripes'
39
26
  ]
40
27
 
41
28
  const visSupportsDateCategoryAxis = () => {
@@ -77,7 +64,8 @@ export const useEditorPermissions = () => {
77
64
  'Forest Plot',
78
65
  'Spark Line',
79
66
  'Sankey',
80
- 'Bump Chart'
67
+ 'Bump Chart',
68
+ 'Warming Stripes'
81
69
  ]
82
70
  if (disabledCharts.includes(visualizationType)) return false
83
71
  return true
@@ -91,7 +79,8 @@ export const useEditorPermissions = () => {
91
79
  'Forest Plot',
92
80
  'Spark Line',
93
81
  'Sankey',
94
- 'Bump Chart'
82
+ 'Bump Chart',
83
+ 'Warming Stripes'
95
84
  ]
96
85
  if (disabledCharts.includes(visualizationType)) return false
97
86
  return true
@@ -107,6 +96,8 @@ export const useEditorPermissions = () => {
107
96
  return false
108
97
  case 'Sankey':
109
98
  return false
99
+ case 'Warming Stripes':
100
+ return true
110
101
  default:
111
102
  return true
112
103
  }
@@ -154,7 +145,11 @@ export const useEditorPermissions = () => {
154
145
  }
155
146
  const visHasBrushChart = () => {
156
147
  if (config.xAxis.type === 'categorical') return false
157
- return ['Line', 'Bar', 'Area Chart', 'Combo'].includes(visualizationType) && orientation === 'vertical'
148
+ // Allow Line charts, vertical Bar charts (both stacked and grouped), and vertical Area charts
149
+ if (visualizationType === 'Line' && orientation === 'vertical') return true
150
+ if (visualizationType === 'Bar' && orientation === 'vertical') return true
151
+ if (visualizationType === 'Area Chart' && orientation === 'vertical') return true
152
+ return false
158
153
  }
159
154
 
160
155
  const visHasBarBorders = () => {
@@ -167,6 +162,8 @@ export const useEditorPermissions = () => {
167
162
 
168
163
  const visHasDataCutoff = () => {
169
164
  switch (visualizationType) {
165
+ case 'Warming Stripes':
166
+ return false
170
167
  case 'Sankey':
171
168
  return false
172
169
  case 'Forest Plot':
@@ -182,7 +179,9 @@ export const useEditorPermissions = () => {
182
179
  }
183
180
  }
184
181
 
185
- const visHasSelectableLegendValues = !['Box Plot', 'Forest Plot', 'Spark Line'].includes(visualizationType)
182
+ const visHasSelectableLegendValues = !['Box Plot', 'Forest Plot', 'Spark Line', 'Warming Stripes'].includes(
183
+ visualizationType
184
+ )
186
185
  const visHasLegendAxisAlign = () => {
187
186
  return visualizationType === 'Bar' && visualizationSubType === 'stacked' && config.legend.behavior === 'isolate'
188
187
  }
@@ -191,7 +190,7 @@ export const useEditorPermissions = () => {
191
190
  }
192
191
 
193
192
  const visSupportsTooltipOpacity = () => {
194
- const disabledCharts = ['Spark Line', 'Sankey']
193
+ const disabledCharts = ['Spark Line', 'Sankey', 'Warming Stripes']
195
194
  if (disabledCharts.includes(visualizationType)) return false
196
195
  return true
197
196
  }
@@ -203,7 +202,7 @@ export const useEditorPermissions = () => {
203
202
  }
204
203
 
205
204
  const visSupportsSequentialPallete = () => {
206
- const disabledCharts = ['Paired Bar', 'Deviation Bar', 'Forest Plot', 'Forecasting', 'Sankey']
205
+ const disabledCharts = ['Line', 'Paired Bar', 'Deviation Bar', 'Forest Plot', 'Forecasting', 'Sankey']
207
206
  if (disabledCharts.includes(visualizationType)) return false
208
207
  return true
209
208
  }
@@ -221,19 +220,19 @@ export const useEditorPermissions = () => {
221
220
  }
222
221
 
223
222
  const visSupportsDateCategoryAxisLabel = () => {
224
- const disabledCharts = ['Forest Plot', 'Spark Line', 'Bump Chart']
223
+ const disabledCharts = ['Forest Plot', 'Spark Line', 'Bump Chart', 'Warming Stripes']
225
224
  if (disabledCharts.includes(visualizationType)) return false
226
225
  return true
227
226
  }
228
227
 
229
228
  const visSupportsDateCategoryAxisLine = () => {
230
- const disabledCharts = ['Forest Plot', 'Spark Line']
229
+ const disabledCharts = ['Forest Plot', 'Spark Line', 'Warming Stripes']
231
230
  if (disabledCharts.includes(visualizationType)) return false
232
231
  return true
233
232
  }
234
233
 
235
234
  const visSupportsDateCategoryAxisTicks = () => {
236
- const disabledCharts = ['Forest Plot', 'Spark Line']
235
+ const disabledCharts = ['Forest Plot', 'Spark Line', 'Warming Stripes']
237
236
  if (disabledCharts.includes(visualizationType)) return false
238
237
  return true
239
238
  }
@@ -257,7 +256,7 @@ export const useEditorPermissions = () => {
257
256
  }
258
257
 
259
258
  const visSupportsRegions = () => {
260
- const disabledCharts = ['Forest Plot', 'Pie', 'Paired Bar', 'Spark Line', 'Sankey']
259
+ const disabledCharts = ['Forest Plot', 'Pie', 'Paired Bar', 'Spark Line', 'Sankey', 'Warming Stripes']
261
260
  if (disabledCharts.includes(visualizationType)) return false
262
261
  return true
263
262
  }
@@ -275,14 +274,13 @@ export const useEditorPermissions = () => {
275
274
  }
276
275
 
277
276
  const visSupportsFilters = () => {
278
- const disabledCharts = ['Forest Plot', 'Sankey']
277
+ const disabledCharts = ['Forest Plot', 'Sankey', 'Warming Stripes']
279
278
  if (disabledCharts.includes(visualizationType)) return false
280
279
  return true
281
280
  }
282
281
 
283
282
  const visSupportsValueAxisGridLines = () => {
284
283
  const disabledCharts = ['Forest Plot']
285
- if (orientation === 'horizontal') return false
286
284
  if (disabledCharts.includes(visualizationType)) return false
287
285
  return true
288
286
  }
@@ -316,13 +314,13 @@ export const useEditorPermissions = () => {
316
314
  }
317
315
 
318
316
  const visSupportsBarThickness = () => {
319
- const disabledCharts = ['Forest Plot']
317
+ const disabledCharts = ['Forest Plot', 'Warming Stripes']
320
318
  if (disabledCharts.includes(visualizationType)) return false
321
319
  return true
322
320
  }
323
321
 
324
322
  const visSupportsChartHeight = () => {
325
- const disabledCharts = ['Spark Line']
323
+ const disabledCharts = ['Spark Line', 'Warming Stripes']
326
324
  if (disabledCharts.includes(visualizationType)) return false
327
325
  return true
328
326
  }
@@ -334,7 +332,7 @@ export const useEditorPermissions = () => {
334
332
  }
335
333
 
336
334
  const visSupportsLeftValueAxis = () => {
337
- const disabledCharts = ['Spark Line', 'Sankey']
335
+ const disabledCharts = ['Spark Line', 'Sankey', 'Warming Stripes']
338
336
  if (disabledCharts.includes(visualizationType)) return false
339
337
  return true
340
338
  }
@@ -349,6 +347,7 @@ export const useEditorPermissions = () => {
349
347
  const disabledCharts = ['Spark Line', 'Sankey', 'Bump Chart']
350
348
  if (disabledCharts.includes(visualizationType)) return false
351
349
  if (config.orientation !== 'horizontal') return false
350
+ if (config.orientation === 'horizontal' && visualizationType === 'Bar' && !config.isLollipopChart) return false
352
351
  return true
353
352
  }
354
353
 
@@ -383,6 +382,12 @@ export const useEditorPermissions = () => {
383
382
  )
384
383
  }
385
384
 
385
+ const visSupportsSmallMultiples = () => {
386
+ const enabledCharts = ['Line', 'Bar', 'Area Chart', 'Combo', 'Box Plot', 'Scatter Plot', 'Warming Stripes']
387
+ if (enabledCharts.includes(visualizationType) && config.orientation !== 'horizontal') return true
388
+ return false
389
+ }
390
+
386
391
  const visSupportsYPadding = () => {
387
392
  return !config.yAxis.inlineLabel || !config.yAxis.inlineLabel?.includes(' ')
388
393
  }
@@ -411,7 +416,6 @@ export const useEditorPermissions = () => {
411
416
 
412
417
  return {
413
418
  enabledChartTypes,
414
- headerColors,
415
419
  visCanAnimate,
416
420
  visHasAnchors,
417
421
  visHasBarBorders,
@@ -460,6 +464,7 @@ export const useEditorPermissions = () => {
460
464
  visSupportsValueAxisMax,
461
465
  visSupportsValueAxisMin,
462
466
  visSupportsDynamicSeries,
467
+ visSupportsSmallMultiples,
463
468
  visSupportsYPadding,
464
469
  visHasSingleSeriesTooltip,
465
470
  visHasCategoricalAxis
@@ -14,6 +14,102 @@ import { curveMonotoneX } from '@visx/curve'
14
14
  import { Bar, Area, LinePath } from '@visx/shape'
15
15
  import { Group } from '@visx/group'
16
16
 
17
+ // Helper function to check if a value is numeric/calculable
18
+ const isCalculable = (value: any): boolean => {
19
+ if (value === null || value === undefined || value === '' || value === 'NA') return false
20
+ const num = typeof value === 'string' ? parseFloat(value.replace(/,/g, '')) : Number(value)
21
+ return !isNaN(num) && isFinite(num)
22
+ }
23
+
24
+ // Helper function to filter and sort forecast data, splitting into segments at gaps
25
+ const prepareForecastData = (
26
+ data: Record<string, any>[],
27
+ xAxisKey: string,
28
+ highKey: string,
29
+ lowKey: string
30
+ ): Record<string, any>[][] => {
31
+ if (!data || data.length === 0) return []
32
+
33
+ // Filter out invalid data points (where confidence intervals are not calculable)
34
+ const validData = data.filter(d => {
35
+ const high = d[highKey]
36
+ const low = d[lowKey]
37
+ return isCalculable(high) && isCalculable(low) && d[xAxisKey]
38
+ })
39
+
40
+ if (validData.length === 0) return []
41
+
42
+ // Sort by date
43
+ const sortedData = [...validData].sort((a, b) => {
44
+ const dateA = Date.parse(a[xAxisKey])
45
+ const dateB = Date.parse(b[xAxisKey])
46
+ if (isNaN(dateA) || isNaN(dateB)) return 0
47
+ return dateA - dateB
48
+ })
49
+
50
+ // Split into segments when there are gaps
51
+ // Calculate intervals between consecutive points to detect gaps
52
+ const intervals: number[] = []
53
+ for (let i = 1; i < sortedData.length; i++) {
54
+ const currentDate = Date.parse(sortedData[i][xAxisKey])
55
+ const prevDate = Date.parse(sortedData[i - 1][xAxisKey])
56
+ if (!isNaN(currentDate) && !isNaN(prevDate)) {
57
+ intervals.push(currentDate - prevDate)
58
+ }
59
+ }
60
+
61
+ // Calculate median interval (more robust than average for detecting gaps)
62
+ const medianInterval =
63
+ intervals.length > 0
64
+ ? [...intervals].sort((a, b) => a - b)[Math.floor(intervals.length / 2)]
65
+ : 7 * 24 * 60 * 60 * 1000 // Default to 7 days if no intervals
66
+
67
+ // Threshold: gap is more than 2x the median interval, or more than 30 days
68
+ const gapThreshold = Math.max(medianInterval * 2, 30 * 24 * 60 * 60 * 1000)
69
+
70
+ const segments: Record<string, any>[][] = []
71
+ let currentSegment: Record<string, any>[] = []
72
+
73
+ for (let i = 0; i < sortedData.length; i++) {
74
+ const current = sortedData[i]
75
+ const prev = sortedData[i - 1]
76
+
77
+ if (i === 0) {
78
+ // First data point starts a new segment
79
+ currentSegment = [current]
80
+ } else {
81
+ const currentDate = Date.parse(current[xAxisKey])
82
+ const prevDate = Date.parse(prev[xAxisKey])
83
+
84
+ if (isNaN(currentDate) || isNaN(prevDate)) {
85
+ // If dates are invalid, continue current segment
86
+ currentSegment.push(current)
87
+ } else {
88
+ const interval = currentDate - prevDate
89
+ const hasGap = interval > gapThreshold
90
+
91
+ if (hasGap) {
92
+ // Save current segment and start a new one
93
+ if (currentSegment.length > 0) {
94
+ segments.push(currentSegment)
95
+ }
96
+ currentSegment = [current]
97
+ } else {
98
+ // Continue current segment
99
+ currentSegment.push(current)
100
+ }
101
+ }
102
+ }
103
+ }
104
+
105
+ // Add the last segment
106
+ if (currentSegment.length > 0) {
107
+ segments.push(currentSegment)
108
+ }
109
+
110
+ return segments.length > 0 ? segments : [sortedData]
111
+ }
112
+
17
113
  const Forecasting = ({ xScale, yScale, height, width, handleTooltipMouseOver, handleTooltipMouseOff }) => {
18
114
  const { transformedData: data, rawData, config, seriesHighlight, parseDate } = useContext(ConfigContext)
19
115
  const { xAxis, yAxis, legend, runtime } = config
@@ -61,7 +157,7 @@ const Forecasting = ({ xScale, yScale, height, width, handleTooltipMouseOver, ha
61
157
  return (
62
158
  <Group
63
159
  className={`forecasting-areas-combo-${index}`}
64
- key={`forecasting-areas--stage-${replace(stage.key, / /g, '—')}-${index}`}
160
+ key={`forecasting-areas--stage-${replace(stage.key, / /g, '—')}-${index}`}
65
161
  >
66
162
  {group.confidenceIntervals?.map((ciGroup, ciGroupIndex) => {
67
163
  const palette = forecastingPalettes[stage.color] || false
@@ -78,35 +174,57 @@ const Forecasting = ({ xScale, yScale, height, width, handleTooltipMouseOver, ha
78
174
  return isReversed ? palette?.[1] || 'transparent' : palette?.[4] || 'transparent'
79
175
  }
80
176
 
81
- if (ciGroup.high === '' || ciGroup.low === '') return
177
+ if (ciGroup.high === '' || ciGroup.low === '') return null
178
+
179
+ // Prepare data: filter invalid values, sort by date, and split into segments at gaps
180
+ const dataSegments = prepareForecastData(bridgedData, xAxis.dataKey, ciGroup.high, ciGroup.low)
181
+
82
182
  return (
83
183
  <Group
84
184
  key={`forecasting-areas--stage-${replace(
85
185
  stage.key,
86
- / /g,
186
+ / /g,
87
187
  '—'
88
188
  )}--group-${stageIndex}-${ciGroupIndex}`}
89
189
  >
90
- {/* prettier-ignore */}
91
- <Area
92
- curve={curveMonotoneX}
93
- data={bridgedData}
94
- fill={getFill()}
95
- opacity={transparentArea? 0.1 : 0.5 }
96
- x={d => xScale(Date.parse(d[xAxis.dataKey]))}
97
- y0={d => yScale(d[ciGroup.low])}
98
- y1={d => yScale(d[ciGroup.high])}
99
- />
100
-
101
- {ciGroupIndex === 0 && (
102
- <>
190
+ {dataSegments.map((segment, segmentIndex) => (
191
+ <Group key={`segment-${segmentIndex}`}>
103
192
  {/* prettier-ignore */}
104
- <LinePath data={bridgedData} x={d => Number(xScale(Date.parse(d[xAxis.dataKey])))} y={d => Number(yScale(d[ciGroup.high]))} curve={curveMonotoneX} stroke={getStroke()} strokeWidth={1} strokeOpacity={1} />
193
+ <Area
194
+ curve={curveMonotoneX}
195
+ data={segment}
196
+ fill={getFill()}
197
+ opacity={transparentArea ? 0.1 : 0.5}
198
+ x={d => xScale(Date.parse(d[xAxis.dataKey]))}
199
+ y0={d => yScale(d[ciGroup.low])}
200
+ y1={d => yScale(d[ciGroup.high])}
201
+ />
105
202
 
106
- {/* prettier-ignore */}
107
- <LinePath data={bridgedData} x={d => Number(xScale(Date.parse(d[xAxis.dataKey])))} y={d => Number(yScale(d[ciGroup.low]))} curve={curveMonotoneX} stroke={getStroke()} strokeWidth={1} strokeOpacity={1} />
108
- </>
109
- )}
203
+ {ciGroupIndex === 0 && (
204
+ <>
205
+ <LinePath
206
+ data={segment}
207
+ x={d => Number(xScale(Date.parse(d[xAxis.dataKey])))}
208
+ y={d => Number(yScale(d[ciGroup.high]))}
209
+ curve={curveMonotoneX}
210
+ stroke={getStroke()}
211
+ strokeWidth={1}
212
+ strokeOpacity={1}
213
+ />
214
+
215
+ <LinePath
216
+ data={segment}
217
+ x={d => Number(xScale(Date.parse(d[xAxis.dataKey])))}
218
+ y={d => Number(yScale(d[ciGroup.low]))}
219
+ curve={curveMonotoneX}
220
+ stroke={getStroke()}
221
+ strokeWidth={1}
222
+ strokeOpacity={1}
223
+ />
224
+ </>
225
+ )}
226
+ </Group>
227
+ ))}
110
228
  </Group>
111
229
  )
112
230
  })}
@@ -23,7 +23,7 @@ import { getVizTitle, getVizSubType } from '@cdc/core/helpers/metrics/utils'
23
23
 
24
24
  const LEGEND_PADDING = 36
25
25
 
26
- export interface LegendProps {
26
+ interface LegendProps {
27
27
  colorScale: ColorScale
28
28
  config: ChartConfig
29
29
  currentViewport: ViewportSize
@@ -149,9 +149,12 @@ const Legend: React.FC<LegendProps> = forwardRef(
149
149
  eventType: `chart_legend_item_toggled` as any,
150
150
  eventAction: 'keydown',
151
151
  eventLabel: interactionLabel,
152
- specifics: config.visualizationType === 'Bar'
153
- ? `label: ${label.text}, orientation: ${config.orientation === 'horizontal' ? 'horizontal' : 'vertical'}, mode: ${legend.behavior}`
154
- : `label: ${label.text}, mode: ${legend.behavior}`,
152
+ specifics:
153
+ config.visualizationType === 'Bar'
154
+ ? `label: ${label.text}, orientation: ${
155
+ config.orientation === 'horizontal' ? 'horizontal' : 'vertical'
156
+ }, mode: ${legend.behavior}`
157
+ : `label: ${label.text}, mode: ${legend.behavior}`
155
158
  })
156
159
  highlight(label)
157
160
  }
@@ -164,9 +167,12 @@ const Legend: React.FC<LegendProps> = forwardRef(
164
167
  eventType: `chart_legend_item_toggled` as any,
165
168
  eventAction: 'click',
166
169
  eventLabel: interactionLabel,
167
- specifics: config.visualizationType === 'Bar'
168
- ? `label: ${label.text}, orientation: ${config.orientation === 'horizontal' ? 'horizontal' : 'vertical'}, mode: ${legend.behavior}`
169
- : `label: ${label.text}, mode: ${legend.behavior}`,
170
+ specifics:
171
+ config.visualizationType === 'Bar'
172
+ ? `label: ${label.text}, orientation: ${
173
+ config.orientation === 'horizontal' ? 'horizontal' : 'vertical'
174
+ }, mode: ${legend.behavior}`
175
+ : `label: ${label.text}, mode: ${legend.behavior}`,
170
176
 
171
177
  vizTitle: getVizTitle(config)
172
178
  })
@@ -238,8 +244,9 @@ const Legend: React.FC<LegendProps> = forwardRef(
238
244
  {/* Pattern Legend Items */}
239
245
  {config.legend.patterns && Object.keys(config.legend.patterns).length > 0 && (
240
246
  <div
241
- className={`legend-patterns d-flex ${['top', 'bottom'].includes(config.legend.position) ? 'flex-row flex-wrap' : 'flex-column'
242
- }`}
247
+ className={`legend-patterns d-flex ${
248
+ ['top', 'bottom'].includes(config.legend.position) ? 'flex-row flex-wrap' : 'flex-column'
249
+ }`}
243
250
  >
244
251
  {Object.entries(config.legend.patterns).map(([key, pattern]) => {
245
252
  const patternId = `legend-pattern-${key}`
@@ -18,13 +18,14 @@ const Legend = forwardRef((props, ref) => {
18
18
  currentViewport,
19
19
  dimensions,
20
20
  getTextWidth,
21
- transformedData
21
+ transformedData,
22
+ formatNumber
22
23
  } = useContext(ConfigContext)
23
24
  if (!config.legend) return null
24
25
  // create fn to reverse labels while legend is Bottom. Legend-right , legend-left works by default
25
26
  const { interactionLabel } = props
26
27
 
27
- const createLegendLabels = createFormatLabels(config, tableData, data, colorScale)
28
+ const createLegendLabels = createFormatLabels(config, tableData, data, colorScale, formatNumber)
28
29
 
29
30
  return (
30
31
  <Fragment>