Issue
Starting from an already existing matplotlib figure I added a footer by appending a small axis to the bottom. The idea is to fill this footer with an arbitrary number of logos. I want to place the logos side by side (separated by a user controllable pad parameter) so that they fill the footer in height, thus computing their width so as to maintain the aspect ratio locked.
This is a standalone MRE which summarizes my approach
from matplotlib.image import imread as read_png
import matplotlib.cbook as cbook
import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.axes_grid1 import make_axes_locatable
from matplotlib.offsetbox import AnnotationBbox, OffsetImage
fig = plt.figure(1, figsize=(6, 6))
ax = plt.gca()
ax.set_axis_off()
X, Y = np.meshgrid(np.linspace(-3.0, 3.0, 100),
np.linspace(-2.0, 2.0, 100))
Z1 = np.exp(-X**2 - Y**2)
Z2 = np.exp(-(X * 10)**2 - (Y * 10)**2)
z = Z1 * 30 + 50 * Z2
cs = ax.contourf(X, Y, z, cmap='PuBu_r')
ax_divider = make_axes_locatable(ax)
ax_footer = ax_divider.append_axes(
"bottom", size="10%", pad=0.1)
ax_footer.set_axis_off()
def add_logo(ax, logo, zoom, pos):
img_logo = OffsetImage(read_png(logo), zoom=zoom)
logo_ann = AnnotationBbox(
img_logo, pos, xycoords='axes fraction',
frameon=False, box_alignment=(0, 0.25))
at = ax.add_artist(logo_ann)
return at
add_logo(ax_footer,
cbook.get_sample_data('grace_hopper.jpg'),
zoom=0.07,
pos=(0, 0))
add_logo(ax_footer,
cbook.get_sample_data('grace_hopper.jpg'),
zoom=0.07,
pos=(0.2, 0))
plt.show()
which produces
However I have to explicitly set up zoom
and pos
so that the figures occupy the right space.
Is there a way to automatically compute these parameters so that the figures are automatically placed next to each other and they fill the footer axis? Or maybe there is a better approach to that?
I saw many examples defining a subplots with different rows and columns to place the figures but I already have the axis. I haven't found any way to split an already existing axis into an arbitrary number of inset axes of the same width.
-------- EDIT --------------
Expanding on the amazing answer of Jody Klymak this is my "final" version. Once you have a generic axis to put the logos in (I still use make_axes_locatable
and append_axes
) you can just use this function
def add_logos(ax, logos, x_padding=10):
left = 0
for _, logo in enumerate(logos):
imdata = read_png(logo)
w = imdata.shape[1]
h = imdata.shape[0]
ax.imshow(imdata,
extent=[left, left + w, 0, h])
left = left + w + x_padding
# set the limits, otherwise the focus in on the last image
ax.set_xlim(0, left)
ax.set_ylim(0, h)
# anchor the fixed aspect-ratio axes on the left.
ax.set_anchor('W')
To be used as
add_logos(ax_footer, ["../input/logos/Logonuovo.png",
"../input/logos/earth_networks_logo.png"])
Solution
This requires a bit of book keeping, and buffer
is hardwired, but it stacks images next to each other in a relatively straightforward way. Do ax_footer.set_axis_on()
to see what is going on more clearly. But basically this puts all the logos on an axes, and uses the extent
keyword argument of imshow
to place them appropriately.
Note this is relatively robust to different sized logos. If you want the logos aligned vertically, you can do that too.
There are ways to do this with HPacker
s etc, but they are needlessly complicated in my opinion.
from matplotlib.image import imread as read_png
import matplotlib.cbook as cbook
import matplotlib.pyplot as plt
import numpy as np
fig, axs = plt.subplot_mosaic([['a'], ['b']], height_ratios=[1, 0.3],
figsize=(4, 4), layout='constrained')
X, Y = np.meshgrid(np.linspace(-3.0, 3.0, 100),
np.linspace(-2.0, 2.0, 100))
Z1 = np.exp(-X**2 - Y**2)
Z2 = np.exp(-(X * 10)**2 - (Y * 10)**2)
z = Z1 * 30 + 50 * Z2
ax = axs['a']
cs = ax.contourf(X, Y, z, cmap='PuBu_r')
ax.set_axis_off()
ax_footer = axs['b']
ax_footer.set_axis_off()
nlogo = 3
left = 0
buffer = 30
maxy = -100
for i in range(nlogo):
logo = cbook.get_sample_data('grace_hopper.jpg')
imdata = read_png(logo)[:-(i*80+1), :]
w = imdata.shape[1]
h = imdata.shape[0]
ax_footer.imshow(imdata, extent=[left, left + w, 0, h])
left = left + w + buffer
maxy = np.max([maxy, h])
# set the limits, otherwise the focus in on the last image
ax_footer.set_xlim(0, left)
ax_footer.set_ylim(0, maxy)
# anchor the fixed aspect-ratio axes on the left.
ax_footer.set_anchor('W')
plt.show()
Answered By - Jody Klymak
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.