Skip to content

Commit

Permalink
Minor ticks (#5287)
Browse files Browse the repository at this point in the history
* Swap tick anchorpoint

* `Guide$build_ticks()` accepts a length value

* Add tick arguments

* `GuideAxis$extract_key()` can get minor ticks

* `GuideAxis$build_ticks()` makes draws minor ticks

* Fix bug with unlabelled breaks

* Adjust tick spacing

* Finishing touches

* Add test

* Accept tick-ordering changes in snapshots

* Add news bullet

* Fix expression labels

* Document

* Update snapshot

* minor tick theme options

* Revise minor to use theme

* Use `rel()` for minor ticks

* Biparental inheritance for minor tick length leaf nodes

* change minor break extraction

* Skip tick calculation with blank elements

* clean up axis ticks building

* change measurement function to width_cm/height_cm

* Remove redundant function
  • Loading branch information
teunbrand committed Oct 24, 2023
1 parent 1b2c312 commit f74dbbe
Show file tree
Hide file tree
Showing 45 changed files with 622 additions and 313 deletions.
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@
* More informative error for mismatched
`direction`/`theme(legend.direction = ...)` arguments (#4364, #4930).
* `guide_coloursteps()` and `guide_bins()` sort breaks (#5152).
* `guide_axis()` gains a `minor.ticks` argument to draw minor ticks (#4387).
* `guide_axis()` gains a `cap` argument that can be used to trim the
axis line to extreme breaks (#4907).
* `guide_colourbar()` and `guide_coloursteps()` merge properly when one
Expand Down
16 changes: 11 additions & 5 deletions R/guide-.R
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,14 @@ Guide <- ggproto(
},

# Renders tickmarks
build_ticks = function(key, elements, params, position = params$position) {
build_ticks = function(key, elements, params, position = params$position,
length = elements$ticks_length) {
if (!inherits(elements, "element")) {
elements <- elements$ticks
}
if (!inherits(elements, "element_line")) {
return(zeroGrob())
}

if (!is.list(key)) {
breaks <- key
Expand All @@ -365,8 +372,7 @@ Guide <- ggproto(
return(zeroGrob())
}

tick_len <- rep(elements$ticks_length %||% unit(0.2, "npc"),
length.out = n_breaks)
tick_len <- rep(length %||% unit(0.2, "npc"), length.out = n_breaks)

# Resolve mark
mark <- unit(rep(breaks, each = 2), "npc")
Expand All @@ -375,12 +381,12 @@ Guide <- ggproto(
pos <- unname(c(top = 1, bottom = 0, left = 0, right = 1)[position])
dir <- -2 * pos + 1
pos <- unit(rep(pos, 2 * n_breaks), "npc")
dir <- rep(vec_interleave(0, dir), n_breaks) * tick_len
dir <- rep(vec_interleave(dir, 0), n_breaks) * tick_len
tick <- pos + dir

# Build grob
flip_element_grob(
elements$ticks,
elements,
x = tick, y = mark,
id.lengths = rep(2, n_breaks),
flip = position %in% c("top", "bottom")
Expand Down
99 changes: 78 additions & 21 deletions R/guide-axis.R
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
#' @param n.dodge The number of rows (for vertical axes) or columns (for
#' horizontal axes) that should be used to render the labels. This is
#' useful for displaying labels that would otherwise overlap.
#' @param minor.ticks Whether to draw the minor ticks (`TRUE`) or not draw
#' minor ticks (`FALSE`, default).
#' @param cap A `character` to cut the axis line back to the last breaks. Can
#' be `"none"` (default) to draw the axis line along the whole panel, or
#' `"upper"` and `"lower"` to draw the axis to the upper or lower break, or
Expand Down Expand Up @@ -42,23 +44,23 @@
#' # can also be used to add a duplicate guide
#' p + guides(x = guide_axis(n.dodge = 2), y.sec = guide_axis())
guide_axis <- function(title = waiver(), check.overlap = FALSE, angle = NULL,
n.dodge = 1, cap = "none", order = 0,
position = waiver()) {

n.dodge = 1, minor.ticks = FALSE, cap = "none",
order = 0, position = waiver()) {
check_bool(minor.ticks)
if (is.logical(cap)) {
check_bool(cap)
cap <- if (cap) "both" else "none"
}
cap <- arg_match0(cap, c("none", "both", "upper", "lower"))


new_guide(
title = title,

# customisations
check.overlap = check.overlap,
angle = angle,
n.dodge = n.dodge,
minor.ticks = minor.ticks,
cap = cap,

# parameter
Expand Down Expand Up @@ -87,6 +89,7 @@ GuideAxis <- ggproto(
direction = NULL,
angle = NULL,
n.dodge = 1,
minor.ticks = FALSE,
cap = "none",
order = 0,
check.overlap = FALSE
Expand All @@ -100,9 +103,37 @@ GuideAxis <- ggproto(
line = "axis.line",
text = "axis.text",
ticks = "axis.ticks",
ticks_length = "axis.ticks.length"
minor = "axis.minor.ticks",
major_length = "axis.ticks.length",
minor_length = "axis.minor.ticks.length"
),

extract_key = function(scale, aesthetic, minor.ticks, ...) {
major <- Guide$extract_key(scale, aesthetic, ...)
if (!minor.ticks) {
return(major)
}

minor_breaks <- scale$get_breaks_minor()
minor_breaks <- setdiff(minor_breaks, major$.value)
minor_breaks <- minor_breaks[is.finite(minor_breaks)]

if (length(minor_breaks) < 1) {
return(major)
}

minor <- data_frame0(!!aesthetic := scale$map(minor_breaks))
minor$.value <- minor_breaks
minor$.type <- "minor"

if (nrow(major) > 0) {
major$.type <- "major"
vec_rbind(major, minor)
} else {
minor
}
},

extract_params = function(scale, params, ...) {
params$name <- paste0(params$name, "_", params$aesthetic)
params
Expand Down Expand Up @@ -185,7 +216,7 @@ GuideAxis <- ggproto(
},

setup_elements = function(params, elements, theme) {
axis_elem <- c("line", "text", "ticks", "ticks_length")
axis_elem <- c("line", "text", "ticks", "minor", "major_length", "minor_length")
is_char <- vapply(elements[axis_elem], is.character, logical(1))
axis_elem <- axis_elem[is_char]
elements[axis_elem] <- lapply(
Expand Down Expand Up @@ -225,26 +256,17 @@ GuideAxis <- ggproto(
"horizontal"
}

# TODO: delete following comment at some point:
# I found the 'position_*'/'non-position_*' and '*_dim' names confusing.
# For my own understanding, these have been renamed as follows:
# * 'aes' and 'orth_aes' for the aesthetic direction and the direction
# orthogonal to the aesthetic direction, respectively.
# * 'para_sizes' and 'orth_size(s)' for the dimension parallel to the
# aesthetic and orthogonal to the aesthetic respectively.
# I also tried to trim down the verbosity of the variable names a bit

new_params <- c("aes", "orth_aes", "para_sizes", "orth_size", "orth_sizes",
"vertical", "measure_gtable", "measure_text")
if (direction == "vertical") {
params[new_params] <- list(
"y", "x", "heights", "width", "widths",
TRUE, gtable_width, grobWidth
TRUE, gtable_width, width_cm
)
} else {
params[new_params] <- list(
"x", "y", "widths", "height", "heights",
FALSE, gtable_height, grobHeight
FALSE, gtable_height, height_cm
)
}

Expand Down Expand Up @@ -275,7 +297,32 @@ GuideAxis <- ggproto(
)
},

build_ticks = function(key, elements, params, position = params$opposite) {

major <- Guide$build_ticks(
vec_slice(key, (key$.type %||% "major") == "major"),
elements$ticks, params, position,
elements$major_length
)

if (!params$minor.ticks) {
return(major)
}

minor <- Guide$build_ticks(
vec_slice(key, (key$.type %||% "major") == "minor"),
elements$minor, params, position,
elements$minor_length
)
grobTree(major, minor, name = "ticks")
},

build_labels = function(key, elements, params) {

if (".type" %in% names(key)) {
key <- vec_slice(key, key$.type == "major")
}

labels <- validate_labels(key$.label)
n_labels <- length(labels)

Expand Down Expand Up @@ -309,10 +356,20 @@ GuideAxis <- ggproto(

measure <- params$measure_text

length <- elements$ticks_length
spacer <- max(unit(0, "pt"), -1 * length)
labels <- do.call(unit.c, lapply(grobs$labels, measure))
title <- measure(grobs$title)
# Ticks
major_cm <- convertUnit(elements$major_length, "cm", valueOnly = TRUE)
range <- range(0, major_cm)
if (params$minor.ticks && !inherits(elements$minor, "element_blank")) {
minor_cm <- convertUnit(elements$minor_length, "cm", valueOnly = TRUE)
range <- range(range, minor_cm)
}

length <- unit(range[2], "cm")
spacer <- max(unit(0, "pt"), unit(-1 * diff(range), "cm"))

# Text
labels <- unit(measure(grobs$label), "cm")
title <- unit(measure(grobs$title), "cm")

sizes <- unit.c(length, spacer, labels, title)
if (params$lab_first) {
Expand Down
3 changes: 3 additions & 0 deletions R/theme-defaults.R
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ theme_grey <- function(base_size = 11, base_family = "",
axis.ticks.length.y = NULL,
axis.ticks.length.y.left = NULL,
axis.ticks.length.y.right = NULL,
axis.minor.ticks.length = rel(0.75),
axis.title.x = element_text(
margin = margin(t = half_line / 2),
vjust = 1
Expand Down Expand Up @@ -478,6 +479,7 @@ theme_void <- function(base_size = 11, base_family = "",
axis.ticks.length.y = NULL,
axis.ticks.length.y.left = NULL,
axis.ticks.length.y.right = NULL,
axis.minor.ticks.length = unit(0, "pt"),
legend.box = NULL,
legend.key.size = unit(1.2, "lines"),
legend.position = "right",
Expand Down Expand Up @@ -559,6 +561,7 @@ theme_test <- function(base_size = 11, base_family = "",
axis.ticks.length.y = NULL,
axis.ticks.length.y.left = NULL,
axis.ticks.length.y.right = NULL,
axis.minor.ticks.length = rel(0.75),
axis.title.x = element_text(
margin = margin(t = half_line / 2),
vjust = 1
Expand Down
25 changes: 25 additions & 0 deletions R/theme-elements.R
Original file line number Diff line number Diff line change
Expand Up @@ -441,32 +441,57 @@ el_def <- function(class = NULL, inherit = NULL, description = NULL) {
axis.line.y = el_def("element_line", "axis.line"),
axis.line.y.left = el_def("element_line", "axis.line.y"),
axis.line.y.right = el_def("element_line", "axis.line.y"),

axis.text.x = el_def("element_text", "axis.text"),
axis.text.x.top = el_def("element_text", "axis.text.x"),
axis.text.x.bottom = el_def("element_text", "axis.text.x"),
axis.text.y = el_def("element_text", "axis.text"),
axis.text.y.left = el_def("element_text", "axis.text.y"),
axis.text.y.right = el_def("element_text", "axis.text.y"),

axis.ticks.length = el_def("unit"),
axis.ticks.length.x = el_def(c("unit", "rel"), "axis.ticks.length"),
axis.ticks.length.x.top = el_def(c("unit", "rel"), "axis.ticks.length.x"),
axis.ticks.length.x.bottom = el_def(c("unit", "rel"), "axis.ticks.length.x"),
axis.ticks.length.y = el_def(c("unit", "rel"), "axis.ticks.length"),
axis.ticks.length.y.left = el_def(c("unit", "rel"), "axis.ticks.length.y"),
axis.ticks.length.y.right = el_def(c("unit", "rel"), "axis.ticks.length.y"),

axis.ticks.x = el_def("element_line", "axis.ticks"),
axis.ticks.x.top = el_def("element_line", "axis.ticks.x"),
axis.ticks.x.bottom = el_def("element_line", "axis.ticks.x"),
axis.ticks.y = el_def("element_line", "axis.ticks"),
axis.ticks.y.left = el_def("element_line", "axis.ticks.y"),
axis.ticks.y.right = el_def("element_line", "axis.ticks.y"),

axis.title.x = el_def("element_text", "axis.title"),
axis.title.x.top = el_def("element_text", "axis.title.x"),
axis.title.x.bottom = el_def("element_text", "axis.title.x"),
axis.title.y = el_def("element_text", "axis.title"),
axis.title.y.left = el_def("element_text", "axis.title.y"),
axis.title.y.right = el_def("element_text", "axis.title.y"),

axis.minor.ticks.x.top = el_def("element_line", "axis.ticks.x.top"),
axis.minor.ticks.x.bottom = el_def("element_line", "axis.ticks.x.bottom"),
axis.minor.ticks.y.left = el_def("element_line", "axis.ticks.y.left"),
axis.minor.ticks.y.right = el_def("element_line", "axis.ticks.y.right"),

axis.minor.ticks.length = el_def(c("unit", "rel")),
axis.minor.ticks.length.x = el_def(c("unit", "rel"), "axis.minor.ticks.length"),
axis.minor.ticks.length.x.top = el_def(
c("unit", "rel"), c("axis.minor.ticks.length.x", "axis.ticks.length.x.top")
),
axis.minor.ticks.length.x.bottom = el_def(
c("unit", "rel"), c("axis.minor.ticks.length.x", "axis.ticks.length.x.bottom")
),
axis.minor.ticks.length.y = el_def(c("unit", "rel"), "axis.minor.ticks.length"),
axis.minor.ticks.length.y.left = el_def(
c("unit", "rel"), c("axis.minor.ticks.length.y", "axis.ticks.length.y.left")
),
axis.minor.ticks.length.y.right = el_def(
c("unit", "rel"), c("axis.minor.ticks.length.y", "axis.ticks.length.y.right")
),

legend.background = el_def("element_rect", "rect"),
legend.margin = el_def("margin"),
legend.spacing = el_def("unit"),
Expand Down
16 changes: 16 additions & 0 deletions R/theme.R
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,13 @@
#' `axis.ticks.y.left`, `axis.ticks.y.right`). `axis.ticks.*.*` inherits from
#' `axis.ticks.*` which inherits from `axis.ticks`, which in turn inherits
#' from `line`
#' @param axis.minor.ticks.x.top,axis.minor.ticks.x.bottom,axis.minor.ticks.y.left,axis.minor.ticks.y.right,
#' minor tick marks along axes ([element_line()]). `axis.minor.ticks.*.*`
#' inherit from the corresponding major ticks `axis.ticks.*.*`.
#' @param axis.ticks.length,axis.ticks.length.x,axis.ticks.length.x.top,axis.ticks.length.x.bottom,axis.ticks.length.y,axis.ticks.length.y.left,axis.ticks.length.y.right
#' length of tick marks (`unit`)
#' @param axis.minor.ticks.length,axis.minor.ticks.length.x,axis.minor.ticks.length.x.top,axis.minor.ticks.length.x.bottom,axis.minor.ticks.length.y,axis.minor.ticks.length.y.left,axis.minor.ticks.length.y.right
#' length of minor tick marks (`unit`), or relative to `axis.ticks.length` when provided with `rel()`.
#' @param axis.line,axis.line.x,axis.line.x.top,axis.line.x.bottom,axis.line.y,axis.line.y.left,axis.line.y.right
#' lines along axes ([element_line()]). Specify lines along all axes (`axis.line`),
#' lines for each plane (using `axis.line.x` or `axis.line.y`), or individually
Expand Down Expand Up @@ -302,13 +307,24 @@ theme <- function(line,
axis.ticks.y,
axis.ticks.y.left,
axis.ticks.y.right,
axis.minor.ticks.x.top,
axis.minor.ticks.x.bottom,
axis.minor.ticks.y.left,
axis.minor.ticks.y.right,
axis.ticks.length,
axis.ticks.length.x,
axis.ticks.length.x.top,
axis.ticks.length.x.bottom,
axis.ticks.length.y,
axis.ticks.length.y.left,
axis.ticks.length.y.right,
axis.minor.ticks.length,
axis.minor.ticks.length.x,
axis.minor.ticks.length.x.top,
axis.minor.ticks.length.x.bottom,
axis.minor.ticks.length.y,
axis.minor.ticks.length.y.left,
axis.minor.ticks.length.y.right,
axis.line,
axis.line.x,
axis.line.x.top,
Expand Down
4 changes: 4 additions & 0 deletions man/guide_axis.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit f74dbbe

Please sign in to comment.