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