diff --git a/docs/data/charts/zoom-and-pan/ZoomControlled.js b/docs/data/charts/zoom-and-pan/ZoomControlled.js
new file mode 100644
index 0000000000000..82a17f60a3262
--- /dev/null
+++ b/docs/data/charts/zoom-and-pan/ZoomControlled.js
@@ -0,0 +1,145 @@
+import * as React from 'react';
+import { LineChartPro } from '@mui/x-charts-pro/LineChartPro';
+
+import { Button } from '@mui/base';
+
+export default function ZoomControlled() {
+ const [zoom, setZoom] = React.useState([
+ {
+ axisId: 'my-x-axis',
+ start: 20,
+ end: 40,
+ },
+ ]);
+
+ return (
+
+
+ i),
+ },
+ ]}
+ />
+
+ );
+}
+
+const data = [
+ {
+ y1: 443.28,
+ y2: 153.9,
+ },
+ {
+ y1: 110.5,
+ y2: 217.8,
+ },
+ {
+ y1: 175.23,
+ y2: 286.32,
+ },
+ {
+ y1: 195.97,
+ y2: 325.12,
+ },
+ {
+ y1: 351.77,
+ y2: 144.58,
+ },
+ {
+ y1: 43.253,
+ y2: 146.51,
+ },
+ {
+ y1: 376.34,
+ y2: 309.69,
+ },
+ {
+ y1: 31.514,
+ y2: 236.38,
+ },
+ {
+ y1: 231.31,
+ y2: 440.72,
+ },
+ {
+ y1: 108.04,
+ y2: 20.29,
+ },
+ {
+ y1: 321.77,
+ y2: 484.17,
+ },
+ {
+ y1: 120.18,
+ y2: 54.962,
+ },
+ {
+ y1: 366.2,
+ y2: 418.5,
+ },
+ {
+ y1: 451.45,
+ y2: 181.32,
+ },
+ {
+ y1: 294.8,
+ y2: 440.9,
+ },
+ {
+ y1: 121.83,
+ y2: 273.52,
+ },
+ {
+ y1: 287.7,
+ y2: 346.7,
+ },
+ {
+ y1: 134.06,
+ y2: 74.528,
+ },
+ {
+ y1: 104.5,
+ y2: 150.9,
+ },
+ {
+ y1: 413.07,
+ y2: 26.483,
+ },
+ {
+ y1: 74.68,
+ y2: 333.2,
+ },
+ {
+ y1: 360.6,
+ y2: 422.0,
+ },
+ {
+ y1: 330.72,
+ y2: 488.06,
+ },
+];
+
+const chartProps = {
+ width: 600,
+ height: 300,
+ series: [
+ {
+ label: 'Series A',
+ data: data.map((v) => v.y1),
+ },
+ {
+ label: 'Series B',
+ data: data.map((v) => v.y2),
+ },
+ ],
+};
diff --git a/docs/data/charts/zoom-and-pan/ZoomControlled.tsx b/docs/data/charts/zoom-and-pan/ZoomControlled.tsx
new file mode 100644
index 0000000000000..643f2fd65a7a7
--- /dev/null
+++ b/docs/data/charts/zoom-and-pan/ZoomControlled.tsx
@@ -0,0 +1,145 @@
+import * as React from 'react';
+import { LineChartPro } from '@mui/x-charts-pro/LineChartPro';
+import { ZoomData } from '@mui/x-charts-pro/context';
+import { Button } from '@mui/base';
+
+export default function ZoomControlled() {
+ const [zoom, setZoom] = React.useState([
+ {
+ axisId: 'my-x-axis',
+ start: 20,
+ end: 40,
+ },
+ ]);
+
+ return (
+
+
+ i),
+ },
+ ]}
+ />
+
+ );
+}
+
+const data = [
+ {
+ y1: 443.28,
+ y2: 153.9,
+ },
+ {
+ y1: 110.5,
+ y2: 217.8,
+ },
+ {
+ y1: 175.23,
+ y2: 286.32,
+ },
+ {
+ y1: 195.97,
+ y2: 325.12,
+ },
+ {
+ y1: 351.77,
+ y2: 144.58,
+ },
+ {
+ y1: 43.253,
+ y2: 146.51,
+ },
+ {
+ y1: 376.34,
+ y2: 309.69,
+ },
+ {
+ y1: 31.514,
+ y2: 236.38,
+ },
+ {
+ y1: 231.31,
+ y2: 440.72,
+ },
+ {
+ y1: 108.04,
+ y2: 20.29,
+ },
+ {
+ y1: 321.77,
+ y2: 484.17,
+ },
+ {
+ y1: 120.18,
+ y2: 54.962,
+ },
+ {
+ y1: 366.2,
+ y2: 418.5,
+ },
+ {
+ y1: 451.45,
+ y2: 181.32,
+ },
+ {
+ y1: 294.8,
+ y2: 440.9,
+ },
+ {
+ y1: 121.83,
+ y2: 273.52,
+ },
+ {
+ y1: 287.7,
+ y2: 346.7,
+ },
+ {
+ y1: 134.06,
+ y2: 74.528,
+ },
+ {
+ y1: 104.5,
+ y2: 150.9,
+ },
+ {
+ y1: 413.07,
+ y2: 26.483,
+ },
+ {
+ y1: 74.68,
+ y2: 333.2,
+ },
+ {
+ y1: 360.6,
+ y2: 422.0,
+ },
+ {
+ y1: 330.72,
+ y2: 488.06,
+ },
+];
+
+const chartProps = {
+ width: 600,
+ height: 300,
+ series: [
+ {
+ label: 'Series A',
+ data: data.map((v) => v.y1),
+ },
+ {
+ label: 'Series B',
+ data: data.map((v) => v.y2),
+ },
+ ],
+};
diff --git a/docs/data/charts/zoom-and-pan/ZoomControlled.tsx.preview b/docs/data/charts/zoom-and-pan/ZoomControlled.tsx.preview
new file mode 100644
index 0000000000000..443aef289aa8f
--- /dev/null
+++ b/docs/data/charts/zoom-and-pan/ZoomControlled.tsx.preview
@@ -0,0 +1,16 @@
+
+ i),
+ },
+ ]}
+/>
\ No newline at end of file
diff --git a/docs/data/charts/zoom-and-pan/zoom-and-pan.md b/docs/data/charts/zoom-and-pan/zoom-and-pan.md
index 293d55e2bf919..389b055de0999 100644
--- a/docs/data/charts/zoom-and-pan/zoom-and-pan.md
+++ b/docs/data/charts/zoom-and-pan/zoom-and-pan.md
@@ -47,3 +47,18 @@ The following options are available:
- **panning**: Enables or disables panning.
{{"demo": "ZoomOptionsNoSnap.js", "hideToolbar": true, "bg": "playground"}}
+
+## Controlled zoom
+
+You can control the zoom state by setting the `zoom` and `onZoomChange` props.
+This way, you can control the zoom state from outside the chart.
+
+The `onZoomChange` prop is a function that receives the new zoom state.
+
+While the `zoom` prop is an array of objects that define the zoom state for each axis with zoom enabled.
+
+- **axisId**: The id of the axis to control.
+- **start**: The starting percentage of the axis range.
+- **end**: The ending percentage of the zoom range.
+
+{{"demo": "ZoomControlled.js"}}
diff --git a/docs/pages/x/api/charts/chart-container-pro.json b/docs/pages/x/api/charts/chart-container-pro.json
index 85cd769e2e54a..a26642badf560 100644
--- a/docs/pages/x/api/charts/chart-container-pro.json
+++ b/docs/pages/x/api/charts/chart-container-pro.json
@@ -32,6 +32,13 @@
"describedArgs": ["highlightedItem"]
}
},
+ "onZoomChange": {
+ "type": { "name": "func" },
+ "signature": {
+ "type": "function(zoomData: Array) => void",
+ "describedArgs": ["zoomData"]
+ }
+ },
"plugins": { "type": { "name": "arrayOf", "description": "Array<object>" } },
"xAxis": {
"type": {
@@ -50,6 +57,12 @@
"name": "arrayOf",
"description": "Array<{ colorMap?: { colors: Array<string>, type: 'ordinal', unknownColor?: string, values?: Array<Date
| number
| string> }
| { color: Array<string>
| func, max?: Date
| number, min?: Date
| number, type: 'continuous' }
| { colors: Array<string>, thresholds: Array<Date
| number>, type: 'piecewise' }, data?: array, dataKey?: string, id?: string, max?: number, min?: number }>"
}
+ },
+ "zoom": {
+ "type": {
+ "name": "arrayOf",
+ "description": "Array<{ axisId: number
| string, end: number, start: number }>"
+ }
}
},
"name": "ChartContainerPro",
diff --git a/docs/pages/x/api/charts/responsive-chart-container-pro.json b/docs/pages/x/api/charts/responsive-chart-container-pro.json
index c404bc2fd96b1..8e58fe0bbfd67 100644
--- a/docs/pages/x/api/charts/responsive-chart-container-pro.json
+++ b/docs/pages/x/api/charts/responsive-chart-container-pro.json
@@ -31,6 +31,13 @@
"describedArgs": ["highlightedItem"]
}
},
+ "onZoomChange": {
+ "type": { "name": "func" },
+ "signature": {
+ "type": "function(zoomData: Array) => void",
+ "describedArgs": ["zoomData"]
+ }
+ },
"plugins": { "type": { "name": "arrayOf", "description": "Array<object>" } },
"width": { "type": { "name": "number" } },
"xAxis": {
@@ -50,6 +57,12 @@
"name": "arrayOf",
"description": "Array<{ colorMap?: { colors: Array<string>, type: 'ordinal', unknownColor?: string, values?: Array<Date
| number
| string> }
| { color: Array<string>
| func, max?: Date
| number, min?: Date
| number, type: 'continuous' }
| { colors: Array<string>, thresholds: Array<Date
| number>, type: 'piecewise' }, data?: array, dataKey?: string, id?: string, max?: number, min?: number }>"
}
+ },
+ "zoom": {
+ "type": {
+ "name": "arrayOf",
+ "description": "Array<{ axisId: number
| string, end: number, start: number }>"
+ }
}
},
"name": "ResponsiveChartContainerPro",
diff --git a/docs/pages/x/api/charts/scatter-chart-pro.json b/docs/pages/x/api/charts/scatter-chart-pro.json
index dbe112e7cf9b6..570d57a2c3d75 100644
--- a/docs/pages/x/api/charts/scatter-chart-pro.json
+++ b/docs/pages/x/api/charts/scatter-chart-pro.json
@@ -62,6 +62,13 @@
"describedArgs": ["event", "scatterItemIdentifier"]
}
},
+ "onZoomChange": {
+ "type": { "name": "func" },
+ "signature": {
+ "type": "function(zoomData: Array) => void",
+ "describedArgs": ["zoomData"]
+ }
+ },
"rightAxis": {
"type": { "name": "union", "description": "object
| string" },
"default": "null"
@@ -103,6 +110,12 @@
"name": "arrayOf",
"description": "Array<{ colorMap?: { colors: Array<string>, type: 'ordinal', unknownColor?: string, values?: Array<Date
| number
| string> }
| { color: Array<string>
| func, max?: Date
| number, min?: Date
| number, type: 'continuous' }
| { colors: Array<string>, thresholds: Array<Date
| number>, type: 'piecewise' }, data?: array, dataKey?: string, id?: string, max?: number, min?: number }>"
}
+ },
+ "zoom": {
+ "type": {
+ "name": "arrayOf",
+ "description": "Array<{ axisId: number
| string, end: number, start: number }>"
+ }
}
},
"name": "ScatterChartPro",
diff --git a/docs/translations/api-docs/charts/chart-container-pro/chart-container-pro.json b/docs/translations/api-docs/charts/chart-container-pro/chart-container-pro.json
index 5a13d2067ae7c..20c5b4a301057 100644
--- a/docs/translations/api-docs/charts/chart-container-pro/chart-container-pro.json
+++ b/docs/translations/api-docs/charts/chart-container-pro/chart-container-pro.json
@@ -19,6 +19,10 @@
"description": "The callback fired when the highlighted item changes.",
"typeDescriptions": { "highlightedItem": "The newly highlighted item." }
},
+ "onZoomChange": {
+ "description": "Callback fired when the zoom has changed.",
+ "typeDescriptions": { "zoomData": "Updated zoom data." }
+ },
"plugins": {
"description": "An array of plugins defining how to preprocess data. If not provided, the container supports line, bar, scatter and pie charts."
},
@@ -32,7 +36,8 @@
"yAxis": {
"description": "The configuration of the y-axes. If not provided, a default axis config is used. An array of AxisConfig objects."
},
- "zAxis": { "description": "The configuration of the z-axes." }
+ "zAxis": { "description": "The configuration of the z-axes." },
+ "zoom": { "description": "The list of zoom data related to each axis." }
},
"classDescriptions": {}
}
diff --git a/docs/translations/api-docs/charts/responsive-chart-container-pro/responsive-chart-container-pro.json b/docs/translations/api-docs/charts/responsive-chart-container-pro/responsive-chart-container-pro.json
index 1623c12c44cda..469366dd9aad7 100644
--- a/docs/translations/api-docs/charts/responsive-chart-container-pro/responsive-chart-container-pro.json
+++ b/docs/translations/api-docs/charts/responsive-chart-container-pro/responsive-chart-container-pro.json
@@ -21,6 +21,10 @@
"description": "The callback fired when the highlighted item changes.",
"typeDescriptions": { "highlightedItem": "The newly highlighted item." }
},
+ "onZoomChange": {
+ "description": "Callback fired when the zoom has changed.",
+ "typeDescriptions": { "zoomData": "Updated zoom data." }
+ },
"plugins": {
"description": "An array of plugins defining how to preprocess data. If not provided, the container supports line, bar, scatter and pie charts."
},
@@ -36,7 +40,8 @@
"yAxis": {
"description": "The configuration of the y-axes. If not provided, a default axis config is used. An array of AxisConfig objects."
},
- "zAxis": { "description": "The configuration of the z-axes." }
+ "zAxis": { "description": "The configuration of the z-axes." },
+ "zoom": { "description": "The list of zoom data related to each axis." }
},
"classDescriptions": {}
}
diff --git a/docs/translations/api-docs/charts/scatter-chart-pro/scatter-chart-pro.json b/docs/translations/api-docs/charts/scatter-chart-pro/scatter-chart-pro.json
index 28a931b143ccb..6a6c1e0b1bf6a 100644
--- a/docs/translations/api-docs/charts/scatter-chart-pro/scatter-chart-pro.json
+++ b/docs/translations/api-docs/charts/scatter-chart-pro/scatter-chart-pro.json
@@ -43,6 +43,10 @@
"scatterItemIdentifier": "The scatter item identifier."
}
},
+ "onZoomChange": {
+ "description": "Callback fired when the zoom has changed.",
+ "typeDescriptions": { "zoomData": "Updated zoom data." }
+ },
"rightAxis": {
"description": "Indicate which axis to display the right of the charts. Can be a string (the id of the axis) or an object ChartsYAxisProps
."
},
@@ -70,7 +74,8 @@
"yAxis": {
"description": "The configuration of the y-axes. If not provided, a default axis config is used. An array of AxisConfig objects."
},
- "zAxis": { "description": "The configuration of the z-axes." }
+ "zAxis": { "description": "The configuration of the z-axes." },
+ "zoom": { "description": "The list of zoom data related to each axis." }
},
"classDescriptions": {}
}
diff --git a/packages/x-charts-pro/src/BarChartPro/BarChartPro.tsx b/packages/x-charts-pro/src/BarChartPro/BarChartPro.tsx
index 6db9610abedf8..9f03659068b1b 100644
--- a/packages/x-charts-pro/src/BarChartPro/BarChartPro.tsx
+++ b/packages/x-charts-pro/src/BarChartPro/BarChartPro.tsx
@@ -14,8 +14,9 @@ import { BarPlotProps } from '@mui/x-charts';
import { ResponsiveChartContainerPro } from '../ResponsiveChartContainerPro';
import { ZoomSetup } from '../context/ZoomProvider/ZoomSetup';
import { useZoom } from '../context/ZoomProvider/useZoom';
+import { ZoomProps } from '../context/ZoomProvider';
-export interface BarChartProProps extends BarChartProps {}
+export interface BarChartProProps extends BarChartProps, ZoomProps {}
/**
* Demos:
@@ -29,6 +30,7 @@ export interface BarChartProProps extends BarChartProps {}
* - [BarChart API](https://mui.com/x/api/charts/bar-chart/)
*/
const BarChartPro = React.forwardRef(function BarChartPro(props: BarChartProProps, ref) {
+ const { zoom, onZoomChange, ...other } = props;
const {
chartContainerProps,
barPlotProps,
@@ -42,10 +44,15 @@ const BarChartPro = React.forwardRef(function BarChartPro(props: BarChartProProp
legendProps,
tooltipProps,
children,
- } = useBarChartProps(props);
+ } = useBarChartProps(other);
return (
-
+
{props.onAxisClick && }
{props.grid && }
diff --git a/packages/x-charts-pro/src/ChartContainerPro/ChartContainerPro.tsx b/packages/x-charts-pro/src/ChartContainerPro/ChartContainerPro.tsx
index 09dc026abacc0..eac5d772c89db 100644
--- a/packages/x-charts-pro/src/ChartContainerPro/ChartContainerPro.tsx
+++ b/packages/x-charts-pro/src/ChartContainerPro/ChartContainerPro.tsx
@@ -9,33 +9,32 @@ import {
DrawingProvider,
InteractionProvider,
SeriesContextProvider,
- useChartContainerProps,
} from '@mui/x-charts/internals';
import { useLicenseVerifier } from '@mui/x-license/useLicenseVerifier';
import { getReleaseInfo } from '../internals/utils/releaseInfo';
import { CartesianContextProviderPro } from '../context/CartesianProviderPro';
-import { ZoomProvider } from '../context/ZoomProvider';
+import { ZoomProps, ZoomProvider } from '../context/ZoomProvider';
+import { useChartContainerProProps } from './useChartContainerProProps';
const releaseInfo = getReleaseInfo();
-export interface ChartContainerProProps extends ChartContainerProps {}
+export interface ChartContainerProProps extends ChartContainerProps, ZoomProps {}
const ChartContainerPro = React.forwardRef(function ChartContainer(
props: ChartContainerProProps,
ref,
) {
const {
- children,
+ zoomProviderProps,
drawingProviderProps,
colorProviderProps,
seriesContextProps,
- cartesianContextProps,
zAxisContextProps,
highlightedProviderProps,
+ cartesianContextProps,
chartsSurfaceProps,
- xAxis,
- yAxis,
- } = useChartContainerProps(props, ref);
+ children,
+ } = useChartContainerProProps(props, ref);
useLicenseVerifier('x-charts-pro', releaseInfo);
@@ -43,7 +42,7 @@ const ChartContainerPro = React.forwardRef(function ChartContainer(
-
+
@@ -115,6 +114,12 @@ ChartContainerPro.propTypes = {
* @param {HighlightItemData | null} highlightedItem The newly highlighted item.
*/
onHighlightChange: PropTypes.func,
+ /**
+ * Callback fired when the zoom has changed.
+ *
+ * @param {ZoomData[]} zoomData Updated zoom data.
+ */
+ onZoomChange: PropTypes.func,
/**
* An array of plugins defining how to preprocess data.
* If not provided, the container supports line, bar, scatter and pie charts.
@@ -345,6 +350,16 @@ ChartContainerPro.propTypes = {
min: PropTypes.number,
}),
),
+ /**
+ * The list of zoom data related to each axis.
+ */
+ zoom: PropTypes.arrayOf(
+ PropTypes.shape({
+ axisId: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
+ end: PropTypes.number.isRequired,
+ start: PropTypes.number.isRequired,
+ }),
+ ),
} as any;
export { ChartContainerPro };
diff --git a/packages/x-charts-pro/src/ChartContainerPro/useChartContainerProProps.ts b/packages/x-charts-pro/src/ChartContainerPro/useChartContainerProProps.ts
new file mode 100644
index 0000000000000..fa2c8e6d0ba4d
--- /dev/null
+++ b/packages/x-charts-pro/src/ChartContainerPro/useChartContainerProProps.ts
@@ -0,0 +1,42 @@
+import { useChartContainerProps } from '@mui/x-charts/internals';
+import { ZoomProviderProps } from '../context/ZoomProvider';
+import type { ChartContainerProProps } from './ChartContainerPro';
+
+export const useChartContainerProProps = (
+ props: ChartContainerProProps,
+ ref: React.ForwardedRef,
+) => {
+ const { zoom, onZoomChange, ...baseProps } = props;
+
+ const {
+ children,
+ drawingProviderProps,
+ colorProviderProps,
+ seriesContextProps,
+ cartesianContextProps,
+ zAxisContextProps,
+ highlightedProviderProps,
+ chartsSurfaceProps,
+ xAxis,
+ yAxis,
+ } = useChartContainerProps(baseProps, ref);
+
+ const zoomProviderProps: Omit = {
+ zoom,
+ onZoomChange,
+ xAxis,
+ yAxis,
+ };
+
+ return {
+ zoomProviderProps,
+ children,
+ drawingProviderProps,
+ colorProviderProps,
+ seriesContextProps,
+ cartesianContextProps,
+ zAxisContextProps,
+ highlightedProviderProps,
+ chartsSurfaceProps,
+ };
+};
diff --git a/packages/x-charts-pro/src/Heatmap/Heatmap.tsx b/packages/x-charts-pro/src/Heatmap/Heatmap.tsx
index a7936aa4847dd..42f67ece5b60d 100644
--- a/packages/x-charts-pro/src/Heatmap/Heatmap.tsx
+++ b/packages/x-charts-pro/src/Heatmap/Heatmap.tsx
@@ -50,7 +50,10 @@ export interface HeatmapSlotProps
HeatmapItemSlotProps {}
export interface HeatmapProps
- extends Omit,
+ extends Omit<
+ ResponsiveChartContainerProProps,
+ 'series' | 'plugins' | 'xAxis' | 'yAxis' | 'zoom' | 'onZoomChange'
+ >,
Omit,
Omit,
ChartsOnAxisClickHandlerProps {
diff --git a/packages/x-charts-pro/src/LineChartPro/LineChartPro.tsx b/packages/x-charts-pro/src/LineChartPro/LineChartPro.tsx
index a96716f8b22a9..323d37fb05d53 100644
--- a/packages/x-charts-pro/src/LineChartPro/LineChartPro.tsx
+++ b/packages/x-charts-pro/src/LineChartPro/LineChartPro.tsx
@@ -22,8 +22,9 @@ import { MarkPlotProps } from '@mui/x-charts';
import { ResponsiveChartContainerPro } from '../ResponsiveChartContainerPro';
import { ZoomSetup } from '../context/ZoomProvider/ZoomSetup';
import { useZoom } from '../context/ZoomProvider/useZoom';
+import { ZoomProps } from '../context/ZoomProvider';
-export interface LineChartProProps extends LineChartProps {}
+export interface LineChartProProps extends LineChartProps, ZoomProps {}
/**
* Demos:
@@ -36,6 +37,7 @@ export interface LineChartProProps extends LineChartProps {}
* - [LineChart API](https://mui.com/x/api/charts/line-chart/)
*/
const LineChartPro = React.forwardRef(function LineChartPro(props: LineChartProProps, ref) {
+ const { zoom, onZoomChange, ...other } = props;
const {
chartContainerProps,
axisClickHandlerProps,
@@ -52,10 +54,15 @@ const LineChartPro = React.forwardRef(function LineChartPro(props: LineChartProP
legendProps,
tooltipProps,
children,
- } = useLineChartProps(props);
+ } = useLineChartProps(other);
return (
-
+
{props.onAxisClick && }
{props.grid && }
diff --git a/packages/x-charts-pro/src/ResponsiveChartContainerPro/ResponsiveChartContainerPro.tsx b/packages/x-charts-pro/src/ResponsiveChartContainerPro/ResponsiveChartContainerPro.tsx
index c856012e26b7b..13251cc66229e 100644
--- a/packages/x-charts-pro/src/ResponsiveChartContainerPro/ResponsiveChartContainerPro.tsx
+++ b/packages/x-charts-pro/src/ResponsiveChartContainerPro/ResponsiveChartContainerPro.tsx
@@ -2,11 +2,15 @@ import * as React from 'react';
import PropTypes from 'prop-types';
import { Watermark } from '@mui/x-license/Watermark';
import { ResponsiveChartContainerProps } from '@mui/x-charts/ResponsiveChartContainer';
-import { ResizableContainer, useResponsiveChartContainerProps } from '@mui/x-charts/internals';
+import { ResizableContainer } from '@mui/x-charts/internals';
import { getReleaseInfo } from '../internals/utils/releaseInfo';
import { ChartContainerPro } from '../ChartContainerPro';
+import { ZoomProps } from '../context/ZoomProvider';
+import { useResponsiveChartContainerProProps } from './useResponsiveChartContainerProProps';
-export interface ResponsiveChartContainerProProps extends ResponsiveChartContainerProps {}
+export interface ResponsiveChartContainerProProps
+ extends ResponsiveChartContainerProps,
+ ZoomProps {}
const releaseInfo = getReleaseInfo();
@@ -14,12 +18,12 @@ const ResponsiveChartContainerPro = React.forwardRef(function ResponsiveChartCon
props: ResponsiveChartContainerProProps,
ref,
) {
- const { chartContainerProps, resizableChartContainerProps, hasIntrinsicSize } =
- useResponsiveChartContainerProps(props, ref);
+ const { chartContainerProProps, resizableChartContainerProps, hasIntrinsicSize } =
+ useResponsiveChartContainerProProps(props, ref);
return (
- {hasIntrinsicSize ? : null}
+ {hasIntrinsicSize ? : null}
);
@@ -77,6 +81,12 @@ ResponsiveChartContainerPro.propTypes = {
* @param {HighlightItemData | null} highlightedItem The newly highlighted item.
*/
onHighlightChange: PropTypes.func,
+ /**
+ * Callback fired when the zoom has changed.
+ *
+ * @param {ZoomData[]} zoomData Updated zoom data.
+ */
+ onZoomChange: PropTypes.func,
/**
* An array of plugins defining how to preprocess data.
* If not provided, the container supports line, bar, scatter and pie charts.
@@ -307,6 +317,16 @@ ResponsiveChartContainerPro.propTypes = {
min: PropTypes.number,
}),
),
+ /**
+ * The list of zoom data related to each axis.
+ */
+ zoom: PropTypes.arrayOf(
+ PropTypes.shape({
+ axisId: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
+ end: PropTypes.number.isRequired,
+ start: PropTypes.number.isRequired,
+ }),
+ ),
} as any;
export { ResponsiveChartContainerPro };
diff --git a/packages/x-charts-pro/src/ResponsiveChartContainerPro/useResponsiveChartContainerProProps.ts b/packages/x-charts-pro/src/ResponsiveChartContainerPro/useResponsiveChartContainerProProps.ts
new file mode 100644
index 0000000000000..363e4f9c9fc3f
--- /dev/null
+++ b/packages/x-charts-pro/src/ResponsiveChartContainerPro/useResponsiveChartContainerProProps.ts
@@ -0,0 +1,27 @@
+import { useResponsiveChartContainerProps } from '@mui/x-charts/internals';
+import type { ChartContainerProProps } from '../ChartContainerPro';
+import type { ResponsiveChartContainerProProps } from './ResponsiveChartContainerPro';
+
+export const useResponsiveChartContainerProProps = (
+ props: ResponsiveChartContainerProProps,
+ ref: React.ForwardedRef,
+) => {
+ const { zoom, onZoomChange, ...baseProps } = props;
+
+ const chartContainerProProps: Pick = {
+ zoom,
+ onZoomChange,
+ };
+
+ const { chartContainerProps, resizableChartContainerProps, hasIntrinsicSize } =
+ useResponsiveChartContainerProps(baseProps, ref);
+
+ return {
+ chartContainerProProps: {
+ ...chartContainerProps,
+ ...chartContainerProProps,
+ },
+ resizableChartContainerProps,
+ hasIntrinsicSize,
+ };
+};
diff --git a/packages/x-charts-pro/src/ScatterChartPro/ScatterChartPro.tsx b/packages/x-charts-pro/src/ScatterChartPro/ScatterChartPro.tsx
index 59fbed96eb847..57e5168a52472 100644
--- a/packages/x-charts-pro/src/ScatterChartPro/ScatterChartPro.tsx
+++ b/packages/x-charts-pro/src/ScatterChartPro/ScatterChartPro.tsx
@@ -12,8 +12,9 @@ import { ChartsTooltip } from '@mui/x-charts/ChartsTooltip';
import { useScatterChartProps } from '@mui/x-charts/internals';
import { ResponsiveChartContainerPro } from '../ResponsiveChartContainerPro';
import { ZoomSetup } from '../context/ZoomProvider/ZoomSetup';
+import { ZoomProps } from '../context/ZoomProvider';
-export interface ScatterChartProProps extends ScatterChartProps {}
+export interface ScatterChartProProps extends ScatterChartProps, ZoomProps {}
/**
* Demos:
@@ -29,6 +30,7 @@ const ScatterChartPro = React.forwardRef(function ScatterChartPro(
props: ScatterChartProProps,
ref,
) {
+ const { zoom, onZoomChange, ...other } = props;
const {
chartContainerProps,
zAxisProps,
@@ -41,9 +43,15 @@ const ScatterChartPro = React.forwardRef(function ScatterChartPro(
axisHighlightProps,
tooltipProps,
children,
- } = useScatterChartProps(props);
+ } = useScatterChartProps(other);
+
return (
-
+
{!props.disableVoronoi && }
@@ -173,6 +181,12 @@ ScatterChartPro.propTypes = {
* @param {ScatterItemIdentifier} scatterItemIdentifier The scatter item identifier.
*/
onItemClick: PropTypes.func,
+ /**
+ * Callback fired when the zoom has changed.
+ *
+ * @param {ZoomData[]} zoomData Updated zoom data.
+ */
+ onZoomChange: PropTypes.func,
/**
* Indicate which axis to display the right of the charts.
* Can be a string (the id of the axis) or an object `ChartsYAxisProps`.
@@ -437,6 +451,16 @@ ScatterChartPro.propTypes = {
min: PropTypes.number,
}),
),
+ /**
+ * The list of zoom data related to each axis.
+ */
+ zoom: PropTypes.arrayOf(
+ PropTypes.shape({
+ axisId: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
+ end: PropTypes.number.isRequired,
+ start: PropTypes.number.isRequired,
+ }),
+ ),
} as any;
export { ScatterChartPro };
diff --git a/packages/x-charts-pro/src/context/ZoomProvider/Zoom.types.ts b/packages/x-charts-pro/src/context/ZoomProvider/Zoom.types.ts
index 2cc44f9c06205..ac3f9990fc69a 100644
--- a/packages/x-charts-pro/src/context/ZoomProvider/Zoom.types.ts
+++ b/packages/x-charts-pro/src/context/ZoomProvider/Zoom.types.ts
@@ -1,5 +1,59 @@
import { AxisId } from '@mui/x-charts/internals';
+export type ZoomProviderProps = {
+ children: React.ReactNode;
+ /**
+ * The configuration of the x-axes.
+ * If not provided, a default axis config is used.
+ * An array of [[AxisConfig]] objects.
+ */
+ xAxis?: AxisConfigForZoom[];
+ /**
+ * The configuration of the y-axes.
+ * If not provided, a default axis config is used.
+ * An array of [[AxisConfig]] objects.
+ */
+ yAxis?: AxisConfigForZoom[];
+} & ZoomProps;
+
+/**
+ * Represents the state of the ZoomProvider.
+ */
+export type ZoomState = {
+ /**
+ * Whether zooming is enabled.
+ */
+ isZoomEnabled: boolean;
+ /**
+ * Whether panning is enabled.
+ */
+ isPanEnabled: boolean;
+ /**
+ * The zoom options for each axis.
+ */
+ options: Record;
+ /**
+ * The zoom data for each axis
+ * @default []
+ */
+ zoomData: ZoomData[];
+ /**
+ * Set the zoom data for each axis.
+ * @param {ZoomData[]} zoomData The new zoom data.
+ */
+ setZoomData: (zoomData: ZoomData[] | ((zoomData: ZoomData[]) => ZoomData[])) => void;
+ /**
+ * Whether the user is currently interacting with the chart.
+ * This is useful to prevent animations from running while the user is interacting.
+ */
+ isInteracting: boolean;
+ /**
+ * Set the interaction state of the chart.
+ * @param {boolean} isInteracting The new interaction state.
+ */
+ setIsInteracting: (isInteracting: boolean) => void;
+};
+
export type ZoomOptions = {
/**
* The starting percentage of the zoom range. In the range of 0 to 100.
@@ -64,8 +118,16 @@ export type ZoomData = {
};
export type ZoomProps = {
+ /**
+ * The list of zoom data related to each axis.
+ */
zoom?: ZoomData[];
- onZoomChange?: (zoom: ZoomData[]) => void;
+ /**
+ * Callback fired when the zoom has changed.
+ *
+ * @param {ZoomData[]} zoomData Updated zoom data.
+ */
+ onZoomChange?: (zoomData: ZoomData[] | ((zoomData: ZoomData[]) => ZoomData[])) => void;
};
export type DefaultizedZoomOptions = Required & {
diff --git a/packages/x-charts-pro/src/context/ZoomProvider/ZoomContext.ts b/packages/x-charts-pro/src/context/ZoomProvider/ZoomContext.ts
index 88edfe25d42ce..a4cecd94d2286 100644
--- a/packages/x-charts-pro/src/context/ZoomProvider/ZoomContext.ts
+++ b/packages/x-charts-pro/src/context/ZoomProvider/ZoomContext.ts
@@ -1,16 +1,6 @@
import * as React from 'react';
-import { AxisId, Initializable } from '@mui/x-charts/internals';
-import { DefaultizedZoomOptions, ZoomData } from './Zoom.types';
-
-export type ZoomState = {
- isZoomEnabled: boolean;
- isPanEnabled: boolean;
- options: Record;
- zoomData: ZoomData[];
- setZoomData: (zoomData: ZoomData[]) => void;
- isInteracting: boolean;
- setIsInteracting: (isInteracting: boolean) => void;
-};
+import { Initializable } from '@mui/x-charts/internals';
+import { ZoomState } from './Zoom.types';
export const ZoomContext = React.createContext>({
isInitialized: false,
diff --git a/packages/x-charts-pro/src/context/ZoomProvider/ZoomProvider.tsx b/packages/x-charts-pro/src/context/ZoomProvider/ZoomProvider.tsx
index 9e3cff88622d1..6c55b67693187 100644
--- a/packages/x-charts-pro/src/context/ZoomProvider/ZoomProvider.tsx
+++ b/packages/x-charts-pro/src/context/ZoomProvider/ZoomProvider.tsx
@@ -1,25 +1,12 @@
import * as React from 'react';
-import { ZoomContext, ZoomState } from './ZoomContext';
+import useControlled from '@mui/utils/useControlled';
+import { Initializable } from '@mui/x-charts/internals';
+import { ZoomContext } from './ZoomContext';
import { defaultizeZoom } from './defaultizeZoom';
-import { AxisConfigForZoom, ZoomData } from './Zoom.types';
+import { ZoomData, ZoomProviderProps, ZoomState } from './Zoom.types';
+import { initializeZoomData } from './initializeZoomData';
-type ZoomProviderProps = {
- children: React.ReactNode;
- /**
- * The configuration of the x-axes.
- * If not provided, a default axis config is used.
- * An array of [[AxisConfig]] objects.
- */
- xAxis?: AxisConfigForZoom[];
- /**
- * The configuration of the y-axes.
- * If not provided, a default axis config is used.
- * An array of [[AxisConfig]] objects.
- */
- yAxis?: AxisConfigForZoom[];
-};
-
-export function ZoomProvider({ children, xAxis, yAxis }: ZoomProviderProps) {
+export function ZoomProvider({ children, xAxis, yAxis, zoom, onZoomChange }: ZoomProviderProps) {
const [isInteracting, setIsInteracting] = React.useState(false);
const options = React.useMemo(
@@ -34,28 +21,40 @@ export function ZoomProvider({ children, xAxis, yAxis }: ZoomProviderProps) {
[xAxis, yAxis],
);
- const [zoomData, setZoomData] = React.useState(() =>
- Object.values(options).map(({ axisId, minStart: start, maxEnd: end }) => ({
- axisId,
- start,
- end,
- })),
+ // Default zoom data is initialized only once when uncontrolled. If the user changes the options
+ // after the initial render, the zoom data will not be updated until the next zoom interaction.
+ // This is required to avoid warnings about controlled/uncontrolled components.
+ const defaultZoomData = React.useRef(initializeZoomData(options));
+
+ const [zoomData, setZoomData] = useControlled({
+ controlled: zoom,
+ default: defaultZoomData.current,
+ name: 'ZoomProvider',
+ state: 'zoom',
+ });
+
+ const setZoomDataCallback = React.useCallback(
+ (newZoomData) => {
+ setZoomData(newZoomData);
+ onZoomChange?.(newZoomData);
+ },
+ [setZoomData, onZoomChange],
);
- const value = React.useMemo(
+ const value = React.useMemo>(
() => ({
isInitialized: true,
data: {
- isZoomEnabled: zoomData.length > 0,
+ isZoomEnabled: Object.keys(options).length > 0,
isPanEnabled: isPanEnabled(options),
options,
zoomData,
- setZoomData,
+ setZoomData: setZoomDataCallback,
isInteracting,
setIsInteracting,
},
}),
- [zoomData, setZoomData, isInteracting, setIsInteracting, options],
+ [zoomData, isInteracting, setIsInteracting, options, setZoomDataCallback],
);
return {children};
diff --git a/packages/x-charts-pro/src/context/ZoomProvider/ZoomSetup.ts b/packages/x-charts-pro/src/context/ZoomProvider/ZoomSetup.ts
index 072baa872d421..775b173dabdf1 100644
--- a/packages/x-charts-pro/src/context/ZoomProvider/ZoomSetup.ts
+++ b/packages/x-charts-pro/src/context/ZoomProvider/ZoomSetup.ts
@@ -1,6 +1,13 @@
import { useSetupPan } from './useSetupPan';
import { useSetupZoom } from './useSetupZoom';
+/**
+ * Sets up the zoom functionality if using composition or a custom chart.
+ *
+ * Simply add this component at the same level as the chart component to enable zooming and panning.
+ *
+ * See: [Composition](https://mui.com/x/react-charts/composition/)
+ */
function ZoomSetup() {
useSetupZoom();
useSetupPan();
diff --git a/packages/x-charts-pro/src/context/ZoomProvider/initializeZoomData.ts b/packages/x-charts-pro/src/context/ZoomProvider/initializeZoomData.ts
new file mode 100644
index 0000000000000..454865c586486
--- /dev/null
+++ b/packages/x-charts-pro/src/context/ZoomProvider/initializeZoomData.ts
@@ -0,0 +1,11 @@
+import { ZoomState } from './Zoom.types';
+
+// This function is used to initialize the zoom data when it is not provided by the user.
+// It is helpful to avoid the need to provide the possibly auto-generated id for each axis.
+export const initializeZoomData = (options: ZoomState['options']) => {
+ return Object.values(options).map(({ axisId, minStart: start, maxEnd: end }) => ({
+ axisId,
+ start,
+ end,
+ }));
+};
diff --git a/packages/x-charts-pro/src/context/ZoomProvider/useSetupZoom.ts b/packages/x-charts-pro/src/context/ZoomProvider/useSetupZoom.ts
index 876f6bd873a2a..c73900cd34bab 100644
--- a/packages/x-charts-pro/src/context/ZoomProvider/useSetupZoom.ts
+++ b/packages/x-charts-pro/src/context/ZoomProvider/useSetupZoom.ts
@@ -55,7 +55,7 @@ const zoomAtPoint = (
};
export const useSetupZoom = () => {
- const { zoomData, setZoomData, isZoomEnabled, options, setIsInteracting } = useZoom();
+ const { setZoomData, isZoomEnabled, options, setIsInteracting } = useZoom();
const drawingArea = useDrawingArea();
const svgRef = useSvgRef();
@@ -91,24 +91,28 @@ export const useSetupZoom = () => {
setIsInteracting(false);
}, 166);
- const newZoomData = zoomData.map((zoom) => {
- const option = options[zoom.axisId];
- const centerRatio =
- option.axisDirection === 'x'
- ? getHorizontalCenterRatio(point, drawingArea)
- : getVerticalCenterRatio(point, drawingArea);
+ setZoomData((prevZoomData) => {
+ return prevZoomData.map((zoom) => {
+ const option = options[zoom.axisId];
+ if (!option) {
+ return zoom;
+ }
- const { scaleRatio, isZoomIn } = getWheelScaleRatio(event, option.step);
- const [newMinRange, newMaxRange] = zoomAtPoint(centerRatio, scaleRatio, zoom, option);
+ const centerRatio =
+ option.axisDirection === 'x'
+ ? getHorizontalCenterRatio(point, drawingArea)
+ : getVerticalCenterRatio(point, drawingArea);
- if (!isSpanValid(newMinRange, newMaxRange, isZoomIn, option)) {
- return zoom;
- }
+ const { scaleRatio, isZoomIn } = getWheelScaleRatio(event, option.step);
+ const [newMinRange, newMaxRange] = zoomAtPoint(centerRatio, scaleRatio, zoom, option);
- return { axisId: zoom.axisId, start: newMinRange, end: newMaxRange };
- });
+ if (!isSpanValid(newMinRange, newMaxRange, isZoomIn, option)) {
+ return zoom;
+ }
- setZoomData(newZoomData);
+ return { axisId: zoom.axisId, start: newMinRange, end: newMaxRange };
+ });
+ });
};
function pointerDownHandler(event: PointerEvent) {
@@ -134,39 +138,41 @@ export const useSetupZoom = () => {
const firstEvent = eventCacheRef.current[0];
const curDiff = getDiff(eventCacheRef.current);
- const newZoomData = zoomData.map((zoom) => {
- const option = options[zoom.axisId];
-
- const { scaleRatio, isZoomIn } = getPinchScaleRatio(
- curDiff,
- eventPrevDiff.current,
- option.step,
- );
-
- // If the scale ratio is 0, it means the pinch gesture is not valid.
- if (scaleRatio === 0) {
- eventPrevDiff.current = curDiff;
- return zoom;
- }
-
- const point = getSVGPoint(element, firstEvent);
-
- const centerRatio =
- option.axisDirection === 'x'
- ? getHorizontalCenterRatio(point, drawingArea)
- : getVerticalCenterRatio(point, drawingArea);
-
- const [newMinRange, newMaxRange] = zoomAtPoint(centerRatio, scaleRatio, zoom, option);
-
- if (!isSpanValid(newMinRange, newMaxRange, isZoomIn, option)) {
- return zoom;
- }
-
- return { axisId: zoom.axisId, start: newMinRange, end: newMaxRange };
+ setZoomData((prevZoomData) => {
+ const newZoomData = prevZoomData.map((zoom) => {
+ const option = options[zoom.axisId];
+ if (!option) {
+ return zoom;
+ }
+
+ const { scaleRatio, isZoomIn } = getPinchScaleRatio(
+ curDiff,
+ eventPrevDiff.current,
+ option.step,
+ );
+
+ // If the scale ratio is 0, it means the pinch gesture is not valid.
+ if (scaleRatio === 0) {
+ return zoom;
+ }
+
+ const point = getSVGPoint(element, firstEvent);
+
+ const centerRatio =
+ option.axisDirection === 'x'
+ ? getHorizontalCenterRatio(point, drawingArea)
+ : getVerticalCenterRatio(point, drawingArea);
+
+ const [newMinRange, newMaxRange] = zoomAtPoint(centerRatio, scaleRatio, zoom, option);
+
+ if (!isSpanValid(newMinRange, newMaxRange, isZoomIn, option)) {
+ return zoom;
+ }
+ return { axisId: zoom.axisId, start: newMinRange, end: newMaxRange };
+ });
+ eventPrevDiff.current = curDiff;
+ return newZoomData;
});
-
- eventPrevDiff.current = curDiff;
- setZoomData(newZoomData);
}
function pointerUpHandler(event: PointerEvent) {
@@ -210,7 +216,7 @@ export const useSetupZoom = () => {
clearTimeout(interactionTimeoutRef.current);
}
};
- }, [svgRef, setZoomData, zoomData, drawingArea, isZoomEnabled, options, setIsInteracting]);
+ }, [svgRef, setZoomData, drawingArea, isZoomEnabled, options, setIsInteracting]);
};
/**
diff --git a/packages/x-charts-pro/src/context/ZoomProvider/useZoom.ts b/packages/x-charts-pro/src/context/ZoomProvider/useZoom.ts
index 59bda96ca395a..af53a694c4482 100644
--- a/packages/x-charts-pro/src/context/ZoomProvider/useZoom.ts
+++ b/packages/x-charts-pro/src/context/ZoomProvider/useZoom.ts
@@ -1,7 +1,23 @@
import * as React from 'react';
import { ZoomContext } from './ZoomContext';
+import { ZoomState } from './Zoom.types';
+
+/**
+ * Get access to the zoom state.
+ *
+ * @returns {ZoomState} The zoom state.
+ */
+export function useZoom(): ZoomState {
+ const { data, isInitialized } = React.useContext(ZoomContext);
+
+ if (!isInitialized) {
+ throw new Error(
+ [
+ 'MUI X: Could not find the zoom context.',
+ 'It looks like you rendered your component outside of a ChartsContainer parent component.',
+ ].join('\n'),
+ );
+ }
-export const useZoom = () => {
- const { data } = React.useContext(ZoomContext);
return data;
-};
+}
diff --git a/packages/x-charts-pro/src/context/index.ts b/packages/x-charts-pro/src/context/index.ts
new file mode 100644
index 0000000000000..d0634736be8a8
--- /dev/null
+++ b/packages/x-charts-pro/src/context/index.ts
@@ -0,0 +1,4 @@
+// # Zoom & Pan
+export type { ZoomOptions, ZoomData, ZoomProps, ZoomState } from './ZoomProvider/Zoom.types';
+export * from './ZoomProvider/useZoom';
+export * from './ZoomProvider/ZoomSetup';
diff --git a/packages/x-charts-pro/src/index.ts b/packages/x-charts-pro/src/index.ts
index eade0525b9407..34c2369623813 100644
--- a/packages/x-charts-pro/src/index.ts
+++ b/packages/x-charts-pro/src/index.ts
@@ -33,3 +33,6 @@ export * from './ChartContainerPro';
export * from './ScatterChartPro';
export * from './BarChartPro';
export * from './LineChartPro';
+
+// Pro context
+export * from './context';
diff --git a/packages/x-charts-pro/src/typeOverloads/modules.ts b/packages/x-charts-pro/src/typeOverloads/modules.ts
index 5dae3f83d41ed..78c705633fce2 100644
--- a/packages/x-charts-pro/src/typeOverloads/modules.ts
+++ b/packages/x-charts-pro/src/typeOverloads/modules.ts
@@ -4,7 +4,7 @@ import {
HeatmapSeriesType,
DefaultizedHeatmapSeriesType,
} from '../models/seriesType/heatmap';
-import { ZoomOptions } from '../context/ZoomProvider/Zoom.types';
+import { ZoomOptions } from '../context/ZoomProvider';
declare module '@mui/x-charts/internals' {
interface ChartsSeriesConfig {
diff --git a/scripts/x-charts-pro.exports.json b/scripts/x-charts-pro.exports.json
index bec72c5ad30ab..7da58b547b024 100644
--- a/scripts/x-charts-pro.exports.json
+++ b/scripts/x-charts-pro.exports.json
@@ -321,6 +321,12 @@
{ "name": "useYColorScale", "kind": "Function" },
{ "name": "useYScale", "kind": "Function" },
{ "name": "useZColorScale", "kind": "Function" },
+ { "name": "useZoom", "kind": "Function" },
{ "name": "ZAxisContextProvider", "kind": "Function" },
- { "name": "ZAxisContextProviderProps", "kind": "TypeAlias" }
+ { "name": "ZAxisContextProviderProps", "kind": "TypeAlias" },
+ { "name": "ZoomData", "kind": "TypeAlias" },
+ { "name": "ZoomOptions", "kind": "TypeAlias" },
+ { "name": "ZoomProps", "kind": "TypeAlias" },
+ { "name": "ZoomSetup", "kind": "Function" },
+ { "name": "ZoomState", "kind": "TypeAlias" }
]