Search code examples
javascriptrd3.jsgraph-visualizationnetworkd3

Add interactive tooltips in networkD3 package in R


I'm working on a project where I'm visualizing a network graph using the networkD3 package in R. The network consists of nodes and edges, represented by two data frames: node_df and edge_df, respectively. The node_df data frame contains information about the nodes, including their IDs, labels, groups, and colors. The edge_df data frame contains information about the edges, including the source and target nodes, as well as edge labels.

I've tried various approaches to visualize the network graph and add interactivity to it, adding tooltips to nodes and edges. However, I haven't been successful on adding the tooltips on the arrows when hovering the mouse.

Here's a summary of the code I've tried so far:

Data

> head(edge_df, 10)
   id from to  rel label penwidth       color fontname fontsize weight constraint
1   1    2 16 <NA>   183 5.000000 dodgerblue4    Arial       10      1       TRUE
2   2    3  6 <NA>     1 1.021858 dodgerblue4    Arial       10      1       TRUE
3   3    3 11 <NA>    10 1.218579 dodgerblue4    Arial       10      1       TRUE
4   4    3 17 <NA>     5 1.109290 dodgerblue4    Arial       10      1       TRUE
5   5    3 19 <NA>     2 1.043716 dodgerblue4    Arial       10      1       TRUE
6   6    4  4 <NA>     2 1.043716 dodgerblue4    Arial       10      1       TRUE
7   7    4 21 <NA>     1 1.021858 dodgerblue4    Arial       10      1       TRUE
8   8    5 17 <NA>    17 1.371585 dodgerblue4    Arial       10      1       TRUE
9   9    6  6 <NA>     1 1.021858 dodgerblue4    Arial       10      1       TRUE
10 10    6 10 <NA>     5 1.109290 dodgerblue4    Arial       10      1       TRUE
> head(node_df, 10)
   id type                     label     shape color_level          style   fontcolor       color                   tooltip
1   1 <NA>                       End    circle         Inf rounded,filled      brown4      brown4       ARTIFICIAL_END\n183
2   2 <NA>                     Start    circle         Inf rounded,filled chartreuse4 chartreuse4     ARTIFICIAL_START\n183
3   3 <NA>          Analyze Done\n18 rectangle  0.08333333 rounded,filled       black        grey          Analyze Done\n18
4   4 <NA>               Approved\n3 rectangle  0.01388889 rounded,filled       black        grey               Approved\n3
5   5 <NA> Back from Development\n17 rectangle  0.07870370 rounded,filled       black        grey Back from Development\n17
6   6 <NA>                Backlog\n9 rectangle  0.04166667 rounded,filled       black        grey                Backlog\n9
7   7 <NA>              Cancelled\n1 rectangle  0.00462963 rounded,filled       black        grey              Cancelled\n1
8   8 <NA>               Closed\n138 rectangle  0.63888889 rounded,filled       white        grey               Closed\n138
9   9 <NA>           Dispatched\n166 rectangle  0.76851852 rounded,filled       white        grey           Dispatched\n166
10 10 <NA>                 Done\n216 rectangle  1.00000000 rounded,filled       white        grey                 Done\n216
   penwidth fixedsize fontname fontsize fillcolor                     Group
1       1.5     FALSE    Arial       10     white                       End
2       1.5     FALSE    Arial       10     white                     Start
3       1.5     FALSE    Arial       10   #ECE7F2          Analyze Done\n18
4       1.5     FALSE    Arial       10   #FFF7FB               Approved\n3
5       1.5     FALSE    Arial       10   #ECE7F2 Back from Development\n17
6       1.5     FALSE    Arial       10   #FFF7FB                Backlog\n9
7       1.5     FALSE    Arial       10   #FFF7FB              Cancelled\n1
8       1.5     FALSE    Arial       10   #74A9CF               Closed\n138
9       1.5     FALSE    Arial       10   #3690C0           Dispatched\n166
10      1.5     FALSE    Arial       10   #034E7B                 Done\n216

Code

# Add a dummy "Group" column to nodes dataframe
node_df$Group <- node_df$label

# 
edges_net <- edge_df[, c("from", "to", "label")]
colnames(edges_net) <- c("from", "to", "title")

nodes_net <- node_df[, c("id", "label", "Group", "fillcolor")]
colnames(nodes_net) <- c("id", "node_label", "Group", "nodes_color")

# Subtract 1 from the "from" and "to" columns to zero-index them
edges_net$from <- edges_net$from - 1
edges_net$to <- edges_net$to - 1

# set node size
nodes_net$NodeSize <- 20

# JS 
clickJS <- "
d3.selectAll('.xtooltip').remove(); 
d3.select('body').append('div')
  .attr('class', 'xtooltip')
  .style('position', 'absolute')
  .style('border', '1px solid #999')
  .style('border-radius', '3px')
  .style('padding', '5px')
  .style('opacity', '0.85')
  .style('background-color', '#fff')
  .style('box-shadow', '2px 2px 6px #888888')
  .html('name: ' + d.name + '<br>' + 'group: ' + d.group)
  .style('left', (d3.event.pageX) + 'px')
  .style('top', (d3.event.pageY - 28) + 'px');
"
# plot network
my_network<- networkD3::forceNetwork(
  Links = edges_net,
  Nodes = nodes_net,
  Source = "from",
  Target = "to", 
  Value = "title", 
  NodeID = "node_label",
  Group = "Group",
  colourScale = networkD3::JS("d3.scaleOrdinal(d3.schemeCategory20);"),
  linkDistance = 300,
  linkWidth = networkD3::JS("function(d) { return Math.sqrt(d.value);}"),
  radiusCalculation = networkD3::JS(" Math.sqrt(d.nodesize)+6"),
  Nodesize = "NodeSize",
  charge = - 30,,
  linkColour = "black",
  opacity = 0.8,
  zoom = T,
  legend = T,
  arrows = T,
  bounded = T, 
  opacityNoHover = 1.5,
  fontSize = 12,
  clickAction = clickJS
# 
)

# Increase the size of nodes
my_network$x$width <- '1200px'
my_network$x$height <- '800px'


# Get the target variable in fn$x$links (an integer id) to show up as a tooltip when user hovers over a link (i.e. edge) in the graph
fnrender <- htmlwidgets::onRender(
  my_network,
  '
  function(el, x) {
    d3.selectAll(".link").append("svg:title")
      .text(function(d) { return d.source.name + " -> " + d.target.name; })
  }
  '
)
# display the result
fnrender

Produced graph

network

The objective of my project is to create an interactive network visualization where users can hover on nodes to view additional information and hover over edges to see edge labels.


Solution

  • I modified your code so that it's reproducible and tried it. I see the tooltip on the links, though on my browser you have to hover for about 0.5-1 second before it shows up.

    edge_df <- tibble::tribble(
      ~id, ~from, ~to, ~rel, ~label, ~penwidth, ~color, ~fontname, ~fontsize, ~weight, ~constraint,
      1,    1,  6, NA,   183, 5.000000, "dodgerblue4",    "Arial",       10,      1,       TRUE,
      2,    2,  5, NA,     1, 1.021858, "dodgerblue4",    "Arial",       10,      1,       TRUE,
      3,    2,  7, NA,    10, 1.218579, "dodgerblue4",    "Arial",       10,      1,       TRUE,
      4,    2,  8, NA,     5, 1.109290, "dodgerblue4",    "Arial",       10,      1,       TRUE,
      5,    2,  9, NA,     2, 1.043716, "dodgerblue4",    "Arial",       10,      1,       TRUE,
      6,    3,  3, NA,     2, 1.043716, "dodgerblue4",    "Arial",       10,      1,       TRUE,
      7,    3, 10, NA,     1, 1.021858, "dodgerblue4",    "Arial",       10,      1,       TRUE,
      8,    4,  6, NA,    17, 1.371585, "dodgerblue4",    "Arial",       10,      1,       TRUE,
      9,    5,  5, NA,     1, 1.021858, "dodgerblue4",    "Arial",       10,      1,       TRUE,
      10,   5, 10, NA,     5, 1.109290, "dodgerblue4",    "Arial",       10,      1,       TRUE
    )
    
    node_df <- tibble::tribble(
    ~id, ~type,               ~label,     ~shape,    ~color_level,      ~style,           ~fontcolor,    ~color, ~tooltip, ~penwidth, ~fixedsize, ~fontname, ~fontsize, ~fillcolor,                     ~Group,
    1,  NA,                      "End",    "circle",         Inf, "rounded,filled",      "brown4",      "brown4",       "ARTIFICIAL_END\n1831",       1.5,     FALSE,    "Arial",       10,     "white",                       "End",
    2,  NA,                    "Start",    "circle",         Inf, "rounded,filled", "chartreuse4", "chartreuse4",     "ARTIFICIAL_START\n1832",       1.5,     FALSE,    "Arial",       10,     "white",                     "Start",
    3,  NA,         "Analyze Done\n18", "rectangle",  0.08333333, "rounded,filled",       "black",        "grey",          "Analyze Done\n183",       1.5,     FALSE,    "Arial",       10,   "#ECE7F2",          "Analyze Done\n18",
    4,  NA,              "Approved\n3", "rectangle",  0.01388889, "rounded,filled",       "black",        "grey",               "Approved\n34",       1.5,     FALSE,    "Arial",       10,   "#FFF7FB",               "Approved\n3",
    5,  NA,"Back from Development\n17", "rectangle",  0.07870370, "rounded,filled",       "black",        "grey", "Back from Development\n175",       1.5,     FALSE,    "Arial",       10,   "#ECE7F2", "Back from Development\n17",
    6,  NA,               "Backlog\n9", "rectangle",  0.04166667, "rounded,filled",       "black",        "grey",                "Backlog\n96",       1.5,     FALSE,    "Arial",       10,   "#FFF7FB",                "Backlog\n9",
    7,  NA,             "Cancelled\n1", "rectangle",  0.00462963, "rounded,filled",       "black",        "grey",              "Cancelled\n17",       1.5,     FALSE,    "Arial",       10,   "#FFF7FB",              "Cancelled\n1",
    8,  NA,              "Closed\n138", "rectangle",  0.63888889, "rounded,filled",       "white",        "grey",               "Closed\n1388",       1.5,     FALSE,    "Arial",       10,   "#74A9CF",               "Closed\n138",
    9,  NA,          "Dispatched\n166", "rectangle",  0.76851852, "rounded,filled",       "white",        "grey",           "Dispatched\n1669",       1.5,     FALSE,    "Arial",       10,   "#3690C0",           "Dispatched\n166",
    10, NA,                "Done\n216", "rectangle",  1.00000000, "rounded,filled",       "white",        "grey",                "Done\n21610",      1.5,     FALSE,    "Arial",        10,    "#034E7B",                 "Done\n216"
    )
    
    
    
    # Add a dummy "Group" column to nodes dataframe
    node_df$Group <- node_df$label
    
    # 
    edges_net <- edge_df[, c("from", "to", "label")]
    colnames(edges_net) <- c("from", "to", "title")
    
    nodes_net <- node_df[, c("id", "label", "Group", "fillcolor")]
    colnames(nodes_net) <- c("id", "node_label", "Group", "nodes_color")
    
    # Subtract 1 from the "from" and "to" columns to zero-index them
    edges_net$from <- edges_net$from - 1
    edges_net$to <- edges_net$to - 1
    
    # set node size
    nodes_net$NodeSize <- 20
    
    # JS 
    clickJS <- "
    d3.selectAll('.xtooltip').remove(); 
    d3.select('body').append('div')
      .attr('class', 'xtooltip')
      .style('position', 'absolute')
      .style('border', '1px solid #999')
      .style('border-radius', '3px')
      .style('padding', '5px')
      .style('opacity', '0.85')
      .style('background-color', '#fff')
      .style('box-shadow', '2px 2px 6px #888888')
      .html('name: ' + d.name + '<br>' + 'group: ' + d.group)
      .style('left', (d3.event.pageX) + 'px')
      .style('top', (d3.event.pageY - 28) + 'px');
    "
    # plot network
    my_network<- networkD3::forceNetwork(
      Links = edges_net,
      Nodes = nodes_net,
      Source = "from",
      Target = "to", 
      Value = "title", 
      NodeID = "node_label",
      Group = "Group",
      colourScale = networkD3::JS("d3.scaleOrdinal(d3.schemeCategory20);"),
      linkDistance = 300,
      linkWidth = networkD3::JS("function(d) { return Math.sqrt(d.value);}"),
      radiusCalculation = networkD3::JS(" Math.sqrt(d.nodesize)+6"),
      Nodesize = "NodeSize",
      charge = - 30,,
      linkColour = "black",
      opacity = 0.8,
      zoom = T,
      legend = T,
      arrows = T,
      bounded = T, 
      opacityNoHover = 1.5,
      fontSize = 12,
      clickAction = clickJS
      # 
    )
    #> Links is a tbl_df. Converting to a plain data frame.
    #> Nodes is a tbl_df. Converting to a plain data frame.
    
    # Increase the size of nodes
    my_network$x$width <- '1200px'
    my_network$x$height <- '800px'
    
    
    # Get the target variable in fn$x$links (an integer id) to show up as a tooltip when user hovers over a link (i.e. edge) in the graph
    fnrender <- htmlwidgets::onRender(
      my_network,
      '
      function(el, x) {
        d3.selectAll(".link").append("svg:title")
          .text(function(d) { return d.source.name + " -> " + d.target.name; })
      }
      '
    )
    # display the result
    fnrender