Skip to content

Commit 63608e5

Browse files
committed
Merge pull request #416 from ropensci/feature/transmit
Send 'plotly_click' and 'plotly_selected' events to shiny when in shinyMode
2 parents b79e9d2 + 8c5cd8e commit 63608e5

File tree

21 files changed

+551
-121
lines changed

21 files changed

+551
-121
lines changed

DESCRIPTION

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Package: plotly
22
Title: Create Interactive Web Graphics via Plotly's JavaScript Graphing Library
3-
Version: 2.4.4
3+
Version: 2.5.0
44
Authors@R: c(person("Carson", "Sievert", role = c("aut", "cre"),
55
email = "[email protected]"),
66
person("Chris", "Parmer", role = c("aut", "cph"),

NAMESPACE

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export(add_trace)
77
export(as.widget)
88
export(config)
99
export(embed_notebook)
10+
export(event_data)
1011
export(get_figure)
1112
export(gg2list)
1213
export(ggplot_build2)

NEWS

+16
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,19 @@
1+
2.5.0 -- 1 Mar 2015
2+
3+
NEW FEATURES
4+
5+
* New event_data() function provides easy access to plotly events in shiny.
6+
For an example, see https://github.com/ropensci/plotly/tree/master/inst/examples/plotlyEvents
7+
8+
* plot_ly() and ggplotly() gain a source argument to differentiate between
9+
plotly events in shiny apps with multiple plots. ggplotly() also gains width
10+
and height arguments.
11+
12+
CHANGES
13+
14+
The arguments filename, fileopt, world_readable in ggplotly() were removed as
15+
they should be provided to plotly_POST() instead.
16+
117
2.4.4 -- 13 Feb 2015
218

319
as.widget() now returns htmlwidget objects untouched. See #449.

R/ggplotly.R

+13-18
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,9 @@
44
#' \url{https://plot.ly/ggplot2}
55
#'
66
#' @param p a ggplot object.
7-
#' @param filename character string describing the name of the plot in your plotly account.
8-
#' Use / to specify directories. If a directory path does not exist it will be created.
9-
#' If this argument is not specified and the title of the plot exists,
10-
#' that will be used for the filename.
11-
#' @param fileopt character string describing whether to create a "new" plotly, "overwrite" an existing plotly,
12-
#' "append" data to existing plotly, or "extend" it.
13-
#' @param world_readable logical. If \code{TRUE}, the graph is viewable
14-
#' by anyone who has the link and in the owner's plotly account.
15-
#' If \code{FALSE}, graph is only viewable in the owner's plotly account.
7+
#' @param width Width of the plot in pixels (optional, defaults to automatic sizing).
8+
#' @param height Height of the plot in pixels (optional, defaults to automatic sizing).
9+
#' @param source Only relevant for \link{event_data}.
1610
#' @seealso \link{signup}, \link{plot_ly}
1711
#' @import httr jsonlite
1812
#' @export
@@ -32,13 +26,9 @@
3226
#' ggplotly(viz)
3327
#' }
3428
#'
35-
ggplotly <- function(p = ggplot2::last_plot(), filename, fileopt,
36-
world_readable = TRUE) {
37-
l <- gg2list(p)
38-
# tack on special keyword arguments
39-
if (!missing(filename)) l$filename <- filename
40-
if (!missing(fileopt)) l$fileopt <- fileopt
41-
l$world_readable <- world_readable
29+
ggplotly <- function(p = ggplot2::last_plot(), width = NULL, height = NULL,
30+
source = "A") {
31+
l <- gg2list(p, width = width, height = height, source = source)
4232
hash_plot(p$data, l)
4333
}
4434

@@ -116,9 +106,12 @@ guide_names <- function(p, aes = c("shape", "fill", "alpha", "area",
116106
#' Convert a ggplot to a list.
117107
#' @import ggplot2
118108
#' @param p ggplot2 plot.
109+
#' @param width Width of the plot in pixels (optional, defaults to automatic sizing).
110+
#' @param height Height of the plot in pixels (optional, defaults to automatic sizing).
111+
#' @param source Only relevant for \link{event_data}.
119112
#' @return figure object (list with names "data" and "layout").
120113
#' @export
121-
gg2list <- function(p) {
114+
gg2list <- function(p, width = NULL, height = NULL, source = "A") {
122115
# ggplot now applies geom_blank() (instead of erroring) when no layers exist
123116
if (length(p$layers) == 0) p <- p + geom_blank()
124117
layout <- list()
@@ -960,6 +953,8 @@ gg2list <- function(p) {
960953
}
961954

962955
l <- list(data = flipped.traces, layout = flipped.layout)
963-
956+
l$width <- width
957+
l$height <- width
958+
l$source <- source
964959
structure(add_boxed(rm_asis(l)), class = "plotly")
965960
}

R/plotly.R

+4-2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#' @param height Height in pixels (optional, defaults to automatic sizing).
2424
#' @param inherit logical. Should future traces inherit properties from this initial trace?
2525
#' @param evaluate logical. Evaluate arguments when this function is called?
26+
#' @param source Only relevant for \link{event_data}.
2627
#' @seealso \code{\link{layout}()}, \code{\link{add_trace}()}, \code{\link{style}()}
2728
#' @author Carson Sievert
2829
#' @export
@@ -66,7 +67,7 @@
6667
plot_ly <- function(data = data.frame(), ..., type = "scatter",
6768
group, color, colors, symbol, symbols, size,
6869
width = NULL, height = NULL, inherit = FALSE,
69-
evaluate = FALSE) {
70+
evaluate = FALSE, source = "A") {
7071
# "native" plotly arguments
7172
argz <- substitute(list(...))
7273
# old arguments to this function that are no longer supported
@@ -97,7 +98,8 @@ plot_ly <- function(data = data.frame(), ..., type = "scatter",
9798
layout = NULL,
9899
url = NULL,
99100
width = width,
100-
height = height
101+
height = height,
102+
source = source
101103
)
102104

103105
if (evaluate) p <- plotly_build(p)

R/shiny.R

+30
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,33 @@ renderPlotly <- function(expr, env = parent.frame(), quoted = FALSE) {
2929
expr <- call("as.widget", expr)
3030
shinyRenderWidget(expr, plotlyOutput, env, quoted = TRUE)
3131
}
32+
33+
34+
#' Access plotly user input event data in shiny
35+
#'
36+
#' This function must be called within a reactive shiny context.
37+
#'
38+
#' @param event The type of plotly event. Currently 'plotly_hover',
39+
#' 'plotly_click', and 'plotly_selected' are supported.
40+
#' @param source Which plot should the listener be tied to? This
41+
#' (character string) should match the value of \code{source} in \link{plot_ly}.
42+
#' @export
43+
#' @author Carson Sievert
44+
#' @examples \dontrun{
45+
#' shiny::runApp(system.file("examples", "events", package = "plotly"))
46+
#' }
47+
48+
event_data <- function(event = c("plotly_hover", "plotly_click", "plotly_selected"),
49+
source = "A") {
50+
session <- shiny::getDefaultReactiveDomain()
51+
if (is.null(session)) {
52+
stop("No reactive domain detected. This function can only be called \n",
53+
"from within a reactive shiny context.")
54+
}
55+
val <- session$input[[sprintf(".clientValue-%s-%s", event[1], source)]]
56+
if (event[1] == "plotly_selected" && !is.null(val)) {
57+
data.frame(lapply(val, as.numeric))
58+
} else {
59+
val
60+
}
61+
}

inst/examples/brush/app.R

-43
This file was deleted.

inst/examples/lmGadget/app.R

+86
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# Many thanks to RStudio for shiny gadgets
2+
# And special thanks to Winston Chang for the inspiration
3+
# https://gist.github.com/wch/c4b857d73493e6550cba
4+
library(shiny)
5+
library(miniUI)
6+
library(plotly)
7+
8+
#' Shiny gadget for interactive linear model fitting
9+
#'
10+
#' Click on points to add/remove them from consideration
11+
#'
12+
#' @param dat a data.frame
13+
#' @param x a character string specifying the x variable
14+
#' @param y a character string specifying the y variable
15+
16+
lmGadget <- function(dat, x, y) {
17+
18+
ui <- miniPage(
19+
gadgetTitleBar("Interactive lm"),
20+
miniContentPanel(
21+
fillRow(
22+
flex = c(NA, 1),
23+
fillCol(
24+
width = "100px",
25+
selectInput("degree", "Polynomial degree", c(1, 2, 3, 4))
26+
),
27+
plotlyOutput("plot1", height = "100%")
28+
)
29+
)
30+
)
31+
32+
# mechanism for managing selected points
33+
init <- function() {
34+
selected <- rep(FALSE, nrow(dat))
35+
function(x) {
36+
selected <<- xor(selected, x)
37+
selected
38+
}
39+
}
40+
selection <- init()
41+
42+
server <- function(input, output) {
43+
44+
# obtain a subset of the data that is still under consideration
45+
left <- reactive({
46+
d <- event_data("plotly_click")
47+
if (!is.null(d)) {
48+
dat <- dat[!selection(row.names(dat) %in% d[["key"]]), ]
49+
}
50+
dat
51+
})
52+
53+
# fit a model to subsetted data
54+
refit <- reactive({
55+
req(input$degree)
56+
formula <- as.formula(
57+
sprintf("%s ~ poly(%s, degree = %s)", y, x, input$degree)
58+
)
59+
lm(formula, left())
60+
})
61+
62+
output$plot1 <- renderPlotly({
63+
dat2 <- left()
64+
dat2$yhat <- as.numeric(fitted(refit()))
65+
# sort data by 'x' variable so we draw a line (not a path)
66+
dat2 <- dat2[order(dat2[, x]), ]
67+
68+
plot_ly(x = dat[, x], y = dat[, y], key = row.names(dat), mode = "markers",
69+
marker = list(color = toRGB("grey90"), size = 10)) %>%
70+
add_trace(x = dat2[, x], y = dat2[, y], mode = "markers",
71+
marker = list(color = toRGB("black"), size = 10)) %>%
72+
add_trace(x = dat2[, x], y = dat2$yhat, mode = "lines",
73+
marker = list(color = toRGB("black"))) %>%
74+
layout(showlegend = FALSE, xaxis = list(title = x), yaxis = list(title = y))
75+
})
76+
77+
# Return the most recent fitted model, when we press "done"
78+
observeEvent(input$done, {
79+
stopApp(refit())
80+
})
81+
}
82+
83+
runGadget(ui, server)
84+
}
85+
86+
m <- lmGadget(mtcars, "wt", "mpg")

inst/examples/map_click/app.R

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# git checkout feature/transmit
2+
# R CMD install ./
3+
4+
library(shiny)
5+
library(plotly)
6+
7+
ui <- fluidPage(
8+
plotlyOutput("plot"),
9+
verbatimTextOutput("click")
10+
)
11+
12+
server <- function(input, output, session) {
13+
14+
output$plot <- renderPlotly({
15+
# specify some map projection/options
16+
g <- list(
17+
scope = 'usa',
18+
projection = list(type = 'albers usa'),
19+
lakecolor = toRGB('white')
20+
)
21+
plot_ly(z = state.area, text = state.name, locations = state.abb,
22+
type = 'choropleth', locationmode = 'USA-states') %>%
23+
layout(geo = g)
24+
})
25+
26+
output$click <- renderPrint({
27+
d <- event_data("plotly_click")
28+
if (is.null(d)) "Click on a state to view event data" else d
29+
})
30+
31+
}
32+
33+
shinyApp(ui, server)
+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
Title: Passing plotly selections to shiny via crosstalk
2+
Author: Plotly, Inc.
3+
AuthorUrl: https://plot.ly/r/
4+
License: MIT
5+
DisplayMode: Showcase
6+
Tags: plotly, crosstalk, shiny
7+
Type: Shiny
8+

inst/examples/plotlyEvents/app.R

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
library(shiny)
2+
library(plotly)
3+
4+
ui <- fluidPage(
5+
radioButtons("plotType", "Plot Type:", choices = c("ggplotly", "plotly")),
6+
plotlyOutput("plot"),
7+
verbatimTextOutput("hover"),
8+
verbatimTextOutput("click"),
9+
verbatimTextOutput("brush")
10+
)
11+
12+
server <- function(input, output, session) {
13+
14+
output$plot <- renderPlotly({
15+
if (identical(input$plotType, "ggplotly")) {
16+
p <- ggplot(mtcars, aes(x = mpg, y = wt)) + geom_point()
17+
ggplotly(p) %>% layout(dragmode = "select")
18+
} else {
19+
plot_ly(mtcars, x = mpg, y = wt, mode = "markers") %>%
20+
layout(dragmode = "select")
21+
}
22+
})
23+
24+
output$hover <- renderPrint({
25+
d <- event_data("plotly_hover")
26+
if (is.null(d)) "Hover events appear here (unhover to clear)" else d
27+
})
28+
29+
output$click <- renderPrint({
30+
d <- event_data("plotly_click")
31+
if (is.null(d)) "Click events appear here (double-click to clear)" else d
32+
})
33+
34+
output$brush <- renderPrint({
35+
d <- event_data("plotly_selected")
36+
if (is.null(d)) "Click and drag events (i.e., select/lasso) appear here (double-click to clear)" else d
37+
})
38+
39+
}
40+
41+
shinyApp(ui, server, options = list(display.mode = "showcase"))

0 commit comments

Comments
 (0)