<!DOCTYPE html>
<meta charset="utf-8">
<title>Hive Plots</title>
<style>
@import url(../style.css?20120427);
.axis {
stroke: #000;
stroke-width: 1.5px;
}
.node circle {
stroke: #000;
}
.link {
fill: none;
stroke: #999;
stroke-width: 1.5px;
stroke-opacity: .3;
}
.link.active {
stroke: red;
stroke-width: 2px;
stroke-opacity: 1;
}
.link.ortholog {
stroke: green;
stroke-width: 2px;
stroke-opacity: 1;
}
.node circle.active {
stroke: red;
stroke-width: 3px;
}
</style>
<h1>TreeFam Homology Hive Plots</h1>
<p>Mouseover to show the homology relationships for the corresponding gene.
<p id="info">Loading…
<p id="chart">
<p style="margin-top:4em;">Special thanks to <a href="http://www.cfcl.com/rdm/">Rich Morin</a> for assisting in the implementation of hive plots in D3!
<footer>
<aside>March 18, 2012</aside>
<a href="../" rel="author">Mike Bostock</a>
</footer>
<script src="http://d3js.org/d3.v2.js?2.8.1"></script>
<script>
var width = 960,
height = 850,
innerRadius = 40,
outerRadius = 640,
majorAngle = 2 * Math.PI / 3,
minorAngle = 1 * Math.PI / 12;
var angle = d3.scale.ordinal()
//.domain(["source", "source-target", "target-source", "target"])
.domain(["source", "source-target", "target-source", "target"])
// .range([0, majorAngle - minorAngle, majorAngle + minorAngle, 2 * majorAngle])
.range([0, majorAngle - minorAngle, majorAngle + minorAngle, 2 * majorAngle]);
var radius = d3.scale.linear()
.range([innerRadius, outerRadius]);
var color = d3.scale.category10();
var svg = d3.select("#chart").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
//.attr("transform", "translate(" + outerRadius * .20 + "," + outerRadius * .57 + ")")
.attr("transform", "translate(140,40)")
;
// Load the data and display the plot!
d3.json("treefam.json", function(nodes) {
var nodesByName = {},
genesByName = {},
links = [],
geneNodes = [],
homologyNodes = [],
ignoreSpecies = {},
ignoreRank = {},
formatNumber = d3.format(",d"),
defaultInfo;
image_path = "../../lib/images/";
var rank_x = 200;
var species_x = 400;
var homologs_x = 600;
var gene_distance = 12;
var rank_distance = 12;
var species_distance = 12;
var homologs_distance = 12;
var rankNodes = [];
var speciesNodes = [];
// iterating over all input genes
// Construct an index by node name.
nodes.forEach(function(d) {
d.connectors = [];
d.packageName = d.name.split(".")[0];
genesByName[d.name] = d;
});
//console.log(genesByName);
// Convert the import lists into links with sources and targets.
nodes.forEach(function(source) {
// console.log(source);
//console.log(source.imports);
source.imports.forEach(function(targetName) {
// console.log(targetName);
var target = genesByName[targetName];
//console.log("adding to links: "+links.length);
//if(!target){console.log("could not find target for "+targetName.target);}
//if (!source.source) {
// console.log("source: add to connectors");
// source.connectors.push(source.source = {node: source, degree: 0});
//}
//if (!target.target){
//console.log("target: add to connectors");
// target.connectors.push(target.target = {node: target, degree: 0});
//}
//rank2species.push(d.rank);
links.push({source: source, target: targetName});
if( ignoreRank[targetName.rank] === undefined ) {
rankNodes.push(targetName);
ignoreRank[targetName.rank] = 1;
}
if( ignoreSpecies[targetName.taxon] === undefined ) {
speciesNodes.push(targetName);
ignoreSpecies[targetName.taxon] = 1;
}
// if( ignoreS[targetName.taxon] === undefined ) {
homologyNodes.push(targetName);
//ignoreHomology[targetName.taxon] = 1;
//}
});
geneNodes.push(source);
});
console.log("species");
console.log(speciesNodes);
//console.log(nodes);
//test
// console.log("geneNodes");
// console.log(geneNodes);
// console.log("homologyNodes");
// console.log(homologyNodes);
// test
nodes.forEach(function(node) {
if (node.source && node.target) {
node.type = node.source.type = "target-source";
node.target.type = "source-target";
} else if (node.source) {
node.type = node.source.type = "source";
} else if (node.target) {
node.type = node.target.type = "target";
} else {
node.homologs = [{node: node}];
node.type = "source";
}
});
//console.log(nodes);
//var
// Nest nodes by type, for computing the rank.
var nodesByType = d3.nest()
.key(function(d) { return d.type; })
.sortKeys(d3.ascending)
.entries(nodes);
// Duplicate the target-source axis as source-target.
// nodesByType.push({key: "source-target", values: nodesByType[2].values});
// Compute the rank for each type, with padding between packages.
nodesByType.forEach(function(type) {
var lastName = type.values[0].packageName, count = 0;
type.values.forEach(function(d, i) {
if (d.packageName != lastName) lastName = d.packageName, count += 2;
d.index = count++;
});
type.count = count - 1;
});
var specieslookup = {};
var species_counter = 0;
homologyNodes.forEach(function(d) {
//console.log(d.name);
if( specieslookup[d.name] === undefined ) {
// console.log("not found yet");
specieslookup[d.name] = species_counter++;
d.index = specieslookup[d.name];
console.log(d.index);
}else{
// console.log("found!");
d.index = specieslookup[d.name];
}
//console.log(d.index);
});
console.log(specieslookup);
// dsfsdf
/* svg.selectAll(".axis")
.data(nodesByType)
.enter().append("line")
.attr("class", "axis")
.attr("transform", function(d) { return "rotate(" + degrees(angle(d.key)) + ")"; })
.attr("x1", radius(-2))
.attr("x2", function(d) { return radius(d.count + 2); });
*/
var lineFunction = d3.svg.line()
.x(function(d) { return d.x; })
.y(function(d) { return d.y; })
.interpolate("basis");
console.log(links.length+" links found");
console.log(links);
// dffdf
// Draw the links.
svg.append("g")
.attr("class", "links")
.selectAll(".link")
.data(links)
.enter().append("path")
.attr("class", "link")
.attr("d", function(d){
//console.log(d.source);
//console.log(d.target);
var source_name = d.source.name;
var target_name = d.target.name;
var source_index = d.source.index;
var target_index = specieslookup[d.target.name];
console.log("source: "+source_name+" "+source_index+" and target: "+target_name+" "+target_index);
var source_x = 0 ;
var source_y = 50+ (source_index*7);
var target_x = 700 ;
var target_y = 50 + (target_index*20);
console.log(""+d.source.name+" has "+source_x+"y"+source_y+"x"+target_x+"y"+target_y);
var lineData = [ { "x": source_x, "y": source_y}, { "x": target_x, "y": target_y}];
return lineFunction(lineData);
//return "M10,25L10,75L60,75L10,25";
}
)
.on("mouseover", linkMouseover)
.on("mouseout", mouseout);
//looool
var silent_counter=0;
// Draw gene nodes
var all_gene_nodes = svg.append("g")
.attr("class", "nodes")
.selectAll(".node")
.data(geneNodes)
.enter().append("g")
.attr("class", "node")
.style("fill", function(d) {
console.log("color: for "+d.name+" and package: "+d.packageName+" is "+color(d.packageName));
return color(d.packageName);
})
.on("mouseover", nodeMouseover)
.on("mouseout", mouseout);
/* var all_gene_circles = all_gene_nodes.selectAll("circle")
.data(function(d) { return d.homologs; })
.enter().append("circle")
.attr("cy", function(d,i) {
console.log("node index : "+i+" "+(40 + (silent_counter++ * 10)));
return 40 + (silent_counter++ * 10);
})
.attr("cx", function(d){
if(d.type = "source"){return 0;}
else{ return 30;}
})
.attr("r", 4);
*/
var all_gene_circles = all_gene_nodes.append("svg:image")
//.attr("y", -10)
.attr("y", function(d,i) {
console.log("node index : "+i+" "+(40 + (silent_counter++ * 10)));
return 30 + (silent_counter++ * gene_distance);
})
.attr("x", function(d){
if(d.type = "source"){return 0;}
else{ return 30;}
})
.attr("text-anchor", function(d){ return "end";})
.attr("width", 12).attr("height", 12)
//.attr("xlink:href", function(d) { return d.children == null? image_path+"/thumb_"+d.taxon+".png" : ""; });
.attr("xlink:href", function(d) { return "../data/MB_dna.gif"; });
all_gene_nodes.insert("svg:text")
.attr("x", -8)
.attr("y", function(d,i){ return 55 + (i * gene_distance*2);})
//.attr("class","innerNode_label")
.attr("font-size", "15")
.attr("text-anchor", function(d){ return "end";})
.text(function(d) { return d.name;});
silent_counter = 0;
// draw rank nodes
var all_rank_nodes = svg.append("g")
.attr("class", "nodes")
.selectAll(".node")
.data(rankNodes)
.enter().append("g")
.attr("class", "node")
.style("fill", function(d) {
console.log("color: for "+d.name+" and package: "+d.rank+" is "+color(d.rank));
return color(d.rank);
})
.on("mouseover", nodeMouseover)
.on("mouseout", mouseout);
var all_rank_circles = all_rank_nodes
.append("circle")
.attr("cy", function(d,i) {
console.log("node index : "+i+" "+(40 + (silent_counter++ * rank_distance)));
return 40 + (silent_counter++ * 10);
})
.attr("cx", function(d){
return rank_x;
})
.attr("r", 4);
all_rank_nodes.insert("svg:text")
.attr("x", rank_x + 10)
.attr("y", function(d,i){ return 55 + (i * rank_distance * 2);})
//.attr("class","innerNode_label")
.attr("font-size", "15")
.attr("text-anchor", function(d){ return "start";})
.text(function(d) { return d.rank;});
silent_counter = 0;
var all_species_nodes = svg.append("g")
.attr("class", "nodes")
.selectAll(".node")
.data(speciesNodes)
.enter().append("g")
.attr("class", "node")
.style("fill", function(d) {
console.log("color: for "+d.name+" and package: "+d.rank+" is "+color(d.rank));
return color(d.rank);
})
.on("mouseover", nodeMouseover)
.on("mouseout", mouseout);
var all_species_circles = all_species_nodes.append("svg:image")
//.attr("y", -10)
.attr("y", function(d,i) {
console.log("node index : "+i+" "+(40 + (silent_counter++ * species_distance)));
return 30 + (silent_counter++ * 10);
})
.attr("x", function(d){
if(d.type = "source"){return species_x;}
else{ return 30;}
})
.attr("text-anchor", function(d){ return "end";})
.attr("width", 15).attr("height", 15)
//.attr("xlink:href", function(d) { return d.children == null? image_path+"/thumb_"+d.taxon+".png" : ""; });
.attr("xlink:href", function(d) { return image_path+"/thumb_"+d.taxon+".png"; });
all_species_nodes.insert("svg:text")
.attr("x", species_x + 10)
.attr("y", function(d,i){ return 55 + (i * species_distance * 2);})
//.attr("class","innerNode_label")
.attr("font-size", "15")
.attr("text-anchor", function(d){ return "start";})
.text(function(d) { return d.taxon;});
/*var all_homology_nodes = svg.append("g")
.attr("class", "nodes")
.selectAll(".node")
.data(homologyNodes)
.enter().append("g")
.attr("class", "node")
.style("fill", function(d) {
console.log("color: for "+d.name+" and package: "+d.rank+" is "+color(d.rank));
return color(d.rank);
})
.on("mouseover", nodeMouseover)
.on("mouseout", mouseout);
var all_homology_circles = all_homology_nodes.append("svg:image")
//.attr("y", -10)
.attr("y", function(d,i) {
console.log("node index : "+i+" "+(40 + (silent_counter++ * homologs_distance)));
return 30 + (silent_counter++ * homologs_distance);
})
.attr("x", function(d){
if(d.type = "source"){return homologs_x;}
else{ return 30;}
})
.attr("text-anchor", function(d){ return "end";})
.attr("width", 15).attr("height", 15)
//.attr("xlink:href", function(d) { return d.children == null? image_path+"/thumb_"+d.taxon+".png" : ""; });
.attr("xlink:href", function(d) { return "../data/MB_dna.gif"; });
all_species_nodes.insert("svg:text")
.attr("x", homologs_x + 10)
.attr("y", function(d,i){ return 55 + (i * homologs_distance * 2);})
//.attr("class","innerNode_label")
.attr("font-size", "15")
.attr("text-anchor", function(d){ return "start";})
.text(function(d) { return d.name;});
*/
// Initialize the info display.
var info = d3.select("#info")
.text(defaultInfo = "Showing " + formatNumber(links.length) + " homologies for " + formatNumber(nodes.length) + " genes.");
/*
// test
// Construct an index by node name.
nodes.forEach(function(d) {
d.connectors = [];
d.packageName = d.name.split(".")[1];
nodesByName[d.name] = d;
});
// Convert the import lists into links with sources and targets.
nodes.forEach(function(source) {
source.imports.forEach(function(targetName) {
var target = nodesByName[targetName];
if (!source.source) source.connectors.push(source.source = {node: source, degree: 0});
if (!target.target) target.connectors.push(target.target = {node: target, degree: 0});
links.push({source: source.source, target: target.target});
});
});
// Determine the type of each node, based on incoming and outgoing links.
nodes.forEach(function(node) {
if (node.source && node.target) {
// node.type = node.source.type = "target-source";
node.target.type = "source-target";
} else if (node.source) {
node.type = node.source.type = "source";
} else if (node.target) {
node.type = node.target.type = "target";
} else {
node.connectors = [{node: node}];
node.type = "source";
}
});
// Normally, Hive Plots sort nodes by degree along each axis. However, since
// this example visualizes a package hierarchy, we get more interesting
// results if we group nodes by package. We don't need to sort explicitly
// because the data file is already sorted by class name.
// Nest nodes by type, for computing the rank.
var nodesByType = d3.nest()
.key(function(d) { return d.type; })
.sortKeys(d3.ascending)
.entries(nodes);
// Duplicate the target-source axis as source-target.
nodesByType.push({key: "source-target", values: nodesByType[2].values});
// Compute the rank for each type, with padding between packages.
nodesByType.forEach(function(type) {
var lastName = type.values[0].packageName, count = 0;
type.values.forEach(function(d, i) {
if (d.packageName != lastName) lastName = d.packageName, count += 2;
d.index = count++;
});
type.count = count - 1;
});
// Set the radius domain.
radius.domain(d3.extent(nodes, function(d) { return d.index; }));
// Draw the axes.
svg.selectAll(".axis")
.data(nodesByType)
.enter().append("line")
.attr("class", "axis")
.attr("transform", function(d) { return "rotate(" + degrees(angle(d.key)) + ")"; })
.attr("x1", radius(-2))
.attr("x2", function(d) { return radius(d.count + 2); });
// Draw the links.
svg.append("g")
.attr("class", "links")
.selectAll(".link")
.data(links)
.enter().append("path")
.attr("class", "link")
.attr("d", link()
.angle(function(d) { return angle(d.type); })
.radius(function(d) { return radius(d.node.index); }))
.on("mouseover", linkMouseover)
.on("mouseout", mouseout);
// Draw the nodes. Note that each node can have up to two connectors,
// representing the source (outgoing) and target (incoming) links.
svg.append("g")
.attr("class", "nodes")
.selectAll(".node")
.data(nodes)
.enter().append("g")
.attr("class", "node")
.style("fill", function(d) { return color(d.packageName); })
.selectAll("circle")
.data(function(d) { return d.connectors; })
.enter().append("circle")
.attr("transform", function(d) {
console.log("rotate "+d.type+"(" + degrees(angle(d.type)) + ")");
return "rotate(" + degrees(angle(d.type)) + ")"; })
.attr("cx", function(d) {
console.log("node index : "+d.node.index+" "+radius(d.node.index));
return radius(d.node.index);
return radius(d.node.index); })
.attr("r", 4)
.on("mouseover", nodeMouseover)
.on("mouseout", mouseout);
*/
// Highlight the link and connected nodes on mouseover.
function linkMouseover(d) {
//console.log(d);
console.log(d.source.name);
svg.selectAll(".link").classed("active", function(p) {
//console.log(d.name+" == "+p.name);
//console.log(p);
//console.log(p.source.name);
//console.log("");
return p.source.name === d.source.name;
});
svg.selectAll(".node circle").classed("active", function(p) { return p === d.source || p === d.target; });
console.log(d.source.name + " → " + d.target.name);
info.text(d.source.name + " → " + d.target.name);
}
// Highlight the node and connected links on mouseover.
function nodeMouseover(d) {
var all_nodes = svg.selectAll(".link").classed("active", function(p) { return p.source.name === d.name || p.target.name === d.name; });
d3.select(this).classed("active", true);
info.text(d.name);
}
// Clear any highlighted nodes or links.
function mouseout() {
svg.selectAll(".active").classed("active", false);
info.text(defaultInfo);
}
});
// A shape generator for Hive links, based on a source and a target.
// The source and target are defined in polar coordinates (angle and radius).
// Ratio links can also be drawn by using a startRadius and endRadius.
// This class is modeled after d3.svg.chord.
function link() {
var source = function(d) { return d.source; },
target = function(d) { return d.target; },
angle = function(d) { return d.angle; },
startRadius = function(d) { return d.radius; },
endRadius = startRadius,
arcOffset = -Math.PI / 2;
console.log("now we are in link with "+source+" and "+target+"");
function link(d, i) {
var s = node(source, this, d, i),
t = node(target, this, d, i),
x;
if (t.a < s.a) x = t, t = s, s = x;
if (t.a - s.a > Math.PI) s.a += 2 * Math.PI;
var a1 = s.a + (t.a - s.a) / 3,
a2 = t.a - (t.a - s.a) / 3;
return s.r0 - s.r1 || t.r0 - t.r1
? "M" + Math.cos(s.a) * s.r0 + "," + Math.sin(s.a) * s.r0
+ "L" + Math.cos(s.a) * s.r1 + "," + Math.sin(s.a) * s.r1
+ "C" + Math.cos(a1) * s.r1 + "," + Math.sin(a1) * s.r1
+ " " + Math.cos(a2) * t.r1 + "," + Math.sin(a2) * t.r1
+ " " + Math.cos(t.a) * t.r1 + "," + Math.sin(t.a) * t.r1
+ "L" + Math.cos(t.a) * t.r0 + "," + Math.sin(t.a) * t.r0
+ "C" + Math.cos(a2) * t.r0 + "," + Math.sin(a2) * t.r0
+ " " + Math.cos(a1) * s.r0 + "," + Math.sin(a1) * s.r0
+ " " + Math.cos(s.a) * s.r0 + "," + Math.sin(s.a) * s.r0
: "M" + Math.cos(s.a) * s.r0 + "," + Math.sin(s.a) * s.r0
+ "C" + Math.cos(a1) * s.r1 + "," + Math.sin(a1) * s.r1
+ " " + Math.cos(a2) * t.r1 + "," + Math.sin(a2) * t.r1
+ " " + Math.cos(t.a) * t.r1 + "," + Math.sin(t.a) * t.r1;
}
function node(method, thiz, d, i) {
var node = method.call(thiz, d, i),
a = +(typeof angle === "function" ? angle.call(thiz, node, i) : angle) + arcOffset,
r0 = +(typeof startRadius === "function" ? startRadius.call(thiz, node, i) : startRadius),
r1 = (startRadius === endRadius ? r0 : +(typeof endRadius === "function" ? endRadius.call(thiz, node, i) : endRadius));
return {r0: r0, r1: r1, a: a};
}
link.source = function(_) {
if (!arguments.length) return source;
source = _;
return link;
};
link.target = function(_) {
if (!arguments.length) return target;
target = _;
return link;
};
link.angle = function(_) {
if (!arguments.length) return angle;
angle = _;
return link;
};
link.radius = function(_) {
if (!arguments.length) return startRadius;
startRadius = endRadius = _;
return link;
};
link.startRadius = function(_) {
if (!arguments.length) return startRadius;
startRadius = _;
return link;
};
link.endRadius = function(_) {
if (!arguments.length) return endRadius;
endRadius = _;
return link;
};
return link;
}
function degrees(radians) {
return radians / Math.PI * 180 - 90;
}
</script>