Source code for plotnine.facets.labelling

from __future__ import annotations

import typing
from abc import ABCMeta, abstractmethod

from ..exceptions import PlotnineError

if typing.TYPE_CHECKING:
    from typing import Callable, Optional

    from ..iapi import strip_label_details
    from ..typing import (
        CanBeStripLabellingFunc,
        StripLabellingFunc,
        StripLabellingFuncNames,
    )


[docs]def label_value( label_info: strip_label_details, multi_line: bool = True ) -> strip_label_details: """ Keep value as the label Parameters ---------- label_info : strip_label_details Label information whose values will be returned multi_line : bool Whether to place each variable on a separate line Returns ------- out : strip_label_details Label text strings """ label_info = label_info.copy() if not multi_line: label_info = label_info.collapse() return label_info
[docs]def label_both( label_info: strip_label_details, multi_line: bool = True, sep: str = ": " ) -> strip_label_details: """ Concatenate the facet variable with the value Parameters ---------- label_info : strip_label_details Label information to be modified. multi_line : bool Whether to place each variable on a separate line sep : str Separation between variable name and value Returns ------- out : strip_label_details Label information """ label_info = label_info.copy() for var, lvalue in label_info.variables.items(): label_info.variables[var] = f"{var}{sep}{lvalue}" if not multi_line: label_info = label_info.collapse() return label_info
[docs]def label_context( label_info: strip_label_details, multi_line: bool = True, sep: str = ": " ) -> strip_label_details: """ Create an unabiguous label string If facetting over a single variable, `label_value` is used, if two or more variables then `label_both` is used. Parameters ---------- label_info : strip_label_details Label information multi_line : bool Whether to place each variable on a separate line sep : str Separation between variable name and value Returns ------- out : str Contatenated label values (or pairs of variable names & values) """ if len(label_info) == 1: return label_value(label_info, multi_line) else: return label_both(label_info, multi_line, sep)
LABELLERS: dict[StripLabellingFuncNames, StripLabellingFunc] = { "label_value": label_value, "label_both": label_both, "label_context": label_context, }
[docs]def as_labeller( x: Optional[CanBeStripLabellingFunc] = None, default: CanBeStripLabellingFunc = label_value, multi_line: bool = True, ) -> labeller: """ Coerse to labeller Parameters ---------- x : function | dict Object to coerce default : str | function Default labeller. If it is a string, it should be the name of one the labelling functions provided by plotnine. multi_line : bool Whether to place each variable on a separate line Returns ------- out : labeller Labelling function """ if x is None: x = default if isinstance(x, labeller): return x x = _as_strip_labelling_func(x) return labeller(rows=x, cols=x, multi_line=multi_line)
[docs]class labeller: """ Facet Strip Labelling When called with strip_label_details knows how to alter the strip labels along either dimension. Parameters ---------- rows : str | function | None How to label the rows cols : str | function | None How to label the columns multi_line : bool Whether to place each variable on a separate line default : function | str Fallback labelling function. If it is a string, it should be the name of one the labelling functions provided by plotnine. kwargs : dict {variable name : function | string} pairs for renaming variables. A function to rename the variable or a string name. """ def __init__( self, rows: Optional[CanBeStripLabellingFunc] = None, cols: Optional[CanBeStripLabellingFunc] = None, multi_line: bool = True, default: CanBeStripLabellingFunc = "label_value", **kwargs: Callable[[str], str], ): # Sort out the labellers along each dimension self.rows_labeller = _as_strip_labelling_func(rows, default) self.cols_labeller = _as_strip_labelling_func(cols, default) self.multi_line = multi_line self.variable_maps = kwargs def __call__(self, label_info: strip_label_details) -> strip_label_details: """ Called to do the labelling """ variable_maps = { k: v for k, v in self.variable_maps.items() if k in label_info.variables } # No variable specific labeller if label_info.meta["dimension"] == "rows": result = self.rows_labeller(label_info) else: result = self.cols_labeller(label_info) # Make dict_labeler for the variable specific labelers # do the label and merge if variable_maps: d = { value: variable_maps[var] for var, value in label_info.variables.items() if var in variable_maps } func = _as_strip_labelling_func(d) result2 = func(label_info) result.variables.update(result2.variables) if not self.multi_line: result = result.collapse() return result
def _as_strip_labelling_func( fobj: Optional[CanBeStripLabellingFunc], default: CanBeStripLabellingFunc = "label_value", ) -> StripLabellingFunc: """ Create a function that can operate on strip_label_details """ if fobj is None: fobj = default if isinstance(fobj, str) and fobj in LABELLERS: return LABELLERS[fobj] if isinstance(fobj, _core_labeller): return fobj elif callable(fobj): if fobj.__name__ in LABELLERS: return fobj # type: ignore else: return _function_labeller(fobj) # type: ignore elif isinstance(fobj, dict): return _dict_labeller(fobj) else: msg = f"Could not create a labelling function for with `{fobj}`." raise PlotnineError(msg) class _core_labeller(metaclass=ABCMeta): """ Per item """ @abstractmethod def __call__(self, label_info: strip_label_details) -> strip_label_details: pass class _function_labeller(_core_labeller): """ Use a function turn facet value into a label Parameters ---------- func : callable Function to label an individual string """ def __init__(self, func: Callable[[str], str]): self.func = func def __call__(self, label_info: strip_label_details) -> strip_label_details: label_info = label_info.copy() variables = label_info.variables for facet_var, facet_value in variables.items(): variables[facet_var] = self.func(facet_value) return label_info class _dict_labeller(_core_labeller): """ Use a dict to alter specific facet values Parameters ---------- lookup : dict A dict of the one of the forms - {facet_value: label_value} - {facet_value: callable(<label_value>)} """ def __init__( self, lookup: dict[str, str] | dict[str, Callable[[str], str]] ): self.lookup = lookup def __call__(self, label_info: strip_label_details) -> strip_label_details: label_info = label_info.copy() variables = label_info.variables # Replace facet_value with values from the lookup table # If the value is function, call it the result of calling function for facet_var, facet_value in variables.items(): target = self.lookup.get(facet_value) if target is None: continue elif callable(target): variables[facet_var] = target(facet_value) else: variables[facet_var] = target return label_info