Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /*
- d3.phylogram.js
- Wrapper around a d3-based phylogram (tree where branch lengths are scaled)
- Also includes a radial dendrogram visualization (branch lengths not scaled)
- along with some helper methods for building angled-branch trees.
- d3.phylogram.build(selector, nodes, options)
- Creates a phylogram.
- Arguments:
- selector: selector of an element that will contain the SVG
- nodes: JS object of nodes
- Options:
- width
- Width of the vis, will attempt to set a default based on the width of
- the container.
- height
- Height of the vis, will attempt to set a default based on the height
- of the container.
- vis
- Pre-constructed d3 vis.
- tree
- Pre-constructed d3 tree layout.
- children
- Function for retrieving an array of children given a node. Default is
- to assume each node has an attribute called "branchset"
- diagonal
- Function that creates the d attribute for an svg:path. Defaults to a
- right-angle diagonal.
- skipTicks
- Skip the tick rule.
- skipBranchLengthScaling
- Make a dendrogram instead of a phylogram.
- skipTreeNodeStyle
- Don't draw circles at the root and leaf nodes
- skipInteriorBranchLengths
- Don't draw the interior branch lengths
- skipLabels
- Don't add labels at leaf nodes
- strokeWidth
- Size for stroke that draws the tree (e.g. "1px")
- strokeColor
- Color for stroke (e.g. "#dedede")
- d3.phylogram.buildRadial(selector, nodes, options)
- Creates a radial dendrogram.
- Options: same as build, but without diagonal, skipTicks, and
- skipBranchLengthScaling
- d3.phylogram.rightAngleDiagonal()
- Similar to d3.diagonal except it create an orthogonal crook instead of a
- smooth Bezier curve.
- d3.phylogram.radialRightAngleDiagonal()
- d3.phylogram.rightAngleDiagonal for radial layouts.
- */
- if (!d3) { throw "d3 wasn't included!"};
- (function() {
- d3.phylogram = {}
- d3.phylogram.rightAngleDiagonal = function() {
- var projection = function(d) { return [d.y, d.x]; }
- var path = function(pathData) {
- return "M" + pathData[0] + ' ' + pathData[1] + " " + pathData[2];
- }
- function diagonal(diagonalPath, i) {
- var source = diagonalPath.source,
- target = diagonalPath.target,
- midpointX = (source.x + target.x) / 2,
- midpointY = (source.y + target.y) / 2,
- pathData = [source, {x: target.x, y: source.y}, target];
- pathData = pathData.map(projection);
- return path(pathData)
- }
- diagonal.projection = function(x) {
- if (!arguments.length) return projection;
- projection = x;
- return diagonal;
- };
- diagonal.path = function(x) {
- if (!arguments.length) return path;
- path = x;
- return diagonal;
- };
- return diagonal;
- }
- d3.phylogram.radialRightAngleDiagonal = function() {
- return d3.phylogram.rightAngleDiagonal()
- .path(function(pathData) {
- var src = pathData[0],
- mid = pathData[1],
- dst = pathData[2],
- radius = Math.sqrt(src[0]*src[0] + src[1]*src[1]),
- srcAngle = d3.phylogram.coordinateToAngle(src, radius),
- midAngle = d3.phylogram.coordinateToAngle(mid, radius),
- clockwise = Math.abs(midAngle - srcAngle) > Math.PI ? midAngle <= srcAngle : midAngle > srcAngle,
- rotation = 0,
- largeArc = 0,
- sweep = clockwise ? 0 : 1;
- return 'M' + src + ' ' +
- "A" + [radius,radius] + ' ' + rotation + ' ' + largeArc+','+sweep + ' ' + mid +
- 'L' + dst;
- })
- .projection(function(d) {
- var r = d.y, a = (d.x - 90) / 180 * Math.PI;
- return [r * Math.cos(a), r * Math.sin(a)];
- })
- }
- // Convert XY and radius to angle of a circle centered at 0,0
- d3.phylogram.coordinateToAngle = function(coord, radius) {
- var wholeAngle = 2 * Math.PI,
- quarterAngle = wholeAngle / 4
- var coordQuad = coord[0] >= 0 ? (coord[1] >= 0 ? 1 : 2) : (coord[1] >= 0 ? 4 : 3),
- coordBaseAngle = Math.abs(Math.asin(coord[1] / radius))
- // Since this is just based on the angle of the right triangle formed
- // by the coordinate and the origin, each quad will have different
- // offsets
- switch (coordQuad) {
- case 1:
- coordAngle = quarterAngle - coordBaseAngle
- break
- case 2:
- coordAngle = quarterAngle + coordBaseAngle
- break
- case 3:
- coordAngle = 2*quarterAngle + quarterAngle - coordBaseAngle
- break
- case 4:
- coordAngle = 3*quarterAngle + coordBaseAngle
- }
- return coordAngle
- }
- d3.phylogram.styleTreeNodes = function(vis) {
- vis.selectAll('g.leaf.node')
- .append("svg:circle")
- .attr("r", 4.5)
- .attr('stroke', 'yellowGreen')
- .attr('fill', 'greenYellow')
- .attr('stroke-width', '2px');
- vis.selectAll('g.root.node')
- .append('svg:circle')
- .attr("r", 4.5)
- .attr('fill', 'steelblue')
- .attr('stroke', '#369')
- .attr('stroke-width', '2px');
- }
- function scaleBranchLengths(nodes, w) {
- // Visit all nodes and adjust y pos width distance metric
- var visitPreOrder = function(root, callback) {
- callback(root)
- if (root.children) {
- for (var i = root.children.length - 1; i >= 0; i--){
- visitPreOrder(root.children[i], callback)
- };
- }
- }
- visitPreOrder(nodes[0], function(node) {
- node.rootDist = (node.parent ? node.parent.rootDist : 0) + (node.data.length || 0)
- })
- var rootDists = nodes.map(function(n) { return n.rootDist; });
- var yscale = d3.scale.linear()
- .domain([0, d3.max(rootDists)])
- .range([0, w]);
- visitPreOrder(nodes[0], function(node) {
- node.y = yscale(node.rootDist)
- })
- return yscale
- }
- d3.phylogram.build = function(selector, nodes, options) {
- options = options || {}
- var w = options.width || d3.select(selector).style('width') || d3.select(selector).attr('width'),
- h = options.height || d3.select(selector).style('height') || d3.select(selector).attr('height'),
- w = parseInt(w),
- h = parseInt(h);
- var tree = options.tree || d3.layout.cluster()
- .size([h, w])
- .sort(function(node) { return node.children ? node.children.length : -1; })
- .children(options.children || function(node) {
- return node.branchset
- });
- var diagonal = options.diagonal || d3.phylogram.rightAngleDiagonal();
- var vis = options.vis || d3.select(selector).append("svg:svg")
- .attr("width", w + 300)
- .attr("height", h + 30)
- .append("svg:g")
- .attr("transform", "translate(20, 20)");
- var nodes = tree(nodes);
- var strokeWidth = options.strokeWidth || "4px";
- var strokeColor = options.strokeColor || "#aaa";
- if (options.skipBranchLengthScaling) {
- var yscale = d3.scale.linear()
- .domain([0, w])
- .range([0, w]);
- } else {
- var yscale = scaleBranchLengths(nodes, w)
- }
- if (!options.skipTicks) {
- vis.selectAll('line')
- .data(yscale.ticks(10))
- .enter().append('svg:line')
- .attr('y1', 0)
- .attr('y2', h)
- .attr('x1', yscale)
- .attr('x2', yscale)
- .attr("stroke", "#ddd");
- vis.selectAll("text.rule")
- .data(yscale.ticks(10))
- .enter().append("svg:text")
- .attr("class", "rule")
- .attr("x", yscale)
- .attr("y", 0)
- .attr("dy", -3)
- .attr("text-anchor", "middle")
- .attr('font-size', '8px')
- .attr('fill', '#ccc')
- .text(function(d) { return Math.round(d*100) / 100; });
- }
- var link = vis.selectAll("path.link")
- .data(tree.links(nodes))
- .enter().append("svg:path")
- .attr("class", "link")
- .attr("d", diagonal)
- .attr("fill", "none")
- .attr("stroke", strokeColor)
- .attr("stroke-width", strokeWidth);
- var node = vis.selectAll("g.node")
- .data(nodes)
- .enter().append("svg:g")
- .attr("class", function(n) {
- if (n.children) {
- if (n.depth == 0) {
- return "root node"
- } else {
- return "inner node"
- }
- } else {
- return "leaf node"
- }
- })
- .attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; })
- if (!options.skipTreeNodeStyle) {
- d3.phylogram.styleTreeNodes(vis)
- }
- if (!options.skipInteriorBranchLengths) {
- vis.selectAll('g.inner.node')
- .append("svg:text")
- .attr("dx", -6)
- .attr("dy", -6)
- .attr("text-anchor", 'end')
- .attr('font-size', '8px')
- .attr('fill', '#ccc')
- .text(function(d) { return d.data.length; });
- }
- if (!options.skipLabels) {
- vis.selectAll('g.leaf.node').append("svg:text")
- .attr("dx", 8)
- .attr("dy", 3)
- .attr("text-anchor", "start")
- .attr('font-family', 'Helvetica Neue, Helvetica, sans-serif')
- .attr('font-size', '10px')
- .attr('fill', 'black')
- .text(function(d) { return d.data.name + ' ('+d.data.length+')'; });
- }
- return {tree: tree, vis: vis}
- }
- d3.phylogram.buildRadial = function(selector, nodes, options) {
- options = options || {}
- var w = options.width || d3.select(selector).style('width') || d3.select(selector).attr('width'),
- r = w / 2,
- labelWidth = options.skipLabels ? 10 : options.labelWidth || 120;
- var vis = d3.select(selector).append("svg:svg")
- .attr("width", r * 2)
- .attr("height", r * 2)
- .append("svg:g")
- .attr("transform", "translate(" + r + "," + r + ")");
- var tree = d3.layout.tree()
- .size([360, r - labelWidth])
- .sort(function(node) { return node.children ? node.children.length : -1; })
- .children(options.children || function(node) {
- return node.branchset
- })
- .separation(function(a, b) { return (a.parent == b.parent ? 1 : 2) / a.depth; });
- var phylogram = d3.phylogram.build(selector, nodes, {
- vis: vis,
- tree: tree,
- skipBranchLengthScaling: true,
- skipTicks: true,
- skipLabels: options.skipLabels,
- diagonal: d3.phylogram.radialRightAngleDiagonal()
- })
- vis.selectAll('g.node')
- .attr("transform", function(d) { return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")"; })
- if (!options.skipLabels) {
- vis.selectAll('g.leaf.node text')
- .attr("dx", function(d) { return d.x < 180 ? 8 : -8; })
- .attr("dy", ".31em")
- .attr("text-anchor", function(d) { return d.x < 180 ? "start" : "end"; })
- .attr("transform", function(d) { return d.x < 180 ? null : "rotate(180)"; })
- .attr('font-family', 'Helvetica Neue, Helvetica, sans-serif')
- .attr('font-size', '10px')
- .attr('fill', 'black')
- .text(function(d) { return d.data.name; });
- vis.selectAll('g.inner.node text')
- .attr("dx", function(d) { return d.x < 180 ? -6 : 6; })
- .attr("text-anchor", function(d) { return d.x < 180 ? "end" : "start"; })
- .attr("transform", function(d) { return d.x < 180 ? null : "rotate(180)"; });
- }
- return {tree: tree, vis: vis}
- }
- }());
Add Comment
Please, Sign In to add comment