Skip to content

Instantly share code, notes, and snippets.

@willzjc
Last active January 7, 2018 04:45
Show Gist options
  • Save willzjc/f3ddf3db8ad30f0bf1d982a6f13bfb36 to your computer and use it in GitHub Desktop.
Save willzjc/f3ddf3db8ad30f0bf1d982a6f13bfb36 to your computer and use it in GitHub Desktop.
Chord diagram to explore GTrend Relations
license: mit
border: no
scrolling: no
Chord to represent google trends
who overlap years
USD CAD 4083
CAD USD 4083
USD JPY 3942
JPY USD 3942
EUR AUD 3713
AUD EUR 3713
USD AUD 3567
AUD USD 3567
USD EUR 3412
EUR USD 3412
CAD EUR 3363
EUR CAD 3363
CAD AUD 2913
AUD CAD 2913
GBP AUD 2911
AUD GBP 2911
GBP JPY 2770
JPY GBP 2770
EUR JPY 2681
JPY EUR 2681
GBP CAD 2625
CAD GBP 2625
JPY AUD 1919
AUD JPY 1919
EUR SEK 1876
SEK EUR 1876
CAD JPY 1765
JPY CAD 1765
GBP EUR 1740
EUR GBP 1740
GBP CHF 1710
CHF GBP 1710
USD CHF 1349
CHF USD 1349
CHF AUD 1280
AUD CHF 1280
USD GBP 1185
GBP USD 1185
CAD CHF 1172
CHF CAD 1172
USD SEK 1170
SEK USD 1170
GBP SEK 1163
SEK GBP 1163
CHF JPY 1006
JPY CHF 1006
CHF SEK 556
SEK CHF 556
CHF EUR 530
EUR CHF 530
CAD SEK 493
SEK CAD 493
JPY SEK 301
SEK JPY 301
AUD SEK 216
<!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: 2px;
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: 30px;}
</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 = 880, h = 700, 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) + " gTrend rating for " + 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>
</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