@cdc/chart 4.24.5 → 4.24.9
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 +44197 -38258
- package/examples/cases-year.json +13379 -0
- package/examples/feature/annotations/index.json +542 -0
- package/examples/gallery/bar-chart-vertical/combo-line-chart.json +76 -15
- package/examples/gallery/bar-chart-vertical/vertical-bar-chart-stacked.json +5 -5
- package/examples/xaxis.json +493 -0
- package/index.html +20 -10
- package/package.json +5 -4
- package/src/CdcChart.tsx +462 -172
- package/src/_stories/Chart.Legend.Gradient.tsx +19 -0
- package/src/_stories/Chart.stories.tsx +18 -171
- package/src/_stories/ChartAnnotation.stories.tsx +32 -0
- package/src/_stories/_mock/annotation_category_mock.json +473 -0
- package/src/_stories/_mock/annotation_date-linear_mock.json +530 -0
- package/{examples/feature/line/line-chart.json → src/_stories/_mock/annotation_date-time_mock.json} +150 -69
- package/src/_stories/_mock/legend.gradient_mock.json +236 -0
- package/src/_stories/_mock/line_chart_two_points_new_chart.json +128 -0
- package/src/_stories/_mock/line_chart_two_points_regression_test.json +127 -0
- package/src/_stories/_mock/lollipop.json +171 -0
- package/src/components/Annotations/components/AnnotationDraggable.styles.css +31 -0
- package/src/components/Annotations/components/AnnotationDraggable.tsx +207 -0
- package/src/components/Annotations/components/AnnotationDropdown.styles.css +14 -0
- package/src/components/Annotations/components/AnnotationDropdown.tsx +72 -0
- package/src/components/Annotations/components/AnnotationList.styles.css +45 -0
- package/src/components/Annotations/components/AnnotationList.tsx +42 -0
- package/src/components/Annotations/components/findNearestDatum.ts +138 -0
- package/src/components/Annotations/components/helpers/index.tsx +46 -0
- package/src/components/Annotations/index.tsx +13 -0
- package/src/components/AreaChart/components/AreaChart.Stacked.jsx +1 -1
- package/src/components/AreaChart/components/AreaChart.jsx +1 -1
- package/src/components/Axis/Categorical.Axis.tsx +145 -0
- package/src/components/BarChart/components/BarChart.Horizontal.tsx +47 -44
- package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +0 -1
- package/src/components/BarChart/components/BarChart.StackedVertical.tsx +11 -14
- package/src/components/BarChart/components/BarChart.Vertical.tsx +67 -30
- package/src/components/BarChart/helpers/index.ts +91 -0
- package/src/components/BrushChart.tsx +205 -0
- package/src/components/EditorPanel/EditorPanel.tsx +1794 -403
- package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +320 -0
- package/src/components/EditorPanel/components/Panels/Panel.General.tsx +282 -18
- package/src/components/EditorPanel/components/Panels/Panel.Sankey.tsx +43 -8
- package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +4 -4
- package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +4 -13
- package/src/components/EditorPanel/components/Panels/index.tsx +3 -1
- package/src/components/EditorPanel/components/panels.scss +4 -0
- package/src/components/EditorPanel/editor-panel.scss +35 -3
- package/src/components/EditorPanel/{useEditorPermissions.js → useEditorPermissions.ts} +105 -17
- package/src/components/Legend/Legend.Component.tsx +185 -194
- package/src/components/Legend/Legend.Suppression.tsx +146 -0
- package/src/components/Legend/Legend.tsx +21 -5
- package/src/components/Legend/helpers/createFormatLabels.tsx +1 -1
- package/src/components/Legend/helpers/index.ts +35 -0
- package/src/components/LegendWrapper.tsx +26 -0
- package/src/components/LineChart/LineChartProps.ts +1 -15
- package/src/components/LineChart/components/LineChart.BumpCircle.tsx +103 -0
- package/src/components/LineChart/components/LineChart.Circle.tsx +47 -8
- package/src/components/LineChart/helpers.ts +72 -14
- package/src/components/LineChart/index.tsx +117 -42
- package/src/components/LinearChart.jsx +179 -136
- package/src/components/LinearChart.tsx +1366 -0
- package/src/components/PairedBarChart.jsx +9 -9
- package/src/components/PieChart/PieChart.tsx +75 -18
- package/src/components/Sankey/index.tsx +89 -30
- package/src/components/ScatterPlot/ScatterPlot.jsx +22 -8
- package/src/components/Sparkline/components/SparkLine.tsx +2 -2
- package/src/components/ZoomBrush.tsx +90 -44
- package/src/data/initial-state.js +25 -7
- package/src/helpers/handleChartTabbing.ts +8 -0
- package/src/helpers/isConvertLineToBarGraph.ts +4 -0
- package/src/hooks/{useBarChart.js → useBarChart.ts} +2 -40
- package/src/hooks/useColorScale.ts +1 -1
- package/src/hooks/useLegendClasses.ts +68 -0
- package/src/hooks/useMinMax.ts +12 -7
- package/src/hooks/useScales.ts +58 -26
- package/src/hooks/useTooltip.tsx +135 -25
- package/src/scss/DataTable.scss +2 -1
- package/src/scss/main.scss +128 -28
- package/src/types/ChartConfig.ts +83 -10
- package/src/types/ChartContext.ts +14 -4
- package/tests-examples/helpers/testZeroValue.test.ts +30 -0
- package/LICENSE +0 -201
- package/src/components/BrushHandle.jsx +0 -17
- package/src/components/LineChart/index.scss +0 -1
- package/src/helpers/filterData.ts +0 -18
- package/src/helpers/tests/computeMarginBottom.test.ts +0 -21
- package/src/hooks/useLegendClasses.js +0 -31
- /package/src/hooks/{useReduceData.js → useReduceData.ts} +0 -0
|
@@ -13,6 +13,7 @@ import { BarGroup } from '@visx/shape'
|
|
|
13
13
|
// CDC core components and helpers
|
|
14
14
|
import { getContrastColor } from '@cdc/core/helpers/cove/accessibility'
|
|
15
15
|
import createBarElement from '@cdc/core/components/createBarElement'
|
|
16
|
+
import { getBarConfig, testZeroValue } from '../helpers'
|
|
16
17
|
|
|
17
18
|
// Third party libraries
|
|
18
19
|
import chroma from 'chroma-js'
|
|
@@ -24,25 +25,8 @@ import { ChartContext } from '../../../types/ChartContext'
|
|
|
24
25
|
export const BarChartHorizontal = () => {
|
|
25
26
|
const { xScale, yScale, yMax, seriesScale } = useContext<BarChartContextValues>(BarChartContext)
|
|
26
27
|
const { transformedData: data, tableData, colorScale, seriesHighlight, config, formatNumber, formatDate, parseDate, setSharedFilter, isNumber, getTextWidth, getYAxisData, getXAxisData } = useContext<ChartContext>(ConfigContext)
|
|
27
|
-
const {
|
|
28
|
-
|
|
29
|
-
barBorderWidth,
|
|
30
|
-
updateBars,
|
|
31
|
-
assignColorsToValues,
|
|
32
|
-
section,
|
|
33
|
-
fontSize,
|
|
34
|
-
isLabelBelowBar,
|
|
35
|
-
displayNumbersOnBar,
|
|
36
|
-
lollipopBarWidth,
|
|
37
|
-
lollipopShapeSize,
|
|
38
|
-
getHighlightedBarColorByValue,
|
|
39
|
-
getHighlightedBarByValue,
|
|
40
|
-
getAdditionalColumn,
|
|
41
|
-
hoveredBar,
|
|
42
|
-
onMouseLeaveBar,
|
|
43
|
-
onMouseOverBar,
|
|
44
|
-
composeSuppressionBars
|
|
45
|
-
} = useBarChart()
|
|
28
|
+
const { isHorizontal, barBorderWidth, updateBars, assignColorsToValues, section, fontSize, isLabelBelowBar, displayNumbersOnBar, lollipopBarWidth, lollipopShapeSize, getHighlightedBarColorByValue, getHighlightedBarByValue, getAdditionalColumn, hoveredBar, onMouseLeaveBar, onMouseOverBar } =
|
|
29
|
+
useBarChart()
|
|
46
30
|
|
|
47
31
|
const { HighLightedBarUtils } = useHighlightedBars(config)
|
|
48
32
|
|
|
@@ -67,9 +51,7 @@ export const BarChartHorizontal = () => {
|
|
|
67
51
|
return updateBars(barGroups).map((barGroup, index) => (
|
|
68
52
|
<Group className={`bar-group-${barGroup.index}-${barGroup.x0}--${index} ${config.orientation}`} key={`bar-group-${barGroup.index}-${barGroup.x0}--${index}`} id={`bar-group-${barGroup.index}-${barGroup.x0}--${index}`} top={barGroup.y}>
|
|
69
53
|
{barGroup.bars.map((bar, index) => {
|
|
70
|
-
const scaleVal = config.
|
|
71
|
-
const { suppresedBarHeight: suppresedBarWidth, getIconSize, getIconPadding, getVerticalAnchor, isSuppressed } = composeSuppressionBars({ bar })
|
|
72
|
-
|
|
54
|
+
const scaleVal = config.yAxis.type === 'logarithmic' ? 0.1 : 0
|
|
73
55
|
let highlightedBarValues = config.highlightedBarValues.map(item => item.value).filter(item => item !== ('' || undefined))
|
|
74
56
|
highlightedBarValues = config.xAxis.type === 'date' ? HighLightedBarUtils.formatDates(highlightedBarValues) : highlightedBarValues
|
|
75
57
|
let transparentBar = config.legend.behavior === 'highlight' && seriesHighlight.length > 0 && seriesHighlight.indexOf(bar.key) === -1
|
|
@@ -80,27 +62,28 @@ export const BarChartHorizontal = () => {
|
|
|
80
62
|
numbericBarHeight = 25
|
|
81
63
|
}
|
|
82
64
|
let barY = bar.value >= 0 && isNumber(bar.value) ? bar.y : yScale(scaleVal)
|
|
83
|
-
const
|
|
84
|
-
// const suppresedBarWidth = config.xAxis.showSuppressedLine ? 4 : 0
|
|
65
|
+
const defaultBarWidth = Math.abs(xScale(bar.value) - xScale(scaleVal))
|
|
85
66
|
const isPositiveBar = bar.value >= 0 && isNumber(bar.value)
|
|
86
|
-
|
|
67
|
+
|
|
68
|
+
const { barWidthHorizontal: barWidth, isSuppressed, getAbsentDataLabel } = getBarConfig({ bar, defaultBarWidth, config, isNumber, getTextWidth, isVertical: false })
|
|
87
69
|
const barX = bar.value < 0 ? Math.abs(xScale(bar.value)) : xScale(scaleVal)
|
|
88
70
|
const yAxisValue = formatNumber(bar.value, 'left')
|
|
89
71
|
const xAxisValue = config.runtime[section].type === 'date' ? formatDate(parseDate(data[barGroup.index][config.runtime.originalXAxis.dataKey])) : data[barGroup.index][config.runtime.originalXAxis.dataKey]
|
|
90
72
|
|
|
91
73
|
const barPosition = !isPositiveBar ? 'below' : 'above'
|
|
92
|
-
const
|
|
74
|
+
const absentDataLabel = getAbsentDataLabel(yAxisValue)
|
|
75
|
+
const barDefaultLabel = !config.yAxis.displayNumbersOnBar ? '' : yAxisValue
|
|
93
76
|
|
|
94
77
|
// check if bar text/value string fits into each bars.
|
|
95
|
-
|
|
96
|
-
|
|
78
|
+
const textWidth = getTextWidth(barDefaultLabel, `normal ${fontSize[config.fontSize]}px sans-serif`)
|
|
79
|
+
const textFits = Number(textWidth) < defaultBarWidth - 5
|
|
97
80
|
|
|
98
81
|
// control text position
|
|
99
82
|
let textAnchor = textFits ? 'end' : 'start'
|
|
100
83
|
let textAnchorLollipop = 'start'
|
|
101
84
|
let textPadding = textFits ? -5 : 5
|
|
102
85
|
let textPaddingLollipop = 10
|
|
103
|
-
//
|
|
86
|
+
//if bars are negative we change positions of text
|
|
104
87
|
if (barPosition === 'below') {
|
|
105
88
|
textAnchor = textFits ? 'start' : 'end'
|
|
106
89
|
textPadding = textFits ? 5 : -5
|
|
@@ -134,7 +117,8 @@ export const BarChartHorizontal = () => {
|
|
|
134
117
|
const highlightedBar = getHighlightedBarByValue(xAxisValue)
|
|
135
118
|
const borderColor = isHighlightedBar ? highlightedBarColor : config.barHasBorder === 'true' ? '#000' : 'transparent'
|
|
136
119
|
const borderWidth = isHighlightedBar ? highlightedBar.borderWidth : config.isLollipopChart ? 0 : config.barHasBorder === 'true' ? barBorderWidth : 0
|
|
137
|
-
const displaylollipopShape =
|
|
120
|
+
const displaylollipopShape = testZeroValue(bar.value) ? 'none' : 'block'
|
|
121
|
+
|
|
138
122
|
// update label color
|
|
139
123
|
if (barColor && labelColor && textFits) {
|
|
140
124
|
labelColor = getContrastColor('#000', barColor)
|
|
@@ -147,8 +131,6 @@ export const BarChartHorizontal = () => {
|
|
|
147
131
|
return barColor
|
|
148
132
|
}
|
|
149
133
|
|
|
150
|
-
const iconPadding = symbol => (symbol === 'Asterisk' ? '3px' : symbol === 'Double Asterisks' ? '4px' : '12px')
|
|
151
|
-
|
|
152
134
|
return (
|
|
153
135
|
<Group key={`${barGroup.index}--${index}`}>
|
|
154
136
|
<Group key={`bar-sub-group-${barGroup.index}-${barGroup.x0}-${barY}--${index}`}>
|
|
@@ -185,44 +167,65 @@ export const BarChartHorizontal = () => {
|
|
|
185
167
|
// check if user selected column
|
|
186
168
|
const selectedSuppressionColumn = !pd.column || pd.column === bar.key
|
|
187
169
|
// compare entered suppressed value with data value
|
|
188
|
-
const isValueMatch = String(pd.value) === String(
|
|
170
|
+
const isValueMatch = String(pd.value) === String(bar.value) && pd.value !== ''
|
|
189
171
|
const isSuppressed = isValueMatch && selectedSuppressionColumn
|
|
190
|
-
|
|
172
|
+
|
|
173
|
+
if (!isSuppressed || pd.hideBarSymbol || !config.general.showSuppressedSymbol) {
|
|
191
174
|
return
|
|
192
175
|
}
|
|
176
|
+
|
|
177
|
+
const hasAsterisk = String(pd.symbol).includes('Asterisk')
|
|
178
|
+
const verticalAnchor = hasAsterisk ? 'middle' : 'end'
|
|
179
|
+
const iconSize = pd.symbol === 'Asterisk' ? barHeight * 1.2 : pd.symbol === 'Double Asterisk' ? barHeight : barHeight / 1.5
|
|
180
|
+
const fillColor = pd.displayGray ? '#8b8b8a' : '#000'
|
|
193
181
|
return (
|
|
194
182
|
<Text // prettier-ignore
|
|
195
183
|
key={index}
|
|
196
|
-
fontSize={
|
|
197
|
-
angle={90}
|
|
184
|
+
fontSize={iconSize}
|
|
198
185
|
display={displayBar ? 'block' : 'none'}
|
|
199
186
|
opacity={transparentBar ? 0.5 : 1}
|
|
200
187
|
x={barX}
|
|
201
188
|
y={config.barHeight / 2 + config.barHeight * bar.index}
|
|
202
|
-
fill={
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
textAnchor=
|
|
189
|
+
fill={fillColor}
|
|
190
|
+
dy={config.barHeight / 5}
|
|
191
|
+
dx={10}
|
|
192
|
+
textAnchor='start'
|
|
193
|
+
verticalAnchor={verticalAnchor}
|
|
206
194
|
>
|
|
207
195
|
{pd.iconCode}
|
|
208
196
|
</Text>
|
|
209
197
|
)
|
|
210
198
|
})}
|
|
211
199
|
|
|
212
|
-
{!config.isLollipopChart &&
|
|
200
|
+
{!config.isLollipopChart && (
|
|
213
201
|
<Text // prettier-ignore
|
|
214
202
|
display={displayBar ? 'block' : 'none'}
|
|
215
203
|
x={bar.y}
|
|
204
|
+
opacity={transparentBar ? 0.5 : 1}
|
|
216
205
|
y={config.barHeight / 2 + config.barHeight * bar.index}
|
|
217
206
|
fill={labelColor}
|
|
218
207
|
dx={textPadding}
|
|
219
208
|
verticalAnchor='middle'
|
|
220
209
|
textAnchor={textAnchor}
|
|
221
210
|
>
|
|
222
|
-
{
|
|
211
|
+
{testZeroValue(bar.value) ? '' : barDefaultLabel}
|
|
223
212
|
</Text>
|
|
224
213
|
)}
|
|
225
|
-
|
|
214
|
+
<Text // prettier-ignore
|
|
215
|
+
display={displayBar ? 'block' : 'none'}
|
|
216
|
+
x={bar.y}
|
|
217
|
+
opacity={transparentBar ? 0.5 : 1}
|
|
218
|
+
y={config.barHeight / 2 + config.barHeight * bar.index}
|
|
219
|
+
fill={labelColor}
|
|
220
|
+
dx={absentDataLabel === 'N/A' ? 20 : textPadding}
|
|
221
|
+
dy={config.isLollipopChart ? -10 : 0}
|
|
222
|
+
verticalAnchor='middle'
|
|
223
|
+
textAnchor={absentDataLabel === 'N/A' ? 'middle' : textAnchor}
|
|
224
|
+
>
|
|
225
|
+
{absentDataLabel}
|
|
226
|
+
</Text>
|
|
227
|
+
|
|
228
|
+
{config.isLollipopChart && (
|
|
226
229
|
<Text // prettier-ignore
|
|
227
230
|
display={displayBar ? 'block' : 'none'}
|
|
228
231
|
x={bar.y}
|
|
@@ -233,7 +236,7 @@ export const BarChartHorizontal = () => {
|
|
|
233
236
|
verticalAnchor='middle'
|
|
234
237
|
fontWeight={'normal'}
|
|
235
238
|
>
|
|
236
|
-
{
|
|
239
|
+
{testZeroValue(bar.value) ? '' : barDefaultLabel}
|
|
237
240
|
</Text>
|
|
238
241
|
)}
|
|
239
242
|
{isLabelBelowBar && !config.yAxis.hideLabel && (
|
|
@@ -50,7 +50,6 @@ const BarChartStackedHorizontal = () => {
|
|
|
50
50
|
const yAxisValue = config.runtime.yAxis.type === 'date' ? formatDate(parseDate(data[bar.index][config.runtime.originalXAxis.dataKey])) : data[bar.index][config.runtime.originalXAxis.dataKey]
|
|
51
51
|
const yAxisTooltip = config.runtime.yAxis.label ? `${config.runtime.yAxis.label}: ${yAxisValue}` : yAxisValue
|
|
52
52
|
const textWidth = getTextWidth(xAxisValue, `normal ${fontSize[config.fontSize]}px sans-serif`)
|
|
53
|
-
|
|
54
53
|
const additionalColTooltip = getAdditionalColumn(hoveredBar)
|
|
55
54
|
const tooltipBody = `${config.runtime.seriesLabels[bar.key]}: ${xAxisValue}`
|
|
56
55
|
const tooltip = `<ul>
|
|
@@ -12,11 +12,13 @@ import createBarElement from '@cdc/core/components/createBarElement'
|
|
|
12
12
|
|
|
13
13
|
const BarChartStackedVertical = () => {
|
|
14
14
|
const [barWidth, setBarWidth] = useState(0)
|
|
15
|
-
const { xScale, yScale, xMax, yMax } = useContext(BarChartContext)
|
|
15
|
+
const { xScale, yScale, seriesScale, xMax, yMax } = useContext(BarChartContext)
|
|
16
16
|
const { transformedData, colorScale, seriesHighlight, config, formatNumber, formatDate, parseDate, setSharedFilter } = useContext(ConfigContext)
|
|
17
17
|
const { isHorizontal, barBorderWidth, applyRadius, hoveredBar, getAdditionalColumn, onMouseLeaveBar, onMouseOverBar, barStackedSeriesKeys } = useBarChart()
|
|
18
18
|
const { orientation } = config
|
|
19
|
-
|
|
19
|
+
|
|
20
|
+
const data = config.brush?.active && config.brush.data?.length ? config.brush.data : transformedData
|
|
21
|
+
const isDateAxisType = config.runtime.xAxis.type === 'date-time' || config.runtime.xAxis.type === 'date'
|
|
20
22
|
|
|
21
23
|
return (
|
|
22
24
|
config.visualizationSubType === 'stacked' &&
|
|
@@ -28,16 +30,14 @@ const BarChartStackedVertical = () => {
|
|
|
28
30
|
barStack.bars.map(bar => {
|
|
29
31
|
let transparentBar = config.legend.behavior === 'highlight' && seriesHighlight.length > 0 && seriesHighlight.indexOf(bar.key) === -1
|
|
30
32
|
let displayBar = config.legend.behavior === 'highlight' || seriesHighlight.length === 0 || seriesHighlight.indexOf(bar.key) !== -1
|
|
31
|
-
let barThickness =
|
|
32
|
-
|
|
33
|
-
let offset = (barThickness * (1 - (config.barThickness || 0.8))) / 2
|
|
33
|
+
let barThickness = isDateAxisType ? seriesScale.range()[1] - seriesScale.range()[0] : xMax / barStack.bars.length
|
|
34
|
+
if (config.runtime.xAxis.type !== 'date') barThickness = config.barThickness * barThickness
|
|
34
35
|
// tooltips
|
|
35
36
|
const rawXValue = bar.bar.data[config.runtime.xAxis.dataKey]
|
|
36
|
-
const xAxisValue =
|
|
37
|
+
const xAxisValue = isDateAxisType ? formatDate(parseDate(rawXValue)) : rawXValue
|
|
37
38
|
const yAxisValue = formatNumber(bar.bar ? bar.bar.data[bar.key] : 0, 'left')
|
|
38
39
|
if (!yAxisValue) return
|
|
39
|
-
const barX = xScale(
|
|
40
|
-
const style = applyRadius(barStack.index)
|
|
40
|
+
const barX = xScale(isDateAxisType ? parseDate(rawXValue) : rawXValue) - (isDateAxisType ? barThickness / 2 : 0)
|
|
41
41
|
const xAxisTooltip = config.runtime.xAxis.label ? `${config.runtime.xAxis.label}: ${xAxisValue}` : xAxisValue
|
|
42
42
|
const additionalColTooltip = getAdditionalColumn(hoveredBar)
|
|
43
43
|
const tooltipBody = `${config.runtime.seriesLabels[bar.key]}: ${yAxisValue}`
|
|
@@ -47,14 +47,11 @@ const BarChartStackedVertical = () => {
|
|
|
47
47
|
<li class="tooltip-body ">${additionalColTooltip}</li>
|
|
48
48
|
</li></ul>`
|
|
49
49
|
|
|
50
|
-
setBarWidth(
|
|
50
|
+
setBarWidth(barThickness)
|
|
51
51
|
|
|
52
52
|
return (
|
|
53
53
|
<Group key={`${barStack.index}--${bar.index}--${orientation}`}>
|
|
54
54
|
<Group key={`bar-stack-${barStack.index}-${bar.index}`} id={`barStack${barStack.index}-${bar.index}`} className='stack vertical'>
|
|
55
|
-
<Text display={config.labels && displayBar ? 'block' : 'none'} opacity={transparentBar ? 0.5 : 1} x={barX + barWidth / 2} y={bar.y - 5} fill={'#000'} textAnchor='middle'>
|
|
56
|
-
{yAxisValue}
|
|
57
|
-
</Text>
|
|
58
55
|
{createBarElement({
|
|
59
56
|
config: config,
|
|
60
57
|
seriesHighlight,
|
|
@@ -63,7 +60,7 @@ const BarChartStackedVertical = () => {
|
|
|
63
60
|
borderColor: '#333',
|
|
64
61
|
borderStyle: 'solid',
|
|
65
62
|
borderWidth: `${config.barHasBorder === 'true' ? barBorderWidth : 0}px`,
|
|
66
|
-
width:
|
|
63
|
+
width: barThickness,
|
|
67
64
|
height: bar.height,
|
|
68
65
|
x: barX,
|
|
69
66
|
y: bar.y,
|
|
@@ -80,7 +77,7 @@ const BarChartStackedVertical = () => {
|
|
|
80
77
|
},
|
|
81
78
|
styleOverrides: {
|
|
82
79
|
animationDelay: `${barStack.index * 0.5}s`,
|
|
83
|
-
transformOrigin: `${
|
|
80
|
+
transformOrigin: `${barThickness / 2}px ${bar.y + bar.height}px`,
|
|
84
81
|
opacity: transparentBar ? 0.2 : 1,
|
|
85
82
|
display: displayBar ? 'block' : 'none'
|
|
86
83
|
}
|
|
@@ -5,6 +5,8 @@ import BarChartContext, { type BarChartContextValues } from './context'
|
|
|
5
5
|
// Local hooks
|
|
6
6
|
import { useBarChart } from '../../../hooks/useBarChart'
|
|
7
7
|
import { useHighlightedBars } from '../../../hooks/useHighlightedBars'
|
|
8
|
+
import { getBarConfig, testZeroValue } from '../helpers'
|
|
9
|
+
import { isConvertLineToBarGraph } from '../../../helpers/isConvertLineToBarGraph'
|
|
8
10
|
// VisX library imports
|
|
9
11
|
import { Group } from '@visx/group'
|
|
10
12
|
import { Text } from '@visx/text'
|
|
@@ -25,10 +27,22 @@ export const BarChartVertical = () => {
|
|
|
25
27
|
const [barWidth, setBarWidth] = useState(0)
|
|
26
28
|
const [totalBarsInGroup, setTotalBarsInGroup] = useState(0)
|
|
27
29
|
// prettier-ignore
|
|
28
|
-
const {
|
|
30
|
+
const {
|
|
31
|
+
// prettier-ignore
|
|
32
|
+
assignColorsToValues,
|
|
33
|
+
barBorderWidth,
|
|
34
|
+
getAdditionalColumn,
|
|
35
|
+
getHighlightedBarByValue,
|
|
36
|
+
getHighlightedBarColorByValue,
|
|
37
|
+
lollipopBarWidth,
|
|
38
|
+
lollipopShapeSize,
|
|
39
|
+
onMouseLeaveBar,
|
|
40
|
+
onMouseOverBar,
|
|
41
|
+
section
|
|
42
|
+
} = useBarChart()
|
|
29
43
|
|
|
30
44
|
// prettier-ignore
|
|
31
|
-
const { colorScale, config, dashboardConfig, tableData, formatDate, formatNumber, getXAxisData, getYAxisData, isNumber, parseDate, seriesHighlight, setSharedFilter, transformedData,
|
|
45
|
+
const { colorScale, config, dashboardConfig, tableData, formatDate, formatNumber, getXAxisData, getYAxisData, isNumber, parseDate, seriesHighlight, setSharedFilter, transformedData, brushConfig, getTextWidth } = useContext<ChartContext>(ConfigContext)
|
|
32
46
|
const { HighLightedBarUtils } = useHighlightedBars(config)
|
|
33
47
|
let data = transformedData
|
|
34
48
|
// check if user add suppression
|
|
@@ -44,7 +58,7 @@ export const BarChartVertical = () => {
|
|
|
44
58
|
|
|
45
59
|
return (
|
|
46
60
|
config.visualizationSubType !== 'stacked' &&
|
|
47
|
-
(config.visualizationType === 'Bar' || config.visualizationType === 'Combo') &&
|
|
61
|
+
(config.visualizationType === 'Bar' || config.visualizationType === 'Combo' || isConvertLineToBarGraph(config.visualizationType, data, config.allowLineToBarGraph)) &&
|
|
48
62
|
config.orientation === 'vertical' && (
|
|
49
63
|
<Group>
|
|
50
64
|
<BarGroup
|
|
@@ -64,31 +78,33 @@ export const BarChartVertical = () => {
|
|
|
64
78
|
>
|
|
65
79
|
{barGroups => {
|
|
66
80
|
return barGroups.map((barGroup, index) => (
|
|
67
|
-
<Group
|
|
81
|
+
<Group
|
|
82
|
+
className={`bar-group-${barGroup.index}-${barGroup.x0}--${index} ${config.orientation}`}
|
|
83
|
+
key={`bar-group-${barGroup.index}-${barGroup.x0}--${index}`}
|
|
84
|
+
id={`bar-group-${barGroup.index}-${barGroup.x0}--${index}`}
|
|
85
|
+
left={barGroup.x0}
|
|
86
|
+
>
|
|
68
87
|
{barGroup.bars.map((bar, index) => {
|
|
69
|
-
const
|
|
70
|
-
const scaleVal = config.useLogScale ? 0.1 : 0
|
|
88
|
+
const scaleVal = config.yAxis.type === 'logarithmic' ? 0.1 : 0
|
|
71
89
|
let highlightedBarValues = config.highlightedBarValues.map(item => item.value).filter(item => item !== ('' || undefined))
|
|
72
90
|
highlightedBarValues = config.xAxis.type === 'date' ? HighLightedBarUtils.formatDates(highlightedBarValues) : highlightedBarValues
|
|
73
91
|
const transparentBar = config.legend.behavior === 'highlight' && seriesHighlight.length > 0 && seriesHighlight.indexOf(bar.key) === -1
|
|
74
92
|
const displayBar = config.legend.behavior === 'highlight' || seriesHighlight.length === 0 || seriesHighlight.indexOf(bar.key) !== -1
|
|
75
|
-
const barHeightBase = Math.abs(yScale(bar.value) - yScale(scaleVal))
|
|
76
|
-
const barYBase = bar.value >= 0 && isNumber(bar.value) ? bar.y : yScale(0)
|
|
77
|
-
const barY = isSuppressed ? yScale(scaleVal) - suppresedBarHeight : barYBase
|
|
78
93
|
|
|
79
|
-
let barGroupWidth = seriesScale.range()[1]
|
|
80
|
-
|
|
81
|
-
|
|
94
|
+
let barGroupWidth = seriesScale.range()[1] - seriesScale.range()[0]
|
|
95
|
+
const defaultBarHeight = Math.abs(yScale(bar.value) - yScale(scaleVal))
|
|
96
|
+
const defaultBarY = bar.value >= 0 && isNumber(bar.value) ? bar.y : yScale(0)
|
|
97
|
+
let barWidth = config.isLollipopChart ? lollipopBarWidth : seriesScale.bandwidth()
|
|
82
98
|
let barX = bar.x + (config.isLollipopChart ? (barGroupWidth / barGroup.bars.length - lollipopBarWidth) / 2 : 0) - (config.xAxis.type === 'date-time' ? barGroupWidth / 2 : 0)
|
|
83
99
|
setBarWidth(barWidth)
|
|
84
100
|
setTotalBarsInGroup(barGroup.bars.length)
|
|
85
101
|
const yAxisValue = formatNumber(/[a-zA-Z]/.test(String(bar.value)) ? '' : bar.value, 'left')
|
|
86
|
-
const xAxisValue =
|
|
102
|
+
const xAxisValue =
|
|
103
|
+
config.runtime[section].type === 'date' ? formatDate(parseDate(data[barGroup.index][config.runtime.originalXAxis.dataKey])) : data[barGroup.index][config.runtime.originalXAxis.dataKey]
|
|
87
104
|
|
|
88
105
|
// create new Index for bars with negative values
|
|
89
106
|
const newIndex = bar.value < 0 ? -1 : index
|
|
90
107
|
// tooltips
|
|
91
|
-
|
|
92
108
|
const additionalColTooltip = getAdditionalColumn(bar.key, data[barGroup.index][config.runtime.originalXAxis.dataKey])
|
|
93
109
|
let xAxisTooltip = config.runtime.xAxis.label ? `${config.runtime.xAxis.label}: ${xAxisValue}` : xAxisValue
|
|
94
110
|
const tooltipBody = `${config.runtime.seriesLabels[bar.key]}: ${yAxisValue}`
|
|
@@ -103,7 +119,6 @@ export const BarChartVertical = () => {
|
|
|
103
119
|
let labelColor = '#000000'
|
|
104
120
|
labelColor = HighLightedBarUtils.checkFontColor(yAxisValue, highlightedBarValues, labelColor) // Set if background is transparent'
|
|
105
121
|
let barColor = config.runtime.seriesLabels && config.runtime.seriesLabels[bar.key] ? colorScale(config.runtime.seriesLabels[bar.key]) : colorScale(bar.key)
|
|
106
|
-
barColor = assignColorsToValues(barGroups.length, barGroup.index, barColor) // Color code by category
|
|
107
122
|
const isRegularLollipopColor = config.isLollipopChart && config.lollipopColorStyle === 'regular'
|
|
108
123
|
const isTwoToneLollipopColor = config.isLollipopChart && config.lollipopColorStyle === 'two-tone'
|
|
109
124
|
const isHighlightedBar = highlightedBarValues?.includes(xAxisValue)
|
|
@@ -111,10 +126,13 @@ export const BarChartVertical = () => {
|
|
|
111
126
|
const highlightedBar = getHighlightedBarByValue(xAxisValue)
|
|
112
127
|
const borderColor = isHighlightedBar ? highlightedBarColor : config.barHasBorder === 'true' ? '#000' : 'transparent'
|
|
113
128
|
const borderWidth = isHighlightedBar ? highlightedBar.borderWidth : config.isLollipopChart ? 0 : config.barHasBorder === 'true' ? barBorderWidth : 0
|
|
114
|
-
const barValueLabel = isSuppressed ? '' : yAxisValue
|
|
115
|
-
const barHeight = isSuppressed ? suppresedBarHeight : barHeightBase
|
|
116
129
|
|
|
117
|
-
const
|
|
130
|
+
const { barHeight, isSuppressed, getBarY, getAbsentDataLabel } = getBarConfig({ bar, defaultBarHeight, config, isNumber, getTextWidth, barWidth, isVertical: true, yAxisValue })
|
|
131
|
+
|
|
132
|
+
const absentDataLabel = getAbsentDataLabel(yAxisValue)
|
|
133
|
+
const barDefaultLabel = isSuppressed || !config.labels ? '' : yAxisValue
|
|
134
|
+
const barY = getBarY(defaultBarY, yScale(scaleVal))
|
|
135
|
+
const displaylollipopShape = testZeroValue(bar.value) ? 'none' : 'block'
|
|
118
136
|
const getBarBackgroundColor = (barColor: string, filteredOutColor?: string): string => {
|
|
119
137
|
let _barColor = barColor
|
|
120
138
|
let _filteredOutColor = filteredOutColor || '#f2f2f2'
|
|
@@ -143,13 +161,16 @@ export const BarChartVertical = () => {
|
|
|
143
161
|
: colorScale(config.runtime.seriesLabels[bar.key])
|
|
144
162
|
|
|
145
163
|
if (isRegularLollipopColor) _barColor = barColor
|
|
146
|
-
|
|
164
|
+
|
|
147
165
|
if (isHighlightedBar) _barColor = 'transparent'
|
|
166
|
+
if (config.legend.colorCode) _barColor = assignColorsToValues(barGroups.length, barGroup.index, barColor)
|
|
167
|
+
if (isTwoToneLollipopColor) _barColor = chroma(barColor).brighten(1)
|
|
148
168
|
return _barColor
|
|
149
169
|
}
|
|
150
170
|
|
|
151
171
|
// if this is a two tone lollipop slightly lighten the bar.
|
|
152
172
|
if (isTwoToneLollipopColor) _barColor = chroma(barColor).brighten(1)
|
|
173
|
+
if (config.legend.colorCode) _barColor = assignColorsToValues(barGroups.length, barGroup.index, barColor)
|
|
153
174
|
|
|
154
175
|
// if we're highlighting a bar make it invisible since it gets a border
|
|
155
176
|
if (isHighlightedBar) _barColor = 'transparent'
|
|
@@ -194,24 +215,29 @@ export const BarChartVertical = () => {
|
|
|
194
215
|
const selectedSuppressionColumn = !pd.column || pd.column === bar.key
|
|
195
216
|
// compare entered suppressed value with data value
|
|
196
217
|
const isValueMatch = String(pd.value) === String(bar.value) && pd.value !== ''
|
|
197
|
-
|
|
218
|
+
const isSuppressed = isValueMatch && selectedSuppressionColumn
|
|
198
219
|
|
|
199
|
-
if (!isSuppressed || barWidth < 10 || !config.
|
|
220
|
+
if (!isSuppressed || barWidth < 10 || !config.general.showSuppressedSymbol || pd.hideBarSymbol) {
|
|
200
221
|
return
|
|
201
222
|
}
|
|
223
|
+
const hasAsterisk = String(pd.symbol).includes('Asterisk')
|
|
224
|
+
const yPadding = hasAsterisk ? -5 : -8
|
|
225
|
+
const verticalAnchor = hasAsterisk ? 'middle' : 'end'
|
|
226
|
+
const iconSize = pd.symbol === 'Asterisk' ? barWidth * 1.2 : pd.symbol === 'Double Asterisk' ? barWidth : barWidth / 1.5
|
|
227
|
+
const fillColor = pd.displayGray ? '#8b8b8a' : '#000'
|
|
202
228
|
|
|
203
229
|
return (
|
|
204
230
|
<Text // prettier-ignore
|
|
205
231
|
key={index}
|
|
206
|
-
dy={
|
|
232
|
+
dy={yPadding}
|
|
207
233
|
display={displayBar ? 'block' : 'none'}
|
|
208
234
|
opacity={transparentBar ? 0.5 : 1}
|
|
209
235
|
x={barX + barWidth / 2}
|
|
210
236
|
y={barY}
|
|
211
|
-
verticalAnchor={
|
|
212
|
-
fill={
|
|
237
|
+
verticalAnchor={verticalAnchor}
|
|
238
|
+
fill={fillColor}
|
|
213
239
|
textAnchor='middle'
|
|
214
|
-
fontSize={`${
|
|
240
|
+
fontSize={`${iconSize}px`}
|
|
215
241
|
>
|
|
216
242
|
{pd.iconCode}
|
|
217
243
|
</Text>
|
|
@@ -219,14 +245,25 @@ export const BarChartVertical = () => {
|
|
|
219
245
|
})}
|
|
220
246
|
|
|
221
247
|
<Text // prettier-ignore
|
|
222
|
-
display={
|
|
248
|
+
display={displayBar ? 'block' : 'none'}
|
|
249
|
+
opacity={transparentBar ? 0.5 : 1}
|
|
250
|
+
x={barX + barWidth / 2}
|
|
251
|
+
y={barY - 5}
|
|
252
|
+
fill={labelColor}
|
|
253
|
+
textAnchor='middle'
|
|
254
|
+
>
|
|
255
|
+
{testZeroValue(bar.value) ? '' : barDefaultLabel}
|
|
256
|
+
</Text>
|
|
257
|
+
<Text // prettier-ignore
|
|
258
|
+
display={displayBar ? 'block' : 'none'}
|
|
223
259
|
opacity={transparentBar ? 0.5 : 1}
|
|
224
260
|
x={barX + barWidth / 2}
|
|
225
261
|
y={barY - 5}
|
|
226
262
|
fill={labelColor}
|
|
227
263
|
textAnchor='middle'
|
|
264
|
+
fontSize={config.isLollipopChart ? null : barWidth / 2}
|
|
228
265
|
>
|
|
229
|
-
{
|
|
266
|
+
{absentDataLabel}
|
|
230
267
|
</Text>
|
|
231
268
|
|
|
232
269
|
{config.isLollipopChart && config.lollipopShape === 'circle' && (
|
|
@@ -235,7 +272,7 @@ export const BarChartVertical = () => {
|
|
|
235
272
|
cx={barX + lollipopShapeSize / 3.5}
|
|
236
273
|
cy={bar.y}
|
|
237
274
|
r={lollipopShapeSize / 2}
|
|
238
|
-
fill={
|
|
275
|
+
fill={getBarBackgroundColor(colorScale(config.runtime.seriesLabels[bar.key]))}
|
|
239
276
|
key={`circle--${bar.index}`}
|
|
240
277
|
data-tooltip-html={tooltip}
|
|
241
278
|
data-tooltip-id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
|
|
@@ -249,7 +286,7 @@ export const BarChartVertical = () => {
|
|
|
249
286
|
y={barY}
|
|
250
287
|
width={lollipopShapeSize}
|
|
251
288
|
height={lollipopShapeSize}
|
|
252
|
-
fill={
|
|
289
|
+
fill={getBarBackgroundColor(colorScale(config.runtime.seriesLabels[bar.key]))}
|
|
253
290
|
key={`circle--${bar.index}`}
|
|
254
291
|
data-tooltip-html={tooltip}
|
|
255
292
|
data-tooltip-id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
|
|
@@ -273,7 +310,7 @@ export const BarChartVertical = () => {
|
|
|
273
310
|
let upperPos
|
|
274
311
|
let lowerPos
|
|
275
312
|
let tickWidth = 5
|
|
276
|
-
xPos = xScale(getXAxisData(d)) + (config.xAxis.type !== 'date
|
|
313
|
+
xPos = xScale(getXAxisData(d)) + (config.xAxis.type !== 'date-time' ? seriesScale.range()[1] / 2 : 0)
|
|
277
314
|
upperPos = yScale(getYAxisData(d, config.confidenceKeys.lower))
|
|
278
315
|
lowerPos = yScale(getYAxisData(d, config.confidenceKeys.upper))
|
|
279
316
|
return (
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// Define an interface for the function's parameter
|
|
2
|
+
interface BarConfigProps {
|
|
3
|
+
defaultBarWidth?: number
|
|
4
|
+
defaultBarHeight?: number
|
|
5
|
+
bar?: { [key: string]: any }
|
|
6
|
+
isNumber?: Function
|
|
7
|
+
config: { [key: string]: any }
|
|
8
|
+
getTextWidth: (a: string, b: string) => string
|
|
9
|
+
barWidth: number
|
|
10
|
+
isVertical: boolean
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Function to create bar width based on suppression status and missing data label
|
|
14
|
+
export const getBarConfig = ({ bar, defaultBarHeight, defaultBarWidth, config, isNumber, getTextWidth, barWidth, isVertical }: BarConfigProps) => {
|
|
15
|
+
const heightMini = 3 /// height of small bars aka suppressed/NA/Zero valued
|
|
16
|
+
let barHeight = defaultBarHeight
|
|
17
|
+
|
|
18
|
+
let barWidthHorizontal = defaultBarWidth
|
|
19
|
+
|
|
20
|
+
let barLabel = ''
|
|
21
|
+
let isSuppressed = false
|
|
22
|
+
let showMissingDataLabel = false
|
|
23
|
+
const showSuppressedSymbol = config.general.showSuppressedSymbol
|
|
24
|
+
|
|
25
|
+
config.preliminaryData.forEach(pd => {
|
|
26
|
+
const hasColumn = !pd.column || pd.column === bar.key
|
|
27
|
+
if (hasColumn && pd.type === 'suppression' && pd.value && String(pd.value) === String(bar.value)) {
|
|
28
|
+
if (!pd.hideBarSymbol && showSuppressedSymbol) {
|
|
29
|
+
barHeight = barWidth > 10 ? heightMini : 0
|
|
30
|
+
barWidthHorizontal = heightMini
|
|
31
|
+
isSuppressed = true
|
|
32
|
+
} else {
|
|
33
|
+
barHeight = 0
|
|
34
|
+
barWidthHorizontal = 0
|
|
35
|
+
isSuppressed = true
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
// Handle undefined, null, or non-calculable bar.value
|
|
41
|
+
if (!isSuppressed && !isNumber(bar.value) && config.general.showMissingDataLabel) {
|
|
42
|
+
const labelWidth = getTextWidth(barLabel, `normal ${barWidth / 2}px sans-serif`)
|
|
43
|
+
const labelFits = Number(labelWidth) < barWidth && barWidth > 10
|
|
44
|
+
showMissingDataLabel = true
|
|
45
|
+
barHeight = labelFits ? heightMini : 0
|
|
46
|
+
barWidthHorizontal = heightMini
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const getBarY = (defaultBarY, yScale) => {
|
|
50
|
+
// calculate Y position of small bars (suppressed,N/A,Zero valued) bars
|
|
51
|
+
if (isSuppressed || showMissingDataLabel) {
|
|
52
|
+
if (config.isLollipopChart) {
|
|
53
|
+
return yScale - heightMini * 2
|
|
54
|
+
} else {
|
|
55
|
+
return yScale - heightMini
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return defaultBarY
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Function to determine the label for a bar in a bar chart vertical/Horizontal
|
|
62
|
+
const getAbsentDataLabel = yAxisValue => {
|
|
63
|
+
// Initialize label with the yAxisValue
|
|
64
|
+
let label = ''
|
|
65
|
+
// Check if the label is exactly '0' and if so, hide it
|
|
66
|
+
if (String(yAxisValue) === '0') label = ''
|
|
67
|
+
// Check if the bar is marked as suppressed. If so, do not show any label.
|
|
68
|
+
if (isSuppressed) label = ''
|
|
69
|
+
// If the config is set to show a label for missing data, display 'N/A'
|
|
70
|
+
if (showMissingDataLabel) label = 'N/A'
|
|
71
|
+
|
|
72
|
+
// determine label width in pixels & check if it fits to the bar width
|
|
73
|
+
const labelWidth = getTextWidth(barLabel, `normal ${barWidth / 2}px sans-serif`)
|
|
74
|
+
const labelFits = Number(labelWidth) < barWidth && barWidth > 10
|
|
75
|
+
if (config.isLollipopChart) {
|
|
76
|
+
return label
|
|
77
|
+
} else {
|
|
78
|
+
return labelFits && isVertical ? label : !isVertical ? label : ''
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return { barWidthHorizontal, barHeight, isSuppressed, showMissingDataLabel, getBarY, getAbsentDataLabel }
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export const testZeroValue = value => {
|
|
86
|
+
if (value === undefined || value === null) {
|
|
87
|
+
return
|
|
88
|
+
}
|
|
89
|
+
const regex = /^0(\.0)?$/
|
|
90
|
+
return regex.test(value.toString())
|
|
91
|
+
}
|