@cdc/chart 4.25.8 → 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 (89) hide show
  1. package/.claude/settings.local.json +9 -0
  2. package/dist/cdcchart.js +37524 -35243
  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 +1 -2
  17. package/src/CdcChartComponent.tsx +174 -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 +159 -20
  40. package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +138 -5
  41. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +215 -73
  42. package/src/components/BarChart/components/BarChart.Vertical.tsx +153 -21
  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/DeviationBar.jsx +9 -6
  48. package/src/components/EditorPanel/EditorPanel.tsx +364 -39
  49. package/src/components/EditorPanel/EditorPanelContext.ts +3 -0
  50. package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +414 -0
  51. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +28 -20
  52. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +115 -120
  53. package/src/components/EditorPanel/components/Panels/index.tsx +3 -1
  54. package/src/components/EditorPanel/components/Panels/panelVisual.styles.css +0 -8
  55. package/src/components/EditorPanel/helpers/updateFieldRankByValue.ts +49 -48
  56. package/src/components/Forecasting/Forecasting.tsx +36 -6
  57. package/src/components/ForestPlot/ForestPlot.tsx +11 -7
  58. package/src/components/ForestPlot/ForestPlotProps.ts +1 -1
  59. package/src/components/Legend/Legend.Component.tsx +106 -13
  60. package/src/components/Legend/helpers/createFormatLabels.tsx +230 -171
  61. package/src/components/LegendWrapper.tsx +1 -1
  62. package/src/components/LineChart/components/LineChart.Circle.tsx +2 -2
  63. package/src/components/LineChart/index.tsx +2 -2
  64. package/src/components/LinearChart.tsx +22 -5
  65. package/src/components/PairedBarChart.jsx +6 -4
  66. package/src/components/PieChart/PieChart.tsx +170 -54
  67. package/src/components/Sankey/components/Sankey.tsx +7 -1
  68. package/src/components/ScatterPlot/ScatterPlot.jsx +32 -4
  69. package/src/data/initial-state.js +315 -293
  70. package/src/helpers/buildForecastPaletteMappings.ts +112 -0
  71. package/src/helpers/buildForecastPaletteOptions.ts +109 -0
  72. package/src/helpers/getColorScale.ts +72 -8
  73. package/src/helpers/getNewRuntime.ts +1 -1
  74. package/src/helpers/getTransformedData.ts +1 -1
  75. package/src/hooks/useChartHoverAnalytics.tsx +44 -0
  76. package/src/hooks/useReduceData.ts +105 -70
  77. package/src/hooks/useTooltip.tsx +57 -15
  78. package/src/index.jsx +0 -2
  79. package/src/scss/main.scss +12 -0
  80. package/src/store/chart.reducer.ts +1 -1
  81. package/src/test/CdcChart.test.jsx +8 -3
  82. package/src/types/ChartConfig.ts +30 -6
  83. package/src/types/ChartContext.ts +1 -0
  84. package/vite.config.js +1 -1
  85. package/vitest.config.ts +16 -0
  86. package/src/coreStyles_chart.scss +0 -3
  87. package/src/helpers/configHelpers.ts +0 -28
  88. package/src/helpers/generateColorsArray.ts +0 -8
  89. 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
@@ -17,8 +18,6 @@ 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
20
  import { isMobileFontViewport } from '@cdc/core/helpers/viewports'
20
- // Third party libraries
21
- import chroma from 'chroma-js'
22
21
  // Types
23
22
  import { type ChartContext } from '../../../types/ChartContext'
24
23
  import _ from 'lodash'
@@ -59,6 +58,8 @@ export const BarChartVertical = () => {
59
58
 
60
59
  const { HighLightedBarUtils } = useHighlightedBars(config)
61
60
 
61
+ const LABEL_FONT_SIZE = isMobileFontViewport(currentViewport) ? 13 : 16
62
+
62
63
  const root = document.documentElement
63
64
 
64
65
  let data = transformedData
@@ -76,11 +77,80 @@ export const BarChartVertical = () => {
76
77
  config.confidenceKeys.lower !== ''
77
78
 
78
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
+
79
148
  return (
80
149
  config.visualizationSubType !== 'stacked' &&
81
150
  (config.visualizationType === 'Bar' || config.visualizationType === 'Combo' || convertLineToBarGraph) &&
82
151
  config.orientation === 'vertical' && (
83
152
  <Group>
153
+ {renderPatternDefs()}
84
154
  <BarGroup
85
155
  data={_data}
86
156
  keys={config.runtime.barSeriesKeys || config.runtime.seriesKeys}
@@ -170,7 +240,8 @@ export const BarChartVertical = () => {
170
240
  config,
171
241
  barWidth,
172
242
  isVertical: true,
173
- yAxisValue
243
+ yAxisValue,
244
+ labelFontSize: LABEL_FONT_SIZE
174
245
  })
175
246
  // configure colors
176
247
  let labelColor = APP_FONT_COLOR
@@ -236,12 +307,16 @@ export const BarChartVertical = () => {
236
307
  if (isHighlightedBar) _barColor = 'transparent'
237
308
  if (config.legend.colorCode)
238
309
  _barColor = assignColorsToValues(barGroups.length, barGroup.index, barColor)
239
- if (isTwoToneLollipopColor) _barColor = chroma(barColor).brighten(1)
310
+ if (isTwoToneLollipopColor) {
311
+ _barColor = getLollipopStemColor(barColor)
312
+ }
240
313
  return _barColor
241
314
  }
242
315
 
243
- // if this is a two tone lollipop slightly lighten the bar.
244
- 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
+ }
245
320
  if (config.legend.colorCode)
246
321
  _barColor = assignColorsToValues(barGroups.length, barGroup.index, barColor)
247
322
 
@@ -250,6 +325,36 @@ export const BarChartVertical = () => {
250
325
  return _barColor
251
326
  }
252
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
+
253
358
  // Confidence Interval Variables
254
359
  const tickWidth = 5
255
360
  const xPos = barX + (config.xAxis.type !== 'date-time' ? barWidth / 2 : 0)
@@ -268,16 +373,15 @@ export const BarChartVertical = () => {
268
373
 
269
374
  const BAR_LABEL_PADDING = 10
270
375
 
271
- const LABEL_FONT_SIZE = isMobileFontViewport(currentViewport) ? 13 : 16
272
-
273
376
  return (
274
377
  <Group display={hideGroup} key={`${barGroup.index}--${index}`}>
275
378
  <Group key={`bar-sub-group-${barGroup.index}-${barGroup.x0}-${barY}--${index}`}>
379
+ {/* Base colored bar */}
276
380
  {createBarElement({
277
381
  config: config,
278
382
  index: newIndex,
279
383
  id: `barGroup${barGroup.index}`,
280
- background: getBarBackgroundColor(colorScale(config.runtime.seriesLabels[bar.key])),
384
+ background: baseBackground,
281
385
  borderColor,
282
386
  borderStyle: 'solid',
283
387
  borderWidth: `${borderWidth}px`,
@@ -285,7 +389,7 @@ export const BarChartVertical = () => {
285
389
  height: barHeight,
286
390
  x: barX,
287
391
  y: barY,
288
- onMouseOver: e => onMouseOverBar(xAxisValue, bar.key, e, data),
392
+ onMouseOver: e => onMouseOverBar(xAxisValue, bar.key, e, data, bar.value),
289
393
  onMouseLeave: onMouseLeaveBar,
290
394
  tooltipHtml: tooltip,
291
395
  tooltipId: `cdc-open-viz-tooltip-${config.runtime.uniqueId}`,
@@ -304,6 +408,32 @@ export const BarChartVertical = () => {
304
408
  }
305
409
  })}
306
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
+
307
437
  {(absentDataLabel || isSuppressed) && (
308
438
  <rect
309
439
  x={barX}
@@ -337,13 +467,7 @@ export const BarChartVertical = () => {
337
467
  const hasAsterisk = String(pd.symbol).includes('Asterisk')
338
468
  const yPadding = hasAsterisk ? -5 : -8
339
469
  const verticalAnchor = hasAsterisk ? 'middle' : 'end'
340
- const iconSize =
341
- pd.symbol === 'Asterisk'
342
- ? barWidth * 1.2
343
- : pd.symbol === 'Double Asterisk'
344
- ? barWidth
345
- : barWidth / 1.5
346
- const fillColor = pd.displayGray ? '#8b8b8a' : '#000'
470
+ const fillColor = pd.displayGray ? '#8b8b8a' : APP_FONT_COLOR
347
471
 
348
472
  return (
349
473
  <Text // prettier-ignore
@@ -356,7 +480,7 @@ export const BarChartVertical = () => {
356
480
  verticalAnchor={verticalAnchor}
357
481
  fill={fillColor}
358
482
  textAnchor='middle'
359
- fontSize={`${iconSize}px`}
483
+ fontSize={LABEL_FONT_SIZE}
360
484
  >
361
485
  {pd.iconCode}
362
486
  </Text>
@@ -390,7 +514,11 @@ export const BarChartVertical = () => {
390
514
  cx={barX + lollipopShapeSize / 3.5}
391
515
  cy={bar.y}
392
516
  r={lollipopShapeSize / 2}
393
- 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
+ }
394
522
  key={`circle--${bar.index}`}
395
523
  data-tooltip-html={tooltip}
396
524
  data-tooltip-id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
@@ -404,7 +532,11 @@ export const BarChartVertical = () => {
404
532
  y={bar.y}
405
533
  width={lollipopShapeSize}
406
534
  height={lollipopShapeSize}
407
- 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
+ }
408
540
  key={`circle--${bar.index}`}
409
541
  data-tooltip-html={tooltip}
410
542
  data-tooltip-id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}