@cdc/chart 4.23.1 → 4.23.3
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 +56289 -702
- package/examples/Barchart_with_negative.json +34 -0
- package/examples/area-chart.json +187 -0
- package/examples/big-small-test-bar.json +328 -0
- package/examples/big-small-test-line.json +328 -0
- package/examples/big-small-test-negative.json +328 -0
- package/examples/box-plot.json +1 -2
- package/examples/dynamic-legends.json +1 -1
- package/examples/example-bar-chart-nonnumeric.json +36 -0
- package/examples/example-bar-chart.json +36 -0
- package/examples/example-combo-bar-nonnumeric.json +105 -0
- package/examples/example-sparkline.json +76 -0
- package/examples/gallery/bar-chart-horizontal/horizontal-bar-chart.json +31 -172
- package/examples/gallery/bar-chart-vertical/vertical-bar-chart-categorical.json +1 -1
- 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/bar-chart-vertical/vertical-bar-chart.json +2 -2
- package/examples/gallery/line/line.json +1 -0
- package/examples/gallery/paired-bar/paired-bar-chart.json +65 -13
- package/examples/horizontal-chart-max-increase.json +38 -0
- package/examples/line-chart-max-increase.json +32 -0
- package/examples/line-chart-nonnumeric.json +32 -0
- package/examples/line-chart.json +21 -63
- package/examples/newdata.json +1 -1
- package/examples/planet-combo-example-config.json +143 -20
- package/examples/planet-deviation-config.json +168 -0
- package/examples/planet-deviation-data.json +38 -0
- package/examples/planet-example-config.json +139 -20
- package/examples/planet-example-data-max-increase.json +56 -0
- package/examples/planet-example-data-nonnumeric.json +56 -0
- package/examples/planet-example-data.json +9 -9
- package/examples/planet-pie-example-config-nonnumeric.json +30 -0
- package/examples/scatterplot-continuous.csv +17 -0
- package/examples/scatterplot.json +136 -0
- package/examples/sparkline-chart-nonnumeric.json +76 -0
- package/examples/stacked-vertical-bar-example-negative.json +154 -0
- package/examples/stacked-vertical-bar-example-nonnumerics.json +154 -0
- package/index.html +91 -0
- package/package.json +33 -24
- package/src/{CdcChart.tsx → CdcChart.jsx} +196 -124
- package/src/components/AreaChart.jsx +198 -0
- package/src/components/{BarChart.tsx → BarChart.jsx} +154 -122
- package/src/components/BoxPlot.jsx +101 -0
- package/src/components/{DataTable.tsx → DataTable.jsx} +109 -28
- package/src/components/DeviationBar.jsx +191 -0
- package/src/components/{EditorPanel.js → EditorPanel.jsx} +676 -157
- package/src/components/{Filters.js → Filters.jsx} +6 -11
- package/src/components/Legend.jsx +316 -0
- package/src/components/{LineChart.tsx → LineChart.jsx} +22 -26
- package/src/components/{LinearChart.tsx → LinearChart.jsx} +214 -91
- package/src/components/{PairedBarChart.tsx → PairedBarChart.jsx} +44 -78
- package/src/components/{PieChart.tsx → PieChart.jsx} +26 -44
- package/src/components/ScatterPlot.jsx +51 -0
- package/src/components/SparkLine.jsx +218 -0
- package/src/components/{useIntersectionObserver.tsx → useIntersectionObserver.jsx} +2 -2
- package/src/data/initial-state.js +51 -5
- package/src/hooks/useColorPalette.js +68 -0
- package/src/hooks/{useReduceData.ts → useReduceData.js} +26 -16
- package/src/hooks/useRightAxis.js +3 -1
- package/src/index.jsx +16 -0
- package/src/scss/DataTable.scss +22 -0
- package/src/scss/editor-panel.scss +5 -0
- package/src/scss/main.scss +30 -10
- package/src/test/CdcChart.test.jsx +6 -0
- package/vite.config.js +4 -0
- package/dist/495.js +0 -3
- package/dist/703.js +0 -1
- package/src/components/BoxPlot.js +0 -92
- package/src/components/Legend.js +0 -291
- package/src/components/SparkLine.js +0 -185
- package/src/hooks/useColorPalette.ts +0 -76
- package/src/index.html +0 -67
- package/src/index.tsx +0 -18
- /package/src/{context.tsx → ConfigContext.jsx} +0 -0
|
@@ -4,11 +4,15 @@ import { BarGroup, BarStack } from '@visx/shape'
|
|
|
4
4
|
import { Text } from '@visx/text'
|
|
5
5
|
import chroma from 'chroma-js'
|
|
6
6
|
import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
7
|
-
import
|
|
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, parseDate } = useContext
|
|
11
|
+
const { transformedData: data, colorScale, seriesHighlight, config, formatNumber, updateConfig, colorPalettes, formatDate, isNumber, cleanData, getTextWidth, parseDate } = useContext(ConfigContext)
|
|
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)
|
|
15
|
+
|
|
12
16
|
const { orientation, visualizationSubType } = config
|
|
13
17
|
const isHorizontal = orientation === 'horizontal'
|
|
14
18
|
|
|
@@ -25,15 +29,20 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
25
29
|
const radius = config.roundingStyle === 'standard' ? '8px' : config.roundingStyle === 'shallow' ? '5px' : config.roundingStyle === 'finger' ? '15px' : '0px'
|
|
26
30
|
const stackCount = config.runtime.seriesKeys.length
|
|
27
31
|
const barBorderWidth = 1
|
|
28
|
-
const fontSize = { small:
|
|
32
|
+
const fontSize = { small: 16, medium: 18, large: 20 }
|
|
29
33
|
const hasMultipleSeries = Object.keys(config.runtime.seriesLabels).length > 1
|
|
30
34
|
|
|
31
|
-
const applyRadius = (index
|
|
35
|
+
const applyRadius = (index, isNegative) => {
|
|
32
36
|
if (index === undefined || index === null || !isRounded) return
|
|
33
37
|
let style = {}
|
|
34
38
|
|
|
35
39
|
if ((isStacked && index + 1 === stackCount) || !isStacked) {
|
|
36
|
-
|
|
40
|
+
if (isNegative) {
|
|
41
|
+
// reverse borderRadius to bottom
|
|
42
|
+
style = isHorizontal ? { borderRadius: `0 ${radius} ${radius} 0` } : { borderRadius: `0 0 ${radius} ${radius}` }
|
|
43
|
+
} else {
|
|
44
|
+
style = isHorizontal ? { borderRadius: `0 ${radius} ${radius} 0` } : { borderRadius: `${radius} ${radius} 0 0` }
|
|
45
|
+
}
|
|
37
46
|
}
|
|
38
47
|
if (tipRounding === 'full' && isStacked && index === 0 && stackCount > 1) {
|
|
39
48
|
style = isHorizontal ? { borderRadius: `${radius} 0 0 ${radius}` } : { borderRadius: `0 0 ${radius} ${radius}` }
|
|
@@ -41,7 +50,6 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
41
50
|
if (tipRounding === 'full' && ((isStacked && index === 0 && stackCount === 1) || !isStacked)) {
|
|
42
51
|
style = { borderRadius: radius }
|
|
43
52
|
}
|
|
44
|
-
|
|
45
53
|
return style
|
|
46
54
|
}
|
|
47
55
|
// }
|
|
@@ -76,7 +84,7 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
76
84
|
|
|
77
85
|
// return new updated bars/groupes
|
|
78
86
|
return barsArr.map((bar, i) => {
|
|
79
|
-
// set bars Y
|
|
87
|
+
// set bars Y dynamically to handle space between bars
|
|
80
88
|
let y = 0
|
|
81
89
|
bar.index !== 0 && (y = (barHeight + barSpace + labelHeight) * i)
|
|
82
90
|
|
|
@@ -84,16 +92,6 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
84
92
|
})
|
|
85
93
|
}
|
|
86
94
|
|
|
87
|
-
function getTextWidth(text, font) {
|
|
88
|
-
// function calculates the width of given text and its font-size
|
|
89
|
-
const canvas = document.createElement('canvas')
|
|
90
|
-
const context = canvas.getContext('2d')
|
|
91
|
-
|
|
92
|
-
context.font = font || getComputedStyle(document.body).font
|
|
93
|
-
|
|
94
|
-
return Math.ceil(context.measureText(text).width)
|
|
95
|
-
}
|
|
96
|
-
|
|
97
95
|
// Using State
|
|
98
96
|
const [textWidth, setTextWidth] = useState(null)
|
|
99
97
|
|
|
@@ -107,13 +105,13 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
107
105
|
}
|
|
108
106
|
})
|
|
109
107
|
}
|
|
110
|
-
}, [config, updateConfig])
|
|
108
|
+
}, [config, updateConfig]) // eslint-disable-line
|
|
111
109
|
|
|
112
110
|
useEffect(() => {
|
|
113
111
|
if (config.isLollipopChart === false && config.barHeight < 25) {
|
|
114
112
|
updateConfig({ ...config, barHeight: 25 })
|
|
115
113
|
}
|
|
116
|
-
}, [config.isLollipopChart])
|
|
114
|
+
}, [config.isLollipopChart]) // eslint-disable-line
|
|
117
115
|
|
|
118
116
|
useEffect(() => {
|
|
119
117
|
if (config.visualizationSubType === 'horizontal') {
|
|
@@ -122,7 +120,7 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
122
120
|
orientation: 'horizontal'
|
|
123
121
|
})
|
|
124
122
|
}
|
|
125
|
-
}, [])
|
|
123
|
+
}, []) // eslint-disable-line
|
|
126
124
|
|
|
127
125
|
useEffect(() => {
|
|
128
126
|
if (config.barStyle === 'lollipop' && !config.isLollipopChart) {
|
|
@@ -131,14 +129,14 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
131
129
|
if (isRounded || config.barStyle === 'flat') {
|
|
132
130
|
updateConfig({ ...config, isLollipopChart: false })
|
|
133
131
|
}
|
|
134
|
-
}, [config.barStyle])
|
|
132
|
+
}, [config.barStyle]) // eslint-disable-line
|
|
135
133
|
|
|
136
134
|
return (
|
|
137
135
|
<ErrorBoundary component='BarChart'>
|
|
138
|
-
<Group left={config.runtime.yAxis.size}>
|
|
136
|
+
<Group left={parseFloat(config.runtime.yAxis.size)}>
|
|
139
137
|
{/* Stacked Vertical */}
|
|
140
138
|
{config.visualizationSubType === 'stacked' && !isHorizontal && (
|
|
141
|
-
<BarStack data={
|
|
139
|
+
<BarStack data={cleanedData} keys={config.runtime.barSeriesKeys || config.runtime.seriesKeys} x={d => d[config.runtime.xAxis.dataKey]} xScale={xScale} yScale={yScale} color={colorScale}>
|
|
142
140
|
{barStacks =>
|
|
143
141
|
barStacks.reverse().map(barStack =>
|
|
144
142
|
barStack.bars.map(bar => {
|
|
@@ -147,10 +145,10 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
147
145
|
let barThickness = xMax / barStack.bars.length
|
|
148
146
|
let barThicknessAdjusted = barThickness * (config.barThickness || 0.8)
|
|
149
147
|
let offset = (barThickness * (1 - (config.barThickness || 0.8))) / 2
|
|
150
|
-
const style = applyRadius(barStack.index)
|
|
151
148
|
// tooltips
|
|
152
149
|
const xAxisValue = config.runtime.xAxis.type === 'date' ? formatDate(parseDate(data[bar.index][config.runtime.xAxis.dataKey])) : data[bar.index][config.runtime.xAxis.dataKey]
|
|
153
|
-
const yAxisValue = formatNumber(bar.bar ? bar.bar.data[bar.key] : 0)
|
|
150
|
+
const yAxisValue = formatNumber(bar.bar ? bar.bar.data[bar.key] : 0, 'left')
|
|
151
|
+
const style = applyRadius(barStack.index, yAxisValue < 0)
|
|
154
152
|
let yAxisTooltip = config.runtime.yAxis.label ? `${config.runtime.yAxis.label}: ${yAxisValue}` : yAxisValue
|
|
155
153
|
const xAxisTooltip = config.runtime.xAxis.label ? `${config.runtime.xAxis.label}: ${xAxisValue}` : xAxisValue
|
|
156
154
|
if (!hasMultipleSeries) {
|
|
@@ -162,21 +160,20 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
162
160
|
${yAxisTooltip}<br />
|
|
163
161
|
${xAxisTooltip}
|
|
164
162
|
</div>`
|
|
165
|
-
|
|
166
163
|
return (
|
|
167
|
-
|
|
164
|
+
<Group key={`${barStack.index}--${bar.index}--${orientation}`}>
|
|
168
165
|
<style>
|
|
169
166
|
{`
|
|
170
167
|
#barStack${barStack.index}-${bar.index} rect,
|
|
171
168
|
#barStack${barStack.index}-${bar.index} foreignObject{
|
|
172
|
-
animation-delay: ${barStack.index}
|
|
169
|
+
animation-delay: ${barStack.index * 0.5}s;
|
|
173
170
|
transform-origin: ${barThicknessAdjusted / 2}px ${bar.y + bar.height}px
|
|
174
171
|
}
|
|
175
172
|
`}
|
|
176
173
|
</style>
|
|
177
174
|
<Group key={`bar-stack-${barStack.index}-${bar.index}`} id={`barStack${barStack.index}-${bar.index}`} className='stack vertical'>
|
|
178
175
|
<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'>
|
|
179
|
-
{
|
|
176
|
+
{yAxisValue}
|
|
180
177
|
</Text>
|
|
181
178
|
<foreignObject
|
|
182
179
|
key={`bar-stack-${barStack.index}-${bar.index}`}
|
|
@@ -187,11 +184,11 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
187
184
|
style={{ background: bar.color, border: `${config.barHasBorder === 'true' ? barBorderWidth : 0}px solid #333`, ...style }}
|
|
188
185
|
opacity={transparentBar ? 0.5 : 1}
|
|
189
186
|
display={displayBar ? 'block' : 'none'}
|
|
190
|
-
data-
|
|
191
|
-
data-
|
|
187
|
+
data-tooltip-html={tooltip}
|
|
188
|
+
data-tooltip-id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
|
|
192
189
|
></foreignObject>
|
|
193
190
|
</Group>
|
|
194
|
-
|
|
191
|
+
</Group>
|
|
195
192
|
)
|
|
196
193
|
})
|
|
197
194
|
)
|
|
@@ -202,18 +199,18 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
202
199
|
{/* Stacked Horizontal */}
|
|
203
200
|
{config.visualizationSubType === 'stacked' && isHorizontal && (
|
|
204
201
|
<>
|
|
205
|
-
<BarStackHorizontal data={
|
|
202
|
+
<BarStackHorizontal data={cleanedData} keys={config.runtime.barSeriesKeys || config.runtime.seriesKeys} height={yMax} y={d => d[config.runtime.yAxis.dataKey]} xScale={xScale} yScale={yScale} color={colorScale} offset='none'>
|
|
206
203
|
{barStacks =>
|
|
207
204
|
barStacks.map(barStack =>
|
|
208
205
|
updateBars(barStack.bars).map((bar, index) => {
|
|
209
206
|
let transparentBar = config.legend.behavior === 'highlight' && seriesHighlight.length > 0 && seriesHighlight.indexOf(bar.key) === -1
|
|
210
207
|
let displayBar = config.legend.behavior === 'highlight' || seriesHighlight.length === 0 || seriesHighlight.indexOf(bar.key) !== -1
|
|
211
208
|
config.barHeight = Number(config.barHeight)
|
|
212
|
-
const style = applyRadius(barStack.index)
|
|
213
209
|
let labelColor = '#000000'
|
|
214
210
|
// tooltips
|
|
215
|
-
const xAxisValue = formatNumber(data[bar.index][bar.key])
|
|
211
|
+
const xAxisValue = formatNumber(data[bar.index][bar.key], 'left')
|
|
216
212
|
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, yAxisValue < 0)
|
|
217
214
|
let yAxisTooltip = config.runtime.yAxis.label ? `${config.runtime.yAxis.label}: ${yAxisValue}` : yAxisValue
|
|
218
215
|
let xAxisTooltip = config.runtime.xAxis.label ? `${config.runtime.xAxis.label}: ${xAxisValue}` : xAxisValue
|
|
219
216
|
if (!hasMultipleSeries) {
|
|
@@ -230,53 +227,64 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
230
227
|
}
|
|
231
228
|
|
|
232
229
|
return (
|
|
233
|
-
|
|
234
|
-
<
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
textAnchor='start'
|
|
254
|
-
verticalAnchor='start'
|
|
255
|
-
>
|
|
256
|
-
{yAxisValue}
|
|
257
|
-
</Text>
|
|
258
|
-
)}
|
|
259
|
-
|
|
260
|
-
{displayNumbersOnBar && textWidth + 50 < bar.width && (
|
|
261
|
-
<Text
|
|
230
|
+
<>
|
|
231
|
+
<style>
|
|
232
|
+
{`
|
|
233
|
+
#barStack${barStack.index}-${bar.index} rect,
|
|
234
|
+
#barStack${barStack.index}-${bar.index} foreignObject{
|
|
235
|
+
animation-delay: ${barStack.index * 0.5}s;
|
|
236
|
+
transform-origin: ${bar.x}px
|
|
237
|
+
}
|
|
238
|
+
`}
|
|
239
|
+
</style>
|
|
240
|
+
<Group key={index} id={`barStack${barStack.index}-${bar.index}`} className='stack horizontal'>
|
|
241
|
+
<foreignObject
|
|
242
|
+
key={`barstack-horizontal-${barStack.index}-${bar.index}-${index}`}
|
|
243
|
+
className={`animated-chart group ${animatedChart ? 'animated' : ''}`}
|
|
244
|
+
x={bar.x}
|
|
245
|
+
y={bar.y}
|
|
246
|
+
width={bar.width}
|
|
247
|
+
height={bar.height}
|
|
248
|
+
style={{ background: bar.color, border: `${config.barHasBorder === 'true' ? barBorderWidth : 0}px solid #333`, ...style }}
|
|
249
|
+
opacity={transparentBar ? 0.5 : 1}
|
|
262
250
|
display={displayBar ? 'block' : 'none'}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
251
|
+
data-tooltip-html={tooltip}
|
|
252
|
+
data-tooltip-id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
|
|
253
|
+
></foreignObject>
|
|
254
|
+
|
|
255
|
+
{orientation === 'horizontal' && visualizationSubType === 'stacked' && isLabelBelowBar && barStack.index === 0 && !config.yAxis.hideLabel && (
|
|
256
|
+
<Text
|
|
257
|
+
x={`${bar.x + (config.isLollipopChart ? 15 : 5)}`} // padding
|
|
258
|
+
y={bar.y + bar.height * 1.2}
|
|
259
|
+
fill={'#000000'}
|
|
260
|
+
textAnchor='start'
|
|
261
|
+
verticalAnchor='start'
|
|
262
|
+
>
|
|
263
|
+
{yAxisValue}
|
|
264
|
+
</Text>
|
|
265
|
+
)}
|
|
266
|
+
|
|
267
|
+
{displayNumbersOnBar && textWidth < bar.width && (
|
|
268
|
+
<Text
|
|
269
|
+
display={displayBar ? 'block' : 'none'}
|
|
270
|
+
x={bar.x + barStack.bars[bar.index].width / 2} // padding
|
|
271
|
+
y={bar.y + bar.height / 2}
|
|
272
|
+
fill={labelColor}
|
|
273
|
+
textAnchor='middle'
|
|
274
|
+
verticalAnchor='middle'
|
|
275
|
+
innerRef={e => {
|
|
276
|
+
if (e) {
|
|
277
|
+
// use font sizes and padding to set the bar height
|
|
278
|
+
let elem = e.getBBox()
|
|
279
|
+
setTextWidth(elem.width)
|
|
280
|
+
}
|
|
281
|
+
}}
|
|
282
|
+
>
|
|
283
|
+
{xAxisValue}
|
|
284
|
+
</Text>
|
|
285
|
+
)}
|
|
286
|
+
</Group>
|
|
287
|
+
</>
|
|
280
288
|
)
|
|
281
289
|
})
|
|
282
290
|
)
|
|
@@ -289,10 +297,10 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
289
297
|
{config.visualizationSubType !== 'stacked' && (
|
|
290
298
|
<Group>
|
|
291
299
|
<BarGroup
|
|
292
|
-
data={
|
|
300
|
+
data={cleanedData}
|
|
293
301
|
keys={config.runtime.barSeriesKeys || config.runtime.seriesKeys}
|
|
294
302
|
height={yMax}
|
|
295
|
-
x0={
|
|
303
|
+
x0={d => d[config.runtime.originalXAxis.dataKey]}
|
|
296
304
|
x0Scale={config.runtime.horizontal ? yScale : xScale}
|
|
297
305
|
x1Scale={seriesScale}
|
|
298
306
|
yScale={config.runtime.horizontal ? xScale : yScale}
|
|
@@ -305,14 +313,15 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
305
313
|
<Group
|
|
306
314
|
className={`bar-group-${barGroup.index}-${barGroup.x0}--${index} ${config.orientation}`}
|
|
307
315
|
key={`bar-group-${barGroup.index}-${barGroup.x0}--${index}`}
|
|
316
|
+
id={`bar-group-${barGroup.index}-${barGroup.x0}--${index}`}
|
|
308
317
|
top={config.runtime.horizontal ? barGroup.y : 0}
|
|
309
318
|
left={config.runtime.horizontal ? 0 : (xMax / barGroups.length) * barGroup.index}
|
|
310
319
|
>
|
|
311
320
|
{barGroup.bars.map((bar, index) => {
|
|
312
321
|
let transparentBar = config.legend.behavior === 'highlight' && seriesHighlight.length > 0 && seriesHighlight.indexOf(bar.key) === -1
|
|
313
322
|
let displayBar = config.legend.behavior === 'highlight' || seriesHighlight.length === 0 || seriesHighlight.indexOf(bar.key) !== -1
|
|
314
|
-
let barHeight = orientation === 'horizontal' ? config.barHeight : Math.abs(yScale(bar.value) - yScale(0))
|
|
315
|
-
let barY = bar.value >= 0 ? bar.y : yScale(0)
|
|
323
|
+
let barHeight = orientation === 'horizontal' ? config.barHeight : isNumber(Math.abs(yScale(bar.value) - yScale(0))) ? Math.abs(yScale(bar.value) - yScale(0)) : 0
|
|
324
|
+
let barY = bar.value >= 0 && isNumber(bar.value) ? bar.y : yScale(0)
|
|
316
325
|
let barGroupWidth = ((config.runtime.horizontal ? yMax : xMax) / barGroups.length) * (config.barThickness || 0.8)
|
|
317
326
|
let offset = (((config.runtime.horizontal ? yMax : xMax) / barGroups.length) * (1 - (config.barThickness || 0.8))) / 2
|
|
318
327
|
|
|
@@ -333,7 +342,7 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
333
342
|
}
|
|
334
343
|
if (config.legend.colorCode && config.series.length === 1) barColor = palette[barGroup.index]
|
|
335
344
|
|
|
336
|
-
let yAxisValue = formatNumber(bar.value)
|
|
345
|
+
let yAxisValue = formatNumber(bar.value, 'left')
|
|
337
346
|
let xAxisValue = config.runtime[section].type === 'date' ? formatDate(parseDate(data[barGroup.index][config.runtime.originalXAxis.dataKey])) : data[barGroup.index][config.runtime.originalXAxis.dataKey]
|
|
338
347
|
|
|
339
348
|
if (config.runtime.horizontal) {
|
|
@@ -342,18 +351,18 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
342
351
|
xAxisValue = tempValue
|
|
343
352
|
barWidth = config.barHeight
|
|
344
353
|
}
|
|
354
|
+
// check if bar text/value string fits into each bars.
|
|
355
|
+
let textWidth = getTextWidth(xAxisValue, `normal ${fontSize[config.fontSize]}px sans-serif`)
|
|
356
|
+
let textFits = textWidth < bar.y - 5 // minus padding 5
|
|
357
|
+
|
|
345
358
|
let labelColor = '#000000'
|
|
346
359
|
|
|
347
360
|
// Set label color
|
|
348
361
|
if (chroma.contrast(labelColor, barColor) < 4.9) {
|
|
349
|
-
labelColor = '#FFFFFF'
|
|
362
|
+
if (textFits) labelColor = '#FFFFFF'
|
|
350
363
|
}
|
|
351
364
|
|
|
352
|
-
const style = applyRadius(index)
|
|
353
|
-
|
|
354
|
-
// check if bar text/value string fits into each bars.
|
|
355
|
-
let textWidth = getTextWidth(xAxisValue, config.fontSize)
|
|
356
|
-
let doesTextFit = (textWidth / bar.y) * 100 < 48
|
|
365
|
+
const style = applyRadius(index, yAxisValue < 0)
|
|
357
366
|
|
|
358
367
|
let yAxisTooltip = config.runtime.yAxis.label ? `${config.runtime.yAxis.label}: ${yAxisValue}` : yAxisValue
|
|
359
368
|
let xAxisTooltip = config.runtime.xAxis.label ? `${config.runtime.xAxis.label}: ${xAxisValue}` : xAxisValue
|
|
@@ -371,7 +380,7 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
371
380
|
</div>`
|
|
372
381
|
|
|
373
382
|
return (
|
|
374
|
-
|
|
383
|
+
<Group key={`${barGroup.index}--${index}--${orientation}`}>
|
|
375
384
|
{/* This feels gross but inline transition was not working well*/}
|
|
376
385
|
<style>
|
|
377
386
|
{`
|
|
@@ -396,18 +405,18 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
396
405
|
}}
|
|
397
406
|
opacity={transparentBar ? 0.5 : 1}
|
|
398
407
|
display={displayBar ? 'block' : 'none'}
|
|
399
|
-
data-
|
|
400
|
-
data-
|
|
408
|
+
data-tooltip-html={tooltip}
|
|
409
|
+
data-tooltip-id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
|
|
401
410
|
></foreignObject>
|
|
402
411
|
{orientation === 'horizontal' && !config.isLollipopChart && displayNumbersOnBar && (
|
|
403
|
-
<Text
|
|
412
|
+
<Text // prettier-ignore
|
|
404
413
|
display={displayBar ? 'block' : 'none'}
|
|
405
414
|
x={bar.y}
|
|
406
415
|
y={config.barHeight / 2 + config.barHeight * bar.index}
|
|
407
416
|
fill={labelColor}
|
|
408
|
-
dx={
|
|
417
|
+
dx={textFits ? -5 : 5}
|
|
409
418
|
verticalAnchor='middle'
|
|
410
|
-
textAnchor={
|
|
419
|
+
textAnchor={textFits ? 'end' : 'start'}
|
|
411
420
|
>
|
|
412
421
|
{xAxisValue}
|
|
413
422
|
</Text>
|
|
@@ -438,7 +447,7 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
438
447
|
;
|
|
439
448
|
{orientation === 'vertical' && (
|
|
440
449
|
<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'>
|
|
441
|
-
{
|
|
450
|
+
{yAxisValue}
|
|
442
451
|
</Text>
|
|
443
452
|
)}
|
|
444
453
|
;
|
|
@@ -449,8 +458,8 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
449
458
|
r={lollipopShapeSize / 2}
|
|
450
459
|
fill={barColor}
|
|
451
460
|
key={`circle--${bar.index}`}
|
|
452
|
-
data-
|
|
453
|
-
data-
|
|
461
|
+
data-tooltip-html={tooltip}
|
|
462
|
+
data-tooltip-id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
|
|
454
463
|
style={{ filter: 'unset', opacity: 1 }}
|
|
455
464
|
/>
|
|
456
465
|
)}
|
|
@@ -462,15 +471,15 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
462
471
|
height={lollipopShapeSize}
|
|
463
472
|
fill={barColor}
|
|
464
473
|
key={`circle--${bar.index}`}
|
|
465
|
-
data-
|
|
466
|
-
data-
|
|
474
|
+
data-tooltip-html={tooltip}
|
|
475
|
+
data-tooltip-id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
|
|
467
476
|
style={{ opacity: 1, filter: 'unset' }}
|
|
468
477
|
>
|
|
469
478
|
<animate attributeName='height' values={`0, ${lollipopShapeSize}`} dur='2.5s' />
|
|
470
479
|
</rect>
|
|
471
480
|
)}
|
|
472
481
|
</Group>
|
|
473
|
-
|
|
482
|
+
</Group>
|
|
474
483
|
)
|
|
475
484
|
})}
|
|
476
485
|
</Group>
|
|
@@ -480,25 +489,48 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
480
489
|
|
|
481
490
|
{Object.keys(config.confidenceKeys).length > 0
|
|
482
491
|
? data.map(d => {
|
|
483
|
-
let xPos
|
|
484
|
-
let upperPos
|
|
485
|
-
let lowerPos
|
|
492
|
+
let xPos, yPos
|
|
493
|
+
let upperPos
|
|
494
|
+
let lowerPos
|
|
486
495
|
let tickWidth = 5
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
496
|
+
// DEV-3264 Make Confidence Intervals work on horizontal bar charts
|
|
497
|
+
if (orientation === 'horizontal') {
|
|
498
|
+
yPos = yScale(getXAxisData(d)) - 0.75 * config.barHeight
|
|
499
|
+
upperPos = xScale(getYAxisData(d, config.confidenceKeys.upper))
|
|
500
|
+
lowerPos = xScale(getYAxisData(d, config.confidenceKeys.lower))
|
|
501
|
+
return (
|
|
502
|
+
<path
|
|
503
|
+
key={`confidence-interval-h-${yPos}-${d[config.runtime.originalXAxis.dataKey]}`}
|
|
504
|
+
stroke='#333'
|
|
505
|
+
strokeWidth='px'
|
|
506
|
+
d={`
|
|
507
|
+
M${lowerPos} ${yPos - tickWidth}
|
|
508
|
+
L${lowerPos} ${yPos + tickWidth}
|
|
509
|
+
M${lowerPos} ${yPos}
|
|
510
|
+
L${upperPos} ${yPos}
|
|
511
|
+
M${upperPos} ${yPos - tickWidth}
|
|
512
|
+
L${upperPos} ${yPos + tickWidth} `}
|
|
513
|
+
/>
|
|
514
|
+
)
|
|
515
|
+
} else {
|
|
516
|
+
xPos = xScale(getXAxisData(d))
|
|
517
|
+
upperPos = yScale(getYAxisData(d, config.confidenceKeys.lower))
|
|
518
|
+
lowerPos = yScale(getYAxisData(d, config.confidenceKeys.upper))
|
|
519
|
+
return (
|
|
520
|
+
<path
|
|
521
|
+
key={`confidence-interval-v-${yPos}-${d[config.runtime.originalXAxis.dataKey]}`}
|
|
522
|
+
stroke='#333'
|
|
523
|
+
strokeWidth='px'
|
|
524
|
+
d={`
|
|
525
|
+
M${xPos - tickWidth} ${upperPos}
|
|
526
|
+
L${xPos + tickWidth} ${upperPos}
|
|
527
|
+
M${xPos} ${upperPos}
|
|
528
|
+
L${xPos} ${lowerPos}
|
|
529
|
+
M${xPos - tickWidth} ${lowerPos}
|
|
530
|
+
L${xPos + tickWidth} ${lowerPos}`}
|
|
531
|
+
/>
|
|
532
|
+
)
|
|
533
|
+
}
|
|
502
534
|
})
|
|
503
535
|
: ''}
|
|
504
536
|
</Group>
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import React, { useContext, useEffect } from 'react'
|
|
2
|
+
import { BoxPlot } from '@visx/stats'
|
|
3
|
+
import { Group } from '@visx/group'
|
|
4
|
+
import ConfigContext from '../ConfigContext'
|
|
5
|
+
import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
6
|
+
import { colorPalettesChart } from '@cdc/core/data/colorPalettes'
|
|
7
|
+
|
|
8
|
+
const CoveBoxPlot = ({ xScale, yScale }) => {
|
|
9
|
+
const { config, setConfig } = useContext(ConfigContext)
|
|
10
|
+
const boxWidth = xScale.bandwidth()
|
|
11
|
+
const constrainedWidth = Math.min(40, boxWidth)
|
|
12
|
+
const color_0 = colorPalettesChart[config?.palette][0] ? colorPalettesChart[config?.palette][0] : '#000'
|
|
13
|
+
|
|
14
|
+
// tooltips
|
|
15
|
+
const tooltip_id = `cdc-open-viz-tooltip-${config.runtime.uniqueId}`
|
|
16
|
+
const handleTooltip = d => {
|
|
17
|
+
return `
|
|
18
|
+
<strong>${d.columnCategory}</strong></br>
|
|
19
|
+
${config.boxplot.labels.q1}: ${d.columnFirstQuartile}<br/>
|
|
20
|
+
${config.boxplot.labels.q3}: ${d.columnThirdQuartile}<br/>
|
|
21
|
+
${config.boxplot.labels.iqr}: ${d.columnIqr}<br/>
|
|
22
|
+
${config.boxplot.labels.median}: ${d.columnMedian}
|
|
23
|
+
`
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
if (config.legend.hide === false) {
|
|
28
|
+
setConfig({
|
|
29
|
+
...config,
|
|
30
|
+
legend: {
|
|
31
|
+
...config.legend,
|
|
32
|
+
hide: true
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
}
|
|
36
|
+
}, []) // eslint-disable-line
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<ErrorBoundary component='BoxPlot'>
|
|
40
|
+
<Group className='boxplot' key={`boxplot-group`}>
|
|
41
|
+
{config.boxplot.plots.map((d, i) => {
|
|
42
|
+
const offset = boxWidth - constrainedWidth
|
|
43
|
+
const radius = 4
|
|
44
|
+
return (
|
|
45
|
+
<Group key={`boxplotplot-${i}`}>
|
|
46
|
+
{config.boxplot.plotNonOutlierValues &&
|
|
47
|
+
d.nonOutlierValues.map((value, index) => {
|
|
48
|
+
return <circle cx={xScale(d.columnCategory) + Number(config.yAxis.size) + boxWidth / 2} cy={yScale(value)} r={radius} fill={'#ccc'} style={{ opacity: 1, fillOpacity: 1, stroke: 'black' }} key={`boxplot-${i}--circle-${index}`} />
|
|
49
|
+
})}
|
|
50
|
+
<BoxPlot
|
|
51
|
+
data-left={xScale(d.columnCategory) + config.yAxis.size + offset / 2 + 0.5}
|
|
52
|
+
key={`box-plot-${i}`}
|
|
53
|
+
min={Number(d.columnMin)}
|
|
54
|
+
max={Number(d.columnMax)}
|
|
55
|
+
left={Number(xScale(d.columnCategory)) + Number(config.yAxis.size) + offset / 2 + 0.5}
|
|
56
|
+
firstQuartile={Number(d.columnFirstQuartile)}
|
|
57
|
+
thirdQuartile={Number(d.columnThirdQuartile)}
|
|
58
|
+
median={Number(d.columnMedian)}
|
|
59
|
+
boxWidth={constrainedWidth}
|
|
60
|
+
fill={color_0}
|
|
61
|
+
fillOpacity={0.5}
|
|
62
|
+
stroke='black'
|
|
63
|
+
valueScale={yScale}
|
|
64
|
+
outliers={config.boxplot.plotOutlierValues ? d.columnOutliers : []}
|
|
65
|
+
outlierProps={{
|
|
66
|
+
style: {
|
|
67
|
+
fill: `${color_0}`,
|
|
68
|
+
opacity: 1
|
|
69
|
+
}
|
|
70
|
+
}}
|
|
71
|
+
medianProps={{
|
|
72
|
+
style: {
|
|
73
|
+
stroke: 'black'
|
|
74
|
+
}
|
|
75
|
+
}}
|
|
76
|
+
boxProps={{
|
|
77
|
+
style: {
|
|
78
|
+
stroke: 'black',
|
|
79
|
+
strokeWidth: config.boxplot.borders === 'true' ? 1 : 0
|
|
80
|
+
}
|
|
81
|
+
}}
|
|
82
|
+
maxProps={{
|
|
83
|
+
style: {
|
|
84
|
+
stroke: 'black'
|
|
85
|
+
}
|
|
86
|
+
}}
|
|
87
|
+
container
|
|
88
|
+
containerProps={{
|
|
89
|
+
'data-tooltip-html': handleTooltip(d),
|
|
90
|
+
'data-tooltip-id': tooltip_id
|
|
91
|
+
}}
|
|
92
|
+
/>
|
|
93
|
+
</Group>
|
|
94
|
+
)
|
|
95
|
+
})}
|
|
96
|
+
</Group>
|
|
97
|
+
</ErrorBoundary>
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export default CoveBoxPlot
|