40
40
)
41
41
from seaborn ._core .exceptions import PlotSpecError
42
42
from seaborn ._core .rules import categorical_order
43
- from seaborn ._compat import set_layout_engine
43
+ from seaborn ._compat import get_layout_engine , set_layout_engine
44
44
from seaborn .rcmod import axes_style , plotting_context
45
45
from seaborn .palettes import color_palette
46
46
@@ -810,6 +810,7 @@ def layout(
810
810
* ,
811
811
size : tuple [float , float ] | Default = default ,
812
812
engine : str | None | Default = default ,
813
+ extent : tuple [float , float , float , float ] | Default = default ,
813
814
) -> Plot :
814
815
"""
815
816
Control the figure size and layout.
@@ -825,9 +826,14 @@ def layout(
825
826
size : (width, height)
826
827
Size of the resulting figure, in inches. Size is inclusive of legend when
827
828
using pyplot, but not otherwise.
828
- engine : {{"tight", "constrained", None }}
829
+ engine : {{"tight", "constrained", "none" }}
829
830
Name of method for automatically adjusting the layout to remove overlap.
830
831
The default depends on whether :meth:`Plot.on` is used.
832
+ extent : (left, bottom, right, top)
833
+ Boundaries of the plot layout, in fractions of the figure size. Takes
834
+ effect through the layout engine; exact results will vary across engines.
835
+ Note: the extent includes axis decorations when using a layout engine,
836
+ but it is exclusive of them when `engine="none"`.
831
837
832
838
Examples
833
839
--------
@@ -845,6 +851,8 @@ def layout(
845
851
new ._figure_spec ["figsize" ] = size
846
852
if engine is not default :
847
853
new ._layout_spec ["engine" ] = engine
854
+ if extent is not default :
855
+ new ._layout_spec ["extent" ] = extent
848
856
849
857
return new
850
858
@@ -1793,12 +1801,32 @@ def _finalize_figure(self, p: Plot) -> None:
1793
1801
if axis_key in self ._scales : # TODO when would it not be?
1794
1802
self ._scales [axis_key ]._finalize (p , axis_obj )
1795
1803
1796
- if (engine := p ._layout_spec .get ("engine" , default )) is not default :
1804
+ if (engine_name := p ._layout_spec .get ("engine" , default )) is not default :
1797
1805
# None is a valid arg for Figure.set_layout_engine, hence `default`
1798
- set_layout_engine (self ._figure , engine )
1806
+ set_layout_engine (self ._figure , engine_name )
1799
1807
elif p ._target is None :
1800
1808
# Don't modify the layout engine if the user supplied their own
1801
1809
# matplotlib figure and didn't specify an engine through Plot
1802
1810
# TODO switch default to "constrained"?
1803
1811
# TODO either way, make configurable
1804
1812
set_layout_engine (self ._figure , "tight" )
1813
+
1814
+ if (extent := p ._layout_spec .get ("extent" )) is not None :
1815
+ engine = get_layout_engine (self ._figure )
1816
+ if engine is None :
1817
+ self ._figure .subplots_adjust (* extent )
1818
+ else :
1819
+ # Note the different parameterization for the layout engine rect...
1820
+ left , bottom , right , top = extent
1821
+ width , height = right - left , top - bottom
1822
+ try :
1823
+ # The base LayoutEngine.set method doesn't have rect= so we need
1824
+ # to avoid typechecking this statement. We also catch a TypeError
1825
+ # as a plugin LayoutEngine may not support it either.
1826
+ # Alternatively we could guard this with a check on the engine type,
1827
+ # but that would make later-developed engines would un-useable.
1828
+ engine .set (rect = [left , bottom , width , height ]) # type: ignore
1829
+ except TypeError :
1830
+ # Should we warn / raise? Note that we don't expect to get here
1831
+ # under any normal circumstances.
1832
+ pass
0 commit comments