Skip to content

Add facetgrid_figsize option to set_options#11158

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

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

Conversation

@kkollsga
Copy link
Copy Markdown
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)

@Illviljan
Copy link
Copy Markdown
Contributor

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

@kkollsga
Copy link
Copy Markdown
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")

Copy link
Copy Markdown
Collaborator

@headtr1ck headtr1ck left a comment

Choose a reason for hiding this comment

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

I don't really see the usecase, but if you went through the effort of setting up a PR there must be some :)
LGTM.

@kkollsga
Copy link
Copy Markdown
Contributor Author

Thanks for the review. The use case comes from #11103, users with matplotlib stylesheets for fixed-size publication figures (IEEE, AGU, etc.) or corporate style guides, where col=/row= silently overrides their rcParams['figure.figsize']. Users without stylesheets won't notice any difference.

@headtr1ck
Copy link
Copy Markdown
Collaborator

Have to be careful when merging #11266, those two PRs need some alignment. But nothing difficult.

@headtr1ck
Copy link
Copy Markdown
Collaborator

There was always some hesitation of adding global configs in the past but I think this is a useful addition.

@headtr1ck
Copy link
Copy Markdown
Collaborator

Actually... Why don't we also support setting the figsize directly as well?

@kkollsga
Copy link
Copy Markdown
Contributor Author

kkollsga commented Mar 29, 2026

Actually... Why don't we also support setting the figsize directly as well?

Good point! A tuple value could work nicely as a third mode alongside "computed" and "rcparams":

# 1. "computed" — current default, figsize scales with panel count
xr.set_options(facetgrid_figsize="computed")

# 2. "rcparams" — defers to matplotlib global state / stylesheet
#    e.g. with a corporate or publication style template:
plt.style.use("acme_corp.mplstyle")  # sets figure.figsize: 7.2, 4.5
xr.set_options(facetgrid_figsize="rcparams")

# 3. tuple — fixed figsize scoped to xarray, independent of mpl.rcParams
#    useful when you want consistent facet grids without touching global state
xr.set_options(facetgrid_figsize=(12, 6))

Let me implement this, and push an update to the pr.

kkollsga and others added 2 commits March 29, 2026 11:33
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>
Extend facetgrid_figsize to accept a (width, height) tuple in addition
to "computed" and "rcparams". Also move figsize resolution before grid
shape computation for better composition with col_wrap="auto" (pydata#11266).

Co-authored-by: Claude <noreply@anthropic.com>
@kkollsga kkollsga force-pushed the fix-11103-facetgrid-figsize-option branch from 87ab727 to 6b1a002 Compare March 29, 2026 09:40
@kkollsga
Copy link
Copy Markdown
Contributor Author

I also looked at the #11266 alignment you mentioned. The figsize from the global option was being resolved after the grid shape computation, so _auto_grid in #11266 wouldn't have access to it. I moved the resolution step earlier so that both features compose cleanly. Whichever PR merges second should only need a straightforward rebase.

@headtr1ck headtr1ck added the plan to merge Final call for comments label Mar 29, 2026
Copy link
Copy Markdown
Collaborator

@headtr1ck headtr1ck left a comment

Choose a reason for hiding this comment

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

Actually one more thing: can you change bullet point 3 here: https://github.com/pydata/xarray/blob/main/doc%2Fuser-guide%2Foptions.rst to be more general about plotting and add your new option.
Thanks!

Co-authored-by: Claude <noreply@anthropic.com>
@kkollsga kkollsga force-pushed the fix-11103-facetgrid-figsize-option branch from 5c0a6db to 9e28044 Compare March 29, 2026 14:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

plan to merge Final call for comments topic-plotting

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']

3 participants