christophergandrud / networkD3

D3 JavaScript Network Graphs from R
http://christophergandrud.github.io/networkD3
650 stars 269 forks source link

dateInput and dateRangeInput stop working when a Sankey Network is rendered #214

Closed octaviancorlade closed 5 years ago

octaviancorlade commented 7 years ago

I don't know whether it's a problem in networkD3 or in Shiny. When first loading the page, no Sankey Network, the dateInput works. As soon as the Sankey Network (the simple one from the help example) is rendered, the dateInput stops working properly:

library(shiny)
library(networkD3)

ui <- fluidPage(

   sidebarLayout(
      sidebarPanel(
         dateInput("date", "Date: "),
         actionButton("showSankey", "Show Sankey")
      ),

      mainPanel(
        sankeyNetworkOutput("plotSankeyGraph")
      )
   )
)

server <- function(input, output) {

  observeEvent(input$showSankey, {

    output$plotSankeyGraph <- renderSankeyNetwork({
       URL <- paste0('https://cdn.rawgit.com/christophergandrud/networkD3/',
                     'master/JSONdata/energy.json')
       energy <- jsonlite::fromJSON(URL)

       sankeyNetwork(Links = energy$links, Nodes = energy$nodes, Source = 'source',
                     Target = 'target', Value = 'value', NodeID = 'name',
                     units = 'TWh', fontSize = 12, nodeWidth = 30)
     })

  })
}

shinyApp(ui = ui, server = server)

Using networkD3 0.4 and Shiny 1.0.5 (R 3.4.1).

cjyetman commented 7 years ago

Thanks for the reproducible example!

I can confirm that there seems to be some conflict between specifically networkD3's sankeyNetwork() plot and shiny's dateInput. For instance, the dateInput seems to work as expected when included on the same page as a forceNetwork() plot...

library(shiny)
library(networkD3)

ui <- fluidPage(
  mainPanel(
    dateInput("date", "Date: "),
    forceNetworkOutput("plotForceGraph")
  )
)

server <- function(input, output) {
  output$plotForceGraph <- renderForceNetwork({
    data(MisLinks)
    data(MisNodes)
    forceNetwork(Links = MisLinks, Nodes = MisNodes, Source = "source",
                 Target = "target", Value = "value", NodeID = "name",
                 Group = "group", opacity = 0.4, zoom = TRUE)
  })
}

shinyApp(ui = ui, server = server)

but does not function as expected when included on a page with a sankeyNetwork() plot...

library(shiny)
library(networkD3)

ui <- fluidPage(
  mainPanel(
    dateInput("date", "Date: "),
    sankeyNetworkOutput("plotSankeyGraph")
  )
)

server <- function(input, output) {
  output$plotSankeyGraph <- renderSankeyNetwork({
    URL <- paste0('https://cdn.rawgit.com/christophergandrud/networkD3/',
                  'master/JSONdata/energy.json')
    energy <- jsonlite::fromJSON(URL)
    sankeyNetwork(Links = energy$links, Nodes = energy$nodes, Source = 'source',
                  Target = 'target', Value = 'value', NodeID = 'name',
                  units = 'TWh', fontSize = 12, nodeWidth = 30)
  })
}

shinyApp(ui = ui, server = server)

I suspect there's some JavaScript and/or CSS conflict between the two, but that will take some effort to track down exactly.

cjyetman commented 7 years ago

I narrowed this down to the sankeyNetwork.js script adding a <body> tag within the <foreignObject>, which conflicts with what bootstrap-datepicker is doing. It creates a <foreignObject> tag inside the <title> tag to enable tooltips with multiple lines. I'm not sure what the consequences would be of not adding the <body> tag, and it would have to be tested under numerous browser/version/os/etc. conditions, but the following minimal-ish examples demonstrate the problem, and avoiding the problem while maintaining the multiple-line-tooltip, at least in my current setup (MacOS_10.12.6/Safari_10.1.2).

demo the problem...

<!DOCTYPE html>
<html >
<head>
  <meta charset="UTF-8">
  <link rel='stylesheet prefetch' href='http://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css'>
  <link rel='stylesheet prefetch' href='https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datepicker/1.6.4/css/bootstrap-datepicker3.min.css'>
  <link rel='stylesheet prefetch' href='http://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css'>
</head>

<body>

  <input type="text" class="date-picker form-control">
  <br>

  <svg width="100px" height="100px">
    <rect height="50" width="50" style="fill: rgb(199, 199, 199); cursor: move;">
    </rect>
  </svg>

  <script src='http://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.min.js'></script>
  <script src='http://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js'></script>
  <script src='https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datepicker/1.6.4/js/bootstrap-datepicker.js'></script>
  <script src='https://cdnjs.cloudflare.com/ajax/libs/d3/4.9.1/d3.js'></script>

  <script>$('.date-picker').datepicker({});</script>

  <script>
  d3.select("svg rect")
    .append("title")
    .append("foreignObject")
    .append("xhtml:body")
    .html("<pre>\u2192 Wind\n289 TWh</pre>");
  </script>

</body>
</html>

demo avoiding the problem...

<!DOCTYPE html>
<html >
<head>
  <meta charset="UTF-8">
  <link rel='stylesheet prefetch' href='http://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css'>
  <link rel='stylesheet prefetch' href='https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datepicker/1.6.4/css/bootstrap-datepicker3.min.css'>
  <link rel='stylesheet prefetch' href='http://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css'>
</head>

<body>

  <input type="text" class="date-picker form-control">
  <br>

  <svg width="100px" height="100px">
    <rect height="50" width="50" style="fill: rgb(199, 199, 199); cursor: move;">
    </rect>
  </svg>

  <script src='http://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.min.js'></script>
  <script src='http://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js'></script>
  <script src='https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datepicker/1.6.4/js/bootstrap-datepicker.js'></script>
  <script src='https://cdnjs.cloudflare.com/ajax/libs/d3/4.9.1/d3.js'></script>

  <script>$('.date-picker').datepicker({});</script>

  <script>
  d3.select("svg rect")
    .append("title")
    .append("foreignObject")
    .html("<pre>\u2192 Wind\n289 TWh</pre>");
  </script>

</body>
</html>

PR #180 is when this was implemented, and issue #175 (particularly this comment) has some background on how/why this was implemented, but I don't see anything that says explicitly why the <body> tag is necessary. Anyone know more about this than me? Definitely need to test on IE versions... IE tends to be the most picky about these type of things.

octaviancorlade commented 7 years ago

Hopefully this will fix.

Thank you for the prompt responses!

It wasn't working on IE11 (and maybe others) without the tag, but replacing

 with  did the job.

cjyetman commented 6 years ago

It would be good to submit this issue upstream to bootstrap-datepicker also, since the structure...

<foreignObject>
    <xhtml:body>
        <pre>
        </pre>
    </xhtml:body>
</foreignObject>

inside an SVG is technically valid HTML/SVG as far as I can tell.

octaviancorlade commented 6 years ago

It looks like bootstrap-datepicker is supporting such structure, while the problem is that d3.js is not appending the xhtml namespace to the body tag:

https://jsfiddle.net/9w31r03x/

This makes my PR #215 to fix the issue technically not correct because d3.selection.append() is also replacing <xhtml:pre> with <pre>, therefore not explicitly using the correct namespace (though it still seems to be working on a variety of browsers/OS)

cjyetman commented 6 years ago

I still think just removing the body tag altogether is the way to go... the xhtml prefix doesn't seem to make any difference in my testing.

cjyetman commented 6 years ago

For instance, if you simply comment out line 7 in the JS of your example, .append("xhtml:body"), it works as expected for me (macOS 10.12.6/Safari 11.0).

octaviancorlade commented 6 years ago

Yes and that's what PR #215 basically does, though currently in the wrong way.

For completeness, I said it's "technically" not correct because in SVG 1.1 the foreignObject would need a way to know what type of markup is in it, and from the SVG 2 draft:

The HTML parser treats elements inside the ‘foreignObject’ equivalent to elements inside an HTML document fragment.

cjyetman commented 6 years ago

Sorry, somehow I must have misread PR #215 before... I thought it removed the <pre> tag. I give it a :thumbsup:

balintba commented 5 years ago

Dear All,

I see that this issue is known for quite some time now. I wanted to use the Sankey network in my application, but I also have several dateInputs which break down upon loading the Sankey plot. What do you think, can I expect a solution in the forseeable future. As I see the pull request has not been accepted yet. Thanks in advance!

cjyetman commented 5 years ago

Looks like this is/will be finally fixed upstream with https://github.com/rstudio/shiny/commit/3d117807ac4ffe14942e41edd8e8a5b364dc80a6

will close this issue once I have verified it

cjyetman commented 5 years ago

This problem appears to be resolved in the upstream fix in shiny which is in the current release version on CRAN (v1.3.0). Closing issue