Creating a Sankey Diagram with D3 Charts

Well Hello There

Recently I have seen a sharp uptick in the number of Sankey Diagrams on one of my favourite Subreddits r/DataIsBeautiful and decided it was about time I gave one a go.

What is a Sankey Diagram?

A Sankey Diagram is a visualisation technique that displays data flows. A number of items(nodes) are represented by rectangles or text and the links between them are represented with lines that have a width proportional to the importance of the flow. They can be used to show how the data flows from source to end, or how the data evolves through each link.
Use cases include showing the source of energy, paths and it's usage , another could be showing the flow of an infection such as Covid in a local community.

What We'll build

We will create a simple Sankey Diagram to will display the distribution flow of items sold to customers from a ficticious store offering instore and online sales to customers.

Getting Started

First off add the script links for D3.js and following that the Sankey Plugin. The order here is important ! Then within the body tag create a Div element which we will insert the diagram into.

<!-- Load d3  -->
<script src="https://d3js.org/d3.v4.min.js"></script>

<!-- Load  sankey.js  -->
<script src="https://cdn.jsdelivr.net/gh/holtzy/D3-graph-gallery@master/LIB/sankey.js"></script>


<body>
    <div id="sankey_example"></div>
</body>


Node and Link Structure

Next prepare the nodes and link variables. You could fetch these from an external source but I have created my own arrays. Their format is like below.
// Nodes are identified by the node property 
var nodes = [{
  node: 0,
  name: "Takeaway"
}];
// The Links require a weight / value 
// The Source and target properties are the values from the node property in the above.
var links = [{
  source: 9,
  target: 1,
  value: 4
}]


Initialize the Sankey Diagram

Now comes the meat of the task - creating the diagram itself !
// set the dimensions of the graph and margins
    var margin = {top: 10, right: 10, bottom: 10, left: 10},
        width = (window.screen.availWidth /2) - margin.left - margin.right,
        height = (window.screen.availHeight -100) - margin.top - margin.bottom;
    
    // append the svg object to the body of the page
    var svg = d3.select("#sankey_example").append("svg")
        .attr("width", width + margin.left + margin.right)
        .attr("height", height + margin.top + margin.bottom)
      .append("g")
        .attr("transform",
              "translate(" + margin.left + "," + margin.top + ")");
    
    // Color scale used
    var color = d3.scaleOrdinal(d3.schemeCategory10);
    
    // Set the sankey diagram properties
    var sankey = d3.sankey()
        .nodeWidth(36)
        .nodePadding(290)
        .size([width, height]);
    

      // Constructs a new Sankey generator with the default settings.
      // Using the nodes and links from our data
      sankey
          .nodes(nodes)
          .links(links)
          .layout(32);
    
      // Add in the links
      var link = svg.append("g")
        .selectAll(".link")
        .data(links)
        .enter()
        .append("path")
          .attr("class", "link")
          .attr("d", sankey.link() )
          .style("stroke-width", function(d) { return Math.max(1, d.dy); })
          .sort(function(a, b) { return b.dy - a.dy; })
         ;
      // Add the link titles
      link.append("title")
        .text(function(d) {
    	  	return d.source.name + " → " +  d.target.name + "\n" + (d.value); });
        
      // add in the nodes
      var node = svg.append("g")
        .selectAll(".node")
        .data(nodes)
        .enter().append("g")
          .attr("class", "node")
          .attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
          .call(d3.drag()
            .subject(function(d) { return d; })
            .on("start", function() { this.parentNode.appendChild(this); })
            .on("drag", dragmove));
    
      // add the rectangles for the nodes
      node
        .append("rect")
          .attr("height", function(d) { return d.dy; })
          .attr("width", sankey.nodeWidth())
          .style("fill", function(d) { return d.color = color(d.name.replace(/ .*/, "")); })
          .style("stroke", function(d) { return d3.rgb(d.color).darker(2); })
        // Add hover text
        .append("title")
          .text(function(d) { return d.name + "\n" + "There is " + d.value + " items of this type."; });
    
      // add in the title for the nodes
        node
          .append("text")
            .attr("x", -6)
            .attr("y", function(d) { return d.dy / 2; })
            .attr("dy", ".35em")
            .attr("text-anchor", "end")
            .attr("transform", null)
            .text(function(d) { return d.name; })
          .filter(function(d) { return d.x < width / 2; })
            .attr("x", 6 + sankey.nodeWidth())
            .attr("text-anchor", "start");
    
      // the function for moving the nodes
      function dragmove(d) {
        d3.select(this)
          .attr("transform",
                "translate("
                   + d.x + ","
                   + (d.y = Math.max(
                      0, Math.min(height - d.dy, d3.event.y))
                     ) + ")");
        sankey.relayout();
        link.attr("d", sankey.link() );
      }
    


Adding Interactivity

Next we can add some simple interactivity with an onlick function. I have added a simple Alert() but you can add anything you want here !

// Add Link Interactivity 
      link.on("click", function(d){
            console.log(d);
            alert("From:  " + d.source.name +" To:"  +d.target.name   +" Value:"+d.value);
          })


Wrapping Up

That completes our basic example. You can see the full example in the JSfiddle below.

Comments