Clover icon

Coverage Report

  1. Project Clover database Fri Dec 6 2024 13:47:14 GMT
  2. Package jalview.renderer.seqfeatures

File FeatureRenderer.java

 

Coverage histogram

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

Code metrics

96
160
9
1
600
350
70
0.44
17.78
9
7.78

Classes

Class Line # Actions
FeatureRenderer 42 160 70
0.5320754653.2%
 

Contributing tests

This file is covered by 201 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  549 toggle public FeatureRenderer(AlignViewportI viewport)
53    {
54  549 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  17056 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  17056 int charHeight = av.getCharHeight();
78  17056 int charWidth = av.getCharWidth();
79  17056 boolean validCharWidth = av.isValidCharWidth();
80   
81  17056 if (featureStart > end || featureEnd < start)
82    {
83  0 return false;
84    }
85   
86  17056 if (featureStart < start)
87    {
88  0 featureStart = start;
89    }
90  17056 if (featureEnd >= end)
91    {
92  460 featureEnd = end;
93    }
94  17056 int pady = (y1 + charHeight) - charHeight / 5;
95   
96  17056 FontMetrics fm = g.getFontMetrics();
97  45934 for (int i = featureStart; i <= featureEnd; i++)
98    {
99  28878 char s = seq.getCharAt(i);
100   
101  28878 if (Comparison.isGap(s))
102    {
103  0 continue;
104    }
105   
106  28878 g.setColor(featureColour);
107   
108  28878 g.fillRect((i - start) * charWidth, y1, charWidth, charHeight);
109   
110  28878 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  28872 g.setColor(Color.white);
120  28872 int charOffset = (charWidth - fm.charWidth(s)) / 2;
121  28872 g.drawString(String.valueOf(s),
122    charOffset + (charWidth * (i - start)), pady);
123    }
124  17056 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  28992 toggle @Override
220    public Color findFeatureColour(SequenceI seq, int column, Graphics g)
221    {
222  28993 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  28992 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  4008 return null;
235    }
236   
237  24984 Color renderedColour = null;
238  24984 if (transparency == 1.0f)
239    {
240    /*
241    * simple case - just find the topmost rendered visible feature colour
242    */
243  24981 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  24984 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  1098 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  1098 ContiguousI visiblePositions = seq.findPositions(start + 1, end + 1);
284  1098 if (visiblePositions == null || !seq.getFeatures().hasFeatures()
285    && !av.isShowComplementFeatures())
286    {
287  166 return null;
288    }
289   
290  932 updateFeatures();
291   
292  932 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  932 Color drawnColour = null;
300   
301    /*
302    * draw 'complement' features below ours if configured to do so
303    */
304  932 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  26491 for (int renderIndex = 0; renderIndex < renderOrder.length; renderIndex++)
315    {
316  25559 String type = renderOrder[renderIndex];
317  25559 if (!showFeatureOfType(type))
318    {
319  18693 continue;
320    }
321   
322  6866 FeatureColourI fc = getFeatureStyle(type);
323  6866 List<SequenceFeature> overlaps = seq.getFeatures().findFeatures(
324    visiblePositions.getBegin(), visiblePositions.getEnd(), type);
325   
326  6866 if (overlaps.size() > 1 && fc.isSimpleColour())
327    {
328  1408 filterFeaturesForDisplay(overlaps);
329    }
330   
331  6866 for (SequenceFeature sf : overlaps)
332    {
333  17056 Color featureColour = getColor(sf, fc);
334  17056 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  17056 int visibleStart = sf.getBegin();
348  17056 if (visibleStart < visiblePositions.getBegin())
349    {
350  108 visibleStart = sf.isContactFeature() ? sf.getEnd()
351    : visiblePositions.getBegin();
352    }
353  17056 int visibleEnd = sf.getEnd();
354  17056 if (visibleEnd > visiblePositions.getEnd())
355    {
356  16 visibleEnd = sf.isContactFeature() ? sf.getBegin()
357    : visiblePositions.getEnd();
358    }
359   
360  17056 int featureStartCol = seq.findIndex(visibleStart);
361  17056 int featureEndCol = sf.begin == sf.end ? featureStartCol
362    : seq.findIndex(visibleEnd);
363   
364    // Color featureColour = getColour(sequenceFeature);
365   
366  17056 boolean isContactFeature = sf.isContactFeature();
367   
368  17056 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  17056 boolean drawn = renderFeature(g, seq, featureStartCol - 1,
404    featureEndCol - 1, featureColour, start, end, y1,
405    colourOnly);
406  17056 if (drawn)
407    {
408  17056 drawnColour = featureColour;
409    }
410    /*}*/
411    }
412    }
413    }
414   
415    /*
416    * draw 'complement' features above ours if configured to do so
417    */
418  932 if (av.isShowComplementFeatures() && av.isShowComplementFeaturesOnTop())
419    {
420  0 drawnColour = drawComplementFeatures(g, seq, start, end, y1,
421    colourOnly, visiblePositions, drawnColour);
422    }
423   
424  932 if (transparency != 1.0f && g != null)
425    {
426    /*
427    * reset transparency
428    */
429  4 Graphics2D g2 = (Graphics2D) g;
430  4 g2.setComposite(NO_TRANSPARENCY);
431    }
432   
433  932 return drawnColour;
434    }
435   
436    /**
437    * Find any features on the CDS/protein complement of the sequence region and
438    * draw them, with visibility and colouring as configured in the complementary
439    * viewport
440    *
441    * @param g
442    * @param seq
443    * @param start
444    * @param end
445    * @param y1
446    * @param colourOnly
447    * @param visiblePositions
448    * @param drawnColour
449    * @return
450    */
 
451  0 toggle Color drawComplementFeatures(final Graphics g, final SequenceI seq,
452    int start, int end, int y1, boolean colourOnly,
453    ContiguousI visiblePositions, Color drawnColour)
454    {
455  0 AlignViewportI comp = av.getCodingComplement();
456  0 FeatureRenderer fr2 = Desktop.getAlignFrameFor(comp)
457    .getFeatureRenderer();
458   
459  0 final int visibleStart = visiblePositions.getBegin();
460  0 final int visibleEnd = visiblePositions.getEnd();
461   
462  0 for (int pos = visibleStart; pos <= visibleEnd; pos++)
463    {
464  0 int column = seq.findIndex(pos);
465  0 MappedFeatures mf = fr2.findComplementFeaturesAtResidue(seq, pos);
466  0 if (mf != null)
467    {
468  0 for (SequenceFeature sf : mf.features)
469    {
470  0 FeatureColourI fc = fr2.getFeatureStyle(sf.getType());
471  0 Color featureColour = fr2.getColor(sf, fc);
472  0 renderFeature(g, seq, column - 1, column - 1, featureColour,
473    start, end, y1, colourOnly);
474  0 drawnColour = featureColour;
475    }
476    }
477    }
478  0 return drawnColour;
479    }
480   
481    /**
482    * Called when alignment in associated view has new/modified features to
483    * discover and display.
484    *
485    */
 
486  19 toggle @Override
487    public void featuresAdded()
488    {
489  19 findAllFeatures();
490    }
491   
492    /**
493    * Returns the sequence feature colour rendered at the given column position,
494    * or null if none found. The feature of highest render order (i.e. on top) is
495    * found, subject to both feature type and feature group being visible, and
496    * its colour returned. This method is suitable when no feature transparency
497    * applied (only the topmost visible feature colour is rendered).
498    * <p>
499    * Note this method does not check for a gap in the column so would return the
500    * colour for features enclosing a gapped column. Check for gap before calling
501    * if different behaviour is wanted.
502    *
503    * @param seq
504    * @param column
505    * (1..)
506    * @return
507    */
 
508  24981 toggle Color findFeatureColour(SequenceI seq, int column)
509    {
510    /*
511    * check for new feature added while processing
512    */
513  24981 updateFeatures();
514   
515    /*
516    * show complement features on top (if configured to show them)
517    */
518  24980 if (av.isShowComplementFeatures() && av.isShowComplementFeaturesOnTop())
519    {
520  0 Color col = findComplementFeatureColour(seq, column);
521  0 if (col != null)
522    {
523  0 return col;
524    }
525    }
526   
527    /*
528    * inspect features in reverse renderOrder (the last in the array is
529    * displayed on top) until we find one that is rendered at the position
530    */
531  24980 for (int renderIndex = renderOrder.length
532  738637 - 1; renderIndex >= 0; renderIndex--)
533    {
534  721462 String type = renderOrder[renderIndex];
535  722915 if (!showFeatureOfType(type))
536    {
537  462282 continue;
538    }
539   
540    /*
541    * find features of this type, and the colour of the _last_ one
542    * (the one that would be drawn on top) that has a colour
543    */
544  260701 List<SequenceFeature> overlaps = seq.findFeatures(column, column,
545    type);
546  260532 for (int i = overlaps.size() - 1; i >= 0; i--)
547    {
548  7652 SequenceFeature sequenceFeature = overlaps.get(i);
549  7652 if (!featureGroupNotShown(sequenceFeature))
550    {
551  7651 Color col = getColour(sequenceFeature);
552  7651 if (col != null)
553    {
554  7647 return col;
555    }
556    }
557    }
558    }
559   
560    /*
561    * show complement features underneath (if configured to show them)
562    */
563  17334 Color col = null;
564  17334 if (av.isShowComplementFeatures()
565    && !av.isShowComplementFeaturesOnTop())
566    {
567  0 col = findComplementFeatureColour(seq, column);
568    }
569   
570  17334 return col;
571    }
572   
 
573  0 toggle Color findComplementFeatureColour(SequenceI seq, int column)
574    {
575  0 AlignViewportI complement = av.getCodingComplement();
576  0 AlignFrame af = Desktop.getAlignFrameFor(complement);
577  0 FeatureRendererModel fr2 = af.getFeatureRenderer();
578  0 MappedFeatures mf = fr2.findComplementFeaturesAtResidue(seq,
579    seq.findPosition(column - 1));
580  0 if (mf == null)
581    {
582  0 return null;
583    }
584  0 ReverseListIterator<SequenceFeature> it = new ReverseListIterator<>(
585    mf.features);
586  0 while (it.hasNext())
587    {
588  0 SequenceFeature sf = it.next();
589  0 if (!fr2.featureGroupNotShown(sf))
590    {
591  0 Color col = fr2.getColour(sf);
592  0 if (col != null)
593    {
594  0 return col;
595    }
596    }
597    }
598  0 return null;
599    }
600    }