@cdc/chart 4.23.2 → 4.23.4
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 +42292 -40337
- package/examples/feature/__data__/area-chart.json +56 -0
- package/examples/{planet-example-data.json → feature/__data__/planet-example-data-max-increase.json} +4 -4
- package/examples/feature/__data__/planet-example-data.json +68 -0
- package/examples/feature/area/area-chart.json +244 -0
- package/examples/{example-bar-chart.json → feature/bar/example-bar-chart.json} +4 -1
- package/examples/feature/bar/horizontal-chart-max-increase.json +44 -0
- package/examples/{horizontal-chart.json → feature/bar/horizontal-chart.json} +10 -4
- package/examples/{horizontal-stacked-bar-chart.json → feature/bar/horizontal-stacked-bar-chart.json} +7 -3
- package/examples/{planet-chart-horizontal-example-config.json → feature/bar/planet-chart-horizontal-example-config.json} +8 -3
- package/examples/feature/bar/planet-example-config.json +156 -0
- package/examples/{box-plot.json → feature/boxplot/boxplot.json} +7 -8
- package/examples/feature/boxplot/testing.csv +38 -0
- package/examples/feature/combo/combochart-categories_are_numbers .json +18 -0
- package/examples/{planet-combo-example-config.json → feature/combo/planet-combo-example-config.json} +1 -1
- package/examples/feature/deviation/planet-deviation-config.json +168 -0
- package/examples/feature/deviation/planet-deviation-data.json +38 -0
- package/examples/feature/filters/filter-testing.json +178 -0
- package/examples/feature/forecasting/case_date_example.csv +130 -0
- package/examples/feature/forecasting/effective_reproduction.json +202 -0
- package/examples/feature/forecasting/r_data.csv +130 -0
- package/examples/feature/line/line-chart-max-increase.json +32 -0
- package/examples/feature/line/line-chart.json +124 -0
- package/examples/{paired-bar-example.json → feature/paired-bar/paired-bar-example.json} +10 -4
- package/examples/{planet-pie-example-config.json → feature/pie/planet-pie-example-config.json} +2 -2
- package/examples/{scatterplot-continuous.csv → feature/scatterplot/scatterplot-continuous.csv} +3 -3
- package/examples/{scatterplot.json → feature/scatterplot/scatterplot.json} +3 -3
- package/examples/feature/sparkline/example-sparkline.json +76 -0
- package/examples/feature/tests-big-small/big-small-test-bar.json +328 -0
- package/examples/feature/tests-big-small/big-small-test-line.json +328 -0
- package/examples/feature/tests-big-small/big-small-test-negative.json +328 -0
- package/examples/{case-rate-example-config.json → feature/tests-case-rate/case-rate-example-config.json} +2 -2
- package/examples/{covid-confidence-example-config.json → feature/tests-covid/covid-confidence-example-config.json} +8 -3
- package/examples/{covid-example-config.json → feature/tests-covid/covid-example-config.json} +7 -3
- package/examples/{cutoff-example-config.json → feature/tests-cutoff/cutoff-example-config.json} +7 -3
- package/examples/{date-exclusions-config.json → feature/tests-date-exclusions/date-exclusions-config.json} +2 -2
- package/examples/{example-bar-chart-nonnumeric.json → feature/tests-non-numerics/example-bar-chart-nonnumeric.json} +1 -1
- package/examples/{line-chart-nonnumeric.json → feature/tests-non-numerics/line-chart-nonnumeric.json} +5 -5
- package/examples/{planet-pie-example-config-nonnumeric.json → feature/tests-non-numerics/planet-pie-example-config-nonnumeric.json} +2 -2
- package/examples/{sparkline-chart-nonnumeric.json → feature/tests-non-numerics/sparkline-chart-nonnumeric.json} +2 -2
- package/examples/gallery/bar-chart-horizontal/horizontal-bar-chart.json +31 -172
- package/examples/gallery/bar-chart-vertical/combo-line-chart.json +145 -7
- package/examples/gallery/bar-chart-vertical/vertical-bar-chart-confidence.json +1 -0
- package/examples/gallery/bar-chart-vertical/vertical-bar-chart-with-confidence.json +96 -14
- package/examples/gallery/line/line.json +1 -0
- package/examples/gallery/paired-bar/paired-bar-chart.json +1 -0
- package/index.html +76 -35
- package/package.json +6 -3
- package/src/CdcChart.jsx +245 -106
- package/src/components/AreaChart.jsx +233 -0
- package/src/components/BarChart.jsx +103 -62
- package/src/components/BoxPlot.jsx +39 -18
- package/src/components/DataTable.jsx +26 -21
- package/src/components/DeviationBar.jsx +191 -0
- package/src/components/EditorPanel.jsx +662 -298
- package/src/components/Legend.jsx +59 -46
- package/src/components/LineChart.jsx +12 -36
- package/src/components/LinearChart.jsx +163 -64
- package/src/components/PairedBarChart.jsx +6 -7
- package/src/components/PieChart.jsx +12 -17
- package/src/components/ScatterPlot.jsx +19 -16
- package/src/components/SparkLine.jsx +84 -118
- package/src/components/useIntersectionObserver.jsx +1 -1
- package/src/data/initial-state.js +27 -7
- package/src/hooks/useColorPalette.js +58 -48
- package/src/hooks/useReduceData.js +3 -4
- package/src/index.jsx +3 -2
- package/src/scss/editor-panel.scss +20 -0
- package/src/scss/main.scss +8 -6
- package/src/test/CdcChart.test.jsx +6 -0
- package/examples/box-plot.csv +0 -5
- package/examples/dynamic-legends.json +0 -125
- package/examples/line-chart.json +0 -34
- package/examples/planet-example-config.json +0 -37
- package/examples/temp-example-config.json +0 -64
- package/examples/temp-example-data.json +0 -130
- package/src/components/Filters.jsx +0 -125
- /package/examples/{age-adjusted-rates.json → feature/__data__/age-adjusted-rates.json} +0 -0
- /package/examples/{new-data.csv → feature/__data__/new-data.csv} +0 -0
- /package/examples/{Barchart_with_negative.json → feature/bar/Barchart_with_negative.json} +0 -0
- /package/examples/{stacked-vertical-bar-example-negative.json → feature/bar/stacked-vertical-bar-example-negative.json} +0 -0
- /package/examples/{stacked-vertical-bar-example.json → feature/bar/stacked-vertical-bar-example.json} +0 -0
- /package/examples/{box-plot-data.json → feature/boxplot/box-plot-data.json} +0 -0
- /package/examples/{newdata.json → feature/boxplot/boxplot-data.json} +0 -0
- /package/examples/{paired-bar-data.json → feature/paired-bar/paired-bar-data.json} +0 -0
- /package/examples/{paired-bar-formatted.json → feature/paired-bar/paired-bar-formatted.json} +0 -0
- /package/examples/{case-rate-example-data.json → feature/tests-case-rate/case-rate-example-data.json} +0 -0
- /package/examples/{covid-example-data-confidence.json → feature/tests-covid/covid-example-data-confidence.json} +0 -0
- /package/examples/{covid-example-data.json → feature/tests-covid/covid-example-data.json} +0 -0
- /package/examples/{cutoff-example-data.json → feature/tests-cutoff/cutoff-example-data.json} +0 -0
- /package/examples/{date-exclusions-data.json → feature/tests-date-exclusions/date-exclusions-data.json} +0 -0
- /package/examples/{example-combo-bar-nonnumeric.json → feature/tests-non-numerics/example-combo-bar-nonnumeric.json} +0 -0
- /package/examples/{planet-example-data-nonnumeric.json → feature/tests-non-numerics/planet-example-data-nonnumeric.json} +0 -0
- /package/examples/{stacked-vertical-bar-example-nonnumerics.json → feature/tests-non-numerics/stacked-vertical-bar-example-nonnumerics.json} +0 -0
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import React, { useContext, useEffect, useState } from 'react'
|
|
2
|
+
|
|
3
|
+
// cdc
|
|
4
|
+
import ConfigContext from '../ConfigContext'
|
|
5
|
+
import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
6
|
+
import { colorPalettesChart } from '@cdc/core/data/colorPalettes'
|
|
7
|
+
|
|
8
|
+
// visx & d3
|
|
9
|
+
import * as allCurves from '@visx/curve'
|
|
10
|
+
import { AreaClosed, LinePath, Bar } from '@visx/shape'
|
|
11
|
+
import { Group } from '@visx/group'
|
|
12
|
+
import { useTooltip, useTooltipInPortal, defaultStyles, Tooltip } from '@visx/tooltip'
|
|
13
|
+
import { localPoint } from '@visx/event'
|
|
14
|
+
import { bisector } from 'd3-array'
|
|
15
|
+
|
|
16
|
+
const CoveAreaChart = ({ xScale, yScale, yMax, xMax, chartRef }) => {
|
|
17
|
+
// enable various console logs in the file
|
|
18
|
+
const DEBUG = false
|
|
19
|
+
const [chartPosition, setChartPosition] = useState(null)
|
|
20
|
+
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
setChartPosition(chartRef.current.getBoundingClientRect())
|
|
23
|
+
}, [chartRef])
|
|
24
|
+
|
|
25
|
+
// import data from context
|
|
26
|
+
const { transformedData: data, config, handleLineType, parseDate, formatDate, formatNumber, seriesHighlight, colorScale } = useContext(ConfigContext)
|
|
27
|
+
const tooltip_id = `cdc-open-viz-tooltip-${config.runtime.uniqueId}`
|
|
28
|
+
|
|
29
|
+
// import tooltip helpers
|
|
30
|
+
const { tooltipData, showTooltip } = useTooltip()
|
|
31
|
+
|
|
32
|
+
// here we're inside of the svg,
|
|
33
|
+
// it appears we need to use TooltipInPortal.
|
|
34
|
+
const { TooltipInPortal } = useTooltipInPortal({
|
|
35
|
+
detectBounds: true,
|
|
36
|
+
// when tooltip containers are scrolled, this will correctly update the Tooltip position
|
|
37
|
+
scroll: true
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
// Draw transparent bars over the chart to get tooltip data
|
|
41
|
+
// Turn DEBUG on for additional context.
|
|
42
|
+
if (!data) return
|
|
43
|
+
let barThickness = xMax / data
|
|
44
|
+
let barThicknessAdjusted = barThickness * (config.barThickness || 0.8)
|
|
45
|
+
let offset = (barThickness * (1 - (config.barThickness || 0.8))) / 2
|
|
46
|
+
|
|
47
|
+
// Tooltip helper for getting data to the closest date/category hovered.
|
|
48
|
+
const getXValueFromCoordinate = x => {
|
|
49
|
+
if (config.xAxis.type === 'categorical') {
|
|
50
|
+
let eachBand = xScale.step()
|
|
51
|
+
let numerator = x
|
|
52
|
+
const index = Math.floor(Number(numerator) / eachBand)
|
|
53
|
+
return xScale.domain()[index - 1] // fixes off by 1 error
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (config.xAxis.type === 'date') {
|
|
57
|
+
const bisectDate = bisector(d => parseDate(d[config.xAxis.dataKey])).left
|
|
58
|
+
if (!x) return
|
|
59
|
+
if (!xScale) return
|
|
60
|
+
const x0 = xScale.invert(x)
|
|
61
|
+
const index = bisectDate(config.data, x0, 1)
|
|
62
|
+
const val = parseDate(config.data[index - 1][config.xAxis.dataKey])
|
|
63
|
+
return val
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const handleMouseOver = (e, data) => {
|
|
68
|
+
// get the svg coordinates of the mouse
|
|
69
|
+
// and get the closest values
|
|
70
|
+
const eventSvgCoords = localPoint(e)
|
|
71
|
+
const { x, y } = eventSvgCoords
|
|
72
|
+
|
|
73
|
+
let closestXScaleValue = getXValueFromCoordinate(x)
|
|
74
|
+
let formattedDate = formatDate(closestXScaleValue)
|
|
75
|
+
|
|
76
|
+
let yScaleValues
|
|
77
|
+
if (config.xAxis.type === 'categorical') {
|
|
78
|
+
yScaleValues = data.filter(d => d[config.xAxis.dataKey] === closestXScaleValue)
|
|
79
|
+
} else {
|
|
80
|
+
yScaleValues = data.filter(d => formatDate(parseDate(d[config.xAxis.dataKey])) === formattedDate)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
let seriesToInclude = []
|
|
84
|
+
let yScaleMaxValues = []
|
|
85
|
+
let itemsToLoop = [config.runtime.xAxis.dataKey, ...config.runtime.seriesKeys]
|
|
86
|
+
|
|
87
|
+
itemsToLoop.map(seriesKey => {
|
|
88
|
+
if (!seriesKey) return
|
|
89
|
+
if (!yScaleValues[0]) return
|
|
90
|
+
for (const item of Object.entries(yScaleValues[0])) {
|
|
91
|
+
if (item[0] === seriesKey) {
|
|
92
|
+
seriesToInclude.push(item)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
// filter out the series that aren't added to the map.
|
|
98
|
+
seriesToInclude.map(series => yScaleMaxValues.push(Number(yScaleValues[0][series])))
|
|
99
|
+
if (!seriesToInclude) return
|
|
100
|
+
let tooltipDataFromSeries = Object.fromEntries(seriesToInclude) ? Object.fromEntries(seriesToInclude) : {}
|
|
101
|
+
|
|
102
|
+
let tooltipData = {}
|
|
103
|
+
tooltipData.data = tooltipDataFromSeries
|
|
104
|
+
tooltipData.dataXPosition = x + 20
|
|
105
|
+
tooltipData.dataYPosition = y - 100
|
|
106
|
+
|
|
107
|
+
let tooltipInformation = {
|
|
108
|
+
tooltipData: tooltipData,
|
|
109
|
+
tooltipTop: 0,
|
|
110
|
+
tooltipValues: yScaleValues,
|
|
111
|
+
tooltipLeft: x
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
showTooltip(tooltipInformation)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const TooltipListItem = ({ item }) => {
|
|
118
|
+
const [label, value] = item
|
|
119
|
+
return label === config.xAxis.dataKey ? `${label}: ${value}` : `${label}: ${formatNumber(value, 'left')}`
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const handleX = d => {
|
|
123
|
+
return config.xAxis.type === 'date' ? xScale(parseDate(d[config.xAxis.dataKey])) : xScale(d[config.xAxis.dataKey])
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const handleY = (d, index) => {
|
|
127
|
+
return yScale(d[config.series[index].dataKey])
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return (
|
|
131
|
+
data && (
|
|
132
|
+
<ErrorBoundary component='AreaChart'>
|
|
133
|
+
<Group className='area-chart' key='area-wrapper' left={Number(config.yAxis.size)}>
|
|
134
|
+
{(config.runtime.areaSeriesKeys || config.runtime.seriesKeys).map((s, index) => {
|
|
135
|
+
let seriesColor = colorPalettesChart[config.palette][index]
|
|
136
|
+
let curveType = allCurves[s.lineType]
|
|
137
|
+
let transparentArea = config.legend.behavior === 'highlight' && seriesHighlight.length > 0 && seriesHighlight.indexOf(s.dataKey) === -1
|
|
138
|
+
let displayArea = config.legend.behavior === 'highlight' || seriesHighlight.length === 0 || seriesHighlight.indexOf(s.dataKey) !== -1
|
|
139
|
+
|
|
140
|
+
data.map(d => xScale(parseDate(d[config.xAxis.dataKey])))
|
|
141
|
+
|
|
142
|
+
return (
|
|
143
|
+
<React.Fragment key={index}>
|
|
144
|
+
{/* prettier-ignore */}
|
|
145
|
+
<LinePath
|
|
146
|
+
data={data}
|
|
147
|
+
x={d => handleX(d)}
|
|
148
|
+
y={d => yScale(d[config.series[index].dataKey])}
|
|
149
|
+
stroke={displayArea ? seriesColor : 'transparent'}
|
|
150
|
+
strokeWidth={2}
|
|
151
|
+
strokeOpacity={1}
|
|
152
|
+
shapeRendering='geometricPrecision'
|
|
153
|
+
curve={curveType}
|
|
154
|
+
strokeDasharray={s.type ? handleLineType(s.type) : 0}
|
|
155
|
+
/>
|
|
156
|
+
|
|
157
|
+
{/* prettier-ignore */}
|
|
158
|
+
<AreaClosed
|
|
159
|
+
key={'area-chart'}
|
|
160
|
+
fill={ displayArea ? colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[s.dataKey] : s.dataKey) : '#000' : 'transparent'}
|
|
161
|
+
fillOpacity={transparentArea ? 0.25 : 0.5}
|
|
162
|
+
data={data} x={d => handleX(d)}
|
|
163
|
+
y={d => handleY(d, index)}
|
|
164
|
+
yScale={yScale}
|
|
165
|
+
curve={curveType}
|
|
166
|
+
strokeDasharray={s.type ? handleLineType(s.typ) : 0}
|
|
167
|
+
/>
|
|
168
|
+
|
|
169
|
+
{/* Transparent bar for tooltips */}
|
|
170
|
+
{/* prettier-ignore */}
|
|
171
|
+
<Bar
|
|
172
|
+
width={ Number(xMax)}
|
|
173
|
+
height={ Number(yMax)}
|
|
174
|
+
fill={DEBUG ? 'red' : 'transparent'}
|
|
175
|
+
fillOpacity={0.05}
|
|
176
|
+
style={DEBUG ? { stroke: 'black', strokeWidth: 2 } : {}}
|
|
177
|
+
onMouseMove={e => handleMouseOver(e, data)}
|
|
178
|
+
/>
|
|
179
|
+
|
|
180
|
+
{/* circles that appear on hover */}
|
|
181
|
+
{tooltipData && (
|
|
182
|
+
<circle
|
|
183
|
+
cx={config.xAxis.type === 'categorical' ? xScale(tooltipData.data[config.xAxis.dataKey]) : xScale(parseDate(tooltipData.data[config.xAxis.dataKey]))}
|
|
184
|
+
cy={yScale(tooltipData.data[s.dataKey])}
|
|
185
|
+
r={4.5}
|
|
186
|
+
opacity={1}
|
|
187
|
+
fillOpacity={1}
|
|
188
|
+
fill={displayArea ? (colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[s.dataKey] : s.dataKey) : '#000') : 'transparent'}
|
|
189
|
+
style={{ filter: 'unset', opacity: 1 }}
|
|
190
|
+
/>
|
|
191
|
+
)}
|
|
192
|
+
|
|
193
|
+
{/* another tool for showing bars during debug mode. */}
|
|
194
|
+
{DEBUG &&
|
|
195
|
+
data.map((item, index) => {
|
|
196
|
+
return (
|
|
197
|
+
<Bar
|
|
198
|
+
className='bar-here'
|
|
199
|
+
x={Number(barThickness * index + offset)}
|
|
200
|
+
y={d => Number(yScale(d[config.series[index].dataKey]))}
|
|
201
|
+
yScale={yScale}
|
|
202
|
+
width={barThicknessAdjusted}
|
|
203
|
+
height={yMax}
|
|
204
|
+
fill={'transparent'}
|
|
205
|
+
fillOpacity={1}
|
|
206
|
+
style={{ stroke: 'black', strokeWidth: 2 }}
|
|
207
|
+
onMouseMove={e => handleMouseOver(e, data)}
|
|
208
|
+
/>
|
|
209
|
+
)
|
|
210
|
+
})}
|
|
211
|
+
|
|
212
|
+
{tooltipData && (
|
|
213
|
+
<TooltipInPortal key={Math.random()} top={tooltipData.dataYPosition + chartPosition?.top} left={tooltipData.dataXPosition + chartPosition?.left} style={defaultStyles}>
|
|
214
|
+
<ul style={{ listStyle: 'none', paddingLeft: 'unset', fontFamily: 'sans-serif', margin: 'auto', lineHeight: '1rem' }} data-tooltip-id={tooltip_id}>
|
|
215
|
+
{typeof tooltipData === 'object' &&
|
|
216
|
+
Object.entries(tooltipData.data).map(item => (
|
|
217
|
+
<li style={{ padding: '2.5px 0' }}>
|
|
218
|
+
<TooltipListItem item={item} />
|
|
219
|
+
</li>
|
|
220
|
+
))}
|
|
221
|
+
</ul>
|
|
222
|
+
</TooltipInPortal>
|
|
223
|
+
)}
|
|
224
|
+
</React.Fragment>
|
|
225
|
+
)
|
|
226
|
+
})}
|
|
227
|
+
</Group>
|
|
228
|
+
</ErrorBoundary>
|
|
229
|
+
)
|
|
230
|
+
)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export default CoveAreaChart
|
|
@@ -8,10 +8,7 @@ import ConfigContext from '../ConfigContext'
|
|
|
8
8
|
import { BarStackHorizontal } from '@visx/shape'
|
|
9
9
|
|
|
10
10
|
export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getXAxisData, getYAxisData, animatedChart, visible }) {
|
|
11
|
-
const { transformedData: data, colorScale, seriesHighlight, config, formatNumber, updateConfig, colorPalettes, formatDate, isNumber,
|
|
12
|
-
// Just do this once up front otherwise we end up
|
|
13
|
-
// calling clean several times on same set of data (TT)
|
|
14
|
-
const cleanedData = cleanData(data, config.xAxis.dataKey)
|
|
11
|
+
const { transformedData: data, colorScale, seriesHighlight, config, formatNumber, updateConfig, colorPalettes, formatDate, isNumber, getTextWidth, parseDate } = useContext(ConfigContext)
|
|
15
12
|
|
|
16
13
|
const { orientation, visualizationSubType } = config
|
|
17
14
|
const isHorizontal = orientation === 'horizontal'
|
|
@@ -32,17 +29,15 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
32
29
|
const fontSize = { small: 16, medium: 18, large: 20 }
|
|
33
30
|
const hasMultipleSeries = Object.keys(config.runtime.seriesLabels).length > 1
|
|
34
31
|
|
|
35
|
-
const applyRadius =
|
|
32
|
+
const applyRadius = index => {
|
|
36
33
|
if (index === undefined || index === null || !isRounded) return
|
|
37
34
|
let style = {}
|
|
38
35
|
|
|
39
36
|
if ((isStacked && index + 1 === stackCount) || !isStacked) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
style = isHorizontal ? { borderRadius: `0 ${radius} ${radius} 0` } : { borderRadius: `${radius} ${radius} 0 0` }
|
|
45
|
-
}
|
|
37
|
+
style = isHorizontal ? { borderRadius: `0 ${radius} ${radius} 0` } : { borderRadius: `${radius} ${radius} 0 0` }
|
|
38
|
+
}
|
|
39
|
+
if (!isStacked && index === -1) {
|
|
40
|
+
style = isHorizontal ? { borderRadius: `${radius} 0 0 ${radius} ` } : { borderRadius: ` 0 0 ${radius} ${radius}` }
|
|
46
41
|
}
|
|
47
42
|
if (tipRounding === 'full' && isStacked && index === 0 && stackCount > 1) {
|
|
48
43
|
style = isHorizontal ? { borderRadius: `${radius} 0 0 ${radius}` } : { borderRadius: `0 0 ${radius} ${radius}` }
|
|
@@ -52,7 +47,6 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
52
47
|
}
|
|
53
48
|
return style
|
|
54
49
|
}
|
|
55
|
-
// }
|
|
56
50
|
|
|
57
51
|
const updateBars = defaultBars => {
|
|
58
52
|
// function updates stacked && regular && lollipop horizontal bars
|
|
@@ -84,7 +78,7 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
84
78
|
|
|
85
79
|
// return new updated bars/groupes
|
|
86
80
|
return barsArr.map((bar, i) => {
|
|
87
|
-
// set bars Y
|
|
81
|
+
// set bars Y dynamically to handle space between bars
|
|
88
82
|
let y = 0
|
|
89
83
|
bar.index !== 0 && (y = (barHeight + barSpace + labelHeight) * i)
|
|
90
84
|
|
|
@@ -105,13 +99,13 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
105
99
|
}
|
|
106
100
|
})
|
|
107
101
|
}
|
|
108
|
-
}, [config, updateConfig])
|
|
102
|
+
}, [config, updateConfig]) // eslint-disable-line
|
|
109
103
|
|
|
110
104
|
useEffect(() => {
|
|
111
105
|
if (config.isLollipopChart === false && config.barHeight < 25) {
|
|
112
106
|
updateConfig({ ...config, barHeight: 25 })
|
|
113
107
|
}
|
|
114
|
-
}, [config.isLollipopChart])
|
|
108
|
+
}, [config.isLollipopChart]) // eslint-disable-line
|
|
115
109
|
|
|
116
110
|
useEffect(() => {
|
|
117
111
|
if (config.visualizationSubType === 'horizontal') {
|
|
@@ -120,7 +114,7 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
120
114
|
orientation: 'horizontal'
|
|
121
115
|
})
|
|
122
116
|
}
|
|
123
|
-
}, [])
|
|
117
|
+
}, []) // eslint-disable-line
|
|
124
118
|
|
|
125
119
|
useEffect(() => {
|
|
126
120
|
if (config.barStyle === 'lollipop' && !config.isLollipopChart) {
|
|
@@ -129,14 +123,14 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
129
123
|
if (isRounded || config.barStyle === 'flat') {
|
|
130
124
|
updateConfig({ ...config, isLollipopChart: false })
|
|
131
125
|
}
|
|
132
|
-
}, [config.barStyle])
|
|
126
|
+
}, [config.barStyle]) // eslint-disable-line
|
|
133
127
|
|
|
134
128
|
return (
|
|
135
129
|
<ErrorBoundary component='BarChart'>
|
|
136
130
|
<Group left={parseFloat(config.runtime.yAxis.size)}>
|
|
137
131
|
{/* Stacked Vertical */}
|
|
138
132
|
{config.visualizationSubType === 'stacked' && !isHorizontal && (
|
|
139
|
-
<BarStack data={
|
|
133
|
+
<BarStack data={data} keys={config.runtime.barSeriesKeys || config.runtime.seriesKeys} x={d => d[config.runtime.xAxis.dataKey]} xScale={xScale} yScale={yScale} color={colorScale}>
|
|
140
134
|
{barStacks =>
|
|
141
135
|
barStacks.reverse().map(barStack =>
|
|
142
136
|
barStack.bars.map(bar => {
|
|
@@ -147,8 +141,9 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
147
141
|
let offset = (barThickness * (1 - (config.barThickness || 0.8))) / 2
|
|
148
142
|
// tooltips
|
|
149
143
|
const xAxisValue = config.runtime.xAxis.type === 'date' ? formatDate(parseDate(data[bar.index][config.runtime.xAxis.dataKey])) : data[bar.index][config.runtime.xAxis.dataKey]
|
|
150
|
-
const yAxisValue = formatNumber(bar.bar ? bar.bar.data[bar.key] : 0)
|
|
151
|
-
|
|
144
|
+
const yAxisValue = formatNumber(bar.bar ? bar.bar.data[bar.key] : 0, 'left')
|
|
145
|
+
|
|
146
|
+
const style = applyRadius(barStack.index)
|
|
152
147
|
let yAxisTooltip = config.runtime.yAxis.label ? `${config.runtime.yAxis.label}: ${yAxisValue}` : yAxisValue
|
|
153
148
|
const xAxisTooltip = config.runtime.xAxis.label ? `${config.runtime.xAxis.label}: ${xAxisValue}` : xAxisValue
|
|
154
149
|
if (!hasMultipleSeries) {
|
|
@@ -161,7 +156,7 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
161
156
|
${xAxisTooltip}
|
|
162
157
|
</div>`
|
|
163
158
|
return (
|
|
164
|
-
|
|
159
|
+
<Group key={`${barStack.index}--${bar.index}--${orientation}`}>
|
|
165
160
|
<style>
|
|
166
161
|
{`
|
|
167
162
|
#barStack${barStack.index}-${bar.index} rect,
|
|
@@ -173,7 +168,7 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
173
168
|
</style>
|
|
174
169
|
<Group key={`bar-stack-${barStack.index}-${bar.index}`} id={`barStack${barStack.index}-${bar.index}`} className='stack vertical'>
|
|
175
170
|
<Text display={config.labels && displayBar ? 'block' : 'none'} opacity={transparentBar ? 0.5 : 1} x={barThickness * bar.index + offset} y={bar.y - 5} fill={bar.color} textAnchor='middle'>
|
|
176
|
-
{
|
|
171
|
+
{yAxisValue}
|
|
177
172
|
</Text>
|
|
178
173
|
<foreignObject
|
|
179
174
|
key={`bar-stack-${barStack.index}-${bar.index}`}
|
|
@@ -188,7 +183,7 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
188
183
|
data-tooltip-id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
|
|
189
184
|
></foreignObject>
|
|
190
185
|
</Group>
|
|
191
|
-
|
|
186
|
+
</Group>
|
|
192
187
|
)
|
|
193
188
|
})
|
|
194
189
|
)
|
|
@@ -199,7 +194,7 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
199
194
|
{/* Stacked Horizontal */}
|
|
200
195
|
{config.visualizationSubType === 'stacked' && isHorizontal && (
|
|
201
196
|
<>
|
|
202
|
-
<BarStackHorizontal data={
|
|
197
|
+
<BarStackHorizontal data={data} keys={config.runtime.barSeriesKeys || config.runtime.seriesKeys} height={yMax} y={d => d[config.runtime.yAxis.dataKey]} xScale={xScale} yScale={yScale} color={colorScale} offset='none'>
|
|
203
198
|
{barStacks =>
|
|
204
199
|
barStacks.map(barStack =>
|
|
205
200
|
updateBars(barStack.bars).map((bar, index) => {
|
|
@@ -208,9 +203,9 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
208
203
|
config.barHeight = Number(config.barHeight)
|
|
209
204
|
let labelColor = '#000000'
|
|
210
205
|
// tooltips
|
|
211
|
-
const xAxisValue = formatNumber(data[bar.index][bar.key])
|
|
206
|
+
const xAxisValue = formatNumber(data[bar.index][bar.key], 'left')
|
|
212
207
|
const yAxisValue = config.runtime.yAxis.type === 'date' ? formatDate(parseDate(data[bar.index][config.runtime.originalXAxis.dataKey])) : data[bar.index][config.runtime.originalXAxis.dataKey]
|
|
213
|
-
const style = applyRadius(barStack.index
|
|
208
|
+
const style = applyRadius(barStack.index)
|
|
214
209
|
let yAxisTooltip = config.runtime.yAxis.label ? `${config.runtime.yAxis.label}: ${yAxisValue}` : yAxisValue
|
|
215
210
|
let xAxisTooltip = config.runtime.xAxis.label ? `${config.runtime.xAxis.label}: ${xAxisValue}` : xAxisValue
|
|
216
211
|
if (!hasMultipleSeries) {
|
|
@@ -264,7 +259,7 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
264
259
|
</Text>
|
|
265
260
|
)}
|
|
266
261
|
|
|
267
|
-
{displayNumbersOnBar && textWidth
|
|
262
|
+
{displayNumbersOnBar && textWidth < bar.width && (
|
|
268
263
|
<Text
|
|
269
264
|
display={displayBar ? 'block' : 'none'}
|
|
270
265
|
x={bar.x + barStack.bars[bar.index].width / 2} // padding
|
|
@@ -280,7 +275,7 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
280
275
|
}
|
|
281
276
|
}}
|
|
282
277
|
>
|
|
283
|
-
{
|
|
278
|
+
{xAxisValue}
|
|
284
279
|
</Text>
|
|
285
280
|
)}
|
|
286
281
|
</Group>
|
|
@@ -297,7 +292,7 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
297
292
|
{config.visualizationSubType !== 'stacked' && (
|
|
298
293
|
<Group>
|
|
299
294
|
<BarGroup
|
|
300
|
-
data={
|
|
295
|
+
data={data}
|
|
301
296
|
keys={config.runtime.barSeriesKeys || config.runtime.seriesKeys}
|
|
302
297
|
height={yMax}
|
|
303
298
|
x0={d => d[config.runtime.originalXAxis.dataKey]}
|
|
@@ -313,6 +308,7 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
313
308
|
<Group
|
|
314
309
|
className={`bar-group-${barGroup.index}-${barGroup.x0}--${index} ${config.orientation}`}
|
|
315
310
|
key={`bar-group-${barGroup.index}-${barGroup.x0}--${index}`}
|
|
311
|
+
id={`bar-group-${barGroup.index}-${barGroup.x0}--${index}`}
|
|
316
312
|
top={config.runtime.horizontal ? barGroup.y : 0}
|
|
317
313
|
left={config.runtime.horizontal ? 0 : (xMax / barGroups.length) * barGroup.index}
|
|
318
314
|
>
|
|
@@ -323,7 +319,8 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
323
319
|
let barY = bar.value >= 0 && isNumber(bar.value) ? bar.y : yScale(0)
|
|
324
320
|
let barGroupWidth = ((config.runtime.horizontal ? yMax : xMax) / barGroups.length) * (config.barThickness || 0.8)
|
|
325
321
|
let offset = (((config.runtime.horizontal ? yMax : xMax) / barGroups.length) * (1 - (config.barThickness || 0.8))) / 2
|
|
326
|
-
|
|
322
|
+
const barX = bar.value < 0 ? Math.abs(xScale(bar.value)) : xScale(0)
|
|
323
|
+
const barWidthHorizontal = Math.abs(xScale(bar.value) - xScale(0))
|
|
327
324
|
// ! Unsure if this should go back.
|
|
328
325
|
if (config.isLollipopChart) {
|
|
329
326
|
offset = (config.runtime.horizontal ? yMax : xMax) / barGroups.length / 2 - lollipopBarWidth / 2
|
|
@@ -341,7 +338,7 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
341
338
|
}
|
|
342
339
|
if (config.legend.colorCode && config.series.length === 1) barColor = palette[barGroup.index]
|
|
343
340
|
|
|
344
|
-
let yAxisValue = formatNumber(bar.value)
|
|
341
|
+
let yAxisValue = formatNumber(bar.value, 'left')
|
|
345
342
|
let xAxisValue = config.runtime[section].type === 'date' ? formatDate(parseDate(data[barGroup.index][config.runtime.originalXAxis.dataKey])) : data[barGroup.index][config.runtime.originalXAxis.dataKey]
|
|
346
343
|
|
|
347
344
|
if (config.runtime.horizontal) {
|
|
@@ -350,18 +347,38 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
350
347
|
xAxisValue = tempValue
|
|
351
348
|
barWidth = config.barHeight
|
|
352
349
|
}
|
|
350
|
+
|
|
351
|
+
const barPosition = bar.value < 0 ? 'below' : 'above'
|
|
352
|
+
const textX = barPosition === 'below' ? 0 : 0
|
|
353
|
+
|
|
353
354
|
// check if bar text/value string fits into each bars.
|
|
354
355
|
let textWidth = getTextWidth(xAxisValue, `normal ${fontSize[config.fontSize]}px sans-serif`)
|
|
355
|
-
let textFits = textWidth <
|
|
356
|
-
|
|
356
|
+
let textFits = textWidth < barWidthHorizontal - 5 // minus padding 5
|
|
357
357
|
let labelColor = '#000000'
|
|
358
358
|
|
|
359
359
|
// Set label color
|
|
360
360
|
if (chroma.contrast(labelColor, barColor) < 4.9) {
|
|
361
|
-
|
|
361
|
+
textFits ? (labelColor = '#FFFFFF') : '#000000'
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// control text position
|
|
365
|
+
let textAnchor = textFits ? 'end' : 'start'
|
|
366
|
+
let textAnchorLollipop = 'start'
|
|
367
|
+
let textPadding = textFits ? -5 : 5
|
|
368
|
+
let textPaddingLollipop = 10
|
|
369
|
+
// if bars are negative we change positions of text
|
|
370
|
+
if (barPosition === 'below') {
|
|
371
|
+
textAnchor = textFits ? 'start' : 'end'
|
|
372
|
+
textPadding = textFits ? 5 : -5
|
|
373
|
+
if (config.isLollipopChart) {
|
|
374
|
+
textAnchorLollipop = 'end'
|
|
375
|
+
textPaddingLollipop = -10
|
|
376
|
+
}
|
|
362
377
|
}
|
|
363
378
|
|
|
364
|
-
|
|
379
|
+
// create new Index based on bar value for border Radius
|
|
380
|
+
const newIndex = bar.value < 0 ? -1 : index
|
|
381
|
+
const style = applyRadius(newIndex)
|
|
365
382
|
|
|
366
383
|
let yAxisTooltip = config.runtime.yAxis.label ? `${config.runtime.yAxis.label}: ${yAxisValue}` : yAxisValue
|
|
367
384
|
let xAxisTooltip = config.runtime.xAxis.label ? `${config.runtime.xAxis.label}: ${xAxisValue}` : xAxisValue
|
|
@@ -379,7 +396,7 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
379
396
|
</div>`
|
|
380
397
|
|
|
381
398
|
return (
|
|
382
|
-
|
|
399
|
+
<Group key={`${barGroup.index}--${index}--${orientation}`}>
|
|
383
400
|
{/* This feels gross but inline transition was not working well*/}
|
|
384
401
|
<style>
|
|
385
402
|
{`
|
|
@@ -393,9 +410,9 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
393
410
|
<foreignObject
|
|
394
411
|
id={`barGroup${barGroup.index}`}
|
|
395
412
|
key={`bar-group-bar-${barGroup.index}-${bar.index}-${bar.value}-${bar.key}`}
|
|
396
|
-
x={config.runtime.horizontal ?
|
|
413
|
+
x={config.runtime.horizontal ? barX : barWidth * bar.index + offset}
|
|
397
414
|
y={config.runtime.horizontal ? barWidth * bar.index : barY}
|
|
398
|
-
width={config.runtime.horizontal ?
|
|
415
|
+
width={config.runtime.horizontal ? barWidthHorizontal : barWidth}
|
|
399
416
|
height={isHorizontal && !config.isLollipopChart ? barWidth : isHorizontal && config.isLollipopChart ? lollipopBarWidth : barHeight}
|
|
400
417
|
style={{
|
|
401
418
|
background: config.isLollipopChart && config.lollipopColorStyle === 'regular' ? barColor : config.isLollipopChart && config.lollipopColorStyle === 'two-tone' ? chroma(barColor).brighten(1) : barColor,
|
|
@@ -413,9 +430,9 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
413
430
|
x={bar.y}
|
|
414
431
|
y={config.barHeight / 2 + config.barHeight * bar.index}
|
|
415
432
|
fill={labelColor}
|
|
416
|
-
dx={
|
|
433
|
+
dx={textPadding}
|
|
417
434
|
verticalAnchor='middle'
|
|
418
|
-
textAnchor={
|
|
435
|
+
textAnchor={textAnchor}
|
|
419
436
|
>
|
|
420
437
|
{xAxisValue}
|
|
421
438
|
</Text>
|
|
@@ -424,10 +441,11 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
424
441
|
{orientation === 'horizontal' && config.isLollipopChart && displayNumbersOnBar && (
|
|
425
442
|
<Text
|
|
426
443
|
display={displayBar ? 'block' : 'none'}
|
|
427
|
-
x={
|
|
444
|
+
x={bar.y} // padding
|
|
428
445
|
y={0}
|
|
429
446
|
fill={'#000000'}
|
|
430
|
-
|
|
447
|
+
dx={textPaddingLollipop}
|
|
448
|
+
textAnchor={textAnchorLollipop}
|
|
431
449
|
verticalAnchor='middle'
|
|
432
450
|
fontWeight={'normal'}
|
|
433
451
|
>
|
|
@@ -446,7 +464,7 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
446
464
|
;
|
|
447
465
|
{orientation === 'vertical' && (
|
|
448
466
|
<Text display={config.labels && displayBar ? 'block' : 'none'} opacity={transparentBar ? 0.5 : 1} x={barWidth * (bar.index + 0.5) + offset} y={barY - 5} fill={barColor} textAnchor='middle'>
|
|
449
|
-
{
|
|
467
|
+
{yAxisValue}
|
|
450
468
|
</Text>
|
|
451
469
|
)}
|
|
452
470
|
;
|
|
@@ -478,7 +496,7 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
478
496
|
</rect>
|
|
479
497
|
)}
|
|
480
498
|
</Group>
|
|
481
|
-
|
|
499
|
+
</Group>
|
|
482
500
|
)
|
|
483
501
|
})}
|
|
484
502
|
</Group>
|
|
@@ -488,25 +506,48 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
488
506
|
|
|
489
507
|
{Object.keys(config.confidenceKeys).length > 0
|
|
490
508
|
? data.map(d => {
|
|
491
|
-
let xPos
|
|
492
|
-
let upperPos
|
|
493
|
-
let lowerPos
|
|
509
|
+
let xPos, yPos
|
|
510
|
+
let upperPos
|
|
511
|
+
let lowerPos
|
|
494
512
|
let tickWidth = 5
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
513
|
+
// DEV-3264 Make Confidence Intervals work on horizontal bar charts
|
|
514
|
+
if (orientation === 'horizontal') {
|
|
515
|
+
yPos = yScale(getXAxisData(d)) - 0.75 * config.barHeight
|
|
516
|
+
upperPos = xScale(getYAxisData(d, config.confidenceKeys.upper))
|
|
517
|
+
lowerPos = xScale(getYAxisData(d, config.confidenceKeys.lower))
|
|
518
|
+
return (
|
|
519
|
+
<path
|
|
520
|
+
key={`confidence-interval-h-${yPos}-${d[config.runtime.originalXAxis.dataKey]}`}
|
|
521
|
+
stroke='#333'
|
|
522
|
+
strokeWidth='px'
|
|
523
|
+
d={`
|
|
524
|
+
M${lowerPos} ${yPos - tickWidth}
|
|
525
|
+
L${lowerPos} ${yPos + tickWidth}
|
|
526
|
+
M${lowerPos} ${yPos}
|
|
527
|
+
L${upperPos} ${yPos}
|
|
528
|
+
M${upperPos} ${yPos - tickWidth}
|
|
529
|
+
L${upperPos} ${yPos + tickWidth} `}
|
|
530
|
+
/>
|
|
531
|
+
)
|
|
532
|
+
} else {
|
|
533
|
+
xPos = xScale(getXAxisData(d))
|
|
534
|
+
upperPos = yScale(getYAxisData(d, config.confidenceKeys.lower))
|
|
535
|
+
lowerPos = yScale(getYAxisData(d, config.confidenceKeys.upper))
|
|
536
|
+
return (
|
|
537
|
+
<path
|
|
538
|
+
key={`confidence-interval-v-${yPos}-${d[config.runtime.originalXAxis.dataKey]}`}
|
|
539
|
+
stroke='#333'
|
|
540
|
+
strokeWidth='px'
|
|
541
|
+
d={`
|
|
542
|
+
M${xPos - tickWidth} ${upperPos}
|
|
543
|
+
L${xPos + tickWidth} ${upperPos}
|
|
544
|
+
M${xPos} ${upperPos}
|
|
545
|
+
L${xPos} ${lowerPos}
|
|
546
|
+
M${xPos - tickWidth} ${lowerPos}
|
|
547
|
+
L${xPos + tickWidth} ${lowerPos}`}
|
|
548
|
+
/>
|
|
549
|
+
)
|
|
550
|
+
}
|
|
510
551
|
})
|
|
511
552
|
: ''}
|
|
512
553
|
</Group>
|