normalised logo rendering (JAL-958)
[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
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
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
498         if (cs instanceof ClustalxColourScheme)
499         {
500           ((ClustalxColourScheme) cs).resetClustalX(sequences, getWidth());
501         }
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
522             if (cs instanceof ClustalxColourScheme)
523             {
524               ((ClustalxColourScheme) cs).resetClustalX(sequences,
525                       getWidth());
526             }
527           }
528         }
529       }
530     } catch (java.lang.OutOfMemoryError err)
531     {
532       // TODO: catch OOM
533       System.out.println("Out of memory loading groups: " + err);
534     }
535
536   }
537
538   private void _updateConservationRow(Conservation c)
539   {
540     if (conservation == null)
541     {
542       getConservation();
543     }
544     // update Labels
545     conservation.label = "Conservation for " + getName();
546     conservation.description = "Conservation for group " + getName()
547             + " less than " + consPercGaps + "% gaps";
548     // preserve width if already set
549     int aWidth = (conservation.annotations != null) ? (endRes < conservation.annotations.length ? conservation.annotations.length
550             : endRes + 1)
551             : endRes + 1;
552     conservation.annotations = null;
553     conservation.annotations = new Annotation[aWidth]; // should be alignment
554                                                        // width
555     c.completeAnnotations(conservation, null, startRes, endRes + 1);
556   }
557
558   public Hashtable[] consensusData = null;
559
560   private void _updateConsensusRow(Hashtable[] cnsns)
561   {
562     if (consensus == null)
563     {
564       getConsensus();
565     }
566     consensus.label = "Consensus for " + getName();
567     consensus.description = "Percent Identity";
568     consensusData = cnsns;
569     // preserve width if already set
570     int aWidth = (consensus.annotations != null) ? (endRes < consensus.annotations.length ? consensus.annotations.length
571             : endRes + 1)
572             : endRes + 1;
573     consensus.annotations = null;
574     consensus.annotations = new Annotation[aWidth]; // should be alignment width
575
576     AAFrequency.completeConsensus(consensus, cnsns, startRes, endRes + 1,
577             ignoreGapsInConsensus, showSequenceLogo); // TODO: setting container
578                                                       // for
579                                                       // ignoreGapsInConsensusCalculation);
580   }
581
582   /**
583    * @param s
584    *          sequence to either add or remove from group
585    * @param recalc
586    *          flag passed to delete/addSequence to indicate if group properties
587    *          should be recalculated
588    */
589   public void addOrRemove(SequenceI s, boolean recalc)
590   {
591     if (sequences.contains(s))
592     {
593       deleteSequence(s, recalc);
594     }
595     else
596     {
597       addSequence(s, recalc);
598     }
599   }
600
601   /**
602    * DOCUMENT ME!
603    * 
604    * @param s
605    *          DOCUMENT ME!
606    * @param recalc
607    *          DOCUMENT ME!
608    */
609   public void deleteSequence(SequenceI s, boolean recalc)
610   {
611     sequences.removeElement(s);
612
613     if (recalc)
614     {
615       recalcConservation();
616     }
617   }
618
619   /**
620    * DOCUMENT ME!
621    * 
622    * @return DOCUMENT ME!
623    */
624   public int getStartRes()
625   {
626     return startRes;
627   }
628
629   /**
630    * DOCUMENT ME!
631    * 
632    * @return DOCUMENT ME!
633    */
634   public int getEndRes()
635   {
636     return endRes;
637   }
638
639   /**
640    * Set the first column selected by this group. Runs from 0<=i<N_cols
641    * 
642    * @param i
643    */
644   public void setStartRes(int i)
645   {
646     startRes = i;
647   }
648
649   /**
650    * Set the groups last selected column. Runs from 0<=i<N_cols
651    * 
652    * @param i
653    */
654   public void setEndRes(int i)
655   {
656     endRes = i;
657   }
658
659   /**
660    * DOCUMENT ME!
661    * 
662    * @return DOCUMENT ME!
663    */
664   public int getSize()
665   {
666     return sequences.size();
667   }
668
669   /**
670    * DOCUMENT ME!
671    * 
672    * @param i
673    *          DOCUMENT ME!
674    * 
675    * @return DOCUMENT ME!
676    */
677   public SequenceI getSequenceAt(int i)
678   {
679     return (SequenceI) sequences.elementAt(i);
680   }
681
682   /**
683    * DOCUMENT ME!
684    * 
685    * @param state
686    *          DOCUMENT ME!
687    */
688   public void setColourText(boolean state)
689   {
690     colourText = state;
691   }
692
693   /**
694    * DOCUMENT ME!
695    * 
696    * @return DOCUMENT ME!
697    */
698   public boolean getColourText()
699   {
700     return colourText;
701   }
702
703   /**
704    * DOCUMENT ME!
705    * 
706    * @param state
707    *          DOCUMENT ME!
708    */
709   public void setDisplayText(boolean state)
710   {
711     displayText = state;
712   }
713
714   /**
715    * DOCUMENT ME!
716    * 
717    * @return DOCUMENT ME!
718    */
719   public boolean getDisplayText()
720   {
721     return displayText;
722   }
723
724   /**
725    * DOCUMENT ME!
726    * 
727    * @param state
728    *          DOCUMENT ME!
729    */
730   public void setDisplayBoxes(boolean state)
731   {
732     displayBoxes = state;
733   }
734
735   /**
736    * DOCUMENT ME!
737    * 
738    * @return DOCUMENT ME!
739    */
740   public boolean getDisplayBoxes()
741   {
742     return displayBoxes;
743   }
744
745   /**
746    * DOCUMENT ME!
747    * 
748    * @return DOCUMENT ME!
749    */
750   public int getWidth()
751   {
752     // MC This needs to get reset when characters are inserted and deleted
753     if (sequences.size() > 0)
754     {
755       width = ((SequenceI) sequences.elementAt(0)).getLength();
756     }
757
758     for (int i = 1; i < sequences.size(); i++)
759     {
760       SequenceI seq = (SequenceI) sequences.elementAt(i);
761
762       if (seq.getLength() > width)
763       {
764         width = seq.getLength();
765       }
766     }
767
768     return width;
769   }
770
771   /**
772    * DOCUMENT ME!
773    * 
774    * @param c
775    *          DOCUMENT ME!
776    */
777   public void setOutlineColour(Color c)
778   {
779     outlineColour = c;
780   }
781
782   /**
783    * DOCUMENT ME!
784    * 
785    * @return DOCUMENT ME!
786    */
787   public Color getOutlineColour()
788   {
789     return outlineColour;
790   }
791
792   /**
793    * 
794    * returns the sequences in the group ordered by the ordering given by al.
795    * this used to return an array with null entries regardless, new behaviour is
796    * below. TODO: verify that this does not affect use in applet or application
797    * 
798    * @param al
799    *          Alignment
800    * @return SequenceI[] intersection of sequences in group with al, ordered by
801    *         al, or null if group does not intersect with al
802    */
803   public SequenceI[] getSequencesInOrder(AlignmentI al)
804   {
805     return getSequencesInOrder(al, true);
806   }
807
808   /**
809    * return an array representing the intersection of the group with al,
810    * optionally returning an array the size of al.getHeight() where nulls mark
811    * the non-intersected sequences
812    * 
813    * @param al
814    * @param trim
815    * @return null or array
816    */
817   public SequenceI[] getSequencesInOrder(AlignmentI al, boolean trim)
818   {
819     int sSize = sequences.size();
820     int alHeight = al.getHeight();
821
822     SequenceI[] seqs = new SequenceI[(trim) ? sSize : alHeight];
823
824     int index = 0;
825     for (int i = 0; i < alHeight && index < sSize; i++)
826     {
827       if (sequences.contains(al.getSequenceAt(i)))
828       {
829         seqs[(trim) ? index : i] = al.getSequenceAt(i);
830         index++;
831       }
832     }
833     if (index == 0)
834     {
835       return null;
836     }
837     if (!trim)
838     {
839       return seqs;
840     }
841     if (index < seqs.length)
842     {
843       SequenceI[] dummy = seqs;
844       seqs = new SequenceI[index];
845       while (--index >= 0)
846       {
847         seqs[index] = dummy[index];
848         dummy[index] = null;
849       }
850     }
851     return seqs;
852   }
853
854   /**
855    * @return the idColour
856    */
857   public Color getIdColour()
858   {
859     return idColour;
860   }
861
862   /**
863    * @param idColour
864    *          the idColour to set
865    */
866   public void setIdColour(Color idColour)
867   {
868     this.idColour = idColour;
869   }
870
871   /**
872    * @return the representative sequence for this group
873    */
874   public SequenceI getSeqrep()
875   {
876     return seqrep;
877   }
878
879   /**
880    * set the representative sequence for this group. Note - this affects the
881    * interpretation of the Hidereps attribute.
882    * 
883    * @param seqrep
884    *          the seqrep to set (null means no sequence representative)
885    */
886   public void setSeqrep(SequenceI seqrep)
887   {
888     this.seqrep = seqrep;
889   }
890
891   /**
892    * 
893    * @return true if group has a sequence representative
894    */
895   public boolean hasSeqrep()
896   {
897     return seqrep != null;
898   }
899
900   /**
901    * visibility of rows or represented rows covered by group
902    */
903   private boolean hidereps = false;
904
905   /**
906    * set visibility of sequences covered by (if no sequence representative is
907    * defined) or represented by this group.
908    * 
909    * @param visibility
910    */
911   public void setHidereps(boolean visibility)
912   {
913     hidereps = visibility;
914   }
915
916   /**
917    * 
918    * @return true if sequences represented (or covered) by this group should be
919    *         hidden
920    */
921   public boolean isHidereps()
922   {
923     return hidereps;
924   }
925
926   /**
927    * visibility of columns intersecting this group
928    */
929   private boolean hidecols = false;
930
931   /**
932    * set intended visibility of columns covered by this group
933    * 
934    * @param visibility
935    */
936   public void setHideCols(boolean visibility)
937   {
938     hidecols = visibility;
939   }
940
941   /**
942    * 
943    * @return true if columns covered by group should be hidden
944    */
945   public boolean isHideCols()
946   {
947     return hidecols;
948   }
949
950   /**
951    * create a new sequence group from the intersection of this group with an
952    * alignment Hashtable of hidden representatives
953    * 
954    * @param alignment
955    *          (may not be null)
956    * @param hashtable
957    *          (may be null)
958    * @return new group containing sequences common to this group and alignment
959    */
960   public SequenceGroup intersect(AlignmentI alignment, Hashtable hashtable)
961   {
962     SequenceGroup sgroup = new SequenceGroup(this);
963     SequenceI[] insect = getSequencesInOrder(alignment);
964     sgroup.sequences = new Vector();
965     for (int s = 0; insect != null && s < insect.length; s++)
966     {
967       if (hashtable == null || hashtable.containsKey(insect[s]))
968       {
969         sgroup.sequences.addElement(insect[s]);
970       }
971     }
972     // Enumeration en =getSequences(hashtable).elements();
973     // while (en.hasMoreElements())
974     // {
975     // SequenceI elem = (SequenceI) en.nextElement();
976     // if (alignment.getSequences().contains(elem))
977     // {
978     // sgroup.addSequence(elem, false);
979     // }
980     // }
981     return sgroup;
982   }
983
984   /**
985    * @return the showUnconserved
986    */
987   public boolean getShowNonconserved()
988   {
989     return showNonconserved;
990   }
991
992   /**
993    * @param showNonconserved
994    *          the showUnconserved to set
995    */
996   public void setShowNonconserved(boolean displayNonconserved)
997   {
998     this.showNonconserved = displayNonconserved;
999   }
1000
1001   AlignmentAnnotation consensus = null, conservation = null;
1002
1003   /**
1004    * flag indicating if consensus histogram should be rendered
1005    */
1006   private boolean showConsensusHistogram;
1007
1008   /**
1009    * set this alignmentAnnotation object as the one used to render consensus
1010    * annotation
1011    * 
1012    * @param aan
1013    */
1014   public void setConsensus(AlignmentAnnotation aan)
1015   {
1016     if (consensus == null)
1017     {
1018       consensus = aan;
1019     }
1020   }
1021
1022   /**
1023    * 
1024    * @return automatically calculated consensus row
1025    */
1026   public AlignmentAnnotation getConsensus()
1027   {
1028     // TODO get or calculate and get consensus annotation row for this group
1029     int aWidth = this.getWidth();
1030     // pointer
1031     // possibility
1032     // here.
1033     if (aWidth < 0)
1034     {
1035       return null;
1036     }
1037     if (consensus == null)
1038     {
1039       consensus = new AlignmentAnnotation("", "", new Annotation[1], 0f,
1040               100f, AlignmentAnnotation.BAR_GRAPH);
1041     }
1042     consensus.hasText = true;
1043     consensus.autoCalculated = true;
1044     consensus.groupRef = this;
1045     consensus.label = "Consensus for " + getName();
1046     consensus.description = "Percent Identity";
1047     return consensus;
1048   }
1049
1050   /**
1051    * set this alignmentAnnotation object as the one used to render consensus
1052    * annotation
1053    * 
1054    * @param aan
1055    */
1056   public void setConservationRow(AlignmentAnnotation aan)
1057   {
1058     if (conservation == null)
1059     {
1060       conservation = aan;
1061     }
1062   }
1063
1064   /**
1065    * get the conservation annotation row for this group
1066    * 
1067    * @return autoCalculated annotation row
1068    */
1069   public AlignmentAnnotation getConservationRow()
1070   {
1071     if (conservation == null)
1072     {
1073       conservation = new AlignmentAnnotation("", "", new Annotation[1], 0f,
1074               11f, AlignmentAnnotation.BAR_GRAPH);
1075     }
1076
1077     conservation.hasText = true;
1078     conservation.autoCalculated = true;
1079     conservation.groupRef = this;
1080     conservation.label = "Conservation for " + getName();
1081     conservation.description = "Conservation for group " + getName()
1082             + " less than " + consPercGaps + "% gaps";
1083     return conservation;
1084   }
1085
1086   /**
1087    * 
1088    * @return true if annotation rows have been instantiated for this group
1089    */
1090   public boolean hasAnnotationRows()
1091   {
1092     return consensus != null || conservation != null;
1093   }
1094
1095   public SequenceI getConsensusSeq()
1096   {
1097     getConsensus();
1098     StringBuffer seqs = new StringBuffer();
1099     for (int i = 0; i < consensus.annotations.length; i++)
1100     {
1101       if (consensus.annotations[i] != null)
1102       {
1103         if (consensus.annotations[i].description.charAt(0) == '[')
1104         {
1105           seqs.append(consensus.annotations[i].description.charAt(1));
1106         }
1107         else
1108         {
1109           seqs.append(consensus.annotations[i].displayCharacter);
1110         }
1111       }
1112     }
1113
1114     SequenceI sq = new Sequence("Group" + getName() + " Consensus",
1115             seqs.toString());
1116     sq.setDescription("Percentage Identity Consensus "
1117             + ((ignoreGapsInConsensus) ? " without gaps" : ""));
1118     return sq;
1119   }
1120
1121   public void setIgnoreGapsConsensus(boolean state)
1122   {
1123     if (this.ignoreGapsInConsensus != state && consensus != null)
1124     {
1125       ignoreGapsInConsensus = state;
1126       recalcConservation();
1127     }
1128     ignoreGapsInConsensus = state;
1129   }
1130
1131   public boolean getIgnoreGapsConsensus()
1132   {
1133     return ignoreGapsInConsensus;
1134   }
1135
1136   /**
1137    * @param showSequenceLogo
1138    *          indicates if a sequence logo is shown for consensus annotation
1139    */
1140   public void setshowSequenceLogo(boolean showSequenceLogo)
1141   {
1142     // TODO: decouple calculation from settings update
1143     if (this.showSequenceLogo != showSequenceLogo && consensus != null)
1144     {
1145       this.showSequenceLogo = showSequenceLogo;
1146       recalcConservation();
1147     }
1148     this.showSequenceLogo = showSequenceLogo;
1149   }
1150
1151   /**
1152    * 
1153    * @param showConsHist
1154    *          flag indicating if the consensus histogram for this group should
1155    *          be rendered
1156    */
1157   public void setShowConsensusHistogram(boolean showConsHist)
1158   {
1159
1160     if (showConsensusHistogram != showConsHist && consensus != null)
1161     {
1162       this.showConsensusHistogram = showConsHist;
1163       recalcConservation();
1164     }
1165     this.showConsensusHistogram = showConsHist;
1166   }
1167
1168   /**
1169    * @return the showConsensusHistogram
1170    */
1171   public boolean isShowConsensusHistogram()
1172   {
1173     return showConsensusHistogram;
1174   }
1175
1176   /**
1177    * set flag indicating if logo should be normalised when rendered
1178    * @param norm
1179    */
1180   public void setNormaliseSequenceLogo(boolean norm)
1181   {
1182     normaliseSequenceLogo=norm;
1183   }
1184   public boolean isNormaliseSequenceLogo()
1185   {
1186     return normaliseSequenceLogo;
1187   }
1188 }