Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sankey diagram transition

Tags:

I would like to know if there is an easy way to modify the Sankey diagram example so that there is smooth transition to new data. For example, imagine I have different datafiles (energy1.json, energy2.json ...) how could d3 plot a Sankey diagram for the first dataset, then waits and later on rearanges the boxes disposition to represent the second dataset?

like image 451
Mermoz Avatar asked Nov 28 '12 11:11

Mermoz


People also ask

When should you use a Sankey diagram?

Sankey diagrams are used to show weighted networks, i.e. flows. It can happen with several data structure: evolution: the nodes are duplicated in 2 or more groups that represent stages. Connections show the evolution between two states, like in the migration example above.

Does the length of a Sankey diagram matter?

(The length of the arrows does not matter in a Sankey Diagram.) Useful energy transfers are shown going left to right. Wasteful energy transfers are shown going downwards. Power stations are usually not very efficient.


2 Answers

This is possible. Here's one approach using a csv file. Working sankey here: https://www.betterment.com/resources/investment-strategy/portfolio-management/portfolio-diversification/

  1. Define a global array outside of your d3.csv call.

    var portfolioValues = []; 
  2. When parsing the csv to create the node/link structure, push values to your global array.

    d3.csv("etf-geo.csv", function(error, data) {     graph = {"nodes" : [], "links" : []};     data.forEach(function (d, i) {         var item = { source: d.source, target: d.target, values: [] };         for (var j=0; j < 101; j++) {             item.values.push(d['value'+j.toString()]);         }         portfolioValues.push(item);         graph.nodes.push({ "name": d.source });         graph.nodes.push({ "name": d.target });         graph.links.push({             source: portfolioValues[i].source,             target: portfolioValues[i].target,             value: portfolioValues[i].values[startingAllocation]         });     });  //this handy little function returns only the distinct / unique nodes graph.nodes = d3.keys(     d3.nest()         .key(function (d) { return d.name; })         .map(graph.nodes) );  // it appears d3 with force layout wants a numeric source and target // so loop through each link replacing the text with its index from node graph.links.forEach(function (d, i) {     graph.links[i].source = graph.nodes.indexOf(graph.links[i].source);     graph.links[i].target = graph.nodes.indexOf(graph.links[i].target);     portfolioValues[i].source = graph.links[i].source;     portfolioValues[i].target = graph.links[i].target; });  // now loop through each nodes to make nodes an array of objects // rather than an array of strings graph.nodes.forEach(function (d, i) {     graph.nodes[i] = { "name": d }; });  // construct sankey sankey     .nodes(graph.nodes)     .links(graph.links)     .layout(); 
  3. Listen for a change and pass user input to your update function.

    $(".sankey-slider").bind("slider:changed", function (event, data) {  slideValue = data.value;  updateData(parseInt(slideValue));   }); 
  4. Create a temporary array and retrieve the correct values from the global array. Call the sankey functions to recalculate the layout.

        var newLinks = [];      portfolioValues.forEach(function(p, i) {         newLinks.push({           source: p.source,           target: p.target,           value: p.values[allocation]         });     });      graph.links = newLinks;      sankey     .nodes(graph.nodes)     .links(graph.links)     .size([width, height])     .layout(); 
  5. Select each element that needs to be changed and pass the new data values.

    d3.selectAll(".link")   .data(graph.links)   .attr("d", path)   .attr("id", function(d,i){     d.id = i;     return "link-"+i;   })   .style("stroke-width", function(d) { return Math.max(1, d.dy); })   .sort(function(a, b) { return b.dy - a.dy; });  d3.selectAll(".node").attr("transform", function(d) {   return "translate(" + d.x + "," + d.y + ")"; });  d3.selectAll("rect") .attr("height", function(d) { return d.dy; }) .on("mouseover",highlight_node_links) .on("mouseout",onNodeMouseout); 

Working sankey here: https://www.betterment.com/resources/investment-strategy/portfolio-management/portfolio-diversification/

like image 151
Joe Jansen Avatar answered Oct 15 '22 01:10

Joe Jansen


Since the automatic positioning of nodes includes a part which tries to minimize link distance in a connected graph which is an np optimization problem, any kind of optimizer can potentially jump from one minimum to another leading to a jump in layout. So a guaranteed smooth transition wont be possible.

The closest possible solution would probably be to linearly interpolate between the two input data sets and thereby generate a series of graphs which (depending on the data) more or less smoothly transition from one two the other.

Hope this helps.

like image 43
Cornelius Avatar answered Oct 15 '22 02:10

Cornelius