|
<!DOCTYPE html> |
|
<meta charset="utf-8"> |
|
<style> |
|
|
|
body { |
|
font: 10px sans-serif; |
|
} |
|
|
|
.group-tick line { |
|
stroke: #000; |
|
} |
|
|
|
.ribbons { |
|
fill-opacity: 0.67; |
|
} |
|
|
|
|
|
</style> |
|
<svg width="1000" height="750"></svg> |
|
<script src="https://d3js.org/d3.v4.min.js"></script> |
|
<script> |
|
|
|
var json_file = "https://api.github.com/search/issues?q=repo:datacite/datacite+-label:user%20story"; |
|
//var json_file = "issues.json"; // for local dev |
|
|
|
var svg = d3.select("svg"), |
|
width = +svg.attr("width"), |
|
height = +svg.attr("height"), |
|
outerRadius = Math.min(width, height) * 0.5 - 40, |
|
innerRadius = outerRadius - 30; |
|
|
|
var formatValue = d3.formatPrefix(",.0", 1e3); |
|
|
|
var chord = d3.chord() |
|
.padAngle(0.05) |
|
.sortSubgroups(d3.descending); |
|
|
|
var arc = d3.arc() |
|
.innerRadius(innerRadius) |
|
.outerRadius(outerRadius); |
|
|
|
var ribbon = d3.ribbon() |
|
.radius(innerRadius); |
|
|
|
var ribbons; |
|
var labels; |
|
|
|
var color = d3.scaleOrdinal() |
|
.domain(d3.range(4)) |
|
.range(["#000000", "#FFDD89", "#957244", "#F26223"]); |
|
|
|
d3.json(json_file, function(data){ |
|
|
|
var transformed = transformData(data); |
|
var matrix = transformed.matrix; |
|
labels = transformed.labels; |
|
|
|
var g = svg.append("g") |
|
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")") |
|
.datum(chord(matrix)); |
|
|
|
var group = g.append("g") |
|
.attr("class", "groups") |
|
.selectAll("g") |
|
.data(function(chords) { return chords.groups; }) |
|
.enter().append("g") |
|
.append("a").attr("href", function(d) { |
|
return 'https://github.com/datacite/datacite/issues?q=label%3A"' + labels[d.index].name + '"' ; |
|
}) |
|
.attr("target", "_blank") |
|
.on("mouseover", handleMouseOver()) |
|
.on("mouseout", handleMouseOut()); |
|
|
|
group.append("path") |
|
.style("fill", function(d) { return labels[d.index].color; }) |
|
.style("stroke", function(d) { return d3.rgb(labels[d.index].color).darker(); }) |
|
.attr("d", arc); |
|
|
|
|
|
ribbons = g.append("g") |
|
.attr("class", "ribbons") |
|
.selectAll("path") |
|
.data(function(chords) { return chords; }) |
|
.enter().append("path") |
|
.attr("d", ribbon) |
|
.style("fill", function(d) { return labels[d.source.index].color; }) |
|
.style("stroke", function(d) { return d3.rgb(labels[d.source.index].color).darker(); }); |
|
|
|
|
|
var groupText = group.append("text") |
|
.each(function(d) { d.angle = (d.startAngle + d.endAngle) / 2; }) |
|
.attr("x", 6) |
|
.attr("dy", ".35em") |
|
.attr("transform", function(d) { |
|
return "rotate(" + (d.angle * 180 / Math.PI - 90) + ")" |
|
+ "translate(" + (innerRadius + 26) + ")" |
|
+ (d.angle > Math.PI ? " " : ""); |
|
}) |
|
.text(function(d,i) { return labels[i].name;}); |
|
|
|
}); // end d3.json |
|
|
|
// creates matrix from the json data |
|
function transformData(data){ |
|
|
|
var matrix = []; |
|
var labels = []; |
|
|
|
// extract labels and create the matrix and the list of values |
|
data.items.forEach(function(item){ |
|
item.labels.forEach(function(label){ |
|
labels[label.name] = {'name': label.name, 'color': label.color, 'url': label.url}; |
|
|
|
item.labels.forEach(function(related){ |
|
if(!matrix[label.name]){ |
|
matrix[label.name] = []; |
|
} |
|
if(!matrix[label.name][related.name]){ |
|
matrix[label.name][related.name] = 0; |
|
} |
|
if(label.id != related.id){ |
|
matrix[label.name][related.name]++; |
|
} |
|
}); |
|
}); |
|
}); |
|
|
|
// now put the labels into an array for easier handling |
|
var sorted_labels = []; |
|
Object.keys(labels).sort().forEach(function(lcol){ |
|
sorted_labels.push(labels[lcol]); |
|
}); |
|
labels = sorted_labels.sort(getSortMethod('+color','+name')); |
|
|
|
|
|
var ret = []; |
|
var ret_labels = []; |
|
|
|
//Object.keys(labels).sort().forEach(function(lcol){ |
|
labels.forEach(function(lcol){ |
|
var r = []; |
|
labels.forEach(function(lrow){ |
|
if(matrix[lcol.name][lrow.name]){ |
|
r.push(matrix[lcol.name][lrow.name]); |
|
} |
|
else{ |
|
r.push(0); |
|
} |
|
}); |
|
ret.push(r); |
|
}); |
|
|
|
|
|
|
|
return {'matrix': ret, 'labels': labels}; |
|
} |
|
|
|
function getSortMethod(){ |
|
var _args = Array.prototype.slice.call(arguments); |
|
return function(a, b){ |
|
for(var x in _args){ |
|
var ax = a[_args[x].substring(1)]; |
|
var bx = b[_args[x].substring(1)]; |
|
var cx; |
|
|
|
ax = typeof ax == "string" ? ax.toLowerCase() : ax / 1; |
|
bx = typeof bx == "string" ? bx.toLowerCase() : bx / 1; |
|
|
|
if(_args[x].substring(0,1) == "-"){cx = ax; ax = bx; bx = cx;} |
|
if(ax != bx){return ax < bx ? -1 : 1;} |
|
} |
|
} |
|
} |
|
|
|
function fade(opacity) { |
|
return function(d, i) { |
|
ribbons |
|
.filter(function(d) { |
|
return d.source.index != i && d.target.index != i; |
|
}) |
|
.style("opacity", opacity); |
|
ribbons |
|
.filter(function(d) { |
|
return d.source.index == i || d.target.index == i; |
|
}) |
|
.style("fill", function(d) { console.log(labels[i].color); return "#" + labels[i].color;}); |
|
|
|
}; |
|
} |
|
|
|
function handleMouseOver(){ |
|
return function(d,i){ |
|
ribbons.filter(function(d) { return d.source.index != i && d.target.index != i; }) |
|
.style("opacity", 0.05); |
|
ribbons.filter(function(d) { return d.source.index == i || d.target.index == i; }) |
|
.style("fill", function(d) { return "#" + labels[i].color;}) |
|
.style("opacity",1); |
|
|
|
}; |
|
} |
|
function handleMouseOut(){ |
|
return function(d,i){ |
|
// ribbons.filter(function(d) { return d.source.index != i && d.target.index != i; }) |
|
// .style("opacity", 1); |
|
|
|
}; |
|
} |
|
</script> |