Skip to content

Get event_data working on shiny modules #700

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 10 commits into from
Sep 12, 2016
Merged
4 changes: 2 additions & 2 deletions 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: 4.3.6
Version: 4.3.7
Authors@R: c(person("Carson", "Sievert", role = c("aut", "cre"),
email = "[email protected]"),
person("Chris", "Parmer", role = c("aut", "cph"),
Expand Down Expand Up @@ -44,7 +44,7 @@ Suggests:
testthat,
knitr,
devtools,
shiny,
shiny (>= 0.14),
htmltools,
curl,
rmarkdown,
Expand Down
6 changes: 6 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
# 4.3.7 -- 11 September 2016

## BUG FIXES

* `event_data()` now works inside shiny modules (#659). For an example, see <https://github.com/ropensci/plotly/tree/master/inst/examples/plotlyShinyModules>

# 4.3.6 -- 9 September 2016

## CHANGES
Expand Down
2 changes: 2 additions & 0 deletions R/plotly_build.R
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ plotly_build.plotly <- function(p) {
})
dataArrayAttrs <- names(Attrs)[as.logical(isArray)]
tr <- trace[names(trace) %in% c(npscales(), special_attrs(trace), dataArrayAttrs)]
# TODO: does it make sense to "train" matrices/2D-tables (e.g. z)?
tr <- tr[vapply(tr, function(x) is.null(dim(x)), logical(1))]
builtData <- tibble::as_tibble(tr)

# avoid clobbering I() (i.e., variables that shouldn't be scaled)
Expand Down
8 changes: 5 additions & 3 deletions R/shiny.R
Original file line number Diff line number Diff line change
Expand Up @@ -41,19 +41,21 @@ renderPlotly <- function(expr, env = parent.frame(), quoted = FALSE) {
#' @param source Which plot should the listener be tied to? This
#' (character string) should match the value of \code{source} in
#' \code{\link{plot_ly}()}.
#' @param session a shiny session object (the default should almost always be used).
#' @export
#' @author Carson Sievert
#' @examples \dontrun{
#' shiny::runApp(system.file("examples", "plotlyEvents", package = "plotly"))
#' }

event_data <- function(event = c("plotly_hover", "plotly_click", "plotly_selected",
"plotly_relayout"), source = "A") {
session <- shiny::getDefaultReactiveDomain()
"plotly_relayout"), source = "A",
session = shiny::getDefaultReactiveDomain()) {
if (is.null(session)) {
stop("No reactive domain detected. This function can only be called \n",
"from within a reactive shiny context.")
}
val <- session$input[[sprintf(".clientValue-%s-%s", event[1], source)]]
src <- sprintf(".clientValue-%s-%s", event[1], source)
val <- session$rootScope()$input[[src]]
if (is.null(val)) val else jsonlite::fromJSON(val)
}
48 changes: 48 additions & 0 deletions inst/examples/plotlyShinyModules/app.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
library(shiny)
library(plotly)

reusableUI <- function(id = NULL) {
ns <- NS(id)

fluidRow(
column(4, plotlyOutput(ns("p1"))),
column(4, plotlyOutput(ns("p2"))),
column(4, verbatimTextOutput(ns("ev1"))),
column(4, verbatimTextOutput(ns("ev2")))
)
}

viz <- function(input, output, session, src) {

# if you want, you can distinguish between events *within* a module
src2 <- paste0(src, "2")

output$p1 <- renderPlotly({
plot_ly(mtcars, x = ~mpg, y = ~disp,
key = row.names(mtcars), source = src)
})
output$p2 <- renderPlotly({
plot_ly(mtcars, x = ~mpg, y = ~disp,
key = row.names(mtcars), source = src2)
})
output$ev1 <- renderPrint({
event_data("plotly_hover", source = src)
})
output$ev2 <- renderPrint({
event_data("plotly_hover", source = src2)
})

}

ui <- fluidPage(
reusableUI("one"),
reusableUI("two")
)

server <- function(input, output, session) {
# use the src argument to namespace plotly events
callModule(viz, "one", src = "A")
callModule(viz, "two", src = "B")
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jcheng5 this example seems to still work without passing the session to plot_ly()/event_data(). Would u still suggest doing so?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, you generally don't want to be explicitly passing the session--just let it use the default.

Copy link
Contributor

@jcheng5 jcheng5 Sep 6, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same goes for the callModule calls, they shouldn't need explicit session arguments passed to them either.

Copy link
Contributor

@jcheng5 jcheng5 Sep 6, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I think what I said to you last week was that you should explicitly pass the source, not the session. So viz would have a source = "A" param that's passed to plot_ly and event_data; that way, the main UI can decide whether reusableUI("one") and reusableUI("two") are linked together or separately, by passing the same (or default) or different source values.

shinyApp(ui, server)
6 changes: 2 additions & 4 deletions inst/htmlwidgets/plotly.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,8 @@ HTMLWidgets.widget({
attachKey("key");
return obj;
});
Shiny.onInputChange(
".clientValue-" + eventType + "-" + x.source,
JSON.stringify(d)
);
var src = ".clientValue-" + eventType + "-" + x.source;
Shiny.onInputChange(src, JSON.stringify(d));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason you're doing JSON.stringify() and jsonlite::fromJSON explicitly? If you just pass d from here, and don't JSON-decode from R, I think it should work fine--we do the JSON encoding for you.

Copy link
Collaborator Author

@cpsievert cpsievert Sep 6, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm pretty sure I did that because the default translates an array of objects to a named character vector, when I really want a data frame. Would you go about ensuring a data frame in another way?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's fair. It does mean you are sending double-encoded JSON data across the wire which will bloat things up a little, but I guess these are pretty small packets anyway.

};
};

Expand Down
5 changes: 4 additions & 1 deletion man/event_data.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.