Skip to content

Commit 1cf9b72

Browse files
author
Toby Dylan Hocking
committed
Merge d142570 into f4bc6b9
2 parents f4bc6b9 + d142570 commit 1cf9b72

File tree

7 files changed

+431
-94
lines changed

7 files changed

+431
-94
lines changed

Diff for: DESCRIPTION

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Package: plotly
22
Type: Package
33
Title: Interactive, publication-quality graphs online.
4-
Version: 0.5.30
4+
Version: 0.5.31
55
Authors@R: c(person("Chris", "Parmer", role = c("aut", "cre"),
66
email = "[email protected]"),
77
person("Scott", "Chamberlain", role = "aut",

Diff for: NEWS

+4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
0.5.31 -- 5 May 2015
2+
3+
Add test-cookbook-lines.R and fix bugs that showed up in those tests.
4+
15
0.5.30 -- 4 May 2015
26

37
Let gg2list() return a figure object.

Diff for: R/ggplotly.R

+100-79
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,12 @@ markLegends <-
6262
errorbarh=c("colour", "linetype"),
6363
area=c("colour", "fill"),
6464
step=c("linetype", "size", "colour"),
65-
boxplot=c("x"),
6665
text=c("colour"))
6766

6867
markUnique <- as.character(unique(unlist(markLegends)))
6968

69+
markSplit <- c(markLegends,list(boxplot=c("x")))
70+
7071
#' Convert a ggplot to a list.
7172
#' @import ggplot2
7273
#' @param p ggplot2 plot.
@@ -97,29 +98,91 @@ gg2list <- function(p) {
9798
# worry about combining global and layer-specific aes/data later.
9899
for(layer.i in seq_along(p$layers)) {
99100
layer.aes <- p$layers[[layer.i]]$mapping
100-
to.copy <- names(p$mapping)[!names(p$mapping) %in% names(layer.aes)]
101-
layer.aes[to.copy] <- p$mapping[to.copy]
102-
mark.names <- markUnique[markUnique %in% names(layer.aes)]
101+
if(p$layers[[layer.i]]$inherit.aes){
102+
to.copy <- names(p$mapping)[!names(p$mapping) %in% names(layer.aes)]
103+
layer.aes[to.copy] <- p$mapping[to.copy]
104+
}
105+
mark.names <- names(layer.aes) # make aes.name for all aes.
103106
name.names <- sprintf("%s.name", mark.names)
104107
layer.aes[name.names] <- layer.aes[mark.names]
105108
p$layers[[layer.i]]$mapping <- layer.aes
106109
if(!is.data.frame(p$layers[[layer.i]]$data)){
107110
p$layers[[layer.i]]$data <- p$data
108111
}
109112
}
113+
114+
# Test fill and color to see if they encode a quantitative
115+
# variable. This may be useful for several reasons: (1) it is
116+
# sometimes possible to plot several different colors in the same
117+
# trace (e.g. points), and that is faster for large numbers of
118+
# data points and colors; (2) factors on x or y axes should be
119+
# sent to plotly as characters, not as numeric data (which is
120+
# what ggplot_build gives us).
121+
misc <- list()
122+
for(a in c("fill", "colour", "x", "y", "size")){
123+
for(data.type in c("continuous", "date", "datetime", "discrete")){
124+
fun.name <- sprintf("scale_%s_%s", a, data.type)
125+
misc.name <- paste0("is.", data.type)
126+
misc[[misc.name]][[a]] <- tryCatch({
127+
fun <- get(fun.name)
128+
suppressMessages({
129+
with.scale <- original.p + fun()
130+
})
131+
ggplot_build(with.scale)
132+
TRUE
133+
}, error=function(e){
134+
FALSE
135+
})
136+
}
137+
}
138+
139+
## scales are needed for legend ordering.
140+
misc$breaks <- list()
141+
for(sc in p$scales$scales){
142+
a.vec <- sc$aesthetics
143+
default.breaks <- inherits(sc$breaks, "waiver")
144+
if (length(a.vec) == 1 && (!default.breaks) ) {
145+
## TODO: generalize for x/y scales too.
146+
br <- sc$breaks
147+
ranks <- seq_along(br)
148+
names(ranks) <- br
149+
misc$breaks[[a.vec]] <- ranks
150+
}
151+
## store if this is a reverse scale so we can undo that later.
152+
if(is.character(sc$trans$name)){
153+
misc$trans[sc$aesthetics] <- sc$trans$name
154+
}
155+
}
156+
reverse.aes <- names(misc$trans)[misc$trans=="reverse"]
110157

111158
# Extract data from built ggplots
112159
built <- ggplot_build2(p)
113-
# Get global x-range now because we need some of its info in layer2traces
114-
ggranges <- built$panel$ranges
115-
# Extract x.range
116-
xrange <- sapply(ggranges, `[[`, "x.range", simplify=FALSE, USE.NAMES=FALSE)
117-
ggxmin <- min(sapply(xrange, min))
118-
ggxmax <- max(sapply(xrange, max))
119-
# Extract y.range
120-
yrange <- sapply(ggranges, `[[`, "y.range", simplify=FALSE, USE.NAMES=FALSE)
121-
ggymin <- min(sapply(yrange, min))
122-
ggymax <- max(sapply(yrange, max))
160+
# Get global ranges now because we need some of its info in layer2traces
161+
ranges.list <- list()
162+
for(xy in c("x", "y")){
163+
use.ranges <-
164+
misc$is.continuous[[xy]] ||
165+
misc$is.date[[xy]] ||
166+
misc$is.datetime[[xy]]
167+
range.values <- if(use.ranges){
168+
range.name <- paste0(xy, ".range")
169+
sapply(built$panel$ranges, "[[", range.name)
170+
}else{
171+
## for categorical variables on the axes, panel$ranges info is
172+
## meaningless.
173+
name.name <- paste0(xy, ".name")
174+
sapply(built$data, function(df){
175+
if(name.name %in% names(df)){
176+
## usually for discrete data there is a .name column.
177+
paste(df[[name.name]])
178+
}else{
179+
## for heatmaps there may not be.
180+
df[[xy]]
181+
}
182+
})
183+
}
184+
ranges.list[[xy]] <- range(range.values)
185+
}
123186

124187
# Get global size range because we need some of its info in layer2traces
125188
if ("size.name" %in% name.names) {
@@ -135,51 +198,7 @@ gg2list <- function(p) {
135198

136199
# for each layer, there is a correpsonding data.frame which
137200
# evaluates the aesthetic mapping.
138-
df <- built$data[[i]]
139-
140-
# Test fill and color to see if they encode a quantitative
141-
# variable. This may be useful for several reasons: (1) it is
142-
# sometimes possible to plot several different colors in the same
143-
# trace (e.g. points), and that is faster for large numbers of
144-
# data points and colors; (2) factors on x or y axes should be
145-
# sent to plotly as characters, not as numeric data (which is
146-
# what ggplot_build gives us).
147-
misc <- list()
148-
for(a in c("fill", "colour", "x", "y", "size")){
149-
for(data.type in c("continuous", "date", "datetime", "discrete")){
150-
fun.name <- sprintf("scale_%s_%s", a, data.type)
151-
misc.name <- paste0("is.", data.type)
152-
misc[[misc.name]][[a]] <- tryCatch({
153-
fun <- get(fun.name)
154-
suppressMessages({
155-
with.scale <- original.p + fun()
156-
})
157-
ggplot_build(with.scale)
158-
TRUE
159-
}, error=function(e){
160-
FALSE
161-
})
162-
}
163-
}
164-
165-
# scales are needed for legend ordering.
166-
misc$breaks <- list()
167-
for(sc in p$scales$scales){
168-
a.vec <- sc$aesthetics
169-
default.breaks <- inherits(sc$breaks, "waiver")
170-
if (length(a.vec) == 1 && (!default.breaks) ) {
171-
# TODO: generalize for x/y scales too.
172-
br <- sc$breaks
173-
ranks <- seq_along(br)
174-
names(ranks) <- br
175-
misc$breaks[[a.vec]] <- ranks
176-
}
177-
## store if this is a reverse scale so we can undo that later.
178-
if(is.character(sc$trans$name)){
179-
misc$trans[sc$aesthetics] <- sc$trans$name
180-
}
181-
}
182-
reverse.aes <- names(misc$trans)[misc$trans=="reverse"]
201+
df <- built$data[[i]]
183202

184203
# get gglayout now because we need some of its info in layer2traces
185204
gglayout <- built$panel$layout
@@ -203,21 +222,24 @@ gg2list <- function(p) {
203222
for (a in replace.aes) {
204223
prestats[[a]] <- -1 * prestats[[a]]
205224
}
206-
misc$prestats.data <-
225+
L$prestats.data <-
207226
merge(prestats,
208227
gglayout[, c("PANEL", "plotly.row", "COL")])
209-
210-
# Add global x-range info
211-
misc$prestats.data$globxmin <- ggxmin
212-
misc$prestats.data$globxmax <- ggxmax
213-
# Add global y-range info
214-
misc$prestats.data$globymin <- ggymin
215-
misc$prestats.data$globymax <- ggymax
228+
229+
# Add global range info.
230+
for(xy in names(ranges.list)){
231+
range.vec <- ranges.list[[xy]]
232+
names(range.vec) <- c("min", "max")
233+
for(range.name in names(range.vec)){
234+
glob.name <- paste0("glob", xy, range.name)
235+
L$prestats.data[[glob.name]] <- range.vec[[range.name]]
236+
}
237+
}
216238

217239
# Add global size info if relevant
218240
if ("size.name" %in% name.names) {
219-
misc$prestats.data$globsizemin <- ggsizemin
220-
misc$prestats.data$globsizemax <- ggsizemax
241+
L$prestats.data$globsizemin <- ggsizemin
242+
L$prestats.data$globsizemax <- ggsizemax
221243
}
222244

223245
# This extracts essential info for this geom/layer.
@@ -415,7 +437,7 @@ gg2list <- function(p) {
415437
sc$limits
416438
}else{
417439
if(misc$is.continuous[[xy]]){
418-
ggranges[[1]][[s("%s.range")]] #TODO: facets!
440+
built$panel$ranges[[1]][[s("%s.range")]] #TODO: facets!
419441
}else{ # for a discrete scale, range should be NULL.
420442
NULL
421443
}
@@ -594,9 +616,6 @@ gg2list <- function(p) {
594616
layout$annotations <- annotations
595617
}
596618

597-
# Remove legend if theme has no legend position
598-
layout$showlegend <- !(theme.pars$legend.position=="none")
599-
600619
# Main plot title.
601620
layout$title <- built$plot$labels$title
602621

@@ -612,10 +631,6 @@ gg2list <- function(p) {
612631
layout$legend <- list(bordercolor="transparent",
613632
x=1.05, y=1/2,
614633
xanchor="center", yanchor="top")
615-
# Workaround for removing unnecessary legends.
616-
# [markUnique != "x"] is for boxplot's particular case.
617-
if (any(names(layer.aes) %in% markUnique[markUnique != "x"]) == FALSE)
618-
layout$showlegend <- FALSE
619634

620635
## Legend hiding when guides(fill="none").
621636
legends.present <- unique(unlist(layer.legends))
@@ -628,12 +643,17 @@ gg2list <- function(p) {
628643
is.hidden <- function(x){
629644
is.false(x) || is.none(x)
630645
}
646+
layout$showlegend <- if(length(legends.present) == 0) FALSE else TRUE
631647
for(a in legends.present){
632648
if(is.hidden(p$guides[[a]])){
633649
layout$showlegend <- FALSE
634650
}
635651
}
636-
652+
# Legend hiding from theme.
653+
if(theme.pars$legend.position=="none"){
654+
layout$showlegend <- FALSE
655+
}
656+
637657
# Only show a legend title if there is at least 1 trace with
638658
# showlegend=TRUE.
639659
trace.showlegend <- sapply(trace.list, "[[", "showlegend")
@@ -817,6 +837,7 @@ gg2list <- function(p) {
817837
fill_set <- unlist(lapply(merged.traces, entries, "fillcolor"))
818838
line_set <- unlist(lapply(merged.traces, entries, c("line", "color")))
819839
mark_set <- unlist(lapply(merged.traces, entries, c("marker", "color")))
840+
mode_set <- lapply(merged.traces, "[[", "mode")
820841
legend_intersect <- function(x, y) {
821842
i <- intersect(x, y)
822843
# restrict intersection to valid legend entries
@@ -825,7 +846,7 @@ gg2list <- function(p) {
825846
# if there is a mark & line legend, get rid of line
826847
t1 <- line_set %in% legend_intersect(mark_set, line_set)
827848
# that is, unless the mode is 'lines+markers'...
828-
t1 <- t1 & !(unlist(lapply(merged.traces, "[[", "mode")) %in% "lines+markers")
849+
t1 <- t1 & !(mode_set %in% "lines+markers")
829850
# if there is a mark & fill legend, get rid of fill
830851
t2 <- fill_set %in% legend_intersect(mark_set, fill_set)
831852
# if there is a line & fill legend, get rid of fill

Diff for: R/trace_generation.R

+19-14
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#' Convert a layer to a list of traces. Called from gg2list()
22
#' @param l one layer of the ggplot object
33
#' @param d one layer of calculated data from ggplot2::ggplot_build(p)
4-
#' @param misc named list.
4+
#' @param misc named list of plot info, independent of layer.
55
#' @return list representing a layer, with corresponding aesthetics, ranges, and groups.
66
#' @export
77
layer2traces <- function(l, d, misc) {
@@ -12,7 +12,7 @@ layer2traces <- function(l, d, misc) {
1212
}
1313
g <- list(geom=l$geom$objname,
1414
data=not.na(d),
15-
prestats.data=not.na(misc$prestats.data))
15+
prestats.data=not.na(l$prestats.data))
1616

1717
# needed for when group, etc. is an expression.
1818
g$aes <- sapply(l$mapping, function(k) as.character(as.expression(k)))
@@ -46,21 +46,22 @@ layer2traces <- function(l, d, misc) {
4646
g$geom <- "bar"
4747
bargap <- 0
4848
}
49-
49+
5050
# For non-numeric data on the axes, we should take the values from
5151
# the original data.
5252
for (axis.name in c("x", "y")) {
5353
if (!misc$is.continuous[[axis.name]]) {
5454
aes.names <- paste0(axis.name, c("", "end", "min", "max"))
5555
aes.used <- aes.names[aes.names %in% names(g$aes)]
5656
for(a in aes.used) {
57+
a.name <- paste0(a, ".name")
5758
col.name <- g$aes[aes.used]
5859
dtemp <- l$data[[col.name]]
5960
if (is.null(dtemp)) {
60-
if (!inherits(g$data[[paste0(a, ".name")]], "NULL")) {
61+
if (!is.null(g$data[[a.name]])) {
6162
# Handle the case where as.Date() is passed in aes argument.
62-
if (class(g$data[[a]]) != class(g$data[[paste0(a, ".name")]])) {
63-
g$data[[a]] <- g$data[[paste0(a, ".name")]]
63+
if (class(g$data[[a]]) != class(g$data[[a.name]])) {
64+
g$data[[a]] <- g$data[[a.name]]
6465
data.vec <- g$data[[a]]
6566
}
6667
}
@@ -86,14 +87,18 @@ layer2traces <- function(l, d, misc) {
8687
} else if (inherits(data.vec, "factor")) {
8788
# Re-order data so that Plotly gets it right from ggplot2.
8889
g$data <- g$data[order(g$data[[a]]), ]
89-
data.vec <- data.vec[match(g$data[[a]], as.numeric(data.vec))]
90+
vec.i <- match(g$data[[a]], as.numeric(data.vec))
91+
if(anyNA(vec.i)){
92+
vec.i <- match(g$data[[a.name]], data.vec)
93+
}
94+
data.vec <- data.vec[vec.i]
9095
g$prestats.data <- g$prestats.data[order(g$prestats.data[[a]]), ]
91-
pdata.vec <- pdata.vec[match(g$prestats.data[[a]],
92-
as.numeric(pdata.vec))]
96+
pvec.i <- match(g$prestats.data[[a]], as.numeric(pdata.vec))
97+
pdata.vec <- pdata.vec[pvec.i]
9398
if (length(pdata.vec) == length(data.vec))
9499
pdata.vec <- data.vec
95100
if (!is.factor(pdata.vec))
96-
pdata.vec <- g$prestats.data[[paste0(a, ".name")]]
101+
pdata.vec <- g$prestats.data[[a.name]]
97102
}
98103
g$data[[a]] <- data.vec
99104
g$prestats.data[[a]] <- pdata.vec
@@ -131,7 +136,7 @@ layer2traces <- function(l, d, misc) {
131136

132137
# symbol=circle,square,diamond,cross,x,
133138
# triangle-up,triangle-down,triangle-left,triangle-right
134-
139+
135140
# First convert to a "basic" geom, e.g. segments become lines.
136141
convert <- toBasic[[g$geom]]
137142
basic <- if(is.null(convert)){
@@ -141,8 +146,8 @@ layer2traces <- function(l, d, misc) {
141146
}
142147
# Then split on visual characteristics that will get different
143148
# legend entries.
144-
data.list <- if (basic$geom %in% names(markLegends)) {
145-
mark.names <- markLegends[[basic$geom]]
149+
data.list <- if (basic$geom %in% names(markSplit)) {
150+
mark.names <- markSplit[[basic$geom]]
146151
# However, continuously colored points are an exception: they do
147152
# not need a legend entry, and they can be efficiently rendered
148153
# using just 1 trace.
@@ -173,7 +178,7 @@ layer2traces <- function(l, d, misc) {
173178
}
174179
# Split hline and vline when multiple panels or intercepts:
175180
# Need multiple traces accordingly.
176-
if (g$geom == "hline" || g$geom == "vline") {
181+
if (g$geom %in% c("hline", "vline")) {
177182
intercept <- paste0(ifelse(g$geom == "hline", "y", "x"), "intercept")
178183
vec.list <- basic$data[c("PANEL", intercept)]
179184
df.list <- split(basic$data, vec.list, drop=TRUE)

0 commit comments

Comments
 (0)