Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

matplotlib: Get resulting bounding box of `bbox_inches=tight`

Tags:

matplotlib

I often produce individual figures with matplotlib which are supposed to be aligned vertically or horizontally e.g. in a LaTeX document. My goals are:

  1. Avoid excessive margins or clipping in all figures. For stand-alone figures this is easily solved by using bbox_inches='tight' in savefig.
  2. Use the exact same bounding box for all figures, since I want my axes elements to line up nicely in the resulting document.

My current solution is a very inconvenient trial and error approach: I manually try to guess reasonable margins and set them via plt.subplots_adjust(). This often takes a lot of time until I have a satisfactory result.

I'm wondering if it is possible to combine the power of bbox_inches='tight' with plt.subplots_adjust()? Is it possible to programmatically get the resulting bounding box of bbox_inches='tight'? This would allow my to determining the bounding box for the figure with the largest axis labels/titles, and use this bounding box for all other figures.

like image 663
bluenote10 Avatar asked Feb 24 '15 10:02

bluenote10


1 Answers

Thanks to @Schorsch and @tcaswell for pointing out that ax.get_tightbbox almost solves this problem. The only tricky part is to convert the renderer dependent bbox into an "inches" bbox. The general solution is:

tight_bbox_raw = ax.get_tightbbox(fig.canvas.get_renderer())
tight_bbox = TransformedBbox(tight_bbox_raw, Affine2D().scale(1./fig.dpi))

I can now reuse the tight_bbox for my other plots as well, resulting in nice, identically boxed figures.

I have also written a small helper class -- not elegant, but useful: I call max_box_tracker.add_figure for every figure I create, and it will internally update a maximum bounding box. After generating all plots, I can print out the resulting bounding box (directly in the form of code to add to my script).

class MaxBoxTracker:
  def __init__(self):
    self.box = None
  def add_figure(self, fig, ax):
    from matplotlib.transforms import TransformedBbox, Affine2D
    box_raw = ax.get_tightbbox(fig.canvas.get_renderer())
    box = TransformedBbox(box_raw, Affine2D().scale(1./fig.dpi)).get_points()
    if self.box is None:
      self.box = box
    else:
      self.box[0][0] = min(self.box[0][0], box[0][0])
      self.box[0][1] = min(self.box[0][1], box[0][1])
      self.box[1][0] = max(self.box[1][0], box[1][0])
      self.box[1][1] = max(self.box[1][1], box[1][1])
  def print_max(self):
    print 'Code to set maximum bbox:\nfrom matplotlib.transforms import Bbox\ntight_bbox = Bbox([[%f, %f], [%f, %f]])' % (
      self.box[0][0], self.box[0][1], self.box[1][0], self.box[1][1]
    )
like image 179
bluenote10 Avatar answered Jan 01 '23 09:01

bluenote10