JAL-3210 fudge for JalviewJS to avoid IntervalStore incompatibility
[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 java.awt.AlphaComposite;
24 import java.awt.Color;
25 import java.awt.FontMetrics;
26 import java.awt.Graphics;
27 import java.awt.Graphics2D;
28 import java.util.List;
29
30 import jalview.api.AlignViewportI;
31 import jalview.api.FeatureColourI;
32 import jalview.datamodel.ContiguousI;
33 import jalview.datamodel.MappedFeatures;
34 import jalview.datamodel.SequenceFeature;
35 import jalview.datamodel.SequenceI;
36 import jalview.gui.AlignFrame;
37 import jalview.gui.Desktop;
38 import jalview.util.Comparison;
39 import jalview.util.ReverseListIterator;
40 import jalview.viewmodel.seqfeatures.FeatureRendererModel;
41
42 public class FeatureRenderer extends FeatureRendererModel
43 {
44   private static final AlphaComposite NO_TRANSPARENCY = AlphaComposite
45           .getInstance(AlphaComposite.SRC_OVER, 1.0f);
46
47   /**
48    * Constructor given a viewport
49    * 
50    * @param viewport
51    */
52   public FeatureRenderer(AlignViewportI viewport)
53   {
54     this.av = viewport;
55   }
56
57   /**
58    * Renders the sequence using the given feature colour between the given start
59    * and end columns. Returns true if at least one column is drawn, else false
60    * (the feature range does not overlap the start and end positions).
61    * 
62    * @param g
63    * @param seq
64    * @param featureStart
65    * @param featureEnd
66    * @param featureColour
67    * @param start
68    * @param end
69    * @param y1
70    * @param colourOnly
71    * @return
72    */
73   boolean renderFeature(Graphics g, SequenceI seq, int featureStart,
74           int featureEnd, Color featureColour, int start, int end, int y1,
75           boolean colourOnly)
76   {
77     int charHeight = av.getCharHeight();
78     int charWidth = av.getCharWidth();
79     boolean validCharWidth = av.isValidCharWidth();
80
81     if (featureStart > end || featureEnd < start)
82     {
83       return false;
84     }
85
86     if (featureStart < start)
87     {
88       featureStart = start;
89     }
90     if (featureEnd >= end)
91     {
92       featureEnd = end;
93     }
94     int pady = (y1 + charHeight) - charHeight / 5;
95
96     FontMetrics fm = g.getFontMetrics();
97     for (int i = featureStart; i <= featureEnd; i++)
98     {
99       char s = seq.getCharAt(i);
100
101       if (Comparison.isGap(s))
102       {
103         continue;
104       }
105
106       g.setColor(featureColour);
107
108       g.fillRect((i - start) * charWidth, y1, charWidth, charHeight);
109
110       if (colourOnly || !validCharWidth)
111       {
112         continue;
113       }
114
115       g.setColor(Color.white);
116       int charOffset = (charWidth - fm.charWidth(s)) / 2;
117       g.drawString(String.valueOf(s),
118               charOffset + (charWidth * (i - start)), pady);
119     }
120     return true;
121   }
122
123   /**
124    * Renders the sequence using the given SCORE feature colour between the given
125    * start and end columns. Returns true if at least one column is drawn, else
126    * false (the feature range does not overlap the start and end positions).
127    * 
128    * @param g
129    * @param seq
130    * @param fstart
131    * @param fend
132    * @param featureColour
133    * @param start
134    * @param end
135    * @param y1
136    * @param bs
137    * @param colourOnly
138    * @return
139    */
140   boolean renderScoreFeature(Graphics g, SequenceI seq, int fstart,
141           int fend, Color featureColour, int start, int end, int y1,
142           byte[] bs, boolean colourOnly)
143   {
144     if (fstart > end || fend < start)
145     {
146       return false;
147     }
148
149     if (fstart < start)
150     { // fix for if the feature we have starts before the sequence start,
151       fstart = start; // but the feature end is still valid!!
152     }
153
154     if (fend >= end)
155     {
156       fend = end;
157     }
158     int charHeight = av.getCharHeight();
159     int pady = (y1 + charHeight) - charHeight / 5;
160     int ystrt = 0, yend = charHeight;
161     if (bs[0] != 0)
162     {
163       // signed - zero is always middle of residue line.
164       if (bs[1] < 128)
165       {
166         yend = charHeight * (128 - bs[1]) / 512;
167         ystrt = charHeight - yend / 2;
168       }
169       else
170       {
171         ystrt = charHeight / 2;
172         yend = charHeight * (bs[1] - 128) / 512;
173       }
174     }
175     else
176     {
177       yend = charHeight * bs[1] / 255;
178       ystrt = charHeight - yend;
179
180     }
181
182     FontMetrics fm = g.getFontMetrics();
183     int charWidth = av.getCharWidth();
184
185     for (int i = fstart; i <= fend; i++)
186     {
187       char s = seq.getCharAt(i);
188
189       if (Comparison.isGap(s))
190       {
191         continue;
192       }
193
194       g.setColor(featureColour);
195       int x = (i - start) * charWidth;
196       g.drawRect(x, y1, charWidth, charHeight);
197       g.fillRect(x, y1 + ystrt, charWidth, yend);
198
199       if (colourOnly || !av.isValidCharWidth())
200       {
201         continue;
202       }
203
204       g.setColor(Color.black);
205       int charOffset = (charWidth - fm.charWidth(s)) / 2;
206       g.drawString(String.valueOf(s),
207               charOffset + (charWidth * (i - start)), pady);
208     }
209     return true;
210   }
211
212   /**
213    * {@inheritDoc}
214    */
215   @Override
216   public Color findFeatureColour(SequenceI seq, int column, Graphics g)
217   {
218     if (!av.isShowSequenceFeatures())
219     {
220       return null;
221     }
222
223     // column is 'base 1' but getCharAt is an array index (ie from 0)
224     if (Comparison.isGap(seq.getCharAt(column - 1)))
225     {
226       /*
227        * returning null allows the colour scheme to provide gap colour
228        * - normally white, but can be customised
229        */
230       return null;
231     }
232
233     Color renderedColour = null;
234     if (transparency == 1.0f)
235     {
236       /*
237        * simple case - just find the topmost rendered visible feature colour
238        */
239       renderedColour = findFeatureColour(seq, column);
240     }
241     else
242     {
243       /*
244        * transparency case - draw all visible features in render order to
245        * build up a composite colour on the graphics context
246        */
247       renderedColour = drawSequence(g, seq, column, column, 0, true);
248     }
249     return renderedColour;
250   }
251
252   /**
253    * Draws the sequence features on the graphics context, or just determines the
254    * colour that would be drawn (if flag colourOnly is true). Returns the last
255    * colour drawn (which may not be the effective colour if transparency
256    * applies), or null if no feature is drawn in the range given.
257    * 
258    * @param g
259    *          the graphics context to draw on (may be null if colourOnly==true)
260    * @param seq
261    * @param start
262    *          start column
263    * @param end
264    *          end column
265    * @param y1
266    *          vertical offset at which to draw on the graphics
267    * @param colourOnly
268    *          if true, only do enough to determine the colour for the position,
269    *          do not draw the character
270    * @return
271    */
272   public synchronized Color drawSequence(final Graphics g,
273           final SequenceI seq, int start, int end, int y1,
274           boolean colourOnly)
275   {
276     /*
277      * if columns are all gapped, or sequence has no features, nothing to do
278      */
279     ContiguousI visiblePositions = seq.findPositions(start + 1, end + 1);
280     if (visiblePositions == null || !seq.getFeatures().hasFeatures()
281             && !av.isShowComplementFeatures())
282     {
283       return null;
284     }
285
286     updateFeatures();
287
288     if (transparency != 1f && g != null)
289     {
290       Graphics2D g2 = (Graphics2D) g;
291       g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
292               transparency));
293     }
294
295     Color drawnColour = null;
296
297     /*
298      * draw 'complement' features below ours if configured to do so
299      */
300     if (av.isShowComplementFeatures()
301             && !av.isShowComplementFeaturesOnTop())
302     {
303       drawnColour = drawComplementFeatures(g, seq, start, end, y1,
304               colourOnly, visiblePositions, drawnColour);
305     }
306
307     /*
308      * iterate over features in ordering of their rendering (last is on top)
309      */
310     for (int renderIndex = 0; renderIndex < renderOrder.length; renderIndex++)
311     {
312       String type = renderOrder[renderIndex];
313       if (!showFeatureOfType(type))
314       {
315         continue;
316       }
317
318       FeatureColourI fc = getFeatureStyle(type);
319       List<SequenceFeature> overlaps = seq.getFeatures().findFeatures(
320               visiblePositions.getBegin(), visiblePositions.getEnd(), type);
321
322       if (overlaps.size() > 1 && fc.isSimpleColour())
323       {
324         filterFeaturesForDisplay(overlaps);
325       }
326
327       for (SequenceFeature sf : overlaps)
328       {
329         Color featureColour = getColor(sf, fc);
330         if (featureColour == null)
331         {
332           /*
333            * feature excluded by filters, or colour threshold
334            */
335           continue;
336         }
337
338         /*
339          * if feature starts/ends outside the visible range,
340          * restrict to visible positions (or if a contact feature,
341          * to a single position)
342          */
343         int visibleStart = sf.getBegin();
344         if (visibleStart < visiblePositions.getBegin())
345         {
346           visibleStart = sf.isContactFeature() ? sf.getEnd()
347                   : visiblePositions.getBegin();
348         }
349         int visibleEnd = sf.getEnd();
350         if (visibleEnd > visiblePositions.getEnd())
351         {
352           visibleEnd = sf.isContactFeature() ? sf.getBegin()
353                   : visiblePositions.getEnd();
354         }
355
356         int featureStartCol = seq.findIndex(visibleStart);
357         int featureEndCol = sf.begin == sf.end ? featureStartCol : seq
358                 .findIndex(visibleEnd);
359
360         // Color featureColour = getColour(sequenceFeature);
361
362         boolean isContactFeature = sf.isContactFeature();
363
364         if (isContactFeature)
365         {
366           boolean drawn = renderFeature(g, seq, featureStartCol - 1,
367                   featureStartCol - 1, featureColour, start, end, y1,
368                   colourOnly);
369           drawn |= renderFeature(g, seq, featureEndCol - 1,
370                   featureEndCol - 1, featureColour, start, end, y1,
371                   colourOnly);
372           if (drawn)
373           {
374             drawnColour = featureColour;
375           }
376         }
377         else
378         {
379           /*
380            * showing feature score by height of colour
381            * is not implemented as a selectable option 
382            *
383           if (av.isShowSequenceFeaturesHeight()
384                   && !Float.isNaN(sequenceFeature.score))
385           {
386             boolean drawn = renderScoreFeature(g, seq,
387                     seq.findIndex(sequenceFeature.begin) - 1,
388                     seq.findIndex(sequenceFeature.end) - 1, featureColour,
389                     start, end, y1, normaliseScore(sequenceFeature),
390                     colourOnly);
391             if (drawn)
392             {
393               drawnColour = featureColour;
394             }
395           }
396           else
397           {
398           */
399             boolean drawn = renderFeature(g, seq,
400                     featureStartCol - 1,
401                     featureEndCol - 1, featureColour,
402                     start, end, y1, colourOnly);
403             if (drawn)
404             {
405               drawnColour = featureColour;
406             }
407           /*}*/
408         }
409       }
410     }
411
412     /*
413      * draw 'complement' features above ours if configured to do so
414      */
415     if (av.isShowComplementFeatures() && av.isShowComplementFeaturesOnTop())
416     {
417       drawnColour = drawComplementFeatures(g, seq, start, end, y1,
418               colourOnly, visiblePositions, drawnColour);
419     }
420
421     if (transparency != 1.0f && g != null)
422     {
423       /*
424        * reset transparency
425        */
426       Graphics2D g2 = (Graphics2D) g;
427       g2.setComposite(NO_TRANSPARENCY);
428     }
429
430     return drawnColour;
431   }
432
433   /**
434    * Find any features on the CDS/protein complement of the sequence region and
435    * draw them, with visibility and colouring as configured in the complementary
436    * viewport
437    * 
438    * @param g
439    * @param seq
440    * @param start
441    * @param end
442    * @param y1
443    * @param colourOnly
444    * @param visiblePositions
445    * @param drawnColour
446    * @return
447    */
448   Color drawComplementFeatures(final Graphics g, final SequenceI seq,
449           int start, int end, int y1, boolean colourOnly,
450           ContiguousI visiblePositions, Color drawnColour)
451   {
452     AlignViewportI comp = av.getCodingComplement();
453     FeatureRenderer fr2 = Desktop.getAlignFrameFor(comp)
454             .getFeatureRenderer();
455
456     final int visibleStart = visiblePositions.getBegin();
457     final int visibleEnd = visiblePositions.getEnd();
458
459     for (int pos = visibleStart; pos <= visibleEnd; pos++)
460     {
461       int column = seq.findIndex(pos);
462       MappedFeatures mf = fr2
463               .findComplementFeaturesAtResidue(seq, pos);
464       if (mf != null)
465       {
466         for (SequenceFeature sf : mf.features)
467         {
468           FeatureColourI fc = fr2.getFeatureStyle(sf.getType());
469           Color featureColour = fr2.getColor(sf, fc);
470           renderFeature(g, seq, column - 1, column - 1, featureColour,
471                   start, end, y1, colourOnly);
472           drawnColour = featureColour;
473         }
474       }
475     }
476     return drawnColour;
477   }
478
479   /**
480    * Called when alignment in associated view has new/modified features to
481    * discover and display.
482    * 
483    */
484   @Override
485   public void featuresAdded()
486   {
487     findAllFeatures();
488   }
489
490   /**
491    * Returns the sequence feature colour rendered at the given column position,
492    * or null if none found. The feature of highest render order (i.e. on top) is
493    * found, subject to both feature type and feature group being visible, and
494    * its colour returned. This method is suitable when no feature transparency
495    * applied (only the topmost visible feature colour is rendered).
496    * <p>
497    * Note this method does not check for a gap in the column so would return the
498    * colour for features enclosing a gapped column. Check for gap before calling
499    * if different behaviour is wanted.
500    * 
501    * @param seq
502    * @param column
503    *          (1..)
504    * @return
505    */
506   Color findFeatureColour(SequenceI seq, int column)
507   {
508     /*
509      * check for new feature added while processing
510      */
511     updateFeatures();
512
513     /*
514      * show complement features on top (if configured to show them)
515      */
516     if (av.isShowComplementFeatures() && av.isShowComplementFeaturesOnTop())
517     {
518       Color col = findComplementFeatureColour(seq, column);
519       if (col != null)
520       {
521         return col;
522       }
523     }
524
525     /*
526      * inspect features in reverse renderOrder (the last in the array is 
527      * displayed on top) until we find one that is rendered at the position
528      */
529     for (int renderIndex = renderOrder.length
530             - 1; renderIndex >= 0; renderIndex--)
531     {
532       String type = renderOrder[renderIndex];
533       if (!showFeatureOfType(type))
534       {
535         continue;
536       }
537
538       List<SequenceFeature> overlaps = seq.findFeatures(column, column,
539               type);
540       for (SequenceFeature sequenceFeature : overlaps)
541       {
542         if (!featureGroupNotShown(sequenceFeature))
543         {
544           Color col = getColour(sequenceFeature);
545           if (col != null)
546           {
547             return col;
548           }
549         }
550       }
551     }
552
553     /*
554      * show complement features underneath (if configured to show them)
555      */
556     Color col = null;
557     if (av.isShowComplementFeatures()
558             && !av.isShowComplementFeaturesOnTop())
559     {
560       col = findComplementFeatureColour(seq, column);
561     }
562
563     return col;
564   }
565
566   Color findComplementFeatureColour(SequenceI seq, int column)
567   {
568     AlignViewportI complement = av.getCodingComplement();
569     AlignFrame af = Desktop.getAlignFrameFor(complement);
570     FeatureRendererModel fr2 = af.getFeatureRenderer();
571     MappedFeatures mf = fr2.findComplementFeaturesAtResidue(
572             seq, seq.findPosition(column - 1));
573     if (mf == null)
574     {
575       return null;
576     }
577     ReverseListIterator<SequenceFeature> it = new ReverseListIterator<>(
578             mf.features);
579     while (it.hasNext())
580     {
581       SequenceFeature sf = it.next();
582       if (!fr2.featureGroupNotShown(sf))
583       {
584         Color col = fr2.getColour(sf);
585         if (col != null)
586         {
587           return col;
588         }
589       }
590     }
591     return null;
592   }
593 }