Search code examples
jsond3.jstransitionsankey-diagram

Sankey diagram transition


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?


Solution

  • This is possible. Here's one approach using a csv file.

    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);