Skip to content

Figure.set_size_cm and Figure(figsize_cm=) as an alternative to figsize/set_size_inches #12402

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
wavexx opened this issue Oct 4, 2018 · 26 comments
Labels
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

Comments

@wavexx
Copy link
Contributor

wavexx commented Oct 4, 2018

I grew tired of having to convert my units just for the sake of specifying the figure size. I'd like to specify the figure size directly in a metric unit, nominally centimeters (or millimeters, no difference). In most EU countries metric is the norm. I only have to deal with inches when looking at monitor sizes, printing, and matplotlib :(

I've found at least two issues about this: #1369 and the more generic #9226.

Of course, having support for generic unit specifiers would be nice, but I'm not entirely satisfied by the extra complexity of #9226. I'm also thorn about (size, unit) tuples, because I don't ever think I'm going to specify the figure size in AU, or picometers. Cm is regularly used for anything that fits paper.

I see two cases where inches are specified in the rc:

  • figure.figsize
  • savefig.pad_inches

In the api, there is the figsize kwarg, I guess a pad_inches kwarg, and the set_size_inches method.

What abound the KISS approach: add a figsize_cm kwarg, pad_cm, and the set_size_cm method, and the two respective rcParams? Fully backward compatible, and allows metric countries to get by easily.

I could easily contribute such a patch.

@jklymak
Copy link
Member

jklymak commented Oct 4, 2018

pads and margins are often specified in points and ems, as well as cms and ins, so the motivation for something more general was not AU or picometers.

@wavexx
Copy link
Contributor Author

wavexx commented Oct 4, 2018

It's a fair point, and I agree that generalizing the units in the various points would be nice. There are many places where I feel the required units are basically arbitrary. Like in annotations, you can choose between 'offset points' or 'offset pixels'. I often eyeball numbers because I cannot use consistent unit throughout.

I just fear such a task will require a lot of changes to be done, and done well, taking considerable time. And while we wait, I still need to chug down inches just to set the figure size.

@jklymak
Copy link
Member

jklymak commented Oct 4, 2018

I'm -1 on a new kwarg.

I personally think we should allow figsize=(5, 6) and figsize=('10cm', '12cm') So strings are parsed as having units, non-strings are the default inches. We could start with just doing this w/ figsize kwarg and see if expands. Others are against a string parser, but I disagree - its the simplest and doesnt' require someone importing another library.

@jklymak jklymak added this to the v3.1 milestone Oct 4, 2018
@wavexx
Copy link
Contributor Author

wavexx commented Oct 4, 2018

I would be ok with that. A string parser is not great, but not any worse than what we do we colors.
In this case, for set_size_inches, I'd add a set_size() that works in the way you describe. Similarly for kwargs that say "inches". Using set_size_inches('2mm', '2mm') would be weird/bad.

@jklymak
Copy link
Member

jklymak commented Oct 4, 2018

That sounds reasonable to me. But I'd give the folks who didnt like the idea of a string parser a day or so to register their concerns before I spent a lot of time making a PR ;-)

@ImportanceOfBeingErnest
Copy link
Member

You would indeed save two characters of typing.

figsize=("8cm","8cm")####|
figsize=(8/2.54,8/2.54)##|

It is of course an advantage not having to remember the number 2.54.
I wouldn't complete oppose the option to use other units, but I think it'll somehow need to be consistent. If you specified the figure size in centimeters, will fig.get_figwidth return inches? Or centimeters? If there was a fig.get_figwidth_cm would it return a number, or a string?
Would the dpi also get its centimeter-equivalent dpcm? Or would there be a fig.dpcm_scale_trans transform?

Essentially instead of the number 2.54 the user will then need to remember which methods exist for centimeters and which don't and possibly their return type. As soon as the "5cm" is introduced, where do you stop, i.e. would plt.bar(x,y, width="0.5cm") ok? But then, why not plt.bar(x,y, width="5mm") as well?

At which point do you evaluate the conversion? Since 8.0 == (8.0/2.54)*2.54 is False, I suppose it might matter.

In total, I think this requires a lot of thought and a clear concept.

@wavexx
Copy link
Contributor Author

wavexx commented Oct 5, 2018 via email

@jklymak
Copy link
Member

jklymak commented Oct 5, 2018

There was a big discussion about this in #9226. I didn’t come away from that with many compelling arguments for anything other than allowing simple strings. I think the only issue is that we want to be consistent across the library in what strings are chosen

I’m not convinced there is any need to worry about the return type units. If folks are going to do math with the return type they can look up whatever conversion they need.

If at some point we want to be pedantic and include inverse units like dpi I suppose that could happen but we don’t get an issue every few months asking to specify dpi in dots per millimeters, so I think that could be put off.

@wavexx
Copy link
Contributor Author

wavexx commented May 4, 2020

Still waiting patiently for this. It's amazing how anything involving printing is stuck with inches, but I'd expect something a bit better out of a scientific plotting package... :/

@jklymak
Copy link
Member

jklymak commented May 4, 2020

Printing is done in points, and a point is 1/72 of an inch. Given there is an easy conversion to centimeters, I don't think there is any appetite for a new way to parse the fig size options. If you wanted to propose a set_figsize_cm thats probably not too hard to add...

@wavexx
Copy link
Contributor Author

wavexx commented May 4, 2020 via email

@jklymak
Copy link
Member

jklymak commented May 4, 2020

dpi is in inches, and points are in inches. When we pursued this before, people went down the rabbit hole of wanting dots-per-cm, etc. The other problem was that figsize is passed to various other routines like the __init__ for Figure and subplots and allowing different units would be a cause for confusion if we had both set_figsize_inches and set_figsize_cm, or we would need a way to parse the input to figsize. I tried to introduce that, but it was deemed too complicated for relatively little benefit, an opinion I now largely agree with. Also, it would still have a conversion problem with folks called get_figsize.

I appreciate the historic artifact of inches is un-aesthetic to those raised on metric. But it is pretty ingrained in the printing and publishing world, so not a completely irrational choice. And dividing by 2.54 is pretty easy if a bit more typing.

@wavexx
Copy link
Contributor Author

wavexx commented May 4, 2020 via email

@alkhwarizmi
Copy link

Well,

I understand that inches and dpi units are used widely in the publishing industries. That's why we are not proposing to drop them in favour of international metric system units! Just give the freedom to the people using other metric standards, to work in the way they prefer (which by the way are the majority of the people living on the planet, take a look to this picture).

I think those who claims that the cost-benefits ratio for supporting arbitrary units deep down in the library is not good enough for its technical complexity, greatly underestimate the political and marketing reasons for doing it. I fully agree with wavexx that nowadays people expect this sort of things from a scientific visualisation software. Saying to the user community "take this soup or leave it", sound a bit... imperialistic. In my opinion, a reason to fork, for example.

Please take this a critique in a constructive way and not as an insult. I know how much work it is to create and maintain a software package, which, amazingly, you and the community are providing for free. Many benefit (including myself) from your work. Nevertheless on this point, in my opinion there is room for improvement.

Sincerely,

dpi is in inches, and points are in inches. When we pursued this before, people went down the rabbit hole of wanting dots-per-cm, etc. The other problem was that figsize is passed to various other routines like the __init__ for Figure and subplots and allowing different units would be a cause for confusion if we had both set_figsize_inches and set_figsize_cm, or we would need a way to parse the input to figsize. I tried to introduce that, but it was deemed too complicated for relatively little benefit, an opinion I now largely agree with. Also, it would still have a conversion problem with folks called get_figsize.

I appreciate the historic artifact of inches is un-aesthetic to those raised on metric. But it is pretty ingrained in the printing and publishing world, so not a completely irrational choice. And dividing by 2.54 is pretty easy if a bit more typing.

@timhoffm
Copy link
Member

timhoffm commented Aug 25, 2020

@alkhwarizmi I appreciate your feedback.

API design is a trade-off between features, simplicity, consistency and backward-compatibility. In this context I don't see a way of adding the feature "metric figure sizes" without significantly affecting the other design aspects.

Some additional comments.

  1. More often than not (at least in my personal experience) one wants figure sizes in pixels. Since the pixel resolution is defined as dpi and defaults to dpi=100, that's straight forward: figsize=(4, 3) gives 400x300 pixels. This is not a case against metric, but illustrates, that inches have their justification based on a common use-case.

  2. Without extra API one can created a slightly sophisticated but easily readable workaround:

    cm = 1/2.54  # cm in inches
    figsize=(2*cm, 4*cm)
    
  3. I don't see a reasonable way to expand the API to include cm, that

    • works easily across all functions using figure sizes
    • is consistent between setting and getting - It would be awkward if I could set the figsize in cm but only get the value in inches.
    • does not bloat the API by duplicating functions - I stongly believe that the clutter of adding methods like set_figsize_cm() or get_figsize_cm() is not worth the gain.

    If you have a stong API design proposal how to solve this, I'm happy to review that.

@wavexx
Copy link
Contributor Author

wavexx commented Aug 26, 2020

Is the exposed API the only real issue here?
What about an rc parameter specifying the input units in all functions currently expecting inches?

As in: rcParams['auto_mm_to_inch'] or something and then we go about all functions that accept inches and simply add a wrapper function to the input argument and convert to the internal representation?

Like I said before, I don't actually mind if the unit I extract from the internal APIs are inches. I rarely do so and I wouldn't mind an inconsistency there. Perhaps not the best, but still not too far from your example of abusing DPI=100 to have useful pixel sizes.

To reiterate, the problem for me is not the easiness of the conversion. Maybe if you're used to the imperial system you somewhat tolerate having these random factors in the code that's ok, but to me all printing units (from inches to "points") are totally meaningless in a metric system. I pick up a ruler and measure stuff, and I need to undo/redo this crap all the time.

We're quite lucky in the end that most of the time we don't need physical units to match, so what I personally do is render everything proportionally and to just scale down. I know it's a straw-man argument, since this is what historically has always been done, so .. I'm actually fine with hacks like what I proposed above as long as it improves the everyday ergonomics.

@anntzer
Copy link
Contributor

anntzer commented Aug 26, 2020

(Just to be clear, note that many of the core devs here who have expressed concern about the proposal (here or in #9226) are not from the US, but indeed from metric system places, namely the EU (well, I don't know about the Canadian here :-)).)

@wavexx
Copy link
Contributor Author

wavexx commented Aug 26, 2020

As a dev myself, I would also frown on such a proposal. It's objectively ugly, especially considering how the documentation would look like, and also regarding the inconsistency of the input/output. So, yes, I also agree with you!

But wow, I'm super-frustrated with the status quo.

@jklymak
Copy link
Member

jklymak commented Aug 26, 2020

Anyone is of course, welcome to take up #12415. But it was closed because its hard to come up with an API that everyone will like that allows you to specify two different units. In the end I did not deem it worth pushing. SO my opinions here are just my paraphrase of the concensus from those discussions.

@alkhwarizmi
Copy link

@timhoffm
Thank you Tim for your kind reply.

I am speaking as a user and I don't know enough about the matplotlib details to propose a new API. I would like to give back to the community by coding something, but I think it would be better to start with something easier ;). I can imagine that what I have in mind could be really too much or even impossible to achieve (if changes in the API would be needed). However, here is the idea:

  1. The figure and all the relevant objects within it hold a state about units, specifying if they are absolute or relative and, if absolute, the unit of measure, if relative, to what they are relative. For certain objects (like the figure) relative units make no sense.
  2. Those units are not to be interpreted as "the units of the figure(or object)", but as "the units the user wants to use when communicating with the figure (or object)". In other words when he/she calls get_size, set_size and the like.
  3. What numbers are really stored in the objects is not relevant for the user (take inches, cm or AU, what you are using now), all the relevant input and output conversions should be done by the getters and setters (which I imagine they are set_size() and get_size() for most objects, although things will surely be more complicated than this naif view).
  4. No set_size_inches, set_size_cm, get_size_cm etc. should exist.
  5. A method to set the units should exist for every object that has dimensions, in principle users should be allowed to use one unit on an object and other units in sub-objects (e.g. ext in pt, boxes in cm).
  6. There should be way to set the same absolute units to the entire object hierarchy for objects whose size is not specified in relative units.

Final point. As my comment was somehow not completely technical, and I pretty much like the idiom you suggested at point 2 (and other suggested as well), I wonder what would happen if you change all the examples in the documentation including that idiom, and you make 50% of the examples with inches = 1; fig.set_size(2*inches, 3*inches) and 50% of the examples with cm = 1/2.54; fig.set_size(2*cm, 3*cm) (plus the management of the returned size data if there are examples where this applies). I bet people will comply less about not knowing how to deal with SI units but in the end will start asking why your are not doing the products or divisions inside the functions.

Thank you again for the opportunity to speak. Very much appreciated.

@jklymak
Copy link
Member

jklymak commented Aug 26, 2020

@alkhwarizmi Please read the discussions in #9226 and #12415

@tacaswell
Copy link
Member

As a dev myself, I would also frown on such a proposal. It's objectively ugly, especially considering how the documentation would look like, and also regarding the inconsistency of the input/output. So, yes, I also agree with you!

But wow, I'm super-frustrated with the status quo.

This is a very succinct description of the tensions in maintaining an old, large, and widely used project @wavexx !

@timhoffm
Copy link
Member

@alkhwarizmi What you are scetching is a more general layout system allowing Artists in relative or absolute positions given in various units. This would be a solution to be considered if starting from scratch. Unfortunately, Matplotlib does work differently internally and the general solution would require a major redesign.

But even when limiting to figure size: Holding hidden state and changing the return value of get_size() based on that can be surprising. Even more so if that would be controlled by a global parameter like rcParams['auto_mm_to_inch'] proposed above.

If anything, something like #12415 seems most viable to me. My impression is that none of the core developers sees this as a priority, justifying to work on this themselves. It would need an external champion to work through all the details and push it forward. If somebody is interested in working on this, please comment in #12415.

In #18360 I've written an example for best practices working with different units. This is as good as we can get without introducing new API.

@jklymak
Copy link
Member

jklymak commented Aug 26, 2020

looking back, it seems #12415 was close, but a couple of core devs were somewhat negative, and folks stopped reviewing it, and I lost interest compared to other things I thought were more worth harassing potential reviewers for. So, if someone really wants to take the torch up and make a new PR based off #12415, I wouldn't object. In the meantime, I think #18360 is pretty useful.

@timhoffm
Copy link
Member

If somebody wants to pick up #12415, please comment on that PR. We will then re-review the PR to determine if the concept has fundamental approval among core developers. This makes sure, that your work on the PR can get accepted.

@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 May 17, 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 Jun 16, 2023
@github-actions github-actions bot closed this as not planned Won't fix, can't repro, duplicate, stale Jun 16, 2023
timhoffm added a commit to timhoffm/matplotlib that referenced this issue Feb 12, 2025
Reviving the spirit of matplotlib#12402 and matplotlib#12415, because both had significant user votes on GitHub.

This PR is intentionally minimal to only expand the `figsize` parameter when creating a figure. This should be the most relevant use case. Later changing the figure size or reading it is probably less necessary. The minimal approach removes the need to track and return sizes. It is just an enhanced specification capability which directly parses to the internally used inch unit.
timhoffm added a commit to timhoffm/matplotlib that referenced this issue Feb 12, 2025
Reviving the spirit of matplotlib#12402 and matplotlib#12415, because both had significant user votes on GitHub.

This PR is intentionally minimal to only expand the `figsize` parameter when creating a figure. This should be the most relevant use case. Later changing the figure size or reading it is probably less necessary. The minimal approach removes the need to track and return sizes. It is just an enhanced specification capability which directly parses to the internally used inch unit.
timhoffm added a commit to timhoffm/matplotlib that referenced this issue Feb 12, 2025
Reviving the spirit of matplotlib#12402 and matplotlib#12415, because both had significant user votes on GitHub.

This PR is intentionally minimal to only expand the `figsize` parameter when creating a figure. This should be the most relevant use case. Later changing the figure size or reading it is probably less necessary. The minimal approach removes the need to track and return sizes. It is just an enhanced specification capability which directly parses to the internally used inch unit.
timhoffm added a commit to timhoffm/matplotlib that referenced this issue Feb 12, 2025
Reviving the spirit of matplotlib#12402 and matplotlib#12415, because both had significant user votes on GitHub.

This PR is intentionally minimal to only expand the `figsize` parameter when creating a figure. This should be the most relevant use case. Later changing the figure size or reading it is probably less necessary. The minimal approach removes the need to track and return sizes. It is just an enhanced specification capability which directly parses to the internally used inch unit.
timhoffm added a commit to timhoffm/matplotlib that referenced this issue Mar 10, 2025
Reviving the spirit of matplotlib#12402 and matplotlib#12415, because both had significant user votes on GitHub.

This PR is intentionally minimal to only expand the `figsize` parameter when creating a figure. This should be the most relevant use case. Later changing the figure size or reading it is probably less necessary. The minimal approach removes the need to track and return sizes. It is just an enhanced specification capability which directly parses to the internally used inch unit.
timhoffm added a commit to timhoffm/matplotlib that referenced this issue Mar 13, 2025
Reviving the spirit of matplotlib#12402 and matplotlib#12415, because both had significant user votes on GitHub.

This PR is intentionally minimal to only expand the `figsize` parameter when creating a figure. This should be the most relevant use case. Later changing the figure size or reading it is probably less necessary. The minimal approach removes the need to track and return sizes. It is just an enhanced specification capability which directly parses to the internally used inch unit.
timhoffm added a commit to timhoffm/matplotlib that referenced this issue Mar 15, 2025
Reviving the spirit of matplotlib#12402 and matplotlib#12415, because both had significant user votes on GitHub.

This PR is intentionally minimal to only expand the `figsize` parameter when creating a figure. This should be the most relevant use case. Later changing the figure size or reading it is probably less necessary. The minimal approach removes the need to track and return sizes. It is just an enhanced specification capability which directly parses to the internally used inch unit.
timhoffm added a commit to timhoffm/matplotlib that referenced this issue Apr 6, 2025
Reviving the spirit of matplotlib#12402 and matplotlib#12415, because both had significant user votes on GitHub.

This PR is intentionally minimal to only expand the `figsize` parameter when creating a figure. This should be the most relevant use case. Later changing the figure size or reading it is probably less necessary. The minimal approach removes the need to track and return sizes. It is just an enhanced specification capability which directly parses to the internally used inch unit.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
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
Projects
None yet
Development

No branches or pull requests

7 participants