14

I want to highlight the whole path when i click on the node to know the whole story of specific node and here's an example- http://bl.ocks.org/git-ashish/8959771 .

Please check this link and you will find the function that highlight the path in javscript, but take care please , this function doesn't do what i want, it highlight links related to the clicked node and the links related to the target nodes. what i want is to highlight all links related to the clicked node.

d3 Sankey - Highlight all connected paths from start to end

Here's an example of what i need, enter image description here This is the whole graph,what i need is, when i click on Bangkok, it highlight all the nodes that in the same raw with Bangkok in the dataframe , like highlight the link to ClimateChange and EnergyShortage, .... then highlight Infrastructure&Ecosystems, and Leadership&strategy, and .... That's what i want. Here's another picture showing the nodes that related to Bangkok using shiny to analyze it .

enter image description here

Here's what happens when i use highlight_node_links which in the bl.ocks and the linked question, and that's wrong, and doesn't show the relation between nodes and Bangkoks. enter image description here

Here's the data for Bangkok to show you how the columns related to each other, and when you use this data, it gonna generate the second picture only.

structure(list(City = c("Bangkok", "Bangkok", "Bangkok", "Bangkok", 
"Bangkok", "Bangkok", "Bangkok", "Bangkok", "Bangkok", "Bangkok", 
"Bangkok", "Bangkok", "Bangkok", "Bangkok", "Bangkok", "Bangkok"
), ResiliencyChallenge = c("ClimateChange", "ClimateChange", 
"ClimateChange", "ClimateChange", "ClimateChange", "InfrastructureFaliure", 
"EnergyShortage", "Pollution", "Pollution", "Pollution", "TransportationSystemFailure", 
"TransportationSystemFailure", "TransportationSystemFailure", 
"TransportationSystemFailure", "TransportationSystemFailure", 
"TransportationSystemFailure"), CRI.Dimesnsion.1 = c("Infrastructure & Ecosystems", 
"Infrastructure & Ecosystems", "Infrastructure & Ecosystems", 
"Infrastructure & Ecosystems", "Infrastructure & Ecosystems", 
"Infrastructure & Ecosystems", "Infrastructure & Ecosystems", 
"Leadership & Strategy", "Leadership & Strategy", "Infrastructure & Ecosystems", 
"Infrastructure & Ecosystems", "Infrastructure & Ecosystems", 
"Infrastructure & Ecosystems", "Infrastructure & Ecosystems", 
"Infrastructure & Ecosystems", "Leadership & Strategy"), Implementation.time.frame = c("Short-term", 
"Short-term", "Short-term", "Short-term", "Short-term", "Mid-term", 
"Long-term", "Short-term", "Short-term", "Mid-term", "Mid-term", 
"Short-term", "Short-term", "Short-term", "Short-term", "Short-term"
), Goal = c("Goal13", "Goal13", "Goal13", "Goal13", "Goal13", 
"Goal12", "Goal12", "Goal11", "Goal11", "Goal11", "Goal11", "Goal11", 
"Goal11", "Goal11", "Goal11", "Goal11")), .Names = c("City", 
"ResiliencyChallenge", "CRI.Dimesnsion.1", "Implementation.time.frame", 
"Goal"), class = "data.frame", row.names = c(NA, -16L))
CJ Yetman
  • 8,373
  • 2
  • 24
  • 56
  • in JavaScript, or in R using `networkD3`? The code you copy-pasted is R using `networkD3`... and in the result of that code, when you hover over a node it already does what it sounds like you want, highlighting all the links related to that node. Do you want it to work on click instead? Or are you looking for a JavaScript implementation of `networkD3`'s output with highlighting of the node's links on hover? – CJ Yetman Sep 14 '17 at 09:13
  • I want to highlight all the nodes and links that related to the clicked node, this can only happen using Javascript and when i searched to insert this function into `networkD3`,i found that i've to use onRender in htmlwidget, but i couldn't succeed, and when i tried to change the content of the networkD3 manually through manipulating the `sankeyNetwork` widget , i succeed on doing this, but i found that the Javascript function with is `highlight_node_links` in the link above, is wrong, it doesn't do what i want, Please help me. – Omar Abd El-Naser Sep 14 '17 at 11:24
  • Can you be more specific about what you mean by “related”? And explain specifically how the behavior you want is different than the JavaScript example you posted and the default on hover behavior of networkD3? – CJ Yetman Sep 14 '17 at 11:31
  • I'll upload a picture for my data and i'll show you what i mean, thanks – Omar Abd El-Naser Sep 14 '17 at 11:40
  • Please check the question again, is it clear ? i can do what i want in shiny, but it doesn't look good and i've to use shiny to show the patterns and i don't want to depend always on shiny because i cant save it as .html – Omar Abd El-Naser Sep 14 '17 at 11:53
  • I don't understand how that is different than the bl.ock example that you linked to here: http://bl.ocks.org/git-ashish/8959771 If you go there and click the "Nuclear" node, all of the links that follow from "Nuclear" are highlighted. – CJ Yetman Sep 14 '17 at 11:59
  • To know the problem in the bl.ocks, please click on Wave in the left column, you will find that everything related to Electricity grid node will be highlighted, not only the links that related to Wave – Omar Abd El-Naser Sep 14 '17 at 12:01
  • but you say in your question text that if you click on Bangkok that it should highlight ClimateChange and EnergyShortage, .... then highlight Infrastructure&Ecosystems, and Leadership&strategy? – CJ Yetman Sep 14 '17 at 12:34
  • and how is it different than the default behavior of hovering over a node in the networkD3 output (other than triggering on hover instead of on click)? – CJ Yetman Sep 14 '17 at 12:43
  • Please check my data for Bangkok, and according to click, i want to use click not hovering because there're tooltips when i hover on the link, so when i hover on node to show the relations between nodes then i move my mouse to the link, the highlighted links will back to normal and that's the reason i wanna have to use click event – Omar Abd El-Naser Sep 14 '17 at 12:46
  • so you want the default behavior from hovering in networkD3's output, but you want it to happen on click? – CJ Yetman Sep 14 '17 at 12:48
  • Yep, but when i hover on Bangkok, the default hovering is to highlight the only links going to the next column only .... what i want is to highlight all the links to the other columns that are in the same row with Bangkok in the dataframe – Omar Abd El-Naser Sep 14 '17 at 12:49
  • right, that's the default behavior of the bl.ocks example... so which do you want? – CJ Yetman Sep 14 '17 at 12:50
  • i need the hovering system in the bl.ocks, but the highlighted function in bl.ocks has a problem , you can check this when you click on Wave node in the left column in bl.ocks example, it will high light the link between Wave and Electricity grid, now the problem happens, which is , it highlightes all the links the has Electricity grid as a source node, and these links doesn't have a relation with Wave node, that's not clear? please tell me and i'll try to explain it in another way – Omar Abd El-Naser Sep 14 '17 at 12:55
  • they are "related" because they are targets of a node that is a target of "Wave". The same happens for "Nuclear"... it highlights the direct link to "thermal Generation", and it also higlights the links to "Losses" and "Industry" etc. because they are target nodes of the target nodes. – CJ Yetman Sep 14 '17 at 12:59
  • Please check my third pic and the second pic, i want only the links in the second pic to be highlighted . – Omar Abd El-Naser Sep 14 '17 at 13:01
  • For simplicity, i'm using the sankey graph like collapsible tree.... every column of the the sankey graph describe something related to the Bangkok. – Omar Abd El-Naser Sep 14 '17 at 13:07
  • You would need to arrange your data differently. Whatever data you give to sankeyNetwork to produce your image#1 does not identify, for example, the links that follow from "Infrastructure Failure" that began from "Bangkok" specifically. If you look at your data, you'll see that there is only 1 link following from "Infrastructure Failure", though there are 2 links leading to "Infrastructure Failure" from "Bangkok" and "Surat". You would have to arrange your data differently to be able to highlight the separate groups of links. – CJ Yetman Sep 14 '17 at 13:15
  • May i talk with yo please through chat ? – Omar Abd El-Naser Sep 14 '17 at 13:19
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/154452/discussion-between-omar-abd-el-naser-and-cj-yetman). – Omar Abd El-Naser Sep 14 '17 at 13:20
  • Please check my shiny graph https://setsna2.shinyapps.io/Sankey-ShinyForAllCities/ to know my data, and please take care of the below note in red – Omar Abd El-Naser Sep 14 '17 at 13:25
  • your Shiny app probably subsets the data and then regenerates the plot – CJ Yetman Sep 14 '17 at 13:36
  • does my answer below sufficiently resolve your problem? – CJ Yetman Apr 12 '18 at 18:34

2 Answers2

2

Given the R code data structure you provided...

First, sankeyNetwork expects data that lists edges/links and the nodes that are connected by those links. Your data has a... let's call it a "traveler"-centric format, where each row of your data is related to a specific "path". So first you need to convert that data into the type of data that sankeyNetwork needs, while retaining the information needed to identify links to the path they came from. Additionally, your data only has one city in it, so it will be hard to see the result unless there's at least two different origins for the paths in your data, so I'll duplicate it and attribute the second set to a different city. Here's an example of that...

library(tidyverse)

# duplicate the data for another city so we have more than 1 origin
links <-
  df %>%
  full_join(mutate(df, City = "Hong Kong")) %>%
  mutate(row = row_number()) %>%
  mutate(origin = .[[1]]) %>%
  gather("column", "source", -row, -origin) %>%
  mutate(column = match(column, names(df))) %>%
  arrange(row, column) %>%
  group_by(row) %>%
  mutate(target = lead(source)) %>%
  ungroup() %>%
  filter(!is.na(target)) %>%
  select(source, target, origin) %>%
  group_by(source, target, origin) %>%
  summarise(count = n()) %>%
  ungroup()

nodes <- data.frame(name = unique(c(links$source, links$target)))
links$source <- match(links$source, nodes$name) - 1
links$target <- match(links$target, nodes$name) - 1

Now you have a links and nodes data frame in the form that sankeyNetwork expects, and the links data frame has an extra column origin that identifies which city each link is on the path from. You can now plot this with sankeyNetwork, add back in the origin data since it gets stripped out, and then use htmlwidgets::onRender to assign a click behavior that changes the opacity of any link whose origin is the city node that was clicked...

library(networkD3)
library(htmlwidgets)

sn <- sankeyNetwork(Links = links, Nodes = nodes, Source = 'source',
                    Target = 'target', Value = 'count', NodeID = 'name')

# add origin back into the links data because sankeyNetwork strips it out
sn$x$links$origin <- links$origin


# add onRender JavaScript to set the click behavior
htmlwidgets::onRender(
  sn,
  '
  function(el, x) {
    var nodes = d3.selectAll(".node");
    var links = d3.selectAll(".link");
    nodes.on("mousedown.drag", null); // remove the drag because it conflicts
    nodes.on("click", clicked);
    function clicked(d, i) {
      links
        .style("stroke-opacity", function(d1) {
            return d1.origin == d.name ? 0.5 : 0.2;
          });
    }
  }
  '
)

Here is a simplified version of the above answer (with a smaller example dataset) which keeps each "path" separate, rather than aggregating like paths and incrementing a count/Value variable.

library(dplyr)
library(tidyr)
library(networkD3)
library(htmlwidgets)

df <- read.csv(header = T, as.is = T, text = '
name,origin,layover,destination
Bob,Baltimore,Chicago,Los Angeles
Bob,Baltimore,Chicago,Seattle
Bob,New York,St Louis,Austin
Bob,New York,Chicago,Seattle
Tom,Baltimore,Chicago,Los Angeles
Tom,New York,St Louis,San Diego
Tom,New York,Chicago,Seattle
Tom,New York,New Orleans,Austin
')

links <-
  df %>%
  mutate(row = row_number()) %>%
  mutate(traveler = .[[1]]) %>%
  gather("column", "source", -row, -traveler) %>%
  mutate(column = match(column, names(df))) %>%
  arrange(row, column) %>%
  group_by(row) %>%
  mutate(target = lead(source)) %>%
  ungroup() %>%
  filter(!is.na(target)) %>%
  select(source, target, traveler) %>%
  group_by(source, target, traveler) %>%
  summarise(count = n()) %>%
  ungroup()

nodes <- data.frame(name = unique(c(links$source, links$target)))
links$source <- match(links$source, nodes$name) - 1
links$target <- match(links$target, nodes$name) - 1

sn <- sankeyNetwork(Links = links, Nodes = nodes, Source = 'source',
                    Target = 'target', Value = 'count', NodeID = 'name')

# add origin back into the links data because sankeyNetwork strips it out
sn$x$links$traveler <- links$traveler

# add onRender JavaScript to set the click behavior
htmlwidgets::onRender(
  sn,
  '
  function(el, x) {
    var nodes = d3.selectAll(".node");
    var links = d3.selectAll(".link");
    nodes.select("rect").style("cursor", "pointer");
    nodes.on("mousedown.drag", null); // remove the drag because it conflicts
    //nodes.on("mouseout", null);
    nodes.on("click", clicked);
    function clicked(d, i) {
      links
        .style("stroke-opacity", function(d1) {
            return d1.traveler == d.name ? 0.5 : 0.2;
          });
    }
  }
  '
)

enter image description here enter image description here

CJ Yetman
  • 8,373
  • 2
  • 24
  • 56
2

The implementation for this question is in this shiny app.

https://setsna2.shinyapps.io/sankey-shinyforallcities/

I had to modify networkD3 from inside, i installed it normally and copied it inside the directory that contains the shiny app and put the package inside R-lib.

I made some modification to sankeyNetwork.js function that plot the sankey graph. Here's a picture for the directory, it shows the structure of the directory to reach the place that has sankeyNetwork.js to change it manually.

Please notice that the version of sankeyNetwork.js i used and uploaded in this question is old, it's from 2 years ago, so u can download the new version of networkD3 and just modify the part i'll mention next. enter image description here What i changed in sankeyNetwork.js is to add

    .on('mouseover', function(node) {
        Shiny.onInputChange("node_name", node.name);
    })

Which means if someone hover on a node, i'll transfer the nodename as "node_name" variable to my R session by using Shiny.onInputChange, u can read more about this shiny function online.

Here's the sankeyNetwork.js i used to know what i mean.

Now, if someone hover on a node, i can get the name of this node and send it to R, and if he moved away his cursor, i won't get any name, that's the core idea.

You can check the code of my shiny app by clicking here

You can see part of Data0 variable here also Goals variable from here.

In R code, you gonna find some comments say "for debug use this code" or comments within the code, so if u run these comments, u will understand how the data looks like before running the shiny app to fully understand how sankey graphs reads the data and how it should look like.

In R code, you gonna find this part which is reading the node_name from sankeyNetwork.js

        NodeName <- reactive({ 
                if(length(input$node_name)>0){return(as.character(input$node_name))}
                else{return(0)}
        })

Then the next part in the code is to check if the NodeName is in my Nodes dataframe, if it exists, then i'll get all the nodes that related to this node, then i'll get the links ids that connect these nodes with each other, and please notice that the links id start from 0 not from 1, because javascript starts from 0 and R starts from 1.

Now we have the NodeName that the user is hovering on, and the Links that related to this node, now we can make the sankey graph and save it in sn, then i remove the old tooltip and add a new one.

Using onRender to modify sankey graph while using shiny and i used it to make the Highlighting function to modify sankey graph while running shiny and when the user hover on a node, i'll get the name of the node then gets the links ids and search for the links ids in the existed sankey graph and increase it's opacity.

Please note that if u run the application, u gonna get errors, u have to upload it on shinyapps.io to debug it, that was the way i was checking if my application works correct or not, maybe u can find another way to debug.