-
Notifications
You must be signed in to change notification settings - Fork 633
Toby cookbook lines #196
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Toby cookbook lines #196
Changes from 21 commits
e47ca93
3ef9be5
b60d935
1bea709
8e68d8a
305ad6c
7308760
6c72262
84ab366
6318787
4a2dc57
a4d0059
4a7f713
56ede93
3105143
bd8c3f6
5fa2434
96f0d40
f0eb05e
8f937e0
9ab52e1
f90928b
d142570
ab4ca1c
498455d
4cfedf9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,7 @@ | ||
Package: plotly | ||
Type: Package | ||
Title: Interactive, publication-quality graphs online. | ||
Version: 0.5.29 | ||
Version: 0.5.30 | ||
Authors@R: c(person("Chris", "Parmer", role = c("aut", "cre"), | ||
email = "[email protected]"), | ||
person("Scott", "Chamberlain", role = "aut", | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -62,11 +62,12 @@ markLegends <- | |
errorbarh=c("colour", "linetype"), | ||
area=c("colour", "fill"), | ||
step=c("linetype", "size", "colour"), | ||
boxplot=c("x"), | ||
text=c("colour")) | ||
|
||
markUnique <- as.character(unique(unlist(markLegends))) | ||
|
||
markSplit <- c(markLegends,list(boxplot=c("x"))) | ||
|
||
#' Convert a ggplot to a list. | ||
#' @import ggplot2 | ||
#' @param p ggplot2 plot. | ||
|
@@ -97,29 +98,91 @@ gg2list <- function(p){ | |
# worry about combining global and layer-specific aes/data later. | ||
for(layer.i in seq_along(p$layers)) { | ||
layer.aes <- p$layers[[layer.i]]$mapping | ||
to.copy <- names(p$mapping)[!names(p$mapping) %in% names(layer.aes)] | ||
layer.aes[to.copy] <- p$mapping[to.copy] | ||
mark.names <- markUnique[markUnique %in% names(layer.aes)] | ||
if(p$layers[[layer.i]]$inherit.aes){ | ||
to.copy <- names(p$mapping)[!names(p$mapping) %in% names(layer.aes)] | ||
layer.aes[to.copy] <- p$mapping[to.copy] | ||
} | ||
mark.names <- names(layer.aes) # make aes.name for all aes. | ||
name.names <- sprintf("%s.name", mark.names) | ||
layer.aes[name.names] <- layer.aes[mark.names] | ||
p$layers[[layer.i]]$mapping <- layer.aes | ||
if(!is.data.frame(p$layers[[layer.i]]$data)){ | ||
p$layers[[layer.i]]$data <- p$data | ||
} | ||
} | ||
|
||
# Test fill and color to see if they encode a quantitative | ||
# variable. This may be useful for several reasons: (1) it is | ||
# sometimes possible to plot several different colors in the same | ||
# trace (e.g. points), and that is faster for large numbers of | ||
# data points and colors; (2) factors on x or y axes should be | ||
# sent to plotly as characters, not as numeric data (which is | ||
# what ggplot_build gives us). | ||
misc <- list() | ||
for(a in c("fill", "colour", "x", "y", "size")){ | ||
for(data.type in c("continuous", "date", "datetime", "discrete")){ | ||
fun.name <- sprintf("scale_%s_%s", a, data.type) | ||
misc.name <- paste0("is.", data.type) | ||
misc[[misc.name]][[a]] <- tryCatch({ | ||
fun <- get(fun.name) | ||
suppressMessages({ | ||
with.scale <- original.p + fun() | ||
}) | ||
ggplot_build(with.scale) | ||
TRUE | ||
}, error=function(e){ | ||
FALSE | ||
}) | ||
} | ||
} | ||
|
||
## scales are needed for legend ordering. | ||
misc$breaks <- list() | ||
for(sc in p$scales$scales){ | ||
a.vec <- sc$aesthetics | ||
default.breaks <- inherits(sc$breaks, "waiver") | ||
if (length(a.vec) == 1 && (!default.breaks) ) { | ||
## TODO: generalize for x/y scales too. | ||
br <- sc$breaks | ||
ranks <- seq_along(br) | ||
names(ranks) <- br | ||
misc$breaks[[a.vec]] <- ranks | ||
} | ||
## store if this is a reverse scale so we can undo that later. | ||
if(is.character(sc$trans$name)){ | ||
misc$trans[sc$aesthetics] <- sc$trans$name | ||
} | ||
} | ||
reverse.aes <- names(misc$trans)[misc$trans=="reverse"] | ||
|
||
# Extract data from built ggplots | ||
built <- ggplot_build2(p) | ||
# Get global x-range now because we need some of its info in layer2traces | ||
ggranges <- built$panel$ranges | ||
# Extract x.range | ||
xrange <- sapply(ggranges, `[[`, "x.range", simplify=FALSE, USE.NAMES=FALSE) | ||
ggxmin <- min(sapply(xrange, min)) | ||
ggxmax <- max(sapply(xrange, max)) | ||
# Extract y.range | ||
yrange <- sapply(ggranges, `[[`, "y.range", simplify=FALSE, USE.NAMES=FALSE) | ||
ggymin <- min(sapply(yrange, min)) | ||
ggymax <- max(sapply(yrange, max)) | ||
# Get global ranges now because we need some of its info in layer2traces | ||
ranges.list <- list() | ||
for(xy in c("x", "y")){ | ||
use.ranges <- | ||
misc$is.continuous[[xy]] || | ||
misc$is.date[[xy]] || | ||
misc$is.datetime[[xy]] | ||
range.values <- if(use.ranges){ | ||
range.name <- paste0(xy, ".range") | ||
sapply(built$panel$ranges, "[[", range.name) | ||
}else{ | ||
## for categorical variables on the axes, panel$ranges info is | ||
## meaningless. | ||
name.name <- paste0(xy, ".name") | ||
sapply(built$data, function(df){ | ||
if(name.name %in% names(df)){ | ||
## usually for discrete data there is a .name column. | ||
paste(df[[name.name]]) | ||
}else{ | ||
## for heatmaps there may not be. | ||
df[[xy]] | ||
} | ||
}) | ||
} | ||
ranges.list[[xy]] <- range(range.values) | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. getting global ranges is more complicated now that we respect inherit.aes |
||
|
||
# Get global size range because we need some of its info in layer2traces | ||
if ("size.name" %in% name.names) { | ||
|
@@ -135,51 +198,7 @@ gg2list <- function(p){ | |
|
||
# for each layer, there is a correpsonding data.frame which | ||
# evaluates the aesthetic mapping. | ||
df <- built$data[[i]] | ||
|
||
# Test fill and color to see if they encode a quantitative | ||
# variable. This may be useful for several reasons: (1) it is | ||
# sometimes possible to plot several different colors in the same | ||
# trace (e.g. points), and that is faster for large numbers of | ||
# data points and colors; (2) factors on x or y axes should be | ||
# sent to plotly as characters, not as numeric data (which is | ||
# what ggplot_build gives us). | ||
misc <- list() | ||
for(a in c("fill", "colour", "x", "y", "size")){ | ||
for(data.type in c("continuous", "date", "datetime", "discrete")){ | ||
fun.name <- sprintf("scale_%s_%s", a, data.type) | ||
misc.name <- paste0("is.", data.type) | ||
misc[[misc.name]][[a]] <- tryCatch({ | ||
fun <- get(fun.name) | ||
suppressMessages({ | ||
with.scale <- original.p + fun() | ||
}) | ||
ggplot_build(with.scale) | ||
TRUE | ||
}, error=function(e){ | ||
FALSE | ||
}) | ||
} | ||
} | ||
|
||
# scales are needed for legend ordering. | ||
misc$breaks <- list() | ||
for(sc in p$scales$scales){ | ||
a.vec <- sc$aesthetics | ||
default.breaks <- inherits(sc$breaks, "waiver") | ||
if (length(a.vec) == 1 && (!default.breaks) ) { | ||
# TODO: generalize for x/y scales too. | ||
br <- sc$breaks | ||
ranks <- seq_along(br) | ||
names(ranks) <- br | ||
misc$breaks[[a.vec]] <- ranks | ||
} | ||
## store if this is a reverse scale so we can undo that later. | ||
if(is.character(sc$trans$name)){ | ||
misc$trans[sc$aesthetics] <- sc$trans$name | ||
} | ||
} | ||
reverse.aes <- names(misc$trans)[misc$trans=="reverse"] | ||
df <- built$data[[i]] | ||
|
||
# get gglayout now because we need some of its info in layer2traces | ||
gglayout <- built$panel$layout | ||
|
@@ -203,21 +222,24 @@ gg2list <- function(p){ | |
for (a in replace.aes) { | ||
prestats[[a]] <- -1 * prestats[[a]] | ||
} | ||
misc$prestats.data <- | ||
L$prestats.data <- | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. prestats.data is layer-specific (misc is plot-specific) |
||
merge(prestats, | ||
gglayout[, c("PANEL", "plotly.row", "COL")]) | ||
|
||
# Add global x-range info | ||
misc$prestats.data$globxmin <- ggxmin | ||
misc$prestats.data$globxmax <- ggxmax | ||
# Add global y-range info | ||
misc$prestats.data$globymin <- ggymin | ||
misc$prestats.data$globymax <- ggymax | ||
|
||
# Add global range info. | ||
for(xy in names(ranges.list)){ | ||
range.vec <- ranges.list[[xy]] | ||
names(range.vec) <- c("min", "max") | ||
for(range.name in names(range.vec)){ | ||
glob.name <- paste0("glob", xy, range.name) | ||
L$prestats.data[[glob.name]] <- range.vec[[range.name]] | ||
} | ||
} | ||
|
||
# Add global size info if relevant | ||
if ("size.name" %in% name.names) { | ||
misc$prestats.data$globsizemin <- ggsizemin | ||
misc$prestats.data$globsizemax <- ggsizemax | ||
L$prestats.data$globsizemin <- ggsizemin | ||
L$prestats.data$globsizemax <- ggsizemax | ||
} | ||
|
||
# This extracts essential info for this geom/layer. | ||
|
@@ -415,7 +437,7 @@ gg2list <- function(p){ | |
sc$limits | ||
}else{ | ||
if(misc$is.continuous[[xy]]){ | ||
ggranges[[1]][[s("%s.range")]] #TODO: facets! | ||
built$panel$ranges[[1]][[s("%s.range")]] #TODO: facets! | ||
}else{ # for a discrete scale, range should be NULL. | ||
NULL | ||
} | ||
|
@@ -594,9 +616,6 @@ gg2list <- function(p){ | |
layout$annotations <- annotations | ||
} | ||
|
||
# Remove legend if theme has no legend position | ||
layout$showlegend <- !(theme.pars$legend.position=="none") | ||
|
||
# Main plot title. | ||
layout$title <- built$plot$labels$title | ||
|
||
|
@@ -612,10 +631,6 @@ gg2list <- function(p){ | |
layout$legend <- list(bordercolor="transparent", | ||
x=1.05, y=1/2, | ||
xanchor="center", yanchor="top") | ||
# Workaround for removing unnecessary legends. | ||
# [markUnique != "x"] is for boxplot's particular case. | ||
if (any(names(layer.aes) %in% markUnique[markUnique != "x"]) == FALSE) | ||
layout$showlegend <- FALSE | ||
|
||
## Legend hiding when guides(fill="none"). | ||
legends.present <- unique(unlist(layer.legends)) | ||
|
@@ -628,12 +643,17 @@ gg2list <- function(p){ | |
is.hidden <- function(x){ | ||
is.false(x) || is.none(x) | ||
} | ||
layout$showlegend <- if(length(legends.present) == 0) FALSE else TRUE | ||
for(a in legends.present){ | ||
if(is.hidden(p$guides[[a]])){ | ||
layout$showlegend <- FALSE | ||
} | ||
} | ||
|
||
# Legend hiding from theme. | ||
if(theme.pars$legend.position=="none"){ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Spaces at least around |
||
layout$showlegend <- FALSE | ||
} | ||
|
||
# Only show a legend title if there is at least 1 trace with | ||
# showlegend=TRUE. | ||
trace.showlegend <- sapply(trace.list, "[[", "showlegend") | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,7 @@ | ||
#' Convert a layer to a list of traces. Called from gg2list() | ||
#' @param l one layer of the ggplot object | ||
#' @param d one layer of calculated data from ggplot2::ggplot_build(p) | ||
#' @param misc named list. | ||
#' @param misc named list of plot info, independent of layer. | ||
#' @return list representing a layer, with corresponding aesthetics, ranges, and groups. | ||
#' @export | ||
layer2traces <- function(l, d, misc) { | ||
|
@@ -12,7 +12,7 @@ layer2traces <- function(l, d, misc) { | |
} | ||
g <- list(geom=l$geom$objname, | ||
data=not.na(d), | ||
prestats.data=not.na(misc$prestats.data)) | ||
prestats.data=not.na(l$prestats.data)) | ||
|
||
# needed for when group, etc. is an expression. | ||
g$aes <- sapply(l$mapping, function(k) as.character(as.expression(k))) | ||
|
@@ -46,21 +46,22 @@ layer2traces <- function(l, d, misc) { | |
g$geom <- "bar" | ||
bargap <- 0 | ||
} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, I'm pretty sure this indentation is from RStudio. |
||
# For non-numeric data on the axes, we should take the values from | ||
# the original data. | ||
for (axis.name in c("x", "y")) { | ||
if (!misc$is.continuous[[axis.name]]) { | ||
aes.names <- paste0(axis.name, c("", "end", "min", "max")) | ||
aes.used <- aes.names[aes.names %in% names(g$aes)] | ||
for(a in aes.used) { | ||
a.name <- paste0(a, ".name") | ||
col.name <- g$aes[aes.used] | ||
dtemp <- l$data[[col.name]] | ||
if (is.null(dtemp)) { | ||
if (!inherits(g$data[[paste0(a, ".name")]], "NULL")) { | ||
if (!is.null(g$data[[a.name]])) { | ||
# Handle the case where as.Date() is passed in aes argument. | ||
if (class(g$data[[a]]) != class(g$data[[paste0(a, ".name")]])) { | ||
g$data[[a]] <- g$data[[paste0(a, ".name")]] | ||
if (class(g$data[[a]]) != class(g$data[[a.name]])) { | ||
g$data[[a]] <- g$data[[a.name]] | ||
data.vec <- g$data[[a]] | ||
} | ||
} | ||
|
@@ -86,14 +87,18 @@ layer2traces <- function(l, d, misc) { | |
} else if (inherits(data.vec, "factor")) { | ||
# Re-order data so that Plotly gets it right from ggplot2. | ||
g$data <- g$data[order(g$data[[a]]), ] | ||
data.vec <- data.vec[match(g$data[[a]], as.numeric(data.vec))] | ||
vec.i <- match(g$data[[a]], as.numeric(data.vec)) | ||
if(anyNA(vec.i)){ | ||
vec.i <- match(g$data[[a.name]], data.vec) | ||
} | ||
data.vec <- data.vec[vec.i] | ||
g$prestats.data <- g$prestats.data[order(g$prestats.data[[a]]), ] | ||
pdata.vec <- pdata.vec[match(g$prestats.data[[a]], | ||
as.numeric(pdata.vec))] | ||
pvec.i <- match(g$prestats.data[[a]], as.numeric(pdata.vec)) | ||
pdata.vec <- pdata.vec[pvec.i] | ||
if (length(pdata.vec) == length(data.vec)) | ||
pdata.vec <- data.vec | ||
if (!is.factor(pdata.vec)) | ||
pdata.vec <- g$prestats.data[[paste0(a, ".name")]] | ||
pdata.vec <- g$prestats.data[[a.name]] | ||
} | ||
g$data[[a]] <- data.vec | ||
g$prestats.data[[a]] <- pdata.vec | ||
|
@@ -131,7 +136,7 @@ layer2traces <- function(l, d, misc) { | |
|
||
# symbol=circle,square,diamond,cross,x, | ||
# triangle-up,triangle-down,triangle-left,triangle-right | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here. |
||
# First convert to a "basic" geom, e.g. segments become lines. | ||
convert <- toBasic[[g$geom]] | ||
basic <- if(is.null(convert)){ | ||
|
@@ -141,8 +146,8 @@ layer2traces <- function(l, d, misc) { | |
} | ||
# Then split on visual characteristics that will get different | ||
# legend entries. | ||
data.list <- if (basic$geom %in% names(markLegends)) { | ||
mark.names <- markLegends[[basic$geom]] | ||
data.list <- if (basic$geom %in% names(markSplit)) { | ||
mark.names <- markSplit[[basic$geom]] | ||
# However, continuously colored points are an exception: they do | ||
# not need a legend entry, and they can be efficiently rendered | ||
# using just 1 trace. | ||
|
@@ -173,7 +178,7 @@ layer2traces <- function(l, d, misc) { | |
} | ||
# Split hline and vline when multiple panels or intercepts: | ||
# Need multiple traces accordingly. | ||
if (g$geom == "hline" || g$geom == "vline") { | ||
if (g$geom %in% c("hline", "vline")) { | ||
intercept <- paste0(ifelse(g$geom == "hline", "y", "x"), "intercept") | ||
vec.list <- basic$data[c("PANEL", intercept)] | ||
df.list <- split(basic$data, vec.list, drop=TRUE) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this whole block which creates misc was inside the for(layer.i) block below, which is potentially confusing, since misc contains plot-specific, not layer-specific, information.