Last active
July 6, 2016 09:29
-
-
Save baderone/254205ac408d0b5ec581a8f83610dcd5 to your computer and use it in GitHub Desktop.
Responsive Chord
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<head> | |
<meta name="viewport" content="width=device-width, initial-scale=1"> | |
<link rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css"> | |
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.0/jquery.min.js"></script> | |
<script src="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script> | |
<meta charset="utf-8"> | |
<!-- affinity group data, circle rotation, auto-resize svg image, chord function, configuration --> | |
<title>Chord Diagram - Nepal migration visualization</title> | |
<style> | |
#visual { | |
font: 14px sans-serif; | |
} | |
.chord path { | |
fill-opacity: .67; | |
stroke: #000; | |
stroke-width: .5px; | |
} | |
@media only screen and (min-device-width: 320px) and (max-device-width: 568px) { | |
#visual { | |
-webkit-user-select: none; | |
font-size: 1.2em; | |
} | |
} | |
@media only screen and (min-device-width: 768px) and (max-device-width: 1024px) { | |
#visual { | |
-webkit-user-select: none; | |
} | |
} | |
html { | |
position: relative; | |
min-height: 100%; | |
} | |
body { | |
/* Margin bottom by footer height */ | |
margin-bottom: 60px; | |
} | |
.footer { | |
position: absolute; | |
bottom: 0; | |
width: 100%; | |
/* Set the fixed height of the footer here */ | |
height: 60px; | |
background-color: #f5f5f5; | |
} | |
#title { | |
position: absolute; | |
top: 0; | |
z-index: 1; | |
display: block; | |
font-size: 22px; | |
font-weight: bold; | |
text-shadow: 0 1px 0 #fff; | |
top: 30px; | |
left: 30px; | |
text-align: left; | |
} | |
#owner{ | |
position: absolute; | |
z-index: 1; | |
display: block; | |
font-weight: 300; | |
text-shadow: 0 1px 0 #fff; | |
top: 900px; | |
left: 30px; | |
text-align: left; | |
color: #474747; | |
padding-bottom: 20px; | |
font-size: 10px; | |
} | |
#SideText{ | |
position: absolute; | |
display: block; | |
font-size: 12px; | |
color: #474747; | |
top: 40px; | |
left: 600px; | |
width: 230px; | |
} | |
#BelowText{ | |
position: absolute; | |
font-size: 14px; | |
color: #474747; | |
top: 900px; | |
left: 30px; | |
width: 1000px; | |
} | |
line { | |
stroke: #000; | |
stroke-width: 1.px; | |
} | |
.hint { | |
position: absolute; | |
left: 0; | |
top: 30px; | |
width: 500px; | |
font-size: 12px; | |
color: #999; | |
} | |
text { | |
font-size: 10px; | |
} | |
.titles{ | |
font-size: 14px; | |
} | |
.annotate{ | |
font-size: 11px; | |
fill: #474747; | |
} | |
path.chord { | |
fill-opacity: .80; | |
} | |
.pTitle { | |
font-size: 16px; | |
text-shadow: 0 1px 0 #fff; | |
fill: #474747; | |
font-weight: bold; | |
} | |
.survey { | |
font-size: 10px; | |
} | |
</style> | |
</head> | |
<div class="intror"> | |
<h2>How to read</h2> | |
<p >The circlized graph shows the villages of origin in color and the destinations in gray. <br>Each strip represents a number of people emigrating to the same destination country. <br>Move your mouse over each village to see the different.</p> | |
</div> | |
<footer class="footer"><p id="p0" class="pTitle Side" style="opacity: 1;">Chord Diagram</p> | |
<p id="p1" class="Side" style="opacity: 1;">This chord diagram shows relationships in terms of migration from Nepali villages to the world.</p> | |
<p id="p2" class="Side" style="opacity: 1;">The colored parts are the origin and the grey parts are the destinations.</p> | |
<p id="p3" class="Side" style="opacity: 1;">The numbers show the number of people that migrated from each village.</p> | |
</footer> | |
<body> | |
<script src="http://d3js.org/d3.v3.min.js"></script> | |
<script> | |
var visual = document.getElementById("visual"); | |
// persons moving between ... | |
var matrix = [ | |
[0,0,0,0,0,1,8,0,1,8,0,2,14,1,6,0,0,0], | |
[0,0,0,0,0,1,5,1,3,7,2,0,6,0,7,2,0,0], | |
[0,0,0,0,0,1,12,0,2,10,0,0,1,0,4,0,0,0], | |
[0,0,0,0,0,0,34,0,0,16,0,0,1,0,8,0,0,0], | |
[0,0,0,0,0,0,46,1,4,1,2,0,0,0,12,8,0,0], | |
[0,0,0,0,0,5,0,0,0,0,0,0,0,0,0,0,0,0], | |
[0,0,0,0,0,0,5,0,0,0,0,0,0,0,0,0,0,0], | |
[0,0,0,0,0,0,0,5,0,0,0,0,0,0,0,0,0,0], | |
[0,0,0,0,0,0,0,0,5,0,0,0,0,0,0,0,0,0], | |
[0,0,0,0,0,0,0,0,0,5,0,0,0,0,0,0,0,0], | |
[0,0,0,0,0,0,0,0,0,0,5,0,0,0,0,0,0,0], | |
[0,0,0,0,0,0,0,0,0,0,0,5,0,0,0,0,0,0], | |
[0,0,0,0,0,0,0,0,0,0,0,0,5,0,0,0,0,0], | |
[0,0,0,0,0,0,0,0,0,0,0,0,0,5,0,0,0,0], | |
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,0,0,0], | |
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,0,0], | |
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,0], | |
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5] | |
]; | |
var array = ["Chisopani","Ghatichina","Lower Shidane","Makwanpur","Thulakhet", | |
"Bahrain","Nepal","Dubai","Hongkong","India","Japan","Korea", | |
"Kuwait","Malaysia","Oman","Qatar","S.A.","U.K."]; | |
var rotation = 0; | |
var chord_options = { | |
"gnames": array, | |
"rotation": rotation, | |
"colors": ["#76AE2B","#1383FA","#FC0178","#3C3F53","#E49813","#CCCCCC","#CCCCCC","#CCCCCC","#CCCCCC","#CCCCCC","#CCCCCC","#CCCCCC","#CCCCCC","#CCCCCC","#CCCCCC","#CCCCCC", | |
"#CCCCCC","#CCCCCC"] | |
}; | |
function Chord(container, options, matrix) { | |
// initialize the chord configuration variables | |
var config = { | |
width: 640, | |
height: 560, | |
rotation: 0, | |
textgap: 26, | |
colors: ["#76AE2B","#1383FA","#FC0178","#3C3F53","#E49813","#CCCCCC","#CCCCCC","#CCCCCC","#CCCCCC","#CCCCCC","#CCCCCC","#CCCCCC","#CCCCCC","#CCCCCC","#CCCCCC","#CCCCCC", | |
"#CCCCCC","#CCCCCC"] | |
}; | |
// add options to the chord configuration object | |
if (options) { | |
extend(config, options); | |
} | |
// set chord visualization variables from the configuration object | |
var offset = Math.PI * config.rotation, | |
width = config.width, | |
height = config.height, | |
textgap = config.textgap | |
colors = config.colors; | |
// set viewBox and aspect ratio to enable a resize of the visual dimensions | |
var viewBoxDimensions = "0 0 " + width + " " + height, | |
aspect = width / height; | |
if (config.gnames) { | |
gnames = config.gnames; | |
} else { | |
// make a list of names | |
gnames = []; | |
for (var i=97; i<matrix.length; i++) { | |
gnames.push(String.fromCharCode(i)); | |
} | |
} | |
// start the d3 magic | |
var chord = d3.layout.chord() | |
.padding(.04) | |
.sortSubgroups(d3.descending) | |
.sortChords(d3.ascending) //which chord should be shown on top when chords cross. Now the biggest chord is at the bottom | |
.matrix(matrix); | |
var innerRadius = Math.min(width, height) * .35, | |
outerRadius = innerRadius * 1.1; | |
var fill = d3.scale.ordinal() | |
.domain(d3.range(array.length-1)) | |
.range(colors); | |
var svg = d3.select("body").append("svg") | |
.attr("id", "visual") | |
.attr("viewBox", viewBoxDimensions) | |
.attr("preserveAspectRatio", "xMinYMid") // add viewBox and preserveAspectRatio | |
.attr("width", width) | |
.attr("height", height) | |
.append("g") | |
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")"); | |
var g = svg.selectAll("g.group") | |
.data(chord.groups) | |
.enter().append("svg:g") | |
.attr("class", "group"); | |
g.append("svg:path") | |
.style("fill", function(d) { return fill(d.index); }) | |
.style("stroke", function(d) { return fill(d.index); }) | |
.attr("id", function(d, i) { return "group" + d.index; }) | |
.attr("d", d3.svg.arc().innerRadius(innerRadius).outerRadius(outerRadius).startAngle(startAngle).endAngle(endAngle)) | |
.on("mouseover", fade(.02)) | |
.on("mouseout", fade(.80)); | |
//////////////////////////////////////////////////////////// | |
////////////////// Append Names //////////////////////////// | |
//////////////////////////////////////////////////////////// | |
g.append("svg:text") | |
.each(function(d) {d.angle = ((d.startAngle + d.endAngle) / 2) + offset; }) | |
.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(" + (innerRadius + 55) + ")" | |
+ (d.angle > Math.PI ? "rotate(180)" : ""); | |
}) | |
.text(function(d) { return gnames[d.index]; }); | |
svg.append("g") | |
.attr("class", "chord") | |
.selectAll("path") | |
.data(chord.chords) | |
.enter().append("path") | |
.attr("d", d3.svg.chord().radius(innerRadius).startAngle(startAngle).endAngle(endAngle)) | |
.style("fill", function(d) { return fill(d.source.index); }) | |
.style("opacity", 1) | |
.append("svg:title") | |
.text(function(d) { | |
return d.source.value + " people from " + gnames[d.source.index] + " migrated to " + gnames[d.target.index]; | |
}); | |
//////////////////////////////////////////////////////////// | |
////////////////// Append Ticks //////////////////////////// | |
//////////////////////////////////////////////////////////// | |
var ticks = svg.append("svg:g").selectAll("g.ticks") | |
.data(chord.groups) | |
.enter().append("svg:g").selectAll("g.ticks") | |
.attr("class", "ticks") | |
.data(groupTicks) | |
.enter().append("svg:g") | |
.attr("transform", function(d) { | |
return "rotate(" + (d.angle * 180 / Math.PI - 90) + ")" | |
+ "translate(" + outerRadius+40 + ",0)"; | |
}); | |
ticks.append("svg:line") | |
.attr("x1", 1) | |
.attr("y1", 0) | |
.attr("x2", 5) | |
.attr("y2", 0) | |
.style("stroke", "#000"); | |
ticks.append("svg:text") | |
.attr("x", 8) | |
.attr("dy", ".35em") | |
.attr("transform", function(d) { return d.angle > Math.PI ? "rotate(180)translate(-16)" : null; }) | |
.style("text-anchor", function(d) { return d.angle > Math.PI ? "end" : null; }) | |
.text(function(d) { return d.label; }); | |
// helper functions start here | |
function startAngle(d) { | |
return d.startAngle + offset; | |
} | |
function endAngle(d) { | |
return d.endAngle + offset; | |
} | |
function extend(a, b) { | |
for( var i in b ) { | |
a[ i ] = b[ i ]; | |
} | |
} | |
// Returns an event handler for fading a given chord group. | |
function fade(opacity) { | |
return function(g, i) { | |
svg.selectAll(".chord path") | |
.filter(function(d) { return d.source.index != i && d.target.index != i; }) | |
.transition() | |
.style("opacity", opacity); | |
}; | |
} | |
// Returns an array of tick angles and labels, given a group. | |
function groupTicks(d) { | |
var k = (d.endAngle - d.startAngle) / d.value; | |
return d3.range(0, d.value, 1).map(function(v, i) { | |
return { | |
angle: v * k + d.startAngle, | |
label: i % 5 ? null : v + "p" | |
}; | |
}); | |
}//groupTicks | |
window.onresize = function() { | |
var targetWidth = (window.innerWidth < width)? window.innerWidth : width; | |
var svg = d3.select("#visual") | |
.attr("width", targetWidth) | |
.attr("height", targetWidth / aspect); | |
} | |
} | |
window.onload = function() { | |
Chord(visual, chord_options, matrix); | |
} | |
d3.select(self.frameElement).style("height", "600px"); | |
//////////////////////////////////////////////////////////// | |
////////////////// Extra Functions ///////////////////////// | |
//////////////////////////////////////////////////////////// | |
<!-- | |
// Returns an event handler for fading a given chord group. | |
function fadeIn(opacity) { | |
return function(d, i) { | |
d3.selectAll(".Side") | |
.transition() | |
.duration(200) | |
.style("opacity", 0.0001); | |
d3.selectAll(".annotate") | |
.transition() | |
.duration(200) | |
.style("opacity", 0.0001); | |
d3.select("#p0").transition().duration(200).delay(200).style("opacity", 1).text(array[i]); | |
if(i == 0) { //Chisopani | |
d3.select("#p1").transition().duration(200).delay(200).style("opacity", 1).text("One third of all emigration from Chisopani is directed towards Kuwait."); | |
d3.select("#p2").transition().duration(200).delay(200).style("opacity", 1).text("One third of emigration is directed towards India and inside Nepal"); | |
d3.select("#p3").transition().duration(200).delay(200).style("opacity", 1).text("The last part of emigration is directed towards Oman, Malaysia, Korea, Hongkong, and Bahrain"); | |
d3.select("#p4").transition().duration(200).delay(200).style("opacity", 1).text(" "); | |
d3.select("#p5").transition().duration(200).delay(200).style("opacity", 1).text(" "); | |
} else if (i == 1) { //Ghatichina | |
d3.select("#p1").transition().duration(200).delay(200).style("opacity", 1).text("More than half of emigration from Ghatichina is directed towards three countries: Oman, Kuwait, and India"); | |
d3.select("#p2").transition().duration(200).delay(200).style("opacity", 1).text("The other half is distributed in descending order to: Nepal, Japan, Qatar, Hongkong, Dubai, and Bahrain "); | |
d3.select("#p3").transition().duration(200).delay(200).style("opacity", 1).text(""); | |
d3.select("#p4").transition().duration(200).delay(200).style("opacity", 1).text(""); | |
d3.select("#p5").transition().duration(200).delay(200).style("opacity", 1).text(" "); | |
} else if (i == 2) { //Lower Shidane | |
d3.select("#p1").transition().duration(200).delay(200).style("opacity", 1).text("One third of emigration from Lower Shidane stays inside Nepal"); | |
d3.select("#p2").transition().duration(200).delay(200).style("opacity", 1).text("One third of emigration is directed towards India"); | |
d3.select("#p3").transition().duration(200).delay(200).style("opacity", 1).text("And the last third is distributed to Oman, Hongkong, Kuwait, and Bahrain"); | |
d3.select("#p4").transition().duration(200).delay(200).style("opacity", 1).text(" "); | |
d3.select("#p5").transition().duration(200).delay(200).style("opacity", 1).text(" "); | |
} else if (i == 3) { //Makwanpur | |
d3.select("#p1").transition().duration(200).delay(200).style("opacity", 1).text("From Makwanpur more than half of the people move inside Nepal"); | |
d3.select("#p2").transition().duration(200).delay(200).style("opacity", 1).text("More than a quarter emigrates to India"); | |
d3.select("#p3").transition().duration(200).delay(200).style("opacity", 1).text("Another quarter of emigration is directed towards Oman and Kuwait"); | |
d3.select("#p4").transition().duration(200).delay(200).style("opacity", 1).text(""); | |
d3.select("#p5").transition().duration(200).delay(200).style("opacity", 1).text(""); | |
} else if (i == 4) { //Thulakhet | |
d3.select("#p1").transition().duration(200).delay(200).style("opacity", 1).text("Two thirds of out-migration from Thulakhet stays inside Nepal"); | |
d3.select("#p2").transition().duration(200).delay(200).style("opacity", 1).text("The last third is directed towards Qatar, Oman, Japan, India, Hongkong, and Dubai"); | |
d3.select("#p3").transition().duration(200).delay(200).style("opacity", 1).text(""); | |
d3.select("#p4").transition().duration(200).delay(200).style("opacity", 1).text(""); | |
d3.select("#p5").transition().duration(200).delay(200).style("opacity", 1).text(""); | |
}; | |
svg.selectAll("path.chord") | |
.filter(function(d) { return d.source.index != i && d.target.index != i; }) | |
.transition() | |
.style("stroke-opacity", opacity) | |
.style("fill-opacity", opacity); | |
}; | |
}//fadeIn | |
// Returns an event handler for fading a given chord group. | |
function fadeOut(opacity) { | |
return function(d, i) { | |
d3.selectAll(".Side") | |
.transition() | |
.duration(500) | |
.style("opacity", 0.0001); | |
d3.selectAll(".annotate") | |
.transition() | |
.duration(500) | |
.style("opacity", 1); | |
d3.select("#p0").transition().duration(200).delay(200).style("opacity", 1).text("Chord Diagram") | |
d3.select("#p1").transition().duration(200).delay(200).style("opacity", 1).text("This chord diagram shows relationships in terms of migration from Nepali villages to the world.") | |
d3.select("#p2").transition().duration(200).delay(200).style("opacity", 1).text("The colored parts are the origin and the grey parts are the destinations.") | |
d3.select("#p3").transition().duration(200).delay(200).style("opacity", 1).text("The numbers show the number of people that migrated") | |
svg.selectAll("path.chord") | |
.filter(function(d) { return d.source.index != i && d.target.index != i; }) | |
.transition() | |
.style("stroke-opacity", opacity) | |
.style("fill-opacity", opacity); | |
}; | |
}//fadeOut | |
// Returns an array of tick angles and labels, given a group. | |
function groupTicks(d) { | |
var k = (d.endAngle - d.startAngle) / d.value; | |
return d3.range(0, d.value, 1).map(function(v, i) { | |
return { | |
angle: v * k + d.startAngle, | |
label: i % 5 ? null : v + "p" | |
}; | |
}); | |
}//groupTicks | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Until now, the dynamic footer is not working, so the footer (code rows 150-154) should updated by mouse-over accoding to Extras function code rows 383-480