Skip to content

Instantly share code, notes, and snippets.

@kueda
Last active May 8, 2024 17:33
Show Gist options
  • Save kueda/1036776 to your computer and use it in GitHub Desktop.
Save kueda/1036776 to your computer and use it in GitHub Desktop.

Revisions

  1. kueda revised this gist Nov 27, 2013. 3 changed files with 134 additions and 5 deletions.
    33 changes: 30 additions & 3 deletions d3.phylogram.js
    Original file line number Diff line number Diff line change
    @@ -4,6 +4,33 @@
    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:
    @@ -156,7 +183,7 @@ if (!d3) { throw "d3 wasn't included!"};
    }
    }
    visitPreOrder(nodes[0], function(node) {
    node.rootDist = (node.parent ? node.parent.rootDist : 0) + (node.data.length || 0)
    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()
    @@ -255,7 +282,7 @@ if (!d3) { throw "d3 wasn't included!"};
    .attr("text-anchor", 'end')
    .attr('font-size', '8px')
    .attr('fill', '#ccc')
    .text(function(d) { return d.data.length; });
    .text(function(d) { return d.length; });

    vis.selectAll('g.leaf.node').append("svg:text")
    .attr("dx", 8)
    @@ -264,7 +291,7 @@ if (!d3) { throw "d3 wasn't included!"};
    .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+')'; });
    .text(function(d) { return d.name + ' ('+d.length+')'; });
    }

    return {tree: tree, vis: vis}
    4 changes: 2 additions & 2 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -3,8 +3,8 @@
    <head>
    <meta content='text/html;charset=UTF-8' http-equiv='content-type'>
    <title>Right-angle phylograms and dendrograms with d3</title>
    <script src="https://raw.github.com/mbostock/d3/master/d3.v2.js" type="text/javascript"></script>
    <script src="https://raw.github.com/jasondavies/newick.js/master/src/newick.js" type="text/javascript"></script>
    <script src="http://d3js.org/d3.v3.min.js" type="text/javascript"></script>
    <script src="newick.js" type="text/javascript"></script>
    <script src="d3.phylogram.js" type="text/javascript"></script>
    <script>
    function load() {
    102 changes: 102 additions & 0 deletions newick.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,102 @@
    /**
    * Newick format parser in JavaScript.
    *
    * Copyright (c) Jason Davies 2010.
    *
    * Permission is hereby granted, free of charge, to any person obtaining a copy
    * of this software and associated documentation files (the "Software"), to deal
    * in the Software without restriction, including without limitation the rights
    * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    * copies of the Software, and to permit persons to whom the Software is
    * furnished to do so, subject to the following conditions:
    *
    * The above copyright notice and this permission notice shall be included in
    * all copies or substantial portions of the Software.
    *
    * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    * THE SOFTWARE.
    *
    * Example tree (from http://en.wikipedia.org/wiki/Newick_format):
    *
    * +--0.1--A
    * F-----0.2-----B +-------0.3----C
    * +------------------0.5-----E
    * +---------0.4------D
    *
    * Newick format:
    * (A:0.1,B:0.2,(C:0.3,D:0.4)E:0.5)F;
    *
    * Converted to JSON:
    * {
    * name: "F",
    * branchset: [
    * {name: "A", length: 0.1},
    * {name: "B", length: 0.2},
    * {
    * name: "E",
    * length: 0.5,
    * branchset: [
    * {name: "C", length: 0.3},
    * {name: "D", length: 0.4}
    * ]
    * }
    * ]
    * }
    *
    * Converted to JSON, but with no names or lengths:
    * {
    * branchset: [
    * {}, {}, {
    * branchset: [{}, {}]
    * }
    * ]
    * }
    */
    (function(exports) {
    exports.parse = function(s) {
    var ancestors = [];
    var tree = {};
    var tokens = s.split(/\s*(;|\(|\)|,|:)\s*/);
    for (var i=0; i<tokens.length; i++) {
    var token = tokens[i];
    switch (token) {
    case '(': // new branchset
    var subtree = {};
    tree.branchset = [subtree];
    ancestors.push(tree);
    tree = subtree;
    break;
    case ',': // another branch
    var subtree = {};
    ancestors[ancestors.length-1].branchset.push(subtree);
    tree = subtree;
    break;
    case ')': // optional name next
    tree = ancestors.pop();
    break;
    case ':': // optional length next
    break;
    default:
    var x = tokens[i-1];
    if (x == ')' || x == '(' || x == ',') {
    tree.name = token;
    } else if (x == ':') {
    tree.length = parseFloat(token);
    }
    }
    }
    return tree;
    };
    })(
    // exports will be set in any commonjs platform; use it if it's available
    typeof exports !== "undefined" ?
    exports :
    // otherwise construct a name space. outside the anonymous function,
    // "this" will always be "window" in a browser, even in strict mode.
    this.Newick = {}
    );
  2. kueda revised this gist Mar 14, 2012. 1 changed file with 1 addition and 2 deletions.
    3 changes: 1 addition & 2 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -3,8 +3,7 @@
    <head>
    <meta content='text/html;charset=UTF-8' http-equiv='content-type'>
    <title>Right-angle phylograms and dendrograms with d3</title>
    <script src="https://raw.github.com/mbostock/d3/master/d3.js" type="text/javascript"></script>
    <script src="https://raw.github.com/mbostock/d3/master/d3.layout.js" type="text/javascript"></script>
    <script src="https://raw.github.com/mbostock/d3/master/d3.v2.js" type="text/javascript"></script>
    <script src="https://raw.github.com/jasondavies/newick.js/master/src/newick.js" type="text/javascript"></script>
    <script src="d3.phylogram.js" type="text/javascript"></script>
    <script>
  3. kueda revised this gist Jun 21, 2011. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion readme.mkd
    Original file line number Diff line number Diff line change
    @@ -1 +1 @@
    Demonstration of two common tree visualizations using [d3](http://mbostock.github.com/d3) and [newick.js](https://github.com/jasondavies/newick.js). I hadn't found any examples of these kinds of right-angle trees so I figured I'd share. Input data is a Newick-formatted guide tree from a clustalw multiple sequence alignment on some cytochrome b sequences from several North American snakes. And it turns out the creator of newick.js also has an implementation of this kind of radial tree: check out http://www.jasondavies.com/tree-of-life/
    Demonstration of two common tree visualizations using [d3](http://mbostock.github.com/d3) and [newick.js](https://github.com/jasondavies/newick.js). I hadn't found any examples of these kinds of right-angle trees so I figured I'd share. Input data is a Newick-formatted guide tree from a clustalw multiple sequence alignment on some cytochrome b sequences from several North American snakes. And it turns out the creator of newick.js also has an implementation of this kind of radial tree: check out [http://www.jasondavies.com/tree-of-life/](http://www.jasondavies.com/tree-of-life/)
  4. kueda revised this gist Jun 21, 2011. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion readme.mkd
    Original file line number Diff line number Diff line change
    @@ -1 +1 @@
    Demonstration of two common tree visualizations using [d3](http://mbostock.github.com/d3) and [newick.js](https://github.com/jasondavies/newick.js). I hadn't found any examples of these kinds of right-angle trees so I figured I'd share. Input data is a Newick-formatted guide tree from a clustalw multiple sequence alignment on some cytochrome b sequences from several North American snakes.
    Demonstration of two common tree visualizations using [d3](http://mbostock.github.com/d3) and [newick.js](https://github.com/jasondavies/newick.js). I hadn't found any examples of these kinds of right-angle trees so I figured I'd share. Input data is a Newick-formatted guide tree from a clustalw multiple sequence alignment on some cytochrome b sequences from several North American snakes. And it turns out the creator of newick.js also has an implementation of this kind of radial tree: check out http://www.jasondavies.com/tree-of-life/
  5. kueda revised this gist Jun 20, 2011. 2 changed files with 1 addition and 1 deletion.
    1 change: 0 additions & 1 deletion README
    Original file line number Diff line number Diff line change
    @@ -1 +0,0 @@
    Demonstration of two common tree visualizations using <a href="http://mbostock.github.com/d3/">d3</a>, <a href="https://github.com/jasondavies/newick.js">newick.js</a>. I hadn't found any examples of these kinds of right-angle trees so I figured I'd share. Input data is a Newick-formatted guide tree from a clustalw multiple sequence alignment on some cytochrome B sequences from several North American snakes.
    1 change: 1 addition & 0 deletions readme.mkd
    Original file line number Diff line number Diff line change
    @@ -0,0 +1 @@
    Demonstration of two common tree visualizations using [d3](http://mbostock.github.com/d3) and [newick.js](https://github.com/jasondavies/newick.js). I hadn't found any examples of these kinds of right-angle trees so I figured I'd share. Input data is a Newick-formatted guide tree from a clustalw multiple sequence alignment on some cytochrome b sequences from several North American snakes.
  6. kueda revised this gist Jun 20, 2011. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions README
    Original file line number Diff line number Diff line change
    @@ -0,0 +1 @@
    Demonstration of two common tree visualizations using <a href="http://mbostock.github.com/d3/">d3</a>, <a href="https://github.com/jasondavies/newick.js">newick.js</a>. I hadn't found any examples of these kinds of right-angle trees so I figured I'd share. Input data is a Newick-formatted guide tree from a clustalw multiple sequence alignment on some cytochrome B sequences from several North American snakes.
  7. kueda revised this gist Jun 20, 2011. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion index.html
    Original file line number Diff line number Diff line change
    @@ -22,7 +22,7 @@
    buildNewickNodes(newick)

    d3.phylogram.buildRadial('#radialtree', newick, {
    width: 300,
    width: 400,
    skipLabels: true
    })

  8. kueda revised this gist Jun 20, 2011. 2 changed files with 9 additions and 4 deletions.
    2 changes: 1 addition & 1 deletion d3.phylogram.js
    Original file line number Diff line number Diff line change
    @@ -274,7 +274,7 @@ if (!d3) { throw "d3 wasn't included!"};
    options = options || {}
    var w = options.width || d3.select(selector).style('width') || d3.select(selector).attr('width'),
    r = w / 2,
    labelWidth = options.skipLabels ? 0 : options.labelWidth || 120;
    labelWidth = options.skipLabels ? 10 : options.labelWidth || 120;

    var vis = d3.select(selector).append("svg:svg")
    .attr("width", r * 2)
    11 changes: 8 additions & 3 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -22,15 +22,20 @@
    buildNewickNodes(newick)

    d3.phylogram.buildRadial('#radialtree', newick, {
    width: 250,
    width: 300,
    skipLabels: true
    })

    d3.phylogram.build('#phylogram', newick, {
    width: 250,
    height: 250
    width: 300,
    height: 400
    });
    }
    </script>
    <style type="text/css" media="screen">
    body { font-family: "Helvetica Neue", Helvetica, sans-serif; }
    td { vertical-align: top; }
    </style>
    </head>
    <body onload="load()">
    <table>
  9. kueda revised this gist Jun 20, 2011. 2 changed files with 18 additions and 9 deletions.
    5 changes: 3 additions & 2 deletions d3.phylogram.js
    Original file line number Diff line number Diff line change
    @@ -273,7 +273,8 @@ if (!d3) { throw "d3 wasn't included!"};
    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;
    r = w / 2,
    labelWidth = options.skipLabels ? 0 : options.labelWidth || 120;

    var vis = d3.select(selector).append("svg:svg")
    .attr("width", r * 2)
    @@ -282,7 +283,7 @@ if (!d3) { throw "d3 wasn't included!"};
    .attr("transform", "translate(" + r + "," + r + ")");

    var tree = d3.layout.tree()
    .size([360, r - 120])
    .size([360, r - labelWidth])
    .sort(function(node) { return node.children ? node.children.length : -1; })
    .children(options.children || function(node) {
    return node.branchset
    22 changes: 15 additions & 7 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -22,20 +22,28 @@
    buildNewickNodes(newick)

    d3.phylogram.buildRadial('#radialtree', newick, {
    width: 250, skipLabels: true
    width: 250,
    skipLabels: true
    })
    d3.phylogram.build('#phylogram', newick, {
    width: 250,
    width: 250,
    height: 250
    });
    }
    </script>
    </head>
    <body onload="load()">
    <h2>Circular Dendrogram</h2>
    <div id='radialtree'></div>

    <h2>Phylogram</h2>
    <div id='phylogram'></div>
    <table>
    <tr>
    <td>
    <h2>Circular Dendrogram</h2>
    <div id='radialtree'></div>
    </td>
    <td>
    <h2>Phylogram</h2>
    <div id='phylogram'></div>
    </td>
    </tr>
    </table>
    </body>
    </html>
  10. kueda revised this gist Jun 20, 2011. 2 changed files with 39 additions and 34 deletions.
    71 changes: 38 additions & 33 deletions d3.phylogram.js
    Original file line number Diff line number Diff line change
    @@ -246,24 +246,26 @@ if (!d3) { throw "d3 wasn't included!"};
    .attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; })

    d3.phylogram.styleTreeNodes(vis)

    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; });

    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+')'; });

    if (!options.skipLabels) {
    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; });

    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}
    }
    @@ -292,25 +294,28 @@ if (!d3) { throw "d3 wasn't included!"};
    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 + ")"; })

    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)"; });

    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}
    }
    2 changes: 1 addition & 1 deletion index.html
    Original file line number Diff line number Diff line change
    @@ -22,7 +22,7 @@
    buildNewickNodes(newick)

    d3.phylogram.buildRadial('#radialtree', newick, {
    width: 250
    width: 250, skipLabels: true
    })
    d3.phylogram.build('#phylogram', newick, {
    width: 250,
  11. kueda revised this gist Jun 20, 2011. 1 changed file with 3 additions and 3 deletions.
    6 changes: 3 additions & 3 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -22,11 +22,11 @@
    buildNewickNodes(newick)

    d3.phylogram.buildRadial('#radialtree', newick, {
    width: parseInt(d3.select('#radialtree').style('width')) * 0.75,
    width: 250
    })
    d3.phylogram.build('#phylogram', newick, {
    width: parseInt(d3.select('#phylogram').style('width')) * 0.75,
    height: newickNodes.length * 20
    width: 250,
    height: 250
    });
    }
    </script>
  12. kueda revised this gist Jun 20, 2011. 1 changed file with 19 additions and 23 deletions.
    42 changes: 19 additions & 23 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -8,34 +8,30 @@
    <script src="https://raw.github.com/jasondavies/newick.js/master/src/newick.js" type="text/javascript"></script>
    <script src="d3.phylogram.js" type="text/javascript"></script>
    <script>
    //<![CDATA[
    $(document).ready(function() {
    var newick = Newick.parse("(((Crotalus_oreganus_oreganus_cytochrome_b:0.00800,Crotalus_horridus_cytochrome_b:0.05866):0.04732,(Thamnophis_elegans_terrestris_cytochrome_b:0.00366,Thamnophis_atratus_cytochrome_b:0.00172):0.06255):0.00555,(Pituophis_catenifer_vertebralis_cytochrome_b:0.00552,Lampropeltis_getula_cytochrome_b:0.02035):0.05762,((Diadophis_punctatus_cytochrome_b:0.06486,Contia_tenuis_cytochrome_b:0.05342):0.01037,Hypsiglena_torquata_cytochrome_b:0.05346):0.00779);")
    var newickNodes = []
    function buildNewickNodes(node, callback) {
    newickNodes.push(node)
    if (node.branchset) {
    for (var i=0; i < node.branchset.length; i++) {
    buildNewickNodes(node.branchset[i])
    }
    function load() {
    var newick = Newick.parse("(((Crotalus_oreganus_oreganus_cytochrome_b:0.00800,Crotalus_horridus_cytochrome_b:0.05866):0.04732,(Thamnophis_elegans_terrestris_cytochrome_b:0.00366,Thamnophis_atratus_cytochrome_b:0.00172):0.06255):0.00555,(Pituophis_catenifer_vertebralis_cytochrome_b:0.00552,Lampropeltis_getula_cytochrome_b:0.02035):0.05762,((Diadophis_punctatus_cytochrome_b:0.06486,Contia_tenuis_cytochrome_b:0.05342):0.01037,Hypsiglena_torquata_cytochrome_b:0.05346):0.00779);")
    var newickNodes = []
    function buildNewickNodes(node, callback) {
    newickNodes.push(node)
    if (node.branchset) {
    for (var i=0; i < node.branchset.length; i++) {
    buildNewickNodes(node.branchset[i])
    }
    }
    buildNewickNodes(newick)

    if ($('#radialtree').length > 0) {
    d3.phylogram.buildRadial('#radialtree', newick, {width: $('#radialtree').width()})
    }
    if ($('#radialtree').length > 0) {
    d3.phylogram.build('#phylogram', newick, {
    width: $('#phylogram').width() * 0.75,
    height: newickNodes.length * 20
    });
    }
    }
    buildNewickNodes(newick)

    d3.phylogram.buildRadial('#radialtree', newick, {
    width: parseInt(d3.select('#radialtree').style('width')) * 0.75,
    })
    //]]>
    d3.phylogram.build('#phylogram', newick, {
    width: parseInt(d3.select('#phylogram').style('width')) * 0.75,
    height: newickNodes.length * 20
    });
    }
    </script>
    </head>
    <body>
    <body onload="load()">
    <h2>Circular Dendrogram</h2>
    <div id='radialtree'></div>

  13. kueda created this gist Jun 20, 2011.
    317 changes: 317 additions & 0 deletions d3.phylogram.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,317 @@
    /*
    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.
    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);

    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", "#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 + ")"; })

    d3.phylogram.styleTreeNodes(vis)

    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; });

    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;

    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 - 120])
    .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,
    diagonal: d3.phylogram.radialRightAngleDiagonal()
    })
    vis.selectAll('g.node')
    .attr("transform", function(d) { return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")"; })

    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}
    }
    }());
    45 changes: 45 additions & 0 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,45 @@
    <!DOCTYPE html>
    <html lang='en' xml:lang='en' xmlns='http://www.w3.org/1999/xhtml'>
    <head>
    <meta content='text/html;charset=UTF-8' http-equiv='content-type'>
    <title>Right-angle phylograms and dendrograms with d3</title>
    <script src="https://raw.github.com/mbostock/d3/master/d3.js" type="text/javascript"></script>
    <script src="https://raw.github.com/mbostock/d3/master/d3.layout.js" type="text/javascript"></script>
    <script src="https://raw.github.com/jasondavies/newick.js/master/src/newick.js" type="text/javascript"></script>
    <script src="d3.phylogram.js" type="text/javascript"></script>
    <script>
    //<![CDATA[
    $(document).ready(function() {
    var newick = Newick.parse("(((Crotalus_oreganus_oreganus_cytochrome_b:0.00800,Crotalus_horridus_cytochrome_b:0.05866):0.04732,(Thamnophis_elegans_terrestris_cytochrome_b:0.00366,Thamnophis_atratus_cytochrome_b:0.00172):0.06255):0.00555,(Pituophis_catenifer_vertebralis_cytochrome_b:0.00552,Lampropeltis_getula_cytochrome_b:0.02035):0.05762,((Diadophis_punctatus_cytochrome_b:0.06486,Contia_tenuis_cytochrome_b:0.05342):0.01037,Hypsiglena_torquata_cytochrome_b:0.05346):0.00779);")
    var newickNodes = []
    function buildNewickNodes(node, callback) {
    newickNodes.push(node)
    if (node.branchset) {
    for (var i=0; i < node.branchset.length; i++) {
    buildNewickNodes(node.branchset[i])
    }
    }
    }
    buildNewickNodes(newick)

    if ($('#radialtree').length > 0) {
    d3.phylogram.buildRadial('#radialtree', newick, {width: $('#radialtree').width()})
    }
    if ($('#radialtree').length > 0) {
    d3.phylogram.build('#phylogram', newick, {
    width: $('#phylogram').width() * 0.75,
    height: newickNodes.length * 20
    });
    }
    })
    //]]>
    </script>
    </head>
    <body>
    <h2>Circular Dendrogram</h2>
    <div id='radialtree'></div>

    <h2>Phylogram</h2>
    <div id='phylogram'></div>
    </body>
    </html>