Skip to content

Instantly share code, notes, and snippets.

@MisunoKitara
Last active January 8, 2019 12:47
Show Gist options
  • Save MisunoKitara/1c3d39ca986756fb6c579d8ed48a29dd to your computer and use it in GitHub Desktop.
Save MisunoKitara/1c3d39ca986756fb6c579d8ed48a29dd to your computer and use it in GitHub Desktop.
2018: Stretched Chord Diagram ATUB
license: mit

An example of using the Circular Flow diagram with actual data, as explained in my blog on How to create a Flow diagram with a circular Twist

It shows what types of educations (on the left) lead to what types of occupations (on the right) about 1.5 years after graduating. The data is based on about 18000 HBO graduates in 2014 in the Netherlands used for the State of the State project

forked from nbremer's block: Stretched Chord Diagram - From educations to occupations

forked from jonpage's block: Stretched Chord Diagram - From educations to occupations

forked from MisunoKitara's block: 2018: Stretched Chord Diagram ATAB

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>Team coherentie: ATUB</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">Hoeveel interactie is er tussen de teams binnen ATUB?</div>
<div id="chart"></div>
<div style="width: 100%;">
<div class="explanation">
Dit diagram toont de team-interactie voor dienst ATUB. Om een duidelijk overzicht te houden zijn niet alle teams opgenomen in dit diagram, alleen diegene met het grootste aandeel.
</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("Team.in");
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("Team dat het ticket oplost");
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 = ["Domein Studenten","Domein Onderzoek","Domein ISP-ES","Domein CRM","Domein Onderwijs","Servicepunt","Logistiek en Financien","Other", "",
"Other.in","Servicepunt.in","Domein Onderwijs.in","Domein CRM.in","Domein ISP-ES.in","Domein Onderzoek.in","Domein Studenten.in",""];
var respondents = 6406, //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, 10, 516, 39, 1, 36, 18, 1381, 0], // Domein Studenten
[0, 0,0,0,0,0,0,0, 0, 31, 102, 0, 0, 1, 1027, 1, 0], // Domein Onderzoek
[0, 0,0,0,0,0,0,0, 0, 3, 249, 59, 0, 828, 1, 42, 0], // Domein ISP-ES
[0, 0,0,0,0,0,0,0, 0, 8, 123, 6, 813, 0, 1, 1, 0], // Domein CRM
[0, 0,0,0,0,0,0,0, 0, 6, 171, 710, 3, 29, 1, 36, 0], // Domein Onderwijs
[0, 0,0,0,0,0,0,0, 0, 0, 0, 3, 0, 2, 1, 1, 0], // Servicepunt
[0, 0,0,0,0,0,0,0, 0, 0, 0, 2, 1, 0, 17, 0, 0], // Logistiek en Financien
[0, 0,0,0,0,0,0,0, 0, 0, 0, 54, 11, 9, 21, 31, 0], // Other
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,emptyStroke], //dummyBottom
[10, 31, 3, 8, 6, 0, 0, 0, 0, 0,0,0,0,0,0,0, 0], //Other.in
[516, 102, 249, 123, 171, 0, 0, 0, 0, 0,0,0,0,0,0,0, 0], //Servicepunt.in
[39, 0, 59, 6, 710, 3, 2, 54, 0, 0,0,0,0,0,0,0, 0], //Domein Onderwijs.in
[1, 0, 0, 813, 3, 0, 1, 11, 0, 0,0,0,0,0,0,0, 0], //Domein CRM.in
[36, 1, 828, 0, 29, 2, 0, 9, 0, 0,0,0,0,0,0,0, 0], //Domein ISP-ES.in
[18, 1027, 1, 1, 1, 1, 17, 21, 0, 0,0,0,0,0,0,0, 0], //Domein Onderzoek.in
[1381, 1, 42, 1, 36, 1, 0, 31, 0, 0,0,0,0,0,0,0, 0], //Domein Studenten.in
[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