@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
|
@@ -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 {
|
|
5
|
+
import { PatternLines, PatternCircles, PatternWaves } from '@visx/pattern'
|
|
6
6
|
import BarChartContext from './context'
|
|
7
7
|
import Regions from '../../Regions'
|
|
8
|
-
import {
|
|
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
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
-
|
|
121
|
-
)
|
|
252
|
+
)
|
|
253
|
+
})
|
|
122
254
|
})
|
|
123
|
-
|
|
124
|
-
}
|
|
255
|
+
}}
|
|
125
256
|
</BarStack>
|
|
126
|
-
<Regions
|
|
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)
|
|
310
|
+
if (isTwoToneLollipopColor) {
|
|
311
|
+
_barColor = getLollipopStemColor(barColor)
|
|
312
|
+
}
|
|
240
313
|
return _barColor
|
|
241
314
|
}
|
|
242
315
|
|
|
243
|
-
// if this is a two tone lollipop
|
|
244
|
-
if (isTwoToneLollipopColor)
|
|
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:
|
|
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
|
|
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={
|
|
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={
|
|
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={
|
|
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}`}
|