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.api.FeatureColourI;
25 import jalview.datamodel.Range;
26 import jalview.datamodel.SequenceFeature;
27 import jalview.datamodel.SequenceI;
28 import jalview.gui.AlignFrame;
29 import jalview.gui.Desktop;
30 import jalview.util.Comparison;
31 import jalview.viewmodel.seqfeatures.FeatureRendererModel;
33 import java.awt.AlphaComposite;
34 import java.awt.Color;
35 import java.awt.FontMetrics;
36 import java.awt.Graphics;
37 import java.awt.Graphics2D;
38 import java.util.List;
40 public class FeatureRenderer extends FeatureRendererModel
42 private static final AlphaComposite NO_TRANSPARENCY = AlphaComposite
43 .getInstance(AlphaComposite.SRC_OVER, 1.0f);
46 * Constructor given a viewport
50 public FeatureRenderer(AlignViewportI viewport)
56 * Renders the sequence using the given feature colour between the given start
57 * and end columns. Returns true if at least one column is drawn, else false
58 * (the feature range does not overlap the start and end positions).
64 * @param featureColour
71 boolean renderFeature(Graphics g, SequenceI seq, int featureStart,
72 int featureEnd, Color featureColour, int start, int end, int y1,
75 int charHeight = av.getCharHeight();
76 int charWidth = av.getCharWidth();
77 boolean validCharWidth = av.isValidCharWidth();
79 if (featureStart > end || featureEnd < start)
84 if (featureStart < start)
88 if (featureEnd >= end)
92 int pady = (y1 + charHeight) - charHeight / 5;
94 FontMetrics fm = g.getFontMetrics();
95 for (int i = featureStart; i <= featureEnd; i++)
97 char s = seq.getCharAt(i);
99 if (Comparison.isGap(s))
104 g.setColor(featureColour);
106 g.fillRect((i - start) * charWidth, y1, charWidth, charHeight);
108 if (colourOnly || !validCharWidth)
113 g.setColor(Color.white);
114 int charOffset = (charWidth - fm.charWidth(s)) / 2;
115 g.drawString(String.valueOf(s),
116 charOffset + (charWidth * (i - start)), pady);
122 * Renders the sequence using the given SCORE feature colour between the given
123 * start and end columns. Returns true if at least one column is drawn, else
124 * false (the feature range does not overlap the start and end positions).
130 * @param featureColour
138 boolean renderScoreFeature(Graphics g, SequenceI seq, int fstart,
139 int fend, Color featureColour, int start, int end, int y1,
140 byte[] bs, boolean colourOnly)
142 if (fstart > end || fend < start)
148 { // fix for if the feature we have starts before the sequence start,
149 fstart = start; // but the feature end is still valid!!
156 int charHeight = av.getCharHeight();
157 int pady = (y1 + charHeight) - charHeight / 5;
158 int ystrt = 0, yend = charHeight;
161 // signed - zero is always middle of residue line.
164 yend = charHeight * (128 - bs[1]) / 512;
165 ystrt = charHeight - yend / 2;
169 ystrt = charHeight / 2;
170 yend = charHeight * (bs[1] - 128) / 512;
175 yend = charHeight * bs[1] / 255;
176 ystrt = charHeight - yend;
180 FontMetrics fm = g.getFontMetrics();
181 int charWidth = av.getCharWidth();
183 for (int i = fstart; i <= fend; i++)
185 char s = seq.getCharAt(i);
187 if (Comparison.isGap(s))
192 g.setColor(featureColour);
193 int x = (i - start) * charWidth;
194 g.drawRect(x, y1, charWidth, charHeight);
195 g.fillRect(x, y1 + ystrt, charWidth, yend);
197 if (colourOnly || !av.isValidCharWidth())
202 g.setColor(Color.black);
203 int charOffset = (charWidth - fm.charWidth(s)) / 2;
204 g.drawString(String.valueOf(s),
205 charOffset + (charWidth * (i - start)), pady);
214 public Color findFeatureColour(SequenceI seq, int column, Graphics g)
216 if (!av.isShowSequenceFeatures())
221 // column is 'base 1' but getCharAt is an array index (ie from 0)
222 if (Comparison.isGap(seq.getCharAt(column - 1)))
225 * returning null allows the colour scheme to provide gap colour
226 * - normally white, but can be customised
231 Color renderedColour = null;
232 if (transparency == 1.0f)
235 * simple case - just find the topmost rendered visible feature colour
237 renderedColour = findFeatureColour(seq, column);
242 * transparency case - draw all visible features in render order to
243 * build up a composite colour on the graphics context
245 renderedColour = drawSequence(g, seq, column, column, 0, true);
247 return renderedColour;
251 * Draws the sequence features on the graphics context, or just determines the
252 * colour that would be drawn (if flag colourOnly is true). Returns the last
253 * colour drawn (which may not be the effective colour if transparency
254 * applies), or null if no feature is drawn in the range given.
257 * the graphics context to draw on (may be null if colourOnly==true)
264 * vertical offset at which to draw on the graphics
266 * if true, only do enough to determine the colour for the position,
267 * do not draw the character
270 public synchronized Color drawSequence(final Graphics g,
271 final SequenceI seq, int start, int end, int y1,
275 * if columns are all gapped, or sequence has no features, nothing to do
277 Range visiblePositions = seq.findPositions(start+1, end+1);
278 if (visiblePositions == null || (!seq.getFeatures().hasFeatures()
279 && !av.isShowComplementFeatures()))
286 if (transparency != 1f && g != null)
288 Graphics2D g2 = (Graphics2D) g;
289 g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
293 Color drawnColour = null;
296 * iterate over features in ordering of their rendering (last is on top)
298 for (int renderIndex = 0; renderIndex < renderOrder.length; renderIndex++)
300 String type = renderOrder[renderIndex];
301 if (!showFeatureOfType(type))
306 FeatureColourI fc = getFeatureStyle(type);
307 List<SequenceFeature> overlaps = seq.getFeatures().findFeatures(
308 visiblePositions.getBegin(), visiblePositions.getEnd(), type);
310 if (fc.isSimpleColour())
312 filterFeaturesForDisplay(overlaps);
315 for (SequenceFeature sf : overlaps)
317 Color featureColour = getColor(sf, fc);
318 if (featureColour == null)
321 * feature excluded by filters, or colour threshold
327 * if feature starts/ends outside the visible range,
328 * restrict to visible positions (or if a contact feature,
329 * to a single position)
331 int visibleStart = sf.getBegin();
332 if (visibleStart < visiblePositions.getBegin())
334 visibleStart = sf.isContactFeature() ? sf.getEnd()
335 : visiblePositions.getBegin();
337 int visibleEnd = sf.getEnd();
338 if (visibleEnd > visiblePositions.getEnd())
340 visibleEnd = sf.isContactFeature() ? sf.getBegin()
341 : visiblePositions.getEnd();
344 int featureStartCol = seq.findIndex(visibleStart);
345 int featureEndCol = sf.begin == sf.end ? featureStartCol : seq
346 .findIndex(visibleEnd);
348 // Color featureColour = getColour(sequenceFeature);
350 boolean isContactFeature = sf.isContactFeature();
352 if (isContactFeature)
354 boolean drawn = renderFeature(g, seq, featureStartCol - 1,
355 featureStartCol - 1, featureColour, start, end, y1,
357 drawn |= renderFeature(g, seq, featureEndCol - 1,
358 featureEndCol - 1, featureColour, start, end, y1,
362 drawnColour = featureColour;
368 * showing feature score by height of colour
369 * is not implemented as a selectable option
371 if (av.isShowSequenceFeaturesHeight()
372 && !Float.isNaN(sequenceFeature.score))
374 boolean drawn = renderScoreFeature(g, seq,
375 seq.findIndex(sequenceFeature.begin) - 1,
376 seq.findIndex(sequenceFeature.end) - 1, featureColour,
377 start, end, y1, normaliseScore(sequenceFeature),
381 drawnColour = featureColour;
387 boolean drawn = renderFeature(g, seq,
389 featureEndCol - 1, featureColour,
390 start, end, y1, colourOnly);
393 drawnColour = featureColour;
401 * if configured to do so, find and show complement's features
403 if (av.isShowComplementFeatures())
405 AlignViewportI comp = av.getCodingComplement();
406 FeatureRenderer fr2 = Desktop.getAlignFrameFor(comp)
407 .getFeatureRenderer();
408 for (int pos = visiblePositions.start; pos <= visiblePositions.end; pos++)
410 int column = seq.findIndex(pos);
411 // TODO ensure these are in complement's render order (last on top)
412 List<SequenceFeature> features = fr2
413 .findComplementFeaturesAtResidue(seq, pos);
414 for (SequenceFeature sf : features)
416 FeatureColourI fc = fr2.getFeatureStyle(sf.getType());
417 Color featureColour = fr2.getColor(sf, fc);
418 renderFeature(g, seq, column - 1, column - 1, featureColour,
419 start, end, y1, colourOnly);
424 if (transparency != 1.0f && g != null)
429 Graphics2D g2 = (Graphics2D) g;
430 g2.setComposite(NO_TRANSPARENCY);
437 * Called when alignment in associated view has new/modified features to
438 * discover and display.
442 public void featuresAdded()
448 * Returns the sequence feature colour rendered at the given column position,
449 * or null if none found. The feature of highest render order (i.e. on top) is
450 * found, subject to both feature type and feature group being visible, and
451 * its colour returned. This method is suitable when no feature transparency
452 * applied (only the topmost visible feature colour is rendered).
454 * Note this method does not check for a gap in the column so would return the
455 * colour for features enclosing a gapped column. Check for gap before calling
456 * if different behaviour is wanted.
463 Color findFeatureColour(SequenceI seq, int column)
466 * check for new feature added while processing
471 * show complement features on top (if configured to show them)
473 if (av.isShowComplementFeatures())
475 AlignViewportI complement = av.getCodingComplement();
476 AlignFrame af = Desktop.getAlignFrameFor(complement);
477 FeatureRendererModel fr2 = af.getFeatureRenderer();
478 List<SequenceFeature> features = fr2.findComplementFeaturesAtResidue(
479 seq, seq.findPosition(column));
480 // todo: ensure ordered by feature render order
481 for (SequenceFeature sf : features)
483 if (!fr2.featureGroupNotShown(sf))
485 Color col = fr2.getColour(sf);
495 * inspect features in reverse renderOrder (the last in the array is
496 * displayed on top) until we find one that is rendered at the position
498 for (int renderIndex = renderOrder.length
499 - 1; renderIndex >= 0; renderIndex--)
501 String type = renderOrder[renderIndex];
502 if (!showFeatureOfType(type))
507 List<SequenceFeature> overlaps = seq.findFeatures(column, column,
509 for (SequenceFeature sequenceFeature : overlaps)
511 if (!featureGroupNotShown(sequenceFeature))
513 Color col = getColour(sequenceFeature);
523 * no displayed feature found at position