@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.
- package/.claude/settings.local.json +9 -0
- package/dist/cdcchart.js +39551 -37016
- 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 +10 -4
- package/src/CdcChartComponent.tsx +188 -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 +170 -25
- package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +139 -6
- package/src/components/BarChart/components/BarChart.StackedVertical.tsx +215 -73
- package/src/components/BarChart/components/BarChart.Vertical.tsx +172 -23
- 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/Brush/BrushChart.tsx +65 -10
- package/src/components/Brush/BrushController.tsx +37 -5
- package/src/components/Brush/types.tsx +8 -0
- 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.Annotate.tsx +2 -2
- package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +414 -0
- package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +30 -54
- 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 +110 -2
- package/src/components/Legend/Legend.tsx +3 -1
- package/src/components/Legend/helpers/createFormatLabels.tsx +230 -171
- package/src/components/LegendWrapper.tsx +1 -1
- package/src/components/LineChart/components/LineChart.BumpCircle.tsx +27 -26
- package/src/components/LineChart/components/LineChart.Circle.tsx +2 -2
- package/src/components/LineChart/index.tsx +2 -2
- package/src/components/LinearChart.tsx +26 -9
- 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 -292
- 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 +58 -16
- package/src/index.jsx +6 -3
- 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
|
|
@@ -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
|
-
|
|
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
|
-
|
|
127
|
-
|
|
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 =
|
|
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)
|
|
310
|
+
if (isTwoToneLollipopColor) {
|
|
311
|
+
_barColor = getLollipopStemColor(barColor)
|
|
312
|
+
}
|
|
226
313
|
return _barColor
|
|
227
314
|
}
|
|
228
315
|
|
|
229
|
-
// if this is a two tone lollipop
|
|
230
|
-
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
|
+
}
|
|
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:
|
|
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
|
|
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={
|
|
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 :
|
|
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={
|
|
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={
|
|
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}`}
|