Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 470fb6f

Browse files
committed
DOC: manually placing images example
1 parent b01462c commit 470fb6f

File tree

4 files changed

+181
-0
lines changed

4 files changed

+181
-0
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
``transforms.AffineDeltaTransform`` updates correctly on axis limit changes
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3+
4+
Before this change, transform sub-graphs with ``AffineDeltaTransform`` did not update correctly.
5+
This PR ensures that changes to the child transform are passed through correctly.
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
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+
By default the two images are made a similar size, despite one being 1.5 times the width
19+
of the other:
20+
"""
21+
22+
# sphinx_gallery_thumbnail_number = -1
23+
24+
import matplotlib.pyplot as plt
25+
import numpy as np
26+
27+
import matplotlib.patches as mpatches
28+
29+
# make the data:
30+
N = 450
31+
x = np.arange(N) / N
32+
y = np.arange(N) / N
33+
34+
X, Y = np.meshgrid(x, y)
35+
R = np.sqrt(X**2 + Y**2)
36+
f0 = 5
37+
k = 100
38+
a = np.sin(np.pi * 2 * (f0 * R + k * R**2 / 2))
39+
A = a[:100, :300]
40+
B = A[:40, :200]
41+
42+
# plot with default axes handling:
43+
fig, axs = plt.subplots(1, 2, facecolor='aliceblue')
44+
45+
axs[0].imshow(A, vmin=-1, vmax=1)
46+
axs[1].imshow(B, vmin=-1, vmax=1)
47+
48+
49+
def annotate_rect(ax):
50+
# add a rectangle that is the size of the B matrix
51+
rect = mpatches.Rectangle((0, 0), 200, 40, linewidth=1,
52+
edgecolor='r', facecolor='none')
53+
ax.add_patch(rect)
54+
return rect
55+
56+
annotate_rect(axs[0])
57+
58+
# %%
59+
# Note that both images are rendered at a 1:1 ratio, but are made to look almost the
60+
# same width, despite image B being smaller than image A.
61+
#
62+
# If the size of the images are amenable, we can preserve the relative sizes of two
63+
# images by using either the *width_ratio* or *height_ratio* of the subplots. Which
64+
# one you use depends on the shape of the image and the size of the figure.
65+
# We can control the relative sizes using the *width_ratios* argument *if* the images
66+
# are wider than they are tall and shown side by side, as is the case here.
67+
68+
fig, axs = plt.subplots(1, 2, width_ratios=[300/200, 1], facecolor='aliceblue')
69+
70+
axs[0].imshow(A, vmin=-1, vmax=1)
71+
annotate_rect(axs[0])
72+
73+
axs[1].imshow(B, vmin=-1, vmax=1)
74+
75+
# %%
76+
# Given that the data subsample is in the upper left of the larger image,
77+
# it might make sense if the top of the smaller Axes aligned with the top of the larger.
78+
# This can be done manually by using `~.Axes.set_anchor`, and using "NW" (for
79+
# northwest).
80+
81+
fig, axs = plt.subplots(1, 2, width_ratios=[300/200, 1], facecolor='aliceblue')
82+
83+
axs[0].imshow(A, vmin=-1, vmax=1)
84+
annotate_rect(axs[0])
85+
86+
axs[0].set_anchor('NW')
87+
axs[1].imshow(B, vmin=-1, vmax=1)
88+
axs[1].set_anchor('NW')
89+
90+
# %%
91+
# Note that this procedure still leaves large white spaces (that can be trimmed
92+
# in a final product by ``bbox_inches="tight"`` in `~.Figure.savefig`), and is
93+
# not very general. For instance, if the axes had been arranged vertically
94+
# instead of horizontally, setting the height aspect ratio would not have
95+
# helped because the axes are wider than they are tall. For more complicated
96+
# situations it is necessary to place the axes manually.
97+
#
98+
# Manual placement
99+
# ================
100+
#
101+
# We can manually place axes when they are created by passing a position to
102+
# `~.Figure.add_axes`. This position takes the form ``[left bottom width height]`` and
103+
# is in units that are a fraction of the figure width and height. Here we decide how
104+
# large to make the axes based on the size of the images, and add a small buffer of
105+
# 0.35 inches. We do all this at 100 dpi.
106+
107+
dpi = 100 # 100 pixels is one inch
108+
109+
# All variables from here are in pixels:
110+
buffer = 0.35 * dpi # pixels
111+
112+
# Get the position of A axes
113+
left = buffer
114+
bottom = buffer
115+
ny, nx = np.shape(A)
116+
posA = [left, bottom, nx, ny]
117+
# we know this is tallest, so we can already get the fig height (in pixels)
118+
fig_height = bottom + ny + buffer
119+
120+
# place the B axes to the right of the A axes
121+
left = left + nx + buffer
122+
123+
ny, nx = np.shape(B)
124+
# align the bottom so that the top lines up with the top of the A axes:
125+
bottom = fig_height - buffer - ny
126+
posB = [left, bottom, nx, ny]
127+
128+
# now we can get the fig width (in pixels)
129+
fig_width = left + nx + buffer
130+
131+
# figsize must be in inches:
132+
fig = plt.figure(figsize=(fig_width / dpi, fig_height / dpi), facecolor='aliceblue')
133+
134+
# the position posA must be normalized by the figure width and height:
135+
ax = fig.add_axes([posA[0] / fig_width, posA[1] / fig_height,
136+
posA[2] / fig_width, posA[3] / fig_height])
137+
ax.imshow(A, vmin=-1, vmax=1)
138+
annotate_rect(ax)
139+
140+
ax = fig.add_axes([posB[0] / fig_width, posB[1] / fig_height,
141+
posB[2] / fig_width, posB[3] / fig_height])
142+
ax.imshow(B, vmin=-1, vmax=1)
143+
144+
# %%
145+
# Inspection of the image will show that it is exactly 3* 35 + 300 + 200 = 605
146+
# pixels wide, and 2 * 35 + 100 = 170 pixels high (or twice that if the 2x
147+
# version is used by the browser instead). The images should be rendered with
148+
# exactly 1 pixel per data point (or four, if 2x).

lib/matplotlib/tests/test_transforms.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,31 @@ def test_deepcopy(self):
341341
assert_array_equal(s.get_matrix(), a.get_matrix())
342342

343343

344+
class TestAffineDeltaTransform:
345+
def test_invalidate(self):
346+
before = np.array([[1.0, 4.0, 0.0],
347+
[5.0, 1.0, 0.0],
348+
[0.0, 0.0, 1.0]])
349+
after = np.array([[1.0, 3.0, 0.0],
350+
[5.0, 1.0, 0.0],
351+
[0.0, 0.0, 1.0]])
352+
353+
# Translation and skew present
354+
base = mtransforms.Affine2D.from_values(1, 5, 4, 1, 2, 3)
355+
t = mtransforms.AffineDeltaTransform(base)
356+
assert_array_equal(t.get_matrix(), before)
357+
358+
# Mess with the internal structure of `base` without invalidating
359+
# This should not affect this transform because it's a passthrough:
360+
# it's always invalid
361+
base.get_matrix()[0, 1:] = 3
362+
assert_array_equal(t.get_matrix(), after)
363+
364+
# Invalidate the base
365+
base.invalidate()
366+
assert_array_equal(t.get_matrix(), after)
367+
368+
344369
def test_non_affine_caching():
345370
class AssertingNonAffineTransform(mtransforms.Transform):
346371
"""

lib/matplotlib/transforms.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2721,9 +2721,12 @@ class AffineDeltaTransform(Affine2DBase):
27212721
This class is experimental as of 3.3, and the API may change.
27222722
"""
27232723

2724+
pass_through = True
2725+
27242726
def __init__(self, transform, **kwargs):
27252727
super().__init__(**kwargs)
27262728
self._base_transform = transform
2729+
self.set_children(transform)
27272730

27282731
__str__ = _make_str_method("_base_transform")
27292732

0 commit comments

Comments
 (0)