veg / phylotree.js

Interactive viewer of phylogenetic trees
http://phylotree.hyphy.org
MIT License
169 stars 71 forks source link

On click function for leaf nodes #226

Open Georgva opened 3 years ago

Georgva commented 3 years ago

Hi All, first of all thanks for providing this cool library!

I want to create phylogenetic trees based on the mutational profiles of single cells for the recreation of their genealogical order. But the biological stuff is actually not important ;)

I was able to adapt the code to my needs, which is basically the same as in http://phylotree.hyphy.org/, but additionally, I want to give an overview of the different mutations that were gained in every single cell (all cells are displayed as leaves).

To do this, I would like to add an on-click event for the nodes. For example, a list of mutations should show up for the specific node after clicking on it.

Is phylotree.js capable of this? Or are there only click features 'incident branch', 'path to root', 'reroot on this node' and 'hide this node'?

Please consider in your answer that I am a newbie in html and javascript.

My current code looks like this:

 <!DOCTYPE` html>
    <html lang='en'>

 <head>
  <meta charset="utf-8">

  <!--<link rel="stylesheet" href="https://netdna.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">-->
  <link rel="stylesheet" href="../../node_modules/bootstrap/dist/css/bootstrap.min.css">
  <!--<link rel="stylesheet" href="https://netdna.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap-theme.min.css">-->
  <link rel="stylesheet" href="../../node_modules/bootstrap/dist/css/bootstrap-theme.min.css">
  <!--<link href="https://veg.github.io/phylotree.js/phylotree.css" rel="stylesheet">-->
  <link href="https://veg.github.io/phylotree.js/phylotree.css" rel="stylesheet">
  <!--<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/default.min.css">-->
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/default.min.css">

 <!--<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/highlight.min.js"></script>-->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/highlight.min.js"></script>
 <!--<script src="https://code.jquery.com/jquery.js"></script>-->
  <script src="../../node_modules/jquery/dist/jquery.js"></script>
  <!--<script src="https://netdna.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>-->
  <script src="../../node_modules/bootstrap/dist/js/bootstrap.min.js"></script>
  <!--<script src="https://d3js.org/d3.v3.min.js"></script>-->
  <script src="https://d3js.org/d3.v3.min.js"></script>
  <!--<script src="https://veg.github.io/phylotree.js/phylotree.js"></script>-->
  <script src="https://veg.github.io/phylotree.js/phylotree.js"></script>
  <!--<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js" charset="utf-8"></script>-->
  <script src="../../node_modules/underscore/underscore-min.js" charset="utf-8"></script>

  <link href="http://netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css" rel="stylesheet">

  <style>

        .fa-rotate-45 {
          -webkit-transform: rotate(45deg);
          -moz-transform: rotate(45deg);
          -ms-transform: rotate(45deg);
          -o-transform: rotate(45deg);
          transform: rotate(45deg);
        }

        .fa-rotate-135 {
          -webkit-transform: rotate(135deg);
          -moz-transform: rotate(135deg);
          -ms-transform: rotate(135deg);
          -o-transform: rotate(135deg);
          transform: rotate(135deg);
        }

        @media (max-width: 1075px) { 
          .container {
            padding-top: 20px;
          }
        }

  </style>
</head>

<div class = 'container' id = "main_display">

  <div class = 'row'>
    <div class = 'col-md-12'>
        <div class="btn-toolbar" role="toolbar">
          <div class="btn-group">
            <button type="button" class="btn btn-default btn-sm" data-direction = 'vertical' data-amount = '1' title = "Expand vertical spacing">
                <i class="fa fa-arrows-v" ></i>
            </button>
             <button type="button" class="btn btn-default btn-sm" data-direction = 'vertical' data-amount = '-1' title = "Compress vertical spacing">
                <i class="fa  fa-compress fa-rotate-135" ></i>
            </button>
            <button type="button" class="btn btn-default btn-sm" data-direction = 'horizontal' data-amount = '1' title = "Expand horizonal spacing">
                <i class="fa fa-arrows-h" ></i>
            </button>
             <button type="button" class="btn btn-default btn-sm" data-direction = 'horizontal' data-amount = '-1' title = "Compress horizonal spacing">
                <i class="fa  fa-compress fa-rotate-45" ></i>
            </button>
             <button type="button" class="btn btn-default btn-sm" id = "sort_ascending" title = "Sort deepest clades to the bottom">
                <i class="fa fa-sort-amount-asc" ></i>
            </button>
             <button type="button" class="btn btn-default btn-sm" id = "sort_descending" title = "Sort deepest clades to the top">
                <i class="fa fa-sort-amount-desc" ></i>
            </button>
             <button type="button" class="btn btn-default btn-sm" id = "sort_original" title = "Restore original order">
                <i class="fa fa-sort" ></i>
            </button>
             <button type="button" class="btn btn-default btn-sm" id = "save_as_image" title = "Save tree as image">
                <i class="fa fa-floppy-o" ></i>
            </button>
          </div>
        <div class="btn-group" data-toggle="buttons">
          <label class="btn btn-default active btn-sm">
            <input type="radio" name="options" class = "phylotree-layout-mode" data-mode = "linear" autocomplete="off" checked title = "Layout left-to-right"> Linear
          </label>
          <label class="btn btn-default  btn-sm">
            <input type="radio" name="options" class = "phylotree-layout-mode" data-mode = "radial" autocomplete="off" title = "Layout radially"> Radial
          </label>
        </div>

        <div class="btn-group" data-toggle="buttons">
          <label class="btn btn-default active btn-sm">
            <input type="radio" class = "phylotree-align-toggler" data-align = "left" name="options-align" autocomplete="off" checked title = "Align tips labels to branches">
                <i class="fa fa-align-left" ></i>
            </input>

          </label>
          <label class="btn btn-default btn-sm">
           <input type="radio" class = "phylotree-align-toggler" data-align = "right" name="options-align" autocomplete="off" title = "Align tips labels to the edge of the plot">
                <i class="fa fa-align-right" ></i>
            </input>
          </label>
        </div>

       </div>
    </div>
</div>

    <div class = 'row'>
      <div class = 'col-md-12'>
          <div id = 'tree_container' class = 'tree-widget'>
          </div>
      </div>
  </div>
</div>

<body>
  <svg id="tree_display"></svg>

  <script>

    // required to pull apart nodes top, bottom and left, right
$ ("[data-direction]").on ("click", function (e) {
    var which_function = $(this).data ("direction") == 'vertical' ? tree.spacing_x : tree.spacing_y;
    which_function (which_function () + (+ $(this).data ("amount"))).update();
});

//function for making tree radial
$(".phylotree-layout-mode").on ("change", function (e) {
    if ($(this).is(':checked')) {
        if (tree.radial () != ($(this).data ("mode") == "radial")) {
            tree.radial (!tree.radial ()).placenodes().update ();
        }
    }
});

//this function lets the node names align at the right site
$(".phylotree-align-toggler").on ("change", function (e) {
    if ($(this).is(':checked')) {
        if (tree.align_tips ($(this).data ("align") == "right")) {
            tree.placenodes().update ();
        }
    }
});

//necessary to order the tree
function sort_nodes (asc) {
    tree.traverse_and_compute (function (n) {
            var d = 1;
            if (n.children && n.children.length) {
                d += d3.max (n.children, function (d) { return d["count_depth"];});
            }
            n["count_depth"] = d;
        });
        tree.resort_children (function (a,b) {
            return (a["count_depth"] - b["count_depth"]) * (asc ? 1 : -1);
        });
}

//save tree to image
$("#save_as_image").on ("click", function (e) {
    sort_nodes (false);
});  

//necessary to return to actual order
$("#sort_original").on ("click", function (e) {
    tree.resort_children (function (a,b) {
        return a["original_child_order"] - b["original_child_order"];
    });
});

//sort  tree in ascending order
$("#sort_ascending").on ("click", function (e) {
    sort_nodes (true);
});

//sort tree in descending order
$("#sort_descending").on ("click", function (e) {
    sort_nodes (false);
});

// The node styler provides information on styling nodes within the
// phylogeny.
var nodeStyler = function (element, data) {
    if(data.hasOwnProperty('internal_label')) {
        // If the node has an internal label (see below), we display it
        // next to the node by creating a new 'text' element.

        // Make sure we don't already have an internal label node on this SVG node!
        var label = element.selectAll(".internal_label");
        if(label.empty()) {
            var text_label = element.append("text");

            // TODO: Once we're happy with how these elements look,
            // we should move all this complexity into CSS classes.
            text_label.classed("internal_label", true)
                .text(data.internal_label)
                .attr("dx", ".4em")
                .attr("dy", ".3em")
                .style("font-style", "italic")
                .style('font-size', '10px')
                .attr("text-anchor", "start")
                .attr("alignment-baseline", "middle");
        }
    }
}

//function which takes a newick string as input and outputs a tree
function drawATree(newick){
    var tree = d3.layout.phylotree()
        .svg(d3.select("#tree_display"))
        //.svg(d3.select(node_expr))
        // render to this SVG element
        .options({
            brush: false,
            zoom: false,
            "show-scale": true,
            selectable: true, //decide if edges can be selected or not
            collapsible: true, 
            transitions: true,
            // turn off the menu on internal nodes
            "minimum-per-node-spacing": 3
        })
        //uncomment if inner node labels should be displayed
        //.style_nodes(nodeStyler);

    /* the next call creates the tree object, and tree nodes */
    tree(newick)
            // parse the Newick into a d3 hierarchy object with additional fields
        .layout();
            // layout and render the tree
            _.each(tree.get_nodes(), function(node) {
              if(node.children /*&& node.name.startsWith("expected_")*/) {
                node.internal_label = node.name.substring(0);
                //console.log(node.internal_label)
                }
            });

    tree
        //.spacing_x(20).spacing_y(50)
        .placenodes()
        .update();

    // for syntax highlighting
    hljs.initHighlightingOnLoad();

    return tree;

}

    //actually have to read in tree from json file
    var example_tree = "(((((sc58:0.03648727885818031,sc30:0.1646390055082284):6.625616832731166E-4,sc54:0.07263681355120553):0.0866201484483157,(sc49:0.15441344027288773,(((((((sc40:0.25329593940064815,sc37:0.09646165750090659):0.003365399856741107,(sc41:0.10183299272305411,sc38:0.12494811535528662):0.03982059456796846):0.002101982382052852,sc36:0.09936970806982337):0.002806319210073635,sc39:0.11610341879866005):0.11925736639203374,sc42:0.1637297798854414):0.002657365895455911,sc48:0.12072427624477922):0.03497479203823097,sc43:0.1500763422478888):0.021065557850155247):3.344463381973379E-4):2.1153697655688908E-5,(((sc45:0.08294894237623322,sc47:0.2085092282747913):0.012230835645347509,(sc46:0.1408477199913148,(((sc60:0.18566483302306283,sc61:0.1460141668902622):0.10100527707107695,sc27:0.1772509696212226):0.00782313471749887,sc28:0.01728044446851493):0.02622402487006433):1.4939714937098052E-4):0.009106946995471639,((((sc52:0.0567496564528166,((sc10:0.5504529756090385,((sc21:0.05626951530486355,sc19:0.05702759224925178):0.002346469771769742,(((sc18:0.16070780995702116,sc24:5.146421368571826E-4):0.015553531762780591,sc17:0.17452983833717484):0.052474377443669847,((((sc11:0.06393942151165115,(((((sc3:0.1552452697711925,(sc1:0.08476356697206774,((sc2:0.12625606921874308,sc4:0.0035122932330168013):0.004831588921151473,sc8:0.09512419365664619):0.0030494253659795684):9.82664620395626E-4):0.08279729547558465,sc5:6.90307668203559E-4):0.003574551485298436,sc6:0.006159945209916211):0.1025754257999628,sc16:3.1793433236837763E-4):1.3225464341104533E-4,sc7:0.10935728544991666):0.16531047961609277):0.01856590101905242,(sc9:0.25748661682706986,sc13:0.008464708532189709):0.08097374837772402):0.07490222178002377,(sc15:0.004670959214603181,sc25:0.0025711563301131623):0.03714415640911034):0.008372277734685357,((sc23:0.002796918242261265,sc20:0.0019340994448770999):0.07545313994878153,(((sc12:0.06844889104948199,sc22:0.08028337733051201):0.0033150304554360414,sc26:0.3834312509179077):0.01147481460802585,sc14:2.4005010676008407E-4):0.0017381569425643875):0.06200172535148682):0.04355039197174425):0.029879719547589133):0.5242185492849311):0.7443547028498806,sc53:0.01968033417815092):0.08205888947971027):0.00395414641226214,sc56:0.09982571740915804):1.654941980449423E-4,sc34:0.13933937776865082):0.012567818245994991,((sc57:0.16330916100786988,sc55:0.13721597104483335):0.00797673651969054,sc29:0.08364577177027947):0.03801820122420664):8.07113672126922E-4):3.242789785609875E-5):2.5168751246011076E-4,(((sc31:0.09677081178091926,sc32:0.22629042536501165):0.03914546117872383,sc59:0.1411669023962375):3.1697367420655155E-4,(sc35:0.1706120452038145,((sc44:0.05932779374488423,sc33:0.11707203744794839):0.0075400706777962925,(sc50:0.045133353515270785,sc51:0.10064999969411001):0.01833314183409104):0.025320926329602776):0.00835543680226741):1.4243583758591892E-4);"
    var inner_tree = "((((sc4:0.007677396790566162,sc7:0.003449805521467856)IN3:0.021613972538786774,(((sc17:0.001181015392800711,sc13:0.0031245023565947675)IN8:0.023341775472469763,sc1:0.08810353788415672)IN7:0.08389950963826942,sc18:0.04870365182890159)IN6:0.04301167480422979)IN2:0.055746696940291135,(((sc19:0.008331803113780211,sc14:0.0040272867341121215)IN15:0.09104512655172864,(sc20:0.0035169836766599776,sc15:0.007815855914400203)IN18:0.09912916695324903)IN14:0.017694784747857663,(sc16:0.00941489383931516,sc11:0.004672866223824594)IN21:0.08299776463032543)IN13:0.05770882028489088)IN1:0.08328437806598044,((((sc2:0.007546733430273554,sc10:0.010087622984410752)IN27:0.0082574117627511,sc12:7.365183836104295E-4)IN26:0.031184091454516262,(sc6:0.06960562447140793,(sc3:0.00989719496994525,sc5:0.0039021865181385154)IN33:0.0712659254388883)IN31:0.07120691934159183)IN25:0.09521990693235705,(sc9:0.009215463984903055,sc8:0.004131068440343742)IN36:0.09840685532539961)IN24:0.09615129314965215)R;"

    var tree = drawATree(inner_tree);

    //sort tree in descending order
    $("#sort_descending").on ("click", function (e) {
        sort_nodes (false);
    });

  </script>

</body>

</html>
santule commented 2 years ago

Hello, I am also looking for this functionality. Did anyone find the answer? In summary, I want to get the node name/id/descendants/ancestors when the mouse is clicked on the node. I could not find if mouse click events are available. Appreciate your help. Regards, Sanjana

santule commented 2 years ago

I figured this out. For anyone else like me trying to gather the information on nodes as user mouse clicks on node, here is the code that worked for me.

element.on('click', function() { console.log(data.data.name) console.log(tree.getNodeByName(data.data.name)) });

Call this code as a part of the 'node-styler' function. The 'node-styler' function is defined in the tree.render . You can also replace 'click' with 'mouseover' etc.