Skip to content

Carson ribbon #193

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

Merged
merged 13 commits into from
Mar 24, 2015
2 changes: 1 addition & 1 deletion DESCRIPTION
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.26
Version: 0.5.27
Authors@R: c(person("Chris", "Parmer", role = c("aut", "cre"),
email = "[email protected]"),
person("Scott", "Chamberlain", role = "aut",
Expand Down
4 changes: 4 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
0.5.27 -- 19 Mar 2015

Reimplement geom_ribbon as a basic polygon. Fix #191. Fix #192.

0.5.26 -- 18 Mar 2015

Implemented geom_rect #178
Expand Down
44 changes: 42 additions & 2 deletions R/ggplotly.R
Original file line number Diff line number Diff line change
Expand Up @@ -598,13 +598,13 @@ gg2list <- function(p){
layout$showlegend <- FALSE
}
}

# Only show a legend title if there is at least 1 trace with
# showlegend=TRUE.
trace.showlegend <- sapply(trace.list, "[[", "showlegend")
if (any(trace.showlegend) && layout$showlegend && length(p$data)) {
# Retrieve legend title
legend.elements <- sapply(traces, "[[", "name")
legend.elements <- unlist(sapply(traces, "[[", "name"))
legend.title <- ""
for (i in 1:ncol(p$data)) {
if (all(legend.elements %in% unique(p$data[, i])))
Expand Down Expand Up @@ -760,6 +760,46 @@ gg2list <- function(p){
merged.traces[[length(merged.traces)+1]] <- tr
}

# -------------------------------
# avoid redundant legends entries
# -------------------------------
# remove alpha from a color entry
rm_alpha <- function(x) {
if (length(x) == 0) return(x)
pat <- "^rgba\\("
if (!grepl(pat, x)) return(x)
sub(",\\s*[0]?[.]?[0-9]+\\)$", ")", sub(pat, "rgb(", x))
}
# convenient for extracting name/value of legend entries (ignoring alpha)
entries <- function(x, y) {
z <- try(x[[y]], silent = TRUE)
if (inherits(e, "try-error")) {
paste0(x$name, "-")
} else {
paste0(x$name, "-", rm_alpha(z))
}
}
fill_set <- unlist(lapply(merged.traces, entries, "fillcolor"))
line_set <- unlist(lapply(merged.traces, entries, c("line", "color")))
mark_set <- unlist(lapply(merged.traces, entries, c("marker", "color")))
legend_intersect <- function(x, y) {
i <- intersect(x, y)
# restrict intersection to valid legend entries
i[grepl("-rgb[a]?\\(", i)]
}
# if there is a mark & line legend, get rid of line
t1 <- line_set %in% legend_intersect(mark_set, line_set)
# that is, unless the mode is 'lines+markers'...
t1 <- t1 & !(unlist(lapply(merged.traces, "[[", "mode")) %in% "lines+markers")
# if there is a mark & fill legend, get rid of fill
t2 <- fill_set %in% legend_intersect(mark_set, fill_set)
# if there is a line & fill legend, get rid of fill
t3 <- fill_set %in% legend_intersect(line_set, fill_set)
t <- t1 | t2 | t3
for (m in seq_along(merged.traces))
if (isTRUE(merged.traces[[m]]$showlegend && t[m]))
merged.traces[[m]]$showlegend <- FALSE

# Put the traces in correct order, according to any manually
# specified scales. This seems to be repetitive with the trace$rank
# attribute in layer2traces (which is useful for sorting traces that
Expand Down
51 changes: 36 additions & 15 deletions R/trace_generation.R
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,15 @@ layer2traces <- function(l, d, misc) {
# geom_smooth() means geom_line() + geom_ribbon()
# Note the line is always drawn, but ribbon is not if se = FALSE.
if (g$geom == "smooth") {
# If smoothLine has been compiled already, consider smoothRibbon.
# If smoothLine has been compiled already, consider drawing the ribbon
if (isTRUE(misc$smoothLine)) {
misc$smoothLine <- FALSE
if (isTRUE(l$stat_params$se == FALSE)) {
return(NULL)
} else {
g$geom <- "smoothRibbon"
# disregard colour
g$data <- g$data[!grepl("^colour[.name]?", names(g$data))]
}
} else {
misc$smoothLine <- TRUE
Expand Down Expand Up @@ -248,7 +250,6 @@ layer2traces <- function(l, d, misc) {
if (length(unique(name.list)) < 2)
tr$name <- as.character(name.list[[1]])
}

dpd <- data.params$data
if ("PANEL" %in% names(dpd) && nrow(dpd) > 0)
{
Expand Down Expand Up @@ -335,6 +336,11 @@ toBasic <- list(
g$geom <- "polygon"
g
},
ribbon=function(g) {
g$data <- ribbon_dat(g$data)
g$geom <- "polygon"
g
},
path=function(g) {
group2NA(g, "path")
},
Expand Down Expand Up @@ -406,12 +412,15 @@ toBasic <- list(
g
},
smoothLine=function(g) {
if (length(unique(g$data$group)) == 1) g$params$colour <- "#3366FF"
if (length(grep("^colour$", names(g$data))) == 0)
g$params$colour <- "#3366FF"
group2NA(g, "path")
},
smoothRibbon=function(g) {
if (is.null(g$params$alpha)) g$params$alpha <- 0.1
group2NA(g, "ribbon")
if (is.null(g$params$alpha)) g$params$alpha <- 0.2
g$data <- ribbon_dat(g$data)
g$geom <- "polygon"
g
}
)

Expand Down Expand Up @@ -493,6 +502,26 @@ make.errorbar <- function(data, params, xy){
tr
}

# function to transform geom_ribbon data into format plotly likes
# (note this function is also used for geom_smooth)
ribbon_dat <- function(dat) {
n <- nrow(dat)
o <- order(dat$x)
o2 <- order(dat$x, decreasing = TRUE)
used <- c("x", "ymin", "ymax")
not_used <- setdiff(names(dat), used)
# top-half of ribbon
tmp <- dat[o, ]
others <- tmp[not_used]
dat1 <- cbind(x = tmp$x, y = tmp$ymax, others)
dat1[n+1, ] <- cbind(x = tmp$x[n], y = tmp$ymin[n], others[n, ])
# bottom-half of ribbon
tmp2 <- dat[o2, ]
others2 <- tmp2[not_used]
dat2 <- cbind(x = tmp2$x, y = tmp2$ymin, others2)
rbind(dat1, dat2)
}

# Convert basic geoms to traces.
geom2trace <- list(
path=function(data, params) {
Expand All @@ -515,7 +544,8 @@ geom2trace <- list(
mode="lines",
line=paramORdefault(params, aes2line, polygon.line.defaults),
fill="tozerox",
fillcolor=toFill(params$fill))
fillcolor=toFill(params$fill, ifelse(is.null(params$alpha), 1,
params$alpha)))
},
point=function(data, params){
L <- list(x=data$x,
Expand Down Expand Up @@ -667,15 +697,6 @@ geom2trace <- list(
fillcolor=toFill(params$fill, ifelse(is.null(params$alpha), 1,
params$alpha)))
},
ribbon=function(data, params) {
list(x=c(data$x[1], data$x, rev(data$x)),
y=c(data$ymin[1], data$ymax, rev(data$ymin)),
type="scatter",
line=paramORdefault(params, aes2line, ribbon.line.defaults),
fill="tonexty",
fillcolor=toFill(params$fill, ifelse(is.null(params$alpha), 1,
params$alpha)))
},
abline=function(data, params) {
list(x=c(params$xstart, params$xend),
y=c(params$intercept + params$xstart * params$slope,
Expand Down
59 changes: 43 additions & 16 deletions tests/testthat/test-ggplot-ribbon.R
Original file line number Diff line number Diff line change
@@ -1,26 +1,53 @@
context("ribbon")

huron <- data.frame(year=1875:1972, level=as.vector(LakeHuron))
expect_traces <- function(gg, n.traces, name){
stopifnot(is.ggplot(gg))
stopifnot(is.numeric(n.traces))
save_outputs(gg, paste0("ribbon-", name))
L <- gg2list(gg)
is.trace <- names(L) == ""
all.traces <- L[is.trace]
no.data <- sapply(all.traces, function(tr) {
is.null(tr[["x"]]) && is.null(tr[["y"]])
})
has.data <- all.traces[!no.data]
expect_equal(length(has.data), n.traces)
list(traces=has.data, kwargs=L$kwargs)
}

rb <- ggplot(huron, aes(x=year)) + geom_ribbon(aes(ymin=level-1, ymax=level+1))
L <- gg2list(rb)
huron <- data.frame(year = 1875:1972, level = as.vector(LakeHuron))
huron$decade <- with(huron, round(year/10) * 10)
huron$diff <- huron$year - huron$decade

test_that("sanity check for geom_ribbon", {
expect_equal(length(L), 2)
expect_identical(L[[1]]$type, "scatter")
expect_equal(L[[1]]$x, c(huron$year[1], huron$year, rev(huron$year)))
expect_equal(L[[1]]$y, c(huron$level[1]-1, huron$level+1, rev(huron$level-1)))
expect_identical(L[[1]]$line$color, "transparent")
p1 <- ggplot(data = huron) +
geom_ribbon(aes(x = year, ymin = level-1, ymax = level+1),
alpha = 0.1)

test_that("geom_ribbon() creates 1 trace & respects alpha transparency", {
info <- expect_traces(p1, 1, "alpha")
tr <- info$traces[[1]]
expect_match(tr$fillcolor, "0.1)", fixed=TRUE)
})

save_outputs(rb, "ribbon")
p2 <- ggplot(data = huron, aes(group = factor(decade))) +
geom_ribbon(aes(x = diff, ymin = level-0.1, ymax = level+0.1))

test_that("geom_ribbon() with group aesthetic produces 1 trace", {
info <- expect_traces(p2, 1, "group")
})

rb2 <- ggplot(huron, aes(x=year)) +
geom_ribbon(aes(ymin=level-1, ymax=level+1), alpha = 0.1)
L2 <- gg2list(rb2)
p3 <- ggplot(data = huron, aes(colour = factor(decade))) +
geom_ribbon(aes(x = diff, ymin = level-0.1, ymax = level+0.1))

test_that("geom_ribbon respects alpha transparency", {
expect_match(L2[[1]]$fillcolor, "0.1)", fixed=TRUE)
test_that("geom_ribbon() with colour aesthetic produces multiple traces", {
# 10 traces -- one for each decade
info <- expect_traces(p3, 10, "colour")
})

save_outputs(rb2, "ribbon-alpha")
p4 <- ggplot(data = huron, aes(fill = factor(decade))) +
geom_ribbon(aes(x = diff, ymin = level-0.1, ymax = level+0.1))

test_that("geom_ribbon() with fill aesthetic produces multiple traces", {
# 10 traces -- one for each decade
info <- expect_traces(p4, 10, "fill")
})
65 changes: 58 additions & 7 deletions tests/testthat/test-ggplot-smooth.R
Original file line number Diff line number Diff line change
@@ -1,18 +1,69 @@
context("smooth")

expect_traces <- function(gg, n.traces, name){
stopifnot(is.ggplot(gg))
stopifnot(is.numeric(n.traces))
save_outputs(gg, paste0("smooth-", name))
L <- gg2list(gg)
is.trace <- names(L) == ""
all.traces <- L[is.trace]
no.data <- sapply(all.traces, function(tr) {
is.null(tr[["x"]]) && is.null(tr[["y"]])
})
has.data <- all.traces[!no.data]
expect_equal(length(has.data), n.traces)
list(traces=has.data, kwargs=L$kwargs)
}

p <- ggplot(mtcars, aes(mpg, wt)) + geom_point() + geom_smooth()

test_that("geom_point() + geom_smooth() produces 3 traces", {
info <- gg2list(p)
expect_true(sum(names(info) == "") == 3)
save_outputs(p, "smooth")
expect_traces(p, 3, "basic")
})

p2 <- ggplot(mtcars, aes(mpg, wt)) + geom_point() + geom_smooth(se = FALSE)
p2 <- ggplot(mtcars, aes(mpg, wt)) + geom_point() +
geom_smooth(se = FALSE)

test_that("geom_point() + geom_smooth(se = FALSE) produces 2 traces", {
info2 <- gg2list(p2)
expect_true(sum(names(info2) == "") == 2)
save_outputs(p2, "smooth-se-false")
expect_traces(p2, 2, "se-false")
})

d <- diamonds[sample(nrow(diamonds), 1000), ]
p3 <- qplot(carat, price, group = cut, data = d) + geom_smooth()

test_that("geom_smooth() respects group aesthetic", {
info <- expect_traces(p3, 3, "group")
})

p4 <- qplot(carat, price, colour = cut, data = d) + geom_smooth()
p5 <- qplot(carat, price, data = d) + geom_smooth(aes(colour = cut))

test_that("geom_smooth() respects colour aesthetic", {
info <- expect_traces(p4, 11, "colour")
# number of showlegends should equal the number of factor levels
n <- sum(unlist(sapply(info$traces, "[[", "showlegend")))
expect_equal(n, nlevels(d$cut))
info <- expect_traces(p5, 7, "colour2")
n <- sum(unlist(sapply(info$traces, "[[", "showlegend")))
expect_equal(n, nlevels(d$cut))
})

p7 <- qplot(carat, price, data = d) + geom_smooth(aes(fill = cut))

test_that("geom_smooth() respects fill aesthetic", {
info <- expect_traces(p7, 7, "fill2")
n <- sum(unlist(sapply(info$traces, "[[", "showlegend")))
expect_equal(n, nlevels(d$cut))
})

# ensure legend is drawn when needed
p8 <- qplot(carat, price, data = d) + facet_wrap(~cut) +
geom_smooth(aes(colour = cut, fill = cut))

test_that("geom_smooth() works with facets", {
# 3 traces for each panel
info <- expect_traces(p8, 15, "facet")
n <- sum(unlist(sapply(info$traces, "[[", "showlegend")))
expect_equal(n, nlevels(d$cut))
})