diff --git a/docs/data/charts/export/ExportChartAsSvg.js b/docs/data/charts/export/ExportChartAsSvg.js new file mode 100644 index 0000000000000..7e18563ba0923 --- /dev/null +++ b/docs/data/charts/export/ExportChartAsSvg.js @@ -0,0 +1,32 @@ +import * as React from 'react'; +import Button from '@mui/material/Button'; +import Stack from '@mui/material/Stack'; +import { LineChartPro } from '@mui/x-charts-pro/LineChartPro'; +import { useChartProApiRef } from '@mui/x-charts-pro/hooks'; + +export default function ExportChartAsSvg() { + const apiRef = useChartProApiRef(); + + return ( + + +
+ +
+
+ ); +} diff --git a/docs/data/charts/export/ExportChartAsSvg.tsx b/docs/data/charts/export/ExportChartAsSvg.tsx new file mode 100644 index 0000000000000..54e6532153da2 --- /dev/null +++ b/docs/data/charts/export/ExportChartAsSvg.tsx @@ -0,0 +1,32 @@ +import * as React from 'react'; +import Button from '@mui/material/Button'; +import Stack from '@mui/material/Stack'; +import { LineChartPro } from '@mui/x-charts-pro/LineChartPro'; +import { useChartProApiRef } from '@mui/x-charts-pro/hooks'; + +export default function ExportChartAsSvg() { + const apiRef = useChartProApiRef<'line'>(); + + return ( + + +
+ +
+
+ ); +} diff --git a/docs/data/charts/export/export.md b/docs/data/charts/export/export.md index 9743de05bb518..5c18af351cb13 100644 --- a/docs/data/charts/export/export.md +++ b/docs/data/charts/export/export.md @@ -1,14 +1,14 @@ --- title: Charts - Export productId: x-charts -components: ScatterChartPro, BarChartPro, LineChartPro, Heatmap, FunnelChart, RadarChartPro, SankeyChart +components: ScatterChartPro, BarChartPro, LineChartPro, Heatmap, FunnelChart, RadarChartPro, SankeyChart, ChartsToolbarSvgExportTrigger --- # Charts - Export [](/x/introduction/licensing/#pro-plan 'Pro plan')

Let users export a chart as an image or in PDF format.

-Charts can be exported as images, or as PDFs using the browser's native print dialog. +Charts can be exported as images, as SVG files, or as PDFs using the browser's native print dialog. The exporting feature is available for the following charts: - `LineChartPro` @@ -61,23 +61,25 @@ yarn add rasterizehtml ## Export options -Export behavior can be modified with [print](/x/api/charts/chart-print-export-options/) and [image export](/x/api/charts/chart-image-export-options/) options. +Export behavior can be modified with [print](/x/api/charts/chart-print-export-options/), [image export](/x/api/charts/chart-image-export-options/), and [SVG export](/x/api/charts/chart-svg-export-options/) options. These options can be passed to the built-in toolbar using `slotProps.toolbar`, and are then automatically displayed. You can customize their respective behaviors by passing an options object to `slotProps.toolbar`, or to the export trigger itself if you're using a custom toolbar: ```tsx // Default toolbar: - + // Custom trigger: + ``` ### Export formats -To disable the print export, set the `disableToolbarButton` property to `true`. +To disable the print export, set the `disableToolbarButton` property to `true` on `printOptions`. +The SVG export can be disabled the same way through `svgExportOptions`. You can customize image export formats by providing an array of objects to the `imageExportOptions` property. These objects must contain the `type` property which specifies the image format. @@ -95,8 +97,8 @@ The name of the exported file has been customized to resemble the chart's title. To add custom styles or modify the chart's appearance before exporting, use the `onBeforeExport` callback. -When exporting, the chart is first rendered into an iframe and then exported as an image or PDF. -The `onBeforeExport` callback gives you access to the iframe before the export process starts. +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 `` element to be exported instead. For example, you can add the title and caption to the exported chart as shown below: @@ -177,3 +179,25 @@ apiRef.current?.exportAsImage({ pixelRatio: 3 }); ``` {{"demo": "ExportChartAsImage.js"}} + +### Export as SVG + +The `apiRef` prop also exposes the `exportAsSvg()` method to export the chart as a standalone SVG file. +Unlike image export, this requires no extra dependency. + +The output is vector-based, so it stays crisp at any size and remains editable in design tools such as Figma. +Series rendered to a canvas (for example, the WebGL renderer) cannot be vectorized and are embedded as a raster image inside the SVG, while the rest of the chart (axes, labels, legend) stays vector. + +```tsx +apiRef.current?.exportAsSvg({ fileName: 'my-chart' }); +``` + +{{"demo": "ExportChartAsSvg.js"}} + +:::info +The chart is always exported in light mode, regardless of the color scheme of the page it is rendered in. +::: + +:::warning +Styles served from a cross-origin stylesheet cannot be read for security reasons and are omitted from the exported SVG, the same way they are for image export. +::: diff --git a/docs/data/charts/localization/data.json b/docs/data/charts/localization/data.json index 3d8cdeeb6be07..27709d92b8246 100644 --- a/docs/data/charts/localization/data.json +++ b/docs/data/charts/localization/data.json @@ -3,48 +3,48 @@ "languageTag": "fr-FR", "importName": "frFR", "localeName": "French", - "missingKeysCount": 18, - "totalKeysCount": 120, + "missingKeysCount": 19, + "totalKeysCount": 121, "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-charts/src/locales/frFR.ts" }, { "languageTag": "el-GR", "importName": "elGR", "localeName": "Greek", - "missingKeysCount": 17, - "totalKeysCount": 120, + "missingKeysCount": 18, + "totalKeysCount": 121, "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-charts/src/locales/elGR.ts" }, { "languageTag": "nb-NO", "importName": "nbNO", "localeName": "Norwegian (Bokmål)", - "missingKeysCount": 17, - "totalKeysCount": 120, + "missingKeysCount": 18, + "totalKeysCount": 121, "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-charts/src/locales/nbNO.ts" }, { "languageTag": "pt-PT", "importName": "ptPT", "localeName": "Portuguese", - "missingKeysCount": 13, - "totalKeysCount": 120, + "missingKeysCount": 14, + "totalKeysCount": 121, "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-charts/src/locales/ptPT.ts" }, { "languageTag": "pt-BR", "importName": "ptBR", "localeName": "Portuguese (Brazil)", - "missingKeysCount": 17, - "totalKeysCount": 120, + "missingKeysCount": 18, + "totalKeysCount": 121, "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-charts/src/locales/ptBR.ts" }, { "languageTag": "sv-SE", "importName": "svSE", "localeName": "Swedish", - "missingKeysCount": 113, - "totalKeysCount": 120, + "missingKeysCount": 114, + "totalKeysCount": 121, "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-charts/src/locales/svSE.ts" } ] diff --git a/docs/data/chartsApiPages.ts b/docs/data/chartsApiPages.ts index b48b2e5176bd9..4ff6824cf840a 100644 --- a/docs/data/chartsApiPages.ts +++ b/docs/data/chartsApiPages.ts @@ -188,6 +188,11 @@ const chartsApiPages: MuiPage[] = [ title: 'ChartsToolbarRangeButtonTrigger', plan: 'pro', }, + { + pathname: '/x/api/charts/charts-toolbar-svg-export-trigger', + title: 'ChartsToolbarSvgExportTrigger', + plan: 'pro', + }, { pathname: '/x/api/charts/charts-toolbar-zoom-in-trigger', title: 'ChartsToolbarZoomInTrigger', diff --git a/docs/pages/x/api/charts/chart-svg-export-options.js b/docs/pages/x/api/charts/chart-svg-export-options.js new file mode 100644 index 0000000000000..d6d3d755a5b6c --- /dev/null +++ b/docs/pages/x/api/charts/chart-svg-export-options.js @@ -0,0 +1,22 @@ +import * as React from 'react'; +import InterfaceApiPage from 'docs/src/modules/components/InterfaceApiPage'; +import layoutConfig from 'docs/src/modules/utils/dataGridLayoutConfig'; +import { mapApiPageTranslations } from '@mui/internal-core-docs/mapApiPageTranslations'; +import jsonPageContent from './chart-svg-export-options.json'; + +export default function Page(props) { + const { descriptions } = props; + return ( + + ); +} + +export async function getStaticProps() { + const req = require.context( + 'docs/translations/api-docs/charts/', + false, + /\.\/chart-svg-export-options.*.json$/, + ); + const descriptions = mapApiPageTranslations(req); + return { props: { descriptions } }; +} diff --git a/docs/pages/x/api/charts/chart-svg-export-options.json b/docs/pages/x/api/charts/chart-svg-export-options.json new file mode 100644 index 0000000000000..55622731b16aa --- /dev/null +++ b/docs/pages/x/api/charts/chart-svg-export-options.json @@ -0,0 +1,18 @@ +{ + "name": "ChartSvgExportOptions", + "imports": ["import { ChartSvgExportOptions } from '@mui/x-charts-pro'"], + "demos": "", + "properties": { + "copyStyles": { "type": { "description": "boolean" }, "default": "true", "isProPlan": true }, + "fileName": { + "type": { "description": "string" }, + "default": "The title of the document the chart belongs to", + "isProPlan": true + }, + "nonce": { "type": { "description": "string" }, "isProPlan": true }, + "onBeforeExport": { + "type": { "description": "(svg: SVGElement) => Promise<void> | void" }, + "isProPlan": true + } + } +} diff --git a/docs/pages/x/api/charts/charts-toolbar-svg-export-trigger.js b/docs/pages/x/api/charts/charts-toolbar-svg-export-trigger.js new file mode 100644 index 0000000000000..2d76b452a7001 --- /dev/null +++ b/docs/pages/x/api/charts/charts-toolbar-svg-export-trigger.js @@ -0,0 +1,20 @@ +import * as React from 'react'; +import { ApiPage } from '@mui/internal-core-docs/ApiPage'; +import { mapApiPageTranslations } from '@mui/internal-core-docs/mapApiPageTranslations'; +import jsonPageContent from './charts-toolbar-svg-export-trigger.json'; + +export default function Page(props) { + const { descriptions } = props; + return ; +} + +export async function getStaticProps() { + const req = require.context( + 'docs/translations/api-docs/charts/charts-toolbar-svg-export-trigger', + false, + /\.\/charts-toolbar-svg-export-trigger.*\.json$/, + ); + const descriptions = mapApiPageTranslations(req); + + return { props: { descriptions } }; +} diff --git a/docs/pages/x/api/charts/charts-toolbar-svg-export-trigger.json b/docs/pages/x/api/charts/charts-toolbar-svg-export-trigger.json new file mode 100644 index 0000000000000..9a5cf750a6975 --- /dev/null +++ b/docs/pages/x/api/charts/charts-toolbar-svg-export-trigger.json @@ -0,0 +1,14 @@ +{ + "props": {}, + "name": "ChartsToolbarSvgExportTrigger", + "imports": [ + "import { ChartsToolbarSvgExportTrigger } from '@mui/x-charts-pro/ChartsToolbarPro';", + "import { ChartsToolbarSvgExportTrigger } from '@mui/x-charts-pro';" + ], + "classes": [], + "muiName": "MuiChartsToolbarSvgExportTrigger", + "filename": "/packages/x-charts-pro/src/ChartsToolbarPro/ChartsToolbarSvgExportTrigger.tsx", + "inheritance": null, + "demos": "", + "cssComponent": false +} diff --git a/docs/scripts/createXTypeScriptProjects.ts b/docs/scripts/createXTypeScriptProjects.ts index 31d97ce2e9927..d8483f4018dd3 100644 --- a/docs/scripts/createXTypeScriptProjects.ts +++ b/docs/scripts/createXTypeScriptProjects.ts @@ -174,6 +174,7 @@ export const interfacesToDocument: InterfacesToDocumentType[] = [ 'AxisConfig', 'ChartImageExportOptions', 'ChartPrintExportOptions', + 'ChartSvgExportOptions', 'LegendItemParams', ], }, diff --git a/docs/translations/api-docs/charts/chart-svg-export-options.json b/docs/translations/api-docs/charts/chart-svg-export-options.json new file mode 100644 index 0000000000000..b5a5cd85ee625 --- /dev/null +++ b/docs/translations/api-docs/charts/chart-svg-export-options.json @@ -0,0 +1,15 @@ +{ + "interfaceDescription": "The options to apply on the SVG export.", + "propertiesDescriptions": { + "copyStyles": { + "description": "If true, the styles of the page the chart belongs to will be copied to the export iframe.
Copying styles is useful to ensure that the exported chart looks the same as it does on the page." + }, + "fileName": { "description": "The name of the file without the extension." }, + "nonce": { + "description": "A nonce to be used for Content Security Policy (CSP) compliance.
If provided, this nonce will be added to any style elements created during the export process." + }, + "onBeforeExport": { + "description": "Callback function that is called before the export is triggered.
It receives the SVG element to be exported, so it can be modified before serialization
(such as adding elements, updating styles, removing elements, etc.)." + } + } +} diff --git a/docs/translations/api-docs/charts/charts-toolbar-svg-export-trigger/charts-toolbar-svg-export-trigger.json b/docs/translations/api-docs/charts/charts-toolbar-svg-export-trigger/charts-toolbar-svg-export-trigger.json new file mode 100644 index 0000000000000..c8a81ed3c03f6 --- /dev/null +++ b/docs/translations/api-docs/charts/charts-toolbar-svg-export-trigger/charts-toolbar-svg-export-trigger.json @@ -0,0 +1,5 @@ +{ + "componentDescription": "A button that triggers an SVG export.\nIt renders the `baseButton` slot.", + "propDescriptions": {}, + "classDescriptions": {} +} diff --git a/packages/x-charts-premium/src/BarChartPremium/BarChartPremium.tsx b/packages/x-charts-premium/src/BarChartPremium/BarChartPremium.tsx index fa451c09046de..b2e5c80194cf9 100644 --- a/packages/x-charts-premium/src/BarChartPremium/BarChartPremium.tsx +++ b/packages/x-charts-premium/src/BarChartPremium/BarChartPremium.tsx @@ -180,6 +180,7 @@ BarChartPremium.propTypes = { current: PropTypes.shape({ exportAsImage: PropTypes.func.isRequired, exportAsPrint: PropTypes.func.isRequired, + exportAsSvg: PropTypes.func.isRequired, setAxisZoomData: PropTypes.func.isRequired, setZoomData: PropTypes.func.isRequired, }), diff --git a/packages/x-charts-premium/src/CandlestickChart/CandlestickChart.tsx b/packages/x-charts-premium/src/CandlestickChart/CandlestickChart.tsx index 688960ddc4a80..a12a23ee3ba18 100644 --- a/packages/x-charts-premium/src/CandlestickChart/CandlestickChart.tsx +++ b/packages/x-charts-premium/src/CandlestickChart/CandlestickChart.tsx @@ -191,6 +191,7 @@ CandlestickChart.propTypes = { current: PropTypes.shape({ exportAsImage: PropTypes.func.isRequired, exportAsPrint: PropTypes.func.isRequired, + exportAsSvg: PropTypes.func.isRequired, setAxisZoomData: PropTypes.func.isRequired, setZoomData: PropTypes.func.isRequired, }), diff --git a/packages/x-charts-premium/src/HeatmapPremium/HeatmapPremium.tsx b/packages/x-charts-premium/src/HeatmapPremium/HeatmapPremium.tsx index a9ea8840aa4ac..73fd6dcb8d6e8 100644 --- a/packages/x-charts-premium/src/HeatmapPremium/HeatmapPremium.tsx +++ b/packages/x-charts-premium/src/HeatmapPremium/HeatmapPremium.tsx @@ -100,6 +100,7 @@ HeatmapPremium.propTypes = { current: PropTypes.shape({ exportAsImage: PropTypes.func.isRequired, exportAsPrint: PropTypes.func.isRequired, + exportAsSvg: PropTypes.func.isRequired, setAxisZoomData: PropTypes.func.isRequired, setZoomData: PropTypes.func.isRequired, }), diff --git a/packages/x-charts-premium/src/RadialBarChart/RadialBarChart.tsx b/packages/x-charts-premium/src/RadialBarChart/RadialBarChart.tsx index 46094bb431ede..3884558c22d18 100644 --- a/packages/x-charts-premium/src/RadialBarChart/RadialBarChart.tsx +++ b/packages/x-charts-premium/src/RadialBarChart/RadialBarChart.tsx @@ -181,6 +181,7 @@ RadialBarChart.propTypes = { current: PropTypes.shape({ exportAsImage: PropTypes.func.isRequired, exportAsPrint: PropTypes.func.isRequired, + exportAsSvg: PropTypes.func.isRequired, }), }), /** diff --git a/packages/x-charts-premium/src/RadialLineChart/RadialLineChart.tsx b/packages/x-charts-premium/src/RadialLineChart/RadialLineChart.tsx index addbdb07532e6..8fe1938fb6deb 100644 --- a/packages/x-charts-premium/src/RadialLineChart/RadialLineChart.tsx +++ b/packages/x-charts-premium/src/RadialLineChart/RadialLineChart.tsx @@ -194,6 +194,7 @@ RadialLineChart.propTypes = { current: PropTypes.shape({ exportAsImage: PropTypes.func.isRequired, exportAsPrint: PropTypes.func.isRequired, + exportAsSvg: PropTypes.func.isRequired, }), }), /** diff --git a/packages/x-charts-premium/src/ScatterChartPremium/ScatterChartPremium.tsx b/packages/x-charts-premium/src/ScatterChartPremium/ScatterChartPremium.tsx index ed17bea4d8c7a..bd5fc35c81488 100644 --- a/packages/x-charts-premium/src/ScatterChartPremium/ScatterChartPremium.tsx +++ b/packages/x-charts-premium/src/ScatterChartPremium/ScatterChartPremium.tsx @@ -174,6 +174,7 @@ ScatterChartPremium.propTypes = { current: PropTypes.shape({ exportAsImage: PropTypes.func.isRequired, exportAsPrint: PropTypes.func.isRequired, + exportAsSvg: PropTypes.func.isRequired, setAxisZoomData: PropTypes.func.isRequired, setZoomData: PropTypes.func.isRequired, }), diff --git a/packages/x-charts-pro/src/BarChartPro/BarChartPro.tsx b/packages/x-charts-pro/src/BarChartPro/BarChartPro.tsx index 1e478247c9999..4b8e9794bef06 100644 --- a/packages/x-charts-pro/src/BarChartPro/BarChartPro.tsx +++ b/packages/x-charts-pro/src/BarChartPro/BarChartPro.tsx @@ -137,6 +137,7 @@ BarChartPro.propTypes = { current: PropTypes.shape({ exportAsImage: PropTypes.func.isRequired, exportAsPrint: PropTypes.func.isRequired, + exportAsSvg: PropTypes.func.isRequired, setAxisZoomData: PropTypes.func.isRequired, setZoomData: PropTypes.func.isRequired, }), diff --git a/packages/x-charts-pro/src/ChartsToolbarPro/ChartsToolbarPro.tsx b/packages/x-charts-pro/src/ChartsToolbarPro/ChartsToolbarPro.tsx index 69243d3f0791f..0ac69a84b9d7e 100644 --- a/packages/x-charts-pro/src/ChartsToolbarPro/ChartsToolbarPro.tsx +++ b/packages/x-charts-pro/src/ChartsToolbarPro/ChartsToolbarPro.tsx @@ -29,6 +29,10 @@ import { type ChartsToolbarImageExportOptions, ChartsToolbarImageExportTrigger, } from './ChartsToolbarImageExportTrigger'; +import { + type ChartsToolbarSvgExportOptions, + ChartsToolbarSvgExportTrigger, +} from './ChartsToolbarSvgExportTrigger'; export type { RangeButtonFunctionParams, @@ -55,6 +59,7 @@ export interface RangeButtonConfig { export interface ChartsToolbarProProps extends ChartsToolbarProps { printOptions?: ChartsToolbarPrintExportOptions; imageExportOptions?: ChartsToolbarImageExportOptions[]; + svgExportOptions?: ChartsToolbarSvgExportOptions; /** * Configuration for range buttons shown in the toolbar. * Each button zooms the chart to a predefined time range from the end of the data. @@ -87,6 +92,7 @@ const RangeButtonGroup = styled(ToggleButtonGroup, { function ChartsToolbarPro({ printOptions, imageExportOptions: rawImageExportOptions, + svgExportOptions, rangeButtons, rangeButtonsAxisId, ...other @@ -100,7 +106,11 @@ function ChartsToolbarPro({ const exportMenuTriggerId = useId(); const isZoomEnabled = store.use(selectorChartZoomIsEnabled); const imageExportOptionList = rawImageExportOptions ?? DEFAULT_IMAGE_EXPORT_OPTIONS; - const showExportMenu = !printOptions?.disableToolbarButton || imageExportOptionList.length > 0; + + const showExportMenu = + !printOptions?.disableToolbarButton || + imageExportOptionList.length > 0 || + !svgExportOptions?.disableToolbarButton; const children: Array = []; @@ -217,6 +227,15 @@ function ChartsToolbarPro({ {localeText.toolbarExportImage(imageExportOptions.type)}
))} + {!svgExportOptions?.disableToolbarButton && ( + } + options={svgExportOptions} + onClick={closeExportMenu} + > + {localeText.toolbarExportSvg} + + )} , diff --git a/packages/x-charts-pro/src/ChartsToolbarPro/ChartsToolbarSvgExportTrigger.tsx b/packages/x-charts-pro/src/ChartsToolbarPro/ChartsToolbarSvgExportTrigger.tsx new file mode 100644 index 0000000000000..a909a7df04d6c --- /dev/null +++ b/packages/x-charts-pro/src/ChartsToolbarPro/ChartsToolbarSvgExportTrigger.tsx @@ -0,0 +1,75 @@ +import * as React from 'react'; +import PropTypes from 'prop-types'; +import { forwardRef } from '@mui/x-internals/forwardRef'; +import { type RenderProp, useComponentRenderer } from '@mui/x-internals/useComponentRenderer'; +import { useChartsSlots } from '@mui/x-charts/internals'; +import { useChartProApiContext } from '../context'; +import { type ChartSvgExportOptions } from '../internals/plugins/useChartProExport'; +import { type ChartsSlotPropsPro, type ChartsSlotsPro } from '../internals/material'; + +export interface ChartsToolbarSvgExportOptions extends ChartSvgExportOptions { + /** + * If `true`, this export option will be removed from the ChartsToolbarExport menu. + * @default false + */ + disableToolbarButton?: boolean; +} + +export type ChartsToolbarSvgExportTriggerProps = ChartsSlotPropsPro['baseButton'] & { + render?: RenderProp; + options?: ChartsToolbarSvgExportOptions; +}; + +/** + * A button that triggers an SVG export. + * It renders the `baseButton` slot. + * + * Demos: + * + * - [Export](https://mui.com/x/react-charts/export/) + */ +const ChartsToolbarSvgExportTrigger = forwardRef< + HTMLButtonElement, + ChartsToolbarSvgExportTriggerProps +>(function ChartsToolbarSvgExportTrigger(props, ref) { + const { render, options, onClick, ...other } = props; + const { slots, slotProps } = useChartsSlots(); + const apiRef = useChartProApiContext(); + + const handleClick = (event: React.MouseEvent) => { + apiRef.current.exportAsSvg(options); + onClick?.(event); + }; + + const element = useComponentRenderer(slots.baseButton, render, { + ...slotProps?.baseButton, + onClick: handleClick, + ...other, + ref, + }); + + return {element}; +}); + +ChartsToolbarSvgExportTrigger.propTypes = { + // ----------------------------- Warning -------------------------------- + // | These PropTypes are generated from the TypeScript type definitions | + // | To update them edit the TypeScript types and run "pnpm proptypes" | + // ---------------------------------------------------------------------- + className: PropTypes.string, + disabled: PropTypes.bool, + id: PropTypes.string, + options: PropTypes.shape({ + copyStyles: PropTypes.bool, + disableToolbarButton: PropTypes.bool, + fileName: PropTypes.string, + nonce: PropTypes.string, + onBeforeExport: PropTypes.func, + }), + render: PropTypes.oneOfType([PropTypes.element, PropTypes.func]), + size: PropTypes.oneOf(['large', 'medium', 'small']), + style: PropTypes.object, + tabIndex: PropTypes.number, +} as any; + +export { ChartsToolbarSvgExportTrigger }; diff --git a/packages/x-charts-pro/src/ChartsToolbarPro/index.ts b/packages/x-charts-pro/src/ChartsToolbarPro/index.ts index 11e0a4a8d7f4a..cdba4713a3cd2 100644 --- a/packages/x-charts-pro/src/ChartsToolbarPro/index.ts +++ b/packages/x-charts-pro/src/ChartsToolbarPro/index.ts @@ -4,4 +4,5 @@ export * from './ChartsToolbarZoomOutTrigger'; export * from './ChartsToolbarRangeButtonTrigger'; export * from './ChartsToolbarPrintExportTrigger'; export * from './ChartsToolbarImageExportTrigger'; +export * from './ChartsToolbarSvgExportTrigger'; export type { ChartsToolbarProSlots, ChartsToolbarProSlotProps } from './Toolbar.types'; diff --git a/packages/x-charts-pro/src/FunnelChart/FunnelChart.tsx b/packages/x-charts-pro/src/FunnelChart/FunnelChart.tsx index cd05f95517236..cdd95a660dae7 100644 --- a/packages/x-charts-pro/src/FunnelChart/FunnelChart.tsx +++ b/packages/x-charts-pro/src/FunnelChart/FunnelChart.tsx @@ -127,6 +127,7 @@ FunnelChart.propTypes = { current: PropTypes.shape({ exportAsImage: PropTypes.func.isRequired, exportAsPrint: PropTypes.func.isRequired, + exportAsSvg: PropTypes.func.isRequired, }), }), /** diff --git a/packages/x-charts-pro/src/Heatmap/Heatmap.tsx b/packages/x-charts-pro/src/Heatmap/Heatmap.tsx index 1d88b66a2474f..4a818a8d21557 100644 --- a/packages/x-charts-pro/src/Heatmap/Heatmap.tsx +++ b/packages/x-charts-pro/src/Heatmap/Heatmap.tsx @@ -195,6 +195,7 @@ Heatmap.propTypes = { current: PropTypes.shape({ exportAsImage: PropTypes.func.isRequired, exportAsPrint: PropTypes.func.isRequired, + exportAsSvg: PropTypes.func.isRequired, setAxisZoomData: PropTypes.func.isRequired, setZoomData: PropTypes.func.isRequired, }), diff --git a/packages/x-charts-pro/src/LineChartPro/LineChartPro.tsx b/packages/x-charts-pro/src/LineChartPro/LineChartPro.tsx index 911a6d78ea5ae..43e23eaa21fd6 100644 --- a/packages/x-charts-pro/src/LineChartPro/LineChartPro.tsx +++ b/packages/x-charts-pro/src/LineChartPro/LineChartPro.tsx @@ -147,6 +147,7 @@ LineChartPro.propTypes = { current: PropTypes.shape({ exportAsImage: PropTypes.func.isRequired, exportAsPrint: PropTypes.func.isRequired, + exportAsSvg: PropTypes.func.isRequired, setAxisZoomData: PropTypes.func.isRequired, setZoomData: PropTypes.func.isRequired, }), diff --git a/packages/x-charts-pro/src/PieChartPro/PieChartPro.tsx b/packages/x-charts-pro/src/PieChartPro/PieChartPro.tsx index 77eaf4b567b2e..7287036dbd7e1 100644 --- a/packages/x-charts-pro/src/PieChartPro/PieChartPro.tsx +++ b/packages/x-charts-pro/src/PieChartPro/PieChartPro.tsx @@ -134,6 +134,7 @@ PieChartPro.propTypes = { current: PropTypes.shape({ exportAsImage: PropTypes.func.isRequired, exportAsPrint: PropTypes.func.isRequired, + exportAsSvg: PropTypes.func.isRequired, }), }), children: PropTypes.node, diff --git a/packages/x-charts-pro/src/RadarChartPro/RadarChartPro.tsx b/packages/x-charts-pro/src/RadarChartPro/RadarChartPro.tsx index 3211912fbaee5..99458d1bbb264 100644 --- a/packages/x-charts-pro/src/RadarChartPro/RadarChartPro.tsx +++ b/packages/x-charts-pro/src/RadarChartPro/RadarChartPro.tsx @@ -119,6 +119,7 @@ RadarChartPro.propTypes = { current: PropTypes.shape({ exportAsImage: PropTypes.func.isRequired, exportAsPrint: PropTypes.func.isRequired, + exportAsSvg: PropTypes.func.isRequired, }), }), /** diff --git a/packages/x-charts-pro/src/SankeyChart/SankeyChart.tsx b/packages/x-charts-pro/src/SankeyChart/SankeyChart.tsx index 347a429e91028..8f69d564cff07 100644 --- a/packages/x-charts-pro/src/SankeyChart/SankeyChart.tsx +++ b/packages/x-charts-pro/src/SankeyChart/SankeyChart.tsx @@ -90,6 +90,7 @@ SankeyChart.propTypes = { current: PropTypes.shape({ exportAsImage: PropTypes.func.isRequired, exportAsPrint: PropTypes.func.isRequired, + exportAsSvg: PropTypes.func.isRequired, }), }), /** diff --git a/packages/x-charts-pro/src/ScatterChartPro/ScatterChartPro.tsx b/packages/x-charts-pro/src/ScatterChartPro/ScatterChartPro.tsx index aaac4e6962f73..9769ec9457d7c 100644 --- a/packages/x-charts-pro/src/ScatterChartPro/ScatterChartPro.tsx +++ b/packages/x-charts-pro/src/ScatterChartPro/ScatterChartPro.tsx @@ -149,6 +149,7 @@ ScatterChartPro.propTypes = { current: PropTypes.shape({ exportAsImage: PropTypes.func.isRequired, exportAsPrint: PropTypes.func.isRequired, + exportAsSvg: PropTypes.func.isRequired, setAxisZoomData: PropTypes.func.isRequired, setZoomData: PropTypes.func.isRequired, }), diff --git a/packages/x-charts-pro/src/index.ts b/packages/x-charts-pro/src/index.ts index bfcc0a277113e..9df4eaf9ee618 100644 --- a/packages/x-charts-pro/src/index.ts +++ b/packages/x-charts-pro/src/index.ts @@ -66,4 +66,5 @@ export * from './ChartsToolbarPro'; export type { ChartImageExportOptions, ChartPrintExportOptions, + ChartSvgExportOptions, } from './internals/plugins/useChartProExport'; diff --git a/packages/x-charts-pro/src/internals/plugins/useChartProExport/common.ts b/packages/x-charts-pro/src/internals/plugins/useChartProExport/common.ts index 8f74ae1ca2ff2..a96d48e478b5b 100644 --- a/packages/x-charts-pro/src/internals/plugins/useChartProExport/common.ts +++ b/packages/x-charts-pro/src/internals/plugins/useChartProExport/common.ts @@ -1,3 +1,13 @@ +/** + * Triggers a browser download for the given object URL. + */ +export function triggerDownload(url: string, name: string) { + const a = document.createElement('a'); + a.href = url; + a.download = name; + a.click(); +} + export function createExportIframe(title?: string): HTMLIFrameElement { const iframeEl = document.createElement('iframe'); iframeEl.style.position = 'absolute'; diff --git a/packages/x-charts-pro/src/internals/plugins/useChartProExport/exportImage.ts b/packages/x-charts-pro/src/internals/plugins/useChartProExport/exportImage.ts index ca4083d73de21..4af5960c0dd35 100644 --- a/packages/x-charts-pro/src/internals/plugins/useChartProExport/exportImage.ts +++ b/packages/x-charts-pro/src/internals/plugins/useChartProExport/exportImage.ts @@ -1,7 +1,7 @@ import ownerDocument from '@mui/utils/ownerDocument'; import { loadStyleSheets } from '@mui/x-internals/export'; import { warnOnce } from '@mui/x-internals/warning'; -import { applyStyles, copyCanvasesContent, createExportIframe } from './common'; +import { applyStyles, copyCanvasesContent, createExportIframe, triggerDownload } from './common'; import { type ChartImageExportOptions } from './useChartProExport.types'; import { defaultOnBeforeExport } from './defaults'; @@ -142,10 +142,3 @@ export async function exportImage( URL.revokeObjectURL(url); } - -function triggerDownload(url: string, name: string) { - const a = document.createElement('a'); - a.href = url; - a.download = name; - a.click(); -} diff --git a/packages/x-charts-pro/src/internals/plugins/useChartProExport/exportSvg.ts b/packages/x-charts-pro/src/internals/plugins/useChartProExport/exportSvg.ts new file mode 100644 index 0000000000000..8884c5565791d --- /dev/null +++ b/packages/x-charts-pro/src/internals/plugins/useChartProExport/exportSvg.ts @@ -0,0 +1,237 @@ +import ownerDocument from '@mui/utils/ownerDocument'; +import { loadStyleSheets } from '@mui/x-internals/export'; +import { legendClasses } from '@mui/x-charts/ChartsLegend'; +import { createExportIframe, triggerDownload } from './common'; +import { type ChartSvgExportOptions } from './useChartProExport.types'; + +const SVG_NS = 'http://www.w3.org/2000/svg'; +const XLINK_NS = 'http://www.w3.org/1999/xlink'; +// Temporary marker used to locate the chart container inside the cloned tree. +const CONTAINER_MARKER = 'data-mui-svg-export-container'; +const LEGEND_TOP_PADDING = 16; + +type ExportLayer = + | { kind: 'svg'; element: SVGSVGElement } + | { kind: 'canvas'; element: HTMLCanvasElement }; + +function collectLayers(chartContainer: Element): ExportLayer[] { + const layers: ExportLayer[] = []; + for (const child of chartContainer.children) { + // Tag checks, not `instanceof`: these elements live in the iframe's document. + if (child.matches('svg')) { + layers.push({ kind: 'svg', element: child as SVGSVGElement }); + } else { + // The canvas (e.g. WebGL series) can be nested inside a positioning wrapper. + const canvas = child.matches('canvas') + ? (child as HTMLCanvasElement) + : child.querySelector('canvas'); + if (canvas) { + layers.push({ kind: 'canvas', element: canvas }); + } + } + } + return layers; +} + +function collectCssText(root: Document | ShadowRoot): string { + let css = ''; + for (const sheet of root.styleSheets) { + try { + for (const rule of sheet.cssRules) { + css += `${rule.cssText}\n`; + } + } catch { + // Cross-origin stylesheets throw on `cssRules` access. Skip them. + } + } + return css; +} + +function buildLegendGroup( + legendEl: Element, + doc: Document, + originLeft: number, + originTop: number, +): SVGElement | null { + const seriesItems = legendEl.querySelectorAll(`.${legendClasses.series}`); + if (seriesItems.length === 0) { + return null; + } + + const view = legendEl.ownerDocument.defaultView ?? window; + const group = doc.createElementNS(SVG_NS, 'g'); + + seriesItems.forEach((seriesEl) => { + const markEl = seriesEl.children[0] as HTMLElement | undefined; + const labelEl = seriesEl.children[1] as HTMLElement | undefined; + const seriesRect = seriesEl.getBoundingClientRect(); + + // Clone the mark and give it an explicit position/size, since outside its wrapper it no + // longer inherits dimensions from CSS. + const markSvg = markEl?.children[0]?.cloneNode(true) as SVGElement | undefined; + if (markSvg && markEl) { + const markRect = markEl.getBoundingClientRect(); + markSvg.setAttribute('x', `${markRect.left - originLeft}`); + markSvg.setAttribute('y', `${markRect.top - originTop}`); + markSvg.setAttribute('width', `${markRect.width}`); + markSvg.setAttribute('height', `${markRect.height}`); + group.appendChild(markSvg); + } + + // Re-emit the label as ``, centered on the item so it stays aligned with the mark. + if (labelEl) { + const labelRect = labelEl.getBoundingClientRect(); + const labelStyles = view.getComputedStyle(labelEl); + const text = doc.createElementNS(SVG_NS, 'text'); + text.textContent = labelEl.textContent || ''; + text.setAttribute('x', `${labelRect.left - originLeft}`); + text.setAttribute('y', `${seriesRect.top - originTop + seriesRect.height / 2}`); + text.setAttribute('dominant-baseline', 'central'); + text.setAttribute('fill', labelStyles.color); + text.setAttribute('font-family', labelStyles.fontFamily); + text.setAttribute('font-size', labelStyles.fontSize); + text.setAttribute('font-weight', labelStyles.fontWeight); + group.appendChild(text); + } + }); + + return group; +} + +async function exportSvg( + chartRoot: Element, + chartContainer: HTMLElement | SVGSVGElement, + options?: ChartSvgExportOptions, +) { + const { fileName, nonce, copyStyles = true, onBeforeExport } = options ?? {}; + const doc = ownerDocument(chartRoot); + const styleRoot = chartContainer.getRootNode(); + const styleSource = styleRoot instanceof ShadowRoot ? styleRoot : doc; + + // Cloned canvases are blank, so capture the live bitmaps (in document order) before cloning. + const canvasDataUrls = Array.from(chartContainer.querySelectorAll('canvas')).map((canvas) => + canvas.toDataURL(), + ); + + // Render into an isolated iframe forced to light mode so all styles resolve in light mode. + const iframe = createExportIframe(fileName); + const iframeLoaded = new Promise((resolve) => { + iframe.onload = () => resolve(); + }); + doc.body.appendChild(iframe); + await iframeLoaded; + + try { + const exportDoc = iframe.contentDocument!; + exportDoc.documentElement.setAttribute('data-mui-color-scheme', 'light'); + + // Mark the live container so we can find its clone reliably (no class-name dependency). + chartContainer.setAttribute(CONTAINER_MARKER, ''); + const chartClone = chartRoot.cloneNode(true) as Element; + chartContainer.removeAttribute(CONTAINER_MARKER); + + chartClone.querySelectorAll('[data-hide-on-export]').forEach((el) => el.remove()); + exportDoc.body.replaceChildren(chartClone); + exportDoc.body.style.margin = '0px'; + exportDoc.body.style.display = 'block'; + // The chart wrapper is `flex: 1`, so size the body to the live chart or the clone collapses. + const rootRect = chartRoot.getBoundingClientRect(); + exportDoc.body.style.width = `${rootRect.width}px`; + exportDoc.body.style.height = `${rootRect.height}px`; + + // Load the page styles into the iframe so the clone lays out and renders in light mode. + await Promise.all(loadStyleSheets(exportDoc, styleSource, nonce)); + + const containerClone = chartClone.querySelector(`[${CONTAINER_MARKER}]`); + containerClone?.removeAttribute(CONTAINER_MARKER); + const layers = containerClone ? collectLayers(containerClone) : []; + + if (!containerClone || layers.length === 0) { + throw /* minify-error-disabled */ new Error( + 'MUI X Charts: No SVG or canvas layer found in the chart container.\n' + + 'This prevents the chart from being serialized to SVG.\n' + + 'Make sure the chart is rendered before calling `exportAsSvg`.', + ); + } + + // 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'); + + if (copyStyles) { + const styleEl = exportDoc.createElementNS(SVG_NS, 'style'); + if (nonce) { + styleEl.setAttribute('nonce', nonce); + } + styleEl.textContent = collectCssText(styleSource); + outSvg.appendChild(styleEl); + } + + // Composite the plot layers in z-order (DOM order). + let canvasIndex = 0; + layers.forEach((layer) => { + const layerRect = layer.element.getBoundingClientRect(); + const x = layerRect.left - originLeft; + const y = layerRect.top - originTop; + + if (layer.kind === 'canvas') { + // Canvas series can't be vectorized; embed the captured bitmap as an ``. + const dataUrl = canvasDataUrls[canvasIndex] ?? layer.element.toDataURL(); + canvasIndex += 1; + const image = exportDoc.createElementNS(SVG_NS, 'image'); + image.setAttribute('x', `${x}`); + image.setAttribute('y', `${y}`); + image.setAttribute('width', `${layerRect.width}`); + image.setAttribute('height', `${layerRect.height}`); + // `href` for SVG 2/browsers, `xlink:href` for older renderers and design tools. + image.setAttribute('href', dataUrl); + image.setAttributeNS(XLINK_NS, 'xlink:href', dataUrl); + outSvg.appendChild(image); + } else { + // Nest the SVG layer with an explicit position/size so it doesn't collapse. + const clone = layer.element.cloneNode(true) as SVGSVGElement; + clone.setAttribute('x', `${x}`); + clone.setAttribute('y', `${y}`); + clone.setAttribute('width', `${layerRect.width}`); + clone.setAttribute('height', `${layerRect.height}`); + outSvg.appendChild(clone); + } + }); + + if (legendClone) { + const legendGroup = buildLegendGroup(legendClone, exportDoc, originLeft, originTop); + if (legendGroup) { + outSvg.appendChild(legendGroup); + } + } + + await onBeforeExport?.(outSvg); + + const svgString = new XMLSerializer().serializeToString(outSvg); + + const blob = new Blob([svgString], { type: 'image/svg+xml' }); + const url = URL.createObjectURL(blob); + triggerDownload(url, `${fileName || document.title}.svg`); + URL.revokeObjectURL(url); + } finally { + doc.body.removeChild(iframe); + } +} + +export { exportSvg }; diff --git a/packages/x-charts-pro/src/internals/plugins/useChartProExport/useChartProExport.ts b/packages/x-charts-pro/src/internals/plugins/useChartProExport/useChartProExport.ts index dd8c8f5ce9517..024230fe67c87 100644 --- a/packages/x-charts-pro/src/internals/plugins/useChartProExport/useChartProExport.ts +++ b/packages/x-charts-pro/src/internals/plugins/useChartProExport/useChartProExport.ts @@ -1,10 +1,12 @@ import { type ChartPlugin } from '@mui/x-charts/internals'; import { printChart } from './print'; import { exportImage } from './exportImage'; -import { - type ChartImageExportOptions, - type ChartPrintExportOptions, - type UseChartProExportSignature, +import { exportSvg } from './exportSvg'; +import type { + ChartSvgExportOptions, + ChartImageExportOptions, + ChartPrintExportOptions, + UseChartProExportSignature, } from './useChartProExport.types'; function waitForAnimationFrame() { @@ -59,14 +61,35 @@ export const useChartProExport: ChartPlugin = ({ ins } }; + const exportAsSvg = async (options?: ChartSvgExportOptions) => { + const chartRoot = chartRootRef.current; + const svg = chartsLayerContainerRef.current; + + if (chartRoot && svg) { + const enableAnimation = instance.disableAnimation(); + + try { + // Wait for animation frame to ensure the animation finished + await waitForAnimationFrame(); + await exportSvg(chartRoot, svg, options); + } catch (error) { + console.error('MUI X Charts: Error exporting chart as SVG:', error); + } finally { + enableAnimation(); + } + } + }; + return { publicAPI: { exportAsPrint, exportAsImage, + exportAsSvg, }, instance: { exportAsPrint, exportAsImage, + exportAsSvg, }, }; }; diff --git a/packages/x-charts-pro/src/internals/plugins/useChartProExport/useChartProExport.types.ts b/packages/x-charts-pro/src/internals/plugins/useChartProExport/useChartProExport.types.ts index 06f14c91c35b1..e10a9ed83153c 100644 --- a/packages/x-charts-pro/src/internals/plugins/useChartProExport/useChartProExport.types.ts +++ b/packages/x-charts-pro/src/internals/plugins/useChartProExport/useChartProExport.types.ts @@ -70,6 +70,22 @@ export interface ChartImageExportOptions extends ChartExportOptions { pixelRatio?: number; } +/** + * The options to apply on the SVG export. + * @demos + * - [SVG export](https://mui.com/x/react-charts/export/#export-as-svg) + */ +export interface ChartSvgExportOptions extends Omit { + /** + * Callback function that is called before the export is triggered. + * It receives the SVG element to be exported, so it can be modified before serialization + * (such as adding elements, updating styles, removing elements, etc.). + * @param {SVGElement} svg The SVG element to be exported. + * @returns {Promise | void} A promise or void. If a promise is returned, the export will wait for it to resolve before proceeding. + */ + onBeforeExport?: (svg: SVGElement) => Promise | void; +} + export interface UseChartProExportPublicApi { /** * Opens the browser's print dialog, which can be used to print the chart or export it as PDF. @@ -85,6 +101,7 @@ export interface UseChartProExportPublicApi { * @returns {void} */ exportAsImage: (options?: ChartImageExportOptions) => void; + exportAsSvg: (options?: ChartSvgExportOptions) => void; } export interface UseChartProExportInstance extends UseChartProExportPublicApi {} diff --git a/packages/x-charts/src/locales/elGR.ts b/packages/x-charts/src/locales/elGR.ts index 6df0951300b32..6b3cb3f10b22d 100644 --- a/packages/x-charts/src/locales/elGR.ts +++ b/packages/x-charts/src/locales/elGR.ts @@ -15,6 +15,7 @@ export const elGRLocaleText: Partial = { // Toolbar Export Menu toolbarExportPrint: 'Εκτύπωση', toolbarExportImage: (mimeType) => `Εξαγωγή ως ${imageMimeTypes[mimeType] ?? mimeType}`, + // toolbarExportSvg: 'Export as SVG', // Charts renderer configuration chartTypeBar: 'Μπάρα', diff --git a/packages/x-charts/src/locales/enUS.ts b/packages/x-charts/src/locales/enUS.ts index 1867799fd4f15..eb931433a693b 100644 --- a/packages/x-charts/src/locales/enUS.ts +++ b/packages/x-charts/src/locales/enUS.ts @@ -17,6 +17,7 @@ export const enUSLocaleText: ChartsLocaleText = { // Toolbar Export Menu toolbarExportPrint: 'Print', toolbarExportImage: (mimeType) => `Export as ${imageMimeTypes[mimeType] ?? mimeType}`, + toolbarExportSvg: 'Export as SVG', // Charts renderer configuration chartTypeBar: 'Bar', diff --git a/packages/x-charts/src/locales/frFR.ts b/packages/x-charts/src/locales/frFR.ts index d6113d214d535..8a0ef19e329ec 100644 --- a/packages/x-charts/src/locales/frFR.ts +++ b/packages/x-charts/src/locales/frFR.ts @@ -14,6 +14,7 @@ export const frFRLocalText: Partial = { // Toolbar Export Menu // toolbarExportPrint: 'Print', // toolbarExportImage: mimeType => `Export as ${imageMimeTypes[mimeType] ?? mimeType}`, + // toolbarExportSvg: 'Export as SVG', // Charts renderer configuration chartTypeBar: 'Barre', diff --git a/packages/x-charts/src/locales/nbNO.ts b/packages/x-charts/src/locales/nbNO.ts index 702a4b47c9d3e..c20d053361bc6 100644 --- a/packages/x-charts/src/locales/nbNO.ts +++ b/packages/x-charts/src/locales/nbNO.ts @@ -15,6 +15,7 @@ export const nbNOLocaleText: Partial = { // Toolbar Export Menu toolbarExportPrint: 'Skriv ut', toolbarExportImage: (mimeType) => `Eksporter som ${imageMimeTypes[mimeType] ?? mimeType}`, + // toolbarExportSvg: 'Export as SVG', // Charts renderer configuration chartTypeBar: 'Stolpe', diff --git a/packages/x-charts/src/locales/ptBR.ts b/packages/x-charts/src/locales/ptBR.ts index 032f29de7d908..ed65acc183f97 100644 --- a/packages/x-charts/src/locales/ptBR.ts +++ b/packages/x-charts/src/locales/ptBR.ts @@ -15,6 +15,7 @@ export const ptBRLocaleText: Partial = { // Toolbar Export Menu toolbarExportPrint: 'Imprimir', toolbarExportImage: (mimeType) => `Exportar como ${imageMimeTypes[mimeType] ?? mimeType}`, + // toolbarExportSvg: 'Export as SVG', // Charts renderer configuration chartTypeBar: 'Barra', diff --git a/packages/x-charts/src/locales/ptPT.ts b/packages/x-charts/src/locales/ptPT.ts index 6e35dd32ce971..53eec46c113c8 100644 --- a/packages/x-charts/src/locales/ptPT.ts +++ b/packages/x-charts/src/locales/ptPT.ts @@ -15,6 +15,7 @@ export const ptPTLocaleText: Partial = { // Toolbar Export Menu toolbarExportPrint: 'Imprimir', toolbarExportImage: (mimeType) => `Exportar como ${imageMimeTypes[mimeType] ?? mimeType}`, + // toolbarExportSvg: 'Export as SVG', // Charts renderer configuration chartTypeBar: 'Barras', diff --git a/packages/x-charts/src/locales/svSE.ts b/packages/x-charts/src/locales/svSE.ts index 5ea51b30e8a1c..078947fccb934 100644 --- a/packages/x-charts/src/locales/svSE.ts +++ b/packages/x-charts/src/locales/svSE.ts @@ -15,6 +15,7 @@ export const svSELocaleText: Partial = { // Toolbar Export Menu toolbarExportPrint: 'Skriv ut', toolbarExportImage: (mimeType) => `Exportera som ${imageMimeTypes[mimeType] ?? mimeType}`, + // toolbarExportSvg: 'Export as SVG', // Charts renderer configuration // chartTypeBar: 'Bar', diff --git a/packages/x-charts/src/locales/utils/chartsLocaleTextApi.ts b/packages/x-charts/src/locales/utils/chartsLocaleTextApi.ts index 33cae79b36ea3..04b370e9f5364 100644 --- a/packages/x-charts/src/locales/utils/chartsLocaleTextApi.ts +++ b/packages/x-charts/src/locales/utils/chartsLocaleTextApi.ts @@ -25,6 +25,10 @@ export interface ChartsLocaleText { * Text for the print button in the toolbar's export menu. */ toolbarExportPrint: string; + /** + * Text for the SVG export button in the toolbar's export menu. + */ + toolbarExportSvg: string; /** * Text for an "Export as {image type}" button in the toolbar's export menu. * The only format supported in all browsers is 'image/png'. diff --git a/scripts/x-charts-premium.exports.json b/scripts/x-charts-premium.exports.json index 1e349e0a165d6..d47f0ee8ac5c5 100644 --- a/scripts/x-charts-premium.exports.json +++ b/scripts/x-charts-premium.exports.json @@ -229,6 +229,9 @@ { "name": "ChartsToolbarProSlots", "kind": "Interface" }, { "name": "ChartsToolbarRangeButtonTrigger", "kind": "Variable" }, { "name": "ChartsToolbarRangeButtonTriggerProps", "kind": "Interface" }, + { "name": "ChartsToolbarSvgExportOptions", "kind": "Interface" }, + { "name": "ChartsToolbarSvgExportTrigger", "kind": "Variable" }, + { "name": "ChartsToolbarSvgExportTriggerProps", "kind": "TypeAlias" }, { "name": "ChartsToolbarZoomInTrigger", "kind": "Variable" }, { "name": "ChartsToolbarZoomOutTrigger", "kind": "Variable" }, { "name": "ChartsTooltip", "kind": "Function" }, diff --git a/scripts/x-charts-pro.exports.json b/scripts/x-charts-pro.exports.json index 8b9b755427596..3bde3efb4bf1a 100644 --- a/scripts/x-charts-pro.exports.json +++ b/scripts/x-charts-pro.exports.json @@ -211,6 +211,9 @@ { "name": "ChartsToolbarProSlots", "kind": "Interface" }, { "name": "ChartsToolbarRangeButtonTrigger", "kind": "Variable" }, { "name": "ChartsToolbarRangeButtonTriggerProps", "kind": "Interface" }, + { "name": "ChartsToolbarSvgExportOptions", "kind": "Interface" }, + { "name": "ChartsToolbarSvgExportTrigger", "kind": "Variable" }, + { "name": "ChartsToolbarSvgExportTriggerProps", "kind": "TypeAlias" }, { "name": "ChartsToolbarZoomInTrigger", "kind": "Variable" }, { "name": "ChartsToolbarZoomOutTrigger", "kind": "Variable" }, { "name": "ChartsTooltip", "kind": "Function" }, @@ -230,6 +233,7 @@ { "name": "ChartsTooltipSlots", "kind": "Interface" }, { "name": "ChartsTooltipTable", "kind": "Variable" }, { "name": "ChartsTypeFeatureFlags", "kind": "Interface" }, + { "name": "ChartSvgExportOptions", "kind": "Interface" }, { "name": "ChartsWrapper", "kind": "Variable" }, { "name": "ChartsWrapperOwnerState", "kind": "Interface" }, { "name": "ChartsWrapperProps", "kind": "Interface" },