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