@cdc/chart 4.24.4 → 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 +39611 -36038
- package/examples/feature/annotations/index.json +542 -0
- package/examples/xaxis.json +493 -0
- package/index.html +9 -8
- package/package.json +5 -4
- package/src/CdcChart.tsx +115 -71
- package/src/_stories/Chart.stories.tsx +26 -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/bar-chart-suppressed.json +474 -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 +2 -2
- package/src/components/BarChart/components/BarChart.Horizontal.tsx +78 -71
- 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 +100 -87
- package/src/components/BarChart/helpers/index.ts +102 -0
- package/src/components/DeviationBar.jsx +4 -2
- package/src/components/EditorPanel/EditorPanel.tsx +435 -613
- package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +306 -0
- package/src/components/EditorPanel/components/Panels/Panel.General.tsx +135 -7
- package/src/components/EditorPanel/components/Panels/Panel.Sankey.tsx +2 -3
- package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +4 -5
- 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 +23 -3
- package/src/components/Legend/Legend.Component.tsx +66 -15
- package/src/components/Legend/helpers/createFormatLabels.tsx +1 -1
- package/src/components/Legend/helpers/index.ts +5 -0
- package/src/components/LineChart/LineChartProps.ts +16 -6
- package/src/components/LineChart/components/LineChart.Circle.tsx +22 -11
- package/src/components/LineChart/helpers.ts +148 -10
- package/src/components/LineChart/index.tsx +71 -44
- package/src/components/LinearChart.jsx +184 -125
- 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 +120 -55
- 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} +9 -22
- package/src/hooks/useColorScale.ts +1 -1
- package/src/hooks/useMinMax.ts +29 -5
- package/src/hooks/useScales.ts +48 -26
- package/src/hooks/useTooltip.tsx +62 -15
- package/src/scss/main.scss +69 -12
- package/src/types/ChartConfig.ts +53 -16
- package/src/types/ChartContext.ts +13 -0
- package/tests-examples/helpers/testZeroValue.test.ts +30 -0
- package/LICENSE +0 -201
- package/src/_stories/ChartLine.preliminary.tsx +0 -19
- package/src/_stories/ChartSuppress.stories.tsx +0 -19
- package/src/_stories/_mock/suppress_mock.json +0 -911
- package/src/helpers/computeMarginBottom.ts +0 -56
- 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
|
|
@@ -14,8 +14,8 @@ import { bisector } from 'd3-array'
|
|
|
14
14
|
const AreaChart = props => {
|
|
15
15
|
const { xScale, yScale, yMax, xMax, handleTooltipMouseOver, handleTooltipMouseOff, isDebug, children } = props
|
|
16
16
|
// import data from context
|
|
17
|
-
let { transformedData, config, handleLineType, parseDate, formatDate, formatNumber, seriesHighlight, colorScale, rawData } = useContext(ConfigContext)
|
|
18
|
-
const data = config.brush
|
|
17
|
+
let { transformedData, config, handleLineType, parseDate, formatDate, formatNumber, seriesHighlight, colorScale, rawData, brushConfig } = useContext(ConfigContext)
|
|
18
|
+
const data = config.brush?.active && brushConfig.data?.length ? brushConfig.data : transformedData
|
|
19
19
|
|
|
20
20
|
if (!data) return
|
|
21
21
|
|
|
@@ -1,62 +1,42 @@
|
|
|
1
1
|
import React, { useContext } from 'react'
|
|
2
|
+
|
|
3
|
+
// Local context and hooks
|
|
2
4
|
import ConfigContext from '../../../ConfigContext'
|
|
3
5
|
import { useBarChart } from '../../../hooks/useBarChart'
|
|
6
|
+
import { useHighlightedBars } from '../../../hooks/useHighlightedBars'
|
|
7
|
+
|
|
8
|
+
// VisX library imports
|
|
4
9
|
import { Group } from '@visx/group'
|
|
5
10
|
import { Text } from '@visx/text'
|
|
6
11
|
import { BarGroup } from '@visx/shape'
|
|
7
|
-
|
|
8
|
-
|
|
12
|
+
|
|
13
|
+
// CDC core components and helpers
|
|
9
14
|
import { getContrastColor } from '@cdc/core/helpers/cove/accessibility'
|
|
15
|
+
import createBarElement from '@cdc/core/components/createBarElement'
|
|
16
|
+
import { getBarConfig, testZeroValue } from '../helpers'
|
|
10
17
|
|
|
11
|
-
//
|
|
18
|
+
// Third party libraries
|
|
12
19
|
import chroma from 'chroma-js'
|
|
20
|
+
|
|
21
|
+
// Local context and types
|
|
13
22
|
import BarChartContext, { BarChartContextValues } from './context'
|
|
14
23
|
import { ChartContext } from '../../../types/ChartContext'
|
|
15
24
|
|
|
16
|
-
import createBarElement from '@cdc/core/components/createBarElement'
|
|
17
|
-
|
|
18
25
|
export const BarChartHorizontal = () => {
|
|
19
26
|
const { xScale, yScale, yMax, seriesScale } = useContext<BarChartContextValues>(BarChartContext)
|
|
20
|
-
const { transformedData: data, colorScale, seriesHighlight, config, formatNumber, formatDate, parseDate, setSharedFilter, isNumber, getTextWidth, getYAxisData, getXAxisData } = useContext<ChartContext>(ConfigContext)
|
|
21
|
-
const {
|
|
22
|
-
|
|
23
|
-
barBorderWidth,
|
|
24
|
-
updateBars,
|
|
25
|
-
assignColorsToValues,
|
|
26
|
-
section,
|
|
27
|
-
fontSize,
|
|
28
|
-
isLabelBelowBar,
|
|
29
|
-
displayNumbersOnBar,
|
|
30
|
-
lollipopBarWidth,
|
|
31
|
-
lollipopShapeSize,
|
|
32
|
-
getHighlightedBarColorByValue,
|
|
33
|
-
getHighlightedBarByValue,
|
|
34
|
-
generateIconSize,
|
|
35
|
-
getAdditionalColumn,
|
|
36
|
-
hoveredBar,
|
|
37
|
-
onMouseLeaveBar,
|
|
38
|
-
onMouseOverBar
|
|
39
|
-
} = useBarChart()
|
|
27
|
+
const { transformedData: data, tableData, colorScale, seriesHighlight, config, formatNumber, formatDate, parseDate, setSharedFilter, isNumber, getTextWidth, getYAxisData, getXAxisData } = useContext<ChartContext>(ConfigContext)
|
|
28
|
+
const { isHorizontal, barBorderWidth, updateBars, assignColorsToValues, section, fontSize, isLabelBelowBar, displayNumbersOnBar, lollipopBarWidth, lollipopShapeSize, getHighlightedBarColorByValue, getHighlightedBarByValue, getAdditionalColumn, hoveredBar, onMouseLeaveBar, onMouseOverBar } =
|
|
29
|
+
useBarChart()
|
|
40
30
|
|
|
41
31
|
const { HighLightedBarUtils } = useHighlightedBars(config)
|
|
42
|
-
|
|
43
|
-
let icon = null
|
|
44
|
-
const iconSize = generateIconSize(barWidth)
|
|
45
|
-
config.suppressedData?.forEach(d => {
|
|
46
|
-
if (bar.key === d.column && String(bar.value) === String(d.value) && d.icon) {
|
|
47
|
-
icon = <FaStar color='#000' size={iconSize} />
|
|
48
|
-
// icon = <BarIcon color='#000' size={fontSize[config.fontSize] / 1.7} />
|
|
49
|
-
}
|
|
50
|
-
})
|
|
51
|
-
return icon
|
|
52
|
-
}
|
|
32
|
+
|
|
53
33
|
return (
|
|
54
34
|
config.visualizationSubType !== 'stacked' &&
|
|
55
35
|
config.visualizationType === 'Bar' &&
|
|
56
36
|
config.orientation === 'horizontal' && (
|
|
57
37
|
<Group>
|
|
58
38
|
<BarGroup
|
|
59
|
-
data={data}
|
|
39
|
+
data={config.preliminaryData?.some(pd => pd.value && pd.type === 'suppression') ? tableData : data}
|
|
60
40
|
keys={config.runtime.barSeriesKeys || config.runtime.seriesKeys}
|
|
61
41
|
height={yMax}
|
|
62
42
|
x0={d => d[config.runtime.originalXAxis.dataKey]}
|
|
@@ -72,7 +52,6 @@ export const BarChartHorizontal = () => {
|
|
|
72
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}>
|
|
73
53
|
{barGroup.bars.map((bar, index) => {
|
|
74
54
|
const scaleVal = config.useLogScale ? 0.1 : 0
|
|
75
|
-
|
|
76
55
|
let highlightedBarValues = config.highlightedBarValues.map(item => item.value).filter(item => item !== ('' || undefined))
|
|
77
56
|
highlightedBarValues = config.xAxis.type === 'date' ? HighLightedBarUtils.formatDates(highlightedBarValues) : highlightedBarValues
|
|
78
57
|
let transparentBar = config.legend.behavior === 'highlight' && seriesHighlight.length > 0 && seriesHighlight.indexOf(bar.key) === -1
|
|
@@ -83,30 +62,28 @@ export const BarChartHorizontal = () => {
|
|
|
83
62
|
numbericBarHeight = 25
|
|
84
63
|
}
|
|
85
64
|
let barY = bar.value >= 0 && isNumber(bar.value) ? bar.y : yScale(scaleVal)
|
|
86
|
-
const
|
|
87
|
-
const barWidthHorizontal = Math.abs(xScale(bar.value) - xScale(scaleVal))
|
|
88
|
-
const suppresedBarWidth = 25
|
|
65
|
+
const defaultBarWidth = Math.abs(xScale(bar.value) - xScale(scaleVal))
|
|
89
66
|
const isPositiveBar = bar.value >= 0 && isNumber(bar.value)
|
|
90
|
-
let barWidth = bar.value && config.suppressedData.some(({ column, value }) => bar.key === column && bar.value === value) ? suppresedBarWidth : barWidthHorizontal
|
|
91
67
|
|
|
92
|
-
const
|
|
93
|
-
const barX =
|
|
68
|
+
const { barWidthHorizontal: barWidth, isSuppressed, getAbsentDataLabel } = getBarConfig({ bar, defaultBarWidth, config, isNumber, getTextWidth, isVertical: false })
|
|
69
|
+
const barX = bar.value < 0 ? Math.abs(xScale(bar.value)) : xScale(scaleVal)
|
|
94
70
|
const yAxisValue = formatNumber(bar.value, 'left')
|
|
95
71
|
const xAxisValue = config.runtime[section].type === 'date' ? formatDate(parseDate(data[barGroup.index][config.runtime.originalXAxis.dataKey])) : data[barGroup.index][config.runtime.originalXAxis.dataKey]
|
|
96
72
|
|
|
97
73
|
const barPosition = !isPositiveBar ? 'below' : 'above'
|
|
98
|
-
const
|
|
74
|
+
const absentDataLabel = getAbsentDataLabel(yAxisValue)
|
|
75
|
+
const barDefaultLabel = !config.yAxis.displayNumbersOnBar ? '' : yAxisValue
|
|
99
76
|
|
|
100
77
|
// check if bar text/value string fits into each bars.
|
|
101
|
-
|
|
102
|
-
|
|
78
|
+
const textWidth = (getTextWidth as any)(barDefaultLabel, `normal ${fontSize[config.fontSize]}px sans-serif`)
|
|
79
|
+
const textFits = Number(textWidth) < defaultBarWidth - 5
|
|
103
80
|
|
|
104
81
|
// control text position
|
|
105
82
|
let textAnchor = textFits ? 'end' : 'start'
|
|
106
83
|
let textAnchorLollipop = 'start'
|
|
107
84
|
let textPadding = textFits ? -5 : 5
|
|
108
85
|
let textPaddingLollipop = 10
|
|
109
|
-
//
|
|
86
|
+
//if bars are negative we change positions of text
|
|
110
87
|
if (barPosition === 'below') {
|
|
111
88
|
textAnchor = textFits ? 'start' : 'end'
|
|
112
89
|
textPadding = textFits ? 5 : -5
|
|
@@ -140,7 +117,8 @@ export const BarChartHorizontal = () => {
|
|
|
140
117
|
const highlightedBar = getHighlightedBarByValue(xAxisValue)
|
|
141
118
|
const borderColor = isHighlightedBar ? highlightedBarColor : config.barHasBorder === 'true' ? '#000' : 'transparent'
|
|
142
119
|
const borderWidth = isHighlightedBar ? highlightedBar.borderWidth : config.isLollipopChart ? 0 : config.barHasBorder === 'true' ? barBorderWidth : 0
|
|
143
|
-
const displaylollipopShape =
|
|
120
|
+
const displaylollipopShape = testZeroValue(bar.value) ? 'none' : 'block'
|
|
121
|
+
|
|
144
122
|
// update label color
|
|
145
123
|
if (barColor && labelColor && textFits) {
|
|
146
124
|
labelColor = getContrastColor('#000', barColor)
|
|
@@ -185,39 +163,68 @@ export const BarChartHorizontal = () => {
|
|
|
185
163
|
display: displayBar ? 'block' : 'none'
|
|
186
164
|
}
|
|
187
165
|
})}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
data-tooltip-html={tooltip}
|
|
195
|
-
data-tooltip-id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
|
|
196
|
-
onClick={e => {
|
|
197
|
-
e.preventDefault()
|
|
198
|
-
if (setSharedFilter) {
|
|
199
|
-
bar[config.xAxis.dataKey] = yAxisValue
|
|
200
|
-
setSharedFilter(config.uid, bar)
|
|
201
|
-
}
|
|
202
|
-
}}
|
|
203
|
-
>
|
|
204
|
-
{getIcon(bar, barWidth)}
|
|
205
|
-
</g>
|
|
166
|
+
{config.preliminaryData?.map((pd, index) => {
|
|
167
|
+
// check if user selected column
|
|
168
|
+
const selectedSuppressionColumn = !pd.column || pd.column === bar.key
|
|
169
|
+
// compare entered suppressed value with data value
|
|
170
|
+
const isValueMatch = String(pd.value) === String(bar.value) && pd.value !== ''
|
|
171
|
+
const isSuppressed = isValueMatch && selectedSuppressionColumn
|
|
206
172
|
|
|
207
|
-
|
|
173
|
+
if (!isSuppressed || pd.hideBarSymbol || !config.general.showSuppressedSymbol) {
|
|
174
|
+
return
|
|
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
|
+
return (
|
|
181
|
+
<Text // prettier-ignore
|
|
182
|
+
key={index}
|
|
183
|
+
fontSize={iconSize}
|
|
184
|
+
display={displayBar ? 'block' : 'none'}
|
|
185
|
+
opacity={transparentBar ? 0.5 : 1}
|
|
186
|
+
x={barX}
|
|
187
|
+
y={config.barHeight / 2 + config.barHeight * bar.index}
|
|
188
|
+
fill={'#000'}
|
|
189
|
+
dy={config.barHeight / 5}
|
|
190
|
+
dx={10}
|
|
191
|
+
textAnchor='start'
|
|
192
|
+
verticalAnchor={verticalAnchor}
|
|
193
|
+
>
|
|
194
|
+
{pd.iconCode}
|
|
195
|
+
</Text>
|
|
196
|
+
)
|
|
197
|
+
})}
|
|
198
|
+
|
|
199
|
+
{!config.isLollipopChart && (
|
|
208
200
|
<Text // prettier-ignore
|
|
209
201
|
display={displayBar ? 'block' : 'none'}
|
|
210
202
|
x={bar.y}
|
|
203
|
+
opacity={transparentBar ? 0.5 : 1}
|
|
211
204
|
y={config.barHeight / 2 + config.barHeight * bar.index}
|
|
212
205
|
fill={labelColor}
|
|
213
206
|
dx={textPadding}
|
|
214
207
|
verticalAnchor='middle'
|
|
215
208
|
textAnchor={textAnchor}
|
|
216
209
|
>
|
|
217
|
-
{
|
|
210
|
+
{testZeroValue(bar.value) ? '' : barDefaultLabel}
|
|
218
211
|
</Text>
|
|
219
212
|
)}
|
|
220
|
-
|
|
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 && (
|
|
221
228
|
<Text // prettier-ignore
|
|
222
229
|
display={displayBar ? 'block' : 'none'}
|
|
223
230
|
x={bar.y}
|
|
@@ -228,7 +235,7 @@ export const BarChartHorizontal = () => {
|
|
|
228
235
|
verticalAnchor='middle'
|
|
229
236
|
fontWeight={'normal'}
|
|
230
237
|
>
|
|
231
|
-
{
|
|
238
|
+
{testZeroValue(bar.value) ? '' : barDefaultLabel}
|
|
232
239
|
</Text>
|
|
233
240
|
)}
|
|
234
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
|
}
|