Skip to content

Commit c6d77a1

Browse files
committed
Add add_annotations() (as well as purrr dependency)
1 parent 291e84e commit c6d77a1

File tree

9 files changed

+202
-26
lines changed

9 files changed

+202
-26
lines changed

DESCRIPTION

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ Imports:
3535
dplyr,
3636
tibble,
3737
hexbin,
38-
lazyeval (>= 0.2.0)
38+
lazyeval (>= 0.2.0),
39+
purrr
3940
Suggests:
4041
MASS,
4142
maps,

NAMESPACE

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,14 @@ S3method(to_basic,default)
5858
S3method(transmute_,plotly)
5959
S3method(ungroup,plotly)
6060
export("%>%")
61+
export(add_annotations)
6162
export(add_area)
6263
export(add_bars)
6364
export(add_boxplot)
6465
export(add_choropleth)
6566
export(add_contour)
6667
export(add_data)
68+
export(add_fun)
6769
export(add_heatmap)
6870
export(add_histogram)
6971
export(add_histogram2d)
@@ -184,6 +186,7 @@ importFrom(lazyeval,f_new)
184186
importFrom(lazyeval,is_formula)
185187
importFrom(lazyeval,is_lang)
186188
importFrom(magrittr,"%>%")
189+
importFrom(purrr,transpose)
187190
importFrom(stats,complete.cases)
188191
importFrom(stats,quantile)
189192
importFrom(stats,setNames)

R/add.R

Lines changed: 87 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -479,24 +479,92 @@ special_attrs <- function(trace) {
479479
# }
480480

481481

482-
## ------------------------------------------------------------------------
483-
## Non-trace addition
484-
## ------------------------------------------------------------------------
485-
#
486-
##' @export
487-
#add_transform <- function(p, ...) {
488-
# stop("not yet implemented")
489-
#}
490-
#
491-
#
492-
##' @export
493-
#add_shape <- function(p, ...) {
494-
# stop("not yet implemented")
495-
#}
496-
#
497-
##' @export
498-
#add_annotation <- function(p, ...) {
499-
# stop("not yet implemented")
500-
#}
501482

502483

484+
485+
#' Apply function to plot, without modifying data
486+
#'
487+
#' Useful when you need two or more layers that apply a summary statistic
488+
#' to the original data.
489+
#'
490+
#' @param p a plotly object.
491+
#' @param fun a function. Should take a plotly object as input and return a
492+
#' modified plotly object.
493+
#' @export
494+
#' @examples
495+
#'
496+
#' txhousing %>%
497+
#' group_by(city) %>%
498+
#' plot_ly(x = ~date, y = ~median) %>%
499+
#' add_lines(alpha = 0.2, name = "Texan Cities") %>%
500+
#' add_fun(function(plot) {
501+
#' plot %>% filter(city == "Houston") %>% add_lines(name = "Houston")
502+
#' }) %>%
503+
#' add_fun(function(plot) {
504+
#' plot %>% filter(city == "San Antonio") %>% add_lines(name = "San Antonio")
505+
#' })
506+
#'
507+
#' plot_ly(mtcars, x = ~wt, y = ~mpg) %>%
508+
#' add_markers() %>%
509+
#' add_fun(function(p) {
510+
#' p %>% slice(which.max(mpg)) %>%
511+
#' add_annotations("Good mileage")
512+
#' }) %>%
513+
#' add_fun(function(p) {
514+
#' p %>% slice(which.min(mpg)) %>%
515+
#' add_annotations(text = "Bad mileage")
516+
#' })
517+
#'
518+
add_fun <- function(p, fun) {
519+
oldDat <- p$x$cur_data
520+
p <- fun(p)
521+
p$x$cur_data <- oldDat
522+
p$x$attrs[length(p$x$attrs)] <- setNames(
523+
list(p$x$attrs[[length(p$x$attrs)]]), oldDat
524+
)
525+
p
526+
}
527+
528+
529+
#' Add an annotation(s) to a plot
530+
#'
531+
#' @param p a plotly object
532+
#' @param text annotation text (required).
533+
#' @param ... these arguments are documented at \url{https://plot.ly/r/reference/#layout-annotations}
534+
#' @param data a data frame.
535+
#' @author Carson Sievert
536+
#' @export
537+
#' @examples
538+
#'
539+
#' # single annotation
540+
#' plot_ly(mtcars, x = ~wt, y = ~mpg) %>%
541+
#' slice(which.max(mpg)) %>%
542+
#' annotation(text = "Good mileage")
543+
#'
544+
#' # multiple annotations
545+
#' plot_ly(mtcars, x = ~wt, y = ~mpg) %>%
546+
#' filter(gear == 5) %>%
547+
#' annotation("five cylinder", ax = 40)
548+
#'
549+
550+
add_annotations <- function(p, text = NULL, ..., data = NULL) {
551+
if (is.null(text)) {
552+
stop("Must provide text!", call. = FALSE)
553+
}
554+
p <- add_data(p, data)
555+
attrs <- list(text = text, ...)
556+
# x/y/text inherit from plot_ly()
557+
for (i in c("x", "y", "text")) {
558+
attrs[[i]] <- attrs[[i]] %||% p$x$attrs[[1]][[i]]
559+
}
560+
if (is.null(attrs[["text"]])) {
561+
stop("Must supply text to annotation", call. = FALSE)
562+
}
563+
attrs <- list(annotations = attrs)
564+
# similar to layout()
565+
p$x$layoutAttrs <- c(
566+
p$x$layoutAttrs %||% list(),
567+
setNames(list(attrs), p$x$cur_data)
568+
)
569+
p
570+
}

R/imports.R

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#' @importFrom htmlwidgets createWidget sizingPolicy saveWidget
1111
#' @importFrom lazyeval f_eval is_formula all_dots is_lang f_new
1212
#' @importFrom tibble as_tibble
13+
#' @importFrom purrr transpose
1314
NULL
1415

1516

R/layout.R

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ layout.plotly <- function(p, ..., data = NULL) {
3030
p
3131
}
3232

33+
3334
#' Set the default configuration for plotly
3435
#'
3536
#' @param p a plotly object

R/plotly_build.R

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,19 +32,40 @@ plotly_build.gg <- function(p) {
3232
#' @export
3333
plotly_build.plotly <- function(p) {
3434

35+
# make this plot retrievable
36+
set_last_plot(p)
37+
3538
layouts <- Map(function(x, y) {
3639

3740
d <- plotly_data(p, y)
3841
x <- rapply(x, eval_attr, data = d, how = "list")
42+
43+
# if an annotation attribute is an array, expand into multiple annotations
44+
nAnnotations <- max(lengths(x$annotations) %||% 0)
45+
x$annotations <- purrr::transpose(lapply(x$annotations, function(x) {
46+
as.list(rep(x, length.out = nAnnotations))
47+
}))
48+
3949
x[lengths(x) > 0]
4050

4151
}, p$x$layoutAttrs, names2(p$x$layoutAttrs))
4252

43-
# get rid of the data -> layout mapping and merge all the layouts
44-
# into a single layout (more recent layouts will override older ones)
53+
# get rid of the data -> layout mapping
4554
p$x$layoutAttrs <- NULL
55+
56+
# accumulate, rather than override, annotations.
57+
annotations <- Reduce(c, c(
58+
if (is.null(names(p$x$layout$annotations))) p$x$layout$annotations else list(p$x$layout$annotations),
59+
compact(lapply(layouts, "[", "annotations"))
60+
))
61+
# annotations shouldn't have names
62+
annotations <- setNames(annotations[[1]], NULL)
63+
64+
# merge layouts into a single layout (more recent layouts will override older ones)
4665
p$x$layout <- modify_list(p$x$layout, Reduce(modify_list, layouts))
47-
66+
p$x$layout$annotations <- annotations
67+
68+
4869
# If type was not specified in plot_ly(), it doesn't create a trace unless
4970
# there are no other traces
5071
if (is.null(p$x$attrs[[1]][["type"]])) {
@@ -269,8 +290,6 @@ plotly_build.plotly <- function(p) {
269290
p <- verify_hovermode(p)
270291
# try to convert to webgl if toWebGl was used
271292
p <- verify_webgl(p)
272-
# make this plot retrievable
273-
set_last_plot(p)
274293
p
275294
}
276295

man/add_annotations.Rd

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

man/add_fun.Rd

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

man/gg2list.Rd

Lines changed: 3 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)