Skip to content

Instantly share code, notes, and snippets.

Created April 14, 2017 02:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save anonymous/1ae0569c2f567385f2666157d8bba611 to your computer and use it in GitHub Desktop.
Save anonymous/1ae0569c2f567385f2666157d8bba611 to your computer and use it in GitHub Desktop.
Stretched Chord Diagram - From educations to occupations
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 scresawn's block: Stretched Chord Diagram - From educations to occupations

////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////
////////////// Custom Chord Layout Function ////////////////
/////// Places the Chords in the visually best order ///////
///////////////// to reduce overlap ////////////////////////
////////////////////////////////////////////////////////////
///////// Slightly adjusted by Nadieh Bremer ///////////////
//////////////// VisualCinnamon.com ////////////////////////
////////////////////////////////////////////////////////////
//////// Original from the d3.layout.chord() function //////
///////////////// from the d3.js library ///////////////////
//////////////// Created by Mike Bostock ///////////////////
////////////////////////////////////////////////////////////
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>
<!-- 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>
////////////////////////////////////////////////////////////
//////////////////////// 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],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,0,0,11,0,0,24,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,173,43,52,55,36,125,2413,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,16,13,23,10,37,54,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,161,24,17,0,2089,85,60,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,510,0,0,57,0,0,251,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,16,118,10,454,99,1537,271,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,76,21,10,15,125,41,261,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,2206,37,292,32,116,76,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,74,43,116,51,135,752,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,34,0,22,27,156,36,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,1141,0,111,291,0,0,48,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,0,39,0,0,20,109,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,emptyStroke],
[232,32,173,32,161,510,16,76,32,96,15,1141,36,0,0,0,0,0,0,0,0,0],
[65,0,43,16,24,0,118,21,2206,74,34,0,0,0,0,0,0,0,0,0,0,0],
[44,0,52,13,17,0,10,10,37,43,0,111,39,0,0,0,0,0,0,0,0,0],
[57,11,55,23,0,57,454,15,292,116,22,291,0,0,0,0,0,0,0,0,0,0],
[39,0,36,10,2089,0,99,125,32,51,27,0,0,0,0,0,0,0,0,0,0,0],
[123,0,125,37,85,0,1537,41,116,135,156,0,20,0,0,0,0,0,0,0,0,0],
[1373,24,2413,54,60,251,271,261,76,752,36,48,109,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,emptyStroke,0,0,0,0,0,0,0,0]
];
//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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment