Skip to content

Instantly share code, notes, and snippets.

@lsquaredleland
Last active October 4, 2023 08:11
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lsquaredleland/c6dce4eafddf04d023d3 to your computer and use it in GitHub Desktop.
Save lsquaredleland/c6dce4eafddf04d023d3 to your computer and use it in GitHub Desktop.
Circular Sankey

Circular Sankey

Was inspired by some of the diagrams at sankey-diagram.com. They have some stellar examples of sankey diagrams of all sorts. The purpose of a circular sankey is to show how the flow of something might be circular, meaning that some of the original is recycled for the next cycle.

Implimentation

Very similar to a traditional donut chart, except this case the outsideRadius is decreasing. And the difference generates an offshoot.

To Try:

  • Find a way to create a common data structure to populate this visualisation (Especially take into account multiple offshoots)
  • Might want to try to use curved offshoots
  • Experiment with arcs rather than pie slices?
  • Impliment multiple offshoots at the end of a slice
  • Benefit of having an arc inside of the slice -> represent that something has partially diverged
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Circular Sankey</title>
<script src="https://d3js.org/d3.v3.min.js"></script>
</head>
<body>
<div id="chartArea">
</div>
<script src="main_donut.js"></script>
</body>
</html>
var data = [
{v: 350, t: 90, c:[50,25,25]},
{v: 250, t: 50, c:[10,20]},
{v: 220, t: 17.5, c:[10,10]},
{v: 200, t: 30, c:[75,25]},
{v: 100, t: 55, c:[5,15]},
{v: 80, t: 50, c:[10,20]},
{v: 50, t: 67.5}
];
//Is it necessary for data to have values beyond first one..because just subtract
// -> cannot visualise magical unaccounted values
//Data is structured in val of outerRadius + width of slice (time)
//What if the data was represented as a circular linked list....
generateDonut(data);
function generateDonut(data){
const h = 1000;
const w = 1000;
const smallestDim = Math.min(h,w)
const innerRadius = smallestDim/10;
const startAngle = -1*Math.PI;
const rScale = d3.scale.linear()
.domain([0,d3.max(data, function(d) { return d.v; })])
.range([0, smallestDim/2-innerRadius]);
var pie = d3.layout.pie()
.value(function(d){ return d.t || 10; })
.startAngle(startAngle)
.endAngle(startAngle + 2*Math.PI)
.sort(function(a,b) { a.v < b.v; }) //setting the order of the slices
var arc = d3.svg.arc()
//If set inner + outer radius it is not contained in the dom object....
var svg = d3.select("#chartArea").append("svg")
.attr("width", w)
.attr("height", h)
.append("g")
.attr("transform", "translate(" + w / 2 + "," + h / 2 + ")");
const colour = d3.scale.category20b();
var pieData = pie(data)
svg.selectAll("path")
.data(pieData)
.enter().append("path")
.each(function(d, i) {
d.outerRadius = innerRadius + rScale(d.data.v) //might want to scale d.data.h...
d.innerRadius = innerRadius
d.startAngle = startAngle //This makes all arcs have 1 origin -> looks nice
})
.attr("d", arc)
.style('fill', function(d) { return colour(d.outerRadius); })
.each(function(d,i) { createOffShoot(d, i); })
//Bind data to this instead, have 1 g element for each slice + offshoot?
function createOffShoot(d, i) {
const nextSlice = i === pieData.length - 1 ? pieData[0] : pieData[i+1]
const h = rScale(200)
const angle = d.endAngle * 180 / Math.PI - 90;
const offshoots = d.data.c || [];
offshoots.forEach(function(offshoot, i2) {
const offset = rScale(d3.sum(offshoots.slice(0,i2)))
const width = rScale(offshoot)
const x = (d.outerRadius - offset) * Math.sin(d.endAngle);
const y = -1 * (d.outerRadius - offset) * Math.cos(d.endAngle);
const points = x+','+y+' '+(x-width)+','+y+' '+
(x-width)+','+(y+h)+' '+(x-width/2)+','+(y+Math.sin(Math.PI/3)*width+h)+' '+
x+','+(y+h)
//Since all the coordinate of the rect + triangle is known.... -> make polygon?
var outflow = svg.append('g')
.attr('transform', 'rotate(' + angle + ',' + x + ',' + y +')')
.attr('fill', colour(d.outerRadius))
.attr('stroke', "orange")
outflow.append('polygon')
.attr('points', points)
})
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment