@cdc/chart 4.24.5 → 4.24.9

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