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
@headtr1ck
Copy link
Copy Markdown
Collaborator

@Illviljan any last remark before we merge?

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