publiplots.annotate#
- publiplots.annotate(ax, kind='bar_values', *, fmt=None, anchor=None, offset=None, color='auto', pad=None, rotation=None, labels=None, data=None, basis=None, leader=rcParams['pie.leader'], leader_gap=None, elbow_length=None, avoid_overlap=None, label_spacing=None, **text_kws)[source]#
Add value labels to plot marks on
ax.publiplots’ flagship annotation API. Dispatches to a strategy selected by
kindand labels each mark onax(bars, boxes, or points) in place. Offsets are specified in millimetres so labels sit at consistent distances regardless of axes scale.- Parameters:
ax (matplotlib.axes.Axes) – The axes to annotate. Must already have marks drawn on it (e.g. via
publiplots.barplot(),publiplots.boxplot(),publiplots.pointplot(), orpubliplots.lineplot()).kind ({‘bar_values’, ‘bar_custom’, ‘box_stats’, ‘box_custom’, ‘violin_stats’, ‘violin_custom’, ‘point_values’, ‘point_custom’, ‘wedge_values’}, default
'bar_values') –Annotation strategy to use:
'bar_values'— label bars with their values; anchor vocabulary{"outside", "inside", "base", "center"}.'bar_custom'— label bars with user-supplied strings from a DataFrame column or a callablefn(BarRecord) -> str; anchor vocabulary{"outside", "inside", "base", "center"}(same asbar_values).'box_stats'— label box plots with distributional summary statistics.'violin_stats'— the same statistics for violin plots, with default anchor"inside": on the forward side of the median line, inside the body. Labels on dodged vertical violins turn 90° to fit their body unlessrotationis given.'point_values'— label scatter or line-plot points; anchor vocabulary{"top", "bottom", "left", "right", "center"}.'wedge_values'— label pie and donut wedges with their share; anchor vocabulary{"inside", "outside", "auto"}. Under'auto'each label is measured against the room its own wedge has and pushed outside when it does not fit. An outside label hangs off its wedge’s mid-ray in theleaderstyle:'radial'(the default) runs a lineoffsetmm straight out from the rim and centres the label on the mid-rayleader_gapmm beyond its end;'elbow'bends that line into a horizontal tailelbow_lengthmm long, with the label beside it;Nonedraws no line and centres the label so its nearest point isoffsetmm from the rim. Outside labels are decorations, like a title: the axis limits are left alone, so the disc is the same size whichever anchor is used, and the labels extend beyond the axes. On an axes frompubliplots.subplots()the layout grows the figure to hold them; on any other axes nothing does, and labels that reach past the figure edge are cut off. Crowded outside labels are moved apart (avoid_overlap).padhas no effect for this kind, and is not validated for it.
fmt (str, optional) – Either a bare format spec (e.g.
".2f",",.0f") or a format-string template containing{}(e.g."{:,.1f}%"). IfNone(the default), each strategy picks its own default:bar_values,box_stats,point_valuesandviolin_statsdefault to'.2f'; the*_customkinds default to'{}'(raw passthrough of the supplied labels).wedge_valuesdefaults to the wedge’s share as a percentage to one decimal; passing any spec switches it to formatting the wedge’s own value instead.anchor (str, optional) – Where to place the label relative to the mark. Strategy-specific — see
kindabove. IfNone, each strategy picks its own default.offset (float, optional) – Gap in millimetres between the mark (or errorbar cap, when present) and the label, applied outward in the anchor direction.
1.0when not given, except forwedge_values, where it is the distance from the rim to the leader’s end (to the nearest point of the label, in a straight line, withleader=None) and defaults topp.rcParams["pie.label_offset"](2.0).color (str or tuple, default
'auto') –Label color. Accepted values:
'auto'— contrast-aware (composites against translucent fills for legibility).'hue'— inherit the mark’s palette color.Any matplotlib color spec — used verbatim.
pad (float, optional) –
Extra margin in millimetres between the label and the axis edge when auto-expanding axis limits. Defaults to
offsetso the geometry reads:mark → offset → label → pad → axis edge.Ignored by
wedge_values, whose outside labels sit beyond the axes edge rather than inside it.rotation (float or str, optional) –
Label rotation in degrees, counter-clockwise. Defaults to
0, exceptkind='violin_stats', which turns labels on dodged vertical violins 90°. For right-angle multiples (0, 90, 180, 270) the(ha, va)alignment is auto-remapped so the rotated label still sits at the expected offset from the mark, and the categorical axis is also expanded so rotated labels do not clip neighbouring marks. Non-right-angle rotations are passed through to matplotlib as-is; fine-tuning alignment is the caller’s responsibility in that case.wedge_valuesadditionally accepts'tangential'(along the wedge’s arc) and'radial'(along its mid-ray), each flipped where needed so no label reads upside-down. Those names need a mid-ray to be taken from, so any other kind rejects them.labels (str or callable, optional) –
bar_customonly. Either a column name to look up in the cachedpp.barplotsource frame (or indata=when given), or a callablefn(BarRecord) -> strreceiving each bar’s record and returning the label string. Required whenkind='bar_custom'; passing it with any other kind is aTypeError.data (pandas.DataFrame, optional) –
bar_customonly. Overrides the cached source frame for column-based labels. Useful when labels live in a different DataFrame than the one passed topp.barplot.basis ({'total', 'parent'}, optional) –
wedge_valuesonly: whether a percentage is of the whole chart or of the wedge’s parent. Identical on a pie, which has one parent. Default'total'. Passing it with any other kind is aTypeError.leader ({'radial', 'elbow', None}, optional) –
wedge_valuesonly: how an outside label is joined to its wedge.'radial'is a straight line along the mid-ray, with the label centred on that ray;'elbow'adds a horizontal tail toward the label’s side (right for a mid-ray pointing straight up or down), with the label left- or right-aligned beside it;NoneorFalsedraws no line. Defaults topp.rcParams["pie.leader"]('radial'). Withavoid_overlapon, a pie whose outside labels the bounded circle pass does not keep apart draws'elbow'labels, with their lines, whichever of'radial'orNonewas asked for (seeavoid_overlap). A rotated label is placed by its rotated bounding box in every style. Passing it with any other kind is aTypeError.leader_gap (float, optional) –
wedge_valuesonly: millimetres from the leader’s end to the label – the straight-line distance to the nearest point of the label’s box, so it reads the same at every angle. For'elbow'that is horizontal, from the end of the tail. Defaults topp.rcParams["pie.leader_gap"](0.8); unused withleader=None. Passing it with any other kind is aTypeError. For'radial', and foroffsetwithleader=None, the label’s box is measured with the renderer atfigure.dpi, so the gap is exact there; text metrics at another resolution differ slightly, and a figure saved atsavefig.dpi600 can show the gap up to about 0.05 mm off.elbow_length (float, optional) –
wedge_valuesonly: millimetres of an'elbow'leader’s horizontal tail. Defaults topp.rcParams["pie.elbow_length"](2.0); unused by the other styles. Passing it with any other kind is aTypeError.avoid_overlap (bool, optional) –
wedge_valuesonly: move outside labels that would overlap apart, by as little as keeps every pairlabel_spacingapart.'elbow'labels move up or down their column, the elbow moving with them so the tail stays level, and a moved label’s leader is kept from running through another label (a leader that already did so unmoved, as a rotated label’s can, is not always cleared).'radial'and unled labels move round the circle onto a ray off their wedge’s mid-ray, a radial leader leaning to follow – but never further thanacos(R / (R + offset))from the mid-ray,Rthe wedge’s radius, the furthest a leader can lean without cutting back across the chart (about 25 degrees at 40mm and the defaultoffset). Their order is kept, a label that crowds nothing does not move, inside labels never move, and no leader enters the disc. When the bounded solve does not clear every conflict – a run of slivers side by side, or more labels than fit round the circle, and also some pies where a tight radial packing exists but only with leaders lying close to the rim’s tangent, which this solve does not reach for – every outside label of the axes is laid out as an'elbow'label instead, leader and all (drawn too whenleader=None), using theoffset,leader_gapandelbow_lengthin effect, and spread up and down its column, which always has room. How many slivers that takes depends on where they sit and on the chart’s size: on a 25 to 40mm pie, four to six 0.5% wedges already do near twelve or six o’clock, where labels stand side by side, and nine to eleven near three or nine, where they stack; a larger pie takes more. No warning is issued; passmin_sharetopubliplots.pieplot()to pool the slivers if the radial style matters more, oravoid_overlap=Falseto keep every label on its mid-ray whatever overlaps. A pie the circle pass clears keeps the style asked for. Defaults topp.rcParams["pie.avoid_overlap"](True);Falseplaces every label on its own mid-ray. Anything but a bool is aTypeError, as is passing it with any other kind.label_spacing (float, optional) –
wedge_valuesonly: the least gap in millimetres kept between two outside labels’ boxes – the straight-line distance between their nearest points – and between a moved elbow label and the disc. Defaults topp.rcParams["pie.label_spacing"](0.4); unused withavoid_overlap=False. Validated asleader_gapis, and aTypeErrorwith any other kind.**text_kws – Forwarded to
matplotlib.axes.Axes.text()(e.g.fontsize,fontweight).
- Returns:
The Text artists created, in mark order.
- Return type:
- Raises:
ValueError – If
kindis not one of the registered strategies, or ifoffsetis negative, orpadis negative for any kind butwedge_values.TypeError – If an option belonging to one strategy is passed with a
kindthat does not take it:basis=,leader=,leader_gap=,elbow_length=,avoid_overlap=orlabel_spacing=outside the wedge kinds, orlabels=/data=outside the custom-label kinds.
Notes
One call per plot call. Each plot function attaches a fresh meta cache to
ax, and this function consumes the one most recently attached. When several plot calls layer onto one axes — the shape x color idiom, for instance, onepp.pointplotper marker shape over a shared hue — annotate inside the loop:for pipeline, marker in [('a', 'o'), ('b', 'D')]: pp.pointplot(data=df[df.pipe == pipeline], ..., markers=marker, ax=ax) pp.annotate(ax, kind='point_values') # <- inside
A single trailing
pp.annotate(ax, ...)would label only the last call’s marks, because the earlier calls’ caches have been replaced. Passingannotate=to each plot call is equivalent and terser.The converse also holds: calling this twice after the same plot call consumes the same cache twice and draws every label twice. See issue #235.
Examples
Label bars outside the top edge:
>>> import publiplots as pp >>> fig, ax = pp.subplots() >>> pp.barplot(data=df, x='category', y='value', ax=ax) >>> pp.annotate(ax, kind='bar_values', anchor='outside', fmt='.1f')
Label bars inside, near the base, with a percent template:
>>> pp.annotate(ax, kind='bar_values', ... anchor='base', fmt='{:,.1f}%')
Label box-plot statistics:
>>> fig, ax = pp.subplots() >>> pp.boxplot(data=df, x='group', y='value', ax=ax) >>> pp.annotate(ax, kind='box_stats')
Label scatter points to the right, inheriting the palette color:
>>> fig, ax = pp.subplots() >>> pp.scatterplot(data=df, x='x', y='y', hue='group', ax=ax) >>> pp.annotate(ax, kind='point_values', ... anchor='right', color='hue')
Label pie wedges with their share, laid along each arc:
>>> fig, ax = pp.subplots() >>> pp.pieplot(data=df, x='kind', y='n', ax=ax) >>> pp.annotate(ax, kind='wedge_values', rotation='tangential')
Put every wedge label outside, on elbow leaders:
>>> pp.annotate(ax, kind='wedge_values', anchor='outside', ... leader='elbow')