diff --git a/docs/data/charts/bars/BorderRadius.js b/docs/data/charts/bars/BorderRadius.js
index 5d4c25e1a5b8d..03864320f950f 100644
--- a/docs/data/charts/bars/BorderRadius.js
+++ b/docs/data/charts/bars/BorderRadius.js
@@ -1,52 +1,90 @@
import * as React from 'react';
import { BarChart } from '@mui/x-charts/BarChart';
import { axisClasses } from '@mui/x-charts/ChartsAxis';
+import Stack from '@mui/material/Stack';
+import { HighlightedCode } from '@mui/docs/HighlightedCode';
+import TextField from '@mui/material/TextField';
+import MenuItem from '@mui/material/MenuItem';
+import Slider from '@mui/material/Slider';
+import Typography from '@mui/material/Typography';
export default function BorderRadius() {
+ const [layout, setLayout] = React.useState('vertical');
+ const [radius, setRadius] = React.useState(10);
+
return (
-
+
+
+
+ Border Radius
+ setRadius(v)}
+ valueLabelDisplay="auto"
+ min={0}
+ max={50}
+ sx={{ mt: 2 }}
+ />
+
+ setLayout(event.target.value)}
+ >
+
+
+
+
+ `].join(
+ '\n',
+ )}
+ language="jsx"
+ copyButtonHidden
/>
-
+
);
}
const dataset = [
- [59, 57, 86, 21, 'Jan'],
- [50, 52, 78, 28, 'Fev'],
- [47, 53, 106, 41, 'Mar'],
- [54, 56, 92, 73, 'Apr'],
- [57, 69, 92, 99, 'May'],
- [60, 63, 103, 144, 'June'],
- [59, 60, 105, 319, 'July'],
- [65, 60, 106, 249, 'Aug'],
- [51, 51, 95, 131, 'Sept'],
- [60, 65, 97, 55, 'Oct'],
- [67, 64, 76, 48, 'Nov'],
- [61, 70, 103, 25, 'Dec'],
-].map(([london, paris, newYork, seoul, month]) => ({
- london,
- paris,
- newYork,
- seoul,
- month,
+ [3, -7, 'First'],
+ [0, -5, 'Second'],
+ [10, 0, 'Third'],
+ [9, 6, 'Fourth'],
+].map(([high, low, order]) => ({
+ high,
+ low,
+ order,
}));
-
-const valueFormatter = (value) => `${value}mm`;
-
-const chartSetting = {
- series: [{ dataKey: 'seoul', label: 'Seoul rainfall', valueFormatter }],
+const chartSettingsH = {
+ dataset,
height: 300,
+ yAxis: [{ scaleType: 'band', dataKey: 'order' }],
sx: {
[`& .${axisClasses.directionY} .${axisClasses.label}`]: {
transform: 'translateX(-10px)',
},
},
+ slotProps: {
+ legend: {
+ direction: 'row',
+ position: { vertical: 'bottom', horizontal: 'middle' },
+ padding: -5,
+ },
+ },
+};
+const chartSettingsV = {
+ ...chartSettingsH,
+ xAxis: [{ scaleType: 'band', dataKey: 'order' }],
+ yAxis: undefined,
};
diff --git a/docs/data/charts/bars/BorderRadius.tsx b/docs/data/charts/bars/BorderRadius.tsx
index f171db50d7d9f..e1b72ab6feac0 100644
--- a/docs/data/charts/bars/BorderRadius.tsx
+++ b/docs/data/charts/bars/BorderRadius.tsx
@@ -1,52 +1,92 @@
import * as React from 'react';
-import { BarChart } from '@mui/x-charts/BarChart';
+import { BarChart, BarChartProps } from '@mui/x-charts/BarChart';
import { axisClasses } from '@mui/x-charts/ChartsAxis';
+import Stack from '@mui/material/Stack';
+import { HighlightedCode } from '@mui/docs/HighlightedCode';
+import TextField from '@mui/material/TextField';
+import MenuItem from '@mui/material/MenuItem';
+import Slider from '@mui/material/Slider';
+import Typography from '@mui/material/Typography';
export default function BorderRadius() {
+ const [layout, setLayout] = React.useState<'horizontal' | 'vertical'>('vertical');
+ const [radius, setRadius] = React.useState(10);
+
return (
-
+
+
+
+ Border Radius
+ setRadius(v as number)}
+ valueLabelDisplay="auto"
+ min={0}
+ max={50}
+ sx={{ mt: 2 }}
+ />
+
+
+ setLayout(event.target.value as 'horizontal' | 'vertical')
+ }
+ >
+
+
+
+
+ `].join(
+ '\n',
+ )}
+ language="jsx"
+ copyButtonHidden
/>
-
+
);
}
const dataset = [
- [59, 57, 86, 21, 'Jan'],
- [50, 52, 78, 28, 'Fev'],
- [47, 53, 106, 41, 'Mar'],
- [54, 56, 92, 73, 'Apr'],
- [57, 69, 92, 99, 'May'],
- [60, 63, 103, 144, 'June'],
- [59, 60, 105, 319, 'July'],
- [65, 60, 106, 249, 'Aug'],
- [51, 51, 95, 131, 'Sept'],
- [60, 65, 97, 55, 'Oct'],
- [67, 64, 76, 48, 'Nov'],
- [61, 70, 103, 25, 'Dec'],
-].map(([london, paris, newYork, seoul, month]) => ({
- london,
- paris,
- newYork,
- seoul,
- month,
+ [3, -7, 'First'],
+ [0, -5, 'Second'],
+ [10, 0, 'Third'],
+ [9, 6, 'Fourth'],
+].map(([high, low, order]) => ({
+ high,
+ low,
+ order,
}));
-
-const valueFormatter = (value: number | null) => `${value}mm`;
-
-const chartSetting = {
- series: [{ dataKey: 'seoul', label: 'Seoul rainfall', valueFormatter }],
+const chartSettingsH: Partial = {
+ dataset,
height: 300,
+ yAxis: [{ scaleType: 'band', dataKey: 'order' }],
sx: {
[`& .${axisClasses.directionY} .${axisClasses.label}`]: {
transform: 'translateX(-10px)',
},
},
+ slotProps: {
+ legend: {
+ direction: 'row',
+ position: { vertical: 'bottom', horizontal: 'middle' },
+ padding: -5,
+ },
+ },
+};
+const chartSettingsV: Partial = {
+ ...chartSettingsH,
+ xAxis: [{ scaleType: 'band', dataKey: 'order' }],
+ yAxis: undefined,
};
diff --git a/docs/data/charts/bars/BorderRadius.tsx.preview b/docs/data/charts/bars/BorderRadius.tsx.preview
deleted file mode 100644
index a7254b58c0f28..0000000000000
--- a/docs/data/charts/bars/BorderRadius.tsx.preview
+++ /dev/null
@@ -1,9 +0,0 @@
-
\ No newline at end of file
diff --git a/docs/data/charts/bars/bars.md b/docs/data/charts/bars/bars.md
index a3c41180f20c7..9ac226e43ddd4 100644
--- a/docs/data/charts/bars/bars.md
+++ b/docs/data/charts/bars/bars.md
@@ -102,25 +102,12 @@ Learn more about the `colorMap` properties in the [Styling docs](/x/react-charts
### Border Radius
-The border radius can be set by using a [clipPath](https://developer.mozilla.org/en-US/docs/Web/CSS/clip-path) with
-[inset](https://developer.mozilla.org/en-US/docs/Web/CSS/basic-shape/inset) on the BarChart's `bar` [slot](/x/api/charts/bar-chart/#bar-chart-prop-slots)
+To give your bar chart rounded corners, you can change the value of the `borderRadius` property on the [BarChart](/x/api/charts/bar-chart/#bar-chart-prop-slots).
-You can customize any of properties inside `inset`, the first property is "distance from border" and should be left at `0px` else it might break the bars alignment.
-
-```css
-inset(0px round )
-```
+It will work with any positive value and will be properly applied to horizontal layouts, stacks and negative values.
{{"demo": "BorderRadius.js"}}
-:::warning
-There are few limitations to this method though.
-
-- [Stacking](/x/react-charts/bars/#stacking) won't look right with border radius.
-- On charts containing `Negative` and `Positive` values, rounding will apply to all of them in the same way, which might be undesirable.
-
-:::
-
## Click event
Bar charts provides two click handlers:
diff --git a/docs/pages/x/api/charts/bar-chart.json b/docs/pages/x/api/charts/bar-chart.json
index 5e16516b000a7..c371698b721d3 100644
--- a/docs/pages/x/api/charts/bar-chart.json
+++ b/docs/pages/x/api/charts/bar-chart.json
@@ -14,6 +14,7 @@
"text": "highlight docs"
}
},
+ "borderRadius": { "type": { "name": "number" } },
"bottomAxis": {
"type": { "name": "union", "description": "object
| string" },
"default": "xAxisIds[0] The id of the first provided axis"
diff --git a/docs/pages/x/api/charts/bar-plot.json b/docs/pages/x/api/charts/bar-plot.json
index a5f557a11a209..51afe2f2a2d19 100644
--- a/docs/pages/x/api/charts/bar-plot.json
+++ b/docs/pages/x/api/charts/bar-plot.json
@@ -1,5 +1,6 @@
{
"props": {
+ "borderRadius": { "type": { "name": "number" } },
"onItemClick": {
"type": { "name": "func" },
"signature": {
diff --git a/docs/translations/api-docs/charts/bar-chart/bar-chart.json b/docs/translations/api-docs/charts/bar-chart/bar-chart.json
index ee7dd914cfdbd..88dc2317541f3 100644
--- a/docs/translations/api-docs/charts/bar-chart/bar-chart.json
+++ b/docs/translations/api-docs/charts/bar-chart/bar-chart.json
@@ -5,6 +5,7 @@
"description": "The configuration of axes highlight. Default is set to 'band' in the bar direction. Depends on layout
prop.",
"seeMoreText": "See {{link}} for more details."
},
+ "borderRadius": { "description": "Defines the border radius of the bar element." },
"bottomAxis": {
"description": "Indicate which axis to display the bottom of the charts. Can be a string (the id of the axis) or an object ChartsXAxisProps
."
},
diff --git a/docs/translations/api-docs/charts/bar-plot/bar-plot.json b/docs/translations/api-docs/charts/bar-plot/bar-plot.json
index 73f953ae93154..b56710ba45b29 100644
--- a/docs/translations/api-docs/charts/bar-plot/bar-plot.json
+++ b/docs/translations/api-docs/charts/bar-plot/bar-plot.json
@@ -1,6 +1,7 @@
{
"componentDescription": "",
"propDescriptions": {
+ "borderRadius": { "description": "Defines the border radius of the bar element." },
"onItemClick": {
"description": "Callback fired when a bar item is clicked.",
"typeDescriptions": {
diff --git a/packages/x-charts/src/BarChart/BarChart.tsx b/packages/x-charts/src/BarChart/BarChart.tsx
index 78d7ed64dcac8..b67cca32b75f8 100644
--- a/packages/x-charts/src/BarChart/BarChart.tsx
+++ b/packages/x-charts/src/BarChart/BarChart.tsx
@@ -131,6 +131,7 @@ const BarChart = React.forwardRef(function BarChart(props: BarChartProps, ref) {
rightAxis,
bottomAxis,
skipAnimation,
+ borderRadius,
onItemClick,
onAxisClick,
children,
@@ -195,6 +196,7 @@ const BarChart = React.forwardRef(function BarChart(props: BarChartProps, ref) {
slotProps={slotProps}
skipAnimation={skipAnimation}
onItemClick={onItemClick}
+ borderRadius={borderRadius}
/>
@@ -230,6 +232,10 @@ BarChart.propTypes = {
x: PropTypes.oneOf(['band', 'line', 'none']),
y: PropTypes.oneOf(['band', 'line', 'none']),
}),
+ /**
+ * Defines the border radius of the bar element.
+ */
+ borderRadius: PropTypes.number,
/**
* Indicate which axis to display the bottom of the charts.
* Can be a string (the id of the axis) or an object `ChartsXAxisProps`.
diff --git a/packages/x-charts/src/BarChart/BarClipPath.tsx b/packages/x-charts/src/BarChart/BarClipPath.tsx
new file mode 100644
index 0000000000000..b42d1015d7ea9
--- /dev/null
+++ b/packages/x-charts/src/BarChart/BarClipPath.tsx
@@ -0,0 +1,63 @@
+import * as React from 'react';
+import { SpringValue, animated } from '@react-spring/web';
+import { getRadius } from './getRadius';
+
+const buildInset = (corners: {
+ topLeft: number;
+ topRight: number;
+ bottomRight: number;
+ bottomLeft: number;
+}) =>
+ `inset(0px round ${corners.topLeft}px ${corners.topRight}px ${corners.bottomRight}px ${corners.bottomLeft}px)`;
+
+function BarClipRect(props: Record) {
+ const radiusData = props.ownerState;
+
+ return (
+
+ ).to((value) =>
+ buildInset({
+ topLeft: Math.min(value, getRadius('top-left', radiusData)),
+ topRight: Math.min(value, getRadius('top-right', radiusData)),
+ bottomRight: Math.min(value, getRadius('bottom-right', radiusData)),
+ bottomLeft: Math.min(value, getRadius('bottom-left', radiusData)),
+ }),
+ ),
+ }}
+ />
+ );
+}
+
+export interface BarClipPathProps {
+ maskId: string;
+ borderRadius?: number;
+ hasNegative: boolean;
+ hasPositive: boolean;
+ layout?: 'vertical' | 'horizontal';
+ style: {};
+}
+
+/**
+ * @ignore - internal component.
+ */
+function BarClipPath(props: BarClipPathProps) {
+ const { style, maskId, ...rest } = props;
+
+ if (!props.borderRadius || props.borderRadius <= 0) {
+ return null;
+ }
+
+ return (
+
+
+
+ );
+}
+
+export { BarClipPath };
diff --git a/packages/x-charts/src/BarChart/BarElement.tsx b/packages/x-charts/src/BarChart/BarElement.tsx
index 74c9249b8357f..247e1b0344ab0 100644
--- a/packages/x-charts/src/BarChart/BarElement.tsx
+++ b/packages/x-charts/src/BarChart/BarElement.tsx
@@ -131,6 +131,7 @@ function BarElement(props: BarElementProps) {
const classes = useUtilityClasses(ownerState);
const Bar = slots?.bar ?? BarElementPath;
+
const barProps = useSlotProps({
elementType: Bar,
externalSlotProps: slotProps?.bar,
@@ -144,6 +145,7 @@ function BarElement(props: BarElementProps) {
},
ownerState,
});
+
return ;
}
diff --git a/packages/x-charts/src/BarChart/BarPlot.tsx b/packages/x-charts/src/BarChart/BarPlot.tsx
index 4fc7befb51cf3..2d51d448006c4 100644
--- a/packages/x-charts/src/BarChart/BarPlot.tsx
+++ b/packages/x-charts/src/BarChart/BarPlot.tsx
@@ -6,11 +6,12 @@ import { CartesianContext } from '../context/CartesianContextProvider';
import { BarElement, BarElementProps, BarElementSlotProps, BarElementSlots } from './BarElement';
import { AxisDefaultized, isBandScaleConfig, isPointScaleConfig } from '../models/axis';
import { FormatterResult } from '../models/seriesType/config';
-import { HighlightScope } from '../context/HighlightProvider';
-import { BarItemIdentifier, BarSeriesType } from '../models';
+import { BarItemIdentifier } from '../models';
import { DEFAULT_X_AXIS_KEY, DEFAULT_Y_AXIS_KEY } from '../constants';
-import { SeriesId } from '../models/seriesType/common';
import getColor from './getColor';
+import { useChartId } from '../hooks';
+import { AnimationData, CompletedBarData, MaskData } from './types';
+import { BarClipPath } from './BarClipPath';
/**
* Solution of the equations
@@ -63,33 +64,29 @@ export interface BarPlotProps extends Pick,
barItemIdentifier: BarItemIdentifier,
) => void;
+ /**
+ * Defines the border radius of the bar element.
+ */
+ borderRadius?: number;
}
-interface CompletedBarData {
- seriesId: SeriesId;
- dataIndex: number;
- layout: BarSeriesType['layout'];
- x: number;
- y: number;
- xOrigin: number;
- yOrigin: number;
- height: number;
- width: number;
- color: string;
- highlightScope?: Partial;
-}
-
-const useAggregatedData = (): CompletedBarData[] => {
+const useAggregatedData = (): {
+ completedData: CompletedBarData[];
+ masksData: MaskData[];
+} => {
const seriesData =
React.useContext(SeriesContext).bar ??
({ series: {}, stackingGroups: [], seriesOrder: [] } as FormatterResult<'bar'>);
const axisData = React.useContext(CartesianContext);
+ const chartId = useChartId();
const { series, stackingGroups } = seriesData;
const { xAxis, yAxis, xAxisIds, yAxisIds } = axisData;
const defaultXAxisId = xAxisIds[0];
const defaultYAxisId = yAxisIds[0];
+ const masks: Record = {};
+
const data = stackingGroups.flatMap(({ ids: groupIds }, groupIndex) => {
return groupIds.flatMap((seriesId) => {
const xAxisKey = series[seriesId].xAxisKey ?? defaultXAxisId;
@@ -183,13 +180,15 @@ const useAggregatedData = (): CompletedBarData[] => {
const minValueCoord = Math.round(Math.min(...valueCoordinates));
const maxValueCoord = Math.round(Math.max(...valueCoordinates));
- return {
+ const stackId = series[seriesId].stack;
+
+ const result = {
seriesId,
dataIndex,
layout: series[seriesId].layout,
x: verticalLayout
? xScale(xAxis[xAxisKey].data?.[dataIndex])! + barOffset
- : minValueCoord!,
+ : minValueCoord,
y: verticalLayout
? minValueCoord
: yScale(yAxis[yAxisKey].data?.[dataIndex])! + barOffset,
@@ -199,15 +198,45 @@ const useAggregatedData = (): CompletedBarData[] => {
width: verticalLayout ? barWidth : maxValueCoord - minValueCoord,
color: colorGetter(dataIndex),
highlightScope: series[seriesId].highlightScope,
+ value: series[seriesId].data[dataIndex],
+ maskId: `${chartId}_${stackId || seriesId}_${groupIndex}_${dataIndex}`,
};
+
+ if (!masks[result.maskId]) {
+ masks[result.maskId] = {
+ id: result.maskId,
+ width: 0,
+ height: 0,
+ hasNegative: false,
+ hasPositive: false,
+ layout: result.layout,
+ xOrigin: xScale(0)!,
+ yOrigin: yScale(0)!,
+ x: 0,
+ y: 0,
+ };
+ }
+
+ const mask = masks[result.maskId];
+ mask.width = result.layout === 'vertical' ? result.width : mask.width + result.width;
+ mask.height = result.layout === 'vertical' ? mask.height + result.height : result.height;
+ mask.x = Math.min(mask.x === 0 ? Infinity : mask.x, result.x);
+ mask.y = Math.min(mask.y === 0 ? Infinity : mask.y, result.y);
+ mask.hasNegative = mask.hasNegative || (result.value ?? 0) < 0;
+ mask.hasPositive = mask.hasPositive || (result.value ?? 0) > 0;
+
+ return result;
});
});
});
- return data;
+ return {
+ completedData: data,
+ masksData: Object.values(masks),
+ };
};
-const getOutStyle = ({ layout, yOrigin, x, width, y, xOrigin, height }: CompletedBarData) => ({
+const leaveStyle = ({ layout, yOrigin, x, width, y, xOrigin, height }: AnimationData) => ({
...(layout === 'vertical'
? {
y: yOrigin,
@@ -223,7 +252,7 @@ const getOutStyle = ({ layout, yOrigin, x, width, y, xOrigin, height }: Complete
}),
});
-const getInStyle = ({ x, width, y, height }: CompletedBarData) => ({
+const enterStyle = ({ x, width, y, height }: AnimationData) => ({
y,
x,
height,
@@ -242,35 +271,64 @@ const getInStyle = ({ x, width, y, height }: CompletedBarData) => ({
* - [BarPlot API](https://mui.com/x/api/charts/bar-plot/)
*/
function BarPlot(props: BarPlotProps) {
- const completedData = useAggregatedData();
- const { skipAnimation, onItemClick, ...other } = props;
-
+ const { completedData, masksData } = useAggregatedData();
+ const { skipAnimation, onItemClick, borderRadius, ...other } = props;
const transition = useTransition(completedData, {
keys: (bar) => `${bar.seriesId}-${bar.dataIndex}`,
- from: getOutStyle,
- leave: getOutStyle,
- enter: getInStyle,
- update: getInStyle,
+ from: leaveStyle,
+ leave: leaveStyle,
+ enter: enterStyle,
+ update: enterStyle,
+ immediate: skipAnimation,
+ });
+
+ const maskTransition = useTransition(masksData, {
+ keys: (v) => v.id,
+ from: leaveStyle,
+ leave: leaveStyle,
+ enter: enterStyle,
+ update: enterStyle,
immediate: skipAnimation,
});
+
return (
- {transition((style, { seriesId, dataIndex, color, highlightScope }) => (
- {
- onItemClick(event, { type: 'bar', seriesId, dataIndex });
- })
- }
- style={style}
- />
- ))}
+ {maskTransition((style, { id, hasPositive, hasNegative, layout }) => {
+ return (
+
+ );
+ })}
+ {transition((style, { seriesId, dataIndex, color, highlightScope, maskId }) => {
+ const barElement = (
+ {
+ onItemClick(event, { type: 'bar', seriesId, dataIndex });
+ })
+ }
+ style={style}
+ />
+ );
+
+ if (!borderRadius || borderRadius <= 0) {
+ return barElement;
+ }
+
+ return {barElement};
+ })}
);
}
@@ -280,6 +338,10 @@ BarPlot.propTypes = {
// | These PropTypes are generated from the TypeScript type definitions |
// | To update them edit the TypeScript types and run "yarn proptypes" |
// ----------------------------------------------------------------------
+ /**
+ * Defines the border radius of the bar element.
+ */
+ borderRadius: PropTypes.number,
/**
* Callback fired when a bar item is clicked.
* @param {React.MouseEvent} event The event source of the callback.
diff --git a/packages/x-charts/src/BarChart/getRadius.test.ts b/packages/x-charts/src/BarChart/getRadius.test.ts
new file mode 100644
index 0000000000000..461bfa3a47d29
--- /dev/null
+++ b/packages/x-charts/src/BarChart/getRadius.test.ts
@@ -0,0 +1,188 @@
+import { expect } from 'chai';
+import { getRadius } from './getRadius';
+
+describe('getRadius', () => {
+ it('should return 0 if borderRadius is not provided', () => {
+ expect(
+ getRadius('top-left', { hasNegative: false, hasPositive: false, borderRadius: 0 }),
+ ).to.equal(0);
+ expect(
+ getRadius('top-left', { hasNegative: true, hasPositive: true, borderRadius: 0 }),
+ ).to.equal(0);
+ expect(
+ getRadius('top-left', { hasNegative: true, hasPositive: true, borderRadius: undefined }),
+ ).to.equal(0);
+ });
+
+ // ╔═─┐
+ // │ │
+ // └──┘
+ it('should return borderRadius for top-left on vertical layout', () => {
+ expect(
+ getRadius('top-left', {
+ hasNegative: false,
+ hasPositive: true,
+ borderRadius: 10,
+ layout: 'vertical',
+ }),
+ ).to.equal(10);
+ expect(
+ getRadius('top-left', {
+ hasNegative: true,
+ hasPositive: false,
+ borderRadius: 10,
+ layout: 'vertical',
+ }),
+ ).to.equal(0);
+ });
+
+ // ╔═──┐
+ // └───┘
+ it('should return borderRadius for top-left on horizontal layout', () => {
+ expect(
+ getRadius('top-left', {
+ hasNegative: false,
+ hasPositive: true,
+ borderRadius: 10,
+ layout: 'horizontal',
+ }),
+ ).to.equal(0);
+ expect(
+ getRadius('top-left', {
+ hasNegative: true,
+ hasPositive: false,
+ borderRadius: 10,
+ layout: 'horizontal',
+ }),
+ ).to.equal(10);
+ });
+
+ // ┌─═╗
+ // │ │
+ // └──┘
+ it('should return borderRadius for top-right on vertical layout', () => {
+ expect(
+ getRadius('top-right', {
+ hasNegative: false,
+ hasPositive: true,
+ borderRadius: 10,
+ layout: 'vertical',
+ }),
+ ).to.equal(10);
+ expect(
+ getRadius('top-right', {
+ hasNegative: true,
+ hasPositive: false,
+ borderRadius: 10,
+ layout: 'vertical',
+ }),
+ ).to.equal(0);
+ });
+
+ // ┌──═╗
+ // └───┘
+ it('should return borderRadius for top-right on horizontal layout', () => {
+ expect(
+ getRadius('top-right', {
+ hasNegative: false,
+ hasPositive: true,
+ borderRadius: 10,
+ layout: 'horizontal',
+ }),
+ ).to.equal(10);
+ expect(
+ getRadius('top-right', {
+ hasNegative: true,
+ hasPositive: false,
+ borderRadius: 10,
+ layout: 'horizontal',
+ }),
+ ).to.equal(0);
+ });
+
+ // ┌──┐
+ // │ │
+ // ╚═─┘
+ it('should return borderRadius for bottom-right on vertical layout', () => {
+ expect(
+ getRadius('bottom-right', {
+ hasNegative: false,
+ hasPositive: true,
+ borderRadius: 10,
+ layout: 'vertical',
+ }),
+ ).to.equal(0);
+ expect(
+ getRadius('bottom-right', {
+ hasNegative: true,
+ hasPositive: false,
+ borderRadius: 10,
+ layout: 'vertical',
+ }),
+ ).to.equal(10);
+ });
+
+ // ┌───┐
+ // ╚═──┘
+ it('should return borderRadius for bottom-right on horizontal layout', () => {
+ expect(
+ getRadius('bottom-right', {
+ hasNegative: false,
+ hasPositive: true,
+ borderRadius: 10,
+ layout: 'horizontal',
+ }),
+ ).to.equal(10);
+ expect(
+ getRadius('bottom-right', {
+ hasNegative: true,
+ hasPositive: false,
+ borderRadius: 10,
+ layout: 'horizontal',
+ }),
+ ).to.equal(0);
+ });
+
+ // ┌──┐
+ // │ │
+ // └─═╝
+ it('should return borderRadius for bottom-left on vertical layout', () => {
+ expect(
+ getRadius('bottom-left', {
+ hasNegative: false,
+ hasPositive: true,
+ borderRadius: 10,
+ layout: 'vertical',
+ }),
+ ).to.equal(0);
+ expect(
+ getRadius('bottom-left', {
+ hasNegative: true,
+ hasPositive: false,
+ borderRadius: 10,
+ layout: 'vertical',
+ }),
+ ).to.equal(10);
+ });
+
+ // ┌───┐
+ // └──═╝
+ it('should return borderRadius for bottom-left on horizontal layout', () => {
+ expect(
+ getRadius('bottom-left', {
+ hasNegative: false,
+ hasPositive: true,
+ borderRadius: 10,
+ layout: 'horizontal',
+ }),
+ ).to.equal(0);
+ expect(
+ getRadius('bottom-left', {
+ hasNegative: true,
+ hasPositive: false,
+ borderRadius: 10,
+ layout: 'horizontal',
+ }),
+ ).to.equal(10);
+ });
+});
diff --git a/packages/x-charts/src/BarChart/getRadius.ts b/packages/x-charts/src/BarChart/getRadius.ts
new file mode 100644
index 0000000000000..a9ad96230720b
--- /dev/null
+++ b/packages/x-charts/src/BarChart/getRadius.ts
@@ -0,0 +1,43 @@
+type GetRadiusData = {
+ hasNegative: boolean;
+ hasPositive: boolean;
+ borderRadius?: number;
+ layout?: 'vertical' | 'horizontal';
+};
+
+type GetRadiusCorner = 'top-left' | 'top-right' | 'bottom-right' | 'bottom-left';
+
+/**
+ * Returns if the corner should have a radius or not based on the layout and the data.
+ * @param {GetRadiusCorner} corner The corner to check.
+ * @param {GetRadiusData} cornerData The data for the corner.
+ * @returns {number} The radius for the corner.
+ */
+export const getRadius = (
+ corner: GetRadiusCorner,
+ { hasNegative, hasPositive, borderRadius, layout }: GetRadiusData,
+): number => {
+ if (!borderRadius) {
+ return 0;
+ }
+
+ const isVertical = layout === 'vertical';
+
+ if (corner === 'top-left' && ((isVertical && hasPositive) || (!isVertical && hasNegative))) {
+ return borderRadius;
+ }
+
+ if (corner === 'top-right' && ((isVertical && hasPositive) || (!isVertical && hasPositive))) {
+ return borderRadius;
+ }
+
+ if (corner === 'bottom-right' && ((isVertical && hasNegative) || (!isVertical && hasPositive))) {
+ return borderRadius;
+ }
+
+ if (corner === 'bottom-left' && ((isVertical && hasNegative) || (!isVertical && hasNegative))) {
+ return borderRadius;
+ }
+
+ return 0;
+};
diff --git a/packages/x-charts/src/BarChart/types.ts b/packages/x-charts/src/BarChart/types.ts
new file mode 100644
index 0000000000000..e72cdaea18ebb
--- /dev/null
+++ b/packages/x-charts/src/BarChart/types.ts
@@ -0,0 +1,28 @@
+import type { HighlightScope } from '../context';
+import type { BarSeriesType } from '../models';
+import type { SeriesId } from '../models/seriesType/common';
+
+export type AnimationData = {
+ x: number;
+ y: number;
+ width: number;
+ height: number;
+ yOrigin: number;
+ xOrigin: number;
+ layout: BarSeriesType['layout'];
+};
+
+export interface CompletedBarData extends AnimationData {
+ seriesId: SeriesId;
+ dataIndex: number;
+ color: string;
+ value: number | null;
+ highlightScope?: Partial;
+ maskId: string;
+}
+
+export interface MaskData extends AnimationData {
+ id: string;
+ hasNegative: boolean;
+ hasPositive: boolean;
+}