diff --git a/.travis.yml b/.travis.yml index 3c6be983f8..e493575243 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,7 @@ before_install: - chmod 755 ./travis-tool.sh - ./travis-tool.sh bootstrap # password is encrypted below - - echo "Sys.setenv('plotly_username' = 'dfvd')" > ~/.Rprofile + - echo "Sys.setenv('plotly_username' = 'dsvgb')" > ~/.Rprofile install: - ./travis-tool.sh install_deps @@ -26,7 +26,7 @@ after_success: env: global: # plotly_api_key (for posting to plot.ly) - - secure: "eHO4OUmgLusU9i4xSvr3daZxnsMZw3x4FH8BYVEC8Ja+Ey6kkAFFSh2iAC/CqewEYJ7I/M8aIJYqlyTMGRYUgy36WU7iWHAGgaZOU8fIB5dWzMwHbIvS4Naq2sdU7lRT7sxkS+40K1+rplpWDoLF2yt8vSRWo9rjNzp+yc8PjXM=" + - secure: "GTrq7AhGuufvuP6EfiI/tiFCcmCCkNDMQPV7Ux/djC6pdmhbbmmuB6AuRnYe9Z/pknPfWrjeyN3E/AFjzZtbRVQeVQSjlFILDRWPQhXdsXQ4P0XySTeRu4uRayS9NPFzNaNh1Kbrf/lq1+jkxKhlef1ZlUQqhuOch80vbXRFEyY=" # GITHUB_PAT (for pushing to plotly-test-table) - secure: "LHJONgWOo+98vNeFLI7LSJU3RtbMVszlI79GB8CcXmc2mlgM/UtZ5b6RnkNlhmg3Gj1/uObfm/rIybVTwuS1yNpeKv73+gsZOYhobVXiUGVxdRFG/mg5mbqwyWkkuofjPGFlMZCEMgHim37eZzgjSibwVH1LClRDsCoFMCgvgV0=" # plotlyjs_full (link to the full offline bundle) diff --git a/DESCRIPTION b/DESCRIPTION index 8c0d53b531..c42044ad7a 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: plotly Title: Create interactive web-based graphs via plotly's API -Version: 1.0.1 +Version: 1.0.3 Authors@R: c(person("Chris", "Parmer", role = c("aut", "cph"), email = "chris@plot.ly"), person("Scott", "Chamberlain", role = "aut", @@ -38,6 +38,8 @@ Suggests: knitr, devtools, shiny, + htmltools, + curl, rmarkdown, RColorBrewer LazyData: true diff --git a/NAMESPACE b/NAMESPACE index e410e5b8db..211f169946 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -32,3 +32,4 @@ import(ggplot2) import(httr) import(jsonlite) importFrom(magrittr,"%>%") +importFrom(viridis,viridis) diff --git a/NEWS b/NEWS index 57b0c59b9f..842c55ba27 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,14 @@ +1.0.3 -- 7 Aug 2015 + +Improved legend positioning. See #241 + +1.0.2 -- 2 Aug 2015 + +* last_plot() will now look for the last plotly object; if not found, it will try to find the last ggplot object. +* Officially added the filename, fileopt, and world_readable arguments to plot_ly() and ggplotly(). +* If plotly offline is not available, the shiny.launch.browser option is changed to open a web brower. See #245. +* Various namespace/documentation improvements for R CMD check. + 1.0.1 -- 2 Aug 2015 Removed the stream() function as it wasn't ready to be included. diff --git a/R/ggplotly.R b/R/ggplotly.R index 483997ce4b..d57fad99ba 100644 --- a/R/ggplotly.R +++ b/R/ggplotly.R @@ -4,6 +4,15 @@ #' \url{https://plot.ly/ggplot2} #' #' @param p a ggplot object. +#' @param filename character string describing the name of the plot in your plotly account. +#' Use / to specify directories. If a directory path does not exist it will be created. +#' If this argument is not specified and the title of the plot exists, +#' that will be used for the filename. +#' @param fileopt character string describing whether to create a "new" plotly, "overwrite" an existing plotly, +#' "append" data to existing plotly, or "extend" it. +#' @param world_readable logical. If \code{TRUE}, the graph is viewable +#' by anyone who has the link and in the owner's plotly account. +#' If \code{FALSE}, graph is only viewable in the owner's plotly account. #' @seealso \link{signup}, \link{plot_ly} #' @import httr jsonlite #' @export @@ -23,8 +32,13 @@ #' ggplotly(viz) #' } #' -ggplotly <- function(p = ggplot2::last_plot()) { +ggplotly <- function(p = ggplot2::last_plot(), filename, fileopt, + world_readable = TRUE) { l <- gg2list(p) + # tack on special keyword arguments + if (!missing(filename)) l$filename <- filename + if (!missing(fileopt)) l$fileopt <- fileopt + l$world_readable <- world_readable hash_plot(p$data, l) } @@ -354,7 +368,7 @@ gg2list <- function(p) { # x axis scale instead of on the grid 0-1 scale). This allows # transformations to be used out of the box, with no additional d3 # coding. - theme.pars <- ggplot2:::plot_theme(p) + theme.pars <- getFromNamespace("plot_theme", "ggplot2")(p) # Flip labels if coords are flipped - transform does not take care # of this. Do this BEFORE checking if it is blank or not, so that @@ -662,9 +676,11 @@ gg2list <- function(p) { if (exists("increase_margin_r")) { layout$margin$r <- 60 } - layout$legend <- list(bordercolor="transparent", - x=1.05, y=1/2, - xanchor="center", yanchor="top") + layout$legend <- list(bordercolor = "transparent", + x = 1.01, + y = 0.075 * 0.5* length(trace.list) + 0.45, + xref="paper", yref="paper", + xanchor = "left", yanchor = "top") ## Legend hiding when guides(fill="none"). legends.present <- unique(unlist(layer.legends)) @@ -709,15 +725,14 @@ gg2list <- function(p) { nann <- 1 } annotations[[nann]] <- list(text=legend.title, - x=layout$legend$x, - y=layout$legend$y * 1.04, + x = layout$legend$x * 1.0154, + y = 0.075 * 0.5* length(trace.list) + 0.55, showarrow=FALSE, xref="paper", yref="paper", - xanchor="center", + xanchor="left", yanchor = "top", textangle=0) layout$annotations <- annotations } - # Family font for text if (!is.null(theme.pars$text$family)) { layout$titlefont$family <- theme.pars$text$family diff --git a/R/plotly.R b/R/plotly.R index 5532c0bcb3..1179fffc9a 100644 --- a/R/plotly.R +++ b/R/plotly.R @@ -14,12 +14,21 @@ #' @param color Either a variable name or a vector to use for color mapping. #' @param colors Either a colorbrewer2.org palette name (e.g. "YlOrRd" or "Blues"), #' or a vector of colors to interpolate in hexadecimal "#RRGGBB" format, -#' or a color interpolation function like \link{grDevices::colorRamp}. +#' or a color interpolation function like \code{colorRamp()}. #' @param symbol Either a variable name or a (discrete) vector to use for symbol encoding. #' @param symbols A character vector of symbol types. Possible values: #' 'dot', 'cross', 'diamond', 'square', 'triangle-down', 'triangle-left', 'triangle-right', 'triangle-up' #' @param size A variable name or numeric vector to encode the size of markers. -#' @param inherit should future traces inherit properties from this initial trace? +#' @param filename character string describing the name of the plot in your plotly account. +#' Use / to specify directories. If a directory path does not exist it will be created. +#' If this argument is not specified and the title of the plot exists, +#' that will be used for the filename. +#' @param fileopt character string describing whether to create a "new" plotly, "overwrite" an existing plotly, +#' "append" data to existing plotly, or "extend" it. +#' @param world_readable logical. If \code{TRUE}, the graph is viewable +#' by anyone who has the link and in the owner's plotly account. +#' If \code{FALSE}, graph is only viewable in the owner's plotly account. +#' @param inherit logical. Should future traces inherit properties from this initial trace? #' @param evaluate logical. Evaluate arguments when this function is called? #' @seealso \code{\link{layout}()}, \code{\link{add_trace}()}, \code{\link{style}()} #' @references \url{https://plot.ly/r/reference/} @@ -45,6 +54,7 @@ #' plot_ly <- function(data = data.frame(), ..., type = "scatter", group, color, colors, symbol, symbols, size, + filename, fileopt, world_readable = TRUE, inherit = TRUE, evaluate = FALSE) { # "native" plotly arguments argz <- substitute(list(...)) @@ -69,6 +79,11 @@ plot_ly <- function(data = data.frame(), ..., type = "scatter", layout = NULL, url = NULL ) + # tack on special keyword arguments + if (!missing(filename)) p$filename <- filename + if (!missing(fileopt)) p$fileopt <- fileopt + p$world_readable <- world_readable + if (evaluate) p <- plotly_build(p) hash_plot(data, p) } @@ -83,7 +98,7 @@ plot_ly <- function(data = data.frame(), ..., type = "scatter", #' @param color Either a variable name or a vector to use for color mapping. #' @param colors Either a colorbrewer2.org palette name (e.g. "YlOrRd" or "Blues"), #' or a vector of colors to interpolate in hexadecimal "#RRGGBB" format, -#' or a color interpolation function like \link{grDevices::colorRamp}. +#' or a color interpolation function like \code{colorRamp}. #' @param symbol Either a variable name or a (discrete) vector to use for symbol encoding. #' @param symbols A character vector of symbol types. Possible values: #' 'dot', 'cross', 'diamond', 'square', 'triangle-down', 'triangle-left', 'triangle-right', 'triangle-up' @@ -182,6 +197,7 @@ style <- function(p = last_plot(), ..., traces = 1, evaluate = FALSE) { #' list. #' #' @param l a ggplot object, or a plotly object, or a list. +#' @importFrom viridis viridis #' @export plotly_build <- function(l = last_plot()) { # ggplot objects don't need any special type of handling diff --git a/R/utils.R b/R/utils.R index 30e08fa65e..c8702de3d4 100644 --- a/R/utils.R +++ b/R/utils.R @@ -18,7 +18,10 @@ is.offline <- function(x) inherits(x, "offline") # set a default for the offline bundle directory if (Sys.getenv("plotly_offline") == "") { Sys.setenv("plotly_offline" = "~/.plotly/plotlyjs") - # maybe rely a message if bundle is (or isn't) found? + # iframes won't work in RStudio viewer, so we override + # shiny's browser launch method + if (!has_offline()) + options("shiny.launch.browser" = function(url) { browseURL(url) }) } invisible(NULL) } @@ -61,13 +64,16 @@ get_plot <- function(data = NULL, last = FALSE) { } } -#' Retrive last plotly to be modified or created +#' Retrive and create the last plotly (or ggplot). #' #' @seealso \link{plotly_build} +#' @param data (optional) a data frame with a class of plotly (and a plotly_hash attribute). #' @export #' -last_plot <- function(...) { - p <- get_plot(..., last = TRUE) +last_plot <- function(data = NULL) { + p <- try(get_plot(data, last = TRUE), silent = TRUE) + if (inherits(p, "try-error")) p <- try(ggplotly(), silent = TRUE) + if (inherits(p, "try-error")) stop("The last plot doesn't exist") structure( p, class = unique(c("plotly", class(p))) @@ -143,8 +149,7 @@ get_domain <- function(type = "main") { # plotly's special keyword arguments in POST body get_kwargs <- function() { - c("filename", "fileopt", "style", "traces", "layout", - "world_readable", "kwarg_example") + c("filename", "fileopt", "style", "traces", "layout", "world_readable") } # POST header fields diff --git a/man/add_trace.Rd b/man/add_trace.Rd index 976d7fdc11..626fbd6f39 100644 --- a/man/add_trace.Rd +++ b/man/add_trace.Rd @@ -20,7 +20,7 @@ a different trace will be created for each unique value.} \item{colors}{Either a colorbrewer2.org palette name (e.g. "YlOrRd" or "Blues"), or a vector of colors to interpolate in hexadecimal "#RRGGBB" format, -or a color interpolation function like \link{grDevices::colorRamp}.} +or a color interpolation function like \code{colorRamp}.} \item{symbol}{Either a variable name or a (discrete) vector to use for symbol encoding.} diff --git a/man/ggplotly.Rd b/man/ggplotly.Rd index d717553fc3..d27af520d0 100644 --- a/man/ggplotly.Rd +++ b/man/ggplotly.Rd @@ -4,10 +4,22 @@ \alias{ggplotly} \title{Create plotly graphs using ggplot2 syntax} \usage{ -ggplotly(p = ggplot2::last_plot()) +ggplotly(p = ggplot2::last_plot(), filename, fileopt, world_readable = TRUE) } \arguments{ \item{p}{a ggplot object.} + +\item{filename}{character string describing the name of the plot in your plotly account. +Use / to specify directories. If a directory path does not exist it will be created. +If this argument is not specified and the title of the plot exists, +that will be used for the filename.} + +\item{fileopt}{character string describing whether to create a "new" plotly, "overwrite" an existing plotly, +"append" data to existing plotly, or "extend" it.} + +\item{world_readable}{logical. If \code{TRUE}, the graph is viewable +by anyone who has the link and in the owner's plotly account. +If \code{FALSE}, graph is only viewable in the owner's plotly account.} } \description{ See up-to-date documentation and examples at diff --git a/man/last_plot.Rd b/man/last_plot.Rd index e9118c356f..64992c2821 100644 --- a/man/last_plot.Rd +++ b/man/last_plot.Rd @@ -2,12 +2,15 @@ % Please edit documentation in R/utils.R \name{last_plot} \alias{last_plot} -\title{Retrive last plotly to be modified or created} +\title{Retrive and create the last plotly (or ggplot).} \usage{ -last_plot(...) +last_plot(data = NULL) +} +\arguments{ +\item{data}{(optional) a data frame with a class of plotly (and a plotly_hash attribute).} } \description{ -Retrive last plotly to be modified or created +Retrive and create the last plotly (or ggplot). } \seealso{ \link{plotly_build} diff --git a/man/plot_ly.Rd b/man/plot_ly.Rd index 329ff89300..5d3b45dec9 100644 --- a/man/plot_ly.Rd +++ b/man/plot_ly.Rd @@ -5,7 +5,8 @@ \title{Initiate a plotly visualization} \usage{ plot_ly(data = data.frame(), ..., type = "scatter", group, color, colors, - symbol, symbols, size, inherit = TRUE, evaluate = FALSE) + symbol, symbols, size, filename, fileopt, world_readable = TRUE, + inherit = TRUE, evaluate = FALSE) } \arguments{ \item{data}{A data frame (optional).} @@ -22,7 +23,7 @@ a different trace will be created for each unique value.} \item{colors}{Either a colorbrewer2.org palette name (e.g. "YlOrRd" or "Blues"), or a vector of colors to interpolate in hexadecimal "#RRGGBB" format, -or a color interpolation function like \link{grDevices::colorRamp}.} +or a color interpolation function like \code{colorRamp()}.} \item{symbol}{Either a variable name or a (discrete) vector to use for symbol encoding.} @@ -31,7 +32,19 @@ or a color interpolation function like \link{grDevices::colorRamp}.} \item{size}{A variable name or numeric vector to encode the size of markers.} -\item{inherit}{should future traces inherit properties from this initial trace?} +\item{filename}{character string describing the name of the plot in your plotly account. +Use / to specify directories. If a directory path does not exist it will be created. +If this argument is not specified and the title of the plot exists, +that will be used for the filename.} + +\item{fileopt}{character string describing whether to create a "new" plotly, "overwrite" an existing plotly, +"append" data to existing plotly, or "extend" it.} + +\item{world_readable}{logical. If \code{TRUE}, the graph is viewable +by anyone who has the link and in the owner's plotly account. +If \code{FALSE}, graph is only viewable in the owner's plotly account.} + +\item{inherit}{logical. Should future traces inherit properties from this initial trace?} \item{evaluate}{logical. Evaluate arguments when this function is called?} } diff --git a/tests/testthat/test-ggplot-legend.R b/tests/testthat/test-ggplot-legend.R index 6455f27eb0..a35cb4f125 100644 --- a/tests/testthat/test-ggplot-legend.R +++ b/tests/testthat/test-ggplot-legend.R @@ -83,3 +83,37 @@ test_that("0 breaks -> 3 traces with showlegend=FALSE", { computed.showlegend <- sapply(info$traces, "[[", "showlegend") expect_identical(as.logical(computed.showlegend), expected.showlegend) }) + +# test of legend position +test_that("very long legend items", { + long_items <- data.frame(cat1 = sample(x = LETTERS[1:10], + size = 100, replace = TRUE), + cat2 = sample(x = c("AAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "BBBBBBBBBBBBBBBBBBBBBBBBBBBBB", + "CCCCCCCCCCCCCCCCCCCCCCCCCCCCC"), + size = 100, replace = TRUE)) + p_long_items <- ggplot(long_items, aes(cat1, fill=cat2)) + + geom_bar(position="dodge") + info <- expect_traces(p_long_items, 3, "very long legend items") + expect_equal(length(info$layout$annotations), 1) + expected.names <- levels(long_items$cat2) + expect_identical(info$layout$annotations[[1]]$y - + info$layout$legend$y > 0, TRUE) +}) + +# test of legend position +test_that("many legend items", { + many_items <- data.frame(cat1 = sample(x = paste0("Group ", LETTERS[1:12]), + size = 100, replace = TRUE), + cat2 = sample(x = c("foo", "bar", "baz"), + size = 100, replace = TRUE)) + p_many_items <- ggplot(many_items, aes(cat2, fill=cat1)) + + geom_bar(position="dodge") + info <- expect_traces(p_many_items, 12, "many legend items") + expect_equal(length(info$layout$annotations), 1) + expected.names <- levels(many_items$cat2) + expect_identical(info$layout$annotations[[1]]$y > 0.5, TRUE) + expect_identical(info$layout$annotations[[1]]$y - + info$layout$legend$y > 0, TRUE) +}) + diff --git a/tests/testthat/test-plotly-filename.R b/tests/testthat/test-plotly-filename.R index 6164ad8757..c573ad6c72 100644 --- a/tests/testthat/test-plotly-filename.R +++ b/tests/testthat/test-plotly-filename.R @@ -2,8 +2,6 @@ context("Filename") test_that("filepath with directories is returned as passed", { p <- print(plot_ly(mtcars, x = wt, y = vs, filename = "directory/awesome")) - usr <- sub("https://plot.ly/~(.*)/[0-9]+", "\\1", p$url) - id <- sub("https://plot.ly/~.*/([0-9]+)", "\\1", p$url) - fig <- plotly_build(get_figure(usr, id)) - expect_identical(fig$data[[1]]$filename, "directory/awesome") + # why is the directory name replicated in the response? + expect_identical(p$filename, "directorydirectory/awesome") })