From 0b2eeb9692a2dfd69a596252e3b4a1f82db973c6 Mon Sep 17 00:00:00 2001 From: cpsievert Date: Thu, 3 Sep 2015 22:18:24 -0500 Subject: [PATCH 1/4] First stab at implementing images endpoint --- NAMESPACE | 1 + R/plotly.R | 22 ++++++++++++++++++++-- R/plotly_IMAGE.R | 36 ++++++++++++++++++++++++++++++++++++ R/plotly_POST.R | 21 +-------------------- R/process.R | 6 ++++++ R/utils.R | 27 ++++++++++++++++++++------- man/plotly_IMAGE.Rd | 30 ++++++++++++++++++++++++++++++ 7 files changed, 114 insertions(+), 29 deletions(-) create mode 100644 R/plotly_IMAGE.R create mode 100644 man/plotly_IMAGE.Rd diff --git a/NAMESPACE b/NAMESPACE index 211f169946..4cf9db4397 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) 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..d79e252d32 --- /dev/null +++ b/R/plotly_IMAGE.R @@ -0,0 +1,36 @@ +#' 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 encoded A boolean flag for base64 encoding of bytes +#' @export +#' @examples +#' +#' plotly_IMAGE(plot_ly(x = 1:10)) +#' +#' + +plotly_IMAGE <- function(x, width = 1000, height = 500, format = "png", + scale = 4, encoded = FALSE) { + x <- plotly_build(x) + + bod <- list( + figure = to_JSON(x[c("data", "layout")]), + width = width, + height = height, + format = format, + scale = scale, + encoded = encoded + ) + + base_url <- file.path(get_domain("v2"), "images") + resp <- httr::POST(base_url, plotly_headers("v2"), body = bod) + con <- process(struct(resp, "image")) + +} diff --git a/R/plotly_POST.R b/R/plotly_POST.R index 3f49149905..7953679a5e 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,7 +36,7 @@ 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) diff --git a/R/process.R b/R/process.R index d0fe9abf58..fe64a6d49c 100644 --- a/R/process.R +++ b/R/process.R @@ -15,6 +15,12 @@ process.clientresp <- function(resp) { con } +process.image <- function(resp) { + httr::stop_for_status(resp) + browser() + con <- from_JSON(httr::content(resp, as = "text")) +} + 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..d0578695a0 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,24 @@ 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")) +plotly_headers <- function(type = "main") { + usr <- verify("username") + key <- verify("api_key") + h <- if (type == "v2") { + c( + "Plotly-Username" = base64enc::base64encode(charToRaw(usr)), + "Plotly-Apikey" = base64enc::base64encode(charToRaw(key)), + "Plotly-Client-Platform" = paste("R", as.character(packageVersion("plotly"))) + ) + } else { + c( + "plotly-username" = usr, + "plotly-apikey" = key, + "plotly-version" = as.character(packageVersion("plotly")), + "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..12f548a7cd --- /dev/null +++ b/man/plotly_IMAGE.Rd @@ -0,0 +1,30 @@ +% 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, + encoded = FALSE) +} +\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{encoded}{A boolean flag for base64 encoding of bytes} +} +\description{ +The images endpoint turn a plot (which may be given in multiple forms) +into an image of the desired format. +} +\examples{ +plotly_IMAGE(qplot(1:10)) +} + From e72a50f07cc3ae70d9e1017c9d7014f319ece08c Mon Sep 17 00:00:00 2001 From: cpsievert Date: Thu, 3 Sep 2015 22:33:08 -0500 Subject: [PATCH 2/4] import base64enc --- DESCRIPTION | 3 ++- R/utils.R | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 502bf63009..58f4539938 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -30,7 +30,8 @@ Imports: jsonlite, magrittr, digest, - viridis + viridis, + base64enc Suggests: dplyr, maps, diff --git a/R/utils.R b/R/utils.R index d0578695a0..59da913c13 100644 --- a/R/utils.R +++ b/R/utils.R @@ -154,13 +154,14 @@ get_kwargs <- function() { } # POST header fields +#' @importFrom base64enc base64encode plotly_headers <- function(type = "main") { usr <- verify("username") key <- verify("api_key") h <- if (type == "v2") { c( - "Plotly-Username" = base64enc::base64encode(charToRaw(usr)), - "Plotly-Apikey" = base64enc::base64encode(charToRaw(key)), + "plotly-username" = base64enc::base64encode(charToRaw(usr)), + "plotly-apikey" = base64enc::base64encode(charToRaw(key)), "Plotly-Client-Platform" = paste("R", as.character(packageVersion("plotly"))) ) } else { From 681509f0e4bcfadb39616da37024b411c22c1824 Mon Sep 17 00:00:00 2001 From: cpsievert Date: Wed, 9 Sep 2015 14:49:22 -0500 Subject: [PATCH 3/4] plotly_IMAGE() seems to work --- NAMESPACE | 1 + R/plotly_IMAGE.R | 24 +++++++++++++++--------- R/plotly_POST.R | 2 +- R/process.R | 5 +++-- R/utils.R | 11 +++++++---- man/plotly_IMAGE.Rd | 14 +++++++++++--- 6 files changed, 38 insertions(+), 19 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index 4cf9db4397..cb9543f789 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -32,5 +32,6 @@ export(toRGB) import(ggplot2) import(httr) import(jsonlite) +importFrom(base64enc,base64encode) importFrom(magrittr,"%>%") importFrom(viridis,viridis) diff --git a/R/plotly_IMAGE.R b/R/plotly_IMAGE.R index d79e252d32..80d3011b93 100644 --- a/R/plotly_IMAGE.R +++ b/R/plotly_IMAGE.R @@ -8,29 +8,35 @@ #' @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 encoded A boolean flag for base64 encoding of bytes +#' @param out_file A filename for writing the image to a file. +#' @param ... arguments passed onto \code{httr::POST} #' @export #' @examples #' -#' plotly_IMAGE(plot_ly(x = 1:10)) +#' 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, encoded = FALSE) { + scale = 4, out_file, ...) { x <- plotly_build(x) bod <- list( - figure = to_JSON(x[c("data", "layout")]), + figure = x[c("data", "layout")], width = width, height = height, format = format, scale = scale, - encoded = encoded + encoded = FALSE ) - - base_url <- file.path(get_domain("v2"), "images") - resp <- httr::POST(base_url, plotly_headers("v2"), body = bod) + 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 7953679a5e..c3da4b0a5e 100644 --- a/R/plotly_POST.R +++ b/R/plotly_POST.R @@ -41,7 +41,7 @@ plotly_POST <- function(x) { 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 fe64a6d49c..36fd72cf4b 100644 --- a/R/process.R +++ b/R/process.R @@ -17,8 +17,9 @@ process.clientresp <- function(resp) { process.image <- function(resp) { httr::stop_for_status(resp) - browser() - con <- from_JSON(httr::content(resp, as = "text")) + # 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) { diff --git a/R/utils.R b/R/utils.R index 59da913c13..74af1c26c4 100644 --- a/R/utils.R +++ b/R/utils.R @@ -158,17 +158,20 @@ get_kwargs <- function() { 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( - "plotly-username" = base64enc::base64encode(charToRaw(usr)), - "plotly-apikey" = base64enc::base64encode(charToRaw(key)), - "Plotly-Client-Platform" = paste("R", as.character(packageVersion("plotly"))) + "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" = as.character(packageVersion("plotly")), + "plotly-version" = v, "plotly-platform" = "R" ) } diff --git a/man/plotly_IMAGE.Rd b/man/plotly_IMAGE.Rd index 12f548a7cd..c14053bd81 100644 --- a/man/plotly_IMAGE.Rd +++ b/man/plotly_IMAGE.Rd @@ -5,7 +5,7 @@ \title{Create/Modify plotly images} \usage{ plotly_IMAGE(x, width = 1000, height = 500, format = "png", scale = 4, - encoded = FALSE) + out_file, ...) } \arguments{ \item{x}{either a plotly object or a list.} @@ -18,13 +18,21 @@ plotly_IMAGE(x, width = 1000, height = 500, format = "png", scale = 4, \item{scale}{Both png and jpeg formats will be scaled beyond the specified width and height by this number.} -\item{encoded}{A boolean flag for base64 encoding of bytes} +\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{ -plotly_IMAGE(qplot(1:10)) +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") } From de951ded572ecccc9250e5dc717131caf1d2ba40 Mon Sep 17 00:00:00 2001 From: cpsievert Date: Mon, 14 Sep 2015 10:20:58 -0500 Subject: [PATCH 4/4] Bump version; update NEWS; fix filename test --- DESCRIPTION | 2 +- NEWS | 6 ++++++ tests/testthat/test-plotly-filename.R | 3 +-- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 58f4539938..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", 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/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") })