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)} + > + 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) => `${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') + } + > + 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; +}