@cdc/chart 4.25.6 → 4.25.7

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 (36) hide show
  1. package/dist/cdcchart.js +52667 -32246
  2. package/package.json +3 -2
  3. package/src/CdcChartComponent.tsx +13 -9
  4. package/src/_stories/Chart.BoxPlot.stories.tsx +35 -0
  5. package/src/_stories/Chart.stories.tsx +0 -7
  6. package/src/_stories/Chart.tooltip.stories.tsx +35 -275
  7. package/src/_stories/_mock/bar-chart-suppressed.json +2 -80
  8. package/src/_stories/_mock/boxplot_multiseries.json +252 -166
  9. package/src/components/AreaChart/components/AreaChart.Stacked.jsx +1 -1
  10. package/src/components/AreaChart/components/AreaChart.jsx +4 -8
  11. package/src/components/BarChart/components/BarChart.Horizontal.tsx +34 -2
  12. package/src/components/BarChart/components/BarChart.Vertical.tsx +15 -0
  13. package/src/components/BoxPlot/BoxPlot.Horizontal.tsx +131 -0
  14. package/src/components/BoxPlot/{BoxPlot.tsx → BoxPlot.Vertical.tsx} +4 -4
  15. package/src/components/BoxPlot/helpers/index.ts +32 -12
  16. package/src/components/BrushChart.tsx +1 -1
  17. package/src/components/EditorPanel/EditorPanel.tsx +19 -14
  18. package/src/components/EditorPanel/components/Panels/Panel.General.tsx +2 -2
  19. package/src/components/Forecasting/{Forecasting.jsx → Forecasting.tsx} +32 -12
  20. package/src/components/Legend/LegendGroup/LegendGroup.tsx +1 -0
  21. package/src/components/Legend/helpers/index.ts +2 -2
  22. package/src/components/LineChart/helpers.ts +7 -7
  23. package/src/components/LinearChart.tsx +127 -72
  24. package/src/data/initial-state.js +1 -5
  25. package/src/helpers/countNumOfTicks.ts +4 -19
  26. package/src/helpers/filterAndShiftLinearDateTicks.ts +58 -0
  27. package/src/helpers/getBridgedData.ts +13 -0
  28. package/src/helpers/tests/getBridgedData.test.ts +64 -0
  29. package/src/hooks/useScales.ts +42 -42
  30. package/src/hooks/useTooltip.tsx +3 -2
  31. package/src/scss/main.scss +2 -4
  32. package/src/store/chart.actions.ts +2 -2
  33. package/src/store/chart.reducer.ts +4 -12
  34. package/src/types/ChartConfig.ts +1 -6
  35. package/src/components/BoxPlot/index.tsx +0 -3
  36. /package/src/components/Brush/{BrushController..tsx → BrushController.tsx} +0 -0
@@ -59,7 +59,9 @@ export const BarChartHorizontal = () => {
59
59
 
60
60
  const { HighLightedBarUtils } = useHighlightedBars(config)
61
61
 
62
- const hasConfidenceInterval = Object.keys(config.confidenceKeys).length > 0
62
+ const hasConfidenceInterval = [config.confidenceKeys?.upper, config.confidenceKeys?.lower].every(
63
+ v => v != null && String(v).trim() !== ''
64
+ )
63
65
 
64
66
  const _data = getBarData(config, data, hasConfidenceInterval)
65
67
 
@@ -262,6 +264,21 @@ export const BarChartHorizontal = () => {
262
264
  }
263
265
  })}
264
266
 
267
+ {(absentDataLabel || isSuppressed) && (
268
+ <rect
269
+ x={barX}
270
+ y={0}
271
+ width={yMax}
272
+ height={numbericBarHeight}
273
+ fill='transparent'
274
+ data-tooltip-place='top'
275
+ data-tooltip-offset='{"top":3}'
276
+ style={{ pointerEvents: 'all', cursor: 'pointer' }}
277
+ data-tooltip-html={tooltip}
278
+ data-tooltip-id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
279
+ />
280
+ )}
281
+
265
282
  {config.preliminaryData?.map((pd, index) => {
266
283
  // check if user selected column
267
284
  const selectedSuppressionColumn = !pd.column || pd.column === bar.key
@@ -301,7 +318,7 @@ export const BarChartHorizontal = () => {
301
318
  )
302
319
  })}
303
320
 
304
- {!config.isLollipopChart && (
321
+ {!config.isLollipopChart && !hasConfidenceInterval && (
305
322
  <Text // prettier-ignore
306
323
  display={displayBar ? 'block' : 'none'}
307
324
  x={bar.y}
@@ -315,6 +332,21 @@ export const BarChartHorizontal = () => {
315
332
  {testZeroValue(bar.value) ? '' : barDefaultLabel}
316
333
  </Text>
317
334
  )}
335
+
336
+ {!config.isLollipopChart && hasConfidenceInterval && (
337
+ <Text // prettier-ignore
338
+ display={displayBar ? 'block' : 'none'}
339
+ x={bar.value < 0 ? bar.y + barWidth : bar.y - barWidth}
340
+ opacity={transparentBar ? 0.5 : 1}
341
+ y={config.barHeight / 2 + config.barHeight * bar.index}
342
+ fill={labelColor}
343
+ dx={-textPadding}
344
+ verticalAnchor='middle'
345
+ textAnchor={bar.value < 0 ? 'end' : 'start'}
346
+ >
347
+ {testZeroValue(bar.value) ? '' : barDefaultLabel}
348
+ </Text>
349
+ )}
318
350
  <Text // prettier-ignore
319
351
  display={displayBar ? 'block' : 'none'}
320
352
  x={bar.y}
@@ -288,6 +288,21 @@ export const BarChartVertical = () => {
288
288
  }
289
289
  })}
290
290
 
291
+ {(absentDataLabel || isSuppressed) && (
292
+ <rect
293
+ x={barX}
294
+ y={0}
295
+ width={barWidth}
296
+ height={yMax}
297
+ fill='transparent'
298
+ data-tooltip-place='top'
299
+ data-tooltip-offset='{"top":3}'
300
+ style={{ pointerEvents: 'all', cursor: 'pointer' }}
301
+ data-tooltip-html={tooltip}
302
+ data-tooltip-id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
303
+ />
304
+ )}
305
+
291
306
  {config.preliminaryData.map((pd, index) => {
292
307
  // check if user selected column
293
308
  const selectedSuppressionColumn = !pd.column || pd.column === bar.key
@@ -0,0 +1,131 @@
1
+ import React, { useContext } from 'react'
2
+ import { Group } from '@visx/group'
3
+ import { BoxPlot } from '@visx/stats'
4
+ import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
5
+ import ConfigContext from '../../ConfigContext'
6
+ import { handleTooltip, createPlots } from './helpers/index'
7
+ import { APP_FONT_COLOR } from '@cdc/core/helpers/constants'
8
+ import _ from 'lodash'
9
+
10
+ const BoxPlotHorizontal = ({ xScale, yScale, seriesScale }) => {
11
+ const { config, transformedData: data, colorScale, seriesHighlight } = useContext(ConfigContext)
12
+ const yOffset = Number(config.xAxis.size)
13
+ const defaultColor = APP_FONT_COLOR
14
+ const tooltip_id = `cdc-open-viz-tooltip-${config.runtime.uniqueId}`
15
+ // generate summary stats
16
+ const plots = createPlots(data, config)
17
+ const { plotNonOutlierValues, plotOutlierValues } = config.boxplot
18
+
19
+ return (
20
+ <ErrorBoundary component='BoxPlot Horizontal'>
21
+ <Group left={yOffset} top={0} className='boxplot'>
22
+ {plots.map(plot => {
23
+ const category = plot.columnCategory
24
+
25
+ return config.series.map(item => {
26
+ const y0 = yScale(category)
27
+ const offset = seriesScale(item.dataKey)
28
+ const isTransparent =
29
+ config.legend.behavior === 'highlight' &&
30
+ seriesHighlight.length > 0 &&
31
+ seriesHighlight.indexOf(item.dataKey) === -1
32
+ const displayPlot =
33
+ config.legend.behavior === 'highlight' ||
34
+ seriesHighlight.length === 0 ||
35
+ seriesHighlight.indexOf(item.dataKey) !== -1
36
+ const fillOpacity = isTransparent ? 0.3 : 0.7
37
+ // outlier & non-outlier arrays
38
+ const nonOut = plot.columnNonOutliers?.[item.dataKey] || []
39
+ const out = plot.columnOutliers?.[item.dataKey] || []
40
+
41
+ return (
42
+ <Group key={`${category}-${item.dataKey}`} top={y0 + offset}>
43
+ {displayPlot && (
44
+ <BoxPlot
45
+ min={Number(plot.min[item.dataKey])}
46
+ max={Number(plot.max[item.dataKey])}
47
+ thirdQuartile={plot.q3[item.dataKey]}
48
+ firstQuartile={plot.q1[item.dataKey]}
49
+ median={plot.median[item.dataKey]}
50
+ horizontal={true}
51
+ valueScale={xScale}
52
+ boxWidth={seriesScale.bandwidth()}
53
+ fill={colorScale(item.dataKey)}
54
+ fillOpacity={1}
55
+ stroke={defaultColor}
56
+ boxProps={{
57
+ fill: colorScale(item.dataKey),
58
+ strokeWidth: config.boxplot.borders === 'true' ? 1.5 : 0,
59
+ stroke: defaultColor,
60
+ fillOpacity: fillOpacity
61
+ }}
62
+ minProps={{ stroke: defaultColor, strokeWidth: 1, opacity: fillOpacity }}
63
+ maxProps={{ stroke: defaultColor, strokeWidth: 1, opacity: fillOpacity }}
64
+ medianProps={{ stroke: defaultColor, strokeWidth: 1, opacity: fillOpacity }}
65
+ outliers={
66
+ config.boxplot.plotOutlierValues ? _.map(plot.columnOutliers[item.dataKey], item => item) : []
67
+ }
68
+ outlierProps={{
69
+ style: {
70
+ fill: colorScale(item.dataKey),
71
+ opacity: fillOpacity,
72
+ stroke: defaultColor
73
+ }
74
+ }}
75
+ container
76
+ containerProps={{
77
+ 'data-tooltip-html': handleTooltip(
78
+ config.boxplot,
79
+ plot.columnCategory,
80
+ item.dataKey,
81
+ _.round(plot.q1[item.dataKey], config.dataFormat.roundTo),
82
+ _.round(plot.q3[item.dataKey], config.dataFormat.roundTo),
83
+ _.round(plot.median[item.dataKey], config.dataFormat.roundTo),
84
+ _.round(plot.iqr[item.dataKey], config.dataFormat.roundTo),
85
+ config.xAxis.label,
86
+ defaultColor
87
+ ),
88
+ 'data-tooltip-id': tooltip_id,
89
+ tabIndex: -1
90
+ }}
91
+ />
92
+ )}
93
+
94
+ {/* non-outlier points */}
95
+ {plotNonOutlierValues &&
96
+ nonOut.map((value, idx) => (
97
+ <circle
98
+ display={displayPlot ? 'block' : 'none'}
99
+ key={`non-${category}-${item.dataKey}-${idx}`}
100
+ cx={xScale(value)}
101
+ cy={seriesScale.bandwidth() / 2}
102
+ r={4}
103
+ opacity={fillOpacity}
104
+ fill={defaultColor}
105
+ style={{ stroke: defaultColor }}
106
+ />
107
+ ))}
108
+
109
+ {/* outlier points */}
110
+ {plotOutlierValues &&
111
+ out.map((value, idx) => (
112
+ <circle
113
+ display={displayPlot ? 'block' : 'none'}
114
+ key={`out-${category}-${item.dataKey}-${idx}`}
115
+ cx={xScale(value)}
116
+ cy={seriesScale.bandwidth() / 2}
117
+ r={4}
118
+ opacity={fillOpacity}
119
+ fill={defaultColor}
120
+ style={{ stroke: defaultColor }}
121
+ />
122
+ ))}
123
+ </Group>
124
+ )
125
+ })
126
+ })}
127
+ </Group>
128
+ </ErrorBoundary>
129
+ )
130
+ }
131
+ export default BoxPlotHorizontal
@@ -8,7 +8,7 @@ import { handleTooltip, createPlots } from './helpers/index'
8
8
  import { APP_FONT_COLOR } from '@cdc/core/helpers/constants'
9
9
  import _ from 'lodash'
10
10
 
11
- const CoveBoxPlot = ({ xScale, yScale, seriesScale }) => {
11
+ const BoxPlotVertical = ({ xScale, yScale, seriesScale }) => {
12
12
  const { config, colorScale, seriesHighlight, transformedData: data } = useContext(ConfigContext)
13
13
  const { boxplot } = config
14
14
 
@@ -21,7 +21,7 @@ const CoveBoxPlot = ({ xScale, yScale, seriesScale }) => {
21
21
  const color_0 = _.get(colorPalettesChart, [config.palette, 0], '#000')
22
22
  const plots = createPlots(data, config)
23
23
  return (
24
- <ErrorBoundary component='BoxPlot'>
24
+ <ErrorBoundary component='BoxPlot Vertical'>
25
25
  <Group left={Number(config.yAxis.size)} className='boxplot' key={`boxplot-group`}>
26
26
  {plots.map((d, i) => {
27
27
  const offset = boxWidth - constrainedWidth
@@ -41,7 +41,7 @@ const CoveBoxPlot = ({ xScale, yScale, seriesScale }) => {
41
41
  config.legend.behavior === 'highlight' ||
42
42
  seriesHighlight.length === 0 ||
43
43
  seriesHighlight.indexOf(item.dataKey) !== -1
44
- const fillOpacity = isTransparent ? 0.3 : 0.5
44
+ const fillOpacity = isTransparent ? 0.3 : 0.7
45
45
  return (
46
46
  <Group key={`boxplotplot-${item.dataKey}-${index}`}>
47
47
  {boxplot.plotNonOutlierValues &&
@@ -131,4 +131,4 @@ const CoveBoxPlot = ({ xScale, yScale, seriesScale }) => {
131
131
  )
132
132
  }
133
133
 
134
- export default CoveBoxPlot
134
+ export default BoxPlotVertical
@@ -33,7 +33,7 @@ export const calculateBoxPlotStats = (values: number[]) => {
33
33
  if (!values || values.length === 0) return {}
34
34
 
35
35
  // Sort the values
36
- const sortedValues = _.sortBy(values)
36
+ const sortedValues = _.sortBy(values.map(v => Number(v)))
37
37
 
38
38
  // Quartiles
39
39
  const firstQuartile = d3.quantile(sortedValues, 0.25) ?? 0
@@ -45,18 +45,28 @@ export const calculateBoxPlotStats = (values: number[]) => {
45
45
  // Outlier Bounds
46
46
  const lowerBound = firstQuartile - 1.5 * iqr
47
47
  const upperBound = thirdQuartile + 1.5 * iqr
48
+ // const lowerFence = q1 - 1.5 * iqr
49
+ // const upperFence = q3 + 1.5 * iqr
48
50
 
49
51
  // Non-Outlier Values
50
52
  const nonOutliers = sortedValues.filter(value => value >= lowerBound && value <= upperBound)
51
-
53
+ // **Outliers** =
54
+ const outliers = sortedValues.filter(v => v < lowerBound || v > upperBound)
55
+ const whiskerMax =
56
+ sortedValues
57
+ .slice()
58
+ .reverse()
59
+ .find(v => v <= upperBound) ?? thirdQuartile
60
+ const whiskerMin = nonOutliers.length > 0 ? nonOutliers[0] : firstQuartile
52
61
  // Calculate Box Plot Stats
53
62
  return {
54
- min: d3.min(nonOutliers), // Smallest non-outlier value
55
- max: d3.max(nonOutliers), // Largest non-outlier value
56
- median: d3.median(sortedValues), // Median of all values
63
+ min: whiskerMin,
64
+ max: whiskerMax,
65
+ median: d3.median(sortedValues),
57
66
  firstQuartile,
58
67
  thirdQuartile,
59
- iqr
68
+ iqr,
69
+ outliers
60
70
  }
61
71
  }
62
72
 
@@ -109,16 +119,26 @@ export const createPlots = (data, config) => {
109
119
 
110
120
  // Calculate outliers and non-outliers for each series key
111
121
  Object.keys(keyValues).forEach(key => {
112
- const values = keyValues[key]
122
+ const raw = keyValues[key] ?? []
123
+
124
+ // 2) normalize → trim, drop empties/non-numbers, coerce to Number
125
+ const cleaned: number[] = raw
126
+ .map(v => (typeof v === 'string' ? v.trim() : v)) // trim strings
127
+ .filter(v => v != null && v !== '' && !isNaN(+v)) // drop null/''/non-nums
128
+ .map(v => +v)
129
+
130
+ if (cleaned.length === 0) {
131
+ return
132
+ }
113
133
 
114
134
  // Calculate box plot statistics
115
- const { firstQuartile, thirdQuartile, min, max, median, iqr } = calculateBoxPlotStats(values)
135
+ const { firstQuartile, thirdQuartile, min, max, median, iqr, outliers } = calculateBoxPlotStats(cleaned)
116
136
  // Calculate outliers and non-outliers
117
- columnOutliers[key] = calculateOutliers(values, firstQuartile, thirdQuartile).map(Number)
118
- columnNonOutliers[key] = calculateNonOutliers(values, firstQuartile, thirdQuartile).map(Number)
137
+ columnOutliers[key] = calculateOutliers(cleaned, firstQuartile, thirdQuartile).map(Number)
138
+ columnNonOutliers[key] = calculateNonOutliers(cleaned, firstQuartile, thirdQuartile).map(Number)
119
139
  columnMedian[key] = median
120
- columnMin[key] = min
121
- columnMax[key] = max
140
+ columnMin[key] = Number(min)
141
+ columnMax[key] = Number(max)
122
142
  columnQ1[key] = firstQuartile
123
143
  columnQ3[key] = thirdQuartile
124
144
  columnIqr[key] = iqr
@@ -120,7 +120,7 @@ const BrushChart = ({ xMax, yMax }: BrushChartProps) => {
120
120
  svg.call(brushHandle, selection, formattedStartDate, formattedEndDate)
121
121
 
122
122
  const payload = {
123
- active: config.brush.active,
123
+ active: config.xAxis.brushActive,
124
124
  isBrushing: isUserBrushing,
125
125
  data: finalData
126
126
  }
@@ -20,6 +20,7 @@ import DataTableEditor from '@cdc/core/components/EditorPanel/DataTableEditor'
20
20
  import VizFilterEditor from '@cdc/core/components/EditorPanel/VizFilterEditor'
21
21
  import Tooltip from '@cdc/core/components/ui/Tooltip'
22
22
  import { Select, TextField, CheckBox } from '@cdc/core/components/EditorPanel/Inputs'
23
+ import MultiSelect from '@cdc/core/components/MultiSelect'
23
24
  import { viewports } from '@cdc/core/helpers/getViewport'
24
25
  import { approvedCurveTypes } from '@cdc/core/helpers/lineChartHelpers'
25
26
 
@@ -109,7 +110,7 @@ const PreliminaryData: React.FC<PreliminaryProps> = ({ config, updateConfig, dat
109
110
  let preliminaryData = config.preliminaryData ? [...config.preliminaryData] : []
110
111
  const defaultValues = {
111
112
  type: defaultType,
112
- seriesKey: '',
113
+ seriesKeys: [],
113
114
  label: 'Suppressed',
114
115
  column: '',
115
116
  value: '',
@@ -159,7 +160,7 @@ const PreliminaryData: React.FC<PreliminaryProps> = ({ config, updateConfig, dat
159
160
  displayTable,
160
161
  displayTooltip,
161
162
  label,
162
- seriesKey,
163
+ seriesKeys,
163
164
  style,
164
165
  symbol,
165
166
  type,
@@ -384,14 +385,18 @@ const PreliminaryData: React.FC<PreliminaryProps> = ({ config, updateConfig, dat
384
385
  </>
385
386
  ) : (
386
387
  <>
387
- <Select
388
- value={seriesKey}
389
- initial='Select'
390
- fieldName='seriesKey'
391
- label='ASSOCIATE TO SERIES'
392
- updateField={(_, __, fieldName, value) => update(fieldName, value, i)}
393
- options={config.runtime.lineSeriesKeys ?? config.runtime?.seriesKeys}
394
- />
388
+ <label>
389
+ <span className='edit-label'>ASSOCIATE TO THESE SERIES</span>
390
+ <MultiSelect
391
+ fieldName='seriesKeys'
392
+ updateField={(_, __, fieldName, value) => update(fieldName, value, i)}
393
+ options={(config.runtime.lineSeriesKeys ?? config.runtime?.seriesKeys).map(c => ({
394
+ label: c,
395
+ value: c
396
+ }))}
397
+ selected={seriesKeys}
398
+ />
399
+ </label>
395
400
  <Select
396
401
  value={column}
397
402
  initial='Select'
@@ -1193,7 +1198,7 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
1193
1198
  if (isDebug && config?.series?.length === 0) {
1194
1199
  let setdatacol = setDataColumn()
1195
1200
  if (setdatacol !== '') addNewSeries(setdatacol)
1196
- if (isDebug) console.error('### COVE DEBUG: Chart: Setting default datacol=', setdatacol) // eslint-disable-line
1201
+ if (isDebug) console.log('### COVE DEBUG: Chart: Setting default datacol=', setdatacol) // eslint-disable-line
1197
1202
  }
1198
1203
 
1199
1204
  const chartsWithOptions = [
@@ -2926,9 +2931,9 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
2926
2931
  />
2927
2932
  {visHasBrushChart() && (
2928
2933
  <CheckBox
2929
- value={config.brush.active}
2930
- section='brush'
2931
- fieldName='active'
2934
+ value={config.xAxis.brushActive}
2935
+ section='xAxis'
2936
+ fieldName='brushActive'
2932
2937
  label='Brush Slider '
2933
2938
  updateField={updateFieldDeprecated}
2934
2939
  tooltip={
@@ -131,7 +131,7 @@ const PanelGeneral: FC<PanelProps> = props => {
131
131
  options={Object.keys(approvedCurveTypes)}
132
132
  />
133
133
  )}
134
- {visualizationType === 'Bar' && (
134
+ {(visualizationType === 'Bar' || visualizationType === 'Box Plot') && (
135
135
  <Select
136
136
  value={config.orientation || 'vertical'}
137
137
  fieldName='orientation'
@@ -178,7 +178,7 @@ const PanelGeneral: FC<PanelProps> = props => {
178
178
  options={['standard', 'shallow', 'finger']}
179
179
  />
180
180
  )}
181
- {visualizationType === 'Bar' && config.orientation === 'horizontal' && (
181
+ {(visualizationType === 'Bar' || visualizationType === 'Box Plot') && config.orientation === 'horizontal' && (
182
182
  <Select
183
183
  value={config.yAxis.labelPlacement || 'Below Bar'}
184
184
  section='yAxis'
@@ -1,9 +1,10 @@
1
1
  import React, { useContext } from 'react'
2
-
2
+ import { replace } from 'lodash'
3
3
  // cdc
4
4
  import ConfigContext from '../../ConfigContext'
5
5
  import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
6
6
  import { colorPalettesChart, sequentialPalettes } from '@cdc/core/data/colorPalettes'
7
+ import { getBridgedData } from '../../helpers/getBridgedData'
7
8
 
8
9
  // visx & d3
9
10
  import { curveMonotoneX } from '@visx/curve'
@@ -11,7 +12,7 @@ import { Bar, Area, LinePath } from '@visx/shape'
11
12
  import { Group } from '@visx/group'
12
13
 
13
14
  const Forecasting = ({ xScale, yScale, height, width, handleTooltipMouseOver, handleTooltipMouseOff }) => {
14
- const { transformedData: data, rawData, config, seriesHighlight } = useContext(ConfigContext)
15
+ const { transformedData: data, rawData, config, seriesHighlight, parseDate } = useContext(ConfigContext)
15
16
  const { xAxis, yAxis, legend, runtime } = config
16
17
  const DEBUG = false
17
18
 
@@ -23,12 +24,17 @@ const Forecasting = ({ xScale, yScale, height, width, handleTooltipMouseOver, ha
23
24
  if (!group || !group.stages) return false
24
25
  return group.stages.map((stage, stageIndex) => {
25
26
  const { behavior } = legend
26
- const groupData = rawData.filter(d => d[group.stageColumn] === stage.key)
27
- let transparentArea = behavior === 'highlight' && seriesHighlight.length > 0 && seriesHighlight.indexOf(stage.key) === -1
28
- let displayArea = behavior === 'highlight' || seriesHighlight.length === 0 || seriesHighlight.indexOf(stage.key) !== -1
27
+ let transparentArea =
28
+ behavior === 'highlight' && seriesHighlight.length > 0 && seriesHighlight.indexOf(stage.key) === -1
29
+ let displayArea =
30
+ behavior === 'highlight' || seriesHighlight.length === 0 || seriesHighlight.indexOf(stage.key) !== -1
31
+ const bridgedData = getBridgedData(stage.key, group.stageColumn, rawData)
29
32
 
30
33
  return (
31
- <Group className={`forecasting-areas-combo-${index}`} key={`forecasting-areas--stage-${stage.key.replaceAll(' ', '-')}-${index}`}>
34
+ <Group
35
+ className={`forecasting-areas-combo-${index}`}
36
+ key={`forecasting-areas--stage-${replace(stage.key, / /g, '—')}-${index}`}
37
+ >
32
38
  {group.confidenceIntervals?.map((ciGroup, ciGroupIndex) => {
33
39
  const palette = sequentialPalettes[stage.color] || colorPalettesChart[stage.color] || false
34
40
 
@@ -44,13 +50,19 @@ const Forecasting = ({ xScale, yScale, height, width, handleTooltipMouseOver, ha
44
50
 
45
51
  if (ciGroup.high === '' || ciGroup.low === '') return
46
52
  return (
47
- <Group key={`forecasting-areas--stage-${stage.key.replaceAll(' ', '-')}--group-${stageIndex}-${ciGroupIndex}`}>
53
+ <Group
54
+ key={`forecasting-areas--stage-${replace(
55
+ stage.key,
56
+ / /g,
57
+ '—'
58
+ )}--group-${stageIndex}-${ciGroupIndex}`}
59
+ >
48
60
  {/* prettier-ignore */}
49
61
  <Area
50
62
  curve={curveMonotoneX}
51
- data={groupData}
63
+ data={bridgedData}
52
64
  fill={getFill()}
53
- opacity={transparentArea ? 0.1 : .5}
65
+ opacity={transparentArea? 0.1 : 0.5 }
54
66
  x={d => xScale(Date.parse(d[xAxis.dataKey]))}
55
67
  y0={d => yScale(d[ciGroup.low])}
56
68
  y1={d => yScale(d[ciGroup.high])}
@@ -59,10 +71,10 @@ const Forecasting = ({ xScale, yScale, height, width, handleTooltipMouseOver, ha
59
71
  {ciGroupIndex === 0 && (
60
72
  <>
61
73
  {/* prettier-ignore */}
62
- <LinePath data={groupData} x={d => Number(xScale(Date.parse(d[xAxis.dataKey])))} y={d => Number(yScale(d[ciGroup.high]))} curve={curveMonotoneX} stroke={getStroke()} strokeWidth={1} strokeOpacity={1} />
74
+ <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} />
63
75
 
64
76
  {/* prettier-ignore */}
65
- <LinePath data={groupData} x={d => Number(xScale(Date.parse(d[xAxis.dataKey])))} y={d => Number(yScale(d[ciGroup.low]))} curve={curveMonotoneX} stroke={getStroke()} strokeWidth={1} strokeOpacity={1} />
77
+ <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} />
66
78
  </>
67
79
  )}
68
80
  </Group>
@@ -73,7 +85,15 @@ const Forecasting = ({ xScale, yScale, height, width, handleTooltipMouseOver, ha
73
85
  })
74
86
  })}
75
87
  <Group key='tooltip-hover-section'>
76
- <Bar key={'bars'} width={Number(width)} height={Number(height)} fill={DEBUG ? 'red' : 'transparent'} fillOpacity={0.05} onMouseMove={e => handleTooltipMouseOver(e, data)} onMouseOut={handleTooltipMouseOff} />
88
+ <Bar
89
+ key={'bars'}
90
+ width={Number(width)}
91
+ height={Number(height)}
92
+ fill={DEBUG ? 'red' : 'transparent'}
93
+ fillOpacity={0.05}
94
+ onMouseMove={e => handleTooltipMouseOver(e, data)}
95
+ onMouseOut={handleTooltipMouseOff}
96
+ />
77
97
  </Group>
78
98
  </Group>
79
99
  </ErrorBoundary>
@@ -83,6 +83,7 @@ const LegendGroup = ({ formatLabels }) => {
83
83
  }}
84
84
  key={`legend-item-${i}`}
85
85
  tabIndex={0}
86
+ role='button'
86
87
  >
87
88
  <LegendShape shape={config.legend.style === 'boxes' ? 'square' : 'circle'} fill={label.value} />
88
89
  <LegendLabel align='left' margin='0'>
@@ -19,9 +19,9 @@ export const getMarginTop = (isLegendBottom, config) => {
19
19
  if (!isLegendBottom) {
20
20
  return '0px'
21
21
  }
22
- if (isLegendBottom && config.brush.active && !config.legend.hide) {
22
+ if (isLegendBottom && config.xAxis.brushActive && !config.legend.hide) {
23
23
  const additiolMargin = 25
24
- return `${DEFAULT_MARGIN_TOP + config.brush.height + additiolMargin}px`
24
+ return `${DEFAULT_MARGIN_TOP + config.brush?.height + additiolMargin}px`
25
25
  } else {
26
26
  return `${DEFAULT_MARGIN_TOP}px`
27
27
  }
@@ -17,19 +17,19 @@ export const createStyles = (props: StyleProps): Style[] => {
17
17
 
18
18
  const dynamicSeriesKey = dynamicCategory ? originalSeriesKey : seriesKey
19
19
  const validPreliminaryData: PreliminaryDataItem[] = preliminaryData.filter(
20
- pd => pd.seriesKey && pd.column && pd.value && pd.type && pd.style && pd.type === 'effect'
20
+ pd => pd.seriesKeys?.length && pd.column && pd.value && pd.type && pd.style && pd.type === 'effect'
21
21
  )
22
22
  const isEffectLine = (pd, dataPoint) => {
23
23
  if (dynamicCategory) {
24
24
  return (
25
25
  pd.type === 'effect' &&
26
26
  pd.style !== 'Open Circles' &&
27
- pd.seriesKey === seriesKey &&
27
+ pd.seriesKeys.includes(seriesKey) &&
28
28
  String(dataPoint[dynamicSeriesKey]) === String(pd.value)
29
29
  )
30
30
  } else {
31
31
  return (
32
- pd.seriesKey === seriesKey &&
32
+ pd.seriesKeys.includes(seriesKey) &&
33
33
  dataPoint[pd.column] === pd.value &&
34
34
  pd.type === 'effect' &&
35
35
  pd.style !== 'Open Circles'
@@ -71,11 +71,11 @@ export const filterCircles = (
71
71
  ): DataItem[] => {
72
72
  // Filter and map preliminaryData to get circlesFiltered
73
73
  const circlesFiltered = preliminaryData
74
- ?.filter(item => item.style.includes('Circles') && item.type === 'effect')
74
+ ?.filter(item => item.style.includes('Circles') && item.type === 'effect' && item.seriesKeys?.length)
75
75
  .map(item => ({
76
76
  column: item.column,
77
77
  value: item.value,
78
- seriesKey: item.seriesKey,
78
+ seriesKeys: item.seriesKeys,
79
79
  circleSize: item.circleSize,
80
80
  style: item.style
81
81
  }))
@@ -85,7 +85,7 @@ export const filterCircles = (
85
85
  circlesFiltered.forEach(fc => {
86
86
  if (
87
87
  item[fc.column] === fc.value &&
88
- fc.seriesKey === seriesKey &&
88
+ fc.seriesKeys.includes(seriesKey) &&
89
89
  item[seriesKey] &&
90
90
  fc.style === 'Open Circles'
91
91
  ) {
@@ -98,7 +98,7 @@ export const filterCircles = (
98
98
  }
99
99
  if (
100
100
  (!fc.value || item[fc.column] === fc.value) &&
101
- fc.seriesKey === seriesKey &&
101
+ fc.seriesKeys.includes(seriesKey) &&
102
102
  item[seriesKey] &&
103
103
  fc.style === 'Filled Circles'
104
104
  ) {