Source code for plotnine.geoms.geom_text

from contextlib import suppress

from matplotlib.text import Text

try:
    from adjustText import adjust_text
except ImportError:
    HAS_ADJUST_TEXT = False
else:
    HAS_ADJUST_TEXT = True

from ..utils import to_rgba, order_as_mapping_data
from ..doctools import document
from ..positions import position_nudge
from ..exceptions import PlotnineError
from .geom import geom


# Note: hjust & vjust are parameters instead of aesthetics
# due to a limitation imposed by MPL
# see: https://github.com/matplotlib/matplotlib/pull/1181
[docs]@document class geom_text(geom): """ Textual annotations {usage} Parameters ---------- {common_parameters} parse : bool (default: False) If :py:`True`, the labels will be rendered with `latex <http://matplotlib.org/users/usetex.html>`_. family : str (default: None) Font family. fontweight : int or str (default: normal) Font weight. fontstyle : str (default: normal) Font style. One of *normal*, *italic* or *oblique* nudge_x : float (default: 0) Horizontal adjustment to apply to the text nudge_y : float (default: 0) Vertical adjustment to apply to the text adjust_text: dict (default: None) Parameters to :class:`adjustText.adjust_text` will repel overlapping texts. This parameter takes priority of over ``nudge_x`` and ``nudge_y``. See https://github.com/Phlya/adjustText/wiki . format_string : str (default: None) If not :py:`None`, then the text if formatted with this string using :meth:`str.format` path_effects : list (default: None) If not :py:`None`, then the text will use these effects. See `path_effects <https://matplotlib.org/tutorials/advanced/patheffects_guide.html>`_ documentation for more details. See Also -------- matplotlib.text.Text matplotlib.patheffects """ _aesthetics_doc = """ {aesthetics_table} .. rubric:: Aesthetics Descriptions ha Horizontal alignment. One of *left*, *center* or *right.* va Vertical alignment. One of *top*, *center*, *bottom*, *baseline*. """ DEFAULT_AES = {'alpha': 1, 'angle': 0, 'color': 'black', 'size': 11, 'lineheight': 1.2, 'ha': 'center', 'va': 'center'} REQUIRED_AES = {'label', 'x', 'y'} DEFAULT_PARAMS = {'stat': 'identity', 'position': 'identity', 'na_rm': False, 'parse': False, 'family': None, 'fontweight': 'normal', 'fontstyle': 'normal', 'nudge_x': 0, 'nudge_y': 0, 'adjust_text': None, 'format_string': None, 'path_effects': None} def __init__(self, mapping=None, data=None, **kwargs): mapping, data = order_as_mapping_data(mapping, data) nudge_kwargs = {} adjust_text = kwargs.get('adjust_text', None) if adjust_text is None: with suppress(KeyError): nudge_kwargs['x'] = kwargs['nudge_x'] with suppress(KeyError): nudge_kwargs['y'] = kwargs['nudge_y'] if nudge_kwargs: kwargs['position'] = position_nudge(**nudge_kwargs) elif not HAS_ADJUST_TEXT: raise PlotnineError( "To use adjust_text you must install the adjustText " "package." ) # Accomodate the old names if mapping and 'hjust' in mapping: mapping['ha'] = mapping.pop('hjust') if mapping and 'vjust' in mapping: mapping['va'] = mapping.pop('vjust') geom.__init__(self, mapping, data, **kwargs) def setup_data(self, data): parse = self.params['parse'] fmt = self.params['format_string'] # format if fmt: data['label'] = [fmt.format(l) for l in data['label']] # Parse latex if parse: data['label'] = ['${}$'.format(l) for l in data['label']] return data def draw_panel(self, data, panel_params, coord, ax, **params): super().draw_panel(data, panel_params, coord, ax, **params) @staticmethod def draw_group(data, panel_params, coord, ax, **params): data = coord.transform(data, panel_params) # Bind color and alpha color = to_rgba(data['color'], data['alpha']) # Create a dataframe for the plotting data required # by ax.text df = data[['x', 'y', 'size']].copy() df['s'] = data['label'] df['rotation'] = data['angle'] df['linespacing'] = data['lineheight'] df['color'] = color df['ha'] = data['ha'] df['va'] = data['va'] df['family'] = params['family'] df['fontweight'] = params['fontweight'] df['fontstyle'] = params['fontstyle'] df['zorder'] = params['zorder'] df['rasterized'] = params['raster'] df['clip_on'] = True # 'boxstyle' indicates geom_label so we need an MPL bbox draw_label = 'boxstyle' in params if draw_label: fill = to_rgba(data.pop('fill'), data['alpha']) if isinstance(fill, tuple): fill = [list(fill)] * len(data['x']) df['facecolor'] = fill if params['boxstyle'] in ('round', 'round4'): boxstyle = '{},pad={},rounding_size={}'.format( params['boxstyle'], params['label_padding'], params['label_r']) elif params['boxstyle'] in ('roundtooth', 'sawtooth'): boxstyle = '{},pad={},tooth_size={}'.format( params['boxstyle'], params['label_padding'], params['tooth_size']) else: boxstyle = '{},pad={}'.format( params['boxstyle'], params['label_padding']) bbox = {'linewidth': params['label_size'], 'boxstyle': boxstyle} else: bbox = {} # For labels add a bbox for i in range(len(data)): kw = df.iloc[i].to_dict() if draw_label: kw['bbox'] = bbox kw['bbox']['edgecolor'] = params['boxcolor'] or kw['color'] kw['bbox']['facecolor'] = kw.pop('facecolor') text_elem = ax.text(**kw) if params['path_effects']: text_elem.set_path_effects(params['path_effects']) if params['adjust_text']: adjust_text(list(ax.texts), ax=ax, **params['adjust_text']) @staticmethod def draw_legend(data, da, lyr): """ Draw letter 'a' in the box Parameters ---------- data : dataframe da : DrawingArea lyr : layer Returns ------- out : DrawingArea """ key = Text(x=0.5*da.width, y=0.5*da.height, text='a', alpha=data['alpha'], size=data['size'], family=lyr.geom.params['family'], color=data['color'], rotation=data['angle'], horizontalalignment='center', verticalalignment='center') da.add_artist(key) return da