Save a subplot in matplotlib
Asked Answered
S

2

92

Is it possible to save (to a png) an individual subplot in a matplotlib figure? Let's say I have

import pyplot.matplotlib as plt
ax1 = plt.subplot(121)
ax2 = plt.subplot(122)
ax1.plot([1,2,3],[4,5,6])    
ax2.plot([3,4,5],[7,8,9])

Is it possible to save each of the two subplots to different files or at least copy them separately to a new figure to save them?

I am using version 1.0.0 of matplotlib on RHEL 5.

Sharecrop answered 1/12, 2010 at 15:9 Comment(0)
V
164

While @Eli is quite correct that there usually isn't much of a need to do it, it is possible. savefig takes a bbox_inches argument that can be used to selectively save only a portion of a figure to an image.

Here's a quick example:

import matplotlib.pyplot as plt
import matplotlib as mpl
import numpy as np

# Make an example plot with two subplots...
fig = plt.figure()
ax1 = fig.add_subplot(2,1,1)
ax1.plot(range(10), 'b-')

ax2 = fig.add_subplot(2,1,2)
ax2.plot(range(20), 'r^')

# Save the full figure...
fig.savefig('full_figure.png')

# Save just the portion _inside_ the second axis's boundaries
extent = ax2.get_window_extent().transformed(fig.dpi_scale_trans.inverted())
fig.savefig('ax2_figure.png', bbox_inches=extent)

# Pad the saved area by 10% in the x-direction and 20% in the y-direction
fig.savefig('ax2_figure_expanded.png', bbox_inches=extent.expanded(1.1, 1.2))

The full figure: Full Example Figure


Area inside the second subplot: Inside second subplot


Area around the second subplot padded by 10% in the x-direction and 20% in the y-direction: Full second subplot

Voter answered 1/12, 2010 at 20:10 Comment(7)
+1: Wow! I wish I had come across these methods while trying to learn more about Matplotlib! It would be great if the official documentation directed interested readers to these useful corners of Matplotlib, and if the presentation of the relevant concepts were more structured. :)Scout
A day you don't learn something new is a bad day... Well done ++Apostrophize
How to reduce the redundant space in top and right side of the produced figure if we use extent.expanded() method? Can we precisely designate the space in each of the four side of the produce figure? That would be excellent.Madlin
Magical. I don't know you figured this out.Hazem
amazing! (sorry, stackoverflow, had to say it since this is so useful)Mallorca
Amazing! To hide the black remainders of the axes lines, you can toggle ax2.axis('off') and ax2.axis('on') after savingSubmerged
It appears that in the latest versions matplotlib removed dpi_scale_trans. I tried doing a Transform(figure.dpi).inverted(), but it didn't work (and to be honest it was a wild guess). Do you happen to know what's the latest way to apply your solution?Barbell
L
53

Applying the full_extent() function in an answer by @Joe 3 years later from here, you can get exactly what the OP was looking for. Alternatively, you can use Axes.get_tightbbox() which gives a little tighter bounding box

import matplotlib.pyplot as plt
import matplotlib as mpl
import numpy as np
from matplotlib.transforms import Bbox

def full_extent(ax, pad=0.0):
    """Get the full extent of an axes, including axes labels, tick labels, and
    titles."""
    # For text objects, we need to draw the figure first, otherwise the extents
    # are undefined.
    ax.figure.canvas.draw()
    items = ax.get_xticklabels() + ax.get_yticklabels() 
#    items += [ax, ax.title, ax.xaxis.label, ax.yaxis.label]
    items += [ax, ax.title]
    bbox = Bbox.union([item.get_window_extent() for item in items])

    return bbox.expanded(1.0 + pad, 1.0 + pad)

# Make an example plot with two subplots...
fig = plt.figure()
ax1 = fig.add_subplot(2,1,1)
ax1.plot(range(10), 'b-')

ax2 = fig.add_subplot(2,1,2)
ax2.plot(range(20), 'r^')

# Save the full figure...
fig.savefig('full_figure.png')

# Save just the portion _inside_ the second axis's boundaries
extent = full_extent(ax2).transformed(fig.dpi_scale_trans.inverted())
# Alternatively,
# extent = ax.get_tightbbox(fig.canvas.renderer).transformed(fig.dpi_scale_trans.inverted())
fig.savefig('ax2_figure.png', bbox_inches=extent)

I'd post a pic but I lack the reputation points

Legato answered 17/10, 2014 at 20:26 Comment(4)
This answer can be extended to include the text labels by adding items += [ax.get_xaxis().get_label(), ax.get_yaxis().get_label()]. They were cut off before I added that.Sensibility
I get AttributeError: 'FigureCanvasAgg' object has no attribute 'renderer'Viceregal
@Viceregal fig.canvas.get_renderer() does work.Kearney
@MattGibson thanks for your answer, it was useful 😊. However, why is get_tightbbox() merely an alternative answer? It seems to me that it does a clean, reliable job in one line (combined with .expanded() ), whereas the main method shown here seem a lot more complex and I’d say less robust (What if no ticks? What if I want to include xlabel and ylabel for sure without risking cutting through them?). Am I missing some drawback of get_tightbbox()? I am tempted to post a new answer with only the get_tightbbox() method so that it is easier to read…Neurilemma

© 2022 - 2024 — McMap. All rights reserved.