From 7d5d5b649c7876425aaa33b15eceeb182e138b2a Mon Sep 17 00:00:00 2001 From: Vikash Kumar Date: Sat, 28 Mar 2026 08:10:13 +0530 Subject: [PATCH 01/12] ENH: Support partial figsize with None (#31400) --- lib/matplotlib/figure.py | 16 ++++++++++++++++ lib/matplotlib/tests/test_figure.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index 501b3e32ebf4..b55c85af6417 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -2627,6 +2627,15 @@ def __init__(self, frameon = mpl._val_or_rc(frameon, 'figure.frameon') figsize = _parse_figsize(figsize, dpi) + width, height = figsize + if width is None and height is None: + raise ValueError("Only one of width or height can be None") + default_width, default_height = mpl.rcParams["figure.figsize"] + if width is None: + width = default_width + if height is None: + height = default_height + figsize = (width, height) if not np.isfinite(figsize).all() or (np.array(figsize) < 0).any(): raise ValueError('figure size must be positive finite not ' @@ -3155,6 +3164,13 @@ def set_size_inches(self, w, h=None, forward=True): """ if h is None: # Got called with a single pair as argument. w, h = w + if w is None and h is None: + raise ValueError("Only one of width or height can be None") + default_width, default_height = mpl.rcParams["figure.figsize"] + if w is None: + w = default_width + if h is None: + h = default_height size = np.array([w, h]) if not np.isfinite(size).all() or (size < 0).any(): raise ValueError(f'figure size must be positive finite not {size}') diff --git a/lib/matplotlib/tests/test_figure.py b/lib/matplotlib/tests/test_figure.py index bb358fa6269f..cd72933a1d82 100644 --- a/lib/matplotlib/tests/test_figure.py +++ b/lib/matplotlib/tests/test_figure.py @@ -1883,6 +1883,34 @@ def test_figsize(figsize, figsize_inches): assert tuple(fig.get_size_inches()) == figsize_inches +def test_figsize_partial_none(): + default_w, default_h = mpl.rcParams["figure.figsize"] + + fig = plt.figure(figsize=(None, 4)) + w, h = fig.get_size_inches() + assert w == default_w + assert h == 4 + plt.close(fig) + + fig = plt.figure(figsize=(6, None)) + w, h = fig.get_size_inches() + assert w == 6 + assert h == default_h + plt.close(fig) + + fig = plt.figure() + fig.set_size_inches(None, 4) + w, h = fig.get_size_inches() + assert w == default_w + assert h == 4 + plt.close(fig) + + +def test_figsize_both_none(): + with pytest.raises(ValueError, match="Only one of width or height can be None"): + plt.figure(figsize=(None, None)) + + def test_figsize_invalid_unit(): with pytest.raises(ValueError, match="Invalid unit 'um'"): plt.figure(figsize=(6, 4, "um")) From 1e23dd1b4285405d840e9f848b79664531f51d3f Mon Sep 17 00:00:00 2001 From: Vikash Kumar Date: Sat, 28 Mar 2026 17:56:17 +0530 Subject: [PATCH 02/12] ENH: disallow None in Figure.set_size_inches --- lib/matplotlib/figure.py | 22 +++++++++++++--------- lib/matplotlib/tests/test_figure.py | 22 +++++++++++++--------- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index b55c85af6417..8b41d64af4d8 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -2629,7 +2629,9 @@ def __init__(self, figsize = _parse_figsize(figsize, dpi) width, height = figsize if width is None and height is None: - raise ValueError("Only one of width or height can be None") + raise ValueError( + "figsize=(None, None) is invalid; at least one of width or " + "height must be provided") default_width, default_height = mpl.rcParams["figure.figsize"] if width is None: width = default_width @@ -3163,14 +3165,16 @@ def set_size_inches(self, w, h=None, forward=True): To transform from pixels to inches divide by `Figure.dpi`. """ if h is None: # Got called with a single pair as argument. - w, h = w - if w is None and h is None: - raise ValueError("Only one of width or height can be None") - default_width, default_height = mpl.rcParams["figure.figsize"] - if w is None: - w = default_width - if h is None: - h = default_height + try: + w, h = w + except (TypeError, ValueError): + raise ValueError( + "Figure.set_size_inches does not accept None; provide " + "both width and height explicitly") from None + if w is None or h is None: + raise ValueError( + "Figure.set_size_inches does not accept None; provide both " + "width and height explicitly") size = np.array([w, h]) if not np.isfinite(size).all() or (size < 0).any(): raise ValueError(f'figure size must be positive finite not {size}') diff --git a/lib/matplotlib/tests/test_figure.py b/lib/matplotlib/tests/test_figure.py index cd72933a1d82..e15010eb8f28 100644 --- a/lib/matplotlib/tests/test_figure.py +++ b/lib/matplotlib/tests/test_figure.py @@ -1898,19 +1898,23 @@ def test_figsize_partial_none(): assert h == default_h plt.close(fig) - fig = plt.figure() - fig.set_size_inches(None, 4) - w, h = fig.get_size_inches() - assert w == default_w - assert h == 4 - plt.close(fig) - - def test_figsize_both_none(): - with pytest.raises(ValueError, match="Only one of width or height can be None"): + with pytest.raises(ValueError, + match=r"figsize=\(None, None\) is invalid"): plt.figure(figsize=(None, None)) +@pytest.mark.parametrize("width, height", [ + (None, 3), + (6, None), +]) +def test_set_size_inches_rejects_none(width, height): + fig = Figure() + with pytest.raises(ValueError, + match=r"Figure\.set_size_inches does not accept None"): + fig.set_size_inches(width, height) + + def test_figsize_invalid_unit(): with pytest.raises(ValueError, match="Invalid unit 'um'"): plt.figure(figsize=(6, 4, "um")) From 3e4ceb56161bf2b8571f4c22bf23e5d2b4d39c96 Mon Sep 17 00:00:00 2001 From: Vikash Kumar <163628932+Vikash-Kumar-23@users.noreply.github.com> Date: Sun, 29 Mar 2026 19:54:40 +0530 Subject: [PATCH 03/12] Update lib/matplotlib/figure.py Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> --- lib/matplotlib/figure.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index 8b41d64af4d8..ef90f244df6b 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -3165,16 +3165,6 @@ def set_size_inches(self, w, h=None, forward=True): To transform from pixels to inches divide by `Figure.dpi`. """ if h is None: # Got called with a single pair as argument. - try: - w, h = w - except (TypeError, ValueError): - raise ValueError( - "Figure.set_size_inches does not accept None; provide " - "both width and height explicitly") from None - if w is None or h is None: - raise ValueError( - "Figure.set_size_inches does not accept None; provide both " - "width and height explicitly") size = np.array([w, h]) if not np.isfinite(size).all() or (size < 0).any(): raise ValueError(f'figure size must be positive finite not {size}') From 251786cb8cf207fbfb27cc9df16684941d92e9ec Mon Sep 17 00:00:00 2001 From: Vikash Kumar <163628932+Vikash-Kumar-23@users.noreply.github.com> Date: Sun, 29 Mar 2026 19:54:52 +0530 Subject: [PATCH 04/12] Update lib/matplotlib/tests/test_figure.py Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> --- lib/matplotlib/tests/test_figure.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/lib/matplotlib/tests/test_figure.py b/lib/matplotlib/tests/test_figure.py index e15010eb8f28..59c778163553 100644 --- a/lib/matplotlib/tests/test_figure.py +++ b/lib/matplotlib/tests/test_figure.py @@ -1904,17 +1904,6 @@ def test_figsize_both_none(): plt.figure(figsize=(None, None)) -@pytest.mark.parametrize("width, height", [ - (None, 3), - (6, None), -]) -def test_set_size_inches_rejects_none(width, height): - fig = Figure() - with pytest.raises(ValueError, - match=r"Figure\.set_size_inches does not accept None"): - fig.set_size_inches(width, height) - - def test_figsize_invalid_unit(): with pytest.raises(ValueError, match="Invalid unit 'um'"): plt.figure(figsize=(6, 4, "um")) From 42f1478197f780cac0f6269cf9f1c8788440475a Mon Sep 17 00:00:00 2001 From: Vikash Kumar <163628932+Vikash-Kumar-23@users.noreply.github.com> Date: Sun, 29 Mar 2026 19:55:02 +0530 Subject: [PATCH 05/12] Update lib/matplotlib/tests/test_figure.py Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> --- lib/matplotlib/tests/test_figure.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/matplotlib/tests/test_figure.py b/lib/matplotlib/tests/test_figure.py index 59c778163553..bcb8454d5a9e 100644 --- a/lib/matplotlib/tests/test_figure.py +++ b/lib/matplotlib/tests/test_figure.py @@ -1888,9 +1888,7 @@ def test_figsize_partial_none(): fig = plt.figure(figsize=(None, 4)) w, h = fig.get_size_inches() - assert w == default_w - assert h == 4 - plt.close(fig) + assert w, h == default_w, 4 fig = plt.figure(figsize=(6, None)) w, h = fig.get_size_inches() From 9131eea7b89aadb7726180caf34652fc3dc71efc Mon Sep 17 00:00:00 2001 From: Vikash Kumar <163628932+Vikash-Kumar-23@users.noreply.github.com> Date: Sun, 29 Mar 2026 19:55:13 +0530 Subject: [PATCH 06/12] Update lib/matplotlib/tests/test_figure.py Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> --- lib/matplotlib/tests/test_figure.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/matplotlib/tests/test_figure.py b/lib/matplotlib/tests/test_figure.py index bcb8454d5a9e..027f9a09a152 100644 --- a/lib/matplotlib/tests/test_figure.py +++ b/lib/matplotlib/tests/test_figure.py @@ -1892,9 +1892,7 @@ def test_figsize_partial_none(): fig = plt.figure(figsize=(6, None)) w, h = fig.get_size_inches() - assert w == 6 - assert h == default_h - plt.close(fig) + assert w, h == 6, default_h def test_figsize_both_none(): with pytest.raises(ValueError, From 4e7e12628b52238a70252b260c20535876bfc941 Mon Sep 17 00:00:00 2001 From: Vikash Kumar Date: Sun, 29 Mar 2026 20:24:59 +0530 Subject: [PATCH 07/12] ENH: parse partial None figsize in _parse_figsize --- lib/matplotlib/figure.py | 48 +++++++++++++++++++---------- lib/matplotlib/tests/test_figure.py | 5 +-- 2 files changed, 34 insertions(+), 19 deletions(-) diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index ef90f244df6b..63cb8d8610d4 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -2627,17 +2627,6 @@ def __init__(self, frameon = mpl._val_or_rc(frameon, 'figure.frameon') figsize = _parse_figsize(figsize, dpi) - width, height = figsize - if width is None and height is None: - raise ValueError( - "figsize=(None, None) is invalid; at least one of width or " - "height must be provided") - default_width, default_height = mpl.rcParams["figure.figsize"] - if width is None: - width = default_width - if height is None: - height = default_height - figsize = (width, height) if not np.isfinite(figsize).all() or (np.array(figsize) < 0).any(): raise ValueError('figure size must be positive finite not ' @@ -3165,6 +3154,16 @@ def set_size_inches(self, w, h=None, forward=True): To transform from pixels to inches divide by `Figure.dpi`. """ if h is None: # Got called with a single pair as argument. + try: + w, h = w + except (TypeError, ValueError): + raise ValueError( + "Figure.set_size_inches does not accept None; provide " + "both width and height explicitly") from None + if w is None or h is None: + raise ValueError( + "Figure.set_size_inches does not accept None; provide both " + "width and height explicitly") size = np.array([w, h]) if not np.isfinite(size).all() or (size < 0).any(): raise ValueError(f'figure size must be positive finite not {size}') @@ -3773,25 +3772,40 @@ def _parse_figsize(figsize, dpi): """ num_parts = len(figsize) if num_parts == 2: - return figsize + x, y = figsize elif num_parts == 3: x, y, unit = figsize if unit == 'in': pass elif unit == 'cm': - x /= 2.54 - y /= 2.54 + if x is not None: + x /= 2.54 + if y is not None: + y /= 2.54 elif unit == 'px': - x /= dpi - y /= dpi + if x is not None: + x /= dpi + if y is not None: + y /= dpi else: raise ValueError( f"Invalid unit {unit!r} in 'figsize'; " "supported units are 'in', 'cm', 'px'" ) - return x, y else: raise ValueError( "Invalid figsize format, expected (x, y) or (x, y, unit) but got " f"{figsize!r}" ) + + if x is None and y is None: + raise ValueError( + "figsize=(None, None) is invalid; at least one of width or " + "height must be provided") + + default_width, default_height = mpl.rcParams["figure.figsize"] + if x is None: + x = default_width + if y is None: + y = default_height + return x, y diff --git a/lib/matplotlib/tests/test_figure.py b/lib/matplotlib/tests/test_figure.py index 027f9a09a152..0e793a6e7671 100644 --- a/lib/matplotlib/tests/test_figure.py +++ b/lib/matplotlib/tests/test_figure.py @@ -1888,11 +1888,12 @@ def test_figsize_partial_none(): fig = plt.figure(figsize=(None, 4)) w, h = fig.get_size_inches() - assert w, h == default_w, 4 + assert (w, h) == (default_w, 4) fig = plt.figure(figsize=(6, None)) w, h = fig.get_size_inches() - assert w, h == 6, default_h + assert (w, h) == (6, default_h) + def test_figsize_both_none(): with pytest.raises(ValueError, From be08eaa0a453d9f09d9ba0de87cd63d0ef2dcb79 Mon Sep 17 00:00:00 2001 From: Vikash Kumar Date: Mon, 30 Mar 2026 17:51:53 +0530 Subject: [PATCH 08/12] Simplify set_size_inches argument handling --- lib/matplotlib/figure.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index 63cb8d8610d4..21f6f8c00fe8 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -3154,12 +3154,7 @@ def set_size_inches(self, w, h=None, forward=True): To transform from pixels to inches divide by `Figure.dpi`. """ if h is None: # Got called with a single pair as argument. - try: - w, h = w - except (TypeError, ValueError): - raise ValueError( - "Figure.set_size_inches does not accept None; provide " - "both width and height explicitly") from None + w, h = w if w is None or h is None: raise ValueError( "Figure.set_size_inches does not accept None; provide both " From 86830f0ea79000d99ff8a56c46418e760c38f0ec Mon Sep 17 00:00:00 2001 From: Vikash Kumar Date: Mon, 30 Mar 2026 18:25:36 +0530 Subject: [PATCH 09/12] DOC: document partial None support for figsize --- doc/release/next_whats_new/partial_figsize_none.rst | 12 ++++++++++++ lib/matplotlib/figure.py | 4 ++++ lib/matplotlib/pyplot.py | 4 ++++ 3 files changed, 20 insertions(+) create mode 100644 doc/release/next_whats_new/partial_figsize_none.rst diff --git a/doc/release/next_whats_new/partial_figsize_none.rst b/doc/release/next_whats_new/partial_figsize_none.rst new file mode 100644 index 000000000000..37c3c9b2f547 --- /dev/null +++ b/doc/release/next_whats_new/partial_figsize_none.rst @@ -0,0 +1,12 @@ +Partial ``None`` support for ``figsize`` at figure creation +------------------------------------------------------------ + +Figure creation now accepts a single ``None`` in ``figsize``. +Passing ``(None, h)`` uses the default width from :rc:`figure.figsize`, and +passing ``(w, None)`` uses the default height. +Passing ``(None, None)`` is invalid and raises a `ValueError`. + +For example:: + + import matplotlib.pyplot as plt + fig = plt.figure(figsize=(None, 4)) diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index 21f6f8c00fe8..29c10c51fef3 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -2492,6 +2492,10 @@ def __init__(self, - a tuple ``(width, height)``, which is interpreted in inches, i.e. as ``(width, height, "in")``. + One of *width* or *height* may be ``None``; the missing value is + taken from :rc:`figure.figsize`. Passing ``(None, None)`` is not + allowed and raises a `ValueError`. + dpi : float, default: :rc:`figure.dpi` Dots per inch. diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 483bc031336c..014f0696b43d 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -947,6 +947,10 @@ def figure( "px". - a tuple ``(x, y)``, which is interpreted as ``(x, y, "inch")``. + One of *width* or *height* may be ``None``; the missing value is taken + from :rc:`figure.figsize`. Passing ``(None, None)`` is not allowed and + raises a `ValueError`. + dpi : float, default: :rc:`figure.dpi` The resolution of the figure in dots-per-inch. From d731bd44d93c1287a2b6a21d034d7cccfd56f492 Mon Sep 17 00:00:00 2001 From: Vikash Kumar <163628932+Vikash-Kumar-23@users.noreply.github.com> Date: Tue, 31 Mar 2026 05:35:34 +0530 Subject: [PATCH 10/12] Update doc/release/next_whats_new/partial_figsize_none.rst Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> --- doc/release/next_whats_new/partial_figsize_none.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/release/next_whats_new/partial_figsize_none.rst b/doc/release/next_whats_new/partial_figsize_none.rst index 37c3c9b2f547..43bd3e34cc73 100644 --- a/doc/release/next_whats_new/partial_figsize_none.rst +++ b/doc/release/next_whats_new/partial_figsize_none.rst @@ -1,5 +1,5 @@ -Partial ``None`` support for ``figsize`` at figure creation ------------------------------------------------------------- +Partial ``figsize`` specification at figure creation +---------------------------------------------------- Figure creation now accepts a single ``None`` in ``figsize``. Passing ``(None, h)`` uses the default width from :rc:`figure.figsize`, and From cf868b9820eee3ff9b176e89a379875723cec4cf Mon Sep 17 00:00:00 2001 From: Vikash Kumar <163628932+Vikash-Kumar-23@users.noreply.github.com> Date: Tue, 31 Mar 2026 05:35:53 +0530 Subject: [PATCH 11/12] Update lib/matplotlib/figure.py Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> --- lib/matplotlib/figure.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index 29c10c51fef3..ad0206e0db5c 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -2492,9 +2492,8 @@ def __init__(self, - a tuple ``(width, height)``, which is interpreted in inches, i.e. as ``(width, height, "in")``. - One of *width* or *height* may be ``None``; the missing value is - taken from :rc:`figure.figsize`. Passing ``(None, None)`` is not - allowed and raises a `ValueError`. + One of *width* or *height* may be ``None``; the respective value is + taken from :rc:`figure.figsize`. dpi : float, default: :rc:`figure.dpi` Dots per inch. From c1653cffde70916599fcf2b6bb0a66fdad213303 Mon Sep 17 00:00:00 2001 From: Vikash Kumar <163628932+Vikash-Kumar-23@users.noreply.github.com> Date: Tue, 31 Mar 2026 05:36:11 +0530 Subject: [PATCH 12/12] Update lib/matplotlib/pyplot.py Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> --- lib/matplotlib/pyplot.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 014f0696b43d..ed7821ca1b27 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -947,9 +947,8 @@ def figure( "px". - a tuple ``(x, y)``, which is interpreted as ``(x, y, "inch")``. - One of *width* or *height* may be ``None``; the missing value is taken - from :rc:`figure.figsize`. Passing ``(None, None)`` is not allowed and - raises a `ValueError`. + One of *width* or *height* may be ``None``; the respective value is taken + from :rc:`figure.figsize`. dpi : float, default: :rc:`figure.dpi` The resolution of the figure in dots-per-inch.