@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.
- package/dist/cdcchart.js +30989 -29057
- package/examples/feature/bar/example-bar-chart.json +1 -46
- package/examples/feature/bar/lollipop.json +156 -0
- package/examples/feature/combo/planet-combo-example-config.json +99 -9
- package/examples/feature/dev-4261.json +399 -0
- package/examples/feature/forest-plot/{broken.json → linear.json} +55 -50
- package/examples/feature/forest-plot/logarithmic.json +306 -0
- package/examples/feature/line/line-points.json +340 -0
- package/examples/feature/regions/index.json +462 -0
- package/examples/gallery/bar-chart-vertical/combo-line-chart.json +181 -48
- package/examples/sparkline-multilple.json +846 -0
- package/index.html +10 -6
- package/package.json +3 -3
- package/src/CdcChart.tsx +75 -63
- package/src/_stories/Chart.stories.tsx +188 -0
- package/src/_stories/Chart.tooltip.stories.tsx +305 -0
- package/src/_stories/ChartBrush.stories.tsx +19 -0
- package/src/_stories/ChartSuppress.stories.tsx +19 -0
- package/src/_stories/_mock/brush_mock.json +393 -0
- package/src/_stories/_mock/suppress_mock.json +911 -0
- package/src/components/AreaChart.Stacked.jsx +4 -5
- package/src/components/AreaChart.jsx +6 -35
- package/src/components/{BarChart.Horizontal.jsx → BarChart.Horizontal.tsx} +106 -29
- package/src/components/{BarChart.StackedHorizontal.jsx → BarChart.StackedHorizontal.tsx} +22 -17
- package/src/components/{BarChart.StackedVertical.jsx → BarChart.StackedVertical.tsx} +22 -26
- package/src/components/{BarChart.Vertical.jsx → BarChart.Vertical.tsx} +175 -31
- package/src/components/BarChart.jsx +1 -1
- package/src/components/DeviationBar.jsx +4 -3
- package/src/components/EditorPanel.jsx +176 -50
- package/src/components/ForestPlot/ForestPlotProps.ts +11 -0
- package/src/components/ForestPlot/Readme.md +0 -0
- package/src/components/ForestPlot/index.scss +1 -0
- package/src/components/{ForestPlot.jsx → ForestPlot/index.tsx} +51 -31
- package/src/components/ForestPlotSettings.jsx +162 -113
- package/src/components/Legend.jsx +36 -3
- package/src/components/{LineChart.Circle.tsx → LineChart/LineChart.Circle.tsx} +26 -29
- package/src/components/LineChart/LineChartProps.ts +17 -0
- package/src/components/LineChart/index.scss +1 -0
- package/src/components/{LineChart.tsx → LineChart/index.tsx} +64 -35
- package/src/components/LinearChart.jsx +120 -60
- package/src/components/PairedBarChart.jsx +2 -2
- package/src/components/Series.jsx +22 -3
- package/src/components/ZoomBrush.tsx +168 -0
- package/src/data/initial-state.js +27 -12
- package/src/hooks/useBarChart.js +71 -7
- package/src/hooks/useColorScale.ts +50 -0
- package/src/hooks/useEditorPermissions.js +22 -4
- package/src/hooks/{useMinMax.js → useMinMax.ts} +75 -23
- package/src/hooks/{useRightAxis.js → useRightAxis.ts} +10 -2
- package/src/hooks/{useScales.js → useScales.ts} +64 -17
- package/src/hooks/useTooltip.jsx +68 -41
- package/src/scss/main.scss +3 -35
- package/src/types/ChartConfig.ts +43 -0
- package/src/types/ChartContext.ts +38 -0
- package/src/types/ChartProps.ts +7 -0
- 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 '
|
|
10
|
-
import useRightAxis from '
|
|
9
|
+
import ConfigContext from '../../ConfigContext'
|
|
10
|
+
import useRightAxis from '../../hooks/useRightAxis'
|
|
11
11
|
import LineChartCircle from './LineChart.Circle'
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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 =
|
|
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={
|
|
48
|
-
display={
|
|
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
|
-
{
|
|
78
|
-
|
|
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
|
-
|
|
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
|
-
{
|
|
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
|
|
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
|
|
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
|
-
|
|
64
|
-
|
|
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 {
|
|
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 {
|
|
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
|
|
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 +
|
|
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
|
|
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
|
-
|
|
727
|
-
|
|
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
|
|