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