Skip to content

Commit 03408e3

Browse files
[charts] Add basic export as PDF/print functionality (#17285)
1 parent e18c8ba commit 03408e3

38 files changed

+908
-47
lines changed
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import * as React from 'react';
2+
import { BarPlot } from '@mui/x-charts/BarChart';
3+
import { LinePlot, MarkPlot } from '@mui/x-charts/LineChart';
4+
import { ChartsXAxis } from '@mui/x-charts/ChartsXAxis';
5+
import { useChartRootRef } from '@mui/x-charts/hooks';
6+
import Button from '@mui/material/Button';
7+
8+
import { Stack } from '@mui/system';
9+
import { ChartDataProviderPro } from '@mui/x-charts-pro/ChartDataProviderPro';
10+
import { ChartsSurface } from '@mui/x-charts/ChartsSurface';
11+
import { ChartsYAxis } from '@mui/x-charts/ChartsYAxis';
12+
import { ChartsLegend } from '@mui/x-charts/ChartsLegend';
13+
14+
function CustomChartWrapper({ children }) {
15+
const chartRootRef = useChartRootRef();
16+
17+
return (
18+
<div
19+
ref={chartRootRef}
20+
style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}
21+
>
22+
{children}
23+
</div>
24+
);
25+
}
26+
27+
export default function ExportCompositionNoSnap() {
28+
const apiRef = React.useRef(undefined);
29+
30+
return (
31+
<Stack width="100%">
32+
<ChartDataProviderPro
33+
apiRef={apiRef}
34+
height={300}
35+
series={[
36+
{
37+
type: 'bar',
38+
data: [1, 2, 3, 2, 1],
39+
label: 'Bar',
40+
},
41+
{
42+
type: 'line',
43+
data: [4, 3, 1, 3, 4],
44+
label: 'Line',
45+
},
46+
]}
47+
xAxis={[
48+
{
49+
data: ['A', 'B', 'C', 'D', 'E'],
50+
scaleType: 'band',
51+
id: 'x-axis-id',
52+
height: 24,
53+
},
54+
]}
55+
yAxis={[{ width: 20 }]}
56+
>
57+
<CustomChartWrapper>
58+
<ChartsLegend direction="horizontal" />
59+
<ChartsSurface>
60+
<BarPlot />
61+
<LinePlot />
62+
<MarkPlot />
63+
<ChartsXAxis axisId="x-axis-id" />
64+
<ChartsYAxis />
65+
</ChartsSurface>
66+
</CustomChartWrapper>
67+
</ChartDataProviderPro>
68+
69+
<Button onClick={() => apiRef.current?.exportAsPrint()}>Print</Button>
70+
</Stack>
71+
);
72+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import * as React from 'react';
2+
import { BarPlot } from '@mui/x-charts/BarChart';
3+
import { LinePlot, MarkPlot } from '@mui/x-charts/LineChart';
4+
import { ChartsXAxis } from '@mui/x-charts/ChartsXAxis';
5+
import { useChartRootRef } from '@mui/x-charts/hooks';
6+
import Button from '@mui/material/Button';
7+
import { ChartProApi } from '@mui/x-charts-pro/ChartContainerPro';
8+
import { Stack } from '@mui/system';
9+
import { ChartDataProviderPro } from '@mui/x-charts-pro/ChartDataProviderPro';
10+
import { ChartsSurface } from '@mui/x-charts/ChartsSurface';
11+
import { ChartsYAxis } from '@mui/x-charts/ChartsYAxis';
12+
import { ChartsLegend } from '@mui/x-charts/ChartsLegend';
13+
14+
function CustomChartWrapper({ children }: React.PropsWithChildren) {
15+
const chartRootRef = useChartRootRef();
16+
17+
return (
18+
<div
19+
ref={chartRootRef}
20+
style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}
21+
>
22+
{children}
23+
</div>
24+
);
25+
}
26+
27+
export default function ExportCompositionNoSnap() {
28+
const apiRef = React.useRef<ChartProApi>(undefined);
29+
30+
return (
31+
<Stack width="100%">
32+
<ChartDataProviderPro
33+
apiRef={apiRef}
34+
height={300}
35+
series={[
36+
{
37+
type: 'bar',
38+
data: [1, 2, 3, 2, 1],
39+
label: 'Bar',
40+
},
41+
{
42+
type: 'line',
43+
data: [4, 3, 1, 3, 4],
44+
label: 'Line',
45+
},
46+
]}
47+
xAxis={[
48+
{
49+
data: ['A', 'B', 'C', 'D', 'E'],
50+
scaleType: 'band',
51+
id: 'x-axis-id',
52+
height: 24,
53+
},
54+
]}
55+
yAxis={[{ width: 20 }]}
56+
>
57+
<CustomChartWrapper>
58+
<ChartsLegend direction="horizontal" />
59+
<ChartsSurface>
60+
<BarPlot />
61+
<LinePlot />
62+
<MarkPlot />
63+
<ChartsXAxis axisId="x-axis-id" />
64+
<ChartsYAxis />
65+
</ChartsSurface>
66+
</CustomChartWrapper>
67+
</ChartDataProviderPro>
68+
69+
<Button onClick={() => apiRef.current?.exportAsPrint()}>Print</Button>
70+
</Stack>
71+
);
72+
}

docs/data/charts/export/PrintChart.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import * as React from 'react';
2+
import Stack from '@mui/material/Stack';
3+
import Button from '@mui/material/Button';
4+
import { ScatterChartPro } from '@mui/x-charts-pro/ScatterChartPro';
5+
6+
import { data } from './randomData';
7+
8+
export default function PrintChart() {
9+
const apiRef = React.useRef(undefined);
10+
11+
return (
12+
<Stack width="100%">
13+
<ScatterChartPro
14+
apiRef={apiRef}
15+
height={300}
16+
series={[
17+
{
18+
label: 'Series A',
19+
data: data.map((v) => ({ x: v.x1, y: v.y1, id: v.id })),
20+
},
21+
{
22+
label: 'Series B',
23+
data: data.map((v) => ({ x: v.x1, y: v.y2, id: v.id })),
24+
},
25+
]}
26+
/>
27+
<Button onClick={() => apiRef.current?.exportAsPrint()}>Print</Button>
28+
</Stack>
29+
);
30+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import * as React from 'react';
2+
import Stack from '@mui/material/Stack';
3+
import Button from '@mui/material/Button';
4+
import { ScatterChartPro } from '@mui/x-charts-pro/ScatterChartPro';
5+
import { ChartProApi } from '@mui/x-charts-pro/ChartContainerPro';
6+
import { data } from './randomData';
7+
8+
export default function PrintChart() {
9+
const apiRef = React.useRef<ChartProApi>(undefined);
10+
11+
return (
12+
<Stack width="100%">
13+
<ScatterChartPro
14+
apiRef={apiRef}
15+
height={300}
16+
series={[
17+
{
18+
label: 'Series A',
19+
data: data.map((v) => ({ x: v.x1, y: v.y1, id: v.id })),
20+
},
21+
{
22+
label: 'Series B',
23+
data: data.map((v) => ({ x: v.x1, y: v.y2, id: v.id })),
24+
},
25+
]}
26+
/>
27+
<Button onClick={() => apiRef.current?.exportAsPrint()}>Print</Button>
28+
</Stack>
29+
);
30+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<ScatterChartPro
2+
apiRef={apiRef}
3+
height={300}
4+
series={[
5+
{
6+
label: 'Series A',
7+
data: data.map((v) => ({ x: v.x1, y: v.y1, id: v.id })),
8+
},
9+
{
10+
label: 'Series B',
11+
data: data.map((v) => ({ x: v.x1, y: v.y2, id: v.id })),
12+
},
13+
]}
14+
/>
15+
<Button onClick={() => apiRef.current?.exportAsPrint()}>Print</Button>

docs/data/charts/export/export.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
---
2+
title: Charts - Export
3+
productId: x-charts
4+
components: ScatterChartPro, BarChartPro, LineChartPro
5+
---
6+
7+
# Charts - Export [<span class="plan-pro"></span>](/x/introduction/licensing/#pro-plan 'Pro plan')
8+
9+
<p class="description">Charts can be printed and exported as PDF.</p>
10+
11+
Export is available on the **Pro**[<span class="plan-pro"></span>](/x/introduction/licensing/#pro-plan 'Pro plan') versions of the charts: `<LineChartPro />`, `<BarChartPro />`, `<ScatterChartPro />`.
12+
13+
## Print/Export as PDF
14+
15+
The `apiRef` prop exposes a `exportAsPrint` method that can be used to open the browser's print dialog.
16+
17+
The print dialog allows you to print the chart or save it as a PDF, as well as configuring other settings.
18+
19+
{{"demo": "PrintChart.js"}}
20+
21+
## Composition
22+
23+
As detailed in the [Composition](/x/react-charts/composition/) section, charts can alternatively be composed of more specific components to create custom visualizations.
24+
25+
When exporting a chart, the `ChartsWrapper` element is considered the root element of the chart, and every descendant is included in the export.
26+
As such, you need to ensure that the `ChartsWrapper` element is the root element of the chart you want to export.
27+
28+
If you want to use a custom wrapper element, you need to use the `useChartRootRef` hook to set the reference to the chart's root element so that exporting works properly, as exemplified below.
29+
30+
{{"demo": "ExportCompositionNoSnap.js"}}

docs/data/charts/export/randomData.js

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
export const data = [
2+
{
3+
id: 'data-0',
4+
x1: 329.39,
5+
y1: 443.28,
6+
y2: 153.9,
7+
},
8+
{
9+
id: 'data-1',
10+
x1: 96.94,
11+
y1: 110.5,
12+
y2: 217.8,
13+
},
14+
{
15+
id: 'data-2',
16+
x1: 336.35,
17+
y1: 175.23,
18+
y2: 286.32,
19+
},
20+
{
21+
id: 'data-3',
22+
x1: 159.44,
23+
y1: 195.97,
24+
y2: 325.12,
25+
},
26+
{
27+
id: 'data-4',
28+
x1: 188.86,
29+
y1: 351.77,
30+
y2: 144.58,
31+
},
32+
{
33+
id: 'data-5',
34+
x1: 143.86,
35+
y1: 43.253,
36+
y2: 146.51,
37+
},
38+
{
39+
id: 'data-6',
40+
x1: 202.02,
41+
y1: 376.34,
42+
y2: 309.69,
43+
},
44+
{
45+
id: 'data-7',
46+
x1: 384.41,
47+
y1: 31.514,
48+
y2: 236.38,
49+
},
50+
{
51+
id: 'data-8',
52+
x1: 256.76,
53+
y1: 231.31,
54+
y2: 440.72,
55+
},
56+
{
57+
id: 'data-9',
58+
x1: 143.79,
59+
y1: 108.04,
60+
y2: 20.29,
61+
},
62+
{
63+
id: 'data-10',
64+
x1: 103.48,
65+
y1: 321.77,
66+
y2: 484.17,
67+
},
68+
{
69+
id: 'data-11',
70+
x1: 272.39,
71+
y1: 120.18,
72+
y2: 54.962,
73+
},
74+
{
75+
id: 'data-12',
76+
x1: 23.57,
77+
y1: 366.2,
78+
y2: 418.5,
79+
},
80+
{
81+
id: 'data-13',
82+
x1: 219.73,
83+
y1: 451.45,
84+
y2: 181.32,
85+
},
86+
{
87+
id: 'data-14',
88+
x1: 54.99,
89+
y1: 294.8,
90+
y2: 440.9,
91+
},
92+
{
93+
id: 'data-15',
94+
x1: 134.13,
95+
y1: 121.83,
96+
y2: 273.52,
97+
},
98+
{
99+
id: 'data-16',
100+
x1: 12.7,
101+
y1: 287.7,
102+
y2: 346.7,
103+
},
104+
{
105+
id: 'data-17',
106+
x1: 176.51,
107+
y1: 134.06,
108+
y2: 74.528,
109+
},
110+
{
111+
id: 'data-18',
112+
x1: 65.05,
113+
y1: 104.5,
114+
y2: 150.9,
115+
},
116+
{
117+
id: 'data-19',
118+
x1: 162.25,
119+
y1: 413.07,
120+
y2: 26.483,
121+
},
122+
{
123+
id: 'data-20',
124+
x1: 68.88,
125+
y1: 74.68,
126+
y2: 333.2,
127+
},
128+
{
129+
id: 'data-21',
130+
x1: 95.29,
131+
y1: 360.6,
132+
y2: 422.0,
133+
},
134+
{
135+
id: 'data-22',
136+
x1: 390.62,
137+
y1: 330.72,
138+
y2: 488.06,
139+
},
140+
];

0 commit comments

Comments
 (0)