individual label format settings for each annotation row. Removed excess term in...
[jalview.git] / src / jalview / datamodel / AlignmentAnnotation.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.Enumeration;
22 import java.util.Hashtable;
23
24 /**
25  * DOCUMENT ME!
26  * 
27  * @author $author$
28  * @version $Revision$
29  */
30 public class AlignmentAnnotation
31 {
32   /**
33    * If true, this annotations is calculated every edit, eg consensus, quality
34    * or conservation graphs
35    */
36   public boolean autoCalculated = false;
37
38   public String annotationId;
39
40   public SequenceI sequenceRef;
41
42   /** DOCUMENT ME!! */
43   public String label;
44
45   /** DOCUMENT ME!! */
46   public String description;
47
48   /** DOCUMENT ME!! */
49   public Annotation[] annotations;
50
51   public java.util.Hashtable sequenceMapping;
52
53   /** DOCUMENT ME!! */
54   public float graphMin;
55
56   /** DOCUMENT ME!! */
57   public float graphMax;
58
59   /**
60    * Score associated with label and description.
61    */
62   public double score = Double.NaN;
63
64   /**
65    * flag indicating if annotation has a score.
66    */
67   public boolean hasScore = false;
68
69   public GraphLine threshold;
70
71   // Graphical hints and tips
72
73   /** DOCUMENT ME!! */
74   public boolean editable = false;
75
76   /** DOCUMENT ME!! */
77   public boolean hasIcons; //
78
79   /** DOCUMENT ME!! */
80   public boolean hasText;
81
82   /** DOCUMENT ME!! */
83   public boolean visible = true;
84
85   public int graphGroup = -1;
86
87   /** DOCUMENT ME!! */
88   public int height = 0;
89
90   public int graph = 0;
91
92   public int graphHeight = 40;
93
94   public boolean padGaps = false;
95
96   public static final int NO_GRAPH = 0;
97
98   public static final int BAR_GRAPH = 1;
99
100   public static final int LINE_GRAPH = 2;
101
102   public boolean belowAlignment = true;
103
104   public SequenceGroup groupRef =null ;
105
106   /**
107    * display every column label, even if there is a row of identical labels
108    */
109   public boolean showAllColLabels=false;
110   
111   /**
112    * scale the column label to fit within the alignment column.
113    */
114   public boolean scaleColLabel = false;
115
116   /**
117    * centre the column labels relative to the alignment column
118    */
119   public boolean centreColLabels = false;
120
121   
122   /* (non-Javadoc)
123    * @see java.lang.Object#finalize()
124    */
125   protected void finalize() throws Throwable
126   {
127     groupRef = null;
128     super.finalize();
129   }
130
131   public static int getGraphValueFromString(String string)
132   {
133     if (string.equalsIgnoreCase("BAR_GRAPH"))
134     {
135       return BAR_GRAPH;
136     }
137     else if (string.equalsIgnoreCase("LINE_GRAPH"))
138     {
139       return LINE_GRAPH;
140     }
141     else
142     {
143       return NO_GRAPH;
144     }
145   }
146
147   /**
148    * Creates a new AlignmentAnnotation object.
149    * 
150    * @param label
151    *                short label shown under sequence labels
152    * @param description
153    *                text displayed on mouseover
154    * @param annotations
155    *                set of positional annotation elements
156    */
157   public AlignmentAnnotation(String label, String description,
158           Annotation[] annotations)
159   {
160     // always editable?
161     editable = true;
162     this.label = label;
163     this.description = description;
164     this.annotations = annotations;
165
166     validateRangeAndDisplay();
167   }
168
169   void areLabelsSecondaryStructure()
170   {
171     boolean nonSSLabel = false;
172     char firstChar = 0;
173     for (int i = 0; i < annotations.length; i++)
174     {
175       if (annotations[i] == null)
176       {
177         continue;
178       }
179       if (annotations[i].secondaryStructure == 'H'
180               || annotations[i].secondaryStructure == 'E')
181       {
182         hasIcons = true;
183       }
184
185       if (annotations[i].displayCharacter == null)
186       {
187         continue;
188       }
189       if (annotations[i].displayCharacter.length() == 1)
190       {
191         firstChar = annotations[i].displayCharacter.charAt(0);
192         // check to see if it looks like a sequence or is secondary structure
193         // labelling.
194         if (
195         // Uncomment to only catch case where displayCharacter==secondary
196         // Structure
197         // to correctly redisplay SS annotation imported from Stockholm,
198         // exported to JalviewXML and read back in again.
199         // &&
200         // annotations[i].displayCharacter.charAt(0)==annotations[i].secondaryStructure
201         firstChar != 'H'
202                 && firstChar != 'E'
203                 && firstChar != '-'
204                 && firstChar < jalview.schemes.ResidueProperties.aaIndex.length)
205         {
206           if (jalview.schemes.ResidueProperties.aaIndex[firstChar] < 23)
207           {
208             nonSSLabel = true;
209           }
210         }
211       }
212
213       if (annotations[i].displayCharacter.length() > 0)
214       {
215         hasText = true;
216       }
217     }
218
219     if (nonSSLabel)
220     {
221       hasIcons = false;
222       for (int j = 0; j < annotations.length; j++)
223       {
224         if (annotations[j] != null
225                 && annotations[j].secondaryStructure != ' ')
226         {
227           annotations[j].displayCharacter = String
228                   .valueOf(annotations[j].secondaryStructure);
229           annotations[j].secondaryStructure = ' ';
230         }
231
232       }
233     }
234
235     annotationId = this.hashCode() + "";
236   }
237
238   /**
239    * Creates a new AlignmentAnnotation object.
240    * 
241    * @param label
242    *                DOCUMENT ME!
243    * @param description
244    *                DOCUMENT ME!
245    * @param annotations
246    *                DOCUMENT ME!
247    * @param min
248    *                DOCUMENT ME!
249    * @param max
250    *                DOCUMENT ME!
251    * @param winLength
252    *                DOCUMENT ME!
253    */
254   public AlignmentAnnotation(String label, String description,
255           Annotation[] annotations, float min, float max, int graphType)
256   {
257     // graphs are not editable
258     editable = graphType == 0;
259
260     this.label = label;
261     this.description = description;
262     this.annotations = annotations;
263     graph = graphType;
264     graphMin = min;
265     graphMax = max;
266     validateRangeAndDisplay();
267   }
268
269   /**
270    * checks graphMin and graphMax, secondary structure symbols, sets graphType
271    * appropriately, sets null labels to the empty string if appropriate.
272    */
273   private void validateRangeAndDisplay()
274   {
275
276     if (annotations == null)
277     {
278       visible = false; // try to prevent renderer from displaying.
279       return; // this is a non-annotation row annotation - ie a sequence score.
280     }
281
282     int graphType = graph;
283     float min = graphMin;
284     float max = graphMax;
285     boolean drawValues = true;
286
287     if (min == max)
288     {
289       min = 999999999;
290       for (int i = 0; i < annotations.length; i++)
291       {
292         if (annotations[i] == null)
293         {
294           continue;
295         }
296
297         if (drawValues && annotations[i].displayCharacter != null
298                 && annotations[i].displayCharacter.length() > 1)
299         {
300           drawValues = false;
301         }
302
303         if (annotations[i].value > max)
304         {
305           max = annotations[i].value;
306         }
307
308         if (annotations[i].value < min)
309         {
310           min = annotations[i].value;
311         }
312       }
313     }
314
315     graphMin = min;
316     graphMax = max;
317
318     areLabelsSecondaryStructure();
319
320     if (!drawValues && graphType != NO_GRAPH)
321     {
322       for (int i = 0; i < annotations.length; i++)
323       {
324         if (annotations[i] != null)
325         {
326           annotations[i].displayCharacter = "";
327         }
328       }
329     }
330   }
331
332   /**
333    * Copy constructor creates a new independent annotation row with the same
334    * associated sequenceRef
335    * 
336    * @param annotation
337    */
338   public AlignmentAnnotation(AlignmentAnnotation annotation)
339   {
340     this.label = new String(annotation.label);
341     if (annotation.description != null)
342       this.description = new String(annotation.description);
343     this.graphMin = annotation.graphMin;
344     this.graphMax = annotation.graphMax;
345     this.graph = annotation.graph;
346     this.graphHeight = annotation.graphHeight;
347     this.graphGroup = annotation.graphGroup;
348     this.groupRef = annotation.groupRef;
349     this.editable = annotation.editable;
350     this.autoCalculated = annotation.autoCalculated;
351     this.hasIcons = annotation.hasIcons;
352     this.hasText = annotation.hasText;
353     this.height = annotation.height;
354     this.label = annotation.label;
355     this.padGaps = annotation.padGaps;
356     this.visible = annotation.visible;
357     if (this.hasScore = annotation.hasScore)
358     {
359       this.score = annotation.score;
360     }
361     if (threshold != null)
362     {
363       threshold = new GraphLine(annotation.threshold);
364     }
365     if (annotation.annotations != null)
366     {
367       Annotation[] ann = annotation.annotations;
368       this.annotations = new Annotation[ann.length];
369       for (int i = 0; i < ann.length; i++)
370       {
371         annotations[i] = new Annotation(ann[i]);
372       }
373       ;
374       if (annotation.sequenceRef != null)
375       {
376         this.sequenceRef = annotation.sequenceRef;
377         if (annotation.sequenceMapping != null)
378         {
379           Integer p = null;
380           sequenceMapping = new Hashtable();
381           Enumeration pos = annotation.sequenceMapping.keys();
382           while (pos.hasMoreElements())
383           {
384             // could optimise this!
385             p = (Integer) pos.nextElement();
386             Annotation a = (Annotation) annotation.sequenceMapping.get(p);
387             if (a == null)
388             {
389               continue;
390             }
391             for (int i = 0; i < ann.length; i++)
392             {
393               if (ann[i] == a)
394               {
395                 sequenceMapping.put(p, annotations[i]);
396               }
397             }
398           }
399         }
400         else
401         {
402           this.sequenceMapping = null;
403         }
404       }
405     }
406     validateRangeAndDisplay(); // construct hashcodes, etc.
407   }
408
409   /**
410    * clip the annotation to the columns given by startRes and endRes (inclusive)
411    * and prune any existing sequenceMapping to just those columns.
412    * 
413    * @param startRes
414    * @param endRes
415    */
416   public void restrict(int startRes, int endRes)
417   {
418     if (annotations == null)
419     {
420       // non-positional
421       return;
422     }
423     if (startRes < 0)
424       startRes = 0;
425     if (startRes >= annotations.length)
426       startRes = annotations.length - 1;
427     if (endRes >= annotations.length)
428       endRes = annotations.length - 1;
429     if (annotations == null)
430       return;
431     Annotation[] temp = new Annotation[endRes - startRes + 1];
432     if (startRes < annotations.length)
433     {
434       System.arraycopy(annotations, startRes, temp, 0, endRes - startRes
435               + 1);
436     }
437     if (sequenceRef != null)
438     {
439       // Clip the mapping, if it exists.
440       int spos = sequenceRef.findPosition(startRes);
441       int epos = sequenceRef.findPosition(endRes);
442       if (sequenceMapping != null)
443       {
444         Hashtable newmapping = new Hashtable();
445         Enumeration e = sequenceMapping.keys();
446         while (e.hasMoreElements())
447         {
448           Integer pos = (Integer) e.nextElement();
449           if (pos.intValue() >= spos && pos.intValue() <= epos)
450           {
451             newmapping.put(pos, sequenceMapping.get(pos));
452           }
453         }
454         sequenceMapping.clear();
455         sequenceMapping = newmapping;
456       }
457     }
458     annotations = temp;
459   }
460
461   /**
462    * set the annotation row to be at least length Annotations
463    * 
464    * @param length
465    *                minimum number of columns required in the annotation row
466    * @return false if the annotation row is greater than length
467    */
468   public boolean padAnnotation(int length)
469   {
470     if (annotations == null)
471     {
472       return true; // annotation row is correct - null == not visible and
473                     // undefined length
474     }
475     if (annotations.length < length)
476     {
477       Annotation[] na = new Annotation[length];
478       System.arraycopy(annotations, 0, na, 0, annotations.length);
479       annotations = na;
480       return true;
481     }
482     return annotations.length > length;
483
484   }
485
486   /**
487    * DOCUMENT ME!
488    * 
489    * @return DOCUMENT ME!
490    */
491   public String toString()
492   {
493     StringBuffer buffer = new StringBuffer();
494
495     for (int i = 0; i < annotations.length; i++)
496     {
497       if (annotations[i] != null)
498       {
499         if (graph != 0)
500         {
501           buffer.append(annotations[i].value);
502         }
503         else if (hasIcons)
504         {
505           buffer.append(annotations[i].secondaryStructure);
506         }
507         else
508         {
509           buffer.append(annotations[i].displayCharacter);
510         }
511       }
512
513       buffer.append(", ");
514     }
515     // TODO: remove disgusting hack for 'special' treatment of consensus line.
516     if (label.indexOf("Consensus")==0)
517     {
518       buffer.append("\n");
519
520       for (int i = 0; i < annotations.length; i++)
521       {
522         if (annotations[i] != null)
523         {
524           buffer.append(annotations[i].description);
525         }
526
527         buffer.append(", ");
528       }
529     }
530
531     return buffer.toString();
532   }
533
534   public void setThreshold(GraphLine line)
535   {
536     threshold = line;
537   }
538
539   public GraphLine getThreshold()
540   {
541     return threshold;
542   }
543
544   /**
545    * Attach the annotation to seqRef, starting from startRes position. If
546    * alreadyMapped is true then the indices of the annotation[] array are
547    * sequence positions rather than alignment column positions.
548    * 
549    * @param seqRef
550    * @param startRes
551    * @param alreadyMapped
552    */
553   public void createSequenceMapping(SequenceI seqRef, int startRes,
554           boolean alreadyMapped)
555   {
556
557     if (seqRef == null)
558     {
559       return;
560     }
561     sequenceRef = seqRef;
562     if (annotations == null)
563     {
564       return;
565     }
566     sequenceMapping = new java.util.Hashtable();
567
568     int seqPos;
569
570     for (int i = 0; i < annotations.length; i++)
571     {
572       if (annotations[i] != null)
573       {
574         if (alreadyMapped)
575         {
576           seqPos = seqRef.findPosition(i);
577         }
578         else
579         {
580           seqPos = i + startRes;
581         }
582
583         sequenceMapping.put(new Integer(seqPos), annotations[i]);
584       }
585     }
586
587   }
588
589   public void adjustForAlignment()
590   {
591     if (sequenceRef == null)
592       return;
593
594     if (annotations == null)
595     {
596       return;
597     }
598
599     int a = 0, aSize = sequenceRef.getLength();
600
601     if (aSize == 0)
602     {
603       // Its been deleted
604       return;
605     }
606
607     int position;
608     Annotation[] temp = new Annotation[aSize];
609     Integer index;
610
611     for (a = sequenceRef.getStart(); a <= sequenceRef.getEnd(); a++)
612     {
613       index = new Integer(a);
614       if (sequenceMapping.containsKey(index))
615       {
616         position = sequenceRef.findIndex(a) - 1;
617
618         temp[position] = (Annotation) sequenceMapping.get(index);
619       }
620     }
621
622     annotations = temp;
623   }
624
625   /**
626    * remove any null entries in annotation row and return the number of non-null
627    * annotation elements.
628    * 
629    * @return
630    */
631   public int compactAnnotationArray()
632   {
633     int i = 0, iSize = annotations.length;
634     while (i < iSize)
635     {
636       if (annotations[i] == null)
637       {
638         if (i + 1 < iSize)
639           System.arraycopy(annotations, i + 1, annotations, i, iSize - i
640                   - 1);
641         iSize--;
642       }
643       else
644       {
645         i++;
646       }
647     }
648     Annotation[] ann = annotations;
649     annotations = new Annotation[i];
650     System.arraycopy(ann, 0, annotations, 0, i);
651     ann = null;
652     return iSize;
653   }
654
655   /**
656    * Associate this annotion with the aligned residues of a particular sequence.
657    * sequenceMapping will be updated in the following way: null sequenceI -
658    * existing mapping will be discarded but annotations left in mapped
659    * positions. valid sequenceI not equal to current sequenceRef: mapping is
660    * discarded and rebuilt assuming 1:1 correspondence TODO: overload with
661    * parameter to specify correspondence between current and new sequenceRef
662    * 
663    * @param sequenceI
664    */
665   public void setSequenceRef(SequenceI sequenceI)
666   {
667     if (sequenceI != null)
668     {
669       if (sequenceRef != null)
670       {
671         if (sequenceRef != sequenceI
672                 && !sequenceRef.equals(sequenceI)
673                 && sequenceRef.getDatasetSequence() != sequenceI
674                         .getDatasetSequence())
675         {
676           // if sequenceRef isn't intersecting with sequenceI
677           // throw away old mapping and reconstruct.
678           sequenceRef = null;
679           if (sequenceMapping != null)
680           {
681             sequenceMapping = null;
682             // compactAnnotationArray();
683           }
684           createSequenceMapping(sequenceI, 1, true);
685           adjustForAlignment();
686         }
687         else
688         {
689           // Mapping carried over
690           sequenceRef = sequenceI;
691         }
692       }
693       else
694       {
695         // No mapping exists
696         createSequenceMapping(sequenceI, 1, true);
697         adjustForAlignment();
698       }
699     }
700     else
701     {
702       // throw away the mapping without compacting.
703       sequenceMapping = null;
704       sequenceRef = null;
705     }
706   }
707
708   /**
709    * @return the score
710    */
711   public double getScore()
712   {
713     return score;
714   }
715
716   /**
717    * @param score
718    *                the score to set
719    */
720   public void setScore(double score)
721   {
722     hasScore = true;
723     this.score = score;
724   }
725
726   /**
727    * 
728    * @return true if annotation has an associated score
729    */
730   public boolean hasScore()
731   {
732     return hasScore || !Double.isNaN(score);
733   }
734
735   /**
736    * Score only annotation
737    * 
738    * @param label
739    * @param description
740    * @param score
741    */
742   public AlignmentAnnotation(String label, String description, double score)
743   {
744     this(label, description, null);
745     setScore(score);
746   }
747
748   /**
749    * copy constructor with edit based on the hidden columns marked in colSel
750    * 
751    * @param alignmentAnnotation
752    * @param colSel
753    */
754   public AlignmentAnnotation(AlignmentAnnotation alignmentAnnotation,
755           ColumnSelection colSel)
756   {
757     this(alignmentAnnotation);
758     if (annotations == null)
759     {
760       return;
761     }
762     colSel.makeVisibleAnnotation(this);
763   }
764
765   public void setPadGaps(boolean padgaps, char gapchar)
766   {
767     this.padGaps = padgaps;
768     if (padgaps)
769     {
770       hasText = true;
771       for (int i = 0; i < annotations.length; i++)
772       {
773         if (annotations[i] == null)
774           annotations[i] = new Annotation(String.valueOf(gapchar), null,
775                   ' ', 0f);
776         else if (annotations[i].displayCharacter == null
777                 || annotations[i].displayCharacter.equals(" "))
778           annotations[i].displayCharacter = String.valueOf(gapchar);
779       }
780     }
781   }
782
783   /**
784    * format description string for display
785    * 
786    * @param seqname
787    * @return Get the annotation description string optionally prefixed by
788    *         associated sequence name (if any)
789    */
790   public String getDescription(boolean seqname)
791   {
792     if (seqname && this.sequenceRef != null)
793     {
794       return sequenceRef.getName() + " : " + description;
795     }
796     return description;
797   }
798 }