Is it possible to select a graphviz node in a shiny app (renderGrViz) and then link to other information?

耗尽温柔 提交于 2020-02-03 09:09:10

问题


I would like to add a feature (a popover, or some other function?) to the central graphviz nodes in my shiny app that, when selected by mouse click, they display information (e.g. the info column in the centre_nodes file). Given that there's a tooltip property to these nodes, I think/hope the computer must 'see' them but I haven't figured out a way to connect the two and establish this behaviour...

I've explored a variety of methods (e.g. reactR, html tags, hover.css, shinyBS, d3, pipeR, XML, htmltools) but think it must be possible to do with css or htmlwidgets but my knowledge of this code is limited and I've not figured out a solution yet. I have a feeling its simple for folks familiar with this type of coding....

This is an example shiny app using DiagrammeR and grViz:

library(DiagrammeR)
library(shiny)

ui = shinyUI(fluidPage(grVizOutput('graphV'))) 

server = function(input, output) { 
      output$graphV <- renderGrViz({ 
        grViz( "digraph test{
                         A[tooltip='A word']; 
                         B[tooltip='Another word'];
                         A -> B;}" )
        })}

shinyApp(ui = ui, server = server)

The hopeful result is that I can select a node by clicking on, and then information will be displayed associated with that node. Through the tooltip function I can scroll over a node and information can be displayed but a response to a click would be much more aesthetically appealing. Even being able to change the appearance of the tool tip would be an improvement on what's there currently.

Any help is massively appreciated!


回答1:


Here is a draft on how to add html elements according to the element being clicked. Maybe you could elaborate a bit more where you want them to be placed or if you are also fine adding other shiny elements instead (would be easier).

The idea in the code below is to add an onclick listener. Shortcut is to implement it with shinyjs. Identifier of the elements seems to be node1, node2, etc.

Then there are multiple options. Giving that information back to "R/Shiny" and add shiny elements via Shiny.OnInputChanged(...) or appending html elements via javascript. Using Shiny.OnInputChanged(...) is more easy, so i tried if i get the second one to work as well. An example is given below.

It was not straightforward to add the new html delements within the graph and place them in front of it. One could experiment by calibrating the css to show it in front (style="z-index: -1"), but maybe it would be a good point to further specify the optimal solution.

(For sake of completeness, the added elements can of course be removed as well, so that only one "tooltip" is shown at a time,...)

Reproducible example:

library(DiagrammeR)
library(shiny)
library(shinyjs)

texts <- c("Great div for A", "Even better div for B")

jsCode <- paste0("
    elem = document.getElementById('graphV');
        var node = document.createElement('div');
        var textnode = document.createTextNode('", texts,"');
        node.appendChild(textnode);
        elem.appendChild(node);
")

ui = shinyUI(
  fluidPage(
    useShinyjs(),
    grVizOutput('graphV')
  )
) 

server = function(input, output, session) {

  observe({
    for(nodeNr in 1:length(jsCode)){
      local({
        jsToAdd <- jsCode[nodeNr]
        shinyjs::onclick(paste0("node", nodeNr), runjs(jsToAdd)) 
      })

    }
  })

  output$graphV <- renderGrViz({ 
    grViz( "digraph test{
           A[tooltip='A word']; 
           B[tooltip='Another word'];
           A -> B;}" )
})}

shinyApp(ui = ui, server = server)

Failed attempt to let tooltip overlay the diagram:

library(DiagrammeR)
library(shiny)
library(shinyjs)

texts <- c("Great div for A", "Even better div for B")

jsCode <- paste0("
                 elem = document.getElementById('node1');
                 var node = document.createElement('div');
                 var textnode = document.createTextNode('", texts,"');
                 node.appendChild(textnode);
                 node.classList.add('mystyle');
                 elem.appendChild(node);
                 ")

ui = shinyUI(
  fluidPage(
    useShinyjs(),
    tags$style("
       .mystyle {
          z-index: 100 !important;
          background-color: coral;
          font-size: 25px;
       }

       #node1 {
         width: 50px; 
         z-index: unset !important;
         background-color: blue;
       }
    "),
    grVizOutput('graphV')
  )
) 

server = function(input, output, session) {

  observe({
    for(nodeNr in 1:length(jsCode)){
      local({
        jsToAdd <- jsCode[nodeNr]
        shinyjs::onclick(paste0("node", nodeNr), runjs(jsToAdd)) 
      })

    }
  })

  output$graphV <- renderGrViz({ 
    grViz( "digraph test{
           A[tooltip='A word']; 
           B[tooltip='Another word'];
           A -> B;}" )
})}

shinyApp(ui = ui, server = server)

Edit for question in comments:

For example, if you have a 40 node graph, is there a way to automatically link a node id to a node description (in the case above, the 'Great div for A')? Or will i just need to order the texts file in the order the nodes are listed in the graphviz code?

Correct. In the diagrammeR code, it seems to just counts upwards for the ids: "node1, node2, node3,...).

So i just do the same in shiny:

 for(nodeNr in 1:length(jsCode)){
      local({
        jsToAdd <- jsCode[nodeNr]
        shinyjs::onclick(paste0("node", nodeNr), runjs(jsToAdd)) 
      })

    }

As you can see for node number paste0("node", nodeNr) the javascript code jsCode[nodeNr] will be assigned for the click event. And jsCode[nodeNr] will include texts[nodeNr]. (Note that jsCode will be a vector of strings according to the length of texts.



来源:https://stackoverflow.com/questions/57601875/is-it-possible-to-select-a-graphviz-node-in-a-shiny-app-rendergrviz-and-then-l

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!