timelyportfolio/parcoords

in `Shiny` `JS` getting passed as a string

timelyportfolio opened this issue · 18 comments

@jcheng5, @ramnathv, @yihui | I wondered if you might be able to add some insight on this strange situation. When using parcoords, the JS_EVALS get passed as a character and fail when rendered with renderParcoords in Shiny, but work perfectly even in Shiny when rendered statically with parcoords. Here is a simple example that fails on my machine. sessionInfo() pasted below code. I'll keep digging but I have not found anything so far.

library(shiny)
library(parcoords)

p1 <- parcoords(
  mtcars,
  ,color = list(
    colorScale=htmlwidgets::JS('d3.scale.category10()')
    , colorBy = "cyl"
  )
)

# test p1 ; should color by cyl
p1

# JS_evals
htmlwidgets:::JSEvals(p1$x)

ui <- shinyUI(fluidPage(
  tags$h1("shiny")
  ,parcoordsOutput("DiamondPlot")
  ,tags$h1("static")
  ,p1
))

server <- function(input, output, session) {
  output$DiamondPlot <- renderParcoords({
    p1
  })
}

shinyApp(ui,server)
R version 3.1.2 (2014-10-31)
Platform: x86_64-w64-mingw32/x64 (64-bit)

locale:
[1] LC_COLLATE=English_United States.1252  LC_CTYPE=English_United States.1252   
[3] LC_MONETARY=English_United States.1252 LC_NUMERIC=C                          
[5] LC_TIME=English_United States.1252    

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
[1] parcoords_0.1     shiny_0.11.1.9002

loaded via a namespace (and not attached):
 [1] digest_0.6.4      htmltools_0.2.7   htmlwidgets_0.3.2 httpuv_1.3.2      jsonlite_0.9.14  
 [6] mime_0.2          R6_2.0.1          Rcpp_0.11.4.6     RJSONIO_1.3-0     tools_3.1.2      
[11] xtable_1.7-4      yaml_2.1.13 

It works for me. What error are you talking about?

So you get two parcoords? If so this is what I feared and will make things more difficult. I and another user get an empty box on top (see #4). I just hopped off my computer but the error I get is colorScale not a function because it is a string.

Yes. I get two parcoords. My version of htmltools is different. I don't know if any changes there affect this situation.

Unfortunately, updating htmltools to rstudio/htmltools master did not change the result. I should also add that I get the same error in Chrome, Firefox, and RStudio Viewer.

image

image

static

image

shiny (not working)

image

never mind the DT works fine.


library(shiny)
library(DT)
d1 <- datatable(head(iris, 20), options = list(
  initComplete = JS(
    "function(settings, json) {",
      "$(this.api().table().header()).css({'background-color': '#000', 'color': '#fff'});",
    "}")
))

d1


ui <- shinyUI(fluidPage(
  tags$h1("shiny")
  ,dataTableOutput("testDT")
  ,tags$h1("static")
  ,d1
))

server <- function(input, output, session) {
  output$testDT <- renderDataTable({
    d1
  })
}

shinyApp(ui,server)

I am able to reproduce this error with shiny_0.11.1.9002. It worked fine with shiny_0.11.1. So the issue must be some changes made between. It works with shiny_0.11.1.9000. So must be something that changed after that. I suspect it is the switch from RJSONIO to jsonlite.

rstudio/shiny@defedda

CONFIRMED: It works with all versions prior to this commit.

Yes, I'm pretty sure we will need a few changes in our widget packages after switching to jsonlite.

Sure. I will start working on a jsonlite branch of htmlwidgets. Are there known differences in mappings between RJSONIO and jsonlite, specific to the way they are used within shiny?

Ok, good to know. I thought @yihui already had a jsonlite pull from November.

I just rebased ramnathv/htmlwidgets#28 and I will have to figure out the consequences of switching to jsonlite in htmlwidgets.

Great! We can test things out with your jsonlite branch before merging changes back in. We would also need to list down common changes that package authors would have to make in order to switch their widgets to the new version.

wch commented

I think I've found the difference. Inspecting the JSON in the static version (generated by RJSONIO?), I see:

"evals":["options.color.colorScale"]

And for the Shiny version, with options(shiny.trace=TRUE), I see:

"evals":"options.color.colorScale"

This probably has to do with the behavior when auto_unbox=TRUE. What is the structure of the object before it's converted to JSON?

Previously, Shiny used RJSONIO::toJSON(); now it uses a wrapper function around jsonlite::toJSON(), with a bunch of options set: https://github.com/rstudio/shiny/blob/e4a211b/R/shiny.R#L83-L94
Ideally those would have the same output, but they clearly do not right now.

I've encountered two other issues for jsonlite that could potentially cause problems. One of them is easy to work around (and I've already added the workaround in shiny): jeroen/jsonlite#71. The other one is harder to work around: jeroen/jsonlite#76. But neither of them seem to be at issue here.

wch commented

I think the difference is in how RJSONIO and jsonlite handle objects wrapped with I().

The htmlwidgets code uses I() for evals. It's something like this:

str(I("options.color.colorScale"))
# Class 'AsIs'  chr "options.color.colorScale"

Here's how the two packages convert this differently:

RJSONIO:::toJSON(list(evals = I("options.color.colorScale")))
# [1] "{\n \"evals\": [ \"options.color.colorScale\" ] \n}"

# This wraps jsonlite::toJSON with various extra args
shiny:::toJSON(list(evals = I("options.color.colorScale")))
# {"evals":"options.color.colorScale"} 

Without the I(), here's what they give

RJSONIO:::toJSON(list(evals = "options.color.colorScale"))
# [1] "{\n \"evals\": \"options.color.colorScale\" \n}"

shiny:::toJSON(list(evals = "options.color.colorScale"))
# {"evals":"options.color.colorScale"} 

The question is, which behavior makes more sense? I don't see any reason why I() should result in an extra [], but I could be missing something.

In htmlwidgets, we need auto_unbox = FALSE in this particular case. The reason is here: https://github.com/ramnathv/htmlwidgets/blob/921ba807c85404f6b8b16a8adb15c4401cf1a922/inst/www/htmlwidgets.js#L379-L381 We need data.evals to be always an array. Hence I used I() to tell RJSONIO that this vector must be converted to an array.

This issue should be easy to solve in htmlwidgets, and I'll send a PR shortly.

I don't see any reason why I() should result in an extra [], but I could be missing something.

That's simply how you opt out of auto-unboxing on a per-vector basis in RJSONIO. From ?RJSONIO::toJSON:

Objects of class AsIs in R, i.e. that are enclosed in a call to I() are treated as containers even if
they are of length 1. This allows callers to indicate the desired representation of an R "scalar" as an
array of length 1 in JSON

wch commented

OK, that sounds like something that should be added to jsonlite's auto_unbox behavior. I'll file an issue with jsonlite about that.

wch commented

Update: The dev version of jsonlite now has the same behavior that RJSONIO does. In other words, the I() tells it to keep [], even around single-element vectors.