Skip to content

Better subplot interface #526

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 37 commits into from
May 16, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
4521993
first stab at subplot rewrite
cpsievert Mar 19, 2016
888999e
Merge branch 'master' of github.com:ropensci/plotly into fix/subplot
cpsievert Mar 19, 2016
c7098c7
start thinking about shapes/annotations
cpsievert Mar 22, 2016
0dd4d63
reposition shapes/annotations
cpsievert Mar 24, 2016
72cb6b8
add meta-info to identify axis titles; simplify facet strip drawing l…
cpsievert Mar 24, 2016
b6fb58e
safeguard against missing info
cpsievert Mar 24, 2016
70ca674
safeguard against missing values in subplot; ggplotly is now generic …
cpsievert Mar 24, 2016
3fb6754
Merge branch 'master' of github.com:ropensci/plotly into fix/subplot
cpsievert Mar 25, 2016
d8f2f7a
add plot title to subplot
cpsievert Mar 31, 2016
ab2de06
merge with master
cpsievert Apr 1, 2016
a45749c
Implement widths/heights arguments
cpsievert Apr 5, 2016
a8c1628
add shareX/shareY arguments
cpsievert Apr 5, 2016
9e2d138
Merge branch 'master' of github.com:ropensci/plotly into fix/subplot
cpsievert Apr 5, 2016
c61675f
fix test typo
cpsievert Apr 5, 2016
f8eedaa
Merge branch 'master' of github.com:ropensci/plotly into fix/subplot
cpsievert Apr 18, 2016
4655d89
Merge branch 'master' of github.com:ropensci/plotly into fix/subplot
cpsievert Apr 26, 2016
193ba4e
Merge branch 'master' of github.com:ropensci/plotly into fix/subplot
cpsievert May 5, 2016
eb9c080
merge conflicts
cpsievert May 5, 2016
d10b5a9
make plotly_build a generic function
cpsievert May 5, 2016
975733d
add keep_titles argument
cpsievert May 6, 2016
98dcd96
traces with missing axis object references should generate new plots
cpsievert May 12, 2016
961e73d
Merge branch 'master' of github.com:ropensci/plotly into fix/subplot
cpsievert May 12, 2016
79650d7
better defaults; start on a vignette
cpsievert May 12, 2016
dee87c2
Remove keep_titles in favor of titleX/titleY
cpsievert May 12, 2016
f342c0b
better sizing defaults
cpsievert May 12, 2016
a978fbc
Merge branch 'master' of github.com:ropensci/plotly into fix/subplot
cpsievert May 13, 2016
ab93d5a
subplot now accepts a list of plots
cpsievert May 14, 2016
81bcdd5
don't draw blank or interior facet strip in grid layout; add ... argu…
cpsievert May 14, 2016
0020348
add to vignette; better check for list of plots
cpsievert May 14, 2016
8b15c00
make code a bit more readable; improve axis sharing logic
cpsievert May 16, 2016
88591f6
improve ggplotly.ggmatrix logic
cpsievert May 16, 2016
b881917
moar subplot tests
cpsievert May 16, 2016
35ae74a
supply defaults for geo
cpsievert May 16, 2016
957923c
cleanup
cpsievert May 16, 2016
80007ac
a few vignette edits
cpsievert May 16, 2016
f1eee2b
bump version; update news
cpsievert May 16, 2016
cd62ed5
fix typo; document
cpsievert May 16, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Package: plotly
Title: Create Interactive Web Graphics via 'plotly.js'
Version: 3.5.7
Version: 3.6.0
Authors@R: c(person("Carson", "Sievert", role = c("aut", "cre"),
email = "[email protected]"),
person("Chris", "Parmer", role = c("aut", "cph"),
Expand Down
6 changes: 6 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ S3method(geom2trace,GeomPolygon)
S3method(geom2trace,GeomText)
S3method(geom2trace,GeomTile)
S3method(geom2trace,default)
S3method(ggplotly,ggmatrix)
S3method(ggplotly,ggplot)
S3method(plotly_build,gg)
S3method(plotly_build,plotly_built)
S3method(plotly_build,plotly_hash)
S3method(plotly_build,plotly_subplot)
S3method(print,figure)
S3method(print,plotly_built)
S3method(print,plotly_hash)
Expand Down
17 changes: 17 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
3.6.0 -- 16 May 2016

NEW FEATURES & CHANGES:

* Many improvements to the subplot() function:
* ggplot2 objects are now officially supported (#520).
* Several new arguments allow one to synchronize x/y axes (#298), height/width (#376), hide/show x/y axis titles.
* A list of plots can now be passed to the first argument.
* A new vignette with examples and more explanation can be accessed via `vignette("subplot")`.

* ggplotly() is now a generic function with a method for ggmatrix objects.
* plotly_build() is now a generic function.

BUG FIX:

Column facet strips will no longer be drawn when there is only one column.

3.5.7 -- 13 May 2016

CHANGES:
Expand Down
111 changes: 76 additions & 35 deletions R/ggplotly.R
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#' \code{tooltip = c("y", "x", "colour")} if you want y first, x second, and
#' colour last.
#' @param source Only relevant for \link{event_data}.
#' @param ... arguments passed onto methods.
#' @seealso \link{signup}, \link{plot_ly}
#' @return a plotly object
#' @export
Expand All @@ -31,7 +32,46 @@
#' }
#'
ggplotly <- function(p = ggplot2::last_plot(), width = NULL, height = NULL,
tooltip = "all", source = "A") {
tooltip = "all", source = "A", ...) {
UseMethod("ggplotly", p)
}

#' @export
ggplotly.ggmatrix <- function(p = ggplot2::last_plot(), width = NULL,
height = NULL, tooltip = "all", source = "A", ...) {
subplotList <- list()
for (i in seq_len(p$ncol)) {
columnList <- list()
for (j in seq_len(p$nrow)) {
thisPlot <- p[j, i]
if (i == 1) {
if (p$showYAxisPlotLabels) thisPlot <- thisPlot + ylab(p$yAxisLabels[j])
} else {
# y-axes are never drawn on the interior, and diagonal plots are densities,
# so it doesn't make sense to synch zoom actions on y
thisPlot <- thisPlot +
theme(
axis.ticks.y = element_blank(),
axis.text.y = element_blank()
)
}
columnList <- c(columnList, list(ggplotly(thisPlot, tooltip = tooltip)))
}
# conditioned on a column in a ggmatrix, the x-axis should be on the
# same scale.
s <- subplot(columnList, nrows = p$nrow, margin = 0.01, shareX = TRUE, titleY = TRUE)
subplotList <- c(subplotList, list(s))
}
s <- layout(subplot(subplotList, nrows = 1), width = width, height = height)
if (nchar(p$title) > 0) {
s <- layout(s, title = p$title)
}
hash_plot(p$data, plotly_build(s))
}

#' @export
ggplotly.ggplot <- function(p = ggplot2::last_plot(), width = NULL,
height = NULL, tooltip = "all", source = "A", ...) {
l <- gg2list(p, width = width, height = height, tooltip = tooltip, source = source)
hash_plot(p$data, l)
}
Expand All @@ -44,9 +84,10 @@ ggplotly <- function(p = ggplot2::last_plot(), width = NULL, height = NULL,
#' tooltip. The default, "all", means show all the aesthetic tooltips
#' (including the unofficial "text" aesthetic).
#' @param source Only relevant for \link{event_data}.
#' @param ... currently not used
#' @return a 'built' plotly object (list with names "data" and "layout").
#' @export
gg2list <- function(p, width = NULL, height = NULL, tooltip = "all", source = "A") {
gg2list <- function(p, width = NULL, height = NULL, tooltip = "all", source = "A", ...) {
# ------------------------------------------------------------------------
# Our internal version of ggplot2::ggplot_build(). Modified from
# https://github.com/hadley/ggplot2/blob/0cd0ba/R/plot-build.r#L18-L92
Expand Down Expand Up @@ -425,55 +466,55 @@ gg2list <- function(p, width = NULL, height = NULL, tooltip = "all", source = "A
gglayout$annotations,
make_label(
faced(axisTitleText, axisTitle$face), x, y, el = axisTitle,
xanchor = "center", yanchor = "middle"
xanchor = "center", yanchor = "middle", annotationType = "axis"
)
)
}
}
}

if (has_facet(p)) {
gglayout[[axisName]]$title <- ""
}

if (has_facet(p)) gglayout[[axisName]]$title <- ""
} # end of axis loop

# theme(panel.border = ) -> plotly rect shape
xdom <- gglayout[[lay[, "xaxis"]]]$domain
ydom <- gglayout[[lay[, "yaxis"]]]$domain
border <- make_panel_border(xdom, ydom, theme)
gglayout$shapes <- c(gglayout$shapes, border)

# facet strips -> plotly annotations
if (!is_blank(theme[["strip.text.x"]]) &&
(inherits(p$facet, "wrap") || inherits(p$facet, "grid") && lay$ROW == 1)) {
vars <- ifelse(inherits(p$facet, "wrap"), "facets", "cols")
txt <- paste(
p$facet$labeller(lay[names(p$facet[[vars]])]), collapse = ", "
if (has_facet(p)) {
col_vars <- ifelse(inherits(p$facet, "wrap"), "facets", "cols")
col_txt <- paste(
p$facet$labeller(lay[names(p$facet[[col_vars]])]), collapse = ", "
)
lab <- make_label(
txt, x = mean(xdom), y = max(ydom),
el = theme[["strip.text.x"]] %||% theme[["strip.text"]],
xanchor = "center", yanchor = "bottom"
)
gglayout$annotations <- c(gglayout$annotations, lab)
strip <- make_strip_rect(xdom, ydom, theme, "top")
gglayout$shapes <- c(gglayout$shapes, strip)
}
if (inherits(p$facet, "grid") && lay$COL == nCols && nRows > 1 &&
!is_blank(theme[["strip.text.y"]])) {
txt <- paste(
if (is_blank(theme[["strip.text.x"]])) col_txt <- ""
if (inherits(p$facet, "grid") && lay$ROW != 1) col_txt <- ""
if (nchar(col_txt) > 0) {
col_lab <- make_label(
col_txt, x = mean(xdom), y = max(ydom),
el = theme[["strip.text.x"]] %||% theme[["strip.text"]],
xanchor = "center", yanchor = "bottom"
)
gglayout$annotations <- c(gglayout$annotations, col_lab)
strip <- make_strip_rect(xdom, ydom, theme, "top")
gglayout$shapes <- c(gglayout$shapes, strip)
}
row_txt <- paste(
p$facet$labeller(lay[names(p$facet$rows)]), collapse = ", "
)
lab <- make_label(
txt, x = max(xdom), y = mean(ydom),
el = theme[["strip.text.y"]] %||% theme[["strip.text"]],
xanchor = "left", yanchor = "middle"
)
gglayout$annotations <- c(gglayout$annotations, lab)
strip <- make_strip_rect(xdom, ydom, theme, "right")
gglayout$shapes <- c(gglayout$shapes, strip)
if (is_blank(theme[["strip.text.y"]])) row_txt <- ""
if (inherits(p$facet, "grid") && lay$COL != nCols) row_txt <- ""
if (nchar(row_txt) > 0) {
row_lab <- make_label(
row_txt, x = max(xdom), y = mean(ydom),
el = theme[["strip.text.y"]] %||% theme[["strip.text"]],
xanchor = "left", yanchor = "middle"
)
gglayout$annotations <- c(gglayout$annotations, row_lab)
strip <- make_strip_rect(xdom, ydom, theme, "right")
gglayout$shapes <- c(gglayout$shapes, strip)
}
}

} # end of panel loop

# ------------------------------------------------------------------------
Expand Down
51 changes: 41 additions & 10 deletions R/plotly.R
Original file line number Diff line number Diff line change
Expand Up @@ -221,20 +221,51 @@ style <- function(p = last_plot(), ..., traces = 1, evaluate = FALSE) {
hash_plot(data, p)
}

#' Build a plotly object before viewing it
#' Create a 'plotly_built' object
#'
#' For convenience and efficiency purposes, plotly objects are subject to lazy
#' evaluation. That is, the actual content behind a plotly object is not
#' created until it is absolutely necessary. In some instances, you may want
#' to perform this evaluation yourself, and work directly with the resulting
#' list.
#' This generic function creates the list object sent to plotly.js
#' for rendering. Using this function can be useful for overriding defaults
#' provided by \code{ggplotly}/\code{plot_ly} or for debugging rendering
#' errors.
#'
#' @param l a ggplot object, or a plotly object, or a list.
#' @param l a ggplot object, or a plotly_hash object, or a list.
#' @export
#' @examples
#'
#' p <- plot_ly()
#' # data frame
#' str(p)
#' # the actual list of options sent to plotly.js
#' str(plotly_build(p))
#'
#' p <- qplot(data = mtcars, wt, mpg, geom = c("point", "smooth"))
#' l <- plotly_build(p)
#' # turn off hoverinfo for the smooth (but keep it for the points)
#' l$data[[2]]$hoverinfo <- "none"
#' l$data[[3]]$hoverinfo <- "none"
#' l
#'
plotly_build <- function(l = last_plot()) {
#if (inherits(l, "ggmatrix"))
# ggplot objects don't need any special type of handling
if (ggplot2::is.ggplot(l)) return(gg2list(l))
UseMethod("plotly_build")
}

#' @export
plotly_build.plotly_built <- function(l = last_plot()) {
l
}

#' @export
plotly_build.plotly_subplot <- function(l = last_plot()) {
prefix_class(get_plot(l), "plotly_built")
}

#' @export
plotly_build.gg <- function(l = last_plot()) {
prefix_class(get_plot(ggplotly(l)), "plotly_built")
}

#' @export
plotly_build.plotly_hash <- function(l = last_plot()) {
l <- get_plot(l)
# assume unnamed list elements are data/traces
nms <- names(l)
Expand Down
Loading