[charts] Export charts as SVG#22661
Conversation
Deploy previewBundle size
Check out the code infra dashboard for more information about this PR. |
There was a problem hiding this comment.
Pull request overview
Adds first-class SVG export support to MUI X Charts Pro/Premium, including a new toolbar trigger, API surface (exportAsSvg), localization key, and documentation/demo updates.
Changes:
- Introduces
exportAsSvg()in the Pro export plugin, including SVG serialization and canvas-layer raster embedding. - Adds a new
ChartsToolbarSvgExportTriggerand integrates it intoChartsToolbarPro’s export menu + locale text. - Adds docs/demo content and an API reference page for the new toolbar trigger.
Reviewed changes
Copilot reviewed 31 out of 31 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| packages/x-charts/src/locales/utils/chartsLocaleTextApi.ts | Adds new locale key for SVG export menu item text. |
| packages/x-charts/src/locales/enUS.ts | Provides English string for the new SVG export menu item. |
| packages/x-charts-pro/src/ScatterChartPro/ScatterChartPro.tsx | Extends apiRef propTypes to include exportAsSvg. |
| packages/x-charts-pro/src/SankeyChart/SankeyChart.tsx | Extends apiRef propTypes to include exportAsSvg. |
| packages/x-charts-pro/src/RadarChartPro/RadarChartPro.tsx | Extends apiRef propTypes to include exportAsSvg. |
| packages/x-charts-pro/src/PieChartPro/PieChartPro.tsx | Extends apiRef propTypes to include exportAsSvg. |
| packages/x-charts-pro/src/LineChartPro/LineChartPro.tsx | Extends apiRef propTypes to include exportAsSvg. |
| packages/x-charts-pro/src/internals/plugins/useChartProExport/useChartProExport.types.ts | Adds ChartSvgExportOptions and exportAsSvg to the public export API types. |
| packages/x-charts-pro/src/internals/plugins/useChartProExport/useChartProExport.ts | Wires exportAsSvg into the export plugin public API and instance. |
| packages/x-charts-pro/src/internals/plugins/useChartProExport/exportSvg.ts | Implements SVG export composition (SVG layers + rasterized canvas layers + legend serialization). |
| packages/x-charts-pro/src/internals/plugins/useChartProExport/exportImage.ts | Refactors download triggering to shared helper. |
| packages/x-charts-pro/src/internals/plugins/useChartProExport/common.ts | Introduces shared triggerDownload helper for export flows. |
| packages/x-charts-pro/src/Heatmap/Heatmap.tsx | Extends apiRef propTypes to include exportAsSvg. |
| packages/x-charts-pro/src/FunnelChart/FunnelChart.tsx | Extends apiRef propTypes to include exportAsSvg. |
| packages/x-charts-pro/src/ChartsToolbarPro/index.ts | Re-exports the new SVG export trigger component. |
| packages/x-charts-pro/src/ChartsToolbarPro/ChartsToolbarSvgExportTrigger.tsx | Adds a new toolbar trigger component to call exportAsSvg. |
| packages/x-charts-pro/src/ChartsToolbarPro/ChartsToolbarPro.tsx | Adds SVG export entry to the export menu and exposes svgExportOptions. |
| packages/x-charts-pro/src/BarChartPro/BarChartPro.tsx | Extends apiRef propTypes to include exportAsSvg. |
| packages/x-charts-premium/src/ScatterChartPremium/ScatterChartPremium.tsx | Extends apiRef propTypes to include exportAsSvg. |
| packages/x-charts-premium/src/RadialLineChart/RadialLineChart.tsx | Extends apiRef propTypes to include exportAsSvg. |
| packages/x-charts-premium/src/RadialBarChart/RadialBarChart.tsx | Extends apiRef propTypes to include exportAsSvg. |
| packages/x-charts-premium/src/HeatmapPremium/HeatmapPremium.tsx | Extends apiRef propTypes to include exportAsSvg. |
| packages/x-charts-premium/src/CandlestickChart/CandlestickChart.tsx | Extends apiRef propTypes to include exportAsSvg. |
| packages/x-charts-premium/src/BarChartPremium/BarChartPremium.tsx | Extends apiRef propTypes to include exportAsSvg. |
| docs/translations/api-docs/charts/charts-toolbar-svg-export-trigger/charts-toolbar-svg-export-trigger.json | Adds translation stub for the new API page. |
| docs/pages/x/api/charts/charts-toolbar-svg-export-trigger.json | Adds generated API docs metadata for the new component. |
| docs/pages/x/api/charts/charts-toolbar-svg-export-trigger.js | Adds the new API docs page entry point. |
| docs/data/chartsApiPages.ts | Registers the new API page in the charts API pages list. |
| docs/data/charts/export/ExportChartAsSvg.tsx | Adds TS demo showcasing exportAsSvg. |
| docs/data/charts/export/ExportChartAsSvg.js | Adds JS demo showcasing exportAsSvg. |
| docs/data/charts/export/export.md | Documents SVG export and adds the new demo section. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const blob = new Blob([svgString], { type: 'image/svg+xml' }); | ||
| const url = URL.createObjectURL(blob); | ||
| triggerDownload(url, fileName || `${document.title}.svg`); | ||
| URL.revokeObjectURL(url); |
| function buildLegendGroup( | ||
| chartRoot: Element, | ||
| doc: Document, | ||
| originLeft: number, | ||
| originTop: number, | ||
| ): SVGElement | null { |
There was a problem hiding this comment.
There was a problem hiding this comment.
I tried the export in dark mode from https://deploy-preview-22661--material-ui-x.netlify.app/x/react-charts/export/#default-toolbar
And legend text are white
| For image and PDF export, `onBeforeExport` receives the iframe the chart is rendered into before the export process starts. | ||
| SVG export serializes an SVG document, so its `onBeforeExport` receives the `<svg>` element to be exported instead. |
There was a problem hiding this comment.
WOuld be easier if the onBeforeExport got the same iframe input, and add a warning in the SVG export section to say
:::warning
The SVG export is a serialization of the outer svg component.
If you CSS modification or add elements outside, they will have no impact ont the generated svg file
:::
Having same onBeforeExport signature should simplify sharing customization across different exports
| :::info | ||
| The chart is always exported in light mode, regardless of the color scheme of the page it is rendered in. | ||
| ::: | ||
|
|
There was a problem hiding this comment.
This is a general information impacting also PNG and PDF. Might be better to put it higher in the docs. And maybe add an issue to up-vote for dark mode support
| toolbarExportImage: (mimeType) => `Export as ${imageMimeTypes[mimeType] ?? mimeType}`, | ||
| toolbarExportSvg: 'Export as SVG', |
There was a problem hiding this comment.
WHat about add image/svg+xml mimeType to avoid introducing toolbarExportSvg
| // Span the union of the plot and legend boxes; their top-left is the export origin. | ||
| const legendClone = chartClone.querySelector(`.${legendClasses.root}`); | ||
| const rects = [containerClone.getBoundingClientRect()]; | ||
| if (legendClone) { | ||
| rects.push(legendClone.getBoundingClientRect()); | ||
| } | ||
| const topPadding = legendClone ? LEGEND_TOP_PADDING : 0; | ||
| const originLeft = Math.min(...rects.map((rect) => rect.left)); | ||
| const originTop = Math.min(...rects.map((rect) => rect.top)) - topPadding; | ||
| const width = Math.max(...rects.map((rect) => rect.right)) - originLeft; | ||
| const height = Math.max(...rects.map((rect) => rect.bottom)) - originTop; | ||
|
|
||
| const outSvg = exportDoc.createElementNS(SVG_NS, 'svg'); | ||
| outSvg.setAttribute('xmlns', SVG_NS); | ||
| outSvg.setAttribute('xmlns:xlink', XLINK_NS); | ||
| outSvg.setAttribute('width', `${width}`); | ||
| outSvg.setAttribute('height', `${height}`); | ||
| outSvg.setAttribute('viewBox', `0 0 ${width} ${height}`); | ||
| outSvg.setAttribute('data-mui-color-scheme', 'light'); | ||
|
|
There was a problem hiding this comment.
That's fairly limited. It assumes users only use our default legend with appropriate classes
Did you consider using the satori library to map https://github.com/vercel/satori ?
There was a problem hiding this comment.
Maybe satori is not well suited because it's designer to run in node runtime. Here we are in the browser so we can leverage it to measure text position and style
Text will need an adaptation: color in HTML becomes fill in SVG
Instead of limiting ourself to the legend.root could we search for
- all nodes with text content -> reproduce them with a
<text/> - all nodes with border/backgroundColor -> reproduce them with a
<rect />
There was a problem hiding this comment.
all nodes with text content -> reproduce them with a
all nodes with border/backgroundColor -> reproduce them with a
It's an interesting approach, but should we also pass the classnames through?
I think the issue with trying to be too smart about it is that we can't really cover all ends unless we render exactly what the user provides with the classes they provide 😢
We can try though 🤷
There was a problem hiding this comment.
I think the issue with trying to be too smart about it is that we can't really cover all ends unless we render exactly what the user provides with the classes they provide 😢
Yes, especially with features in HTML/CSS not present in the SVG. For example: the text wrapping 🫣

closes #17964
Preview: https://deploy-preview-22661--material-ui-x.netlify.app/x/react-charts/export/#default-toolbar
https://deploy-preview-22661--material-ui-x.netlify.app/x/react-charts/export/#export-as-svg
Main file to be reviewed: https://github.com/mui/mui-x/pull/22661/changes#diff-6b2ed30a4ec02b18484bb8960682846ccf5aa941a336fdec6880e6e986037b64