ggpubr 1.0.0 is on CRAN: https://cran.r-project.org/package=ggpubr
Install with:
install.packages("ggpubr")New features
New functions
- Added
format_p_value(),get_p_format_style(), andlist_p_format_styles(). - Added style presets (
default,apa,nejm,lancet,ama,graphpad,scientific).
New arguments, computed variables, and input support
-
ggmaplot()gains an optionalfacet.byargument to split the MA plot into
multiple panels (e.g. one per contrast or species). Top genes are selected per
panel, while point colors use the same significance thresholds in every panel.
Default output (nofacet.by) is unchanged (#498). -
stat_cor()andstat_regline_equation()gain alabel.anchorargument.
Withlabel.anchor = "panel"the label is placed at the true panel-relative
position (npc), so labels stay aligned across panels or facets whose axis
ranges differ - e.g.facet_wrap(scales = "free_y"),geom_smooth()
extending each panel by a different amount, or separate plots combined with
ggarrange(). The defaultlabel.anchor = "data"keeps the previous
data-range placement, so existing plots are unchanged (#248). -
geom_bracket()gains anorientationargument.orientation = "vertical"
draws a native vertical bracket (the bar spans the y axis, the tips point left
and the label is rotated), specified withymin/ymax/x.position— useful
to annotate plots where the comparison runs along the y axis, e.g.
Kaplan-Meier curves (it is designed for a continuous x axis). The default
orientation = "horizontal"is unchanged, and it cannot be combined with
coord.flip = TRUE(#456). -
ggpaired()gains an opt-injitterargument that spreads the paired points
horizontally to reduce overlap. Each subject (id) gets a single offset, so
its two points move together and the connecting line stays intact, and only
the horizontal positions change (the values are never moved). Default
jitter = 0leaves the plot unchanged (#407). -
ggboxplot(),ggviolin()andggstripchart()gain an opt-inshow.n
argument. Whenshow.n = TRUE, the number of observations ("n = <count>")
is displayed at the top of each group. When the groups are dodged (a
color/fillgrouping with a dodgingposition), one count is shown per
group; otherwise a single count is shown per x-axis tick. Counts respect
select/removeand are computed per facet. Default isshow.n = FALSE, so
existing plots are unchanged (#598, #627). -
ggarrange()gains aspacingargument to increase the gap between the
arranged plots (a long-requested option). It adds a uniform margin, in
text-line units, around each plot. Default is0(no extra space; each plot
keeps its own margins, so existing arrangements are unchanged) (#151). -
compare_means()gains anidargument for paired comparisons. Without it, a
paired test (paired = TRUE) pairs observations by row order, so the p-value is
wrong when the data are not sorted so that the compared groups align by subject.
Passingid(the name of a subject-identifier column) pairs the observations by
id instead — row-order independent, using only the complete pairs (via
rstatix, so the result matches a directrstatix::t_test()/wilcox_test()
call). It works for a two-group, a pairwise (more than two groups) and a
ref.groupcomparison witht.test/wilcox.test. Default (id = NULL) is
unchanged (#560). -
stat_compare_means()also gains theidargument, so a paired test can be
aligned by subject id directly on a plot (e.g.ggpaired(...) + stat_compare_means(paired = TRUE, id = "subject")), displaying the correct
p-value even when the data are not sorted by subject. Plots withoutidare
unchanged (#560). -
geom_pwc()andstat_pwc()gain ap.adjust.nargument giving the number of
comparisons to use for the p-value adjustment (passed asnto
stats::p.adjust()). Default isNULL(adjust by the number of computed
p-values, unchanged behavior); set it when the displayed comparisons are a
subset of a larger family so the adjustment reflects that larger size. It must
be at least the number of p-values being adjusted, and applies to the
panel-level adjustment (p.adjust.by = "panel") and the single-comparison
case (#612). -
annotate_figure()gainscolumn.titlesandrow.titlesarguments to add a
title above each column and to the left of each row of an arranged grid
(useful for publication panels). Each accepts a character vector (one title
per column / per row, rendered in bold) or a list of grobs (e.g. built with
text_grob()) for full styling control;row.titlesare rotated 90 degrees.
The titles are placed in reserved space (they do not overlap the plots) and
are evenly spaced, so they assume equal-sized columns / rows (theggarrange()
default). Default isNULL(no titles; existing figures are unchanged) (#573). -
ggbarplot()gains anumeric.x.axisargument (already available in
ggline()andggerrorplot()). Withnumeric.x.axis = TRUEthe x variable
is kept numeric instead of being coerced to a discrete factor, so bars are
drawn at their numeric x positions (e.g. a time axis). Default isFALSE
(unchanged behavior) (#463). -
geom_bracket()andstat_pvalue_manual()gain atip.length.refargument
controlling whattip.lengthis a fraction of. With the default
tip.length.ref = "data"the tips are a fraction of the data range (unchanged
behavior). Withtip.length.ref = "axis"the tips are a fraction of the
y-axis range (ylim/scale_y_*), which renders at the same physical fraction
across plots and therefore gives visually constant tip lengths regardless of
the data range - useful to keep tips consistent across facets or across
separate plots with different scales (#362).stat_compare_means(comparisons =)
draws its brackets viaggsignif::geom_signif()and does not support
tip.length.ref; passing it there now emits an informative message pointing to
stat_pvalue_manual(..., tip.length.ref = "axis")instead of being silently
ignored (#362). -
stat_cor()andstat_regline_equation()gain alabel.y.stepargument giving
the vertical spacing (in text-line units) between the labels of successive
groups. Default is1.4(unchanged behavior). Settinglabel.y.step = 0stops
the per-group vertical shift so labels align across facet panels when a factor
is mapped to an aesthetic that also defines the facets (the long-standing
"labels climbing stairs across facets" behavior; #248). This control was
inspired by thevstepargument in the ggpmisc package (by Pedro J. Aphalo),
from which ggpubr's correlation/regression label-positioning logic was
originally adapted; this is acknowledged via@seealsoin?stat_cor. -
ggviolin()gains adropargument (passed toggplot2::geom_violin()),
defaultTRUE(unchanged behavior). Previously the argument was silently
dropped, so grouped violins where a sub-sample has too few points to draw a
density (whichgeom_violin()removes, including from the dodge position)
could not be kept aligned with added boxplots / dot plots. Setting
drop = FALSEtogether withposition = position_dodge(0.8, preserve = "single")
now reserves the empty dodge lane so all geoms stay aligned (#381). -
ggarrange()gained the ability to choose which plot(s) supply the shared legend:
common.legendnow also accepts one or several plot indices.common.legend = 2
uses the second plot's legend (handy when the first plot's legend is not
representative, e.g. a group is missing in the first plot), and
common.legend = c(1, 2)keeps and combines the legends of the listed plots into a
single shared block (side by side forlegend = "top"/"bottom", stacked for
"left"/"right") - useful when the arranged plots genuinely need different
legends. The documentation ofcommon.legendwas also clarified:common.legend = TRUEkeeps the first plot's legend (it does not merge or validate legends), so plots
should share a consistent scale for a single shared legend to be correct. Logical
TRUE/FALSEbehave exactly as before (#347). -
ggdonutchart()gained alabel.repelargument (defaultFALSE). WhenTRUE, slice
labels are placed withggrepel::geom_text_repel()and connected to their slice with
leader lines, so the labels of many small slices no longer overlap (or get dropped). The
default output is unchanged (#655). -
ggboxplot()now accepts apositionargument (e.g.
position = position_dodge(0.9)), likeggviolin()/ggdotplot()already do.
Previously passingpositionerrored (formal argument "position" matched by multiple actual arguments) because the dodge was hardcoded; the default is
unchanged (position_dodge(0.8)), so grouped box plots look the same unless
you set it (#615). -
ggsummarytable()gains anangleargument to rotate the summary-table text;
default (angle = 0) is unchanged (#595). -
ggmaplot()gains aline.colorargument to set the threshold line color;
default ("black") is unchanged (#322). -
ggarrange()gains abyrowargument (defaultTRUE) to fill the plot grid
by column (byrow = FALSE) instead of by row; forwarded to
cowplot::plot_grid()(#225). -
stat_regline_equation()gainscoef.digitsandrr.digitsarguments to
control the number of significant digits shown for the regression-equation
coefficients and R2; defaults (2) reproduce the previous output (#312). -
Added formatting parameters to statistical helpers:
p.format.stylep.digitsp.leading.zerop.min.thresholdp.decimal.mark
-
Added
p.format.signifsupport and related label handling paths. -
stat_pvalue_manual()gains ap.digitsargument (default3) that formats
numeric p-value label columns for display (e.g.label = "p"or
label = "p.adj"), using the sameformat_p_value()engine as
stat_anova_test(). This restores clean labels (e.g.0.0156instead of
0.015625) with rstatix>= 1.0.0, which now returns full-precision pairwise
p-values. Character labels (significance symbols, pre-formatted strings, glue
expressions) are unaffected. Setp.digits = NULLto print the raw value. -
stat_pvalue_manual()additionally gainsp.format.style,p.leading.zero,
p.min.thresholdandp.decimal.mark, matching the p-value formatting
arguments already available instat_compare_means(),geom_pwc()and
stat_anova_test(). These are opt-in and default so that the rendered labels
are unchanged; for examplep.min.threshold = 0.001displays very small
p-values as< 0.001, andp.format.style = "nejm"applies a journal style. -
stat_cor()gains two label-formatting arguments:r.leading.zero— set toFALSEto drop the leading zero of the
correlation coefficient (e.g..73instead of0.73), completing
APA-style reporting together withp.leading.zero(#540). The dropped
leading zero is preserved through plotmath rendering (the value is quoted
so it is not silently re-normalized back to0.73in the default
expressionoutput).p.coef.name— symbol for the p-value label; use"P"for an uppercase
p-value (#541).
-
stat_cor()exposes two new computed variables,rmseandrmse.label, for
the root mean square deviation (RMSE/RMSD) betweenxandy— useful for
reporting agreement between paired measurements on the same scale (e.g.
predicted vs. reference values). Display it with
aes(label = after_stat(rmse.label)), or combine it with the correlation
coefficient usingpaste(). The default label is unchanged (#458). -
stat_cor()can now display the confidence interval of the correlation
coefficient, via the new computed variablesconf.int.low,conf.int.high
andconf.int.label(e.g."95% CI [0.21, 0.75]") and aconf.levelargument
(default0.95). Show it withaes(label = after_stat(conf.int.label)), or
combine it with the coefficient usingpaste(). The confidence interval is
available formethod = "pearson"only; it isNAfor Spearman/Kendall. The
default label is unchanged (#418).
Main changes
Compatibility
- Added compatibility updates for modern
ggplot2,dplyr, andtidyr. - Updated legacy
sizeusage tolinewidthwhere required by recentggplot2. - Completed the
size->linewidthmigration for the remaining line layers that
still passed the deprecatedsizeargument: the mean/median reference line added
bygghistogram(add = ...)andggdensity(add = ...), and the connector segments
ofggdotchart(add = "segments"). These no longer emit ggplot2's "sizeaesthetic
for lines was deprecated" or "Ignoring empty aesthetic:size" warnings, and the
requested line width is now applied vialinewidth. ggmaplot()no longer emits an "Ignoring empty aesthetic:size" warning on its
default call; the point layer setssizeonly when the user supplies a value.- Replaced deprecated tidyverse APIs in affected helper functions.
- Added package startup lock-file checks and
clean_lock_files()helper. - Relaxed minimum
ggrepeldependency to>= 0.9.2to keep Ubuntu oldrel
(R 4.4.x) CI dependency resolution working. geom_pwc()now detects an absentref.groupin a grouped subset via the
rstatix_missing_ref_groupcondition class raised by recentrstatix
(walking the parent chain withrlang::cnd_inherits()), in addition to
matching the error message. This keeps the "skip ref-less subsets" behaviour
working afterrstatixmade that error message clearer (rstatix #153); older
rstatixversions are still handled via the message fallback.- The minimum required
rstatixversion is now>= 1.0.0(the version that
introduced theidargument used bycompare_means(id = ), and the
full-precision pairwise p-values relied on by the p-value formatting).
Validation and messaging
- Hardened sparse-group handling in
stat_compare_means()andgeom_pwc(). - Non-comparable grouped subsets (insufficient levels/observations) are now skipped
while preserving valid inferential comparisons in the same layer. - Added per-group skip diagnostics in
geom_pwc()so users can see which grouped
subsets were skipped and why (e.g., missingref.groupor insufficient levels). - Added regression tests for mixed comparable/non-comparable subsets and fully sparse
subsets. - Passing
ggtheme = NULLto the plotting functions (e.g.ggscatter(),
ggboxplot(),ggline(),gghistogram(), ...) now skips applying a ggpubr
theme, so the plot keeps ggplot2's default theme or the theme set globally with
theme_set(). Previously an explicitggtheme = NULLwas treated like an unset
argument andtheme_pubr()was still applied. Calls that omitggthemeor pass
a specific theme are unchanged (#561). compare_means()now adjusts p-values within eachgroup.bylevel (and
response) rather than pooling all groups together, so a grouped adjustment
matches filtering to one group and adjusting there. This changesp.adjvalues
for calls that usegroup.by; ungrouped calls are unchanged (#200).stat_compare_means(comparisons = )now emits a one-time message (and the docs
note) clarifying that the displayed pairwise p-values are not adjusted for
multiple comparisons, pointing togeom_pwc()/stat_pvalue_manual()+
compare_means(p.adjust.method = )for corrected p-values (#293).
Documentation
- Documented a recipe for labelling groups with significance letters (compact letter display):
compute the pairwise comparisons and derive the letters withrstatix::add_cld(), then place
them withgeom_text()(see the "Significance letters" section in?compare_means). Also covers
the per-control letters case (a = differs from control 1, b = differs from control 2) (#464, #434). - Documented that a summarized
ggbarplot()(e.g.add = "mean_se") must be faceted with
thefacet.by=argument, not by appending+ facet_wrap()/+ facet_grid(): the summaries
are pre-computed overfacet.by, so a manually added facet pools the bars (and, for stacked
bars, the error bars) across all panels (#739). - Clarified documentation to match existing behavior: the
ggthemedefault per
function, the p-value threshold/precision defaults, thestat_compare_means()
label separator (test method name, not correlation coefficient), and the
package Description's list of p-value formatting styles. Thanks to @erdeyl (#749). - Expanded test coverage for formatting helpers and compatibility paths.
- Regenerated manuals and package documentation after source sync.
- Closes #663 (
stat_compare_means()fatal warning/error in sparse grouped subsets with moderntidyr). - Closes #334 (
stat_compare_means(label = "p.format")rounding and formatting control). - Related: #540, #626.
Credits
- Contributor: Laszlo Erdey (Faculty of Economics and Business, University of Debrecen, Hungary).
- Thanks to @gumeo (#418), @byzheng (#608) and @JulesROBIN15 (#625) whose pull
requests proposed features shipped in this release: thestat_cor()
confidence interval, thestat_cor()RMSE label, and a manual number of
comparisons for p-value adjustment ingeom_pwc()/stat_pwc().
Minor changes
- Fixed ColorBrewer sequential palette parsing in
.get_brewer_pal()so
"YlOrBr"is recognized correctly. - Updated
ggexport()to respectverbose = FALSEby suppressing filename
print()output in multi-file raster/vector exports. - Hardened
format_p_value()by validating non-NULLp.min.thresholdas a
single positive finite number. - Updated
create_p_label()to preserveNAvalues inp.format(returning
NA_character_instead of stringifying to"p = NA"). theme_pubr()now draws the axis tick marks in black with linewidth0.5,
matching the axis lines; previously the ticks inherited a lighter grey/thinner
style, visibly inconsistent when zoomed (#668).theme_pubr()now setsstrip.clip = "off"so the facet strip background
border renders at its fulllinewidth. With theggplot2(>= 3.5.0) default
(strip.clip = "on") the border was clipped to the strip area, cutting the
outer half of the stroke so it looked thinner and misaligned with the panel
when zoomed (follow-up to #668). Note: a facet label wider than its panel now
overflows the strip instead of being truncated; restore clipping with
+ theme(strip.clip = "on")if needed.xticks.by/yticks.bynow anchor the axis breaks to round multiples of the
step (e.g. 0, 100, 200) instead of the slightly-negative expanded axis minimum,
which produced odd labels such as-20, 80, 180on bar plots with
ylim = c(0, 400)(#313).- Import the
%||%operator fromrlang; it is used ingeom_bracket()and
geom_pwc()but base R only provides it since R 4.4, so it could be unresolved
on the R (>= 4.1) versions the package supports (#665).
Bug fixes
stat_welch_anova_test(label = "as_detailed_italic")and
stat_welch_anova_test(label = "as_detailed_expression")now display the numeric
F statistic instead ofFALSE. Thanks to @erdeyl (#749).ggbarplot()now supports mappingalphato a discrete grouping variable together
with a summary (e.g.alpha = clarity, add = "mean_ci", position = position_dodge()).
Previously this errored at draw time ("alpha * 255": non-numeric argument to binary operator). The mean/CI is now computed per subgroup, the bars are faded per thealpha
variable, and the error bars are dodged to stay aligned with their bars. Bar plots
without analphagrouping variable are unchanged (#404).ggpar()no longer errors onggsurvplotobjects (or any plot whose theme uses
ggtext::element_markdown(), e.g. survminer's risk-table strata labels). Such
markdown label elements are now left intact instead of triggering an "Only elements
of the same class can be merged" error; output for all other plots is unchanged (#382).geom_pwc()with an explicit list ofcomparisonsno longer drops a whole facet/panel
when a single requested pair cannot be tested (e.g. a group that is entirelyNAor has
fewer than two observations). The untestable comparison is skipped (with a message) and
the remaining valid comparisons are still drawn (#542).compare_means()andstat_compare_means()now forward extra test options
(e.g.alternative = "greater") to the paired tests aligned by subjectid,
which were previously ignored on that path. Grouped and faceted paired-id
comparisons also skip a subset that cannot be tested (fewer than two groups or
no complete pairs) instead of failing the whole result/layer, while still
reporting genuinely ambiguous data (e.g. duplicated ids). Thanks to
@erdeyl (#732).ggbarplot()now places the error bars of a stacked bar chart correctly when the
data mix positive and negative values (e.g. above/below-ground measurements). The
stacked error bars are cumulated per sign, matchingposition_stack(), so a negative
segment's error bar is drawn on its own side instead of being displaced to the other
side (previously the misplacement also flipped with the factor-level order). Stacked
charts with single-sign data are unchanged (#426).ggviolin()now keeps grouped violins aligned with their added box/dot layers by
default when a sub-group is too sparse forgeom_violin()to compute a density
(a single data point). Previously the sparse sub-group was dropped from the dodge,
so the remaining violin re-centered and no longer lined up. Whendrop/position
are left at their defaults and such a one-point grouped cell is present, the empty
dodge lane is now reserved automatically. Balanced, ungrouped, faceted, and
legitimately-unbalanced plots are unchanged, and an explicitdrop/position
still takes precedence (#381).geom_bracket()(andstat_pvalue_manual()) now draw visible tips for a single
bracket placed over a stat-computedysuch asgeom_bar()/geom_histogram().
Previously the bracket tips collapsed into a flat line because the y-axis range
was trained only on the bracket's singley.position; the tips are now sized
against the fully-trained panel range at draw time, so they render correctly even
withcoord_cartesian(ylim = ...). Brackets with an already non-zero y range are
unchanged (#631).ggline()now dodges the jittered points together with the line and error
bars. Previously, in a grouped line plot withadd = "jitter"and
position = position_dodge(), only the line and summary (e.g.mean_se) were
dodged while the jittered points stayed centered, so they no longer sat under
their group. The jitter now dodges by the same width. Plots without a
position_dodge()(the defaultposition = "identity") are unchanged (#436).ggbarplot()now lines the error bars up with the bars when
position = position_dodge2()is used.position_dodge2()places elements
according to their own width, so the thin error bars did not sit on the centre
of the wider bars they belong to (most visible withpreserve = "single"when
one x group has fewer bars than another). The error bars are now drawn at the
actual bar positions. Other positions (identity,position_dodge(),
position_stack()) are unchanged (#363).geom_bracket()andstat_pvalue_manual()now place brackets correctly on a
transformed y axis (e.g.scale_y_log10()).y.positionis given in data units
but was previously used directly in the scale's transformed space, so brackets
landed far off (e.g. at10^30) and squashed the plot. The brackety.position
is now run through the scale's transformation; on an untransformed (identity)
scale this is a no-op, so existing plots are unchanged (#342).- The core plotting functions (
ggscatter(),ggboxplot(),ggviolin(),
ggline(),ggbarplot(),gghistogram(),ggdensity(),ggdotplot(),
ggstripchart(),ggerrorplot(),ggecdf(),ggdotchart(),ggpaired(),
ggqqplot()) now accept the British spellingcolouras an alias for
color. Previouslycolourwas silently ignored (#317). stat_regline_equation()now displays the correct equation for orthogonal
polynomial fits such asformula = y ~ poly(x, 2). Previously the orthogonal
basis coefficients were printed as if they were raw polynomial coefficients,
giving a wrong equation that did not match the fitted curve. For a simple
poly(x, k)term with an intercept, the equation is now computed from an
equivalentraw = TRUEfit (identical curve, R², AIC, BIC). Linear,
raw = TRUE, andI(x^2)formulas are unaffected; no-intercept models and
transformed poly arguments (e.g.poly(log(x), 2)) keep their previous
behavior (#653).ggscatterhist()now aligns the marginal plots with the main scatter plot's
axes. The margins were built from the raw data, so anything that changed the
scatter limits (ellipse = TRUE,positionjitter, explicitxlim/ylim)
left the marginal histograms/densities misaligned with the scatter. Each
margin now inherits the scatter's axis range (#220, #420).geom_pwc()now places comparison brackets over the correct groups when an
entire x-axis group has onlyNAvalues. Such a group is dropped before the
statistical test runs; previously the surviving groups were renumbered from 1,
which shifted the remaining brackets left. The bracket positions are now
anchored to the discrete x scale so they stay aligned with the plotted groups
(#575).ggballoonplot()now honors user-suppliedxlab/ylabinstead of always
blanking the axis titles; axis titles are still blank by default (#639).ggpar(legend.title = )now also titlessizeandalphalegends, not just
colour/fill/linetype/shape(#412).facet()/panel.labs: a NAMEDpanel.labsvector is now matched to the data
levels by name, fixing mislabeled panels when the order differed; unnamed
labels still map positionally (#643).stat_compare_means()no longer fails with a "*.npc coord ..." error when a
groupedxvalue is0(or negative);.group_coord()now guards against a
non-positive group index and falls back to the first label coordinate (#594).ggpar()/plot functions now applyxticks.byandyticks.bytogether; an
internalelse ifpreviously droppedxticks.bywheneveryticks.bywas
also supplied (#333).stat_compare_means()now forwards thefamilyargument to the comparison
bracket labels; it was previously ignored whenevercomparisonswas set
(#592, #624).ggline()now supports two grouping variables at once (e.g.colorand
linetype, or an explicitgrouppluscolor); these are combined into a
single interaction group so the right points are connected. Previously this
errored with "the condition has length > 1" (#616, #375).- A NAMED
palettevector no longer emits a spurious "No shared levels found
betweennames(values)..." warning; the manual color/fill scale is now only
applied to an aesthetic actually mapped in the plot. Unnamed palettes are
unchanged, and a named palette whose names genuinely don't match still warns
(#642). ggviolin()no longer crashes withscales = "free"(or"free_x"/"free_y").
The facet argumentscaleswas being partial-matched to the violinscale
parameter; it is now read with exact matching, soscalescontrols faceting
and the violinscalekeeps its default (#398).xlim/ylimare now honored withorientation = "horizontal"(and
rotate = TRUE). The limits are passed tocoord_flip()instead of a separate
coord_cartesian()that was silently replaced, which also removes the
"Coordinate system already present" warning (#646).ggpar()(and the plot functions) now honorlegend.directionfor all legend
positions; it was previously ignored forlegend = "top"/"bottom"(#652).compare_means()no longer errors ("Can't extract columns that don't exist")
when a formula variable name contains a space (e.g.len ~ `spa ced`); the
backticks that R adds to non-syntactic term names are now stripped before
matching data columns (#385).table_cell_font()andtable_cell_bg()can now style an individual header
cell (row = 1), not only body cells; they previously matched only the
core-*grobs and silently did nothing for the header (#535).ggmaplot()now draws the non-significant ("NS") points behind the significant
ones, so the up/down-regulated hits are no longer hidden under the grey NS
cloud; the legend order (Up, Down, NS) is unchanged (#365).ggboxplot()now forwardscoeftogeom_boxplot(), socoef = 0can be used
to omit the whiskers; it was previously dropped bygeom_exec()(#517).tab_add_title()/tab_add_footnote(): thejustargument now actually
positions the text —"center"/"right"anchor the title/footnote across the
table width instead of around a fixed point — and the text is no longer clipped
when it is wider than the table (#302).annotate_figure(): the figure label (fig.lab) now uses a length-independent
horizontal justification, so labels of different lengths keep the same anchor
and captions align across figures; previously a longer label was shifted away
from the corner (#185).