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?
SymptomCauseFix
Blank axes, no errorEmpty data after filteringCheck the mask before plotting
Line stops halfwayA NaN in the middlenp.isnan, then interpolate or drop
Only markers visibleVery few points close togetherReduce markers, add a line
Values clipped at the edgeLimits too tightautoscale or margins
Nothing on a log axisZero or negative valuessymlog 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_layout reserves space for labels automatically; call it via the layout keyword 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.

Scales, ticks and date axes Plotting directly from pandas and NumPy

Last refreshed 2026-09-18.