Skip to content

Instantly share code, notes, and snippets.

@eesur
Last active June 21, 2016 19:04
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 eesur/0e9820fb577370a13099 to your computer and use it in GitHub Desktop.
Save eesur/0e9820fb577370a13099 to your computer and use it in GitHub Desktop.
Chord diagram to explore overlap of lifespans
who overlap years
g1 g2 35
g1 g3 60
g1 g4 5
g1 g5 0
g1 g6 0
g1 g7 0
g1 g8 0
g1 g9 0
g1 g10 0
g2 g1 35
g2 g3 48
g2 g4 18
g2 g5 0
g2 g6 0
g2 g7 0
g2 g8 0
g2 g9 0
g2 g10 0
g3 g1 60
g3 g2 48
g3 g4 40
g3 g5 11
g3 g6 0
g3 g7 0
g3 g8 0
g3 g9 0
g3 g10 0
g4 g1 5
g4 g2 18
g4 g3 40
g4 g5 18
g4 g6 0
g4 g7 0
g4 g8 0
g4 g9 0
g4 g10 0
g5 g1 0
g5 g2 0
g5 g3 11
g5 g4 18
g5 g6 11
g5 g7 0
g5 g8 0
g5 g9 0
g5 g10 0
g6 g1 0
g6 g2 0
g6 g3 0
g6 g4 0
g6 g5 0
g6 g7 49
g6 g8 14
g6 g9 0
g6 g10 0
g7 g1 0
g7 g2 0
g7 g3 0
g7 g4 0
g7 g5 0
g7 g6 14
g7 g8 5
g7 g9 40
g7 g10 0
g8 g1 0
g8 g2 0
g8 g3 0
g8 g4 0
g8 g5 0
g8 g6 0
g8 g7 5
g8 g9 8
g8 g10 0
g9 g1 0
g9 g2 0
g9 g3 0
g9 g4 0
g9 g5 0
g9 g6 0
g9 g7 14
g9 g8 8
g9 g10 9
g10 g1 0
g10 g2 0
g10 g3 0
g10 g4 0
g10 g5 0
g10 g6 0
g10 g7 0
g10 g8 0
g10 g9 9
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
@import url(http://fonts.googleapis.com/css?family=Source+Code+Pro:400,600);
body {font-family: "Source Code Pro", Consolas, monaco, monospace; line-height: 160%; font-size: 14px; margin: 0; }
#tooltip {
color: #454545;
opacity: .9;
background: #eee;
padding: 5px;
border: none;
border-radius: 5px;
position: absolute;
z-index: 10;
visibility: hidden;
white-space: nowrap;
pointer-events: none;
}
#circle circle {
fill: none;
pointer-events: all;
}
path.group {
fill-opacity: .8;
}
path.chord {
fill-opacity: .8;
stroke: #000;
stroke-width: .25px;
}
#circle:hover path.fade {
display: none;
}
aside {padding: 20px;}
h1, h3 {font-size: 14px; font-weight: normal; padding-left: 40px;}
</style>
</head>
<body>
<section id="diagram"></section>
<div id="tooltip"></div>
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.4.13/d3.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.7.0/underscore-min.js"></script>
<script src="mapper.js"></script>
<script>
//*******************************************************************
// CREATE MATRIX AND MAP
//*******************************************************************
d3.csv('gurus.csv', function (error, data) {
var mpr = chordMpr(data);
mpr
.addValuesToMap('who')
.setFilter(function (row, a, b) {
return (row.who === a.name && row.overlap === b.name)
})
.setAccessor(function (recs, a, b) {
if (!recs[0]) return 0;
return +recs[0].years;
});
drawChords(mpr.getMatrix(), mpr.getMap());
});
//*******************************************************************
// DRAW THE CHORD DIAGRAM
//*******************************************************************
function drawChords (matrix, mmap) {
var w = 980, h = 800, r1 = h / 2, r0 = r1 - 100;
var fill = d3.scale.category10();
var chord = d3.layout.chord()
.padding(.02)
.sortSubgroups(d3.descending)
.sortChords(d3.descending);
var arc = d3.svg.arc()
.innerRadius(r0)
.outerRadius(r0 + 20);
var svg = d3.select("#diagram").append("svg")
.attr("width", w)
.attr("height", h)
.append("svg:g")
.attr("id", "circle")
.attr("transform", "translate(" + w / 2 + "," + h / 2 + ")");
svg.append("circle")
.attr("r", r0 + 20);
var rdr = chordRdr(matrix, mmap);
chord.matrix(matrix);
var g = svg.selectAll("g.group")
.data(chord.groups())
.enter().append("svg:g")
.attr("class", "group")
.on("mouseover", mouseover)
.on("mouseout", function (d) { d3.select("#tooltip").style("visibility", "hidden") });
g.append("svg:path")
.style("stroke", "none")
.style("fill", function(d) { return fill(d.index); })
.attr("d", arc);
g.append("svg:text")
.each(function(d) { d.angle = (d.startAngle + d.endAngle) / 2; })
.attr("dy", ".35em")
.attr("text-anchor", function(d) { return d.angle > Math.PI ? "end" : null; })
.attr("transform", function(d) {
return "rotate(" + (d.angle * 180 / Math.PI - 90) + ")"
+ "translate(" + (r0 + 26) + ")"
+ (d.angle > Math.PI ? "rotate(180)" : "");
})
.text(function(d) { return rdr(d).gname; });
var chordPaths = svg.selectAll("path.chord")
.data(chord.chords())
.enter().append("svg:path")
.attr("class", "chord")
.style("stroke", function(d) { return d3.rgb(fill(d.target.index)).darker(); })
.style("fill", function(d) { return fill(d.target.index); })
.attr("d", d3.svg.chord().radius(r0))
.on("mouseover", function (d) {
d3.select("#tooltip")
.style("visibility", "visible")
.html(chordTip(rdr(d)))
.style("top", function () { return (d3.event.pageY - 100)+"px"})
.style("left", function () { return (d3.event.pageX - 100)+"px";})
})
.on("mouseout", function (d) { d3.select("#tooltip").style("visibility", "hidden") });
function chordTip (d) {
var p = d3.format(".0%"), q = d3.format("0d")
return "Chord info:<br/>"
+ q(d.svalue) + " years overlap with " + d.sname + " and " + d.tname
}
function groupTip (d) {
var guru = d.gname, q = d3.format("0d");
switch (guru) {
case "g1": return "Guru Nanak"; //+ " lived for 70 years";
break;
case "g2": return "Guru Angad"; // + " lived for 48 years";
break;
case "g3": return "Guru Amar Das"; // + " lived for 95 years";
break;
case "g4": return "Guru Ram Das"; // + " lived for 47 years";
break;
case "g5": return "Guru Arjun Dev"; // + " lived for 43 years";
break;
case "g6": return "Guru Har Gobind"; // + " lived for 49 years";
break;
case "g7": return "Guru Har Rai"; // + " lived for 31 years";
break;
case "g8": return "Guru Har Krishan"; // + " lived for 8 years";
break;
case "g9": return "Guru Tegh Bahadar"; // + " lived for 54 years";
break;
case "g10": return "Guru Gobind Singh"; // + " lived for 42 years";
break;
default : return d.gname;
}
}
function mouseover(d, i) {
d3.select("#tooltip")
.style("visibility", "visible")
.html(groupTip(rdr(d)))
.style("top", function () { return (d3.event.pageY - 80)+"px"})
.style("left", function () { return (d3.event.pageX - 130)+"px";})
chordPaths.classed("fade", function(p) {
return p.source.index != i
&& p.target.index != i;
});
}
}
</script>
<aside>
<h1><a href="http://en.wikipedia.org/wiki/Chord_diagram">Chord diagram</a> to explore the overlap in years between different <a href="http://en.wikipedia.org/wiki/Sikh_gurus">Sikh gurus</a> life spans.</h1>
<h3>Key:</h3>
<ul>
<li>g1: Guru Nanak</li>
<li>g2: Guru Angad</li>
<li>g3: Guru Amar Das</li>
<li>g4: Guru Ram Das</li>
<li>g5: Guru Arjun Dev</li>
<li>g6: Guru Har Gobind</li>
<li>g7: Guru Har Rai</li>
<li>g8: Guru Har Krishan</li>
<li>g9: Guru Tegh Bahadar</li>
<li>g10: Guru Gobind Singh</li>
</ul>
</aside>
</body>
</html>
//*******************************************************************
// CHORD MAPPER
//*******************************************************************
function chordMpr (data) {
var mpr = {}, mmap = {}, n = 0,
matrix = [], filter, accessor;
mpr.setFilter = function (fun) {
filter = fun;
return this;
},
mpr.setAccessor = function (fun) {
accessor = fun;
return this;
},
mpr.getMatrix = function () {
matrix = [];
_.each(mmap, function (a) {
if (!matrix[a.id]) matrix[a.id] = [];
_.each(mmap, function (b) {
var recs = _.filter(data, function (row) {
return filter(row, a, b);
})
matrix[a.id][b.id] = accessor(recs, a, b);
});
});
return matrix;
},
mpr.getMap = function () {
return mmap;
},
mpr.printMatrix = function () {
_.each(matrix, function (elem) {
console.log(elem);
})
},
mpr.addToMap = function (value, info) {
if (!mmap[value]) {
mmap[value] = { name: value, id: n++, data: info }
}
},
mpr.addValuesToMap = function (varName, info) {
var values = _.uniq(_.pluck(data, varName));
_.map(values, function (v) {
if (!mmap[v]) {
mmap[v] = { name: v, id: n++, data: info }
}
});
return this;
}
return mpr;
}
//*******************************************************************
// CHORD READER
//*******************************************************************
function chordRdr (matrix, mmap) {
return function (d) {
var i,j,s,t,g,m = {};
if (d.source) {
i = d.source.index; j = d.target.index;
s = _.where(mmap, {id: i });
t = _.where(mmap, {id: j });
m.sname = s[0].name;
m.sdata = d.source.value;
m.svalue = +d.source.value;
m.stotal = _.reduce(matrix[i], function (k, n) { return k + n }, 0);
m.tname = t[0].name;
m.tdata = d.target.value;
m.tvalue = +d.target.value;
m.ttotal = _.reduce(matrix[j], function (k, n) { return k + n }, 0);
} else {
g = _.where(mmap, {id: d.index });
m.gname = g[0].name;
m.gdata = g[0].data;
m.gvalue = d.value;
}
m.mtotal = _.reduce(matrix, function (m1, n1) {
return m1 + _.reduce(n1, function (m2, n2) { return m2 + n2}, 0);
}, 0);
return m;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment