JAL-2068 scale annotation display character to width for feature count annotation
[jalview.git] / src / jalview / workers / ColumnCounterWorker.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.workers;
22
23 import jalview.api.AlignViewportI;
24 import jalview.api.AlignmentViewPanel;
25 import jalview.datamodel.AlignmentAnnotation;
26 import jalview.datamodel.AlignmentI;
27 import jalview.datamodel.Annotation;
28 import jalview.datamodel.SequenceFeature;
29 import jalview.datamodel.SequenceI;
30 import jalview.renderer.seqfeatures.FeatureRenderer;
31 import jalview.util.ColorUtils;
32 import jalview.util.Comparison;
33
34 import java.awt.Color;
35 import java.util.ArrayList;
36 import java.util.List;
37
38 /**
39  * A class to compute an alignment annotation with column counts of any
40  * properties of interest of positions in an alignment. <br>
41  * This is designed to be extensible, by supplying to the constructor an object
42  * that computes a count for each residue position, based on the residue value
43  * and any sequence features at that position.
44  * 
45  */
46 class ColumnCounterWorker extends AlignCalcWorker
47 {
48   FeatureCounterI counter;
49
50   /**
51    * Constructor registers the annotation for the given alignment frame
52    * 
53    * @param af
54    * @param counter
55    */
56   public ColumnCounterWorker(AlignViewportI viewport,
57           AlignmentViewPanel panel, FeatureCounterI counter)
58   {
59     super(viewport, panel);
60     ourAnnots = new ArrayList<AlignmentAnnotation>();
61     this.counter = counter;
62     calcMan.registerWorker(this);
63   }
64
65   /**
66    * method called under control of AlignCalcManager to recompute the annotation
67    * when the alignment changes
68    */
69   @Override
70   public void run()
71   {
72     try
73     {
74       calcMan.notifyStart(this);
75
76       while (!calcMan.notifyWorking(this))
77       {
78         try
79         {
80           Thread.sleep(200);
81         } catch (InterruptedException ex)
82         {
83           ex.printStackTrace();
84         }
85       }
86       if (alignViewport.isClosed())
87       {
88         abortAndDestroy();
89         return;
90       }
91
92       removeAnnotation();
93       if (alignViewport.getAlignment() != null)
94       {
95         try
96         {
97           computeAnnotations();
98         } catch (IndexOutOfBoundsException x)
99         {
100           // probable race condition. just finish and return without any fuss.
101           return;
102         }
103       }
104     } catch (OutOfMemoryError error)
105     {
106       ap.raiseOOMWarning("calculating feature counts", error);
107       calcMan.disableWorker(this);
108     } finally
109     {
110       calcMan.workerComplete(this);
111     }
112
113     if (ap != null)
114     {
115       ap.adjustAnnotationHeight();
116       ap.paintAlignment(true);
117     }
118
119   }
120
121   /**
122    * Scan each column of the alignment to calculate a count by feature type. Set
123    * the count as the value of the alignment annotation for that feature type.
124    */
125   void computeAnnotations()
126   {
127     FeatureRenderer fr = new FeatureRenderer(alignViewport);
128     // TODO use the commented out code once JAL-2075 is fixed
129     // to get adequate performance on genomic length sequence
130     AlignmentI alignment = alignViewport.getAlignment();
131     // AlignmentView alignmentView = alignViewport.getAlignmentView(false);
132     // AlignmentI alignment = alignmentView.getVisibleAlignment(' ');
133
134     // int width = alignmentView.getWidth();
135     int width = alignment.getWidth();
136     int height = alignment.getHeight();
137     int[] counts = new int[width];
138     int max = 0;
139
140     for (int col = 0; col < width; col++)
141     {
142       int count = 0;
143       for (int row = 0; row < height; row++)
144       {
145         count += countFeaturesAt(alignment, col, row, fr);
146       }
147       counts[col] = count;
148       max = Math.max(count, max);
149     }
150
151     Annotation[] anns = new Annotation[width];
152     /*
153      * add non-zero counts as annotations
154      */
155     for (int i = 0; i < counts.length; i++)
156     {
157       int count = counts[i];
158       if (count > 0)
159       {
160         Color color = ColorUtils.getGraduatedColour(count, 0, Color.cyan,
161                 max, Color.blue);
162         anns[i] = new Annotation(String.valueOf(count),
163                 String.valueOf(count), '0', count, color);
164       }
165     }
166
167     /*
168      * construct the annotation, save it and add it to the displayed alignment
169      */
170     AlignmentAnnotation ann = new AlignmentAnnotation(counter.getName(),
171             counter.getDescription(), anns);
172     ann.showAllColLabels = true;
173     ann.scaleColLabel = true;
174     ann.graph = AlignmentAnnotation.BAR_GRAPH;
175     ourAnnots.add(ann);
176     alignViewport.getAlignment().addAnnotation(ann);
177   }
178
179   /**
180    * Returns a count of any feature types present at the specified position of
181    * the alignment
182    * 
183    * @param alignment
184    * @param col
185    * @param row
186    * @param fr
187    */
188   int countFeaturesAt(AlignmentI alignment, int col, int row,
189           FeatureRenderer fr)
190   {
191     SequenceI seq = alignment.getSequenceAt(row);
192     if (seq == null)
193     {
194       return 0;
195     }
196     if (col >= seq.getLength())
197     {
198       return 0;// sequence doesn't extend this far
199     }
200     char res = seq.getCharAt(col);
201     if (Comparison.isGap(res))
202     {
203       return 0;
204     }
205     int pos = seq.findPosition(col);
206
207     /*
208      * compute a count for any displayed features at residue
209      */
210     // NB have to adjust pos if using AlignmentView.getVisibleAlignment
211     // see JAL-2075
212     List<SequenceFeature> features = fr.findFeaturesAtRes(seq, pos);
213     int count = this.counter.count(String.valueOf(res), features);
214     return count;
215   }
216
217   /**
218    * Method called when the user changes display options that may affect how the
219    * annotation is rendered, but do not change its values. Currently no such
220    * options affect user-defined annotation, so this method does nothing.
221    */
222   @Override
223   public void updateAnnotation()
224   {
225     // do nothing
226   }
227
228   /**
229    * Answers true to indicate that if this worker's annotation is deleted from
230    * the display, the worker should also be removed. This prevents it running
231    * and recreating the annotation when the alignment changes.
232    */
233   @Override
234   public boolean isDeletable()
235   {
236     return true;
237   }
238 }