Clover icon

Coverage Report

  1. Project Clover database Thu Aug 13 2020 12:04:21 BST
  2. Package jalview.renderer.seqfeatures

File FeatureRenderer.java

 

Coverage histogram

../../../img/srcFileCovDistChart6.png
33% of files have more coverage

Code metrics

94
159
9
1
597
351
69
0.43
17.67
9
7.67

Classes

Class Line # Actions
FeatureRenderer 42 159 69
0.5267175452.7%
 

Contributing tests

This file is covered by 114 tests. .

Source view

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