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 jalview.api.AlignViewportI;
24 import jalview.datamodel.SequenceFeature;
25 import jalview.datamodel.SequenceI;
26 import jalview.util.Comparison;
27 import jalview.viewmodel.seqfeatures.FeatureRendererModel;
29 import java.awt.AlphaComposite;
30 import java.awt.Color;
31 import java.awt.FontMetrics;
32 import java.awt.Graphics;
33 import java.awt.Graphics2D;
35 public class FeatureRenderer extends FeatureRendererModel
37 private static final AlphaComposite NO_TRANSPARENCY = AlphaComposite
38 .getInstance(AlphaComposite.SRC_OVER, 1.0f);
41 * Constructor given a viewport
45 public FeatureRenderer(AlignViewportI viewport)
51 * Renders the sequence using the given feature colour between the given start
52 * and end columns. Returns true if at least one column is drawn, else false
53 * (the feature range does not overlap the start and end positions).
59 * @param featureColour
66 boolean renderFeature(Graphics g, SequenceI seq, int featureStart,
67 int featureEnd, Color featureColour, int start, int end, int y1,
70 int charHeight = av.getCharHeight();
71 int charWidth = av.getCharWidth();
72 boolean validCharWidth = av.isValidCharWidth();
74 if (featureStart > end || featureEnd < start)
79 if (featureStart < start)
83 if (featureEnd >= end)
87 int pady = (y1 + charHeight) - charHeight / 5;
89 FontMetrics fm = g.getFontMetrics();
90 for (int i = featureStart; i <= featureEnd; i++)
92 char s = seq.getCharAt(i);
94 if (Comparison.isGap(s))
99 g.setColor(featureColour);
101 g.fillRect((i - start) * charWidth, y1, charWidth, charHeight);
103 if (colourOnly || !validCharWidth)
108 g.setColor(Color.white);
109 int charOffset = (charWidth - fm.charWidth(s)) / 2;
110 g.drawString(String.valueOf(s),
111 charOffset + (charWidth * (i - start)), pady);
117 * Renders the sequence using the given SCORE feature colour between the given
118 * start and end columns. Returns true if at least one column is drawn, else
119 * false (the feature range does not overlap the start and end positions).
125 * @param featureColour
133 boolean renderScoreFeature(Graphics g, SequenceI seq, int fstart,
134 int fend, Color featureColour, int start, int end, int y1,
135 byte[] bs, boolean colourOnly)
137 if (fstart > end || fend < start)
143 { // fix for if the feature we have starts before the sequence start,
144 fstart = start; // but the feature end is still valid!!
151 int charHeight = av.getCharHeight();
152 int pady = (y1 + charHeight) - charHeight / 5;
153 int ystrt = 0, yend = charHeight;
156 // signed - zero is always middle of residue line.
159 yend = charHeight * (128 - bs[1]) / 512;
160 ystrt = charHeight - yend / 2;
164 ystrt = charHeight / 2;
165 yend = charHeight * (bs[1] - 128) / 512;
170 yend = charHeight * bs[1] / 255;
171 ystrt = charHeight - yend;
175 FontMetrics fm = g.getFontMetrics();
176 int charWidth = av.getCharWidth();
178 for (int i = fstart; i <= fend; i++)
180 char s = seq.getCharAt(i);
182 if (Comparison.isGap(s))
187 g.setColor(featureColour);
188 int x = (i - start) * charWidth;
189 g.drawRect(x, y1, charWidth, charHeight);
190 g.fillRect(x, y1 + ystrt, charWidth, yend);
192 if (colourOnly || !av.isValidCharWidth())
197 g.setColor(Color.black);
198 int charOffset = (charWidth - fm.charWidth(s)) / 2;
199 g.drawString(String.valueOf(s),
200 charOffset + (charWidth * (i - start)), pady);
209 public Color findFeatureColour(SequenceI seq, int column, Graphics g)
211 if (!av.isShowSequenceFeatures())
216 SequenceFeature[] sequenceFeatures = seq.getSequenceFeatures();
218 if (sequenceFeatures == null || sequenceFeatures.length == 0)
223 if (Comparison.isGap(seq.getCharAt(column)))
226 * returning null allows the colour scheme to provide gap colour
227 * - normally white, but can be customised otherwise
232 Color renderedColour = null;
233 if (transparency == 1.0f)
236 * simple case - just find the topmost rendered visible feature colour
238 renderedColour = findFeatureColour(seq, seq.findPosition(column));
243 * transparency case - draw all visible features in render order to
244 * build up a composite colour on the graphics context
246 renderedColour = drawSequence(g, seq, column, column, 0, true);
248 return renderedColour;
252 * Draws the sequence features on the graphics context, or just determines the
253 * colour that would be drawn (if flag colourOnly is true). Returns the last
254 * colour drawn (which may not be the effective colour if transparency
255 * applies), or null if no feature is drawn in the range given.
258 * the graphics context to draw on (may be null if colourOnly==true)
265 * vertical offset at which to draw on the graphics
267 * if true, only do enough to determine the colour for the position,
268 * do not draw the character
271 public synchronized Color drawSequence(final Graphics g,
272 final SequenceI seq, int start, int end, int y1,
275 SequenceFeature[] sequenceFeatures = seq.getSequenceFeatures();
276 if (sequenceFeatures == null || sequenceFeatures.length == 0)
283 if (transparency != 1f && g != null)
285 Graphics2D g2 = (Graphics2D) g;
286 g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
290 int startPos = seq.findPosition(start);
291 int endPos = seq.findPosition(end);
293 int sfSize = sequenceFeatures.length;
294 Color drawnColour = null;
297 * iterate over features in ordering of their rendering (last is on top)
299 for (int renderIndex = 0; renderIndex < renderOrder.length; renderIndex++)
301 String type = renderOrder[renderIndex];
302 if (!showFeatureOfType(type))
307 // loop through all features in sequence to find
308 // current feature to render
309 for (int sfindex = 0; sfindex < sfSize; sfindex++)
311 final SequenceFeature sequenceFeature = sequenceFeatures[sfindex];
312 if (!sequenceFeature.type.equals(type))
318 * a feature type may be flagged as shown but the group
319 * an instance of it belongs to may be hidden
321 if (featureGroupNotShown(sequenceFeature))
327 * check feature overlaps the target range
328 * TODO: efficient retrieval of features overlapping a range
330 if (sequenceFeature.getBegin() > endPos
331 || sequenceFeature.getEnd() < startPos)
336 Color featureColour = getColour(sequenceFeature);
337 if (featureColour == null)
339 // score feature outwith threshold for colouring
343 boolean isContactFeature = sequenceFeature.isContactFeature();
345 if (isContactFeature)
347 boolean drawn = renderFeature(g, seq,
348 seq.findIndex(sequenceFeature.begin) - 1,
349 seq.findIndex(sequenceFeature.begin) - 1, featureColour,
350 start, end, y1, colourOnly);
351 drawn |= renderFeature(g, seq,
352 seq.findIndex(sequenceFeature.end) - 1,
353 seq.findIndex(sequenceFeature.end) - 1, featureColour,
354 start, end, y1, colourOnly);
357 drawnColour = featureColour;
362 if (av.isShowSequenceFeaturesHeight()
363 && !Float.isNaN(sequenceFeature.score))
365 boolean drawn = renderScoreFeature(g, seq,
366 seq.findIndex(sequenceFeature.begin) - 1,
367 seq.findIndex(sequenceFeature.end) - 1, featureColour,
368 start, end, y1, normaliseScore(sequenceFeature),
372 drawnColour = featureColour;
377 boolean drawn = renderFeature(g, seq,
378 seq.findIndex(sequenceFeature.begin) - 1,
379 seq.findIndex(sequenceFeature.end) - 1, featureColour,
380 start, end, y1, colourOnly);
383 drawnColour = featureColour;
390 if (transparency != 1.0f && g != null)
395 Graphics2D g2 = (Graphics2D) g;
396 g2.setComposite(NO_TRANSPARENCY);
403 * Answers true if the feature belongs to a feature group which is not
404 * currently displayed, else false
406 * @param sequenceFeature
409 protected boolean featureGroupNotShown(
410 final SequenceFeature sequenceFeature)
412 return featureGroups != null && sequenceFeature.featureGroup != null
413 && sequenceFeature.featureGroup.length() != 0
414 && featureGroups.containsKey(sequenceFeature.featureGroup)
415 && !featureGroups.get(sequenceFeature.featureGroup)
420 * Called when alignment in associated view has new/modified features to
421 * discover and display.
425 public void featuresAdded()
431 * Returns the sequence feature colour rendered at the given sequence
432 * position, or null if none found. The feature of highest render order (i.e.
433 * on top) is found, subject to both feature type and feature group being
434 * visible, and its colour returned.
440 Color findFeatureColour(SequenceI seq, int pos)
442 SequenceFeature[] sequenceFeatures = seq.getSequenceFeatures();
443 if (sequenceFeatures == null || sequenceFeatures.length == 0)
449 * check for new feature added while processing
454 * inspect features in reverse renderOrder (the last in the array is
455 * displayed on top) until we find one that is rendered at the position
457 for (int renderIndex = renderOrder.length
458 - 1; renderIndex >= 0; renderIndex--)
460 String type = renderOrder[renderIndex];
461 if (!showFeatureOfType(type))
466 for (int sfindex = 0; sfindex < sequenceFeatures.length; sfindex++)
468 SequenceFeature sequenceFeature = sequenceFeatures[sfindex];
469 if (!sequenceFeature.type.equals(type))
474 if (featureGroupNotShown(sequenceFeature))
480 * check the column position is within the feature range
481 * (or is one of the two contact positions for a contact feature)
483 boolean featureIsAtPosition = sequenceFeature.begin <= pos
484 && sequenceFeature.end >= pos;
485 if (sequenceFeature.isContactFeature())
487 featureIsAtPosition = sequenceFeature.begin == pos
488 || sequenceFeature.end == pos;
490 if (featureIsAtPosition)
492 return getColour(sequenceFeature);
498 * no displayed feature found at position