Skip to content
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

Authoring widgets with an alternate representation #166

Open
cpsievert opened this issue Oct 31, 2015 · 12 comments
Open

Authoring widgets with an alternate representation #166

cpsievert opened this issue Oct 31, 2015 · 12 comments

Comments

@cpsievert
Copy link
Collaborator

plotly is about to convert to htmlwidgets, but I had to produce some hacks, since I'd like to keep the 'central object' in plotly a data frame (with some metadata attached). I like this design since it allows us to mix visualization and data manipulation verbs, for example:

library(plotly)
plot_ly(economics, x = date, y = uempmed, mode = "markers") %>%
  dplyr::filter(uempmed == max(uempmed)) %>%
  layout(
    annotations = list(x = date, y = uempmed, text = "Peak", showarrow = T),
    title = "Median duration of unemployment (in weeks)", 
    showlegend = F
  )

newplot 1

In order to get this working in the R console and knitr/rmarkdown, I decided to convert a plotly object to an htmlwidget object in the print.plotly() method. For shiny, since it assumes that the expression generates an htmlwidget object, I had to modify the body of htmlwidgets::shinyRenderWidget() slightly (I guess we could also assume users know to print() their plotly objects in renderPlotly(), but I'd rather not impose that on them).

If nothing else, I hope this helps out other htmlwidgets authors in a similar pickle. More optimistically, maybe this will spark some discussion around a more standard way to do a work-around like this.

@ramnathv
Copy link
Owner

@cpsievert I need to look deeper into what you are doing, but based on a quick read, it seems like you are doing what @hafen was doing as well with a custom print method for rbokeh, although your motivations were different. In the end, his issue got solved by the preRenderHook function that we added.

I think, there might be a cleaner way to do this, without hacking at the internals of htmlwidgets. Can you point me to a version of plot_ly that does NOT use this hack, so that I can prototype a couple of ideas I have to make this work?

@cpsievert
Copy link
Collaborator Author

I don't think preRenderHook provides a solution for this use case.

We don't have a version of plotly that uses htmlwidgets without this hack. We recently added an 'offline' feature for paying customers, but it doesn't use htmlwidgets at all since the plotly.js source wasn't bundled with the package (it will be now).

If you're curious, you can grab the plotlyjs source and poke around with what's on master right now:

dir.create("~/.plotly/plotlyjs", recursive = TRUE)
download.file("https://raw.githubusercontent.com/ropensci/plotly/htmlwidgets/inst/htmlwidgets/lib/plotlyjs/plotly.min.js", "~/.plotly/plotlyjs/plotly.min.js")
install_github("ropensci/plotly")

@jcheng5
Copy link
Collaborator

jcheng5 commented Oct 31, 2015

cc @hadley to comment on "mix visualization and data manipulation verbs" which ggvis does as well (in a different way).

@jcheng5
Copy link
Collaborator

jcheng5 commented Oct 31, 2015

I haven't tried this but could you do:

renderPlotly <- function(expr, env = parent.frame(), quoted = FALSE) {
  if (!quoted) { expr <- substitute(expr) } # force quoted
  expr <- call("toWidget", expr)
  shinyRenderWidget(expr, plotlyOutput, env, quoted = TRUE)
}

@cpsievert
Copy link
Collaborator Author

ooh, that'd be awesome @jcheng5, but I think it requires toWidget() to be exported; otherwise I get Error in func() : could not find function "toWidget"

@hadley
Copy link

hadley commented Nov 2, 2015

Is there any reason you don't take the same approach as ggvis, and just override the appropriate dplyr methods?

@cpsievert
Copy link
Collaborator Author

I suppose one reason is that it works with any function that inputs/outputs a data frame

@hadley
Copy link

hadley commented Nov 2, 2015

And preserves all the attributes of the data frame.

@jcheng5
Copy link
Collaborator

jcheng5 commented Nov 2, 2015

renderPlotly <- function(expr, env = parent.frame(), quoted = FALSE) {
  if (!quoted) { expr <- substitute(expr) } # force quoted
  expr <- as.call(list(call(":::", quote("plotly"), quote("toWidget")), expr))
  shinyRenderWidget(expr, plotlyOutput, env, quoted = TRUE)
}

@cpsievert
Copy link
Collaborator Author

@jcheng5 thanks, that works great!

@hadley Just to clarify, are you saying preserving attributes is a reason to not use the ggvis approach?

FYI, the plotly approach should work even when a manipulation removes attributes. A "plotly_hash" attribute points to a key in a special environment (plotly:::plotlyEnv) containing the vis mappings/properties. These keys also track of the order in which they were created, so if we need to modify a visualization and the "plotly_hash" attribute is not found, it just grabs the last plot (not sure if this is entirely foolproof, but it works for pipelines; can you see any immediate problems with it?).

@cpsievert
Copy link
Collaborator Author

Here is an example (augment() removes attributes attached by plot_ly())

library(plotly)
library(broom)
library(dplyr)
economics %>%
  mutate(rate = unemploy / pop) %>%
  plot_ly(x = date, y = rate, name = "raw") %>%
  loess(rate ~ as.numeric(date), data = .) %>%
  augment() %>%
  add_trace(y = .fitted, name = "smooth")

https://plot.ly/~agvd/1717

@hadley
Copy link

hadley commented Nov 3, 2015

@cpsievert to me, that sounds like a system that will work fine 95% of the time, but when it doesn't you'll have a heck of a time figuring out what went wrong

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants