@cdc/chart 4.22.11 → 4.23.1
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/495.js +3 -0
- package/dist/703.js +1 -0
- package/dist/cdcchart.js +723 -6
- package/examples/box-plot-data.json +71 -0
- package/examples/box-plot.csv +5 -0
- package/examples/{private/yaxis-test.json → box-plot.json} +47 -56
- package/examples/gallery/bar-chart-vertical/combo-line-chart.json +3 -1
- package/examples/gallery/bar-chart-vertical/vertical-bar-chart.json +85 -16
- package/examples/new-data.csv +17 -0
- package/examples/newdata.json +90 -0
- package/package.json +3 -2
- package/src/CdcChart.tsx +150 -94
- package/src/components/BarChart.tsx +156 -226
- package/src/components/BoxPlot.js +92 -0
- package/src/components/DataTable.tsx +28 -12
- package/src/components/EditorPanel.js +151 -104
- package/src/components/Filters.js +131 -0
- package/src/components/Legend.js +8 -1
- package/src/components/LineChart.tsx +64 -13
- package/src/components/LinearChart.tsx +120 -81
- package/src/components/PairedBarChart.tsx +1 -1
- package/src/components/PieChart.tsx +12 -2
- package/src/components/useIntersectionObserver.tsx +9 -7
- package/src/data/initial-state.js +14 -8
- package/src/hooks/useReduceData.ts +8 -5
- package/src/index.html +51 -51
- package/src/scss/DataTable.scss +1 -1
- package/src/scss/main.scss +53 -22
- package/examples/private/filters.json +0 -170
- package/examples/private/line-test-data.json +0 -22
- package/examples/private/line-test-two.json +0 -210
- package/examples/private/line-test.json +0 -102
- package/examples/private/new.json +0 -48800
- package/examples/private/newtest.csv +0 -101
- package/examples/private/shawn.json +0 -1106
- package/examples/private/test.json +0 -10124
- package/examples/private/yaxis-testing.csv +0 -27
- package/examples/private/yaxis.json +0 -28
|
@@ -5,11 +5,10 @@ import { Text } from '@visx/text'
|
|
|
5
5
|
import chroma from 'chroma-js'
|
|
6
6
|
import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
7
7
|
import Context from '../context'
|
|
8
|
-
import ReactTooltip from 'react-tooltip'
|
|
9
8
|
import { BarStackHorizontal } from '@visx/shape'
|
|
10
9
|
|
|
11
10
|
export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getXAxisData, getYAxisData, animatedChart, visible }) {
|
|
12
|
-
const { transformedData: data, colorScale, seriesHighlight, config, formatNumber, updateConfig,
|
|
11
|
+
const { transformedData: data, colorScale, seriesHighlight, config, formatNumber, updateConfig, colorPalettes, formatDate, parseDate } = useContext<any>(Context)
|
|
13
12
|
const { orientation, visualizationSubType } = config
|
|
14
13
|
const isHorizontal = orientation === 'horizontal'
|
|
15
14
|
|
|
@@ -17,9 +16,6 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
17
16
|
const lollipopShapeSize = config.lollipopSize === 'large' ? 14 : config.lollipopSize === 'medium' ? 12 : 10
|
|
18
17
|
|
|
19
18
|
const isLabelBelowBar = config.yAxis.labelPlacement === 'Below Bar'
|
|
20
|
-
const isLabelOnYAxis = config.yAxis.labelPlacement === 'On Date/Category Axis'
|
|
21
|
-
const isLabelOnBar = config.yAxis.labelPlacement === 'On Bar'
|
|
22
|
-
const isLabelMissing = !config.yAxis.labelPlacement
|
|
23
19
|
const displayNumbersOnBar = config.yAxis.displayNumbersOnBar
|
|
24
20
|
const section = config.orientation === 'horizontal' ? 'yAxis' : 'xAxis'
|
|
25
21
|
|
|
@@ -29,6 +25,8 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
29
25
|
const radius = config.roundingStyle === 'standard' ? '8px' : config.roundingStyle === 'shallow' ? '5px' : config.roundingStyle === 'finger' ? '15px' : '0px'
|
|
30
26
|
const stackCount = config.runtime.seriesKeys.length
|
|
31
27
|
const barBorderWidth = 1
|
|
28
|
+
const fontSize = { small: 14, medium: 16, large: 18 }
|
|
29
|
+
const hasMultipleSeries = Object.keys(config.runtime.seriesLabels).length > 1
|
|
32
30
|
|
|
33
31
|
const applyRadius = (index: number) => {
|
|
34
32
|
if (index === undefined || index === null || !isRounded) return
|
|
@@ -46,9 +44,57 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
46
44
|
|
|
47
45
|
return style
|
|
48
46
|
}
|
|
47
|
+
// }
|
|
48
|
+
|
|
49
|
+
const updateBars = defaultBars => {
|
|
50
|
+
// function updates stacked && regular && lollipop horizontal bars
|
|
51
|
+
if (config.visualizationType !== 'Bar' && !isHorizontal) return defaultBars
|
|
52
|
+
|
|
53
|
+
const barsArr = [...defaultBars]
|
|
54
|
+
let barHeight
|
|
55
|
+
|
|
56
|
+
const heights = {
|
|
57
|
+
stacked: config.barHeight,
|
|
58
|
+
lollipop: lollipopBarWidth
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (!isStacked) {
|
|
62
|
+
barHeight = heights[config.isLollipopChart ? 'lollipop' : 'stacked'] * stackCount
|
|
63
|
+
} else {
|
|
64
|
+
barHeight = heights.stacked
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const labelHeight = isLabelBelowBar ? fontSize[config.fontSize] * 1.2 : 0
|
|
68
|
+
let barSpace = Number(config.barSpace)
|
|
69
|
+
|
|
70
|
+
// calculate height of container based height, space and fontSize of labels
|
|
71
|
+
let totalHeight = barsArr.length * (barHeight + labelHeight + barSpace)
|
|
72
|
+
|
|
73
|
+
if (isHorizontal) {
|
|
74
|
+
config.heights.horizontal = totalHeight
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// return new updated bars/groupes
|
|
78
|
+
return barsArr.map((bar, i) => {
|
|
79
|
+
// set bars Y dynamycly to handle space between bars
|
|
80
|
+
let y = 0
|
|
81
|
+
bar.index !== 0 && (y = (barHeight + barSpace + labelHeight) * i)
|
|
82
|
+
|
|
83
|
+
return { ...bar, y: y, height: barHeight }
|
|
84
|
+
})
|
|
85
|
+
}
|
|
86
|
+
|
|
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
|
+
}
|
|
49
96
|
|
|
50
97
|
// Using State
|
|
51
|
-
const [horizBarHeight, setHorizBarHeight] = useState(null)
|
|
52
98
|
const [textWidth, setTextWidth] = useState(null)
|
|
53
99
|
|
|
54
100
|
useEffect(() => {
|
|
@@ -87,8 +133,6 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
87
133
|
}
|
|
88
134
|
}, [config.barStyle])
|
|
89
135
|
|
|
90
|
-
// config.runtime.seriesKeys.sort().reverse();
|
|
91
|
-
|
|
92
136
|
return (
|
|
93
137
|
<ErrorBoundary component='BarChart'>
|
|
94
138
|
<Group left={config.runtime.yAxis.size}>
|
|
@@ -98,22 +142,26 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
98
142
|
{barStacks =>
|
|
99
143
|
barStacks.reverse().map(barStack =>
|
|
100
144
|
barStack.bars.map(bar => {
|
|
101
|
-
const xAxisValue = config.runtime.xAxis.type === 'date' ? formatDate(parseDate(data[bar.index][config.runtime.xAxis.dataKey])) : data[bar.index][config.runtime.xAxis.dataKey]
|
|
102
|
-
const yAxisValue = formatNumber(bar.bar ? bar.bar.data[bar.key] : 0)
|
|
103
|
-
let yAxisTooltip =config.runtime.yAxis.isLegendValue ? `${bar.key}: ${yAxisValue}` : config.runtime.yAxis.label ? `${config.runtime.yAxis.label}: ${yAxisValue}` : yAxisValue;
|
|
104
|
-
let xAxisTooltip = config.runtime.xAxis.label ? `${config.runtime.xAxis.label}: ${xAxisValue}` : xAxisValue
|
|
105
|
-
|
|
106
|
-
const tooltip = `<div>
|
|
107
|
-
${yAxisTooltip}<br />
|
|
108
|
-
${xAxisTooltip}<br />
|
|
109
|
-
${config.seriesLabel ? `${config.seriesLabel}: ${bar.key}` : ''}`
|
|
110
|
-
|
|
111
145
|
let transparentBar = config.legend.behavior === 'highlight' && seriesHighlight.length > 0 && seriesHighlight.indexOf(bar.key) === -1
|
|
112
146
|
let displayBar = config.legend.behavior === 'highlight' || seriesHighlight.length === 0 || seriesHighlight.indexOf(bar.key) !== -1
|
|
113
147
|
let barThickness = xMax / barStack.bars.length
|
|
114
148
|
let barThicknessAdjusted = barThickness * (config.barThickness || 0.8)
|
|
115
149
|
let offset = (barThickness * (1 - (config.barThickness || 0.8))) / 2
|
|
116
150
|
const style = applyRadius(barStack.index)
|
|
151
|
+
// tooltips
|
|
152
|
+
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)
|
|
154
|
+
let yAxisTooltip = config.runtime.yAxis.label ? `${config.runtime.yAxis.label}: ${yAxisValue}` : yAxisValue
|
|
155
|
+
const xAxisTooltip = config.runtime.xAxis.label ? `${config.runtime.xAxis.label}: ${xAxisValue}` : xAxisValue
|
|
156
|
+
if (!hasMultipleSeries) {
|
|
157
|
+
yAxisTooltip = config.isLegendValue ? `${bar.key}: ${yAxisValue}` : config.runtime.yAxis.label ? `${config.runtime.yAxis.label}: ${yAxisValue}` : yAxisValue
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const tooltip = `<div>
|
|
161
|
+
${config.legend.showLegendValuesTooltip && config.runtime.seriesLabels && hasMultipleSeries ? `${config.runtime.seriesLabels[bar.key] || ''}<br/>` : ''}
|
|
162
|
+
${yAxisTooltip}<br />
|
|
163
|
+
${xAxisTooltip}
|
|
164
|
+
</div>`
|
|
117
165
|
|
|
118
166
|
return (
|
|
119
167
|
<>
|
|
@@ -127,7 +175,7 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
127
175
|
`}
|
|
128
176
|
</style>
|
|
129
177
|
<Group key={`bar-stack-${barStack.index}-${bar.index}`} id={`barStack${barStack.index}-${bar.index}`} className='stack vertical'>
|
|
130
|
-
<Text display={config.labels && displayBar ? 'block' : 'none'} opacity={transparentBar ? 0.5 : 1} x={barThickness *
|
|
178
|
+
<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'>
|
|
131
179
|
{formatNumber(bar.bar ? bar.bar.data[bar.key] : 0)}
|
|
132
180
|
</Text>
|
|
133
181
|
<foreignObject
|
|
@@ -141,9 +189,7 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
141
189
|
display={displayBar ? 'block' : 'none'}
|
|
142
190
|
data-tip={tooltip}
|
|
143
191
|
data-for={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
|
|
144
|
-
>
|
|
145
|
-
{' '}
|
|
146
|
-
</foreignObject>
|
|
192
|
+
></foreignObject>
|
|
147
193
|
</Group>
|
|
148
194
|
</>
|
|
149
195
|
)
|
|
@@ -159,44 +205,25 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
159
205
|
<BarStackHorizontal data={data} keys={config.runtime.barSeriesKeys || config.runtime.seriesKeys} height={yMax} y={(d: any) => d[config.runtime.yAxis.dataKey]} xScale={xScale} yScale={yScale} color={colorScale} offset='none'>
|
|
160
206
|
{barStacks =>
|
|
161
207
|
barStacks.map(barStack =>
|
|
162
|
-
barStack.bars.map((bar, index) => {
|
|
163
|
-
const yAxisValue = formatNumber(data[bar.index][bar.key]);
|
|
164
|
-
const xAxisValue = config.runtime.yAxis.type === 'date' ? formatDate(parseDate(data[bar.index][config.runtime.originalXAxis.dataKey])) : data[bar.index][config.runtime.originalXAxis.dataKey]
|
|
165
|
-
let yAxisTooltip = config.yAxis.isLegendValue ? `${bar.key}: ${yAxisValue}` : config.yAxis.label ? `${config.yAxis.label}: ${yAxisValue}` :`${yAxisValue}`
|
|
166
|
-
let xAxisTooltip = config.xAxis.label ? `${config.xAxis.label}: ${xAxisValue}` : xAxisValue
|
|
167
|
-
// let yAxisTooltip = config.yAxis.label ? `${config.yAxis.label}: ${data[bar.index][bar.key]}` : `${bar.key}: ${data[bar.index][bar.key]}`
|
|
168
|
-
// let xAxisTooltip = config.xAxis.label ? `${config.xAxis.label}: ${data[bar.index][config.runtime.originalXAxis.dataKey]}` :`${data[bar.index].name}`
|
|
169
|
-
|
|
170
|
-
const tooltip = `<div>
|
|
171
|
-
${yAxisTooltip}<br />
|
|
172
|
-
${xAxisTooltip}<br />
|
|
173
|
-
${config.seriesLabel ? `${config.seriesLabel}: ${bar.key}` : ''}`
|
|
208
|
+
updateBars(barStack.bars).map((bar, index) => {
|
|
174
209
|
let transparentBar = config.legend.behavior === 'highlight' && seriesHighlight.length > 0 && seriesHighlight.indexOf(bar.key) === -1
|
|
175
210
|
let displayBar = config.legend.behavior === 'highlight' || seriesHighlight.length === 0 || seriesHighlight.indexOf(bar.key) !== -1
|
|
176
|
-
const barsPerGroup = config.series.length
|
|
177
|
-
let barHeight = config.barHeight ? config.barHeight : 25
|
|
178
|
-
let barPadding = barHeight
|
|
179
|
-
|
|
180
211
|
config.barHeight = Number(config.barHeight)
|
|
181
212
|
const style = applyRadius(barStack.index)
|
|
182
|
-
|
|
183
|
-
if (orientation === 'horizontal') {
|
|
184
|
-
if (isLabelBelowBar || isLabelMissing || isLabelOnYAxis) {
|
|
185
|
-
if (barHeight < 40) {
|
|
186
|
-
config.barPadding = 40
|
|
187
|
-
} else {
|
|
188
|
-
config.barPadding = Number(barPadding)
|
|
189
|
-
}
|
|
190
|
-
} else {
|
|
191
|
-
config.barPadding = Number(barPadding) / 2
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
config.height = Number(barHeight) * data.length + Number(config.barPadding) * data.length
|
|
196
|
-
|
|
197
213
|
let labelColor = '#000000'
|
|
198
|
-
|
|
199
|
-
|
|
214
|
+
// tooltips
|
|
215
|
+
const xAxisValue = formatNumber(data[bar.index][bar.key])
|
|
216
|
+
const yAxisValue = config.runtime.yAxis.type === 'date' ? formatDate(parseDate(data[bar.index][config.runtime.originalXAxis.dataKey])) : data[bar.index][config.runtime.originalXAxis.dataKey]
|
|
217
|
+
let yAxisTooltip = config.runtime.yAxis.label ? `${config.runtime.yAxis.label}: ${yAxisValue}` : yAxisValue
|
|
218
|
+
let xAxisTooltip = config.runtime.xAxis.label ? `${config.runtime.xAxis.label}: ${xAxisValue}` : xAxisValue
|
|
219
|
+
if (!hasMultipleSeries) {
|
|
220
|
+
xAxisTooltip = config.isLegendValue ? `${bar.key}: ${xAxisValue}` : config.runtime.xAxis.label ? `${config.runtime.xAxis.label}: ${xAxisValue}` : xAxisTooltip
|
|
221
|
+
}
|
|
222
|
+
const tooltip = `<div>
|
|
223
|
+
${config.legend.showLegendValuesTooltip && config.runtime.seriesLabels && hasMultipleSeries ? `${config.runtime.seriesLabels[bar.key] || ''}<br/>` : ''}
|
|
224
|
+
${yAxisTooltip}<br />
|
|
225
|
+
${xAxisTooltip}
|
|
226
|
+
</div>`
|
|
200
227
|
|
|
201
228
|
if (chroma.contrast(labelColor, bar.color) < 4.9) {
|
|
202
229
|
labelColor = '#FFFFFF'
|
|
@@ -208,9 +235,9 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
208
235
|
key={`barstack-horizontal-${barStack.index}-${bar.index}-${index}`}
|
|
209
236
|
className={`animated-chart group ${animatedChart ? 'animated' : ''}`}
|
|
210
237
|
x={bar.x}
|
|
211
|
-
y={bar.y
|
|
238
|
+
y={bar.y}
|
|
212
239
|
width={bar.width}
|
|
213
|
-
height={
|
|
240
|
+
height={bar.height}
|
|
214
241
|
style={{ background: bar.color, border: `${config.barHasBorder === 'true' ? barBorderWidth : 0}px solid #333`, ...style }}
|
|
215
242
|
opacity={transparentBar ? 0.5 : 1}
|
|
216
243
|
display={displayBar ? 'block' : 'none'}
|
|
@@ -221,12 +248,12 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
221
248
|
{orientation === 'horizontal' && visualizationSubType === 'stacked' && isLabelBelowBar && barStack.index === 0 && !config.yAxis.hideLabel && (
|
|
222
249
|
<Text
|
|
223
250
|
x={`${bar.x + (config.isLollipopChart ? 15 : 5)}`} // padding
|
|
224
|
-
y={
|
|
251
|
+
y={bar.y + bar.height * 1.2}
|
|
225
252
|
fill={'#000000'}
|
|
226
253
|
textAnchor='start'
|
|
227
254
|
verticalAnchor='start'
|
|
228
255
|
>
|
|
229
|
-
{
|
|
256
|
+
{yAxisValue}
|
|
230
257
|
</Text>
|
|
231
258
|
)}
|
|
232
259
|
|
|
@@ -234,7 +261,7 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
234
261
|
<Text
|
|
235
262
|
display={displayBar ? 'block' : 'none'}
|
|
236
263
|
x={bar.x + barStack.bars[bar.index].width / 2} // padding
|
|
237
|
-
y={
|
|
264
|
+
y={bar.y + bar.height / 2}
|
|
238
265
|
fill={labelColor}
|
|
239
266
|
textAnchor='middle'
|
|
240
267
|
verticalAnchor='middle'
|
|
@@ -274,35 +301,11 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
274
301
|
}}
|
|
275
302
|
>
|
|
276
303
|
{barGroups => {
|
|
277
|
-
|
|
278
|
-
if (orientation === 'horizontal') {
|
|
279
|
-
const barsPerGroup = config.series.length
|
|
280
|
-
let barHeight = config.barHeight ? config.barHeight : 25
|
|
281
|
-
let barPadding = barHeight
|
|
282
|
-
barType = 'horizontal'
|
|
283
|
-
|
|
284
|
-
if (isLabelBelowBar || isLabelMissing || isLabelOnYAxis) {
|
|
285
|
-
if (barHeight < 40) {
|
|
286
|
-
config.barPadding = 40
|
|
287
|
-
} else {
|
|
288
|
-
config.barPadding = barPadding
|
|
289
|
-
}
|
|
290
|
-
} else {
|
|
291
|
-
config.barPadding = barPadding / 2
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
if (config.isLollipopChart && config.yAxis.labelPlacement === 'Below Bar') {
|
|
295
|
-
config.barPadding = config.barPadding + 7
|
|
296
|
-
}
|
|
297
|
-
config.barHeight = config.isLollipopChart ? lollipopBarWidth : barHeight
|
|
298
|
-
config.height = barsPerGroup * barHeight * barGroups.length + config.barPadding * barGroups.length
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
return barGroups.map((barGroup, index) => (
|
|
304
|
+
return updateBars(barGroups).map((barGroup, index) => (
|
|
302
305
|
<Group
|
|
303
|
-
className={`bar-group-${barGroup.index}-${barGroup.x0}--${index} ${
|
|
306
|
+
className={`bar-group-${barGroup.index}-${barGroup.x0}--${index} ${config.orientation}`}
|
|
304
307
|
key={`bar-group-${barGroup.index}-${barGroup.x0}--${index}`}
|
|
305
|
-
top={config.runtime.horizontal ?
|
|
308
|
+
top={config.runtime.horizontal ? barGroup.y : 0}
|
|
306
309
|
left={config.runtime.horizontal ? 0 : (xMax / barGroups.length) * barGroup.index}
|
|
307
310
|
>
|
|
308
311
|
{barGroup.bars.map((bar, index) => {
|
|
@@ -317,7 +320,6 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
317
320
|
if (config.isLollipopChart) {
|
|
318
321
|
offset = (config.runtime.horizontal ? yMax : xMax) / barGroups.length / 2 - lollipopBarWidth / 2
|
|
319
322
|
}
|
|
320
|
-
|
|
321
323
|
const set = new Set()
|
|
322
324
|
data.forEach(d => set.add(d[config.legend.colorCode]))
|
|
323
325
|
const uniqValues = Array.from(set)
|
|
@@ -340,10 +342,6 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
340
342
|
xAxisValue = tempValue
|
|
341
343
|
barWidth = config.barHeight
|
|
342
344
|
}
|
|
343
|
-
|
|
344
|
-
let yAxisTooltip = config.runtime.yAxis.isLegendValue ? `${bar.key} : ${yAxisValue}`: config.runtime.yAxis.label ? `${config.runtime.yAxis.label}: ${yAxisValue}` :yAxisValue;
|
|
345
|
-
let xAxisTooltip =config.runtime.xAxis.isLegendValue ? ` ${bar.key} :${xAxisValue}` : config.runtime.xAxis.label ? `${config.runtime.xAxis.label}: ${xAxisValue}` : xAxisValue
|
|
346
|
-
let horizBarLabelPadding = null
|
|
347
345
|
let labelColor = '#000000'
|
|
348
346
|
|
|
349
347
|
// Set label color
|
|
@@ -351,20 +349,26 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
351
349
|
labelColor = '#FFFFFF'
|
|
352
350
|
}
|
|
353
351
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
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
|
|
357
|
+
|
|
358
|
+
let yAxisTooltip = config.runtime.yAxis.label ? `${config.runtime.yAxis.label}: ${yAxisValue}` : yAxisValue
|
|
359
|
+
let xAxisTooltip = config.runtime.xAxis.label ? `${config.runtime.xAxis.label}: ${xAxisValue}` : xAxisValue
|
|
360
|
+
if (!hasMultipleSeries && config.runtime.horizontal) {
|
|
361
|
+
xAxisTooltip = config.isLegendValue ? `${bar.key}: ${xAxisValue}` : config.runtime.xAxis.label ? `${config.runtime.xAxis.label}: ${xAxisValue}` : xAxisValue
|
|
362
|
+
}
|
|
363
|
+
if (!hasMultipleSeries && !config.runtime.horizontal) {
|
|
364
|
+
yAxisTooltip = config.isLegendValue ? `${bar.key}: ${yAxisValue}` : config.runtime.yAxis.label ? `${config.runtime.yAxis.label}: ${yAxisValue}` : yAxisValue
|
|
361
365
|
}
|
|
362
|
-
|
|
366
|
+
|
|
363
367
|
const tooltip = `<div>
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
+
${config.legend.showLegendValuesTooltip && config.runtime.seriesLabels && hasMultipleSeries ? `${config.runtime.seriesLabels[bar.key] || ''}<br/>` : ''}
|
|
369
|
+
${yAxisTooltip}<br />
|
|
370
|
+
${xAxisTooltip}
|
|
371
|
+
</div>`
|
|
368
372
|
|
|
369
373
|
return (
|
|
370
374
|
<>
|
|
@@ -378,16 +382,13 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
378
382
|
`}
|
|
379
383
|
</style>
|
|
380
384
|
<Group key={`bar-sub-group-${barGroup.index}-${barGroup.x0}-${barY}--${index}`}>
|
|
381
|
-
<Text display={config.labels && displayBar ? 'block' : 'none'} opacity={transparentBar ? 0.5 : 1} x={barWidth * (barGroup.bars.length - bar.index - 0.5) + offset} y={barY - 5} fill={barColor} textAnchor='middle'>
|
|
382
|
-
{formatNumber(bar.value)}
|
|
383
|
-
</Text>
|
|
384
385
|
<foreignObject
|
|
385
386
|
id={`barGroup${barGroup.index}`}
|
|
386
387
|
key={`bar-group-bar-${barGroup.index}-${bar.index}-${bar.value}-${bar.key}`}
|
|
387
|
-
x={config.runtime.horizontal ? 0 : barWidth *
|
|
388
|
-
y={config.runtime.horizontal ? barWidth *
|
|
388
|
+
x={config.runtime.horizontal ? 0 : barWidth * bar.index + offset}
|
|
389
|
+
y={config.runtime.horizontal ? barWidth * bar.index : barY}
|
|
389
390
|
width={config.runtime.horizontal ? bar.y : barWidth}
|
|
390
|
-
height={config.
|
|
391
|
+
height={isHorizontal && !config.isLollipopChart ? barWidth : isHorizontal && config.isLollipopChart ? lollipopBarWidth : barHeight}
|
|
391
392
|
style={{
|
|
392
393
|
background: config.isLollipopChart && config.lollipopColorStyle === 'regular' ? barColor : config.isLollipopChart && config.lollipopColorStyle === 'two-tone' ? chroma(barColor).brighten(1) : barColor,
|
|
393
394
|
border: `${config.isLollipopChart ? 0 : config.barHasBorder === 'true' ? barBorderWidth : 0}px solid #333`,
|
|
@@ -398,10 +399,53 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
398
399
|
data-tip={tooltip}
|
|
399
400
|
data-for={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
|
|
400
401
|
></foreignObject>
|
|
402
|
+
{orientation === 'horizontal' && !config.isLollipopChart && displayNumbersOnBar && (
|
|
403
|
+
<Text
|
|
404
|
+
display={displayBar ? 'block' : 'none'}
|
|
405
|
+
x={bar.y}
|
|
406
|
+
y={config.barHeight / 2 + config.barHeight * bar.index}
|
|
407
|
+
fill={labelColor}
|
|
408
|
+
dx={doesTextFit ? -5 : 5} // X padding
|
|
409
|
+
verticalAnchor='middle'
|
|
410
|
+
textAnchor={doesTextFit ? 'end' : 'start'}
|
|
411
|
+
>
|
|
412
|
+
{xAxisValue}
|
|
413
|
+
</Text>
|
|
414
|
+
)}
|
|
415
|
+
;
|
|
416
|
+
{orientation === 'horizontal' && config.isLollipopChart && displayNumbersOnBar && (
|
|
417
|
+
<Text
|
|
418
|
+
display={displayBar ? 'block' : 'none'}
|
|
419
|
+
x={`${bar.y + (config.isLollipopChart ? 15 : 5) + (config.isLollipopChart && barGroup.bars.length === bar.index ? offset : 0)}`} // padding
|
|
420
|
+
y={0}
|
|
421
|
+
fill={'#000000'}
|
|
422
|
+
textAnchor='start'
|
|
423
|
+
verticalAnchor='middle'
|
|
424
|
+
fontWeight={'normal'}
|
|
425
|
+
>
|
|
426
|
+
{xAxisValue}
|
|
427
|
+
</Text>
|
|
428
|
+
)}
|
|
429
|
+
{orientation === 'horizontal' && isLabelBelowBar && !config.yAxis.hideLabel && (
|
|
430
|
+
<Text x={config.yAxis.hideAxis ? 0 : 5} y={barGroup.height} dy={4} verticalAnchor={'start'} textAnchor={'start'}>
|
|
431
|
+
{config.runtime.yAxis.type === 'date'
|
|
432
|
+
? formatDate(parseDate(data[barGroup.index][config.runtime.originalXAxis.dataKey]))
|
|
433
|
+
: isHorizontal
|
|
434
|
+
? data[barGroup.index][config.runtime.originalXAxis.dataKey]
|
|
435
|
+
: formatNumber(data[barGroup.index][config.runtime.originalXAxis.dataKey])}
|
|
436
|
+
</Text>
|
|
437
|
+
)}
|
|
438
|
+
;
|
|
439
|
+
{orientation === 'vertical' && (
|
|
440
|
+
<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
|
+
{bar.value}
|
|
442
|
+
</Text>
|
|
443
|
+
)}
|
|
444
|
+
;
|
|
401
445
|
{config.isLollipopChart && config.lollipopShape === 'circle' && (
|
|
402
446
|
<circle
|
|
403
447
|
cx={orientation === 'horizontal' ? bar.y : barWidth * (barGroup.bars.length - bar.index - 1) + (isLabelBelowBar && orientation === 'horizontal' ? 0 : offset) + lollipopShapeSize / 3.5}
|
|
404
|
-
cy={orientation === 'horizontal' ?
|
|
448
|
+
cy={orientation === 'horizontal' ? 0 + lollipopBarWidth / 2 : bar.y}
|
|
405
449
|
r={lollipopShapeSize / 2}
|
|
406
450
|
fill={barColor}
|
|
407
451
|
key={`circle--${bar.index}`}
|
|
@@ -413,7 +457,7 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
413
457
|
{config.isLollipopChart && config.lollipopShape === 'square' && (
|
|
414
458
|
<rect
|
|
415
459
|
x={orientation === 'horizontal' && bar.y > 10 ? bar.y - lollipopShapeSize / 2 : orientation === 'horizontal' && bar.y < 10 ? 0 : orientation !== 'horizontal' ? offset - lollipopBarWidth / 2 : barWidth * (barGroup.bars.length - bar.index - 1) + offset - 5.25}
|
|
416
|
-
y={orientation === 'horizontal' ? 0 - lollipopBarWidth / 2
|
|
460
|
+
y={orientation === 'horizontal' ? 0 - lollipopBarWidth / 2 : barY}
|
|
417
461
|
width={lollipopShapeSize}
|
|
418
462
|
height={lollipopShapeSize}
|
|
419
463
|
fill={barColor}
|
|
@@ -425,120 +469,6 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
425
469
|
<animate attributeName='height' values={`0, ${lollipopShapeSize}`} dur='2.5s' />
|
|
426
470
|
</rect>
|
|
427
471
|
)}
|
|
428
|
-
{orientation === 'horizontal' && textWidth + 100 < bar.y
|
|
429
|
-
? config.yAxis.labelPlacement === 'On Bar' && (
|
|
430
|
-
<Group>
|
|
431
|
-
<Text
|
|
432
|
-
innerRef={e => {
|
|
433
|
-
if (e) {
|
|
434
|
-
// use font sizes and padding to set the bar height
|
|
435
|
-
let elem = e.getBBox()
|
|
436
|
-
setTextWidth(elem.width)
|
|
437
|
-
config.barHeight = elem.height * 2 + horizBarLabelPadding * 2 + onBarTextSpacing / 2
|
|
438
|
-
config.barPadding = horizBarHeight / 2
|
|
439
|
-
}
|
|
440
|
-
}}
|
|
441
|
-
x={bar.y - horizBarLabelPadding}
|
|
442
|
-
y={barHeight * (barGroup.bars.length - bar.index - 1) + horizBarLabelPadding * 2}
|
|
443
|
-
fill={labelColor}
|
|
444
|
-
textAnchor='end'
|
|
445
|
-
>
|
|
446
|
-
{yAxisValue}
|
|
447
|
-
</Text>
|
|
448
|
-
<Text x={bar.y - horizBarLabelPadding} y={barWidth * (barGroup.bars.length - bar.index - 1) + horizBarLabelPadding * 2 + onBarTextSpacing} fill={labelColor} textAnchor='end'>
|
|
449
|
-
{xAxisValue}
|
|
450
|
-
</Text>
|
|
451
|
-
</Group>
|
|
452
|
-
)
|
|
453
|
-
: isLabelOnBar && (
|
|
454
|
-
<Group>
|
|
455
|
-
{/* hide y label if we're only showing data on bar */}
|
|
456
|
-
<Text x={bar.y + horizBarLabelPadding} y={barWidth * (barGroup.bars.length - bar.index - 1) + horizBarLabelPadding * 2} fill={'#000'} textAnchor='start' verticalAnchor='end'>
|
|
457
|
-
{yAxisValue}
|
|
458
|
-
</Text>
|
|
459
|
-
<Text x={bar.y + horizBarLabelPadding} y={barWidth * (barGroup.bars.length - bar.index - 1) + horizBarLabelPadding * 2 + onBarTextSpacing} fill={'#000'} textAnchor='start' verticalAnchor='start'>
|
|
460
|
-
{xAxisValue}
|
|
461
|
-
</Text>
|
|
462
|
-
</Group>
|
|
463
|
-
)}
|
|
464
|
-
|
|
465
|
-
{orientation === 'horizontal' && isLabelBelowBar && !config.yAxis.hideLabel && (
|
|
466
|
-
<>
|
|
467
|
-
<Text
|
|
468
|
-
x={config.yAxis.hideAxis ? 0 : 5} // padding
|
|
469
|
-
y={config.isLollipopChart ? lollipopShapeSize * config.series.length + 2 : barWidth * config.series.length + 7}
|
|
470
|
-
verticalAnchor={'start'}
|
|
471
|
-
textAnchor={'start'}
|
|
472
|
-
>
|
|
473
|
-
{config.runtime.yAxis.type === 'date'
|
|
474
|
-
? formatDate(parseDate(data[barGroup.index][config.runtime.originalXAxis.dataKey]))
|
|
475
|
-
: isHorizontal
|
|
476
|
-
? data[barGroup.index][config.runtime.originalXAxis.dataKey]
|
|
477
|
-
: formatNumber(data[barGroup.index][config.runtime.originalXAxis.dataKey])}
|
|
478
|
-
</Text>
|
|
479
|
-
|
|
480
|
-
{displayNumbersOnBar ? (
|
|
481
|
-
textWidth + 100 < bar.y && !config.isLollipopChart ? (
|
|
482
|
-
<Text
|
|
483
|
-
display={displayBar ? 'block' : 'none'}
|
|
484
|
-
x={bar.y - 5} // padding
|
|
485
|
-
y={config.isLollipopChart ? offset : config.barHeight / 2 + config.barHeight * (barGroup.bars.length - bar.index - 1)}
|
|
486
|
-
fill={labelColor}
|
|
487
|
-
textAnchor='end'
|
|
488
|
-
verticalAnchor='middle'
|
|
489
|
-
>
|
|
490
|
-
{xAxisValue}
|
|
491
|
-
</Text>
|
|
492
|
-
) : (
|
|
493
|
-
<Text
|
|
494
|
-
display={displayBar ? 'block' : 'none'}
|
|
495
|
-
x={`${bar.y + (config.isLollipopChart ? 15 : 5) + (config.isLollipopChart && barGroup.bars.length === bar.index ? offset : 0)}`} // padding
|
|
496
|
-
y={config.isLollipopChart ? 0 : config.barHeight / 2 + config.barHeight * (barGroup.bars.length - bar.index - 1)}
|
|
497
|
-
fill={'#000000'}
|
|
498
|
-
textAnchor='start'
|
|
499
|
-
verticalAnchor='middle'
|
|
500
|
-
fontWeight={'normal'}
|
|
501
|
-
>
|
|
502
|
-
{xAxisValue}
|
|
503
|
-
</Text>
|
|
504
|
-
)
|
|
505
|
-
) : (
|
|
506
|
-
''
|
|
507
|
-
)}
|
|
508
|
-
</>
|
|
509
|
-
)}
|
|
510
|
-
|
|
511
|
-
{isLabelOnYAxis && orientation === 'horizontal' && (
|
|
512
|
-
<>
|
|
513
|
-
{displayNumbersOnBar ? (
|
|
514
|
-
textWidth + 100 < bar.y && !config.isLollipopChart ? (
|
|
515
|
-
<Text
|
|
516
|
-
display={displayBar ? 'block' : 'none'}
|
|
517
|
-
x={bar.y - 5} // padding
|
|
518
|
-
y={config.isLollipopChart ? config.barHeight * (barGroup.bars.length - bar.index - 1) + offset : config.barHeight * (barGroup.bars.length - bar.index - 1) + config.barHeight / 2}
|
|
519
|
-
fill={labelColor}
|
|
520
|
-
textAnchor='end'
|
|
521
|
-
verticalAnchor='middle'
|
|
522
|
-
>
|
|
523
|
-
{formatNumber(xAxisValue)}
|
|
524
|
-
</Text>
|
|
525
|
-
) : (
|
|
526
|
-
<Text
|
|
527
|
-
display={displayBar ? 'block' : 'none'}
|
|
528
|
-
x={`${bar.y + (config.isLollipopChart ? 15 : 5)}`} // padding
|
|
529
|
-
y={config.isLollipopChart ? config.barHeight * (barGroup.bars.length - bar.index - 1) + offset : config.barHeight * (barGroup.bars.length - bar.index - 1) + config.barHeight / 2}
|
|
530
|
-
fill={'#000000'}
|
|
531
|
-
textAnchor='start'
|
|
532
|
-
verticalAnchor='middle'
|
|
533
|
-
>
|
|
534
|
-
{formatNumber(xAxisValue)}
|
|
535
|
-
</Text>
|
|
536
|
-
)
|
|
537
|
-
) : (
|
|
538
|
-
''
|
|
539
|
-
)}
|
|
540
|
-
</>
|
|
541
|
-
)}
|
|
542
472
|
</Group>
|
|
543
473
|
</>
|
|
544
474
|
)
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import React, { useContext, useEffect } from 'react'
|
|
2
|
+
import { BoxPlot } from '@visx/stats'
|
|
3
|
+
import { Group } from '@visx/group'
|
|
4
|
+
import { scaleBand, scaleLinear } from '@visx/scale'
|
|
5
|
+
import Context from '../context'
|
|
6
|
+
import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
7
|
+
import { colorPalettesChart } from '@cdc/core/data/colorPalettes'
|
|
8
|
+
import ReactTooltip from 'react-tooltip'
|
|
9
|
+
|
|
10
|
+
const CoveBoxPlot = ({ xScale, yScale }) => {
|
|
11
|
+
const { transformedData: data, config } = useContext(Context)
|
|
12
|
+
|
|
13
|
+
const {
|
|
14
|
+
boxplot: { columnFirstQuartile, columnThirdQuartile, columnMax, columnMin, columnMedian, columnOutliers },
|
|
15
|
+
width,
|
|
16
|
+
height
|
|
17
|
+
} = config
|
|
18
|
+
|
|
19
|
+
const yMax = config.height - config.runtime.xAxis.size
|
|
20
|
+
const xMax = config.width - config.runtime.yAxis.size - config.yAxis.rightAxisSize
|
|
21
|
+
const boxWidth = xScale.bandwidth()
|
|
22
|
+
const constrainedWidth = Math.min(20, boxWidth)
|
|
23
|
+
const color_0 = colorPalettesChart[config?.palette][0] ? colorPalettesChart[config?.palette][0] : '#000'
|
|
24
|
+
|
|
25
|
+
// tooltips
|
|
26
|
+
const tooltip_id = `cdc-open-viz-tooltip-${config.runtime.uniqueId}`
|
|
27
|
+
const handleTooltip = d => {
|
|
28
|
+
return `
|
|
29
|
+
<strong>${d.columnCategory}</strong></br>
|
|
30
|
+
Q1: ${d.columnFirstQuartile}<br/>
|
|
31
|
+
Q3: ${d.columnThirdQuartile}<br/>
|
|
32
|
+
IQR: ${d.columnIqr}<br/>
|
|
33
|
+
Median: ${d.columnMedian}
|
|
34
|
+
`
|
|
35
|
+
}
|
|
36
|
+
return (
|
|
37
|
+
<ErrorBoundary component='BoxPlot'>
|
|
38
|
+
<Group className='boxplot' key='boxplot-wrapper'>
|
|
39
|
+
{config.boxplot.map((d, i) => {
|
|
40
|
+
const offset = boxWidth - constrainedWidth
|
|
41
|
+
return (
|
|
42
|
+
<BoxPlot
|
|
43
|
+
key={`box-plot-${i}`}
|
|
44
|
+
min={d.columnMin}
|
|
45
|
+
max={d.columnMax}
|
|
46
|
+
left={xScale(d.columnCategory) + config.yAxis.size + offset / 2 + 0.5}
|
|
47
|
+
firstQuartile={d.columnFirstQuartile}
|
|
48
|
+
thirdQuartile={d.columnThirdQuartile}
|
|
49
|
+
median={d.columnMedian}
|
|
50
|
+
boxWidth={constrainedWidth}
|
|
51
|
+
fill={color_0}
|
|
52
|
+
fillOpacity={0.5}
|
|
53
|
+
stroke='black'
|
|
54
|
+
strokeWidth={1}
|
|
55
|
+
valueScale={yScale}
|
|
56
|
+
outliers={d.columnOutliers}
|
|
57
|
+
outlierProps={{
|
|
58
|
+
style: {
|
|
59
|
+
fill: `${color_0}`,
|
|
60
|
+
opacity: 1
|
|
61
|
+
}
|
|
62
|
+
}}
|
|
63
|
+
medianProps={{
|
|
64
|
+
style: {
|
|
65
|
+
stroke: 'black'
|
|
66
|
+
}
|
|
67
|
+
}}
|
|
68
|
+
boxProps={{
|
|
69
|
+
style: {
|
|
70
|
+
stroke: 'black'
|
|
71
|
+
},
|
|
72
|
+
'data-tip': 'cool'
|
|
73
|
+
}}
|
|
74
|
+
maxProps={{
|
|
75
|
+
style: {
|
|
76
|
+
stroke: 'black'
|
|
77
|
+
}
|
|
78
|
+
}}
|
|
79
|
+
container
|
|
80
|
+
containerProps={{
|
|
81
|
+
'data-tip': handleTooltip(d),
|
|
82
|
+
'data-for': tooltip_id
|
|
83
|
+
}}
|
|
84
|
+
/>
|
|
85
|
+
)
|
|
86
|
+
})}
|
|
87
|
+
</Group>
|
|
88
|
+
</ErrorBoundary>
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export default CoveBoxPlot
|