Skip to content

Commit a9daa28

Browse files
committed
Merge pull request #193 from ropensci/carson-ribbon
Carson ribbon
2 parents 90c55a3 + ef301b0 commit a9daa28

File tree

6 files changed

+184
-41
lines changed

6 files changed

+184
-41
lines changed

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.26
4+
Version: 0.5.27
55
Authors@R: c(person("Chris", "Parmer", role = c("aut", "cre"),
66
email = "[email protected]"),
77
person("Scott", "Chamberlain", role = "aut",

NEWS

+4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
0.5.27 -- 19 Mar 2015
2+
3+
Reimplement geom_ribbon as a basic polygon. Fix #191. Fix #192.
4+
15
0.5.26 -- 18 Mar 2015
26

37
Implemented geom_rect #178

R/ggplotly.R

+42-2
Original file line numberDiff line numberDiff line change
@@ -598,13 +598,13 @@ gg2list <- function(p){
598598
layout$showlegend <- FALSE
599599
}
600600
}
601-
601+
602602
# Only show a legend title if there is at least 1 trace with
603603
# showlegend=TRUE.
604604
trace.showlegend <- sapply(trace.list, "[[", "showlegend")
605605
if (any(trace.showlegend) && layout$showlegend && length(p$data)) {
606606
# Retrieve legend title
607-
legend.elements <- sapply(traces, "[[", "name")
607+
legend.elements <- unlist(sapply(traces, "[[", "name"))
608608
legend.title <- ""
609609
for (i in 1:ncol(p$data)) {
610610
if (all(legend.elements %in% unique(p$data[, i])))
@@ -760,6 +760,46 @@ gg2list <- function(p){
760760
merged.traces[[length(merged.traces)+1]] <- tr
761761
}
762762

763+
# -------------------------------
764+
# avoid redundant legends entries
765+
# -------------------------------
766+
# remove alpha from a color entry
767+
rm_alpha <- function(x) {
768+
if (length(x) == 0) return(x)
769+
pat <- "^rgba\\("
770+
if (!grepl(pat, x)) return(x)
771+
sub(",\\s*[0]?[.]?[0-9]+\\)$", ")", sub(pat, "rgb(", x))
772+
}
773+
# convenient for extracting name/value of legend entries (ignoring alpha)
774+
entries <- function(x, y) {
775+
z <- try(x[[y]], silent = TRUE)
776+
if (inherits(e, "try-error")) {
777+
paste0(x$name, "-")
778+
} else {
779+
paste0(x$name, "-", rm_alpha(z))
780+
}
781+
}
782+
fill_set <- unlist(lapply(merged.traces, entries, "fillcolor"))
783+
line_set <- unlist(lapply(merged.traces, entries, c("line", "color")))
784+
mark_set <- unlist(lapply(merged.traces, entries, c("marker", "color")))
785+
legend_intersect <- function(x, y) {
786+
i <- intersect(x, y)
787+
# restrict intersection to valid legend entries
788+
i[grepl("-rgb[a]?\\(", i)]
789+
}
790+
# if there is a mark & line legend, get rid of line
791+
t1 <- line_set %in% legend_intersect(mark_set, line_set)
792+
# that is, unless the mode is 'lines+markers'...
793+
t1 <- t1 & !(unlist(lapply(merged.traces, "[[", "mode")) %in% "lines+markers")
794+
# if there is a mark & fill legend, get rid of fill
795+
t2 <- fill_set %in% legend_intersect(mark_set, fill_set)
796+
# if there is a line & fill legend, get rid of fill
797+
t3 <- fill_set %in% legend_intersect(line_set, fill_set)
798+
t <- t1 | t2 | t3
799+
for (m in seq_along(merged.traces))
800+
if (isTRUE(merged.traces[[m]]$showlegend && t[m]))
801+
merged.traces[[m]]$showlegend <- FALSE
802+
763803
# Put the traces in correct order, according to any manually
764804
# specified scales. This seems to be repetitive with the trace$rank
765805
# attribute in layer2traces (which is useful for sorting traces that

R/trace_generation.R

+36-15
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,15 @@ layer2traces <- function(l, d, misc) {
2626
# geom_smooth() means geom_line() + geom_ribbon()
2727
# Note the line is always drawn, but ribbon is not if se = FALSE.
2828
if (g$geom == "smooth") {
29-
# If smoothLine has been compiled already, consider smoothRibbon.
29+
# If smoothLine has been compiled already, consider drawing the ribbon
3030
if (isTRUE(misc$smoothLine)) {
3131
misc$smoothLine <- FALSE
3232
if (isTRUE(l$stat_params$se == FALSE)) {
3333
return(NULL)
3434
} else {
3535
g$geom <- "smoothRibbon"
36+
# disregard colour
37+
g$data <- g$data[!grepl("^colour[.name]?", names(g$data))]
3638
}
3739
} else {
3840
misc$smoothLine <- TRUE
@@ -248,7 +250,6 @@ layer2traces <- function(l, d, misc) {
248250
if (length(unique(name.list)) < 2)
249251
tr$name <- as.character(name.list[[1]])
250252
}
251-
252253
dpd <- data.params$data
253254
if ("PANEL" %in% names(dpd) && nrow(dpd) > 0)
254255
{
@@ -335,6 +336,11 @@ toBasic <- list(
335336
g$geom <- "polygon"
336337
g
337338
},
339+
ribbon=function(g) {
340+
g$data <- ribbon_dat(g$data)
341+
g$geom <- "polygon"
342+
g
343+
},
338344
path=function(g) {
339345
group2NA(g, "path")
340346
},
@@ -406,12 +412,15 @@ toBasic <- list(
406412
g
407413
},
408414
smoothLine=function(g) {
409-
if (length(unique(g$data$group)) == 1) g$params$colour <- "#3366FF"
415+
if (length(grep("^colour$", names(g$data))) == 0)
416+
g$params$colour <- "#3366FF"
410417
group2NA(g, "path")
411418
},
412419
smoothRibbon=function(g) {
413-
if (is.null(g$params$alpha)) g$params$alpha <- 0.1
414-
group2NA(g, "ribbon")
420+
if (is.null(g$params$alpha)) g$params$alpha <- 0.2
421+
g$data <- ribbon_dat(g$data)
422+
g$geom <- "polygon"
423+
g
415424
}
416425
)
417426

@@ -493,6 +502,26 @@ make.errorbar <- function(data, params, xy){
493502
tr
494503
}
495504

505+
# function to transform geom_ribbon data into format plotly likes
506+
# (note this function is also used for geom_smooth)
507+
ribbon_dat <- function(dat) {
508+
n <- nrow(dat)
509+
o <- order(dat$x)
510+
o2 <- order(dat$x, decreasing = TRUE)
511+
used <- c("x", "ymin", "ymax")
512+
not_used <- setdiff(names(dat), used)
513+
# top-half of ribbon
514+
tmp <- dat[o, ]
515+
others <- tmp[not_used]
516+
dat1 <- cbind(x = tmp$x, y = tmp$ymax, others)
517+
dat1[n+1, ] <- cbind(x = tmp$x[n], y = tmp$ymin[n], others[n, ])
518+
# bottom-half of ribbon
519+
tmp2 <- dat[o2, ]
520+
others2 <- tmp2[not_used]
521+
dat2 <- cbind(x = tmp2$x, y = tmp2$ymin, others2)
522+
rbind(dat1, dat2)
523+
}
524+
496525
# Convert basic geoms to traces.
497526
geom2trace <- list(
498527
path=function(data, params) {
@@ -515,7 +544,8 @@ geom2trace <- list(
515544
mode="lines",
516545
line=paramORdefault(params, aes2line, polygon.line.defaults),
517546
fill="tozerox",
518-
fillcolor=toFill(params$fill))
547+
fillcolor=toFill(params$fill, ifelse(is.null(params$alpha), 1,
548+
params$alpha)))
519549
},
520550
point=function(data, params){
521551
L <- list(x=data$x,
@@ -667,15 +697,6 @@ geom2trace <- list(
667697
fillcolor=toFill(params$fill, ifelse(is.null(params$alpha), 1,
668698
params$alpha)))
669699
},
670-
ribbon=function(data, params) {
671-
list(x=c(data$x[1], data$x, rev(data$x)),
672-
y=c(data$ymin[1], data$ymax, rev(data$ymin)),
673-
type="scatter",
674-
line=paramORdefault(params, aes2line, ribbon.line.defaults),
675-
fill="tonexty",
676-
fillcolor=toFill(params$fill, ifelse(is.null(params$alpha), 1,
677-
params$alpha)))
678-
},
679700
abline=function(data, params) {
680701
list(x=c(params$xstart, params$xend),
681702
y=c(params$intercept + params$xstart * params$slope,

tests/testthat/test-ggplot-ribbon.R

+43-16
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,53 @@
11
context("ribbon")
22

3-
huron <- data.frame(year=1875:1972, level=as.vector(LakeHuron))
3+
expect_traces <- function(gg, n.traces, name){
4+
stopifnot(is.ggplot(gg))
5+
stopifnot(is.numeric(n.traces))
6+
save_outputs(gg, paste0("ribbon-", name))
7+
L <- gg2list(gg)
8+
is.trace <- names(L) == ""
9+
all.traces <- L[is.trace]
10+
no.data <- sapply(all.traces, function(tr) {
11+
is.null(tr[["x"]]) && is.null(tr[["y"]])
12+
})
13+
has.data <- all.traces[!no.data]
14+
expect_equal(length(has.data), n.traces)
15+
list(traces=has.data, kwargs=L$kwargs)
16+
}
417

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

8-
test_that("sanity check for geom_ribbon", {
9-
expect_equal(length(L), 2)
10-
expect_identical(L[[1]]$type, "scatter")
11-
expect_equal(L[[1]]$x, c(huron$year[1], huron$year, rev(huron$year)))
12-
expect_equal(L[[1]]$y, c(huron$level[1]-1, huron$level+1, rev(huron$level-1)))
13-
expect_identical(L[[1]]$line$color, "transparent")
22+
p1 <- ggplot(data = huron) +
23+
geom_ribbon(aes(x = year, ymin = level-1, ymax = level+1),
24+
alpha = 0.1)
25+
26+
test_that("geom_ribbon() creates 1 trace & respects alpha transparency", {
27+
info <- expect_traces(p1, 1, "alpha")
28+
tr <- info$traces[[1]]
29+
expect_match(tr$fillcolor, "0.1)", fixed=TRUE)
1430
})
1531

16-
save_outputs(rb, "ribbon")
32+
p2 <- ggplot(data = huron, aes(group = factor(decade))) +
33+
geom_ribbon(aes(x = diff, ymin = level-0.1, ymax = level+0.1))
34+
35+
test_that("geom_ribbon() with group aesthetic produces 1 trace", {
36+
info <- expect_traces(p2, 1, "group")
37+
})
1738

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

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

26-
save_outputs(rb2, "ribbon-alpha")
47+
p4 <- ggplot(data = huron, aes(fill = factor(decade))) +
48+
geom_ribbon(aes(x = diff, ymin = level-0.1, ymax = level+0.1))
49+
50+
test_that("geom_ribbon() with fill aesthetic produces multiple traces", {
51+
# 10 traces -- one for each decade
52+
info <- expect_traces(p4, 10, "fill")
53+
})

tests/testthat/test-ggplot-smooth.R

+58-7
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,69 @@
11
context("smooth")
22

3+
expect_traces <- function(gg, n.traces, name){
4+
stopifnot(is.ggplot(gg))
5+
stopifnot(is.numeric(n.traces))
6+
save_outputs(gg, paste0("smooth-", name))
7+
L <- gg2list(gg)
8+
is.trace <- names(L) == ""
9+
all.traces <- L[is.trace]
10+
no.data <- sapply(all.traces, function(tr) {
11+
is.null(tr[["x"]]) && is.null(tr[["y"]])
12+
})
13+
has.data <- all.traces[!no.data]
14+
expect_equal(length(has.data), n.traces)
15+
list(traces=has.data, kwargs=L$kwargs)
16+
}
17+
318
p <- ggplot(mtcars, aes(mpg, wt)) + geom_point() + geom_smooth()
419

520
test_that("geom_point() + geom_smooth() produces 3 traces", {
6-
info <- gg2list(p)
7-
expect_true(sum(names(info) == "") == 3)
8-
save_outputs(p, "smooth")
21+
expect_traces(p, 3, "basic")
922
})
1023

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

1327
test_that("geom_point() + geom_smooth(se = FALSE) produces 2 traces", {
14-
info2 <- gg2list(p2)
15-
expect_true(sum(names(info2) == "") == 2)
16-
save_outputs(p2, "smooth-se-false")
28+
expect_traces(p2, 2, "se-false")
29+
})
30+
31+
d <- diamonds[sample(nrow(diamonds), 1000), ]
32+
p3 <- qplot(carat, price, group = cut, data = d) + geom_smooth()
33+
34+
test_that("geom_smooth() respects group aesthetic", {
35+
info <- expect_traces(p3, 3, "group")
36+
})
37+
38+
p4 <- qplot(carat, price, colour = cut, data = d) + geom_smooth()
39+
p5 <- qplot(carat, price, data = d) + geom_smooth(aes(colour = cut))
40+
41+
test_that("geom_smooth() respects colour aesthetic", {
42+
info <- expect_traces(p4, 11, "colour")
43+
# number of showlegends should equal the number of factor levels
44+
n <- sum(unlist(sapply(info$traces, "[[", "showlegend")))
45+
expect_equal(n, nlevels(d$cut))
46+
info <- expect_traces(p5, 7, "colour2")
47+
n <- sum(unlist(sapply(info$traces, "[[", "showlegend")))
48+
expect_equal(n, nlevels(d$cut))
49+
})
50+
51+
p7 <- qplot(carat, price, data = d) + geom_smooth(aes(fill = cut))
52+
53+
test_that("geom_smooth() respects fill aesthetic", {
54+
info <- expect_traces(p7, 7, "fill2")
55+
n <- sum(unlist(sapply(info$traces, "[[", "showlegend")))
56+
expect_equal(n, nlevels(d$cut))
57+
})
58+
59+
# ensure legend is drawn when needed
60+
p8 <- qplot(carat, price, data = d) + facet_wrap(~cut) +
61+
geom_smooth(aes(colour = cut, fill = cut))
62+
63+
test_that("geom_smooth() works with facets", {
64+
# 3 traces for each panel
65+
info <- expect_traces(p8, 15, "facet")
66+
n <- sum(unlist(sapply(info$traces, "[[", "showlegend")))
67+
expect_equal(n, nlevels(d$cut))
1768
})
1869

0 commit comments

Comments
 (0)