Source code for plotnine_extra.facets.facetted_pos_scales

"""
Per-panel position scales for facetted plots.

This module provides :func:`facetted_pos_scales`, which allows
applying distinct position scales (x and/or y) to individual
facet panels.  When added to a :class:`~plotnine.ggplot` object
via the ``+`` operator, the per-panel scales replace the cloned
defaults that plotnine creates during ``init_scales``.

.. note::

   The facet used in the plot must produce a layout where each
   panel has a unique ``SCALE_X`` / ``SCALE_Y`` value (i.e.
   ``scales="free"`` or ``scales="free_x"``/``"free_y"``, or
   the panel must be produced by :class:`facet_grid2` with
   ``independent="x"``/``"y"``/``"all"``).  When scales are
   shared (``SCALE_X == 1`` for all panels) there is only a
   single scale object and per-panel overrides have no effect.
"""

from __future__ import annotations

from copy import copy
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from typing import Optional, Sequence

    from plotnine import ggplot


class FacettedPosScales:
    """
    Container for per-panel position scales.

    Holds lists of x and/or y scales that are applied
    individually to each facet panel column (x) or row (y).

    When added to a ggplot (via ``+``), it stores itself on the
    plot object so that :class:`facet_grid2` or
    :class:`facet_wrap2` can pick it up during scale
    initialisation.

    Parameters
    ----------
    x : list of scales, optional
        Position scales for the x-axis, one per panel or per
        unique ``SCALE_X`` group.  ``None`` entries are
        skipped (the default scale is kept for that panel).
    y : list of scales, optional
        Position scales for the y-axis, one per panel or per
        unique ``SCALE_Y`` group.  ``None`` entries are
        skipped.
    """

    def __init__(
        self,
        x: Optional[Sequence] = None,
        y: Optional[Sequence] = None,
    ):
        self.x: list = list(x) if x is not None else []
        self.y: list = list(y) if y is not None else []

    def __radd__(self, other: ggplot) -> ggplot:
        """
        Allow ``ggplot() + facetted_pos_scales(...)``.

        Attaches this object to the plot so that compatible
        facets can consume it during scale initialisation.
        """
        other._facetted_pos_scales = self  # type: ignore[attr-defined]
        return other

    def apply(self, scales_ns: object) -> None:
        """
        Replace scale objects in *scales_ns* with those stored here.

        Parameters
        ----------
        scales_ns : SimpleNamespace
            The namespace returned by ``facet.init_scales()``.
            It has ``.x`` and ``.y`` attributes, each a
            :class:`~plotnine.scales.scales.Scales` list.
        """
        if self.x and hasattr(scales_ns, "x"):
            panel_scales_x = scales_ns.x  # type: ignore[union-attr]
            for i, sc in enumerate(self.x):
                if sc is not None and i < len(panel_scales_x):
                    cloned = sc.clone() if hasattr(sc, "clone") else copy(sc)
                    panel_scales_x[i] = cloned

        if self.y and hasattr(scales_ns, "y"):
            panel_scales_y = scales_ns.y  # type: ignore[union-attr]
            for i, sc in enumerate(self.y):
                if sc is not None and i < len(panel_scales_y):
                    cloned = sc.clone() if hasattr(sc, "clone") else copy(sc)
                    panel_scales_y[i] = cloned


[docs] def facetted_pos_scales( x: Optional[Sequence] = None, y: Optional[Sequence] = None, ) -> FacettedPosScales: """ Set individual position scales for facet panels. Returns an object that, when added to a :class:`~plotnine.ggplot`, attaches per-panel position scales. Compatible facets (e.g. :class:`facet_grid2`, :class:`facet_wrap2`) will pick up these scales and apply them during plotting. Parameters ---------- x : list of scales, optional Position scales for the x-axis, one per panel column (or per unique ``SCALE_X`` group). y : list of scales, optional Position scales for the y-axis, one per panel row (or per unique ``SCALE_Y`` group). Returns ------- FacettedPosScales Object that can be added to a ggplot to apply per-panel position scales. Examples -------- .. code-block:: python from plotnine import * from plotnine_extra.facets import ( facet_wrap2, facetted_pos_scales, ) ( ggplot(df, aes("x", "y")) + geom_point() + facet_wrap2("group", scales="free_x") + facetted_pos_scales( x=[ scale_x_continuous(limits=(0, 10)), scale_x_continuous(limits=(0, 50)), ] ) ) """ return FacettedPosScales(x=x, y=y)