diff --git a/DESCRIPTION b/DESCRIPTION index 2d1a5a66f7..d7cf54ea92 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -75,3 +75,42 @@ LazyData: true RoxygenNote: 6.1.1 Encoding: UTF-8 Roxygen: list(markdown = TRUE) +Collate: + 'add.R' + 'animate.R' + 'api.R' + 'api_exports.R' + 'data.R' + 'deprecated.R' + 'dev.R' + 'embed.R' + 'export.R' + 'ggplotly.R' + 'gginteractive.R' + 'group2NA.R' + 'helpers.R' + 'highlight.R' + 'imports.R' + 'last_plot.R' + 'layers2layout.R' + 'layers2traces.R' + 'layout.R' + 'mathjax.R' + 'orca.R' + 'partial_bundles.R' + 'pipe.R' + 'plotly.R' + 'plotly_IMAGE.R' + 'plotly_build.R' + 'plotly_data.R' + 'plotly_example.R' + 'print.R' + 'process.R' + 'proxy.R' + 'sf.R' + 'shiny.R' + 'signup.R' + 'style.R' + 'subplots.R' + 'toRGB.R' + 'utils.R' diff --git a/NAMESPACE b/NAMESPACE index dd80145f23..55650c84c3 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -21,6 +21,7 @@ S3method(geom2trace,GeomText) S3method(geom2trace,GeomTile) S3method(geom2trace,default) S3method(ggplot,plotly) +S3method(ggplot_add,gginteractive) S3method(ggplotly,"NULL") S3method(ggplotly,ggmatrix) S3method(ggplotly,ggplot) @@ -136,6 +137,7 @@ export(filter_) export(geom2trace) export(get_figure) export(gg2list) +export(gginteractive) export(ggplotly) export(group2NA) export(group_by) @@ -222,6 +224,7 @@ importFrom(dplyr,summarise_) importFrom(dplyr,transmute) importFrom(dplyr,transmute_) importFrom(dplyr,ungroup) +importFrom(ggplot2,ggplot_add) importFrom(grDevices,as.raster) importFrom(grDevices,col2rgb) importFrom(grDevices,dev.list) @@ -257,6 +260,7 @@ importFrom(lazyeval,f_new) importFrom(lazyeval,is_formula) importFrom(lazyeval,is_lang) importFrom(magrittr,"%>%") +importFrom(purrr,partial) importFrom(purrr,transpose) importFrom(rlang,eval_tidy) importFrom(stats,complete.cases) diff --git a/R/gginteractive.R b/R/gginteractive.R new file mode 100644 index 0000000000..b61df5dd7c --- /dev/null +++ b/R/gginteractive.R @@ -0,0 +1,30 @@ +#' @include ggplotly.R +#' @rdname ggplotly +#' @importFrom purrr partial +#' +#' @param interactive +#' If `TRUE` (default), then ggplot object is converted to plotly object. +#' +#' @export +gginteractive <- function( + interactive = TRUE, width = NULL, height = NULL, tooltip = "all", + dynamicTicks = FALSE, layerData = 1, originalData = TRUE, source = "A", ... +) { + structure( + if (interactive) { + partial( + ggplotly, + width = width, height = height, tooltip = tooltip, + dynamicTicks = dynamicTicks, layerData = layerData, + originalData = originalData, source = source, ... + ) + } else { + identity + }, + class = c("gginteractive", "function") + ) +} + +#' @importFrom ggplot2 ggplot_add +#' @export +ggplot_add.gginteractive <- function(object, plot, object_name) object(plot) diff --git a/R/ggplotly.R b/R/ggplotly.R index 6f3e638e6d..e2aff2ff13 100644 --- a/R/ggplotly.R +++ b/R/ggplotly.R @@ -1,7 +1,6 @@ #' Convert ggplot2 to plotly #' -#' This function converts a [ggplot2::ggplot()] object to a -#' plotly object. +#' These functions convert a [ggplot2::ggplot()] object to a plotly object. #' #' @details Conversion of relative sizes depends on the size of the current #' graphics device (if no device is open, width/height of a new (off-screen) @@ -38,6 +37,7 @@ #' # simple example #' ggiris <- qplot(Petal.Width, Sepal.Length, data = iris, color = Species) #' ggplotly(ggiris) +#' ggiris + gginteractive() #' #' data(canada.cities, package = "maps") #' viz <- ggplot(canada.cities, aes(long, lat)) + diff --git a/man/ggplotly.Rd b/man/ggplotly.Rd index d5f3ec205e..e3b3c6a4ec 100644 --- a/man/ggplotly.Rd +++ b/man/ggplotly.Rd @@ -1,12 +1,17 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/ggplotly.R +% Please edit documentation in R/ggplotly.R, R/gginteractive.R \name{ggplotly} \alias{ggplotly} +\alias{gginteractive} \title{Convert ggplot2 to plotly} \usage{ ggplotly(p = ggplot2::last_plot(), width = NULL, height = NULL, tooltip = "all", dynamicTicks = FALSE, layerData = 1, originalData = TRUE, source = "A", ...) + +gginteractive(interactive = TRUE, width = NULL, height = NULL, + tooltip = "all", dynamicTicks = FALSE, layerData = 1, + originalData = TRUE, source = "A", ...) } \arguments{ \item{p}{a ggplot object.} @@ -36,10 +41,11 @@ with the source argument in \code{\link[=event_data]{event_data()}} to retrieve event data corresponding to a specific plot (shiny apps can have multiple plots).} \item{...}{arguments passed onto methods.} + +\item{interactive}{If \code{TRUE} (default), then ggplot object is converted to plotly object.} } \description{ -This function converts a \code{\link[ggplot2:ggplot]{ggplot2::ggplot()}} object to a -plotly object. +These functions convert a \code{\link[ggplot2:ggplot]{ggplot2::ggplot()}} object to a plotly object. } \details{ Conversion of relative sizes depends on the size of the current @@ -54,6 +60,7 @@ shiny app, see \code{plotly_example("shiny", "ggplotly_sizing")}. # simple example ggiris <- qplot(Petal.Width, Sepal.Length, data = iris, color = Species) ggplotly(ggiris) +ggiris + gginteractive() data(canada.cities, package = "maps") viz <- ggplot(canada.cities, aes(long, lat)) + diff --git a/tests/testthat/test-ggplot-ggplotly.R b/tests/testthat/test-ggplot-ggplotly.R index d2c5276c33..a0994abc32 100644 --- a/tests/testthat/test-ggplot-ggplotly.R +++ b/tests/testthat/test-ggplot-ggplotly.R @@ -3,29 +3,53 @@ context("ggplotly+plotly") p <- ggplot(txhousing, aes(x = date, y = median, group = city)) + geom_line(alpha = 0.3) +expect_identical_plotly <- function(object, expected, ...) { + expect_identical(names(object$x), names(expected$x)) + + exceptions <- c("attrs", "cur_data", "visdat", "layoutAttrs") + object$x <- object$x[setdiff(names(object$x), exceptions)] + expected$x <- expected$x[setdiff(names(expected$x), exceptions)] + expect_identical(object, expected, ...) +} + test_that("ggplotly returns original data with special attributes", { dat <- ggplotly(p) %>% plotly_data() expect_equivalent(dat, p$data) expect_equivalent(as.character(dplyr::groups(dat)), "city") + expect_equivalent(dat, plotly_data(p + gginteractive())) }) test_that("can filter data returned by ggplotly", { dat <- ggplotly(p) %>% filter(city == "Houston") %>% plotly_data() expect_equivalent(dat, subset(p$data, city == "Houston")) expect_equivalent(as.character(dplyr::groups(dat)), "city") + expect_equivalent( + dat, (p + gginteractive()) %>% filter(city == "Houston") %>% plotly_data() + ) }) test_that("can add traces with original _and_ scaled data", { l1 <- ggplotly(p) %>% add_lines() %>% plotly_build() expect_equivalent(length(l1$x$data), 2) + expect_identical_plotly( + l1, (p + gginteractive()) %>% add_lines() %>% plotly_build() + ) l2 <- ggplotly(p, originalData = FALSE) %>% add_lines() %>% plotly_build() # ideally we'd test that the two plots have the same data, but # for some reason R CMD check throws an error which I can't replicate :( expect_equivalent(length(l2$x$data), 2) + expect_identical_plotly( + l2, (p + gginteractive(originalData = FALSE)) %>% add_lines() %>% plotly_build() + ) }) test_that("can access ggplot data in layout()", { l <- ggplotly(p) %>% layout(title = ~range(date)) expect_equivalent(plotly_build(l)$x$layout$title, range(txhousing$date)) + expect_identical_plotly(l, (p + gginteractive()) %>% layout(title = ~range(date))) +}) + +test_that("can suppress conversion of ggplot to plotly", { + expect_s3_class(p + gginteractive(interactive = FALSE), class(p)) })