QQMerge branch 'develop' of https://source.jalview.org/git/jalview into develop
[jalview.git] / src / jalview / analysis / NJTree.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
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.
11  *  
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.
16  * 
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.
20  */
21 package jalview.analysis;
22
23 import jalview.api.analysis.ScoreModelI;
24 import jalview.datamodel.AlignmentView;
25 import jalview.datamodel.BinaryNode;
26 import jalview.datamodel.CigarArray;
27 import jalview.datamodel.NodeTransformI;
28 import jalview.datamodel.SeqCigar;
29 import jalview.datamodel.Sequence;
30 import jalview.datamodel.SequenceI;
31 import jalview.datamodel.SequenceNode;
32 import jalview.io.NewickFile;
33 import jalview.schemes.ResidueProperties;
34
35 import java.util.Enumeration;
36 import java.util.List;
37 import java.util.Vector;
38
39 /**
40  * DOCUMENT ME!
41  * 
42  * @author $author$
43  * @version $Revision$
44  */
45 public class NJTree
46 {
47   Vector<Cluster> cluster;
48
49   SequenceI[] sequence;
50
51   // SequenceData is a string representation of what the user
52   // sees. The display may contain hidden columns.
53   public AlignmentView seqData = null;
54
55   int[] done;
56
57   int noseqs;
58
59   int noClus;
60
61   float[][] distance;
62
63   int mini;
64
65   int minj;
66
67   float ri;
68
69   float rj;
70
71   Vector<SequenceNode> groups = new Vector<SequenceNode>();
72
73   SequenceNode maxdist;
74
75   SequenceNode top;
76
77   float maxDistValue;
78
79   float maxheight;
80
81   int ycount;
82
83   Vector<SequenceNode> node;
84
85   String type;
86
87   String pwtype;
88
89   Object found = null;
90
91   boolean hasDistances = true; // normal case for jalview trees
92
93   boolean hasBootstrap = false; // normal case for jalview trees
94
95   private boolean hasRootDistance = true;
96
97   /**
98    * Create a new NJTree object with leaves associated with sequences in seqs,
99    * and original alignment data represented by Cigar strings.
100    * 
101    * @param seqs
102    *          SequenceI[]
103    * @param odata
104    *          Cigar[]
105    * @param treefile
106    *          NewickFile
107    */
108   public NJTree(SequenceI[] seqs, AlignmentView odata, NewickFile treefile)
109   {
110     this(seqs, treefile);
111     if (odata != null)
112     {
113       seqData = odata;
114     }
115     /*
116      * sequenceString = new String[odata.length]; char gapChar =
117      * jalview.util.Comparison.GapChars.charAt(0); for (int i = 0; i <
118      * odata.length; i++) { SequenceI oseq_aligned = odata[i].getSeq(gapChar);
119      * sequenceString[i] = oseq_aligned.getSequence(); }
120      */
121   }
122
123   /**
124    * Creates a new NJTree object from a tree from an external source
125    * 
126    * @param seqs
127    *          SequenceI which should be associated with leafs of treefile
128    * @param treefile
129    *          A parsed tree
130    */
131   public NJTree(SequenceI[] seqs, NewickFile treefile)
132   {
133     this.sequence = seqs;
134     top = treefile.getTree();
135
136     /**
137      * There is no dependent alignment to be recovered from an imported tree.
138      * 
139      * if (sequenceString == null) { sequenceString = new String[seqs.length];
140      * for (int i = 0; i < seqs.length; i++) { sequenceString[i] =
141      * seqs[i].getSequence(); } }
142      */
143
144     hasDistances = treefile.HasDistances();
145     hasBootstrap = treefile.HasBootstrap();
146     hasRootDistance = treefile.HasRootDistance();
147
148     maxheight = findHeight(top);
149
150     SequenceIdMatcher algnIds = new SequenceIdMatcher(seqs);
151
152     Vector<SequenceNode> leaves = findLeaves(top);
153
154     int i = 0;
155     int namesleft = seqs.length;
156
157     SequenceNode j;
158     SequenceI nam;
159     String realnam;
160     Vector<SequenceI> one2many = new Vector<SequenceI>();
161     int countOne2Many = 0;
162     while (i < leaves.size())
163     {
164       j = leaves.elementAt(i++);
165       realnam = j.getName();
166       nam = null;
167
168       if (namesleft > -1)
169       {
170         nam = algnIds.findIdMatch(realnam);
171       }
172
173       if (nam != null)
174       {
175         j.setElement(nam);
176         if (one2many.contains(nam))
177         {
178           countOne2Many++;
179           // if (jalview.bin.Cache.log.isDebugEnabled())
180           // jalview.bin.Cache.log.debug("One 2 many relationship for
181           // "+nam.getName());
182         }
183         else
184         {
185           one2many.addElement(nam);
186           namesleft--;
187         }
188       }
189       else
190       {
191         j.setElement(new Sequence(realnam, "THISISAPLACEHLDER"));
192         j.setPlaceholder(true);
193       }
194     }
195     // if (jalview.bin.Cache.log.isDebugEnabled() && countOne2Many>0) {
196     // jalview.bin.Cache.log.debug("There were "+countOne2Many+" alignment
197     // sequence ids (out of "+one2many.size()+" unique ids) linked to two or
198     // more leaves.");
199     // }
200     // one2many.clear();
201   }
202
203   /**
204    * Creates a new NJTree object.
205    * 
206    * @param sequence
207    *          DOCUMENT ME!
208    * @param type
209    *          DOCUMENT ME!
210    * @param pwtype
211    *          DOCUMENT ME!
212    * @param start
213    *          DOCUMENT ME!
214    * @param end
215    *          DOCUMENT ME!
216    */
217   public NJTree(SequenceI[] sequence, AlignmentView seqData, String type,
218           String pwtype, ScoreModelI sm, int start, int end)
219   {
220     this.sequence = sequence;
221     this.node = new Vector<SequenceNode>();
222     this.type = type;
223     this.pwtype = pwtype;
224     if (seqData != null)
225     {
226       this.seqData = seqData;
227     }
228     else
229     {
230       SeqCigar[] seqs = new SeqCigar[sequence.length];
231       for (int i = 0; i < sequence.length; i++)
232       {
233         seqs[i] = new SeqCigar(sequence[i], start, end);
234       }
235       CigarArray sdata = new CigarArray(seqs);
236       sdata.addOperation(CigarArray.M, end - start + 1);
237       this.seqData = new AlignmentView(sdata, start);
238     }
239     // System.err.println("Made seqData");// dbg
240     if (!(type.equals("NJ")))
241     {
242       type = "AV";
243     }
244
245     if (sm == null && !(pwtype.equals("PID")))
246     {
247       if (ResidueProperties.getScoreMatrix(pwtype) == null)
248       {
249         pwtype = "BLOSUM62";
250       }
251     }
252
253     int i = 0;
254
255     done = new int[sequence.length];
256
257     while ((i < sequence.length) && (sequence[i] != null))
258     {
259       done[i] = 0;
260       i++;
261     }
262
263     noseqs = i++;
264
265     distance = findDistances(sm);
266     // System.err.println("Made distances");// dbg
267     makeLeaves();
268     // System.err.println("Made leaves");// dbg
269
270     noClus = cluster.size();
271
272     cluster();
273     // System.err.println("Made clusters");// dbg
274
275   }
276
277   /**
278    * Generate a string representation of the Tree
279    * 
280    * @return Newick File with all tree data available
281    */
282   @Override
283   public String toString()
284   {
285     jalview.io.NewickFile fout = new jalview.io.NewickFile(getTopNode());
286
287     return fout.print(isHasBootstrap(), isHasDistances(),
288             isHasRootDistance()); // output all data available for tree
289   }
290
291   /**
292    * 
293    * used when the alignment associated to a tree has changed.
294    * 
295    * @param list
296    *          Sequence set to be associated with tree nodes
297    */
298   public void UpdatePlaceHolders(List<SequenceI> list)
299   {
300     Vector<SequenceNode> leaves = findLeaves(top);
301
302     int sz = leaves.size();
303     SequenceIdMatcher seqmatcher = null;
304     int i = 0;
305
306     while (i < sz)
307     {
308       SequenceNode leaf = leaves.elementAt(i++);
309
310       if (list.contains(leaf.element()))
311       {
312         leaf.setPlaceholder(false);
313       }
314       else
315       {
316         if (seqmatcher == null)
317         {
318           // Only create this the first time we need it
319           SequenceI[] seqs = new SequenceI[list.size()];
320
321           for (int j = 0; j < seqs.length; j++)
322           {
323             seqs[j] = list.get(j);
324           }
325
326           seqmatcher = new SequenceIdMatcher(seqs);
327         }
328
329         SequenceI nam = seqmatcher.findIdMatch(leaf.getName());
330
331         if (nam != null)
332         {
333           if (!leaf.isPlaceholder())
334           {
335             // remapping the node to a new sequenceI - should remove any refs to
336             // old one.
337             // TODO - make many sequenceI to one leaf mappings possible!
338             // (JBPNote)
339           }
340           leaf.setPlaceholder(false);
341           leaf.setElement(nam);
342         }
343         else
344         {
345           if (!leaf.isPlaceholder())
346           {
347             // Construct a new placeholder sequence object for this leaf
348             leaf.setElement(new Sequence(leaf.getName(),
349                     "THISISAPLACEHLDER"));
350           }
351           leaf.setPlaceholder(true);
352
353         }
354       }
355     }
356   }
357
358   /**
359    * rename any nodes according to their associated sequence. This will modify
360    * the tree's metadata! (ie the original NewickFile or newly generated
361    * BinaryTree's label data)
362    */
363   public void renameAssociatedNodes()
364   {
365     applyToNodes(new NodeTransformI()
366     {
367
368       @Override
369       public void transform(BinaryNode nd)
370       {
371         Object el = nd.element();
372         if (el != null && el instanceof SequenceI)
373         {
374           nd.setName(((SequenceI) el).getName());
375         }
376       }
377     });
378   }
379
380   /**
381    * DOCUMENT ME!
382    */
383   public void cluster()
384   {
385     while (noClus > 2)
386     {
387       if (type.equals("NJ"))
388       {
389         findMinNJDistance();
390       }
391       else
392       {
393         findMinDistance();
394       }
395
396       Cluster c = joinClusters(mini, minj);
397
398       done[minj] = 1;
399
400       cluster.setElementAt(null, minj);
401       cluster.setElementAt(c, mini);
402
403       noClus--;
404     }
405
406     boolean onefound = false;
407
408     int one = -1;
409     int two = -1;
410
411     for (int i = 0; i < noseqs; i++)
412     {
413       if (done[i] != 1)
414       {
415         if (onefound == false)
416         {
417           two = i;
418           onefound = true;
419         }
420         else
421         {
422           one = i;
423         }
424       }
425     }
426
427     joinClusters(one, two);
428     top = (node.elementAt(one));
429
430     reCount(top);
431     findHeight(top);
432     findMaxDist(top);
433   }
434
435   /**
436    * DOCUMENT ME!
437    * 
438    * @param i
439    *          DOCUMENT ME!
440    * @param j
441    *          DOCUMENT ME!
442    * 
443    * @return DOCUMENT ME!
444    */
445   public Cluster joinClusters(int i, int j)
446   {
447     float dist = distance[i][j];
448
449     int noi = cluster.elementAt(i).value.length;
450     int noj = cluster.elementAt(j).value.length;
451
452     int[] value = new int[noi + noj];
453
454     for (int ii = 0; ii < noi; ii++)
455     {
456       value[ii] = cluster.elementAt(i).value[ii];
457     }
458
459     for (int ii = noi; ii < (noi + noj); ii++)
460     {
461       value[ii] = cluster.elementAt(j).value[ii - noi];
462     }
463
464     Cluster c = new Cluster(value);
465
466     ri = findr(i, j);
467     rj = findr(j, i);
468
469     if (type.equals("NJ"))
470     {
471       findClusterNJDistance(i, j);
472     }
473     else
474     {
475       findClusterDistance(i, j);
476     }
477
478     SequenceNode sn = new SequenceNode();
479
480     sn.setLeft((node.elementAt(i)));
481     sn.setRight((node.elementAt(j)));
482
483     SequenceNode tmpi = (node.elementAt(i));
484     SequenceNode tmpj = (node.elementAt(j));
485
486     if (type.equals("NJ"))
487     {
488       findNewNJDistances(tmpi, tmpj, dist);
489     }
490     else
491     {
492       findNewDistances(tmpi, tmpj, dist);
493     }
494
495     tmpi.setParent(sn);
496     tmpj.setParent(sn);
497
498     node.setElementAt(sn, i);
499
500     return c;
501   }
502
503   /**
504    * DOCUMENT ME!
505    * 
506    * @param tmpi
507    *          DOCUMENT ME!
508    * @param tmpj
509    *          DOCUMENT ME!
510    * @param dist
511    *          DOCUMENT ME!
512    */
513   public void findNewNJDistances(SequenceNode tmpi, SequenceNode tmpj,
514           float dist)
515   {
516
517     tmpi.dist = ((dist + ri) - rj) / 2;
518     tmpj.dist = (dist - tmpi.dist);
519
520     if (tmpi.dist < 0)
521     {
522       tmpi.dist = 0;
523     }
524
525     if (tmpj.dist < 0)
526     {
527       tmpj.dist = 0;
528     }
529   }
530
531   /**
532    * DOCUMENT ME!
533    * 
534    * @param tmpi
535    *          DOCUMENT ME!
536    * @param tmpj
537    *          DOCUMENT ME!
538    * @param dist
539    *          DOCUMENT ME!
540    */
541   public void findNewDistances(SequenceNode tmpi, SequenceNode tmpj,
542           float dist)
543   {
544     float ih = 0;
545     float jh = 0;
546
547     SequenceNode sni = tmpi;
548     SequenceNode snj = tmpj;
549
550     while (sni != null)
551     {
552       ih = ih + sni.dist;
553       sni = (SequenceNode) sni.left();
554     }
555
556     while (snj != null)
557     {
558       jh = jh + snj.dist;
559       snj = (SequenceNode) snj.left();
560     }
561
562     tmpi.dist = ((dist / 2) - ih);
563     tmpj.dist = ((dist / 2) - jh);
564   }
565
566   /**
567    * DOCUMENT ME!
568    * 
569    * @param i
570    *          DOCUMENT ME!
571    * @param j
572    *          DOCUMENT ME!
573    */
574   public void findClusterDistance(int i, int j)
575   {
576     int noi = cluster.elementAt(i).value.length;
577     int noj = cluster.elementAt(j).value.length;
578
579     // New distances from cluster to others
580     float[] newdist = new float[noseqs];
581
582     for (int l = 0; l < noseqs; l++)
583     {
584       if ((l != i) && (l != j))
585       {
586         newdist[l] = ((distance[i][l] * noi) + (distance[j][l] * noj))
587                 / (noi + noj);
588       }
589       else
590       {
591         newdist[l] = 0;
592       }
593     }
594
595     for (int ii = 0; ii < noseqs; ii++)
596     {
597       distance[i][ii] = newdist[ii];
598       distance[ii][i] = newdist[ii];
599     }
600   }
601
602   /**
603    * DOCUMENT ME!
604    * 
605    * @param i
606    *          DOCUMENT ME!
607    * @param j
608    *          DOCUMENT ME!
609    */
610   public void findClusterNJDistance(int i, int j)
611   {
612
613     // New distances from cluster to others
614     float[] newdist = new float[noseqs];
615
616     for (int l = 0; l < noseqs; l++)
617     {
618       if ((l != i) && (l != j))
619       {
620         newdist[l] = ((distance[i][l] + distance[j][l]) - distance[i][j]) / 2;
621       }
622       else
623       {
624         newdist[l] = 0;
625       }
626     }
627
628     for (int ii = 0; ii < noseqs; ii++)
629     {
630       distance[i][ii] = newdist[ii];
631       distance[ii][i] = newdist[ii];
632     }
633   }
634
635   /**
636    * DOCUMENT ME!
637    * 
638    * @param i
639    *          DOCUMENT ME!
640    * @param j
641    *          DOCUMENT ME!
642    * 
643    * @return DOCUMENT ME!
644    */
645   public float findr(int i, int j)
646   {
647     float tmp = 1;
648
649     for (int k = 0; k < noseqs; k++)
650     {
651       if ((k != i) && (k != j) && (done[k] != 1))
652       {
653         tmp = tmp + distance[i][k];
654       }
655     }
656
657     if (noClus > 2)
658     {
659       tmp = tmp / (noClus - 2);
660     }
661
662     return tmp;
663   }
664
665   /**
666    * DOCUMENT ME!
667    * 
668    * @return DOCUMENT ME!
669    */
670   public float findMinNJDistance()
671   {
672     float min = 100000;
673
674     for (int i = 0; i < (noseqs - 1); i++)
675     {
676       for (int j = i + 1; j < noseqs; j++)
677       {
678         if ((done[i] != 1) && (done[j] != 1))
679         {
680           float tmp = distance[i][j] - (findr(i, j) + findr(j, i));
681
682           if (tmp < min)
683           {
684             mini = i;
685             minj = j;
686
687             min = tmp;
688           }
689         }
690       }
691     }
692
693     return min;
694   }
695
696   /**
697    * DOCUMENT ME!
698    * 
699    * @return DOCUMENT ME!
700    */
701   public float findMinDistance()
702   {
703     float min = 100000;
704
705     for (int i = 0; i < (noseqs - 1); i++)
706     {
707       for (int j = i + 1; j < noseqs; j++)
708       {
709         if ((done[i] != 1) && (done[j] != 1))
710         {
711           if (distance[i][j] < min)
712           {
713             mini = i;
714             minj = j;
715
716             min = distance[i][j];
717           }
718         }
719       }
720     }
721
722     return min;
723   }
724
725   /**
726    * Calculate a distance matrix given the sequence input data and score model
727    * 
728    * @return similarity matrix used to compute tree
729    */
730   public float[][] findDistances(ScoreModelI _pwmatrix)
731   {
732
733     float[][] dist = new float[noseqs][noseqs];
734     if (_pwmatrix == null)
735     {
736       // Resolve substitution model
737       _pwmatrix = ResidueProperties.getScoreModel(pwtype);
738       if (_pwmatrix == null)
739       {
740         _pwmatrix = ResidueProperties.getScoreMatrix("BLOSUM62");
741       }
742     }
743     dist = _pwmatrix.findDistances(seqData);
744     return dist;
745
746   }
747
748   /**
749    * DOCUMENT ME!
750    */
751   public void makeLeaves()
752   {
753     cluster = new Vector<Cluster>();
754
755     for (int i = 0; i < noseqs; i++)
756     {
757       SequenceNode sn = new SequenceNode();
758
759       sn.setElement(sequence[i]);
760       sn.setName(sequence[i].getName());
761       node.addElement(sn);
762
763       int[] value = new int[1];
764       value[0] = i;
765
766       Cluster c = new Cluster(value);
767       cluster.addElement(c);
768     }
769   }
770
771   /**
772    * Search for leaf nodes below (or at) the given node
773    * 
774    * @param nd
775    *          root node to search from
776    * 
777    * @return
778    */
779   public Vector<SequenceNode> findLeaves(SequenceNode nd)
780   {
781     Vector<SequenceNode> leaves = new Vector<SequenceNode>();
782     findLeaves(nd, leaves);
783     return leaves;
784   }
785
786   /**
787    * Search for leaf nodes.
788    * 
789    * @param nd
790    *          root node to search from
791    * @param leaves
792    *          Vector of leaves to add leaf node objects too.
793    * 
794    * @return Vector of leaf nodes on binary tree
795    */
796   Vector<SequenceNode> findLeaves(SequenceNode nd,
797           Vector<SequenceNode> leaves)
798   {
799     if (nd == null)
800     {
801       return leaves;
802     }
803
804     if ((nd.left() == null) && (nd.right() == null)) // Interior node
805     // detection
806     {
807       leaves.addElement(nd);
808
809       return leaves;
810     }
811     else
812     {
813       /*
814        * TODO: Identify internal nodes... if (node.isSequenceLabel()) {
815        * leaves.addElement(node); }
816        */
817       findLeaves((SequenceNode) nd.left(), leaves);
818       findLeaves((SequenceNode) nd.right(), leaves);
819     }
820
821     return leaves;
822   }
823
824   /**
825    * Find the leaf node with a particular ycount
826    * 
827    * @param nd
828    *          initial point on tree to search from
829    * @param count
830    *          value to search for
831    * 
832    * @return null or the node with ycound=count
833    */
834   public Object findLeaf(SequenceNode nd, int count)
835   {
836     found = _findLeaf(nd, count);
837
838     return found;
839   }
840
841   /*
842    * #see findLeaf(SequenceNode node, count)
843    */
844   public Object _findLeaf(SequenceNode nd, int count)
845   {
846     if (nd == null)
847     {
848       return null;
849     }
850
851     if (nd.ycount == count)
852     {
853       found = nd.element();
854
855       return found;
856     }
857     else
858     {
859       _findLeaf((SequenceNode) nd.left(), count);
860       _findLeaf((SequenceNode) nd.right(), count);
861     }
862
863     return found;
864   }
865
866   /**
867    * printNode is mainly for debugging purposes.
868    * 
869    * @param nd
870    *          SequenceNode
871    */
872   public void printNode(SequenceNode nd)
873   {
874     if (nd == null)
875     {
876       return;
877     }
878
879     if ((nd.left() == null) && (nd.right() == null))
880     {
881       System.out
882               .println("Leaf = " + ((SequenceI) nd.element()).getName());
883       System.out.println("Dist " + nd.dist);
884       System.out.println("Boot " + nd.getBootstrap());
885     }
886     else
887     {
888       System.out.println("Dist " + nd.dist);
889       printNode((SequenceNode) nd.left());
890       printNode((SequenceNode) nd.right());
891     }
892   }
893
894   /**
895    * DOCUMENT ME!
896    * 
897    * @param nd
898    *          DOCUMENT ME!
899    */
900   public void findMaxDist(SequenceNode nd)
901   {
902     if (nd == null)
903     {
904       return;
905     }
906
907     if ((nd.left() == null) && (nd.right() == null))
908     {
909       float dist = nd.dist;
910
911       if (dist > maxDistValue)
912       {
913         maxdist = nd;
914         maxDistValue = dist;
915       }
916     }
917     else
918     {
919       findMaxDist((SequenceNode) nd.left());
920       findMaxDist((SequenceNode) nd.right());
921     }
922   }
923
924   /**
925    * DOCUMENT ME!
926    * 
927    * @return DOCUMENT ME!
928    */
929   public Vector<SequenceNode> getGroups()
930   {
931     return groups;
932   }
933
934   /**
935    * DOCUMENT ME!
936    * 
937    * @return DOCUMENT ME!
938    */
939   public float getMaxHeight()
940   {
941     return maxheight;
942   }
943
944   /**
945    * DOCUMENT ME!
946    * 
947    * @param nd
948    *          DOCUMENT ME!
949    * @param threshold
950    *          DOCUMENT ME!
951    */
952   public void groupNodes(SequenceNode nd, float threshold)
953   {
954     if (nd == null)
955     {
956       return;
957     }
958
959     if ((nd.height / maxheight) > threshold)
960     {
961       groups.addElement(nd);
962     }
963     else
964     {
965       groupNodes((SequenceNode) nd.left(), threshold);
966       groupNodes((SequenceNode) nd.right(), threshold);
967     }
968   }
969
970   /**
971    * DOCUMENT ME!
972    * 
973    * @param nd
974    *          DOCUMENT ME!
975    * 
976    * @return DOCUMENT ME!
977    */
978   public float findHeight(SequenceNode nd)
979   {
980     if (nd == null)
981     {
982       return maxheight;
983     }
984
985     if ((nd.left() == null) && (nd.right() == null))
986     {
987       nd.height = ((SequenceNode) nd.parent()).height + nd.dist;
988
989       if (nd.height > maxheight)
990       {
991         return nd.height;
992       }
993       else
994       {
995         return maxheight;
996       }
997     }
998     else
999     {
1000       if (nd.parent() != null)
1001       {
1002         nd.height = ((SequenceNode) nd.parent()).height + nd.dist;
1003       }
1004       else
1005       {
1006         maxheight = 0;
1007         nd.height = (float) 0.0;
1008       }
1009
1010       maxheight = findHeight((SequenceNode) (nd.left()));
1011       maxheight = findHeight((SequenceNode) (nd.right()));
1012     }
1013
1014     return maxheight;
1015   }
1016
1017   /**
1018    * DOCUMENT ME!
1019    * 
1020    * @return DOCUMENT ME!
1021    */
1022   public SequenceNode reRoot()
1023   {
1024     if (maxdist != null)
1025     {
1026       ycount = 0;
1027
1028       float tmpdist = maxdist.dist;
1029
1030       // New top
1031       SequenceNode sn = new SequenceNode();
1032       sn.setParent(null);
1033
1034       // New right hand of top
1035       SequenceNode snr = (SequenceNode) maxdist.parent();
1036       changeDirection(snr, maxdist);
1037       System.out.println("Printing reversed tree");
1038       printN(snr);
1039       snr.dist = tmpdist / 2;
1040       maxdist.dist = tmpdist / 2;
1041
1042       snr.setParent(sn);
1043       maxdist.setParent(sn);
1044
1045       sn.setRight(snr);
1046       sn.setLeft(maxdist);
1047
1048       top = sn;
1049
1050       ycount = 0;
1051       reCount(top);
1052       findHeight(top);
1053     }
1054
1055     return top;
1056   }
1057
1058   /**
1059    * 
1060    * @return true if original sequence data can be recovered
1061    */
1062   public boolean hasOriginalSequenceData()
1063   {
1064     return seqData != null;
1065   }
1066
1067   /**
1068    * Returns original alignment data used for calculation - or null where not
1069    * available.
1070    * 
1071    * @return null or cut'n'pasteable alignment
1072    */
1073   public String printOriginalSequenceData(char gapChar)
1074   {
1075     if (seqData == null)
1076     {
1077       return null;
1078     }
1079
1080     StringBuffer sb = new StringBuffer();
1081     String[] seqdatas = seqData.getSequenceStrings(gapChar);
1082     for (int i = 0; i < seqdatas.length; i++)
1083     {
1084       sb.append(new jalview.util.Format("%-" + 15 + "s").form(sequence[i]
1085               .getName()));
1086       sb.append(" " + seqdatas[i] + "\n");
1087     }
1088     return sb.toString();
1089   }
1090
1091   /**
1092    * DOCUMENT ME!
1093    * 
1094    * @param nd
1095    *          DOCUMENT ME!
1096    */
1097   public void printN(SequenceNode nd)
1098   {
1099     if (nd == null)
1100     {
1101       return;
1102     }
1103
1104     if ((nd.left() != null) && (nd.right() != null))
1105     {
1106       printN((SequenceNode) nd.left());
1107       printN((SequenceNode) nd.right());
1108     }
1109     else
1110     {
1111       System.out.println(" name = "
1112               + ((SequenceI) nd.element()).getName());
1113     }
1114
1115     System.out.println(" dist = " + nd.dist + " " + nd.count + " "
1116             + nd.height);
1117   }
1118
1119   /**
1120    * DOCUMENT ME!
1121    * 
1122    * @param nd
1123    *          DOCUMENT ME!
1124    */
1125   public void reCount(SequenceNode nd)
1126   {
1127     ycount = 0;
1128     _lycount = 0;
1129     // _lylimit = this.node.size();
1130     _reCount(nd);
1131   }
1132
1133   private long _lycount = 0, _lylimit = 0;
1134
1135   /**
1136    * DOCUMENT ME!
1137    * 
1138    * @param nd
1139    *          DOCUMENT ME!
1140    */
1141   public void _reCount(SequenceNode nd)
1142   {
1143     // if (_lycount<_lylimit)
1144     // {
1145     // System.err.println("Warning: depth of _recount greater than number of nodes.");
1146     // }
1147     if (nd == null)
1148     {
1149       return;
1150     }
1151     _lycount++;
1152
1153     if ((nd.left() != null) && (nd.right() != null))
1154     {
1155
1156       _reCount((SequenceNode) nd.left());
1157       _reCount((SequenceNode) nd.right());
1158
1159       SequenceNode l = (SequenceNode) nd.left();
1160       SequenceNode r = (SequenceNode) nd.right();
1161
1162       nd.count = l.count + r.count;
1163       nd.ycount = (l.ycount + r.ycount) / 2;
1164     }
1165     else
1166     {
1167       nd.count = 1;
1168       nd.ycount = ycount++;
1169     }
1170     _lycount--;
1171   }
1172
1173   /**
1174    * DOCUMENT ME!
1175    * 
1176    * @param nd
1177    *          DOCUMENT ME!
1178    */
1179   public void swapNodes(SequenceNode nd)
1180   {
1181     if (nd == null)
1182     {
1183       return;
1184     }
1185
1186     SequenceNode tmp = (SequenceNode) nd.left();
1187
1188     nd.setLeft(nd.right());
1189     nd.setRight(tmp);
1190   }
1191
1192   /**
1193    * DOCUMENT ME!
1194    * 
1195    * @param nd
1196    *          DOCUMENT ME!
1197    * @param dir
1198    *          DOCUMENT ME!
1199    */
1200   public void changeDirection(SequenceNode nd, SequenceNode dir)
1201   {
1202     if (nd == null)
1203     {
1204       return;
1205     }
1206
1207     if (nd.parent() != top)
1208     {
1209       changeDirection((SequenceNode) nd.parent(), nd);
1210
1211       SequenceNode tmp = (SequenceNode) nd.parent();
1212
1213       if (dir == nd.left())
1214       {
1215         nd.setParent(dir);
1216         nd.setLeft(tmp);
1217       }
1218       else if (dir == nd.right())
1219       {
1220         nd.setParent(dir);
1221         nd.setRight(tmp);
1222       }
1223     }
1224     else
1225     {
1226       if (dir == nd.left())
1227       {
1228         nd.setParent(nd.left());
1229
1230         if (top.left() == nd)
1231         {
1232           nd.setRight(top.right());
1233         }
1234         else
1235         {
1236           nd.setRight(top.left());
1237         }
1238       }
1239       else
1240       {
1241         nd.setParent(nd.right());
1242
1243         if (top.left() == nd)
1244         {
1245           nd.setLeft(top.right());
1246         }
1247         else
1248         {
1249           nd.setLeft(top.left());
1250         }
1251       }
1252     }
1253   }
1254
1255   /**
1256    * DOCUMENT ME!
1257    * 
1258    * @return DOCUMENT ME!
1259    */
1260   public SequenceNode getMaxDist()
1261   {
1262     return maxdist;
1263   }
1264
1265   /**
1266    * DOCUMENT ME!
1267    * 
1268    * @return DOCUMENT ME!
1269    */
1270   public SequenceNode getTopNode()
1271   {
1272     return top;
1273   }
1274
1275   /**
1276    * 
1277    * @return true if tree has real distances
1278    */
1279   public boolean isHasDistances()
1280   {
1281     return hasDistances;
1282   }
1283
1284   /**
1285    * 
1286    * @return true if tree has real bootstrap values
1287    */
1288   public boolean isHasBootstrap()
1289   {
1290     return hasBootstrap;
1291   }
1292
1293   public boolean isHasRootDistance()
1294   {
1295     return hasRootDistance;
1296   }
1297
1298   /**
1299    * apply the given transform to all the nodes in the tree.
1300    * 
1301    * @param nodeTransformI
1302    */
1303   public void applyToNodes(NodeTransformI nodeTransformI)
1304   {
1305     for (Enumeration<SequenceNode> nodes = node.elements(); nodes
1306             .hasMoreElements(); nodeTransformI
1307             .transform(nodes.nextElement()))
1308     {
1309       ;
1310     }
1311   }
1312 }
1313
1314 /**
1315  * DOCUMENT ME!
1316  * 
1317  * @author $author$
1318  * @version $Revision$
1319  */
1320 class Cluster
1321 {
1322   int[] value;
1323
1324   /**
1325    * Creates a new Cluster object.
1326    * 
1327    * @param value
1328    *          DOCUMENT ME!
1329    */
1330   public Cluster(int[] value)
1331   {
1332     this.value = value;
1333   }
1334 }