1

The problem

I am trying to plot an image next to some data. However, I would like the image to expand to be flush with the plot labels. For example, the following code (using this tutorial image):

# make the incorrect figure
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
fig = plt.figure(figsize=(8,3))
ax_image = plt.subplot(1,2,1)
plt.imshow(mpimg.imread('stinkbug.png'))
plt.subplot(1,2,2)
plt.plot([0,1],[2,3])
plt.ylabel("y")
plt.xlabel("want image flush with bottom of this label")
fig.tight_layout()
ax_image.axis('off')
fig.savefig("incorrect.png")

yields this plot, with extra whitespace:

incorrect image

The hacky attempt at a solution

I would like a plot that doesn't waste whitespace. The following hacky code (in the vein of this SO link) accomplishes this:

# make the correct figure with manually changing the size
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
fig = plt.figure(figsize=(8,3))
ax_image = fig.add_axes([0.02,0,0.45,1.0])
plt.imshow(mpimg.imread('stinkbug.png'))
plt.subplot(1,2,2)
plt.plot([0,1],[2,3])
plt.ylabel("y")
plt.xlabel("want image flush with bottom of this label")
fig.tight_layout()
ax_image.axis('off')
fig.savefig("correct.png")

yielding the following figure:

correct image

The question

Is there any way to plot an image flush with other subplots, without having to resort to manual adjustment of figure sizes?

heisenBug
  • 865
  • 9
  • 19
  • If the motivation is not to waste space, the bottom of the image's ticklabels should be aligned with the bottom of the plot's xlabel, right? – ImportanceOfBeingErnest Aug 31 '18 at 15:25
  • That is a good point. My final image won't have any ticks or tick labels, just an internal scalebar. I will edit the question to remove the ticks. I would like the bottom of the image flush with the bottom of the x label of the other subplot. – heisenBug Aug 31 '18 at 15:26
  • Ok so there is a problem when the image needs to keep its equal aspect ratio but is enlarged as shown here, as it might then overlap the right axes. Should the right axes' width then shrink to make more space for the image? Should equal aspect be given up? – ImportanceOfBeingErnest Aug 31 '18 at 16:00
  • The best thing would be to keep the aspect ratio at whatever it was, increasing both dimensions of the image so that the image is flush. Essentially 'resizing' the image so that it maintains its aspect ratio, but is just larger. – heisenBug Aug 31 '18 at 17:50
  • Ok, done. Just note that it might lead to undesired results when using images with smaller height to width ratio. – ImportanceOfBeingErnest Aug 31 '18 at 17:59

1 Answers1

1

You may get the union of the bounding box of the right axes and its label and set the position of the left axes such that it starts at the vertial position of the union bounding box. The following assumes some automatic aspect on the image, i.e. the image is skewed in one direction.

import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import matplotlib.transforms as mtrans

fig, (ax_image, ax) = plt.subplots(ncols=2, figsize=(8,3))

ax_image.imshow(mpimg.imread('https://matplotlib.org/_images/stinkbug.png'))
ax_image.set_aspect("auto")
ax.plot([0,1],[2,3])
ax.set_ylabel("y")
xlabel = ax.set_xlabel("want image flush with bottom of this label")
fig.tight_layout()
ax_image.axis('off')

fig.canvas.draw()
xlabel_bbox = ax.xaxis.get_tightbbox(fig.canvas.get_renderer())
xlabel_bbox = xlabel_bbox.transformed(fig.transFigure.inverted())
bbox1 = ax.get_position().union((ax.get_position(),xlabel_bbox))
bbox2 = ax_image.get_position()
bbox3 = mtrans.Bbox.from_bounds(bbox2.x0, bbox1.y0, bbox2.width, bbox1.height)
ax_image.set_position(bbox3)

plt.show()

enter image description here

When keeping the aspect constant, you may let the image scale up in width-direction. This has the drawback that it might overlay the right axes or exceed the figure to the left.

import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import matplotlib.transforms as mtrans

fig, (ax_image, ax) = plt.subplots(ncols=2, figsize=(8,3))

ax_image.imshow(mpimg.imread('https://matplotlib.org/_images/stinkbug.png'))
ax.plot([0,1],[2,3])
ax.set_ylabel("y")
xlabel = ax.set_xlabel("want image flush with bottom of this label")
fig.tight_layout()
ax_image.axis('off')

fig.canvas.draw()
xlabel_bbox = ax.xaxis.get_tightbbox(fig.canvas.get_renderer())
xlabel_bbox = xlabel_bbox.transformed(fig.transFigure.inverted())
bbox1 = ax.get_position().union((ax.get_position(),xlabel_bbox))
bbox2 = ax_image.get_position()
aspect=bbox2.height/bbox2.width
bbox3 = mtrans.Bbox.from_bounds(bbox2.x0-(bbox1.height/aspect-bbox2.width)/2., 
                                bbox1.y0, bbox1.height/aspect, bbox1.height)
ax_image.set_position(bbox3)

plt.show()

enter image description here

ImportanceOfBeingErnest
  • 321,279
  • 53
  • 665
  • 712