legend_style(by = <aes>, justification = ...) collided when two or more legends shared the same edge. The 1.1.9 fix routed each call to a single global theme(legend.justification.<side> = ...), so the second call overwrote the first — legend_style(by = "colour", justification = "left") + legend_style(by = "fill", justification = "right") on the top edge ended up with both legends pinned to the right. Per-legend justification now also stashes the requested rail position on the plot and rewrites the guide-box-<side> internal gtable at render time so each legend sits at its own fraction of the rail. The render-time path also reorders the guides so the gtable cell order matches the requested rail order. The global theme write is kept as a fallback for the single-legend case. (reported by Youtao)The render-time gtable post-processor checks ggplot2's guide-box layout shape (column count and pad-cell unit type) and falls back to the previous behavior with a one-shot warning if it doesn't match what ggguides was tested against (currently ggplot2 4.0.3) — so a future ggplot2 internal change degrades gracefully instead of producing wrong output.
If a guide already has an explicit guide_legend(order = N) set, per-legend justification reordering on that side is skipped and a warning is emitted, so the user-supplied order is never silently overwritten.
legend_style(by = <aes>, justification = ...) had no visible effect. The update attached the justification to the guide's embedded theme via guide_legend(theme = theme(legend.justification.<side> = ...)), but ggplot2 >= 3.5 does not consult the side-specific justification elements inside a guide's embedded theme — only the whole-plot theme is read. Legends stayed centered along their rail regardless of the justification value. The NEWS entry for 1.1.5 that introduced this argument was therefore incorrect: the workaround Youtao documented, theme(legend.justification.<side> = ...), was the only thing that actually worked. 1.1.9 fixes this by routing per-guide justification to a whole-plot theme element keyed on the guide's resolved side, so legend_top(by = "colour") + legend_style(by = "colour", justification = "left") now slides the colour legend to the left end of the top rail as documented. If you had a theme(legend.justification.<side> = ...) workaround in your code, you can remove it. (reported by Youtao)
legend_style(justification = ...) without by was writing to the generic legend.justification, which ggplot2 coerces per axis and mis-maps mixed-axis scalars (e.g., "left" on a vertical rail becomes 0 — bottom). It now writes to the side-specific theme elements (legend.justification.top/.bottom for horizontal scalars; legend.justification.left/.right for vertical scalars; all four for "center" and numerics).
legend_left(), legend_right(), legend_top(), and legend_bottom() were writing to legend.justification, the generic fallback element. In ggplot2 >= 3.5.0 each side has its own element — legend.justification.left, legend.justification.right, legend.justification.top, legend.justification.bottom — and the generic fallback is silently coerced per axis. legend_left() in particular was sending "left" to a vertical rail, which ggplot2 coerced to 0 (bottom), so the legend was actually pinned to the bottom of the left edge when users expected it centered. The four side functions now write to the side-specific element and default to "center", which is what the documentation has always described. (reported by Youtao)legend_left(), legend_right(), legend_top(), and legend_bottom() gain a justification argument. It slides the legend along its rail: "top"/"center"/"bottom" (or a number in [0, 1]) for left/right, "left"/"center"/"right" for top/bottom. This is the side-legend counterpart to legend_inside(justification = ...). For per-guide control when several legends sit on different sides, keep using legend_style(by = ..., justification = ...).?legend_left (and the three siblings) now explain the "rail" semantics and note the naming asymmetry with legend_inside(): on a side, the justification keyword refers to where the legend sits along the panel edge; inside, it refers to which corner of the legend anchors to the (x, y) position.legend_inside(justification = ...) had no visible effect because the function was writing to legend.justification — the theme element that anchors side legends. In ggplot2 >= 3.5.0 the anchor for legend.position = "inside" is a separate element, legend.justification.inside. legend_inside() now writes to that element, so the justification argument (and the justifications implied by position = "topright", "bottomleft", etc.) move the legend as documented. Users who had been working around the bug with theme(legend.justification = ...) should remove that override. (reported by Youtao)legend_*(by = <aes>) and legend_style(by = <aes>) now compose correctly when chained for the same aesthetic. Previously, each call built a fresh guide_legend() that replaced the prior one, so legend_top(by = "colour") + legend_style(by = "colour", margin = ...) silently reset the colour legend's position back to the plot default. The per-aesthetic helpers now return a ggguides update object whose ggplot_add method merges new params into the existing guide. This fixes the "Four Legends, One per Side" vignette example. (reported by Youtao)legend_inside(): renamed just argument to justification for consistency with legend_style(justification = ...) and ggplot2's legend.justification theme element. The old just name still works but emits a deprecation warning.legend_style() gains a justification argument. With by = NULL it sets legend.justification globally; with by = "<aes>" it slides a single legend along its side via guide_legend(theme = ...). Useful when four legends sit on four different sides and each needs its own alignment.multiple-legends vignette: added a "Four Legends, One per Side" section showing per-legend side, justification, and margin adjustments together.\donttest{} with if(requireNamespace()) conditionals for examples using suggested packagesget_legend() returning empty grob with ggplot2 3.5.0+ (guide-box naming changed to position-specific names like "guide-box-right")@return documentation to all S3 methods (ggplot_add, print, plot, and ggplotGrob methods)\dontrun{} to \donttest{} in examples that require suggested packageslegend_keys(): Added detailed documentation explaining how to use filled shapes (21-25) with different outline/fill color combinations. Clarified that "colored fill with black outline" requires mapping both color and fill aesthetics in the original plot (#1).
Added new example showing correct usage for colored fills with black outlines.