10000 ENH: Support units when specifying the figsize · matplotlib/matplotlib@d443e9d · GitHub
[go: up one dir, main page]

Skip to content

Commit d443e9d

Browse files
committed
ENH: Support units when specifying the figsize
Reviving the spirit of #12402 and #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.
1 parent 33dbc47 commit d443e9d

File tree

5 files changed

+94
-6
lines changed

5 files changed

+94
-6
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
Figure size units
2+
-----------------
3+
4+
When creating figures, it is now possible to define figure sizes in cm or pixel.
5+
6+
Up to now the figure size is specified via ``plt.figure(..., figsize=(6, 4))``,
7+
and the given numbers are interpreted as inches. It is now possible to add a
8+
unit string to the tuple, i.e. ``plt.figure(..., figsize=(600, 400, "px"))``.
9+
Supported unit strings are "in", "cm", "px".

lib/matplotlib/figure.py

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2475,8 +2475,13 @@ def __init__(self,
24752475
"""
24762476
Parameters
24772477
----------
2478-
figsize : 2-tuple of floats, default: :rc:`figure.figsize`
2479-
Figure dimension ``(width, height)`` in inches.
2478+
figsize : (float, float) or (float, float, str), default: :rc:`figure.figsize`
2479+
The figure dimensions. This can be
2480+
2481+
- a tuple ``(width, height, unit)``, where *unit* is one of "in" (inch),
2482+
"cm" (centimenter), "px" (pixel).
2483+
- a tuple ``(width, height)``, which is interpreted in inches, i.e. as
2484+
``(width, height, "in")``.
24802485
24812486
dpi : float, default: :rc:`figure.dpi`
24822487
Dots per inch.
@@ -2612,6 +2617,8 @@ def __init__(self,
26122617
edgecolor = mpl._val_or_rc(edgecolor, 'figure.edgecolor')
26132618
frameon = mpl._val_or_rc(frameon, 'figure.frameon')
26142619

2620+
figsize = _parse_figsize(figsize, dpi)
2621+
26152622
if not np.isfinite(figsize).all() or (np.array(figsize) < 0).any():
26162623
raise ValueError('figure size must be positive finite not '
26172624
f'{figsize}')
@@ -3713,3 +3720,46 @@ def figaspect(arg):
37133720
# the min/max dimensions (we don't want figures 10 feet tall!)
37143721
newsize = np.clip(newsize, figsize_min, figsize_max)
37153722
return newsize
3723+
3724+
3725+
def _parse_figsize(figsize, dpi):
3726+
"""
3727+
Convert a figsize expression to (width, height) in inches.
3728+
3729+
Parameters
3730+
----------
3731+
figsize : (float, float) or (float, float, str)
3732+
This can be
3733+
3734+
- a tuple ``(width, height, unit)``, where *unit* is one of "in" (inch),
3735+
"cm" (centimenter), "px" (pixel).
3736+
- a tuple ``(width, height)``, which is interpreted in inches, i.e. as
3737+
``(width, height, "in")``.
3738+
3739+
dpi : float
3740+
The dots-per-inch; used for converting 'px' to 'in'.
3741+
"""
3742+
num_parts = len(figsize)
3743+
if num_parts == 2:
3744+
return figsize
3745+
elif num_parts == 3:
3746+
x, y, unit = figsize
3747+
if unit == 'in':
3748+
pass
3749+
elif unit == 'cm':
3750+
x /= 2.54
3751+
y /= 2.54
3752+
elif unit == 'px':
3753+
x /= dpi
3754+
y /= dpi
3755+
else:
3756+
raise ValueError(
3757+
f"Invalid unit {unit!r} in 'figsize'; "
3758+
"supported units are 'in', 'cm', 'px'"
3759+
)
3760+
return x, y
3761+
else:
3762+
raise ValueError(
3763+
"Invalid figsize format, expected (x, y) or (x, y, unit) but got "
3764+
f"{figsize!r}"
3765+
)

lib/matplotlib/figure.pyi

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,9 @@ class Figure(FigureBase):
318318
subplotpars: SubplotParams
319319
def __init__(
320320
self,
321-
figsize: tuple[float, float] | None = ...,
321+
figsize: tuple[float, float]
322+
| tuple[float, float, Literal["in", "cm", "px"]]
323+
| None = ...,
322324
dpi: float | None = ...,
323325
*,
324326
facecolor: ColorType | None = ...,
@@ -421,3 +423,8 @@ class Figure(FigureBase):
421423
def figaspect(
422424
arg: float | ArrayLike,
423425
) -> np.ndarray[tuple[Literal[2]], np.dtype[np.float64]]: ...
426+
427+
def _parse_figsize(
428+
figsize: tuple[float, float] | tuple[float, float, Literal["in", "cm", "px"]],
429+
dpi: float
430+
) -> tuple[float, float]: ...

lib/matplotlib/pyplot.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -875,7 +875,9 @@ def figure(
875875
# autoincrement if None, else integer from 1-N
876876
num: int | str | Figure | SubFigure | None = None,
877877
# defaults to rc figure.figsize
878-
figsize: ArrayLike | None = None,
878+
figsize: ArrayLike # a 2-element ndarray is accepted as well
879+
| tuple[float, float, Literal["in", "cm", "px"]]
880+
| None = None,
879881
# defaults to rc figure.dpi
880882
dpi: float | None = None,
881883
*,
@@ -908,8 +910,12 @@ def figure(
908910
window title is set to this value. If num is a ``SubFigure``, its
909911
parent ``Figure`` is activated.
910912
911-
figsize : (float, float), default: :rc:`figure.figsize`
912-
Width, height in inches.
913+
figsize : (float, float) or (float, float, str), default: :rc:`figure.figsize`
914+
The figure dimensions. This can be
915+
916+
- a tuple ``(width, height, unit)``, where *unit* is one of "inch", "cm",
917+
"px".
918+
- a tuple ``(x, y)``, which is interpreted as ``(x, y, "inch")``.
913919
914920
dpi : float, default: :rc:`figure.dpi`
915921
The resolution of the figure in dots-per-inch.

lib/matplotlib/tests/test_figure.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1819,3 +1819,19 @@ def test_subfigure_stale_propagation():
18191819
sfig2.stale = True
18201820
assert sfig1.stale
18211821
assert fig.stale
1822+
1823+
1824+
@pytest.mark.parametrize("figsize, figsize_inches", [
1825+
((6, 4), (6, 4)),
1826+
((6, 4, "in"), (6, 4)),
1827+
((5.08, 2.54, "cm"), (2, 1)),
1828+
((600, 400, "px"), (6, 4)),
1829+
])
1830+
def test_figsize(figsize, figsize_inches):
1831+
fig = plt.figure(figsize=figsize, dpi=100)
1832+
assert tuple(fig.get_size_inches()) == figsize_inches
1833+
1834+
1835+
def test_figsize_invalid_unit():
1836+
with pytest.raises(ValueError, match="Invalid unit 'um'"):
1837+
plt.figure(figsize=(6, 4, "um"))

0 commit comments

Comments
 (0)
0