@cdc/chart 4.25.6-2 → 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 (54) hide show
  1. package/dist/cdcchart.js +52634 -30848
  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 +3 -3
  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/LinearChart.tsx +63 -15
  23. package/src/data/initial-state.js +1 -5
  24. package/src/helpers/filterAndShiftLinearDateTicks.ts +58 -0
  25. package/src/helpers/getBridgedData.ts +13 -0
  26. package/src/helpers/tests/getBridgedData.test.ts +64 -0
  27. package/src/hooks/useScales.ts +42 -42
  28. package/src/hooks/useTooltip.tsx +3 -2
  29. package/src/scss/main.scss +2 -8
  30. package/src/store/chart.actions.ts +2 -2
  31. package/src/store/chart.reducer.ts +4 -12
  32. package/src/types/ChartConfig.ts +0 -5
  33. package/examples/private/0527.json +0 -1
  34. package/examples/private/DEV-8850-2.json +0 -493
  35. package/examples/private/DEV-9822.json +0 -574
  36. package/examples/private/DEV-9840.json +0 -553
  37. package/examples/private/DEV-9850-3.json +0 -461
  38. package/examples/private/chart.json +0 -1084
  39. package/examples/private/ci_formatted.json +0 -202
  40. package/examples/private/ci_issue.json +0 -3016
  41. package/examples/private/completed.json +0 -634
  42. package/examples/private/dem-data-long.csv +0 -20
  43. package/examples/private/dem-data-long.json +0 -36
  44. package/examples/private/demographic_data.csv +0 -157
  45. package/examples/private/demographic_data.json +0 -2654
  46. package/examples/private/demographic_dynamic.json +0 -443
  47. package/examples/private/demographic_standard.json +0 -560
  48. package/examples/private/ehdi.json +0 -29939
  49. package/examples/private/line-issue.json +0 -497
  50. package/examples/private/not-loading.json +0 -360
  51. package/examples/private/test.json +0 -493
  52. package/examples/private/testing-pie.json +0 -436
  53. package/src/components/BoxPlot/index.tsx +0 -3
  54. /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
  }
@@ -2931,9 +2931,9 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
2931
2931
  />
2932
2932
  {visHasBrushChart() && (
2933
2933
  <CheckBox
2934
- value={config.brush.active}
2935
- section='brush'
2936
- fieldName='active'
2934
+ value={config.xAxis.brushActive}
2935
+ section='xAxis'
2936
+ fieldName='brushActive'
2937
2937
  label='Brush Slider '
2938
2938
  updateField={updateFieldDeprecated}
2939
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
  }
@@ -14,7 +14,8 @@ import { isDateScale } from '@cdc/core/helpers/cove/date'
14
14
  import { AreaChartStacked } from './AreaChart'
15
15
  import BarChart from './BarChart'
16
16
  import ConfigContext from '../ConfigContext'
17
- import BoxPlot from './BoxPlot'
17
+ import BoxPlotVertical from './BoxPlot/BoxPlot.Vertical'
18
+ import BoxPlotHorizontal from './BoxPlot/BoxPlot.Horizontal'
18
19
  import ScatterPlot from './ScatterPlot'
19
20
  import DeviationBar from './DeviationBar'
20
21
  import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
@@ -25,17 +26,20 @@ import PairedBarChart from './PairedBarChart'
25
26
  import useIntersectionObserver from '../hooks/useIntersectionObserver'
26
27
  import Regions from './Regions'
27
28
  import CategoricalYAxis from './Axis/Categorical.Axis'
29
+ import BrushChart from './Brush/BrushController'
28
30
 
29
31
  // Helpers
30
32
  import { isLegendWrapViewport, isMobileHeightViewport } from '@cdc/core/helpers/viewports'
31
33
  import { getTextWidth } from '@cdc/core/helpers/getTextWidth'
32
34
  import { calcInitialHeight, handleAutoPaddingRight } from '../helpers/sizeHelpers'
35
+ import { filterAndShiftLinearDateTicks } from '../helpers/filterAndShiftLinearDateTicks'
33
36
 
34
37
  // Hooks
35
38
  import useMinMax from '../hooks/useMinMax'
36
39
  import useReduceData from '../hooks/useReduceData'
37
40
  import useRightAxis from '../hooks/useRightAxis'
38
- import useScales, { getTickValues, filterAndShiftLinearDateTicks } from '../hooks/useScales'
41
+ import useScales, { getTickValues } from '../hooks/useScales'
42
+
39
43
  import getTopAxis from '../helpers/getTopAxis'
40
44
  import { useTooltip as useCoveTooltip } from '../hooks/useTooltip'
41
45
  import { useEditorPermissions } from './EditorPanel/useEditorPermissions'
@@ -43,7 +47,6 @@ import Annotation from './Annotations'
43
47
  import { BlurStrokeText } from '@cdc/core/components/BlurStrokeText'
44
48
  import { countNumOfTicks } from '../helpers/countNumOfTicks'
45
49
  import HoverLine from './HoverLine/HoverLine'
46
- import BrushChart from './BrushChart'
47
50
 
48
51
  type LinearChartProps = {
49
52
  parentWidth: number
@@ -94,7 +97,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
94
97
  tableData,
95
98
  transformedData: data,
96
99
  seriesHighlight,
97
- brushConfig
100
+
98
101
  } = useContext(ConfigContext)
99
102
 
100
103
  // CONFIG
@@ -208,10 +211,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
208
211
  ? parseDate(d[config.runtime.originalXAxis.dataKey]).getTime()
209
212
  : d[config.runtime.originalXAxis.dataKey]
210
213
  const getYAxisData = (d, seriesKey) => d[seriesKey]
211
- const xAxisDataMapped =
212
- config.brush.active && brushConfig.data?.length
213
- ? brushConfig.data.map(d => getXAxisData(d))
214
- : data.map(d => getXAxisData(d))
214
+ const xAxisDataMapped = data.map(d => getXAxisData(d))
215
215
  const section = config.orientation === 'horizontal' || config.visualizationType === 'Forest Plot' ? 'yAxis' : 'xAxis'
216
216
  const properties = {
217
217
  data,
@@ -418,8 +418,9 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
418
418
  const topLabelOnGridline = topYLabelRef.current && yAxis.labelsAboveGridlines
419
419
 
420
420
  // Heights to add
421
- const brushHeight = brush?.active ? brush?.height + brush?.height : 0
422
- const brushHeightWithMargin = config.brush?.active ? brushHeight + brushHeight : 0
421
+
422
+ const brushHeight = 25
423
+ const brushHeightWithMargin = config.xAxis.brushActive ? brushHeight + brushHeight : 0
423
424
  const forestRowsHeight = isForestPlot ? config.data.length * forestPlot.rowHeight : 0
424
425
  const topLabelOnGridlineHeight = topLabelOnGridline ? topYLabelRef.current.getBBox().height : 0
425
426
  const additionalHeight = axisBottomHeight + brushHeightWithMargin + forestRowsHeight + topLabelOnGridlineHeight
@@ -447,7 +448,15 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
447
448
  const legendIsLeftOrRight =
448
449
  legend?.position !== 'top' && legend?.position !== 'bottom' && !isLegendWrapViewport(currentViewport)
449
450
  legendRef.current.style.transform = legendIsLeftOrRight ? `translateY(${topLabelOnGridlineHeight}px)` : 'none'
450
- }, [axisBottomRef.current, config, bottomLabelStart, brush, currentViewport, topYLabelRef.current, initialHeight])
451
+ }, [
452
+ axisBottomRef.current,
453
+ config,
454
+ bottomLabelStart,
455
+ config.xAxis.brushActive,
456
+ currentViewport,
457
+ topYLabelRef.current,
458
+ initialHeight
459
+ ])
451
460
 
452
461
  useEffect(() => {
453
462
  if (lastMaxValue.current === maxValue) return
@@ -745,8 +754,19 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
745
754
  showTooltip={showTooltip}
746
755
  />
747
756
  )}
748
- {visualizationType === 'Box Plot' && (
749
- <BoxPlot
757
+ {visualizationType === 'Box Plot' && config.orientation === 'vertical' && (
758
+ <BoxPlotVertical
759
+ seriesScale={seriesScale}
760
+ xMax={xMax}
761
+ yMax={yMax}
762
+ min={min}
763
+ max={max}
764
+ xScale={xScale}
765
+ yScale={yScale}
766
+ />
767
+ )}
768
+ {visualizationType === 'Box Plot' && config.orientation === 'horizontal' && (
769
+ <BoxPlotHorizontal
750
770
  seriesScale={seriesScale}
751
771
  xMax={xMax}
752
772
  yMax={yMax}
@@ -864,7 +884,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
864
884
  />
865
885
  )}
866
886
  {/*Brush chart */}
867
- {config.brush.active && config.xAxis.type !== 'categorical' && <BrushChart xMax={xMax} yMax={yMax} />}
887
+ {config.xAxis.brushActive && config.xAxis.type !== 'categorical' && <BrushChart xMax={xMax} yMax={yMax} />}
868
888
  {/* Line chart */}
869
889
  {/* TODO: Make this just line or combo? */}
870
890
  {!['Paired Bar', 'Box Plot', 'Area Chart', 'Scatter Plot', 'Deviation Bar', 'Forecasting', 'Bar'].includes(
@@ -1028,13 +1048,22 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1028
1048
  stroke='#000'
1029
1049
  />
1030
1050
  )}
1031
- {yScale.domain()[0] < 0 && (
1051
+ {orientation === 'vertical' && yScale.domain()[0] < 0 && (
1052
+ // draw from the Left of the chart …
1032
1053
  <Line
1033
1054
  from={{ x: props.axisFromPoint.x, y: yScale(0) }}
1034
1055
  to={{ x: xMax, y: yScale(0) }}
1035
1056
  stroke='#333'
1036
1057
  />
1037
1058
  )}
1059
+ {orientation === 'horizontal' && xScale.domain()[0] < 0 && (
1060
+ <Line
1061
+ // draw from the top of the char
1062
+ from={{ x: xScale(0), y: 0 }}
1063
+ to={{ x: xScale(0), y: yMax }}
1064
+ stroke='#333'
1065
+ />
1066
+ )}
1038
1067
  {visualizationType === 'Bar' && orientation === 'horizontal' && xScale.domain()[0] < 0 && (
1039
1068
  <Line
1040
1069
  from={{ x: xScale(0), y: 0 }}
@@ -1079,6 +1108,25 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1079
1108
  )}
1080
1109
 
1081
1110
  {orientation === 'horizontal' &&
1111
+ visualizationType === 'Box Plot' &&
1112
+ config.yAxis.labelPlacement === 'On Date/Category Axis' &&
1113
+ !config.yAxis.hideLabel && (
1114
+ <Text
1115
+ x={tick.to.x}
1116
+ y={yScale(tick.value) + yScale.bandwidth() / 2}
1117
+ transform={`rotate(${
1118
+ config.orientation === 'horizontal' ? config.runtime.yAxis.tickRotation || 0 : 0
1119
+ }, ${tick.to.x}, ${tick.to.y})`}
1120
+ verticalAnchor={'middle'}
1121
+ textAnchor={'end'}
1122
+ fontSize={tickLabelFontSize}
1123
+ >
1124
+ {tick.formattedValue}
1125
+ </Text>
1126
+ )}
1127
+
1128
+ {orientation === 'horizontal' &&
1129
+ visualizationType !== 'Box Plot' &&
1082
1130
  visualizationSubType !== 'stacked' &&
1083
1131
  config.yAxis.labelPlacement === 'On Date/Category Axis' &&
1084
1132
  !config.yAxis.hideLabel && (