-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
DOC: manually placing images example #28775
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
6e5e401
to
9bd9577
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems good to me (and also familiar… 😉). Just a few minor comments.
galleries/examples/images_contours_and_fields/image_exact_placement.py
Outdated
Show resolved
Hide resolved
galleries/examples/images_contours_and_fields/image_exact_placement.py
Outdated
Show resolved
Hide resolved
galleries/examples/images_contours_and_fields/image_exact_placement.py
Outdated
Show resolved
Hide resolved
galleries/examples/images_contours_and_fields/image_exact_placement.py
Outdated
Show resolved
Hide resolved
galleries/examples/images_contours_and_fields/image_exact_placement.py
Outdated
Show resolved
Hide resolved
galleries/examples/images_contours_and_fields/image_exact_placement.py
Outdated
Show resolved
Hide resolved
9bd9577
to
141f811
Compare
Thanks @rcomer - yes this came from the latest stack overflow to ask for this. It is asked periodically, and I did not see that we had a good example to point to. |
141f811
to
72cc35e
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suggest to add # sphinx_gallery_thumbnail_number = -1
so that the last image will be used as thumbnail.
72cc35e
to
567f867
Compare
Yes, good idea - I also toyed with the idea of making the default figure size more appropriate, but I think it's reasonable to use the default so folks can see how it can "go wrong". |
567f867
to
94da849
Compare
galleries/examples/images_contours_and_fields/image_exact_placement.py
Outdated
Show resolved
Hide resolved
galleries/examples/images_contours_and_fields/image_exact_placement.py
Outdated
Show resolved
Hide resolved
galleries/examples/images_contours_and_fields/image_exact_placement.py
Outdated
Show resolved
Hide resolved
galleries/examples/images_contours_and_fields/image_exact_placement.py
Outdated
Show resolved
Hide resolved
dpi = 100 # 100 pixels is one inch | ||
buffer = 0.35 * dpi # pixels | ||
left = buffer | ||
bottom = buffer | ||
ny, nx = np.shape(A) | ||
posA = [left, bottom, nx, ny] | ||
# we know this is tallest, so we can get the fig height: | ||
figh = bottom + ny + buffer | ||
|
||
# place the B axes | ||
left = left + nx + buffer | ||
ny, nx = np.shape(B) | ||
posB = [left, figh - buffer - ny, nx, ny] | ||
|
||
# get the fig width | ||
figw = left + nx + buffer | ||
|
||
fig = plt.figure(figsize=(figw / dpi, figh / dpi), facecolor='aliceblue') | ||
|
||
ax = fig.add_axes([posA[0] / figw, posA[1] / figh, posA[2] / figw, posA[3] / figh]) | ||
ax.imshow(A, vmin=-1, vmax=1) | ||
rect = mpatches.Rectangle((0, 0), 200, 40, linewidth=1, edgecolor='r', facecolor='none') | ||
ax.add_patch(rect) | ||
|
||
ax = fig.add_axes([posB[0] / figw, posB[1] / figh, posB[2] / figw, posB[3] / figh]) | ||
ax.imshow(B, vmin=-1, vmax=1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion structuring to make the calculations more understandable:
- Calculate desired figure height and width in pixel
- Create figure (needs calculation of figure size in inches)
- Add axes (needs calculation of dimensions in figure coordinates)
dpi = 100 # 100 pixels is one inch | |
buffer = 0.35 * dpi # pixels | |
left = buffer | |
bottom = buffer | |
ny, nx = np.shape(A) | |
posA = [left, bottom, nx, ny] | |
# we know this is tallest, so we can get the fig height: | |
figh = bottom + ny + buffer | |
# place the B axes | |
left = left + nx + buffer | |
ny, nx = np.shape(B) | |
posB = [left, figh - buffer - ny, nx, ny] | |
# get the fig width | |
figw = left + nx + buffer | |
fig = plt.figure(figsize=(figw / dpi, figh / dpi), facecolor='aliceblue') | |
ax = fig.add_axes([posA[0] / figw, posA[1] / figh, posA[2] / figw, posA[3] / figh]) | |
ax.imshow(A, vmin=-1, vmax=1) | |
rect = mpatches.Rectangle((0, 0), 200, 40, linewidth=1, edgecolor='r', facecolor='none') | |
ax.add_patch(rect) | |
ax = fig.add_axes([posB[0] / figw, posB[1] / figh, posB[2] / figw, posB[3] / figh]) | |
ax.imshow(B, vmin=-1, vmax=1) | |
margin_px = 35 # in pixels | |
ny_A, nx_A = np.shape(A) | |
ny_B, nx_B = np.shape(B) | |
fig_height_px = max(ny_A, ny_B) + 2 * margin_px | |
fig_width_px = nx_A + nx_B + 2 * margin_px | |
# calculate figsize in inches | |
figsize = (fig_width_px / dpi, fig_height_px / dpi) | |
fig = plt.figure(figsize=figsize, facecolor='aliceblue') | |
# add axes in figure coordinates | |
ax1 = fig.add_axes([ | |
margin_px / fig_width_px, # left | |
margin_px / fig_height_px, # bottom | |
nx_A / fig_width_px, # width | |
ny_A / fig_height_px # height | |
]) | |
ax2 = fig.add_axes([ | |
margin_px / fig_width_px, # left | |
(fig_height_px - margin_px - ny) / fig_height_px, # bottom | |
nx_B / fig_width_px, # width | |
ny_B / fig_height_px # height | |
]) | |
ax1.imshow(A, vmin=-1, vmax=1) | |
rect = mpatches.Rectangle((0, 0), 200, 40, linewidth=1, edgecolor='r', facecolor='none') | |
ax.add_patch(rect) | |
ax2.imshow(B, vmin=-1, vmax=1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A and B are purposefully different widths, so I don't think this is correct.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are right. The numbers / expressions are not correct. Still the idea (which possibly can be communicated more explicitly) is that you can place everything at exact pixel positions. And to do that, you have to follow the three steps above.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Possibly now correct. - I don't have an interpreter at hand, so can't check, but I hope you get the idea.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do get the idea, but not convinced pre-calculating the figure dimensions buys you anything. Also, imagine we were to add a third or fourth image in there, we would need to change the code in two places.
Just for context, I originally wrote as a for-loop to allow flexibility, but backed off on that as perhaps too magical.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just for context, I originally wrote as a for-loop to allow flexibility, but backed off on that as perhaps too magical.
👍 Examples must show the pattern, not necessarily a generic solution if that makes the code harder to understand.
I had difficulty following your code. Reflecting on it, the reasons were
- lines 100-115 define a lot of variables, which are sometimes
a) tersely named (figh
)
b) cryptic data structuresposB = [left, figh - buffer - ny, nx, ny]
c) a mixture of definitions for later use (e.g.figh
) and temporary local variables that are reassigned (left
,nx
, `ny´) - It's unclear that these are all in pixels
- Transformation calculations to inches (for figsize) and figure units (for add_axes) are hard to understand, because both the input units (see 2.) and the resulting units are not clear.
I tried a more minimal refactor of your code to alleviate these points, but I ended up with almost the same code as I've proposed above 🤷♂️.
94da849
to
470fb6f
Compare
470fb6f
to
6faa8e0
Compare
6faa8e0
to
ea6cad9
Compare
The doc build is failing with
|
Please rebase. #28779 fixes the doc build. |
ea6cad9
to
1c67870
Compare
I'm +1 on adjusting the figure size right away for all figures. Adjusting the figsize is something one would always do if the default aspect is not a good fit for the content. This is something we just do as needed without talking about it (example: https://matplotlib.org/stable/gallery/lines_bars_and_markers/categorical_variables.html). Just looking at the figures, one could get the impression, that it's a feature of the "manual" approach that you don't have the large figure whitespace. To take this effect out, all figures should have the same (or similar) size. |
I'm not sure I agree with that. Of course people should change the figure size, but there is endless confusion about fixed ratio images and why there is so much white space. If we show the white space, I think on the balance we are helping folks understand this is not something matplotlib will do for them automagically. |
galleries/examples/images_contours_and_fields/image_exact_placement.py
Outdated
Show resolved
Hide resolved
# helped because the axes are wider than they are tall. For more complicated | ||
# situations it is necessary to place the axes manually. | ||
# | ||
# Manual placement |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a bit torn on the motivation for this: You can get away without by using an appropriate figsize (which is much simpler).
The advantages I see is
- pixel-perfect positioning (if you need that)
- in this case 1:1 mapping from data points to visual pixels.
Maybe these should be highlighted more.
@timhoffm perhaps this is worse, but this change I don't set the figsize for the first "default" plot, but then do set it for the customized plots, with a bit of explanation. That doesn't make the narrative shorter, but it does go through the steps in the way a user may go through them. |
I like this. It's clear without making too much fuss about the figsize topic. |
galleries/examples/images_contours_and_fields/image_exact_placement.py
Outdated
Show resolved
Hide resolved
galleries/examples/images_contours_and_fields/image_exact_placement.py
Outdated
Show resolved
Hide resolved
galleries/examples/images_contours_and_fields/image_exact_placement.py
Outdated
Show resolved
Hide resolved
2c77577
to
c55cfb0
Compare
Co-authored-by: Tim Hoffmann <[email protected]>
28a5c4a
to
30a2a9f
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Take or leave the comments. May self-merge afterwards.
fit inside the parent `~.axes.Axes`. This can mean that images that have very | ||
different original sizes can end up appearing similar in size. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Optional: Slightly more concise wording:
fit inside the parent `~.axes.Axes`. This can mean that images that have very | |
different original sizes can end up appearing similar in size. | |
fit inside the parent `~.axes.Axes`. As a result, images with very different | |
original sizes may end up appearing similar in size. |
galleries/examples/images_contours_and_fields/image_exact_placement.py
Outdated
Show resolved
Hide resolved
galleries/examples/images_contours_and_fields/image_exact_placement.py
Outdated
Show resolved
Hide resolved
Co-authored-by: Tim Hoffmann <[email protected]>
New example showing a couple of methods for dealing with images that you want to keep the same reelative size.