LESSON 168 · PRACTICAL PYTHON26 min

Matplotlib savefig: Export a PNG Without Clipped Labels

Save the exact Figure you created to a PNG file at 200 DPI with labels inside the output.

Example check: Worked and guided code samples are checked against CPython 3.13. Practice uses Pyodide 0.29.5 (Python 3.13) in your browser. How we check lessons

Video lesson: Matplotlib savefig: Export a PNG Without Clipped Labels

The preview is stored on this site. YouTube loads only when you press Play. Watch on YouTube

Read the video transcript

Select a timestamp to watch that moment on YouTube.

  1. 00:00

    Suppose a script builds two charts and must export the first one. You may expect a later call to pyplot.savefig to save whichever chart you had in mind, but pyplot has a current- figure state. After creating the second chart, that state points to the second Figure. This tutorial is about matplotlib savefig, so the central habit is to hold on to the Figure returned by subplots and call fig.savefig on that exact object. We will also choose a PNG resolution, keep labels visible, and verify that a real output file was written. The demonstration uses the Agg backend, which renders without a desktop window.

  2. 00:40

    It saves inside a temporary directory so the experiment does not overwrite an existing report. First, let us reproduce a valid export that is still the wrong export. The chart may be used in a report or thumbnail, where a missing title or a cropped tick label changes its meaning. Export is part of the data product, so we will verify both the file's identity and whether its readable visual elements survive. The broken run creates one chart labelled Intended and then creates a second chart labelled Other. pyplot.savefig writes a PNG successfully, but it writes the current second Figure. A success status or a nonempty file does not prove that the intended plot was

  3. 01:22

    saved. This kind of mistake is easy to miss in a batch job because there may be no visible interactive window. The problem is not the PNG format or the path. It is the source Figure selected by global state. A reliable fix should not depend on which Figure happened to be current at the time of saving. Notice that the first Figure still exists; we can keep its object reference and export it directly. In a notebook, plt.show may also change what you see on screen without changing the reference to the Figure you want to export. Treat display and serialization as separate actions.

  4. 02:02

    A visually correct notebook cell is not proof that the correct file was written. The diagnostic run compares each Figure's number with the pyplot current Figure number. The numbers differ after the second plot is created, which explains why a plain pyplot.savefig targets the other chart. In a short notebook this may feel obvious, but a long report generator can create helper plots between chart construction and export. Treat the Figure object as the chart's identity. An Axes object holds the drawn data and labels, while the Figure owns the canvas that savefig serializes. After saving, close both figures to release resources.

  5. 02:44

    For production use, also make sure the target directory exists, and use a deliberate filename suffix such as .png so the output format is clear. This diagnostic is more reliable than inferring from filenames or variable names. A Python variable named sales_chart does not force pyplot to make it current. The object identity check shows exactly which Figure a stateful call would use. The corrected run saves the held first Figure with fig.savefig. We set dpi to two hundred for a useful raster resolution and bbox_inches to tight so axis labels and titles near the edge are included.

  6. 03:25

    A six-by-three-inch Figure at two hundred DPI has a nominal twelve-hundred-by-six- hundred-pixel canvas. Tight bounding-box adjustment can expand or crop the output, so do not claim those final dimensions without measuring the actual file. The code checks that the temporary PNG exists and that its first eight bytes match the PNG signature. That is an encoding check, not a visual inspection of plotted values. For a published chart, look at the image as well. If the chart is intended for a small screen, test the actual exported image at that size. A high DPI cannot repair text that was too small in the Figure design, and a tight

  7. 04:06

    bounding box cannot supply labels that were never drawn. The final check writes two figures again, saves the first directly, and verifies that the output is PNG while the current pyplot Figure is still the second. That demonstrates that fig.savefig does not depend on the active Figure. We also close the figures after the export. If you need exact pixel dimensions, avoid assuming bbox_inches tight preserves the nominal canvas. Control margins intentionally and inspect the saved size. If you need vector output, choose SVG or PDF and review how fonts and layout appear there; DPI mainly concerns raster formats.

  8. 04:50

    The browser exercise on the lesson page uses a Figure spy to inspect the savefig call without creating a permanent file, while this terminal demonstration uses actual Matplotlib. The takeaway is to save the intended Figure explicitly and verify the artifact you produced. The PNG signature protects against a mislabeled format or empty output, but it cannot tell us whether the plotted series or colors are correct. Keep a short visual review in the publishing workflow, especially for charts used outside the lesson page.

Understand the concept

matplotlib savefig writes a Figure to a file. In scripts with multiple charts, pyplot.savefig can export whichever Figure is currently active. Hold on to the Figure returned by plt.subplots() and call fig.savefig(path). That targets the intended chart explicitly and avoids a common empty-plot export.

For PNG output, dpi sets raster resolution. A 6 by 3 inch figure at 200 DPI has a nominal 1200 by 600 pixel canvas. bbox_inches='tight' can change those final dimensions because it adjusts the saved area to include labels and title. Do not promise exact pixel dimensions while also using automatic tight cropping; inspect the output if pixel size is contractual.

Use a path ending in .png and create its parent directory when needed. The local example writes into a temporary directory, checks that the output starts with PNG signature bytes, and closes the Figure after saving. Save before plt.close(fig). A headless machine can select Matplotlib's Agg backend before importing pyplot.

The browser task checks the fig.savefig call with a Figure spy, so it does not create a permanent file in the learner's browser. The local run block executes real Matplotlib and verifies a real PNG. This page is specifically about chart export; the statistics guide covers numbers to plot and the pathlib guide covers filesystem paths.

  • Figure.savefig targets the chosen chart
  • dpi sets raster resolution
  • tight bounding box helps include labels
  • close after saving

See it step by step

Read the code, predict the output, then compare it with the result.

01. Save the held Figure

PYTHON
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from pathlib import Path
from tempfile import TemporaryDirectory
fig, ax = plt.subplots()
ax.plot([1, 2], [3, 5])
with TemporaryDirectory() as folder:
    path = Path(folder) / 'plot.png'
    fig.savefig(path, dpi=200)
    print(path.exists())
plt.close(fig)
EXPECTED OUTPUT
True

Saving the held Figure avoids relying on pyplot's active state.

02. Verify a PNG signature

PYTHON
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from io import BytesIO
fig, ax = plt.subplots()
ax.plot([1, 2], [2, 1])
buffer = BytesIO()
fig.savefig(buffer, format='png', dpi=200, bbox_inches='tight')
print(buffer.getvalue()[:8] == b'\x89PNG\r\n\x1a\n')
plt.close(fig)
EXPECTED OUTPUT
True

An in-memory buffer proves that the Figure was encoded as PNG.

03. Test the call without a file

PYTHON
class Spy:
    def savefig(self, path, **options):
        print(path, options['dpi'], options['bbox_inches'])
Spy().savefig('orders.png', dpi=200, bbox_inches='tight')
EXPECTED OUTPUT
orders.png 200 tight

The browser task checks the export API without a persistent file.

Run the complete example locally

Run locally after python -m pip install matplotlib. The code saves a temporary real PNG and checks its signature. The browser task uses a Figure spy to inspect the export call.

PYTHON
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from pathlib import Path
from tempfile import TemporaryDirectory

fig, ax = plt.subplots(figsize=(6, 3))
ax.plot([1, 2, 3], [2, 4, 3], marker='o')
ax.set(xlabel='Day', ylabel='Orders', title='Orders by day')
with TemporaryDirectory() as folder:
    path = Path(folder) / 'orders.png'
    fig.savefig(path, dpi=200, bbox_inches='tight')
    print(path.exists(), path.read_bytes()[:8] == b'\x89PNG\r\n\x1a\n')
plt.close(fig)
EXPECTED OUTPUT
True True

A closer look

Follow the reasoning, inspect each result, then try the suggested changes in the console below.

01 / 03

Save the Figure you created

A pyplot state-machine call saves the current Figure. If another Figure was created before export, that may be the wrong one. Keep the object returned by subplots and save it directly. Give each Figure a clear variable name and keep its Axes nearby while drawing. This keeps plot construction, labeling, and export in one reviewable sequence instead of relying on implicit global state.

Set labels and title before exporting. Close the Figure only after savefig returns, especially when a loop generates many reports.

PYTHON
class FigureSpy:
    def savefig(self, path, **options):
        print(path, options)
fig = FigureSpy()
fig.savefig('orders.png', dpi=200, bbox_inches='tight')
EXPECTED OUTPUT
orders.png {'dpi': 200, 'bbox_inches': 'tight'}
Follow the reasoning
  1. Capture fig from subplots.
  2. Call fig.savefig on that same object.
02 / 03

Choose DPI and a bounding box

DPI matters for raster formats such as PNG; it does not change the plotted data. At 6 by 3 inches and 200 DPI, the nominal canvas is 1200 by 600 pixels. For vector formats such as SVG, DPI has a different role because lines remain scalable. Choose the output format for the destination and verify the actual rendered result in that destination.

bbox_inches='tight' helps include labels outside the default axes but may alter output dimensions. Verify final pixels if an exact image size is required.

PYTHON
width_inches, height_inches, dpi = 6, 3, 200
print(width_inches * dpi, height_inches * dpi)
EXPECTED OUTPUT
1200 600
Follow the reasoning
  1. Calculate nominal dimensions.
  2. Explain why tight cropping may change the saved dimensions.
03 / 03

Verify a real PNG

A successful savefig call should leave a file at the expected path. Use a temporary directory while learning so repeated runs cannot overwrite an important report. After checking the signature, open the chart and read its title, axis labels, and plotted values. Automated byte checks can prove file type, but they cannot detect an incorrect chart or poor legibility.

A nonempty file alone does not prove it contains the intended chart. Check its PNG signature, then visually inspect the axis labels and plotted values.

PYTHON
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from io import BytesIO
fig, ax = plt.subplots()
ax.plot([1, 2], [2, 1])
buffer = BytesIO()
fig.savefig(buffer, format='png', dpi=200, bbox_inches='tight')
print(buffer.getvalue()[:8] == b'\x89PNG\r\n\x1a\n')
plt.close(fig)
EXPECTED OUTPUT
True
Follow the reasoning
  1. Run the complete local Matplotlib block.
  2. Confirm the file and PNG bytes, then inspect the chart.

Try it in Python

Edit the example and run it. Python starts in your browser the first time you click Run.

Python console

Ready to run
Need input()? Add one value per line
Ctrl / ⌘ + Enter to run
OUTPUT
Your output appears here.
Your turnCall savefig on the supplied Figure with the given path, dpi=200, and bbox_inches='tight'. The spy should show both options. Run the local example for a real PNG.
Need a hint?

Use fig.savefig(path, dpi=200, bbox_inches='tight') rather than plt.savefig().

Complete the task and select Check task to verify your code.

    Quick quiz

    Three questions. You can change your answers and try again.

    01.Why call fig.savefig in a multi-figure script?

    02.What does dpi control for PNG output?

    03.Can tight cropping change final image dimensions?

    Typical mistakes

    Everyone meets these errors. See what causes them and how to fix them.

    Saving the wrong active figure

    COMMON MISTAKE
    plt.savefig('chart.png') after creating another figure
    FIX
    fig.savefig('chart.png')

    What happens: An empty or different chart is exported.
    Keep and save the intended Figure object.

    Assuming tight crop preserves exact pixels

    COMMON MISTAKE
    fig.savefig(path, dpi=200, bbox_inches='tight')  # always 1200x600
    FIX
    Inspect the saved dimensions if exact pixels matter.

    What happens: The resulting dimensions differ from the assumed canvas.
    Bounding-box cropping changes the output canvas.

    Browse this lesson’s problem fixes

    Sources and further reading