0bd7266e69abff83cbdca3e0873bb56837ec83f7
[jalview.git] / src / jalview / datamodel / SequenceGroup.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8)
3  * Copyright (C) 2012 J Procter, AM Waterhouse, LM Lui, J Engelhardt, 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   /**
97    * flag indicating if logo should be rendered normalised
98    */
99   private boolean normaliseSequenceLogo;
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
289   public List<SequenceI> getSequences()
290   {
291     return sequences;
292   }
293
294   public List<SequenceI> getSequences(
295           Map<SequenceI, SequenceCollectionI> hiddenReps)
296   {
297     if (hiddenReps == null)
298     {
299       return sequences;
300     }
301     else
302     {
303       Vector allSequences = new Vector();
304       SequenceI seq;
305       for (int i = 0; i < sequences.size(); i++)
306       {
307         seq = (SequenceI) sequences.elementAt(i);
308         allSequences.addElement(seq);
309         if (hiddenReps.containsKey(seq))
310         {
311           SequenceCollectionI hsg = hiddenReps.get(seq);
312           for (SequenceI seq2 : hsg.getSequences())
313           {
314             if (seq2 != seq && !allSequences.contains(seq2))
315             {
316               allSequences.addElement(seq2);
317             }
318           }
319         }
320       }
321
322       return allSequences;
323     }
324   }
325
326   public SequenceI[] getSequencesAsArray(
327           Map<SequenceI, SequenceCollectionI> map)
328   {
329     List<SequenceI> tmp = getSequences(map);
330     if (tmp == null)
331     {
332       return null;
333     }
334     return tmp.toArray(new SequenceI[tmp.size()]);
335   }
336
337   /**
338    * DOCUMENT ME!
339    * 
340    * @param col
341    *          DOCUMENT ME!
342    * 
343    * @return DOCUMENT ME!
344    */
345   public boolean adjustForRemoveLeft(int col)
346   {
347     // return value is true if the group still exists
348     if (startRes >= col)
349     {
350       startRes = startRes - col;
351     }
352
353     if (endRes >= col)
354     {
355       endRes = endRes - col;
356
357       if (startRes > endRes)
358       {
359         startRes = 0;
360       }
361     }
362     else
363     {
364       // must delete this group!!
365       return false;
366     }
367
368     return true;
369   }
370
371   /**
372    * DOCUMENT ME!
373    * 
374    * @param col
375    *          DOCUMENT ME!
376    * 
377    * @return DOCUMENT ME!
378    */
379   public boolean adjustForRemoveRight(int col)
380   {
381     if (startRes > col)
382     {
383       // delete this group
384       return false;
385     }
386
387     if (endRes >= col)
388     {
389       endRes = col;
390     }
391
392     return true;
393   }
394
395   /**
396    * DOCUMENT ME!
397    * 
398    * @return DOCUMENT ME!
399    */
400   public String getName()
401   {
402     return groupName;
403   }
404
405   public String getDescription()
406   {
407     return description;
408   }
409
410   /**
411    * DOCUMENT ME!
412    * 
413    * @param name
414    *          DOCUMENT ME!
415    */
416   public void setName(String name)
417   {
418     groupName = name;
419     // TODO: URGENT: update dependent objects (annotation row)
420   }
421
422   public void setDescription(String desc)
423   {
424     description = desc;
425   }
426
427   /**
428    * DOCUMENT ME!
429    * 
430    * @return DOCUMENT ME!
431    */
432   public Conservation getConservation()
433   {
434     return conserve;
435   }
436
437   /**
438    * DOCUMENT ME!
439    * 
440    * @param c
441    *          DOCUMENT ME!
442    */
443   public void setConservation(Conservation c)
444   {
445     conserve = c;
446   }
447
448   /**
449    * Add s to this sequence group. If aligment sequence is already contained in
450    * group, it will not be added again, but recalculation may happen if the flag
451    * is set.
452    * 
453    * @param s
454    *          alignment sequence to be added
455    * @param recalc
456    *          true means Group's conservation should be recalculated
457    */
458   public void addSequence(SequenceI s, boolean recalc)
459   {
460     if (s != null && !sequences.contains(s))
461     {
462       sequences.addElement(s);
463     }
464
465     if (recalc)
466     {
467       recalcConservation();
468     }
469   }
470
471   /**
472    * Max Gaps Threshold for performing a conservation calculation TODO: make
473    * this a configurable property - or global to an alignment view
474    */
475   private int consPercGaps = 25;
476
477   /**
478    * calculate residue conservation for group - but only if necessary.
479    */
480   public void recalcConservation()
481   {
482     if (cs == null && consensus == null && conservation == null)
483     {
484       return;
485     }
486     if (cs != null)
487     {
488       cs.alignmentChanged(this, null);
489     }
490     try
491     {
492       Hashtable cnsns[] = AAFrequency.calculate(sequences, startRes,
493               endRes + 1, showSequenceLogo);
494       if (consensus != null)
495       {
496         _updateConsensusRow(cnsns);
497       }
498       if (cs != null)
499       {
500         cs.setConsensus(cnsns);
501         cs.alignmentChanged(this, null);
502       }
503
504       if ((conservation != null)
505               || (cs != null && cs.conservationApplied()))
506       {
507         Conservation c = new Conservation(groupName,
508                 ResidueProperties.propHash, 3, sequences, startRes,
509                 endRes + 1);
510         c.calculate();
511         c.verdict(false, consPercGaps);
512         if (conservation != null)
513         {
514           _updateConservationRow(c);
515         }
516         if (cs != null)
517         {
518           if (cs.conservationApplied())
519           {
520             cs.setConservation(c);
521             cs.alignmentChanged(this, null);
522           }
523         }
524       }
525     } catch (java.lang.OutOfMemoryError err)
526     {
527       // TODO: catch OOM
528       System.out.println("Out of memory loading groups: " + err);
529     }
530
531   }
532
533   private void _updateConservationRow(Conservation c)
534   {
535     if (conservation == null)
536     {
537       getConservation();
538     }
539     // update Labels
540     conservation.label = "Conservation for " + getName();
541     conservation.description = "Conservation for group " + getName()
542             + " less than " + consPercGaps + "% gaps";
543     // preserve width if already set
544     int aWidth = (conservation.annotations != null) ? (endRes < conservation.annotations.length ? conservation.annotations.length
545             : endRes + 1)
546             : endRes + 1;
547     conservation.annotations = null;
548     conservation.annotations = new Annotation[aWidth]; // should be alignment
549                                                        // width
550     c.completeAnnotations(conservation, null, startRes, endRes + 1);
551   }
552
553   public Hashtable[] consensusData = null;
554
555   private void _updateConsensusRow(Hashtable[] cnsns)
556   {
557     if (consensus == null)
558     {
559       getConsensus();
560     }
561     consensus.label = "Consensus for " + getName();
562     consensus.description = "Percent Identity";
563     consensusData = cnsns;
564     // preserve width if already set
565     int aWidth = (consensus.annotations != null) ? (endRes < consensus.annotations.length ? consensus.annotations.length
566             : endRes + 1)
567             : endRes + 1;
568     consensus.annotations = null;
569     consensus.annotations = new Annotation[aWidth]; // should be alignment width
570
571     AAFrequency.completeConsensus(consensus, cnsns, startRes, endRes + 1,
572             ignoreGapsInConsensus, showSequenceLogo); // TODO: setting container
573                                                       // for
574                                                       // ignoreGapsInConsensusCalculation);
575   }
576
577   /**
578    * @param s
579    *          sequence to either add or remove from group
580    * @param recalc
581    *          flag passed to delete/addSequence to indicate if group properties
582    *          should be recalculated
583    */
584   public void addOrRemove(SequenceI s, boolean recalc)
585   {
586     if (sequences.contains(s))
587     {
588       deleteSequence(s, recalc);
589     }
590     else
591     {
592       addSequence(s, recalc);
593     }
594   }
595
596   /**
597    * DOCUMENT ME!
598    * 
599    * @param s
600    *          DOCUMENT ME!
601    * @param recalc
602    *          DOCUMENT ME!
603    */
604   public void deleteSequence(SequenceI s, boolean recalc)
605   {
606     sequences.removeElement(s);
607
608     if (recalc)
609     {
610       recalcConservation();
611     }
612   }
613
614   /**
615    * 
616    * 
617    * @return the first column selected by this group. Runs from 0<=i<N_cols
618    */
619   public int getStartRes()
620   {
621     return startRes;
622   }
623
624   /**
625    * 
626    * @return the groups last selected column. Runs from 0<=i<N_cols
627    */
628   public int getEndRes()
629   {
630     return endRes;
631   }
632
633   /**
634    * Set the first column selected by this group. Runs from 0<=i<N_cols
635    * 
636    * @param i
637    */
638   public void setStartRes(int i)
639   {
640     startRes = i;
641   }
642
643   /**
644    * Set the groups last selected column. Runs from 0<=i<N_cols
645    * 
646    * @param i
647    */
648   public void setEndRes(int i)
649   {
650     endRes = i;
651   }
652
653   /**
654    * DOCUMENT ME!
655    * 
656    * @return DOCUMENT ME!
657    */
658   public int getSize()
659   {
660     return sequences.size();
661   }
662
663   /**
664    * DOCUMENT ME!
665    * 
666    * @param i
667    *          DOCUMENT ME!
668    * 
669    * @return DOCUMENT ME!
670    */
671   public SequenceI getSequenceAt(int i)
672   {
673     return (SequenceI) sequences.elementAt(i);
674   }
675
676   /**
677    * DOCUMENT ME!
678    * 
679    * @param state
680    *          DOCUMENT ME!
681    */
682   public void setColourText(boolean state)
683   {
684     colourText = state;
685   }
686
687   /**
688    * DOCUMENT ME!
689    * 
690    * @return DOCUMENT ME!
691    */
692   public boolean getColourText()
693   {
694     return colourText;
695   }
696
697   /**
698    * DOCUMENT ME!
699    * 
700    * @param state
701    *          DOCUMENT ME!
702    */
703   public void setDisplayText(boolean state)
704   {
705     displayText = state;
706   }
707
708   /**
709    * DOCUMENT ME!
710    * 
711    * @return DOCUMENT ME!
712    */
713   public boolean getDisplayText()
714   {
715     return displayText;
716   }
717
718   /**
719    * DOCUMENT ME!
720    * 
721    * @param state
722    *          DOCUMENT ME!
723    */
724   public void setDisplayBoxes(boolean state)
725   {
726     displayBoxes = state;
727   }
728
729   /**
730    * DOCUMENT ME!
731    * 
732    * @return DOCUMENT ME!
733    */
734   public boolean getDisplayBoxes()
735   {
736     return displayBoxes;
737   }
738
739   /**
740    * DOCUMENT ME!
741    * 
742    * @return DOCUMENT ME!
743    */
744   public int getWidth()
745   {
746     // MC This needs to get reset when characters are inserted and deleted
747     if (sequences.size() > 0)
748     {
749       width = ((SequenceI) sequences.elementAt(0)).getLength();
750     }
751
752     for (int i = 1; i < sequences.size(); i++)
753     {
754       SequenceI seq = (SequenceI) sequences.elementAt(i);
755
756       if (seq.getLength() > width)
757       {
758         width = seq.getLength();
759       }
760     }
761
762     return width;
763   }
764
765   /**
766    * DOCUMENT ME!
767    * 
768    * @param c
769    *          DOCUMENT ME!
770    */
771   public void setOutlineColour(Color c)
772   {
773     outlineColour = c;
774   }
775
776   /**
777    * DOCUMENT ME!
778    * 
779    * @return DOCUMENT ME!
780    */
781   public Color getOutlineColour()
782   {
783     return outlineColour;
784   }
785
786   /**
787    * 
788    * returns the sequences in the group ordered by the ordering given by al.
789    * this used to return an array with null entries regardless, new behaviour is
790    * below. TODO: verify that this does not affect use in applet or application
791    * 
792    * @param al
793    *          Alignment
794    * @return SequenceI[] intersection of sequences in group with al, ordered by
795    *         al, or null if group does not intersect with al
796    */
797   public SequenceI[] getSequencesInOrder(AlignmentI al)
798   {
799     return getSequencesInOrder(al, true);
800   }
801
802   /**
803    * return an array representing the intersection of the group with al,
804    * optionally returning an array the size of al.getHeight() where nulls mark
805    * the non-intersected sequences
806    * 
807    * @param al
808    * @param trim
809    * @return null or array
810    */
811   public SequenceI[] getSequencesInOrder(AlignmentI al, boolean trim)
812   {
813     int sSize = sequences.size();
814     int alHeight = al.getHeight();
815
816     SequenceI[] seqs = new SequenceI[(trim) ? sSize : alHeight];
817
818     int index = 0;
819     for (int i = 0; i < alHeight && index < sSize; i++)
820     {
821       if (sequences.contains(al.getSequenceAt(i)))
822       {
823         seqs[(trim) ? index : i] = al.getSequenceAt(i);
824         index++;
825       }
826     }
827     if (index == 0)
828     {
829       return null;
830     }
831     if (!trim)
832     {
833       return seqs;
834     }
835     if (index < seqs.length)
836     {
837       SequenceI[] dummy = seqs;
838       seqs = new SequenceI[index];
839       while (--index >= 0)
840       {
841         seqs[index] = dummy[index];
842         dummy[index] = null;
843       }
844     }
845     return seqs;
846   }
847
848   /**
849    * @return the idColour
850    */
851   public Color getIdColour()
852   {
853     return idColour;
854   }
855
856   /**
857    * @param idColour
858    *          the idColour to set
859    */
860   public void setIdColour(Color idColour)
861   {
862     this.idColour = idColour;
863   }
864
865   /**
866    * @return the representative sequence for this group
867    */
868   public SequenceI getSeqrep()
869   {
870     return seqrep;
871   }
872
873   /**
874    * set the representative sequence for this group. Note - this affects the
875    * interpretation of the Hidereps attribute.
876    * 
877    * @param seqrep
878    *          the seqrep to set (null means no sequence representative)
879    */
880   public void setSeqrep(SequenceI seqrep)
881   {
882     this.seqrep = seqrep;
883   }
884
885   /**
886    * 
887    * @return true if group has a sequence representative
888    */
889   public boolean hasSeqrep()
890   {
891     return seqrep != null;
892   }
893
894   /**
895    * visibility of rows or represented rows covered by group
896    */
897   private boolean hidereps = false;
898
899   /**
900    * set visibility of sequences covered by (if no sequence representative is
901    * defined) or represented by this group.
902    * 
903    * @param visibility
904    */
905   public void setHidereps(boolean visibility)
906   {
907     hidereps = visibility;
908   }
909
910   /**
911    * 
912    * @return true if sequences represented (or covered) by this group should be
913    *         hidden
914    */
915   public boolean isHidereps()
916   {
917     return hidereps;
918   }
919
920   /**
921    * visibility of columns intersecting this group
922    */
923   private boolean hidecols = false;
924
925   /**
926    * set intended visibility of columns covered by this group
927    * 
928    * @param visibility
929    */
930   public void setHideCols(boolean visibility)
931   {
932     hidecols = visibility;
933   }
934
935   /**
936    * 
937    * @return true if columns covered by group should be hidden
938    */
939   public boolean isHideCols()
940   {
941     return hidecols;
942   }
943
944   /**
945    * create a new sequence group from the intersection of this group with an
946    * alignment Hashtable of hidden representatives
947    * 
948    * @param alignment
949    *          (may not be null)
950    * @param map
951    *          (may be null)
952    * @return new group containing sequences common to this group and alignment
953    */
954   public SequenceGroup intersect(AlignmentI alignment,
955           Map<SequenceI, SequenceCollectionI> map)
956   {
957     SequenceGroup sgroup = new SequenceGroup(this);
958     SequenceI[] insect = getSequencesInOrder(alignment);
959     sgroup.sequences = new Vector();
960     for (int s = 0; insect != null && s < insect.length; s++)
961     {
962       if (map == null || map.containsKey(insect[s]))
963       {
964         sgroup.sequences.addElement(insect[s]);
965       }
966     }
967     // Enumeration en =getSequences(hashtable).elements();
968     // while (en.hasMoreElements())
969     // {
970     // SequenceI elem = (SequenceI) en.nextElement();
971     // if (alignment.getSequences().contains(elem))
972     // {
973     // sgroup.addSequence(elem, false);
974     // }
975     // }
976     return sgroup;
977   }
978
979   /**
980    * @return the showUnconserved
981    */
982   public boolean getShowNonconserved()
983   {
984     return showNonconserved;
985   }
986
987   /**
988    * @param showNonconserved
989    *          the showUnconserved to set
990    */
991   public void setShowNonconserved(boolean displayNonconserved)
992   {
993     this.showNonconserved = displayNonconserved;
994   }
995
996   AlignmentAnnotation consensus = null, conservation = null;
997
998   /**
999    * flag indicating if consensus histogram should be rendered
1000    */
1001   private boolean showConsensusHistogram;
1002
1003   /**
1004    * set this alignmentAnnotation object as the one used to render consensus
1005    * annotation
1006    * 
1007    * @param aan
1008    */
1009   public void setConsensus(AlignmentAnnotation aan)
1010   {
1011     if (consensus == null)
1012     {
1013       consensus = aan;
1014     }
1015   }
1016
1017   /**
1018    * 
1019    * @return automatically calculated consensus row
1020    */
1021   public AlignmentAnnotation getConsensus()
1022   {
1023     // TODO get or calculate and get consensus annotation row for this group
1024     int aWidth = this.getWidth();
1025     // pointer
1026     // possibility
1027     // here.
1028     if (aWidth < 0)
1029     {
1030       return null;
1031     }
1032     if (consensus == null)
1033     {
1034       consensus = new AlignmentAnnotation("", "", new Annotation[1], 0f,
1035               100f, AlignmentAnnotation.BAR_GRAPH);
1036       consensus.hasText = true;
1037       consensus.autoCalculated = true;
1038       consensus.groupRef = this;
1039       consensus.label = "Consensus for " + getName();
1040       consensus.description = "Percent Identity";
1041     }
1042     return consensus;
1043   }
1044
1045   /**
1046    * set this alignmentAnnotation object as the one used to render consensus
1047    * annotation
1048    * 
1049    * @param aan
1050    */
1051   public void setConservationRow(AlignmentAnnotation aan)
1052   {
1053     if (conservation == null)
1054     {
1055       conservation = aan;
1056     }
1057   }
1058
1059   /**
1060    * get the conservation annotation row for this group
1061    * 
1062    * @return autoCalculated annotation row
1063    */
1064   public AlignmentAnnotation getConservationRow()
1065   {
1066     if (conservation == null)
1067     {
1068       conservation = new AlignmentAnnotation("", "", new Annotation[1], 0f,
1069               11f, AlignmentAnnotation.BAR_GRAPH);
1070     }
1071
1072     conservation.hasText = true;
1073     conservation.autoCalculated = true;
1074     conservation.groupRef = this;
1075     conservation.label = "Conservation for " + getName();
1076     conservation.description = "Conservation for group " + getName()
1077             + " less than " + consPercGaps + "% gaps";
1078     return conservation;
1079   }
1080
1081   /**
1082    * 
1083    * @return true if annotation rows have been instantiated for this group
1084    */
1085   public boolean hasAnnotationRows()
1086   {
1087     return consensus != null || conservation != null;
1088   }
1089
1090   public SequenceI getConsensusSeq()
1091   {
1092     getConsensus();
1093     StringBuffer seqs = new StringBuffer();
1094     for (int i = 0; i < consensus.annotations.length; i++)
1095     {
1096       if (consensus.annotations[i] != null)
1097       {
1098         if (consensus.annotations[i].description.charAt(0) == '[')
1099         {
1100           seqs.append(consensus.annotations[i].description.charAt(1));
1101         }
1102         else
1103         {
1104           seqs.append(consensus.annotations[i].displayCharacter);
1105         }
1106       }
1107     }
1108
1109     SequenceI sq = new Sequence("Group" + getName() + " Consensus",
1110             seqs.toString());
1111     sq.setDescription("Percentage Identity Consensus "
1112             + ((ignoreGapsInConsensus) ? " without gaps" : ""));
1113     return sq;
1114   }
1115
1116   public void setIgnoreGapsConsensus(boolean state)
1117   {
1118     if (this.ignoreGapsInConsensus != state && consensus != null)
1119     {
1120       ignoreGapsInConsensus = state;
1121       recalcConservation();
1122     }
1123     ignoreGapsInConsensus = state;
1124   }
1125
1126   public boolean getIgnoreGapsConsensus()
1127   {
1128     return ignoreGapsInConsensus;
1129   }
1130
1131   /**
1132    * @param showSequenceLogo
1133    *          indicates if a sequence logo is shown for consensus annotation
1134    */
1135   public void setshowSequenceLogo(boolean showSequenceLogo)
1136   {
1137     // TODO: decouple calculation from settings update
1138     if (this.showSequenceLogo != showSequenceLogo && consensus != null)
1139     {
1140       this.showSequenceLogo = showSequenceLogo;
1141       recalcConservation();
1142     }
1143     this.showSequenceLogo = showSequenceLogo;
1144   }
1145
1146   /**
1147    * 
1148    * @param showConsHist
1149    *          flag indicating if the consensus histogram for this group should
1150    *          be rendered
1151    */
1152   public void setShowConsensusHistogram(boolean showConsHist)
1153   {
1154
1155     if (showConsensusHistogram != showConsHist && consensus != null)
1156     {
1157       this.showConsensusHistogram = showConsHist;
1158       recalcConservation();
1159     }
1160     this.showConsensusHistogram = showConsHist;
1161   }
1162
1163   /**
1164    * @return the showConsensusHistogram
1165    */
1166   public boolean isShowConsensusHistogram()
1167   {
1168     return showConsensusHistogram;
1169   }
1170
1171   /**
1172    * set flag indicating if logo should be normalised when rendered
1173    * 
1174    * @param norm
1175    */
1176   public void setNormaliseSequenceLogo(boolean norm)
1177   {
1178     normaliseSequenceLogo = norm;
1179   }
1180
1181   public boolean isNormaliseSequenceLogo()
1182   {
1183     return normaliseSequenceLogo;
1184   }
1185
1186   @Override
1187   /**
1188    * returns a new array with all annotation involving this group
1189    */
1190   public AlignmentAnnotation[] getAlignmentAnnotation()
1191   {
1192     // TODO add in other methods like 'getAlignmentAnnotation(String label),
1193     // etc'
1194     ArrayList<AlignmentAnnotation> annot = new ArrayList<AlignmentAnnotation>();
1195     for (SequenceI seq : (Vector<SequenceI>) sequences)
1196     {
1197       for (AlignmentAnnotation al : seq.getAnnotation())
1198       {
1199         if (al.groupRef == this)
1200         {
1201           annot.add(al);
1202         }
1203       }
1204     }
1205     if (consensus != null)
1206     {
1207       annot.add(consensus);
1208     }
1209     if (conservation != null)
1210     {
1211       annot.add(conservation);
1212     }
1213     return annot.toArray(new AlignmentAnnotation[0]);
1214   }
1215
1216   @Override
1217   public Iterable<AlignmentAnnotation> findAnnotation(String calcId)
1218   {
1219     ArrayList<AlignmentAnnotation> aa = new ArrayList<AlignmentAnnotation>();
1220     for (AlignmentAnnotation a : getAlignmentAnnotation())
1221     {
1222       if (a.getCalcId() == calcId)
1223       {
1224         aa.add(a);
1225       }
1226     }
1227     return aa;
1228   }
1229
1230   public void clear()
1231   {
1232     sequences.clear();
1233   }
1234 }