package com.compomics.util.gui.protein;
import com.compomics.util.Util;
import java.awt.event.MouseEvent;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseMotionAdapter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
/**
* A panel for displaying modification profiles. (Based on the
* SequenceFragmentationPanel.)
*
* @author Harald Barsnes
* @author Kenny Helsens
* @author Lennart Martens
*/
public class SequenceModificationPanel extends JPanel {
/**
* A map of the rectangles used to draw each profile peak. This map is later
* used for the tooltip for each peak.
*/
private HashMap<String, Rectangle> fragmentIonRectangles;
/**
* Elementary data for composing the Panel.
*/
private String[] iSequenceComponents;
/**
* The list of modification profiles.
*/
private ArrayList<ModificationProfile> profiles;
/**
* The font to use.
*/
private Font iBaseFont = new Font("Monospaced", Font.PLAIN, 14);
/**
* The maximum bar height.
*/
private final double iMaxBarHeight = 20;
/**
* The width of the bars.
*/
private final int iBarWidth = 3;
/**
* The horizontal space.
*/
private final int iHorizontalSpace = 3;
/**
* The x-axis start position.
*/
private final int iXStart = 30;
/**
* The y-axis start position.
*/
private final int iYStart = 10;
/**
* This boolean holds whether or not the given sequence is a modified
* sequence or a normal peptide sequence.
*
* Normal: KENNY Modified: NH2-K<Ace>ENNY-COOH
*/
private boolean isModifiedSequence;
/**
* The name of the score above of the sequence.
*/
private String score1Name;
/**
* The name of the score under of the sequence.
*/
private String score2Name;
/**
* Creates a new SequenceFragmentationPanel.
*
* @param aSequence String with the Modified Sequence of a peptide
* identification.
* @param profiles ArrayList with the modification profiles.
* @param boolModifiedSequence boolean describing the sequence. This
* constructor can be used to enter a ModifiedSequence or a normal sequence.
* @param score1Name the name of the score above of the sequence
* @param score2Name the name of the score under the sequence
* @throws java.awt.HeadlessException if GraphicsEnvironment.isHeadless()
* returns true.
* @see java.awt.GraphicsEnvironment#isHeadless
* @see javax.swing.JComponent#getDefaultLocale
*/
public SequenceModificationPanel(String aSequence, ArrayList<ModificationProfile> profiles, boolean boolModifiedSequence, String score1Name, String score2Name) throws HeadlessException {
super();
this.score1Name = score1Name;
this.score2Name = score2Name;
isModifiedSequence = boolModifiedSequence;
iSequenceComponents = parseSequenceIntoComponents(aSequence);
this.profiles = profiles;
this.setPreferredSize(new Dimension(estimateWidth(), estimateHeight()));
this.setMaximumSize(new Dimension(estimateWidth(), estimateHeight()));
fragmentIonRectangles = new HashMap<String, Rectangle>();
addMouseMotionListener(new MouseMotionAdapter() {
public void mouseMoved(MouseEvent me) {
mouseMovedHandler(me);
}
});
}
/**
* Paints the SequenceModificationPanel.
*
* Based on the given ModifiedSequence Components and Modification profile,
* a visualisation is drawn on a Graphics object showing the profile above
* the sequence.
*
* @param g the specified Graphics window
* @see java.awt.Component#update(java.awt.Graphics)
*/
public void paint(Graphics g) {
super.paint(g);
Graphics2D g2 = (Graphics2D) g;
// Set the base font, monospaced!
g2.setFont(iBaseFont);
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// Drawing offsets.
int yLocation = ((int) iMaxBarHeight) + iYStart;
int xLocation = iXStart;
int lFontHeight = g2.getFontMetrics().getHeight();
Double lMidStringHeight = yLocation - lFontHeight * 0.2;
Double aboveSequenceHeight = yLocation - lFontHeight * 0.5;
Double belowSequenceHeight = yLocation + lFontHeight * 0.15;
// find max a score
double maxAScore = 0;
for (int i = 0; i < iSequenceComponents.length; i++) {
for (int j = 0; j < profiles.size(); j++) {
ModificationProfile currentModificationProfile = profiles.get(j);
if (maxAScore < currentModificationProfile.getProfile()[i][ModificationProfile.SCORE_2_ROW_INDEX]) {
maxAScore = currentModificationProfile.getProfile()[i][ModificationProfile.SCORE_2_ROW_INDEX];
}
}
}
for (int i = 0; i < iSequenceComponents.length; i++) {
// reset base color to black.
g2.setColor(Color.black);
// Draw this amino acid.
g2.setColor(Color.black);
g2.drawString(iSequenceComponents[i], xLocation, yLocation);
int tempXLocation = xLocation;
// special case for modifications on the first reidue
if (i == 0) {
xLocation += g2.getFontMetrics().stringWidth(iSequenceComponents[i]) - g2.getFontMetrics().stringWidth("X");
}
// draw bars below the sequence
for (int j = 0; j < profiles.size(); j++) {
ModificationProfile currentModificationProfile = profiles.get(j);
if (currentModificationProfile.getProfile()[i][ModificationProfile.SCORE_2_ROW_INDEX] > 0) {
g2.setColor(currentModificationProfile.getColor());
int lBarHeight = (int) ((currentModificationProfile.getProfile()[i][ModificationProfile.SCORE_2_ROW_INDEX] / maxAScore) * iMaxBarHeight);
if (lBarHeight < 5) {
lBarHeight = 7;
}
int barStart = belowSequenceHeight.intValue() + 1;
Rectangle tempRectangle = new Rectangle(xLocation + 1, barStart, g2.getFontMetrics().stringWidth("X") - 2, lBarHeight);
g2.fill(tempRectangle);
fragmentIonRectangles.put(currentModificationProfile.getPtmName() + " (" + (i + 1) + ")"
+ " [" + score2Name + ": " + Util.roundDouble(currentModificationProfile.getProfile()[i][ModificationProfile.SCORE_2_ROW_INDEX], 2) + "]",
tempRectangle);
g2.setColor(Color.black);
}
}
// draw bars above the sequence
for (int j = 0; j < profiles.size(); j++) {
ModificationProfile currentModificationProfile = profiles.get(j);
if (currentModificationProfile.getProfile()[i][ModificationProfile.SCORE_1_ROW_INDEX] > 0) {
g2.setColor(currentModificationProfile.getColor());
int lBarHeight = (int) ((currentModificationProfile.getProfile()[i][ModificationProfile.SCORE_1_ROW_INDEX] / 100) * iMaxBarHeight);
if (lBarHeight < 5) {
lBarHeight = 7;
}
int barStart = aboveSequenceHeight.intValue() - 2 - lBarHeight;
Rectangle tempRectangle = new Rectangle(xLocation + 1, barStart, g2.getFontMetrics().stringWidth("X") - 2, lBarHeight);
g2.fill(tempRectangle);
fragmentIonRectangles.put(currentModificationProfile.getPtmName() + " (" + (i + 1) + ")"
+ " [" + score1Name + ": " + Util.roundDouble(currentModificationProfile.getProfile()[i][ModificationProfile.SCORE_1_ROW_INDEX], 2) + "]",
tempRectangle);
g2.setColor(Color.black);
}
}
xLocation = tempXLocation;
// Move the XLocation forwards with the component's length and the horizontal spacer..
xLocation = xLocation + g2.getFontMetrics().stringWidth(iSequenceComponents[i]) + iHorizontalSpace;
// Move the XLocation forwards with the component's length and the horizontal spacer..
xLocation = xLocation + iBarWidth + iHorizontalSpace;
}
this.setPreferredSize(new Dimension(xLocation, 200));
}
/**
* This method can parse a modified sequence String into a String[] with
* different components.
*
* @param aSequence String with the Modified sequence of a peptideHit.
* @return the modified sequence of the peptidehit in a String[]. Example:
* The peptide Ace-K<AceD3>ENNYR-COOH will return a String[] with
* [0]Ace-K<AceD3>
* [1]E [2]N [3]N [4]Y [5]R-COOH
*/
private String[] parseSequenceIntoComponents(String aSequence) {
String[] result;
if (isModifiedSequence) {
// Given sequence is a ModifiedSequence!
ArrayList parts = new ArrayList();
String temp = aSequence;
int start = 0;
if (temp.startsWith("#")) {
int nterm = temp.indexOf("#", start + 1);
start = temp.indexOf("-", nterm);
} else {
start = temp.indexOf("-");
}
start++;
String part = temp.substring(0, start).trim();
temp = temp.substring(start).trim();
int endIndex = 1;
if (temp.charAt(endIndex) == '<') {
endIndex++;
while (temp.charAt(endIndex) != '>') {
endIndex++;
}
endIndex++;
}
part += temp.substring(0, endIndex);
temp = temp.substring(endIndex);
parts.add(part);
while (temp.length() > 0) {
start = 0;
endIndex = 1;
if (temp.charAt(start + endIndex) == '<') {
endIndex++;
while (temp.charAt(start + endIndex) != '>') {
endIndex++;
}
endIndex++;
}
if (temp.charAt(start + endIndex) == '-') {
endIndex = temp.length();
}
part = temp.substring(0, endIndex);
temp = temp.substring(endIndex);
parts.add(part);
}
result = new String[parts.size()];
parts.toArray(result);
} else {
// Given sequence is a flat sequence!
result = new String[aSequence.length()];
for (int i = 0; i < result.length; i++) {
result[i] = Character.toString(aSequence.charAt(i));
}
}
return result;
}
/**
* Returns an estimation of the width.
*
* @return an estimation of the width
*/
private int estimateWidth() {
int lEstimateX = iXStart;
for (int i = 0; i < iSequenceComponents.length; i++) {
// Move X for a text component.
lEstimateX += this.getFontMetrics(iBaseFont).stringWidth(iSequenceComponents[i]) + iHorizontalSpace;
// Move the XLocation forwards with the component's length and the horizontal spacer.
lEstimateX += iBarWidth + iHorizontalSpace;
}
lEstimateX += iXStart;
return lEstimateX;
}
/**
* Returns an estimation of the height.
*
* @return an estimation of the height
*/
private int estimateHeight() {
int lEstimateY = 0;
lEstimateY += 2 * iXStart;
lEstimateY += 1.8 * iMaxBarHeight;
return lEstimateY;
}
/**
* Set the Sequence for the SequenceFragmentationPanel.
*
* @param lSequence String with peptide sequence.
* @param boolModifiedSequence Boolean whether lSequence is a Modified
* Sequence "NH2-K<Ace>KENNY-COOH" or a Flat Sequence "KENNY".
*/
public void setSequence(String lSequence, boolean boolModifiedSequence) {
isModifiedSequence = boolModifiedSequence;
iSequenceComponents = parseSequenceIntoComponents(lSequence);
}
/**
* If the mouse hovers over one of the fragment ion peaks the tooltip is set
* to the fragment ion type and number. If not the tooltip is set to null.
*/
private void mouseMovedHandler(MouseEvent me) {
String tooltip = null;
Iterator<String> ions = fragmentIonRectangles.keySet().iterator();
boolean matchFound = false;
// iterate the peak rectangles and look for matches
while (ions.hasNext() && !matchFound) {
String key = ions.next();
if (fragmentIonRectangles.get(key).contains(me.getPoint())) {
tooltip = key;
matchFound = true;
}
}
this.setToolTipText(tooltip);
}
}