2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
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.
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.
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.
21 package jalview.renderer.seqfeatures;
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;
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;
42 public class FeatureRenderer extends FeatureRendererModel
44 private static final AlphaComposite NO_TRANSPARENCY = AlphaComposite
45 .getInstance(AlphaComposite.SRC_OVER, 1.0f);
48 * Constructor given a viewport
52 public FeatureRenderer(AlignViewportI viewport)
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).
66 * @param featureColour
73 boolean renderFeature(Graphics g, SequenceI seq, int featureStart,
74 int featureEnd, Color featureColour, int start, int end, int y1,
77 int charHeight = av.getCharHeight();
78 int charWidth = av.getCharWidth();
79 boolean validCharWidth = av.isValidCharWidth();
81 if (featureStart > end || featureEnd < start)
86 if (featureStart < start)
90 if (featureEnd >= end)
94 int pady = (y1 + charHeight) - charHeight / 5;
96 FontMetrics fm = g.getFontMetrics();
97 for (int i = featureStart; i <= featureEnd; i++)
99 char s = seq.getCharAt(i);
101 if (Comparison.isGap(s))
106 g.setColor(featureColour);
108 g.fillRect((i - start) * charWidth, y1, charWidth, charHeight);
110 if (colourOnly || !validCharWidth)
116 * JAL-3045 text is always drawn over features, even if
117 * 'Show Text' is unchecked in the format menu
119 g.setColor(Color.white);
120 int charOffset = (charWidth - fm.charWidth(s)) / 2;
121 g.drawString(String.valueOf(s),
122 charOffset + (charWidth * (i - start)), pady);
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).
136 * @param featureColour
144 boolean renderScoreFeature(Graphics g, SequenceI seq, int fstart,
145 int fend, Color featureColour, int start, int end, int y1,
146 byte[] bs, boolean colourOnly)
148 if (fstart > end || fend < start)
154 { // fix for if the feature we have starts before the sequence start,
155 fstart = start; // but the feature end is still valid!!
162 int charHeight = av.getCharHeight();
163 int pady = (y1 + charHeight) - charHeight / 5;
164 int ystrt = 0, yend = charHeight;
167 // signed - zero is always middle of residue line.
170 yend = charHeight * (128 - bs[1]) / 512;
171 ystrt = charHeight - yend / 2;
175 ystrt = charHeight / 2;
176 yend = charHeight * (bs[1] - 128) / 512;
181 yend = charHeight * bs[1] / 255;
182 ystrt = charHeight - yend;
186 FontMetrics fm = g.getFontMetrics();
187 int charWidth = av.getCharWidth();
189 for (int i = fstart; i <= fend; i++)
191 char s = seq.getCharAt(i);
193 if (Comparison.isGap(s))
198 g.setColor(featureColour);
199 int x = (i - start) * charWidth;
200 g.drawRect(x, y1, charWidth, charHeight);
201 g.fillRect(x, y1 + ystrt, charWidth, yend);
203 if (colourOnly || !av.isValidCharWidth())
208 g.setColor(Color.black);
209 int charOffset = (charWidth - fm.charWidth(s)) / 2;
210 g.drawString(String.valueOf(s),
211 charOffset + (charWidth * (i - start)), pady);
220 public Color findFeatureColour(SequenceI seq, int column, Graphics g)
222 if (!av.isShowSequenceFeatures())
227 // column is 'base 1' but getCharAt is an array index (ie from 0)
228 if (Comparison.isGap(seq.getCharAt(column - 1)))
231 * returning null allows the colour scheme to provide gap colour
232 * - normally white, but can be customised
237 Color renderedColour = null;
238 if (transparency == 1.0f)
241 * simple case - just find the topmost rendered visible feature colour
243 renderedColour = findFeatureColour(seq, column);
248 * transparency case - draw all visible features in render order to
249 * build up a composite colour on the graphics context
251 renderedColour = drawSequence(g, seq, column, column, 0, true);
253 return renderedColour;
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.
263 * the graphics context to draw on (may be null if colourOnly==true)
270 * vertical offset at which to draw on the graphics
272 * if true, only do enough to determine the colour for the position,
273 * do not draw the character
276 public synchronized Color drawSequence(final Graphics g,
277 final SequenceI seq, int start, int end, int y1,
281 * if columns are all gapped, or sequence has no features, nothing to do
283 ContiguousI visiblePositions = seq.findPositions(start + 1, end + 1);
284 if (visiblePositions == null || !seq.getFeatures().hasFeatures()
285 && !av.isShowComplementFeatures())
292 if (transparency != 1f && g != null)
294 Graphics2D g2 = (Graphics2D) g;
295 g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
299 Color drawnColour = null;
302 * draw 'complement' features below ours if configured to do so
304 if (av.isShowComplementFeatures()
305 && !av.isShowComplementFeaturesOnTop())
307 drawnColour = drawComplementFeatures(g, seq, start, end, y1,
308 colourOnly, visiblePositions, drawnColour);
312 * iterate over features in ordering of their rendering (last is on top)
314 for (int renderIndex = 0; renderIndex < renderOrder.length; renderIndex++)
316 String type = renderOrder[renderIndex];
317 if (!showFeatureOfType(type))
322 FeatureColourI fc = getFeatureStyle(type);
323 List<SequenceFeature> overlaps = seq.getFeatures().findFeatures(
324 visiblePositions.getBegin(), visiblePositions.getEnd(), type);
326 if (overlaps.size() > 1 && fc.isSimpleColour())
328 filterFeaturesForDisplay(overlaps);
331 for (SequenceFeature sf : overlaps)
333 Color featureColour = getColor(sf, fc);
334 if (featureColour == null)
337 * feature excluded by filters, or colour threshold
343 * if feature starts/ends outside the visible range,
344 * restrict to visible positions (or if a contact feature,
345 * to a single position)
347 int visibleStart = sf.getBegin();
348 if (visibleStart < visiblePositions.getBegin())
350 visibleStart = sf.isContactFeature() ? sf.getEnd()
351 : visiblePositions.getBegin();
353 int visibleEnd = sf.getEnd();
354 if (visibleEnd > visiblePositions.getEnd())
356 visibleEnd = sf.isContactFeature() ? sf.getBegin()
357 : visiblePositions.getEnd();
360 int featureStartCol = seq.findIndex(visibleStart);
361 int featureEndCol = sf.begin == sf.end ? featureStartCol : seq
362 .findIndex(visibleEnd);
364 // Color featureColour = getColour(sequenceFeature);
366 boolean isContactFeature = sf.isContactFeature();
368 if (isContactFeature)
370 boolean drawn = renderFeature(g, seq, featureStartCol - 1,
371 featureStartCol - 1, featureColour, start, end, y1,
373 drawn |= renderFeature(g, seq, featureEndCol - 1,
374 featureEndCol - 1, featureColour, start, end, y1,
378 drawnColour = featureColour;
384 * showing feature score by height of colour
385 * is not implemented as a selectable option
387 if (av.isShowSequenceFeaturesHeight()
388 && !Float.isNaN(sequenceFeature.score))
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),
397 drawnColour = featureColour;
403 boolean drawn = renderFeature(g, seq,
405 featureEndCol - 1, featureColour,
406 start, end, y1, colourOnly);
409 drawnColour = featureColour;
417 * draw 'complement' features above ours if configured to do so
419 if (av.isShowComplementFeatures() && av.isShowComplementFeaturesOnTop())
421 drawnColour = drawComplementFeatures(g, seq, start, end, y1,
422 colourOnly, visiblePositions, drawnColour);
425 if (transparency != 1.0f && g != null)
430 Graphics2D g2 = (Graphics2D) g;
431 g2.setComposite(NO_TRANSPARENCY);
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
448 * @param visiblePositions
452 Color drawComplementFeatures(final Graphics g, final SequenceI seq,
453 int start, int end, int y1, boolean colourOnly,
454 ContiguousI visiblePositions, Color drawnColour)
456 AlignViewportI comp = av.getCodingComplement();
457 FeatureRenderer fr2 = Desktop.getAlignFrameFor(comp)
458 .getFeatureRenderer();
460 final int visibleStart = visiblePositions.getBegin();
461 final int visibleEnd = visiblePositions.getEnd();
463 for (int pos = visibleStart; pos <= visibleEnd; pos++)
465 int column = seq.findIndex(pos);
466 MappedFeatures mf = fr2
467 .findComplementFeaturesAtResidue(seq, pos);
470 for (SequenceFeature sf : mf.features)
472 FeatureColourI fc = fr2.getFeatureStyle(sf.getType());
473 Color featureColour = fr2.getColor(sf, fc);
474 renderFeature(g, seq, column - 1, column - 1, featureColour,
475 start, end, y1, colourOnly);
476 drawnColour = featureColour;
484 * Called when alignment in associated view has new/modified features to
485 * discover and display.
489 public void featuresAdded()
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).
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.
510 Color findFeatureColour(SequenceI seq, int column)
513 * check for new feature added while processing
518 * show complement features on top (if configured to show them)
520 if (av.isShowComplementFeatures() && av.isShowComplementFeaturesOnTop())
522 Color col = findComplementFeatureColour(seq, column);
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
533 for (int renderIndex = renderOrder.length
534 - 1; renderIndex >= 0; renderIndex--)
536 String type = renderOrder[renderIndex];
537 if (!showFeatureOfType(type))
542 List<SequenceFeature> overlaps = seq.findFeatures(column, column,
544 for (SequenceFeature sequenceFeature : overlaps)
546 if (!featureGroupNotShown(sequenceFeature))
548 Color col = getColour(sequenceFeature);
558 * show complement features underneath (if configured to show them)
561 if (av.isShowComplementFeatures()
562 && !av.isShowComplementFeaturesOnTop())
564 col = findComplementFeatureColour(seq, column);
570 Color findComplementFeatureColour(SequenceI seq, int column)
572 AlignViewportI complement = av.getCodingComplement();
573 AlignFrame af = Desktop.getAlignFrameFor(complement);
574 FeatureRendererModel fr2 = af.getFeatureRenderer();
575 MappedFeatures mf = fr2.findComplementFeaturesAtResidue(
576 seq, seq.findPosition(column - 1));
581 ReverseListIterator<SequenceFeature> it = new ReverseListIterator<>(
585 SequenceFeature sf = it.next();
586 if (!fr2.featureGroupNotShown(sf))
588 Color col = fr2.getColour(sf);