@cdc/chart 4.24.10 → 4.24.12-2
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 +35019 -34301
- 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 +126 -14
- package/examples/feature/tests-date-exclusions/date-exclusions-config.json +372 -12
- package/examples/private/DEV-8850-2.json +493 -0
- package/examples/private/DEV-9822.json +574 -0
- package/examples/private/DEV-9840.json +553 -0
- package/examples/private/DEV-9850-3.json +461 -0
- package/examples/private/chart.json +1084 -0
- package/examples/private/ci_formatted.json +202 -0
- package/examples/private/ci_issue.json +3016 -0
- package/examples/private/completed.json +634 -0
- package/examples/private/dem-data-long.csv +20 -0
- package/examples/private/dem-data-long.json +36 -0
- package/examples/private/demographic_data.csv +157 -0
- package/examples/private/demographic_data.json +2654 -0
- package/examples/private/demographic_dynamic.json +443 -0
- package/examples/private/demographic_standard.json +560 -0
- package/examples/private/ehdi.json +29939 -0
- package/examples/private/test.json +493 -0
- package/index.html +10 -7
- package/package.json +2 -2
- package/src/CdcChart.tsx +132 -152
- package/src/_stories/Chart.Anchors.stories.tsx +31 -0
- package/src/_stories/Chart.CustomColors.stories.tsx +19 -0
- package/src/_stories/Chart.DynamicSeries.stories.tsx +34 -0
- package/src/_stories/Chart.Legend.Gradient.stories.tsx +42 -1
- package/src/_stories/Chart.stories.tsx +37 -6
- package/src/_stories/ChartAxisLabels.stories.tsx +4 -1
- 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/{examples/feature/area/area-chart-date-city-temperature.json → src/_stories/_mock/area_chart_stacked.json} +125 -27
- 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/src/_stories/_mock/line_chart_dynamic_ci.json +493 -0
- package/src/_stories/_mock/line_chart_non_dynamic_ci.json +522 -0
- package/{examples/feature/scatterplot/scatterplot.json → src/_stories/_mock/scatterplot_mock.json} +62 -92
- package/src/_stories/_mock/short_dates.json +288 -0
- package/src/_stories/_mock/suppression_mock.json +1549 -0
- package/src/components/AreaChart/components/AreaChart.Stacked.jsx +15 -3
- package/src/components/Axis/Categorical.Axis.tsx +2 -2
- package/src/components/BarChart/components/BarChart.Horizontal.tsx +46 -37
- package/src/components/BarChart/components/BarChart.StackedVertical.tsx +43 -9
- package/src/components/BarChart/components/BarChart.Vertical.tsx +53 -47
- package/src/components/BarChart/helpers/getBarData.ts +28 -0
- package/src/components/BarChart/helpers/index.ts +1 -2
- package/src/components/BarChart/helpers/tests/getBarData.test.ts +74 -0
- package/src/components/BoxPlot/BoxPlot.tsx +131 -0
- package/src/components/BoxPlot/helpers/index.ts +54 -0
- package/src/components/BrushChart.tsx +23 -26
- package/src/components/EditorPanel/EditorPanel.tsx +117 -139
- 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 +122 -56
- package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +1 -2
- package/src/components/EditorPanel/useEditorPermissions.ts +20 -2
- package/src/components/Legend/Legend.Component.tsx +11 -12
- package/src/components/Legend/Legend.tsx +16 -16
- package/src/components/Legend/helpers/getLegendClasses.ts +59 -0
- package/src/components/Legend/helpers/index.ts +2 -1
- package/src/components/Legend/tests/getLegendClasses.test.ts +115 -0
- package/src/components/LineChart/components/LineChart.Circle.tsx +1 -1
- package/src/components/LineChart/helpers.ts +49 -43
- package/src/components/LineChart/index.tsx +135 -83
- package/src/components/LinearChart.tsx +196 -181
- package/src/components/PieChart/PieChart.tsx +7 -1
- package/src/components/Sankey/components/ColumnList.tsx +19 -0
- package/src/components/Sankey/components/Sankey.tsx +479 -0
- package/src/components/Sankey/helpers/getSankeyTooltip.tsx +33 -0
- package/src/components/Sankey/index.tsx +1 -492
- package/src/components/Sankey/sankey.scss +22 -21
- package/src/components/Sankey/types/index.ts +1 -1
- package/src/components/Sankey/useSankeyAlert.tsx +60 -0
- package/src/components/ScatterPlot/ScatterPlot.jsx +20 -4
- package/src/data/initial-state.js +7 -12
- package/src/helpers/countNumOfTicks.ts +57 -0
- package/src/helpers/getQuartiles.ts +15 -18
- package/src/hooks/useMinMax.ts +44 -16
- package/src/hooks/useReduceData.ts +43 -10
- package/src/hooks/useScales.ts +90 -35
- package/src/hooks/useTooltip.tsx +59 -50
- package/src/scss/DataTable.scss +5 -0
- package/src/scss/main.scss +6 -20
- 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
- package/src/hooks/useLegendClasses.ts +0 -72
|
@@ -33,7 +33,13 @@ const AreaChartStacked = ({ xScale, yScale, yMax, xMax, handleTooltipMouseOver,
|
|
|
33
33
|
data && (
|
|
34
34
|
<svg height={Number(yMax)}>
|
|
35
35
|
<ErrorBoundary component='AreaChartStacked'>
|
|
36
|
-
<Group
|
|
36
|
+
<Group
|
|
37
|
+
className='area-chart'
|
|
38
|
+
key='area-wrapper'
|
|
39
|
+
left={Number(config.yAxis.size) + strokeWidth / 2}
|
|
40
|
+
height={Number(yMax)}
|
|
41
|
+
style={{ overflow: 'hidden' }}
|
|
42
|
+
>
|
|
37
43
|
<AreaStack
|
|
38
44
|
data={data}
|
|
39
45
|
keys={config.runtime.areaSeriesKeys.map(s => s.dataKey) || config.series.map(s => s.dataKey)}
|
|
@@ -44,8 +50,14 @@ const AreaChartStacked = ({ xScale, yScale, yMax, xMax, handleTooltipMouseOver,
|
|
|
44
50
|
>
|
|
45
51
|
{({ stacks, path }) => {
|
|
46
52
|
return stacks.map((stack, stackIndex) => {
|
|
47
|
-
let transparentArea =
|
|
48
|
-
|
|
53
|
+
let transparentArea =
|
|
54
|
+
config.legend.behavior === 'highlight' &&
|
|
55
|
+
seriesHighlight.length > 0 &&
|
|
56
|
+
seriesHighlight.indexOf(stack.key) === -1
|
|
57
|
+
let displayArea =
|
|
58
|
+
config.legend.behavior === 'highlight' ||
|
|
59
|
+
seriesHighlight.length === 0 ||
|
|
60
|
+
seriesHighlight.indexOf(stack.key) !== -1
|
|
49
61
|
|
|
50
62
|
return (
|
|
51
63
|
// prettier-ignore
|
|
@@ -44,7 +44,7 @@ const CategoricalYAxis = ({ yMax, leftSize, max, xMax }) => {
|
|
|
44
44
|
if (categoryObj[lastheight] === '') {
|
|
45
45
|
// Calculate the sum of the numeric values of all other heights
|
|
46
46
|
const sumOfValues = heights.slice(0, -1).reduce((sum, label) => {
|
|
47
|
-
const value =
|
|
47
|
+
const value = Number(categoryObj[label])
|
|
48
48
|
return sum + (isNaN(value) ? 0 : value)
|
|
49
49
|
}, 0)
|
|
50
50
|
|
|
@@ -52,7 +52,7 @@ const CategoricalYAxis = ({ yMax, leftSize, max, xMax }) => {
|
|
|
52
52
|
const newValue = max - sumOfValues
|
|
53
53
|
|
|
54
54
|
// Update the last height with the new value
|
|
55
|
-
categoryObj[lastheight] = newValue
|
|
55
|
+
categoryObj[lastheight] = newValue
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
return [categoryObj]
|
|
@@ -22,6 +22,8 @@ import chroma from 'chroma-js'
|
|
|
22
22
|
// Local context and types
|
|
23
23
|
import BarChartContext, { BarChartContextValues } from './context'
|
|
24
24
|
import { ChartContext } from '../../../types/ChartContext'
|
|
25
|
+
import _ from 'lodash'
|
|
26
|
+
import { getBarData } from '../helpers/getBarData'
|
|
25
27
|
|
|
26
28
|
export const BarChartHorizontal = () => {
|
|
27
29
|
const { xScale, yScale, yMax, seriesScale } = useContext<BarChartContextValues>(BarChartContext)
|
|
@@ -35,9 +37,7 @@ export const BarChartHorizontal = () => {
|
|
|
35
37
|
formatDate,
|
|
36
38
|
parseDate,
|
|
37
39
|
setSharedFilter,
|
|
38
|
-
isNumber
|
|
39
|
-
getYAxisData,
|
|
40
|
-
getXAxisData
|
|
40
|
+
isNumber
|
|
41
41
|
} = useContext<ChartContext>(ConfigContext)
|
|
42
42
|
const {
|
|
43
43
|
isHorizontal,
|
|
@@ -60,13 +60,21 @@ export const BarChartHorizontal = () => {
|
|
|
60
60
|
|
|
61
61
|
const { HighLightedBarUtils } = useHighlightedBars(config)
|
|
62
62
|
|
|
63
|
+
const hasConfidenceInterval = Object.keys(config.confidenceKeys).length > 0
|
|
64
|
+
|
|
65
|
+
const _data = getBarData(config, data, hasConfidenceInterval)
|
|
66
|
+
|
|
67
|
+
const root = document.documentElement
|
|
68
|
+
|
|
69
|
+
const coolGray90 = getComputedStyle(root).getPropertyValue('--cool-gray-90')
|
|
70
|
+
|
|
63
71
|
return (
|
|
64
72
|
config.visualizationSubType !== 'stacked' &&
|
|
65
73
|
config.visualizationType === 'Bar' &&
|
|
66
74
|
config.orientation === 'horizontal' && (
|
|
67
75
|
<Group>
|
|
68
76
|
<BarGroup
|
|
69
|
-
data={config.preliminaryData?.some(pd => pd.value && pd.type === 'suppression') ? tableData :
|
|
77
|
+
data={config.preliminaryData?.some(pd => pd.value && pd.type === 'suppression') ? tableData : _data}
|
|
70
78
|
keys={config.runtime.barSeriesKeys || config.runtime.seriesKeys}
|
|
71
79
|
height={yMax}
|
|
72
80
|
x0={d => d[config.runtime.originalXAxis.dataKey]}
|
|
@@ -86,6 +94,8 @@ export const BarChartHorizontal = () => {
|
|
|
86
94
|
top={barGroup.y}
|
|
87
95
|
>
|
|
88
96
|
{barGroup.bars.map((bar, index) => {
|
|
97
|
+
const datum = _data[barGroup.index]
|
|
98
|
+
const dataValue = datum[config.runtime.originalXAxis.dataKey]
|
|
89
99
|
const scaleVal = config.yAxis.type === 'logarithmic' ? 0.1 : 0
|
|
90
100
|
let highlightedBarValues = config.highlightedBarValues
|
|
91
101
|
.map(item => item.value)
|
|
@@ -119,9 +129,7 @@ export const BarChartHorizontal = () => {
|
|
|
119
129
|
const barX = bar.value < 0 ? Math.abs(xScale(bar.value)) : xScale(scaleVal)
|
|
120
130
|
const yAxisValue = formatNumber(bar.value, 'left')
|
|
121
131
|
const xAxisValue =
|
|
122
|
-
config.runtime[section].type === 'date'
|
|
123
|
-
? formatDate(parseDate(data[barGroup.index][config.runtime.originalXAxis.dataKey]))
|
|
124
|
-
: data[barGroup.index][config.runtime.originalXAxis.dataKey]
|
|
132
|
+
config.runtime[section].type === 'date' ? formatDate(parseDate(dataValue)) : dataValue
|
|
125
133
|
|
|
126
134
|
const barPosition = !isPositiveBar ? 'below' : 'above'
|
|
127
135
|
const absentDataLabel = getAbsentDataLabel(yAxisValue)
|
|
@@ -167,7 +175,10 @@ export const BarChartHorizontal = () => {
|
|
|
167
175
|
config.runtime.seriesLabels && config.runtime.seriesLabels[bar.key]
|
|
168
176
|
? colorScale(config.runtime.seriesLabels[bar.key])
|
|
169
177
|
: colorScale(bar.key)
|
|
170
|
-
|
|
178
|
+
const hasDynamicCategory = config.series.find(s => s.dynamicCategory)
|
|
179
|
+
if (!hasDynamicCategory) {
|
|
180
|
+
barColor = assignColorsToValues(barGroups.length, barGroup.index, barColor) // Color code by category
|
|
181
|
+
}
|
|
171
182
|
const isRegularLollipopColor = config.isLollipopChart && config.lollipopColorStyle === 'regular'
|
|
172
183
|
const isTwoToneLollipopColor = config.isLollipopChart && config.lollipopColorStyle === 'two-tone'
|
|
173
184
|
const isHighlightedBar = highlightedBarValues?.includes(xAxisValue)
|
|
@@ -199,6 +210,16 @@ export const BarChartHorizontal = () => {
|
|
|
199
210
|
return barColor
|
|
200
211
|
}
|
|
201
212
|
|
|
213
|
+
// Confidence Interval Variables
|
|
214
|
+
const tickWidth = 5
|
|
215
|
+
const yPos = barHeight * bar.index + barHeight / 2
|
|
216
|
+
const [upperPos, lowerPos] = ['upper', 'lower'].map(position => {
|
|
217
|
+
if (!hasConfidenceInterval) return
|
|
218
|
+
const d = datum.dynamicData ? datum.CI[bar.key][position] : datum[config.confidenceKeys[position]]
|
|
219
|
+
return xScale(d)
|
|
220
|
+
})
|
|
221
|
+
// End Confidence Interval Variables
|
|
222
|
+
|
|
202
223
|
return (
|
|
203
224
|
<Group key={`${barGroup.index}--${index}`}>
|
|
204
225
|
<Group key={`bar-sub-group-${barGroup.index}-${barGroup.x0}-${barY}--${index}`}>
|
|
@@ -321,10 +342,10 @@ export const BarChartHorizontal = () => {
|
|
|
321
342
|
textAnchor={'start'}
|
|
322
343
|
>
|
|
323
344
|
{config.runtime.yAxis.type === 'date'
|
|
324
|
-
? formatDate(parseDate(
|
|
345
|
+
? formatDate(parseDate(dataValue))
|
|
325
346
|
: isHorizontal
|
|
326
|
-
?
|
|
327
|
-
: formatNumber(
|
|
347
|
+
? dataValue
|
|
348
|
+
: formatNumber(dataValue)}
|
|
328
349
|
</Text>
|
|
329
350
|
)}
|
|
330
351
|
|
|
@@ -357,6 +378,20 @@ export const BarChartHorizontal = () => {
|
|
|
357
378
|
<animate attributeName='height' values={`0, ${lollipopShapeSize}`} dur='2.5s' />
|
|
358
379
|
</rect>
|
|
359
380
|
)}
|
|
381
|
+
{hasConfidenceInterval && (
|
|
382
|
+
<path
|
|
383
|
+
key={`confidence-interval-h-${yPos}-${datum[config.runtime.originalXAxis.dataKey]}`}
|
|
384
|
+
stroke={coolGray90}
|
|
385
|
+
strokeWidth='px'
|
|
386
|
+
d={`
|
|
387
|
+
M${lowerPos} ${yPos - tickWidth}
|
|
388
|
+
L${lowerPos} ${yPos + tickWidth}
|
|
389
|
+
M${lowerPos} ${yPos}
|
|
390
|
+
L${upperPos} ${yPos}
|
|
391
|
+
M${upperPos} ${yPos - tickWidth}
|
|
392
|
+
L${upperPos} ${yPos + tickWidth} `}
|
|
393
|
+
/>
|
|
394
|
+
)}
|
|
360
395
|
</Group>
|
|
361
396
|
</Group>
|
|
362
397
|
)
|
|
@@ -365,32 +400,6 @@ export const BarChartHorizontal = () => {
|
|
|
365
400
|
))
|
|
366
401
|
}}
|
|
367
402
|
</BarGroup>
|
|
368
|
-
|
|
369
|
-
{Object.keys(config.confidenceKeys).length > 0
|
|
370
|
-
? data.map(d => {
|
|
371
|
-
let xPos, yPos
|
|
372
|
-
let upperPos
|
|
373
|
-
let lowerPos
|
|
374
|
-
let tickWidth = 5
|
|
375
|
-
yPos = yScale(getXAxisData(d)) - 0.75 * config.barHeight
|
|
376
|
-
upperPos = xScale(getYAxisData(d, config.confidenceKeys.upper))
|
|
377
|
-
lowerPos = xScale(getYAxisData(d, config.confidenceKeys.lower))
|
|
378
|
-
return (
|
|
379
|
-
<path
|
|
380
|
-
key={`confidence-interval-h-${yPos}-${d[config.runtime.originalXAxis.dataKey]}`}
|
|
381
|
-
stroke='#333'
|
|
382
|
-
strokeWidth='px'
|
|
383
|
-
d={`
|
|
384
|
-
M${lowerPos} ${yPos - tickWidth}
|
|
385
|
-
L${lowerPos} ${yPos + tickWidth}
|
|
386
|
-
M${lowerPos} ${yPos}
|
|
387
|
-
L${upperPos} ${yPos}
|
|
388
|
-
M${upperPos} ${yPos - tickWidth}
|
|
389
|
-
L${upperPos} ${yPos + tickWidth} `}
|
|
390
|
-
/>
|
|
391
|
-
)
|
|
392
|
-
})
|
|
393
|
-
: ''}
|
|
394
403
|
</Group>
|
|
395
404
|
)
|
|
396
405
|
)
|
|
@@ -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,14 @@ 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 _ from 'lodash'
|
|
25
|
+
import { getBarData } from '../helpers/getBarData'
|
|
23
26
|
|
|
24
27
|
export const BarChartVertical = () => {
|
|
25
28
|
const { xScale, yScale, xMax, yMax, seriesScale } = useContext<BarChartContextValues>(BarChartContext)
|
|
@@ -42,8 +45,14 @@ export const BarChartVertical = () => {
|
|
|
42
45
|
} = useBarChart()
|
|
43
46
|
|
|
44
47
|
// prettier-ignore
|
|
45
|
-
const { colorScale, config, dashboardConfig, tableData, formatDate, formatNumber,
|
|
48
|
+
const { colorScale, config, dashboardConfig, tableData, formatDate, formatNumber, parseDate, seriesHighlight, setSharedFilter, transformedData, brushConfig } = useContext<ChartContext>(ConfigContext)
|
|
49
|
+
|
|
46
50
|
const { HighLightedBarUtils } = useHighlightedBars(config)
|
|
51
|
+
|
|
52
|
+
const root = document.documentElement
|
|
53
|
+
|
|
54
|
+
const coolGray90 = getComputedStyle(root).getPropertyValue('--cool-gray-90')
|
|
55
|
+
|
|
47
56
|
let data = transformedData
|
|
48
57
|
// check if user add suppression
|
|
49
58
|
const isSuppressionActive = config.preliminaryData.some(pd => pd.value && pd.type === 'suppression')
|
|
@@ -56,6 +65,9 @@ export const BarChartVertical = () => {
|
|
|
56
65
|
data = brushConfig.data
|
|
57
66
|
}
|
|
58
67
|
|
|
68
|
+
const hasConfidenceInterval = Object.keys(config.confidenceKeys).length > 0
|
|
69
|
+
|
|
70
|
+
const _data = getBarData(config, data, hasConfidenceInterval)
|
|
59
71
|
return (
|
|
60
72
|
config.visualizationSubType !== 'stacked' &&
|
|
61
73
|
(config.visualizationType === 'Bar' ||
|
|
@@ -64,7 +76,7 @@ export const BarChartVertical = () => {
|
|
|
64
76
|
config.orientation === 'vertical' && (
|
|
65
77
|
<Group>
|
|
66
78
|
<BarGroup
|
|
67
|
-
data={
|
|
79
|
+
data={_data}
|
|
68
80
|
keys={config.runtime.barSeriesKeys || config.runtime.seriesKeys}
|
|
69
81
|
height={yMax}
|
|
70
82
|
x0={d => {
|
|
@@ -79,14 +91,16 @@ export const BarChartVertical = () => {
|
|
|
79
91
|
}}
|
|
80
92
|
>
|
|
81
93
|
{barGroups => {
|
|
82
|
-
return barGroups.map((barGroup,
|
|
94
|
+
return barGroups.map((barGroup, _index) => (
|
|
83
95
|
<Group
|
|
84
|
-
className={`bar-group-${barGroup.index}-${barGroup.x0}--${
|
|
85
|
-
key={`bar-group-${barGroup.index}-${barGroup.x0}--${
|
|
86
|
-
id={`bar-group-${barGroup.index}-${barGroup.x0}--${
|
|
96
|
+
className={`bar-group-${barGroup.index}-${barGroup.x0}--${_index} ${config.orientation}`}
|
|
97
|
+
key={`bar-group-${barGroup.index}-${barGroup.x0}--${_index}`}
|
|
98
|
+
id={`bar-group-${barGroup.index}-${barGroup.x0}--${_index}`}
|
|
87
99
|
left={barGroup.x0}
|
|
88
100
|
>
|
|
89
101
|
{barGroup.bars.map((bar, index) => {
|
|
102
|
+
const datum = _data[barGroup.index]
|
|
103
|
+
const dataValue = datum[config.runtime.originalXAxis.dataKey]
|
|
90
104
|
const scaleVal = config.yAxis.type === 'logarithmic' ? 0.1 : 0
|
|
91
105
|
let highlightedBarValues = config.highlightedBarValues
|
|
92
106
|
.map(item => item.value)
|
|
@@ -116,17 +130,12 @@ export const BarChartVertical = () => {
|
|
|
116
130
|
setTotalBarsInGroup(barGroup.bars.length)
|
|
117
131
|
const yAxisValue = formatNumber(/[a-zA-Z]/.test(String(bar.value)) ? '' : bar.value, 'left')
|
|
118
132
|
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]
|
|
133
|
+
config.runtime[section].type === 'date' ? formatDate(parseDate(dataValue)) : dataValue
|
|
122
134
|
|
|
123
135
|
// create new Index for bars with negative values
|
|
124
136
|
const newIndex = bar.value < 0 ? -1 : index
|
|
125
137
|
// tooltips
|
|
126
|
-
const additionalColTooltip = getAdditionalColumn(
|
|
127
|
-
bar.key,
|
|
128
|
-
data[barGroup.index][config.runtime.originalXAxis.dataKey]
|
|
129
|
-
)
|
|
138
|
+
const additionalColTooltip = getAdditionalColumn(bar.key, dataValue)
|
|
130
139
|
let xAxisTooltip = config.runtime.xAxis.label
|
|
131
140
|
? `${config.runtime.xAxis.label}: ${xAxisValue}`
|
|
132
141
|
: xAxisValue
|
|
@@ -141,10 +150,6 @@ export const BarChartVertical = () => {
|
|
|
141
150
|
// configure colors
|
|
142
151
|
let labelColor = '#000000'
|
|
143
152
|
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
153
|
const isRegularLollipopColor = config.isLollipopChart && config.lollipopColorStyle === 'regular'
|
|
149
154
|
const isTwoToneLollipopColor = config.isLollipopChart && config.lollipopColorStyle === 'two-tone'
|
|
150
155
|
const isHighlightedBar = highlightedBarValues?.includes(xAxisValue)
|
|
@@ -167,10 +172,8 @@ export const BarChartVertical = () => {
|
|
|
167
172
|
bar,
|
|
168
173
|
defaultBarHeight,
|
|
169
174
|
config,
|
|
170
|
-
isNumber,
|
|
171
175
|
barWidth,
|
|
172
|
-
isVertical: true
|
|
173
|
-
yAxisValue
|
|
176
|
+
isVertical: true
|
|
174
177
|
})
|
|
175
178
|
|
|
176
179
|
const absentDataLabel = getAbsentDataLabel(yAxisValue)
|
|
@@ -225,6 +228,22 @@ export const BarChartVertical = () => {
|
|
|
225
228
|
return _barColor
|
|
226
229
|
}
|
|
227
230
|
|
|
231
|
+
// Confidence Interval Variables
|
|
232
|
+
const tickWidth = 5
|
|
233
|
+
const xPos = barX + (config.xAxis.type !== 'date-time' ? barWidth / 2 : 0)
|
|
234
|
+
|
|
235
|
+
const upperPos = yScale(
|
|
236
|
+
datum.dynamicData && datum?.CI?.[bar.key]
|
|
237
|
+
? datum.CI[bar.key].upper
|
|
238
|
+
: datum[config.confidenceKeys.upper]
|
|
239
|
+
)
|
|
240
|
+
const lowerPos = yScale(
|
|
241
|
+
datum.dynamicData && datum?.CI?.[bar.key]
|
|
242
|
+
? datum.CI[bar.key].lower
|
|
243
|
+
: datum[config.confidenceKeys.lower]
|
|
244
|
+
)
|
|
245
|
+
// End Confidence Interval Variables
|
|
246
|
+
|
|
228
247
|
return (
|
|
229
248
|
<Group key={`${barGroup.index}--${index}`}>
|
|
230
249
|
<Group key={`bar-sub-group-${barGroup.index}-${barGroup.x0}-${barY}--${index}`}>
|
|
@@ -341,7 +360,7 @@ export const BarChartVertical = () => {
|
|
|
341
360
|
<rect
|
|
342
361
|
display={displaylollipopShape}
|
|
343
362
|
x={barX - lollipopBarWidth / 2}
|
|
344
|
-
y={
|
|
363
|
+
y={bar.y}
|
|
345
364
|
width={lollipopShapeSize}
|
|
346
365
|
height={lollipopShapeSize}
|
|
347
366
|
fill={getBarBackgroundColor(colorScale(config.runtime.seriesLabels[bar.key]))}
|
|
@@ -353,6 +372,19 @@ export const BarChartVertical = () => {
|
|
|
353
372
|
<animate attributeName='height' values={`0, ${lollipopShapeSize}`} dur='2.5s' />
|
|
354
373
|
</rect>
|
|
355
374
|
)}
|
|
375
|
+
{hasConfidenceInterval && bar.value !== undefined && datum && (
|
|
376
|
+
<path
|
|
377
|
+
key={`confidence-interval-v-${datum[config.runtime.originalXAxis.dataKey]}`}
|
|
378
|
+
stroke={coolGray90}
|
|
379
|
+
strokeWidth='px'
|
|
380
|
+
d={`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
|
+
)}
|
|
356
388
|
</Group>
|
|
357
389
|
</Group>
|
|
358
390
|
)
|
|
@@ -362,32 +394,6 @@ export const BarChartVertical = () => {
|
|
|
362
394
|
}}
|
|
363
395
|
</BarGroup>
|
|
364
396
|
|
|
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
397
|
<Regions xScale={xScale} yMax={yMax} barWidth={barWidth} totalBarsInGroup={totalBarsInGroup} />
|
|
392
398
|
</Group>
|
|
393
399
|
)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import _ from 'lodash'
|
|
2
|
+
import { TransformedData } from '../../../types/ChartContext'
|
|
3
|
+
import { ChartConfig } from '../../../types/ChartConfig'
|
|
4
|
+
|
|
5
|
+
export const getBarData = (config: ChartConfig, data: TransformedData[], hasConfidenceInterval: boolean) => {
|
|
6
|
+
const dynamicSeries = config.series.find(s => s.dynamicCategory)
|
|
7
|
+
if (!dynamicSeries) return data
|
|
8
|
+
const { dynamicCategory, dataKey } = dynamicSeries
|
|
9
|
+
const xAxisKey = config.runtime.originalXAxis.dataKey
|
|
10
|
+
const xAxisGroupDataLookup = _.groupBy(data, xAxisKey)
|
|
11
|
+
return Object.values(xAxisGroupDataLookup).map(group => {
|
|
12
|
+
return group.reduce((acc, datum) => {
|
|
13
|
+
const dataValue = datum[dataKey]
|
|
14
|
+
const dataCategory = datum[dynamicCategory]
|
|
15
|
+
if (hasConfidenceInterval) {
|
|
16
|
+
const { lower, upper } = config.confidenceKeys
|
|
17
|
+
if (!acc.CI) acc.CI = {}
|
|
18
|
+
const lowerValue = datum[lower]
|
|
19
|
+
const upperValue = datum[upper]
|
|
20
|
+
acc.CI[dataCategory] = { lower: lowerValue, upper: upperValue }
|
|
21
|
+
}
|
|
22
|
+
acc[dataCategory] = dataValue
|
|
23
|
+
acc[xAxisKey] = datum[xAxisKey]
|
|
24
|
+
acc.dynamicData = true
|
|
25
|
+
return acc
|
|
26
|
+
}, {})
|
|
27
|
+
})
|
|
28
|
+
}
|
|
@@ -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,74 @@
|
|
|
1
|
+
import { getBarData } from '../getBarData'
|
|
2
|
+
|
|
3
|
+
import { TransformedData } from '../../../../types/ChartContext'
|
|
4
|
+
import { ChartConfig } from '../../../../types/ChartConfig'
|
|
5
|
+
|
|
6
|
+
describe('getBarData', () => {
|
|
7
|
+
it('should return the original data when there is no dynamicSeries', () => {
|
|
8
|
+
const config = {
|
|
9
|
+
series: [{ dataKey: 'value' }],
|
|
10
|
+
runtime: { originalXAxis: { dataKey: 'category' } }
|
|
11
|
+
} as ChartConfig
|
|
12
|
+
const data: TransformedData[] = [
|
|
13
|
+
{ category: 'A', value: 10 },
|
|
14
|
+
{ category: 'B', value: 20 }
|
|
15
|
+
]
|
|
16
|
+
const hasConfidenceInterval = false
|
|
17
|
+
|
|
18
|
+
const result = getBarData(config, data, hasConfidenceInterval)
|
|
19
|
+
|
|
20
|
+
expect(result).toEqual(data)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('should return transformed data when there is a dynamicSeries but no confidence interval', () => {
|
|
24
|
+
const config = {
|
|
25
|
+
series: [{ dataKey: 'value', dynamicCategory: 'subCategory' }],
|
|
26
|
+
runtime: { originalXAxis: { dataKey: 'category' } }
|
|
27
|
+
} as ChartConfig
|
|
28
|
+
const data: TransformedData[] = [
|
|
29
|
+
{ category: 'A', subCategory: 'X', value: 10 },
|
|
30
|
+
{ category: 'A', subCategory: 'Y', value: 20 },
|
|
31
|
+
{ category: 'B', subCategory: 'X', value: 30 }
|
|
32
|
+
]
|
|
33
|
+
const hasConfidenceInterval = false
|
|
34
|
+
|
|
35
|
+
const result = getBarData(config, data, hasConfidenceInterval)
|
|
36
|
+
|
|
37
|
+
expect(result).toEqual([
|
|
38
|
+
{ category: 'A', X: 10, Y: 20, dynamicData: true },
|
|
39
|
+
{ category: 'B', X: 30, dynamicData: true }
|
|
40
|
+
])
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('should return transformed data with confidence intervals when there is a dynamicSeries and confidence interval', () => {
|
|
44
|
+
const config = {
|
|
45
|
+
series: [{ dataKey: 'value', dynamicCategory: 'subCategory' }],
|
|
46
|
+
runtime: { originalXAxis: { dataKey: 'category' } },
|
|
47
|
+
confidenceKeys: { lower: 'lowerCI', upper: 'upperCI' }
|
|
48
|
+
} as ChartConfig
|
|
49
|
+
const data: TransformedData[] = [
|
|
50
|
+
{ category: 'A', subCategory: 'X', value: 10, lowerCI: 5, upperCI: 15 },
|
|
51
|
+
{ category: 'A', subCategory: 'Y', value: 20, lowerCI: 15, upperCI: 25 },
|
|
52
|
+
{ category: 'B', subCategory: 'X', value: 30, lowerCI: 25, upperCI: 35 }
|
|
53
|
+
]
|
|
54
|
+
const hasConfidenceInterval = true
|
|
55
|
+
|
|
56
|
+
const result = getBarData(config, data, hasConfidenceInterval)
|
|
57
|
+
|
|
58
|
+
expect(result).toEqual([
|
|
59
|
+
{
|
|
60
|
+
category: 'A',
|
|
61
|
+
X: 10,
|
|
62
|
+
Y: 20,
|
|
63
|
+
CI: { X: { lower: 5, upper: 15 }, Y: { lower: 15, upper: 25 } },
|
|
64
|
+
dynamicData: true
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
category: 'B',
|
|
68
|
+
X: 30,
|
|
69
|
+
CI: { X: { lower: 25, upper: 35 } },
|
|
70
|
+
dynamicData: true
|
|
71
|
+
}
|
|
72
|
+
])
|
|
73
|
+
})
|
|
74
|
+
})
|