Source code for plotnine.geoms.annotate

from __future__ import annotations

import typing

import pandas as pd

from ..exceptions import PlotnineError
from ..geoms.geom import geom as geom_base_class
from ..mapping import aes
from ..mapping.aes import POSITION_AESTHETICS
from ..utils import Registry, is_scalar_or_string

if typing.TYPE_CHECKING:
    from typing import Any

    from plotnine.typing import Ggplot, Layer


[docs]class annotate: """ Create an annotation layer Parameters ---------- geom : geom or str geom to use for annotation, or name of geom (e.g. 'point'). x : float Position y : float Position xmin : float Position ymin : float Position xmax : float Position ymax : float Position xend : float Position yend : float Position xintercept : float Position yintercept : float Position kwargs : dict Other aesthetics or parameters to the geom. Notes ----- The positioning aethetics ``x, y, xmin, ymin, xmax, ymax, xend, yend, xintercept, yintercept`` depend on which `geom` is used. You should choose or ignore accordingly. All `geoms` are created with :code:`stat='identity'`. """ _annotation_geom: geom_base_class def __init__( self, geom: str | type[geom_base_class], x: float | None = None, y: float | None = None, xmin: float | None = None, xmax: float | None = None, xend: float | None = None, xintercept: float | None = None, ymin: float | None = None, ymax: float | None = None, yend: float | None = None, yintercept: float | None = None, **kwargs: Any, ): variables = locals() # position only, and combined aesthetics pos_aesthetics = { loc: variables[loc] for loc in POSITION_AESTHETICS if variables[loc] is not None } aesthetics = pos_aesthetics.copy() aesthetics.update(kwargs) # Check if the aesthetics are of compatible lengths lengths, info_tokens = [], [] for ae, val in aesthetics.items(): if is_scalar_or_string(val): continue lengths.append(len(val)) info_tokens.append((ae, len(val))) if len(set(lengths)) > 1: details = ", ".join([f"{n} ({l})" for n, l in info_tokens]) msg = f"Unequal parameter lengths: {details}" raise PlotnineError(msg) # Stop pandas from complaining about all scalars if all(is_scalar_or_string(val) for val in pos_aesthetics.values()): for ae in pos_aesthetics.keys(): pos_aesthetics[ae] = [pos_aesthetics[ae]] break data = pd.DataFrame(pos_aesthetics) if isinstance(geom, str): geom_klass: type[geom_base_class] = Registry[f"geom_{geom}"] elif isinstance(geom, type) and issubclass(geom, geom_base_class): geom_klass = geom else: raise PlotnineError( "geom must either be a plotnine.geom.geom() " "descendant (e.g. plotnine.geom_point), or " "a string naming a geom (e.g. 'point', 'text', " f"...). Got {repr(geom)}" ) mappings = aes(**{str(ae): ae for ae in data.columns}) # The positions are mapped, the rest are manual settings self._annotation_geom = geom_klass( mappings, data, stat="identity", inherit_aes=False, show_legend=False, **kwargs, ) def __radd__(self, gg: Ggplot) -> Ggplot: """ Add to ggplot """ gg += self.to_layer() # Add layer return gg def to_layer(self) -> Layer: """ Make a layer that represents this annotation Returns ------- out : layer Layer """ return self._annotation_geom.to_layer()