-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add geom_marquee, and element_marquee along with various bug fixes in…
… marquee_grob found during development of the ggplot2 features
- Loading branch information
Showing
6 changed files
with
553 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
#' ggplot2 theme element supporting marquee syntax | ||
#' | ||
#' This theme element is a drop-in replacement for `ggplot2::element_text()`. It | ||
#' works by integrating the various style settings of the element into the base | ||
#' style of the provided style set. If a margin is given, it is set on the body | ||
#' tag with [skip_inherit()]. The default width is `NA` meaning that it will | ||
#' span as long as the given text is, doing no line wrapping. You can set it to | ||
#' any unit to make it fit within a specific width. However, this may not work | ||
#' as expected with rotated text (you may get lucky). Note that you may see | ||
#' small shifts in the visuals when going from `element_text()` to | ||
#' `element_marquee()` as size reporting may differ between the two elements. | ||
#' | ||
#' @param family The font family of the base style | ||
#' @param colour The font colour of the base style | ||
#' @param size The font size of the base style | ||
#' @param lineheight The lineheight of the base style | ||
#' @param margin The margin for the body tag | ||
#' @param style A style set to base the rendering on | ||
#' @param width The maximum width of the text. See the description for some | ||
#' caveats for this | ||
#' @inheritParams ggplot2::element_text | ||
#' | ||
#' @export | ||
#' | ||
#' @examples | ||
#' if (is_installed("ggplot2")) { | ||
#' library(ggplot2) | ||
#' p <- ggplot(mtcars) + | ||
#' geom_point(aes(mpg, disp)) + | ||
#' labs(title = "A {.red *marquee*} title\n* Look at this bullet list\n\n* great, huh?") + | ||
#' theme_gray(base_size = 6) + | ||
#' theme(title = element_marquee()) | ||
#' | ||
#' plot(p) | ||
#' | ||
#' ggplot(mtcars) + | ||
#' geom_histogram(aes(x = mpg)) + | ||
#' labs(title = "I put a plot in your title so you can plot while you title\n\n![](p)\n\nWhat more could you _possibly_ want?") + | ||
#' theme(title = element_marquee()) | ||
#' } | ||
#' | ||
element_marquee <- function(family = NULL, colour = NULL, size = NULL, hjust = NULL, | ||
vjust = NULL, angle = NULL, lineheight = NULL, | ||
color = NULL, margin = NULL, style = NULL, width = NULL, | ||
inherit.blank = FALSE, ...) { | ||
if (!is.null(color)) | ||
colour <- color | ||
n <- max(length(family), length(colour), length(size), | ||
length(hjust), length(vjust), length(angle), length(lineheight)) | ||
if (n > 1) { | ||
cli::cli_warn(c("Vectorized input to {.fn element_text} is not officially supported.", | ||
i = "Results may be unexpected or may change in future versions of ggplot2.")) | ||
} | ||
structure(list(family = family, colour = colour, size = size, hjust = hjust, | ||
vjust = vjust, angle = angle, lineheight = lineheight, | ||
margin = margin, style = style, width = width, | ||
inherit.blank = inherit.blank), | ||
class = c("element_marquee", "element_text", "element")) | ||
} | ||
#' @export | ||
element_grob.element_marquee <- function(element, label = "", x = NULL, y = NULL, family = NULL, | ||
colour = NULL, size = NULL, hjust = NULL, vjust = NULL, | ||
angle = NULL, lineheight = NULL, margin = NULL, margin_x = FALSE, | ||
margin_y = FALSE, style = NULL, width = NULL, ...) { | ||
if (is.null(label)) return(ggplot2::zeroGrob()) | ||
style <- style %||% element$style %||% classic_style() | ||
style <- modify_style(style, "base", | ||
family = family %||% element$family %||% style$base$family, | ||
color = colour %||% element$colour %||% style$base$color, | ||
size = size %||% element$size %||% style$base$size, | ||
lineheight = lineheight %||% element$lineheight %||% style$base$lineheight | ||
) | ||
margin <- margin %||% element$margin | ||
if (!is.null(margin)) { | ||
pad <- skip_inherit(box( | ||
if (margin_y) margin[1] else 0, | ||
if (margin_x) margin[2] else 0, | ||
if (margin_y) margin[3] else 0, | ||
if (margin_x) margin[4] else 0 | ||
)) | ||
style <- modify_style(style, "body", padding = pad) | ||
} | ||
just <- rotate_just(angle %||% element$angle, hjust %||% element$hjust, vjust %||% element$vjust) | ||
n <- max(length(x), length(y), 1) | ||
x <- x %||% rep(just$hjust, n) | ||
y <- y %||% rep(just$vjust, n) | ||
width <- width %||% element$width %||% NA | ||
angle <- angle %||% element$angle %||% 0 | ||
marquee_grob(label, style, x = x, y = y, width = width, | ||
hjust = hjust %||% element$hjust, vjust = vjust %||% element$vjust, angle = angle) | ||
} | ||
|
||
on_load({ | ||
on_package_load("ggplot2", registerS3method("element_grob", "element_marquee", element_grob.element_marquee, asNamespace("ggplot2"))) | ||
}) | ||
|
||
# Adaption of ggplot2:::rotate_just() to work with additional just keywords | ||
rotate_just <- function (angle, hjust, vjust) { | ||
angle <- (angle %||% 0)%%360 | ||
if (is.character(hjust)) { | ||
hjust <- switch(hjust, "left" = , "left-ink" = 0, "center" = , "center-ink" = 0.5, "right" = , "right-ink" = 1) | ||
} | ||
if (is.character(vjust)) { | ||
vjust <- switch(vjust, "bottom" = , "bottom-ink" = ,"last-line" = 0, "center" = , "center-ink" = 0.5, "top" = , "top-ink" = , "first-line" = 1) | ||
} | ||
size <- vctrs::vec_size_common(angle, hjust, vjust) | ||
angle <- vctrs::vec_recycle(angle, size) | ||
hjust <- vctrs::vec_recycle(hjust, size) | ||
vjust <- vctrs::vec_recycle(vjust, size) | ||
case <- findInterval(angle, c(0, 90, 180, 270, 360)) | ||
hnew <- hjust | ||
vnew <- vjust | ||
is_case <- which(case == 2) | ||
hnew[is_case] <- 1 - vjust[is_case] | ||
vnew[is_case] <- hjust[is_case] | ||
is_case <- which(case == 3) | ||
hnew[is_case] <- 1 - hjust[is_case] | ||
vnew[is_case] <- 1 - vjust[is_case] | ||
is_case <- which(case == 4) | ||
hnew[is_case] <- vjust[is_case] | ||
vnew[is_case] <- 1 - hjust[is_case] | ||
list(hjust = hnew, vjust = vnew) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,180 @@ | ||
#' Draw text formatted with marquee | ||
#' | ||
#' The geom is an extension of `geom_text()` and `geom_label()` that allows you | ||
#' to draw richly formatted text in marquee-markdown format in your plot. For | ||
#' plain text it is a near-drop-in replacement for the above geoms except some | ||
#' sizing might be very slightly different. However, using this geom you are | ||
#' able to access the much more powerful font settings available in marquee, so | ||
#' even then it might make sense to opt for this geom. | ||
#' | ||
#' @inheritParams ggplot2::geom_text | ||
#' | ||
#' @details | ||
#' Styling of the text is based on a style set with the exception that the | ||
#' standard aesthetics such as family, size, colour, fill, etc. are recognized | ||
#' and applied to the base tag style. The default style set ([classic_style]) | ||
#' can be changed using the style aesthetic which can take a vector of style | ||
#' sets so that each text can rely on it's own style if needed. As with | ||
#' [element_marquee()], the `fill` aesthetic is treated differently and not | ||
#' applied to the base tag, but to the body tag as a [skip_inherit()] style so | ||
#' as to not propagate the fill. | ||
#' | ||
#' Contrary to the standard text and label geoms, `geom_marquee()` takes a | ||
#' `width` aesthetic that can be used to turn on soft wrapping of text. The | ||
#' default value (`NA`) lets the text run as long as it want's (honoring hard | ||
#' breaks), but setting this to something else will instruct marquee to use at | ||
#' most that amount of space. You can use grid units to set it to an absolute | ||
#' amount. | ||
#' | ||
#' @export | ||
#' | ||
#' @examples | ||
#' # Standard use | ||
#' p <- ggplot(mtcars, aes(wt, mpg)) | ||
#' p + geom_marquee(aes(label = rownames(mtcars))) | ||
#' | ||
#' # Make use of more powerful font features (note, result may depend on fonts | ||
#' # installed on the system) | ||
#' p + geom_marquee( | ||
#' aes(label = rownames(mtcars)), | ||
#' style = classic_style(weight = "thin", width = "condensed") | ||
#' ) | ||
#' | ||
#' # Turn on line wrapping | ||
#' p + geom_marquee(aes(label = rownames(mtcars)), width = unit(2, "cm")) | ||
#' | ||
#' # Style like label | ||
#' label_style <- modify_style( | ||
#' classic_style(), | ||
#' "body", | ||
#' padding = skip_inherit(box(4)), | ||
#' border = "black", | ||
#' border_size = skip_inherit(box(1)), | ||
#' border_radius = 3 | ||
#' ) | ||
#' p + geom_marquee(aes(label = rownames(mtcars), fill = gear), style = label_style) | ||
#' | ||
#' # Use markdown to style the text | ||
#' red_bold_names <- sub("(\\w+)", "{.red **\\1**}", rownames(mtcars)) | ||
#' p + geom_marquee(aes(label = red_bold_names)) | ||
#' | ||
geom_marquee <- function(mapping = NULL, data = NULL, stat = "identity", | ||
position = "identity", ..., size.unit = "mm", | ||
na.rm = FALSE, show.legend = NA, inherit.aes = TRUE) { | ||
check_installed("ggplot2") | ||
|
||
ggplot2::layer( | ||
data = data, mapping = mapping, stat = stat, geom = GeomMarquee$geom, | ||
position = position, show.legend = show.legend, inherit.aes = inherit.aes, | ||
params = list2(size.unit = size.unit, na.rm = na.rm, ...) | ||
) | ||
} | ||
|
||
GeomMarquee <- new_environment(list(geom = NULL)) | ||
|
||
make_marquee_geom <- function() { | ||
GeomMarquee$geom <- ggplot2::ggproto( | ||
"GeomMarquee", ggplot2::Geom, | ||
|
||
required_aes = c("x", "y", "label"), | ||
|
||
default_aes = ggplot2::aes( | ||
colour = "black", fill = NA, size = 3.88, angle = 0, | ||
hjust = 0.5, vjust = 0.5, alpha = NA, family = "", lineheight = 1.2, | ||
style = classic_style(), width = NA | ||
), | ||
|
||
draw_panel = function(data, panel_params, coord, na.rm = FALSE, size.unit = "mm") { | ||
lab <- data$label | ||
|
||
styles <- data$style | ||
if (!is_style_set(styles)) { | ||
stop_input_type(styles, "a marquee_style_set object", arg = "style") | ||
} | ||
check_character(data$family, arg = "family") | ||
size <- data$size * resolve_text_unit(size.unit) * 72.72 / 72 | ||
check_numeric(size, arg = "size") | ||
check_numeric(data$lineheight) | ||
colour <- ggplot2::alpha(data$colour, data$alpha) | ||
check_character(colour, arg = "colour") | ||
if (!is.character(data$fill) && | ||
!(is.logical(data$fill) && all(is.na(data$fill))) && | ||
!all(vapply(data$fill, function(x) is.character(x) || inherits(x, "GridPattern"), logical(1)))) { | ||
stop_input_type(data$fill, "a character vector or a list of strings and patters", arg = "fill") | ||
} | ||
for (i in seq_along(styles)) { | ||
styles[[i]]$base$family <- data$family[[i]] | ||
styles[[i]]$base$size <- size[[i]] | ||
styles[[i]]$base$lineheight <- data$lineheight[[i]] | ||
styles[[i]]$base$color <- colour[[i]] | ||
if (!"body" %in% names(styles[[i]])) { | ||
styles[[i]]$body <- style() | ||
} | ||
styles[[i]]$body$background <- skip_inherit(data$fill[[i]]) | ||
} | ||
|
||
data <- coord$transform(data, panel_params) | ||
|
||
data$vjust <- compute_just(data$vjust, data$y, data$x, data$angle) | ||
data$hjust <- compute_just(data$hjust, data$x, data$y, data$angle) | ||
|
||
marquee_grob( | ||
text = lab, style = styles, | ||
x = data$x, y = data$y, width = data$width, | ||
hjust = data$hjust, vjust = data$vjust, | ||
angle = data$angle, name = "geom_marquee" | ||
) | ||
}, | ||
|
||
draw_key = ggplot2::draw_key_label | ||
) | ||
} | ||
|
||
on_load(on_package_load("ggplot2", { | ||
make_marquee_geom() | ||
})) | ||
|
||
combine_styles <- function(style, family, size, lineheight, color, background) {browser() | ||
style <- modify_style(style, "base", family = family, size = size, color = color, lineheight = lineheight) | ||
modify_style(style, "body", background = skip_inherit(background)) | ||
} | ||
|
||
resolve_text_unit <- function(unit) { | ||
unit <- arg_match0(unit, c("mm", "pt", "cm", "in", "pc", "Pt")) | ||
switch( | ||
unit, | ||
"mm" = 2.845276, | ||
"cm" = 28.45276, | ||
"in" = 72.27, | ||
"pc" = 12, | ||
"Pt" = 72 / 72.27, | ||
1 | ||
) | ||
} | ||
|
||
compute_just <- function (just, a = 0.5, b = a, angle = 0) { | ||
if (!is.character(just)) { | ||
return(just) | ||
} | ||
if (any(grepl("outward|inward", just))) { | ||
angle <- angle%%360 | ||
angle <- ifelse(angle > 180, angle - 360, angle) | ||
angle <- ifelse(angle < -180, angle + 360, angle) | ||
rotated_forward <- grepl("outward|inward", just) & (angle > 45 & angle < 135) | ||
rotated_backwards <- grepl("outward|inward", just) & (angle < -45 & angle > -135) | ||
ab <- ifelse(rotated_forward | rotated_backwards, b, a) | ||
just_swap <- rotated_backwards | abs(angle) > 135 | ||
inward <- (just == "inward" & !just_swap | just == "outward" & just_swap) | ||
just[inward] <- c("left", "center", "right")[just_dir(ab[inward])] | ||
outward <- (just == "outward" & !just_swap) | (just == "inward" & just_swap) | ||
just[outward] <- c("right", "center", "left")[just_dir(ab[outward])] | ||
} | ||
just | ||
} | ||
|
||
just_dir <- function (x, tol = 0.001) { | ||
out <- rep(2L, length(x)) | ||
out[x < 0.5 - tol] <- 1L | ||
out[x > 0.5 + tol] <- 3L | ||
out | ||
} |
Oops, something went wrong.