|
| 1 | +""" |
| 2 | +========================================= |
| 3 | +Placing images, preserving relative sizes |
| 4 | +========================================= |
| 5 | +
|
| 6 | +By default Matplotlib resamples images created with `~.Axes.imshow` to |
| 7 | +fit inside the parent `~.axes.Axes`. This can mean that images that have very |
| 8 | +different original sizes can end up appearing similar in size. |
| 9 | +
|
| 10 | +Sometimes, however, it is desirable to keep the images the same relative size, or |
| 11 | +even to make the images keep exactly the same pixels as the original data. |
| 12 | +Matplotlib does not automatically make either of these things happen, |
| 13 | +but it is possible with some manual manipulation. |
| 14 | +
|
| 15 | +Preserving relative sizes |
| 16 | +========================= |
| 17 | +
|
| 18 | +If the size of the images are amenable, we can preserve the relative sizes of two |
| 19 | +images by using either the *width_ratio* or *height_ratio* of the subplots. Which |
| 20 | +one you use depends on the shape of the image and the size of the figure. |
| 21 | +
|
| 22 | +By default the two images are made a similar size, despite one being 1.5 times the width |
| 23 | +of the other: |
| 24 | +""" |
| 25 | + |
| 26 | +# sphinx_gallery_thumbnail_number = -1 |
| 27 | + |
| 28 | +import matplotlib.pyplot as plt |
| 29 | +import numpy as np |
| 30 | + |
| 31 | +import matplotlib.patches as mpatches |
| 32 | + |
| 33 | +# make the data: |
| 34 | +N = 450 |
| 35 | +x = np.arange(N) / N |
| 36 | +y = np.arange(N) / N |
| 37 | + |
| 38 | +X, Y = np.meshgrid(x, y) |
| 39 | +R = np.sqrt(X**2 + Y**2) |
| 40 | +f0 = 5 |
| 41 | +k = 100 |
| 42 | +a = np.sin(np.pi * 2 * (f0 * R + k * R**2 / 2)) |
| 43 | +A = a[:100, :300] |
| 44 | +B = A[:40, :200] |
| 45 | + |
| 46 | +# plot with default axes handling: |
| 47 | +fig, axs = plt.subplots(1, 2, facecolor='aliceblue') |
| 48 | + |
| 49 | +axs[0].imshow(A, vmin=-1, vmax=1) |
| 50 | +axs[1].imshow(B, vmin=-1, vmax=1) |
| 51 | +rect = mpatches.Rectangle((0, 0), 200, 40, linewidth=1, edgecolor='r', facecolor='none') |
| 52 | +axs[0].add_patch(rect) |
| 53 | + |
| 54 | +# %% |
| 55 | +# Note that both images are rendered at a 1:1 ratio, but are made to look almost the |
| 56 | +# same width, despite image B being smaller than image A. |
| 57 | +# |
| 58 | +# We can control the relative sizes using the *width_ratios* argument *if* the images |
| 59 | +# are wider than they are tall and shown side by side, as is the case here. |
| 60 | + |
| 61 | +fig, axs = plt.subplots(1, 2, width_ratios=[300/200, 1], facecolor='aliceblue') |
| 62 | + |
| 63 | +axs[0].imshow(A, vmin=-1, vmax=1) |
| 64 | +rect = mpatches.Rectangle((0, 0), 200, 40, linewidth=1, edgecolor='r', facecolor='none') |
| 65 | +axs[0].add_patch(rect) |
| 66 | +axs[1].imshow(B, vmin=-1, vmax=1) |
| 67 | + |
| 68 | +# %% |
| 69 | +# Given that the data subsample is in the upper left of the larger image, |
| 70 | +# it might make sense if the top of the smaller Axes aligned with the top of the larger. |
| 71 | +# This can be done manually by using `~.Axes.set_anchor`, and using "NW" (for |
| 72 | +# northwest). |
| 73 | + |
| 74 | +fig, axs = plt.subplots(1, 2, width_ratios=[300/200, 1], facecolor='aliceblue') |
| 75 | + |
| 76 | +axs[0].imshow(A, vmin=-1, vmax=1) |
| 77 | +rect = mpatches.Rectangle((0, 0), 200, 40, linewidth=1, edgecolor='r', facecolor='none') |
| 78 | +axs[0].add_patch(rect) |
| 79 | +axs[0].set_anchor('NW') |
| 80 | +axs[1].imshow(B, vmin=-1, vmax=1) |
| 81 | +axs[1].set_anchor('NW') |
| 82 | + |
| 83 | +# %% |
| 84 | +# Note that this procedure still leaves large white spaces (that can be trimmed |
| 85 | +# in a final product by ``bbox_inches="tight"`` in `~.Figure.savefig`), and is |
| 86 | +# not very general. For instance, if the axes had been arranged vertically |
| 87 | +# instead of horizontally, setting the height aspect ratio would not have |
| 88 | +# helped because the axes are wider than they are tall. For more complicated |
| 89 | +# situations it is necessary to place the axes manually. |
| 90 | +# |
| 91 | +# Manual placement |
| 92 | +# ================ |
| 93 | +# |
| 94 | +# We can manually place axes when they are created by passing a position to |
| 95 | +# `~.Figure.add_axes`. This position takes the form ``[left bottom width height]`` and |
| 96 | +# is in units that are a fraction of the figure width and height. Here we decide how |
| 97 | +# large to make the axes based on the size of the images, and add a small buffer of |
| 98 | +# 0.35 inches. We do all this at 100 dpi. |
| 99 | + |
| 100 | +dpi = 100 # 100 pixels is one inch |
| 101 | +buffer = 0.35 * dpi # pixels |
| 102 | +left = buffer |
| 103 | +bottom = buffer |
| 104 | +ny, nx = np.shape(A) |
| 105 | +posA = [left, bottom, nx, ny] |
| 106 | +# we know this is tallest, so we can get the fig height: |
| 107 | +figh = bottom + ny + buffer |
| 108 | + |
| 109 | +# place the B axes |
| 110 | +left = left + nx + buffer |
| 111 | +ny, nx = np.shape(B) |
| 112 | +posB = [left, figh - buffer - ny, nx, ny] |
| 113 | + |
| 114 | +# get the fig width |
| 115 | +figw = left + nx + buffer |
| 116 | + |
| 117 | +fig = plt.figure(figsize=(figw / dpi, figh / dpi), facecolor='aliceblue') |
| 118 | + |
| 119 | +ax = fig.add_axes([posA[0] / figw, posA[1] / figh, posA[2] / figw, posA[3] / figh]) |
| 120 | +ax.imshow(A, vmin=-1, vmax=1) |
| 121 | +rect = mpatches.Rectangle((0, 0), 200, 40, linewidth=1, edgecolor='r', facecolor='none') |
| 122 | +ax.add_patch(rect) |
| 123 | + |
| 124 | +ax = fig.add_axes([posB[0] / figw, posB[1] / figh, posB[2] / figw, posB[3] / figh]) |
| 125 | +ax.imshow(B, vmin=-1, vmax=1) |
| 126 | + |
| 127 | +# %% |
| 128 | +# Inspection of the image will show that it is exactly 3* 35 + 300 + 200 = 605 |
| 129 | +# pixels wide, and 2 * 35 + 100 = 170 pixels high (or twice that if the 2x |
| 130 | +# version is used by the browser instead). The images should be rendered with |
| 131 | +# exactly 1 pixel per data point (or four, if 2x). |
0 commit comments