Skip to content
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions docs/data/charts/export/ExportChartAsSvg.js
Original file line number Diff line number Diff line change
@@ -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 (
<Stack sx={{ width: '100%', gap: 2 }}>
<LineChartPro
apiRef={apiRef}
xAxis={[{ data: [1, 2, 3, 5, 8, 10] }]}
series={[
{ data: [4, 9, 1, 4, 9, 6], label: 'Series A' },
{ data: [2, 5.5, 2, 8.5, 1.5, 5], area: true, label: 'Series B' },
]}
height={300}
grid={{ vertical: true, horizontal: true }}
/>
<div>
<Button
onClick={() => apiRef.current.exportAsSvg({ fileName: 'chart' })}
variant="contained"
>
Export SVG
</Button>
</div>
</Stack>
);
}
32 changes: 32 additions & 0 deletions docs/data/charts/export/ExportChartAsSvg.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<Stack sx={{ width: '100%', gap: 2 }}>
<LineChartPro
apiRef={apiRef}
xAxis={[{ data: [1, 2, 3, 5, 8, 10] }]}
series={[
{ data: [4, 9, 1, 4, 9, 6], label: 'Series A' },
{ data: [2, 5.5, 2, 8.5, 1.5, 5], area: true, label: 'Series B' },
]}
height={300}
grid={{ vertical: true, horizontal: true }}
/>
<div>
<Button
onClick={() => apiRef.current!.exportAsSvg({ fileName: 'chart' })}
variant="contained"
>
Export SVG
</Button>
</div>
</Stack>
);
}
22 changes: 20 additions & 2 deletions docs/data/charts/export/export.md
Original file line number Diff line number Diff line change
@@ -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 [<span class="plan-pro"></span>](/x/introduction/licensing/#pro-plan 'Pro plan')

<p class="description">Let users export a chart as an image or in PDF format.</p>

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`
Expand Down Expand Up @@ -177,3 +177,21 @@ 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"}}

:::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.
:::
5 changes: 5 additions & 0 deletions docs/data/chartsApiPages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
20 changes: 20 additions & 0 deletions docs/pages/x/api/charts/charts-toolbar-svg-export-trigger.js
Original file line number Diff line number Diff line change
@@ -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 <ApiPage descriptions={descriptions} pageContent={jsonPageContent} />;
}

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 } };
}
14 changes: 14 additions & 0 deletions docs/pages/x/api/charts/charts-toolbar-svg-export-trigger.json
Original file line number Diff line number Diff line change
@@ -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": "<ul><li><a href=\"/x/react-charts/export/\">Charts - Export <a href=\"/x/introduction/licensing/#pro-plan\" title=\"Pro plan\"><span class=\"plan-pro\"></span></a></a></li></ul>",
"cssComponent": false
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"componentDescription": "A button that triggers an SVG export.\nIt renders the `baseButton` slot.",
"propDescriptions": {},
"classDescriptions": {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,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,
}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ RadialBarChart.propTypes = {
current: PropTypes.shape({
exportAsImage: PropTypes.func.isRequired,
exportAsPrint: PropTypes.func.isRequired,
exportAsSvg: PropTypes.func.isRequired,
}),
}),
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ RadialLineChart.propTypes = {
current: PropTypes.shape({
exportAsImage: PropTypes.func.isRequired,
exportAsPrint: PropTypes.func.isRequired,
exportAsSvg: PropTypes.func.isRequired,
}),
}),
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}),
Expand Down
1 change: 1 addition & 0 deletions packages/x-charts-pro/src/BarChartPro/BarChartPro.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ import {
type ChartsToolbarImageExportOptions,
ChartsToolbarImageExportTrigger,
} from './ChartsToolbarImageExportTrigger';
import {
type ChartsToolbarSvgExportOptions,
ChartsToolbarSvgExportTrigger,
} from './ChartsToolbarSvgExportTrigger';

export type {
RangeButtonFunctionParams,
Expand All @@ -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.
Expand Down Expand Up @@ -87,6 +92,7 @@ const RangeButtonGroup = styled(ToggleButtonGroup, {
function ChartsToolbarPro({
printOptions,
imageExportOptions: rawImageExportOptions,
svgExportOptions,
rangeButtons,
rangeButtonsAxisId,
...other
Expand All @@ -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<React.JSX.Element> = [];

Expand Down Expand Up @@ -217,6 +227,15 @@ function ChartsToolbarPro({
{localeText.toolbarExportImage(imageExportOptions.type)}
</ChartsToolbarImageExportTrigger>
))}
{!svgExportOptions?.disableToolbarButton && (
<ChartsToolbarSvgExportTrigger
render={<MenuItem dense {...slotProps?.baseMenuItem} />}
options={svgExportOptions}
onClick={closeExportMenu}
>
{localeText.toolbarExportSvg}
</ChartsToolbarSvgExportTrigger>
)}
</MenuList>
</ChartsMenu>
</React.Fragment>,
Expand Down
Original file line number Diff line number Diff line change
@@ -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<ChartsSlotPropsPro['baseButton']>;
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<ChartsSlotsPro>();
const apiRef = useChartProApiContext();

const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
apiRef.current.exportAsSvg(options);
onClick?.(event);
};

const element = useComponentRenderer(slots.baseButton, render, {
...slotProps?.baseButton,
onClick: handleClick,
...other,
ref,
});

return <React.Fragment>{element}</React.Fragment>;
});

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 };
1 change: 1 addition & 0 deletions packages/x-charts-pro/src/ChartsToolbarPro/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
1 change: 1 addition & 0 deletions packages/x-charts-pro/src/FunnelChart/FunnelChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ FunnelChart.propTypes = {
current: PropTypes.shape({
exportAsImage: PropTypes.func.isRequired,
exportAsPrint: PropTypes.func.isRequired,
exportAsSvg: PropTypes.func.isRequired,
}),
}),
/**
Expand Down
1 change: 1 addition & 0 deletions packages/x-charts-pro/src/Heatmap/Heatmap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}),
Expand Down
1 change: 1 addition & 0 deletions packages/x-charts-pro/src/LineChartPro/LineChartPro.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}),
Expand Down
1 change: 1 addition & 0 deletions packages/x-charts-pro/src/PieChartPro/PieChartPro.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ PieChartPro.propTypes = {
current: PropTypes.shape({
exportAsImage: PropTypes.func.isRequired,
exportAsPrint: PropTypes.func.isRequired,
exportAsSvg: PropTypes.func.isRequired,
}),
}),
children: PropTypes.node,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ RadarChartPro.propTypes = {
current: PropTypes.shape({
exportAsImage: PropTypes.func.isRequired,
exportAsPrint: PropTypes.func.isRequired,
exportAsSvg: PropTypes.func.isRequired,
}),
}),
/**
Expand Down
Loading
Loading