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.ContiguousI;
26 import jalview.datamodel.SequenceFeature;
27 import jalview.datamodel.SequenceI;
28 import jalview.util.Comparison;
29 import jalview.util.Platform;
30 import jalview.viewmodel.seqfeatures.FeatureRendererModel;
32 import java.awt.AlphaComposite;
33 import java.awt.Color;
34 import java.awt.FontMetrics;
35 import java.awt.Graphics;
36 import java.awt.Graphics2D;
37 import java.util.ArrayList;
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();
96 for (int i = featureStart; i <= featureEnd; i++)
99 // colourOnly is just for Overview -- no need to check this again
101 if (!colourOnly && Comparison.isGap(s = seq.getCharAt(i)))
106 g.setColor(featureColour);
108 g.fillRect((i - start) * charWidth, y1, charWidth, charHeight);
120 g.setColor(Color.white);
121 int charOffset = (charWidth - fm.charWidth(s)) / 2;
122 g.drawString(String.valueOf(s),
123 charOffset + (charWidth * (i - start)), pady);
130 * BH - this method is never called?
132 * Renders the sequence using the given SCORE feature colour between the given
133 * start and end columns. Returns true if at least one column is drawn, else
134 * false (the feature range does not overlap the start and end positions).
140 * @param featureColour
148 boolean renderScoreFeature(Graphics g, SequenceI seq, int fstart,
149 int fend, Color featureColour, int start, int end, int y1,
150 byte[] bs, boolean colourOnly)
152 if (fstart > end || fend < start)
158 { // fix for if the feature we have starts before the sequence start,
159 fstart = start; // but the feature end is still valid!!
166 int charHeight = av.getCharHeight();
167 int pady = (y1 + charHeight) - charHeight / 5;
168 int ystrt = 0, yend = charHeight;
171 // signed - zero is always middle of residue line.
174 yend = charHeight * (128 - bs[1]) / 512;
175 ystrt = charHeight - yend / 2;
179 ystrt = charHeight / 2;
180 yend = charHeight * (bs[1] - 128) / 512;
185 yend = charHeight * bs[1] / 255;
186 ystrt = charHeight - yend;
190 FontMetrics fm = g.getFontMetrics();
191 int charWidth = av.getCharWidth();
193 for (int i = fstart; i <= fend; i++)
195 char s = seq.getCharAt(i);
197 if (Comparison.isGap(s))
202 g.setColor(featureColour);
203 int x = (i - start) * charWidth;
204 g.drawRect(x, y1, charWidth, charHeight);
205 g.fillRect(x, y1 + ystrt, charWidth, yend);
207 if (colourOnly || !av.isValidCharWidth())
212 g.setColor(Color.black);
213 int charOffset = (charWidth - fm.charWidth(s)) / 2;
214 g.drawString(String.valueOf(s),
215 charOffset + (charWidth * (i - start)), pady);
224 public Color findFeatureColour(SequenceI seq, int column, Graphics g)
227 // this is already checked in FeatureColorFinder
228 // if (!av.isShowSequenceFeatures())
233 // column is 'base 1' but getCharAt is an array index (ie from 0)
234 if (Comparison.isGap(seq.getCharAt(column - 1)))
237 * returning null allows the colour scheme to provide gap colour
238 * - normally white, but can be customised
243 Color renderedColour = null;
244 if (transparency == 1.0f)
247 * simple case - just find the topmost rendered visible feature colour
249 renderedColour = findFeatureColour(seq, column);
254 * transparency case - draw all visible features in render order to
255 * build up a composite colour on the graphics context
257 renderedColour = drawSequence(g, seq, column, column, 0, true);
259 return renderedColour;
263 * Draws the sequence features on the graphics context, or just determines the
264 * colour that would be drawn (if flag colourOnly is true). Returns the last
265 * colour drawn (which may not be the effective colour if transparency
266 * applies), or null if no feature is drawn in the range given.
269 * the graphics context to draw on (may be null only if t == 1 from
277 * vertical offset at which to draw on the graphics
279 * if true, only do enough to determine the colour for the position,
280 * do not draw the character
283 public synchronized Color drawSequence(final Graphics g,
284 final SequenceI seq, int start, int end, int y1,
287 // from SeqCanvas and OverviewRender
289 * if columns are all gapped, or sequence has no features, nothing to do
291 ContiguousI visiblePositions;
292 if (!seq.getFeatures().hasFeatures() || (visiblePositions = seq
293 .findPositions(start + 1, end + 1)) == null)
298 int vp0 = visiblePositions.getBegin();
299 int vp1 = visiblePositions.getEnd();
303 if (transparency != 1f) // g cannot be null here if trans == 1f - BH // && g
306 ((Graphics2D) g).setComposite(
307 AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
311 Color drawnColour = null;
314 * iterate over features in ordering of their rendering (last is on top)
316 for (int renderIndex = 0, n = renderOrder.length; renderIndex < n; renderIndex++)
318 String type = renderOrder[renderIndex];
319 if (!seq.hasFeatures(type) || !showFeatureOfType(type))
324 FeatureColourI fc = getFeatureStyle(type);
325 List<SequenceFeature> overlaps = seq.getFeatures().findFeatures(vp0,
328 // colourOnly (i.e. Overview) can only be here if translucent, so
329 // there is no need to check for filtering
330 if (!colourOnly && fc.isSimpleColour())
332 filterFeaturesForDisplay(overlaps);
335 for (int i = overlaps.size(); --i >= 0;)
337 SequenceFeature sf = overlaps.get(i);
338 Color featureColour = getColor(sf, fc);
339 if (featureColour == null)
342 * feature excluded by visibility settings, filters, or colour threshold
348 * if feature starts/ends outside the visible range,
349 * restrict to visible positions (or if a contact feature,
350 * to a single position)
352 int sf0 = sf.getBegin();
353 int sf1 = sf.getEnd();
354 int visibleStart = sf0;
355 if (visibleStart < vp0)
357 visibleStart = sf.isContactFeature() ? sf1 : vp0;
359 int visibleEnd = sf1;
360 if (visibleEnd > vp1)
362 visibleEnd = sf.isContactFeature() ? sf0 : vp1;
365 int featureStartCol = seq.findIndex(visibleStart);
366 int featureEndCol = (sf.begin == sf.end ? featureStartCol
367 : seq.findIndex(visibleEnd));
369 // Color featureColour = getColour(sequenceFeature);
371 boolean isContactFeature = sf.isContactFeature();
373 if (isContactFeature)
375 boolean drawn = renderFeature(g, seq, featureStartCol - 1,
376 featureStartCol - 1, featureColour, start, end, y1,
378 drawn |= renderFeature(g, seq, featureEndCol - 1,
379 featureEndCol - 1, featureColour, start, end, y1,
383 drawnColour = featureColour;
389 * showing feature score by height of colour
390 * is not implemented as a selectable option
392 if (av.isShowSequenceFeaturesHeight()
393 && !Float.isNaN(sequenceFeature.score))
395 boolean drawn = renderScoreFeature(g, seq,
396 seq.findIndex(sequenceFeature.begin) - 1,
397 seq.findIndex(sequenceFeature.end) - 1, featureColour,
398 start, end, y1, normaliseScore(sequenceFeature),
402 drawnColour = featureColour;
408 boolean drawn = renderFeature(g, seq, featureStartCol - 1,
409 featureEndCol - 1, featureColour, start, end, y1,
413 drawnColour = featureColour;
420 if (transparency != 1.0f)
425 ((Graphics2D) g).setComposite(NO_TRANSPARENCY);
432 * Called when alignment in associated view has new/modified features to
433 * discover and display.
437 public void featuresAdded()
442 private List<SequenceFeature> overlaps = (Platform.isJS()
447 * Returns the sequence feature colour rendered at the given column position,
448 * or null if none found. The feature of highest render order (i.e. on top) is
449 * found, subject to both feature type and feature group being visible, and
450 * its colour returned. This method is suitable when no feature transparency
451 * applied (only the topmost visible feature colour is rendered).
453 * Note this method does not check for a gap in the column so would return the
454 * colour for features enclosing a gapped column. Check for gap before calling
455 * if different behaviour is wanted.
459 * Adds a result ArrayList to parameters in order to avoid an unnecessary
460 * construction of that for every pixel checked.
468 private Color findFeatureColour(SequenceI seq, int column)
471 * check for new feature added while processing
476 * inspect features in reverse renderOrder (the last in the array is
477 * displayed on top) until we find one that is rendered at the position
479 for (int renderIndex = renderOrder.length; --renderIndex >= 0;)
481 String type = renderOrder[renderIndex];
482 if (!seq.hasFeatures(type) || !showFeatureOfType(type))
487 if (overlaps != null)
491 List<SequenceFeature> list = seq.findFeatures(column, type, overlaps);
494 for (int i = 0, n = list.size(); i < n; i++)
496 SequenceFeature sf = list.get(i);
497 if (featureGroupNotShown(sf))
501 Color col = getColour(sf);
511 * no displayed feature found at position