block by syntagmatic 77c7f7e8802e8824eed473dd065c450b

Sankey Transitions

Full Screen

This example created for development purposes using the d3-sankey plugin for D3 4.0.

Based on xaranke’s port of Mike Bostock’s Sankey Diagram with drag behavior enabled and several fixes for the d3 version 4.x release.

It would be nice if d3-sankey’s value accessor was configurable, to avoid creating the empty_links data structure in the code below.

index.html

<!DOCTYPE html>
<html>
<meta charset="utf-8">
<title>Sankey Diagram</title>
<style>
body {
  font-family: sans-serif;
}

#chart {
  height: 500px;
}

.node rect {
  cursor: move;
  fill-opacity: .9;
  shape-rendering: crispEdges;
}

.node text {
  pointer-events: none;
  font-size: 12px;
}

.link {
  fill: none;
  stroke: #000;
  stroke-opacity: .16;
}

.link:hover {
  stroke-opacity: .5;
}

</style>
<body>
<div id="chart"></div>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://unpkg.com/d3-sankey@0.4.1"></script>
<script>

var margin = {
    top: 1,
    right: 1,
    bottom: 6,
    left: 1
  },
  width = 960 - margin.left - margin.right,
  height = 500 - margin.top - margin.bottom;

var fontScale = d3.scaleLinear()
  .range([8, 30]);

var formatNumber = d3.format(",.0f"),
  format = function(d) {
    return formatNumber(d) + " TWh";
  },
  color = d3.scaleOrdinal(d3.schemeCategory20);

var svg = d3.select("#chart").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 + ")");

var weightText = svg.append("text")
  .text("Links weighted equally")
  .attr("x", width/2-170)
  .attr("y", height)
  .style("font-size", "24px");

var sankey = d3.sankey()
  .nodeWidth(15)
  .nodePadding(10)
  .size([width, height]);

var path = sankey.link();

d3.json("energy.json", function(energy) {
  var empty_links = energy.links.map(function(d) {
    d.id = d.source + " -> " + d.target;
    return {
      source: d.source,
      target: d.target,
      id: d.id,
      value: 1
    }
  });

  sankey
    .nodes(energy.nodes)
    .links(empty_links)
    .layout(32);

  fontScale.domain(d3.extent(energy.nodes, function(d) { return d.value }));

  var link = svg.append("g").selectAll(".link")
    .data(empty_links, function(d) { return d.id; })
    .enter().append("path")
    .attr("class", "link")
    .attr("d", path)
    .style("stroke-width", function(d) {
      return Math.max(1, d.dy) + "px";
    })
    .sort(function(a, b) {
      return b.dy - a.dy;
    });

  link.append("title")
    .text(function(d) {
      return d.source.name + " → " + d.target.name + "\n" + format(d.value);
    });

  var node = svg.append("g").selectAll(".node")
    .data(energy.nodes, function(d) { return d.name; })
    .enter().append("g")
    .attr("class", "node")
    .attr("transform", function(d) {
      return "translate(" + d.x + "," + d.y + ")";
    });

  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(1.8);
    })
    .append("title")
    .text(function(d) {
      return d.name + "\n" + format(d.value);
    });

  node.append("text")
    .attr("x", -6)
    .attr("y", function(d) {
      return d.dy / 2;
    })
    .attr("dy", ".35em")
    .attr("text-anchor", "end")
    .attr("transform", null)
    .style("fill", function(d) {
      return d3.rgb(d.color).darker(2.4);
    })
    .text(function(d) {
      return d.name;
    })
    .style("font-size", function(d) {
      return Math.floor(fontScale(d.value)) + "px";
    })
    .filter(function(d) {
      return d.x < width / 2;
    })
    .attr("x", 6 + sankey.nodeWidth())
    .attr("text-anchor", "start");

  function update(nodeData, linkData) {
    sankey
      .nodes(nodeData)
      .links(linkData)
      .layout(32);

    sankey.relayout();
    fontScale.domain(d3.extent(nodeData, function(d) { return d.value }));

    svg.selectAll(".link")
      .data(linkData, function(d) { return d.id; })
      .sort(function(a, b) {
        return b.dy - a.dy;
      })
      .transition()
      .duration(1300)
      .attr("d", path)
      .style("stroke-width", function(d) {
        return Math.max(1, d.dy) + "px";
      });

    svg.selectAll(".node")
      .data(nodeData, function(d) { return d.name; })
      .transition()
      .duration(1300)
      .attr("transform", function(d) {
        return "translate(" + d.x + "," + d.y + ")";
      });

    svg.selectAll(".node rect")
      .transition()
      .duration(1300)
      .attr("height", function(d) {
        return d.dy;
      });

    svg.selectAll(".node text")
      .transition()
      .duration(1300)
      .attr("y", function(d) {
        return d.dy / 2;
      })
      .style("font-size", function(d) {
        return Math.floor(fontScale(d.value)) + "px";
      });
  };

  var counter = 0;
  function toggleTransition() {
    counter++;
    var activeLinks = counter % 2 ? energy.links : empty_links;
    weightText.text(counter % 2 ? "Links weighted by value" : "Links weighted equally");
    update(energy.nodes, activeLinks);
    setTimeout(toggleTransition, 2400);
  };

  setTimeout(toggleTransition, 2400);
});

</script>

energy.json

{
  "nodes": [{
    "name": "Agricultural 'waste'"
  }, {
    "name": "Bio-conversion"
  }, {
    "name": "Liquid"
  }, {
    "name": "Losses"
  }, {
    "name": "Solid"
  }, {
    "name": "Gas"
  }, {
    "name": "Biofuel imports"
  }, {
    "name": "Biomass imports"
  }, {
    "name": "Coal imports"
  }, {
    "name": "Coal"
  }, {
    "name": "Coal reserves"
  }, {
    "name": "District heating"
  }, {
    "name": "Industry"
  }, {
    "name": "Heating and cooling - commercial"
  }, {
    "name": "Heating and cooling - homes"
  }, {
    "name": "Electricity grid"
  }, {
    "name": "Over generation / exports"
  }, {
    "name": "H2 conversion"
  }, {
    "name": "Road transport"
  }, {
    "name": "Agriculture"
  }, {
    "name": "Rail transport"
  }, {
    "name": "Lighting & appliances - commercial"
  }, {
    "name": "Lighting & appliances - homes"
  }, {
    "name": "Gas imports"
  }, {
    "name": "Ngas"
  }, {
    "name": "Gas reserves"
  }, {
    "name": "Thermal generation"
  }, {
    "name": "Geothermal"
  }, {
    "name": "H2"
  }, {
    "name": "Hydro"
  }, {
    "name": "International shipping"
  }, {
    "name": "Domestic aviation"
  }, {
    "name": "International aviation"
  }, {
    "name": "National navigation"
  }, {
    "name": "Marine algae"
  }, {
    "name": "Nuclear"
  }, {
    "name": "Oil imports"
  }, {
    "name": "Oil"
  }, {
    "name": "Oil reserves"
  }, {
    "name": "Other waste"
  }, {
    "name": "Pumped heat"
  }, {
    "name": "Solar PV"
  }, {
    "name": "Solar Thermal"
  }, {
    "name": "Solar"
  }, {
    "name": "Tidal"
  }, {
    "name": "UK land based bioenergy"
  }, {
    "name": "Wave"
  }, {
    "name": "Wind"
  }],
  "links": [{
    "source": 0,
    "target": 1,
    "value": 124.729
  }, {
    "source": 1,
    "target": 2,
    "value": 0.597
  }, {
    "source": 1,
    "target": 3,
    "value": 26.862
  }, {
    "source": 1,
    "target": 4,
    "value": 280.322
  }, {
    "source": 1,
    "target": 5,
    "value": 81.144
  }, {
    "source": 6,
    "target": 2,
    "value": 35
  }, {
    "source": 7,
    "target": 4,
    "value": 35
  }, {
    "source": 8,
    "target": 9,
    "value": 11.606
  }, {
    "source": 10,
    "target": 9,
    "value": 63.965
  }, {
    "source": 9,
    "target": 4,
    "value": 75.571
  }, {
    "source": 11,
    "target": 12,
    "value": 10.639
  }, {
    "source": 11,
    "target": 13,
    "value": 22.505
  }, {
    "source": 11,
    "target": 14,
    "value": 46.184
  }, {
    "source": 15,
    "target": 16,
    "value": 104.453
  }, {
    "source": 15,
    "target": 14,
    "value": 113.726
  }, {
    "source": 15,
    "target": 17,
    "value": 27.14
  }, {
    "source": 15,
    "target": 12,
    "value": 342.165
  }, {
    "source": 15,
    "target": 18,
    "value": 37.797
  }, {
    "source": 15,
    "target": 19,
    "value": 4.412
  }, {
    "source": 15,
    "target": 13,
    "value": 40.858
  }, {
    "source": 15,
    "target": 3,
    "value": 56.691
  }, {
    "source": 15,
    "target": 20,
    "value": 7.863
  }, {
    "source": 15,
    "target": 21,
    "value": 90.008
  }, {
    "source": 15,
    "target": 22,
    "value": 93.494
  }, {
    "source": 23,
    "target": 24,
    "value": 40.719
  }, {
    "source": 25,
    "target": 24,
    "value": 82.233
  }, {
    "source": 5,
    "target": 13,
    "value": 0.129
  }, {
    "source": 5,
    "target": 3,
    "value": 1.401
  }, {
    "source": 5,
    "target": 26,
    "value": 151.891
  }, {
    "source": 5,
    "target": 19,
    "value": 2.096
  }, {
    "source": 5,
    "target": 12,
    "value": 48.58
  }, {
    "source": 27,
    "target": 15,
    "value": 7.013
  }, {
    "source": 17,
    "target": 28,
    "value": 20.897
  }, {
    "source": 17,
    "target": 3,
    "value": 6.242
  }, {
    "source": 28,
    "target": 18,
    "value": 20.897
  }, {
    "source": 29,
    "target": 15,
    "value": 6.995
  }, {
    "source": 2,
    "target": 12,
    "value": 121.066
  }, {
    "source": 2,
    "target": 30,
    "value": 128.69
  }, {
    "source": 2,
    "target": 18,
    "value": 135.835
  }, {
    "source": 2,
    "target": 31,
    "value": 14.458
  }, {
    "source": 2,
    "target": 32,
    "value": 206.267
  }, {
    "source": 2,
    "target": 19,
    "value": 3.64
  }, {
    "source": 2,
    "target": 33,
    "value": 33.218
  }, {
    "source": 2,
    "target": 20,
    "value": 4.413
  }, {
    "source": 34,
    "target": 1,
    "value": 4.375
  }, {
    "source": 24,
    "target": 5,
    "value": 122.952
  }, {
    "source": 35,
    "target": 26,
    "value": 839.978
  }, {
    "source": 36,
    "target": 37,
    "value": 504.287
  }, {
    "source": 38,
    "target": 37,
    "value": 107.703
  }, {
    "source": 37,
    "target": 2,
    "value": 611.99
  }, {
    "source": 39,
    "target": 4,
    "value": 56.587
  }, {
    "source": 39,
    "target": 1,
    "value": 77.81
  }, {
    "source": 40,
    "target": 14,
    "value": 193.026
  }, {
    "source": 40,
    "target": 13,
    "value": 70.672
  }, {
    "source": 41,
    "target": 15,
    "value": 59.901
  }, {
    "source": 42,
    "target": 14,
    "value": 19.263
  }, {
    "source": 43,
    "target": 42,
    "value": 19.263
  }, {
    "source": 43,
    "target": 41,
    "value": 59.901
  }, {
    "source": 4,
    "target": 19,
    "value": 0.882
  }, {
    "source": 4,
    "target": 26,
    "value": 400.12
  }, {
    "source": 4,
    "target": 12,
    "value": 46.477
  }, {
    "source": 26,
    "target": 15,
    "value": 525.531
  }, {
    "source": 26,
    "target": 3,
    "value": 787.129
  }, {
    "source": 26,
    "target": 11,
    "value": 79.329
  }, {
    "source": 44,
    "target": 15,
    "value": 9.452
  }, {
    "source": 45,
    "target": 1,
    "value": 182.01
  }, {
    "source": 46,
    "target": 15,
    "value": 19.013
  }, {
    "source": 47,
    "target": 15,
    "value": 289.366
  }]
}