Skip to content

Commit 3b644ce

Browse files
[charts] Add bar gradient example (#19174)
Signed-off-by: Bernardo Belchior <[email protected]> Co-authored-by: Jose C Quintas Jr <[email protected]>
1 parent 0e3b886 commit 3b644ce

File tree

9 files changed

+590
-214
lines changed

9 files changed

+590
-214
lines changed

docs/data/charts/bars/BarGradient.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export default function BarGradient() {
2424
{...settings}
2525
sx={{
2626
[`& .${barClasses.series}[data-series="2"] .${barElementClasses.root}`]: {
27-
fill: 'url(#gradient)',
27+
fill: 'url(#bar-gradient)',
2828
},
2929
[`& .${barClasses.seriesLabels}[data-series="2"] .${barLabelClasses.root}`]:
3030
{
@@ -33,7 +33,7 @@ export default function BarGradient() {
3333
}}
3434
>
3535
<defs>
36-
<Gradient id="gradient" />
36+
<Gradient id="bar-gradient" />
3737
</defs>
3838
</BarChart>
3939
);

docs/data/charts/bars/BarGradient.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export default function BarGradient() {
2424
{...settings}
2525
sx={{
2626
[`& .${barClasses.series}[data-series="2"] .${barElementClasses.root}`]: {
27-
fill: 'url(#gradient)',
27+
fill: 'url(#bar-gradient)',
2828
},
2929
[`& .${barClasses.seriesLabels}[data-series="2"] .${barLabelClasses.root}`]:
3030
{
@@ -33,7 +33,7 @@ export default function BarGradient() {
3333
}}
3434
>
3535
<defs>
36-
<Gradient id="gradient" />
36+
<Gradient id="bar-gradient" />
3737
</defs>
3838
</BarChart>
3939
);

docs/data/charts/bars/BarGradient.tsx.preview

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
{...settings}
33
sx={{
44
[`& .${barClasses.series}[data-series="2"] .${barElementClasses.root}`]: {
5-
fill: 'url(#gradient)',
5+
fill: 'url(#bar-gradient)',
66
},
77
[`& .${barClasses.seriesLabels}[data-series="2"] .${barLabelClasses.root}`]:
88
{
@@ -11,6 +11,6 @@
1111
}}
1212
>
1313
<defs>
14-
<Gradient id="gradient" />
14+
<Gradient id="bar-gradient" />
1515
</defs>
1616
</BarChart>
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import * as React from 'react';
2+
import { BarChart, barElementClasses } from '@mui/x-charts/BarChart';
3+
import Stack from '@mui/material/Stack';
4+
import Typography from '@mui/material/Typography';
5+
import FormControl from '@mui/material/FormControl';
6+
import FormLabel from '@mui/material/FormLabel';
7+
import RadioGroup from '@mui/material/RadioGroup';
8+
import FormControlLabel from '@mui/material/FormControlLabel';
9+
import Radio from '@mui/material/Radio';
10+
import { countryData } from '../dataset/countryData';
11+
import householdSavings from '../dataset/oecdHouseholdSavings2024.json';
12+
13+
/* Source: https://www.oecd.org/en/data/indicators/household-savings.html */
14+
const savings = Object.entries(householdSavings)
15+
.map(([country, value]) => ({
16+
country,
17+
value,
18+
}))
19+
.sort((a, b) => a.value - b.value);
20+
21+
const percentageFormatter = new Intl.NumberFormat('en-US', {
22+
style: 'percent',
23+
maximumFractionDigits: 1,
24+
});
25+
26+
const settings = {
27+
height: 600,
28+
xAxis: [{ label: 'Household Savings (%)' }],
29+
layout: 'horizontal',
30+
hideLegend: true,
31+
};
32+
33+
export default function BarOECDHouseholdSavings() {
34+
const [gradientUnits, setGradientUnits] = React.useState('userSpaceOnUse');
35+
36+
return (
37+
<Stack width="100%">
38+
<Typography variant="h6" textAlign="center">
39+
Household Savings in OECD Countries (2016)
40+
</Typography>
41+
42+
<FormControl fullWidth>
43+
<FormLabel id="gradient-units-label">Gradient Units</FormLabel>
44+
<RadioGroup
45+
row
46+
aria-labelledby="gradient-units-label"
47+
name="gradient-units"
48+
value={gradientUnits}
49+
onChange={(event) => setGradientUnits(event.target.value)}
50+
>
51+
<FormControlLabel
52+
value="objectBoundingBox"
53+
control={<Radio />}
54+
label="objectBoundingBox (default)"
55+
/>
56+
<FormControlLabel
57+
value="userSpaceOnUse"
58+
control={<Radio />}
59+
label="userSpaceOnUse"
60+
/>
61+
</RadioGroup>
62+
</FormControl>
63+
64+
<BarChart
65+
{...settings}
66+
yAxis={[
67+
{
68+
data: savings.map((v) => v.country),
69+
valueFormatter: (value) => countryData[value].country,
70+
width: 100,
71+
},
72+
]}
73+
series={[
74+
{
75+
label: 'Household Savings',
76+
data: savings.map((v) => v.value),
77+
color: 'url(#savings-gradient)',
78+
valueFormatter: (value) => percentageFormatter.format(value / 100),
79+
},
80+
]}
81+
sx={
82+
gradientUnits === 'userSpaceOnUse'
83+
? {
84+
[`.${barElementClasses.root}`]: {
85+
fill: 'url(#savings-gradient-user-space)',
86+
},
87+
}
88+
: undefined
89+
}
90+
>
91+
<Gradient id="savings-gradient" x1="0" x2="100%" />
92+
<Gradient
93+
id="savings-gradient-user-space"
94+
gradientUnits="userSpaceOnUse"
95+
x1="150"
96+
x2="100%"
97+
/>
98+
</BarChart>
99+
<Typography variant="caption">Source: Our World in Data</Typography>
100+
</Stack>
101+
);
102+
}
103+
104+
function Gradient(props) {
105+
return (
106+
<linearGradient x1="0" y1="0%" x2="100%" y2="0%" {...props}>
107+
<stop offset="0" stopColor="#ff2f1b" />
108+
<stop offset="0.5" stopColor="#fce202" />
109+
<stop offset="1" stopColor="#02b32b" />
110+
</linearGradient>
111+
);
112+
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import * as React from 'react';
2+
import { BarChart, BarChartProps, barElementClasses } from '@mui/x-charts/BarChart';
3+
import Stack from '@mui/material/Stack';
4+
import Typography from '@mui/material/Typography';
5+
import FormControl from '@mui/material/FormControl';
6+
import FormLabel from '@mui/material/FormLabel';
7+
import RadioGroup from '@mui/material/RadioGroup';
8+
import FormControlLabel from '@mui/material/FormControlLabel';
9+
import Radio from '@mui/material/Radio';
10+
import { countryData } from '../dataset/countryData';
11+
import householdSavings from '../dataset/oecdHouseholdSavings2024.json';
12+
13+
/* Source: https://www.oecd.org/en/data/indicators/household-savings.html */
14+
const savings = Object.entries(householdSavings)
15+
.map(([country, value]) => ({
16+
country,
17+
value,
18+
}))
19+
.sort((a, b) => a.value - b.value);
20+
21+
const percentageFormatter = new Intl.NumberFormat('en-US', {
22+
style: 'percent',
23+
maximumFractionDigits: 1,
24+
});
25+
26+
const settings = {
27+
height: 600,
28+
xAxis: [{ label: 'Household Savings (%)' }],
29+
layout: 'horizontal',
30+
hideLegend: true,
31+
} satisfies Partial<BarChartProps>;
32+
33+
export default function BarOECDHouseholdSavings() {
34+
const [gradientUnits, setGradientUnits] = React.useState<
35+
'objectBoundingBox' | 'userSpaceOnUse'
36+
>('userSpaceOnUse');
37+
38+
return (
39+
<Stack width="100%">
40+
<Typography variant="h6" textAlign="center">
41+
Household Savings in OECD Countries (2016)
42+
</Typography>
43+
44+
<FormControl fullWidth>
45+
<FormLabel id="gradient-units-label">Gradient Units</FormLabel>
46+
<RadioGroup
47+
row
48+
aria-labelledby="gradient-units-label"
49+
name="gradient-units"
50+
value={gradientUnits}
51+
onChange={(event) =>
52+
setGradientUnits(
53+
event.target.value as 'objectBoundingBox' | 'userSpaceOnUse',
54+
)
55+
}
56+
>
57+
<FormControlLabel
58+
value="objectBoundingBox"
59+
control={<Radio />}
60+
label="objectBoundingBox (default)"
61+
/>
62+
<FormControlLabel
63+
value="userSpaceOnUse"
64+
control={<Radio />}
65+
label="userSpaceOnUse"
66+
/>
67+
</RadioGroup>
68+
</FormControl>
69+
70+
<BarChart
71+
{...settings}
72+
yAxis={[
73+
{
74+
data: savings.map((v) => v.country),
75+
valueFormatter: (value: keyof typeof countryData) =>
76+
countryData[value].country,
77+
width: 100,
78+
},
79+
]}
80+
series={[
81+
{
82+
label: 'Household Savings',
83+
data: savings.map((v) => v.value),
84+
color: 'url(#savings-gradient)',
85+
valueFormatter: (value) => percentageFormatter.format(value! / 100),
86+
},
87+
]}
88+
sx={
89+
gradientUnits === 'userSpaceOnUse'
90+
? {
91+
[`.${barElementClasses.root}`]: {
92+
fill: 'url(#savings-gradient-user-space)',
93+
},
94+
}
95+
: undefined
96+
}
97+
>
98+
<Gradient id="savings-gradient" x1="0" x2="100%" />
99+
<Gradient
100+
id="savings-gradient-user-space"
101+
gradientUnits="userSpaceOnUse"
102+
x1="150"
103+
x2="100%"
104+
/>
105+
</BarChart>
106+
<Typography variant="caption">Source: Our World in Data</Typography>
107+
</Stack>
108+
);
109+
}
110+
111+
function Gradient(props: React.SVGProps<SVGLinearGradientElement>) {
112+
return (
113+
<linearGradient x1="0" y1="0%" x2="100%" y2="0%" {...props}>
114+
<stop offset="0" stopColor="#ff2f1b" />
115+
<stop offset="0.5" stopColor="#fce202" />
116+
<stop offset="1" stopColor="#02b32b" />
117+
</linearGradient>
118+
);
119+
}

docs/data/charts/bars/bars.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,25 @@ You can use this attribute to target elements based on their series.
135135

136136
{{"demo": "BarGradient.js"}}
137137

138+
### Gradients
139+
140+
By default, a gradient's units are set to [`objectBoundingBox`](https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Attribute/gradientUnits#objectboundingbox).
141+
When applied to a bar, the gradient stretches to fill the entire size of the bar, regardless of the bar's value.
142+
143+
Alternatively, you can set `gradientUnits` to `userSpaceOnUse`, which stretches the gradient to fill the entire size of the chart.
144+
`userSpaceOnUse` means that the gradient's coordinates are relative to the SVG, meaning that a gradient with `x1="0"` and `x2="100%"` stretches across the entire width of the SVG.
145+
This effectively reveals the gradient depending on the bar's value, as the gradient is clipped to the bar's size.
146+
147+
{{"demo": "BarOECDHouseholdSavings.js"}}
148+
149+
Note that, in the example above, there are two gradients:
150+
151+
- The series `color` property references the gradient with `gradientUnits="objectBoundingBox"`, which is applied to the tooltip, legend, and other elements that reference the series color.
152+
- The bar's `fill` property is overridden using CSS to reference the gradient with `gradientUnits="userSpaceOnUse"`.
153+
154+
The first gradient is used for elements showing the whole gradient, such as tooltips and legend.
155+
The second one is shown in the bars themselves that display the part of the gradient that corresponds to their value.
156+
138157
## Labels
139158

140159
You can display labels on the bars.

0 commit comments

Comments
 (0)