@cdc/chart 4.23.10 → 4.23.11

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 (56) hide show
  1. package/dist/cdcchart.js +30989 -29057
  2. package/examples/feature/bar/example-bar-chart.json +1 -46
  3. package/examples/feature/bar/lollipop.json +156 -0
  4. package/examples/feature/combo/planet-combo-example-config.json +99 -9
  5. package/examples/feature/dev-4261.json +399 -0
  6. package/examples/feature/forest-plot/{broken.json → linear.json} +55 -50
  7. package/examples/feature/forest-plot/logarithmic.json +306 -0
  8. package/examples/feature/line/line-points.json +340 -0
  9. package/examples/feature/regions/index.json +462 -0
  10. package/examples/gallery/bar-chart-vertical/combo-line-chart.json +181 -48
  11. package/examples/sparkline-multilple.json +846 -0
  12. package/index.html +10 -6
  13. package/package.json +3 -3
  14. package/src/CdcChart.tsx +75 -63
  15. package/src/_stories/Chart.stories.tsx +188 -0
  16. package/src/_stories/Chart.tooltip.stories.tsx +305 -0
  17. package/src/_stories/ChartBrush.stories.tsx +19 -0
  18. package/src/_stories/ChartSuppress.stories.tsx +19 -0
  19. package/src/_stories/_mock/brush_mock.json +393 -0
  20. package/src/_stories/_mock/suppress_mock.json +911 -0
  21. package/src/components/AreaChart.Stacked.jsx +4 -5
  22. package/src/components/AreaChart.jsx +6 -35
  23. package/src/components/{BarChart.Horizontal.jsx → BarChart.Horizontal.tsx} +106 -29
  24. package/src/components/{BarChart.StackedHorizontal.jsx → BarChart.StackedHorizontal.tsx} +22 -17
  25. package/src/components/{BarChart.StackedVertical.jsx → BarChart.StackedVertical.tsx} +22 -26
  26. package/src/components/{BarChart.Vertical.jsx → BarChart.Vertical.tsx} +175 -31
  27. package/src/components/BarChart.jsx +1 -1
  28. package/src/components/DeviationBar.jsx +4 -3
  29. package/src/components/EditorPanel.jsx +176 -50
  30. package/src/components/ForestPlot/ForestPlotProps.ts +11 -0
  31. package/src/components/ForestPlot/Readme.md +0 -0
  32. package/src/components/ForestPlot/index.scss +1 -0
  33. package/src/components/{ForestPlot.jsx → ForestPlot/index.tsx} +51 -31
  34. package/src/components/ForestPlotSettings.jsx +162 -113
  35. package/src/components/Legend.jsx +36 -3
  36. package/src/components/{LineChart.Circle.tsx → LineChart/LineChart.Circle.tsx} +26 -29
  37. package/src/components/LineChart/LineChartProps.ts +17 -0
  38. package/src/components/LineChart/index.scss +1 -0
  39. package/src/components/{LineChart.tsx → LineChart/index.tsx} +64 -35
  40. package/src/components/LinearChart.jsx +120 -60
  41. package/src/components/PairedBarChart.jsx +2 -2
  42. package/src/components/Series.jsx +22 -3
  43. package/src/components/ZoomBrush.tsx +168 -0
  44. package/src/data/initial-state.js +27 -12
  45. package/src/hooks/useBarChart.js +71 -7
  46. package/src/hooks/useColorScale.ts +50 -0
  47. package/src/hooks/useEditorPermissions.js +22 -4
  48. package/src/hooks/{useMinMax.js → useMinMax.ts} +75 -23
  49. package/src/hooks/{useRightAxis.js → useRightAxis.ts} +10 -2
  50. package/src/hooks/{useScales.js → useScales.ts} +64 -17
  51. package/src/hooks/useTooltip.jsx +68 -41
  52. package/src/scss/main.scss +3 -35
  53. package/src/types/ChartConfig.ts +43 -0
  54. package/src/types/ChartContext.ts +38 -0
  55. package/src/types/ChartProps.ts +7 -0
  56. package/src/types/ForestPlot.ts +60 -0
@@ -6,28 +6,47 @@ import { LinePath, Bar } from '@visx/shape'
6
6
  import { Text } from '@visx/text'
7
7
 
8
8
  import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
9
- import ConfigContext from '../ConfigContext'
10
- import useRightAxis from '../hooks/useRightAxis'
9
+ import ConfigContext from '../../ConfigContext'
10
+ import useRightAxis from '../../hooks/useRightAxis'
11
11
  import LineChartCircle from './LineChart.Circle'
12
12
 
13
- const LineChart = ({ xScale, yScale, getXAxisData, getYAxisData, xMax, yMax, handleTooltipMouseOver, handleTooltipMouseOff, showTooltip, seriesStyle = 'Line', svgRef, handleTooltipClick, tooltipData }) => {
14
- // Not sure why there's a redraw here.
15
-
16
- const { colorPalettes, transformedData: data, colorScale, seriesHighlight, config, formatNumber, formatDate, parseDate, isNumber, updateConfig, handleLineType, dashboardConfig, tableData } = useContext(ConfigContext)
13
+ // types
14
+ import { type ChartContext } from '../../types/ChartContext'
15
+ import { type LineChartProps } from './LineChartProps'
16
+
17
+ const LineChart = (props: LineChartProps) => {
18
+ // prettier-ignore
19
+ const {
20
+ getXAxisData,
21
+ getYAxisData,
22
+ handleTooltipClick,
23
+ handleTooltipMouseOff,
24
+ handleTooltipMouseOver,
25
+ tooltipData,
26
+ xMax,
27
+ xScale,
28
+ yMax,
29
+ yScale,
30
+ } = props
31
+
32
+ // prettier-ignore
33
+ const {
34
+ colorScale,
35
+ config,
36
+ formatNumber,
37
+ handleLineType,
38
+ isNumber,
39
+ parseDate,
40
+ seriesHighlight,
41
+ tableData,
42
+ transformedData: data,
43
+ updateConfig,
44
+ } = useContext<ChartContext>(ConfigContext)
17
45
  const { yScaleRight } = useRightAxis({ config, yMax, data, updateConfig })
18
-
19
46
  if (!handleTooltipMouseOver) return
20
- const handleAxisFormating = (axis = 'left', label, value) => {
21
- // if this is an x axis category/date value return without doing any formatting.
22
- // if (label === config.runtime.xAxis.label) return value
23
- axis = String(axis).toLocaleLowerCase()
24
- if (label) {
25
- return `${label}: ${formatNumber(value, axis)}`
26
- }
27
- return `${formatNumber(value, axis)}`
28
- }
29
47
 
30
48
  const DEBUG = false
49
+ const { lineDatapointStyle, showLineSeriesLabels, legend } = config
31
50
 
32
51
  return (
33
52
  <ErrorBoundary component='LineChart'>
@@ -39,21 +58,19 @@ const LineChart = ({ xScale, yScale, getXAxisData, getYAxisData, xMax, yMax, han
39
58
  const seriesData = config.series.filter(item => item.dataKey === seriesKey)
40
59
  const seriesAxis = seriesData[0].axis ? seriesData[0].axis : 'left'
41
60
 
42
- let displayArea = config.legend.behavior === 'highlight' || seriesHighlight.length === 0 || seriesHighlight.indexOf(seriesKey) !== -1
61
+ let displayArea = legend.behavior === 'highlight' || seriesHighlight.length === 0 || seriesHighlight.indexOf(seriesKey) !== -1
43
62
 
44
63
  return (
45
64
  <Group
46
65
  key={`series-${seriesKey}`}
47
- opacity={config.legend.behavior === 'highlight' && seriesHighlight.length > 0 && seriesHighlight.indexOf(seriesKey) === -1 ? 0.5 : 1}
48
- display={config.legend.behavior === 'highlight' || (seriesHighlight.length === 0 && !config.legend.dynamicLegend) || seriesHighlight.indexOf(seriesKey) !== -1 ? 'block' : 'none'}
66
+ opacity={legend.behavior === 'highlight' && seriesHighlight.length > 0 && seriesHighlight.indexOf(seriesKey) === -1 ? 0.5 : 1}
67
+ display={legend.behavior === 'highlight' || (seriesHighlight.length === 0 && !legend.dynamicLegend) || seriesHighlight.indexOf(seriesKey) !== -1 ? 'block' : 'none'}
49
68
  >
50
69
  {data.map((d, dataIndex) => {
51
70
  // Find the series object from the config.series array that has a dataKey matching the seriesKey variable.
52
71
  const series = config.series.find(({ dataKey }) => dataKey === seriesKey)
53
72
  const { axis } = series
54
73
 
55
- const xAxisValue = config.runtime.xAxis.type === 'date' ? formatDate(parseDate(d[config.runtime.xAxis.dataKey])) : d[config.runtime.xAxis.dataKey]
56
- const yAxisValue = getYAxisData(d, seriesKey)
57
74
  const hasMultipleSeries = Object.keys(config.runtime.seriesLabels).length > 1
58
75
  const labeltype = axis === 'Right' ? 'rightLabel' : 'label'
59
76
  let label = config.runtime.yAxis[labeltype]
@@ -74,28 +91,39 @@ const LineChart = ({ xScale, yScale, getXAxisData, getYAxisData, xMax, yMax, han
74
91
  {formatNumber(d[seriesKey], 'left')}
75
92
  </Text>
76
93
 
77
- {config.lineDatapointStyle === 'hidden' ||
78
- (config.lineDatapointStyle === 'always show' && <LineChartCircle d={d} config={config} seriesKey={seriesKey} displayArea={displayArea} tooltipData={tooltipData} xScale={xScale} yScale={yScale} colorScale={colorScale} parseDate={parseDate} yScaleRight={yScaleRight} />)}
94
+ {(lineDatapointStyle === 'hidden' || lineDatapointStyle === 'always show') && (
95
+ <LineChartCircle
96
+ data={data}
97
+ d={d}
98
+ config={config}
99
+ seriesKey={seriesKey}
100
+ displayArea={displayArea}
101
+ tooltipData={tooltipData}
102
+ xScale={xScale}
103
+ yScale={yScale}
104
+ colorScale={colorScale}
105
+ parseDate={parseDate}
106
+ yScaleRight={yScaleRight}
107
+ seriesAxis={seriesAxis}
108
+ key={`line-circle--${dataIndex}`}
109
+ />
110
+ )}
79
111
  </Group>
80
112
  )
81
113
  )
82
114
  })}
83
-
84
- <>{config.lineDatapointStyle === 'hover' && <LineChartCircle config={config} seriesKey={seriesKey} displayArea={displayArea} tooltipData={tooltipData} xScale={xScale} yScale={yScale} colorScale={colorScale} parseDate={parseDate} yScaleRight={yScaleRight} seriesAxis={seriesAxis} />}</>
85
-
115
+ <>
116
+ {lineDatapointStyle === 'hover' && (
117
+ <LineChartCircle data={data} config={config} seriesKey={seriesKey} displayArea={displayArea} tooltipData={tooltipData} xScale={xScale} yScale={yScale} colorScale={colorScale} parseDate={parseDate} yScaleRight={yScaleRight} seriesAxis={seriesAxis} />
118
+ )}
119
+ </>
120
+ {/* STANDARD LINE */}
86
121
  <LinePath
87
122
  curve={allCurves[seriesData[0].lineType]}
88
123
  data={data}
89
124
  x={d => xScale(getXAxisData(d))}
90
125
  y={d => (seriesAxis === 'Right' ? yScaleRight(getYAxisData(d, seriesKey)) : yScale(getYAxisData(d, seriesKey)))}
91
- stroke={
92
- config.customColors
93
- ? config.customColors[index]
94
- : colorScale
95
- ? colorPalettes[config.palette][index]
96
- : // fallback
97
- '#000'
98
- }
126
+ stroke={colorScale ? colorScale(config.runtime.seriesLabels[seriesKey]) : '#000'}
99
127
  strokeWidth={2}
100
128
  strokeOpacity={1}
101
129
  strokeDasharray={lineType ? handleLineType(lineType) : 0}
@@ -103,6 +131,7 @@ const LineChart = ({ xScale, yScale, getXAxisData, getYAxisData, xMax, yMax, han
103
131
  return item[seriesKey] !== '' && item[seriesKey] !== null && item[seriesKey] !== undefined
104
132
  }}
105
133
  />
134
+ {/* ANIMATED LINE */}
106
135
  {config.animate && (
107
136
  <LinePath
108
137
  className='animation'
@@ -121,7 +150,7 @@ const LineChart = ({ xScale, yScale, getXAxisData, getYAxisData, xMax, yMax, han
121
150
  />
122
151
  )}
123
152
  {/* Render series labels at end if each line if selected in the editor */}
124
- {config.showLineSeriesLabels &&
153
+ {showLineSeriesLabels &&
125
154
  (config.runtime.lineSeriesKeys || config.runtime.seriesKeys).map(seriesKey => {
126
155
  let lastDatum
127
156
  for (let i = data.length - 1; i >= 0; i--) {
@@ -1,4 +1,4 @@
1
- import React, { useContext, useEffect, useRef, useState, useMemo } from 'react'
1
+ import React, { useContext, useEffect, useRef, useState } from 'react'
2
2
 
3
3
  // Libraries
4
4
  import { AxisLeft, AxisBottom, AxisRight, AxisTop } from '@visx/axis'
@@ -6,7 +6,7 @@ import { Group } from '@visx/group'
6
6
  import { Line, Bar } from '@visx/shape'
7
7
  import { Text } from '@visx/text'
8
8
  import { Tooltip as ReactTooltip } from 'react-tooltip'
9
- import { useTooltip, TooltipWithBounds, useTooltipInPortal } from '@visx/tooltip'
9
+ import { useTooltip, TooltipWithBounds } from '@visx/tooltip'
10
10
 
11
11
  // CDC Components
12
12
  import AreaChart from './AreaChart'
@@ -30,13 +30,14 @@ import useRightAxis from '../hooks/useRightAxis'
30
30
  import useScales from '../hooks/useScales'
31
31
  import useTopAxis from '../hooks/useTopAxis'
32
32
  import { useTooltip as useCoveTooltip } from '../hooks/useTooltip'
33
+ import { useEditorPermissions } from '../hooks/useEditorPermissions'
33
34
 
34
35
  // styles
35
36
  import '../scss/LinearChart.scss'
37
+ import ZoomBrush from './ZoomBrush'
36
38
 
37
39
  const LinearChart = props => {
38
40
  const { isEditor, isDashboard, transformedData: data, dimensions, config, parseDate, formatDate, currentViewport, formatNumber, handleChartAriaLabels, updateConfig, handleLineType, rawData, capitalize, setSharedFilter, setSharedFilterValue, getTextWidth, isDebug } = useContext(ConfigContext)
39
-
40
41
  // todo: start destructuring this file for conciseness
41
42
  const { visualizationType, visualizationSubType, orientation, xAxis, yAxis, runtime, debugSvg } = config
42
43
 
@@ -59,15 +60,16 @@ const LinearChart = props => {
59
60
  height = height + config.data.length * config.forestPlot.rowHeight
60
61
  yMax = yMax + config.data.length * config.forestPlot.rowHeight
61
62
  }
62
-
63
- let dynamicMarginTop = 0 || config.dynamicMarginTop
64
- const marginTop = 20
63
+ if (config.brush.active) {
64
+ height = height + config.brush.height
65
+ }
65
66
 
66
67
  // hooks % states
67
68
  const { minValue, maxValue, existPositiveValue, isAllLine } = useReduceData(config, data)
68
- const { yScaleRight, hasRightAxis } = useRightAxis({ config, yMax, data, updateConfig })
69
+ const { visSupportsReactTooltip } = useEditorPermissions()
69
70
  const { hasTopAxis } = useTopAxis(config)
70
71
  const [animatedChart, setAnimatedChart] = useState(false)
72
+ const [point, setPoint] = useState({ x: 0, y: 0 })
71
73
 
72
74
  // refs
73
75
  const triggerRef = useRef()
@@ -79,11 +81,12 @@ const LinearChart = props => {
79
81
  // getters & functions
80
82
  const getXAxisData = d => (config.runtime.xAxis.type === 'date' ? parseDate(d[config.runtime.originalXAxis.dataKey]).getTime() : d[config.runtime.originalXAxis.dataKey])
81
83
  const getYAxisData = (d, seriesKey) => d[seriesKey]
82
- const xAxisDataMapped = data.map(d => getXAxisData(d))
84
+ const xAxisDataMapped = config.brush.active && config.brush.data?.length ? config.brush.data.map(d => getXAxisData(d)) : data.map(d => getXAxisData(d))
83
85
  const section = config.orientation === 'horizontal' ? 'yAxis' : 'xAxis'
84
86
  const properties = { data, config, minValue, maxValue, isAllLine, existPositiveValue, xAxisDataMapped, xMax, yMax }
85
- const { min, max } = useMinMax(properties)
86
- const { xScale, yScale, seriesScale, g1xScale, g2xScale, xScaleNoPadding } = useScales({ ...properties, min, max })
87
+ const { min, max, leftMax, rightMax } = useMinMax(properties)
88
+ const { yScaleRight, hasRightAxis } = useRightAxis({ config, yMax, data, updateConfig })
89
+ const { xScale, yScale, seriesScale, g1xScale, g2xScale, xScaleNoPadding, xScaleBrush } = useScales({ ...properties, min, max, leftMax, rightMax })
87
90
 
88
91
  // sets the portal x/y for where tooltips should appear on the page.
89
92
  const [chartPosition, setChartPosition] = useState(null)
@@ -109,6 +112,8 @@ const LinearChart = props => {
109
112
  // when logarithmic scale applied change value FIRST of tick
110
113
  tick = 0
111
114
  }
115
+
116
+ if (config.visualizationType === 'Forest Plot') return formatNumber(tick, 'bottom', false, false, false, true)
112
117
  if (runtime.xAxis.type === 'date') return formatDate(tick)
113
118
  if (orientation === 'horizontal') return formatNumber(tick, 'left', shouldAbbreviate)
114
119
  if (config.xAxis.type === 'continuous') return formatNumber(tick, 'bottom', shouldAbbreviate)
@@ -147,7 +152,12 @@ const LinearChart = props => {
147
152
  tickCount = 4 // same default as standalone components
148
153
  }
149
154
  }
155
+
156
+ if (config.visualizationType === 'Forest Plot') {
157
+ tickCount = config.xAxis.numTicks !== '' ? config.xAxis.numTicks : 4
158
+ }
150
159
  }
160
+
151
161
  return tickCount
152
162
  }
153
163
 
@@ -206,56 +216,35 @@ const LinearChart = props => {
206
216
  return countNumOfTicks('yAxis')
207
217
  }
208
218
 
219
+ const onMouseMove = event => {
220
+ const svgRect = event.currentTarget.getBoundingClientRect()
221
+ const x = event.clientX - svgRect.left
222
+ const y = event.clientY - svgRect.top
223
+
224
+ setPoint({
225
+ x,
226
+ y
227
+ })
228
+ }
229
+
209
230
  return isNaN(width) ? (
210
231
  <React.Fragment></React.Fragment>
211
232
  ) : (
212
233
  <ErrorBoundary component='LinearChart'>
213
234
  {/* ! Notice - div needed for tooltip boundaries (flip/flop) */}
214
235
  <div style={{ width: `${width}px`, height: `${height}px`, overflow: 'visible' }} className='tooltip-boundary'>
215
- <svg width={'100%'} height={'100%'} className={`linear ${config.animate ? 'animated' : ''} ${animatedChart && config.animate ? 'animate' : ''} ${debugSvg && 'debug'}`} role='img' aria-label={handleChartAriaLabels(config)} ref={svgRef} style={{ overflow: 'visible' }}>
236
+ <svg
237
+ // onMouseLeave={() => setPoint(null)}
238
+ onMouseMove={onMouseMove}
239
+ width={'100%'}
240
+ height={'100%'}
241
+ className={`linear ${config.animate ? 'animated' : ''} ${animatedChart && config.animate ? 'animate' : ''} ${debugSvg && 'debug'}`}
242
+ role='img'
243
+ aria-label={handleChartAriaLabels(config)}
244
+ ref={svgRef}
245
+ style={{ overflow: 'visible' }}
246
+ >
216
247
  <Bar width={width} height={height} fill={'transparent'}></Bar> {/* Highlighted regions */}
217
- {config.regions
218
- ? config.regions.map(region => {
219
- if (!Object.keys(region).includes('from') || !Object.keys(region).includes('to')) return null
220
-
221
- let from
222
- let to
223
- let width
224
-
225
- if (config.xAxis.type === 'date') {
226
- from = xScale(parseDate(region.from).getTime())
227
- to = xScale(parseDate(region.to).getTime())
228
- width = to - from
229
- }
230
-
231
- if (config.xAxis.type === 'categorical') {
232
- from = xScale(region.from)
233
- to = xScale(region.to)
234
- width = to - from
235
- }
236
-
237
- if (!from) return null
238
- if (!to) return null
239
-
240
- return (
241
- <Group className='regions' left={Number(runtime.yAxis.size)} key={region.label}>
242
- <path
243
- stroke='#333'
244
- d={`M${from} -5
245
- L${from} 5
246
- M${from} 0
247
- L${to} 0
248
- M${to} -5
249
- L${to} 5`}
250
- />
251
- <rect x={from} y={0} width={width} height={yMax} fill={region.background} opacity={0.3} />
252
- <Text x={from + width / 2} y={5} fill={region.color} verticalAnchor='start' textAnchor='middle'>
253
- {region.label}
254
- </Text>
255
- </Group>
256
- )
257
- })
258
- : ''}
259
248
  {/* Y axis */}
260
249
  {!['Spark Line', 'Forest Plot'].includes(visualizationType) && (
261
250
  <AxisLeft scale={yScale} tickLength={config.useLogScale ? 6 : 8} left={Number(runtime.yAxis.size) - config.yAxis.axisPadding} label={runtime.yAxis.label} stroke='#333' tickFormat={(tick, i) => handleLeftTickFormatting(tick, i)} numTicks={handleNumTicks()}>
@@ -422,8 +411,11 @@ const LinearChart = props => {
422
411
  }
423
412
  })
424
413
 
425
- dynamicMarginTop = areTicksTouching && config.isResponsiveTicks ? tickWidthMax + defaultTickLength + marginTop : 0
414
+ const dynamicMarginTop = areTicksTouching && config.isResponsiveTicks ? tickWidthMax + defaultTickLength + 20 : 0
415
+ const rotation = Number(config.xAxis.tickRotation) > 0 ? Number(config.xAxis.tickRotation) : 0
416
+
426
417
  config.dynamicMarginTop = dynamicMarginTop
418
+ config.xAxis.tickWidthMax = tickWidthMax
427
419
 
428
420
  return (
429
421
  <Group className='bottom-axis'>
@@ -463,7 +455,21 @@ const LinearChart = props => {
463
455
  )
464
456
  })}
465
457
  {!config.xAxis.hideAxis && <Line from={props.axisFromPoint} to={props.axisToPoint} stroke='#333' />}
466
- <Text x={axisCenter} y={config.orientation === 'horizontal' ? dynamicMarginTop || config.xAxis.labelOffset : dynamicMarginTop || config.xAxis.size} textAnchor='middle' fontWeight='bold' fill={config.xAxis.labelColor}>
458
+ <Text
459
+ x={axisCenter}
460
+ y={
461
+ config.orientation === 'horizontal'
462
+ ? dynamicMarginTop || config.xAxis.labelOffset
463
+ : config.isResponsiveTicks && dynamicMarginTop && !isHorizontal
464
+ ? dynamicMarginTop
465
+ : Number(rotation) && !config.isResponsiveTicks && !isHorizontal
466
+ ? Number(rotation + tickWidthMax / 1.3)
467
+ : Number(config.xAxis.labelOffset)
468
+ }
469
+ textAnchor='middle'
470
+ fontWeight='bold'
471
+ fill={config.xAxis.labelColor}
472
+ >
467
473
  {props.label}
468
474
  </Text>
469
475
  </Group>
@@ -537,7 +543,7 @@ const LinearChart = props => {
537
543
  </AxisBottom>
538
544
  </>
539
545
  )}
540
- {visualizationType === 'Deviation Bar' && <DeviationBar animatedChart={animatedChart} xScale={xScale} yScale={yScale} width={xMax} height={yMax} />}
546
+ {visualizationType === 'Deviation Bar' && config.series?.length === 1 && <DeviationBar animatedChart={animatedChart} xScale={xScale} yScale={yScale} width={xMax} height={yMax} />}
541
547
  {visualizationType === 'Paired Bar' && <PairedBarChart originalWidth={width} width={xMax} height={yMax} />}
542
548
  {visualizationType === 'Scatter Plot' && (
543
549
  <ScatterPlot
@@ -641,6 +647,8 @@ const LinearChart = props => {
641
647
  config={config}
642
648
  />
643
649
  )}
650
+ {/*Zoom Brush */}
651
+ {['Line', 'Bar', 'Combo', 'Area Chart'].includes(config.visualizationType) && !isHorizontal && <ZoomBrush xScaleBrush={xScaleBrush} yScale={yScale} xMax={xMax} yMax={yMax} />}
644
652
  {/* Line chart */}
645
653
  {/* TODO: Make this just line or combo? */}
646
654
  {visualizationType !== 'Bar' && visualizationType !== 'Paired Bar' && visualizationType !== 'Box Plot' && visualizationType !== 'Area Chart' && visualizationType !== 'Scatter Plot' && visualizationType !== 'Deviation Bar' && visualizationType !== 'Forecasting' && (
@@ -699,6 +707,49 @@ const LinearChart = props => {
699
707
  />
700
708
  )
701
709
  })}
710
+ {/* we are handling regions in bar charts differently, so that we can calculate the bar group into the region space. */}
711
+ {config.regions && config.visualizationType !== 'Bar' && config.orientation === 'vertical'
712
+ ? config.regions.map(region => {
713
+ if (!Object.keys(region).includes('from') || !Object.keys(region).includes('to')) return null
714
+
715
+ let from
716
+ let to
717
+ let width
718
+
719
+ if (config.xAxis.type === 'date') {
720
+ from = xScale(parseDate(region.from).getTime())
721
+ to = xScale(parseDate(region.to).getTime())
722
+ width = to - from
723
+ }
724
+
725
+ if (config.xAxis.type === 'categorical') {
726
+ from = xScale(region.from)
727
+ to = xScale(region.to)
728
+ width = to - from
729
+ }
730
+
731
+ if (!from) return null
732
+ if (!to) return null
733
+
734
+ return (
735
+ <Group className='regions' left={Number(runtime.yAxis.size)} key={region.label} onMouseMove={handleTooltipMouseOver} onMouseLeave={handleTooltipMouseOff} handleTooltipClick={handleTooltipClick} tooltipData={JSON.stringify(tooltipData)} showTooltip={showTooltip}>
736
+ <path
737
+ stroke='#333'
738
+ d={`M${from} -5
739
+ L${from} 5
740
+ M${from} 0
741
+ L${to} 0
742
+ M${to} -5
743
+ L${to} 5`}
744
+ />
745
+ <rect x={from} y={0} width={width} height={yMax} fill={region.background} opacity={0.3} />
746
+ <Text x={from + width / 2} y={5} fill={region.color} verticalAnchor='start' textAnchor='middle'>
747
+ {region.label}
748
+ </Text>
749
+ </Group>
750
+ )
751
+ })
752
+ : ''}
702
753
  {chartHasTooltipGuides && showTooltip && tooltipData && config.visual.verticalHoverLine && (
703
754
  <Group key='tooltipLine-vertical' className='vertical-tooltip-line'>
704
755
  <Line from={{ x: tooltipData.dataXPosition - 10, y: 0 }} to={{ x: tooltipData.dataXPosition - 10, y: yMax }} stroke={'black'} strokeWidth={1} pointerEvents='none' strokeDasharray='5,5' className='vertical-tooltip-line' />
@@ -714,8 +765,18 @@ const LinearChart = props => {
714
765
  {config.chartMessage.noData}
715
766
  </Text>
716
767
  )}
768
+ {config.visualizationType === 'Bar' && config.tooltips.singleSeries && config.visual.horizontalHoverLine && (
769
+ <Group key='tooltipLine-horizontal' className='horizontal-tooltip-line' left={config.yAxis.size ? config.yAxis.size : 0}>
770
+ <Line from={{ x: 0, y: point.y }} to={{ x: xMax, y: point.y }} stroke={'black'} strokeWidth={1} pointerEvents='none' strokeDasharray='5,5' className='horizontal-tooltip-line' />
771
+ </Group>
772
+ )}
773
+ {config.visualizationType === 'Bar' && config.tooltips.singleSeries && config.visual.verticalHoverLine && (
774
+ <Group key='tooltipLine-vertical' className='vertical-tooltip-line'>
775
+ <Line from={{ x: point.x, y: 0 }} to={{ x: point.x, y: yMax }} stroke={'black'} strokeWidth={1} pointerEvents='none' strokeDasharray='5,5' className='vertical-tooltip-line' />
776
+ </Group>
777
+ )}
717
778
  </svg>
718
- {tooltipData && Object.entries(tooltipData.data).length > 0 && tooltipOpen && showTooltip && tooltipData.dataYPosition && tooltipData.dataXPosition && (
779
+ {tooltipData && Object.entries(tooltipData.data).length > 0 && tooltipOpen && showTooltip && tooltipData.dataYPosition && tooltipData.dataXPosition && !config.tooltips.singleSeries && (
719
780
  <>
720
781
  <style>{`.tooltip {background-color: rgba(255,255,255, ${config.tooltips.opacity / 100}) !important`}</style>
721
782
  <TooltipWithBounds key={Math.random()} className={'tooltip cdc-open-viz-module'} left={tooltipLeft} top={tooltipTop}>
@@ -723,9 +784,8 @@ const LinearChart = props => {
723
784
  </TooltipWithBounds>
724
785
  </>
725
786
  )}
726
- {(config.orientation === 'horizontal' || config.visualizationType === 'Scatter Plot' || config.visualizationType === 'Box Plot') && (
727
- <ReactTooltip id={`cdc-open-viz-tooltip-${runtime.uniqueId}`} variant='light' arrowColor='rgba(0,0,0,0)' className='tooltip' style={{ background: `rgba(255,255,255, ${config.tooltips.opacity / 100})`, color: 'black' }} />
728
- )}
787
+
788
+ {visSupportsReactTooltip() && <ReactTooltip id={`cdc-open-viz-tooltip-${runtime.uniqueId}`} variant='light' arrowColor='rgba(0,0,0,0)' className='tooltip' style={{ background: `rgba(255,255,255, ${config.tooltips.opacity / 100})`, color: 'black' }} />}
729
789
  <div className='animation-trigger' ref={triggerRef} />
730
790
  </div>
731
791
  </ErrorBoundary>
@@ -47,11 +47,11 @@ const PairedBarChart = ({ width, height, originalWidth }) => {
47
47
  // Set label color
48
48
  let labelColor = '#000000'
49
49
 
50
- if (chroma.contrast(labelColor, groupOne.color) < 4.9) {
50
+ if (groupOne.color && chroma.contrast(labelColor, groupOne.color) < 4.9) {
51
51
  groupOne.labelColor = '#FFFFFF'
52
52
  }
53
53
 
54
- if (chroma.contrast(labelColor, groupTwo.color) < 4.9) {
54
+ if (groupTwo.color && chroma.contrast(labelColor, groupTwo.color) < 4.9) {
55
55
  groupTwo.labelColor = '#FFFFFF'
56
56
  }
57
57
 
@@ -16,7 +16,7 @@ import { colorPalettesChart, sequentialPalettes } from '@cdc/core/data/colorPale
16
16
  const SeriesContext = React.createContext()
17
17
 
18
18
  const SeriesWrapper = props => {
19
- const { updateConfig, config } = useContext(ConfigContext)
19
+ const { updateConfig, config, rawData } = useContext(ConfigContext)
20
20
 
21
21
  const { getColumns, selectComponent } = props
22
22
 
@@ -26,6 +26,26 @@ const SeriesWrapper = props => {
26
26
  let series = [...config.series]
27
27
  series[index][property] = value
28
28
 
29
+ // Reset bars to the left axis if changed.
30
+ if (property === 'type') {
31
+ if (value === 'Bar') {
32
+ series[index].axis = 'Left'
33
+ }
34
+ }
35
+
36
+ // dynamically add in the forecasting fields
37
+ if (series[index].type === 'Forecasting') {
38
+ let forecastingStages = Array.from(new Set(rawData.map(item => item[series[index].dataKey])))
39
+ let forecastingStageArr = []
40
+
41
+ forecastingStages.forEach(stage => {
42
+ forecastingStageArr.push({ key: stage })
43
+ })
44
+ // debugger
45
+ series[index].stages = forecastingStageArr
46
+ series[index].stageColumn = series[index].dataKey
47
+ }
48
+
29
49
  updateConfig({ ...config, series })
30
50
  }
31
51
 
@@ -124,7 +144,6 @@ const SeriesDropdownForecastingStage = props => {
124
144
  const { index, series } = props
125
145
 
126
146
  // Only combo charts are allowed to have different options
127
- if (series.type !== 'Forecasting') return
128
147
 
129
148
  return (
130
149
  <InputSelect
@@ -408,7 +427,7 @@ const SeriesDropdownConfidenceInterval = props => {
408
427
  const SeriesInputName = props => {
409
428
  const { series, index: i } = props
410
429
  const { config, updateConfig } = useContext(ConfigContext)
411
- const adjustableNameSeriesTypes = ['Bar', 'Line', 'Area Chart']
430
+ const adjustableNameSeriesTypes = ['Bar', 'Line', 'Area Chart', 'dashed-sm', 'dashed-md', 'dashed-lg']
412
431
 
413
432
  if (config.visualizationType === 'Combo') return
414
433