@cdc/chart 4.25.7 → 4.25.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 (95) hide show
  1. package/.claude/settings.local.json +9 -0
  2. package/dist/cdcchart.js +39551 -37016
  3. package/examples/feature/__data__/planet-example-data.json +0 -30
  4. package/examples/grouped-bar-test.json +400 -0
  5. package/examples/private/d.json +382 -0
  6. package/examples/private/example-2.json +49784 -0
  7. package/examples/private/f2.json +1 -0
  8. package/examples/private/f4.json +1577 -0
  9. package/examples/private/forecast.json +1180 -0
  10. package/examples/private/lollipop.json +468 -0
  11. package/examples/private/new.json +48756 -0
  12. package/examples/private/pie-chart-legend.json +904 -0
  13. package/examples/suppressed_tooltip.json +480 -0
  14. package/index.html +10 -22
  15. package/package.json +25 -7
  16. package/src/CdcChart.tsx +10 -4
  17. package/src/CdcChartComponent.tsx +188 -32
  18. package/src/_stories/Chart.Anchors.stories.tsx +2 -2
  19. package/src/_stories/Chart.BoxPlot.stories.tsx +1 -1
  20. package/src/_stories/Chart.CI.stories.tsx +1 -1
  21. package/src/_stories/Chart.CustomColors.stories.tsx +1 -1
  22. package/src/_stories/Chart.DynamicSeries.stories.tsx +2 -2
  23. package/src/_stories/Chart.Filters.stories.tsx +2 -2
  24. package/src/_stories/Chart.Legend.Gradient.stories.tsx +2 -2
  25. package/src/_stories/Chart.Patterns.stories.tsx +19 -0
  26. package/src/_stories/Chart.ScatterPlot.stories.tsx +1 -1
  27. package/src/_stories/Chart.stories.tsx +8 -5
  28. package/src/_stories/Chart.tooltip.stories.tsx +1 -1
  29. package/src/_stories/ChartAnnotation.stories.tsx +1 -1
  30. package/src/_stories/ChartAxisLabels.stories.tsx +2 -2
  31. package/src/_stories/ChartAxisTitles.stories.tsx +2 -2
  32. package/src/_stories/ChartEditor.stories.tsx +60 -60
  33. package/src/_stories/ChartLine.Suppression.stories.tsx +1 -1
  34. package/src/_stories/ChartLine.Symbols.stories.tsx +1 -1
  35. package/src/_stories/ChartPrefixSuffix.stories.tsx +2 -2
  36. package/src/_stories/_mock/stacked-pattern-test.json +520 -0
  37. package/src/components/Annotations/components/AnnotationDraggable.tsx +1 -0
  38. package/src/components/Annotations/components/AnnotationDropdown.tsx +1 -1
  39. package/src/components/BarChart/components/BarChart.Horizontal.tsx +170 -25
  40. package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +139 -6
  41. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +215 -73
  42. package/src/components/BarChart/components/BarChart.Vertical.tsx +172 -23
  43. package/src/components/BarChart/helpers/index.ts +43 -4
  44. package/src/components/BarChart/helpers/lollipopColors.ts +27 -0
  45. package/src/components/BarChart/helpers/useBarChart.ts +25 -3
  46. package/src/components/BoxPlot/BoxPlot.Vertical.tsx +2 -1
  47. package/src/components/Brush/BrushChart.tsx +65 -10
  48. package/src/components/Brush/BrushController.tsx +37 -5
  49. package/src/components/Brush/types.tsx +8 -0
  50. package/src/components/DeviationBar.jsx +9 -6
  51. package/src/components/EditorPanel/EditorPanel.tsx +364 -39
  52. package/src/components/EditorPanel/EditorPanelContext.ts +3 -0
  53. package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +2 -2
  54. package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +414 -0
  55. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +30 -54
  56. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +115 -120
  57. package/src/components/EditorPanel/components/Panels/index.tsx +3 -1
  58. package/src/components/EditorPanel/components/Panels/panelVisual.styles.css +0 -8
  59. package/src/components/EditorPanel/helpers/updateFieldRankByValue.ts +49 -48
  60. package/src/components/Forecasting/Forecasting.tsx +36 -6
  61. package/src/components/ForestPlot/ForestPlot.tsx +11 -7
  62. package/src/components/ForestPlot/ForestPlotProps.ts +1 -1
  63. package/src/components/Legend/Legend.Component.tsx +110 -2
  64. package/src/components/Legend/Legend.tsx +3 -1
  65. package/src/components/Legend/helpers/createFormatLabels.tsx +230 -171
  66. package/src/components/LegendWrapper.tsx +1 -1
  67. package/src/components/LineChart/components/LineChart.BumpCircle.tsx +27 -26
  68. package/src/components/LineChart/components/LineChart.Circle.tsx +2 -2
  69. package/src/components/LineChart/index.tsx +2 -2
  70. package/src/components/LinearChart.tsx +26 -9
  71. package/src/components/PairedBarChart.jsx +6 -4
  72. package/src/components/PieChart/PieChart.tsx +170 -54
  73. package/src/components/Sankey/components/Sankey.tsx +7 -1
  74. package/src/components/ScatterPlot/ScatterPlot.jsx +32 -4
  75. package/src/data/initial-state.js +315 -292
  76. package/src/helpers/buildForecastPaletteMappings.ts +112 -0
  77. package/src/helpers/buildForecastPaletteOptions.ts +109 -0
  78. package/src/helpers/getColorScale.ts +72 -8
  79. package/src/helpers/getNewRuntime.ts +1 -1
  80. package/src/helpers/getTransformedData.ts +1 -1
  81. package/src/hooks/useChartHoverAnalytics.tsx +44 -0
  82. package/src/hooks/useReduceData.ts +105 -70
  83. package/src/hooks/useTooltip.tsx +58 -16
  84. package/src/index.jsx +6 -3
  85. package/src/scss/main.scss +12 -0
  86. package/src/store/chart.reducer.ts +1 -1
  87. package/src/test/CdcChart.test.jsx +8 -3
  88. package/src/types/ChartConfig.ts +30 -6
  89. package/src/types/ChartContext.ts +1 -0
  90. package/vite.config.js +1 -1
  91. package/vitest.config.ts +16 -0
  92. package/src/coreStyles_chart.scss +0 -3
  93. package/src/helpers/configHelpers.ts +0 -28
  94. package/src/helpers/generateColorsArray.ts +0 -8
  95. package/src/hooks/useColorPalette.js +0 -76
@@ -2,10 +2,10 @@ import React, { useContext, useState } from 'react'
2
2
  import ConfigContext from '../../../ConfigContext'
3
3
  import { BarStack } from '@visx/shape'
4
4
  import { Group } from '@visx/group'
5
- import { Text } from '@visx/text'
5
+ import { PatternLines, PatternCircles, PatternWaves } from '@visx/pattern'
6
6
  import BarChartContext from './context'
7
7
  import Regions from '../../Regions'
8
- import { isDateScale } from '@cdc/core/helpers/cove/date'
8
+ import { addMinimumBarHeights } from '../helpers'
9
9
 
10
10
  import createBarElement from '@cdc/core/components/createBarElement'
11
11
 
@@ -31,10 +31,78 @@ const BarChartStackedVertical = () => {
31
31
  const isDateAxisType = config.runtime.xAxis.type === 'date-time' || config.runtime.xAxis.type === 'date'
32
32
  const isDateTimeScaleAxisType = config.runtime.xAxis.type === 'date-time'
33
33
 
34
+ // Pattern helper function
35
+ const renderPatternDefs = () => {
36
+ if (!config.legend.patterns || Object.keys(config.legend.patterns).length === 0) {
37
+ return null
38
+ }
39
+
40
+ return (
41
+ <defs>
42
+ {Object.entries(config.legend.patterns).map(([key, pattern]) => {
43
+ const patternId = `chart-pattern-${key}`
44
+ const size = pattern.patternSize || 8
45
+
46
+ switch (pattern.shape) {
47
+ case 'circles':
48
+ return (
49
+ <PatternCircles
50
+ key={patternId}
51
+ id={patternId}
52
+ height={size}
53
+ width={size}
54
+ fill={pattern.color}
55
+ radius={1.25}
56
+ />
57
+ )
58
+ case 'lines':
59
+ return (
60
+ <PatternLines
61
+ key={patternId}
62
+ id={patternId}
63
+ height={size}
64
+ width={size}
65
+ stroke={pattern.color}
66
+ strokeWidth={0.75}
67
+ orientation={['horizontal']}
68
+ />
69
+ )
70
+ case 'diagonalLines':
71
+ return (
72
+ <PatternLines
73
+ key={patternId}
74
+ id={patternId}
75
+ height={size}
76
+ width={size}
77
+ stroke={pattern.color}
78
+ strokeWidth={0.75}
79
+ orientation={['diagonalRightToLeft']}
80
+ />
81
+ )
82
+ case 'waves':
83
+ return (
84
+ <PatternWaves
85
+ key={patternId}
86
+ id={patternId}
87
+ height={size}
88
+ width={size}
89
+ fill={pattern.color}
90
+ strokeWidth={0.25}
91
+ />
92
+ )
93
+ default:
94
+ return null
95
+ }
96
+ })}
97
+ </defs>
98
+ )
99
+ }
100
+
34
101
  return (
35
102
  config.visualizationSubType === 'stacked' &&
36
103
  !isHorizontal && (
37
104
  <>
105
+ {renderPatternDefs()}
38
106
  <BarStack
39
107
  data={data}
40
108
  keys={barStackedSeriesKeys}
@@ -43,87 +111,161 @@ const BarChartStackedVertical = () => {
43
111
  yScale={yScale}
44
112
  color={colorScale}
45
113
  >
46
- {barStacks =>
47
- barStacks.reverse().map(barStack =>
48
- barStack.bars.map(bar => {
49
- let transparentBar =
50
- config.legend.behavior === 'highlight' &&
51
- seriesHighlight.length > 0 &&
52
- seriesHighlight.indexOf(bar.key) === -1
53
- let displayBar =
54
- config.legend.behavior === 'highlight' ||
55
- seriesHighlight.length === 0 ||
56
- seriesHighlight.indexOf(bar.key) !== -1
57
- let barThickness = isDateAxisType
58
- ? seriesScale.range()[1] - seriesScale.range()[0]
59
- : xMax / barStack.bars.length
60
- if (config.runtime.xAxis.type !== 'date') barThickness = config.barThickness * barThickness
61
- // tooltips
62
- const rawXValue = bar.bar.data[config.runtime.xAxis.dataKey]
63
- const xAxisValue = isDateAxisType ? formatDate(parseDate(rawXValue)) : rawXValue
64
- const yAxisValue = formatNumber(bar.bar ? bar.bar.data[bar.key] : 0, 'left')
65
- if (!yAxisValue) return
66
- const barX =
67
- xScale(isDateAxisType ? parseDate(rawXValue) : rawXValue) -
68
- (isDateTimeScaleAxisType ? barThickness / 2 : 0)
69
- const xAxisTooltip = config.runtime.xAxis.label
70
- ? `${config.runtime.xAxis.label}: ${xAxisValue}`
71
- : xAxisValue
72
- const additionalColTooltip = getAdditionalColumn(bar.key, hoveredBar)
73
- const tooltipBody = `${config.runtime.seriesLabels[bar.key]}: ${yAxisValue}`
74
- const tooltip = `<ul>
114
+ {barStacks => {
115
+ return addMinimumBarHeights(barStacks)
116
+ .reverse()
117
+ .map(barStack => {
118
+ return barStack.bars.map(bar => {
119
+ let transparentBar =
120
+ config.legend.behavior === 'highlight' &&
121
+ seriesHighlight.length > 0 &&
122
+ seriesHighlight.indexOf(bar.key) === -1
123
+ let displayBar =
124
+ config.legend.behavior === 'highlight' ||
125
+ seriesHighlight.length === 0 ||
126
+ seriesHighlight.indexOf(bar.key) !== -1
127
+ let barThickness = isDateAxisType
128
+ ? seriesScale.range()[1] - seriesScale.range()[0]
129
+ : xMax / barStack.bars.length
130
+ if (config.runtime.xAxis.type !== 'date') barThickness = config.barThickness * barThickness
131
+ // tooltips
132
+ const rawXValue = bar.bar.data[config.runtime.xAxis.dataKey]
133
+ const xAxisValue = isDateAxisType ? formatDate(parseDate(rawXValue)) : rawXValue
134
+ const yAxisValue = formatNumber(bar.bar ? bar.bar.data[bar.key] : 0, 'left')
135
+ if (!yAxisValue) return
136
+ const barX =
137
+ xScale(isDateAxisType ? parseDate(rawXValue) : rawXValue) -
138
+ (isDateTimeScaleAxisType ? barThickness / 2 : 0)
139
+ const xAxisTooltip = config.runtime.xAxis.label
140
+ ? `${config.runtime.xAxis.label}: ${xAxisValue}`
141
+ : xAxisValue
142
+ const additionalColTooltip = getAdditionalColumn(bar.key, hoveredBar)
143
+ const tooltipBody = `${config.runtime.seriesLabels[bar.key]}: ${yAxisValue}`
144
+ const tooltip = `<ul>
75
145
  <li class="tooltip-heading"">${xAxisTooltip}</li>
76
146
  <li class="tooltip-body ">${tooltipBody}</li>
77
147
  <li class="tooltip-body ">${additionalColTooltip}</li>
78
148
  </li></ul>`
79
149
 
80
- setBarWidth(barThickness)
81
-
82
- return (
83
- <Group key={`${barStack.index}--${bar.index}--${orientation}`}>
84
- <Group
85
- key={`bar-stack-${barStack.index}-${bar.index}`}
86
- id={`barStack${barStack.index}-${bar.index}`}
87
- className='stack vertical'
88
- >
89
- {createBarElement({
90
- config: config,
91
- seriesHighlight,
92
- index: barStack.index,
93
- background: colorScale(config.runtime.seriesLabels[bar.key]),
94
- borderColor: '#333',
95
- borderStyle: 'solid',
96
- borderWidth: `${config.barHasBorder === 'true' ? barBorderWidth : 0}px`,
97
- width: barThickness,
98
- height: bar.height,
99
- x: barX,
100
- y: bar.y,
101
- onMouseOver: e => onMouseOverBar(xAxisValue, bar.key, e, data),
102
- onMouseLeave: onMouseLeaveBar,
103
- tooltipHtml: tooltip,
104
- tooltipId: `cdc-open-viz-tooltip-${config.runtime.uniqueId}`,
105
- onClick: e => {
106
- e.preventDefault()
107
- if (setSharedFilter) {
108
- bar[config.xAxis.dataKey] = xAxisValue
109
- setSharedFilter(config.uid, bar)
150
+ setBarWidth(barThickness)
151
+
152
+ // Check if this bar should use a pattern
153
+ const getPatternUrl = (): string | null => {
154
+ if (!config.legend.patterns || Object.keys(config.legend.patterns).length === 0) {
155
+ return null
156
+ }
157
+
158
+ // Find a pattern that matches this specific bar
159
+ for (const [patternKey, patternObj] of Object.entries(config.legend.patterns)) {
160
+ const pattern = patternObj as any
161
+ if (pattern?.dataKey && pattern?.dataValue) {
162
+ // For stacked bar charts, check if the pattern's dataKey matches the current bar's series key
163
+ // and if the pattern's dataValue matches the current bar's value
164
+ const barValue = bar.bar.data[bar.key]
165
+ if (pattern.dataKey === bar.key && String(barValue) === String(pattern.dataValue)) {
166
+ return `url(#chart-pattern-${patternKey})`
167
+ }
168
+ // Fallback for non-series pattern matching (like the original stacked pattern test)
169
+ // Only check this if the pattern dataKey is NOT a series key
170
+ else if (!config.runtime.seriesLabels || !config.runtime.seriesLabels[pattern.dataKey]) {
171
+ const dataFieldValue = bar.bar.data[pattern.dataKey]
172
+ if (String(dataFieldValue) === String(pattern.dataValue)) {
173
+ return `url(#chart-pattern-${patternKey})`
110
174
  }
111
- },
112
- styleOverrides: {
113
- animationDelay: `${barStack.index * 0.5}s`,
114
- transformOrigin: `${barThickness / 2}px ${bar.y + bar.height}px`,
115
- opacity: transparentBar ? 0.2 : 1,
116
- display: displayBar ? 'block' : 'none'
117
175
  }
118
- })}
176
+ }
177
+ }
178
+
179
+ return null
180
+ }
181
+
182
+ const patternUrl = getPatternUrl()
183
+
184
+ return (
185
+ <Group key={`${barStack.index}--${bar.index}--${orientation}`}>
186
+ <Group
187
+ key={`bar-stack-${barStack.index}-${bar.index}`}
188
+ id={`barStack${barStack.index}-${bar.index}`}
189
+ className='stack vertical'
190
+ >
191
+ {/* Base colored bar */}
192
+ {createBarElement({
193
+ config: config,
194
+ seriesHighlight,
195
+ index: barStack.index,
196
+ background: colorScale(config.runtime.seriesLabels[bar.key]),
197
+ borderColor: '#333',
198
+ borderStyle: 'solid',
199
+ borderWidth: `${config.barHasBorder === 'true' ? barBorderWidth : 0}px`,
200
+ width: barThickness,
201
+ height: bar.height,
202
+ x: barX,
203
+ y: bar.y,
204
+ onMouseOver: e => onMouseOverBar(xAxisValue, bar.key, e, data, bar.bar.data[bar.key]),
205
+ onMouseLeave: onMouseLeaveBar,
206
+ tooltipHtml: tooltip,
207
+ tooltipId: `cdc-open-viz-tooltip-${config.runtime.uniqueId}`,
208
+ onClick: e => {
209
+ e.preventDefault()
210
+ if (setSharedFilter) {
211
+ bar[config.xAxis.dataKey] = xAxisValue
212
+ setSharedFilter(config.uid, bar)
213
+ }
214
+ },
215
+ styleOverrides: {
216
+ animationDelay: `${barStack.index * 0.5}s`,
217
+ transformOrigin: `${barThickness / 2}px ${bar.y + bar.height}px`,
218
+ opacity: transparentBar ? 0.2 : 1,
219
+ display: displayBar ? 'block' : 'none'
220
+ }
221
+ })}
222
+
223
+ {/* Pattern overlay if pattern exists */}
224
+ {patternUrl &&
225
+ createBarElement({
226
+ config: config,
227
+ seriesHighlight,
228
+ index: barStack.index,
229
+ background: patternUrl, // Use pattern as background
230
+ borderColor: 'transparent',
231
+ borderStyle: 'none',
232
+ borderWidth: '0px',
233
+ width: barThickness,
234
+ height: bar.height,
235
+ x: barX,
236
+ y: bar.y,
237
+ onMouseOver: () => {}, // No interaction
238
+ onMouseLeave: () => {}, // No interaction
239
+ tooltipHtml: '',
240
+ tooltipId: '',
241
+ onClick: () => {}, // No interaction
242
+ styleOverrides: {
243
+ animationDelay: `${barStack.index * 0.5}s`,
244
+ transformOrigin: `${barThickness / 2}px ${bar.y + bar.height}px`,
245
+ opacity: transparentBar ? 0.2 : 1,
246
+ display: displayBar ? 'block' : 'none',
247
+ pointerEvents: 'none' // Let clicks pass through to base bar
248
+ }
249
+ })}
250
+ </Group>
119
251
  </Group>
120
- </Group>
121
- )
252
+ )
253
+ })
122
254
  })
123
- )
124
- }
255
+ }}
125
256
  </BarStack>
126
- <Regions xScale={xScale} yMax={yMax} barWidth={barWidth} totalBarsInGroup={1} />
257
+ <Regions
258
+ xScale={xScale}
259
+ yMax={yMax}
260
+ barWidth={barWidth}
261
+ totalBarsInGroup={1}
262
+ handleTooltipMouseOff={() => {}}
263
+ handleTooltipMouseOver={() => {}}
264
+ handleTooltipClick={() => {}}
265
+ tooltipData={null}
266
+ showTooltip={() => {}}
267
+ hideTooltip={() => {}}
268
+ />
127
269
  </>
128
270
  )
129
271
  )
@@ -4,11 +4,12 @@ import ConfigContext from '../../../ConfigContext'
4
4
  import BarChartContext, { type BarChartContextValues } from './context'
5
5
  // Local hooks
6
6
  import { useHighlightedBars } from '../../../hooks/useHighlightedBars'
7
- import { getBarConfig, testZeroValue } from '../helpers'
7
+ import { getBarConfig, testZeroValue, getLollipopStemColor, getLollipopHeadColor } from '../helpers'
8
8
  // VisX library imports
9
9
  import { Group } from '@visx/group'
10
10
  import { Text } from '@visx/text'
11
11
  import { BarGroup } from '@visx/shape'
12
+ import { PatternLines, PatternCircles, PatternWaves } from '@visx/pattern'
12
13
  // Local components
13
14
  import Regions from '../../Regions'
14
15
  // CDC core components and helpers
@@ -16,8 +17,7 @@ import { isDateScale } from '@cdc/core/helpers/cove/date'
16
17
  import isNumber from '@cdc/core/helpers/isNumber'
17
18
  import createBarElement from '@cdc/core/components/createBarElement'
18
19
  import { APP_FONT_COLOR } from '@cdc/core/helpers/constants'
19
- // Third party libraries
20
- import chroma from 'chroma-js'
20
+ import { isMobileFontViewport } from '@cdc/core/helpers/viewports'
21
21
  // Types
22
22
  import { type ChartContext } from '../../../types/ChartContext'
23
23
  import _ from 'lodash'
@@ -45,6 +45,7 @@ export const BarChartVertical = () => {
45
45
  const {
46
46
  colorScale,
47
47
  config,
48
+ currentViewport,
48
49
  dashboardConfig,
49
50
  tableData,
50
51
  formatDate,
@@ -57,6 +58,8 @@ export const BarChartVertical = () => {
57
58
 
58
59
  const { HighLightedBarUtils } = useHighlightedBars(config)
59
60
 
61
+ const LABEL_FONT_SIZE = isMobileFontViewport(currentViewport) ? 13 : 16
62
+
60
63
  const root = document.documentElement
61
64
 
62
65
  let data = transformedData
@@ -74,11 +77,80 @@ export const BarChartVertical = () => {
74
77
  config.confidenceKeys.lower !== ''
75
78
 
76
79
  const _data = getBarData(config, data, hasConfidenceInterval)
80
+
81
+ // Pattern helper function
82
+ const renderPatternDefs = () => {
83
+ if (!config.legend.patterns || Object.keys(config.legend.patterns).length === 0) {
84
+ return null
85
+ }
86
+
87
+ return (
88
+ <defs>
89
+ {Object.entries(config.legend.patterns).map(([key, pattern]) => {
90
+ const patternId = `chart-pattern-${key}`
91
+ const size = pattern.patternSize || 8
92
+
93
+ switch (pattern.shape) {
94
+ case 'circles':
95
+ return (
96
+ <PatternCircles
97
+ key={patternId}
98
+ id={patternId}
99
+ height={size}
100
+ width={size}
101
+ fill={pattern.color}
102
+ radius={1.25}
103
+ />
104
+ )
105
+ case 'lines':
106
+ return (
107
+ <PatternLines
108
+ key={patternId}
109
+ id={patternId}
110
+ height={size}
111
+ width={size}
112
+ stroke={pattern.color}
113
+ strokeWidth={0.75}
114
+ orientation={['horizontal']}
115
+ />
116
+ )
117
+ case 'diagonalLines':
118
+ return (
119
+ <PatternLines
120
+ key={patternId}
121
+ id={patternId}
122
+ height={size}
123
+ width={size}
124
+ stroke={pattern.color}
125
+ strokeWidth={0.75}
126
+ orientation={['diagonalRightToLeft']}
127
+ />
128
+ )
129
+ case 'waves':
130
+ return (
131
+ <PatternWaves
132
+ key={patternId}
133
+ id={patternId}
134
+ height={size}
135
+ width={size}
136
+ fill={pattern.color}
137
+ strokeWidth={0.25}
138
+ />
139
+ )
140
+ default:
141
+ return null
142
+ }
143
+ })}
144
+ </defs>
145
+ )
146
+ }
147
+
77
148
  return (
78
149
  config.visualizationSubType !== 'stacked' &&
79
150
  (config.visualizationType === 'Bar' || config.visualizationType === 'Combo' || convertLineToBarGraph) &&
80
151
  config.orientation === 'vertical' && (
81
152
  <Group>
153
+ {renderPatternDefs()}
82
154
  <BarGroup
83
155
  data={_data}
84
156
  keys={config.runtime.barSeriesKeys || config.runtime.seriesKeys}
@@ -123,8 +195,20 @@ export const BarChartVertical = () => {
123
195
  seriesHighlight.indexOf(bar.key) !== -1
124
196
 
125
197
  let barGroupWidth = seriesScale.range()[1] - seriesScale.range()[0]
126
- const defaultBarHeight = Math.abs(yScale(bar.value) - yScale(scaleVal))
127
- const defaultBarY = bar.value >= 0 && isNumber(bar.value) ? bar.y : yScale(0)
198
+ let defaultBarHeight = Math.abs(yScale(bar.value) - yScale(scaleVal))
199
+ let defaultBarY = bar.value >= 0 && isNumber(bar.value) ? bar.y : yScale(0)
200
+
201
+ const MINIMUM_BAR_HEIGHT = 3
202
+ if (
203
+ bar.value >= 0 &&
204
+ isNumber(bar.value) &&
205
+ barGroup.bars.length === 1 &&
206
+ defaultBarHeight < MINIMUM_BAR_HEIGHT
207
+ ) {
208
+ defaultBarHeight = MINIMUM_BAR_HEIGHT
209
+ defaultBarY = yScale(0) - MINIMUM_BAR_HEIGHT
210
+ }
211
+
128
212
  let barWidth = config.isLollipopChart ? lollipopBarWidth : seriesScale.bandwidth()
129
213
  let barX =
130
214
  bar.x +
@@ -156,10 +240,11 @@ export const BarChartVertical = () => {
156
240
  config,
157
241
  barWidth,
158
242
  isVertical: true,
159
- yAxisValue
243
+ yAxisValue,
244
+ labelFontSize: LABEL_FONT_SIZE
160
245
  })
161
246
  // configure colors
162
- let labelColor = '#000000'
247
+ let labelColor = APP_FONT_COLOR
163
248
  labelColor = HighLightedBarUtils.checkFontColor(yAxisValue, highlightedBarValues, labelColor) // Set if background is transparent'
164
249
  const isRegularLollipopColor = config.isLollipopChart && config.lollipopColorStyle === 'regular'
165
250
  const isTwoToneLollipopColor = config.isLollipopChart && config.lollipopColorStyle === 'two-tone'
@@ -222,12 +307,16 @@ export const BarChartVertical = () => {
222
307
  if (isHighlightedBar) _barColor = 'transparent'
223
308
  if (config.legend.colorCode)
224
309
  _barColor = assignColorsToValues(barGroups.length, barGroup.index, barColor)
225
- if (isTwoToneLollipopColor) _barColor = chroma(barColor).brighten(1)
310
+ if (isTwoToneLollipopColor) {
311
+ _barColor = getLollipopStemColor(barColor)
312
+ }
226
313
  return _barColor
227
314
  }
228
315
 
229
- // if this is a two tone lollipop slightly lighten the bar.
230
- if (isTwoToneLollipopColor) _barColor = chroma(barColor).brighten(1)
316
+ // if this is a two tone lollipop, ensure stem has good contrast against white but is lighter than head
317
+ if (isTwoToneLollipopColor) {
318
+ _barColor = getLollipopStemColor(barColor)
319
+ }
231
320
  if (config.legend.colorCode)
232
321
  _barColor = assignColorsToValues(barGroups.length, barGroup.index, barColor)
233
322
 
@@ -236,6 +325,36 @@ export const BarChartVertical = () => {
236
325
  return _barColor
237
326
  }
238
327
 
328
+ // Check if this bar should use a pattern
329
+ const getPatternUrl = (): string | null => {
330
+ if (!config.legend.patterns || Object.keys(config.legend.patterns).length === 0) {
331
+ return null
332
+ }
333
+
334
+ // Find a pattern that matches this specific bar
335
+ for (const [patternKey, pattern] of Object.entries(config.legend.patterns)) {
336
+ if (pattern.dataKey && pattern.dataValue) {
337
+ // For grouped bar charts, check if the pattern's dataKey matches the current bar's series key
338
+ // and if the pattern's dataValue matches the current bar's value
339
+ if (pattern.dataKey === bar.key && String(bar.value) === String(pattern.dataValue)) {
340
+ return `url(#chart-pattern-${patternKey})`
341
+ }
342
+ // Fallback for non-grouped charts: check datum field value
343
+ else if (!config.series || config.series.length <= 1) {
344
+ const dataFieldValue = datum[pattern.dataKey]
345
+ if (String(dataFieldValue) === String(pattern.dataValue)) {
346
+ return `url(#chart-pattern-${patternKey})`
347
+ }
348
+ }
349
+ }
350
+ }
351
+
352
+ return null
353
+ }
354
+
355
+ const patternUrl = getPatternUrl()
356
+ const baseBackground = getBarBackgroundColor(colorScale(config.runtime.seriesLabels[bar.key]))
357
+
239
358
  // Confidence Interval Variables
240
359
  const tickWidth = 5
241
360
  const xPos = barX + (config.xAxis.type !== 'date-time' ? barWidth / 2 : 0)
@@ -257,11 +376,12 @@ export const BarChartVertical = () => {
257
376
  return (
258
377
  <Group display={hideGroup} key={`${barGroup.index}--${index}`}>
259
378
  <Group key={`bar-sub-group-${barGroup.index}-${barGroup.x0}-${barY}--${index}`}>
379
+ {/* Base colored bar */}
260
380
  {createBarElement({
261
381
  config: config,
262
382
  index: newIndex,
263
383
  id: `barGroup${barGroup.index}`,
264
- background: getBarBackgroundColor(colorScale(config.runtime.seriesLabels[bar.key])),
384
+ background: baseBackground,
265
385
  borderColor,
266
386
  borderStyle: 'solid',
267
387
  borderWidth: `${borderWidth}px`,
@@ -269,7 +389,7 @@ export const BarChartVertical = () => {
269
389
  height: barHeight,
270
390
  x: barX,
271
391
  y: barY,
272
- onMouseOver: e => onMouseOverBar(xAxisValue, bar.key, e, data),
392
+ onMouseOver: e => onMouseOverBar(xAxisValue, bar.key, e, data, bar.value),
273
393
  onMouseLeave: onMouseLeaveBar,
274
394
  tooltipHtml: tooltip,
275
395
  tooltipId: `cdc-open-viz-tooltip-${config.runtime.uniqueId}`,
@@ -288,6 +408,32 @@ export const BarChartVertical = () => {
288
408
  }
289
409
  })}
290
410
 
411
+ {/* Pattern overlay if pattern exists */}
412
+ {patternUrl &&
413
+ createBarElement({
414
+ config: config,
415
+ index: newIndex,
416
+ background: patternUrl, // Use pattern as background
417
+ borderColor: 'transparent',
418
+ borderStyle: 'none',
419
+ borderWidth: '0px',
420
+ width: barWidth,
421
+ height: barHeight,
422
+ x: barX,
423
+ y: barY,
424
+ onMouseOver: () => {}, // No interaction
425
+ onMouseLeave: () => {}, // No interaction
426
+ tooltipHtml: '',
427
+ tooltipId: '',
428
+ onClick: () => {}, // No interaction
429
+ styleOverrides: {
430
+ transformOrigin: `0 ${barY + barHeight}px`,
431
+ opacity: transparentBar ? 0.2 : 1,
432
+ display: displayBar ? 'block' : 'none',
433
+ pointerEvents: 'none' // Let clicks pass through to base bar
434
+ }
435
+ })}
436
+
291
437
  {(absentDataLabel || isSuppressed) && (
292
438
  <rect
293
439
  x={barX}
@@ -321,13 +467,7 @@ export const BarChartVertical = () => {
321
467
  const hasAsterisk = String(pd.symbol).includes('Asterisk')
322
468
  const yPadding = hasAsterisk ? -5 : -8
323
469
  const verticalAnchor = hasAsterisk ? 'middle' : 'end'
324
- const iconSize =
325
- pd.symbol === 'Asterisk'
326
- ? barWidth * 1.2
327
- : pd.symbol === 'Double Asterisk'
328
- ? barWidth
329
- : barWidth / 1.5
330
- const fillColor = pd.displayGray ? '#8b8b8a' : '#000'
470
+ const fillColor = pd.displayGray ? '#8b8b8a' : APP_FONT_COLOR
331
471
 
332
472
  return (
333
473
  <Text // prettier-ignore
@@ -340,7 +480,7 @@ export const BarChartVertical = () => {
340
480
  verticalAnchor={verticalAnchor}
341
481
  fill={fillColor}
342
482
  textAnchor='middle'
343
- fontSize={`${iconSize}px`}
483
+ fontSize={LABEL_FONT_SIZE}
344
484
  >
345
485
  {pd.iconCode}
346
486
  </Text>
@@ -353,6 +493,7 @@ export const BarChartVertical = () => {
353
493
  y={barY - BAR_LABEL_PADDING}
354
494
  fill={labelColor}
355
495
  textAnchor='middle'
496
+ fontSize={LABEL_FONT_SIZE}
356
497
  >
357
498
  {testZeroValue(bar.value) ? '' : barDefaultLabel}
358
499
  </Text>
@@ -363,7 +504,7 @@ export const BarChartVertical = () => {
363
504
  y={barY - BAR_LABEL_PADDING}
364
505
  fill={labelColor}
365
506
  textAnchor='middle'
366
- fontSize={config.isLollipopChart ? null : barWidth / 2}
507
+ fontSize={config.isLollipopChart ? null : LABEL_FONT_SIZE}
367
508
  >
368
509
  {absentDataLabel}
369
510
  </Text>
@@ -373,7 +514,11 @@ export const BarChartVertical = () => {
373
514
  cx={barX + lollipopShapeSize / 3.5}
374
515
  cy={bar.y}
375
516
  r={lollipopShapeSize / 2}
376
- fill={getBarBackgroundColor(colorScale(config.runtime.seriesLabels[bar.key]))}
517
+ fill={
518
+ isTwoToneLollipopColor
519
+ ? getLollipopHeadColor(colorScale(config.runtime.seriesLabels[bar.key]))
520
+ : colorScale(config.runtime.seriesLabels[bar.key])
521
+ }
377
522
  key={`circle--${bar.index}`}
378
523
  data-tooltip-html={tooltip}
379
524
  data-tooltip-id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
@@ -387,7 +532,11 @@ export const BarChartVertical = () => {
387
532
  y={bar.y}
388
533
  width={lollipopShapeSize}
389
534
  height={lollipopShapeSize}
390
- fill={getBarBackgroundColor(colorScale(config.runtime.seriesLabels[bar.key]))}
535
+ fill={
536
+ isTwoToneLollipopColor
537
+ ? getLollipopHeadColor(colorScale(config.runtime.seriesLabels[bar.key]))
538
+ : colorScale(config.runtime.seriesLabels[bar.key])
539
+ }
391
540
  key={`circle--${bar.index}`}
392
541
  data-tooltip-html={tooltip}
393
542
  data-tooltip-id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}