JAL-2526 Sequence.findPositions to get residue positions for column
[jalview.git] / src / jalview / renderer / seqfeatures / FeatureRenderer.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.renderer.seqfeatures;
22
23 import jalview.api.AlignViewportI;
24 import jalview.datamodel.Range;
25 import jalview.datamodel.SequenceFeature;
26 import jalview.datamodel.SequenceI;
27 import jalview.util.Comparison;
28 import jalview.viewmodel.seqfeatures.FeatureRendererModel;
29
30 import java.awt.AlphaComposite;
31 import java.awt.Color;
32 import java.awt.FontMetrics;
33 import java.awt.Graphics;
34 import java.awt.Graphics2D;
35 import java.util.List;
36
37 public class FeatureRenderer extends FeatureRendererModel
38 {
39   private static final AlphaComposite NO_TRANSPARENCY = AlphaComposite
40           .getInstance(AlphaComposite.SRC_OVER, 1.0f);
41
42   /**
43    * Constructor given a viewport
44    * 
45    * @param viewport
46    */
47   public FeatureRenderer(AlignViewportI viewport)
48   {
49     this.av = viewport;
50   }
51
52   /**
53    * Renders the sequence using the given feature colour between the given start
54    * and end columns. Returns true if at least one column is drawn, else false
55    * (the feature range does not overlap the start and end positions).
56    * 
57    * @param g
58    * @param seq
59    * @param featureStart
60    * @param featureEnd
61    * @param featureColour
62    * @param start
63    * @param end
64    * @param y1
65    * @param colourOnly
66    * @return
67    */
68   boolean renderFeature(Graphics g, SequenceI seq, int featureStart,
69           int featureEnd, Color featureColour, int start, int end, int y1,
70           boolean colourOnly)
71   {
72     int charHeight = av.getCharHeight();
73     int charWidth = av.getCharWidth();
74     boolean validCharWidth = av.isValidCharWidth();
75
76     if (featureStart > end || featureEnd < start)
77     {
78       return false;
79     }
80
81     if (featureStart < start)
82     {
83       featureStart = start;
84     }
85     if (featureEnd >= end)
86     {
87       featureEnd = end;
88     }
89     int pady = (y1 + charHeight) - charHeight / 5;
90
91     FontMetrics fm = g.getFontMetrics();
92     for (int i = featureStart; i <= featureEnd; i++)
93     {
94       char s = seq.getCharAt(i);
95
96       if (Comparison.isGap(s))
97       {
98         continue;
99       }
100
101       g.setColor(featureColour);
102
103       g.fillRect((i - start) * charWidth, y1, charWidth,
104               charHeight);
105
106       if (colourOnly || !validCharWidth)
107       {
108         continue;
109       }
110
111       g.setColor(Color.white);
112       int charOffset = (charWidth - fm.charWidth(s)) / 2;
113       g.drawString(String.valueOf(s), charOffset
114               + (charWidth * (i - start)), pady);
115     }
116     return true;
117   }
118
119   /**
120    * Renders the sequence using the given SCORE feature colour between the given
121    * start and end columns. Returns true if at least one column is drawn, else
122    * false (the feature range does not overlap the start and end positions).
123    * 
124    * @param g
125    * @param seq
126    * @param fstart
127    * @param fend
128    * @param featureColour
129    * @param start
130    * @param end
131    * @param y1
132    * @param bs
133    * @param colourOnly
134    * @return
135    */
136   boolean renderScoreFeature(Graphics g, SequenceI seq, int fstart,
137           int fend, Color featureColour, int start, int end, int y1,
138           byte[] bs, boolean colourOnly)
139   {
140     if (fstart > end || fend < start)
141     {
142       return false;
143     }
144
145     if (fstart < start)
146     { // fix for if the feature we have starts before the sequence start,
147       fstart = start; // but the feature end is still valid!!
148     }
149
150     if (fend >= end)
151     {
152       fend = end;
153     }
154     int charHeight = av.getCharHeight();
155     int pady = (y1 + charHeight) - charHeight / 5;
156     int ystrt = 0, yend = charHeight;
157     if (bs[0] != 0)
158     {
159       // signed - zero is always middle of residue line.
160       if (bs[1] < 128)
161       {
162         yend = charHeight * (128 - bs[1]) / 512;
163         ystrt = charHeight - yend / 2;
164       }
165       else
166       {
167         ystrt = charHeight / 2;
168         yend = charHeight * (bs[1] - 128) / 512;
169       }
170     }
171     else
172     {
173       yend = charHeight * bs[1] / 255;
174       ystrt = charHeight - yend;
175
176     }
177
178     FontMetrics fm = g.getFontMetrics();
179     int charWidth = av.getCharWidth();
180
181     for (int i = fstart; i <= fend; i++)
182     {
183       char s = seq.getCharAt(i);
184
185       if (Comparison.isGap(s))
186       {
187         continue;
188       }
189
190       g.setColor(featureColour);
191       int x = (i - start) * charWidth;
192       g.drawRect(x, y1, charWidth, charHeight);
193       g.fillRect(x, y1 + ystrt, charWidth, yend);
194
195       if (colourOnly || !av.isValidCharWidth())
196       {
197         continue;
198       }
199
200       g.setColor(Color.black);
201       int charOffset = (charWidth - fm.charWidth(s)) / 2;
202       g.drawString(String.valueOf(s), charOffset
203               + (charWidth * (i - start)), pady);
204     }
205     return true;
206   }
207
208   /**
209    * {@inheritDoc}
210    */
211   @Override
212   public Color findFeatureColour(SequenceI seq, int column, Graphics g)
213   {
214     if (!av.isShowSequenceFeatures())
215     {
216       return null;
217     }
218
219     if (Comparison.isGap(seq.getCharAt(column)))
220     {
221       return Color.white;
222     }
223
224     Color renderedColour = null;
225     if (transparency == 1.0f)
226     {
227       /*
228        * simple case - just find the topmost rendered visible feature colour
229        */
230       renderedColour = findFeatureColour(seq, seq.findPosition(column));
231     }
232     else
233     {
234       /*
235        * transparency case - draw all visible features in render order to
236        * build up a composite colour on the graphics context
237        */
238       renderedColour = drawSequence(g, seq, column, column, 0, true);
239     }
240     return renderedColour;
241   }
242
243   /**
244    * Draws the sequence features on the graphics context, or just determines the
245    * colour that would be drawn (if flag colourOnly is true). Returns the last
246    * colour drawn (which may not be the effective colour if transparency
247    * applies), or null if no feature is drawn in the range given.
248    * 
249    * @param g
250    *          the graphics context to draw on (may be null if colourOnly==true)
251    * @param seq
252    * @param start
253    *          start column
254    * @param end
255    *          end column
256    * @param y1
257    *          vertical offset at which to draw on the graphics
258    * @param colourOnly
259    *          if true, only do enough to determine the colour for the position,
260    *          do not draw the character
261    * @return
262    */
263   public synchronized Color drawSequence(final Graphics g,
264           final SequenceI seq, int start, int end, int y1,
265           boolean colourOnly)
266   {
267     if (!seq.getFeatures().hasFeatures())
268     {
269       return null;
270     }
271
272     updateFeatures();
273
274     if (transparency != 1f && g != null)
275     {
276       Graphics2D g2 = (Graphics2D) g;
277       g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
278               transparency));
279     }
280
281     /*
282      * get range of sequence positions within column range
283      */
284     Range seqRange = seq.findPositions(start, end);
285     if (seqRange == null)
286     {
287       return null;
288     }
289
290     Color drawnColour = null;
291
292     /*
293      * iterate over features in ordering of their rendering (last is on top)
294      */
295     for (int renderIndex = 0; renderIndex < renderOrder.length; renderIndex++)
296     {
297       String type = renderOrder[renderIndex];
298       if (!showFeatureOfType(type))
299       {
300         continue;
301       }
302
303       List<SequenceFeature> overlaps = seq.findFeatures(seqRange.start,
304               seqRange.end, type);
305       for (SequenceFeature sequenceFeature : overlaps)
306       {
307         /*
308          * a feature type may be flagged as shown but the group 
309          * an instance of it belongs to may be hidden
310          */
311         if (featureGroupNotShown(sequenceFeature))
312         {
313           continue;
314         }
315
316         Color featureColour = getColour(sequenceFeature);
317         boolean isContactFeature = sequenceFeature.isContactFeature();
318
319         // todo overload findIndex using Location data
320         int featureStartCol = seq.findIndex(sequenceFeature.begin);
321         int featureEndCol = seq.findIndex(sequenceFeature.end);
322         if (isContactFeature)
323         {
324           boolean drawn = renderFeature(g, seq,
325                   featureStartCol - 1,
326                   featureStartCol - 1, featureColour,
327                   start, end, y1, colourOnly);
328           drawn |= renderFeature(g, seq,
329                   featureEndCol - 1,
330                   featureEndCol - 1, featureColour,
331                   start, end, y1, colourOnly);
332           if (drawn)
333           {
334             drawnColour = featureColour;
335           }
336         }
337         else if (showFeature(sequenceFeature))
338         {
339           /*
340            * showing feature score by height of colour
341            * is not implemented as a selectable option 
342            *
343           if (av.isShowSequenceFeaturesHeight()
344                   && !Float.isNaN(sequenceFeature.score))
345           {
346             boolean drawn = renderScoreFeature(g, seq,
347                     seq.findIndex(sequenceFeature.begin) - 1,
348                     seq.findIndex(sequenceFeature.end) - 1, featureColour,
349                     start, end, y1, normaliseScore(sequenceFeature),
350                     colourOnly);
351             if (drawn)
352             {
353               drawnColour = featureColour;
354             }
355           }
356           else
357           {
358           */
359             boolean drawn = renderFeature(g, seq,
360                     featureStartCol - 1,
361                     featureEndCol - 1, featureColour,
362                     start, end, y1, colourOnly);
363             if (drawn)
364             {
365               drawnColour = featureColour;
366             }
367           /*}*/
368         }
369       }
370     }
371
372     if (transparency != 1.0f && g != null)
373     {
374       /*
375        * reset transparency
376        */
377       Graphics2D g2 = (Graphics2D) g;
378       g2.setComposite(NO_TRANSPARENCY);
379     }
380
381     return drawnColour;
382   }
383
384   /**
385    * Called when alignment in associated view has new/modified features to
386    * discover and display.
387    * 
388    */
389   @Override
390   public void featuresAdded()
391   {
392     findAllFeatures();
393   }
394
395   /**
396    * Returns the sequence feature colour rendered at the given sequence
397    * position, or null if none found. The feature of highest render order (i.e.
398    * on top) is found, subject to both feature type and feature group being
399    * visible, and its colour returned.
400    * 
401    * @param seq
402    * @param pos
403    * @return
404    */
405   Color findFeatureColour(SequenceI seq, int pos)
406   {
407     /*
408      * check for new feature added while processing
409      */
410     updateFeatures();
411
412     /*
413      * inspect features in reverse renderOrder (the last in the array is 
414      * displayed on top) until we find one that is rendered at the position
415      */
416     for (int renderIndex = renderOrder.length - 1; renderIndex >= 0; renderIndex--)
417     {
418       String type = renderOrder[renderIndex];
419       if (!showFeatureOfType(type))
420       {
421         continue;
422       }
423
424       List<SequenceFeature> overlaps = seq.findFeatures(pos, pos, type);
425       for (SequenceFeature sequenceFeature : overlaps)
426       {
427         if (!featureGroupNotShown(sequenceFeature))
428         {
429           return getColour(sequenceFeature);
430         }
431       }
432     }
433   
434     /*
435      * no displayed feature found at position
436      */
437     return null;
438   }
439 }