Skip to content

Commit d705371

Browse files
committed
provide scaling escape hatch via I(); add alpha argument; get missing values + group_by() working; various bug fixes and tests
1 parent 77d47ed commit d705371

15 files changed

+583
-282
lines changed

NEWS

+10-8
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,25 @@ BREAKING CHANGES & IMPROVEMENTS:
55
* Formulas (instead of plain expressions) are now required when using variable mappings. For example, `plot_ly(mtcars, x = wt, y = mpg, color = vs)` should now be `plot_ly(mtcars, x = ~wt, y = ~mpg, color = ~vs)`. This is a major breaking change, but it is necessary to ensure that evaluation is correct in all contexts (as a result, `evaluate` argument is now deprecated as it is no longer needed). It also has the benefit of being easier to program with (i.e., writing your own custom functions that wrap `plot_ly()`). For more details, see the [lazyeval vignette](https://github.com/hadley/lazyeval/blob/master/vignettes/lazyeval.Rmd)
66
* The data structure used to represent plotly objects is now an htmlwidget object (instead of a data frame with a special attribute tracking visual mappings). As a result, the `as.widget()` function has been deprecated, and [serialization/memory leak problems](https://github.com/rstudio/shiny/issues/1151) are no longer an issue. This change also implies that arbitrary data manipulation functions can no longer be intermingled inside a plot pipeline, but plotly methods for dplyr's data manipulation verbs are now provided (see `?plotly_data` for examples).
77
* The `group` variable mapping no longer create multiple traces, but instead defines "gaps" within a trace (fixes #418, #381, #577). Groupings should be declared via the new `group_by()` function (see `help(plotly_data)` for examples) instead of the `group` argument (which is now deprecated).
8-
* `plot_ly()` now _initializes_ a plotly object (i.e., won't add a scatter trace by default), meaning that something like `plot_ly(x = 1:10, y = 1:10) %>% add_trace(y = 10:1)` will create one trace, instead of two. However, `plot_ly()` will draw a trace if you manually specify a trace type, for example, `plot_ly(x = 1:10, y = 1:10, type = "scatter") %>% add_trace(y = 10:1)` will draw two scatter traces. If no trace type is provided, a sensible type is inferred from the supplied data, and automatically added (i.e., `plot_ly(x = rnorm(100))` now creates a histogram).
8+
* `plot_ly()` now _initializes_ a plotly object (i.e., won't add a scatter trace by default), meaning that something like `plot_ly(x = 1:10, y = 1:10) %>% add_trace(y = 10:1)` creates one trace, instead of two. That being said, if you manually specify a trace type in `plot_ly()`, it will add a layer with that trace type (e.g. `plot_ly(x = 1:10, y = 1:10, type = "scatter") %>% add_trace(y = 10:1)` draws two scatter traces). If no trace type is provided, a sensible type is inferred from the supplied data, and automatically added (i.e., `plot_ly(x = rnorm(100))` now creates a histogram).
99
* The `inherit` argument is deprecated. Any arguments/attributes specified in `plot_ly()` will automatically be passed along to additional traces added via `add_trace()` (or any of it's `add_*()` siblings).
10-
* Scales for aesthetics (e.g., `color`, `symbol`, `linetype`) are now applied at the plot-level, instead of the trace level. To avoid scaling, wrap the aesthetic value with `I()`, for example: `plot_ly(x = 1:10, y = 1:10, color = I("red"))`.
10+
* Aesthetic scaling (e.g., `color`, `symbol`, `size`) is applied at the plot-level, instead of the trace level.
1111

1212
NEW FEATURES & IMPROVEMENTS:
1313

14-
* Error checking for trace and layout attributes.
15-
* New `add_polygons()`/`add_ribbons()`/`add_area()`/`add_segments()`/`add_lines()`/`add_markers()`/`add_paths()`/`add_text()` functions provide a shorthand for common visual markings.
16-
* New `plotly_data()` function for returning/inspecting data frame(s) associated with a plotly object.
17-
* New `plotly_json()` function for inspecting the data sent to plotly.js (as an R list or JSON).
18-
* Added `linetype`/`linetypes` arguments for mapping discrete variables to line types (this works very much like the `symbol`/`symbols`).
14+
* Added `linetype`/`linetypes` arguments for mapping discrete variables to line types (works very much like the `symbol`/`symbols`).
15+
* Scaling for aesthetics can be avoided via `I()` (closes #428). This is mainly useful for changing default appearance (e.g. `plot_ly(x = 1:10, y = 1:10, color = I(rainbow(10)))`).
16+
* Symbols and linetypes now recognize `pch` and `lty` values (e.g. `plot_ly(x = 1:25, y = 1:25, symbol = I(0:24))`)
17+
* A new `alpha` argument controls the alpha transparency of `color` (e.g. `plot_ly(x = 1:10, y = 1:10, color = I("red"), alpha = 0.1)`).
1918
* Added a `sizes` argument for controlling the range of marker size scaling.
19+
* New `add_polygons()`/`add_ribbons()`/`add_area()`/`add_segments()`/`add_lines()`/`add_markers()`/`add_paths()`/`add_text()` functions provide a shorthand for common special cases of `add_trace()`.
2020
* New `toWebGL()` function for easy conversion from SVG to WebGL.
2121
* New `export()` function makes it easy to save plots as png/jpeg/pdf (fixes #311).
22+
* Misspecified trace/layout attributes produce a warning.
23+
* New `plotly_data()` function for returning/inspecting data frame(s) associated with a plotly object.
24+
* New `plotly_json()` function for inspecting the data sent to plotly.js (as an R list or JSON).
2225
* `layout()` is now a generic function and uses method dispatch to avoid conflicts with `graphics:layout()` (fixes #464).
2326

24-
2527
OTHER CHANGES:
2628

2729
* Upgraded to plotly.js v1.14.1 -- https://github.com/plotly/plotly.js/releases/tag/v1.14.1

R/add.R

+21-34
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ add_data <- function(p, data = NULL) {
3030
#' @param ymin a variable used to define the lower boundary of a polygon.
3131
#' @param ymax a variable used to define the upper boundary of a polygon.
3232
#' @param color Either a variable name or a vector to use for color mapping.
33+
#' @param alpha alpha channel applied to color (on 0-1 scale).
3334
#' @param symbol Either a variable name or a (discrete) vector to use for symbol encoding.
3435
#' @param size A variable name or numeric vector to encode the size of markers.
3536
#' @param linetype Either a variable name or a (discrete) vector to use for linetype encoding.
@@ -84,34 +85,26 @@ add_trace <- function(p, ...,
8485

8586
#' @inheritParams add_trace
8687
#' @rdname add_trace
87-
#' @param fill fill color. Supplies defaults for
88-
#' \url{https://plot.ly/r/reference/#scatter-marker-color}
89-
#' @param stroke stroke color. Supplies defaults for
90-
#' \url{https://plot.ly/r/reference/#scatter-marker-line-color}
91-
#' @param alpha alpha channel for fill/stroke (on 0-1 scale).
9288
#' @export
93-
add_markers <- function(p, x = NULL, y = NULL, fill = "rgba(31, 119, 180, 1)",
94-
stroke = "rgba(31, 119, 180, 1)", alpha = 1, ...) {
89+
add_markers <- function(p, x = NULL, y = NULL, z = NULL, ...) {
9590
if (is.null(x <- x %||% p$x$attrs[[1]][["x"]])) {
9691
stop("Must supply `x` attribute", call. = FALSE)
9792
}
9893
if (is.null(y <- y %||% p$x$attrs[[1]][["y"]])) {
9994
stop("Must supply `y` attribute", call. = FALSE)
10095
}
101-
# TODO: should stroke/fill inherit from the top-level?
102-
marker <- modify_list(p$x$attrs[[1]][["marker"]], list(...)[["marker"]])
103-
marker <- modify_list(marker, list(color = toRGB(fill, alpha)))
104-
marker$line <- modify_list(marker$line, list(color = toRGB(stroke, alpha)))
96+
hasZ <- !is.null(z <- z %||% p$x$attrs[[1]][["z"]])
97+
type <- if (hasZ) "scatter3d" else "scatter"
10598
add_trace(
106-
p, x = x, y = y, marker = marker, type = "scatter", mode = "markers", ...
99+
p, x = x, y = y, type = type, mode = "markers", ...
107100
)
108101
}
109102

110103

111104
#' @inheritParams add_trace
112105
#' @rdname add_trace
113106
#' @export
114-
add_text <- function(p, x = NULL, y = NULL, text = NULL, ...) {
107+
add_text <- function(p, x = NULL, y = NULL, z = NULL, text = NULL, ...) {
115108
if (is.null(x <- x %||% p$x$attrs[[1]][["x"]])) {
116109
stop("Must supply `x` attribute", call. = FALSE)
117110
}
@@ -121,22 +114,26 @@ add_text <- function(p, x = NULL, y = NULL, text = NULL, ...) {
121114
if (is.null(text <- text %||% p$x$attrs[[1]][["text"]])) {
122115
stop("Must supply `text` attribute", call. = FALSE)
123116
}
124-
add_trace(p, x = x, y = y, text = text, type = "scatter", mode = "text", ...)
117+
hasZ <- !is.null(z <- z %||% p$x$attrs[[1]][["z"]])
118+
type <- if (hasZ) "scatter3d" else "scatter"
119+
add_trace(p, x = x, y = y, text = text, type = type, mode = "text", ...)
125120
}
126121

127122

128123
#' @inheritParams add_trace
129124
#' @rdname add_trace
130125
#' @export
131-
add_paths <- function(p, x = NULL, y = NULL, ...) {
126+
add_paths <- function(p, x = NULL, y = NULL, z = NULL, ...) {
132127
if (is.null(x <- x %||% p$x$attrs[[1]][["x"]])) {
133128
stop("Must supply `x` attribute", call. = FALSE)
134129
}
135130
if (is.null(y <- y %||% p$x$attrs[[1]][["y"]])) {
136131
stop("Must supply `y` attribute", call. = FALSE)
137132
}
133+
hasZ <- !is.null(z <- z %||% p$x$attrs[[1]][["z"]])
134+
type <- if (hasZ) "scatter3d" else "scatter"
138135
add_trace_classed(
139-
p, x = x, y = y, class = "plotly_path", type = "scatter", mode = "lines", ...
136+
p, x = x, y = y, class = "plotly_path", type = type, mode = "lines", ...
140137
)
141138
}
142139

@@ -148,19 +145,18 @@ add_paths <- function(p, x = NULL, y = NULL, ...) {
148145
#' group_by(city) %>%
149146
#' plot_ly(x = ~date, y = ~median) %>%
150147
#' add_lines(fill = "black")
151-
add_lines <- function(p, x = NULL, y = NULL, stroke = NULL, alpha = 1, ...) {
148+
add_lines <- function(p, x = NULL, y = NULL, z = NULL, ...) {
152149
if (is.null(x <- x %||% p$x$attrs[[1]][["x"]])) {
153150
stop("Must supply `x` attribute", call. = FALSE)
154151
}
155152
if (is.null(y <- y %||% p$x$attrs[[1]][["y"]])) {
156153
stop("Must supply `y` attribute", call. = FALSE)
157154
}
158-
159-
line <- modify_list(p$x$attrs[[1]][["line"]], list(...)[["line"]])
160-
line <- modify_list(line, list(color = toRGB(stroke, alpha)))
155+
hasZ <- !is.null(z <- z %||% p$x$attrs[[1]][["z"]])
156+
type <- if (hasZ) "scatter3d" else "scatter"
161157

162158
add_trace_classed(
163-
p, x = x, y = y, line = line, class = "plotly_line", type = "scatter", mode = "lines", ...
159+
p, x = x, y = y, class = "plotly_line", type = type, mode = "lines", ...
164160
)
165161
}
166162

@@ -223,9 +219,7 @@ add_polygons <- function(p, x = NULL, y = NULL, ...) {
223219
#' plot_ly(economics, x = ~date) %>%
224220
#' add_ribbons(ymin = ~pce - 1e3, ymax = ~pce + 1e3)
225221

226-
add_ribbons <- function(p, x = NULL, ymin = NULL, ymax = NULL,
227-
fill = "rgba(31, 119, 180, 1)",
228-
stroke = "transparent", alpha = 1, ...) {
222+
add_ribbons <- function(p, x = NULL, ymin = NULL, ymax = NULL, ...) {
229223
if (is.null(x <- x %||% p$x$attrs[[1]][["x"]])) {
230224
stop("Must supply `x` attribute", call. = FALSE)
231225
}
@@ -235,15 +229,11 @@ add_ribbons <- function(p, x = NULL, ymin = NULL, ymax = NULL,
235229
if (is.null(ymax <- ymax %||% p$x$attrs[[1]][["ymax"]])) {
236230
stop("Must supply `ymax` attribute", call. = FALSE)
237231
}
238-
# TODO: should stroke/fill inherit from the top-level?
239-
# probably not, since fill conflicts with scatter's fill attribute!
240-
line <- modify_list(p$x$attrs[[1]][["line"]], list(...)[["line"]])
241-
line <- modify_list(line, list(color = toRGB(stroke, alpha)))
242232

243233
add_trace_classed(
244234
p, class = c("plotly_ribbon", "plotly_polygon"),
245235
x = x, ymin = ymin, ymax = ymax, type = "scatter", mode = "lines",
246-
line = line, fillcolor = toRGB(fill, alpha), fill = "toself", ...
236+
fill = "toself", ...
247237
)
248238
}
249239

@@ -426,9 +416,9 @@ add_surface <- function(p, z = NULL, ...) {
426416
#' @export
427417
#' @examples
428418
#' plot_ly() %>% add_scattergeo()
429-
add_scattergeo <- function(p, ...) {
419+
add_scattergeo <- function(p, geo = "geo", ...) {
430420
add_trace_classed(
431-
p, class = "plotly_scattergeo", type = "scattergeo", ...
421+
p, class = "plotly_scattergeo", type = "scattergeo", geo = geo, ...
432422
)
433423
}
434424

@@ -450,9 +440,6 @@ add_choropleth <- function(p, z = NULL, ...) {
450440
)
451441
}
452442

453-
454-
455-
456443
# attach a class to a trace which informs data processing in plotly_build
457444
add_trace_classed <- function(p, class = "plotly_polygon", ...) {
458445
p <- add_trace(p, ...)

R/layers2traces.R

+12-2
Original file line numberDiff line numberDiff line change
@@ -723,7 +723,12 @@ pch2symbol <- function(x) {
723723
"O" = "circle-open",
724724
"+" = "cross-thin-open"
725725
)
726-
as.character(lookup[as.character(x)])
726+
x <- as.character(x)
727+
idx <- x %in% names(lookup)
728+
if (any(idx)) {
729+
x[idx] <- lookup[x[idx]]
730+
}
731+
as.character(x)
727732
}
728733

729734
# Convert R lty line type codes to plotly "dash" codes.
@@ -756,5 +761,10 @@ lty2dash <- function(x) {
756761
"224282F2" = "dash",
757762
"F1" = "dash"
758763
)
759-
as.character(lookup[as.character(x)])
764+
x <- as.character(x)
765+
idx <- x %in% names(lookup)
766+
if (any(idx)) {
767+
x[idx] <- lookup[x[idx]]
768+
}
769+
as.character(x)
760770
}

R/plotly.R

+21-8
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,25 @@
1010
#' Note that acceptable arguments depend on the value of \code{type}.
1111
#' @param type A character string describing the type of trace. If \code{NULL}
1212
#' (the default), the initial trace type is determined by \code{add_trace}
13-
#' @param color Either a variable name or a vector to use for color mapping.
13+
#' @param color A formula containing a name or expression.
14+
#' Values are scaled and mapped to color codes based on the value of
15+
#' \code{colors} and \code{alpha}. To avoid scaling, wrap with \code{\link{I}()},
16+
#' and provide value(s) that can be converted to rgb color codes by
17+
#' \code{\link[grDevices]{col2rgb}()}.
1418
#' @param colors Either a colorbrewer2.org palette name (e.g. "YlOrRd" or "Blues"),
1519
#' or a vector of colors to interpolate in hexadecimal "#RRGGBB" format,
1620
#' or a color interpolation function like \code{colorRamp()}.
17-
#' @param symbol Either a variable name or a (discrete) vector to use for symbol encoding.
21+
#' @param alpha A number between 0 and 1 specifying the alpha channel applied to color.
22+
#' @param symbol A formula containing a name or expression.
23+
#' Values are scaled and mapped to symbols based on the value of \code{symbols}.
24+
#' To avoid scaling, wrap with \code{\link{I}()}, and provide valid
25+
#' \code{\link{pch}()} values and/or valid plotly symbol(s) as a string
26+
#' \url{}
1827
#' @param symbols A character vector of symbol types. For possible values, see \link{schema}.
1928
#' @param linetype Either a variable name or a (discrete) vector to use for linetype encoding.
2029
#' @param linetypes A character vector of line types. For possible values, see \link{schema}.
2130
#' @param size A variable name or numeric vector to encode the size of markers.
22-
#' @param sizes a numeric vector of length 2 used to scale sizes to pixels.
31+
#' @param sizes A numeric vector of length 2 used to scale sizes to pixels.
2332
#' @param width Width in pixels (optional, defaults to automatic sizing).
2433
#' @param height Height in pixels (optional, defaults to automatic sizing).
2534
#' @param source Only relevant for \link{event_data}.
@@ -35,15 +44,15 @@
3544
#' plot_ly(economics, x = ~date, y = ~pop)
3645
#' # plot_ly() doesn't require data frame(s), which allows one to take
3746
#' # advantage of trace type(s) designed specifically for numeric matrices
38-
#' plot_ly(z = volcano)
39-
#' plot_ly(z = volcano, type = "surface")
47+
#' plot_ly(z = ~volcano)
48+
#' plot_ly(z = ~volcano, type = "surface")
4049
#'
4150
#' # plotly has a functional interface: every plotly function takes a plotly
4251
#' # object as it's first input argument and returns a modified plotly object
43-
#' add_points(plot_ly(economics, x = ~date, y = ~unemploy/pop))
52+
#' add_lines(plot_ly(economics, x = ~date, y = ~unemploy/pop))
4453
#'
4554
#' # To make code more readable, plotly imports the pipe operator from magrittr
46-
#' economics %>% plot_ly(x = ~date, y = ~unemploy/pop) %>% add_points()
55+
#' economics %>% plot_ly(x = ~date, y = ~unemploy/pop) %>% add_lines()
4756
#'
4857
#' # Attributes defined via plot_ly() set 'global' attributes that
4958
#' # are carried onto subsequent traces
@@ -66,9 +75,12 @@
6675
#' }
6776
#'
6877
plot_ly <- function(data = data.frame(), ..., type = NULL, group,
69-
color, colors = NULL, symbol, symbols = NULL,
78+
color, colors = NULL, alpha = 1, symbol, symbols = NULL,
7079
size, sizes = c(10, 100), linetype, linetypes = NULL,
7180
width = NULL, height = NULL, source = "A") {
81+
if (!is.data.frame(data)) {
82+
stop("First argument, `data`, must be a data frame.", call. = FALSE)
83+
}
7284
# "native" plotly arguments
7385
attrs <- list(...)
7486
# warn about old arguments that are no longer supported
@@ -96,6 +108,7 @@ plot_ly <- function(data = data.frame(), ..., type = NULL, group,
96108
attrs$size <- if (!missing(size)) size
97109

98110
attrs$colors <- colors
111+
attrs$alpha <- alpha
99112
attrs$symbols <- symbols
100113
attrs$linetypes <- linetypes
101114
attrs$sizes <- sizes

0 commit comments

Comments
 (0)