Source code for plotnine.guides.guide

from __future__ import annotations

import typing

from ..exceptions import PlotnineError
from ..utils import Registry

if typing.TYPE_CHECKING:
    from typing import Any, Literal, Optional

    import pandas as pd

    from plotnine.typing import Scale, Theme


[docs]class guide(metaclass=Registry): """ Base class for all guides Parameters ---------- title : str | None Title of the guide. If ``None``, title is not shown. Default is the name of the aesthetic or the name specified using :class:`~plotnine.components.labels.lab` title_position : str in ``['top', 'bottom', 'left', 'right']`` Position of title title_theme : element_text Control over the title theme. Default is to use ``legend_title`` in a theme. title_hjust : float Horizontal justification of title text. title_vjust : float Vertical justification of title text. title_separation : float Separation between the title text and the colorbar. Value is in pixels. label : bool Whether to show labels label_position : str in ``['top', 'bottom', 'left', 'right']`` Position of the labels. The defaults are ``'bottom'`` for a horizontal guide and '``right``' for a vertical guide. label_theme : element_text Control over the label theme. Default is to use ``legend_text`` in a theme. label_hjust : float Horizontal justification of label text. label_vjust : float Vertical justification of label text. label_separation : float Separation between the label text and the colorbar. Value is in pixels. direction : str in ``['horizontal', 'vertical']`` Direction of the guide. default_unit : str Unit for ``keywidth`` and ``keyheight`` override_aes : dict Aesthetic parameters of legend key. reverse : bool Whether to reverse the order of the legends. order : int Order of this guide among multiple guides. Should be in the range [0, 99]. Default is ``0``. Notes ----- At the moment not all parameters have been fully implemented. """ __base__ = True # Must be updated before the draw method is called theme: Theme key: pd.DataFrame # title title: str title_position: Literal["top", "bottom", "left", "right"] title_theme = None title_hjust: Optional[float] = None title_vjust: Optional[float] = None # label label = True label_position: Literal["top", "bottom", "left", "right"] label_theme = None label_hjust: Optional[float] = None label_vjust: Optional[float] = None # general direction: Optional[Literal["horizontal", "vertical"]] = None default_unit = "line" override_aes: dict[str, Any] = {} reverse = False order = 0 # Aesthetics covered by the guide available_aes: set[str] = set() def __init__(self, **kwargs): no_default = {"title", "title_position", "label_position"} for k, v in kwargs.items(): if hasattr(self, k) or k in no_default: setattr(self, k, v) else: tpl = "{} does not undestand attribute '{}'" raise PlotnineError(tpl.format(self.__class__.__name__, k)) def _set_defaults(self, theme: Theme): """ Set configuration parameters for drawing guide """ self.theme = theme valid_locations = {"top", "bottom", "left", "right"} _property = theme.themeables.property margin_location_lookup = { # Where to put the margin between the legend and # the axes. Depends on the location of the legend "t": "b", "b": "t", "l": "r", "r": "l", } # label position if not hasattr(self, "label_position"): self.label_position = "right" if self.label_position not in valid_locations: msg = "label position '{}' is invalid" raise PlotnineError(msg.format(self.label_position)) # label margin # legend_text_legend or legend_text_colorbar _legend_type = self.__class__.__name__.split("_")[-1] name = "legend_text_{}".format(_legend_type) loc = margin_location_lookup[self.label_position[0]] margin = _property(name, "margin") self._label_margin = margin.get_as(loc, "pt") # direction of guide if self.direction is None: if self.label_position in ("right", "left"): self.direction = "vertical" else: self.direction = "horizontal" # title position if not hasattr(self, "title_position"): if self.direction == "vertical": self.title_position = "top" elif self.direction == "horizontal": self.title_position = "left" if self.title_position not in valid_locations: msg = "legend title position '{}' is invalid" raise PlotnineError(msg.format(self.title_position)) # title alignment self._title_align = _property("legend_title_align") if self._title_align == "auto": if self.direction == "vertical": self._title_align = "left" else: self._title_align = "center" # by default, direction of each guide depends on # the position all the guides position = _property("legend_position") self.direction = _property("legend_direction") if self.direction == "auto": if position in ("right", "left"): # default self.direction = "vertical" else: self.direction = "horizontal" # title margin loc = margin_location_lookup[self.title_position[0]] margin = _property("legend_title", "margin") self._title_margin = margin.get_as(loc, "pt") # legend_margin self._legend_margin = _property("legend_margin") # legend_entry_spacing self._legend_entry_spacing_x = _property("legend_entry_spacing_x") self._legend_entry_spacing_y = _property("legend_entry_spacing_y") def legend_aesthetics(self, layer, plot): """ Return the aesthetics that contribute to the legend Parameters ---------- layer : Layer Layer whose legend is to be drawn plot : ggplot Plot object Returns ------- matched : list List of the names of the aethetics that contribute to the legend. """ l = layer legend_ae = set(self.key.columns) - {"label"} all_ae = ( l.mapping.keys() | (plot.mapping if l.inherit_aes else set()) | l.stat.DEFAULT_AES.keys() ) geom_ae = l.geom.REQUIRED_AES | l.geom.DEFAULT_AES.keys() matched = all_ae & geom_ae & legend_ae matched = list(matched - set(l.geom.aes_params)) return matched def train( self, scale: Scale, aesthetic: Optional[str] = None ) -> guide | None: """ Create the key for the guide Returns guide if training is successful """