@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.
Files changed (68) hide show
  1. package/dist/cdcchart.js +39128 -35959
  2. package/examples/feature/annotations/index.json +542 -0
  3. package/examples/xaxis.json +493 -0
  4. package/index.html +5 -4
  5. package/package.json +5 -4
  6. package/src/CdcChart.tsx +104 -64
  7. package/src/_stories/Chart.stories.tsx +18 -171
  8. package/src/_stories/ChartAnnotation.stories.tsx +32 -0
  9. package/src/_stories/_mock/annotation_category_mock.json +473 -0
  10. package/src/_stories/_mock/annotation_date-linear_mock.json +530 -0
  11. package/src/_stories/_mock/annotation_date-time_mock.json +530 -0
  12. package/src/_stories/_mock/line_chart_two_points_new_chart.json +128 -0
  13. package/src/_stories/_mock/line_chart_two_points_regression_test.json +127 -0
  14. package/src/_stories/_mock/lollipop.json +171 -0
  15. package/src/components/Annotations/components/AnnotationDraggable.styles.css +31 -0
  16. package/src/components/Annotations/components/AnnotationDraggable.tsx +154 -0
  17. package/src/components/Annotations/components/AnnotationDropdown.styles.css +14 -0
  18. package/src/components/Annotations/components/AnnotationDropdown.tsx +72 -0
  19. package/src/components/Annotations/components/AnnotationList.styles.css +45 -0
  20. package/src/components/Annotations/components/AnnotationList.tsx +42 -0
  21. package/src/components/Annotations/components/findNearestDatum.ts +138 -0
  22. package/src/components/Annotations/components/helpers/index.tsx +46 -0
  23. package/src/components/Annotations/index.tsx +13 -0
  24. package/src/components/AreaChart/components/AreaChart.Stacked.jsx +1 -1
  25. package/src/components/AreaChart/components/AreaChart.jsx +1 -1
  26. package/src/components/BarChart/components/BarChart.Horizontal.tsx +44 -42
  27. package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +1 -2
  28. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +11 -11
  29. package/src/components/BarChart/components/BarChart.Vertical.tsx +50 -22
  30. package/src/components/BarChart/helpers/index.ts +102 -0
  31. package/src/components/EditorPanel/EditorPanel.tsx +232 -98
  32. package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +306 -0
  33. package/src/components/EditorPanel/components/Panels/Panel.General.tsx +117 -6
  34. package/src/components/EditorPanel/components/Panels/Panel.Sankey.tsx +2 -3
  35. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +3 -2
  36. package/src/components/EditorPanel/components/Panels/index.tsx +3 -1
  37. package/src/components/EditorPanel/components/panels.scss +4 -0
  38. package/src/components/EditorPanel/editor-panel.scss +19 -0
  39. package/src/components/EditorPanel/useEditorPermissions.js +19 -2
  40. package/src/components/Legend/Legend.Component.tsx +7 -8
  41. package/src/components/Legend/helpers/createFormatLabels.tsx +1 -1
  42. package/src/components/Legend/helpers/index.ts +5 -0
  43. package/src/components/LineChart/LineChartProps.ts +3 -0
  44. package/src/components/LineChart/helpers.ts +21 -7
  45. package/src/components/LineChart/index.tsx +7 -7
  46. package/src/components/LinearChart.jsx +179 -136
  47. package/src/components/PairedBarChart.jsx +9 -9
  48. package/src/components/PieChart/PieChart.tsx +4 -4
  49. package/src/components/Sankey/index.tsx +73 -20
  50. package/src/components/ScatterPlot/ScatterPlot.jsx +22 -8
  51. package/src/components/ZoomBrush.tsx +90 -44
  52. package/src/data/initial-state.js +14 -6
  53. package/src/helpers/handleChartTabbing.ts +8 -0
  54. package/src/helpers/isConvertLineToBarGraph.ts +4 -0
  55. package/src/hooks/{useBarChart.js → useBarChart.ts} +2 -40
  56. package/src/hooks/useColorScale.ts +1 -1
  57. package/src/hooks/useMinMax.ts +8 -3
  58. package/src/hooks/useScales.ts +25 -3
  59. package/src/hooks/useTooltip.tsx +58 -11
  60. package/src/scss/main.scss +21 -14
  61. package/src/types/ChartConfig.ts +50 -3
  62. package/src/types/ChartContext.ts +9 -0
  63. package/tests-examples/helpers/testZeroValue.test.ts +30 -0
  64. package/LICENSE +0 -201
  65. package/src/helpers/filterData.ts +0 -18
  66. package/src/helpers/tests/computeMarginBottom.test.ts +0 -21
  67. /package/src/hooks/{useLegendClasses.js → useLegendClasses.ts} +0 -0
  68. /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.active && config.brush.data?.length ? config.brush.data : transformedData
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.active && brushConfig.data?.length ? brushConfig.data : transformedData
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
- isHorizontal,
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 barWidthHorizontal = Math.abs(xScale(bar.value) - xScale(scaleVal))
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
- let barWidth = isSuppressed ? suppresedBarWidth : barWidthHorizontal
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 barValueLabel = isSuppressed ? '' : yAxisValue
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
- let textWidth = getTextWidth(xAxisValue, `normal ${fontSize[config.fontSize]}px sans-serif`)
96
- let textFits = Number(textWidth) < barWidthHorizontal - 5 // minus padding 5
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
- // if bars are negative we change positions of text
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 = isSuppressed ? 'none' : 'block'
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(tableData[barGroup.index][bar.key]) && pd.value !== ''
170
+ const isValueMatch = String(pd.value) === String(bar.value) && pd.value !== ''
189
171
  const isSuppressed = isValueMatch && selectedSuppressionColumn
190
- if (!isSuppressed || barHeight < 10 || !config.xAxis.showSuppressedSymbol) {
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={getIconSize(pd.symbol, barHeight)}
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
- dx={iconPadding(pd.symbol)}
204
- verticalAnchor={getVerticalAnchor(pd.symbol)}
205
- textAnchor={'middle'}
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 && displayNumbersOnBar && (
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
- {barValueLabel}
210
+ {testZeroValue(bar.value) ? '' : barDefaultLabel}
223
211
  </Text>
224
212
  )}
225
- {config.isLollipopChart && displayNumbersOnBar && (
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
- {barValueLabel}
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
- const data = config.brush.active && config.brush.data?.length ? config.brush.data : transformedData
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 = config.xAxis.type === 'date-time' ? config.barThickness * (xScale.range()[1] - xScale.range()[0]) : xMax / barStack.bars.length
32
- let barThicknessAdjusted = barThickness * (config.xAxis.type === 'date-time' ? 1 : config.barThickness || 0.8)
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 = config.runtime.xAxis.type === 'date' ? formatDate(parseDate(rawXValue)) : rawXValue
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(config.runtime.xAxis.type === 'date' ? parseDate(rawXValue) : rawXValue) - (config.xAxis.type === 'date' && config.xAxis.sortDates ? barThicknessAdjusted / 2 : 0)
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(barThicknessAdjusted)
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: barThicknessAdjusted,
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: `${barThicknessAdjusted / 2}px ${bar.y + bar.height}px`,
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 { assignColorsToValues, barBorderWidth, getAdditionalColumn, getHighlightedBarByValue, getHighlightedBarColorByValue, lollipopBarWidth, lollipopShapeSize, onMouseLeaveBar, onMouseOverBar, section, composeSuppressionBars, shouldSuppress } = useBarChart()
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, brushConfig, } = useContext<ChartContext>(ConfigContext)
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
- let barWidth = config.isLollipopChart ? lollipopBarWidth : barGroupWidth / barGroup.bars.length
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 displaylollipopShape = isSuppressed ? 'none' : 'block'
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
- let isSuppressed = isValueMatch && selectedSuppressionColumn
210
+ const isSuppressed = isValueMatch && selectedSuppressionColumn
198
211
 
199
- if (!isSuppressed || barWidth < 10 || !config.xAxis.showSuppressedSymbol) {
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={getIconPadding(pd.symbol)}
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={getVerticalAnchor(pd.symbol)}
228
+ verticalAnchor={verticalAnchor}
212
229
  fill={labelColor}
213
230
  textAnchor='middle'
214
- fontSize={`${getIconSize(pd.symbol, barWidth)}px`}
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={config.labels && displayBar ? 'block' : 'none'}
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
- {barValueLabel}
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' || config.xAxis.type !== 'date-time' ? seriesScale.range()[1] / 2 : 0)
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 (