Skip to content

Commit 222d33d

Browse files
authored
Axis caps (#5289)
* Pass around cap argument * Do capping * Document * Add test * Munch decor if appropriate
1 parent 5c11540 commit 222d33d

File tree

5 files changed

+189
-7
lines changed

5 files changed

+189
-7
lines changed

NEWS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@
3636
* More informative error for mismatched
3737
`direction`/`theme(legend.direction = ...)` arguments (#4364, #4930).
3838
* `guide_coloursteps()` and `guide_bins()` sort breaks (#5152).
39+
* `guide_axis()` gains a `cap` argument that can be used to trim the
40+
axis line to extreme breaks (#4907).
3941

4042
* `geom_label()` now uses the `angle` aesthetic (@teunbrand, #2785)
4143
* 'lines' units in `geom_label()`, often used in the `label.padding` argument,

R/guide-axis.R

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@
1414
#' @param n.dodge The number of rows (for vertical axes) or columns (for
1515
#' horizontal axes) that should be used to render the labels. This is
1616
#' useful for displaying labels that would otherwise overlap.
17+
#' @param cap A `character` to cut the axis line back to the last breaks. Can
18+
#' be `"none"` (default) to draw the axis line along the whole panel, or
19+
#' `"upper"` and `"lower"` to draw the axis to the upper or lower break, or
20+
#' `"both"` to only draw the line in between the most extreme breaks. `TRUE`
21+
#' and `FALSE` are shorthand for `"both"` and `"none"` respectively.
1722
#' @param order A positive `integer` of length 1 that specifies the order of
1823
#' this guide among multiple guides. This controls in which order guides are
1924
#' merged if there are multiple guides for the same position. If 0 (default),
@@ -37,14 +42,24 @@
3742
#' # can also be used to add a duplicate guide
3843
#' p + guides(x = guide_axis(n.dodge = 2), y.sec = guide_axis())
3944
guide_axis <- function(title = waiver(), check.overlap = FALSE, angle = NULL,
40-
n.dodge = 1, order = 0, position = waiver()) {
45+
n.dodge = 1, cap = "none", order = 0,
46+
position = waiver()) {
47+
48+
if (is.logical(cap)) {
49+
check_bool(cap)
50+
cap <- if (cap) "both" else "none"
51+
}
52+
cap <- arg_match0(cap, c("none", "both", "upper", "lower"))
53+
54+
4155
new_guide(
4256
title = title,
4357

4458
# customisations
4559
check.overlap = check.overlap,
4660
angle = angle,
4761
n.dodge = n.dodge,
62+
cap = cap,
4863

4964
# parameter
5065
available_aes = c("x", "y"),
@@ -72,6 +87,7 @@ GuideAxis <- ggproto(
7287
direction = NULL,
7388
angle = NULL,
7489
n.dodge = 1,
90+
cap = "none",
7591
order = 0,
7692
check.overlap = FALSE
7793
),
@@ -92,6 +108,25 @@ GuideAxis <- ggproto(
92108
Guide$extract_params(scale, params, hashables)
93109
},
94110

111+
extract_decor = function(scale, aesthetic, position, key, cap = "none", ...) {
112+
113+
value <- c(-Inf, Inf)
114+
if (cap %in% c("both", "upper")) {
115+
value[2] <- max(key[[aesthetic]])
116+
}
117+
if (cap %in% c("both", "lower")) {
118+
value[1] <- min(key[[aesthetic]])
119+
}
120+
121+
opposite <- setdiff(c("x", "y"), aesthetic)
122+
opposite_value <- if (position %in% c("top", "right")) -Inf else Inf
123+
124+
data_frame(
125+
!!aesthetic := value,
126+
!!opposite := opposite_value
127+
)
128+
},
129+
95130
transform = function(self, params, coord, panel_params) {
96131
key <- params$key
97132
position <- params$position
@@ -109,6 +144,8 @@ GuideAxis <- ggproto(
109144
key <- coord$transform(key, panel_params)
110145
params$key <- key
111146

147+
params$decor <- coord_munch(coord, params$decor, panel_params)
148+
112149
# Ported over from `warn_for_position_guide`
113150
# This is trying to catch when a user specifies a position perpendicular
114151
# to the direction of the axis (e.g., a "y" axis on "top").
@@ -228,11 +265,13 @@ GuideAxis <- ggproto(
228265

229266
# The decor in the axis guide is the axis line
230267
build_decor = function(decor, grobs, elements, params) {
231-
exec(
232-
element_grob,
233-
element = elements$line,
234-
!!params$aes := unit(c(0, 1), "npc"),
235-
!!params$orth_aes := unit(rep(params$orth_side, 2), "npc")
268+
if (empty(decor)) {
269+
return(zeroGrob())
270+
}
271+
element_grob(
272+
elements$line,
273+
x = unit(decor$x, "npc"),
274+
y = unit(decor$y, "npc")
236275
)
237276
},
238277

@@ -347,7 +386,8 @@ GuideAxis <- ggproto(
347386
},
348387

349388
draw_early_exit = function(self, params, elements) {
350-
line <- self$build_decor(elements = elements, params = params)
389+
line <- self$build_decor(decor = params$decor, elements = elements,
390+
params = params)
351391
absoluteGrob(
352392
gList(line),
353393
width = grobWidth(line),
@@ -385,11 +425,17 @@ draw_axis <- function(break_positions, break_labels, axis_position, theme,
385425
position = axis_position)
386426
params <- guide$params
387427
aes <- if (axis_position %in% c("top", "bottom")) "x" else "y"
428+
opp <- setdiff(c("x", "y"), aes)
429+
opp_value <- if (axis_position %in% c("top", "right")) 0 else 1
388430
key <- data_frame(
389431
break_positions, break_positions, break_labels,
390432
.name_repair = ~ c(aes, ".value", ".label")
391433
)
392434
params$key <- key
435+
params$decor <- data_frame0(
436+
!!aes := c(0, 1),
437+
!!opp := opp_value
438+
)
393439
guide$draw(theme, params)
394440
}
395441

man/guide_axis.Rd

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 114 additions & 0 deletions
Loading

tests/testthat/test-guides.R

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,19 @@ test_that("Axis titles won't be blown away by coord_*()", {
462462
# expect_doppelganger("guide titles with coord_sf()", plot + coord_sf())
463463
})
464464

465+
test_that("axis guides can be capped", {
466+
p <- ggplot(mtcars, aes(hp, disp)) +
467+
geom_point() +
468+
theme(axis.line = element_line()) +
469+
guides(
470+
x = guide_axis(cap = "both"),
471+
y = guide_axis(cap = "upper"),
472+
y.sec = guide_axis(cap = "lower"),
473+
x.sec = guide_axis(cap = "none")
474+
)
475+
expect_doppelganger("axis guides with capped ends", p)
476+
})
477+
465478
test_that("guides are positioned correctly", {
466479
df <- data_frame(x = 1, y = 1, z = factor("a"))
467480

0 commit comments

Comments
 (0)