Repository: mui/mui-x
Pull request #17916: [charts] Allow exporting a heatmap chart
Author: login: bernardobelchior
Labels: name: type: new feature
description: Expand the scope of the product to solve a new problem.
name: plan: Pro
description: Impact at least one Pro user.
name: scope: charts
description: Changes related to the charts.
Created: 2025-05-20T13:45:09Z | Merged: 2025-05-23T07:40:42Z

Part of https://github.com/mui/mui-x/issues/11746.

Also updated the print example to allow selecting different chart types. 


Diff:
diff --git a/docs/data/charts/export/ExportChartAsImage.tsx b/docs/data/charts/export/ExportChartAsImage.tsx
index b1ebea18faf81..e046742331a04 100644
--- a/docs/data/charts/export/ExportChartAsImage.tsx
+++ b/docs/data/charts/export/ExportChartAsImage.tsx
@@ -73,7 +73,7 @@ function ExportParamsSelector({
 }
 
 export default function ExportChartAsImage() {
-  const apiRef = React.useRef<ChartProApi>(undefined);
+  const apiRef = React.useRef<ChartProApi<'line'>>(undefined);
 
   return (
     <Stack width="100%" gap={2}>
diff --git a/docs/data/charts/export/ExportCompositionNoSnap.tsx b/docs/data/charts/export/ExportCompositionNoSnap.tsx
index 0f0d03c85e464..9a3bc2e79fd4d 100644
--- a/docs/data/charts/export/ExportCompositionNoSnap.tsx
+++ b/docs/data/charts/export/ExportCompositionNoSnap.tsx
@@ -25,7 +25,7 @@ function CustomChartWrapper({ children }: React.PropsWithChildren) {
 }
 
 export default function ExportCompositionNoSnap() {
-  const apiRef = React.useRef<ChartProApi>(undefined);
+  const apiRef = React.useRef<ChartProApi<'composition'>>(undefined);
 
   return (
     <Stack width="100%" sx={{ display: 'block' }}>
diff --git a/docs/data/charts/export/PrintChart.js b/docs/data/charts/export/PrintChart.js
index 9fd8617952a03..2fcb3abbca0f3 100644
--- a/docs/data/charts/export/PrintChart.js
+++ b/docs/data/charts/export/PrintChart.js
@@ -1,11 +1,19 @@
 import * as React from 'react';
 import Stack from '@mui/material/Stack';
 import Button from '@mui/material/Button';
+import Select from '@mui/material/Select';
+import InputLabel from '@mui/material/InputLabel';
+import FormControl from '@mui/material/FormControl';
+import MenuItem from '@mui/material/MenuItem';
 import { ScatterChartPro } from '@mui/x-charts-pro/ScatterChartPro';
 
+import { BarChartPro } from '@mui/x-charts-pro/BarChartPro';
+import { LineChartPro } from '@mui/x-charts-pro/LineChartPro';
+import { Heatmap } from '@mui/x-charts-pro/Heatmap';
 import { data } from './randomData';
+import { heatmapData } from './heatmapData';
 
-const series = [
+const scatterSeries = [
   {
     label: 'Series A',
     data: data.map((v) => ({ x: v.x1, y: v.y1, id: v.id })),
@@ -16,19 +24,90 @@ const series = [
   },
 ];
 
+const series = [
+  { label: 'Series A', data: data.map((p) => p.y1) },
+  { label: 'Series B', data: data.map((p) => p.y2) },
+];
+
 export default function PrintChart() {
+  const [chartType, setChartType] = React.useState('scatter');
   const apiRef = React.useRef(undefined);
 
+  const handleChange = (event) => setChartType(event.target.value);
+
   return (
     <Stack width="100%" sx={{ display: 'block' }}>
-      <Button
-        onClick={() => apiRef.current.exportAsPrint()}
-        variant="contained"
+      <Stack
+        width="100%"
+        direction="row"
+        gap={2}
+        justifyContent="center"
         sx={{ mb: 1 }}
       >
-        Print
-      </Button>
-      <ScatterChartPro apiRef={apiRef} height={300} series={series} />
+        <FormControl sx={{ minWidth: 200 }}>
+          <InputLabel id="chart-type-label">Chart Type</InputLabel>
+          <Select
+            labelId="chart-type-label"
+            id="chart-type-select"
+            value={chartType}
+            label="Chart Type"
+            onChange={handleChange}
+          >
+            <MenuItem value="scatter">Scatter</MenuItem>
+            <MenuItem value="line">Line</MenuItem>
+            <MenuItem value="bar">Bar</MenuItem>
+            <MenuItem value="heatmap">Heatmap</MenuItem>
+          </Select>
+        </FormControl>
+        <Button onClick={() => apiRef.current.exportAsPrint()} variant="contained">
+          Print
+        </Button>
+      </Stack>
+      <Chart apiRef={apiRef} type={chartType} />
     </Stack>
   );
 }
+
+function Chart({ apiRef, type }) {
+  switch (type) {
+    case 'scatter':
+      return <ScatterChartPro apiRef={apiRef} height={300} series={scatterSeries} />;
+
+    case 'line':
+      return (
+        <LineChartPro
+          apiRef={apiRef}
+          height={300}
+          xAxis={[{ data: data.map((p) => p.x1).toSorted((a, b) => a - b) }]}
+          series={series}
+        />
+      );
+
+    case 'bar':
+      return (
+        <BarChartPro
+          apiRef={apiRef}
+          height={300}
+          xAxis={[
+            { data: data.map((p) => Math.round(p.x1)).toSorted((a, b) => a - b) },
+          ]}
+          series={series}
+        />
+      );
+
+    case 'heatmap':
+      return (
+        <Heatmap
+          apiRef={apiRef}
+          xAxis={[{ data: [1, 2, 3, 4] }]}
+          yAxis={[{ data: ['A', 'B', 'C', 'D', 'E'] }]}
+          series={[{ data: heatmapData }]}
+          height={300}
+          hideLegend={false}
+        />
+      );
+
+    default:
+      throw new Error(`Unknown chart type: ${type}`);
+  }
+}
diff --git a/docs/data/charts/export/PrintChart.tsx b/docs/data/charts/export/PrintChart.tsx
index aa7b6d8a20d9c..76215e6368d68 100644
--- a/docs/data/charts/export/PrintChart.tsx
+++ b/docs/data/charts/export/PrintChart.tsx
@@ -1,11 +1,19 @@
 import * as React from 'react';
 import Stack from '@mui/material/Stack';
 import Button from '@mui/material/Button';
+import Select, { SelectChangeEvent } from '@mui/material/Select';
+import InputLabel from '@mui/material/InputLabel';
+import FormControl from '@mui/material/FormControl';
+import MenuItem from '@mui/material/MenuItem';
 import { ScatterChartPro } from '@mui/x-charts-pro/ScatterChartPro';
 import { ChartProApi } from '@mui/x-charts-pro/ChartContainerPro';
+import { BarChartPro } from '@mui/x-charts-pro/BarChartPro';
+import { LineChartPro } from '@mui/x-charts-pro/LineChartPro';
+import { Heatmap } from '@mui/x-charts-pro/Heatmap';
 import { data } from './randomData';
+import { heatmapData } from './heatmapData';
 
-const series = [
+const scatterSeries = [
   {
     label: 'Series A',
     data: data.map((v) => ({ x: v.x1, y: v.y1, id: v.id })),
@@ -15,20 +23,101 @@ const series = [
     data: data.map((v) => ({ x: v.x1, y: v.y2, id: v.id })),
   },
 ];
+const series = [
+  { label: 'Series A', data: data.map((p) => p.y1) },
+  { label: 'Series B', data: data.map((p) => p.y2) },
+];
+
+type ChartType = 'scatter' | 'line' | 'bar' | 'heatmap';
 
 export default function PrintChart() {
+  const [chartType, setChartType] = React.useState<ChartType>('scatter');
   const apiRef = React.useRef<ChartProApi>(undefined);
 
+  const handleChange = (event: SelectChangeEvent) =>
+    setChartType(event.target.value as ChartType);
+
   return (
     <Stack width="100%" sx={{ display: 'block' }}>
-      <Button
-        onClick={() => apiRef.current!.exportAsPrint()}
-        variant="contained"
+      <Stack
+        width="100%"
+        direction="row"
+        gap={2}
+        justifyContent="center"
         sx={{ mb: 1 }}
       >
-        Print
-      </Button>
-      <ScatterChartPro apiRef={apiRef} height={300} series={series} />
+        <FormControl sx={{ minWidth: 200 }}>
+          <InputLabel id="chart-type-label">Chart Type</InputLabel>
+          <Select
+            labelId="chart-type-label"
+            id="chart-type-select"
+            value={chartType}
+            label="Chart Type"
+            onChange={handleChange}
+          >
+            <MenuItem value="scatter">Scatter</MenuItem>
+            <MenuItem value="line">Line</MenuItem>
+            <MenuItem value="bar">Bar</MenuItem>
+            <MenuItem value="heatmap">Heatmap</MenuItem>
+          </Select>
+        </FormControl>
+        <Button onClick={() => apiRef.current!.exportAsPrint()} variant="contained">
+          Print
+        </Button>
+      </Stack>
+      <Chart apiRef={apiRef} type={chartType} />
     </Stack>
   );
 }
+
+function Chart<T extends ChartType = ChartType>({
+  apiRef,
+  type,
+}: {
+  apiRef: React.RefObject<ChartProApi<T> | undefined>;
+  type: T;
+}) {
+  switch (type) {
+    case 'scatter':
+      return (
+        <ScatterChartPro
+          apiRef={apiRef as React.RefObject<ChartProApi<'scatter'> | undefined>}
+          height={300}
+          series={scatterSeries}
+        />
+      );
+    case 'line':
+      return (
+        <LineChartPro
+          apiRef={apiRef as React.RefObject<ChartProApi<'line'> | undefined>}
+          height={300}
+          xAxis={[{ data: data.map((p) => p.x1).toSorted((a, b) => a - b) }]}
+          series={series}
+        />
+      );
+    case 'bar':
+      return (
+        <BarChartPro
+          apiRef={apiRef as React.RefObject<ChartProApi<'bar'> | undefined>}
+          height={300}
+          xAxis={[
+            { data: data.map((p) => Math.round(p.x1)).toSorted((a, b) => a - b) },
+          ]}
+          series={series}
+        />
+      );
+    case 'heatmap':
+      return (
+        <Heatmap
+          apiRef={apiRef as React.RefObject<ChartProApi<'heatmap'> | undefined>}
+          xAxis={[{ data: [1, 2, 3, 4] }]}
+          yAxis={[{ data: ['A', 'B', 'C', 'D', 'E'] }]}
+          series={[{ data: heatmapData }]}
+          height={300}
+          hideLegend={false}
+        />
+      );
+    default:
+      throw new Error(`Unknown chart type: ${type}`);
+  }
+}
diff --git a/docs/data/charts/export/PrintChart.tsx.preview b/docs/data/charts/export/PrintChart.tsx.preview
deleted file mode 100644
index 4014a2409237b..0000000000000
--- a/docs/data/charts/export/PrintChart.tsx.preview
+++ /dev/null
@@ -1,8 +0,0 @@
-<Button
-  onClick={() => apiRef.current!.exportAsPrint()}
-  variant="contained"
-  sx={{ mb: 1 }}
->
-  Print
-</Button>
-<ScatterChartPro apiRef={apiRef} height={300} series={series} />
\ No newline at end of file
diff --git a/docs/data/charts/export/export.md b/docs/data/charts/export/export.md
index 5c558584b03e6..ad0056d0cd474 100644
--- a/docs/data/charts/export/export.md
+++ b/docs/data/charts/export/export.md
@@ -8,7 +8,7 @@ components: ScatterChartPro, BarChartPro, LineChartPro
 
 <p class="description">Charts can be printed and exported as PDF.</p>
 
-Export is available on the Pro version of the charts: `<LineChartPro />`, `<BarChartPro />`, `<ScatterChartPro />`.
+Export is available for the following charts: `<LineChartPro />`, `<BarChartPro />`, `<ScatterChartPro />`, `<Heatmap />`.
 
 ## Print/Export as PDF
 
diff --git a/docs/data/charts/export/heatmapData.js b/docs/data/charts/export/heatmapData.js
new file mode 100644
index 0000000000000..b27cfaf6cbe16
--- /dev/null
+++ b/docs/data/charts/export/heatmapData.js
@@ -0,0 +1,22 @@
+export const heatmapData = [
+  [0, 0, 10],
+  [0, 1, 20],
+  [0, 2, 40],
+  [0, 3, 90],
+  [0, 4, 70],
+  [1, 0, 30],
+  [1, 1, 50],
+  [1, 2, 10],
+  [1, 3, 70],
+  [1, 4, 40],
+  [2, 0, 50],
+  [2, 1, 20],
+  [2, 2, 90],
+  [2, 3, 20],
+  [2, 4, 70],
+  [3, 0, 40],
+  [3, 1, 50],
+  [3, 2, 20],
+  [3, 3, 70],
+  [3, 4, 90],
+];
diff --git a/docs/data/charts/export/heatmapData.ts b/docs/data/charts/export/heatmapData.ts
new file mode 100644
index 0000000000000..b88d55c474ec4
--- /dev/null
+++ b/docs/data/charts/export/heatmapData.ts
@@ -0,0 +1,24 @@
+import { HeatmapValueType } from '@mui/x-charts-pro/models';
+
+export const heatmapData: HeatmapValueType[] = [
+  [0, 0, 10],
+  [0, 1, 20],
+  [0, 2, 40],
+  [0, 3, 90],
+  [0, 4, 70],
+  [1, 0, 30],
+  [1, 1, 50],
+  [1, 2, 10],
+  [1, 3, 70],
+  [1, 4, 40],
+  [2, 0, 50],
+  [2, 1, 20],
+  [2, 2, 90],
+  [2, 3, 20],
+  [2, 4, 70],
+  [3, 0, 40],
+  [3, 1, 50],
+  [3, 2, 20],
+  [3, 3, 70],
+  [3, 4, 90],
+];
diff --git a/packages/x-charts-pro/src/ChartContainerPro/ChartContainerPro.tsx b/packages/x-charts-pro/src/ChartContainerPro/ChartContainerPro.tsx
index 91a742fc91587..41fe63f46c9ba 100644
--- a/packages/x-charts-pro/src/ChartContainerPro/ChartContainerPro.tsx
+++ b/packages/x-charts-pro/src/ChartContainerPro/ChartContainerPro.tsx
@@ -19,8 +19,6 @@ type ChartContainerProComponent = <
   props: ChartContainerProProps<TSeries, TSignatures> & { ref?: React.ForwardedRef<SVGSVGElement> },
 ) => React.JSX.Element;
 
-export type ChartProApi = NonNullable<NonNullable<ChartContainerProProps['apiRef']>['current']>;
-
 /**
  * It sets up the data providers as well as the `<svg>` for the chart.
  *
diff --git a/packages/x-charts-pro/src/ChartContainerPro/ChartProApi.ts b/packages/x-charts-pro/src/ChartContainerPro/ChartProApi.ts
new file mode 100644
index 0000000000000..cc73c9261df7c
--- /dev/null
+++ b/packages/x-charts-pro/src/ChartContainerPro/ChartProApi.ts
@@ -0,0 +1,28 @@
+import { ChartAnyPluginSignature, ChartPublicAPI } from '@mui/x-charts/internals';
+import { HeatmapPluginsSignatures } from '../Heatmap/Heatmap.plugins';
+import { LineChartProPluginsSignatures } from '../LineChartPro/LineChartPro.plugins';
+import { ScatterChartProPluginsSignatures } from '../ScatterChartPro/ScatterChartPro.plugins';
+import { BarChartProPluginsSignatures } from '../BarChartPro/BarChartPro.plugins';
+import { AllPluginSignatures, DefaultPluginSignatures } from '../internals/plugins/allPlugins';
+
+type PluginsPerSeriesType = {
+  heatmap: HeatmapPluginsSignatures;
+  line: LineChartProPluginsSignatures;
+  scatter: ScatterChartProPluginsSignatures;
+  bar: BarChartProPluginsSignatures;
+  /* Special value when creating a chart using composition. */
+  composition: DefaultPluginSignatures;
+};
+
+/**
+ * The API of the chart `apiRef` object.
+ * The chart type can be passed as the first generic parameter to narrow down the API to the specific chart type.
+ * @example ChartProApi<'bar'>
+ */
+export type ChartProApi<
+  TSeries extends keyof PluginsPerSeriesType | undefined = undefined,
+  TSignatures extends
+    readonly ChartAnyPluginSignature[] = TSeries extends keyof PluginsPerSeriesType
+    ? PluginsPerSeriesType[TSeries]
+    : AllPluginSignatures,
+> = ChartPublicAPI<TSignatures>;
diff --git a/packages/x-charts-pro/src/ChartContainerPro/index.ts b/packages/x-charts-pro/src/ChartContainerPro/index.ts
index fc6cf1b3c5168..5e89057b0e215 100644
--- a/packages/x-charts-pro/src/ChartContainerPro/index.ts
+++ b/packages/x-charts-pro/src/ChartContainerPro/index.ts
@@ -1,3 +1,4 @@
 import '../typeOverloads';
 
 export * from './ChartContainerPro';
+export * from './ChartProApi';
diff --git a/packages/x-charts-pro/src/Heatmap/Heatmap.plugins.ts b/packages/x-charts-pro/src/Heatmap/Heatmap.plugins.ts
index c8e6d8551ca2c..38ec10bc4c354 100644
--- a/packages/x-charts-pro/src/Heatmap/Heatmap.plugins.ts
+++ b/packages/x-charts-pro/src/Heatmap/Heatmap.plugins.ts
@@ -9,12 +9,17 @@ import {
   UseChartHighlightSignature,
   ConvertSignaturesIntoPlugins,
 } from '@mui/x-charts/internals';
+import {
+  useChartProExport,
+  UseChartProExportSignature,
+} from '../internals/plugins/useChartProExport';
 
 export type HeatmapPluginsSignatures = [
   UseChartZAxisSignature,
   UseChartCartesianAxisSignature<'heatmap'>,
   UseChartInteractionSignature,
   UseChartHighlightSignature,
+  UseChartProExportSignature,
 ];
 
 export const HEATMAP_PLUGINS = [
@@ -22,4 +27,5 @@ export const HEATMAP_PLUGINS = [
   useChartCartesianAxis,
   useChartInteraction,
   useChartHighlight,
+  useChartProExport,
 ] as ConvertSignaturesIntoPlugins<HeatmapPluginsSignatures>;
diff --git a/packages/x-charts-pro/src/Heatmap/Heatmap.tsx b/packages/x-charts-pro/src/Heatmap/Heatmap.tsx
index e6ff321518d49..bbbfffef85121 100644
--- a/packages/x-charts-pro/src/Heatmap/Heatmap.tsx
+++ b/packages/x-charts-pro/src/Heatmap/Heatmap.tsx
@@ -140,6 +140,7 @@ const Heatmap = React.forwardRef(function Heatmap(
 ) {
   const props = useThemeProps({ props: inProps, name: 'MuiHeatmap' });
   const {
+    apiRef,
     xAxis,
     yAxis,
     zAxis,
@@ -209,6 +210,7 @@ const Heatmap = React.forwardRef(function Heatmap(
 
   return (
     <ChartDataProviderPro<'heatmap', HeatmapPluginsSignatures>
+      apiRef={apiRef}
       seriesConfig={seriesConfig}
       series={series.map((s) => ({
         type: 'heatmap',
@@ -257,7 +259,10 @@ Heatmap.propTypes = {
   // | To update them edit the TypeScript types and run "pnpm proptypes"  |
   // ----------------------------------------------------------------------
   apiRef: PropTypes.shape({
-    current: PropTypes.object,
+    current: PropTypes.shape({
+      exportAsImage: PropTypes.func.isRequired,
+      exportAsPrint: PropTypes.func.isRequired,
+    }),
   }),
   className: PropTypes.string,
   /**


Reviews:
user: login: bernardobelchior
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-20T14:25:52Z
user: login: bernardobelchior
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-20T15:12:53Z
user: login: JCQuintas
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-20T16:37:25Z
user: login: bernardobelchior
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-21T07:10:16Z
user: login: alexfauquette
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-21T13:39:34Z
user: login: JCQuintas
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-21T13:53:15Z
user: login: bernardobelchior
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-21T13:53:41Z
user: login: bernardobelchior
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-22T08:08:36Z
user: login: JCQuintas
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-22T08:48:03Z
user: login: JCQuintas
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-22T16:21:24Z
user: login: bernardobelchior
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-23T06:48:54Z
user: login: JCQuintas
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-23T06:56:51Z
user: login: bernardobelchior
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-23T07:01:02Z
user: login: alexfauquette
body: The mapping from series type to their pluggin signature is elegant :+1:
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-23T07:06:42Z
user: login: JCQuintas
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-23T07:09:51Z
user: login: JCQuintas
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-23T07:10:57Z
user: login: JCQuintas
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-23T07:11:44Z
user: login: bernardobelchior
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-23T07:40:39Z

Review comments:
diff_hunk: @@ -0,0 +1,22 @@
+import { ChartAnyPluginSignature, ChartSeriesType } from '@mui/x-charts/internals';
+import { ChartContainerProProps } from './ChartContainerPro';
+import { HeatmapPluginsSignatures } from '../Heatmap/Heatmap.plugins';
+import { LineChartProPluginsSignatures } from '../LineChartPro/LineChartPro.plugins';
+import { ScatterChartProPluginsSignatures } from '../ScatterChartPro/ScatterChartPro.plugins';
+import { BarChartProPluginsSignatures } from '../BarChartPro/BarChartPro.plugins';
+import { AllPluginSignatures } from '../internals/plugins/allPlugins';
+
+type PluginsPerSeriesType = {
+  heatmap: HeatmapPluginsSignatures;
+  line: LineChartProPluginsSignatures;
+  scatter: ScatterChartProPluginsSignatures;
+  bar: BarChartProPluginsSignatures;
+};
+
+export type ChartProApi<
+  TSeries extends ChartSeriesType = ChartSeriesType,
+  TSignatures extends
+    readonly ChartAnyPluginSignature[] = TSeries extends keyof PluginsPerSeriesType
+    ? PluginsPerSeriesType[TSeries]
+    : AllPluginSignatures,
+> = NonNullable<NonNullable<ChartContainerProProps<TSeries, TSignatures>['apiRef']>['current']>;
path: packages/x-charts-pro/src/ChartContainerPro/ChartProApi.ts
user: login: bernardobelchior
body: The previous implementation would show `setZoomData` as a valid API for the heatmap chart. This implementation isn't breaking (since it defaults to `AllPluginSignatures`), but IMO we should encourage users to use `ChartProApi<'scatter'>` or `ChartProApi<'heatmap'>` for TypeScript to only suggest functions that are valid.

For composition, I think we should add a `composition` "series type" that uses the `DefaultPluginSignatures`. This way, when using composition, users should use `ChartProApi<'composition'>`, and in the next major we can make the `TSeries` parameter mandatory. 

What do you think, @alexfauquette @JCQuintas?
created_at: 2025-05-20T13:57:16Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,19 @@
+import * as React from 'react';
+import useForkRef from '@mui/utils/useForkRef';
+import { ChartsSurface, ChartsSurfaceProps } from '../ChartsSurface';
+import { useChartRootRef } from '../hooks';
+
+export interface ChartsRootSurfaceProps extends ChartsSurfaceProps {}
+
+/**
+ * `ChartsRootSurface` is a wrapper around the `ChartsSurface` component that provides a reference to the chart root element.
+ * This is only needed for charts that aren't wrapped in `ChartsWrapper`.
+ */
+export const ChartsRootSurface = React.forwardRef<SVGSVGElement, ChartsRootSurfaceProps>(
path: packages/x-charts/src/ChartsRootSurface/ChartsRootSurface.tsx
user: login: bernardobelchior
body: Had to create this to set the chart root ref. An alternative would be to default to `svgRef` in the `useChartProExport` plugin, but I thought the current approach would be cleaner. 
created_at: 2025-05-20T14:14:31Z
author_association: MEMBER
diff_hunk: @@ -73,7 +73,7 @@ function ExportParamsSelector({
 }
 
 export default function ExportChartAsImage() {
-  const apiRef = React.useRef<ChartProApi>(undefined);
+  const apiRef = React.useRef<ChartProApi<'line'>>(undefined);
path: docs/data/charts/export/ExportChartAsImage.tsx
user: login: bernardobelchior
body: Unfortunately, I don't think we can keep the current API for `ChartProApi`. Either we create a new type, or we need to accept this breaking change as a bug fix. 

It is a bug because invalid types are accepted. For example, the heatmap's `apiRef` doesn't contain a `setZoomData` property, but the type says it does. 

It's unlikely a lot of people are using it as it was [released in v8.1.0](https://github.com/mui/mui-x/commit/03408e35a8ff6240b65136e1120b20dbf7ca757a), but it's technically a breaking change if we don't flag it as a bug. 

Another option would be to create another type to avoid breaking users and deprecate this one. What are your thoughts? 
created_at: 2025-05-20T15:12:53Z
author_association: MEMBER
diff_hunk: @@ -73,7 +73,7 @@ function ExportParamsSelector({
 }
 
 export default function ExportChartAsImage() {
-  const apiRef = React.useRef<ChartProApi>(undefined);
+  const apiRef = React.useRef<ChartProApi<'line'>>(undefined);
path: docs/data/charts/export/ExportChartAsImage.tsx
user: login: JCQuintas
body: We could just go with the generic one but still allow users to narrow the types with the `<'seriesType'>` notation. 
created_at: 2025-05-20T16:37:25Z
author_association: MEMBER
diff_hunk: @@ -73,7 +73,7 @@ function ExportParamsSelector({
 }
 
 export default function ExportChartAsImage() {
-  const apiRef = React.useRef<ChartProApi>(undefined);
+  const apiRef = React.useRef<ChartProApi<'line'>>(undefined);
path: docs/data/charts/export/ExportChartAsImage.tsx
user: login: bernardobelchior
body: That doesn't work either. I suppose it's the same issue as trying to pass a `RefObject<HTMLElement>` to a `HTMLDivElement`. 
created_at: 2025-05-21T07:10:16Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,19 @@
+import * as React from 'react';
+import useForkRef from '@mui/utils/useForkRef';
+import { ChartsSurface, ChartsSurfaceProps } from '../ChartsSurface';
+import { useChartRootRef } from '../hooks';
+
+export interface ChartsRootSurfaceProps extends ChartsSurfaceProps {}
+
+/**
+ * `ChartsRootSurface` is a wrapper around the `ChartsSurface` component that provides a reference to the chart root element.
+ * This is only needed for charts that aren't wrapped in `ChartsWrapper`.
+ */
+export const ChartsRootSurface = React.forwardRef<SVGSVGElement, ChartsRootSurfaceProps>(
path: packages/x-charts/src/ChartsRootSurface/ChartsRootSurface.tsx
user: login: alexfauquette
body: This PR might solve your issue by introducing the wrapper to heatmap too
https://github.com/mui/mui-x/pull/17943
created_at: 2025-05-21T13:39:34Z
author_association: MEMBER
diff_hunk: @@ -73,7 +73,7 @@ function ExportParamsSelector({
 }
 
 export default function ExportChartAsImage() {
-  const apiRef = React.useRef<ChartProApi>(undefined);
+  const apiRef = React.useRef<ChartProApi<'line'>>(undefined);
path: docs/data/charts/export/ExportChartAsImage.tsx
user: login: JCQuintas
body: I don't get it, if the issue is that you are passing a wide type but it expects a narrow type, just make the expectation wide, so it can accept a narrow one.
created_at: 2025-05-21T13:53:14Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,19 @@
+import * as React from 'react';
+import useForkRef from '@mui/utils/useForkRef';
+import { ChartsSurface, ChartsSurfaceProps } from '../ChartsSurface';
+import { useChartRootRef } from '../hooks';
+
+export interface ChartsRootSurfaceProps extends ChartsSurfaceProps {}
+
+/**
+ * `ChartsRootSurface` is a wrapper around the `ChartsSurface` component that provides a reference to the chart root element.
+ * This is only needed for charts that aren't wrapped in `ChartsWrapper`.
+ */
+export const ChartsRootSurface = React.forwardRef<SVGSVGElement, ChartsRootSurfaceProps>(
path: packages/x-charts/src/ChartsRootSurface/ChartsRootSurface.tsx
user: login: bernardobelchior
body: Yes, it will. I can wait for that to be merged
created_at: 2025-05-21T13:53:41Z
author_association: MEMBER
diff_hunk: @@ -73,7 +73,7 @@ function ExportParamsSelector({
 }
 
 export default function ExportChartAsImage() {
-  const apiRef = React.useRef<ChartProApi>(undefined);
+  const apiRef = React.useRef<ChartProApi<'line'>>(undefined);
path: docs/data/charts/export/ExportChartAsImage.tsx
user: login: bernardobelchior
body: I think I managed to make it work. 

I thought it was another case of this issue with `HTMLDivElement` and `HTMLElement`:

![image](https://github.com/user-attachments/assets/e4eda08c-d40c-4b98-83f3-307c17cd5c49)

I've kept the `ChartProApi<'line'>` here because we should encourage users to use it, but it's usable without it as well:

![image](https://github.com/user-attachments/assets/83ff1481-e773-4efe-8656-e30fe6ae0473)

created_at: 2025-05-22T08:08:36Z
author_association: MEMBER
diff_hunk: @@ -73,7 +73,7 @@ function ExportParamsSelector({
 }
 
 export default function ExportChartAsImage() {
-  const apiRef = React.useRef<ChartProApi>(undefined);
+  const apiRef = React.useRef<ChartProApi<'line'>>(undefined);
path: docs/data/charts/export/ExportChartAsImage.tsx
user: login: JCQuintas
body: nice
created_at: 2025-05-22T08:48:03Z
author_association: MEMBER
diff_hunk: @@ -16,7 +16,7 @@ function CustomChartWrapper({ children }: React.PropsWithChildren) {
 
   return (
     <div
-      ref={chartRootRef}
+      ref={chartRootRef as React.RefObject<HTMLDivElement>}
path: docs/data/charts/export/ExportCompositionNoSnap.tsx
user: login: JCQuintas
body: Is this still necessary? 🤔
created_at: 2025-05-22T16:18:56Z
author_association: MEMBER
diff_hunk: @@ -6,7 +6,7 @@ import { useChartContext } from '../context/ChartProvider';
  * Get the ref for the root chart element.
  * @returns The root chart element ref.
  */
-export function useChartRootRef(): React.RefObject<HTMLDivElement | null> {
+export function useChartRootRef(): React.RefObject<HTMLElement | SVGElement | null> {
path: packages/x-charts/src/hooks/useChartRootRef.ts
user: login: JCQuintas
body: This can probably be reverted?
created_at: 2025-05-22T16:20:42Z
author_association: MEMBER
diff_hunk: @@ -75,7 +75,7 @@ function ChartsWrapper(props: ChartsWrapperProps) {
   const chartRootRef = useChartRootRef();
 
   return (
-    <Root ref={chartRootRef} ownerState={props} sx={sx}>
+    <Root ref={chartRootRef as React.RefObject<HTMLDivElement>} ownerState={props} sx={sx}>
path: packages/x-charts/src/internals/components/ChartsWrapper/ChartsWrapper.tsx
user: login: JCQuintas
body: Same here
created_at: 2025-05-22T16:20:55Z
author_association: MEMBER
diff_hunk: @@ -24,7 +24,7 @@ export interface ChartPluginOptions<TSignature extends ChartAnyPluginSignature>
   /**
    * Reference to the chart root element.
    */
-  chartRootRef: React.RefObject<HTMLDivElement | null>;
+  chartRootRef: React.RefObject<HTMLElement | SVGElement | null>;
path: packages/x-charts/src/internals/plugins/models/plugin.ts
user: login: JCQuintas
body: Same here
created_at: 2025-05-22T16:21:09Z
author_association: MEMBER
diff_hunk: @@ -6,7 +6,7 @@ import { useChartContext } from '../context/ChartProvider';
  * Get the ref for the root chart element.
  * @returns The root chart element ref.
  */
-export function useChartRootRef(): React.RefObject<HTMLDivElement | null> {
+export function useChartRootRef(): React.RefObject<HTMLElement | SVGElement | null> {
path: packages/x-charts/src/hooks/useChartRootRef.ts
user: login: bernardobelchior
body: Not sure to be honest. The reality is that any HTML or SVG elements should be accepted here. 

However, I think doing that now would be a breaking change, so I'll revert it. Maybe that's something we want to change for v9? 
created_at: 2025-05-23T06:47:08Z
author_association: MEMBER
diff_hunk: @@ -6,7 +6,7 @@ import { useChartContext } from '../context/ChartProvider';
  * Get the ref for the root chart element.
  * @returns The root chart element ref.
  */
-export function useChartRootRef(): React.RefObject<HTMLDivElement | null> {
+export function useChartRootRef(): React.RefObject<HTMLElement | SVGElement | null> {
path: packages/x-charts/src/hooks/useChartRootRef.ts
user: login: JCQuintas
body: Oh, if we need to accept `HTMLElement and SVGElement` IIRC we should use `HTMLElement | (HTMLElement & SVGElement)`
created_at: 2025-05-23T06:56:51Z
author_association: MEMBER
diff_hunk: @@ -6,7 +6,7 @@ import { useChartContext } from '../context/ChartProvider';
  * Get the ref for the root chart element.
  * @returns The root chart element ref.
  */
-export function useChartRootRef(): React.RefObject<HTMLDivElement | null> {
+export function useChartRootRef(): React.RefObject<HTMLElement | SVGElement | null> {
path: packages/x-charts/src/hooks/useChartRootRef.ts
user: login: bernardobelchior
body: `HTMLElement | (HTMLElement & SVGElement)`? Why not `HTMLElement | SVGElement`?

I'd like to make this change, but it's breaking and it's unfortunate that we need to ask users to cast the ref object to use it. I wonder if there's some way to infer the type 🤔
created_at: 2025-05-23T07:01:02Z
author_association: MEMBER
diff_hunk: @@ -6,7 +6,7 @@ import { useChartContext } from '../context/ChartProvider';
  * Get the ref for the root chart element.
  * @returns The root chart element ref.
  */
-export function useChartRootRef(): React.RefObject<HTMLDivElement | null> {
+export function useChartRootRef(): React.RefObject<HTMLElement | SVGElement | null> {
path: packages/x-charts/src/hooks/useChartRootRef.ts
user: login: JCQuintas
body: Nvm I couldn't find the reference, I might have been mistaken then 🤔 

I was thinking I used that somewhere to solve this issue of needing to cast the ref, but I tried it out and it doesn't work 😢 
created_at: 2025-05-23T07:09:51Z
author_association: MEMBER
diff_hunk: @@ -6,7 +6,7 @@ import { useChartContext } from '../context/ChartProvider';
  * Get the ref for the root chart element.
  * @returns The root chart element ref.
  */
-export function useChartRootRef(): React.RefObject<HTMLDivElement | null> {
+export function useChartRootRef(): React.RefObject<HTMLElement | SVGElement | null> {
path: packages/x-charts/src/hooks/useChartRootRef.ts
user: login: JCQuintas
body: If you need a broader definition you can also try to use `Element` instead
created_at: 2025-05-23T07:10:57Z
author_association: MEMBER
diff_hunk: @@ -6,7 +6,7 @@ import { useChartContext } from '../context/ChartProvider';
  * Get the ref for the root chart element.
  * @returns The root chart element ref.
  */
-export function useChartRootRef(): React.RefObject<HTMLDivElement | null> {
+export function useChartRootRef(): React.RefObject<HTMLElement | SVGElement | null> {
path: packages/x-charts/src/hooks/useChartRootRef.ts
user: login: bernardobelchior
body: > If you need a broader definition you can also try to use `Element` instead 

Yeah, that could also work, but we still have the typing issue. I don't know how to fix it, but I haven't dug deeper. 
created_at: 2025-05-23T07:40:39Z
author_association: MEMBER

PR comments:
user: login: github-actions[bot]
created_at: 2025-05-20T13:45:21Z
body: Thanks for adding a type label to the PR! 👍
author_association: CONTRIBUTOR
user: login: mui-bot
created_at: 2025-05-20T13:46:44Z
body: 
<!--
  0 failure: 
  0 warning: 
  
  3 markdown notices
  DangerID: danger-id-Danger;
-->



**Deploy preview:** <a href="https://deploy-preview-17916--material-ui-x.netlify.app/">https://deploy-preview-17916--material-ui-x.netlify.app/</a>



**Updated pages:**

- [docs/data/charts/export/export.md](https://deploy-preview-17916--material-ui-x.netlify.app/x/react-charts/export/)


## Bundle size report

**Total Size Change:**<sup>${\tiny{\color{red}▲}}$</sup>+42B<sup>(0.00%)</sup> - **Total Gzip Change:**<sup>${\tiny{\color{green}▼}}$</sup>-7B<sup>(0.00%)</sup>
Files: 118 total (0 added, 0 removed, 15 changed)


<details>
<summary>Show 15 more bundle changes</summary>

**@mui/x-charts-pro**&emsp;**parsed:**<sup>${\tiny{\color{red}▲}}$</sup>+21B<sup>(+0.01%)</sup> **gzip:**<sup>${\tiny{\color{red}▲}}$</sup>+10B<sup>(+0.01%)</sup>
**@mui/x-charts-pro/Heatmap**&emsp;**parsed:**<sup>${\tiny{\color{red}▲}}$</sup>+21B<sup>(+0.01%)</sup> **gzip:**<sup>${\tiny{\color{green}▼}}$</sup>-16B<sup>(-0.03%)</sup>
**@mui/x-charts-pro/BarChartPro**&emsp;**parsed:** 0B<sup>(0.00%)</sup> **gzip:**<sup>${\tiny{\color{green}▼}}$</sup>-2B<sup>(0.00%)</sup>
**@mui/x-charts-pro/ChartContainerPro**&emsp;**parsed:** 0B<sup>(0.00%)</sup> **gzip:**<sup>${\tiny{\color{green}▼}}$</sup>-1B<sup>(0.00%)</sup>
**@mui/x-charts-pro/ChartDataProviderPro**&emsp;**parsed:** 0B<sup>(0.00%)</sup> **gzip:**<sup>${\tiny{\color{red}▲}}$</sup>+1B<sup>(0.00%)</sup>
**@mui/x-charts-pro/FunnelChart**&emsp;**parsed:** 0B<sup>(0.00%)</sup> **gzip:**<sup>${\tiny{\color{red}▲}}$</sup>+1B<sup>(0.00%)</sup>
**@mui/x-charts-pro/ScatterChartPro**&emsp;**parsed:** 0B<sup>(0.00%)</sup> **gzip:**<sup>${\tiny{\color{green}▼}}$</sup>-1B<sup>(0.00%)</sup>
**@mui/x-data-grid-pro/DataGridPro**&emsp;**parsed:** 0B<sup>(0.00%)</sup> **gzip:**<sup>${\tiny{\color{red}▲}}$</sup>+1B<sup>(0.00%)</sup>
**@mui/x-date-pickers-pro/DateRangeCalendar**&emsp;**parsed:** 0B<sup>(0.00%)</sup> **gzip:**<sup>${\tiny{\color{green}▼}}$</sup>-2B<sup>(-0.01%)</sup>
**@mui/x-date-pickers-pro/DateRangePicker**&emsp;**parsed:** 0B<sup>(0.00%)</sup> **gzip:**<sup>${\tiny{\color{red}▲}}$</sup>+1B<sup>(0.00%)</sup>
**@mui/x-date-pickers-pro/DateRangePickerDay**&emsp;**parsed:** 0B<sup>(0.00%)</sup> **gzip:**<sup>${\tiny{\color{red}▲}}$</sup>+1B<sup>(+0.01%)</sup>
**@mui/x-date-pickers-pro/DesktopDateTimeRangePicker**&emsp;**parsed:** 0B<sup>(0.00%)</sup> **gzip:**<sup>${\tiny{\color{green}▼}}$</sup>-1B<sup>(0.00%)</sup>
**@mui/x-date-pickers-pro/MobileDateRangePicker**&emsp;**parsed:** 0B<sup>(0.00%)</sup> **gzip:**<sup>${\tiny{\color{red}▲}}$</sup>+1B<sup>(0.00%)</sup>
**@mui/x-tree-view-pro**&emsp;**parsed:** 0B<sup>(0.00%)</sup> **gzip:**<sup>${\tiny{\color{green}▼}}$</sup>-1B<sup>(0.00%)</sup>
**@mui/x-tree-view-pro/RichTreeViewPro**&emsp;**parsed:** 0B<sup>(0.00%)</sup> **gzip:**<sup>${\tiny{\color{red}▲}}$</sup>+1B<sup>(0.00%)</sup>

</details>

[Details of bundle changes](https://frontend-public.mui.com/size-comparison/mui/mui-x/diff?prNumber=17916&baseRef=master&baseCommit=48666ade5267cdc6e0c7c8481e05c1101a26d316&headCommit=3d939eccc5d1fabf381b13ce18953256092b7c78&circleCIBuildNumber=539485)
<p align="right">
  Generated by :no_entry_sign: <a href="https://danger.systems/js">dangerJS</a> against 3d939eccc5d1fabf381b13ce18953256092b7c78
</p>

author_association: NONE
user: login: github-actions[bot]
created_at: 2025-05-20T13:56:10Z
body: This pull request has conflicts, please resolve those before we can evaluate the pull request.
author_association: CONTRIBUTOR
user: login: codspeed-hq[bot]
created_at: 2025-05-20T14:33:24Z
body: <!-- __CODSPEED_PERFORMANCE_REPORT_COMMENT__ -->
<!-- __CODSPEED_INSTRUMENTATION_PERFORMANCE_REPORT_COMMENT__ -->

## [CodSpeed Performance Report](https://codspeed.io/mui/mui-x/branches/bernardobelchior%3Aexport-heatmap)

### Merging #17916 will **not alter performance**

<sub>Comparing <code>bernardobelchior:export-heatmap</code> (3d939ec) with <code>master</code> (48666ad)</sub>



### Summary

`✅ 9` untouched benchmarks  




author_association: NONE
user: login: github-actions[bot]
created_at: 2025-05-22T13:06:04Z
body: This pull request has conflicts, please resolve those before we can evaluate the pull request.
author_association: CONTRIBUTOR