JAL-1114 - refactor methods handling Vectors and Hashtables to Lists and Maps, and...
[jalview.git] / src / jalview / datamodel / SequenceGroup.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.7)
3  * Copyright (C) 2011 J Procter, AM Waterhouse, J Engelhardt, LM Lui, G Barton, M Clamp, S Searle
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 of the License, or (at your option) any later version.
10  * 
11  * Jalview is distributed in the hope that it will be useful, but 
12  * WITHOUT ANY WARRANTY; without even the implied warranty 
13  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
14  * PURPOSE.  See the GNU General Public License for more details.
15  * 
16  * You should have received a copy of the GNU General Public License along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 package jalview.datamodel;
19
20 import java.util.*;
21 import java.util.List;
22
23 import java.awt.*;
24
25 import jalview.analysis.*;
26 import jalview.schemes.*;
27
28 /**
29  * Collects a set contiguous ranges on a set of sequences
30  * 
31  * @author $author$
32  * @version $Revision$
33  */
34 public class SequenceGroup implements AnnotatedCollectionI
35 {
36   String groupName;
37
38   String description;
39
40   Conservation conserve;
41
42   Vector aaFrequency;
43
44   boolean displayBoxes = true;
45
46   boolean displayText = true;
47
48   boolean colourText = false;
49
50   /**
51    * after Olivier's non-conserved only character display
52    */
53   boolean showNonconserved = false;
54
55   /**
56    * group members
57    */
58   private Vector<SequenceI> sequences = new Vector<SequenceI>();
59
60   /**
61    * representative sequence for this group (if any)
62    */
63   private SequenceI seqrep = null;
64
65   int width = -1;
66
67   /**
68    * Colourscheme applied to group if any
69    */
70   public ColourSchemeI cs;
71
72   int startRes = 0;
73
74   int endRes = 0;
75
76   public Color outlineColour = Color.black;
77
78   public Color idColour = null;
79
80   public int thresholdTextColour = 0;
81
82   public Color textColour = Color.black;
83
84   public Color textColour2 = Color.white;
85
86   /**
87    * consensus calculation property
88    */
89   private boolean ignoreGapsInConsensus = true;
90
91   /**
92    * consensus calculation property
93    */
94   private boolean showSequenceLogo = false;
95   /**
96    * flag indicating if logo should be rendered normalised
97    */
98   private boolean normaliseSequenceLogo;
99
100
101   /**
102    * @return the includeAllConsSymbols
103    */
104   public boolean isShowSequenceLogo()
105   {
106     return showSequenceLogo;
107   }
108
109   /**
110    * Creates a new SequenceGroup object.
111    */
112   public SequenceGroup()
113   {
114     groupName = "JGroup:" + this.hashCode();
115   }
116
117   /**
118    * Creates a new SequenceGroup object.
119    * 
120    * @param sequences
121    * @param groupName
122    * @param scheme
123    * @param displayBoxes
124    * @param displayText
125    * @param colourText
126    * @param start
127    *          first column of group
128    * @param end
129    *          last column of group
130    */
131   public SequenceGroup(Vector sequences, String groupName,
132           ColourSchemeI scheme, boolean displayBoxes, boolean displayText,
133           boolean colourText, int start, int end)
134   {
135     this.sequences = sequences;
136     this.groupName = groupName;
137     this.displayBoxes = displayBoxes;
138     this.displayText = displayText;
139     this.colourText = colourText;
140     this.cs = scheme;
141     startRes = start;
142     endRes = end;
143     recalcConservation();
144   }
145
146   /**
147    * copy constructor
148    * 
149    * @param seqsel
150    */
151   public SequenceGroup(SequenceGroup seqsel)
152   {
153     if (seqsel != null)
154     {
155       sequences = new Vector();
156       Enumeration<SequenceI> sq = seqsel.sequences.elements();
157       while (sq.hasMoreElements())
158       {
159         sequences.addElement(sq.nextElement());
160       }
161       ;
162       if (seqsel.groupName != null)
163       {
164         groupName = new String(seqsel.groupName);
165       }
166       displayBoxes = seqsel.displayBoxes;
167       displayText = seqsel.displayText;
168       colourText = seqsel.colourText;
169       startRes = seqsel.startRes;
170       endRes = seqsel.endRes;
171       cs = seqsel.cs;
172       if (seqsel.description != null)
173         description = new String(seqsel.description);
174       hidecols = seqsel.hidecols;
175       hidereps = seqsel.hidereps;
176       idColour = seqsel.idColour;
177       outlineColour = seqsel.outlineColour;
178       seqrep = seqsel.seqrep;
179       textColour = seqsel.textColour;
180       textColour2 = seqsel.textColour2;
181       thresholdTextColour = seqsel.thresholdTextColour;
182       width = seqsel.width;
183       ignoreGapsInConsensus = seqsel.ignoreGapsInConsensus;
184       if (seqsel.conserve != null)
185       {
186         recalcConservation(); // safer than
187         // aaFrequency = (Vector) seqsel.aaFrequency.clone(); // ??
188       }
189     }
190   }
191
192   public SequenceI[] getSelectionAsNewSequences(AlignmentI align)
193   {
194     int iSize = sequences.size();
195     SequenceI[] seqs = new SequenceI[iSize];
196     SequenceI[] inorder = getSequencesInOrder(align);
197
198     for (int i = 0, ipos = 0; i < inorder.length; i++)
199     {
200       SequenceI seq = inorder[i];
201
202       seqs[ipos] = seq.getSubSequence(startRes, endRes + 1);
203       if (seqs[ipos] != null)
204       {
205         seqs[ipos].setDescription(seq.getDescription());
206         seqs[ipos].setDBRef(seq.getDBRef());
207         seqs[ipos].setSequenceFeatures(seq.getSequenceFeatures());
208         if (seq.getDatasetSequence() != null)
209         {
210           seqs[ipos].setDatasetSequence(seq.getDatasetSequence());
211         }
212
213         if (seq.getAnnotation() != null)
214         {
215           AlignmentAnnotation[] alann = align.getAlignmentAnnotation();
216           // Only copy annotation that is either a score or referenced by the
217           // alignment's annotation vector
218           for (int a = 0; a < seq.getAnnotation().length; a++)
219           {
220             AlignmentAnnotation tocopy = seq.getAnnotation()[a];
221             if (alann != null)
222             {
223               boolean found = false;
224               for (int pos = 0; pos < alann.length; pos++)
225               {
226                 if (alann[pos] == tocopy)
227                 {
228                   found = true;
229                   break;
230                 }
231               }
232               if (!found)
233                 continue;
234             }
235             AlignmentAnnotation newannot = new AlignmentAnnotation(
236                     seq.getAnnotation()[a]);
237             newannot.restrict(startRes, endRes);
238             newannot.setSequenceRef(seqs[ipos]);
239             newannot.adjustForAlignment();
240             seqs[ipos].addAlignmentAnnotation(newannot);
241           }
242         }
243         ipos++;
244       }
245       else
246       {
247         iSize--;
248       }
249     }
250     if (iSize != inorder.length)
251     {
252       SequenceI[] nseqs = new SequenceI[iSize];
253       System.arraycopy(seqs, 0, nseqs, 0, iSize);
254       seqs = nseqs;
255     }
256     return seqs;
257
258   }
259
260   /**
261    * If sequence ends in gaps, the end residue can be correctly calculated here
262    * 
263    * @param seq
264    *          SequenceI
265    * @return int
266    */
267   public int findEndRes(SequenceI seq)
268   {
269     int eres = 0;
270     char ch;
271
272     for (int j = 0; j < endRes + 1 && j < seq.getLength(); j++)
273     {
274       ch = seq.getCharAt(j);
275       if (!jalview.util.Comparison.isGap((ch)))
276       {
277         eres++;
278       }
279     }
280
281     if (eres > 0)
282     {
283       eres += seq.getStart() - 1;
284     }
285
286     return eres;
287   }
288   public List<SequenceI> getSequences()
289   {
290     return sequences;
291   }
292   public List<SequenceI> getSequences(Map<SequenceI, SequenceCollectionI> hiddenReps)
293   {
294     if (hiddenReps == null)
295     {
296       return sequences;
297     }
298     else
299     {
300       Vector allSequences = new Vector();
301       SequenceI seq;
302       for (int i = 0; i < sequences.size(); i++)
303       {
304         seq = (SequenceI) sequences.elementAt(i);
305         allSequences.addElement(seq);
306         if (hiddenReps.containsKey(seq))
307         {
308           SequenceCollectionI hsg = hiddenReps.get(seq);
309           for (SequenceI seq2:hsg.getSequences())
310           {
311             if (seq2 != seq && !allSequences.contains(seq2))
312             {
313               allSequences.addElement(seq2);
314             }
315           }
316         }
317       }
318
319       return allSequences;
320     }
321   }
322
323   public SequenceI[] getSequencesAsArray(Map<SequenceI, SequenceCollectionI> map)
324   {
325     List<SequenceI> tmp = getSequences(map);
326     if (tmp==null)
327     {
328       return null;
329     }
330     return tmp.toArray(new SequenceI[tmp.size()]);
331   }
332
333   /**
334    * DOCUMENT ME!
335    * 
336    * @param col
337    *          DOCUMENT ME!
338    * 
339    * @return DOCUMENT ME!
340    */
341   public boolean adjustForRemoveLeft(int col)
342   {
343     // return value is true if the group still exists
344     if (startRes >= col)
345     {
346       startRes = startRes - col;
347     }
348
349     if (endRes >= col)
350     {
351       endRes = endRes - col;
352
353       if (startRes > endRes)
354       {
355         startRes = 0;
356       }
357     }
358     else
359     {
360       // must delete this group!!
361       return false;
362     }
363
364     return true;
365   }
366
367   /**
368    * DOCUMENT ME!
369    * 
370    * @param col
371    *          DOCUMENT ME!
372    * 
373    * @return DOCUMENT ME!
374    */
375   public boolean adjustForRemoveRight(int col)
376   {
377     if (startRes > col)
378     {
379       // delete this group
380       return false;
381     }
382
383     if (endRes >= col)
384     {
385       endRes = col;
386     }
387
388     return true;
389   }
390
391   /**
392    * DOCUMENT ME!
393    * 
394    * @return DOCUMENT ME!
395    */
396   public String getName()
397   {
398     return groupName;
399   }
400
401   public String getDescription()
402   {
403     return description;
404   }
405
406   /**
407    * DOCUMENT ME!
408    * 
409    * @param name
410    *          DOCUMENT ME!
411    */
412   public void setName(String name)
413   {
414     groupName = name;
415     // TODO: URGENT: update dependent objects (annotation row)
416   }
417
418   public void setDescription(String desc)
419   {
420     description = desc;
421   }
422
423   /**
424    * DOCUMENT ME!
425    * 
426    * @return DOCUMENT ME!
427    */
428   public Conservation getConservation()
429   {
430     return conserve;
431   }
432
433   /**
434    * DOCUMENT ME!
435    * 
436    * @param c
437    *          DOCUMENT ME!
438    */
439   public void setConservation(Conservation c)
440   {
441     conserve = c;
442   }
443
444   /**
445    * Add s to this sequence group. If aligment sequence is already contained in
446    * group, it will not be added again, but recalculation may happen if the flag
447    * is set.
448    * 
449    * @param s
450    *          alignment sequence to be added
451    * @param recalc
452    *          true means Group's conservation should be recalculated
453    */
454   public void addSequence(SequenceI s, boolean recalc)
455   {
456     if (s != null && !sequences.contains(s))
457     {
458       sequences.addElement(s);
459     }
460
461     if (recalc)
462     {
463       recalcConservation();
464     }
465   }
466
467   /**
468    * Max Gaps Threshold for performing a conservation calculation TODO: make
469    * this a configurable property - or global to an alignment view
470    */
471   private int consPercGaps = 25;
472
473   /**
474    * calculate residue conservation for group - but only if necessary.
475    */
476   public void recalcConservation()
477   {
478     if (cs == null && consensus == null && conservation == null)
479     {
480       return;
481     }
482     if (cs!=null)
483     {
484       cs.alignmentChanged(this,null);
485     }
486     try
487     {
488       Hashtable cnsns[] = AAFrequency.calculate(sequences, startRes,
489               endRes + 1, showSequenceLogo);
490       if (consensus != null)
491       {
492         _updateConsensusRow(cnsns);
493       }
494       if (cs != null)
495       {
496         cs.setConsensus(cnsns);
497         cs.alignmentChanged(this,null);
498       }
499
500       if ((conservation != null)
501               || (cs != null && cs.conservationApplied()))
502       {
503         Conservation c = new Conservation(groupName,
504                 ResidueProperties.propHash, 3, sequences, startRes,
505                 endRes + 1);
506         c.calculate();
507         c.verdict(false, consPercGaps);
508         if (conservation != null)
509         {
510           _updateConservationRow(c);
511         }
512         if (cs != null)
513         {
514           if (cs.conservationApplied())
515           {
516             cs.setConservation(c);
517             cs.alignmentChanged(this,null);
518           }
519         }
520       }
521     } catch (java.lang.OutOfMemoryError err)
522     {
523       // TODO: catch OOM
524       System.out.println("Out of memory loading groups: " + err);
525     }
526
527   }
528
529   private void _updateConservationRow(Conservation c)
530   {
531     if (conservation == null)
532     {
533       getConservation();
534     }
535     // update Labels
536     conservation.label = "Conservation for " + getName();
537     conservation.description = "Conservation for group " + getName()
538             + " less than " + consPercGaps + "% gaps";
539     // preserve width if already set
540     int aWidth = (conservation.annotations != null) ? (endRes < conservation.annotations.length ? conservation.annotations.length
541             : endRes + 1)
542             : endRes + 1;
543     conservation.annotations = null;
544     conservation.annotations = new Annotation[aWidth]; // should be alignment
545                                                        // width
546     c.completeAnnotations(conservation, null, startRes, endRes + 1);
547   }
548
549   public Hashtable[] consensusData = null;
550
551   private void _updateConsensusRow(Hashtable[] cnsns)
552   {
553     if (consensus == null)
554     {
555       getConsensus();
556     }
557     consensus.label = "Consensus for " + getName();
558     consensus.description = "Percent Identity";
559     consensusData = cnsns;
560     // preserve width if already set
561     int aWidth = (consensus.annotations != null) ? (endRes < consensus.annotations.length ? consensus.annotations.length
562             : endRes + 1)
563             : endRes + 1;
564     consensus.annotations = null;
565     consensus.annotations = new Annotation[aWidth]; // should be alignment width
566
567     AAFrequency.completeConsensus(consensus, cnsns, startRes, endRes + 1,
568             ignoreGapsInConsensus, showSequenceLogo); // TODO: setting container
569                                                       // for
570                                                       // ignoreGapsInConsensusCalculation);
571   }
572
573   /**
574    * @param s
575    *          sequence to either add or remove from group
576    * @param recalc
577    *          flag passed to delete/addSequence to indicate if group properties
578    *          should be recalculated
579    */
580   public void addOrRemove(SequenceI s, boolean recalc)
581   {
582     if (sequences.contains(s))
583     {
584       deleteSequence(s, recalc);
585     }
586     else
587     {
588       addSequence(s, recalc);
589     }
590   }
591
592   /**
593    * DOCUMENT ME!
594    * 
595    * @param s
596    *          DOCUMENT ME!
597    * @param recalc
598    *          DOCUMENT ME!
599    */
600   public void deleteSequence(SequenceI s, boolean recalc)
601   {
602     sequences.removeElement(s);
603
604     if (recalc)
605     {
606       recalcConservation();
607     }
608   }
609
610   /**
611    * DOCUMENT ME!
612    * 
613    * @return DOCUMENT ME!
614    */
615   public int getStartRes()
616   {
617     return startRes;
618   }
619
620   /**
621    * DOCUMENT ME!
622    * 
623    * @return DOCUMENT ME!
624    */
625   public int getEndRes()
626   {
627     return endRes;
628   }
629
630   /**
631    * Set the first column selected by this group. Runs from 0<=i<N_cols
632    * 
633    * @param i
634    */
635   public void setStartRes(int i)
636   {
637     startRes = i;
638   }
639
640   /**
641    * Set the groups last selected column. Runs from 0<=i<N_cols
642    * 
643    * @param i
644    */
645   public void setEndRes(int i)
646   {
647     endRes = i;
648   }
649
650   /**
651    * DOCUMENT ME!
652    * 
653    * @return DOCUMENT ME!
654    */
655   public int getSize()
656   {
657     return sequences.size();
658   }
659
660   /**
661    * DOCUMENT ME!
662    * 
663    * @param i
664    *          DOCUMENT ME!
665    * 
666    * @return DOCUMENT ME!
667    */
668   public SequenceI getSequenceAt(int i)
669   {
670     return (SequenceI) sequences.elementAt(i);
671   }
672
673   /**
674    * DOCUMENT ME!
675    * 
676    * @param state
677    *          DOCUMENT ME!
678    */
679   public void setColourText(boolean state)
680   {
681     colourText = state;
682   }
683
684   /**
685    * DOCUMENT ME!
686    * 
687    * @return DOCUMENT ME!
688    */
689   public boolean getColourText()
690   {
691     return colourText;
692   }
693
694   /**
695    * DOCUMENT ME!
696    * 
697    * @param state
698    *          DOCUMENT ME!
699    */
700   public void setDisplayText(boolean state)
701   {
702     displayText = state;
703   }
704
705   /**
706    * DOCUMENT ME!
707    * 
708    * @return DOCUMENT ME!
709    */
710   public boolean getDisplayText()
711   {
712     return displayText;
713   }
714
715   /**
716    * DOCUMENT ME!
717    * 
718    * @param state
719    *          DOCUMENT ME!
720    */
721   public void setDisplayBoxes(boolean state)
722   {
723     displayBoxes = state;
724   }
725
726   /**
727    * DOCUMENT ME!
728    * 
729    * @return DOCUMENT ME!
730    */
731   public boolean getDisplayBoxes()
732   {
733     return displayBoxes;
734   }
735
736   /**
737    * DOCUMENT ME!
738    * 
739    * @return DOCUMENT ME!
740    */
741   public int getWidth()
742   {
743     // MC This needs to get reset when characters are inserted and deleted
744     if (sequences.size() > 0)
745     {
746       width = ((SequenceI) sequences.elementAt(0)).getLength();
747     }
748
749     for (int i = 1; i < sequences.size(); i++)
750     {
751       SequenceI seq = (SequenceI) sequences.elementAt(i);
752
753       if (seq.getLength() > width)
754       {
755         width = seq.getLength();
756       }
757     }
758
759     return width;
760   }
761
762   /**
763    * DOCUMENT ME!
764    * 
765    * @param c
766    *          DOCUMENT ME!
767    */
768   public void setOutlineColour(Color c)
769   {
770     outlineColour = c;
771   }
772
773   /**
774    * DOCUMENT ME!
775    * 
776    * @return DOCUMENT ME!
777    */
778   public Color getOutlineColour()
779   {
780     return outlineColour;
781   }
782
783   /**
784    * 
785    * returns the sequences in the group ordered by the ordering given by al.
786    * this used to return an array with null entries regardless, new behaviour is
787    * below. TODO: verify that this does not affect use in applet or application
788    * 
789    * @param al
790    *          Alignment
791    * @return SequenceI[] intersection of sequences in group with al, ordered by
792    *         al, or null if group does not intersect with al
793    */
794   public SequenceI[] getSequencesInOrder(AlignmentI al)
795   {
796     return getSequencesInOrder(al, true);
797   }
798
799   /**
800    * return an array representing the intersection of the group with al,
801    * optionally returning an array the size of al.getHeight() where nulls mark
802    * the non-intersected sequences
803    * 
804    * @param al
805    * @param trim
806    * @return null or array
807    */
808   public SequenceI[] getSequencesInOrder(AlignmentI al, boolean trim)
809   {
810     int sSize = sequences.size();
811     int alHeight = al.getHeight();
812
813     SequenceI[] seqs = new SequenceI[(trim) ? sSize : alHeight];
814
815     int index = 0;
816     for (int i = 0; i < alHeight && index < sSize; i++)
817     {
818       if (sequences.contains(al.getSequenceAt(i)))
819       {
820         seqs[(trim) ? index : i] = al.getSequenceAt(i);
821         index++;
822       }
823     }
824     if (index == 0)
825     {
826       return null;
827     }
828     if (!trim)
829     {
830       return seqs;
831     }
832     if (index < seqs.length)
833     {
834       SequenceI[] dummy = seqs;
835       seqs = new SequenceI[index];
836       while (--index >= 0)
837       {
838         seqs[index] = dummy[index];
839         dummy[index] = null;
840       }
841     }
842     return seqs;
843   }
844
845   /**
846    * @return the idColour
847    */
848   public Color getIdColour()
849   {
850     return idColour;
851   }
852
853   /**
854    * @param idColour
855    *          the idColour to set
856    */
857   public void setIdColour(Color idColour)
858   {
859     this.idColour = idColour;
860   }
861
862   /**
863    * @return the representative sequence for this group
864    */
865   public SequenceI getSeqrep()
866   {
867     return seqrep;
868   }
869
870   /**
871    * set the representative sequence for this group. Note - this affects the
872    * interpretation of the Hidereps attribute.
873    * 
874    * @param seqrep
875    *          the seqrep to set (null means no sequence representative)
876    */
877   public void setSeqrep(SequenceI seqrep)
878   {
879     this.seqrep = seqrep;
880   }
881
882   /**
883    * 
884    * @return true if group has a sequence representative
885    */
886   public boolean hasSeqrep()
887   {
888     return seqrep != null;
889   }
890
891   /**
892    * visibility of rows or represented rows covered by group
893    */
894   private boolean hidereps = false;
895
896   /**
897    * set visibility of sequences covered by (if no sequence representative is
898    * defined) or represented by this group.
899    * 
900    * @param visibility
901    */
902   public void setHidereps(boolean visibility)
903   {
904     hidereps = visibility;
905   }
906
907   /**
908    * 
909    * @return true if sequences represented (or covered) by this group should be
910    *         hidden
911    */
912   public boolean isHidereps()
913   {
914     return hidereps;
915   }
916
917   /**
918    * visibility of columns intersecting this group
919    */
920   private boolean hidecols = false;
921
922   /**
923    * set intended visibility of columns covered by this group
924    * 
925    * @param visibility
926    */
927   public void setHideCols(boolean visibility)
928   {
929     hidecols = visibility;
930   }
931
932   /**
933    * 
934    * @return true if columns covered by group should be hidden
935    */
936   public boolean isHideCols()
937   {
938     return hidecols;
939   }
940
941   /**
942    * create a new sequence group from the intersection of this group with an
943    * alignment Hashtable of hidden representatives
944    * 
945    * @param alignment
946    *          (may not be null)
947    * @param map
948    *          (may be null)
949    * @return new group containing sequences common to this group and alignment
950    */
951   public SequenceGroup intersect(AlignmentI alignment, Map<SequenceI, SequenceCollectionI> map)
952   {
953     SequenceGroup sgroup = new SequenceGroup(this);
954     SequenceI[] insect = getSequencesInOrder(alignment);
955     sgroup.sequences = new Vector();
956     for (int s = 0; insect != null && s < insect.length; s++)
957     {
958       if (map == null || map.containsKey(insect[s]))
959       {
960         sgroup.sequences.addElement(insect[s]);
961       }
962     }
963     // Enumeration en =getSequences(hashtable).elements();
964     // while (en.hasMoreElements())
965     // {
966     // SequenceI elem = (SequenceI) en.nextElement();
967     // if (alignment.getSequences().contains(elem))
968     // {
969     // sgroup.addSequence(elem, false);
970     // }
971     // }
972     return sgroup;
973   }
974
975   /**
976    * @return the showUnconserved
977    */
978   public boolean getShowNonconserved()
979   {
980     return showNonconserved;
981   }
982
983   /**
984    * @param showNonconserved
985    *          the showUnconserved to set
986    */
987   public void setShowNonconserved(boolean displayNonconserved)
988   {
989     this.showNonconserved = displayNonconserved;
990   }
991
992   AlignmentAnnotation consensus = null, conservation = null;
993
994   /**
995    * flag indicating if consensus histogram should be rendered
996    */
997   private boolean showConsensusHistogram;
998
999   /**
1000    * set this alignmentAnnotation object as the one used to render consensus
1001    * annotation
1002    * 
1003    * @param aan
1004    */
1005   public void setConsensus(AlignmentAnnotation aan)
1006   {
1007     if (consensus == null)
1008     {
1009       consensus = aan;
1010     }
1011   }
1012
1013   /**
1014    * 
1015    * @return automatically calculated consensus row
1016    */
1017   public AlignmentAnnotation getConsensus()
1018   {
1019     // TODO get or calculate and get consensus annotation row for this group
1020     int aWidth = this.getWidth();
1021     // pointer
1022     // possibility
1023     // here.
1024     if (aWidth < 0)
1025     {
1026       return null;
1027     }
1028     if (consensus == null)
1029     {
1030       consensus = new AlignmentAnnotation("", "", new Annotation[1], 0f,
1031               100f, AlignmentAnnotation.BAR_GRAPH);
1032     }
1033     consensus.hasText = true;
1034     consensus.autoCalculated = true;
1035     consensus.groupRef = this;
1036     consensus.label = "Consensus for " + getName();
1037     consensus.description = "Percent Identity";
1038     return consensus;
1039   }
1040
1041   /**
1042    * set this alignmentAnnotation object as the one used to render consensus
1043    * annotation
1044    * 
1045    * @param aan
1046    */
1047   public void setConservationRow(AlignmentAnnotation aan)
1048   {
1049     if (conservation == null)
1050     {
1051       conservation = aan;
1052     }
1053   }
1054
1055   /**
1056    * get the conservation annotation row for this group
1057    * 
1058    * @return autoCalculated annotation row
1059    */
1060   public AlignmentAnnotation getConservationRow()
1061   {
1062     if (conservation == null)
1063     {
1064       conservation = new AlignmentAnnotation("", "", new Annotation[1], 0f,
1065               11f, AlignmentAnnotation.BAR_GRAPH);
1066     }
1067
1068     conservation.hasText = true;
1069     conservation.autoCalculated = true;
1070     conservation.groupRef = this;
1071     conservation.label = "Conservation for " + getName();
1072     conservation.description = "Conservation for group " + getName()
1073             + " less than " + consPercGaps + "% gaps";
1074     return conservation;
1075   }
1076
1077   /**
1078    * 
1079    * @return true if annotation rows have been instantiated for this group
1080    */
1081   public boolean hasAnnotationRows()
1082   {
1083     return consensus != null || conservation != null;
1084   }
1085
1086   public SequenceI getConsensusSeq()
1087   {
1088     getConsensus();
1089     StringBuffer seqs = new StringBuffer();
1090     for (int i = 0; i < consensus.annotations.length; i++)
1091     {
1092       if (consensus.annotations[i] != null)
1093       {
1094         if (consensus.annotations[i].description.charAt(0) == '[')
1095         {
1096           seqs.append(consensus.annotations[i].description.charAt(1));
1097         }
1098         else
1099         {
1100           seqs.append(consensus.annotations[i].displayCharacter);
1101         }
1102       }
1103     }
1104
1105     SequenceI sq = new Sequence("Group" + getName() + " Consensus",
1106             seqs.toString());
1107     sq.setDescription("Percentage Identity Consensus "
1108             + ((ignoreGapsInConsensus) ? " without gaps" : ""));
1109     return sq;
1110   }
1111
1112   public void setIgnoreGapsConsensus(boolean state)
1113   {
1114     if (this.ignoreGapsInConsensus != state && consensus != null)
1115     {
1116       ignoreGapsInConsensus = state;
1117       recalcConservation();
1118     }
1119     ignoreGapsInConsensus = state;
1120   }
1121
1122   public boolean getIgnoreGapsConsensus()
1123   {
1124     return ignoreGapsInConsensus;
1125   }
1126
1127   /**
1128    * @param showSequenceLogo
1129    *          indicates if a sequence logo is shown for consensus annotation
1130    */
1131   public void setshowSequenceLogo(boolean showSequenceLogo)
1132   {
1133     // TODO: decouple calculation from settings update
1134     if (this.showSequenceLogo != showSequenceLogo && consensus != null)
1135     {
1136       this.showSequenceLogo = showSequenceLogo;
1137       recalcConservation();
1138     }
1139     this.showSequenceLogo = showSequenceLogo;
1140   }
1141
1142   /**
1143    * 
1144    * @param showConsHist
1145    *          flag indicating if the consensus histogram for this group should
1146    *          be rendered
1147    */
1148   public void setShowConsensusHistogram(boolean showConsHist)
1149   {
1150
1151     if (showConsensusHistogram != showConsHist && consensus != null)
1152     {
1153       this.showConsensusHistogram = showConsHist;
1154       recalcConservation();
1155     }
1156     this.showConsensusHistogram = showConsHist;
1157   }
1158
1159   /**
1160    * @return the showConsensusHistogram
1161    */
1162   public boolean isShowConsensusHistogram()
1163   {
1164     return showConsensusHistogram;
1165   }
1166
1167   /**
1168    * set flag indicating if logo should be normalised when rendered
1169    * @param norm
1170    */
1171   public void setNormaliseSequenceLogo(boolean norm)
1172   {
1173     normaliseSequenceLogo=norm;
1174   }
1175   public boolean isNormaliseSequenceLogo()
1176   {
1177     return normaliseSequenceLogo;
1178   }
1179   @Override
1180   /**
1181    * returns a new array with all annotation involving this group
1182    */
1183   public AlignmentAnnotation[] getAlignmentAnnotation()
1184   {
1185     // TODO add in other methods like 'getAlignmentAnnotation(String label), etc'
1186     ArrayList<AlignmentAnnotation> annot = new ArrayList<AlignmentAnnotation>();
1187     for (SequenceI seq:(Vector<SequenceI>)sequences)
1188     {
1189       for (AlignmentAnnotation al: seq.getAnnotation())
1190       {
1191         if (al.groupRef==this)
1192         {
1193           annot.add(al);
1194         }
1195       }
1196     }
1197     if (consensus!=null)
1198     {
1199       annot.add(consensus);
1200     }
1201     if (conservation!=null)
1202     {
1203       annot.add(conservation);
1204     }
1205     return annot.toArray(new AlignmentAnnotation[0]);
1206   }
1207   @Override
1208   public Iterable<AlignmentAnnotation> findAnnotation(String calcId)
1209   {
1210     ArrayList<AlignmentAnnotation> aa=new ArrayList<AlignmentAnnotation>();
1211     for (AlignmentAnnotation a:getAlignmentAnnotation())
1212     {
1213       if (a.getCalcId()==calcId)
1214       {
1215         aa.add(a);
1216       }
1217     }
1218     return aa;
1219   }
1220
1221   public void clear()
1222   {
1223     sequences.clear();
1224   }
1225 }