@cdc/chart 4.24.5 → 4.24.7
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 +39128 -35959
- package/examples/feature/annotations/index.json +542 -0
- package/examples/xaxis.json +493 -0
- package/index.html +5 -4
- package/package.json +5 -4
- package/src/CdcChart.tsx +104 -64
- 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/src/_stories/_mock/annotation_date-time_mock.json +530 -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 +154 -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/BarChart/components/BarChart.Horizontal.tsx +44 -42
- package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +1 -2
- package/src/components/BarChart/components/BarChart.StackedVertical.tsx +11 -11
- package/src/components/BarChart/components/BarChart.Vertical.tsx +50 -22
- package/src/components/BarChart/helpers/index.ts +102 -0
- package/src/components/EditorPanel/EditorPanel.tsx +232 -98
- package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +306 -0
- package/src/components/EditorPanel/components/Panels/Panel.General.tsx +117 -6
- package/src/components/EditorPanel/components/Panels/Panel.Sankey.tsx +2 -3
- package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +3 -2
- 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 +19 -0
- package/src/components/EditorPanel/useEditorPermissions.js +19 -2
- package/src/components/Legend/Legend.Component.tsx +7 -8
- package/src/components/Legend/helpers/createFormatLabels.tsx +1 -1
- package/src/components/Legend/helpers/index.ts +5 -0
- package/src/components/LineChart/LineChartProps.ts +3 -0
- package/src/components/LineChart/helpers.ts +21 -7
- package/src/components/LineChart/index.tsx +7 -7
- package/src/components/LinearChart.jsx +179 -136
- package/src/components/PairedBarChart.jsx +9 -9
- package/src/components/PieChart/PieChart.tsx +4 -4
- package/src/components/Sankey/index.tsx +73 -20
- package/src/components/ScatterPlot/ScatterPlot.jsx +22 -8
- package/src/components/ZoomBrush.tsx +90 -44
- package/src/data/initial-state.js +14 -6
- 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/useMinMax.ts +8 -3
- package/src/hooks/useScales.ts +25 -3
- package/src/hooks/useTooltip.tsx +58 -11
- package/src/scss/main.scss +21 -14
- package/src/types/ChartConfig.ts +50 -3
- package/src/types/ChartContext.ts +9 -0
- package/tests-examples/helpers/testZeroValue.test.ts +30 -0
- package/LICENSE +0 -201
- package/src/helpers/filterData.ts +0 -18
- package/src/helpers/tests/computeMarginBottom.test.ts +0 -21
- /package/src/hooks/{useLegendClasses.js → useLegendClasses.ts} +0 -0
- /package/src/hooks/{useReduceData.js → useReduceData.ts} +0 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { timeParse } from 'd3-time-format'
|
|
2
|
+
|
|
3
|
+
const getXValueFromCoordinate = (x, isClick = false) => {
|
|
4
|
+
if (visualizationType === 'Pie') return
|
|
5
|
+
if (orientation === 'horizontal') return
|
|
6
|
+
|
|
7
|
+
// Check the type of x equal to point or if the type of xAxis is equal to continuous or date
|
|
8
|
+
if (config.xAxis.type === 'categorical' || (visualizationType === 'Combo' && orientation !== 'horizontal' && visualizationType !== 'Forest Plot')) {
|
|
9
|
+
let range = xScale.range()[1] - xScale.range()[0]
|
|
10
|
+
let eachBand = range / (xScale.domain().length + 1)
|
|
11
|
+
|
|
12
|
+
let numerator = x
|
|
13
|
+
const index = Math.floor((Number(numerator) - eachBand / 2) / eachBand)
|
|
14
|
+
return xScale.domain()[index] // fixes off by 1 error
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (config.xAxis.type === 'date') {
|
|
18
|
+
const xValue = x // Assuming x is the coordinate on the chart
|
|
19
|
+
const xTimestamp = convertXValueToTimestamp(x, 0, xMax, xScale.domain())
|
|
20
|
+
|
|
21
|
+
// Calculate the closest date to the x coordinate
|
|
22
|
+
let closestDate = null
|
|
23
|
+
let minDistance = Number.MAX_VALUE
|
|
24
|
+
|
|
25
|
+
xScale.domain().forEach(timestamp => {
|
|
26
|
+
const distance = Math.abs(xTimestamp - timestamp)
|
|
27
|
+
if (distance < minDistance) {
|
|
28
|
+
minDistance = distance
|
|
29
|
+
closestDate = timestamp
|
|
30
|
+
}
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
return closestDate
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return x
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const findNearestDatum = ({ data, xScale, yScale, config, xMax, annotationSeriesKey }, xPosition) => {
|
|
40
|
+
const { xAxis, visualizationType, orientation } = config
|
|
41
|
+
|
|
42
|
+
const convertXValueToTimestamp = (xValue, minX, maxX, domain, xScale) => {
|
|
43
|
+
let ticks = []
|
|
44
|
+
if (config.xAxis.type === 'date-time') {
|
|
45
|
+
minX = new Date(minX)
|
|
46
|
+
maxX = new Date(maxX)
|
|
47
|
+
domain = domain.map(d => new Date(d))
|
|
48
|
+
ticks = xScale.ticks().map(d => new Date(d))
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Calculate the percentage position of xValue between minX and maxX
|
|
52
|
+
const percentage = (xValue - minX) / (maxX - minX)
|
|
53
|
+
|
|
54
|
+
// Calculate the index in the domain array corresponding to the percentage position
|
|
55
|
+
const index = Math.round(percentage * (domain.length - 1))
|
|
56
|
+
|
|
57
|
+
if (config.xAxis.type === 'date-time') {
|
|
58
|
+
return ticks[index]
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Return the timestamp from the domain array at the calculated index
|
|
62
|
+
return domain[index]
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const getXValueFromCoordinate = (x, isClick = false) => {
|
|
66
|
+
if (visualizationType === 'Pie') return
|
|
67
|
+
if (orientation === 'horizontal') return
|
|
68
|
+
|
|
69
|
+
if (config.xAxis.type === 'date-time') {
|
|
70
|
+
// Calculate the percentage position of xValue between minX and maxX
|
|
71
|
+
const invertedValue = new Date(xScale.invert(x))
|
|
72
|
+
const ticks = config.data.map(d => new Date(d[config.xAxis.dataKey]).getTime())
|
|
73
|
+
let minDistance = Infinity
|
|
74
|
+
let closestDate = null
|
|
75
|
+
|
|
76
|
+
ticks.forEach(timestamp => {
|
|
77
|
+
const distance = Math.abs(invertedValue.getTime() - timestamp)
|
|
78
|
+
if (distance < minDistance) {
|
|
79
|
+
minDistance = distance
|
|
80
|
+
closestDate = timestamp
|
|
81
|
+
}
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
return new Date(closestDate).getTime()
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Check the type of x equal to point or if the type of xAxis is equal to continuous or date
|
|
88
|
+
if (config.xAxis.type === 'categorical' || (visualizationType === 'Combo' && orientation !== 'horizontal' && visualizationType !== 'Forest Plot')) {
|
|
89
|
+
const range = xScale.range()[1] - xScale.range()[0]
|
|
90
|
+
const eachBand = range / (xScale.domain().length + 1)
|
|
91
|
+
|
|
92
|
+
let numerator = x
|
|
93
|
+
const index = Math.floor((Number(numerator) - eachBand / 2) / eachBand)
|
|
94
|
+
return xScale.domain()[index] // fixes off by 1 error
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (config.xAxis.type === 'date') {
|
|
98
|
+
const xValue = x // Assuming x is the coordinate on the chart
|
|
99
|
+
const xTimestamp = convertXValueToTimestamp(x, 0, xMax, xScale.domain(), xScale)
|
|
100
|
+
|
|
101
|
+
// Calculate the closest date to the x coordinate
|
|
102
|
+
let closestDate = null
|
|
103
|
+
let minDistance = Number.MAX_VALUE
|
|
104
|
+
|
|
105
|
+
xScale.domain().forEach(timestamp => {
|
|
106
|
+
const distance = Math.abs(xTimestamp - timestamp)
|
|
107
|
+
if (distance < minDistance) {
|
|
108
|
+
minDistance = distance
|
|
109
|
+
closestDate = timestamp
|
|
110
|
+
}
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
return closestDate
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return x
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const xValue = getXValueFromCoordinate(xPosition - Number(config.yAxis.size || 0))
|
|
120
|
+
|
|
121
|
+
let closestSeries = []
|
|
122
|
+
|
|
123
|
+
if (!xValue) return { x: 0, y: 0 }
|
|
124
|
+
|
|
125
|
+
if (xAxis.type === 'categorical') {
|
|
126
|
+
closestSeries = config.data.filter(d => d[config.xAxis.dataKey] === xValue)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (xAxis.type === 'date' || xAxis.type === 'date-time') {
|
|
130
|
+
closestSeries = config.data.filter(d => new Date(d[config.xAxis.dataKey]).getTime() === xValue)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const y = closestSeries[0][annotationSeriesKey] // Map each key to its corresponding value in data
|
|
134
|
+
const x = xValue
|
|
135
|
+
return { x, y }
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export { findNearestDatum, getXValueFromCoordinate }
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
const applyBandScaleOffset = (num: number, config, xScale) => num + Number(config.yAxis.size) + xScale.bandwidth() / 2
|
|
2
|
+
const handleConnectionHorizontalType = (annotation, xScale, config) => {
|
|
3
|
+
const { connectionLocation } = annotation
|
|
4
|
+
if (connectionLocation === 'right') return 'end'
|
|
5
|
+
if (connectionLocation === 'left') return 'start'
|
|
6
|
+
if (connectionLocation === 'bottom' || connectionLocation === 'top') return 'middle'
|
|
7
|
+
return xScale(annotation.xKey) + annotation.dx < config.yAxis.size ? 'middle' : null
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const handleConnectionVerticalType = (annotation, xScale, config) => {
|
|
11
|
+
const { connectionLocation } = annotation
|
|
12
|
+
if (connectionLocation === 'top') return 'start'
|
|
13
|
+
if (connectionLocation === 'bottom') return 'end'
|
|
14
|
+
if (connectionLocation === 'right' || connectionLocation === 'left') return 'middle'
|
|
15
|
+
return xScale(annotation.xKey) + annotation.dx < config.yAxis.size ? 'end' : null
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const handleMobileXPosition = (annotation, xScale, config) => {
|
|
19
|
+
if (annotation.snapToNearestPoint) {
|
|
20
|
+
return Number(annotation.dx) + xScale(annotation.xKey) + (config.xAxis.type !== 'date-time' ? xScale.bandwidth() / 2 : 0) + Number(config.yAxis.size)
|
|
21
|
+
}
|
|
22
|
+
return Number(annotation.x) + Number(annotation.dx)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const handleMobileYPosition = (annotation, yScale, config) => {
|
|
26
|
+
if (annotation.snapToNearestPoint) {
|
|
27
|
+
return yScale(annotation.yKey) + Number(annotation.dy)
|
|
28
|
+
}
|
|
29
|
+
return Number(annotation.dy) + Number(annotation.y)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const handleTextX = (annotation, xScale, config) => {
|
|
33
|
+
if (annotation.snapToNearestPoint) {
|
|
34
|
+
return Number(annotation.dx) + Number(xScale(annotation.xKey)) + (config.xAxis.type !== 'date-time' ? xScale.bandwidth() / 2 : 0) + Number(config.yAxis.size) - 16 / 3
|
|
35
|
+
}
|
|
36
|
+
return Number(annotation.dx) + Number(annotation.x) - 16 / 3
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const handleTextY = (annotation, yScale, config) => {
|
|
40
|
+
if (annotation.snapToNearestPoint) {
|
|
41
|
+
return yScale(annotation.yKey) + Number(annotation.dy) + 5
|
|
42
|
+
}
|
|
43
|
+
return Number(annotation.y) + Number(annotation.dy) + 16 / 3
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export { applyBandScaleOffset, handleConnectionHorizontalType, handleConnectionVerticalType, handleMobileXPosition, handleMobileYPosition, handleTextX, handleTextY }
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import AnnotationDraggable from './components/AnnotationDraggable'
|
|
2
|
+
import AnnotationList from './components/AnnotationList'
|
|
3
|
+
import AnnotationDropdown from './components/AnnotationDropdown'
|
|
4
|
+
|
|
5
|
+
const Annotation = {
|
|
6
|
+
Draggable: AnnotationDraggable,
|
|
7
|
+
// Mobile auto display
|
|
8
|
+
List: AnnotationList,
|
|
9
|
+
// Desktop Accessible Option
|
|
10
|
+
Dropdown: AnnotationDropdown
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export default Annotation
|
|
@@ -14,7 +14,7 @@ import { approvedCurveTypes } from '@cdc/core/helpers/lineChartHelpers'
|
|
|
14
14
|
const AreaChartStacked = ({ xScale, yScale, yMax, xMax, handleTooltipMouseOver, handleTooltipMouseOff, isDebug }) => {
|
|
15
15
|
// import data from context
|
|
16
16
|
let { transformedData, config, seriesHighlight, colorScale, rawData } = useContext(ConfigContext)
|
|
17
|
-
const data = config.brush
|
|
17
|
+
const data = config.brush?.active && config.brush.data?.length ? config.brush.data : transformedData
|
|
18
18
|
// Draw transparent bars over the chart to get tooltip data
|
|
19
19
|
// Turn DEBUG on for additional context.
|
|
20
20
|
if (!data) return
|
|
@@ -15,7 +15,7 @@ const AreaChart = props => {
|
|
|
15
15
|
const { xScale, yScale, yMax, xMax, handleTooltipMouseOver, handleTooltipMouseOff, isDebug, children } = props
|
|
16
16
|
// import data from context
|
|
17
17
|
let { transformedData, config, handleLineType, parseDate, formatDate, formatNumber, seriesHighlight, colorScale, rawData, brushConfig } = useContext(ConfigContext)
|
|
18
|
-
const data = config.brush
|
|
18
|
+
const data = config.brush?.active && brushConfig.data?.length ? brushConfig.data : transformedData
|
|
19
19
|
|
|
20
20
|
if (!data) return
|
|
21
21
|
|
|
@@ -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
|
|
|
@@ -68,8 +52,6 @@ export const BarChartHorizontal = () => {
|
|
|
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
54
|
const scaleVal = config.useLogScale ? 0.1 : 0
|
|
71
|
-
const { suppresedBarHeight: suppresedBarWidth, getIconSize, getIconPadding, getVerticalAnchor, isSuppressed } = composeSuppressionBars({ bar })
|
|
72
|
-
|
|
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 as any)(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,64 @@ 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
|
|
193
180
|
return (
|
|
194
181
|
<Text // prettier-ignore
|
|
195
182
|
key={index}
|
|
196
|
-
fontSize={
|
|
197
|
-
angle={90}
|
|
183
|
+
fontSize={iconSize}
|
|
198
184
|
display={displayBar ? 'block' : 'none'}
|
|
199
185
|
opacity={transparentBar ? 0.5 : 1}
|
|
200
186
|
x={barX}
|
|
201
187
|
y={config.barHeight / 2 + config.barHeight * bar.index}
|
|
202
188
|
fill={'#000'}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
textAnchor=
|
|
189
|
+
dy={config.barHeight / 5}
|
|
190
|
+
dx={10}
|
|
191
|
+
textAnchor='start'
|
|
192
|
+
verticalAnchor={verticalAnchor}
|
|
206
193
|
>
|
|
207
194
|
{pd.iconCode}
|
|
208
195
|
</Text>
|
|
209
196
|
)
|
|
210
197
|
})}
|
|
211
198
|
|
|
212
|
-
{!config.isLollipopChart &&
|
|
199
|
+
{!config.isLollipopChart && (
|
|
213
200
|
<Text // prettier-ignore
|
|
214
201
|
display={displayBar ? 'block' : 'none'}
|
|
215
202
|
x={bar.y}
|
|
203
|
+
opacity={transparentBar ? 0.5 : 1}
|
|
216
204
|
y={config.barHeight / 2 + config.barHeight * bar.index}
|
|
217
205
|
fill={labelColor}
|
|
218
206
|
dx={textPadding}
|
|
219
207
|
verticalAnchor='middle'
|
|
220
208
|
textAnchor={textAnchor}
|
|
221
209
|
>
|
|
222
|
-
{
|
|
210
|
+
{testZeroValue(bar.value) ? '' : barDefaultLabel}
|
|
223
211
|
</Text>
|
|
224
212
|
)}
|
|
225
|
-
|
|
213
|
+
<Text // prettier-ignore
|
|
214
|
+
display={displayBar ? 'block' : 'none'}
|
|
215
|
+
x={bar.y}
|
|
216
|
+
opacity={transparentBar ? 0.5 : 1}
|
|
217
|
+
y={config.barHeight / 2 + config.barHeight * bar.index}
|
|
218
|
+
fill={labelColor}
|
|
219
|
+
dx={absentDataLabel === 'N/A' ? 20 : textPadding}
|
|
220
|
+
dy={config.isLollipopChart ? -10 : 0}
|
|
221
|
+
verticalAnchor='middle'
|
|
222
|
+
textAnchor={absentDataLabel === 'N/A' ? 'middle' : textAnchor}
|
|
223
|
+
>
|
|
224
|
+
{absentDataLabel}
|
|
225
|
+
</Text>
|
|
226
|
+
|
|
227
|
+
{config.isLollipopChart && (
|
|
226
228
|
<Text // prettier-ignore
|
|
227
229
|
display={displayBar ? 'block' : 'none'}
|
|
228
230
|
x={bar.y}
|
|
@@ -233,7 +235,7 @@ export const BarChartHorizontal = () => {
|
|
|
233
235
|
verticalAnchor='middle'
|
|
234
236
|
fontWeight={'normal'}
|
|
235
237
|
>
|
|
236
|
-
{
|
|
238
|
+
{testZeroValue(bar.value) ? '' : barDefaultLabel}
|
|
237
239
|
</Text>
|
|
238
240
|
)}
|
|
239
241
|
{isLabelBelowBar && !config.yAxis.hideLabel && (
|
|
@@ -49,8 +49,7 @@ const BarChartStackedHorizontal = () => {
|
|
|
49
49
|
const xAxisValue = formatNumber(data[bar.index][bar.key], 'left')
|
|
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
|
-
const textWidth = getTextWidth(xAxisValue, `normal ${fontSize[config.fontSize]}px sans-serif`)
|
|
53
|
-
|
|
52
|
+
const textWidth = (getTextWidth as any)(xAxisValue, `normal ${fontSize[config.fontSize]}px sans-serif`)
|
|
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,7 +47,7 @@ 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}`}>
|
|
@@ -63,7 +63,7 @@ const BarChartStackedVertical = () => {
|
|
|
63
63
|
borderColor: '#333',
|
|
64
64
|
borderStyle: 'solid',
|
|
65
65
|
borderWidth: `${config.barHasBorder === 'true' ? barBorderWidth : 0}px`,
|
|
66
|
-
width:
|
|
66
|
+
width: barThickness,
|
|
67
67
|
height: bar.height,
|
|
68
68
|
x: barX,
|
|
69
69
|
y: bar.y,
|
|
@@ -80,7 +80,7 @@ const BarChartStackedVertical = () => {
|
|
|
80
80
|
},
|
|
81
81
|
styleOverrides: {
|
|
82
82
|
animationDelay: `${barStack.index * 0.5}s`,
|
|
83
|
-
transformOrigin: `${
|
|
83
|
+
transformOrigin: `${barThickness / 2}px ${bar.y + bar.height}px`,
|
|
84
84
|
opacity: transparentBar ? 0.2 : 1,
|
|
85
85
|
display: displayBar ? 'block' : 'none'
|
|
86
86
|
}
|
|
@@ -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
|
|
@@ -66,19 +80,16 @@ export const BarChartVertical = () => {
|
|
|
66
80
|
return barGroups.map((barGroup, index) => (
|
|
67
81
|
<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}`} left={barGroup.x0}>
|
|
68
82
|
{barGroup.bars.map((bar, index) => {
|
|
69
|
-
const { suppresedBarHeight, getIconSize, getIconPadding, getVerticalAnchor, isSuppressed } = composeSuppressionBars({ bar })
|
|
70
83
|
const scaleVal = config.useLogScale ? 0.1 : 0
|
|
71
84
|
let highlightedBarValues = config.highlightedBarValues.map(item => item.value).filter(item => item !== ('' || undefined))
|
|
72
85
|
highlightedBarValues = config.xAxis.type === 'date' ? HighLightedBarUtils.formatDates(highlightedBarValues) : highlightedBarValues
|
|
73
86
|
const transparentBar = config.legend.behavior === 'highlight' && seriesHighlight.length > 0 && seriesHighlight.indexOf(bar.key) === -1
|
|
74
87
|
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
88
|
|
|
79
|
-
let barGroupWidth = seriesScale.range()[1]
|
|
80
|
-
|
|
81
|
-
|
|
89
|
+
let barGroupWidth = seriesScale.range()[1] - seriesScale.range()[0]
|
|
90
|
+
const defaultBarHeight = Math.abs(yScale(bar.value) - yScale(scaleVal))
|
|
91
|
+
const defaultBarY = bar.value >= 0 && isNumber(bar.value) ? bar.y : yScale(0)
|
|
92
|
+
let barWidth = config.isLollipopChart ? lollipopBarWidth : seriesScale.bandwidth()
|
|
82
93
|
let barX = bar.x + (config.isLollipopChart ? (barGroupWidth / barGroup.bars.length - lollipopBarWidth) / 2 : 0) - (config.xAxis.type === 'date-time' ? barGroupWidth / 2 : 0)
|
|
83
94
|
setBarWidth(barWidth)
|
|
84
95
|
setTotalBarsInGroup(barGroup.bars.length)
|
|
@@ -88,7 +99,6 @@ export const BarChartVertical = () => {
|
|
|
88
99
|
// create new Index for bars with negative values
|
|
89
100
|
const newIndex = bar.value < 0 ? -1 : index
|
|
90
101
|
// tooltips
|
|
91
|
-
|
|
92
102
|
const additionalColTooltip = getAdditionalColumn(bar.key, data[barGroup.index][config.runtime.originalXAxis.dataKey])
|
|
93
103
|
let xAxisTooltip = config.runtime.xAxis.label ? `${config.runtime.xAxis.label}: ${xAxisValue}` : xAxisValue
|
|
94
104
|
const tooltipBody = `${config.runtime.seriesLabels[bar.key]}: ${yAxisValue}`
|
|
@@ -111,10 +121,13 @@ export const BarChartVertical = () => {
|
|
|
111
121
|
const highlightedBar = getHighlightedBarByValue(xAxisValue)
|
|
112
122
|
const borderColor = isHighlightedBar ? highlightedBarColor : config.barHasBorder === 'true' ? '#000' : 'transparent'
|
|
113
123
|
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
124
|
|
|
117
|
-
const
|
|
125
|
+
const { barHeight, isSuppressed, getBarY, getAbsentDataLabel } = getBarConfig({ bar, defaultBarHeight, config, isNumber, getTextWidth, barWidth, isVertical: true, yAxisValue })
|
|
126
|
+
|
|
127
|
+
const absentDataLabel = getAbsentDataLabel(yAxisValue)
|
|
128
|
+
const barDefaultLabel = isSuppressed || !config.labels ? '' : yAxisValue
|
|
129
|
+
const barY = getBarY(defaultBarY, yScale(scaleVal))
|
|
130
|
+
const displaylollipopShape = testZeroValue(bar.value) ? 'none' : 'block'
|
|
118
131
|
const getBarBackgroundColor = (barColor: string, filteredOutColor?: string): string => {
|
|
119
132
|
let _barColor = barColor
|
|
120
133
|
let _filteredOutColor = filteredOutColor || '#f2f2f2'
|
|
@@ -194,24 +207,28 @@ export const BarChartVertical = () => {
|
|
|
194
207
|
const selectedSuppressionColumn = !pd.column || pd.column === bar.key
|
|
195
208
|
// compare entered suppressed value with data value
|
|
196
209
|
const isValueMatch = String(pd.value) === String(bar.value) && pd.value !== ''
|
|
197
|
-
|
|
210
|
+
const isSuppressed = isValueMatch && selectedSuppressionColumn
|
|
198
211
|
|
|
199
|
-
if (!isSuppressed || barWidth < 10 || !config.
|
|
212
|
+
if (!isSuppressed || barWidth < 10 || !config.general.showSuppressedSymbol || pd.hideBarSymbol) {
|
|
200
213
|
return
|
|
201
214
|
}
|
|
215
|
+
const hasAsterisk = String(pd.symbol).includes('Asterisk')
|
|
216
|
+
const yPadding = hasAsterisk ? -5 : -8
|
|
217
|
+
const verticalAnchor = hasAsterisk ? 'middle' : 'end'
|
|
218
|
+
const iconSize = pd.symbol === 'Asterisk' ? barWidth * 1.2 : pd.symbol === 'Double Asterisk' ? barWidth : barWidth / 1.5
|
|
202
219
|
|
|
203
220
|
return (
|
|
204
221
|
<Text // prettier-ignore
|
|
205
222
|
key={index}
|
|
206
|
-
dy={
|
|
223
|
+
dy={yPadding}
|
|
207
224
|
display={displayBar ? 'block' : 'none'}
|
|
208
225
|
opacity={transparentBar ? 0.5 : 1}
|
|
209
226
|
x={barX + barWidth / 2}
|
|
210
227
|
y={barY}
|
|
211
|
-
verticalAnchor={
|
|
228
|
+
verticalAnchor={verticalAnchor}
|
|
212
229
|
fill={labelColor}
|
|
213
230
|
textAnchor='middle'
|
|
214
|
-
fontSize={`${
|
|
231
|
+
fontSize={`${iconSize}px`}
|
|
215
232
|
>
|
|
216
233
|
{pd.iconCode}
|
|
217
234
|
</Text>
|
|
@@ -219,14 +236,25 @@ export const BarChartVertical = () => {
|
|
|
219
236
|
})}
|
|
220
237
|
|
|
221
238
|
<Text // prettier-ignore
|
|
222
|
-
display={
|
|
239
|
+
display={displayBar ? 'block' : 'none'}
|
|
240
|
+
opacity={transparentBar ? 0.5 : 1}
|
|
241
|
+
x={barX + barWidth / 2}
|
|
242
|
+
y={barY - 5}
|
|
243
|
+
fill={labelColor}
|
|
244
|
+
textAnchor='middle'
|
|
245
|
+
>
|
|
246
|
+
{testZeroValue(bar.value) ? '' : barDefaultLabel}
|
|
247
|
+
</Text>
|
|
248
|
+
<Text // prettier-ignore
|
|
249
|
+
display={displayBar ? 'block' : 'none'}
|
|
223
250
|
opacity={transparentBar ? 0.5 : 1}
|
|
224
251
|
x={barX + barWidth / 2}
|
|
225
252
|
y={barY - 5}
|
|
226
253
|
fill={labelColor}
|
|
227
254
|
textAnchor='middle'
|
|
255
|
+
fontSize={config.isLollipopChart ? null : barWidth / 2}
|
|
228
256
|
>
|
|
229
|
-
{
|
|
257
|
+
{absentDataLabel}
|
|
230
258
|
</Text>
|
|
231
259
|
|
|
232
260
|
{config.isLollipopChart && config.lollipopShape === 'circle' && (
|
|
@@ -273,7 +301,7 @@ export const BarChartVertical = () => {
|
|
|
273
301
|
let upperPos
|
|
274
302
|
let lowerPos
|
|
275
303
|
let tickWidth = 5
|
|
276
|
-
xPos = xScale(getXAxisData(d)) + (config.xAxis.type !== 'date
|
|
304
|
+
xPos = xScale(getXAxisData(d)) + (config.xAxis.type !== 'date-time' ? seriesScale.range()[1] / 2 : 0)
|
|
277
305
|
upperPos = yScale(getYAxisData(d, config.confidenceKeys.lower))
|
|
278
306
|
lowerPos = yScale(getYAxisData(d, config.confidenceKeys.upper))
|
|
279
307
|
return (
|