@cdc/chart 4.24.10 → 4.24.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cdcchart.js +34618 -33995
- package/examples/feature/boxplot/boxplot-data.json +88 -22
- package/examples/feature/boxplot/boxplot.json +540 -16
- package/examples/feature/boxplot/testing.csv +7 -7
- package/examples/feature/sankey/sankey-example-data.json +0 -1
- package/examples/private/test.json +20092 -0
- package/index.html +3 -3
- package/package.json +2 -2
- package/src/CdcChart.tsx +86 -86
- package/src/_stories/Chart.CustomColors.stories.tsx +19 -0
- package/src/_stories/Chart.DynamicSeries.stories.tsx +27 -0
- package/src/_stories/Chart.Legend.Gradient.stories.tsx +42 -1
- package/src/_stories/Chart.stories.tsx +7 -8
- package/src/_stories/ChartEditor.stories.tsx +27 -0
- package/src/_stories/ChartLine.Suppression.stories.tsx +25 -0
- package/src/_stories/ChartPrefixSuffix.stories.tsx +8 -0
- package/src/_stories/_mock/boxplot_multiseries.json +647 -0
- package/src/_stories/_mock/dynamic_series_bar_config.json +723 -0
- package/src/_stories/_mock/dynamic_series_config.json +979 -0
- package/{examples/feature/scatterplot/scatterplot.json → src/_stories/_mock/scatterplot_mock.json} +62 -92
- package/src/_stories/_mock/suppression_mock.json +1549 -0
- package/src/components/BarChart/components/BarChart.StackedVertical.tsx +43 -9
- package/src/components/BarChart/components/BarChart.Vertical.tsx +60 -42
- package/src/components/BarChart/helpers/index.ts +1 -2
- package/src/components/BoxPlot/BoxPlot.tsx +189 -0
- package/src/components/EditorPanel/EditorPanel.tsx +64 -62
- package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +3 -3
- package/src/components/EditorPanel/components/Panels/Panel.BoxPlot.tsx +51 -6
- package/src/components/EditorPanel/components/Panels/Panel.Regions.tsx +40 -9
- package/src/components/EditorPanel/components/Panels/Panel.Sankey.tsx +3 -3
- package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +121 -56
- package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +1 -2
- package/src/components/EditorPanel/useEditorPermissions.ts +15 -1
- package/src/components/Legend/Legend.Component.tsx +9 -10
- package/src/components/Legend/Legend.tsx +16 -16
- package/src/components/LineChart/helpers.ts +48 -43
- package/src/components/LineChart/index.tsx +88 -82
- package/src/components/LinearChart.tsx +17 -10
- package/src/components/Sankey/index.tsx +50 -32
- package/src/components/Sankey/sankey.scss +6 -5
- package/src/components/Sankey/useSankeyAlert.tsx +60 -0
- package/src/components/ScatterPlot/ScatterPlot.jsx +20 -4
- package/src/data/initial-state.js +3 -9
- package/src/hooks/useLegendClasses.ts +10 -23
- package/src/hooks/useMinMax.ts +27 -13
- package/src/hooks/useReduceData.ts +43 -10
- package/src/hooks/useScales.ts +56 -35
- package/src/hooks/useTooltip.tsx +54 -49
- package/src/scss/main.scss +0 -18
- package/src/types/ChartConfig.ts +6 -19
- package/src/types/ChartContext.ts +4 -1
- package/src/types/ForestPlot.ts +8 -0
- package/src/components/BoxPlot/BoxPlot.jsx +0 -111
|
@@ -13,32 +13,62 @@ import createBarElement from '@cdc/core/components/createBarElement'
|
|
|
13
13
|
const BarChartStackedVertical = () => {
|
|
14
14
|
const [barWidth, setBarWidth] = useState(0)
|
|
15
15
|
const { xScale, yScale, seriesScale, xMax, yMax } = useContext(BarChartContext)
|
|
16
|
-
const { transformedData, colorScale, seriesHighlight, config, formatNumber, formatDate, parseDate, setSharedFilter } =
|
|
17
|
-
|
|
16
|
+
const { transformedData, colorScale, seriesHighlight, config, formatNumber, formatDate, parseDate, setSharedFilter } =
|
|
17
|
+
useContext(ConfigContext)
|
|
18
|
+
const {
|
|
19
|
+
isHorizontal,
|
|
20
|
+
barBorderWidth,
|
|
21
|
+
applyRadius,
|
|
22
|
+
hoveredBar,
|
|
23
|
+
getAdditionalColumn,
|
|
24
|
+
onMouseLeaveBar,
|
|
25
|
+
onMouseOverBar,
|
|
26
|
+
barStackedSeriesKeys
|
|
27
|
+
} = useBarChart()
|
|
18
28
|
const { orientation } = config
|
|
19
29
|
|
|
20
30
|
const data = config.brush?.active && config.brush.data?.length ? config.brush.data : transformedData
|
|
21
31
|
const isDateAxisType = config.runtime.xAxis.type === 'date-time' || config.runtime.xAxis.type === 'date'
|
|
32
|
+
const isDateTimeScaleAxisType = config.runtime.xAxis.type === 'date-time'
|
|
22
33
|
|
|
23
34
|
return (
|
|
24
35
|
config.visualizationSubType === 'stacked' &&
|
|
25
36
|
!isHorizontal && (
|
|
26
37
|
<>
|
|
27
|
-
<BarStack
|
|
38
|
+
<BarStack
|
|
39
|
+
data={data}
|
|
40
|
+
keys={barStackedSeriesKeys}
|
|
41
|
+
x={d => d[config.runtime.xAxis.dataKey]}
|
|
42
|
+
xScale={xScale}
|
|
43
|
+
yScale={yScale}
|
|
44
|
+
color={colorScale}
|
|
45
|
+
>
|
|
28
46
|
{barStacks =>
|
|
29
47
|
barStacks.reverse().map(barStack =>
|
|
30
48
|
barStack.bars.map(bar => {
|
|
31
|
-
let transparentBar =
|
|
32
|
-
|
|
33
|
-
|
|
49
|
+
let transparentBar =
|
|
50
|
+
config.legend.behavior === 'highlight' &&
|
|
51
|
+
seriesHighlight.length > 0 &&
|
|
52
|
+
seriesHighlight.indexOf(bar.key) === -1
|
|
53
|
+
let displayBar =
|
|
54
|
+
config.legend.behavior === 'highlight' ||
|
|
55
|
+
seriesHighlight.length === 0 ||
|
|
56
|
+
seriesHighlight.indexOf(bar.key) !== -1
|
|
57
|
+
let barThickness = isDateAxisType
|
|
58
|
+
? seriesScale.range()[1] - seriesScale.range()[0]
|
|
59
|
+
: xMax / barStack.bars.length
|
|
34
60
|
if (config.runtime.xAxis.type !== 'date') barThickness = config.barThickness * barThickness
|
|
35
61
|
// tooltips
|
|
36
62
|
const rawXValue = bar.bar.data[config.runtime.xAxis.dataKey]
|
|
37
63
|
const xAxisValue = isDateAxisType ? formatDate(parseDate(rawXValue)) : rawXValue
|
|
38
64
|
const yAxisValue = formatNumber(bar.bar ? bar.bar.data[bar.key] : 0, 'left')
|
|
39
65
|
if (!yAxisValue) return
|
|
40
|
-
const barX =
|
|
41
|
-
|
|
66
|
+
const barX =
|
|
67
|
+
xScale(isDateAxisType ? parseDate(rawXValue) : rawXValue) -
|
|
68
|
+
(isDateTimeScaleAxisType ? barThickness / 2 : 0)
|
|
69
|
+
const xAxisTooltip = config.runtime.xAxis.label
|
|
70
|
+
? `${config.runtime.xAxis.label}: ${xAxisValue}`
|
|
71
|
+
: xAxisValue
|
|
42
72
|
const additionalColTooltip = getAdditionalColumn(hoveredBar)
|
|
43
73
|
const tooltipBody = `${config.runtime.seriesLabels[bar.key]}: ${yAxisValue}`
|
|
44
74
|
const tooltip = `<ul>
|
|
@@ -51,7 +81,11 @@ const BarChartStackedVertical = () => {
|
|
|
51
81
|
|
|
52
82
|
return (
|
|
53
83
|
<Group key={`${barStack.index}--${bar.index}--${orientation}`}>
|
|
54
|
-
<Group
|
|
84
|
+
<Group
|
|
85
|
+
key={`bar-stack-${barStack.index}-${bar.index}`}
|
|
86
|
+
id={`barStack${barStack.index}-${bar.index}`}
|
|
87
|
+
className='stack vertical'
|
|
88
|
+
>
|
|
55
89
|
{createBarElement({
|
|
56
90
|
config: config,
|
|
57
91
|
seriesHighlight,
|
|
@@ -15,11 +15,13 @@ import { BarGroup } from '@visx/shape'
|
|
|
15
15
|
import Regions from '../../Regions'
|
|
16
16
|
// CDC core components and helpers
|
|
17
17
|
import { isDateScale } from '@cdc/core/helpers/cove/date'
|
|
18
|
+
import isNumber from '@cdc/core/helpers/isNumber'
|
|
18
19
|
import createBarElement from '@cdc/core/components/createBarElement'
|
|
19
20
|
// Third party libraries
|
|
20
21
|
import chroma from 'chroma-js'
|
|
21
22
|
// Types
|
|
22
23
|
import { type ChartContext } from '../../../types/ChartContext'
|
|
24
|
+
import _, { has } from 'lodash'
|
|
23
25
|
|
|
24
26
|
export const BarChartVertical = () => {
|
|
25
27
|
const { xScale, yScale, xMax, yMax, seriesScale } = useContext<BarChartContextValues>(BarChartContext)
|
|
@@ -42,7 +44,7 @@ export const BarChartVertical = () => {
|
|
|
42
44
|
} = useBarChart()
|
|
43
45
|
|
|
44
46
|
// prettier-ignore
|
|
45
|
-
const { colorScale, config, dashboardConfig, tableData, formatDate, formatNumber, getXAxisData, getYAxisData,
|
|
47
|
+
const { colorScale, config, dashboardConfig, tableData, formatDate, formatNumber, getXAxisData, getYAxisData, parseDate, seriesHighlight, setSharedFilter, transformedData, brushConfig } = useContext<ChartContext>(ConfigContext)
|
|
46
48
|
const { HighLightedBarUtils } = useHighlightedBars(config)
|
|
47
49
|
let data = transformedData
|
|
48
50
|
// check if user add suppression
|
|
@@ -56,6 +58,34 @@ export const BarChartVertical = () => {
|
|
|
56
58
|
data = brushConfig.data
|
|
57
59
|
}
|
|
58
60
|
|
|
61
|
+
const hasConfidenceInterval = Object.keys(config.confidenceKeys).length > 0
|
|
62
|
+
|
|
63
|
+
const getData = () => {
|
|
64
|
+
const dynamicSeries = config.series.find(s => s.dynamicCategory)
|
|
65
|
+
if (!dynamicSeries) return data
|
|
66
|
+
const { dynamicCategory, dataKey } = dynamicSeries
|
|
67
|
+
const xAxisKey = config.runtime.originalXAxis.dataKey
|
|
68
|
+
const xAxisGroupDataLookup = _.groupBy(data, xAxisKey)
|
|
69
|
+
return Object.values(xAxisGroupDataLookup).map(group => {
|
|
70
|
+
return group.reduce((acc, datum) => {
|
|
71
|
+
const dataValue = datum[dataKey]
|
|
72
|
+
const dataCategory = datum[dynamicCategory]
|
|
73
|
+
if (hasConfidenceInterval) {
|
|
74
|
+
const { lower, upper } = config.confidenceKeys
|
|
75
|
+
if (!acc.CI) acc.CI = {}
|
|
76
|
+
const lowerValue = datum[lower]
|
|
77
|
+
const upperValue = datum[upper]
|
|
78
|
+
acc.CI[dataCategory] = { lower: lowerValue, upper: upperValue }
|
|
79
|
+
}
|
|
80
|
+
acc[dataCategory] = dataValue
|
|
81
|
+
acc[xAxisKey] = datum[xAxisKey]
|
|
82
|
+
acc.dynamicData = true
|
|
83
|
+
return acc
|
|
84
|
+
}, {})
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const _data = getData()
|
|
59
89
|
return (
|
|
60
90
|
config.visualizationSubType !== 'stacked' &&
|
|
61
91
|
(config.visualizationType === 'Bar' ||
|
|
@@ -64,7 +94,7 @@ export const BarChartVertical = () => {
|
|
|
64
94
|
config.orientation === 'vertical' && (
|
|
65
95
|
<Group>
|
|
66
96
|
<BarGroup
|
|
67
|
-
data={
|
|
97
|
+
data={_data}
|
|
68
98
|
keys={config.runtime.barSeriesKeys || config.runtime.seriesKeys}
|
|
69
99
|
height={yMax}
|
|
70
100
|
x0={d => {
|
|
@@ -87,6 +117,8 @@ export const BarChartVertical = () => {
|
|
|
87
117
|
left={barGroup.x0}
|
|
88
118
|
>
|
|
89
119
|
{barGroup.bars.map((bar, index) => {
|
|
120
|
+
const datum = _data[barGroup.index]
|
|
121
|
+
const dataValue = datum[config.runtime.originalXAxis.dataKey]
|
|
90
122
|
const scaleVal = config.yAxis.type === 'logarithmic' ? 0.1 : 0
|
|
91
123
|
let highlightedBarValues = config.highlightedBarValues
|
|
92
124
|
.map(item => item.value)
|
|
@@ -116,17 +148,12 @@ export const BarChartVertical = () => {
|
|
|
116
148
|
setTotalBarsInGroup(barGroup.bars.length)
|
|
117
149
|
const yAxisValue = formatNumber(/[a-zA-Z]/.test(String(bar.value)) ? '' : bar.value, 'left')
|
|
118
150
|
const xAxisValue =
|
|
119
|
-
config.runtime[section].type === 'date'
|
|
120
|
-
? formatDate(parseDate(data[barGroup.index][config.runtime.originalXAxis.dataKey]))
|
|
121
|
-
: data[barGroup.index][config.runtime.originalXAxis.dataKey]
|
|
151
|
+
config.runtime[section].type === 'date' ? formatDate(parseDate(dataValue)) : dataValue
|
|
122
152
|
|
|
123
153
|
// create new Index for bars with negative values
|
|
124
154
|
const newIndex = bar.value < 0 ? -1 : index
|
|
125
155
|
// tooltips
|
|
126
|
-
const additionalColTooltip = getAdditionalColumn(
|
|
127
|
-
bar.key,
|
|
128
|
-
data[barGroup.index][config.runtime.originalXAxis.dataKey]
|
|
129
|
-
)
|
|
156
|
+
const additionalColTooltip = getAdditionalColumn(bar.key, dataValue)
|
|
130
157
|
let xAxisTooltip = config.runtime.xAxis.label
|
|
131
158
|
? `${config.runtime.xAxis.label}: ${xAxisValue}`
|
|
132
159
|
: xAxisValue
|
|
@@ -141,10 +168,6 @@ export const BarChartVertical = () => {
|
|
|
141
168
|
// configure colors
|
|
142
169
|
let labelColor = '#000000'
|
|
143
170
|
labelColor = HighLightedBarUtils.checkFontColor(yAxisValue, highlightedBarValues, labelColor) // Set if background is transparent'
|
|
144
|
-
let barColor =
|
|
145
|
-
config.runtime.seriesLabels && config.runtime.seriesLabels[bar.key]
|
|
146
|
-
? colorScale(config.runtime.seriesLabels[bar.key])
|
|
147
|
-
: colorScale(bar.key)
|
|
148
171
|
const isRegularLollipopColor = config.isLollipopChart && config.lollipopColorStyle === 'regular'
|
|
149
172
|
const isTwoToneLollipopColor = config.isLollipopChart && config.lollipopColorStyle === 'two-tone'
|
|
150
173
|
const isHighlightedBar = highlightedBarValues?.includes(xAxisValue)
|
|
@@ -167,10 +190,8 @@ export const BarChartVertical = () => {
|
|
|
167
190
|
bar,
|
|
168
191
|
defaultBarHeight,
|
|
169
192
|
config,
|
|
170
|
-
isNumber,
|
|
171
193
|
barWidth,
|
|
172
|
-
isVertical: true
|
|
173
|
-
yAxisValue
|
|
194
|
+
isVertical: true
|
|
174
195
|
})
|
|
175
196
|
|
|
176
197
|
const absentDataLabel = getAbsentDataLabel(yAxisValue)
|
|
@@ -225,6 +246,16 @@ export const BarChartVertical = () => {
|
|
|
225
246
|
return _barColor
|
|
226
247
|
}
|
|
227
248
|
|
|
249
|
+
// Confidence Interval Variables
|
|
250
|
+
const tickWidth = 5
|
|
251
|
+
const xPos = barX + (config.xAxis.type !== 'date-time' ? barWidth / 2 : 0)
|
|
252
|
+
const [upperPos, lowerPos] = ['upper', 'lower'].map(position => {
|
|
253
|
+
if (!hasConfidenceInterval) return
|
|
254
|
+
const d = datum.dynamicData ? datum.CI[bar.key][position] : datum[config.confidenceKeys[position]]
|
|
255
|
+
return yScale(d)
|
|
256
|
+
})
|
|
257
|
+
// End Confidence Interval Variables
|
|
258
|
+
|
|
228
259
|
return (
|
|
229
260
|
<Group key={`${barGroup.index}--${index}`}>
|
|
230
261
|
<Group key={`bar-sub-group-${barGroup.index}-${barGroup.x0}-${barY}--${index}`}>
|
|
@@ -353,6 +384,19 @@ export const BarChartVertical = () => {
|
|
|
353
384
|
<animate attributeName='height' values={`0, ${lollipopShapeSize}`} dur='2.5s' />
|
|
354
385
|
</rect>
|
|
355
386
|
)}
|
|
387
|
+
{hasConfidenceInterval && (
|
|
388
|
+
<path
|
|
389
|
+
key={`confidence-interval-v-${datum[config.runtime.originalXAxis.dataKey]}`}
|
|
390
|
+
stroke='#333'
|
|
391
|
+
strokeWidth='px'
|
|
392
|
+
d={`M${xPos - tickWidth} ${upperPos}
|
|
393
|
+
L${xPos + tickWidth} ${upperPos}
|
|
394
|
+
M${xPos} ${upperPos}
|
|
395
|
+
L${xPos} ${lowerPos}
|
|
396
|
+
M${xPos - tickWidth} ${lowerPos}
|
|
397
|
+
L${xPos + tickWidth} ${lowerPos}`}
|
|
398
|
+
/>
|
|
399
|
+
)}
|
|
356
400
|
</Group>
|
|
357
401
|
</Group>
|
|
358
402
|
)
|
|
@@ -362,32 +406,6 @@ export const BarChartVertical = () => {
|
|
|
362
406
|
}}
|
|
363
407
|
</BarGroup>
|
|
364
408
|
|
|
365
|
-
{Object.keys(config.confidenceKeys).length > 0
|
|
366
|
-
? data.map(d => {
|
|
367
|
-
let xPos, yPos
|
|
368
|
-
let upperPos
|
|
369
|
-
let lowerPos
|
|
370
|
-
let tickWidth = 5
|
|
371
|
-
xPos = xScale(getXAxisData(d)) + (config.xAxis.type !== 'date-time' ? seriesScale.range()[1] / 2 : 0)
|
|
372
|
-
upperPos = yScale(getYAxisData(d, config.confidenceKeys.lower))
|
|
373
|
-
lowerPos = yScale(getYAxisData(d, config.confidenceKeys.upper))
|
|
374
|
-
return (
|
|
375
|
-
<path
|
|
376
|
-
key={`confidence-interval-v-${yPos}-${d[config.runtime.originalXAxis.dataKey]}`}
|
|
377
|
-
stroke='#333'
|
|
378
|
-
strokeWidth='px'
|
|
379
|
-
d={`
|
|
380
|
-
M${xPos - tickWidth} ${upperPos}
|
|
381
|
-
L${xPos + tickWidth} ${upperPos}
|
|
382
|
-
M${xPos} ${upperPos}
|
|
383
|
-
L${xPos} ${lowerPos}
|
|
384
|
-
M${xPos - tickWidth} ${lowerPos}
|
|
385
|
-
L${xPos + tickWidth} ${lowerPos}`}
|
|
386
|
-
/>
|
|
387
|
-
)
|
|
388
|
-
})
|
|
389
|
-
: ''}
|
|
390
|
-
|
|
391
409
|
<Regions xScale={xScale} yMax={yMax} barWidth={barWidth} totalBarsInGroup={totalBarsInGroup} />
|
|
392
410
|
</Group>
|
|
393
411
|
)
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { getTextWidth } from '@cdc/core/helpers/getTextWidth'
|
|
2
|
+
import isNumber from '@cdc/core/helpers/isNumber'
|
|
2
3
|
|
|
3
4
|
// Define an interface for the function's parameter
|
|
4
5
|
interface BarConfigProps {
|
|
5
6
|
defaultBarWidth?: number
|
|
6
7
|
defaultBarHeight?: number
|
|
7
8
|
bar?: { [key: string]: any }
|
|
8
|
-
isNumber?: Function
|
|
9
9
|
config: { [key: string]: any }
|
|
10
10
|
barWidth: number
|
|
11
11
|
isVertical: boolean
|
|
@@ -17,7 +17,6 @@ export const getBarConfig = ({
|
|
|
17
17
|
defaultBarHeight,
|
|
18
18
|
defaultBarWidth,
|
|
19
19
|
config,
|
|
20
|
-
isNumber,
|
|
21
20
|
barWidth,
|
|
22
21
|
isVertical
|
|
23
22
|
}: BarConfigProps) => {
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import React, { useContext } 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
|
+
import { scaleBand } from '@visx/scale'
|
|
8
|
+
import _ from 'lodash'
|
|
9
|
+
import { max, min, median, quantile } from 'd3-array'
|
|
10
|
+
const CoveBoxPlot = ({ xScale, yScale }) => {
|
|
11
|
+
const { config, colorScale, seriesHighlight, transformedData: data } = useContext(ConfigContext)
|
|
12
|
+
const { boxplot } = config
|
|
13
|
+
|
|
14
|
+
// tooltips
|
|
15
|
+
const tooltip_id = `cdc-open-viz-tooltip-${config.runtime.uniqueId}`
|
|
16
|
+
|
|
17
|
+
const handleTooltip = (d, key, q1, q3, median, iqr) => {
|
|
18
|
+
return `
|
|
19
|
+
<strong>${d.columnCategory}</strong></br>
|
|
20
|
+
<strong>Key:${key}</strong></br>
|
|
21
|
+
${boxplot.labels.q1}: ${q1}<br/>
|
|
22
|
+
${boxplot.labels.q3}: ${q3}<br/>
|
|
23
|
+
${boxplot.labels.iqr}: ${iqr}<br/>
|
|
24
|
+
${boxplot.labels.median}: ${median}
|
|
25
|
+
`
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const boxWidth = xScale.bandwidth()
|
|
29
|
+
const constrainedWidth = Math.min(40, boxWidth)
|
|
30
|
+
const color_0 = colorPalettesChart[config?.palette][0] ? colorPalettesChart[config?.palette][0] : '#000'
|
|
31
|
+
const seriesScale = scaleBand({
|
|
32
|
+
range: [0, config.barThickness * 100 || 1],
|
|
33
|
+
domain: config.series?.map(item => item?.dataKey)
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
const calculateBoxPlotStats = values => {
|
|
37
|
+
if (!values || !values.length) return {}
|
|
38
|
+
const sortedValues = values.sort((a, b) => a - b)
|
|
39
|
+
return {
|
|
40
|
+
min: min(values),
|
|
41
|
+
max: max(values),
|
|
42
|
+
median: median(values),
|
|
43
|
+
firstQuartile: quantile(sortedValues, 0.25),
|
|
44
|
+
thirdQuartile: quantile(sortedValues, 0.75)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const getValuesBySeriesKey = (group: string) => {
|
|
49
|
+
const allSeriesKeys = config.series.map(item => item?.dataKey)
|
|
50
|
+
const result = {}
|
|
51
|
+
const filteredData = data.filter(item => item[config.xAxis.dataKey] === group)
|
|
52
|
+
allSeriesKeys.forEach(key => {
|
|
53
|
+
result[key] = filteredData.map(item => item[key])
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
return result
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
interface Plot {
|
|
60
|
+
columnCategory: string
|
|
61
|
+
keyValues: { [key: string]: number[] }
|
|
62
|
+
}
|
|
63
|
+
const createPlots = data => {
|
|
64
|
+
const dataKeys = data.map(d => d[config.xAxis.dataKey])
|
|
65
|
+
const plots: Plot[] = []
|
|
66
|
+
const groups: string[] = _.uniq(dataKeys)
|
|
67
|
+
if (groups && groups.length > 0) {
|
|
68
|
+
groups.forEach(group => {
|
|
69
|
+
plots.push({
|
|
70
|
+
columnCategory: group,
|
|
71
|
+
keyValues: getValuesBySeriesKey(group)
|
|
72
|
+
})
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
return plots
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<ErrorBoundary component='BoxPlot'>
|
|
80
|
+
<Group left={Number(config.yAxis.size)} className='boxplot' key={`boxplot-group`}>
|
|
81
|
+
{createPlots(data).map((d, i) => {
|
|
82
|
+
const offset = boxWidth - constrainedWidth
|
|
83
|
+
const radius = 4
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<Group
|
|
87
|
+
key={`boxplotplot-${d.columnCategory}`}
|
|
88
|
+
left={xScale(d.columnCategory) + (xScale.bandwidth() - seriesScale.bandwidth()) / 2}
|
|
89
|
+
>
|
|
90
|
+
{config.series.map(item => {
|
|
91
|
+
const valuesByKey = d.keyValues[item.dataKey]
|
|
92
|
+
const { min, max, median, firstQuartile, thirdQuartile } = calculateBoxPlotStats(valuesByKey)
|
|
93
|
+
let iqr = Number(thirdQuartile - firstQuartile).toFixed(config.dataFormat.roundTo)
|
|
94
|
+
|
|
95
|
+
const transparentPlot =
|
|
96
|
+
config.legend.behavior === 'highlight' &&
|
|
97
|
+
seriesHighlight.length > 0 &&
|
|
98
|
+
seriesHighlight.indexOf(item.dataKey) === -1
|
|
99
|
+
const displayPlot =
|
|
100
|
+
config.legend.behavior === 'highlight' ||
|
|
101
|
+
seriesHighlight.length === 0 ||
|
|
102
|
+
seriesHighlight.indexOf(item.dataKey) !== -1
|
|
103
|
+
const fillOpacity = transparentPlot ? 0.3 : 0.5
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<Group key={`boxplotplot-${item}`}>
|
|
107
|
+
{boxplot.plotNonOutlierValues &&
|
|
108
|
+
valuesByKey.map((value, index) => {
|
|
109
|
+
return (
|
|
110
|
+
<circle
|
|
111
|
+
display={displayPlot ? 'block' : 'none'}
|
|
112
|
+
cx={seriesScale(item.dataKey) + seriesScale.bandwidth() / 2}
|
|
113
|
+
cy={yScale(value)}
|
|
114
|
+
r={radius}
|
|
115
|
+
fill={'#ccc'}
|
|
116
|
+
style={{ opacity: fillOpacity, fillOpacity: 1, stroke: 'black' }}
|
|
117
|
+
key={`boxplot-${i}--circle-${index}`}
|
|
118
|
+
/>
|
|
119
|
+
)
|
|
120
|
+
})}
|
|
121
|
+
{displayPlot && (
|
|
122
|
+
<BoxPlot
|
|
123
|
+
display={displayPlot ? 'block' : 'none'}
|
|
124
|
+
data-left={xScale(d.columnCategory) + config.yAxis.size + offset / 2 + 0.5}
|
|
125
|
+
key={`box-plot-${i}-${item}`}
|
|
126
|
+
min={min}
|
|
127
|
+
max={max}
|
|
128
|
+
left={seriesScale(item.dataKey)}
|
|
129
|
+
firstQuartile={firstQuartile}
|
|
130
|
+
thirdQuartile={thirdQuartile}
|
|
131
|
+
median={median}
|
|
132
|
+
boxWidth={seriesScale.bandwidth()}
|
|
133
|
+
fill={colorScale(item.dataKey)}
|
|
134
|
+
fillOpacity={fillOpacity}
|
|
135
|
+
stroke={fillOpacity ? 'rgba(0,0,0,0.2)' : 'rgba(0,0,0,0.1)'}
|
|
136
|
+
valueScale={yScale}
|
|
137
|
+
outliers={boxplot.plotOutlierValues ? d.columnOutliers : []}
|
|
138
|
+
outlierProps={{
|
|
139
|
+
style: {
|
|
140
|
+
fill: `${color_0}`,
|
|
141
|
+
opacity: fillOpacity
|
|
142
|
+
}
|
|
143
|
+
}}
|
|
144
|
+
medianProps={{
|
|
145
|
+
style: {
|
|
146
|
+
stroke: 'black',
|
|
147
|
+
opacity: fillOpacity
|
|
148
|
+
}
|
|
149
|
+
}}
|
|
150
|
+
boxProps={{
|
|
151
|
+
style: {
|
|
152
|
+
stroke: 'black',
|
|
153
|
+
strokeWidth: boxplot.borders === 'true' ? 1 : 0,
|
|
154
|
+
opacity: fillOpacity
|
|
155
|
+
}
|
|
156
|
+
}}
|
|
157
|
+
maxProps={{
|
|
158
|
+
style: {
|
|
159
|
+
stroke: 'black',
|
|
160
|
+
opacity: fillOpacity
|
|
161
|
+
}
|
|
162
|
+
}}
|
|
163
|
+
container
|
|
164
|
+
containerProps={{
|
|
165
|
+
'data-tooltip-html': handleTooltip(
|
|
166
|
+
d,
|
|
167
|
+
item.dataKey,
|
|
168
|
+
firstQuartile,
|
|
169
|
+
thirdQuartile,
|
|
170
|
+
median,
|
|
171
|
+
iqr
|
|
172
|
+
),
|
|
173
|
+
'data-tooltip-id': tooltip_id,
|
|
174
|
+
tabIndex: -1
|
|
175
|
+
}}
|
|
176
|
+
/>
|
|
177
|
+
)}
|
|
178
|
+
</Group>
|
|
179
|
+
)
|
|
180
|
+
})}
|
|
181
|
+
</Group>
|
|
182
|
+
)
|
|
183
|
+
})}
|
|
184
|
+
</Group>
|
|
185
|
+
</ErrorBoundary>
|
|
186
|
+
)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export default CoveBoxPlot
|