Skip to content

ENH: fig.set_size to allow non-inches units #12415

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
wants to merge 4 commits into from

Conversation

jklymak
Copy link
Member

@jklymak jklymak commented Oct 5, 2018

PR Summary

cross-ref #12402, #9226, #1369

Work in progress for opinions....

EDIT: added figsize three-tuple

EDIT: added units kwarg. See last example....

a. this is a very light-weight change that adds convenience for the meterically inclined.
b. if we want to add support for print units from some sort of units library, those can be converted too. i.e. we aren't closing the door on more sophisticated APIs.

import numpy as np
import matplotlib.pyplot as plt

fig, ax = plt.subplots()

fig.set_size((5, 3))
print(fig.get_size_inches())

fig.set_size((5, 3, 'in'))
print(fig.get_size_inches())

fig.set_size((12.7, 7.62, 'cm'))
print(fig.get_size_inches())

fig.set_size(5, 3)
print(fig.get_size_inches())

fig.set_size('5in', '3in')
print(fig.get_size_inches())

fig.set_size('12.7cm', '7.62cm')
print(fig.get_size_inches())

fig.set_size('127mm', '76.2mm')
print(fig.get_size_inches())

fig.set_size('360pt', '216pt')
print(fig.get_size_inches())

fig.set_size(360, 216, units='pt')
print(fig.get_size_inches())
[5. 3.]
[5. 3.]
[5. 3.]
[5. 3.]
[5. 3.]
[5. 3.]
[5. 3.]
[5. 3.]
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(12.7, 7.62, 'cm'))
print(fig.get_size_inches())

fig, ax = plt.subplots(figsize=('12.7cm', '7.62cm'))
print(fig.get_size_inches())

fig, ax = plt.subplots(figsize=(500, 300, 'px'))
print(fig.get_size_inches())
[5. 3.]
[5. 3.]
[5. 3.]

PR Checklist

  • Has Pytest style unit tests
  • Code is Flake 8 compliant
  • New features are documented, with examples if plot related
  • Documentation is sphinx and numpydoc compliant
  • Added an entry to doc/users/next_whats_new/ if major new feature (follow instructions in README.rst there)
  • Documented in doc/api/api_changes.rst if API changed in a backward-incompatible way

@@ -883,8 +929,7 @@ def set_size_inches(self, w, h=None, forward=True):

# the width and height have been passed in as a tuple to the first
# argument, so unpack them
if h is None:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we still need this special casing here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, bad cut and paste job...

if h is None:
w, h = w
if isinstance(w, str):
temp = w
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is temp for?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @tacaswell I didn't really proofread it all - I was more interested in if the API would fly. It also needs tests and more documentation...

@tacaswell tacaswell added this to the v3.1 milestone Oct 5, 2018
@tacaswell
Copy link
Member

This has come up before and I am still not convinced this is a good idea (but am more convinced than I used to be).

@jklymak
Copy link
Member Author

jklymak commented Oct 7, 2018

Any other opinions one way or another for this? I'll give it +1 because I don't think its too crufty, and its backcompatble. @tacaswell seems -0.5?

@timhoffm
Copy link
Member

timhoffm commented Oct 7, 2018

  • I find it ok to be able to specify the size in different units, but still do all the internal handling and the returned size in inches. This is just a convenience converter for the inputs. No need to make all the internal logic and getters unit-aware. After all, setting the size is much more common than querying the size.

  • Personally, I don't use Figure.set_size_inches but only the figsize kwarg. Since this is all about convenience, I'm afraid just adding Figure.set_size will not reach the goal unless we're going to make figsize accept units as well.

    fig, ax = plt.subplots(figsize=(8/2.54, 6/2.54))
    

    is still more convenient than

    fig, ax = plt.subplots()
    fig.set_size(8, 6, unit='cm')
    
  • We should not have Figure.set_size and Figure.set_size_inches as equal options. The should be only one way, meaning we would have to deprecate Figure.set_size_inches.

  • I'm a bit worried about the permitted values. set_size(8, 6, unit='cm') seems perfectly reasonable. set_size('8cm', '6cm') is a bit more unconventional. If it was just about set_size, I wouldn't allow string values. However, that poses the question on plt.subplots(figsize=). Here you would either have plt.subplots(figsize=('8cm', '6cm')) or you would need a separate kwarg plt.subplots(figsize=(8, 6), figunit='cm'). I'm -0.5 on string arguments and -0.8 on extra figure keywords.

    Another alternative would be plt.subplots(figsize=(8, 6, 'cm')), which is not great, but IMHO the best compromise to specify units. Getting a good interface for Figure() and subplots() is the crucial point to me. Otherwise, unit support does not have a real benefit.

To sum up, my recommendation is:

  • Figure.set_size(width : float, height : float, unit='inch', ...)
  • deprecate Figure.set_size_inches
  • accept Figure(figsize=(8, 6, 'cm'))

@ImportanceOfBeingErnest
Copy link
Member

Please don't deprecate Figure.set_size_inches. The number of people who will then need to change their code is certainly higher than the number of people who benefit from introducing the metric stuff.
So from my side -10 on deprecating anything here, ±0 on adding functionality.

@timhoffm
Copy link
Member

timhoffm commented Oct 7, 2018

@ImportanceOfBeingErnest I see your point. OTOH two functions are clutter in the long run. Maybe just add a note to the docstring that set_size() is preferred over set_size_inches() - similar to how we don't deprecate pylab. And maybe maybe deprecated in 4.0?

@jklymak
Copy link
Member Author

jklymak commented Oct 7, 2018

@timhoffm I agree - we need something for the figsize kwarg. This change accepts three-tuple or two-tuple of strings (as well as keeping old behaviour)

@timhoffm
Copy link
Member

timhoffm commented Oct 7, 2018

@jklymak Basically, +1 for supporting the three-tuple figsize. However, I'm still -0.5 on string values.

Both APIs need to be discussed with other devs. It's likely that there will still be changes. You might want to wait with the actual implementation until the API is finally decided upon. Otherwise, you may have duplicate coding work. Just saying. Of course you are free to update proof-of-concept code as we discuss 😄 .

@tacaswell
Copy link
Member

I agree with most of what @timhoffm and @ImportanceOfBeingErnest said (which are the reasons I am not convinced on this).

Balancing {"passing non-inch values to set_size_inches", "we don't want to have two functions that do the same things", "we can not deprecated set_size_inches} has been the sticking point on this before.

I like @timhoffm 's suggestion of letting the figsize kwarg take a triple, a pair of inches, or a pair of strings maybe the best path forward (and leaving set_size_inches alone). It extends the API in a pretty natural way, is back compatible, adds no new names, and does not have any weird name / input clashes.

@timhoffm
Copy link
Member

timhoffm commented Oct 7, 2018

@tacaswell actually, I'm not advocating the "pair of strings" option. I find that a bit unconventional. And it's basically redundant (=not needed) if we have the (width, height, unit) triple.

@anntzer
Copy link
Contributor

anntzer commented Oct 7, 2018

I think figsize=(x, y, "cm") looks ok ((x, y, "px") may be nice to have too).
I'd still introduce set_size and "deprecate-in-docs" (à la pylab) set_size_inches but that's a separate point. (But of course then the question is whether set_size(x, y) should work with the meaning of set_size(x, y, "in")... I guess we can always postpone this question to later :))

@jklymak
Copy link
Member Author

jklymak commented Oct 7, 2018

My vote would be for default inches as the float-only API. Historically it’s what we have offered, internally it’s what we use, and I’m not in favour of some sort of unitized getter for figure size. Anyone who needs to programmatically get the size of a figure is surely capable of converting in their code. Thus allowing inches only as the default is good because it lets the user know that’s our default and internal convention. The units are just there as a mild convenience.

@jklymak
Copy link
Member Author

jklymak commented Oct 7, 2018

px now works (if dpi is set):

fig, ax = plt.subplots(figsize=(500, 300, 'px'))

@@ -2075,3 +2075,26 @@ def _check_and_log_subprocess(command, logger, **kwargs):
.format(command, exc.output.decode('utf-8')))
logger.debug(report)
return report


def _print_unit(st, dpi=None):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

function name seems a bit weird?

Convert from a string with a number and units into inches
"""
print_units = {'cm': 2.54, 'pt': 72.0, 'mm': 25.4, 'in': 1.0}
if len(st) > 2:
Copy link
Contributor

@anntzer anntzer Oct 7, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is that a leftover of supporting "2cm"?
edit: I see that's not removed yet? (not a big deal, just checking)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not leftover yet. I still think it’s an ok API to have both

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No problem, we can discuss this over the call.

@@ -862,19 +870,85 @@ def figimage(self, X, xo=0, yo=0, alpha=None, norm=None, cmap=None,
self.stale = True
return im

def set_size(self, w, h=None, units=None, forward=True):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that we're introducing a new API, let's decide whether we want set_size((w, h)) or set_size(w, h) but don't support both. (I have a mild preference for set_size((w, h)), as that fits the setter model better (figure.set(size=(w, h)))).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don’t think it necessarily is a good idea to make it inconsistent with the existing API. Why do we have to enforce a tuple?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not going to fight too much over this specific case as we're just following a preexisting API, but in general I think we should aim for functions that have a single well-define signature (either taking two floats as separate arguments, or a single pair); otherwise that's how we end up with the mess that's custom arg parsing for plot(), quiver() and friends...

Copy link
Member

@timhoffm timhoffm Oct 8, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that this should be decided. Unfortunately there are good reasons for both.

set_size((w, h)) is more difficult to type and read. It feels a bit artificial, also given that ax.set_xlim(left, right) has a similar signature and I wouldn't vote for changing that.

OTOH, set_size(size) would allow to trivially support size=(6, 4, "cm") throughout the whole library and in all figure-creating functions. If we just have set_size(w, h, unit) we might need some explicit conversion of the tuple.

All over, I have a mild preference for set_size(w, h).

Speaking of which, I'm also fine with the "both" solution set_size(self, w, h=None), basically because we already have precedence. The documentation is more cumbersome, but all use-cases can be covered. So to me, it would be ok to use this and defer a general decision.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, set_xlim((left, right)) also works...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah the boo(x, y) = boo((x, y)) API is quite prevalent. I agree it can make parsing the args a bit of a nuisance, though to me the basic problem w/ plot is the fact we allow, plot(x1, y1, x2, y2).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it was just that. We also have plot(x1, y1, fmt1, x2, y2, fmt2) and even plot(y1, fmt1, y2, fmt2). Welcome to the Matlab way of thinking. But we are getting off-topic.

@jklymak
Copy link
Member Author

jklymak commented Jul 28, 2019

OK, thanks for everyone's work on this. In the spirit of keeping things simple, I think folks will have to make due with converting in their downstream code. If anyone wants to take this up, though, they should feel free.

@jklymak jklymak closed this Jul 28, 2019
timhoffm added a commit to timhoffm/matplotlib that referenced this pull request 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.
@jklymak jklymak deleted the enh-fig-set-size branch February 12, 2025 15:56
timhoffm added a commit to timhoffm/matplotlib that referenced this pull request 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 pull request 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 pull request 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 pull request 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 pull request 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 pull request 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 pull request 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
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants