@cdc/chart 4.24.4 → 4.24.7

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