1 | ;
|
2 |
|
3 |
|
4 | /*
|
5 | trees - a module for trees in gramene
|
6 |
|
7 | */
|
8 | var TreeModel = require('tree-model');
|
9 | var FlatToNested = require('flat-to-nested');
|
10 | var _ = require('lodash');
|
11 | var treemodelExtensions = require('./treemodelExtensions');
|
12 |
|
13 | module.exports = {
|
14 | tree: function (taxonomy) {
|
15 | function cleanUpProperties(taxonomy) {
|
16 | return taxonomy.map(function (taxon) {
|
17 | if (!taxon.is_a) {
|
18 | if (taxon._id !== 1) {
|
19 | throw new Error('unrooted node!');
|
20 | }
|
21 | }
|
22 | if (taxon.hasOwnProperty('property_value')) {
|
23 | taxon.rank = taxon.property_value.replace(/has_rank NCBITaxon:/,'');
|
24 | }
|
25 | return {
|
26 | id: taxon._id,
|
27 | parent: taxon.is_a ? taxon.is_a[0] : undefined,
|
28 | rank: taxon.rank,
|
29 | name: taxon.name,
|
30 | synonyms: taxon.synonym || [],
|
31 | geneCount: taxon.num_genes
|
32 | };
|
33 | });
|
34 | }
|
35 |
|
36 | function createTree(nestedTaxa) {
|
37 | function childNodeNameLexComparator(a, b) {
|
38 | return a.name > b.name ? 1 : -1;
|
39 | }
|
40 | function childNodeGeneCountComparator(a, b) {
|
41 | return a.geneCount < b.geneCount ? 1 : -1;
|
42 | }
|
43 |
|
44 | return new TreeModel({modelComparatorFn: childNodeGeneCountComparator}).parse(nestedTaxa);
|
45 | }
|
46 |
|
47 | function compressTreePaths(tree) {
|
48 | tree.all(function (node) {
|
49 | return !node.isRoot() && node.children.length === 1;
|
50 | }).forEach(function (node) {
|
51 | var parent = node.parent
|
52 | , child = node.children[0];
|
53 |
|
54 | parent.addChild(child);
|
55 | node.drop();
|
56 |
|
57 | // maintain the link from the compressed node to the parent
|
58 | // (this is deleted in call to node.drop())
|
59 | node.parent = parent;
|
60 | node.compressed = true;
|
61 |
|
62 | // Add compressed nodes to child and not parent
|
63 | if (!child.compressedNodes) {
|
64 | child.compressedNodes = [];
|
65 | }
|
66 | child.compressedNodes.push(node);
|
67 | if (node.compressedNodes) {
|
68 | child.compressedNodes = node.compressedNodes.concat(child.compressedNodes);
|
69 | delete node.compressedNodes;
|
70 | }
|
71 | });
|
72 | }
|
73 |
|
74 | //function decorateTree(tree) {
|
75 | // tree.lca = function lowestCommonAncestor(nodes) {
|
76 | // var parentNodesInCommon = _.chain(nodes)
|
77 | // .map(function (node) {
|
78 | // return node.getPath();
|
79 | // })
|
80 | // .reduce(function (acc, nextPath) {
|
81 | // return _.intersection(acc, nextPath)
|
82 | // })
|
83 | // .value();
|
84 | // return parentNodesInCommon.pop();
|
85 | // };
|
86 | //
|
87 | // tree.pathBetween = function pathBetweenNodes(from, to) {
|
88 | //
|
89 | // var lca, fromPath, toPath, fromLcaIdx, toLcaIdx, pathBetween;
|
90 | //
|
91 | // // find the lowest commen ancestor
|
92 | // lca = tree.lca([from, to]);
|
93 | //
|
94 | // // get the full path from -> root, and reverse it
|
95 | // fromPath = _(from.getPath().reverse());
|
96 | //
|
97 | // // get the full path to -> root
|
98 | // toPath = _(to.getPath());
|
99 | //
|
100 | // // find the index of lca in fromPath and toPath
|
101 | // fromLcaIdx = fromPath.findIndex(lca);
|
102 | // toLcaIdx = toPath.findIndex(lca);
|
103 | //
|
104 | // // slice and combine the arrays to get the path between
|
105 | // pathBetween = fromPath.slice(0, fromLcaIdx).concat(toPath.slice(toLcaIdx).value());
|
106 | //
|
107 | // return pathBetween.value();
|
108 | // };
|
109 | //}
|
110 | //
|
111 | //function addPrototypeDecorations(tree) {
|
112 | // var prototree = Object.getPrototypeOf(tree);
|
113 | //
|
114 | // prototree.depth = function calculateEffectiveNodeDepth(includeCompressedNodes) {
|
115 | // var path = this.getPath()
|
116 | // , depth = path.length - 1
|
117 | // , compressedDepth;
|
118 | //
|
119 | // if(includeCompressedNodes) {
|
120 | // compressedDepth = _.reduce(path, function (acc, n) {
|
121 | // return acc + (n.compressedNodes ? n.compressedNodes.length : 0);
|
122 | // }, 0);
|
123 | // depth += compressedDepth;
|
124 | // }
|
125 | //
|
126 | // return depth;
|
127 | // };
|
128 | //
|
129 | // prototree.pathTo = function(to) {
|
130 | // return tree.pathBetween(this, to);
|
131 | // };
|
132 | //
|
133 | // prototree.leafNodes = function findAllLeafNodes() {
|
134 | // return this.all(function (node) { return !node.hasChildren(); });
|
135 | // };
|
136 | //
|
137 | // prototree.lcaWith = function(otherNodes) {
|
138 | // var nodes = _.clone(otherNodes);
|
139 | // nodes.push(this);
|
140 | // return tree.lca(nodes);
|
141 | // }
|
142 | //}
|
143 | //
|
144 | //function indexTree(tree, attrs) {
|
145 | // tree.indices = _.chain(attrs)
|
146 | // .map(function (attr) {
|
147 | // var result = {_attr: attr};
|
148 | // tree.walk(function (node) {
|
149 | // result[node.model[attr]] = node;
|
150 | // });
|
151 | // return result;
|
152 | // })
|
153 | // .indexBy('_attr')
|
154 | // .value();
|
155 | //}
|
156 |
|
157 | var taxa
|
158 | , nestedTaxa
|
159 | , tree;
|
160 |
|
161 | taxa = cleanUpProperties(taxonomy);
|
162 | nestedTaxa = new FlatToNested().convert(taxa);
|
163 | tree = createTree(nestedTaxa);
|
164 | treemodelExtensions.indexTree(tree, ['id', 'name']);
|
165 | compressTreePaths(tree);
|
166 | treemodelExtensions.decorateTree(tree);
|
167 | treemodelExtensions.addPrototypeDecorations(tree);
|
168 |
|
169 | return tree;
|
170 | }
|
171 | }; |
\ | No newline at end of file |