UNPKG

tnt.tree

Version:
479 lines (398 loc) 14.6 kB
var apijs = require("tnt.api"); var tnt_tree_node = require("tnt.tree.node"); var tree = function () { "use strict"; var dispatch = d3.dispatch ("click", "dblclick", "mouseover", "mouseout"); var conf = { duration : 500, // Duration of the transitions node_display : tree.node_display.circle(), label : tree.label.text(), layout : tree.layout.vertical(), // on_click : function () {}, // on_dbl_click : function () {}, // on_mouseover : function () {}, branch_color : 'black', id : function (d) { return d._id; } }; // Keep track of the focused node // TODO: Would it be better to have multiple focused nodes? (ie use an array) var focused_node; // Extra delay in the transitions (TODO: Needed?) var delay = 0; // Ease of the transitions var ease = "cubic-in-out"; // By node data var sp_counts = {}; var scale = false; // The id of the tree container var div_id; // The tree visualization (svg) var svg; var vis; var links_g; var nodes_g; // TODO: For now, counts are given only for leaves // but it may be good to allow counts for internal nodes var counts = {}; // The full tree var base = { tree : undefined, data : undefined, nodes : undefined, links : undefined }; // The curr tree. Needed to re-compute the links / nodes positions of subtrees var curr = { tree : undefined, data : undefined, nodes : undefined, links : undefined }; // The cbak returned var t = function (div) { div_id = d3.select(div).attr("id"); var tree_div = d3.select(div) .append("div") .style("width", (conf.layout.width() + "px")) .attr("class", "tnt_groupDiv"); var cluster = conf.layout.cluster; var n_leaves = curr.tree.get_all_leaves().length; var max_leaf_label_length = function (tree) { var max = 0; var leaves = tree.get_all_leaves(); for (var i=0; i<leaves.length; i++) { var label_width = conf.label.width()(leaves[i]) + d3.functor (conf.node_display.size())(leaves[i]); if (label_width > max) { max = label_width; } } return max; }; var max_leaf_node_height = function (tree) { var max = 0; var leaves = tree.get_all_leaves(); for (var i=0; i<leaves.length; i++) { var node_height = d3.functor(conf.node_display.size())(leaves[i]) * 2; var label_height = d3.functor(conf.label.height())(leaves[i]); max = d3.max([max, node_height, label_height]); } return max; }; var max_label_length = max_leaf_label_length(curr.tree); conf.layout.max_leaf_label_width(max_label_length); var max_node_height = max_leaf_node_height(curr.tree); // Cluster size is the result of... // total width of the vis - transform for the tree - max_leaf_label_width - horizontal transform of the label // TODO: Substitute 15 by the horizontal transform of the nodes var cluster_size_params = { n_leaves : n_leaves, label_height : max_node_height, label_padding : 15 }; conf.layout.adjust_cluster_size(cluster_size_params); var diagonal = conf.layout.diagonal(); var transform = conf.layout.transform_node; svg = tree_div .append("svg") .attr("width", conf.layout.width()) .attr("height", conf.layout.height(cluster_size_params) + 30) .attr("fill", "none"); vis = svg .append("g") .attr("id", "tnt_st_" + div_id) .attr("transform", "translate(" + conf.layout.translate_vis()[0] + "," + conf.layout.translate_vis()[1] + ")"); curr.nodes = cluster.nodes(curr.data); conf.layout.scale_branch_lengths(curr); curr.links = cluster.links(curr.nodes); // LINKS // All the links are grouped in a g element links_g = vis .append("g") .attr("class", "links"); nodes_g = vis .append("g") .attr("class", "nodes"); //var link = vis var link = links_g .selectAll("path.tnt_tree_link") .data(curr.links, function(d){ return conf.id(d.target); }); link .enter() .append("path") .attr("class", "tnt_tree_link") .attr("id", function(d) { return "tnt_tree_link_" + div_id + "_" + conf.id(d.target); }) .style("stroke", function (d) { return d3.functor(conf.branch_color)(tnt_tree_node(d.source), tnt_tree_node(d.target)); }) .attr("d", diagonal); // NODES //var node = vis var node = nodes_g .selectAll("g.tnt_tree_node") .data(curr.nodes, function(d) { return conf.id(d); }); var new_node = node .enter().append("g") .attr("class", function(n) { if (n.children) { if (n.depth === 0) { return "root tnt_tree_node"; } else { return "inner tnt_tree_node"; } } else { return "leaf tnt_tree_node"; } }) .attr("id", function(d) { return "tnt_tree_node_" + div_id + "_" + d._id; }) .attr("transform", transform); // display node shape new_node .each (function (d) { conf.node_display.call(this, tnt_tree_node(d)); }); // display node label new_node .each (function (d) { conf.label.call(this, tnt_tree_node(d), conf.layout.type, d3.functor(conf.node_display.size())(tnt_tree_node(d))); }); new_node.on("click", function (node) { var my_node = tnt_tree_node(node); tree.trigger("node:click", my_node); dispatch.click.call(this, my_node); }); new_node.on("dblclick", function (node) { var my_node = tnt_tree_node(node); tree.trigger("node:dblclick", my_node); dispatch.dblclick.call(this, my_node); }); new_node.on("mouseover", function (node) { var my_node = tnt_tree_node(node); tree.trigger("node:hover", tnt_tree_node(node)); dispatch.mouseover.call(this, my_node); }); new_node.on("mouseout", function (node) { var my_node = tnt_tree_node(node); tree.trigger("node:mouseout", tnt_tree_node(node)); dispatch.mouseout.call(this, my_node); }); // Update plots an updated tree api.method ('update', function() { tree_div .style("width", (conf.layout.width() + "px")); svg.attr("width", conf.layout.width()); var cluster = conf.layout.cluster; var diagonal = conf.layout.diagonal(); var transform = conf.layout.transform_node; var max_label_length = max_leaf_label_length(curr.tree); conf.layout.max_leaf_label_width(max_label_length); var max_node_height = max_leaf_node_height(curr.tree); // Cluster size is the result of... // total width of the vis - transform for the tree - max_leaf_label_width - horizontal transform of the label // TODO: Substitute 15 by the transform of the nodes (probably by selecting one node assuming all the nodes have the same transform var n_leaves = curr.tree.get_all_leaves().length; var cluster_size_params = { n_leaves : n_leaves, label_height : max_node_height, label_padding : 15 }; conf.layout.adjust_cluster_size(cluster_size_params); svg .transition() .duration(conf.duration) .ease(ease) .attr("height", conf.layout.height(cluster_size_params) + 30); // height is in the layout vis .transition() .duration(conf.duration) .attr("transform", "translate(" + conf.layout.translate_vis()[0] + "," + conf.layout.translate_vis()[1] + ")"); curr.nodes = cluster.nodes(curr.data); conf.layout.scale_branch_lengths(curr); curr.links = cluster.links(curr.nodes); // LINKS var link = links_g .selectAll("path.tnt_tree_link") .data(curr.links, function(d){ return conf.id(d.target); }); // NODES var node = nodes_g .selectAll("g.tnt_tree_node") .data(curr.nodes, function(d) { return conf.id(d); }); var exit_link = link .exit() .remove(); link .enter() .append("path") .attr("class", "tnt_tree_link") .attr("id", function (d) { return "tnt_tree_link_" + div_id + "_" + conf.id(d.target); }) .attr("stroke", function (d) { return d3.functor(conf.branch_color)(tnt_tree_node(d.source), tnt_tree_node(d.target)); }) .attr("d", diagonal); link .transition() .ease(ease) .duration(conf.duration) .attr("d", diagonal); // Nodes var new_node = node .enter() .append("g") .attr("class", function(n) { if (n.children) { if (n.depth === 0) { return "root tnt_tree_node"; } else { return "inner tnt_tree_node"; } } else { return "leaf tnt_tree_node"; } }) .attr("id", function (d) { return "tnt_tree_node_" + div_id + "_" + d._id; }) .attr("transform", transform); // Exiting nodes are just removed node .exit() .remove(); new_node.on("click", function (node) { var my_node = tnt_tree_node(node); tree.trigger("node:click", my_node); dispatch.click.call(this, my_node); }); new_node.on("dblclick", function (node) { var my_node = tnt_tree_node(node); tree.trigger("node:dblclick", my_node); dispatch.dblclick.call(this, my_node); }); new_node.on("mouseover", function (node) { var my_node = tnt_tree_node(node); tree.trigger("node:hover", tnt_tree_node(node)); dispatch.mouseover.call(this, my_node); }); new_node.on("mouseout", function (node) { var my_node = tnt_tree_node(node); tree.trigger("node:mouseout", tnt_tree_node(node)); dispatch.mouseout.call(this, my_node); }); // // We need to re-create all the nodes again in case they have changed lively (or the layout) // node.selectAll("*").remove(); // new_node // .each(function (d) { // conf.node_display.call(this, tnt_tree_node(d)); // }); // // // We need to re-create all the labels again in case they have changed lively (or the layout) // new_node // .each (function (d) { // conf.label.call(this, tnt_tree_node(d), conf.layout.type, d3.functor(conf.node_display.size())(tnt_tree_node(d))); // }); t.update_nodes(); node .transition() .ease(ease) .duration(conf.duration) .attr("transform", transform); }); api.method('update_nodes', function () { var node = nodes_g .selectAll("g.tnt_tree_node"); // re-create all the nodes again // node.selectAll("*").remove(); node .each(function () { conf.node_display.reset.call(this); }); node .each(function (d) { //console.log(conf.node_display()); conf.node_display.call(this, tnt_tree_node(d)); }); // re-create all the labels again node .each (function (d) { conf.label.call(this, tnt_tree_node(d), conf.layout.type, d3.functor(conf.node_display.size())(tnt_tree_node(d))); }); }); }; // API var api = apijs (t) .getset (conf); // TODO: Rewrite data using getset / finalizers & transforms api.method ('data', function (d) { if (!arguments.length) { return base.data; } // The original data is stored as the base and curr data base.data = d; curr.data = d; // Set up a new tree based on the data var newtree = tnt_tree_node(base.data); t.root(newtree); tree.trigger("data:hasChanged", base.data); return this; }); // TODO: Rewrite tree using getset / finalizers & transforms api.method ('root', function (myTree) { if (!arguments.length) { return curr.tree; } // The original tree is stored as the base, prev and curr tree base.tree = myTree; curr.tree = base.tree; // prev.tree = base.tree; return this; }); api.method ('subtree', function (curr_nodes, keepSingletons) { var subtree = base.tree.subtree(curr_nodes, keepSingletons); curr.data = subtree.data(); curr.tree = subtree; return this; }); api.method ('focus_node', function (node, keepSingletons) { // find var found_node = t.root().find_node(function (n) { return node.id() === n.id(); }); focused_node = found_node; t.subtree(found_node.get_all_leaves(), keepSingletons); return this; }); api.method ('has_focus', function (node) { return ((focused_node !== undefined) && (focused_node.id() === node.id())); }); api.method ('release_focus', function () { t.data (base.data); focused_node = undefined; return this; }); return d3.rebind (t, dispatch, "on"); }; module.exports = exports = tree;