Skip to content

Commit 51e159b

Browse files
authored
Merge pull request #554 from jcheng5/joe/feature/crosstalk
Initial support for linked views and animation
2 parents 5b4606c + cc74660 commit 51e159b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+3028
-353
lines changed

.travis.yml

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ before_script:
3131
- Rscript -e 'if (length(find.package("devtools", quiet = TRUE)) == 0L) { install.packages("devtools", repos = "http://cran.rstudio.com") }'
3232
- Rscript -e 'devtools::update_packages("devtools", repos = "http://cran.rstudio.com")'
3333
- Rscript -e 'devtools::install_deps(repos = "http://cran.rstudio.com", dependencies = TRUE)'
34+
- Rscript -e 'devtools::install_github("rstudio/crosstalk", repos = "http://cran.rstudio.com")'
3435
- cd ..; rm -f *.tar.gz; R CMD build $R_PKG
3536
- R CMD INSTALL ${R_PKG}_*.tar.gz
3637

DESCRIPTION

+11-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Package: plotly
22
Title: Create Interactive Web Graphics via 'plotly.js'
3-
Version: 4.5.6
3+
Version: 4.5.6.9000
44
Authors@R: c(person("Carson", "Sievert", role = c("aut", "cre"),
55
email = "[email protected]"),
66
person("Chris", "Parmer", role = c("aut", "cph"),
@@ -30,12 +30,16 @@ Imports:
3030
digest,
3131
viridisLite,
3232
base64enc,
33-
htmlwidgets,
33+
htmltools,
34+
htmlwidgets (>= 0.8),
3435
tidyr,
36+
hexbin,
37+
RColorBrewer,
3538
dplyr,
3639
tibble,
37-
hexbin,
3840
lazyeval (>= 0.2.0),
41+
crosstalk,
42+
colourpicker,
3943
purrr
4044
Suggests:
4145
MASS,
@@ -46,14 +50,15 @@ Suggests:
4650
knitr,
4751
devtools,
4852
shiny (>= 0.14),
49-
htmltools,
5053
curl,
5154
rmarkdown,
52-
RColorBrewer,
5355
Rserve,
5456
RSclient,
5557
broom,
5658
webshot,
57-
listviewer
59+
listviewer,
60+
dendextend
5861
LazyData: true
62+
Remotes:
63+
rstudio/crosstalk
5964
RoxygenNote: 5.0.1

NAMESPACE

+16-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
S3method(arrange_,plotly)
44
S3method(distinct_,plotly)
5+
S3method(do_,plotly)
56
S3method(filter_,plotly)
7+
S3method(fortify,SharedData)
68
S3method(geom2trace,GeomBar)
79
S3method(geom2trace,GeomBlank)
810
S3method(geom2trace,GeomBoxplot)
@@ -14,13 +16,15 @@ S3method(geom2trace,GeomPolygon)
1416
S3method(geom2trace,GeomText)
1517
S3method(geom2trace,GeomTile)
1618
S3method(geom2trace,default)
19+
S3method(ggplot,plotly)
1720
S3method(ggplotly,ggmatrix)
1821
S3method(ggplotly,ggplot)
1922
S3method(ggplotly,plotly)
2023
S3method(group_by_,plotly)
2124
S3method(groups,plotly)
2225
S3method(layout,matrix)
2326
S3method(layout,plotly)
27+
S3method(layout,shiny.tag.list)
2428
S3method(mutate_,plotly)
2529
S3method(plotly_build,gg)
2630
S3method(plotly_build,list)
@@ -36,6 +40,7 @@ S3method(to_basic,GeomBoxplot)
3640
S3method(to_basic,GeomContour)
3741
S3method(to_basic,GeomDensity)
3842
S3method(to_basic,GeomDensity2d)
43+
S3method(to_basic,GeomDotplot)
3944
S3method(to_basic,GeomErrorbar)
4045
S3method(to_basic,GeomErrorbarh)
4146
S3method(to_basic,GeomHex)
@@ -82,6 +87,9 @@ export(add_segments)
8287
export(add_surface)
8388
export(add_text)
8489
export(add_trace)
90+
export(animation_button)
91+
export(animation_opts)
92+
export(animation_slider)
8593
export(arrange)
8694
export(arrange_)
8795
export(as.widget)
@@ -107,12 +115,14 @@ export(groups)
107115
export(hide_colorbar)
108116
export(hide_guides)
109117
export(hide_legend)
118+
export(highlight)
110119
export(knit_print.plotly_figure)
111120
export(last_plot)
112121
export(layout)
113122
export(mutate)
114123
export(mutate_)
115124
export(offline)
125+
export(plot_dendro)
116126
export(plot_geo)
117127
export(plot_ly)
118128
export(plot_mapbox)
@@ -146,6 +156,7 @@ export(transmute_)
146156
export(ungroup)
147157
import(ggplot2)
148158
importFrom(base64enc,base64encode)
159+
importFrom(colourpicker,colourWidget)
149160
importFrom(dplyr,arrange)
150161
importFrom(dplyr,arrange_)
151162
importFrom(dplyr,distinct)
@@ -171,8 +182,12 @@ importFrom(dplyr,transmute)
171182
importFrom(dplyr,transmute_)
172183
importFrom(dplyr,ungroup)
173184
importFrom(grDevices,col2rgb)
185+
importFrom(grDevices,extendrange)
174186
importFrom(graphics,layout)
187+
importFrom(htmltools,browsable)
188+
importFrom(htmltools,tagList)
175189
importFrom(htmlwidgets,createWidget)
190+
importFrom(htmlwidgets,onRender)
176191
importFrom(htmlwidgets,saveWidget)
177192
importFrom(htmlwidgets,shinyRenderWidget)
178193
importFrom(htmlwidgets,shinyWidgetOutput)
@@ -194,11 +209,10 @@ importFrom(lazyeval,is_lang)
194209
importFrom(magrittr,"%>%")
195210
importFrom(purrr,transpose)
196211
importFrom(stats,complete.cases)
212+
importFrom(stats,is.leaf)
197213
importFrom(stats,quantile)
198214
importFrom(stats,setNames)
199215
importFrom(tibble,as_tibble)
200-
importFrom(tidyr,gather)
201-
importFrom(tidyr,gather_)
202216
importFrom(tidyr,unnest)
203217
importFrom(utils,browseURL)
204218
importFrom(utils,data)

NEWS.md

+16
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,19 @@
1+
# 4.5.6.9000
2+
3+
## NEW FEATURES
4+
5+
* Added a significant amount of support for "multiple linked views". For some relatively basic examples, see the demos (the ones prefixed with "highlight" are most relevant) -- `demo(package = "plotly")`. For a more comprehensive overview, see <https://cpsievert.github.io/plotly_book/linking-views-without-shiny.html>
6+
* Added the `highlight()` function for configuring selection modes/sequences.
7+
* Added support for animation. For some relatively basic examples, see the examples section of `help(animation)`. For a more thorough overview, see <https://cpsievert.github.io/plotly_book/key-frame-animations.html>
8+
* Added a `frame` argument to `plot_ly()` for creating animations. Also added the `animation_opts()`, `animation_slider()`, and `animation_button()` functions for configuring animation defaults.
9+
* Added a `plot_dendro()` function for a quick and dirty interactive dendrogram with support for hierarchial selection. For more, see -- <https://cpsievert.github.io/plotly_book/linking-views-without-shiny.html#nested-selections>
10+
* Added some basic support for translating the `GeomDotplot` **ggplot2** geom.
11+
* Sensible sizing and positioning defaults are now provided for subplots multiple colorbars.
12+
13+
## CHANGES
14+
15+
* Upgraded to plotly.js v1.20.0 -- https://github.com/plotly/plotly.js/releases/tag/v1.20.0
16+
117
# 4.5.6
218

319
## NEW FEATURES

R/animate.R

+215
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
#' Animation configuration options
2+
#'
3+
#' Animations can be created by either using the \code{frame} argument in
4+
#' \code{\link{plot_ly}()} or the (unofficial) \code{frame} ggplot2 aesthetic in
5+
#' \code{\link{ggplotly}()}. By default, animations populate a play button
6+
#' and slider component for controlling the state of the animation
7+
#' (to pause an animation, click on a relevant location on the slider bar).
8+
#' Both the play button and slider component transition between frames according
9+
#' rules specified by \code{\link{animation_opts}()}.
10+
#'
11+
#' @param p a plotly object.
12+
#' @param frame The amount of time between frames (in milliseconds).
13+
#' Note that this amount should include the \code{transition}.
14+
#' @param transition The duration of the smooth transition between
15+
#' frames (in milliseconds).
16+
#' @param easing The type of transition easing. See the list of options here
17+
#' \url{https://github.com/plotly/plotly.js/blob/master/src/plots/animation_attributes.js}
18+
#' @param redraw Trigger a redraw of the plot at completion of the transition?
19+
#' A redraw may significantly impact performance, but may be necessary to
20+
#' update plot attributes that can't be transitioned.
21+
#' @param mode Describes how a new animate call interacts with currently-running
22+
#' animations. If `immediate`, current animations are interrupted and
23+
#' the new animation is started. If `next`, the current frame is allowed
24+
#' to complete, after which the new animation is started. If `afterall`
25+
#' all existing frames are animated to completion before the new animation
26+
#' is started.
27+
#' @export
28+
#' @rdname animation
29+
#' @author Carson Sievert
30+
#' @examples
31+
#'
32+
#' df <- data.frame(
33+
#' x = c(1, 2, 2, 1, 1, 2),
34+
#' y = c(1, 2, 2, 1, 1, 2),
35+
#' z = c(1, 1, 2, 2, 3, 3)
36+
#' )
37+
#' plot_ly(df) %>%
38+
#' add_markers(x = 1.5, y = 1.5) %>%
39+
#' add_markers(x = ~x, y = ~y, frame = ~z)
40+
#'
41+
#' # it's a good idea to remove smooth transitions when there is
42+
#' # no relationship between objects in each view
43+
#' plot_ly(mtcars, x = ~wt, y = ~mpg, frame = ~cyl) %>%
44+
#' animation_opts(transition = 0)
45+
#'
46+
#' # works the same way with ggplotly
47+
#' if (interactive()) {
48+
#' p <- ggplot(txhousing, aes(month, median)) +
49+
#' geom_line(aes(group = year), alpha = 0.3) +
50+
#' geom_smooth() +
51+
#' geom_line(aes(frame = year, ids = month), color = "red") +
52+
#' facet_wrap(~ city)
53+
#'
54+
#' ggplotly(p, width = 1200, height = 900) %>%
55+
#' animation_opts(1000)
56+
#' }
57+
#'
58+
#'
59+
#' #' # for more, see https://cpsievert.github.io/plotly_book/key-frame-animations.html
60+
#'
61+
animation_opts <- function(p, frame = 500, transition = frame, easing = "linear",
62+
redraw = FALSE, mode = "immediate") {
63+
if (frame < 0) {
64+
stop("frame must be non-negative.", call. = FALSE)
65+
}
66+
if (transition < 0) {
67+
stop("frame must be non-negative.", call. = FALSE)
68+
}
69+
if (frame < transition) {
70+
stop("frame must be larger than transition", call. = FALSE)
71+
}
72+
73+
opts <- list(
74+
transition = list(
75+
duration = transition,
76+
easing = match.arg(easing, easingOpts())
77+
),
78+
frame = list(
79+
duration = frame,
80+
redraw = redraw
81+
),
82+
mode = match.arg(mode, c('immediate', 'next', 'afterall'))
83+
)
84+
85+
# build step will ensure we can access the animation frames
86+
# (required to fill the steps in correctly)
87+
p <- plotly_build(p)
88+
89+
# overwrite the animation options in the slider/button spec
90+
supply_ani_slider(supply_ani_button(p, opts = opts), opts = opts)
91+
}
92+
93+
94+
#' @inheritParams animation_opts
95+
#' @param hide remove the animation slider?
96+
#' @param ... for \code{animation_slider}, attributes are passed to a special
97+
#' layout.sliders object tied to the animation frames.
98+
#' The definition of these attributes may be found here
99+
#' \url{https://github.com/plotly/plotly.js/blob/master/src/components/sliders/attributes.js}
100+
#' For \code{animation_button}, arguments are passed to a special
101+
#' layout.updatemenus button object tied to the animation
102+
#' \url{https://github.com/plotly/plotly.js/blob/master/src/components/updatemenus/attributes.js}
103+
#' @export
104+
#' @rdname animation
105+
animation_slider <- function(p, hide = FALSE, ...) {
106+
107+
p <- plotly_build(p)
108+
isAniSlider <- vapply(p$x$layout$sliders, is_ani_slider, logical(1))
109+
if (hide) {
110+
p$x$layout$sliders[isAniSlider] <- NULL
111+
return(p)
112+
}
113+
p$x$layout$sliders[[which(isAniSlider)]] <- modify_list(
114+
p$x$layout$sliders[[which(isAniSlider)]], list(...)
115+
)
116+
p
117+
118+
}
119+
120+
121+
#' @inheritParams animation_slider
122+
#' @export
123+
#' @rdname animation
124+
animation_button <- function(p, ...) {
125+
126+
p <- plotly_build(p)
127+
isAniButton <- vapply(p$x$layout$updatemenus, is_ani_button, logical(1))
128+
p$x$layout$updatemenus[[which(isAniButton)]] <- modify_list(
129+
p$x$layout$updatemenus[[which(isAniButton)]], list(...)
130+
)
131+
p
132+
}
133+
134+
135+
# supply an animation button if it doesn't exist,
136+
# and _replace_ an existing animation button
137+
supply_ani_button <- function(p, opts = NULL) {
138+
nmenus <- length(p$x$layout$updatemenus)
139+
isAniButton <- vapply(p$x$layout$updatemenus, is_ani_button, logical(1))
140+
idx <- if (sum(isAniButton) == 1) which(isAniButton) else nmenus + 1
141+
p$x$layout$updatemenus[[idx]] <- create_ani_button(opts)
142+
p
143+
}
144+
145+
create_ani_button <- function(opts) {
146+
button <- list(
147+
type = 'buttons',
148+
direction = 'right',
149+
showactive = FALSE,
150+
y = 0,
151+
x = 0,
152+
yanchor = 'top',
153+
xanchor = 'right',
154+
pad = list(t = 60, r = 5),
155+
buttons = list(list(
156+
label = 'Play',
157+
method = 'animate',
158+
args = list(list(), modify_list(list(fromcurrent = TRUE, mode = "immediate"), opts))
159+
))
160+
)
161+
structure(button, class = "aniButton")
162+
}
163+
164+
is_ani_button <- function(obj) {
165+
class(obj) %in% "aniButton"
166+
}
167+
168+
# supply an animation slider if it doesn't exist,
169+
# and _replace_ an existing animation slider
170+
supply_ani_slider <- function(p, opts = NULL, ...) {
171+
nsliders <- length(p$x$layout$sliders)
172+
isAniSlider <- vapply(p$x$layout$sliders, is_ani_slider, logical(1))
173+
hasAniSlider <- sum(isAniSlider) == 1
174+
idx <- if (hasAniSlider) which(isAniSlider) else nsliders + 1
175+
p$x$layout$sliders[[idx]] <- create_ani_slider(p, opts, ...)
176+
p
177+
}
178+
179+
180+
create_ani_slider <- function(p, opts = NULL, ...) {
181+
steps <- lapply(p$x$frames, function(f) {
182+
# frame names should already be formatted
183+
nm <- f[["name"]]
184+
args <- list(list(nm))
185+
args[[2]] <- opts
186+
list(method = "animate", args = args, label = nm, value = nm)
187+
})
188+
189+
# inherit defaults from any existing slider
190+
slider <- modify_list(
191+
p$x$layout$sliders[[vapply(p$x$layout$sliders, is_ani_slider, logical(1))]], list(...)
192+
)
193+
# don't let the user override steps
194+
slider$steps <- steps
195+
196+
# set some opinionated defaults
197+
slider$visible <- slider$visible %||% TRUE
198+
slider$pad$t <- slider$pad[["t"]] %||% 40
199+
structure(slider, class = "aniSlider")
200+
}
201+
202+
is_ani_slider <- function(obj) {
203+
class(obj) %in% "aniSlider"
204+
}
205+
206+
207+
easingOpts <- function() {
208+
c('linear', 'quad', 'cubic', 'sin', 'exp', 'circle', 'elastic', 'back',
209+
'bounce', 'linear-in', 'quad-in', 'cubic-in', 'sin-in', 'exp-in',
210+
'circle-in', 'elastic-in', 'back-in', 'bounce-in', 'linear-out',
211+
'quad-out', 'cubic-out', 'sin-out', 'exp-out', 'circle-out', 'elastic-out',
212+
'back-out', 'bounce-out', 'linear-in-out', 'quad-in-out', 'cubic-in-out',
213+
'sin-in-out', 'exp-in-out', 'circle-in-out', 'elastic-in-out',
214+
'back-in-out', 'bounce-in-out')
215+
}

0 commit comments

Comments
 (0)