@cdc/chart 4.24.9 → 4.24.10

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 (65) hide show
  1. package/LICENSE +201 -0
  2. package/dist/cdcchart.js +43919 -40370
  3. package/index.html +1 -1
  4. package/package.json +2 -2
  5. package/src/CdcChart.tsx +129 -108
  6. package/src/_stories/Chart.Legend.Gradient.stories.tsx +33 -0
  7. package/src/_stories/Chart.stories.tsx +28 -0
  8. package/src/_stories/ChartAxisLabels.stories.tsx +20 -0
  9. package/src/_stories/ChartAxisTitles.stories.tsx +53 -0
  10. package/src/_stories/ChartPrefixSuffix.stories.tsx +151 -0
  11. package/src/_stories/_mock/horizontal_bar.json +257 -0
  12. package/src/_stories/_mock/large_x_axis_labels.json +261 -0
  13. package/src/_stories/_mock/paired-bar.json +262 -0
  14. package/src/_stories/_mock/pie_with_data.json +255 -0
  15. package/src/_stories/_mock/simplified_line.json +1510 -0
  16. package/src/components/Annotations/components/AnnotationDraggable.tsx +0 -3
  17. package/src/components/Annotations/components/AnnotationDropdown.tsx +1 -1
  18. package/src/components/Axis/Categorical.Axis.tsx +22 -4
  19. package/src/components/BarChart/components/BarChart.Horizontal.tsx +95 -16
  20. package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +41 -17
  21. package/src/components/BarChart/components/BarChart.Vertical.tsx +78 -20
  22. package/src/components/BarChart/helpers/index.ts +23 -4
  23. package/src/components/BrushChart.tsx +3 -2
  24. package/src/components/DeviationBar.jsx +58 -8
  25. package/src/components/EditorPanel/EditorPanel.tsx +63 -40
  26. package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +8 -25
  27. package/src/components/EditorPanel/components/Panels/Panel.General.tsx +21 -4
  28. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +297 -35
  29. package/src/components/EditorPanel/components/panels.scss +4 -6
  30. package/src/components/EditorPanel/editor-panel.scss +0 -8
  31. package/src/components/EditorPanel/helpers/tests/updateFieldRankByValue.test.ts +38 -0
  32. package/src/components/EditorPanel/helpers/updateFieldRankByValue.ts +42 -0
  33. package/src/components/EditorPanel/useEditorPermissions.ts +1 -0
  34. package/src/components/ForestPlot/ForestPlot.tsx +2 -3
  35. package/src/components/ForestPlot/ForestPlotProps.ts +2 -0
  36. package/src/components/Legend/Legend.Component.tsx +16 -16
  37. package/src/components/Legend/Legend.Suppression.tsx +25 -20
  38. package/src/components/Legend/Legend.tsx +0 -2
  39. package/src/components/Legend/helpers/index.ts +16 -19
  40. package/src/components/LegendWrapper.tsx +3 -1
  41. package/src/components/LineChart/components/LineChart.Circle.tsx +10 -0
  42. package/src/components/LinearChart.tsx +740 -562
  43. package/src/components/PairedBarChart.jsx +50 -10
  44. package/src/components/PieChart/PieChart.tsx +1 -6
  45. package/src/components/Regions/components/Regions.tsx +33 -19
  46. package/src/components/ZoomBrush.tsx +25 -6
  47. package/src/coreStyles_chart.scss +3 -0
  48. package/src/data/initial-state.js +6 -2
  49. package/src/helpers/configHelpers.ts +28 -0
  50. package/src/helpers/handleRankByValue.ts +15 -0
  51. package/src/helpers/sizeHelpers.ts +25 -0
  52. package/src/helpers/tests/handleRankByValue.test.ts +37 -0
  53. package/src/helpers/tests/sizeHelpers.test.ts +80 -0
  54. package/src/hooks/useColorPalette.js +10 -2
  55. package/src/hooks/useLegendClasses.ts +4 -0
  56. package/src/hooks/useScales.ts +31 -3
  57. package/src/hooks/useTooltip.tsx +9 -5
  58. package/src/index.jsx +1 -0
  59. package/src/scss/DataTable.scss +5 -4
  60. package/src/scss/main.scss +57 -52
  61. package/src/types/ChartConfig.ts +38 -16
  62. package/src/types/ChartContext.ts +18 -14
  63. package/src/_stories/Chart.Legend.Gradient.tsx +0 -19
  64. package/src/_stories/ChartBrush.stories.tsx +0 -19
  65. package/src/components/LinearChart.jsx +0 -817
@@ -32,11 +32,8 @@ const Annotations = ({ xScale, yScale, xScaleAnnotation, xMax, svgRef, onDragSta
32
32
  // prettier-ignore
33
33
  const {
34
34
  config,
35
- currentViewport,
36
35
  dimensions,
37
- isDraggingAnnotation,
38
36
  isEditor,
39
- isLegendBottom,
40
37
  updateConfig
41
38
  } = useContext(ConfigContext)
42
39
 
@@ -30,7 +30,7 @@ const AnnotationDropdown = () => {
30
30
  }
31
31
 
32
32
  const handleSectionClasses = () => {
33
- const classes = [`data-table-container`, viewport, `d-block`, `d-lg-none`]
33
+ const classes = [`data-table-container`, viewport, `d-block`, `d-lg-none`, `w-100`]
34
34
 
35
35
  if (config.general.showAnnotationDropdown) {
36
36
  classes.push('d-lg-block')
@@ -7,9 +7,10 @@ import ConfigContext from '../../ConfigContext'
7
7
  import chroma from 'chroma-js'
8
8
  import createBarElement from '@cdc/core/components/createBarElement'
9
9
  import { useBarChart } from '../../hooks/useBarChart'
10
+ import { getTextWidth } from '@cdc/core/helpers/getTextWidth'
10
11
 
11
12
  const CategoricalYAxis = ({ yMax, leftSize, max, xMax }) => {
12
- const { config, getTextWidth } = useContext(ConfigContext)
13
+ const { config } = useContext(ConfigContext)
13
14
  const { fontSize } = useBarChart()
14
15
 
15
16
  const { orientation } = config
@@ -82,7 +83,14 @@ const CategoricalYAxis = ({ yMax, leftSize, max, xMax }) => {
82
83
  const keys = Object.keys(transformedData[0])
83
84
  return (
84
85
  <Group left={leftSize - xScale.bandwidth()} top={0}>
85
- <BarStack data={transformedData} keys={keys} x={() => xScale(xScaleValue)} xScale={xScale} yScale={yScale} color={colorScale}>
86
+ <BarStack
87
+ data={transformedData}
88
+ keys={keys}
89
+ x={() => xScale(xScaleValue)}
90
+ xScale={xScale}
91
+ yScale={yScale}
92
+ color={colorScale}
93
+ >
86
94
  {barStacks =>
87
95
  barStacks.map(barStack =>
88
96
  barStack.bars.map(bar => {
@@ -96,7 +104,11 @@ const CategoricalYAxis = ({ yMax, leftSize, max, xMax }) => {
96
104
  </li></ul>`
97
105
  return (
98
106
  <Group key={`${barStack.index}--${bar.index}--${orientation}`}>
99
- <Group key={`bar-stack-${barStack.index}-${bar.index}`} id={`barStack${barStack.index}-${bar.index}`} className='stack vertical'>
107
+ <Group
108
+ key={`bar-stack-${barStack.index}-${bar.index}`}
109
+ id={`barStack${barStack.index}-${bar.index}`}
110
+ className='stack vertical'
111
+ >
100
112
  {createBarElement({
101
113
  type: 'axisBar',
102
114
  config: config,
@@ -126,7 +138,13 @@ const CategoricalYAxis = ({ yMax, leftSize, max, xMax }) => {
126
138
  {bar.key}
127
139
  </Text>
128
140
  {/* gridLines */}
129
- {config.runtime.yAxis.gridLines && <Line from={{ x: bar.x + xScale.bandwidth(), y: bar.y }} to={{ x: xMax + xScale.bandwidth(), y: bar.y }} stroke='rgba(0,0,0,0.3)' />}
141
+ {config.runtime.yAxis.gridLines && (
142
+ <Line
143
+ from={{ x: bar.x + xScale.bandwidth(), y: bar.y }}
144
+ to={{ x: xMax + xScale.bandwidth(), y: bar.y }}
145
+ stroke='#d6d6d6'
146
+ />
147
+ )}
130
148
  {/* White background spacing between stackes */}
131
149
  {!isLastIndex && <rect x={bar.x} y={bar.y} width={bar.width} height={1} fill={'#fff'}></rect>}
132
150
  {/* Right side Axis line */}
@@ -14,6 +14,7 @@ import { BarGroup } from '@visx/shape'
14
14
  import { getContrastColor } from '@cdc/core/helpers/cove/accessibility'
15
15
  import createBarElement from '@cdc/core/components/createBarElement'
16
16
  import { getBarConfig, testZeroValue } from '../helpers'
17
+ import { getTextWidth } from '@cdc/core/helpers/getTextWidth'
17
18
 
18
19
  // Third party libraries
19
20
  import chroma from 'chroma-js'
@@ -24,9 +25,38 @@ import { ChartContext } from '../../../types/ChartContext'
24
25
 
25
26
  export const BarChartHorizontal = () => {
26
27
  const { xScale, yScale, yMax, seriesScale } = useContext<BarChartContextValues>(BarChartContext)
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()
28
+ const {
29
+ transformedData: data,
30
+ tableData,
31
+ colorScale,
32
+ seriesHighlight,
33
+ config,
34
+ formatNumber,
35
+ formatDate,
36
+ parseDate,
37
+ setSharedFilter,
38
+ isNumber,
39
+ getYAxisData,
40
+ getXAxisData
41
+ } = useContext<ChartContext>(ConfigContext)
42
+ const {
43
+ isHorizontal,
44
+ barBorderWidth,
45
+ updateBars,
46
+ assignColorsToValues,
47
+ section,
48
+ fontSize,
49
+ isLabelBelowBar,
50
+ displayNumbersOnBar,
51
+ lollipopBarWidth,
52
+ lollipopShapeSize,
53
+ getHighlightedBarColorByValue,
54
+ getHighlightedBarByValue,
55
+ getAdditionalColumn,
56
+ hoveredBar,
57
+ onMouseLeaveBar,
58
+ onMouseOverBar
59
+ } = useBarChart()
30
60
 
31
61
  const { HighLightedBarUtils } = useHighlightedBars(config)
32
62
 
@@ -49,13 +79,29 @@ export const BarChartHorizontal = () => {
49
79
  >
50
80
  {barGroups => {
51
81
  return updateBars(barGroups).map((barGroup, index) => (
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}>
82
+ <Group
83
+ className={`bar-group-${barGroup.index}-${barGroup.x0}--${index} ${config.orientation}`}
84
+ key={`bar-group-${barGroup.index}-${barGroup.x0}--${index}`}
85
+ id={`bar-group-${barGroup.index}-${barGroup.x0}--${index}`}
86
+ top={barGroup.y}
87
+ >
53
88
  {barGroup.bars.map((bar, index) => {
54
89
  const scaleVal = config.yAxis.type === 'logarithmic' ? 0.1 : 0
55
- let highlightedBarValues = config.highlightedBarValues.map(item => item.value).filter(item => item !== ('' || undefined))
56
- highlightedBarValues = config.xAxis.type === 'date' ? HighLightedBarUtils.formatDates(highlightedBarValues) : highlightedBarValues
57
- let transparentBar = config.legend.behavior === 'highlight' && seriesHighlight.length > 0 && seriesHighlight.indexOf(bar.key) === -1
58
- let displayBar = config.legend.behavior === 'highlight' || seriesHighlight.length === 0 || seriesHighlight.indexOf(bar.key) !== -1
90
+ let highlightedBarValues = config.highlightedBarValues
91
+ .map(item => item.value)
92
+ .filter(item => item !== ('' || undefined))
93
+ highlightedBarValues =
94
+ config.xAxis.type === 'date'
95
+ ? HighLightedBarUtils.formatDates(highlightedBarValues)
96
+ : highlightedBarValues
97
+ let transparentBar =
98
+ config.legend.behavior === 'highlight' &&
99
+ seriesHighlight.length > 0 &&
100
+ seriesHighlight.indexOf(bar.key) === -1
101
+ let displayBar =
102
+ config.legend.behavior === 'highlight' ||
103
+ seriesHighlight.length === 0 ||
104
+ seriesHighlight.indexOf(bar.key) !== -1
59
105
  let barHeight = config.barHeight
60
106
  let numbericBarHeight = parseInt(!config.isLollipopChart ? barHeight : lollipopBarWidth)
61
107
  if (isNaN(numbericBarHeight)) {
@@ -65,10 +111,17 @@ export const BarChartHorizontal = () => {
65
111
  const defaultBarWidth = Math.abs(xScale(bar.value) - xScale(scaleVal))
66
112
  const isPositiveBar = bar.value >= 0 && isNumber(bar.value)
67
113
 
68
- const { barWidthHorizontal: barWidth, isSuppressed, getAbsentDataLabel } = getBarConfig({ bar, defaultBarWidth, config, isNumber, getTextWidth, isVertical: false })
114
+ const {
115
+ barWidthHorizontal: barWidth,
116
+ isSuppressed,
117
+ getAbsentDataLabel
118
+ } = getBarConfig({ bar, defaultBarWidth, config, isNumber, isVertical: false })
69
119
  const barX = bar.value < 0 ? Math.abs(xScale(bar.value)) : xScale(scaleVal)
70
120
  const yAxisValue = formatNumber(bar.value, 'left')
71
- const xAxisValue = config.runtime[section].type === 'date' ? formatDate(parseDate(data[barGroup.index][config.runtime.originalXAxis.dataKey])) : data[barGroup.index][config.runtime.originalXAxis.dataKey]
121
+ const xAxisValue =
122
+ config.runtime[section].type === 'date'
123
+ ? formatDate(parseDate(data[barGroup.index][config.runtime.originalXAxis.dataKey]))
124
+ : data[barGroup.index][config.runtime.originalXAxis.dataKey]
72
125
 
73
126
  const barPosition = !isPositiveBar ? 'below' : 'above'
74
127
  const absentDataLabel = getAbsentDataLabel(yAxisValue)
@@ -96,7 +149,9 @@ export const BarChartHorizontal = () => {
96
149
  // create new Index for bars with negative values
97
150
  const newIndex = bar.value < 0 ? -1 : index
98
151
 
99
- let yAxisTooltip = config.runtime.yAxis.label ? `${config.runtime.yAxis.label}: ${xAxisValue}` : xAxisValue
152
+ let yAxisTooltip = config.runtime.yAxis.label
153
+ ? `${config.runtime.yAxis.label}: ${xAxisValue}`
154
+ : xAxisValue
100
155
  const additionalColTooltip = getAdditionalColumn(hoveredBar)
101
156
  const tooltipBody = `${config.runtime.seriesLabels[bar.key]}: ${yAxisValue}`
102
157
  const tooltip = `<ul>
@@ -108,15 +163,28 @@ export const BarChartHorizontal = () => {
108
163
  // configure colors
109
164
  let labelColor = '#000000'
110
165
  labelColor = HighLightedBarUtils.checkFontColor(yAxisValue, highlightedBarValues, labelColor) // Set if background is transparent'
111
- let barColor = config.runtime.seriesLabels && config.runtime.seriesLabels[bar.key] ? colorScale(config.runtime.seriesLabels[bar.key]) : colorScale(bar.key)
166
+ let barColor =
167
+ config.runtime.seriesLabels && config.runtime.seriesLabels[bar.key]
168
+ ? colorScale(config.runtime.seriesLabels[bar.key])
169
+ : colorScale(bar.key)
112
170
  barColor = assignColorsToValues(barGroups.length, barGroup.index, barColor) // Color code by category
113
171
  const isRegularLollipopColor = config.isLollipopChart && config.lollipopColorStyle === 'regular'
114
172
  const isTwoToneLollipopColor = config.isLollipopChart && config.lollipopColorStyle === 'two-tone'
115
173
  const isHighlightedBar = highlightedBarValues?.includes(xAxisValue)
116
174
  const highlightedBarColor = getHighlightedBarColorByValue(xAxisValue)
117
175
  const highlightedBar = getHighlightedBarByValue(xAxisValue)
118
- const borderColor = isHighlightedBar ? highlightedBarColor : config.barHasBorder === 'true' ? '#000' : 'transparent'
119
- const borderWidth = isHighlightedBar ? highlightedBar.borderWidth : config.isLollipopChart ? 0 : config.barHasBorder === 'true' ? barBorderWidth : 0
176
+ const borderColor = isHighlightedBar
177
+ ? highlightedBarColor
178
+ : config.barHasBorder === 'true'
179
+ ? '#000'
180
+ : 'transparent'
181
+ const borderWidth = isHighlightedBar
182
+ ? highlightedBar.borderWidth
183
+ : config.isLollipopChart
184
+ ? 0
185
+ : config.barHasBorder === 'true'
186
+ ? barBorderWidth
187
+ : 0
120
188
  const displaylollipopShape = testZeroValue(bar.value) ? 'none' : 'block'
121
189
 
122
190
  // update label color
@@ -176,7 +244,12 @@ export const BarChartHorizontal = () => {
176
244
 
177
245
  const hasAsterisk = String(pd.symbol).includes('Asterisk')
178
246
  const verticalAnchor = hasAsterisk ? 'middle' : 'end'
179
- const iconSize = pd.symbol === 'Asterisk' ? barHeight * 1.2 : pd.symbol === 'Double Asterisk' ? barHeight : barHeight / 1.5
247
+ const iconSize =
248
+ pd.symbol === 'Asterisk'
249
+ ? barHeight * 1.2
250
+ : pd.symbol === 'Double Asterisk'
251
+ ? barHeight
252
+ : barHeight / 1.5
180
253
  const fillColor = pd.displayGray ? '#8b8b8a' : '#000'
181
254
  return (
182
255
  <Text // prettier-ignore
@@ -240,7 +313,13 @@ export const BarChartHorizontal = () => {
240
313
  </Text>
241
314
  )}
242
315
  {isLabelBelowBar && !config.yAxis.hideLabel && (
243
- <Text x={config.yAxis.hideAxis ? 0 : 5} y={barGroup.height} dy={4} verticalAnchor={'start'} textAnchor={'start'}>
316
+ <Text
317
+ x={config.yAxis.hideAxis ? 0 : 5}
318
+ y={barGroup.height}
319
+ dy={4}
320
+ verticalAnchor={'start'}
321
+ textAnchor={'start'}
322
+ >
244
323
  {config.runtime.yAxis.type === 'date'
245
324
  ? formatDate(parseDate(data[barGroup.index][config.runtime.originalXAxis.dataKey]))
246
325
  : isHorizontal
@@ -5,6 +5,7 @@ import { BarStackHorizontal } from '@visx/shape'
5
5
  import { Group } from '@visx/group'
6
6
  import { Text } from '@visx/text'
7
7
  import { getContrastColor } from '@cdc/core/helpers/cove/accessibility'
8
+ import { getTextWidth } from '@cdc/core/helpers/getTextWidth'
8
9
 
9
10
  // types
10
11
  import BarChartContext, { type BarChartContextValues } from './context'
@@ -22,7 +23,6 @@ const BarChartStackedHorizontal = () => {
22
23
  config,
23
24
  formatDate,
24
25
  formatNumber,
25
- getTextWidth,
26
26
  parseDate,
27
27
  seriesHighlight,
28
28
  setSharedFilter,
@@ -37,18 +37,38 @@ const BarChartStackedHorizontal = () => {
37
37
  config.visualizationSubType === 'stacked' &&
38
38
  isHorizontal && (
39
39
  <>
40
- <BarStackHorizontal data={data} keys={barStackedSeriesKeys} height={yMax} y={d => d[config.runtime.yAxis.dataKey]} xScale={xScale} yScale={yScale} color={colorScale} offset='none'>
40
+ <BarStackHorizontal
41
+ data={data}
42
+ keys={barStackedSeriesKeys}
43
+ height={yMax}
44
+ y={d => d[config.runtime.yAxis.dataKey]}
45
+ xScale={xScale}
46
+ yScale={yScale}
47
+ color={colorScale}
48
+ offset='none'
49
+ >
41
50
  {barStacks =>
42
51
  barStacks.map(barStack =>
43
52
  updateBars(barStack.bars).map((bar, index) => {
44
- const transparentBar = config.legend.behavior === 'highlight' && seriesHighlight.length > 0 && seriesHighlight.indexOf(bar.key) === -1
45
- const displayBar = config.legend.behavior === 'highlight' || seriesHighlight.length === 0 || seriesHighlight.indexOf(bar.key) !== -1
53
+ const transparentBar =
54
+ config.legend.behavior === 'highlight' &&
55
+ seriesHighlight.length > 0 &&
56
+ seriesHighlight.indexOf(bar.key) === -1
57
+ const displayBar =
58
+ config.legend.behavior === 'highlight' ||
59
+ seriesHighlight.length === 0 ||
60
+ seriesHighlight.indexOf(bar.key) !== -1
46
61
  config.barHeight = Number(config.barHeight)
47
62
  const labelColor = getContrastColor('#000', colorScale(config.runtime.seriesLabels[bar.key]))
48
63
  // tooltips
49
64
  const xAxisValue = formatNumber(data[bar.index][bar.key], 'left')
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
- const yAxisTooltip = config.runtime.yAxis.label ? `${config.runtime.yAxis.label}: ${yAxisValue}` : yAxisValue
65
+ const yAxisValue =
66
+ config.runtime.yAxis.type === 'date'
67
+ ? formatDate(parseDate(data[bar.index][config.runtime.originalXAxis.dataKey]))
68
+ : data[bar.index][config.runtime.originalXAxis.dataKey]
69
+ const yAxisTooltip = config.runtime.yAxis.label
70
+ ? `${config.runtime.yAxis.label}: ${yAxisValue}`
71
+ : yAxisValue
52
72
  const textWidth = getTextWidth(xAxisValue, `normal ${fontSize[config.fontSize]}px sans-serif`)
53
73
  const additionalColTooltip = getAdditionalColumn(hoveredBar)
54
74
  const tooltipBody = `${config.runtime.seriesLabels[bar.key]}: ${xAxisValue}`
@@ -93,17 +113,21 @@ const BarChartStackedHorizontal = () => {
93
113
  }
94
114
  })}
95
115
 
96
- {orientation === 'horizontal' && visualizationSubType === 'stacked' && isLabelBelowBar && barStack.index === 0 && !config.yAxis.hideLabel && (
97
- <Text
98
- x={`${bar.x + (config.isLollipopChart ? 15 : 5)}`} // padding
99
- y={bar.y + bar.height * 1.2}
100
- fill={'#000000'}
101
- textAnchor='start'
102
- verticalAnchor='start'
103
- >
104
- {yAxisValue}
105
- </Text>
106
- )}
116
+ {orientation === 'horizontal' &&
117
+ visualizationSubType === 'stacked' &&
118
+ isLabelBelowBar &&
119
+ barStack.index === 0 &&
120
+ !config.yAxis.hideLabel && (
121
+ <Text
122
+ x={`${bar.x + (config.isLollipopChart ? 15 : 5)}`} // padding
123
+ y={bar.y + bar.height * 1.2}
124
+ fill={'#000000'}
125
+ textAnchor='start'
126
+ verticalAnchor='start'
127
+ >
128
+ {yAxisValue}
129
+ </Text>
130
+ )}
107
131
 
108
132
  {displayNumbersOnBar && textWidth < bar.width && (
109
133
  <Text
@@ -42,7 +42,7 @@ export const BarChartVertical = () => {
42
42
  } = useBarChart()
43
43
 
44
44
  // prettier-ignore
45
- const { colorScale, config, dashboardConfig, tableData, formatDate, formatNumber, getXAxisData, getYAxisData, isNumber, parseDate, seriesHighlight, setSharedFilter, transformedData, brushConfig, getTextWidth } = useContext<ChartContext>(ConfigContext)
45
+ const { colorScale, config, dashboardConfig, tableData, formatDate, formatNumber, getXAxisData, getYAxisData, isNumber, parseDate, seriesHighlight, setSharedFilter, transformedData, brushConfig } = useContext<ChartContext>(ConfigContext)
46
46
  const { HighLightedBarUtils } = useHighlightedBars(config)
47
47
  let data = transformedData
48
48
  // check if user add suppression
@@ -58,7 +58,9 @@ export const BarChartVertical = () => {
58
58
 
59
59
  return (
60
60
  config.visualizationSubType !== 'stacked' &&
61
- (config.visualizationType === 'Bar' || config.visualizationType === 'Combo' || isConvertLineToBarGraph(config.visualizationType, data, config.allowLineToBarGraph)) &&
61
+ (config.visualizationType === 'Bar' ||
62
+ config.visualizationType === 'Combo' ||
63
+ isConvertLineToBarGraph(config.visualizationType, data, config.allowLineToBarGraph)) &&
62
64
  config.orientation === 'vertical' && (
63
65
  <Group>
64
66
  <BarGroup
@@ -86,27 +88,48 @@ export const BarChartVertical = () => {
86
88
  >
87
89
  {barGroup.bars.map((bar, index) => {
88
90
  const scaleVal = config.yAxis.type === 'logarithmic' ? 0.1 : 0
89
- let highlightedBarValues = config.highlightedBarValues.map(item => item.value).filter(item => item !== ('' || undefined))
90
- highlightedBarValues = config.xAxis.type === 'date' ? HighLightedBarUtils.formatDates(highlightedBarValues) : highlightedBarValues
91
- const transparentBar = config.legend.behavior === 'highlight' && seriesHighlight.length > 0 && seriesHighlight.indexOf(bar.key) === -1
92
- const displayBar = config.legend.behavior === 'highlight' || seriesHighlight.length === 0 || seriesHighlight.indexOf(bar.key) !== -1
91
+ let highlightedBarValues = config.highlightedBarValues
92
+ .map(item => item.value)
93
+ .filter(item => item !== ('' || undefined))
94
+ highlightedBarValues =
95
+ config.xAxis.type === 'date'
96
+ ? HighLightedBarUtils.formatDates(highlightedBarValues)
97
+ : highlightedBarValues
98
+ const transparentBar =
99
+ config.legend.behavior === 'highlight' &&
100
+ seriesHighlight.length > 0 &&
101
+ seriesHighlight.indexOf(bar.key) === -1
102
+ const displayBar =
103
+ config.legend.behavior === 'highlight' ||
104
+ seriesHighlight.length === 0 ||
105
+ seriesHighlight.indexOf(bar.key) !== -1
93
106
 
94
107
  let barGroupWidth = seriesScale.range()[1] - seriesScale.range()[0]
95
108
  const defaultBarHeight = Math.abs(yScale(bar.value) - yScale(scaleVal))
96
109
  const defaultBarY = bar.value >= 0 && isNumber(bar.value) ? bar.y : yScale(0)
97
110
  let barWidth = config.isLollipopChart ? lollipopBarWidth : seriesScale.bandwidth()
98
- let barX = bar.x + (config.isLollipopChart ? (barGroupWidth / barGroup.bars.length - lollipopBarWidth) / 2 : 0) - (config.xAxis.type === 'date-time' ? barGroupWidth / 2 : 0)
111
+ let barX =
112
+ bar.x +
113
+ (config.isLollipopChart ? (barGroupWidth / barGroup.bars.length - lollipopBarWidth) / 2 : 0) -
114
+ (config.xAxis.type === 'date-time' ? barGroupWidth / 2 : 0)
99
115
  setBarWidth(barWidth)
100
116
  setTotalBarsInGroup(barGroup.bars.length)
101
117
  const yAxisValue = formatNumber(/[a-zA-Z]/.test(String(bar.value)) ? '' : bar.value, 'left')
102
118
  const xAxisValue =
103
- config.runtime[section].type === 'date' ? formatDate(parseDate(data[barGroup.index][config.runtime.originalXAxis.dataKey])) : data[barGroup.index][config.runtime.originalXAxis.dataKey]
119
+ config.runtime[section].type === 'date'
120
+ ? formatDate(parseDate(data[barGroup.index][config.runtime.originalXAxis.dataKey]))
121
+ : data[barGroup.index][config.runtime.originalXAxis.dataKey]
104
122
 
105
123
  // create new Index for bars with negative values
106
124
  const newIndex = bar.value < 0 ? -1 : index
107
125
  // tooltips
108
- const additionalColTooltip = getAdditionalColumn(bar.key, data[barGroup.index][config.runtime.originalXAxis.dataKey])
109
- let xAxisTooltip = config.runtime.xAxis.label ? `${config.runtime.xAxis.label}: ${xAxisValue}` : xAxisValue
126
+ const additionalColTooltip = getAdditionalColumn(
127
+ bar.key,
128
+ data[barGroup.index][config.runtime.originalXAxis.dataKey]
129
+ )
130
+ let xAxisTooltip = config.runtime.xAxis.label
131
+ ? `${config.runtime.xAxis.label}: ${xAxisValue}`
132
+ : xAxisValue
110
133
  const tooltipBody = `${config.runtime.seriesLabels[bar.key]}: ${yAxisValue}`
111
134
 
112
135
  const tooltip = `<ul>
@@ -118,16 +141,37 @@ export const BarChartVertical = () => {
118
141
  // configure colors
119
142
  let labelColor = '#000000'
120
143
  labelColor = HighLightedBarUtils.checkFontColor(yAxisValue, highlightedBarValues, labelColor) // Set if background is transparent'
121
- let barColor = config.runtime.seriesLabels && config.runtime.seriesLabels[bar.key] ? colorScale(config.runtime.seriesLabels[bar.key]) : colorScale(bar.key)
144
+ let barColor =
145
+ config.runtime.seriesLabels && config.runtime.seriesLabels[bar.key]
146
+ ? colorScale(config.runtime.seriesLabels[bar.key])
147
+ : colorScale(bar.key)
122
148
  const isRegularLollipopColor = config.isLollipopChart && config.lollipopColorStyle === 'regular'
123
149
  const isTwoToneLollipopColor = config.isLollipopChart && config.lollipopColorStyle === 'two-tone'
124
150
  const isHighlightedBar = highlightedBarValues?.includes(xAxisValue)
125
151
  const highlightedBarColor = getHighlightedBarColorByValue(xAxisValue)
126
152
  const highlightedBar = getHighlightedBarByValue(xAxisValue)
127
- const borderColor = isHighlightedBar ? highlightedBarColor : config.barHasBorder === 'true' ? '#000' : 'transparent'
128
- const borderWidth = isHighlightedBar ? highlightedBar.borderWidth : config.isLollipopChart ? 0 : config.barHasBorder === 'true' ? barBorderWidth : 0
153
+ const borderColor = isHighlightedBar
154
+ ? highlightedBarColor
155
+ : config.barHasBorder === 'true'
156
+ ? '#000'
157
+ : 'transparent'
158
+ const borderWidth = isHighlightedBar
159
+ ? highlightedBar.borderWidth
160
+ : config.isLollipopChart
161
+ ? 0
162
+ : config.barHasBorder === 'true'
163
+ ? barBorderWidth
164
+ : 0
129
165
 
130
- const { barHeight, isSuppressed, getBarY, getAbsentDataLabel } = getBarConfig({ bar, defaultBarHeight, config, isNumber, getTextWidth, barWidth, isVertical: true, yAxisValue })
166
+ const { barHeight, isSuppressed, getBarY, getAbsentDataLabel } = getBarConfig({
167
+ bar,
168
+ defaultBarHeight,
169
+ config,
170
+ isNumber,
171
+ barWidth,
172
+ isVertical: true,
173
+ yAxisValue
174
+ })
131
175
 
132
176
  const absentDataLabel = getAbsentDataLabel(yAxisValue)
133
177
  const barDefaultLabel = isSuppressed || !config.labels ? '' : yAxisValue
@@ -149,9 +193,11 @@ export const BarChartVertical = () => {
149
193
  ? sharedFilters.map(_sharedFilter => {
150
194
  if (_sharedFilter.setBy === config.uid) {
151
195
  // If the current filter is the reset filter item.
152
- if (_sharedFilter.resetLabel === _sharedFilter.active) return colorScale(config.runtime.seriesLabels[bar.key])
196
+ if (_sharedFilter.resetLabel === _sharedFilter.active)
197
+ return colorScale(config.runtime.seriesLabels[bar.key])
153
198
  // If the current filter is the bars
154
- if (_sharedFilter.active === transformedData[barGroup.index][config.xAxis.dataKey]) return colorScale(config.runtime.seriesLabels[bar.key])
199
+ if (_sharedFilter.active === transformedData[barGroup.index][config.xAxis.dataKey])
200
+ return colorScale(config.runtime.seriesLabels[bar.key])
155
201
  return _filteredOutColor
156
202
  } else {
157
203
  // If the setBy isn't the config.uid return the original barColor
@@ -163,14 +209,16 @@ export const BarChartVertical = () => {
163
209
  if (isRegularLollipopColor) _barColor = barColor
164
210
 
165
211
  if (isHighlightedBar) _barColor = 'transparent'
166
- if (config.legend.colorCode) _barColor = assignColorsToValues(barGroups.length, barGroup.index, barColor)
212
+ if (config.legend.colorCode)
213
+ _barColor = assignColorsToValues(barGroups.length, barGroup.index, barColor)
167
214
  if (isTwoToneLollipopColor) _barColor = chroma(barColor).brighten(1)
168
215
  return _barColor
169
216
  }
170
217
 
171
218
  // if this is a two tone lollipop slightly lighten the bar.
172
219
  if (isTwoToneLollipopColor) _barColor = chroma(barColor).brighten(1)
173
- if (config.legend.colorCode) _barColor = assignColorsToValues(barGroups.length, barGroup.index, barColor)
220
+ if (config.legend.colorCode)
221
+ _barColor = assignColorsToValues(barGroups.length, barGroup.index, barColor)
174
222
 
175
223
  // if we're highlighting a bar make it invisible since it gets a border
176
224
  if (isHighlightedBar) _barColor = 'transparent'
@@ -217,13 +265,23 @@ export const BarChartVertical = () => {
217
265
  const isValueMatch = String(pd.value) === String(bar.value) && pd.value !== ''
218
266
  const isSuppressed = isValueMatch && selectedSuppressionColumn
219
267
 
220
- if (!isSuppressed || barWidth < 10 || !config.general.showSuppressedSymbol || pd.hideBarSymbol) {
268
+ if (
269
+ !isSuppressed ||
270
+ barWidth < 10 ||
271
+ !config.general.showSuppressedSymbol ||
272
+ pd.hideBarSymbol
273
+ ) {
221
274
  return
222
275
  }
223
276
  const hasAsterisk = String(pd.symbol).includes('Asterisk')
224
277
  const yPadding = hasAsterisk ? -5 : -8
225
278
  const verticalAnchor = hasAsterisk ? 'middle' : 'end'
226
- const iconSize = pd.symbol === 'Asterisk' ? barWidth * 1.2 : pd.symbol === 'Double Asterisk' ? barWidth : barWidth / 1.5
279
+ const iconSize =
280
+ pd.symbol === 'Asterisk'
281
+ ? barWidth * 1.2
282
+ : pd.symbol === 'Double Asterisk'
283
+ ? barWidth
284
+ : barWidth / 1.5
227
285
  const fillColor = pd.displayGray ? '#8b8b8a' : '#000'
228
286
 
229
287
  return (
@@ -1,3 +1,5 @@
1
+ import { getTextWidth } from '@cdc/core/helpers/getTextWidth'
2
+
1
3
  // Define an interface for the function's parameter
2
4
  interface BarConfigProps {
3
5
  defaultBarWidth?: number
@@ -5,13 +7,20 @@ interface BarConfigProps {
5
7
  bar?: { [key: string]: any }
6
8
  isNumber?: Function
7
9
  config: { [key: string]: any }
8
- getTextWidth: (a: string, b: string) => string
9
10
  barWidth: number
10
11
  isVertical: boolean
11
12
  }
12
13
 
13
14
  // 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
+ export const getBarConfig = ({
16
+ bar,
17
+ defaultBarHeight,
18
+ defaultBarWidth,
19
+ config,
20
+ isNumber,
21
+ barWidth,
22
+ isVertical
23
+ }: BarConfigProps) => {
15
24
  const heightMini = 3 /// height of small bars aka suppressed/NA/Zero valued
16
25
  let barHeight = defaultBarHeight
17
26
 
@@ -20,6 +29,7 @@ export const getBarConfig = ({ bar, defaultBarHeight, defaultBarWidth, config, i
20
29
  let barLabel = ''
21
30
  let isSuppressed = false
22
31
  let showMissingDataLabel = false
32
+ let showZeroValueData = false
23
33
  const showSuppressedSymbol = config.general.showSuppressedSymbol
24
34
 
25
35
  config.preliminaryData.forEach(pd => {
@@ -45,10 +55,19 @@ export const getBarConfig = ({ bar, defaultBarHeight, defaultBarWidth, config, i
45
55
  barHeight = labelFits ? heightMini : 0
46
56
  barWidthHorizontal = heightMini
47
57
  }
58
+ // Handle undefined, null, or non-calculable bar.value
59
+
60
+ if (!isSuppressed && bar.value === '0' && config.general.showZeroValueData) {
61
+ const labelWidth = getTextWidth('0', `normal ${barWidth / 2}px sans-serif`)
62
+ const labelFits = Number(labelWidth) < barWidth && barWidth > 10
63
+ showZeroValueData = true
64
+ barHeight = labelFits ? heightMini : 0
65
+ barWidthHorizontal = heightMini
66
+ }
48
67
 
49
68
  const getBarY = (defaultBarY, yScale) => {
50
69
  // calculate Y position of small bars (suppressed,N/A,Zero valued) bars
51
- if (isSuppressed || showMissingDataLabel) {
70
+ if (isSuppressed || showMissingDataLabel || showZeroValueData) {
52
71
  if (config.isLollipopChart) {
53
72
  return yScale - heightMini * 2
54
73
  } else {
@@ -68,6 +87,7 @@ export const getBarConfig = ({ bar, defaultBarHeight, defaultBarWidth, config, i
68
87
  if (isSuppressed) label = ''
69
88
  // If the config is set to show a label for missing data, display 'N/A'
70
89
  if (showMissingDataLabel) label = 'N/A'
90
+ if (showZeroValueData) label = '0'
71
91
 
72
92
  // determine label width in pixels & check if it fits to the bar width
73
93
  const labelWidth = getTextWidth(barLabel, `normal ${barWidth / 2}px sans-serif`)
@@ -78,7 +98,6 @@ export const getBarConfig = ({ bar, defaultBarHeight, defaultBarWidth, config, i
78
98
  return labelFits && isVertical ? label : !isVertical ? label : ''
79
99
  }
80
100
  }
81
-
82
101
  return { barWidthHorizontal, barHeight, isSuppressed, showMissingDataLabel, getBarY, getAbsentDataLabel }
83
102
  }
84
103
 
@@ -2,15 +2,16 @@ import { Group } from '@visx/group'
2
2
  import { useContext, useEffect, useRef, useState } from 'react'
3
3
  import ConfigContext from '../ConfigContext'
4
4
  import * as d3 from 'd3'
5
- import { invertValue } from '@cdc/core/helpers/scaling'
6
5
  import { Text } from '@visx/text'
6
+ import { getTextWidth } from '@cdc/core/helpers/getTextWidth'
7
+
7
8
  interface BrushChartProps {
8
9
  xMax: number
9
10
  yMax: number
10
11
  }
11
12
 
12
13
  const BrushChart = ({ xMax, yMax }: BrushChartProps) => {
13
- const { tableData, config, setBrushConfig, getTextWidth, dashboardConfig, formatDate } = useContext(ConfigContext)
14
+ const { tableData, config, setBrushConfig, dashboardConfig, formatDate } = useContext(ConfigContext)
14
15
  const [brushState, setBrushState] = useState({ isBrushing: false, selection: [] })
15
16
  const [brushKey, setBrushKey] = useState(0)
16
17
  const sharedFilters = dashboardConfig?.dashboard?.sharedFilters ?? []