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;
34 import java.awt.image.BufferedImage;
36 public class FeatureRenderer extends FeatureRendererModel
38 private static final AlphaComposite NO_TRANSPARENCY = AlphaComposite
39 .getInstance(AlphaComposite.SRC_OVER, 1.0f);
41 protected SequenceI lastSeq;
43 BufferedImage offscreenImage;
45 private volatile SequenceFeature[] lastSequenceFeatures;
50 * Constructor given a viewport
54 public FeatureRenderer(AlignViewportI viewport)
60 * Renders the sequence using the given feature colour between the given start
61 * and end columns. Returns true if at least one column is drawn, else false
62 * (the feature range does not overlap the start and end positions).
68 * @param featureColour
75 boolean renderFeature(Graphics g, SequenceI seq, int featureStart,
76 int featureEnd, Color featureColour, int start, int end, int y1,
79 int charHeight = av.getCharHeight();
80 int charWidth = av.getCharWidth();
81 boolean validCharWidth = av.isValidCharWidth();
83 if (featureStart > end || featureEnd < start)
88 if (featureStart < start)
92 if (featureEnd >= end)
96 int pady = (y1 + charHeight) - charHeight / 5;
98 FontMetrics fm = g.getFontMetrics();
99 for (int i = featureStart; i <= featureEnd; i++)
101 char s = seq.getCharAt(i);
103 if (Comparison.isGap(s))
108 g.setColor(featureColour);
110 g.fillRect((i - start) * charWidth, y1, charWidth,
113 if (colourOnly || !validCharWidth)
118 g.setColor(Color.white);
119 int charOffset = (charWidth - fm.charWidth(s)) / 2;
120 g.drawString(String.valueOf(s), charOffset
121 + (charWidth * (i - start)), pady);
127 * Renders the sequence using the given SCORE feature colour between the given
128 * start and end columns. Returns true if at least one column is drawn, else
129 * false (the feature range does not overlap the start and end positions).
135 * @param featureColour
143 boolean renderScoreFeature(Graphics g, SequenceI seq, int fstart,
144 int fend, Color featureColour, int start, int end, int y1,
145 byte[] bs, boolean colourOnly)
147 if (fstart > end || fend < start)
153 { // fix for if the feature we have starts before the sequence start,
154 fstart = start; // but the feature end is still valid!!
161 int charHeight = av.getCharHeight();
162 int pady = (y1 + charHeight) - charHeight / 5;
163 int ystrt = 0, yend = charHeight;
166 // signed - zero is always middle of residue line.
169 yend = charHeight * (128 - bs[1]) / 512;
170 ystrt = charHeight - yend / 2;
174 ystrt = charHeight / 2;
175 yend = charHeight * (bs[1] - 128) / 512;
180 yend = charHeight * bs[1] / 255;
181 ystrt = charHeight - yend;
185 FontMetrics fm = g.getFontMetrics();
186 int charWidth = av.getCharWidth();
188 for (int i = fstart; i <= fend; i++)
190 char s = seq.getCharAt(i);
192 if (Comparison.isGap(s))
197 g.setColor(featureColour);
198 int x = (i - start) * charWidth;
199 g.drawRect(x, y1, charWidth, charHeight);
200 g.fillRect(x, y1 + ystrt, charWidth, yend);
202 if (colourOnly || !av.isValidCharWidth())
207 g.setColor(Color.black);
208 int charOffset = (charWidth - fm.charWidth(s)) / 2;
209 g.drawString(String.valueOf(s), charOffset
210 + (charWidth * (i - start)), pady);
216 * This is used by Structure Viewers and the Overview Window to get the
217 * feature colour of the rendered sequence
219 * @param defaultColour
225 public Color findFeatureColour(Color defaultColour, SequenceI seq,
226 int column, Graphics g)
228 if (!av.isShowSequenceFeatures())
230 return defaultColour;
233 SequenceFeature[] sequenceFeatures = seq.getSequenceFeatures();
237 lastSequenceFeatures = sequenceFeatures;
238 if (lastSequenceFeatures != null)
240 sfSize = lastSequenceFeatures.length;
245 if (lastSequenceFeatures != sequenceFeatures)
247 lastSequenceFeatures = sequenceFeatures;
248 if (lastSequenceFeatures != null)
250 sfSize = lastSequenceFeatures.length;
255 if (lastSequenceFeatures == null || sfSize == 0)
257 return defaultColour;
260 if (Comparison.isGap(lastSeq.getCharAt(column)))
265 Color renderedColour = null;
266 if (transparency == 1.0f)
268 renderedColour = findFeatureColour(seq, seq.findPosition(column));
272 renderedColour = drawSequence(g, lastSeq, column, column, 0, true);
274 return renderedColour == null ? defaultColour : renderedColour;
278 * Draws the sequence features on the graphics context, or just determines the
279 * colour that would be drawn (if flag offscreenrender is true).
282 * the graphics context to draw on (may be null if colourOnly==true)
285 * start column (or sequence position in offscreenrender mode)
287 * end column (not used in offscreenrender mode)
289 * vertical offset at which to draw on the graphics
291 * if true, only do enough to determine the colour for the position,
292 * do not draw the character
295 public synchronized Color drawSequence(final Graphics g,
296 final SequenceI seq, int start, int end, int y1,
299 SequenceFeature[] sequenceFeatures = seq.getSequenceFeatures();
300 if (sequenceFeatures == null || sequenceFeatures.length == 0)
307 if (lastSeq == null || seq != lastSeq
308 || sequenceFeatures != lastSequenceFeatures)
311 lastSequenceFeatures = sequenceFeatures;
314 if (transparency != 1f && g != null)
316 Graphics2D g2 = (Graphics2D) g;
317 g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
321 int startPos = lastSeq.findPosition(start);
322 int endPos = lastSeq.findPosition(end);
324 sfSize = lastSequenceFeatures.length;
325 Color drawnColour = null;
328 * iterate over features in ordering of their rendering;
329 * if drawing a range of columns, use render order to ensure last is on top
330 * if drawing a single column (as in findFeatureColour), with no
331 * transparency, work backwards to find the topmost rendered feature colour
333 for (int renderIndex = 0; renderIndex < renderOrder.length; renderIndex++)
335 String type = renderOrder[renderIndex];
336 if (!showFeatureOfType(type))
341 // loop through all features in sequence to find
342 // current feature to render
343 for (int sfindex = 0; sfindex < sfSize; sfindex++)
345 final SequenceFeature sequenceFeature = lastSequenceFeatures[sfindex];
346 if (!sequenceFeature.type.equals(type))
351 if (featureGroupNotShown(sequenceFeature))
357 * check feature overlaps the visible part of the alignment,
358 * unless doing offscreenRender (to the Overview window or a
359 * structure viewer) which is not limited
361 if (sequenceFeature.getBegin() > endPos
362 || sequenceFeature.getEnd() < startPos)
367 Color featureColour = getColour(sequenceFeature);
368 boolean isContactFeature = sequenceFeature.isContactFeature();
370 if (isContactFeature)
372 boolean drawn = renderFeature(g, seq,
373 seq.findIndex(sequenceFeature.begin) - 1,
374 seq.findIndex(sequenceFeature.begin) - 1, featureColour,
375 start, end, y1, colourOnly);
376 drawn |= renderFeature(g, seq,
377 seq.findIndex(sequenceFeature.end) - 1,
378 seq.findIndex(sequenceFeature.end) - 1, featureColour,
379 start, end, y1, colourOnly);
382 drawnColour = featureColour;
385 else if (showFeature(sequenceFeature))
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;
402 boolean drawn = renderFeature(g, seq,
403 seq.findIndex(sequenceFeature.begin) - 1,
404 seq.findIndex(sequenceFeature.end) - 1, featureColour,
405 start, end, y1, colourOnly);
408 drawnColour = featureColour;
415 if (transparency != 1.0f && g != null)
418 * get colour as rendered including transparency
419 * and reset transparency
421 if (offscreenImage != null && drawnColour != null)
423 drawnColour = new Color(offscreenImage.getRGB(0, 0));
425 Graphics2D g2 = (Graphics2D) g;
426 g2.setComposite(NO_TRANSPARENCY);
433 * Answers true if the feature belongs to a feature group which is not
434 * currently displayed, else false
436 * @param sequenceFeature
439 protected boolean featureGroupNotShown(
440 final SequenceFeature sequenceFeature)
442 return featureGroups != null
443 && sequenceFeature.featureGroup != null
444 && sequenceFeature.featureGroup.length() != 0
445 && featureGroups.containsKey(sequenceFeature.featureGroup)
446 && !featureGroups.get(sequenceFeature.featureGroup)
451 * Called when alignment in associated view has new/modified features to
452 * discover and display.
456 public void featuresAdded()
463 * Returns the sequence feature colour rendered at the given sequence
464 * position, or null if none found. The feature of highest render order (i.e.
465 * on top) is found, subject to both feature type and feature group being
466 * visible, and its colour returned.
472 Color findFeatureColour(SequenceI seq, int pos)
474 SequenceFeature[] sequenceFeatures = seq.getSequenceFeatures();
475 if (sequenceFeatures == null || sequenceFeatures.length == 0)
483 * inspect features in reverse renderOrder (the last in the array is
484 * displayed on top) until we find one that is rendered at the position
486 for (int renderIndex = renderOrder.length - 1; renderIndex >= 0; renderIndex--)
488 String type = renderOrder[renderIndex];
489 if (!showFeatureOfType(type))
494 for (int sfindex = 0; sfindex < sequenceFeatures.length; sfindex++)
496 SequenceFeature sequenceFeature = sequenceFeatures[sfindex];
497 if (!sequenceFeature.type.equals(type))
502 if (featureGroupNotShown(sequenceFeature))
507 boolean featureIsAtPosition = sequenceFeature.begin <= pos
508 && sequenceFeature.end >= pos;
509 if (sequenceFeature.isContactFeature())
511 featureIsAtPosition = sequenceFeature.begin == pos
512 || sequenceFeature.end == pos;
514 if (featureIsAtPosition)
516 return getColour(sequenceFeature);
522 * no displayed feature found at position