I would like to create a square plot using multiple axes using make_axes_locateable as demonstrated in the matplotlib documentation. However, while this works o
The axes_grid1's Divider works a bit differently than usual subplots. It cannot directly cope with aspects, because the size of the axes is determined at draw time either in relative or absolute coordinates.
If you want, you can manually specify the axes size in absolute coordinates to obtain a square subplot.
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable, axes_size
x = np.random.normal(512, 112, 240)
y = np.random.normal(0.5, 0.1, 240)
_, ax = plt.subplots()
divider = make_axes_locatable(ax)
xhax = divider.append_axes("top", size=1, pad=0.1, sharex=ax)
yhax = divider.append_axes("right", size=1, pad=0.1, sharey=ax)
horiz = [axes_size.Fixed(2.8), axes_size.Fixed(.1), axes_size.Fixed(1)]
vert = [axes_size.Fixed(2.8), axes_size.Fixed(.1), axes_size.Fixed(1)]
divider.set_horizontal(horiz)
divider.set_vertical(vert)
ax.scatter(x, y)
xhax.hist(x)
yhax.hist(y, orientation="horizontal")
plt.setp(xhax.get_xticklabels(), visible=False)
plt.setp(yhax.get_yticklabels(), visible=False)
plt.show()
This solution is robust against figure size changes in the sense that the grid is always 2.8 + 0.1 + 1 = 3.9 inches wide and heigh. So one just needs to make sure the figure size is always large enough to host the grid. Else it might crop the marginal plots and look like this:
To have an adaptive solution that would still scale with the figure size, one could define a custom Size, which takes the remainder of the absolutely sizes padding and marginal axes and returns the minimum of those in absolute coordinates (inches), for both directions such that the axes is always square.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.transforms import Bbox
from mpl_toolkits.axes_grid1 import make_axes_locatable, axes_size
class RemainderFixed(axes_size.Scaled):
def __init__(self, xsizes, ysizes, divider):
self.xsizes =xsizes
self.ysizes =ysizes
self.div = divider
def get_size(self, renderer):
xrel, xabs = axes_size.AddList(self.xsizes).get_size(renderer)
yrel, yabs = axes_size.AddList(self.ysizes).get_size(renderer)
bb = Bbox.from_bounds(*self.div.get_position()).transformed(self.div._fig.transFigure)
w = bb.width/self.div._fig.dpi - xabs
h = bb.height/self.div._fig.dpi - yabs
return 0, min([w,h])
x = np.random.normal(512, 112, 240)
y = np.random.normal(0.5, 0.1, 240)
fig, ax = plt.subplots()
divider = make_axes_locatable(ax)
margin_size = axes_size.Fixed(1)
pad_size = axes_size.Fixed(.1)
xsizes = [pad_size, margin_size]
ysizes = xsizes
xhax = divider.append_axes("top", size=margin_size, pad=pad_size, sharex=ax)
yhax = divider.append_axes("right", size=margin_size, pad=pad_size, sharey=ax)
divider.set_horizontal([RemainderFixed(xsizes, ysizes, divider)] + xsizes)
divider.set_vertical([RemainderFixed(xsizes, ysizes, divider)] + ysizes)
ax.scatter(x, y)
xhax.hist(x)
yhax.hist(y, orientation="horizontal")
plt.setp(xhax.get_xticklabels(), visible=False)
plt.setp(yhax.get_yticklabels(), visible=False)
plt.show()
Note how the sizes of the marginals is always 1 inch, independent of the figure size how the scatter axes adjusts to the remaining space and stays square.