Skip to content

ENH: Support units when specifying the figsize #29612

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

Merged
merged 1 commit into from
Apr 6, 2025

Conversation

timhoffm
Copy link
Member

@timhoffm timhoffm commented Feb 12, 2025

Reviving the spirit of #12402 and #12415, because both had significant user votes on GitHub. - #12402 is the highest up-voted issue that we have closed as "not planned" (17 up-votes); #12415 got 7 downvotes on the closing note that people should convert themselves.

Also closes #1369.

This PR is intentionally minimal to only expand the figsize parameter when creating a figure. It allows plt.figure(..., figsize=(600, 400, 'px')) in addition to the current plt.figure(..., figsize=(6, 4)) - note that this passes through to all figure-creating methods. 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.

API considerations:

(width, height, unit) is a straight forward extension of (width, height). Discarded alternatives:

  • We don't need separate units for both directions.
  • Gouping the values figsize=((600, 400), "px") does not make it more readable and is very cumbersome to type.
  • All-string expressions figsize="600x400 px" are difficult to parse and remember.
  • a separate figsize_unit parameter would be too verbose plt.figure(..., figsize=(6, 4), figsize_unit="cm")
  • separate mutually exclusive parameters figsize_cm=..., figsize_px=... would lead to parameter explosion and are not very natural to use

We don't render us in a corner or have consisteny issues with other figsize-related API. There is only Figure.set_size_inches() / Figure.get_size_inches() which are very explicit on the unit (maybe even more than reasonable, but that's a different discussion). If desired, we can always later add Figure.get/set_size() or Figure.get/set_figsize() that take a unit parameter.

Comment on lines 3746 to 3754
if unit == 'inch':
pass
elif unit == 'cm':
x /= 2.54
y /= 2.54
elif unit == 'px':
x /= dpi
y /= dpi
Copy link
Contributor

Choose a reason for hiding this comment

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

Question

Centimetres and pixels are specified using the abbreviations 'cm' and 'px'. Inches are specified using 'inch'. For consistency, might it be more meaningful to use 'in' instead?

Copy link
Member Author

Choose a reason for hiding this comment

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

Being a metric system aficionado, I don't have a strong opinion on 'inch' vs 'in'. I find 'in' a bit confusing because it is also a word in other context. 'cm' and 'px' are IMHO more clear. In the end, it likely doesn't matter too much, because it is the default anyway, so it's unlikely users will write this in their code explicitly. In the end this should be decided by people with an imperial background.

Copy link
Member

Choose a reason for hiding this comment

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

I'd just accept both: if 'in' in unit:

Copy link
Member Author

@timhoffm timhoffm Mar 10, 2025

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 what to accept. If we accept "in" and "inch", we probably should also accept "px" and "pixel", but then again, accepting "centimeter" feels a bit silly as that's very long and I don't expect anybody would write that, but for consistency, maybe we should? OTOH, the ValueError message is nice and tight now, adding all the variants would make it unwieldy

The unit part of 'figsize' must be one of 'inch', 'cm', 'px', but found 'foo'

vs

The unit part of 'figsize' must be one of 'inch', 'in', 'centimenter', 'cm', 'pixel', 'px', but found 'foo'

I'm stuck. Maybe then rather drop "inch" in favor of "in"?

Copy link
Member

@jklymak jklymak Mar 10, 2025

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 hurts. However, one could take it to extremes of also including the plurals. So maybe let's just leave the simplest ("in") as you originally suggested and expand later if there is overwhelming demand.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ok, overcoming my awkward feelings towards imperial units, let's keep it simple and just use "in". After all, users won't type this often because it's the default.

Comment on lines 1824 to 1831
@pytest.mark.parametrize("input, output", [
((6, 4), (6, 4)),
((6, 4, "in"), (6, 4)),
((5.08, 2.54, "cm"), (2, 1)),
((600, 400, "px"), (6, 4)),
])
def test__parse_figsize(input, output):
assert _parse_figsize(input, dpi=100) == output
Copy link
Contributor

Choose a reason for hiding this comment

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

Remark

If there could be a test for an invalid unit, I think this should be good to go!✔️

Copy link
Member

@jklymak jklymak left a comment

Choose a reason for hiding this comment

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

This is fine, but consider changing the test to the public API instead of private - someone could erase the call to _parse_figsize and we would never know the public API was broken?

((5.08, 2.54, "cm"), (2, 1)),
((600, 400, "px"), (6, 4)),
])
def test__parse_figsize(input, output):
Copy link
Member

Choose a reason for hiding this comment

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

I'm not a stickler for this, but don't we usually avoid unit tests for private methods, preferring to test the user facing functionality? This could be easily done by making a figure and then checking its size in inches, which is basically what you are doing here anyway.

Copy link
Contributor

@tfpf tfpf left a comment

Choose a reason for hiding this comment

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

Looks good!

@tfpf
Copy link
Contributor

tfpf commented Apr 6, 2025

Is this waiting on something else?

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
Copy link
Member Author

timhoffm commented Apr 6, 2025

This just needs a second review from a core developer. Unfortunately, review time is currently scarce.

@oscargus
Copy link
Member

oscargus commented Apr 6, 2025

Feel free to self-merge if you detect the CI has finished before I do it...

@oscargus oscargus merged commit 4fa258a into matplotlib:main Apr 6, 2025
44 of 46 checks passed
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.

add rc param for centimeter support
4 participants