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

Skip to content

Allow figsize to accept a single float that will scale the rcParam[figsize] #18244

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

Closed
ianhi opened this issue Aug 13, 2020 · 23 comments
Closed
Labels
New feature status: closed as inactive Issues closed by the "Stale" Github Action. Please comment on any you think should still be open. status: inactive Marked by the “Stale” Github Action status: needs comment/discussion needs consensus on next step

Comments

@ianhi
Copy link
Contributor

ianhi commented Aug 13, 2020

Problem

I often want to make a figure that is larger than what I have in my rcParams. However, I would also like to keep the same aspect ratio for this new figure, unfortunately this then requires that I do mental math 😞. For example if I want a figure be to be 1.5 times larger than the rcDefault size of [6.4, 4.8] then I will have to multiply in my head or add a few lines of code. Instead I normally just make a haphazard guess of two numbers of that I hope will result in a similar aspect ratio.

Proposed Solution

Modify the figsize argument to plt.figure to also accept a single float value. When given as (float, float) the original behavior will remain. When given as float then the figsize will be computed as a multiple of the rcParam figsize. Like so:

from collections import Iterable
def figure(figsize=1,*args,**kwargs):
    if not isinstance(figsize, Iterable):
        figsize = [figsize*x for x in rcParams['figure.figsize']]
    return plt.figure(figsize=figsize,*args,**kwargs)

Additional context and prior art

This is somewhat redundant with the dpi argument, but I think the proposed solution is more intuitive.

@jklymak
Copy link
Member

jklymak commented Aug 13, 2020

That doesn't seem completely unreasonable. OTOH, it is idiosyncratic. For instance, I usually want the width of my figures to stay the same (so they fit on a journal page), and the height to change.

This is somewhat redundant with the dpi argument, but I think the proposed solution is more intuitive.

They are not redundant! If you have the letter "A" at 12 points on a figure, figsize says how much of that figure the "A" takes up. dpi says how many dots are used to represent it in a rasterized backend, and nothing about it for a vectorized backend. They control very different things.

@timhoffm
Copy link
Member

Comment on the API

What could a user anticipate figsize=0.75 (or figsize=3/4) to be?

a) an aspect ratio. Width could be taken from rcParams and height be adapted. (Could also be the other way round, but @jklymak made a good point that widths are usually kept more constant.)
b) the scaling factor proposed above.

I'm undecided which one would be the more natural choice. - If a) was, that would be an argument against adding the proposed API. Both are ideosyncratic in that they use rcParams[figure.size], whereas the standard figsize=(with, height) overrides it.

Comment on use case

@ianhi what exactly is your use case?

I'm not quite clear if scaling is a common use case. IMHO larger figures tend to consist of multiple subplots and in that case it's usually reasonable to adapt the aspect ratio as well.

  • Interactive usage / quick look as some data. If you write program, it's probably not a big deal to calculate two numbers instead of using one.
  • Non-resizable backends. If you have a resizable figure, it's faster to resize the figure after showing than typing figsize=foo.

This basically leaves jupyter inline figures with a single axes but lots of data.

Overall the proposed feature is not really a straight forward extension, but OTOH it's also not too outlandish.

@ianhi
Copy link
Contributor Author

ianhi commented Aug 14, 2020

@ianhi what exactly is your use case?
This basically leaves jupyter inline figures with a single axes but lots of data.

It is generally when I rapidly iterating on a plot that has lots of data. I am a heavy user of jupyter notebooks and I tend to want this with both the inline and ipympl backends. With the inline backend I obviously cannot resize a plot post creation, and with the ipympl backend while I can resize with the mouse I prefer not two for two reason:

  1. I use the vim bindings so switching to using the mouse to resize the figure can break flow
    • This is clearly an insufficient reason on it's own...
  2. If I resize the plot and then realize I want to make a small change I will rerun the cell and then will need to manually resize again. It would preferable to just realize that I want the plot to be bigger and only need to add figsize=2 in addition to whatever other change I make.

Arguably I could just make my rcParam figsize bigger, but if I were to do that then all of my plots would be bigger which takes up too much space in the notebook

@WeatherGod
Copy link
Member

WeatherGod commented Aug 14, 2020 via email

@jklymak
Copy link
Member

jklymak commented Aug 14, 2020

figaspect is even wackier. It adjusts the aspect ratio by leaving the height the same as the rcParam, and adjusting the width.

I think assuming that a single float means "make the figure this much bigger" is just one of many choices, and that makes it a hard sell. I don't particularly like overloading kwargs as it just leads to confusion and typos.

OTOH, if you wanted to write something analogous to figaspect and put it in pyplot that might be easier. It'd be self-contained and still give you what you want. i.e. plt.figure(figsize=plt.figsizemult(2.78))

@ianhi
Copy link
Contributor Author

ianhi commented Aug 14, 2020

I think assuming that a single float means "make the figure this much bigger" is just one of many choices

That's reasonable. It seems to be that that there are three basic ways we could imagine specifying figuresize: Explicitly, via figsize and implicitly via aspect ratio and/or scale. I think it would be cool to support all of those options within figure, though it would come at the cost of adding yet more kwargs. How you would you feel about adding two optional arguments to figure: figaspect and figscale both as None or Float.

  • figaspect: take width from rcParams and adapt height
  • figscale(or figsizemult): take width/height after accounting for figaspect and scale it like I originally described.

With both of these arguments deferring to figsize if that was passed?

@dopplershift
Copy link
Contributor

I'm undecided overall, but I will say this most recent suggestion is the one that sounds most reasonable thus far.

@jklymak
Copy link
Member

jklymak commented Aug 14, 2020

I think its fine too - in fact I could imagine using the functionality. @ianhi, if you implement this, suggest warning if conflicting kwargs are passed. Also beware that this has a big documentation footprint (subplots, figure, their pyplot equivalents, etc...) Before embarking on the journey, you may want to get a couple more thumbs up. @timhoffm. @WeatherGod @tacaswell @anntzer @efiring any opinions?

@jklymak jklymak added the status: needs comment/discussion needs consensus on next step label Aug 14, 2020
@mwaskom
Copy link

mwaskom commented Aug 14, 2020

IMO if you implement an aspect parameter, fixing height and varying width would be more in line with how display aspects are usually defined (and how it works in seaborn).

Although matplotlib has a different concept of aspect that may be confusing...

@tacaswell
Copy link
Member

tacaswell commented Aug 14, 2020

Having s = np.array(plt.rcParams['figure.size']) at the the top of your notebook and doing fig = plt.figure(figsize=s*2) does not seem too bad to carry around.

This reminds me of the "should we take fig size as cm as well" discussions where we have historically landed on "would be nice, but not worth the extra complexity". Similarly here, I see how this is a nice quality of life improvement, but am not convinced that it would improve the lives of many people

I'm pretty thoroughly in-different to this proposal (will not block it but also would not merge it).

@jklymak
Copy link
Member

jklymak commented Aug 14, 2020

if you implement an aspect parameter, fixing height and varying width would be more in line with how display aspects are usually defined (and how it works in seaborn).

For sure, varying ideas of how to implement this will be huge problem.

Though I'll note that matplotlib, for whatever historic reason says aspect=3 is three times as tall as wide. https://matplotlib.org/api/_as_gen/matplotlib.axes.Axes.set_aspect.html

Here I was arguing that if you choose a dimension to fix, it should be the width, because when you publish a paper that is usually the narrower constraint. However, if you are making webpages, I guess vertical is usually more constrained, so you'll never make anyone everyone happy...

@efiring
Copy link
Member

efiring commented Aug 14, 2020

Historical footnote: the reason is that I think in terms of sailboats, and a high-aspect-ratio rig is one with a tall mast and a short boom. So, long ago, when I put in aspect ratio handling, that's the way I defined it.

@efiring
Copy link
Member

efiring commented Aug 14, 2020

I'm convinced by #18244 (comment). We can always provide more convenience via additional kwargs, but beyond some point they become counter-productive, adding more complexity to code and documentation than they are worth. The figsize kwarg, as-is, gives complete control, and is not hard to set explicitly to match whatever conditions are desired.

@jklymak
Copy link
Member

jklymak commented Aug 14, 2020

put in aspect ratio handling, that's the way I defined it.

Well, set_aspect would ideally allow a vector instead of a float, which would be the exact converse of the request here 😉.

@dopplershift
Copy link
Contributor

I don’t see the point of set_aspect taking a vector:

set_aspect(h / w)

@jklymak
Copy link
Member

jklymak commented Aug 14, 2020

Just for explicitness if you don't remember its h/w. And as @mwaskom points out, it is pretty conventional to specify aspect ratios as width-to-height. But I'm not seriously advocating we should do this.

@mwaskom
Copy link

mwaskom commented Aug 14, 2020

"That's the way it works on sailboats" is officially my favorite explanation for a matplotlib API quirk.

@jklymak
Copy link
Member

jklymak commented Aug 14, 2020

We are lucky he didn't do ax.set_xlim(port=-1, starboard=+1)

@QuLogic
Copy link
Member

QuLogic commented Aug 14, 2020

However, if you are making webpages, I guess vertical is usually more constrained, so you'll never make anyone everyone happy...

I'd say the opposite. Even if screens happen to be wider than taller, the width is still the fixed constraint, as it's much more natural to scroll vertically than to scroll horizontally.

@anntzer
Copy link
Contributor

anntzer commented Aug 14, 2020

My PoV is pretty close to @efiring's (#18244 (comment)). Also note that if you really want this it is easy to write your own wrapper function(s) around figure() (and friends) that provides the functionality, and use that instead.

@jklymak
Copy link
Member

jklymak commented Aug 14, 2020

@tacaswell, is there an issue tracking the desire to have a tutorial that helps folks make themselves "helper" libraries? I think that was on your todo?

@timhoffm
Copy link
Member

timhoffm commented Aug 14, 2020

I tend to agree with @efiring (#18244 (comment)) as well.

While I see the use case, all possible APIs are a bit awkward, and it's relatively easy to work around this by one of:

Overall, this again is a conflict between "library with a minimal and clear API" and "additional redundance for user convenience to achieve a certian behavior with less code".

Note on kwargs

How you would you feel about adding two optional arguments to figure: figaspect and figscale both as None or Float.

  • figaspect: take width from rcParams and adapt height
  • figscale(or figsizemult): take width/height after accounting for figaspect and scale it like I originally described.

With both of these arguments deferring to figsize if that was passed?

Generally, mutually exclusive (or interfering) parameters are an indication for bad API design. This is also true here, if thinking from the library API perspective. There are better options, but they are not as convenient to type as figscale=2.

In comparison, I'd still favor figsize=2.

Compromise?

Would it be reasonable to make rcParams['figure.figsize'] a ndarray? If so, one could do figure(figsize=2*plt.rcParams[figure.figsize]). That's comfortingly explicit. It's a bit longer to type once, but you can easily scale afterwards.

@github-actions
Copy link

This issue has been marked "inactive" because it has been 365 days since the last comment. If this issue is still present in recent Matplotlib releases, or the feature request is still wanted, please leave a comment and this label will be removed. If there are no updates in another 30 days, this issue will be automatically closed, but you are free to re-open or create a new issue if needed. We value issue reports, and this procedure is meant to help us resurface and prioritize issues that have not been addressed yet, not make them disappear. Thanks for your help!

@github-actions github-actions bot added the status: inactive Marked by the “Stale” Github Action label Aug 16, 2023
@github-actions github-actions bot added the status: closed as inactive Issues closed by the "Stale" Github Action. Please comment on any you think should still be open. label Sep 15, 2023
@github-actions github-actions bot closed this as not planned Won't fix, can't repro, duplicate, stale Sep 15, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
New feature status: closed as inactive Issues closed by the "Stale" Github Action. Please comment on any you think should still be open. status: inactive Marked by the “Stale” Github Action status: needs comment/discussion needs consensus on next step
Projects
None yet
Development

No branches or pull requests

10 participants