Skip to content

Commit 3860461

Browse files
committed
Merge pull request #5 from ropensci/ggplotly
theme elements and simplified ggplotly API
2 parents b4b18cd + 5510154 commit 3860461

File tree

10 files changed

+219
-120
lines changed

10 files changed

+219
-120
lines changed

DESCRIPTION

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
Package: plotly
22
Type: Package
33
Title: Interactive, publication-quality graphs online.
4-
Version: 0.3.7
4+
Version: 0.3.8
55
Authors@R: c(person("Chris", "Parmer", role = c("aut", "cre"),
66
email = "[email protected]"),
77
person("Scott", "Chamberlain", role = "aut",
88
email = "[email protected]"),
99
person("Karthik", "Ram", role = "aut",
10-
email = "[email protected]"))
10+
email = "[email protected]"),
11+
person("Toby", "Hocking", role="aut",
12+
1113
License: MIT + file LICENSE
1214
Description: An interface to plotly's online graphing tools with desktop R
1315
environments. Send data to a plotly account and view the graphs in a web

NAMESPACE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
export(getLegendList)
22
export(gg2list)
3-
export(layer2list)
3+
export(layer2traces)
44
export(plotly)
55
export(signup)
66
export(toRGB)

NEWS

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
0.3.8 -- 21 March 2014.
2+
3+
ggplotly takes the last_plot() by default.
4+
5+
Support for ggplotly layout elements title, tickcolor, gridcolor,
6+
showlegend, plot_bgcolor, paper_bgcolor, tickangle, axis titles, plot
7+
border colors.
8+
19
0.3.7 -- 14 March 2014.
210

311
For ggplotly:

R/ggplotly.R

Lines changed: 86 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,8 @@ gg2list <- function(p){
8686
## Always use identity size scale so that plot.ly gets the real
8787
## units for the size variables.
8888
p <- p+scale_size_identity()
89-
plist <- list()
89+
layout <- list()
90+
trace.list <- list()
9091
## Before building the ggplot, we would like to add aes(name) to
9192
## figure out what the object group is later.
9293
for(layer.i in seq_along(p$layers)){
@@ -106,53 +107,24 @@ gg2list <- function(p){
106107
}
107108
p$layers[[layer.i]]$mapping$name <- group.var
108109
}
109-
plistextra <- ggplot2::ggplot_build(p)
110-
## NOTE: data from ggplot_build have scales already applied. This
111-
## may be a bad thing for log scales.
112-
for(sc in plistextra$plot$scales$scales){
113-
if(sc$scale_name == "manual"){
114-
plist$scales[[sc$aesthetics]] <- sc$palette(0)
115-
}else if(sc$scale_name == "brewer"){
116-
plist$scales[[sc$aesthetics]] <- sc$palette(length(sc$range$range))
117-
}else if(sc$scale_name == "hue"){
118-
plist$scales[[sc$aesthetics]] <- sc$palette(length(sc$range$range))
119-
}else if(sc$scale_name == "linetype_d"){
120-
plist$scales[[sc$aesthetics]] <- sc$palette(length(sc$range$range))
121-
}else if(sc$scale_name == "alpha_c"){
122-
plist$scales[[sc$aesthetics]] <- sc$palette(sc$range$range)
123-
}else if(sc$scale_name == "size_c"){
124-
plist$scales[[sc$aesthetics]] <- sc$palette(sc$range$range)
125-
}else if(sc$scale_name == "gradient"){
126-
plist$scales[[sc$aesthetics]] <- ggplot2:::scale_map(sc, ggplot2:::scale_breaks(sc))
127-
}
128-
}
129-
for(i in seq_along(plistextra$plot$layers)){
110+
## Extract data from built ggplots
111+
built <- ggplot2::ggplot_build(p)
112+
ranges <- built$panel$ranges[[1]]
113+
for(i in seq_along(built$plot$layers)){
130114
## This is the layer from the original ggplot object.
131-
L <- plistextra$plot$layers[[i]]
115+
L <- built$plot$layers[[i]]
132116

133117
## for each layer, there is a correpsonding data.frame which
134118
## evaluates the aesthetic mapping.
135-
df <- plistextra$data[[i]]
119+
df <- built$data[[i]]
136120

137121
## This extracts essential info for this geom/layer.
138-
g <- layer2list(L, df, plistextra$panel$ranges[[1]])
139-
140-
## Idea: use the ggplot2:::coord_transform(coords, data, scales)
141-
## function to handle cases like coord_flip. scales is a list of
142-
## 12, coords is a list(limits=list(x=NULL,y=NULL)) with class
143-
## e.g. c("cartesian","coord"). The result is a transformed data
144-
## frame where all the data values are between 0 and 1.
145-
146-
## TODO: coord_transform maybe won't work for
147-
## geom_dotplot|rect|segment and polar/log transformations, which
148-
## could result in something nonlinear. For the time being it is
149-
## best to just ignore this, but you can look at the source of
150-
## e.g. geom-rect.r in ggplot2 to see how they deal with this by
151-
## doing a piecewise linear interpolation of the shape.
122+
traces <- layer2traces(L, df, ranges)
152123

153-
g$data <- ggplot2:::coord_transform(plistextra$plot$coord, g$data,
154-
plistextra$panel$ranges[[1]])
155-
plist$geoms[[i]] <- g
124+
## Do we really need to coord_transform?
125+
##g$data <- ggplot2:::coord_transform(built$plot$coord, g$data,
126+
## built$panel$ranges[[1]])
127+
trace.list <- c(trace.list, traces)
156128
}
157129
# Export axis specification as a combination of breaks and
158130
# labels, on the relevant axis scale (i.e. so that it can
@@ -164,64 +136,90 @@ gg2list <- function(p){
164136
## Flip labels if coords are flipped - transform does not take care
165137
## of this. Do this BEFORE checking if it is blank or not, so that
166138
## individual axes can be hidden appropriately, e.g. #1.
167-
ranges <- plistextra$panel$ranges[[1]]
168-
if("flip"%in%attr(plistextra$plot$coordinates, "class")){
169-
temp <- plistextra$plot$labels$x
170-
plistextra$plot$labels$x <- plistextra$plot$labels$y
171-
plistextra$plot$labels$y <- temp
139+
## ranges <- built$panel$ranges[[1]]
140+
## if("flip"%in%attr(built$plot$coordinates, "class")){
141+
## temp <- built$plot$labels$x
142+
## built$plot$labels$x <- built$plot$labels$y
143+
## built$plot$labels$y <- temp
144+
## }
145+
e <- function(el.name){
146+
ggplot2::calc_element(el.name, p$theme)
172147
}
173148
is.blank <- function(el.name){
174-
x <- ggplot2::calc_element(el.name, p$theme)
175-
"element_blank"%in%attr(x,"class")
149+
"element_blank"%in%attr(e(el.name),"class")
176150
}
177-
plist$axis <- list()
178151
for(xy in c("x","y")){
152+
ax.list <- list()
179153
s <- function(tmp)sprintf(tmp, xy)
180-
plist$axis[[xy]] <- ranges[[s("%s.major")]]
181-
plist$axis[[s("%slab")]] <- if(is.blank(s("axis.text.%s"))){
182-
NULL
183-
}else{
184-
ranges[[s("%s.labels")]]
154+
ax.list$tickcolor <- toRGB(theme.pars$axis.ticks$colour)
155+
ax.list$gridcolor <- toRGB(theme.pars$panel.grid.major$colour)
156+
## These numeric length variables are not easily convertible.
157+
##ax.list$gridwidth <- as.numeric(theme.pars$panel.grid.major$size)
158+
##ax.list$ticklen <- as.numeric(theme.pars$axis.ticks.length)
159+
ax.list$tickwidth <- theme.pars$axis.ticks$size
160+
tick.text.name <- s("axis.text.%s")
161+
ax.list$showticklabels <- ifelse(is.blank(tick.text.name), FALSE, TRUE)
162+
tick.text <- e(tick.text.name)
163+
ax.list$tickangle <- if(is.numeric(tick.text$angle)){
164+
-tick.text$angle
165+
}
166+
theme2font <- function(text){
167+
if(!is.null(text)){
168+
with(text, {
169+
list(family=family,
170+
size=size,
171+
color=toRGB(colour))
172+
})
173+
}
185174
}
186-
plist$axis[[s("%srange")]] <- ranges[[s("%s.range")]]
187-
plist$axis[[s("%sname")]] <- if(is.blank(s("axis.title.%s"))){
188-
""
175+
## Translate axes labels.
176+
scale.i <- which(p$scales$find(xy))
177+
ax.list$title <- if(length(scale.i)){
178+
sc <- p$scales$scales[[scale.i]]
179+
if(!is.null(sc$name)){
180+
sc$name
181+
}else{
182+
p$labels[[xy]]
183+
}
189184
}else{
190-
plistextra$plot$labels[[xy]]
185+
p$labels[[xy]]
191186
}
192-
plist$axis[[s("%sline")]] <- !is.blank(s("axis.line.%s"))
193-
plist$axis[[s("%sticks")]] <- !is.blank(s("axis.ticks.%s"))
187+
ax.list$tickfont <- theme2font(tick.text)
188+
title.text <- e(s("axis.title.%s"))
189+
ax.list$titlefont <- theme2font(title.text)
190+
ax.list$type <- "linear" ## TODO: log scales?
191+
## Lines drawn around the plot border:
192+
ax.list$showline <- ifelse(is.blank("panel.border"), FALSE, TRUE)
193+
ax.list$linecolor <- toRGB(theme.pars$panel.border$colour)
194+
ax.list$linewidth <- theme.pars$panel.border$size
195+
## Some other params that we used in animint but we don't yet
196+
## translate to plotly:
197+
!is.blank(s("axis.line.%s"))
198+
!is.blank(s("axis.ticks.%s"))
199+
layout[[s("%saxis")]] <- ax.list
194200
}
195201

196-
plist$legend <- getLegendList(plistextra)
197-
if(length(plist$legend)>0){
198-
plist$legend <- plist$legend[which(sapply(plist$legend, function(i) length(i)>0))]
199-
} # only pass out legends that have guide = "legend" or guide="colorbar"
200-
201-
# Remove legend if theme has no legend position
202-
if(theme.pars$legend.position=="none") plist$legend <- NULL
203-
204-
if("element_blank"%in%attr(theme.pars$plot.title, "class")){
205-
plist$title <- ""
206-
} else {
207-
plist$title <- plistextra$plot$labels$title
208-
}
202+
## Remove legend if theme has no legend position
203+
if(theme.pars$legend.position=="none") layout$showlegend <- FALSE
209204

210-
pargs <- list()
211-
for(g in plist$geoms){
212-
pargs <- c(pargs, g$traces)
213-
}
214-
pargs$kwargs <- list()
215-
pargs
205+
## Main plot title.
206+
layout$title <- built$plot$labels$title
207+
208+
## Background color.
209+
layout$plot_bgcolor <- toRGB(theme.pars$panel.background$fill)
210+
layout$paper_bgcolor <- toRGB(theme.pars$plot.background$fill)
211+
212+
trace.list$kwargs <- list(layout=layout)
213+
trace.list
216214
}
217215

218-
#' Convert a layer to a list. Called from gg2list()
216+
#' Convert a layer to a list of traces. Called from gg2list()
219217
#' @param l one layer of the ggplot object
220218
#' @param d one layer of calculated data from ggplot2::ggplot_build(p)
221219
#' @param ranges axes ranges
222220
#' @return list representing a layer, with corresponding aesthetics, ranges, and groups.
223221
#' @export
224-
layer2list <- function(l, d, ranges){
222+
layer2traces <- function(l, d, ranges){
225223
g <- list(geom=l$geom$objname,
226224
data=d)
227225
g$aes <- sapply(l$mapping, function(k) as.character(as.expression(k))) # needed for when group, etc. is an expression
@@ -233,7 +231,7 @@ layer2list <- function(l, d, ranges){
233231
## non-ggplot2 params like name are useful for plot.ly and ggplot2
234232
## places them into stat_params.
235233
for(p.name in names(g$params)){
236-
names(g$params[[p.name]]) <- NULL
234+
names(g$params[[p.name]]) <- NULL #why?
237235
}
238236

239237
## Convert complex ggplot2 geoms so that they are treated as special
@@ -384,19 +382,10 @@ layer2list <- function(l, d, ranges){
384382
g$data <- poly.na.df
385383
}
386384

387-
## Check g$data for color/fill - convert to hexadecimal so JS can
388-
## parse correctly.
389-
for(color.var in c("colour", "color", "fill")){
390-
if(color.var %in% names(g$data)){
391-
g$data[,color.var] <- toRGB(g$data[,color.var])
392-
}
393-
}
394-
395385
if(any(g$data$size == 0, na.rm=TRUE)){
396386
warning(sprintf("geom_%s with size=0 will be invisible",g$geom))
397387
}
398388

399-
g$traces <- list()
400389
group.vars <- c("group",
401390
"color", "colour",
402391
"fill") #TODO.
@@ -417,6 +406,7 @@ layer2list <- function(l, d, ranges){
417406
}else{
418407
list(g$data)
419408
}
409+
## Construct a list of traces.
420410
for(group.i in seq_along(group.list)){
421411
group.data <- group.list[[group.i]]
422412
tr <- group2trace(group.data, g$params, g$geom)
@@ -426,7 +416,7 @@ layer2list <- function(l, d, ranges){
426416
tr$name <- as.character(tr$name[1])
427417
g$traces[[group.i]] <- tr
428418
}
429-
g
419+
g$traces
430420
}
431421

432422
getMarker <- function(df, params, aesConverter, defaults, only=NULL){
@@ -442,6 +432,10 @@ getMarker <- function(df, params, aesConverter, defaults, only=NULL){
442432
}
443433
take.from <- as.list(take.from)
444434
to.write <- take.from[[name]]
435+
if(plotly.name == "color"){ # convert from R to RGB codes.
436+
## TODO: fill?
437+
to.write <- toRGB(to.write)
438+
}
445439
## if(is.null(to.write)){
446440
## print(take.from)
447441
## stop("undefined marker ", name)

R/plotly.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ plotly <- function(username=NULL, key=NULL){
103103
args <- list(...)
104104
return(pub$makecall(args = args, kwargs = kwargs, origin = "plot"))
105105
}
106-
pub$ggplotly <- function(gg){
106+
pub$ggplotly <- function(gg=last_plot()){
107107
if(!is.ggplot(gg)){
108108
stop("gg must be a ggplot")
109109
}

inst/tests/test-ggplot-labels.R

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
context("labels")
2+
3+
test_that("ggtitle is translated correctly", {
4+
ggiris <- ggplot(iris)+
5+
geom_point(aes(Petal.Width, Sepal.Width))+
6+
ggtitle("My amazing plot!")
7+
info <- gg2list(ggiris)
8+
expect_identical(info$kwargs$layout$title, "My amazing plot!")
9+
})
10+
11+
test_that("ylab is translated correctly", {
12+
ggiris <- ggplot(iris)+
13+
geom_point(aes(Petal.Width, Sepal.Width))+
14+
ylab("sepal width")
15+
info <- gg2list(ggiris)
16+
expect_identical(info$kwargs$layout$xaxis$title, "Petal.Width")
17+
expect_identical(info$kwargs$layout$yaxis$title, "sepal width")
18+
})
19+
20+
test_that("scale_x_continuous(name) is translated correctly", {
21+
ggiris <- ggplot(iris)+
22+
geom_point(aes(Petal.Width, Sepal.Width))+
23+
scale_x_continuous("petal width")
24+
info <- gg2list(ggiris)
25+
expect_identical(info$kwargs$layout$xaxis$title, "petal width")
26+
expect_identical(info$kwargs$layout$yaxis$title, "Sepal.Width")
27+
})
28+
29+
test_that("angled ticks are translated correctly", {
30+
ggiris <- ggplot(iris)+
31+
geom_point(aes(Petal.Width, Sepal.Width))+
32+
theme(axis.text.x=element_text(angle=45))
33+
info <- gg2list(ggiris)
34+
expect_identical(info$kwargs$layout$xaxis$tickangle, -45)
35+
})
36+
37+
##TODO: test label colors.

inst/tests/test-ggplot-legend.R

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
context("legends")
2+
3+
test_that("legend can be hidden", {
4+
ggiris <- ggplot(iris)+
5+
geom_point(aes(Petal.Width, Sepal.Width, color=Species))+
6+
theme(legend.position="none")
7+
info <- gg2list(ggiris)
8+
expect_identical(info$kwargs$layout$showlegend, FALSE)
9+
})

0 commit comments

Comments
 (0)