diff --git a/NEWS.md b/NEWS.md index f9bdc996e0..807e9a00fd 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,6 @@ # ggplot2 (development version) +* Axis labels are now justified across facet panels (@teunbrand, #5820) * Fixed bug in `stat_function()` so x-axis title now produced automatically when no data added. (@phispu, #5647). * geom_sf now accepts shape names (@sierrajohnson, #5808) diff --git a/R/guide-axis.R b/R/guide-axis.R index dc57ffd23e..27354e0a94 100644 --- a/R/guide-axis.R +++ b/R/guide-axis.R @@ -424,6 +424,7 @@ GuideAxis <- ggproto( # Unlist the 'label' grobs z <- if (params$position == "left") c(2, 1, 3) else 1:3 z <- rep(z, c(1, length(grobs$labels), 1)) + has_labels <- !is.zero(grobs$labels[[1]]) grobs <- c(list(grobs$ticks), grobs$labels, list(grobs$title)) # Initialise empty gtable @@ -445,10 +446,25 @@ GuideAxis <- ggproto( vp <- exec( viewport, !!params$orth_aes := unit(params$orth_side, "npc"), - !!params$orth_size := params$measure_gtable(gt), + !!params$orth_size := max(params$measure_gtable(gt), unit(1, "npc")), just = params$opposite ) + # Add null-unit padding to justify based on eventual gtable cell shape + # rather than dimensions of this axis alone. + if (has_labels && params$position %in% c("left", "right")) { + where <- layout$l[-c(1, length(layout$l))] + just <- with(elements$text, rotate_just(angle, hjust, vjust))$hjust %||% 0.5 + gt <- gtable_add_cols(gt, unit(just, "null"), pos = min(where) - 1) + gt <- gtable_add_cols(gt, unit(1 - just, "null"), pos = max(where) + 1) + } + if (has_labels && params$position %in% c("top", "bottom")) { + where <- layout$t[-c(1, length(layout$t))] + just <- with(elements$text, rotate_just(angle, hjust, vjust))$vjust %||% 0.5 + gt <- gtable_add_rows(gt, unit(1 - just, "null"), pos = min(where) - 1) + gt <- gtable_add_rows(gt, unit(just, "null"), pos = max(where) + 1) + } + # Assemble with axis line absoluteGrob( gList(axis_line, gt), diff --git a/tests/testthat/_snaps/facet-labels/outside-justified-labels.svg b/tests/testthat/_snaps/facet-labels/outside-justified-labels.svg new file mode 100644 index 0000000000..9b04f0b614 --- /dev/null +++ b/tests/testthat/_snaps/facet-labels/outside-justified-labels.svg @@ -0,0 +1,144 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +C + + + + + + + + + + +D + + + + + + + + + + +A + + + + + + + + + + +B + + + +X +X +X +X +X + +X + +X +X +X +X +X + +X +YYYYY + +Y + + +YYYYY + +Y +x +y +outside-justified labels + + diff --git a/tests/testthat/test-facet-labels.R b/tests/testthat/test-facet-labels.R index 6d086e0b7b..c8613bc978 100644 --- a/tests/testthat/test-facet-labels.R +++ b/tests/testthat/test-facet-labels.R @@ -157,3 +157,30 @@ test_that("parsed labels are rendered correctly", { facet_wrap(~ f, labeller = label_parsed) ) }) + +test_that("outside-justified labels are justified across panels", { + + df <- data.frame( + x = c("X\nX\nX\nX\nX", "X"), + y = c("YYYYY", "Y"), + f1 = c("A", "B"), + f2 = c("C", "D") + ) + + # By default, axis labels are inside-justified so it doesn't matter whether + # justification occurs across panels. This changes for outside-justification. + # See #5820 + + p <- ggplot(df, aes(x, y)) + + geom_point() + + facet_grid(f1 ~ f2, scales = "free") + + guides(x.sec = "axis", y.sec = "axis") + + theme( + axis.text.y.left = element_text(hjust = 0), + axis.text.y.right = element_text(hjust = 1), + axis.text.x.top = element_text(vjust = 1), + axis.text.x.bottom = element_text(vjust = 0) + ) + + expect_doppelganger("outside-justified labels", p) +})