1

I've got code that produces a square image with smaller plots to the left and below the main image plot by using GridSpec with width_ratios and height_ratios:

plot with secondary axes left and below the main image axis

import matplotlib.pyplot as plt
import numpy as np

# Some fake data.
imdata = np.random.random((100, 100))
extradata1 = np.max(imdata, axis=1)
extradata2 = np.max(imdata, axis=0)

fig = plt.figure(constrained_layout=True)
spec = fig.add_gridspec(ncols=2, nrows=2, width_ratios=(1, 8), height_ratios=(8, 1))

# Main image plot.
ax1 = fig.add_subplot(spec[:-1, 1:], aspect='equal')
ax1.imshow(imdata, cmap='viridis')

# Vertical (left) plot.
ax2 = fig.add_subplot(spec[:-1, 0], sharey=ax1)
ax2.plot(extradata1, range(imdata.shape[0]))

# Horizontal (bottom) plot.
ax3 = fig.add_subplot(spec[-1, 1:], sharex=ax1)
ax3.plot(range(imdata.shape[1]), extradata2)

plt.show()

I'd like the height of the left plot and the width of the bottom plot to be equal to the height and width of the main image, respectively. Currently as you can see the horizontal plot is wider than the image's horizontal size, and they also scale differently as the figure is scaled. Is it possible to constrain axis dimensions to those of other axes?

Sean
  • 1,346
  • 13
  • 24

3 Answers3

1

Calling imshow() with aspect='auto' should fix your problem:

ax1.imshow(imdata, cmap='viridis',aspect='auto')

For some more explanation on this, please look here: Imshow: extent and aspect

import matplotlib.pyplot as plt
import numpy as np

# Some fake data.
imdata = np.random.random((100, 100))
extradata1 = np.max(imdata, axis=1)
extradata2 = np.max(imdata, axis=0)

fig = plt.figure(constrained_layout=True)
spec = fig.add_gridspec(ncols=2, nrows=2, width_ratios=(1, 8), height_ratios=(8, 1))

# Main image plot.
ax1 = fig.add_subplot(spec[:-1, 1:])
ax1.imshow(imdata, cmap='viridis',aspect='auto')

# Vertical (left) plot.
ax2 = fig.add_subplot(spec[:-1, 0], sharey=ax1)
ax2.plot(extradata1, range(imdata.shape[0]))

# Horizontal (bottom) plot.
ax3 = fig.add_subplot(spec[-1, 1:], sharex=ax1)
ax3.plot(range(imdata.shape[1]), extradata2)

Result:

enter image description here

Fourier
  • 2,795
  • 3
  • 25
  • 39
  • That doesn't work for me. In any case, is setting the aspect of `ax1` irrelevant here? I want `ax2` and `ax3` to take their size from `ax1`. – Sean Dec 06 '19 at 12:11
  • @Sean The moment the other plots are drawn, the `imshow`'s aspect param is overwritten. – Fourier Dec 06 '19 at 12:16
  • 1
    Ah, I was setting `aspect` on the `fig.add_subplot` call, not the `ax1.imshow` call that you suggested. Doing it on `ax1.imshow` worked. Thanks! – Sean Dec 06 '19 at 12:23
  • @Sean Great, I was worrying already about `matplotlib` versions. – Fourier Dec 06 '19 at 12:25
1

Fourier's answer worked nicely, but I also found that I could get the desired behaviour by changing constrained_layout=True to constrained_layout=False in the plt.figure call.

Sean
  • 1,346
  • 13
  • 24
1

Using aspect aspect="auto" works, it has the disadvantage of giving you non-square pixels.

For this kind of tasks, I found that the axes_grid toolkit is pretty useful

from mpl_toolkits.axes_grid1 import make_axes_locatable

# Some fake data.
imdata = np.random.random((100, 100))
extradata1 = np.max(imdata, axis=1)
extradata2 = np.max(imdata, axis=0)

fig, main_ax = plt.subplots()
divider = make_axes_locatable(main_ax)
bottom_ax = divider.append_axes("bottom", 1.2, pad=0.1, sharex=main_ax)
left_ax = divider.append_axes("left", 1.2, pad=0.1, sharey=main_ax)

bottom_ax.xaxis.set_tick_params(labelbottom=False)
left_ax.yaxis.set_tick_params(labelleft=False)


main_ax.imshow(imdata, cmap='viridis')
left_ax.plot(extradata1, range(imdata.shape[0]))
bottom_ax.plot(range(imdata.shape[1]), extradata2)

plt.show()

enter image description here

Diziet Asahi
  • 38,379
  • 7
  • 60
  • 75