diff --git a/DESCRIPTION b/DESCRIPTION index 357f13bc47..041cd58a3f 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: plotly Type: Package Title: Interactive, publication-quality graphs online. -Version: 0.6 +Version: 0.6.1 Authors@R: c(person("Chris", "Parmer", role = c("aut", "cre"), email = "chris@plot.ly"), person("Scott", "Chamberlain", role = "aut", diff --git a/NEWS b/NEWS index 4e0450fe02..90e3149faf 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,7 @@ +0.6.1 -- 5 May 2015 + +Add test-cookbook-lines.R and fix bugs that showed up in those tests. + 0.6 -- 4 May 2015 Let gg2list() return a figure object (backwards incompatible change). diff --git a/R/ggplotly.R b/R/ggplotly.R index 02973ca2d1..13c80ddf30 100644 --- a/R/ggplotly.R +++ b/R/ggplotly.R @@ -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,9 +98,11 @@ 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 @@ -108,18 +111,78 @@ gg2list <- function(p) { } } + # 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) + } # Get global size range because we need some of its info in layer2traces if ("size.name" %in% name.names) { @@ -127,7 +190,7 @@ gg2list <- function(p) { ggsizemin <- min(unlist(sizerange)) ggsizemax <- max(unlist(sizerange)) } - + layer.legends <- list() for(i in seq_along(built$plot$layers)){ # This is the layer from the original ggplot object. @@ -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 <- 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. @@ -334,7 +356,7 @@ gg2list <- function(p) { grid <- theme.pars$panel.grid grid.major <- theme.pars$panel.grid.major if ((!is.null(grid$linetype) || !is.null(grid.major$linetype)) && - c(grid$linetype, grid.major$linetype) %in% c(2, 3, "dashed", "dotted")) { + c(grid$linetype, grid.major$linetype) %in% c(2, 3, "dashed", "dotted")) { ax.list$gridcolor <- ifelse(is.null(grid.major$colour), toRGB(grid$colour, 0.1), toRGB(grid.major$colour, 0.1)) @@ -370,7 +392,7 @@ gg2list <- function(p) { ax.list$tickangle <- -tick.text$angle } ax.list$tickfont <- theme2font(tick.text) - + ## determine axis type first, since this information is used later ## (trace.order.list is only used for type=category). title.text <- e(s("axis.title.%s")) @@ -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 } @@ -431,7 +453,7 @@ gg2list <- function(p) { }else{ p$labels[[xy]] } - + ax.list$zeroline <- FALSE # ggplot2 plots do not show zero lines # Lines drawn around the plot border. ax.list$showline <- !is.blank("panel.border", TRUE) @@ -581,22 +603,19 @@ gg2list <- function(p) { nann <- nann + 1 } } - # axes titles - annotations[[nann]] <- make.label(xaxis.title, - 0.5, - -outer.margin, - yanchor="top") - nann <- nann + 1 - annotations[[nann]] <- make.label(yaxis.title, - -outer.margin, - 0.5, - textangle=-90) + # axes titles + annotations[[nann]] <- make.label(xaxis.title, + 0.5, + -outer.margin, + yanchor="top") + nann <- nann + 1 + annotations[[nann]] <- make.label(yaxis.title, + -outer.margin, + 0.5, + textangle=-90) 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,11 +631,7 @@ 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)) is.false <- function(x){ @@ -628,11 +643,16 @@ 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"){ + layout$showlegend <- FALSE + } # Only show a legend title if there is at least 1 trace with # showlegend=TRUE. @@ -817,6 +837,7 @@ gg2list <- function(p) { 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"))) + mode_set <- lapply(merged.traces, "[[", "mode") legend_intersect <- function(x, y) { i <- intersect(x, y) # restrict intersection to valid legend entries @@ -825,7 +846,7 @@ gg2list <- function(p) { # 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") + t1 <- t1 & !(mode_set %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 @@ -887,7 +908,7 @@ gg2list <- function(p) { } fig <- list(data=flipped.traces, layout=flipped.layout) - + fig } diff --git a/R/plotly-package.r b/R/plotly-package.r index 2c06bccec8..bbd16326b3 100644 --- a/R/plotly-package.r +++ b/R/plotly-package.r @@ -7,7 +7,7 @@ #' \itemize{ #' \item Package: plotly #' \item Type: Package -#' \item Version: 0.6 +#' \item Version: 0.6.1 #' \item Date: 2014-03-07 #' \item License: MIT #' } diff --git a/R/plotly.R b/R/plotly.R index 062c197b39..5728d656e8 100644 --- a/R/plotly.R +++ b/R/plotly.R @@ -82,7 +82,7 @@ For more help, see https://plot.ly/R or contact .") # public attributes/methods that the user has access to pub <- list(username=username, key=key, filename="from api", fileopt=NULL, - version="0.6") + version="0.6.1") priv <- list() pub$makecall <- function(args, kwargs, origin) { diff --git a/R/trace_generation.R b/R/trace_generation.R index 673e88d80b..0c2598efe3 100644 --- a/R/trace_generation.R +++ b/R/trace_generation.R @@ -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,8 +12,8 @@ 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))) # Partial conversion for geom_violin (Plotly does not offer KDE yet) @@ -54,13 +54,14 @@ layer2traces <- function(l, d, misc) { 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 @@ -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) @@ -242,7 +247,7 @@ layer2traces <- function(l, d, misc) { if (is.null(tr$name) || tr$name %in% names.in.legend) tr$showlegend <- FALSE names.in.legend <- c(names.in.legend, tr$name) - + # special handling for bars if (g$geom == "bar") { tr$bargap <- if (exists("bargap")) bargap else "default" @@ -265,7 +270,7 @@ layer2traces <- function(l, d, misc) { 0 } }) - + ord <- order(sort.val) no.sort <- traces[ord] for(tr.i in seq_along(no.sort)){ diff --git a/man/layer2traces.Rd b/man/layer2traces.Rd index ce362768d5..e1e10cd29b 100644 --- a/man/layer2traces.Rd +++ b/man/layer2traces.Rd @@ -11,7 +11,7 @@ layer2traces(l, d, misc) \item{d}{one layer of calculated data from ggplot2::ggplot_build(p)} -\item{misc}{named list.} +\item{misc}{named list of plot info, independent of layer.} } \value{ list representing a layer, with corresponding aesthetics, ranges, and groups. diff --git a/man/plotly-package.Rd b/man/plotly-package.Rd index 9244d7ce76..69f0c0cde6 100644 --- a/man/plotly-package.Rd +++ b/man/plotly-package.Rd @@ -15,7 +15,7 @@ An example of an interactive graph made from the R API: https://plot.ly/~chris/4 \itemize{ \item Package: plotly \item Type: Package - \item Version: 0.6 + \item Version: 0.6.1 \item Date: 2014-03-07 \item License: MIT } diff --git a/tests/testthat/test-cookbook-axes.R b/tests/testthat/test-cookbook-axes.R index 29375d1c30..44937fe422 100644 --- a/tests/testthat/test-cookbook-axes.R +++ b/tests/testthat/test-cookbook-axes.R @@ -3,7 +3,7 @@ context("cookbook axes") bp <- ggplot(PlantGrowth, aes(x=group, y=weight)) + geom_boxplot() -expect_traces <- function(gg, n.traces, name){ +expect_traces <- function(gg, n.traces, name) { stopifnot(is.ggplot(gg)) stopifnot(is.numeric(n.traces)) save_outputs(gg, paste0("cookbook-axes-", name)) @@ -17,6 +17,32 @@ expect_traces <- function(gg, n.traces, name){ list(traces=has.data, layout=L$layout) } +get_legend <- function(L) { + if (!isTRUE(L$kwargs$layout$showlegend)) { + return(data.frame()) + } + legend.list <- list() + for (tr in L$traces) { + if (is.character(tr$name)) { + legend.list[[tr$name]] <- + data.frame(name=tr$name, showlegend=tr$showlegend) + } + } + legend.df <- do.call(rbind, legend.list) + subset(legend.df, showlegend) +} + +leg <- function(...) { + name <- c(...) + data.frame(name) +} + +expect_legend <- function(L, expected) { + stopifnot(is.data.frame(expected)) + shown <- get_legend(L) + expect_identical(shown$name, expected$name) +} + # Reverse the order of a discrete-valued axis # Get the levels of the factor flevels <- levels(PlantGrowth$group) @@ -31,24 +57,28 @@ test_that("factor levels determine tick order", { trace.names <- sapply(info$traces, "[[", "name") expect_identical(as.character(trace.names), c("trt2", "trt1", "ctrl")) + expect_legend(info, leg()) }) - + ## These two do the same thing; all data points outside the graphing ## range are dropped, resulting in a misleading box plot. bp.ylim.hide <- bp + ylim(5, 7.5) test_that("ylim hides points", { info <- expect_traces(bp.ylim.hide, 3, "ylim.hide") + expect_legend(info, leg()) }) bp.scale.hide <- bp + scale_y_continuous(limits=c(5, 7.5)) test_that("scale_y(limits) hides points", { info <- expect_traces(bp.scale.hide, 3, "scale.hide") + expect_legend(info, leg()) expect_equal(info$layout$yaxis$range, c(5, 7.5)) }) - + bp.coord <- bp + coord_cartesian(ylim=c(5, 7.5)) test_that("Using coord_cartesian zooms into the area", { info <- expect_traces(bp.coord, 3, "coord-ylim") + expect_legend(info, leg()) expect_equal(info$layout$yaxis$range, c(5, 7.5)) }) @@ -63,19 +93,24 @@ sp <- ggplot(dat, aes(xval, yval)) + geom_point() test_that("A scatterplot with regular (linear) axis scaling", { info <- expect_traces(sp, 1, "linear-axes") + # TODO: why does this test take so long? + expect_legend(info, leg()) }) -library(scales) # Need the scales package +library(scales) +# TODO: Add package "scales" to the list of dependencies? sp.log2.scale <- sp + scale_y_continuous(trans=log2_trans()) test_that("log2 scaling of the y axis (with visually-equal spacing)", { info <- expect_traces(sp.log2.scale, 1, "log2-scale") + expect_legend(info, leg()) }) sp.log2.coord <- sp + coord_trans(ytrans="log2") test_that("log2 coordinate transformation with visually-diminishing spacing", { info <- expect_traces(sp.log2.coord, 1, "log2-coord") + expect_legend(info, leg()) }) sp.labels <- sp + @@ -85,12 +120,14 @@ sp.labels <- sp + test_that("log2 transform with labels", { info <- expect_traces(sp.labels, 1, "log2-labels") + expect_legend(info, leg()) }) sp.log10 <- sp + scale_y_log10() test_that("scale_y_log10", { info <- expect_traces(sp.log10, 1, "scale_y_log10") + expect_legend(info, leg()) }) sp.log10.labels <- sp + @@ -99,6 +136,7 @@ sp.log10.labels <- sp + test_that("log10 with exponents on tick labels", { info <- expect_traces(sp.log10.labels, 1, "scale_y_log10-labels") + expect_legend(info, leg()) }) # Data where x ranges from 0-10, y ranges from 0-30 @@ -110,12 +148,14 @@ sp.fixed <- sp + coord_fixed() test_that("Force equal scaling", { info <- expect_traces(sp.fixed, 1, "coord-fixed") + expect_legend(info, leg()) }) sp.ratio <- sp + coord_fixed(ratio=1/3) test_that("coord_fixed(ratio)", { info <- expect_traces(sp.ratio, 1, "coord-fixed-ratio") + expect_legend(info, leg()) }) no.x.title <- bp + @@ -124,6 +164,7 @@ no.x.title <- bp + test_that("coord_fixed(ratio)", { info <- expect_traces(no.x.title, 3, "no-x-title") + expect_legend(info, leg()) }) # Also possible to set the axis label with the scale @@ -134,6 +175,7 @@ bp.scale.name <- bp + scale_x_discrete(name="") + test_that("scale(name)", { info <- expect_traces(bp.scale.name, 3, "scale-name") + expect_legend(info, leg()) }) # Change font options: @@ -147,6 +189,7 @@ bp.fonts <- bp + test_that("element_text face, colour, size, angle, vjust, size", { info <- expect_traces(bp.fonts, 3, "fonts") + expect_legend(info, leg()) x <- info$layout$xaxis xtitle <- x[["titlefont"]] xtick <- x[["tickfont"]] @@ -167,22 +210,24 @@ label.funs <- bp + test_that("In this particular case, x scale has no effect", { info <- expect_traces(label.funs, 3, "label-funs") + expect_legend(info, leg()) }) # Self-defined formatting function for times. timeHMS_formatter <- function(x) { - h <- floor(x/60) - m <- floor(x %% 60) - s <- round(60*(x %% 1)) # Round to nearest second - lab <- sprintf("%02d:%02d:%02d", h, m, s) # Format the strings as HH:MM:SS - lab <- gsub("^00:", "", lab) # Remove leading 00: if present - lab <- gsub("^0", "", lab) # Remove leading 0 if present + h <- floor(x/60) + m <- floor(x %% 60) + s <- round(60*(x %% 1)) # Round to nearest second + lab <- sprintf("%02d:%02d:%02d", h, m, s) # Format the strings as HH:MM:SS + lab <- gsub("^00:", "", lab) # Remove leading 00: if present + lab <- gsub("^0", "", lab) # Remove leading 0 if present } custom.formatter <- bp + scale_y_continuous(label=timeHMS_formatter) test_that("custom HMS formatter function", { info <- expect_traces(custom.formatter, 3, "custom-formatter") + expect_legend(info, leg()) }) blank.minor.major <- bp + @@ -191,6 +236,7 @@ blank.minor.major <- bp + test_that("Hide all the gridlines", { info <- expect_traces(blank.minor.major, 3, "blank-minor-major") + expect_legend(info, leg()) }) blank.minor <- bp + @@ -198,6 +244,7 @@ blank.minor <- bp + test_that("Hide just the minor gridlines", { info <- expect_traces(blank.minor, 3, "blank-minor") + expect_legend(info, leg()) }) blank.x <- bp + @@ -206,6 +253,7 @@ blank.x <- bp + test_that("Hide all the horizontal gridlines", { info <- expect_traces(blank.x, 3, "blank-x") + expect_legend(info, leg()) }) blank.y <- bp + @@ -214,5 +262,5 @@ blank.y <- bp + test_that("Hide all the vertical gridlines", { info <- expect_traces(blank.y, 3, "blank-y") + expect_legend(info, leg()) }) - diff --git a/tests/testthat/test-cookbook-lines.R b/tests/testthat/test-cookbook-lines.R new file mode 100644 index 0000000000..6fd5eb35d8 --- /dev/null +++ b/tests/testthat/test-cookbook-lines.R @@ -0,0 +1,258 @@ +context("cookbook lines") + +expect_traces_shapes <- function(gg, n.traces, n.shapes, name) { + stopifnot(is.ggplot(gg)) + stopifnot(is.numeric(n.traces)) + stopifnot(is.numeric(n.shapes)) + save_outputs(gg, paste0("cookbook-lines-", name)) + L <- gg2list(gg) + all.traces <- L$data + 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) + shapes <- L$layout$shapes + expect_equal(length(shapes), n.shapes) + list(traces = has.data, shapes = shapes, layout = L$layout) +} + +expect_shape <- function(s, ...) { + expected.list <- list(...) + for(key in names(expected.list)) { + value <- expected.list[[key]] + expect_identical(s[[key]], value) + } +} + +# Some sample data +df <- read.table(header = T, text = " + cond result + control 10 +treatment 11.5 +") + +# Basic bar plot +bp <- ggplot(df, aes(x = cond, y = result)) + + geom_bar(position = "dodge", stat = "identity") + +test_that("geom_bar -> 1 trace", { + info <- expect_traces_shapes(bp, 1, 0, "basic-bar") +}) + +# Add a horizontal line +temp <- bp + geom_hline(aes(yintercept = 12)) +test_that("bar + hline = 2 traces", { + info <- expect_traces_shapes(temp, 2, 0, "basic-horizontal-line") +}) + +# Make the line red and dashed +temp <- bp + geom_hline(aes(yintercept=12), colour="#990000", linetype="dashed") +test_that("bar + red dashed hline", { + info <- expect_traces_shapes(temp, 2, 0, "dashed-red-line") + hline.info <- info$traces[[2]] + expect_identical(hline.info$line$color, toRGB("#990000")) + expect_identical(hline.info$line$dash, "dash") +}) + +# Draw separate hlines for each bar. First add another column to df +df$hline <- c(9,12) +# cond result hline +# control 10.0 9 +# treatment 11.5 12 + +# Need to re-specify bp, because the data has changed +bp <- ggplot(df, aes(x=cond, y=result)) + + geom_bar(position=position_dodge(), stat="identity") + +bp.err <- bp + + geom_errorbar(aes(y = hline, ymax = hline, ymin = hline), + colour = "#AA0000") +test_that("Draw with separate lines for each bar", { + expect_traces_shapes(bp.err, 2, 0, "bar-error-wide") +}) + +bp.err.narrow <- bp + + geom_errorbar(width = 0.5, aes(y = hline, ymax = hline, ymin = hline), + colour = "#AA0000") +test_that("Make the lines narrower", { + info <- expect_traces_shapes(bp.err.narrow, 2, 0, "bar-error-narrow") +}) + + +# Can get the same result, even if we get the hline values from a second data frame +# Define data frame with hline +df.hlines <- data.frame(cond=c("control","treatment"), hline=c(9,12)) +# cond hline +# control 9 +# treatment 12 + +bp.err.diff <- bp + + geom_errorbar(data = df.hlines, aes(y = hline, ymax = hline, ymin = hline), + colour = "#AA0000") +test_that("The bar graph are from df, but the lines are from df.hlines", { + info <- expect_traces_shapes(bp.err.diff, 2, 0, "bar-error-diff") +}) + +df <- read.table(header=T, text=" + cond group result hline + control A 10 9 +treatment A 11.5 12 + control B 12 9 +treatment B 14 12 +") +bp <- ggplot(df, aes(x = cond, y = result, fill = group)) + + geom_bar(position = position_dodge(), stat = "identity") +test_that("bar dodged colored -> 1 trace", { + info <- expect_traces_shapes(bp, 2, 0, "bar-dodge-color") +}) +bp.err <- + bp + geom_errorbar(aes(y = hline, ymax = hline, ymin = hline), + linetype = "dashed") +test_that("The error bars get plotted over one another", { + # there are four but it looks like two. + info <- expect_traces_shapes(bp.err, 3, 0, "bar-dodge-color-error") + err.y <- info$traces[[3]]$y + expect_equal(length(err.y), 4) + expect_equal(length(unique(err.y)), 2) +}) + +df <- read.table(header = TRUE, text = " + cond group result hline + control A 10 11 +treatment A 11.5 12 + control B 12 12.5 +treatment B 14 15 +") +bp <- ggplot(df, aes(x = cond, y = result, fill = group)) + + geom_bar(position = position_dodge(), stat = "identity") +bp.err4 <- bp + + geom_errorbar(aes(y = hline, ymax = hline, ymin = hline), + linetype = "dashed", position = position_dodge()) +test_that("4 error bars", { + info <- expect_traces_shapes(bp.err4, 3, 0, "bar-dodge-color-err4") + tr <- info$traces[[3]] + expect_equal(length(tr$y), 4) + expect_equal(length(unique(tr$y)), 4) + expect_equal(length(tr$x), 4) + expect_equal(length(unique(tr$x)), 2) +}) + +df <- read.table(header = T, text = " + cond xval yval + control 11.5 10.8 + control 9.3 12.9 + control 8.0 9.9 + control 11.5 10.1 + control 8.6 8.3 + control 9.9 9.5 + control 8.8 8.7 + control 11.7 10.1 + control 9.7 9.3 + control 9.8 12.0 + treatment 10.4 10.6 + treatment 12.1 8.6 + treatment 11.2 11.0 + treatment 10.0 8.8 + treatment 12.9 9.5 + treatment 9.1 10.0 + treatment 13.4 9.6 + treatment 11.6 9.8 + treatment 11.5 9.8 + treatment 12.0 10.6 +") +sp <- ggplot(df, aes(x = xval, y = yval, colour = cond)) + geom_point() +test_that("basic scatterplot", { + info <- expect_traces_shapes(sp, 2, 0, "scatter-basic") +}) + +temp <- sp + geom_hline(aes(yintercept=10)) +test_that("Add a horizontal line", { + info <- expect_traces_shapes(temp, 3, 0, "scatter-hline") +}) + +temp <- sp + + geom_hline(aes(yintercept = 10)) + + geom_vline(aes(xintercept = 11.5), + colour = "#BB0000", linetype = "dashed") +test_that("Add a red dashed vertical line", { + info <- expect_traces_shapes(temp, 4, 0, "scatter-hline-vline") + expect_true(info$layout$showlegend) + mode <- sapply(info$traces, "[[", "mode") + line.traces <- info$traces[mode == "lines"] + expect_equal(length(line.traces), 2) + dash <- sapply(line.traces, function(tr)tr$line$dash) + dash.traces <- line.traces[dash == "dash"] + expect_equal(length(dash.traces), 1) + dash.trace <- dash.traces[[1]] + expect_identical(dash.trace$line$color, toRGB("#BB0000")) +}) + +temp <- sp + geom_hline(aes(yintercept=10)) + + geom_line(stat="vline", xintercept="mean") +test_that("Add colored lines for the mean xval of each group", { + info <- expect_traces_shapes(temp, 5, 0, "scatter-hline-vline-stat") + expect_true(info$layout$showlegend) + mode <- sapply(info$traces, "[[", "mode") + line.traces <- info$traces[mode == "lines"] + expect_equal(length(line.traces), 3) + lines.by.name <- list() + for(tr in line.traces){ + expect_false(tr$showlegend) + if(is.character(tr$name)){ + lines.by.name[[tr$name]] <- tr + } + } + marker.traces <- info$traces[mode == "markers"] + for(tr in marker.traces){ + expect_true(tr$showlegend) + line.trace <- lines.by.name[[tr$name]] + expect_equal(range(line.trace$y), range(tr$y)) + } +}) + +# Facet, based on cond +spf <- sp + facet_grid(. ~ cond) +test_that("scatter facet -> 2 traces", { + info <- expect_traces_shapes(spf, 2, 0, "scatter-facet") + expect_true(info$traces[[1]]$xaxis != info$traces[[2]]$xaxis) + expect_true(info$traces[[1]]$yaxis == info$traces[[2]]$yaxis) +}) + +temp <- spf + geom_hline(aes(yintercept=10)) +test_that("geom_hline -> 2 more traces", { + info <- expect_traces_shapes(temp, 4, 0, "scatter-facet-hline") + expect_true(info$layout$showlegend) + has.name <- sapply(info$traces, function(tr)is.character(tr$name)) + named.traces <- info$traces[has.name] + expect_equal(length(named.traces), 2) +}) + +df.vlines <- data.frame(cond = levels(df$cond), xval = c(10,11.5)) +# cond xval +# control 10.0 +# treatment 11.5 + +spf.vline <- + spf + + geom_hline(aes(yintercept = 10)) + + geom_vline(aes(xintercept = xval), + data = df.vlines, + colour = "#990000", linetype = "dashed") +test_that("geom_vline -> 2 more traces", { + info <- expect_traces_shapes(spf.vline, 6, 0, "scatter-facet-hline-vline") +}) + +spf.line.stat <- + spf + + geom_hline(aes(yintercept=10)) + + geom_line(stat="vline", xintercept="mean") +test_that("geom_line -> 2 more traces", { + info <- + expect_traces_shapes(spf.line.stat, 6, 0, + "scatter-facet-hline-line-stat") + for(tr in info$traces){ + expected <- ifelse(tr$mode == "markers", TRUE, FALSE) + expect_identical(tr$showlegend, expected) + } +}) diff --git a/tests/testthat/test-ggplot-bar.R b/tests/testthat/test-ggplot-bar.R index 2922321f0e..05c87aa365 100644 --- a/tests/testthat/test-ggplot-bar.R +++ b/tests/testthat/test-ggplot-bar.R @@ -77,6 +77,7 @@ test_that("Very basic bar graph", { expect_null(tr$marker$color) expect_null(tr$marker$line$color) expect_null(tr$marker$line$width) + expect_false(tr$showlegend) } expect_null(info$layout$annotations) expect_false(info$layout$showlegend)