8000 Resolving: Add setter/getter methods for all keyword parameters to Figure.__init__ (Issue: #24617) by zaheerhashmi · Pull Request #27531 · matplotlib/matplotlib · GitHub
[go: up one dir, main page]

Skip to content

Resolving: Add setter/getter methods for all keyword parameters to Figure.__init__ (Issue: #24617) #27531

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

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 128 additions & 2 deletions lib/matplotlib/figure.py
Original file line number Diff line number Diff l 8000 ine change
Expand Up @@ -10,6 +10,9 @@
`SubFigure`) with `Figure.add_subfigure` or `Figure.subfigures` methods
(provisional API v3.4).

`SubplotParams`
Control the default spacing between subplots.

Figures are typically created using pyplot methods `~.pyplot.figure`,
`~.pyplot.subplots`, and `~.pyplot.subplot_mosaic`.

Expand Down Expand Up @@ -48,7 +51,7 @@
import matplotlib.image as mimage

from matplotlib.axes import Axes
from matplotlib.gridspec import GridSpec, SubplotParams
from matplotlib.gridspec import GridSpec
Copy link
Member

Choose a reason for hiding this comment

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

Why did this need to come back?

from matplotlib.layout_engine import (
ConstrainedLayoutEngine, TightLayoutEngine, LayoutEngine,
PlaceHolderLayoutEngine
Expand Down Expand Up @@ -115,6 +118,66 @@ def __setstate__(self, state):
self._counter = itertools.count(next_counter)


class SubplotParams:
"""
A class to hold the parameters for a subplot.
"""

def __init__(self, left=None, bottom=None, right=None, top=None,
wspace=None, hspace=None):
"""
Defaults are given by :rc:`figure.subplot.[name]`.

Parameters
----------
left : float
The position of the left edge of the subplots,
as a fraction of the figure width.
right : float
The position of the right edge of the subplots,
as a fraction of the figure width.
bottom : float
The position of the bottom edge of the subplots,
as a fraction of the figure height.
top : float
The position of the top edge of the subplots,
as a fraction of the figure height.
wspace : float
The width of the padding between subplots,
as a fraction of the average Axes width.
hspace : float
The height of the padding between subplots,
as a fraction of the average Axes height.
"""
for key in ["left", "bottom", "right", "top", "wspace", "hspace"]:
setattr(self, key, mpl.rcParams[f"figure.subplot.{key}"])
self.update(left, bottom, right, top, wspace, hspace)

def update(self, left=None, bottom=None, right=None, top=None,
wspace=None, hspace=None):
"""
Update the dimensions of the passed parameters. *None* means unchanged.
"""
if ((left if left is not None else self.left)
>= (right if right is not None else self.right)):
raise ValueError('left cannot be >= right')
if ((bottom if bottom is not None else self.bottom)
>= (top if top is not None else self.top)):
raise ValueError('bottom cannot be >= top')
if left is not None:
self.left = left
if right is not None:
self.right = right
if bottom is not None:
self.bottom = bottom
if top is not None:
self.top = top
if wspace is not None:
self.wspace = wspace
if hspace is not None:
self.hspace = hspace


class FigureBase(Artist):
"""
Base class for `.Figure` and `.SubFigure` containing the methods that add
Expand Down Expand Up @@ -1291,6 +1354,65 @@ def subplots_adjust(self, left=None, bottom=None, right=None, top=None,
ax._set_position(ax.get_subplotspec().get_position(self))
self.stale = True

def set_subplotpars(self, subplotparams={}):
Copy link
Member

Choose a reason for hiding this comment

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

Default values should not be mutable objects like a dictionary. You should set it to None, and then check that in the function.

Suggested change
def set_subplotpars(self, subplotparams={}):
def set_subplotpars(self, subplotparams=None):

"""
Set the subplot layout parameters.
Accepts either a `.SubplotParams` object, from which the relevant
parameters are copied, or a dictionary of subplot layout parameters.
If a dictionary is provided, this function is a convenience wrapper for
`matplotlib.figure.Figure.subplots_adjust`

Parameters
----------
subplotparams : `~matplotlib.figure.SubplotParams` or dict with keys
"left", "bottom", "right", 'top", "wspace", "hspace"] , optional
SubplotParams object to copy new subplot parameters from, or a dict
of SubplotParams constructor arguments.
By default, an empty dictionary is passed, which maintains the
current state of the figure's `.SubplotParams`

See Also
--------
matplotlib.figure.Figure.subplots_adjust
matplotlib.figure.Figure.get_subplotpars
"""
subplotparams_args = ["left", "bottom", "right",
"top", "wspace", "hspace"]
kwargs = {}
if isinstance(subplotparams, SubplotParams):
for key in subplotparams_args:
kwargs[key] = getattr(subplotparams, key)
elif isinstance(subplotparams, dict):
for key in subplotparams.keys():
if key in subplotparams_args:
kwargs[key] = subplotparams[key]
else:
_api.warn_external(
f"'{key}' is not a valid key for set_subplotpars;"
" this key was ignored.")
Comment on lines +1389 to +1392
Copy link
Member

Choose a reason for hiding this comment

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

As a new function, this should be an error, not a warning.

else:
raise TypeError(
"subplotpars must be a dictionary of keyword-argument pairs or"
" an instance of SubplotParams()")
if kwargs == {}:
self.set_subplotpars(self.get_subplotpars())
Comment on lines +1397 to +1398
Copy link
Member

C 8000 hoose a reason for hiding this comment

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

I don't understand what this is doing?

self.subplots_adjust(**kwargs)

def get_subplotpars(self):
"""
Return the `.SubplotParams` object associated with the Figure.

Returns
-------
`.SubplotParams`

See Also
--------
matplotlib.figure.Figure.subplots_adjust
matplotlib.figure.Figure.get_subplotpars
"""
return self.subplotpars

def align_xlabels(self, axs=None):
"""
Align the xlabels of subplots in the same subplot column if label
Expand Down Expand Up @@ -2306,6 +2428,10 @@ def draw(self, renderer):


@_docstring.interpd
@_api.define_aliases({
"size_inches": ["figsize"],
"layout_engine": ["layout"]
})
class Figure(FigureBase):
"""
The top level container for all the plot elements.
Expand Down Expand Up @@ -2381,7 +2507,7 @@ def __init__(self,
frameon : bool, default: :rc:`figure.frameon`
If ``False``, suppress drawing the figure background patch.

subplotpars : `~matplotlib.gridspec.SubplotParams`
subplotpars : `SubplotParams`
Subplot parameters. If not given, the default subplot
parameters :rc:`figure.subplot.*` are used.

Expand Down
35 changes: 34 additions & 1 deletion lib/matplotlib/figure.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ from matplotlib.backend_bases import (
from matplotlib.colors import Colormap, Normalize
from matplotlib.colorbar import Colorbar
from matplotlib.cm import ScalarMappable
from matplotlib.gridspec import GridSpec, SubplotSpec, SubplotParams as SubplotParams
from matplotlib.gridspec import GridSpec, SubplotSpec
from matplotlib.image import _ImageBase, FigureImage
from matplotlib.layout_engine import LayoutEngine
from matplotlib.legend import Legend
Expand All @@ -28,6 +28,32 @@ from .typing import ColorType, HashableList

_T = TypeVar("_T")

class SubplotParams:
def __init__(
self,
left: float | None = ...,
bottom: float | None = ...,
right: float | None = ...,
top: float | None = ...,
wspace: float | None = ...,
hspace: float | None = ...,
) -> None: ...
left: float
right: float
bottom: float
top: float
wspace: float
hspace: float
def update(
self,
left: float | None = ...,
bottom: float | None = ...,
right: float | None = ...,
top: float | None = ...,
wspace: float | None = ...,
hspace: float | None = ...,
) -> None: ...

class FigureBase(Artist):
artists: list[Artist]
lines: list[Line2D]
Expand Down Expand Up @@ -244,6 +270,13 @@ class FigureBase(Artist):
gridspec_kw: dict[str, Any] | None = ...,
) -> dict[Hashable, Axes]: ...

def set_subplotpars(
self,
subplotparams: SubplotParams | dict[str, Any] = ...,
) -> None: ...

def get_subplotpars(self) -> SubplotParams: ...

class SubFigure(FigureBase):
figure: Figure
subplotpars: SubplotParams
Expand Down
39 changes: 39 additions & 0 deletions lib/matplotlib/tests/test_figure.py
Original file line number Diff line number Diff line change
Expand Up @@ -1652,6 +1652,45 @@ def test_get_constrained_layout_pads():
assert fig.get_constrained_layout_pads() == expected


def test_get_subplot_params():
fig = plt.figure()
subplotparams_keys = ["left", "bottom", "right", "top", "wspace", "hspace"]
subplotparams = fig.get_subplotpars()
test_dict = {}
for key in subplotparams_keys:
attr = getattr(subplotparams, key)
assert attr == mpl.rcParams[f"figure.subplot.{key}"]
test_dict[key] = attr * 2

fig.set_subplotpars(test_dict)
for key, value in test_dict.items():
assert getattr(fig.get_subplotpars(), key) == value

test_dict['foo'] = 'bar'
with pytest.warns(UserWarning,
match="'foo' is not a valid key for set_subplotpars;"
" this key was ignored"):
fig.set_subplotpars(test_dict)

with pytest.raises(TypeError,
match="subplotpars must be a dictionary of "
"keyword-argument pairs or "
"an instance of SubplotParams()"):
fig.set_subplotpars(['foo'])

assert fig.subplotpars == fig.get_subplotpars()


def test_fig_get_set():
varnames = filter(lambda var: var not in ['self', 'kwargs', 'args', 'connect'],
Figure.__init__.__code__.co_varnames)
fig = plt.figure()
for var in varnames:
# if getattr fails then the getter and setter does not exist
getfunc = getattr(fig, f"get_{var}")
setfunc = getattr(fig, f"set_{var}")


def test_not_visible_figure():
fig = Figure()

Expand Down
0