JAL-3878 Add getCalcName to AlignCalcWorkerI.
[jalview.git] / src / jalview / workers / ColumnCounterSetWorker.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 alignment annotations with column counts for a set of
40  * properties of interest on positions in an alignment. <br>
41  * This is designed to be extensible, by supplying to the constructor an object
42  * that computes a vector of counts for each residue position, based on the
43  * residue and and sequence features at that position.
44  * 
45  */
46 class ColumnCounterSetWorker extends AlignCalcWorker
47 {
48   FeatureSetCounterI counter;
49
50   /**
51    * Constructor registers the annotation for the given alignment frame
52    * 
53    * @param af
54    * @param counter
55    */
56   public ColumnCounterSetWorker(AlignViewportI viewport,
57           AlignmentViewPanel panel, FeatureSetCounterI counter)
58   {
59     super(viewport, panel);
60     ourAnnots = new ArrayList<>();
61     this.counter = counter;
62     calcMan.registerWorker(this);
63   }
64
65   @Override
66   public String getCalcName()
67   {
68     return "Column Counter Set";
69   }
70
71   /**
72    * method called under control of AlignCalcManager to recompute the annotation
73    * when the alignment changes
74    */
75   @Override
76   public void run()
77   {
78     boolean annotationAdded = false;
79     if (alignViewport.isClosed())
80     {
81       abortAndDestroy();
82       return;
83     }
84
85     if (alignViewport.getAlignment() != null)
86     {
87       try
88       {
89         annotationAdded = computeAnnotations();
90       } catch (IndexOutOfBoundsException x)
91       {
92         // probable race condition. just finish and return without any fuss.
93         return;
94       }
95     }
96
97     if (ap != null)
98     {
99       if (annotationAdded)
100       {
101         ap.adjustAnnotationHeight();
102       }
103       ap.paintAlignment(true, true);
104     }
105
106   }
107
108   /**
109    * Scan each column of the alignment to calculate a count by feature type. Set
110    * the count as the value of the alignment annotation for that feature type.
111    * 
112    * @return
113    */
114   boolean computeAnnotations()
115   {
116     FeatureRenderer fr = new FeatureRenderer(alignViewport);
117     // TODO use the commented out code once JAL-2075 is fixed
118     // to get adequate performance on genomic length sequence
119     AlignmentI alignment = alignViewport.getAlignment();
120     // AlignmentView alignmentView = alignViewport.getAlignmentView(false);
121     // AlignmentI alignment = alignmentView.getVisibleAlignment(' ');
122
123     int rows = counter.getNames().length;
124
125     int width = alignment.getWidth();
126     int height = alignment.getHeight();
127     int[][] counts = new int[width][rows];
128     int max[] = new int[rows];
129     for (int crow = 0; crow < rows; crow++)
130     {
131       max[crow] = 0;
132     }
133
134     int[] minC = counter.getMinColour();
135     int[] maxC = counter.getMaxColour();
136     Color minColour = new Color(minC[0], minC[1], minC[2]);
137     Color maxColour = new Color(maxC[0], maxC[1], maxC[2]);
138
139     for (int col = 0; col < width; col++)
140     {
141       int[] count = counts[col];
142       for (int crow = 0; crow < rows; crow++)
143       {
144         count[crow] = 0;
145       }
146       for (int row = 0; row < height; row++)
147       {
148         int[] colcount = countFeaturesAt(alignment, col, row, fr);
149         if (colcount != null)
150         {
151           for (int crow = 0; crow < rows; crow++)
152           {
153             count[crow] += colcount[crow];
154           }
155         }
156       }
157       counts[col] = count;
158       for (int crow = 0; crow < rows; crow++)
159       {
160         max[crow] = Math.max(count[crow], max[crow]);
161       }
162     }
163
164     boolean annotationAdded = false;
165
166     for (int anrow = 0; anrow < rows; anrow++)
167     {
168       Annotation[] anns = new Annotation[width];
169       long rmax = 0;
170       /*
171        * add counts as annotations. zeros are needed since select-by-annotation ignores empty annotation positions
172        */
173       for (int i = 0; i < counts.length; i++)
174       {
175         int count = counts[i][anrow];
176
177         Color color = ColorUtils.getGraduatedColour(count, 0, minColour,
178                 max[anrow], maxColour);
179         String str = String.valueOf(count);
180         anns[i] = new Annotation(str, str, '0', count, color);
181         rmax = Math.max(count, rmax);
182       }
183
184       /*
185        * construct or update the annotation
186        */
187       String description = counter.getDescriptions()[anrow];
188       if (!alignment.findAnnotation(description).iterator().hasNext())
189       {
190         annotationAdded = true;
191       }
192       AlignmentAnnotation ann = alignment.findOrCreateAnnotation(
193               counter.getNames()[anrow], description, false, null, null);
194       ann.description = description;
195       ann.showAllColLabels = true;
196       ann.scaleColLabel = true;
197       ann.graph = AlignmentAnnotation.BAR_GRAPH;
198       ann.annotations = anns;
199       ann.graphMin = 0f; // minimum always zero count
200       ann.graphMax = rmax; // maximum count from loop over feature columns
201       ann.validateRangeAndDisplay();
202       if (!ourAnnots.contains(ann))
203       {
204         ourAnnots.add(ann);
205       }
206     }
207     return annotationAdded;
208   }
209
210   /**
211    * Returns a count of any feature types present at the specified position of
212    * the alignment
213    * 
214    * @param alignment
215    * @param col
216    *          (0..)
217    * @param row
218    * @param fr
219    */
220   int[] countFeaturesAt(AlignmentI alignment, int col, int row,
221           FeatureRenderer fr)
222   {
223     SequenceI seq = alignment.getSequenceAt(row);
224     if (seq == null)
225     {
226       return null;
227     }
228     if (col >= seq.getLength())
229     {
230       return null;// sequence doesn't extend this far
231     }
232     char res = seq.getCharAt(col);
233     if (Comparison.isGap(res))
234     {
235       return null;
236     }
237
238     /*
239      * compute a count for any displayed features at residue
240      */
241     // see JAL-2075
242     List<SequenceFeature> features = fr.findFeaturesAtColumn(seq, col + 1);
243     int[] count = this.counter.count(String.valueOf(res), features);
244     return count;
245   }
246
247   /**
248    * Method called when the user changes display options that may affect how the
249    * annotation is rendered, but do not change its values. Currently no such
250    * options affect user-defined annotation, so this method does nothing.
251    */
252   @Override
253   public void updateAnnotation()
254   {
255     // do nothing
256   }
257
258   /**
259    * Answers true to indicate that if this worker's annotation is deleted from
260    * the display, the worker should also be removed. This prevents it running
261    * and recreating the annotation when the alignment changes.
262    */
263   @Override
264   public boolean isDeletable()
265   {
266     return true;
267   }
268 }