@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.
- package/.claude/settings.local.json +9 -0
- package/dist/cdcchart.js +37524 -35243
- package/examples/feature/__data__/planet-example-data.json +0 -30
- package/examples/grouped-bar-test.json +400 -0
- package/examples/private/d.json +382 -0
- package/examples/private/example-2.json +49784 -0
- package/examples/private/f2.json +1 -0
- package/examples/private/f4.json +1577 -0
- package/examples/private/forecast.json +1180 -0
- package/examples/private/lollipop.json +468 -0
- package/examples/private/new.json +48756 -0
- package/examples/private/pie-chart-legend.json +904 -0
- package/examples/suppressed_tooltip.json +480 -0
- package/index.html +10 -22
- package/package.json +25 -7
- package/src/CdcChart.tsx +1 -2
- package/src/CdcChartComponent.tsx +174 -32
- package/src/_stories/Chart.Anchors.stories.tsx +2 -2
- package/src/_stories/Chart.BoxPlot.stories.tsx +1 -1
- package/src/_stories/Chart.CI.stories.tsx +1 -1
- package/src/_stories/Chart.CustomColors.stories.tsx +1 -1
- package/src/_stories/Chart.DynamicSeries.stories.tsx +2 -2
- package/src/_stories/Chart.Filters.stories.tsx +2 -2
- package/src/_stories/Chart.Legend.Gradient.stories.tsx +2 -2
- package/src/_stories/Chart.Patterns.stories.tsx +19 -0
- package/src/_stories/Chart.ScatterPlot.stories.tsx +1 -1
- package/src/_stories/Chart.stories.tsx +8 -5
- package/src/_stories/Chart.tooltip.stories.tsx +1 -1
- package/src/_stories/ChartAnnotation.stories.tsx +1 -1
- package/src/_stories/ChartAxisLabels.stories.tsx +2 -2
- package/src/_stories/ChartAxisTitles.stories.tsx +2 -2
- package/src/_stories/ChartEditor.stories.tsx +60 -60
- package/src/_stories/ChartLine.Suppression.stories.tsx +1 -1
- package/src/_stories/ChartLine.Symbols.stories.tsx +1 -1
- package/src/_stories/ChartPrefixSuffix.stories.tsx +2 -2
- package/src/_stories/_mock/stacked-pattern-test.json +520 -0
- package/src/components/Annotations/components/AnnotationDraggable.tsx +1 -0
- package/src/components/Annotations/components/AnnotationDropdown.tsx +1 -1
- package/src/components/BarChart/components/BarChart.Horizontal.tsx +159 -20
- package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +138 -5
- package/src/components/BarChart/components/BarChart.StackedVertical.tsx +215 -73
- package/src/components/BarChart/components/BarChart.Vertical.tsx +153 -21
- package/src/components/BarChart/helpers/index.ts +43 -4
- package/src/components/BarChart/helpers/lollipopColors.ts +27 -0
- package/src/components/BarChart/helpers/useBarChart.ts +25 -3
- package/src/components/BoxPlot/BoxPlot.Vertical.tsx +2 -1
- package/src/components/DeviationBar.jsx +9 -6
- package/src/components/EditorPanel/EditorPanel.tsx +364 -39
- package/src/components/EditorPanel/EditorPanelContext.ts +3 -0
- package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +414 -0
- package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +28 -20
- package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +115 -120
- package/src/components/EditorPanel/components/Panels/index.tsx +3 -1
- package/src/components/EditorPanel/components/Panels/panelVisual.styles.css +0 -8
- package/src/components/EditorPanel/helpers/updateFieldRankByValue.ts +49 -48
- package/src/components/Forecasting/Forecasting.tsx +36 -6
- package/src/components/ForestPlot/ForestPlot.tsx +11 -7
- package/src/components/ForestPlot/ForestPlotProps.ts +1 -1
- package/src/components/Legend/Legend.Component.tsx +106 -13
- package/src/components/Legend/helpers/createFormatLabels.tsx +230 -171
- package/src/components/LegendWrapper.tsx +1 -1
- package/src/components/LineChart/components/LineChart.Circle.tsx +2 -2
- package/src/components/LineChart/index.tsx +2 -2
- package/src/components/LinearChart.tsx +22 -5
- package/src/components/PairedBarChart.jsx +6 -4
- package/src/components/PieChart/PieChart.tsx +170 -54
- package/src/components/Sankey/components/Sankey.tsx +7 -1
- package/src/components/ScatterPlot/ScatterPlot.jsx +32 -4
- package/src/data/initial-state.js +315 -293
- package/src/helpers/buildForecastPaletteMappings.ts +112 -0
- package/src/helpers/buildForecastPaletteOptions.ts +109 -0
- package/src/helpers/getColorScale.ts +72 -8
- package/src/helpers/getNewRuntime.ts +1 -1
- package/src/helpers/getTransformedData.ts +1 -1
- package/src/hooks/useChartHoverAnalytics.tsx +44 -0
- package/src/hooks/useReduceData.ts +105 -70
- package/src/hooks/useTooltip.tsx +57 -15
- package/src/index.jsx +0 -2
- package/src/scss/main.scss +12 -0
- package/src/store/chart.reducer.ts +1 -1
- package/src/test/CdcChart.test.jsx +8 -3
- package/src/types/ChartConfig.ts +30 -6
- package/src/types/ChartContext.ts +1 -0
- package/vite.config.js +1 -1
- package/vitest.config.ts +16 -0
- package/src/coreStyles_chart.scss +0 -3
- package/src/helpers/configHelpers.ts +0 -28
- package/src/helpers/generateColorsArray.ts +0 -8
- package/src/hooks/useColorPalette.js +0 -76
|
@@ -8,18 +8,17 @@ import { useHighlightedBars } from '../../../hooks/useHighlightedBars'
|
|
|
8
8
|
import { Group } from '@visx/group'
|
|
9
9
|
import { Text } from '@visx/text'
|
|
10
10
|
import { BarGroup } from '@visx/shape'
|
|
11
|
+
import { PatternLines, PatternCircles, PatternWaves } from '@visx/pattern'
|
|
11
12
|
|
|
12
13
|
// CDC core components and helpers
|
|
13
14
|
import { getColorContrast, getContrastColor } from '@cdc/core/helpers/cove/accessibility'
|
|
14
15
|
import { APP_FONT_COLOR } from '@cdc/core/helpers/constants'
|
|
16
|
+
import { isMobileFontViewport } from '@cdc/core/helpers/viewports'
|
|
15
17
|
import createBarElement from '@cdc/core/components/createBarElement'
|
|
16
|
-
import { getBarConfig, testZeroValue } from '../helpers'
|
|
18
|
+
import { getBarConfig, testZeroValue, getLollipopStemColor, getLollipopHeadColor } from '../helpers'
|
|
17
19
|
import { getTextWidth } from '@cdc/core/helpers/getTextWidth'
|
|
18
20
|
import isNumber from '@cdc/core/helpers/isNumber'
|
|
19
21
|
|
|
20
|
-
// Third party libraries
|
|
21
|
-
import chroma from 'chroma-js'
|
|
22
|
-
|
|
23
22
|
// Local context and types
|
|
24
23
|
import BarChartContext, { BarChartContextValues } from './context'
|
|
25
24
|
import { ChartContext } from '../../../types/ChartContext'
|
|
@@ -54,15 +53,85 @@ export const BarChartHorizontal = () => {
|
|
|
54
53
|
formatNumber,
|
|
55
54
|
formatDate,
|
|
56
55
|
parseDate,
|
|
57
|
-
setSharedFilter
|
|
56
|
+
setSharedFilter,
|
|
57
|
+
currentViewport
|
|
58
58
|
} = useContext<ChartContext>(ConfigContext)
|
|
59
59
|
|
|
60
60
|
const { HighLightedBarUtils } = useHighlightedBars(config)
|
|
61
61
|
|
|
62
|
+
const LABEL_FONT_SIZE = isMobileFontViewport(currentViewport) ? 13 : 16
|
|
63
|
+
|
|
62
64
|
const hasConfidenceInterval = [config.confidenceKeys?.upper, config.confidenceKeys?.lower].every(
|
|
63
65
|
v => v != null && String(v).trim() !== ''
|
|
64
66
|
)
|
|
65
67
|
|
|
68
|
+
// Pattern helper function
|
|
69
|
+
const renderPatternDefs = () => {
|
|
70
|
+
if (!config.legend.patterns || Object.keys(config.legend.patterns).length === 0) {
|
|
71
|
+
return null
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<defs>
|
|
76
|
+
{Object.entries(config.legend.patterns).map(([key, pattern]) => {
|
|
77
|
+
const patternId = `chart-pattern-${key}`
|
|
78
|
+
const size = pattern.patternSize || 8
|
|
79
|
+
|
|
80
|
+
switch (pattern.shape) {
|
|
81
|
+
case 'circles':
|
|
82
|
+
return (
|
|
83
|
+
<PatternCircles
|
|
84
|
+
key={patternId}
|
|
85
|
+
id={patternId}
|
|
86
|
+
height={size}
|
|
87
|
+
width={size}
|
|
88
|
+
fill={pattern.color}
|
|
89
|
+
radius={1.25}
|
|
90
|
+
/>
|
|
91
|
+
)
|
|
92
|
+
case 'lines':
|
|
93
|
+
return (
|
|
94
|
+
<PatternLines
|
|
95
|
+
key={patternId}
|
|
96
|
+
id={patternId}
|
|
97
|
+
height={size}
|
|
98
|
+
width={size}
|
|
99
|
+
stroke={pattern.color}
|
|
100
|
+
strokeWidth={0.75}
|
|
101
|
+
orientation={['horizontal']}
|
|
102
|
+
/>
|
|
103
|
+
)
|
|
104
|
+
case 'diagonalLines':
|
|
105
|
+
return (
|
|
106
|
+
<PatternLines
|
|
107
|
+
key={patternId}
|
|
108
|
+
id={patternId}
|
|
109
|
+
height={size}
|
|
110
|
+
width={size}
|
|
111
|
+
stroke={pattern.color}
|
|
112
|
+
strokeWidth={0.75}
|
|
113
|
+
orientation={['diagonalRightToLeft']}
|
|
114
|
+
/>
|
|
115
|
+
)
|
|
116
|
+
case 'waves':
|
|
117
|
+
return (
|
|
118
|
+
<PatternWaves
|
|
119
|
+
key={patternId}
|
|
120
|
+
id={patternId}
|
|
121
|
+
height={size}
|
|
122
|
+
width={size}
|
|
123
|
+
fill={pattern.color}
|
|
124
|
+
strokeWidth={0.25}
|
|
125
|
+
/>
|
|
126
|
+
)
|
|
127
|
+
default:
|
|
128
|
+
return null
|
|
129
|
+
}
|
|
130
|
+
})}
|
|
131
|
+
</defs>
|
|
132
|
+
)
|
|
133
|
+
}
|
|
134
|
+
|
|
66
135
|
const _data = getBarData(config, data, hasConfidenceInterval)
|
|
67
136
|
|
|
68
137
|
return (
|
|
@@ -70,6 +139,7 @@ export const BarChartHorizontal = () => {
|
|
|
70
139
|
config.visualizationType === 'Bar' &&
|
|
71
140
|
config.orientation === 'horizontal' && (
|
|
72
141
|
<Group>
|
|
142
|
+
{renderPatternDefs()}
|
|
73
143
|
<BarGroup
|
|
74
144
|
data={config.preliminaryData?.some(pd => pd.value && pd.type === 'suppression') ? tableData : _data}
|
|
75
145
|
keys={config.runtime.barSeriesKeys || config.runtime.seriesKeys}
|
|
@@ -132,7 +202,15 @@ export const BarChartHorizontal = () => {
|
|
|
132
202
|
barWidthHorizontal: barWidth,
|
|
133
203
|
isSuppressed,
|
|
134
204
|
absentDataLabel
|
|
135
|
-
} = getBarConfig({
|
|
205
|
+
} = getBarConfig({
|
|
206
|
+
bar,
|
|
207
|
+
defaultBarWidth,
|
|
208
|
+
config,
|
|
209
|
+
isVertical: false,
|
|
210
|
+
yAxisValue,
|
|
211
|
+
barWidth: 0,
|
|
212
|
+
labelFontSize: LABEL_FONT_SIZE
|
|
213
|
+
})
|
|
136
214
|
|
|
137
215
|
const barPosition = !isPositiveBar ? 'below' : 'above'
|
|
138
216
|
|
|
@@ -179,7 +257,7 @@ export const BarChartHorizontal = () => {
|
|
|
179
257
|
? colorScale(config.runtime.seriesLabels[bar.key])
|
|
180
258
|
: colorScale(bar.key)
|
|
181
259
|
const hasDynamicCategory = config.series.find(s => s.dynamicCategory)
|
|
182
|
-
if (!hasDynamicCategory) {
|
|
260
|
+
if (!hasDynamicCategory && config.legend.colorCode) {
|
|
183
261
|
barColor = assignColorsToValues(barGroups.length, barGroup.index, barColor) // Color code by category
|
|
184
262
|
}
|
|
185
263
|
const isRegularLollipopColor = config.isLollipopChart && config.lollipopColorStyle === 'regular'
|
|
@@ -215,13 +293,23 @@ export const BarChartHorizontal = () => {
|
|
|
215
293
|
labelColor = '#fff'
|
|
216
294
|
}
|
|
217
295
|
}
|
|
218
|
-
const
|
|
296
|
+
const getBarBackgroundColor = () => {
|
|
219
297
|
if (isRegularLollipopColor) return barColor
|
|
220
|
-
if (isTwoToneLollipopColor)
|
|
298
|
+
if (isTwoToneLollipopColor) {
|
|
299
|
+
return getLollipopStemColor(barColor)
|
|
300
|
+
}
|
|
221
301
|
if (isHighlightedBar) return 'transparent'
|
|
222
302
|
return barColor
|
|
223
303
|
}
|
|
224
304
|
|
|
305
|
+
// Function to get the lollipop head color (should be darker than stem)
|
|
306
|
+
const getLocalLollipopHeadColor = (): string => {
|
|
307
|
+
if (!isTwoToneLollipopColor) {
|
|
308
|
+
return barColor
|
|
309
|
+
}
|
|
310
|
+
return getLollipopHeadColor(barColor)
|
|
311
|
+
}
|
|
312
|
+
|
|
225
313
|
// Confidence Interval Variables
|
|
226
314
|
const tickWidth = 5
|
|
227
315
|
const yPos = barHeight * bar.index + barHeight / 2
|
|
@@ -237,14 +325,45 @@ export const BarChartHorizontal = () => {
|
|
|
237
325
|
return xScale(d)
|
|
238
326
|
})
|
|
239
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()
|
|
357
|
+
|
|
240
358
|
return (
|
|
241
359
|
<Group display={hideGroup} key={`${barGroup.index}--${index}`}>
|
|
242
360
|
<Group key={`bar-sub-group-${barGroup.index}-${barGroup.x0}-${barY}--${index}`}>
|
|
361
|
+
{/* Base colored bar */}
|
|
243
362
|
{createBarElement({
|
|
244
363
|
config: config,
|
|
245
364
|
index: newIndex,
|
|
246
365
|
id: `barGroup${barGroup.index}`,
|
|
247
|
-
background:
|
|
366
|
+
background: baseBackground,
|
|
248
367
|
borderColor,
|
|
249
368
|
borderStyle: 'solid',
|
|
250
369
|
borderWidth: `${borderWidth}px`,
|
|
@@ -252,7 +371,7 @@ export const BarChartHorizontal = () => {
|
|
|
252
371
|
height: numbericBarHeight,
|
|
253
372
|
x: barX,
|
|
254
373
|
y: barHeight * bar.index,
|
|
255
|
-
onMouseOver: e => onMouseOverBar(xAxisValue, bar.key, e, data),
|
|
374
|
+
onMouseOver: e => onMouseOverBar(xAxisValue, bar.key, e, data, bar.value),
|
|
256
375
|
onMouseLeave: onMouseLeaveBar,
|
|
257
376
|
tooltipHtml: tooltip,
|
|
258
377
|
tooltipId: `cdc-open-viz-tooltip-${config.runtime.uniqueId}`,
|
|
@@ -270,6 +389,32 @@ export const BarChartHorizontal = () => {
|
|
|
270
389
|
}
|
|
271
390
|
})}
|
|
272
391
|
|
|
392
|
+
{/* Pattern overlay if pattern exists */}
|
|
393
|
+
{patternUrl &&
|
|
394
|
+
createBarElement({
|
|
395
|
+
config: config,
|
|
396
|
+
index: newIndex,
|
|
397
|
+
background: patternUrl, // Use pattern as background
|
|
398
|
+
borderColor: 'transparent',
|
|
399
|
+
borderStyle: 'none',
|
|
400
|
+
borderWidth: '0px',
|
|
401
|
+
width: barWidth,
|
|
402
|
+
height: numbericBarHeight,
|
|
403
|
+
x: barX,
|
|
404
|
+
y: barHeight * bar.index,
|
|
405
|
+
onMouseOver: () => {}, // No interaction
|
|
406
|
+
onMouseLeave: () => {}, // No interaction
|
|
407
|
+
tooltipHtml: '',
|
|
408
|
+
tooltipId: '',
|
|
409
|
+
onClick: () => {}, // No interaction
|
|
410
|
+
styleOverrides: {
|
|
411
|
+
transformOrigin: `0 ${barY + barHeight}px`,
|
|
412
|
+
opacity: transparentBar ? 0.2 : 1,
|
|
413
|
+
display: displayBar ? 'block' : 'none',
|
|
414
|
+
pointerEvents: 'none' // Let clicks pass through to base bar
|
|
415
|
+
}
|
|
416
|
+
})}
|
|
417
|
+
|
|
273
418
|
{(absentDataLabel || isSuppressed) && (
|
|
274
419
|
<rect
|
|
275
420
|
x={barX}
|
|
@@ -298,17 +443,11 @@ export const BarChartHorizontal = () => {
|
|
|
298
443
|
|
|
299
444
|
const hasAsterisk = String(pd.symbol).includes('Asterisk')
|
|
300
445
|
const verticalAnchor = hasAsterisk ? 'middle' : 'end'
|
|
301
|
-
const iconSize =
|
|
302
|
-
pd.symbol === 'Asterisk'
|
|
303
|
-
? barHeight * 1.2
|
|
304
|
-
: pd.symbol === 'Double Asterisk'
|
|
305
|
-
? barHeight
|
|
306
|
-
: barHeight / 1.5
|
|
307
446
|
const fillColor = pd.displayGray ? '#8b8b8a' : APP_FONT_COLOR
|
|
308
447
|
return (
|
|
309
448
|
<Text // prettier-ignore
|
|
310
449
|
key={index}
|
|
311
|
-
fontSize={
|
|
450
|
+
fontSize={LABEL_FONT_SIZE}
|
|
312
451
|
display={displayBar ? 'block' : 'none'}
|
|
313
452
|
opacity={transparentBar ? 0.5 : 1}
|
|
314
453
|
x={barX}
|
|
@@ -403,7 +542,7 @@ export const BarChartHorizontal = () => {
|
|
|
403
542
|
cx={bar.y}
|
|
404
543
|
cy={barHeight * bar.index + lollipopBarWidth / 2}
|
|
405
544
|
r={lollipopShapeSize / 2}
|
|
406
|
-
fill={
|
|
545
|
+
fill={getLocalLollipopHeadColor()}
|
|
407
546
|
key={`circle--${bar.index}`}
|
|
408
547
|
data-tooltip-html={tooltip}
|
|
409
548
|
data-tooltip-id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
|
|
@@ -417,7 +556,7 @@ export const BarChartHorizontal = () => {
|
|
|
417
556
|
y={0 - lollipopBarWidth / 2}
|
|
418
557
|
width={lollipopShapeSize}
|
|
419
558
|
height={lollipopShapeSize}
|
|
420
|
-
fill={
|
|
559
|
+
fill={getLocalLollipopHeadColor()}
|
|
421
560
|
key={`circle--${bar.index}`}
|
|
422
561
|
data-tooltip-html={tooltip}
|
|
423
562
|
data-tooltip-id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
|
|
@@ -3,6 +3,7 @@ import ConfigContext from '../../../ConfigContext'
|
|
|
3
3
|
import { BarStackHorizontal } from '@visx/shape'
|
|
4
4
|
import { Group } from '@visx/group'
|
|
5
5
|
import { Text } from '@visx/text'
|
|
6
|
+
import { PatternLines, PatternCircles, PatternWaves } from '@visx/pattern'
|
|
6
7
|
import { getColorContrast, getContrastColor } from '@cdc/core/helpers/cove/accessibility'
|
|
7
8
|
import { APP_FONT_COLOR } from '@cdc/core/helpers/constants'
|
|
8
9
|
import { getTextWidth } from '@cdc/core/helpers/getTextWidth'
|
|
@@ -43,10 +44,79 @@ const BarChartStackedHorizontal = () => {
|
|
|
43
44
|
} = barChart
|
|
44
45
|
|
|
45
46
|
const { orientation, visualizationSubType } = config
|
|
47
|
+
|
|
48
|
+
// Pattern helper function
|
|
49
|
+
const renderPatternDefs = () => {
|
|
50
|
+
if (!config.legend.patterns || Object.keys(config.legend.patterns).length === 0) {
|
|
51
|
+
return null
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<defs>
|
|
56
|
+
{Object.entries(config.legend.patterns).map(([key, pattern]) => {
|
|
57
|
+
const patternId = `chart-pattern-${key}`
|
|
58
|
+
const size = pattern.patternSize || 8
|
|
59
|
+
|
|
60
|
+
switch (pattern.shape) {
|
|
61
|
+
case 'circles':
|
|
62
|
+
return (
|
|
63
|
+
<PatternCircles
|
|
64
|
+
key={patternId}
|
|
65
|
+
id={patternId}
|
|
66
|
+
height={size}
|
|
67
|
+
width={size}
|
|
68
|
+
fill={pattern.color}
|
|
69
|
+
radius={1.25}
|
|
70
|
+
/>
|
|
71
|
+
)
|
|
72
|
+
case 'lines':
|
|
73
|
+
return (
|
|
74
|
+
<PatternLines
|
|
75
|
+
key={patternId}
|
|
76
|
+
id={patternId}
|
|
77
|
+
height={size}
|
|
78
|
+
width={size}
|
|
79
|
+
stroke={pattern.color}
|
|
80
|
+
strokeWidth={0.75}
|
|
81
|
+
orientation={['horizontal']}
|
|
82
|
+
/>
|
|
83
|
+
)
|
|
84
|
+
case 'diagonalLines':
|
|
85
|
+
return (
|
|
86
|
+
<PatternLines
|
|
87
|
+
key={patternId}
|
|
88
|
+
id={patternId}
|
|
89
|
+
height={size}
|
|
90
|
+
width={size}
|
|
91
|
+
stroke={pattern.color}
|
|
92
|
+
strokeWidth={0.75}
|
|
93
|
+
orientation={['diagonalRightToLeft']}
|
|
94
|
+
/>
|
|
95
|
+
)
|
|
96
|
+
case 'waves':
|
|
97
|
+
return (
|
|
98
|
+
<PatternWaves
|
|
99
|
+
key={patternId}
|
|
100
|
+
id={patternId}
|
|
101
|
+
height={size}
|
|
102
|
+
width={size}
|
|
103
|
+
fill={pattern.color}
|
|
104
|
+
strokeWidth={0.25}
|
|
105
|
+
/>
|
|
106
|
+
)
|
|
107
|
+
default:
|
|
108
|
+
return null
|
|
109
|
+
}
|
|
110
|
+
})}
|
|
111
|
+
</defs>
|
|
112
|
+
)
|
|
113
|
+
}
|
|
114
|
+
|
|
46
115
|
return (
|
|
47
116
|
config.visualizationSubType === 'stacked' &&
|
|
48
117
|
isHorizontal && (
|
|
49
118
|
<>
|
|
119
|
+
{renderPatternDefs()}
|
|
50
120
|
<BarStackHorizontal
|
|
51
121
|
data={data}
|
|
52
122
|
keys={barStackedSeriesKeys}
|
|
@@ -58,7 +128,7 @@ const BarChartStackedHorizontal = () => {
|
|
|
58
128
|
offset='none'
|
|
59
129
|
>
|
|
60
130
|
{barStacks =>
|
|
61
|
-
barStacks.map(barStack =>
|
|
131
|
+
barStacks.map((barStack, stackIndex) =>
|
|
62
132
|
getHorizontalBarHeights(config, barStack.bars).map((bar, index) => {
|
|
63
133
|
const transparentBar =
|
|
64
134
|
config.legend.behavior === 'highlight' &&
|
|
@@ -73,7 +143,7 @@ const BarChartStackedHorizontal = () => {
|
|
|
73
143
|
let labelColor = getContrastColor(APP_FONT_COLOR, barColor)
|
|
74
144
|
let constrast = getColorContrast(APP_FONT_COLOR, barColor)
|
|
75
145
|
const contrastLevel = 7
|
|
76
|
-
if (constrast < contrastLevel) {
|
|
146
|
+
if (typeof constrast === 'number' && constrast < contrastLevel) {
|
|
77
147
|
labelColor = '#fff'
|
|
78
148
|
}
|
|
79
149
|
// tooltips
|
|
@@ -94,9 +164,41 @@ const BarChartStackedHorizontal = () => {
|
|
|
94
164
|
<li class="tooltip-body ">${additionalColTooltip}</li>
|
|
95
165
|
</li></ul>`
|
|
96
166
|
|
|
167
|
+
// Check if this bar should use a pattern
|
|
168
|
+
const getPatternUrl = (): string | null => {
|
|
169
|
+
if (!config.legend.patterns || Object.keys(config.legend.patterns).length === 0) {
|
|
170
|
+
return null
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Find a pattern that matches this specific bar
|
|
174
|
+
for (const [patternKey, pattern] of Object.entries(config.legend.patterns)) {
|
|
175
|
+
if (pattern.dataKey && pattern.dataValue) {
|
|
176
|
+
// For stacked bar charts, check if the pattern's dataKey matches the current bar's series key
|
|
177
|
+
// and if the pattern's dataValue matches the current bar's value
|
|
178
|
+
const barValue = data[bar.index][bar.key]
|
|
179
|
+
if (pattern.dataKey === bar.key && String(barValue) === String(pattern.dataValue)) {
|
|
180
|
+
return `url(#chart-pattern-${patternKey})`
|
|
181
|
+
}
|
|
182
|
+
// Fallback for non-series pattern matching (like the original stacked pattern test)
|
|
183
|
+
// Only check this if the pattern dataKey is NOT a series key
|
|
184
|
+
else if (!config.runtime.seriesLabels || !config.runtime.seriesLabels[pattern.dataKey]) {
|
|
185
|
+
const dataFieldValue = data[bar.index][pattern.dataKey]
|
|
186
|
+
if (String(dataFieldValue) === String(pattern.dataValue)) {
|
|
187
|
+
return `url(#chart-pattern-${patternKey})`
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return null
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const patternUrl = getPatternUrl()
|
|
197
|
+
|
|
97
198
|
return (
|
|
98
|
-
|
|
199
|
+
<React.Fragment key={`stack-${stackIndex}-bar-${index}-${barStack.index}`}>
|
|
99
200
|
<Group key={index} id={`barStack${barStack.index}-${bar.index}`} className='stack horizontal'>
|
|
201
|
+
{/* Base colored bar */}
|
|
100
202
|
{createBarElement({
|
|
101
203
|
config: config,
|
|
102
204
|
seriesHighlight,
|
|
@@ -110,7 +212,7 @@ const BarChartStackedHorizontal = () => {
|
|
|
110
212
|
height: bar.height,
|
|
111
213
|
x: bar.x,
|
|
112
214
|
y: bar.y,
|
|
113
|
-
onMouseOver: e => onMouseOverBar(yAxisValue, bar.key, e, data),
|
|
215
|
+
onMouseOver: e => onMouseOverBar(yAxisValue, bar.key, e, data, bar.bar.data[bar.key]),
|
|
114
216
|
onMouseLeave: onMouseLeaveBar,
|
|
115
217
|
tooltipHtml: tooltip,
|
|
116
218
|
tooltipId: `cdc-open-viz-tooltip-${config.runtime.uniqueId}`,
|
|
@@ -129,6 +231,37 @@ const BarChartStackedHorizontal = () => {
|
|
|
129
231
|
}
|
|
130
232
|
})}
|
|
131
233
|
|
|
234
|
+
{/* Pattern overlay using createBarElement for consistent animation */}
|
|
235
|
+
{patternUrl &&
|
|
236
|
+
createBarElement({
|
|
237
|
+
config: config,
|
|
238
|
+
seriesHighlight,
|
|
239
|
+
index: barStack.index,
|
|
240
|
+
className: `animated-chart pattern-overlay ${animatedChart ? 'animated' : ''}`,
|
|
241
|
+
background: patternUrl, // Use pattern as background
|
|
242
|
+
borderColor: 'transparent',
|
|
243
|
+
borderStyle: 'none',
|
|
244
|
+
borderWidth: '0px',
|
|
245
|
+
width: bar.width,
|
|
246
|
+
height: bar.height,
|
|
247
|
+
x: bar.x,
|
|
248
|
+
y: bar.y,
|
|
249
|
+
onMouseOver: () => {}, // No interaction
|
|
250
|
+
onMouseLeave: () => {}, // No interaction
|
|
251
|
+
tooltipHtml: '',
|
|
252
|
+
tooltipId: '',
|
|
253
|
+
onClick: () => {}, // No interaction
|
|
254
|
+
styleOverrides: {
|
|
255
|
+
animationDelay: `${barStack.index * 0.5}s`,
|
|
256
|
+
transformOrigin: `${bar.x}px 0`,
|
|
257
|
+
opacity: animatedChart ? 0 : transparentBar ? 0.2 : 1, // Start hidden if animated
|
|
258
|
+
display: displayBar ? 'block' : 'none',
|
|
259
|
+
pointerEvents: 'none', // Let clicks pass through to base bar
|
|
260
|
+
// Force the initial transform state to match CSS animation
|
|
261
|
+
transform: animatedChart ? 'scale(0, 1)' : 'scale(1, 1)'
|
|
262
|
+
}
|
|
263
|
+
})}
|
|
264
|
+
|
|
132
265
|
{orientation === 'horizontal' &&
|
|
133
266
|
visualizationSubType === 'stacked' &&
|
|
134
267
|
isLabelBelowBar &&
|
|
@@ -158,7 +291,7 @@ const BarChartStackedHorizontal = () => {
|
|
|
158
291
|
</Text>
|
|
159
292
|
)}
|
|
160
293
|
</Group>
|
|
161
|
-
|
|
294
|
+
</React.Fragment>
|
|
162
295
|
)
|
|
163
296
|
})
|
|
164
297
|
)
|