Skip to content

Comments

Add facetgrid_figsize option to set_options#11158

Open
kkollsga wants to merge 2 commits intopydata:mainfrom
kkollsga:fix-11103-facetgrid-figsize-option
Open

Add facetgrid_figsize option to set_options#11158
kkollsga wants to merge 2 commits intopydata:mainfrom
kkollsga:fix-11103-facetgrid-figsize-option

Conversation

@kkollsga
Copy link
Contributor

@kkollsga kkollsga commented Feb 10, 2026

Summary

  • Adds a facetgrid_figsize option to xr.set_options() with two modes:
    • "computed" (default) — current behavior, figure size derived from size and aspect
    • "rcparams" — use matplotlib.rcParams['figure.figsize'] as the total figure size
  • Explicit figsize parameter still takes precedence over both modes
  • Follows the existing pattern of plot-related global options (cmap_sequential, cmap_divergent)

Test plan

  • Validator test: invalid value raises ValueError, context manager works for both values
  • Functional test: default behavior unchanged, "rcparams" mode uses mpl.rcParams, explicit figsize overrides
  • Full test_options.py suite passes (21 tests)
  • Full TestFacetGrid class passes (22 tests)
  • pre-commit passes (all hooks)
  • mypy has only pre-existing errors (missing stubs for pytest/dask/flox)

Add a new `facetgrid_figsize` option to `xr.set_options()` that controls
how FacetGrid determines figure size when `figsize` is not explicitly
passed. When set to `"rcparams"`, FacetGrid uses
`matplotlib.rcParams['figure.figsize']` instead of computing size from
`size` and `aspect`. Default is `"computed"` (current behavior).

Co-authored-by: Claude <noreply@anthropic.com>
@Illviljan
Copy link
Contributor

Could you add before and after plots?
I'm in particular interested how labels will look like.

@kkollsga
Copy link
Contributor Author

Good point! Here are before/after comparisons:

3-column facet grid:

facetgrid_comparison_3col

Top: "computed" (default) — current behavior, figsize derived from size × aspect × ncol → (10, 3)
Middle: "rcparams" with rcParams['figure.figsize'] = (10, 4) — similar layout, labels look fine
Bottom: "rcparams" with matplotlib's default (6.4, 4.8) — panels get narrower as expected when using a fixed figure size

6-panel facet grid with col_wrap=3:

facetgrid_comparison_6col

Top: "computed" (default)
Bottom: "rcparams" with rcParams['figure.figsize'] = (12, 8)

The idea is that users opting into "rcparams" mode are typically those who already configure their global figsize to something sensible for their workflow. With a small default figsize the panels naturally get compressed, but that's the expected trade-off of using a fixed size regardless of facet count.

Note: when adding a suptitle to a FacetGrid, it collides with the column titles by default (both modes). Setting y=1.05 fixes this:

g.fig.suptitle("My title", y=1.05)
Script to reproduce these plots
import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np
import xarray as xr
from PIL import Image

np.random.seed(42)


def make_sample_data(n_categories=3):
    labels = [f"category_{i+1}" for i in range(n_categories)]
    return xr.DataArray(
        np.random.randn(20, 25, n_categories),
        dims=["y", "x", "z"],
        coords={
            "x": np.linspace(-10, 10, 25),
            "y": np.linspace(-5, 5, 20),
            "z": labels,
        },
        name="temperature anomaly (°C)",
    )


def stack_images(paths, output, gap=30):
    imgs = [Image.open(p) for p in paths]
    width = max(im.width for im in imgs)
    height = sum(im.height for im in imgs) + gap * (len(imgs) - 1)
    combined = Image.new("RGB", (width, height), (255, 255, 255))
    y_offset = 0
    for im in imgs:
        combined.paste(im, (0, y_offset))
        y_offset += im.height + gap
    combined.save(output)


da3 = make_sample_data(3)
da6 = make_sample_data(6)

# -- Scenario 1: 3 columns --

g = da3.plot.pcolormesh(col="z", cmap="RdBu_r")
g.fig.suptitle('Mode: "computed" (default) — figsize = (10.0, 3.0)',
               fontsize=13, weight="bold", y=1.05)
g.fig.savefig("1a.png", dpi=150, bbox_inches="tight")
plt.close(g.fig)

with mpl.rc_context({"figure.figsize": (10.0, 4.0)}):
    with xr.set_options(facetgrid_figsize="rcparams"):
        g = da3.plot.pcolormesh(col="z", cmap="RdBu_r")
        g.fig.suptitle('Mode: "rcparams" — figsize = (10.0, 4.0)',
                       fontsize=13, weight="bold", y=1.05)
        g.fig.savefig("1b.png", dpi=150, bbox_inches="tight")
        plt.close(g.fig)

with xr.set_options(facetgrid_figsize="rcparams"):
    g = da3.plot.pcolormesh(col="z", cmap="RdBu_r")
    g.fig.suptitle('Mode: "rcparams" — mpl default figsize (6.4, 4.8)',
                   fontsize=13, weight="bold", y=1.05)
    g.fig.savefig("1c.png", dpi=150, bbox_inches="tight")
    plt.close(g.fig)

stack_images(["1a.png", "1b.png", "1c.png"], "facetgrid_comparison_3col.png")

# -- Scenario 2: 6 columns with col_wrap=3 --

g = da6.plot.pcolormesh(col="z", col_wrap=3, cmap="viridis")
g.fig.suptitle('Mode: "computed" (default) — 6 panels, col_wrap=3',
               fontsize=13, weight="bold", y=1.02)
g.fig.savefig("2a.png", dpi=150, bbox_inches="tight")
plt.close(g.fig)

with mpl.rc_context({"figure.figsize": (12.0, 8.0)}):
    with xr.set_options(facetgrid_figsize="rcparams"):
        g = da6.plot.pcolormesh(col="z", col_wrap=3, cmap="viridis")
        g.fig.suptitle('Mode: "rcparams" — figsize = (12.0, 8.0)',
                       fontsize=13, weight="bold", y=1.02)
        g.fig.savefig("2b.png", dpi=150, bbox_inches="tight")
        plt.close(g.fig)

stack_images(["2a.png", "2b.png"], "facetgrid_comparison_6col.png")

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 global config for FacetGrid to always follow matplotlib.rcParams['figure.figsize']

2 participants