Skip to content

Instantly share code, notes, and snippets.

@jonpage
Created November 25, 2015 02:51
Show Gist options
  • Save jonpage/5db8f8367169112d0738 to your computer and use it in GitHub Desktop.
Save jonpage/5db8f8367169112d0738 to your computer and use it in GitHub Desktop.
Stretched Chord Diagram - From educations to occupations
customChordLayout = function() {
var ε = 1e-6, ε2 = ε * ε, π = Math.PI, τ = 2 * π, τε = τ - ε, halfπ = π / 2, d3_radians = π / 180, d3_degrees = 180 / π;
var chord = {}, chords, groups, matrix, n, padding = 0, sortGroups, sortSubgroups, sortChords;
function relayout() {
var subgroups = {}, groupSums = [], groupIndex = d3.range(n), subgroupIndex = [], k, x, x0, i, j;
chords = [];
groups = [];
k = 0, i = -1;
while (++i < n) {
x = 0, j = -1;
while (++j < n) {
x += matrix[i][j];
}
groupSums.push(x);
subgroupIndex.push(d3.range(n).reverse());
k += x;
}
if (sortGroups) {
groupIndex.sort(function(a, b) {
return sortGroups(groupSums[a], groupSums[b]);
});
}
if (sortSubgroups) {
subgroupIndex.forEach(function(d, i) {
d.sort(function(a, b) {
return sortSubgroups(matrix[i][a], matrix[i][b]);
});
});
}
k = (τ - padding * n) / k;
x = 0, i = -1;
while (++i < n) {
x0 = x, j = -1;
while (++j < n) {
var di = groupIndex[i], dj = subgroupIndex[di][j], v = matrix[di][dj], a0 = x, a1 = x += v * k;
subgroups[di + "-" + dj] = {
index: di,
subindex: dj,
startAngle: a0,
endAngle: a1,
value: v
};
}
groups[di] = {
index: di,
startAngle: x0,
endAngle: x,
value: (x - x0) / k
};
x += padding;
}
i = -1;
while (++i < n) {
j = i - 1;
while (++j < n) {
var source = subgroups[i + "-" + j], target = subgroups[j + "-" + i];
if (source.value || target.value) {
chords.push(source.value < target.value ? {
source: target,
target: source
} : {
source: source,
target: target
});
}
}
}
if (sortChords) resort();
}
function resort() {
chords.sort(function(a, b) {
return sortChords((a.source.value + a.target.value) / 2, (b.source.value + b.target.value) / 2);
});
}
chord.matrix = function(x) {
if (!arguments.length) return matrix;
n = (matrix = x) && matrix.length;
chords = groups = null;
return chord;
};
chord.padding = function(x) {
if (!arguments.length) return padding;
padding = x;
chords = groups = null;
return chord;
};
chord.sortGroups = function(x) {
if (!arguments.length) return sortGroups;
sortGroups = x;
chords = groups = null;
return chord;
};
chord.sortSubgroups = function(x) {
if (!arguments.length) return sortSubgroups;
sortSubgroups = x;
chords = null;
return chord;
};
chord.sortChords = function(x) {
if (!arguments.length) return sortChords;
sortChords = x;
if (chords) resort();
return chord;
};
chord.chords = function() {
if (!chords) relayout();
return chords;
};
chord.groups = function() {
if (!groups) relayout();
return groups;
};
return chord;
};
////////////////////////////////////////////////////////////
/////////////// Custom Chord Function //////////////////////
//////// Pulls the chords pullOutSize pixels apart /////////
////////////////// along the x axis ////////////////////////
////////////////////////////////////////////////////////////
///////////// Created by Nadieh Bremer /////////////////////
//////////////// VisualCinnamon.com ////////////////////////
////////////////////////////////////////////////////////////
//// Adjusted from the original d3.svg.chord() function ////
///////////////// from the d3.js library ///////////////////
//////////////// Created by Mike Bostock ///////////////////
////////////////////////////////////////////////////////////
stretchedChord = function() {
var source = d3_source,
target = d3_target,
radius = d3_svg_chordRadius,
startAngle = d3_svg_arcStartAngle,
endAngle = d3_svg_arcEndAngle;
var π = Math.PI,
halfπ = π / 2;
function subgroup(self, f, d, i) {
var subgroup = f.call(self, d, i),
r = radius.call(self, subgroup, i),
a0 = startAngle.call(self, subgroup, i) - halfπ,
a1 = endAngle.call(self, subgroup, i) - halfπ;
return {
r: r,
a0: [a0],
a1: [a1],
p0: [ r * Math.cos(a0), r * Math.sin(a0)],
p1: [ r * Math.cos(a1), r * Math.sin(a1)]
};
}
function arc(r, p, a) {
var sign = (p[0] >= 0 ? 1 : -1);
return "A" + r + "," + r + " 0 " + +(a > π) + ",1 " + (p[0] + sign*pullOutSize) + "," + p[1];
}
function curve(p1) {
var sign = (p1[0] >= 0 ? 1 : -1);
return "Q 0,0 " + (p1[0] + sign*pullOutSize) + "," + p1[1];
}
/*
M = moveto
M x,y
Q = quadratic Bézier curve
Q control-point-x,control-point-y end-point-x, end-point-y
A = elliptical Arc
A rx, ry x-axis-rotation large-arc-flag, sweep-flag end-point-x, end-point-y
Z = closepath
M251.5579641956022,87.98204731514328
A266.5,266.5 0 0,1 244.49937503334525,106.02973926358392
Q 0,0 -177.8355222451483,198.48621369706098
A266.5,266.5 0 0,1 -191.78901944612068,185.0384338992728
Q 0,0 251.5579641956022,87.98204731514328
Z
*/
function chord(d, i) {
var s = subgroup(this, source, d, i),
t = subgroup(this, target, d, i);
return "M" + (s.p0[0] + pullOutSize) + "," + s.p0[1] +
arc(s.r, s.p1, s.a1 - s.a0) +
curve(t.p0) +
arc(t.r, t.p1, t.a1 - t.a0) +
curve(s.p0) +
"Z";
}//chord
chord.radius = function(v) {
if (!arguments.length) return radius;
radius = d3_functor(v);
return chord;
};
chord.source = function(v) {
if (!arguments.length) return source;
source = d3_functor(v);
return chord;
};
chord.target = function(v) {
if (!arguments.length) return target;
target = d3_functor(v);
return chord;
};
chord.startAngle = function(v) {
if (!arguments.length) return startAngle;
startAngle = d3_functor(v);
return chord;
};
chord.endAngle = function(v) {
if (!arguments.length) return endAngle;
endAngle = d3_functor(v);
return chord;
};
return chord;
};
function d3_svg_chordRadius(d) {
return d.radius;
}
function d3_source(d) {
return d.source;
}
function d3_target(d) {
return d.target;
}
function d3_svg_arcStartAngle(d) {
return d.startAngle;
}
function d3_svg_arcEndAngle(d) {
return d.endAngle;
}
function d3_functor(v) {
return typeof v === "function" ? v : function() {
return v;
};
}
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
<title>From your Education to your Job</title>
<!-- D3.js -->
<script src="http://d3js.org/d3.v3.js"></script>
<script src="d3.stretched.chord.js"></script>
<script src="d3.layout.chord.sort.js"></script>
<!-- Pym.js - iframe height handler for the Blog -->
<script src="pym.min.js"></script>
<!-- jQuery -->
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<!-- Open Sans & CSS -->
<link href='http://fonts.googleapis.com/css?family=Open+Sans:700,400,300' rel='stylesheet' type='text/css'>
<style>
body {
font-family: 'Open Sans', sans-serif;
font-size: 12px;
font-weight: 400;
color: #525252;
text-align: center;
}
html, body {
width: auto;
height: auto;
}
line {
stroke: #000;
stroke-width: 1.px;
}
text {
font-size: 8px;
}
.titles{
font-size: 10px;
}
path.chord {
fill-opacity: .80;
}
.title {
text-anchor: middle;
fill: #3B3B3B;
font-weight: 300;
font-size: 16px;
}
.titleLine {
stroke: #DCDCDC;
shape-rendering: crispEdges;
}
.title-h3 {
margin-top: 20px;
margin-bottom: 10px;
font-size: 24px;
font-weight: 500;
line-height: 1.1;
color: #3B3B3B;
}
@media (min-width: 500px) {
.explanation {
width: 50%;
margin: 0 auto;
}
}
</style>
</head>
<body>
<div class="title-h3">How much does your education define where you end up working?</div>
<div id="chart"></div>
<div style="width: 100%;">
<div class="explanation">
This chart shows how students from different educational sciences end up in their occupational sectors a year after graduation.
On the left we see the different educational tracks and on the right are the occupational sectors.
Thus there is a flow from left to right. The thickness of a line represents the number of students.
</div>
</div>
<!-- Animated gradient for Chord Diagram -->
<!-- Doesn't work in IE... -->
<svg height="0cm" width="0cm">
<defs>
<linearGradient id="gradientLinearPerLine" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="-100%" stop-color="#858585" stop-opacity=".5">
<animate attributeName="offset" values="-1;0" dur="9s" repeatCount="indefinite"></animate>
</stop>
<stop offset="-75%" stop-color="#BEBEBE" stop-opacity=".5">
<animate attributeName="offset" values="-0.75;0.25" dur="9s" repeatCount="indefinite"></animate>
</stop>
<stop offset="-50%" stop-color="#858585" stop-opacity=".5">
<animate attributeName="offset" values="-0.5;0.5" dur="9s" repeatCount="indefinite"></animate>
</stop>
<stop offset="-25%" stop-color="#BEBEBE" stop-opacity=".5">
<animate attributeName="offset" values="-0.25;0.75" dur="9s" repeatCount="indefinite"></animate>
</stop>
<stop offset="0%" stop-color="#858585" stop-opacity=".5">
<animate attributeName="offset" values="0;1" dur="9s" repeatCount="indefinite"></animate>
</stop>
<stop offset="25%" stop-color="#BEBEBE" stop-opacity=".5">
<animate attributeName="offset" values="0.25;1.25" dur="9s" repeatCount="indefinite"></animate>
</stop>
<stop offset="50%" stop-color="#858585" stop-opacity=".5">
<animate attributeName="offset" values="0.5;1.5" dur="9s" repeatCount="indefinite"></animate>
</stop>
<stop offset="75%" stop-color="#BEBEBE" stop-opacity=".5">
<animate attributeName="offset" values="0.75;1.75" dur="9s" repeatCount="indefinite"></animate>
</stop>
<stop offset="100%" stop-color="#858585" stop-opacity=".5">
<animate attributeName="offset" values="1;2" dur="9s" repeatCount="indefinite"></animate>
</stop>
</linearGradient>
</defs>
</svg>
<svg height="0cm" width="0cm">
<defs>
<linearGradient id="gradientLinear" x1="-100%" y1="0%" x2="100%" y2="0%" gradientUnits="userSpaceOnUse">
<stop offset="-100%" stop-color="#858585" stop-opacity=".5">
<animate attributeName="offset" values="-1;0" dur="5s" repeatCount="indefinite"></animate>
</stop>
<stop offset="-75%" stop-color="#BEBEBE" stop-opacity=".5">
<animate attributeName="offset" values="-0.75;0.25" dur="5s" repeatCount="indefinite"></animate>
</stop>
<stop offset="-50%" stop-color="#858585" stop-opacity=".5">
<animate attributeName="offset" values="-0.5;0.5" dur="5s" repeatCount="indefinite"></animate>
</stop>
<stop offset="-25%" stop-color="#BEBEBE" stop-opacity=".5">
<animate attributeName="offset" values="-0.25;0.75" dur="5s" repeatCount="indefinite"></animate>
</stop>
<stop offset="0%" stop-color="#858585" stop-opacity=".5">
<animate attributeName="offset" values="0;1" dur="5s" repeatCount="indefinite"></animate>
</stop>
<stop offset="25%" stop-color="#BEBEBE" stop-opacity=".5">
<animate attributeName="offset" values="0.25;1.25" dur="5s" repeatCount="indefinite"></animate>
</stop>
<stop offset="50%" stop-color="#858585" stop-opacity=".5">
<animate attributeName="offset" values="0.5;1.5" dur="5s" repeatCount="indefinite"></animate>
</stop>
<stop offset="75%" stop-color="#BEBEBE" stop-opacity=".5">
<animate attributeName="offset" values="0.75;1.75" dur="5s" repeatCount="indefinite"></animate>
</stop>
<stop offset="100%" stop-color="#858585" stop-opacity=".5">
<animate attributeName="offset" values="1;2" dur="5s" repeatCount="indefinite"></animate>
</stop>
</linearGradient>
</defs>
</svg>
<script src="script.js"></script>
</body>
</html>
/*! pym.js - v0.4.4 - 2015-07-16 */
!function(a){"function"==typeof define&&define.amd?define(a):"undefined"!=typeof module&&module.exports?module.exports=a():window.pym=a.call(this)}(function(){var a="xPYMx",b={},c=function(a){var b=new RegExp("[\\?&]"+a.replace(/[\[]/,"\\[").replace(/[\]]/,"\\]")+"=([^&#]*)"),c=b.exec(location.search);return null===c?"":decodeURIComponent(c[1].replace(/\+/g," "))},d=function(a,b){return"*"===b.xdomain||a.origin.match(new RegExp(b.xdomain+"$"))?!0:void 0},e=function(b,c,d){var e=["pym",b,c,d];return e.join(a)},f=function(b){var c=["pym",b,"(\\S+)","(.+)"];return new RegExp("^"+c.join(a)+"$")},g=function(){for(var a=document.querySelectorAll("[data-pym-src]:not([data-pym-auto-initialized])"),c=a.length,d=0;c>d;++d){var e=a[d];e.setAttribute("data-pym-auto-initialized",""),""===e.id&&(e.id="pym-"+d);var f=e.getAttribute("data-pym-src"),g=e.getAttribute("data-pym-xdomain"),h={};g&&(h.xdomain=g),new b.Parent(e.id,f,h)}};return b.Parent=function(a,b,c){this.id=a,this.url=b,this.el=document.getElementById(a),this.iframe=null,this.settings={xdomain:"*"},this.messageRegex=f(this.id),this.messageHandlers={},c=c||{},this._constructIframe=function(){var a=this.el.offsetWidth.toString();this.iframe=document.createElement("iframe");var b="",c=this.url.indexOf("#");c>-1&&(b=this.url.substring(c,this.url.length),this.url=this.url.substring(0,c)),this.url.indexOf("?")<0?this.url+="?":this.url+="&",this.iframe.src=this.url+"initialWidth="+a+"&childId="+this.id+"&parentUrl="+encodeURIComponent(window.location.href)+b,this.iframe.setAttribute("width","100%"),this.iframe.setAttribute("scrolling","no"),this.iframe.setAttribute("marginheight","0"),this.iframe.setAttribute("frameborder","0"),this.el.appendChild(this.iframe),window.addEventListener("resize",this._onResize)},this._onResize=function(){this.sendWidth()}.bind(this),this._fire=function(a,b){if(a in this.messageHandlers)for(var c=0;c<this.messageHandlers[a].length;c++)this.messageHandlers[a][c].call(this,b)},this.remove=function(){window.removeEventListener("message",this._processMessage),window.removeEventListener("resize",this._onResize),this.el.removeChild(this.iframe)},this._processMessage=function(a){if(d(a,this.settings)&&"string"==typeof a.data){var b=a.data.match(this.messageRegex);if(!b||3!==b.length)return!1;var c=b[1],e=b[2];this._fire(c,e)}}.bind(this),this._onHeightMessage=function(a){var b=parseInt(a);this.iframe.setAttribute("height",b+"px")},this._onNavigateToMessage=function(a){document.location.href=a},this.onMessage=function(a,b){a in this.messageHandlers||(this.messageHandlers[a]=[]),this.messageHandlers[a].push(b)},this.sendMessage=function(a,b){this.el.getElementsByTagName("iframe")[0].contentWindow.postMessage(e(this.id,a,b),"*")},this.sendWidth=function(){var a=this.el.offsetWidth.toString();this.sendMessage("width",a)};for(var g in c)this.settings[g]=c[g];return this.onMessage("height",this._onHeightMessage),this.onMessage("navigateTo",this._onNavigateToMessage),window.addEventListener("message",this._processMessage,!1),this._constructIframe(),this},b.Child=function(b){this.parentWidth=null,this.id=null,this.parentUrl=null,this.settings={renderCallback:null,xdomain:"*",polling:0},this.messageRegex=null,this.messageHandlers={},b=b||{},this.onMessage=function(a,b){a in this.messageHandlers||(this.messageHandlers[a]=[]),this.messageHandlers[a].push(b)},this._fire=function(a,b){if(a in this.messageHandlers)for(var c=0;c<this.messageHandlers[a].length;c++)this.messageHandlers[a][c].call(this,b)},this._processMessage=function(a){if(d(a,this.settings)&&"string"==typeof a.data){var b=a.data.match(this.messageRegex);if(b&&3===b.length){var c=b[1],e=b[2];this._fire(c,e)}}}.bind(this),this._onWidthMessage=function(a){var b=parseInt(a);b!==this.parentWidth&&(this.parentWidth=b,this.settings.renderCallback&&this.settings.renderCallback(b),this.sendHeight())},this.sendMessage=function(a,b){window.parent.postMessage(e(this.id,a,b),"*")},this.sendHeight=function(){var a=document.getElementsByTagName("body")[0].offsetHeight.toString();this.sendMessage("height",a)}.bind(this),this.scrollParentTo=function(a){this.sendMessage("navigateTo","#"+a)},this.navigateParentTo=function(a){this.sendMessage("navigateTo",a)},this.id=c("childId")||b.id,this.messageRegex=new RegExp("^pym"+a+this.id+a+"(\\S+)"+a+"(.+)$");var f=parseInt(c("initialWidth"));this.parentUrl=c("parentUrl"),this.onMessage("width",this._onWidthMessage);for(var g in b)this.settings[g]=b[g];return window.addEventListener("message",this._processMessage,!1),this.settings.renderCallback&&this.settings.renderCallback(f),this.sendHeight(),this.settings.polling&&window.setInterval(this.sendHeight,this.settings.polling),this},g(),b});
////////////////////////////////////////////////////////////
//////////////////////// Set-up ////////////////////////////
////////////////////////////////////////////////////////////
//How many pixels should the two halves be pulled apart
var pullOutSize;
var screenWidth = $(window).innerWidth(),
mobileScreen = (screenWidth > 500 ? false : true);
var margin = {left: 50, top: 10, right: 50, bottom: 10},
width = Math.min(screenWidth, 800) - margin.left - margin.right,
height = (mobileScreen ? 300 : Math.min(screenWidth, 800)*5/6) - margin.top - margin.bottom;
var svg = d3.select("#chart").append("svg")
.attr("width", (width + margin.left + margin.right))
.attr("height", (height + margin.top + margin.bottom));
var wrapper = svg.append("g").attr("class", "chordWrapper")
.attr("transform", "translate(" + (width / 2 + margin.left) + "," + (height / 2 + margin.top) + ")");;
var outerRadius = Math.min(width, height) / 2 - (mobileScreen ? 80 : 100),
innerRadius = outerRadius * 0.95,
opacityDefault = 0.7, //default opacity of chords
opacityLow = 0.02; //hover opacity of those chords not hovered over
//How many pixels should the two halves be pulled apart
pullOutSize = (mobileScreen? 20 : 50)
//////////////////////////////////////////////////////
//////////////////// Titles on top ///////////////////
//////////////////////////////////////////////////////
var titleWrapper = svg.append("g").attr("class", "chordTitleWrapper"),
titleOffset = mobileScreen ? 15 : 40,
titleSeparate = mobileScreen ? 30 : 0;
//Title top left
titleWrapper.append("text")
.attr("class","title left")
.style("font-size", mobileScreen ? "12px" : "16px" )
.attr("x", (width/2 + margin.left - outerRadius - titleSeparate))
.attr("y", titleOffset)
.text("Education");
titleWrapper.append("line")
.attr("class","titleLine left")
.attr("x1", (width/2 + margin.left - outerRadius - titleSeparate)*0.6)
.attr("x2", (width/2 + margin.left - outerRadius - titleSeparate)*1.4)
.attr("y1", titleOffset+8)
.attr("y2", titleOffset+8);
//Title top right
titleWrapper.append("text")
.attr("class","title right")
.style("font-size", mobileScreen ? "12px" : "16px" )
.attr("x", (width/2 + margin.left + outerRadius + titleSeparate))
.attr("y", titleOffset)
.text("Occupation");
titleWrapper.append("line")
.attr("class","titleLine right")
.attr("x1", (width/2 + margin.left - outerRadius - titleSeparate)*0.6 + 2*(outerRadius + titleSeparate))
.attr("x2", (width/2 + margin.left - outerRadius - titleSeparate)*1.4 + 2*(outerRadius + titleSeparate))
.attr("y1", titleOffset+8)
.attr("y2", titleOffset+8);
////////////////////////////////////////////////////////////
////////////////////////// Data ////////////////////////////
////////////////////////////////////////////////////////////
var Names = ["Administrative Staff","Crafts","Business Management","Basic Occupations","Health",
"IT","Juridical & Cultural","Management functions","Teachers",
"Salesmen & Service providers","Caretakers","Science & Engineering", "Other", "",
"Engineering","Education","Agriculture","Art, Language & Culture","Health","Behavior & Social Sciences","Economy",""];
var respondents = 17533, //Total number of respondents (i.e. the number that make up the total group
emptyPerc = 0.5, //What % of the circle should become empty
emptyStroke = Math.round(respondents*emptyPerc);
var matrix = [
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,232,65,44,57,39,123,1373,0], //Administratief personeel
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,0,0,11,0,0,24,0], //Ambachtslieden
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,173,43,52,55,36,125,2413,0], //Bedrijfsbeheer (vak)specialisten
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,16,13,23,10,37,54,0], //Elementaire beroepen
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,161,24,17,0,2089,85,60,0], //Gezondheidszorg (vak)specialisten
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,510,0,0,57,0,0,251,0], //IT (vak)specialisten
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,16,118,10,454,99,1537,271,0], //Juridisch en culturele (vak)specialisten
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,76,21,10,15,125,41,261,0], //Leidinggevende functies
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,2206,37,292,32,116,76,0], //Onderwijsgevenden
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,74,43,116,51,135,752,0], //Verkopers en verleners persoonlijke diensten
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,34,0,22,27,156,36,0], //Verzorgend personeel
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,1141,0,111,291,0,0,48,0], //Wetenschap en techniek (vak)specialisten
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,0,39,0,0,20,109,0], //Other
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,emptyStroke], //dummyBottom
[232,32,173,32,161,510,16,76,32,96,15,1141,36,0,0,0,0,0,0,0,0,0], //Techniek
[65,0,43,16,24,0,118,21,2206,74,34,0,0,0,0,0,0,0,0,0,0,0], //Onderwijs
[44,0,52,13,17,0,10,10,37,43,0,111,39,0,0,0,0,0,0,0,0,0], //Landbouw
[57,11,55,23,0,57,454,15,292,116,22,291,0,0,0,0,0,0,0,0,0,0], //Kunst, Taal en Cultuur
[39,0,36,10,2089,0,99,125,32,51,27,0,0,0,0,0,0,0,0,0,0,0], //Gezondheidszorg
[123,0,125,37,85,0,1537,41,116,135,156,0,20,0,0,0,0,0,0,0,0,0], //Gedrag & Maatschappij
[1373,24,2413,54,60,251,271,261,76,752,36,48,109,0,0,0,0,0,0,0,0,0], //Economie
[0,0,0,0,0,0,0,0,0,0,0,0,0,emptyStroke,0,0,0,0,0,0,0,0] //dummyTop
];
//Calculate how far the Chord Diagram needs to be rotated clockwise to make the dummy
//invisible chord center vertically
var offset = (2 * Math.PI) * (emptyStroke/(respondents + emptyStroke))/4;
//Custom sort function of the chords to keep them in the original order
var chord = customChordLayout() //d3.layout.chord()
.padding(.02)
.sortChords(d3.descending) //which chord should be shown on top when chords cross. Now the biggest chord is at the bottom
.matrix(matrix);
var arc = d3.svg.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius)
.startAngle(startAngle) //startAngle and endAngle now include the offset in degrees
.endAngle(endAngle);
var path = stretchedChord() //Call the stretched chord function
.radius(innerRadius)
.startAngle(startAngle)
.endAngle(endAngle);
////////////////////////////////////////////////////////////
//////////////////// Draw outer Arcs ///////////////////////
////////////////////////////////////////////////////////////
var g = wrapper.selectAll("g.group")
.data(chord.groups)
.enter().append("g")
.attr("class", "group")
.on("mouseover", fade(opacityLow))
.on("mouseout", fade(opacityDefault));
g.append("path")
.style("stroke", function(d,i) { return (Names[i] === "" ? "none" : "#00A1DE"); })
.style("fill", function(d,i) { return (Names[i] === "" ? "none" : "#00A1DE"); })
.style("pointer-events", function(d,i) { return (Names[i] === "" ? "none" : "auto"); })
.attr("d", arc)
.attr("transform", function(d, i) { //Pull the two slices apart
d.pullOutSize = pullOutSize * ( d.startAngle + 0.001 > Math.PI ? -1 : 1);
return "translate(" + d.pullOutSize + ',' + 0 + ")";
});
////////////////////////////////////////////////////////////
////////////////////// Append Names ////////////////////////
////////////////////////////////////////////////////////////
//The text also needs to be displaced in the horizontal directions
//And also rotated with the offset in the clockwise direction
g.append("text")
.each(function(d) { d.angle = ((d.startAngle + d.endAngle) / 2) + offset;})
.attr("dy", ".35em")
.attr("class", "titles")
.style("font-size", mobileScreen ? "8px" : "10px" )
.attr("text-anchor", function(d) { return d.angle > Math.PI ? "end" : null; })
.attr("transform", function(d,i) {
var c = arc.centroid(d);
return "translate(" + (c[0] + d.pullOutSize) + "," + c[1] + ")"
+ "rotate(" + (d.angle * 180 / Math.PI - 90) + ")"
+ "translate(" + 20 + ",0)"
+ (d.angle > Math.PI ? "rotate(180)" : "")
})
.text(function(d,i) { return Names[i]; })
.call(wrapChord, 100);
////////////////////////////////////////////////////////////
//////////////////// Draw inner chords /////////////////////
////////////////////////////////////////////////////////////
wrapper.selectAll("path.chord")
.data(chord.chords)
.enter().append("path")
.attr("class", "chord")
.style("stroke", "none")
//.style("fill", "#C4C4C4")
.style("fill", "url(#gradientLinearPerLine)") //An SVG Gradient to give the impression of a flow from left to right
.style("opacity", function(d) { return (Names[d.source.index] === "" ? 0 : opacityDefault); }) //Make the dummy strokes have a zero opacity (invisible)
.style("pointer-events", function(d,i) { return (Names[d.source.index] === "" ? "none" : "auto"); }) //Remove pointer events from dummy strokes
.attr("d", path)
.on("mouseover", fadeOnChord)
.on("mouseout", fade(opacityDefault));
////////////////////////////////////////////////////////////
////////////////// Extra Functions /////////////////////////
////////////////////////////////////////////////////////////
//Include the offset in de start and end angle to rotate the Chord diagram clockwise
function startAngle(d) { return d.startAngle + offset; }
function endAngle(d) { return d.endAngle + offset; }
// Returns an event handler for fading a given chord group
function fade(opacity) {
return function(d, i) {
wrapper.selectAll("path.chord")
.filter(function(d) { return d.source.index != i && d.target.index != i && Names[d.source.index] != ""; })
.transition()
.style("opacity", opacity);
};
}//fade
// Fade function when hovering over chord
function fadeOnChord(d) {
var chosen = d;
wrapper.selectAll("path.chord")
.transition()
.style("opacity", function(d) {
if (d.source.index == chosen.source.index && d.target.index == chosen.target.index) {
return opacityDefault;
} else {
return opacityLow;
}//else
});
}//fadeOnChord
/*Taken from http://bl.ocks.org/mbostock/7555321
//Wraps SVG text*/
function wrapChord(text, width) {
text.each(function() {
var text = d3.select(this),
words = text.text().split(/\s+/).reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 1.1, // ems
y = 0,
x = 0,
dy = parseFloat(text.attr("dy")),
tspan = text.text(null).append("tspan").attr("x", x).attr("y", y).attr("dy", dy + "em");
while (word = words.pop()) {
line.push(word);
tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(" "));
line = [word];
tspan = text.append("tspan").attr("x", x).attr("y", y).attr("dy", ++lineNumber * lineHeight + dy + "em").text(word);
}
}
});
}//wrapChord
//iFrame handler
var pymChild = new pym.Child();
pymChild.sendHeight()
setTimeout(function() { pymChild.sendHeight(); },5000);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment