diff --git a/docs/data/charts/funnel/FunnelBorderRadius.js b/docs/data/charts/funnel/FunnelBorderRadius.js
new file mode 100644
index 0000000000000..fc51bc47653a2
--- /dev/null
+++ b/docs/data/charts/funnel/FunnelBorderRadius.js
@@ -0,0 +1,29 @@
+import * as React from 'react';
+import Box from '@mui/material/Box';
+import { Unstable_FunnelChart as FunnelChart } from '@mui/x-charts-pro/FunnelChart';
+
+export default function FunnelBorderRadius() {
+ return (
+
+
+
+
+ );
+}
diff --git a/docs/data/charts/funnel/FunnelBorderRadius.tsx b/docs/data/charts/funnel/FunnelBorderRadius.tsx
new file mode 100644
index 0000000000000..fc51bc47653a2
--- /dev/null
+++ b/docs/data/charts/funnel/FunnelBorderRadius.tsx
@@ -0,0 +1,29 @@
+import * as React from 'react';
+import Box from '@mui/material/Box';
+import { Unstable_FunnelChart as FunnelChart } from '@mui/x-charts-pro/FunnelChart';
+
+export default function FunnelBorderRadius() {
+ return (
+
+
+
+
+ );
+}
diff --git a/docs/data/charts/funnel/FunnelCurves.js b/docs/data/charts/funnel/FunnelCurves.js
index 4e67aafae13a1..141b4deb6089b 100644
--- a/docs/data/charts/funnel/FunnelCurves.js
+++ b/docs/data/charts/funnel/FunnelCurves.js
@@ -22,6 +22,12 @@ export default function FunnelCurves() {
min: 0,
max: 20,
},
+ borderRadius: {
+ knob: 'slider',
+ defaultValue: 0,
+ min: 0,
+ max: 20,
+ },
}}
renderDemo={(props) => (
@@ -29,6 +35,7 @@ export default function FunnelCurves() {
series={[
{
curve: props.curveType,
+ borderRadius: props.borderRadius,
layout: 'vertical',
...populationByEducationLevelPercentageSeries,
},
@@ -41,6 +48,7 @@ export default function FunnelCurves() {
series={[
{
curve: props.curveType,
+ borderRadius: props.borderRadius,
layout: 'horizontal',
...populationByEducationLevelPercentageSeries,
},
@@ -55,7 +63,10 @@ export default function FunnelCurves() {
return `import { FunnelChart } from '@mui/x-charts-pro/FunnelChart';
`;
diff --git a/docs/data/charts/funnel/FunnelCurves.tsx b/docs/data/charts/funnel/FunnelCurves.tsx
index be4827ecef762..cc63c398762a0 100644
--- a/docs/data/charts/funnel/FunnelCurves.tsx
+++ b/docs/data/charts/funnel/FunnelCurves.tsx
@@ -23,6 +23,12 @@ export default function FunnelCurves() {
min: 0,
max: 20,
},
+ borderRadius: {
+ knob: 'slider',
+ defaultValue: 0,
+ min: 0,
+ max: 20,
+ },
} as const
}
renderDemo={(props) => (
@@ -31,6 +37,7 @@ export default function FunnelCurves() {
series={[
{
curve: props.curveType,
+ borderRadius: props.borderRadius,
layout: 'vertical',
...populationByEducationLevelPercentageSeries,
},
@@ -43,6 +50,7 @@ export default function FunnelCurves() {
series={[
{
curve: props.curveType,
+ borderRadius: props.borderRadius,
layout: 'horizontal',
...populationByEducationLevelPercentageSeries,
},
@@ -57,7 +65,10 @@ export default function FunnelCurves() {
return `import { FunnelChart } from '@mui/x-charts-pro/FunnelChart';
`;
diff --git a/docs/data/charts/funnel/funnel.md b/docs/data/charts/funnel/funnel.md
index 74c2879a15f9d..ba94ff1a48c2b 100644
--- a/docs/data/charts/funnel/funnel.md
+++ b/docs/data/charts/funnel/funnel.md
@@ -67,6 +67,24 @@ It accepts a number that represents the gap in pixels.
{{"demo": "FunnelGap.js"}}
+### Border radius
+
+The border radius of the sections can be customized by the `borderRadius` property.
+It accepts a number that represents the radius in pixels.
+
+- The `bump` curve interpolation will not respect the border radius.
+- The `linear` curve respects the border radius to some extent due to the angle of the sections.
+- The `step` curve will respect the border radius.
+
+To understand how the border radius interacts with the `curve` prop, see the [curve interpolation example](/x/react-charts/funnel/#curve-interpolation) above.
+
+The `borderRadius` property will also behave differently depending on whether the `gap` property is greater than 0.
+
+- If the `gap` is 0, the border radius will be applied to the corners of the sections that are not connected to another section.
+- If the `gap` is greater than 0, the border radius will be applied to all the corners of the sections.
+
+{{"demo": "FunnelBorderRadius.js"}}
+
### Colors
The funnel colors can be customized in two ways.
diff --git a/packages/x-charts-pro/src/FunnelChart/FunnelPlot.tsx b/packages/x-charts-pro/src/FunnelChart/FunnelPlot.tsx
index ce2b461536bff..0d61e68c5aa37 100644
--- a/packages/x-charts-pro/src/FunnelChart/FunnelPlot.tsx
+++ b/packages/x-charts-pro/src/FunnelChart/FunnelPlot.tsx
@@ -67,8 +67,6 @@ const useAggregatedData = (gap: number | undefined) => {
const xScale = xAxis[xAxisId].scale;
const yScale = yAxis[yAxisId].scale;
- const curve = getFunnelCurve(currentSeries.curve, isHorizontal, gap);
-
const xPosition = (
value: number,
bandIndex: number,
@@ -107,6 +105,15 @@ const useAggregatedData = (gap: number | undefined) => {
})
: currentSeries.sectionLabel;
+ const curve = getFunnelCurve(
+ currentSeries.curve,
+ isHorizontal,
+ gap,
+ dataIndex,
+ currentSeries.dataPoints.length,
+ currentSeries.borderRadius,
+ );
+
const line = d3Line()
.x((d) =>
xPosition(d.x, baseScaleConfig.data?.[dataIndex], d.stackOffset, d.useBandWidth),
diff --git a/packages/x-charts-pro/src/FunnelChart/curves/borderRadiusPolygon.ts b/packages/x-charts-pro/src/FunnelChart/curves/borderRadiusPolygon.ts
new file mode 100644
index 0000000000000..ac42441538b6e
--- /dev/null
+++ b/packages/x-charts-pro/src/FunnelChart/curves/borderRadiusPolygon.ts
@@ -0,0 +1,49 @@
+import { Point } from './curve.types';
+
+const distance = (p1: Point, p2: Point) => Math.sqrt((p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2);
+const lerp = (a: number, b: number, x: number) => a + (b - a) * x;
+const lerp2D = (p1: Point, p2: Point, t: number) => ({
+ x: lerp(p1.x, p2.x, t),
+ y: lerp(p1.y, p2.y, t),
+});
+
+/**
+ * Draws a polygon with rounded corners
+ * @param {CanvasRenderingContext2D} ctx The canvas context
+ * @param {Array} points A list of `{x, y}` points
+ * @param {number} radius how much to round the corners
+ */
+export function borderRadiusPolygon(
+ ctx: CanvasRenderingContext2D,
+ points: Point[],
+ radius: number | number[],
+): void {
+ const numPoints = points.length;
+
+ radius = Array.isArray(radius) ? radius : Array(numPoints).fill(radius);
+
+ const corners: Point[][] = [];
+ for (let i = 0; i < numPoints; i += 1) {
+ const lastPoint = points[i];
+ const thisPoint = points[(i + 1) % numPoints];
+ const nextPoint = points[(i + 2) % numPoints];
+
+ const lastEdgeLength = distance(lastPoint, thisPoint);
+ const lastOffsetDistance = Math.min(lastEdgeLength / 2, radius[i] ?? 0);
+ const start = lerp2D(thisPoint, lastPoint, lastOffsetDistance / lastEdgeLength);
+
+ const nextEdgeLength = distance(nextPoint, thisPoint);
+ const nextOffsetDistance = Math.min(nextEdgeLength / 2, radius[i] ?? 0);
+ const end = lerp2D(thisPoint, nextPoint, nextOffsetDistance / nextEdgeLength);
+
+ corners.push([start, thisPoint, end]);
+ }
+
+ ctx.moveTo(corners[0][0].x, corners[0][0].y);
+ for (const [start, ctrl, end] of corners) {
+ ctx.lineTo(start.x, start.y);
+ ctx.quadraticCurveTo(ctrl.x, ctrl.y, end.x, end.y);
+ }
+
+ ctx.closePath();
+}
diff --git a/packages/x-charts-pro/src/FunnelChart/curves/bump.ts b/packages/x-charts-pro/src/FunnelChart/curves/bump.ts
index c20c58f9de25f..4cdd2db0e6b32 100644
--- a/packages/x-charts-pro/src/FunnelChart/curves/bump.ts
+++ b/packages/x-charts-pro/src/FunnelChart/curves/bump.ts
@@ -1,18 +1,17 @@
+/* eslint-disable class-methods-use-this */
import { CurveGenerator } from '@mui/x-charts-vendor/d3-shape';
/**
* This is a custom "bump" curve generator.
+ * It draws smooth curves for the 4 provided points,
+ * with the option to add a gap between sections while also properly handling the border radius.
*
- * It takes into account the gap between the points and draws a smooth curve between them.
- *
- * It is based on the d3-shape bump curve generator.
+ * The implementation is based on the d3-shape bump curve generator.
* https://github.com/d3/d3-shape/blob/a82254af78f08799c71d7ab25df557c4872a3c51/src/curve/bump.js
*/
export class Bump implements CurveGenerator {
private context: CanvasRenderingContext2D;
- private line: number = NaN;
-
private x: number = NaN;
private y: number = NaN;
@@ -23,30 +22,22 @@ export class Bump implements CurveGenerator {
private gap: number = 0;
- constructor(context: CanvasRenderingContext2D, isHorizontal: boolean, gap: number = 0) {
+ constructor(
+ context: CanvasRenderingContext2D,
+ { isHorizontal, gap }: { isHorizontal: boolean; gap?: number },
+ ) {
this.context = context;
this.isHorizontal = isHorizontal;
- this.gap = gap / 2;
+ this.gap = (gap ?? 0) / 2;
}
- areaStart(): void {
- this.line = 0;
- }
+ areaStart(): void {}
- areaEnd(): void {
- this.line = NaN;
- }
+ areaEnd(): void {}
- lineStart(): void {
- this.currentPoint = 0;
- }
+ lineStart(): void {}
- lineEnd() {
- if (this.line || (this.line !== 0 && this.currentPoint === 1)) {
- this.context.closePath();
- }
- this.line = 1 - this.line;
- }
+ lineEnd(): void {}
point(x: number, y: number): void {
x = +x;
@@ -65,6 +56,9 @@ export class Bump implements CurveGenerator {
this.context.bezierCurveTo((this.x + x) / 2, this.y, (this.x + x) / 2, y, x + this.gap, y);
}
+ if (this.currentPoint === 3) {
+ this.context.closePath();
+ }
this.currentPoint += 1;
this.x = x;
this.y = y;
@@ -84,6 +78,9 @@ export class Bump implements CurveGenerator {
this.context.bezierCurveTo(this.x, (this.y + y) / 2, x, (this.y + y) / 2, x, y + this.gap);
}
+ if (this.currentPoint === 3) {
+ this.context.closePath();
+ }
this.currentPoint += 1;
this.x = x;
this.y = y;
diff --git a/packages/x-charts-pro/src/FunnelChart/curves/curve.types.ts b/packages/x-charts-pro/src/FunnelChart/curves/curve.types.ts
index 572047b231d55..e3f231ff8173c 100644
--- a/packages/x-charts-pro/src/FunnelChart/curves/curve.types.ts
+++ b/packages/x-charts-pro/src/FunnelChart/curves/curve.types.ts
@@ -6,3 +6,8 @@ export type FunnelCurveOptions = {
gap?: number;
};
export type FunnelCurveType = 'linear' | 'step' | 'bump';
+
+export type Point = {
+ x: number;
+ y: number;
+};
diff --git a/packages/x-charts-pro/src/FunnelChart/curves/funnelStep.ts b/packages/x-charts-pro/src/FunnelChart/curves/funnelStep.ts
deleted file mode 100644
index ff03a34e9bd55..0000000000000
--- a/packages/x-charts-pro/src/FunnelChart/curves/funnelStep.ts
+++ /dev/null
@@ -1,96 +0,0 @@
-import { CurveGenerator } from '@mui/x-charts-vendor/d3-shape';
-
-/**
- * This is a custom "step" curve generator for the funnel chart.
- * It is used to draw the funnel using "rectangles" without having to rework the rendering logic.
- *
- * It takes into account the gap between the points and draws a smooth curve between them.
- *
- * It is based on the d3-shape step curve generator.
- * https://github.com/d3/d3-shape/blob/a82254af78f08799c71d7ab25df557c4872a3c51/src/curve/step.js
- */
-export class FunnelStep implements CurveGenerator {
- private context: CanvasRenderingContext2D;
-
- private line: number = NaN;
-
- private x: number = NaN;
-
- private y: number = NaN;
-
- private currentPoint: number = 0;
-
- private isHorizontal: boolean = false;
-
- private gap: number = 0;
-
- constructor(context: CanvasRenderingContext2D, isHorizontal: boolean, gap: number = 0) {
- this.context = context;
- this.isHorizontal = isHorizontal;
- this.gap = gap / 2;
- }
-
- areaStart(): void {
- this.line = 0;
- }
-
- areaEnd(): void {
- this.line = NaN;
- }
-
- lineStart(): void {
- this.x = NaN;
- this.y = NaN;
- this.currentPoint = 0;
- }
-
- lineEnd(): void {
- if (this.currentPoint === 2) {
- this.context.lineTo(this.x, this.y);
- }
- if (this.line || (this.line !== 0 && this.currentPoint === 1)) {
- this.context.closePath();
- }
- if (this.line >= 0) {
- this.line = 1 - this.line;
- }
- }
-
- point(x: number, y: number): void {
- x = +x;
- y = +y;
-
- // 0 is the top-left corner.
- if (this.isHorizontal) {
- if (this.currentPoint === 0) {
- this.context.moveTo(x + this.gap, y);
- } else if (this.currentPoint === 1 || this.currentPoint === 2) {
- this.context.lineTo(x - this.gap, this.y);
- this.context.lineTo(x - this.gap, y);
- } else {
- this.context.lineTo(this.x - this.gap, y);
- this.context.lineTo(x + this.gap, y);
- }
-
- this.currentPoint += 1;
- this.x = x;
- this.y = y;
- return;
- }
-
- // 0 is the top-right corner.
- if (this.currentPoint === 0) {
- this.context.moveTo(x, y + this.gap);
- } else if (this.currentPoint === 3) {
- this.context.lineTo(x, this.y - this.gap);
- this.context.lineTo(x, y + this.gap);
- } else {
- this.context.lineTo(this.x, y - this.gap);
- this.context.lineTo(x, y - this.gap);
- }
-
- this.currentPoint += 1;
- this.x = x;
- this.y = y;
- }
-}
diff --git a/packages/x-charts-pro/src/FunnelChart/curves/getFunnelCurve.ts b/packages/x-charts-pro/src/FunnelChart/curves/getFunnelCurve.ts
index bfde8fcb7c422..85c874060bc8f 100644
--- a/packages/x-charts-pro/src/FunnelChart/curves/getFunnelCurve.ts
+++ b/packages/x-charts-pro/src/FunnelChart/curves/getFunnelCurve.ts
@@ -1,12 +1,12 @@
import { CurveFactory } from '@mui/x-charts-vendor/d3-shape';
import { FunnelCurveType } from './curve.types';
-import { FunnelStep } from './funnelStep';
+import { Step } from './step';
import { Linear } from './linear';
import { Bump } from './bump';
const curveConstructor = (curve: FunnelCurveType | undefined) => {
if (curve === 'step') {
- return FunnelStep;
+ return Step;
}
if (curve === 'bump') {
return Bump;
@@ -17,7 +17,17 @@ const curveConstructor = (curve: FunnelCurveType | undefined) => {
export const getFunnelCurve = (
curve: FunnelCurveType | undefined,
isHorizontal: boolean,
- gap: number = 0,
+ gap: number | undefined,
+ dataIndex: number,
+ totalDataPoints: number,
+ borderRadius: number | undefined,
): CurveFactory => {
- return (context) => new (curveConstructor(curve))(context as any, isHorizontal, gap);
+ return (context) =>
+ new (curveConstructor(curve))(context as any, {
+ isHorizontal,
+ gap,
+ position: dataIndex,
+ sections: totalDataPoints,
+ borderRadius,
+ });
};
diff --git a/packages/x-charts-pro/src/FunnelChart/curves/linear.ts b/packages/x-charts-pro/src/FunnelChart/curves/linear.ts
index 3ad11b75c5f8b..016d92921944e 100644
--- a/packages/x-charts-pro/src/FunnelChart/curves/linear.ts
+++ b/packages/x-charts-pro/src/FunnelChart/curves/linear.ts
@@ -1,4 +1,7 @@
+/* eslint-disable class-methods-use-this */
import { CurveGenerator } from '@mui/x-charts-vendor/d3-shape';
+import { Point } from './curve.types';
+import { borderRadiusPolygon } from './borderRadiusPolygon';
// From point1 to point2, get the x value from y
const xFromY =
@@ -26,102 +29,110 @@ const yFromX =
/**
* This is a custom "linear" curve generator.
+ * It draws straight lines for the 4 provided points,
+ * with the option to add a gap between sections while also properly handling the border radius.
*
- * It takes into account the gap between the points and draws a smooth curve between them.
- *
- * It is based on the d3-shape linear curve generator.
+ * The implementation is based on the d3-shape linear curve generator.
* https://github.com/d3/d3-shape/blob/a82254af78f08799c71d7ab25df557c4872a3c51/src/curve/linear.js
*/
export class Linear implements CurveGenerator {
private context: CanvasRenderingContext2D;
- private line: number = NaN;
-
- private x: number = NaN;
-
- private y: number = NaN;
+ private position: number = 0;
- private currentPoint: number = 0;
+ private sections: number = 0;
private isHorizontal: boolean = false;
private gap: number = 0;
- constructor(context: CanvasRenderingContext2D, isHorizontal: boolean, gap: number = 0) {
+ private borderRadius: number = 0;
+
+ private points: Point[] = [];
+
+ constructor(
+ context: CanvasRenderingContext2D,
+ {
+ isHorizontal,
+ gap,
+ position,
+ sections,
+ borderRadius,
+ }: {
+ isHorizontal: boolean;
+ gap?: number;
+ position?: number;
+ sections?: number;
+ borderRadius?: number;
+ },
+ ) {
this.context = context;
this.isHorizontal = isHorizontal;
- this.gap = gap / 2;
+ this.gap = (gap ?? 0) / 2;
+ this.position = position ?? 0;
+ this.sections = sections ?? 1;
+ this.borderRadius = borderRadius ?? 0;
}
- areaStart(): void {
- this.line = 0;
- }
+ areaStart(): void {}
- areaEnd(): void {
- this.line = NaN;
- }
+ areaEnd(): void {}
- lineStart(): void {
- this.currentPoint = 0;
- }
+ lineStart(): void {}
- lineEnd() {
- if (this.line || (this.line !== 0 && this.currentPoint === 1)) {
- this.context.closePath();
+ lineEnd(): void {}
+
+ point(xIn: number, yIn: number): void {
+ this.points.push({ x: xIn, y: yIn });
+ if (this.points.length < 4) {
+ return;
}
- this.line = 1 - this.line;
- }
- point(x: number, y: number): void {
- x = +x;
- y = +y;
-
- // We draw the lines only at currentPoint 1 & 3 because we need
- // The data of a pair of points to draw the lines.
- // Hence currentPoint 1 draws a line from point 0 to point 1 and point 1 to point 2.
- // currentPoint 3 draws a line from point 2 to point 3 and point 3 to point 0.
-
- if (this.isHorizontal) {
- const yGetter = yFromX(this.x, this.y, x, y);
- let xGap = 0;
-
- // 0 is the top-left corner.
- if (this.currentPoint === 1) {
- xGap = this.x + this.gap;
- this.context.moveTo(xGap, yGetter(xGap));
- this.context.lineTo(xGap, yGetter(xGap));
- xGap = x - this.gap;
- this.context.lineTo(xGap, yGetter(xGap));
- } else if (this.currentPoint === 3) {
- xGap = this.x - this.gap;
- this.context.lineTo(xGap, yGetter(xGap));
- xGap = x + this.gap;
- this.context.lineTo(xGap, yGetter(xGap));
+ // Add gaps where they are needed.
+ this.points = this.points.map((point, index) => {
+ const slopeStart = this.points.at(index <= 1 ? 0 : 2)!;
+ const slopeEnd = this.points.at(index <= 1 ? 1 : 3)!;
+ const yGetter = yFromX(
+ slopeStart.x - this.gap,
+ slopeStart.y,
+ slopeEnd.x - this.gap,
+ slopeEnd.y,
+ );
+ if (this.isHorizontal) {
+ const xGap = point.x + (index === 0 || index === 3 ? this.gap : -this.gap);
+
+ return {
+ x: xGap,
+ y: yGetter(xGap),
+ };
}
- }
- if (!this.isHorizontal) {
- const xGetter = xFromY(this.x, this.y, x, y);
- let yGap = 0;
-
- // 0 is the top-right corner.
- if (this.currentPoint === 1) {
- yGap = this.y + this.gap;
- this.context.moveTo(xGetter(yGap), yGap);
- this.context.lineTo(xGetter(yGap), yGap);
- yGap = y - this.gap;
- this.context.lineTo(xGetter(yGap), yGap);
- } else if (this.currentPoint === 3) {
- yGap = this.y - this.gap;
- this.context.lineTo(xGetter(yGap), yGap);
- yGap = y + this.gap;
- this.context.lineTo(xGetter(yGap), yGap);
+ const xGetter = xFromY(
+ slopeStart.x,
+ slopeStart.y - this.gap,
+ slopeEnd.x,
+ slopeEnd.y - this.gap,
+ );
+ const yGap = point.y + (index === 0 || index === 3 ? this.gap : -this.gap);
+ return {
+ x: xGetter(yGap),
+ y: yGap,
+ };
+ });
+
+ const getBorderRadius = () => {
+ if (this.gap > 0) {
+ return this.borderRadius;
}
- }
+ if (this.position === 0) {
+ return [0, 0, this.borderRadius, this.borderRadius];
+ }
+ if (this.position === this.sections - 1) {
+ return [this.borderRadius, this.borderRadius];
+ }
+ return 0;
+ };
- // Increment the values
- this.currentPoint += 1;
- this.x = x;
- this.y = y;
+ borderRadiusPolygon(this.context, this.points, getBorderRadius());
}
}
diff --git a/packages/x-charts-pro/src/FunnelChart/curves/step.ts b/packages/x-charts-pro/src/FunnelChart/curves/step.ts
new file mode 100644
index 0000000000000..932c54ffc4b1e
--- /dev/null
+++ b/packages/x-charts-pro/src/FunnelChart/curves/step.ts
@@ -0,0 +1,106 @@
+/* eslint-disable class-methods-use-this */
+import { CurveGenerator } from '@mui/x-charts-vendor/d3-shape';
+import { Point } from './curve.types';
+import { borderRadiusPolygon } from './borderRadiusPolygon';
+
+const max = (numbers: number[]) => Math.max(...numbers, -Infinity);
+const min = (numbers: number[]) => Math.min(...numbers, Infinity);
+
+/**
+ * This is a custom "step" curve generator.
+ * It is used to draw "rectangles" from 4 points without having to rework the rendering logic,
+ * with the option to add a gap between sections while also properly handling the border radius.
+ *
+ * It takes the min and max of the x and y coordinates of the points to create a rectangle.
+ *
+ * The implementation is based on the d3-shape step curve generator.
+ * https://github.com/d3/d3-shape/blob/a82254af78f08799c71d7ab25df557c4872a3c51/src/curve/step.js
+ */
+export class Step implements CurveGenerator {
+ private context: CanvasRenderingContext2D;
+
+ private isHorizontal: boolean = false;
+
+ private gap: number = 0;
+
+ private borderRadius: number = 0;
+
+ private position: number = 0;
+
+ private points: Point[] = [];
+
+ constructor(
+ context: CanvasRenderingContext2D,
+ {
+ isHorizontal,
+ gap,
+ position,
+ borderRadius,
+ }: {
+ isHorizontal: boolean;
+ gap?: number;
+ position?: number;
+ sections?: number;
+ borderRadius?: number;
+ },
+ ) {
+ this.context = context;
+ this.isHorizontal = isHorizontal;
+ this.gap = (gap ?? 0) / 2;
+ this.position = position ?? 0;
+ this.borderRadius = borderRadius ?? 0;
+ }
+
+ areaStart(): void {}
+
+ areaEnd(): void {}
+
+ lineStart(): void {}
+
+ lineEnd(): void {}
+
+ point(xIn: number, yIn: number): void {
+ this.points.push({ x: xIn, y: yIn });
+ if (this.points.length < 4) {
+ return;
+ }
+
+ // Ensure we have rectangles instead of trapezoids.
+ this.points = this.points.map((_, index) => {
+ const allX = this.points.map((p) => p.x);
+ const allY = this.points.map((p) => p.y);
+ if (this.isHorizontal) {
+ return {
+ x: index === 1 || index === 2 ? max(allX) : min(allX),
+ y: index <= 1 ? max(allY) : min(allY),
+ };
+ }
+ return {
+ x: index <= 1 ? min(allX) : max(allX),
+ y: index === 1 || index === 2 ? max(allY) : min(allY),
+ };
+ });
+
+ // Add gaps where they are needed.
+ this.points = this.points.map((point, index) => {
+ if (this.isHorizontal) {
+ return {
+ x: point.x + (index === 0 || index === 3 ? this.gap : -this.gap),
+ y: point.y,
+ };
+ }
+ return {
+ x: point.x,
+ y: point.y + (index === 0 || index === 3 ? this.gap : -this.gap),
+ };
+ });
+
+ borderRadiusPolygon(
+ this.context,
+ this.points,
+ this.gap > 0 || this.position === 0
+ ? this.borderRadius
+ : [this.borderRadius, this.borderRadius],
+ );
+ }
+}
diff --git a/packages/x-charts-pro/src/FunnelChart/funnel.types.ts b/packages/x-charts-pro/src/FunnelChart/funnel.types.ts
index f4d91a61c59ca..3a5bb138f66f2 100644
--- a/packages/x-charts-pro/src/FunnelChart/funnel.types.ts
+++ b/packages/x-charts-pro/src/FunnelChart/funnel.types.ts
@@ -58,6 +58,11 @@ export interface FunnelSeriesType
* @default 'linear'
*/
curve?: FunnelCurveType;
+ /**
+ * The radius, in pixels, of the corners of the funnel sections.
+ * @default 8
+ */
+ borderRadius?: number;
/**
* The label configuration for the funnel plot.
* Allows to customize the position and margin of the label that is displayed on the funnel sections.
diff --git a/packages/x-charts-pro/src/FunnelChart/seriesConfig/getSeriesWithDefaultValues.ts b/packages/x-charts-pro/src/FunnelChart/seriesConfig/getSeriesWithDefaultValues.ts
index 14cbadb5b9fc1..19c0764c80038 100644
--- a/packages/x-charts-pro/src/FunnelChart/seriesConfig/getSeriesWithDefaultValues.ts
+++ b/packages/x-charts-pro/src/FunnelChart/seriesConfig/getSeriesWithDefaultValues.ts
@@ -8,6 +8,7 @@ const getSeriesWithDefaultValues: GetSeriesWithDefaultValues<'funnel'> = (
return {
id: seriesData.id ?? `auto-generated-id-${seriesIndex}`,
...seriesData,
+ borderRadius: seriesData.borderRadius ?? 8,
data: seriesData.data.map((d, index) => ({
color: colors[index % colors.length],
...d,