@cdc/chart 4.25.8 → 4.25.11
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-1a1724a1.es.js → cdcchart-dgT_1dIT.es.js} +136 -151
- package/dist/cdcchart.js +44236 -40355
- package/examples/feature/__data__/planet-example-data.json +0 -30
- package/examples/feature/boxplot/valid-boxplot.csv +38 -17
- package/examples/grouped-bar-test.json +400 -0
- package/examples/private/DEV-11825.json +573 -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/na.json +913 -0
- package/examples/private/new.json +48756 -0
- package/examples/private/pie-chart-legend.json +904 -0
- package/examples/private/test-data.csv +28 -0
- package/examples/suppressed_tooltip.json +480 -0
- package/index.html +2 -133
- package/package.json +25 -7
- package/src/CdcChart.tsx +9 -13
- package/src/CdcChartComponent.tsx +403 -92
- 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.Combo.stories.tsx +18 -0
- 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.Forecast.stories.tsx +36 -0
- package/src/_stories/Chart.HTMLInDataTable.stories.tsx +520 -0
- package/src/_stories/Chart.Legend.Gradient.stories.tsx +2 -2
- package/src/_stories/Chart.Patterns.stories.tsx +20 -0
- package/src/_stories/Chart.PreserveDecimals.stories.tsx +220 -0
- package/src/_stories/Chart.ScatterPlot.stories.tsx +1 -1
- package/src/_stories/Chart.SmallMultiples.stories.tsx +47 -0
- package/src/_stories/Chart.stories.tsx +8 -5
- package/src/_stories/Chart.tooltip.stories.tsx +1 -1
- package/src/_stories/ChartAnnotation.stories.tsx +7 -4
- package/src/_stories/ChartAxisLabels.stories.tsx +2 -2
- package/src/_stories/ChartAxisTitles.stories.tsx +2 -2
- package/src/_stories/ChartBar.Editor.stories.tsx +3580 -0
- package/src/_stories/ChartEditor.Editor.stories.tsx +658 -0
- package/src/_stories/ChartEditor.stories.tsx +59 -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/combo.json +451 -0
- package/src/_stories/_mock/editor-test-configs.json +376 -0
- package/src/_stories/_mock/editor-test-datasets.json +477 -0
- package/src/_stories/_mock/editor-tests/bar-chart-editor-test.json +255 -0
- package/src/_stories/_mock/editor-tests/bar-chart-general-test.json +267 -0
- package/src/_stories/_mock/editor-tests/bar-chart-test.json +237 -0
- package/src/_stories/_mock/forecast_combo_with_gaps.json +913 -0
- package/src/_stories/_mock/pie_config.json +257 -62
- package/src/_stories/_mock/small_multiples/small_multiples_bars.json +1944 -0
- package/src/_stories/_mock/small_multiples/small_multiples_big_data_bars.json +1114 -0
- package/src/_stories/_mock/small_multiples/small_multiples_lines.json +2646 -0
- package/src/_stories/_mock/small_multiples/small_multiples_lines_colors.json +1305 -0
- package/src/_stories/_mock/small_multiples/small_multiples_stacked_bars.json +1936 -0
- 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/Annotations/components/findNearestDatum.ts +6 -41
- package/src/components/AreaChart/components/AreaChart.Stacked.jsx +10 -6
- package/src/components/AreaChart/index.tsx +1 -2
- package/src/components/BarChart/components/BarChart.Horizontal.tsx +161 -22
- 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 +155 -22
- 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/BoxPlot/helpers/index.ts +3 -3
- package/src/components/Brush/BrushChart.tsx +1 -1
- package/src/components/DeviationBar.jsx +9 -6
- package/src/components/EditorPanel/EditorPanel.tsx +563 -229
- package/src/components/EditorPanel/EditorPanelContext.ts +3 -0
- package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +96 -111
- package/src/components/EditorPanel/components/Panels/Panel.General.tsx +19 -1
- package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +461 -0
- package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +80 -67
- package/src/components/EditorPanel/components/Panels/Panel.SmallMultiples.tsx +422 -0
- package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +188 -139
- package/src/components/EditorPanel/components/Panels/index.tsx +5 -1
- package/src/components/EditorPanel/components/Panels/panelVisual.styles.css +0 -8
- package/src/components/EditorPanel/editor-panel.scss +0 -20
- package/src/components/EditorPanel/helpers/updateFieldRankByValue.ts +49 -48
- package/src/components/EditorPanel/useEditorPermissions.ts +7 -15
- package/src/components/Forecasting/Forecasting.tsx +175 -27
- package/src/components/ForestPlot/ForestPlot.tsx +11 -7
- package/src/components/ForestPlot/ForestPlotProps.ts +1 -1
- package/src/components/Legend/Legend.Component.tsx +114 -14
- package/src/components/Legend/helpers/createFormatLabels.tsx +230 -171
- package/src/components/Legend/helpers/getLegendClasses.ts +0 -1
- package/src/components/LegendWrapper.tsx +1 -1
- package/src/components/LineChart/LineChartProps.ts +0 -3
- package/src/components/LineChart/components/LineChart.Circle.tsx +2 -2
- package/src/components/LineChart/helpers.ts +1 -1
- package/src/components/LineChart/index.tsx +38 -15
- package/src/components/LinearChart.tsx +96 -84
- package/src/components/PairedBarChart.jsx +6 -4
- package/src/components/PieChart/PieChart.tsx +170 -54
- package/src/components/Regions/components/Regions.tsx +3 -24
- package/src/components/Sankey/components/Sankey.tsx +7 -1
- package/src/components/Sankey/types/index.ts +1 -1
- package/src/components/ScatterPlot/ScatterPlot.jsx +32 -4
- package/src/components/SmallMultiples/SmallMultipleTile.tsx +198 -0
- package/src/components/SmallMultiples/SmallMultiples.css +32 -0
- package/src/components/SmallMultiples/SmallMultiples.tsx +271 -0
- package/src/components/SmallMultiples/index.ts +2 -0
- package/src/data/initial-state.js +327 -293
- package/src/helpers/buildForecastPaletteMappings.ts +112 -0
- package/src/helpers/buildForecastPaletteOptions.ts +71 -0
- package/src/helpers/getColorScale.ts +82 -8
- package/src/{hooks/useMinMax.ts → helpers/getMinMax.ts} +14 -7
- package/src/helpers/getNewRuntime.ts +1 -1
- package/src/helpers/getTransformedData.ts +1 -1
- package/src/helpers/getYAxisAutoPadding.ts +53 -0
- package/src/helpers/smallMultiplesHelpers.ts +529 -0
- package/src/hooks/useChartHoverAnalytics.tsx +44 -0
- package/src/hooks/useProgrammaticTooltip.ts +96 -0
- package/src/hooks/useReduceData.ts +105 -70
- package/src/hooks/useScales.ts +88 -34
- package/src/hooks/useSmallMultipleSynchronization.ts +59 -0
- package/src/hooks/useTooltip.tsx +116 -29
- package/src/index.jsx +0 -2
- package/src/scss/main.scss +13 -80
- package/src/store/chart.actions.ts +2 -0
- package/src/store/chart.reducer.ts +5 -1
- package/src/test/CdcChart.test.jsx +8 -3
- package/src/types/ChartConfig.ts +53 -11
- package/src/types/ChartContext.ts +4 -0
- package/vite.config.js +1 -1
- package/vitest.config.ts +16 -0
- package/src/_stories/_mock/pie_data.json +0 -218
- package/src/components/AreaChart/components/AreaChart.jsx +0 -109
- package/src/coreStyles_chart.scss +0 -3
- package/src/helpers/configHelpers.ts +0 -28
- package/src/helpers/generateColorsArray.ts +0 -8
- package/src/helpers/sort.ts +0 -7
- package/src/hooks/useActiveElement.js +0 -19
- package/src/hooks/useChartClasses.js +0 -41
- 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,14 +18,12 @@ 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'
|
|
25
24
|
import { getBarData } from '../helpers/getBarData'
|
|
26
25
|
|
|
27
|
-
|
|
26
|
+
const BarChartVertical = () => {
|
|
28
27
|
const { xScale, yScale, xMax, yMax, seriesScale, convertLineToBarGraph, barChart } =
|
|
29
28
|
useContext<BarChartContextValues>(BarChartContext)
|
|
30
29
|
const {
|
|
@@ -47,6 +46,7 @@ export const BarChartVertical = () => {
|
|
|
47
46
|
colorScale,
|
|
48
47
|
config,
|
|
49
48
|
currentViewport,
|
|
49
|
+
vizViewport,
|
|
50
50
|
dashboardConfig,
|
|
51
51
|
tableData,
|
|
52
52
|
formatDate,
|
|
@@ -59,6 +59,8 @@ export const BarChartVertical = () => {
|
|
|
59
59
|
|
|
60
60
|
const { HighLightedBarUtils } = useHighlightedBars(config)
|
|
61
61
|
|
|
62
|
+
const LABEL_FONT_SIZE = isMobileFontViewport(vizViewport) ? 13 : 16
|
|
63
|
+
|
|
62
64
|
const root = document.documentElement
|
|
63
65
|
|
|
64
66
|
let data = transformedData
|
|
@@ -76,11 +78,80 @@ export const BarChartVertical = () => {
|
|
|
76
78
|
config.confidenceKeys.lower !== ''
|
|
77
79
|
|
|
78
80
|
const _data = getBarData(config, data, hasConfidenceInterval)
|
|
81
|
+
|
|
82
|
+
// Pattern helper function
|
|
83
|
+
const renderPatternDefs = () => {
|
|
84
|
+
if (!config.legend.patterns || Object.keys(config.legend.patterns).length === 0) {
|
|
85
|
+
return null
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<defs>
|
|
90
|
+
{Object.entries(config.legend.patterns).map(([key, pattern]) => {
|
|
91
|
+
const patternId = `chart-pattern-${key}`
|
|
92
|
+
const size = pattern.patternSize || 8
|
|
93
|
+
|
|
94
|
+
switch (pattern.shape) {
|
|
95
|
+
case 'circles':
|
|
96
|
+
return (
|
|
97
|
+
<PatternCircles
|
|
98
|
+
key={patternId}
|
|
99
|
+
id={patternId}
|
|
100
|
+
height={size}
|
|
101
|
+
width={size}
|
|
102
|
+
fill={pattern.color}
|
|
103
|
+
radius={1.25}
|
|
104
|
+
/>
|
|
105
|
+
)
|
|
106
|
+
case 'lines':
|
|
107
|
+
return (
|
|
108
|
+
<PatternLines
|
|
109
|
+
key={patternId}
|
|
110
|
+
id={patternId}
|
|
111
|
+
height={size}
|
|
112
|
+
width={size}
|
|
113
|
+
stroke={pattern.color}
|
|
114
|
+
strokeWidth={0.75}
|
|
115
|
+
orientation={['horizontal']}
|
|
116
|
+
/>
|
|
117
|
+
)
|
|
118
|
+
case 'diagonalLines':
|
|
119
|
+
return (
|
|
120
|
+
<PatternLines
|
|
121
|
+
key={patternId}
|
|
122
|
+
id={patternId}
|
|
123
|
+
height={size}
|
|
124
|
+
width={size}
|
|
125
|
+
stroke={pattern.color}
|
|
126
|
+
strokeWidth={0.75}
|
|
127
|
+
orientation={['diagonalRightToLeft']}
|
|
128
|
+
/>
|
|
129
|
+
)
|
|
130
|
+
case 'waves':
|
|
131
|
+
return (
|
|
132
|
+
<PatternWaves
|
|
133
|
+
key={patternId}
|
|
134
|
+
id={patternId}
|
|
135
|
+
height={size}
|
|
136
|
+
width={size}
|
|
137
|
+
fill={pattern.color}
|
|
138
|
+
strokeWidth={0.25}
|
|
139
|
+
/>
|
|
140
|
+
)
|
|
141
|
+
default:
|
|
142
|
+
return null
|
|
143
|
+
}
|
|
144
|
+
})}
|
|
145
|
+
</defs>
|
|
146
|
+
)
|
|
147
|
+
}
|
|
148
|
+
|
|
79
149
|
return (
|
|
80
150
|
config.visualizationSubType !== 'stacked' &&
|
|
81
151
|
(config.visualizationType === 'Bar' || config.visualizationType === 'Combo' || convertLineToBarGraph) &&
|
|
82
152
|
config.orientation === 'vertical' && (
|
|
83
153
|
<Group>
|
|
154
|
+
{renderPatternDefs()}
|
|
84
155
|
<BarGroup
|
|
85
156
|
data={_data}
|
|
86
157
|
keys={config.runtime.barSeriesKeys || config.runtime.seriesKeys}
|
|
@@ -170,7 +241,8 @@ export const BarChartVertical = () => {
|
|
|
170
241
|
config,
|
|
171
242
|
barWidth,
|
|
172
243
|
isVertical: true,
|
|
173
|
-
yAxisValue
|
|
244
|
+
yAxisValue,
|
|
245
|
+
labelFontSize: LABEL_FONT_SIZE
|
|
174
246
|
})
|
|
175
247
|
// configure colors
|
|
176
248
|
let labelColor = APP_FONT_COLOR
|
|
@@ -236,12 +308,16 @@ export const BarChartVertical = () => {
|
|
|
236
308
|
if (isHighlightedBar) _barColor = 'transparent'
|
|
237
309
|
if (config.legend.colorCode)
|
|
238
310
|
_barColor = assignColorsToValues(barGroups.length, barGroup.index, barColor)
|
|
239
|
-
if (isTwoToneLollipopColor)
|
|
311
|
+
if (isTwoToneLollipopColor) {
|
|
312
|
+
_barColor = getLollipopStemColor(barColor)
|
|
313
|
+
}
|
|
240
314
|
return _barColor
|
|
241
315
|
}
|
|
242
316
|
|
|
243
|
-
// if this is a two tone lollipop
|
|
244
|
-
if (isTwoToneLollipopColor)
|
|
317
|
+
// if this is a two tone lollipop, ensure stem has good contrast against white but is lighter than head
|
|
318
|
+
if (isTwoToneLollipopColor) {
|
|
319
|
+
_barColor = getLollipopStemColor(barColor)
|
|
320
|
+
}
|
|
245
321
|
if (config.legend.colorCode)
|
|
246
322
|
_barColor = assignColorsToValues(barGroups.length, barGroup.index, barColor)
|
|
247
323
|
|
|
@@ -250,6 +326,36 @@ export const BarChartVertical = () => {
|
|
|
250
326
|
return _barColor
|
|
251
327
|
}
|
|
252
328
|
|
|
329
|
+
// Check if this bar should use a pattern
|
|
330
|
+
const getPatternUrl = (): string | null => {
|
|
331
|
+
if (!config.legend.patterns || Object.keys(config.legend.patterns).length === 0) {
|
|
332
|
+
return null
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Find a pattern that matches this specific bar
|
|
336
|
+
for (const [patternKey, pattern] of Object.entries(config.legend.patterns)) {
|
|
337
|
+
if (pattern.dataKey && pattern.dataValue) {
|
|
338
|
+
// For grouped bar charts, check if the pattern's dataKey matches the current bar's series key
|
|
339
|
+
// and if the pattern's dataValue matches the current bar's value
|
|
340
|
+
if (pattern.dataKey === bar.key && String(bar.value) === String(pattern.dataValue)) {
|
|
341
|
+
return `url(#chart-pattern-${patternKey})`
|
|
342
|
+
}
|
|
343
|
+
// Fallback for non-grouped charts: check datum field value
|
|
344
|
+
else if (!config.series || config.series.length <= 1) {
|
|
345
|
+
const dataFieldValue = datum[pattern.dataKey]
|
|
346
|
+
if (String(dataFieldValue) === String(pattern.dataValue)) {
|
|
347
|
+
return `url(#chart-pattern-${patternKey})`
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
return null
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const patternUrl = getPatternUrl()
|
|
357
|
+
const baseBackground = getBarBackgroundColor(colorScale(config.runtime.seriesLabels[bar.key]))
|
|
358
|
+
|
|
253
359
|
// Confidence Interval Variables
|
|
254
360
|
const tickWidth = 5
|
|
255
361
|
const xPos = barX + (config.xAxis.type !== 'date-time' ? barWidth / 2 : 0)
|
|
@@ -268,16 +374,15 @@ export const BarChartVertical = () => {
|
|
|
268
374
|
|
|
269
375
|
const BAR_LABEL_PADDING = 10
|
|
270
376
|
|
|
271
|
-
const LABEL_FONT_SIZE = isMobileFontViewport(currentViewport) ? 13 : 16
|
|
272
|
-
|
|
273
377
|
return (
|
|
274
378
|
<Group display={hideGroup} key={`${barGroup.index}--${index}`}>
|
|
275
379
|
<Group key={`bar-sub-group-${barGroup.index}-${barGroup.x0}-${barY}--${index}`}>
|
|
380
|
+
{/* Base colored bar */}
|
|
276
381
|
{createBarElement({
|
|
277
382
|
config: config,
|
|
278
383
|
index: newIndex,
|
|
279
384
|
id: `barGroup${barGroup.index}`,
|
|
280
|
-
background:
|
|
385
|
+
background: baseBackground,
|
|
281
386
|
borderColor,
|
|
282
387
|
borderStyle: 'solid',
|
|
283
388
|
borderWidth: `${borderWidth}px`,
|
|
@@ -285,7 +390,7 @@ export const BarChartVertical = () => {
|
|
|
285
390
|
height: barHeight,
|
|
286
391
|
x: barX,
|
|
287
392
|
y: barY,
|
|
288
|
-
onMouseOver: e => onMouseOverBar(xAxisValue, bar.key, e, data),
|
|
393
|
+
onMouseOver: e => onMouseOverBar(xAxisValue, bar.key, e, data, bar.value),
|
|
289
394
|
onMouseLeave: onMouseLeaveBar,
|
|
290
395
|
tooltipHtml: tooltip,
|
|
291
396
|
tooltipId: `cdc-open-viz-tooltip-${config.runtime.uniqueId}`,
|
|
@@ -304,6 +409,32 @@ export const BarChartVertical = () => {
|
|
|
304
409
|
}
|
|
305
410
|
})}
|
|
306
411
|
|
|
412
|
+
{/* Pattern overlay if pattern exists */}
|
|
413
|
+
{patternUrl &&
|
|
414
|
+
createBarElement({
|
|
415
|
+
config: config,
|
|
416
|
+
index: newIndex,
|
|
417
|
+
background: patternUrl, // Use pattern as background
|
|
418
|
+
borderColor: 'transparent',
|
|
419
|
+
borderStyle: 'none',
|
|
420
|
+
borderWidth: '0px',
|
|
421
|
+
width: barWidth,
|
|
422
|
+
height: barHeight,
|
|
423
|
+
x: barX,
|
|
424
|
+
y: barY,
|
|
425
|
+
onMouseOver: () => {}, // No interaction
|
|
426
|
+
onMouseLeave: () => {}, // No interaction
|
|
427
|
+
tooltipHtml: '',
|
|
428
|
+
tooltipId: '',
|
|
429
|
+
onClick: () => {}, // No interaction
|
|
430
|
+
styleOverrides: {
|
|
431
|
+
transformOrigin: `0 ${barY + barHeight}px`,
|
|
432
|
+
opacity: transparentBar ? 0.2 : 1,
|
|
433
|
+
display: displayBar ? 'block' : 'none',
|
|
434
|
+
pointerEvents: 'none' // Let clicks pass through to base bar
|
|
435
|
+
}
|
|
436
|
+
})}
|
|
437
|
+
|
|
307
438
|
{(absentDataLabel || isSuppressed) && (
|
|
308
439
|
<rect
|
|
309
440
|
x={barX}
|
|
@@ -337,13 +468,7 @@ export const BarChartVertical = () => {
|
|
|
337
468
|
const hasAsterisk = String(pd.symbol).includes('Asterisk')
|
|
338
469
|
const yPadding = hasAsterisk ? -5 : -8
|
|
339
470
|
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'
|
|
471
|
+
const fillColor = pd.displayGray ? '#8b8b8a' : APP_FONT_COLOR
|
|
347
472
|
|
|
348
473
|
return (
|
|
349
474
|
<Text // prettier-ignore
|
|
@@ -356,7 +481,7 @@ export const BarChartVertical = () => {
|
|
|
356
481
|
verticalAnchor={verticalAnchor}
|
|
357
482
|
fill={fillColor}
|
|
358
483
|
textAnchor='middle'
|
|
359
|
-
fontSize={
|
|
484
|
+
fontSize={LABEL_FONT_SIZE}
|
|
360
485
|
>
|
|
361
486
|
{pd.iconCode}
|
|
362
487
|
</Text>
|
|
@@ -390,7 +515,11 @@ export const BarChartVertical = () => {
|
|
|
390
515
|
cx={barX + lollipopShapeSize / 3.5}
|
|
391
516
|
cy={bar.y}
|
|
392
517
|
r={lollipopShapeSize / 2}
|
|
393
|
-
fill={
|
|
518
|
+
fill={
|
|
519
|
+
isTwoToneLollipopColor
|
|
520
|
+
? getLollipopHeadColor(colorScale(config.runtime.seriesLabels[bar.key]))
|
|
521
|
+
: colorScale(config.runtime.seriesLabels[bar.key])
|
|
522
|
+
}
|
|
394
523
|
key={`circle--${bar.index}`}
|
|
395
524
|
data-tooltip-html={tooltip}
|
|
396
525
|
data-tooltip-id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
|
|
@@ -404,7 +533,11 @@ export const BarChartVertical = () => {
|
|
|
404
533
|
y={bar.y}
|
|
405
534
|
width={lollipopShapeSize}
|
|
406
535
|
height={lollipopShapeSize}
|
|
407
|
-
fill={
|
|
536
|
+
fill={
|
|
537
|
+
isTwoToneLollipopColor
|
|
538
|
+
? getLollipopHeadColor(colorScale(config.runtime.seriesLabels[bar.key]))
|
|
539
|
+
: colorScale(config.runtime.seriesLabels[bar.key])
|
|
540
|
+
}
|
|
408
541
|
key={`circle--${bar.index}`}
|
|
409
542
|
data-tooltip-html={tooltip}
|
|
410
543
|
data-tooltip-id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
|