javascriptrhtmlwidgetsnetworkd3

How can I create zoom functionality for Sankey Networks using networkD3 and htmlwidgets?


As i the title says. I would like to zoom interactively on Sankey networks, as it is for example possible for Force networks. I assume that it would work by adding a htmlwidgets::onRender() call, containing JS().

I would like to do so via a wrapper around sankeyNetwork() as i have done (badly) with a custom hover tooltip. When i try to use this aproach for zooming i get a lot of uncaught errors; I am a JS noob.

My code is a mess and my question is so general that I belive that no reprex is necessary. If it is necessary, please ask.

Example of (bad) wrapper:

sankeyNetworkWithAge <- function(Links, Nodes, Source, Target, Value, 
                                 NodeID, NodeGroup = NodeID, 
                                 LinkGroup = NULL, ...) {
  # Print debug information
  print("Creating base widget...")
  
  # Create the base widget with tryCatch to catch any errors
  widget <- tryCatch({
    sankeyNetwork(
      Links = Links,
      Nodes = Nodes,
      Source = Source,
      Target = Target,
      Value = Value,
      NodeID = NodeID,
      NodeGroup = NodeGroup,
      LinkGroup = LinkGroup,
      ...
    )
  }, error = function(e) {
    print(paste("Error creating widget:", e$message))
    return(NULL)
  })
  
  # Check if widget creation was successful
  if (is.null(widget)) {
    stop("Failed to create base widget")
  }
  
  print("Adding custom data...")
  
  # Add custom data
  widget$x$options$median_age <- Links$median_age
  
  print("Adding custom JavaScript...")
  
  # Add custom JavaScript
  widget <- htmlwidgets::onRender(
    widget,
    '
        function(el, x) {
            console.log("Running custom JavaScript");
            var medianAges = x.options.median_age;
            
            d3.select(el)
                .selectAll(".link")
                .each(function(d, i) {
                    d.median_age = medianAges[i];
                })
                .select("title")
                .text(function(d) {
                    return d.source.name + " → " + d.target.name + "\\n" +
                           d.value + " Candidates\\n" +
                           "Median Age: " + d.median_age;
                });
        }
        '
  )
  
  print("Returning widget...")
  return(widget)
}

Solution

  • I wouldn't say I recommend this, but here you go...

    library(networkD3)
    
    URL <- paste0(
      "https://cdn.rawgit.com/christophergandrud/networkD3/",
      "master/JSONdata/energy.json")
    Energy <- jsonlite::fromJSON(URL)
    
    widget <- sankeyNetwork(Links = Energy$links, Nodes = Energy$nodes, Source = "source",
                  Target = "target", Value = "value", NodeID = "name",
                  units = "TWh", fontSize = 12, nodeWidth = 30)
    
    widget <- htmlwidgets::onRender(
      widget,
      '
        function(el, x) {
          var zoom = d3.zoom();
          function redraw() {
            d3.select(el).select("g")
              .attr("transform", d3.event.transform);
          }
          zoom.on("zoom", redraw);
    
          d3.select(el).select("svg")
            .attr("pointer-events", "all")
            .call(zoom);
        }
      '
    )
    
    print(widget)