2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
21 package jalview.analysis;
23 import jalview.bin.Cache;
24 import jalview.datamodel.AlignmentView;
25 import jalview.datamodel.BinaryNode;
26 import jalview.datamodel.NodeTransformI;
27 import jalview.datamodel.Sequence;
28 import jalview.datamodel.SequenceI;
29 import jalview.datamodel.SequenceNode;
30 import jalview.io.NewickFile;
32 import java.util.ArrayList;
33 import java.util.Enumeration;
34 import java.util.List;
35 import java.util.Vector;
38 * A model of a tree, either computed by Jalview or loaded from a file or other
41 public class TreeModel
44 SequenceI[] sequences;
47 * SequenceData is a string representation of what the user
48 * sees. The display may contain hidden columns.
50 private AlignmentView seqData;
62 Vector<BinaryNode> node;
64 boolean hasDistances = true; // normal case for jalview trees
66 boolean hasBootstrap = false; // normal case for jalview trees
68 private boolean hasRootDistance = true;
71 * Create a new TreeModel object with leaves associated with sequences in
72 * seqs, and (optionally) original alignment data represented by Cigar strings
81 public TreeModel(SequenceI[] seqs, AlignmentView odata,
84 this(seqs, (SequenceNode) treefile.getTree(), treefile.HasDistances(),
85 treefile.HasBootstrap(), treefile.HasRootDistance());
88 associateLeavesToSequences(seqs);
92 * Constructor given a calculated tree
96 public TreeModel(TreeBuilder tree)
98 this(tree.getSequences(), (SequenceNode) tree.getTopNode(), tree.hasDistances(),
99 tree.hasBootstrap(), tree.hasRootDistance());
100 seqData = tree.getOriginalData();
104 * Constructor given sequences, root node and tree property flags
112 public TreeModel(SequenceI[] seqs, SequenceNode root, boolean hasDist,
113 boolean hasBoot, boolean hasRootDist)
115 this.sequences = seqs;
118 hasDistances = hasDist;
119 hasBootstrap = hasBoot;
120 hasRootDistance = hasRootDist;
122 maxheight = findHeight(top);
128 public void associateLeavesToSequences(SequenceI[] seqs)
130 SequenceIdMatcher algnIds = new SequenceIdMatcher(seqs);
132 Vector<BinaryNode> leaves = findLeaves(top);
135 int namesleft = seqs.length;
140 Vector<SequenceI> one2many = new Vector<SequenceI>();
141 // int countOne2Many = 0;
142 while (i < leaves.size())
144 // TODO - decide if we get rid of the polymorphism here ?
145 j = (SequenceNode)leaves.elementAt(i++);
146 realnam = j.getName();
151 nam = algnIds.findIdMatch(realnam);
157 if (one2many.contains(nam))
160 // if (Cache.isDebugEnabled())
161 // Cache.debug("One 2 many relationship for
166 one2many.addElement(nam);
172 j.setElement(new Sequence(realnam, "THISISAPLACEHLDER"));
173 j.setPlaceholder(true);
176 // if (Cache.isDebugEnabled() && countOne2Many>0) {
177 // Cache.debug("There were "+countOne2Many+" alignment
178 // sequence ids (out of "+one2many.size()+" unique ids) linked to two or
185 * Generate a string representation of the Tree
187 * @return Newick File with all tree data available
189 public String print()
191 NewickFile fout = new NewickFile(getTopNode());
193 return fout.print(hasBootstrap(), hasDistances(), hasRootDistance()); // output
203 * used when the alignment associated to a tree has changed.
206 * Sequence set to be associated with tree nodes
208 public void updatePlaceHolders(List<SequenceI> list)
210 Vector<BinaryNode> leaves = findLeaves(top);
212 int sz = leaves.size();
213 SequenceIdMatcher seqmatcher = null;
218 SequenceNode leaf = (SequenceNode) leaves.elementAt(i++);
220 if (list.contains(leaf.element()))
222 leaf.setPlaceholder(false);
226 if (seqmatcher == null)
228 // Only create this the first time we need it
229 SequenceI[] seqs = new SequenceI[list.size()];
231 for (int j = 0; j < seqs.length; j++)
233 seqs[j] = list.get(j);
236 seqmatcher = new SequenceIdMatcher(seqs);
239 SequenceI nam = seqmatcher.findIdMatch(leaf.getName());
243 if (!leaf.isPlaceholder())
245 // remapping the node to a new sequenceI - should remove any refs to
247 // TODO - make many sequenceI to one leaf mappings possible!
250 leaf.setPlaceholder(false);
251 leaf.setElement(nam);
255 if (!leaf.isPlaceholder())
257 // Construct a new placeholder sequence object for this leaf
259 new Sequence(leaf.getName(), "THISISAPLACEHLDER"));
261 leaf.setPlaceholder(true);
269 * rename any nodes according to their associated sequence. This will modify
270 * the tree's metadata! (ie the original NewickFile or newly generated
271 * BinaryTree's label data)
273 public void renameAssociatedNodes()
275 applyToNodes(new NodeTransformI()
279 public void transform(BinaryNode nd)
281 Object el = nd.element();
282 if (el != null && el instanceof SequenceI)
284 nd.setName(((SequenceI) el).getName());
291 * Search for leaf nodes below (or at) the given node
294 * root node to search from
298 public Vector<BinaryNode> findLeaves(BinaryNode top2)
300 Vector<BinaryNode> leaves = new Vector<BinaryNode>();
301 findLeaves(top2, leaves);
306 * Search for leaf nodes.
309 * root node to search from
311 * Vector of leaves to add leaf node objects too.
313 * @return Vector of leaf nodes on binary tree
315 Vector<BinaryNode> findLeaves(BinaryNode nd,
316 Vector<BinaryNode> leaves)
323 if ((nd.left() == null) && (nd.right() == null)) // Interior node
326 leaves.addElement(nd);
333 * TODO: Identify internal nodes... if (node.isSequenceLabel()) {
334 * leaves.addElement(node); }
336 findLeaves((SequenceNode) nd.left(), leaves);
337 findLeaves((SequenceNode) nd.right(), leaves);
344 * printNode is mainly for debugging purposes.
349 void printNode(BinaryNode nd)
356 if ((nd.left() == null) && (nd.right() == null))
358 System.out.println("Leaf = " + ((SequenceI) nd.element()).getName());
359 System.out.println("Dist " + nd.dist);
360 System.out.println("Boot " + nd.getBootstrap());
364 System.out.println("Dist " + nd.dist);
365 printNode((BinaryNode) nd.left());
366 printNode((BinaryNode) nd.right());
373 * @return DOCUMENT ME!
375 public double getMaxHeight()
381 * Makes a list of groups, where each group is represented by a node whose
382 * height (distance from the root node), as a fraction of the height of the
383 * whole tree, is greater than the given threshold. This corresponds to
384 * selecting the nodes immediately to the right of a vertical line
385 * partitioning the tree (if the tree is drawn with root to the left). Each
386 * such node represents a group that contains all of the sequences linked to
387 * the child leaf nodes.
392 public List<BinaryNode> groupNodes(float threshold)
394 List<BinaryNode> groups = new ArrayList<BinaryNode>();
395 _groupNodes(groups, getTopNode(), threshold);
399 protected void _groupNodes(List<BinaryNode> groups, BinaryNode nd,
407 if ((nd.height / maxheight) > threshold)
413 _groupNodes(groups, (SequenceNode) nd.left(), threshold);
414 _groupNodes(groups, (SequenceNode) nd.right(), threshold);
424 * @return DOCUMENT ME!
426 public double findHeight(BinaryNode nd)
433 if ((nd.left() == null) && (nd.right() == null))
435 nd.height = ((BinaryNode) nd.parent()).height + nd.dist;
437 if (nd.height > maxheight)
448 if (nd.parent() != null)
450 nd.height = ((BinaryNode) nd.parent()).height + nd.dist;
455 nd.height = (float) 0.0;
458 maxheight = findHeight((BinaryNode) (nd.left()));
459 maxheight = findHeight((BinaryNode) (nd.right()));
471 void printN(BinaryNode nd)
478 if ((nd.left() != null) && (nd.right() != null))
480 printN((BinaryNode) nd.left());
481 printN((BinaryNode) nd.right());
485 System.out.println(" name = " + ((SequenceI) nd.element()).getName());
489 " dist = " + nd.dist + " " + nd.count + " " + nd.height);
498 public void reCount(BinaryNode nd)
502 // _lylimit = this.node.size();
506 // private long _lycount = 0, _lylimit = 0;
514 void _reCount(BinaryNode nd)
516 // if (_lycount<_lylimit)
518 // System.err.println("Warning: depth of _recount greater than number of
527 if ((nd.left() != null) && (nd.right() != null))
530 _reCount((BinaryNode) nd.left());
531 _reCount((BinaryNode) nd.right());
533 BinaryNode l = (BinaryNode) nd.left();
534 BinaryNode r = (BinaryNode) nd.right();
536 nd.count = l.count + r.count;
537 nd.ycount = (l.ycount + r.ycount) / 2;
542 nd.ycount = ycount++;
553 public void swapNodes(BinaryNode nd)
560 BinaryNode tmp = (BinaryNode) nd.left();
562 nd.setLeft(nd.right());
574 void changeDirection(BinaryNode nd, BinaryNode dir)
581 if (nd.parent() != top)
583 changeDirection((BinaryNode) nd.parent(), nd);
585 BinaryNode tmp = (BinaryNode) nd.parent();
587 if (dir == nd.left())
592 else if (dir == nd.right())
600 if (dir == nd.left())
602 nd.setParent(nd.left());
604 if (top.left() == nd)
606 nd.setRight(top.right());
610 nd.setRight(top.left());
615 nd.setParent(nd.right());
617 if (top.left() == nd)
619 nd.setLeft(top.right());
623 nd.setLeft(top.left());
632 * @return DOCUMENT ME!
634 public BinaryNode getTopNode()
641 * @return true if tree has real distances
643 public boolean hasDistances()
650 * @return true if tree has real bootstrap values
652 public boolean hasBootstrap()
657 public boolean hasRootDistance()
659 return hasRootDistance;
663 * apply the given transform to all the nodes in the tree.
665 * @param nodeTransformI
667 public void applyToNodes(NodeTransformI nodeTransformI)
669 for (Enumeration<BinaryNode> nodes = node.elements(); nodes
670 .hasMoreElements(); nodeTransformI
671 .transform(nodes.nextElement()))
677 public AlignmentView getOriginalData()