Save multiple plots to pdf

How to save multiple matplotlib plots to a single PDF file from a PyQt6 application
Heads up! You've already completed this tutorial.

I have a PyQt application that displays six Matplotlib plots after a button click. I've added a save button and I want to save all six plots to a single PDF file, but I'm getting errors. Outside of PyQt I know how to use PdfPages and savefig(), but I'm stuck getting it working inside my application. How can I save multiple Matplotlib plots to a PDF from a PyQt app?

Saving multiple Matplotlib plots to a PDF from within a PyQt6 application is very doable — the approach is essentially the same as doing it in a plain Python script, with just a little extra wiring to connect it to your GUI. In this article, we'll walk through how to display multiple plots in a PyQt6 window and then save them all to a single PDF file using Matplotlib's PdfPages.

The core idea

Matplotlib provides a utility called PdfPages (from matplotlib.backends.backend_pdf) that lets you write multiple figures to a single PDF file, one figure per page. The basic pattern looks like this:

python
from matplotlib.backends.backend_pdf import PdfPages

with PdfPages("output.pdf") as pdf:
    pdf.savefig(figure_1)
    pdf.savefig(figure_2)
    # ...and so on

Each call to pdf.savefig() adds a new page to the PDF. This works exactly the same way whether you're running a script or inside a PyQt6 application — you just need to have references to the Matplotlib Figure objects you want to save.

Displaying plots in PyQt6 with Matplotlib

To embed Matplotlib plots in a PyQt6 window, you use FigureCanvasQTAgg as a widget. Each canvas wraps a single Matplotlib Figure. If you want to show six plots in your window, you'll create six Figure objects and six corresponding canvases.

Let's start with a simple example that creates six plots and displays them in a grid layout:

python
from matplotlib.figure import Figure
from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg
import numpy as np

class MplCanvas(FigureCanvasQTAgg):
    """A Matplotlib canvas that can be used as a PyQt6 widget."""

    def __init__(self, parent=None, width=4, height=3, dpi=100):
        self.fig = Figure(figsize=(width, height), dpi=dpi)
        self.axes = self.fig.add_subplot(111)
        super().__init__(self.fig)

This gives us a reusable widget. Each MplCanvas instance holds its own Figure (accessible via self.fig) and a set of axes we can plot on.

Saving all plots to a PDF

When the user clicks the save button, we need to:

  1. Ask the user where to save the file (using QFileDialog).
  2. Open a PdfPages context for that file path.
  3. Loop through all our Figure objects and call pdf.savefig() on each one.

Here's what the save method looks like:

python
from matplotlib.backends.backend_pdf import PdfPages
from PyQt6.QtWidgets import QFileDialog

def save_plots_to_pdf(self):
    file_path, _ = QFileDialog.getSaveFileName(
        self, "Save PDF", "", "PDF Files (*.pdf)"
    )
    if file_path:
        with PdfPages(file_path) as pdf:
            for canvas in self.canvases:
                pdf.savefig(canvas.fig)

Each figure is saved as a separate page in the PDF. The PdfPages context manager handles opening and closing the file for you.

Saving all plots on a single page

If you'd prefer all six plots on a single PDF page instead of six separate pages, you can create a new figure with subplots, copy the data across, and save that single figure. An even simpler approach: create the single-page layout at save time using Matplotlib's subplots:

python
def save_plots_single_page(self):
    file_path, _ = QFileDialog.getSaveFileName(
        self, "Save PDF", "", "PDF Files (*.pdf)"
    )
    if file_path:
        fig, axes = plt.subplots(2, 3, figsize=(12, 8))
        for ax, canvas in zip(axes.flat, self.canvases):
            # Re-draw each plot's data onto the combined figure
            for line in canvas.axes.get_lines():
                ax.plot(line.get_xdata(), line.get_ydata())
            ax.set_title(canvas.axes.get_title())
        fig.tight_layout()
        fig.savefig(file_path)
        plt.close(fig)

This creates a temporary 2×3 grid of subplots, copies the line data from each displayed plot, and saves everything to a single page. For the common case of saving each plot as its own page, the PdfPages approach above is simpler and more reliable.

Complete working example

Here's a full PyQt6 application that creates six plots with random data, displays them in a grid, and provides a button to save them all to a multi-page PDF:

python
import sys
import numpy as np
from matplotlib.figure import Figure
from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg
from matplotlib.backends.backend_pdf import PdfPages

from PyQt6.QtWidgets import (
    QApplication,
    QMainWindow,
    QWidget,
    QGridLayout,
    QPushButton,
    QVBoxLayout,
    QFileDialog,
)

class MplCanvas(FigureCanvasQTAgg):
    """A matplotlib canvas widget for embedding plots in PyQt6."""

    def __init__(self, parent=None, width=4, height=3, dpi=100):
        self.fig = Figure(figsize=(width, height), dpi=dpi)
        self.axes = self.fig.add_subplot(111)
        super().__init__(self.fig)

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Multiple Plots to PDF")

        # Create six plot canvases.
        self.canvases = []
        plot_titles = [
            "Sine Wave",
            "Cosine Wave",
            "Random Walk",
            "Exponential",
            "Quadratic",
            "Noise",
        ]

        x = np.linspace(0, 10, 200)

        plot_data = [
            np.sin(x),
            np.cos(x),
            np.cumsum(np.random.randn(200)),
            np.exp(x / 5),
            x ** 2,
            np.random.randn(200),
        ]

        for title, y in zip(plot_titles, plot_data):
            canvas = MplCanvas(self, width=4, height=3, dpi=100)
            canvas.axes.plot(x, y)
            canvas.axes.set_title(title)
            canvas.fig.tight_layout()
            self.canvases.append(canvas)

        # Arrange the canvases in a 2x3 grid.
        plot_grid = QGridLayout()
        for i, canvas in enumerate(self.canvases):
            row = i // 3
            col = i % 3
            plot_grid.addWidget(canvas, row, col)

        # Add a save button.
        save_button = QPushButton("Save All Plots to PDF")
        save_button.clicked.connect(self.save_to_pdf)

        # Main layout.
        layout = QVBoxLayout()
        layout.addLayout(plot_grid)
        layout.addWidget(save_button)

        container = QWidget()
        container.setLayout(layout)
        self.setCentralWidget(container)

    def save_to_pdf(self):
        file_path, _ = QFileDialog.getSaveFileName(
            self,
            "Save PDF",
            "",
            "PDF Files (*.pdf)",
        )
        if not file_path:
            return

        # Ensure the file has a .pdf extension.
        if not file_path.lower().endswith(".pdf"):
            file_path += ".pdf"

        with PdfPages(file_path) as pdf:
            for canvas in self.canvases:
                pdf.savefig(canvas.fig)

        print(f"Saved {len(self.canvases)} plots to {file_path}")

app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()

Run this and you'll see a window with six plots arranged in a grid. Click "Save All Plots to PDF", choose a location, and all six plots will be saved to a multi-page PDF — one plot per page.

A few things to keep in mind

The figures must exist when you save. Make sure you're keeping references to your Figure objects (or the canvases that hold them). If you create figures in a method and don't store them as instance attributes, they may be garbage collected before you try to save.

PdfPages works with Figure objects, not canvas widgets. When calling pdf.savefig(), pass the Matplotlib Figure instance (e.g., canvas.fig), not the FigureCanvasQTAgg widget itself.

You can customize the PDF output. The savefig() method accepts all the usual Matplotlib keyword arguments — dpi, bbox_inches, facecolor, and so on. For example, to get tighter margins:

python
pdf.savefig(canvas.fig, bbox_inches="tight")

This works the same in PyQt5 and PyQt6. The only differences are the import paths (PyQt5 vs PyQt6) and using app.exec() instead of app.exec_(). The Matplotlib side is identical.

Well done, you've finished this tutorial! Mark As Complete
[[ user.completed.length ]] completed [[ user.streak+1 ]] day streak

Packaging Python Applications with PyInstaller by Martin Fitzpatrick

This step-by-step guide walks you through packaging your own Python applications from simple examples to complete installers and signed executables.

More info Get the book

Martin Fitzpatrick

Save multiple plots to pdf was written by Martin Fitzpatrick with contributions from Leo Well.

Martin Fitzpatrick has been developing Python/Qt apps for 8 years. Building desktop applications to make data-analysis tools more user-friendly, Python was the obvious choice. Starting with Tk, later moving to wxWidgets and finally adopting PyQt. Martin founded PythonGUIs to provide easy to follow GUI programming tutorials to the Python community. He has written a number of popular Python books on the subject.