Debugging plots: empty axes, missing data and overlap
Why a line vanishes, how to stop labels colliding, and how to inspect the artist tree when the picture does not match your intent.
A line that disappears
Most empty-plot problems have one of four causes: NaN in the data, values outside the limits, a log scale rejecting non-positive numbers, or a filter that left the array empty. Check the data before you touch the style.
print(np.isnan(y).sum(), y.size) # missing values break the line
print(y.min(), y.max()) # compare with the current limits
print(ax.get_xlim(), ax.get_ylim())
ax.plot(x, y) # y reaches 1200 but the axis tops out at 10
# fixes
ax.relim() # recompute the data limits
ax.autoscale_view()
ax.set_ylim(bottom=0)
ax.margins(x=0.02, y=0.05)
print(len(ax.lines), ax.lines[0].get_xydata().shape) # is anything attached?| Symptom | Cause | Fix |
|---|---|---|
| Blank axes, no error | Empty data after filtering | Check the mask before plotting |
| Line stops halfway | A NaN in the middle | np.isnan, then interpolate or drop |
| Only markers visible | Very few points close together | Reduce markers, add a line |
| Values clipped at the edge | Limits too tight | autoscale or margins |
| Nothing on a log axis | Zero or negative values | symlog or filter them out |
Overlapping labels and layout
fig, ax = plt.subplots(layout="constrained") # modern replacement for tight_layout
ax.tick_params(axis="x", rotation=45, labelsize=9)
fig.align_labels() # line up shared axis labels
ax.legend(loc="upper left", bbox_to_anchor=(1.0, 1.0)) # outside the data
fig.savefig("chart.png", bbox_inches="tight") # include what overflows
# long tick labels: shorten the strings instead of rotating further
ax.set_xticks(range(len(names)), [n[:18] for n in names], ha="right", rotation=30)constrained_layoutreserves space for labels automatically; call it via thelayoutkeyword at creation.fig.align_labels()makes labels on stacked panels line up even with different tick widths.- A legend inside the axes competes with the data; moving it out costs a little width and removes the collision.
bbox_inches="tight"at save time recovers anything still outside the figure box.
Inspecting the artist tree
ax.get_children() # everything drawn in this axes
ax.lines # Line2D objects
ax.collections # scatter, hexbin, filled contours
ax.patches # bars and wedges
ax.images # imshow output
ax.texts # text and annotations
line = ax.lines[0]
line.get_visible(), line.get_label(), line.get_color()
line.get_alpha()
print(ax.get_legend()) # None if no legend was created
print([t.get_text() for t in ax.texts])⚠️
Layout engines run when the figure is drawn, so calling
tight_layout before adding a legend or annotation has no effect on them. Add everything first, then lay out, then save.FAQ
My plot is empty but there is no error. Where do I start?
Print the shape and the min/max of both arrays, and the current axis limits. In most cases the data is empty, contains NaN, or lies outside the limits that were set earlier.
How do I check what is actually on an axes?
Inspect
ax.lines, ax.collections, ax.patches, ax.images and ax.texts. If a list is empty, the artist was never attached to that axes — often because a different axes was current.Related
Scales, ticks and date axes Plotting directly from pandas and NumPy
Last refreshed 2026-09-18.