A bio chord diagram.
Author: cxhair@163.com
license: mit |
A bio chord diagram.
Author: cxhair@163.com
{ | |
"title": "Chord", | |
"size": [500, 600], | |
"table": { | |
"header": [["cellular.component.organization.or.biogenesis", 8.0], ["metabolic.process", 7.0], ["multicellular.organismal.process", 4.0], ["immune.system.process", 4.0], ["multi-organism.process", 2.00], ["detoxification", 2.00]], | |
"body": [ | |
["QW06727", 1, 1, 1, 1, 0, 1, 1.53474591], | |
["ER8493", 0, 0, 1, 0, 0, 0, 2.513523301], | |
["OPIWZ3", 0, 0, 0, 1, 0, 0, 1.80352426], | |
["GG24R6N9", 1, 0, 1, 0, 0, 0, 2.041762452], | |
["V9GYM3", 1, 1, 1, 0, 1, 0, 1.68518518519], | |
["B2R657", 1, 1, 0, 0, 1, 0, 1.65060240964], | |
["B4DUV1", 1, 0, 0, 0, 0, 0, 1.6106493506], | |
["V9HW62", 0, 1, 0, 0, 0, 0, 1.6025572196], | |
["LH0A024R3E3", 1, 1, 0, 0, 0, 0, 1.6009270965], | |
["KHJBTZM4", 1, 1, 1, 1, 0, 1, 2.57423657424], | |
["Q5U000", 1, 1, 1, 1, 0, 0, 1.54274937133], | |
["P13804", 0, 1, 0, 0, 0, 0, 1.53814884303], | |
["P23142", 1, 1, 0, 0, 1, 0, 2.5827158], | |
["P32189", 0, 1, 0, 0, 0, 0, 1.516545218], | |
["A8K6C1", 1, 1, 1, 0, 0, 0, 1.510312256], | |
["Q8TDB2", 0, 1, 0, 0, 0, 0, 1.5023535], | |
["B2R8Y9", 0, 0, 1, 0, 0, 0, 0.663498836307], | |
["OPHBJH0A0S2Z471", 0, 1, 0, 0, 0, 0, 0.654721658778], | |
["A0A0C4DH39", 1, 1, 0, 1, 1, 0, 0.6501820886], | |
["KHGHBQ7Z333", 1, 1, 0, 0, 0, 0, 0.6291174213], | |
["P02768", 1, 1, 1, 0, 1, 1, 0.5503859826], | |
["P03951", 0, 1, 1, 0, 0, 0, 0.526632067741], | |
["Q02127", 0, 1, 1, 0, 1, 0, 2.006145136], | |
["A8K8P1", 1, 0, 0, 0, 0, 0, 1.987159438], | |
["P00738", 0, 0, 0, 1, 1, 1, 1.97685185185], | |
["B0YIW2", 1, 1, 1, 0, 0, 0, 1.92465753425], | |
["A0A0K0K1L1", 0, 1, 0, 0, 0, 0, 1.87334656085], | |
["B4DI23", 0, 1, 0, 0, 0, 0, 1.74512836375], | |
["A0A024RCW6", 0, 1, 1, 0, 0, 0, 0.524739583333], | |
["A0A0F7G8J1", 0, 1, 1, 0, 1, 0, 0.5155267911], | |
["B2RCQ6", 0, 1, 0, 0, 0, 0, 0.5101132095], | |
["KGYGGVKHUUHU22", 1, 1, 0, 0, 0, 0, 0.3308482805] | |
] | |
}, | |
"gradient": ["#0000ff", "#ffeeff", "#ff0000"], | |
"gradientTitle": "FC", | |
"legendTitle": "Iterms:" | |
} |
<!DOCTYPE html> | |
<head> | |
<meta charset="utf-8"> | |
<script src="https://d3js.org/d3.v3.min.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script> | |
<style> | |
body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; } | |
</style> | |
</head> | |
<body> | |
<div id="container"></div> | |
<script> | |
d3.svg.goChord = function(id, options){ | |
var opt = { | |
title : "", | |
size : [600, 500], | |
gradient : ["#0e0ee8", "#f8f7f7", "#e81a1a"], | |
gradientTitle : "", | |
legendTitle : "", | |
margin : {top: 60, right: 30, bottom: 30, left: 30} | |
}; | |
_.extend(opt, options); | |
// data processing | |
var cData = (function(dt){ | |
dt.table.body.sort(function(a, b){ return _.last(a) - _.last(b); }); | |
dt.table.row = dt.table.body.map(function(d){ | |
return [d.shift(), d.pop()]; | |
}); | |
var cd = {}; | |
// closure values | |
var cSquareMatrix = function(n){ | |
var repeat = String.prototype.repeat, s; | |
if (!repeat) { | |
// "repeat" function is not available in IE | |
// Make sure compatibility with IE | |
repeat = function(str, m){ | |
return _.range(0, m).map(function(){ return str; }).join(""); | |
}; | |
s = "[" + repeat(",0", n).slice(1) + "]"; | |
s = repeat("," + s, n).slice(1); | |
} else { | |
s = "[" + ",0".repeat(n).slice(1) + "]"; | |
s = ("," + s).repeat(n).slice(1); | |
} | |
return JSON.parse("[" + s + "]"); | |
}; | |
var fcDomain = d3.extent(dt.table.row.map(function(d){ return d[1]; })); | |
var lScale = d3.scale.linear() | |
.domain([fcDomain[0], 0.5 * (fcDomain[0] + fcDomain[1]), fcDomain[1]]) | |
.range(dt.gradient); | |
var rScale = d3.scale.linear() | |
.domain(d3.range(0, dt.table.header.length)) | |
.range([].concat(d3.scale.category20().range(), | |
d3.scale.category20b().range())); | |
// initial values | |
cd._padding = 0; | |
cd._innerRadius = 100; | |
cd._outerRadius = 155; | |
cd._groups = null; | |
cd._chords = null; | |
//cd.chd = d3.layout.chord().sortSubgroups(d3.descending); | |
cd.chd = d3.layout.chord(); | |
cd.chord = d3.svg.chord().radius((cd._innerRadius + cd._outerRadius) * 0.5); | |
cd.leftItems = dt.table.row.map(function(d){ return d[0]; }); | |
cd.rightItems = dt.table.header.map(function(d){ return d[0]; }); | |
cd.items = [].concat(cd.rightItems, cd.leftItems); | |
cd.fcDomain = fcDomain; | |
cd.rScale = rScale; | |
cd.lScale = lScale; | |
// right arc generator | |
cd.rArc = d3.svg.arc() | |
.innerRadius(cd._innerRadius) | |
.outerRadius(cd._outerRadius); | |
// left arc generator | |
cd.lArc = d3.svg.arc() | |
.innerRadius((cd._innerRadius + cd._outerRadius) * 0.5) | |
.outerRadius(cd._outerRadius); | |
cd.getMatrix = function(){ | |
if(this.matrix){ return this._matrix; } | |
var matrix = cSquareMatrix(cd.items.length); | |
dt.table.body.forEach(function(row, index){ | |
var columIndex, rowIndex; | |
rowIndex = index + dt.table.header.length; | |
_.each(row, function(val, ii){ | |
columIndex = ii; | |
matrix[rowIndex][columIndex] = val; | |
matrix[columIndex][rowIndex] = val; | |
}); | |
}); | |
this.matrix = matrix; | |
return matrix; | |
}; | |
cd.chd.matrix(cd.getMatrix()); | |
// get-Function and set-Function | |
cd.padding = function(val){ | |
if(!arguments.length){ return this._padding; } | |
this._padding = val; | |
this.chd.padding(val); | |
return this; | |
}; | |
cd.innerRadius = function(val){ | |
if(!arguments.length){ return this._innerRadius; } | |
var half = 0.5 * (val + this._outerRadius); | |
this._innerRadius = val; | |
this.lArc.innerRadius(half); | |
this.rArc.innerRadius(val); | |
this.chord.radius(val); | |
this._groups = null; | |
this._chords = null; | |
return this; | |
}; | |
cd.outerRadius = function(val){ | |
if(!arguments.length){ return this._outerRadius; } | |
var half = 0.5 * (val + this._innerRadius); | |
this._outerRadius = val; | |
this.rArc.outerRadius(val); | |
this.lArc.outerRadius(val); | |
this.lArc.innerRadius(half); | |
this._groups = null; | |
this._chords = null; | |
return this; | |
}; | |
cd.check = function(){ | |
return this._outerRadius > this._innerRadius; | |
}; | |
cd.getGroups = function(){ | |
if(this._groups){ return this._groups; } | |
if(!this.check()){ alert("Bad data!"); return; } | |
var groups = this.chd.groups(), | |
right = dt.table.header.length; | |
_.each(groups, function(dd, ii){ | |
if (ii < right) { | |
_.extend(dd, { | |
color : rScale(ii), | |
zscore : dt.table.header[ii][1], | |
opacity : 0.7, | |
name : cd.items[dd.index] | |
}); | |
} else { | |
_.extend(dd, { | |
color : lScale(dt.table.row[ii - right][1]), | |
fc : dt.table.row[ii - right][1], | |
opacity : 1, | |
name : cd.items[dd.index] | |
}); | |
} | |
}); | |
// cache this value avoiding intensive calculation | |
this._groups = groups; | |
return groups; | |
}; | |
cd.getChords = function(){ | |
if(this._chords){ return this._chords; } | |
if(!this.check()){ alert("Bad data!"); return; } | |
var chords = this.chd.chords().map(function(dd){ | |
dd.color = rScale(dd.source.index); | |
dd.opacity = 1; | |
return dd; | |
}); | |
// cache this value avoiding intensive calculation | |
this._chords = chords; | |
return chords; | |
}; | |
return cd; | |
})(opt); | |
var svg = d3.select("#" + id) | |
.html("") | |
.append("svg") | |
.attr({ | |
width : 100, | |
height: 100, | |
xmlns : "http://www.w3.org/2000/svg" | |
}) | |
.style("font", "12px arial, sans-serif"); | |
var dim = (function(svg, cd, opt){ | |
var dd = {}; | |
dd.outerRadius = 0; | |
dd.outerTextLength = 0; | |
dd.lgdHeight = 0; | |
dd.lgdWidth = 0; | |
dd.lgdBoxSize = 12; | |
dd.grdWidth = 100; | |
dd.grdHeight = 85; | |
dd.smallGap = 10; | |
dd.largeGap = 15; | |
dd.svgSize = [0, 0]; | |
dd.getTextBox = function(text){ | |
var box, node; | |
try { | |
node = svg.append("text").text(text); | |
box = node.node().getBBox(); | |
node.remove(); | |
} catch (err) { | |
box = { width: text.length * 8.2, height: 15, x: null, y: null }; | |
} | |
return box; | |
}; | |
dd.outerRadius = (function(self){ | |
var longText = _.last(_.sortBy(cd.leftItems.slice(), "length")); | |
var box = self.getTextBox(longText); | |
// The bigger value of "factor", | |
// the rarer collison chances on the side texts | |
var factor = 15; | |
var outerRadius = Math.ceil((box.height + factor) * cd.leftItems.length / Math.PI); | |
if (outerRadius < 100){ outerRadius = 100; } | |
self.outerTextLength = Math.ceil(box.width + 10); | |
return outerRadius; | |
})(dd); | |
dd.lgdWidth = (function(self){ | |
var longText = _.last(_.sortBy(cd.rightItems.slice(), "length")); | |
var box = self.getTextBox(longText); | |
return Math.ceil(self.lgdBoxSize + box.width + 5); | |
})(dd); | |
dd.lgdHeight = (cd.rightItems.length + 1) * (dd.lgdBoxSize + 4); | |
// recalculate svg dimension | |
dd.svgSize[0] = opt.margin.left + opt.margin.right + | |
dd.outerRadius * 2 + dd.outerTextLength + dd.largeGap + | |
Math.max(dd.lgdWidth, dd.grdWidth); | |
if(dd.svgSize[0] < opt.size[0]){ | |
dd.outerRadius += (opt.size[0] - dd.svgSize[0]) * 0.5; | |
dd.svgSize[0] = opt.size[0]; | |
} | |
dd.svgSize[1] = opt.margin.top + opt.margin.bottom + | |
Math.max((dd.outerRadius + dd.outerTextLength) * 2, | |
(dd.grdHeight + dd.lgdHeight)); | |
return dd; | |
})(svg, cData, opt); // anonymous function END | |
// resize SVG canvas | |
svg.attr({ width: dim.svgSize[0], height: dim.svgSize[1] }); | |
// defs | |
var gradientID = "BWR-" + (new Date()).getTime(); | |
svg.append("defs") | |
.append("linearGradient") | |
.attr({ | |
id: gradientID, | |
x1: 0, y1: 0, x2: 1, y2: 0 | |
}) | |
.selectAll("stop") | |
.data(["0%", "50%", "100%"]) | |
.enter() | |
.append("stop") | |
.each(function(d, i){ | |
d3.select(this).attr({ offset: d, "stop-color": opt.gradient[i] }); | |
}); | |
var gMain = svg.append("g") | |
.attr("class", "gMain") | |
.attr("transform", "translate(" + [opt.margin.left, opt.margin.top] + ")"); | |
var gChordPie = gMain.append("g") | |
.attr("class", "gChordPie") | |
.attr("transform", "translate(" + [dim.outerRadius + dim.outerTextLength, | |
dim.outerRadius + dim.outerTextLength] + ")"); | |
var gOther = gMain.append("g") | |
.attr("class", "gOther") | |
.attr("transform", "translate(" + | |
(dim.outerRadius * 2 + dim.outerTextLength + dim.largeGap) + ",0)"); | |
cData.outerRadius(dim.outerRadius).innerRadius(dim.outerRadius - 30); | |
gChordPie.append("g") | |
.attr("class", "gChrods") | |
.selectAll("path") | |
.data(cData.getChords()) | |
.enter() | |
.append("path") | |
.on("mouseenter", chordsOnMouseEnter) | |
.on("mouseleave", chordsOnMouseLeave) | |
.attr({ | |
d : cData.chord, | |
stroke : "black", | |
"stroke-width": 0.5, | |
fill : function(d){ return d.color; }, | |
opacity : function(d){ return d.opacity; } | |
}); | |
gChordPie.selectAll("g.gGroups") | |
.data(cData.getGroups()) | |
.enter() | |
.append("g") | |
.attr("class", "gGroups") | |
.each(function(d){ | |
var self = d3.select(this); | |
self.append("path") | |
.on("mouseenter", groupsOnMouseEnter) | |
.on("mouseleave", groupsOnMouseLeave) | |
.attr({ | |
d : ("zscore" in d) ? cData.rArc(d) : cData.lArc(d), | |
stroke : "black", | |
"stroke-width": 0.5, | |
fill : d.color, | |
opacity : d.opacity | |
}) | |
.append("title") | |
.text(function(){ | |
var title = "name: " + d.name; | |
if("zscore" in d){ | |
title += "\nzscore: " + d.zscore; | |
}else{ | |
title += "\n" + opt.gradientTitle + ": " + d.fc; | |
} | |
return title; | |
}); | |
self.append("g") | |
.attr("class", "gName") | |
.attr("transform", function(d){ | |
return "rotate(" + | |
((d.startAngle + d.endAngle) * 0.5 * 180 / Math.PI - 90) + ")" + | |
"translate(" + (cData.outerRadius() + 5) + ",0)"; | |
}) | |
.append("text") | |
.text(d.name) | |
.attr({ transform: "rotate(180)", "text-anchor": "end", y: 5 }) | |
.style("display", ("zscore" in d) ? "none" : "block"); | |
}); | |
// Events | |
function chordsOnMouseEnter(d){ | |
gChordPie.selectAll(".gChrods path").attr("opacity", 0.1); | |
d3.select(this).attr("opacity", 1); | |
gChordPie.selectAll(".gGroups").attr("opacity", function(j){ | |
return ((j.index === d.source.index) || (j.index === d.target.index)) ? 1 : 0.3; | |
}); | |
} | |
function chordsOnMouseLeave(d){ | |
gChordPie.selectAll(".gChrods path").attr("opacity", 1); | |
gChordPie.selectAll(".gGroups").attr("opacity", 1); | |
} | |
function groupsOnMouseEnter(d){ | |
var indexs = []; | |
gChordPie.selectAll(".gChrods path") | |
.each(function(j){ | |
if ((d.index === j.source.index) || | |
(d.index === j.target.index)) { | |
indexs.push(j.source.index); | |
indexs.push(j.target.index); | |
d3.select(this).attr({ "stroke-width": 1, opacity: 1 }); | |
} else { | |
d3.select(this).attr({ "stroke-width": 0, opacity: 0.2 }); | |
} | |
}); | |
gChordPie.selectAll(".gGroups").attr("opacity", function(j){ | |
return _.contains(indexs, j.index) ? 1 : 0.2; | |
}); | |
} | |
function groupsOnMouseLeave(d){ | |
gChordPie.selectAll(".gGroups").attr("opacity", 1); | |
gChordPie.selectAll(".gChrods path") | |
.attr("opacity", 1) | |
.attr("stroke-width", 0.5); | |
} | |
gOther.append("g") | |
.attr("class", "gGradient") | |
.attr("transform", "translate(0, 20)") | |
.call(function(s){ | |
var axis = d3.svg.axis() | |
.ticks(3) | |
.orient("bottom") | |
.scale(d3.scale.linear().range([0, dim.grdWidth]).domain(cData.fcDomain)); | |
s.append("rect").attr({ | |
width : dim.grdWidth, | |
height: 20, | |
fill : "url(#" + gradientID + ")" | |
}); | |
s.append("g") | |
.attr("transform", "translate(0,20)") | |
.call(axis) | |
.call(function(t){ | |
t.select("path").attr({ fill: "none", stroke: "none" }); | |
t.selectAll("line").attr({ stroke: "black", "stroke-width": 1 }); | |
}); | |
s.append("text") | |
.text(("gradientTitle" in opt) ? opt.gradientTitle : "") | |
.attr({ | |
"text-anchor": "middle", | |
transform: "translate(" + (dim.grdWidth * 0.5) + ",-5)", | |
}); | |
}); | |
gOther.append("g") | |
.attr("class", "gLegend") | |
.attr("transform", "translate(0," + dim.grdHeight + ")") | |
.call(function(self){ | |
self.append("text").text(("legendTitle" in opt) ? opt.legendTitle : ""); | |
self.selectAll("g") | |
.data(cData.rightItems) | |
.enter() | |
.append("g") | |
.each(function(dd, ii){ | |
var yoo = d3.select(this); | |
yoo.attr("transform", "translate(0," + ((ii + 1) * 19) + ")"); | |
yoo.append("rect").attr({ | |
width : dim.lgdBoxSize, | |
height: dim.lgdBoxSize, | |
fill : cData.rScale(ii) | |
}); | |
yoo.append("text") | |
.text(dd) | |
.attr({ | |
transform: "translate(" + (dim.lgdBoxSize + 5) + ",10)" | |
}); | |
}); | |
}); | |
// main title | |
gMain.append("text") | |
.text(("title" in opt) ? opt.title : "") | |
.attr({ | |
"text-anchor": "middle", | |
"transform": "translate(" + [dim.outerRadius + dim.outerTextLength, -25] + ")" | |
}) | |
.style("font", "25px arial, sans-serif"); | |
}; // goChord END | |
d3.json("CHROD.json", function(error, data){ | |
if(error){ return console.log(error); } | |
d3.svg.goChord("container", data); | |
}); | |
d3.select(self.frameElement) | |
.style({ | |
width: 950 + "px", | |
height: 750 + "px", | |
overflow: "auto" | |
}); | |
</script> | |
</body> |