/*
* Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
* Copyright (C) $$Year-Rel$$ The Jalview Authors
*
* This file is part of Jalview.
*
* Jalview is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* Jalview is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Jalview. If not, see .
* The Jalview Authors are detailed in the 'AUTHORS' file.
*/
package jalview.renderer.seqfeatures;
import jalview.api.AlignViewportI;
import jalview.datamodel.SequenceFeature;
import jalview.datamodel.SequenceI;
import jalview.util.Comparison;
import jalview.viewmodel.seqfeatures.FeatureRendererModel;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.util.List;
public class FeatureRenderer extends FeatureRendererModel
{
private static final AlphaComposite NO_TRANSPARENCY = AlphaComposite
.getInstance(AlphaComposite.SRC_OVER, 1.0f);
/**
* Constructor given a viewport
*
* @param viewport
*/
public FeatureRenderer(AlignViewportI viewport)
{
this.av = viewport;
}
/**
* Renders the sequence using the given feature colour between the given start
* and end columns. Returns true if at least one column is drawn, else false
* (the feature range does not overlap the start and end positions).
*
* @param g
* @param seq
* @param featureStart
* @param featureEnd
* @param featureColour
* @param start
* @param end
* @param y1
* @param colourOnly
* @return
*/
boolean renderFeature(Graphics g, SequenceI seq, int featureStart,
int featureEnd, Color featureColour, int start, int end, int y1,
boolean colourOnly)
{
int charHeight = av.getCharHeight();
int charWidth = av.getCharWidth();
boolean validCharWidth = av.isValidCharWidth();
if (featureStart > end || featureEnd < start)
{
return false;
}
if (featureStart < start)
{
featureStart = start;
}
if (featureEnd >= end)
{
featureEnd = end;
}
int pady = (y1 + charHeight) - charHeight / 5;
FontMetrics fm = g.getFontMetrics();
for (int i = featureStart; i <= featureEnd; i++)
{
char s = seq.getCharAt(i);
if (Comparison.isGap(s))
{
continue;
}
g.setColor(featureColour);
g.fillRect((i - start) * charWidth, y1, charWidth,
charHeight);
if (colourOnly || !validCharWidth)
{
continue;
}
g.setColor(Color.white);
int charOffset = (charWidth - fm.charWidth(s)) / 2;
g.drawString(String.valueOf(s), charOffset
+ (charWidth * (i - start)), pady);
}
return true;
}
/**
* Renders the sequence using the given SCORE feature colour between the given
* start and end columns. Returns true if at least one column is drawn, else
* false (the feature range does not overlap the start and end positions).
*
* @param g
* @param seq
* @param fstart
* @param fend
* @param featureColour
* @param start
* @param end
* @param y1
* @param bs
* @param colourOnly
* @return
*/
boolean renderScoreFeature(Graphics g, SequenceI seq, int fstart,
int fend, Color featureColour, int start, int end, int y1,
byte[] bs, boolean colourOnly)
{
if (fstart > end || fend < start)
{
return false;
}
if (fstart < start)
{ // fix for if the feature we have starts before the sequence start,
fstart = start; // but the feature end is still valid!!
}
if (fend >= end)
{
fend = end;
}
int charHeight = av.getCharHeight();
int pady = (y1 + charHeight) - charHeight / 5;
int ystrt = 0, yend = charHeight;
if (bs[0] != 0)
{
// signed - zero is always middle of residue line.
if (bs[1] < 128)
{
yend = charHeight * (128 - bs[1]) / 512;
ystrt = charHeight - yend / 2;
}
else
{
ystrt = charHeight / 2;
yend = charHeight * (bs[1] - 128) / 512;
}
}
else
{
yend = charHeight * bs[1] / 255;
ystrt = charHeight - yend;
}
FontMetrics fm = g.getFontMetrics();
int charWidth = av.getCharWidth();
for (int i = fstart; i <= fend; i++)
{
char s = seq.getCharAt(i);
if (Comparison.isGap(s))
{
continue;
}
g.setColor(featureColour);
int x = (i - start) * charWidth;
g.drawRect(x, y1, charWidth, charHeight);
g.fillRect(x, y1 + ystrt, charWidth, yend);
if (colourOnly || !av.isValidCharWidth())
{
continue;
}
g.setColor(Color.black);
int charOffset = (charWidth - fm.charWidth(s)) / 2;
g.drawString(String.valueOf(s), charOffset
+ (charWidth * (i - start)), pady);
}
return true;
}
/**
* {@inheritDoc}
*/
@Override
public Color findFeatureColour(SequenceI seq, int column, Graphics g)
{
if (!av.isShowSequenceFeatures())
{
return null;
}
if (Comparison.isGap(seq.getCharAt(column)))
{
return Color.white;
}
Color renderedColour = null;
if (transparency == 1.0f)
{
/*
* simple case - just find the topmost rendered visible feature colour
*/
renderedColour = findFeatureColour(seq, seq.findPosition(column));
}
else
{
/*
* transparency case - draw all visible features in render order to
* build up a composite colour on the graphics context
*/
renderedColour = drawSequence(g, seq, column, column, 0, true);
}
return renderedColour;
}
/**
* Draws the sequence features on the graphics context, or just determines the
* colour that would be drawn (if flag colourOnly is true). Returns the last
* colour drawn (which may not be the effective colour if transparency
* applies), or null if no feature is drawn in the range given.
*
* @param g
* the graphics context to draw on (may be null if colourOnly==true)
* @param seq
* @param start
* start column
* @param end
* end column
* @param y1
* vertical offset at which to draw on the graphics
* @param colourOnly
* if true, only do enough to determine the colour for the position,
* do not draw the character
* @return
*/
public synchronized Color drawSequence(final Graphics g,
final SequenceI seq, int start, int end, int y1,
boolean colourOnly)
{
if (!seq.getFeatures().hasFeatures())
{
return null;
}
updateFeatures();
if (transparency != 1f && g != null)
{
Graphics2D g2 = (Graphics2D) g;
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
transparency));
}
int startPos = seq.findPosition(start);
int endPos = seq.findPosition(end);// todo a performant overload of this!
Color drawnColour = null;
/*
* iterate over features in ordering of their rendering (last is on top)
*/
for (int renderIndex = 0; renderIndex < renderOrder.length; renderIndex++)
{
String type = renderOrder[renderIndex];
if (!showFeatureOfType(type))
{
continue;
}
List overlaps = seq.findFeatures(startPos, endPos,
type);
for (SequenceFeature sequenceFeature : overlaps)
{
/*
* a feature type may be flagged as shown but the group
* an instance of it belongs to may be hidden
*/
if (featureGroupNotShown(sequenceFeature))
{
continue;
}
Color featureColour = getColour(sequenceFeature);
boolean isContactFeature = sequenceFeature.isContactFeature();
if (isContactFeature)
{
boolean drawn = renderFeature(g, seq,
seq.findIndex(sequenceFeature.begin) - 1,
seq.findIndex(sequenceFeature.begin) - 1, featureColour,
start, end, y1, colourOnly);
drawn |= renderFeature(g, seq,
seq.findIndex(sequenceFeature.end) - 1,
seq.findIndex(sequenceFeature.end) - 1, featureColour,
start, end, y1, colourOnly);
if (drawn)
{
drawnColour = featureColour;
}
}
else if (showFeature(sequenceFeature))
{
/*
* showing feature score by height of colour
* is not implemented as a selectable option
*
if (av.isShowSequenceFeaturesHeight()
&& !Float.isNaN(sequenceFeature.score))
{
boolean drawn = renderScoreFeature(g, seq,
seq.findIndex(sequenceFeature.begin) - 1,
seq.findIndex(sequenceFeature.end) - 1, featureColour,
start, end, y1, normaliseScore(sequenceFeature),
colourOnly);
if (drawn)
{
drawnColour = featureColour;
}
}
else
{
*/
boolean drawn = renderFeature(g, seq,
seq.findIndex(sequenceFeature.begin) - 1,
seq.findIndex(sequenceFeature.end) - 1, featureColour,
start, end, y1, colourOnly);
if (drawn)
{
drawnColour = featureColour;
}
/*}*/
}
}
}
if (transparency != 1.0f && g != null)
{
/*
* reset transparency
*/
Graphics2D g2 = (Graphics2D) g;
g2.setComposite(NO_TRANSPARENCY);
}
return drawnColour;
}
/**
* Called when alignment in associated view has new/modified features to
* discover and display.
*
*/
@Override
public void featuresAdded()
{
findAllFeatures();
}
/**
* Returns the sequence feature colour rendered at the given sequence
* position, or null if none found. The feature of highest render order (i.e.
* on top) is found, subject to both feature type and feature group being
* visible, and its colour returned.
*
* @param seq
* @param pos
* @return
*/
Color findFeatureColour(SequenceI seq, int pos)
{
/*
* check for new feature added while processing
*/
updateFeatures();
/*
* inspect features in reverse renderOrder (the last in the array is
* displayed on top) until we find one that is rendered at the position
*/
for (int renderIndex = renderOrder.length - 1; renderIndex >= 0; renderIndex--)
{
String type = renderOrder[renderIndex];
if (!showFeatureOfType(type))
{
continue;
}
List overlaps = seq.findFeatures(pos, pos, type);
for (SequenceFeature sequenceFeature : overlaps)
{
if (!featureGroupNotShown(sequenceFeature))
{
return getColour(sequenceFeature);
}
}
}
/*
* no displayed feature found at position
*/
return null;
}
}