| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402 | /*  (agr@ncgr.org : this is a significantly modified version of  d3.phylogram.js....  retaining attribution/copyright per below)  d3.phylogram.js http://bl.ocks.org/kueda/1036776  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.  Copyright (c) 2013, Ken-ichi Ueda  All rights reserved.  Redistribution and use in source and binary forms, with or without  modification, are permitted provided that the following conditions are met:  Redistributions of source code must retain the above copyright notice, this  list of conditions and the following disclaimer. Redistributions in binary  form must reproduce the above copyright notice, this list of conditions and  the following disclaimer in the documentation and/or other materials  provided with the distribution.  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE  POSSIBILITY OF SUCH DAMAGE.  DOCUEMENTATION  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.      fill        Function for generating fill color for leaf nodes.      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 "children"      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.    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  }  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.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 fill = options.fill || function(d) {      return 'cyan';    };    var size = options.size || function(d) {      return 6;    }    var nodeMouseOver = options.nodeMouseOver || function(d) {};    var nodeMouseOut  = options.nodeMouseOut  || function(d) {};    var nodeMouseDown = options.nodeMouseDown || function(d) {};        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.children;      });    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);        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', '9px')          .attr('fill', 'grey')          .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", "#aaa")        .attr("stroke-width", "4px");            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 + ")"; })     // style the root node     vis.selectAll('g.root.node')      .append('svg:circle')      .on('click', nodeMouseDown)      .on('mouseover', nodeMouseOver)      .on('mouseout', nodeMouseOut)      .attr("r", size)      .attr('fill', 'dimgrey')      .attr('stroke', 'black')      .attr('stroke-width', '2px');    // style the leaf nodes and add js event handlers    vis.selectAll('g.leaf.node')      .on('click', nodeMouseDown)      .on('mouseover', nodeMouseOver)      .on('mouseout', nodeMouseOut)      .append("svg:circle")      .attr("r", size)      .attr('stroke', 'dimgrey')      .attr('fill', fill)      .attr('stroke-width', '2px');    vis.selectAll('g.inner.node')      .on('click', nodeMouseDown)      .on('mouseover', nodeMouseOver)      .on('mouseout', nodeMouseOut)      .append("svg:circle")      .attr("r", size)      .attr('stroke', 'dimgrey')      .attr('stroke-width', '2px')      .attr('fill', 'white');        if (!options.skipLabels) {      vis.selectAll('g.inner.node')        .append("svg:text")          .attr("dx", -6)          .attr("dy", -6)          .attr("text-anchor", 'end')          .attr('font-size', '9px')          .attr('fill', 'black')        //.text(function(d) { return d.length.toFixed(4); }); // hide length      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.name + ' (' + d.length.toFixed(4) + ')'; // hide length          return d.name;         });    }    return {tree: tree, vis: vis}  }    d3.phylogram.buildRadial = function(selector, nodes, options) {    options = options || {};        var fill = options.fill || function(d) {      return 'cyan';    };    var size = options.size || function(d) {      return 6;    }    var nodeMouseOver = options.nodeMouseOver || function(d) {};    var nodeMouseOut = options.nodeMouseOut || function(d) {};    var nodeMouseDown = options.nodeMouseDown || function(d) {};        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.children;      })      .separation(function(a, b) { return (a.parent == b.parent ? 1 : 2) / a.depth; });    var phylogram = d3.phylogram.build(selector, nodes, {      vis: vis,      tree: tree,      fill : fill,      size: size,      nodeMouseOver : nodeMouseOver,      nodeMouseOut : nodeMouseOut,      nodeMouseDown : nodeMouseDown,      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.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}  }}());
 |