Matplotlib Renders a Blank Figure: A Diagnostic Guide for Silent Failures
Matplotlib plot calls that run without errors but produce blank windows, empty PNGs, or missing lines — a diagnostic table, ordered checks, and fixes tied to each finding.
28 Jul 2026, 23:22 UTC

The condition: the plot call ran, but nothing appears
Your script finishes without an exception, yet plt.show() opens an empty window, savefig() writes a blank PNG, or a notebook cell outputs an empty axes box. This is one of the most common Matplotlib engineering problems precisely because it fails silently — no traceback, no warning, just missing output.
The useful takeaway: a blank figure almost always means one of five things — wrong call ordering, artists on the wrong Axes, empty or non-finite data, limits that exclude your data, or a backend that cannot display. Each has a distinct diagnostic, so you can identify the cause in a few minutes instead of guessing.
Cause and diagnostic table
| Symptom | Likely cause | Quick diagnostic |
|---|---|---|
| Saved file blank, window looked fine | show() called before savefig() | Check call order in the script |
| Axes box renders, no lines or markers | Artists drawn on a different Figure/Axes | ax.has_data() returns False |
| Axes with ticks but nothing drawn | Empty array, all-NaN, or dtype mismatch | Print arr.shape, arr.dtype, np.isfinite(arr).sum() |
| Artists exist but invisible | Data outside view limits, or log scale on non-positive values | ax.get_xlim() / ax.get_ylim() vs. data range |
show() does nothing at all | Non-interactive backend (e.g., Agg on a headless server) | matplotlib.get_backend() |
Ordered checks
Work through these in order; each step rules out a whole category before you touch rendering details.
- Confirm the data exists and is finite. Before plotting, print
len(x),x.dtype, andnp.isfinite(x).sum(). An all-NaN column or an empty DataFrame slice produces an axes with default limits and no visible artists — and no error. - Confirm you drew on the Axes you are displaying. If you mix the pyplot state machine (
plt.plot(...)) with explicit objects (fig, ax = plt.subplots()) across functions or notebook cells, artists can land on a stale figure. Checkplt.get_fignums()andax.has_data()on the Axes you actually save or show. - Check limits and scale. Compare
ax.get_xlim()andax.get_ylim()against your data range. If you set limits manually or plotted on a log scale with zero or negative values (which Matplotlib silently masks), the artists exist but sit outside the view. Tryax.relim(); ax.autoscale_view()to test. - Check show/savefig ordering and the backend. In many backends,
plt.show()clears the current figure after displaying it, so a laterplt.savefig()writes a blank file. Printmatplotlib.get_backend()— if it isAgg(the default on headless servers),show()is a no-op by design. - Check style and colors. A white line on a white background, a fully transparent
alpha=0, or an rcParam override can make a correctly drawn figure look empty. Test with a deliberately loud style:ax.plot(x, y, 'r-o').
Fixes tied to findings
If savefig comes after show: reorder, or better, hold an explicit Figure reference so ordering no longer matters:
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
ax.plot([1, 2, 3], [1, 4, 9])
fig.savefig('out.png') # save BEFORE show
plt.show()
Run this in your script or notebook; no special permissions needed. Verify by opening out.png — it should show the parabola. If you must save after showing, re-draw or keep the fig object and call fig.savefig(), since the explicit figure is not cleared by show() the way the pyplot current figure can be. Note: the exact clearing behavior of show() varies by backend and Matplotlib version, so verify against your installed version rather than assuming.
If artists are on the wrong Axes: pick one API and use it consistently. The object-oriented style (fig, ax = plt.subplots(), then ax.plot(...)) removes ambiguity about which figure owns the artists, which is the usual root cause in multi-function scripts and notebooks.
If the data is the problem: fix upstream, not in Matplotlib. Drop or fill NaNs (df.dropna(subset=['y'])), confirm a filter didn't empty your DataFrame, and convert dtypes explicitly (e.g., pd.to_datetime) before plotting.
If limits exclude the data: call ax.relim() followed by ax.autoscale_view(), or remove stale set_xlim/set_ylim calls. For log scales, filter to positive values or switch to ax.set_yscale('symlog') if zeros and negatives are legitimate.
If the backend is non-interactive: on a headless server, save to file instead of showing — that is what Agg is for. To confirm the backend is the variable, run the same script twice:
# Linux/macOS shell
MPLBACKEND=Agg python plot.py # show() is a no-op; savefig works
MPLBACKEND=TkAgg python plot.py # requires a display and tkinter
The MPLBACKEND environment variable overrides backend selection for that run only, so this is a safe, reversible experiment. TkAgg requires a working display and the tk bindings installed; if it fails to import, that itself tells you the environment is headless.
Minimal reproducer to verify any fix
Whatever the cause, confirm the fix with a known-good baseline. This script must produce a non-blank PNG in any healthy installation:
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
print(matplotlib.__version__, matplotlib.get_backend())
fig, ax = plt.subplots()
ax.plot(np.arange(10), np.arange(10), 'r-o')
print('has_data:', ax.has_data(), 'xlim:', ax.get_xlim())
fig.savefig('t.png')
plt.show()
If t.png is blank here, the problem is environmental (backend, installation), not your plotting code. If it renders correctly, diff its behavior against your failing script using the ordered checks above. Do not assume a blank PNG means no data was plotted — distinguish "no artists" (ax.has_data() is False) from "artists outside limits" (has_data True, limits wrong) before changing code.
Escalation criteria
Escalate beyond your own code when all of the following hold: ax.has_data() is True, limits match the data range, the minimal reproducer above renders correctly, and the figure is still blank in one environment but renders in another. That pattern points to a backend, font, or rendering bug rather than a usage error.
At that point: capture matplotlib.__version__, matplotlib.get_backend(), your OS, and a minimal script that reproduces the blank output, then search the Matplotlib issue tracker and file with that reproducer if nothing matches. Notebook users should also note which integration is active (%matplotlib inline vs %matplotlib widget) — they have different figure-lifecycle rules, and a blank notebook output does not imply the same cause as a blank script window.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.