Search code examples
d3.jssvgchord-diagram

How to change shape of chord diagram from circle to ellipse shape?


I want to create chord diagram using d3js. Here is an example code that I am implementing:

<script src="http://d3js.org/d3.v3.min.js"></script>
<body>
<svg width="960" height="960"></svg>
<script>
d3.text("data.csv", function(error, datas) {
var dataf = d3.csv.parseRows(datas);

dataf.shift();

var text = [];
var nest = d3.nest()
    .key(function(d) { return d[1]; })
    .entries(dataf)
    .sort(function(a, b) { return a.key < b.key ? -1 : 1; });
nest.forEach(function(d) {
    text.push(d.key);
});
nest = d3.nest()
    .key(function(d) { return d[2]; })
    .entries(dataf)
    .sort(function(a, b) { return a.key < b.key ? -1 : 1; });
nest.forEach(function(d) {
    text.push(d.key);
});
var textnested = [];
d3.nest()
    .key(function(d) { return d; })
    .entries(text)
    .forEach(function(d) {
        textnested.push(d.key);
    });
if(textnested.length < text.length) {
    var inDirection = 1;
    text = textnested;
    text.sort(function(a, b) { return a < b ? -1 : 1; });
}
nest = d3.nest()
    .key(function(d) { return d[0]; })
    .entries(dataf);
if(nest.length < dataf.length) {
    nest.forEach(function(d) {
        d.values.forEach(function(e) { e.push(1/d.values.length); });
    });
}

var matrix = [];
text.forEach(function(d, i) {
matrix[i] = d3.range(text.length).map(function() { return 0; });
});
dataf.forEach(function(d) {
var x = text.indexOf(d[1]),
    y = text.indexOf(d[2]);
if(nest.length < dataf.length) matrix[x][y]+=d[d.length-1];
else matrix[x][y]++;
if(!inDirection) {
    if(nest.length < dataf.length) matrix[y][x]+=d[d.length-1];
    else matrix[y][x]++;
}
});
var chord = d3.layout.chord()
.padding(.04)
.matrix(matrix);
 var svg = d3.select("svg")
.append("g")
.attr("transform", "translate(480, 480)");
 var fill = d3.scale.category20();
 var g = svg.selectAll(".group")
.data(chord.groups)
.enter()
.append("g")
.attr("class", "group");
 g.append("path")
.attr("d", d3.svg.arc().innerRadius(350).outerRadius(370))
.style("fill", function(d) { return fill(d.index); })
.on("mouseover", standOut(0.1))
.on("mouseout", standOut(1));
 g.append("text")
.attr("transform", function(d) {
     return "rotate("+(((d.startAngle+d.endAngle)/2) * (180/Math.PI) -    90)+")"
        + "translate(376)" + (((d.startAngle+d.endAngle)/2) > Math.PI ? "rotate(180)" : "");    })
.attr("text-anchor", function(d) { return       ((d.startAngle+d.endAngle)/2) > Math.PI ? "end" : ""; })
.text(function(d) { return text[d.index]; });
svg.selectAll(".chord")
.data(chord.chords)
.enter()
.append("path")
.attr("class", "chord")
.attr("d", d3.svg.chord().radius(350))
.style("fill", function(d) { return fill(d.source.index); })
.style("stroke", function(d) { return   d3.rgb(fill(d.source.index)).darker(); });
function standOut(o) {
return function(d, i) {
    var gstandout = [];
    svg.selectAll(".chord")
        .each(function(e) { e.source.index == i ?  gstandout.push(e.target.index) : e.target.index == i ? gstandout.push(e.source.index) : ""; })
        .filter(function(e) { return e.source.index != i && e.target.index != i; })
        .style("opacity", o);
    svg.selectAll(".group")
        .filter(function(e, j) { return gstandout.indexOf(j) == -1 && j != i; })
        .style("opacity", o);
};
}
});
</script>
 </body>

Following is data.csv file:

"CitingArticle","Cited","Subject"
"Locke et al., 2015","Laosinchai, P.","Agricultural and Biological  Sciences"
"Locke et al., 2015","Laosinchai, P.","Biochemistry, Genetics and Molecular Biology"
"Yasri, 2015","Yasri, P.","Social Sciences"
 "Yang et al., 2015","Panjaburee, P.","Computer Science"
 "Yang et al., 2015","Panjaburee, P.","Social Sciences"
 "Chu et al., 2015","Panjaburee, P.","Computer Science"
 "Chu et al., 2015","Panjaburee, P.","Social Sciences"
 "Yang et al., 2015","Panjaburee, P.","Computer Science"
 "Yang et al., 2015","Panjaburee, P.","Social Sciences"
  "Chu et al., 2015","Panjaburee, P.","Computer Science"
 "Chu et al., 2015","Panjaburee, P.","Social Sciences"
 "Buaraphan & Abedin Forhad, 2015","Buaraphan, K.","Social Sciences"
 "Kim & Choi, 2015","Yodyingyong, S.","Chemical Engineering"
  "Kim & Choi, 2015","Yodyingyong, S.","Chemistry"
  "Kim & Choi, 2015","Yodyingyong, S.","Engineering"
  "Kim & Choi, 2015","Yodyingyong, S.","Materials Science"
  "Kim & Choi, 2015","Yodyingyong, S.","Physics and Astronomy"
  "Bretz & McClary, 2015","Ratanaroutai, T.","Chemistry"
  "Bretz & McClary, 2015","Ratanaroutai, T.","Social Sciences"
   "Panijpan et al., 2015","Sriwattanarothai, N.","Biochemistry, Genetics and Molecular Biology"
  "Panijpan et al., 2015","Sriwattanarothai, N.","Medicine"
  "Durand & Borsa, 2015","Sriwattanarothai, N.","Agricultural and Biological Sciences"
  "Durand & Borsa, 2015","Sriwattanarothai, N.","Biochemistry, Genetics and Molecular Biology"
  "Durand & Borsa, 2015","Sriwattanarothai, N.","Immunology and Microbiology"
 "Durand & Borsa, 2015","Sriwattanarothai, N.","Medicine"
 "Vu et al., 2015","Yodyingyong, S.","Chemical Engineering"
 "Vu et al., 2015","Yodyingyong, S.","Environmental Science"
 "Chang et al., 2015","Yodyingyong, S.","Materials Science"
 "Zhang et al., 2015","Ketpichainarong, W.","Social Sciences"
 "Buaraphan & Abedin Forhad, 2015","Buaraphan, K.","Social Sciences"
 "Cil, 2015","Buaraphan, K.","Social Sciences"
 "Buaraphan & Abedin Forhad, 2015","Buaraphan, K.","Social Sciences"
 "Di Russo et al., 2015","Chenprakhon, P.","Biochemistry, Genetics and Molecular Biology"
 "De Sancho et al., 2015","Chenprakhon, P.","Chemistry"
 "De Sancho et al., 2015","Chenprakhon, P.","Computer Science"
 "Cazade et al., 2015","Chenprakhon, P.","Chemistry"
 "Cazade et al., 2015","Chenprakhon, P.","Medicine"
 "Cazade et al., 2015","Chenprakhon, P.","Physics and Astronomy"

But I want to show chord in ellipse shape rather than circular. Anyone have idea how it can be possible? Your help is highly appreciated.


Solution

  • First of all, the chords are not drawn as circles, but as bezier curves with a single control point in the center of the drawing (which kind of look like circles). I propose to place two control points closer to the rim to obtain an "ellipse-like" effect.

    I'm not sure how to draw such arcs from scratch, but it is possible to edit the d attribute of the paths after they are drawn.

    Here is my editPath function:

    function editPath() {
        var ratio = 0.6;
        var path=d3.select(this).attr("d")
        console.log(path);
        find=path.match(/([\-\.0-9]+)\,([\-\.0-9]+)Q 0,0 ([\-\.0-9]+),([\-\.0-9]+)/);
        if (!find) return;
    
        path=path.replace(/Q 0,0/,"C "+find[1]*ratio+","+find[2]*ratio+" "+find[3]*ratio+","+find[4]*ratio);
    
        console.log(path);
        d3.select(this).attr("d", path)
    }
    

    This function does the following:

    • extract the path commands (path)
    • identify a bezier curve (Q) centered in 0,0
    • extract the coordinates of the starting and ending point (array find)
    • compute the coordinates of the new control points (find[i]*ratio)
    • inject this into a new bezier curve (with two control points, C)
    • replace the orignal path with the new one

    This replaces only one bezier curve in the path, so you then need to call it twice for each chord:

    [...]
     .attr("class", "chord")
     .attr("d", d3.svg.chord().radius(350))
     .each(editPath).each(editPath) //call twice!
    

    I tested this on this example: http://bl.ocks.org/mbostock/raw/4062006/ ... it works there, I hope it should work on any graph using d3.svg.chord.