4

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!

user20650
  • 24,654
  • 5
  • 56
  • 91
rob99985
  • 157
  • 9

1 Answers1

4

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.

Tonio Liebrand
  • 17,189
  • 4
  • 39
  • 59
  • thanks bigData. Re "*Maybe you could elaborate a bit more where you want them to be placed*": I think the hope of the OP (if they can confirm) was to change the hover tooltips (i.e. when hovered over node label A, it shows 'A word') to a click instead. Also to change the default tooltip on each node label from black rectangle with white text to something sexier. – user20650 Aug 24 '19 at 16:34
  • Thanks BigDataScientist, that's closer to what I was thinking and would somewhat work for my purposes but not exactly as envisaged. Is it possible to do as User20650 highlights, and instead of having the text appear at the bottom of the app, appear like a tooltip would (i.e. next to the node) but only on click? Likewise, is it possible to make these tooltips include multiple linked data (E.g. additional information about the node?) and look 'sexier'? ;-) Thanks again for your help though, its closer than it was! – rob99985 Aug 25 '19 at 17:03
  • understood. I keep looking into it, but so far i didnt manage to locate this mouseover event and modify it or to create a tooltip on top. ShinyBS also seems to only offer top, left, bottom, right.. I will notify you if i find another solution or maybe someone else does. – Tonio Liebrand Aug 28 '19 at 08:34
  • a modal would be an overkill, i guess, right? https://shiny.rstudio.com/images/modal-dialog.png – Tonio Liebrand Aug 28 '19 at 09:09
  • Thanks @BigDataScientist ; I had also had a look at ShinyBS but while a popup can be added for the graph id, I couldn't find the correct id / sequence to do similar for the node labels. Approeciate your effort as it seems like it is not straight forward. – user20650 Aug 28 '19 at 17:53
  • so if the shinybs popup could be binded to the nodes, that would be a possible solution for you? – Tonio Liebrand Aug 29 '19 at 22:58
  • Yes, I think a shinybs popup bound to a node would work perfectly! Is that doable??? – rob99985 Aug 30 '19 at 16:23
  • oh sry i wasnt precise enough. I didnt remember that shiny BS also has tooltips. I meant adding a modal like here: https://stackoverflow.com/a/40986600/3502164. But thats maybe an overkill. – Tonio Liebrand Aug 30 '19 at 17:23
  • Oh, no worries, thanks for trying... I've just had a play around with the code. Another question i have is with matching/linking the output of the jsCode function (i.e. 'texts') with a specific node. 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? – rob99985 Aug 31 '19 at 05:19
  • quick answer is yes. Longer description is added at the end of my answer. – Tonio Liebrand Aug 31 '19 at 09:52
  • Thanks! I have the data organised (so that the node descriptions line up with all the nodes) and it's working like a charm in the dummy app. However, when i go to my 'proper' app, it no longer works. The only real difference to the dummy app is that the proper app includes a number of reactive elements (e.g. dropdowns) that change the appearance of the graphviz data (e.g. filter which nodes are highlighted). I'm going to keep fiddling, but just wondering if you know of any reason why reactivity might cause the above code not to work?? – rob99985 Sep 02 '19 at 23:27
  • hmm not really.If you want you could upload the code on github and i can take a look – Tonio Liebrand Sep 03 '19 at 15:22
  • 1
    Figured it out! I hadn't updated the 'graphV' portion of the jsCode. Working as per the example now. I think I can probably take what you've provided and build on it from now. My plan is to place the writing at the side of the graphviz image (vs. the bottom now), have only the text of the most recently selected node show, and stylize the text (i'm thinking to do this with HTML). Thanks again!! :-D – rob99985 Sep 03 '19 at 20:20
  • Apologies, still struggling with the placement of the javascript output. I'm thinking now an acceptable solution would be to include it in the sidebarpanel of my shiny app. I'm wondering if this might be easier to achieve if i use the 'Shiny.OnInputChanged(...)' function that you describe above as being more straightforward. As such, are you able to recreate the code above through this function (vs. appending html elements with javascript)?? – rob99985 Sep 04 '19 at 17:09
  • You could use shinyjs with onclick, Oninpitchanged and renderui for that. Maybe it's good to move to a new question, otherwise we are redirected to chat. Feel free to link it here – Tonio Liebrand Sep 04 '19 at 18:58
  • Thanks, makes sense! Here's the new question: https://stackoverflow.com/questions/57798411/how-to-use-shinyjs-to-link-graphviz-node-data-to-the-shiny-ui-htmloutput. – rob99985 Sep 05 '19 at 04:24