diff --git a/DESCRIPTION b/DESCRIPTION index 502bf63009..49d95b2b3b 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: plotly Title: Create interactive web-based graphs via plotly's API -Version: 1.0.7 +Version: 1.0.8 Authors@R: c(person("Chris", "Parmer", role = c("aut", "cph"), email = "chris@plot.ly"), person("Scott", "Chamberlain", role = "aut", @@ -30,7 +30,8 @@ Imports: jsonlite, magrittr, digest, - viridis + viridis, + base64enc Suggests: dplyr, maps, diff --git a/NAMESPACE b/NAMESPACE index 211f169946..cb9543f789 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -20,6 +20,7 @@ export(paramORdefault) export(plot_ly) export(plotly) export(plotlyOutput) +export(plotly_IMAGE) export(plotly_POST) export(plotly_build) export(plotly_empty) @@ -31,5 +32,6 @@ export(toRGB) import(ggplot2) import(httr) import(jsonlite) +importFrom(base64enc,base64encode) importFrom(magrittr,"%>%") importFrom(viridis,viridis) diff --git a/NEWS b/NEWS index 7935e95029..6f9483b983 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,9 @@ +1.0.8 -- 14 Sep 2015 + +Added the plotly_IMAGES() function which interfaces to the images endpoint https://api.plot.ly/v2/#images + +Details -> https://github.com/ropensci/plotly/pull/279 + 1.0.7 -- 26 Aug 2015 See https://github.com/ropensci/plotly/pull/275 diff --git a/R/plotly.R b/R/plotly.R index de89f3cb1f..382ecd9e0f 100644 --- a/R/plotly.R +++ b/R/plotly.R @@ -316,8 +316,26 @@ plotly_build <- function(l = last_plot()) { } # search for keyword args in traces and place them at the top level kwargs <- lapply(x$data, function(z) z[get_kwargs()]) - if (length(kwargs) == 1) kwargs <- c(kwargs, kwargs) - x <- c(x, Reduce(modifyList, kwargs)) + # later keywords args take precedence + kwargs <- Reduce(modifyList, kwargs) + # empty keyword arguments can cause problems + kwargs <- kwargs[sapply(kwargs, length) > 0] + # filename & fileopt are keyword arguments required by the API + if (!is.null(x$url) || !is.null(kwargs$filename)) + kwargs$fileopt <- "overwrite" + kwargs$fileopt <- kwargs$fileopt %||% "new" + # try our damndest to assign a sensible filename + if (is.null(kwargs$filename)) { + kwargs$filename <- + as.character(kwargs$layout$title) %||% + paste( + c(kwargs$layout$xaxis$title, + kwargs$layout$yaxis$title, + kwargs$layout$zaxis$title), + collapse = " vs. " + ) %||% + "plot from api" + } # traces shouldn't have any names x$data <- setNames(x$data, NULL) # add plotly class mainly for printing method diff --git a/R/plotly_IMAGE.R b/R/plotly_IMAGE.R new file mode 100644 index 0000000000..80d3011b93 --- /dev/null +++ b/R/plotly_IMAGE.R @@ -0,0 +1,42 @@ +#' Create/Modify plotly images +#' +#' The images endpoint turn a plot (which may be given in multiple forms) +#' into an image of the desired format. +#' +#' @param x either a plotly object or a list. +#' @param width Image width in pixels +#' @param height Image height in pixels +#' @param format The desired image format 'png', 'jpeg', 'svg', 'pdf', 'eps', or 'webp' +#' @param scale Both png and jpeg formats will be scaled beyond the specified width and height by this number. +#' @param out_file A filename for writing the image to a file. +#' @param ... arguments passed onto \code{httr::POST} +#' @export +#' @examples +#' +#' p <- plot_ly(x = 1:10) +#' +#' Png <- plotly_IMAGE(p, out_file = "plotly-test-image.png") +#' Jpeg <- plotly_IMAGE(p, format = "jpeg", out_file = "plotly-test-image.jpeg") +#' Svg <- plotly_IMAGE(p, format = "svg", out_file = "plotly-test-image.svg") +#' Pdf <- plotly_IMAGE(p, format = "pdf", out_file = "plotly-test-image.pdf") +#' + +plotly_IMAGE <- function(x, width = 1000, height = 500, format = "png", + scale = 4, out_file, ...) { + x <- plotly_build(x) + + bod <- list( + figure = x[c("data", "layout")], + width = width, + height = height, + format = format, + scale = scale, + encoded = FALSE + ) + base_url <- paste0(get_domain("v2"), "images") + resp <- httr::POST(base_url, plotly_headers("v2"), body = to_JSON(bod), + if (!missing(out_file)) write_disk(out_file, overwrite = TRUE), + ...) + con <- process(struct(resp, "image")) + invisible(con) +} diff --git a/R/plotly_POST.R b/R/plotly_POST.R index 3f49149905..c3da4b0a5e 100644 --- a/R/plotly_POST.R +++ b/R/plotly_POST.R @@ -28,25 +28,6 @@ plotly_POST <- function(x) { x <- plotly_build(x) - # empty keyword arguments can cause problems - kwargs <- x[get_kwargs()] - kwargs <- kwargs[sapply(kwargs, length) > 0] - - # filename & fileopt are keyword arguments required by the API - # (note they can also be specified by the user) - if (!is.null(x$url) || !is.null(kwargs$filename)) kwargs$fileopt <- "overwrite" - if (is.null(kwargs$filename)) { - kwargs$filename <- - as.character(kwargs$layout$title) %||% - paste( - c(kwargs$layout$xaxis$title, - kwargs$layout$yaxis$title, - kwargs$layout$zaxis$title), - collapse = " vs. " - ) %||% - "plot from api" - } - if (is.null(kwargs$fileopt)) kwargs$fileopt <- "new" # construct body of message to plotly server bod <- list( un = verify("username"), @@ -55,12 +36,12 @@ plotly_POST <- function(x) { platform = "R", version = as.character(packageVersion("plotly")), args = to_JSON(x$data), - kwargs = to_JSON(kwargs) + kwargs = to_JSON(x[get_kwargs()]) ) base_url <- file.path(get_domain(), "clientresp") resp <- httr::POST(base_url, body = bod) con <- process(struct(resp, "clientresp")) - msg <- switch(kwargs$fileopt, + msg <- switch(x$fileopt %||% "new", new = "Success! Created a new plotly here -> ", overwrite = "Success! Modified your plotly here -> ") message(msg, con$url) diff --git a/R/process.R b/R/process.R index d0fe9abf58..36fd72cf4b 100644 --- a/R/process.R +++ b/R/process.R @@ -15,6 +15,13 @@ process.clientresp <- function(resp) { con } +process.image <- function(resp) { + httr::stop_for_status(resp) + # httr (should) know to call png::readPNG() which returns raster array + tryCatch(httr::content(resp, as = "parsed"), + error = function(e) httr::content(resp, as = "raw")) +} + process.figure <- function(resp) { httr::stop_for_status(resp) con <- from_JSON(content(resp, as = "text")) diff --git a/R/utils.R b/R/utils.R index c8702de3d4..74af1c26c4 100644 --- a/R/utils.R +++ b/R/utils.R @@ -141,10 +141,11 @@ struct <- function(x, y, ...) { get_domain <- function(type = "main") { if (type == "stream") { Sys.getenv("plotly_streaming_domain", "http://stream.plot.ly") + } else if (type == "v2") { + Sys.getenv("plotly_domain", "https://api.plot.ly/v2/") } else { Sys.getenv("plotly_domain", "https://plot.ly") } - } # plotly's special keyword arguments in POST body @@ -153,12 +154,28 @@ get_kwargs <- function() { } # POST header fields -plotly_headers <- function() { - httr::add_headers(.headers = c( - "plotly-username" = verify("username"), - "plotly-apikey" = verify("api_key"), - "plotly-version" = as.character(packageVersion("plotly")), - "plotly-platform" = "R")) +#' @importFrom base64enc base64encode +plotly_headers <- function(type = "main") { + usr <- verify("username") + key <- verify("api_key") + v <- as.character(packageVersion("plotly")) + h <- if (type == "v2") { + auth <- base64enc::base64encode(charToRaw(paste(usr, key, sep = ":"))) + c( + "authorization" = paste("Basic", auth), + "plotly-client-platform" = paste("R", v), + "plotly_version" = v, + "content-type" = "application/json" + ) + } else { + c( + "plotly-username" = usr, + "plotly-apikey" = key, + "plotly-version" = v, + "plotly-platform" = "R" + ) + } + httr::add_headers(.headers = h) } # try to write environment variables to an .Rprofile diff --git a/man/plotly_IMAGE.Rd b/man/plotly_IMAGE.Rd new file mode 100644 index 0000000000..c14053bd81 --- /dev/null +++ b/man/plotly_IMAGE.Rd @@ -0,0 +1,38 @@ +% Generated by roxygen2 (4.1.1): do not edit by hand +% Please edit documentation in R/plotly_IMAGE.R +\name{plotly_IMAGE} +\alias{plotly_IMAGE} +\title{Create/Modify plotly images} +\usage{ +plotly_IMAGE(x, width = 1000, height = 500, format = "png", scale = 4, + out_file, ...) +} +\arguments{ +\item{x}{either a plotly object or a list.} + +\item{width}{Image width in pixels} + +\item{height}{Image height in pixels} + +\item{format}{The desired image format 'png', 'jpeg', 'svg', 'pdf', 'eps', or 'webp'} + +\item{scale}{Both png and jpeg formats will be scaled beyond the specified width and height by this number.} + +\item{out_file}{A filename for writing the image to a file.} + +\item{...}{arguments passed onto \code{httr::POST}} +} +\description{ +The images endpoint turn a plot (which may be given in multiple forms) +into an image of the desired format. +} +\examples{ +p <- plot_ly(x = 1:10) + +# returns raster array representing the image +img <- plotly_IMAGE(p, out_file = "plotly-test-image.png", overwrite = TRUE) + +img <- plotly_IMAGE(p, format = "pdf", + out_file = "plotly-test-image.pdf") +} + diff --git a/tests/testthat/test-plotly-filename.R b/tests/testthat/test-plotly-filename.R index c573ad6c72..a58f4e17c0 100644 --- a/tests/testthat/test-plotly-filename.R +++ b/tests/testthat/test-plotly-filename.R @@ -2,6 +2,5 @@ context("Filename") test_that("filepath with directories is returned as passed", { p <- print(plot_ly(mtcars, x = wt, y = vs, filename = "directory/awesome")) - # why is the directory name replicated in the response? - expect_identical(p$filename, "directorydirectory/awesome") + expect_match(p$filename, "directory/awesome") })