package com.compomics.util.gui.spectrum;
import com.compomics.util.enumeration.MolecularElement;
import com.compomics.util.general.IsotopicDistribution;
import com.compomics.util.general.IsotopicDistributionSpectrum;
import com.compomics.util.general.MassCalc;
import com.compomics.util.general.UnknownElementMassException;
import com.compomics.util.interfaces.SpectrumFile;
import com.compomics.util.protein.AASequenceImpl;
import com.compomics.util.protein.MolecularFormula;
import javax.swing.*;
import java.awt.*;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.TreeSet;
import javax.swing.border.EtchedBorder;
import org.apache.log4j.Logger;
/**
* This class provides a JPanel that can display a peptide isotopic distribution.
*
* @author Harald Barsnes
* @author Niklaas Colaert
* @author Lennart Martens
* @version $Id$
*/
public class IsotopicDistributionPanel extends GraphicsPanel {
// Class specific log4j logger for MolecularFormula instances.
Logger logger = Logger.getLogger(MolecularFormula.class);
/**
* The color used for the peaks. Default to red.
*/
private Color aSpectrumPeakColor = Color.RED;
/**
* The color used for the area under the curve. Defaults to pink.
*/
private Color aSpectrumProfileModeLineColor = Color.PINK;
/**
* The peptide sequences to display the isotopic distribution for.
*/
private ArrayList<AASequenceImpl> peptideSequences = null;
/**
* The charges of the peptides. Indexed by dataset.
*/
private ArrayList<Integer> peptideCharges = null;
/**
* HashMap with the molecular formula for all the amino acids.
*/
private HashMap<String, MolecularFormula> iElements;
/**
* This constructor creates an IsotopicDistributionPanel based on the passed parameters.
*
* @param peptideSequence the peptide sequence to display the isotopic distribution for
* @param peptideCharge the charge of the peptide
* @param profileMode if true the peaks will be showed in a profile like mode where
* support peaks are added in front of and after the real peak
* (note that this is unlike the profile modes of the other graphics
* panels)
* @param labelDifference the number of neutrons to add due to the label
* @throws IOException if an IOException occurs
*/
public IsotopicDistributionPanel(String peptideSequence, Integer peptideCharge, boolean profileMode, int labelDifference) throws IOException {
if (profileMode) {
this.currentGraphicsPanelType = GraphicsPanelType.isotopicDistributionProfile;
} else {
this.currentGraphicsPanelType = GraphicsPanelType.isotopicDistributionCentroid;
}
// gets the elements that can be used
getElements();
// validate the peptide sequence
AASequenceImpl validatedPeptideSequence = validatePeptideSequence(peptideSequence);
peptideSequences = new ArrayList<AASequenceImpl>();
peptideSequences.add(validatedPeptideSequence);
peptideCharges = new ArrayList<Integer>();
peptideCharges.add(peptideCharge);
// calculate the isotopic distribution
IsotopicDistributionSpectrum isotopicDistributionSpectrum =
calculateIsotopicDistribution(validatedPeptideSequence, peptideCharge, labelDifference);
dataSetCounter = 0;
this.processIsotopicDistribution(isotopicDistributionSpectrum, aSpectrumPeakColor, aSpectrumProfileModeLineColor);
// graphical user interface settings
this.iXAxisStartAtZero = false;
rescaleWithLeftSidePadding();
this.iCurrentDrawStyle = DrawingStyle.LINES;
this.setBorder(BorderFactory.createEtchedBorder(EtchedBorder.LOWERED));
this.setBackground(Color.WHITE);
this.iYAxisLabel = "Int (%)";
this.iSpecPanelListeners = new ArrayList();
this.addListeners();
}
/**
* Calculates the isotopic distribution of the peptide.
*
* @param validatedPeptideSequence the peptide to calculate the isotopic distribution for
* @param peptideCharge the charge of the peptide
* @param labelDifference the number of neutrons to add due to the label
* @return the isotopic distribution as a spectrum
*/
private IsotopicDistributionSpectrum calculateIsotopicDistribution(AASequenceImpl validatedPeptideSequence, Integer peptideCharge, int labedDifference) {
// calculate the m/z value of the peptide
double mzValue = validatedPeptideSequence.getMz(peptideCharge);
// calculate the distribution
IsotopicDistribution lIso = validatedPeptideSequence.getIsotopicDistribution();
//set the label difference if necessary
if(labedDifference>0){
lIso.setLabelDifference(labedDifference);
}
// add the peaks to the dataset
HashMap lPeaks = new HashMap();
try {
for (int i = 0; i < 15; i++) {
// @TODO: refine the adding of additional peaks
int numberOfSidePeaks = 10;
// if profile mode, add some additional "profile mode looking" peaks before the peak
if (currentGraphicsPanelType.equals(GraphicsPanelType.isotopicDistributionProfile)) {
for (int j=0; j < numberOfSidePeaks; j++) {
lPeaks.put(mzValue + (i * (new MassCalc().calculateMass("H") / (double) peptideCharge)) - 0.01*(numberOfSidePeaks-j), lIso.getPercMax()[i] * j*10);
}
}
lPeaks.put(mzValue + (i * (new MassCalc().calculateMass("H") / (double) peptideCharge)), lIso.getPercMax()[i] * 100);
// @TODO: refine the adding of additional peaks
// if profile mode, add some additional "profile mode looking" peaks before the peak
if (currentGraphicsPanelType.equals(GraphicsPanelType.isotopicDistributionProfile)) {
for (int j=1; j <= numberOfSidePeaks; j++) {
lPeaks.put(mzValue + (i * (new MassCalc().calculateMass("H") / (double) peptideCharge)) + 0.01*j, lIso.getPercMax()[i] * (100 - j*10));
}
}
}
} catch (UnknownElementMassException ume) {
logger.error(ume.getMessage(), ume);
}
IsotopicDistributionSpectrum lSpecFile = new IsotopicDistributionSpectrum();
lSpecFile.setCharge(peptideCharge);
lSpecFile.setPrecursorMZ(mzValue);
lSpecFile.setPeaks(lPeaks);
return lSpecFile;
}
/**
* Adds an additional isotopic distribution dataset to be displayed in the same
* panel. Remember to use different colors for the different datasets.
*
* @param peptideSequence the peptide sequence to display the isotopic distribution for
* @param peptideCharge the charge of the peptide
* @param dataPointAndLineColor the color to use for the data points and lines
* @param areaUnderCurveColor the color to use for the area under the curve
* @param labelDifference the number of neutrons to add due to the label
* @throws IOException if an IOException occurs
*/
public void addAdditionalDataset(String peptideSequence, Integer peptideCharge, Color dataPointAndLineColor, Color areaUnderCurveColor, int labelDifference) throws IOException {
// validate the peptide sequence
AASequenceImpl validatedPeptideSequence = validatePeptideSequence(peptideSequence);
peptideSequences.add(validatedPeptideSequence);
peptideCharges.add(peptideCharge);
IsotopicDistributionSpectrum isotopicDistributionSpectrum =
calculateIsotopicDistribution(validatedPeptideSequence, peptideCharge, labelDifference);
this.processIsotopicDistribution(isotopicDistributionSpectrum, dataPointAndLineColor, areaUnderCurveColor);
rescaleWithLeftSidePadding();
this.showFileName = false;
this.showPrecursorDetails = false;
this.showResolution = false;
}
/**
* Rescales to show all peaks, adds a minimum padding on the left side
* to make sure the that first peak is not too close to the y-axis.
*/
private void rescaleWithLeftSidePadding() {
double tempMinXValue = getMinXAxisValue();
tempMinXValue -= 1;
if (tempMinXValue < 0) {
tempMinXValue = 0;
}
this.rescale(tempMinXValue, getMaxXAxisValue());
}
/**
* Validates the peptide sequence to check for non amino acid elements.
*
* @param peptideSequence the peptide sequence to validate
* @return the validated peptide sequence
* @throws IOException
*/
private AASequenceImpl validatePeptideSequence(String peptideSequence) throws IOException {
// get the sequence
String lSeq = peptideSequence;
//exclude unwanted characters
lSeq = lSeq.trim().toUpperCase();
lSeq = lSeq.replace("\n", "");
lSeq = lSeq.replace("\t", "");
lSeq = lSeq.replace(" ", "");
// check the amino acids
for (int i = 0; i < lSeq.length(); i++) {
String lLetter = String.valueOf(lSeq.charAt(i));
if (!isElement(lLetter)) {
throw new IOException(lLetter + " at position " + (i + 1) + " is not a valid element!");
}
}
if (lSeq.length() == 0) {
throw new IOException("Sequence cannot be of length zero!");
}
// return the sequence
return new AASequenceImpl(lSeq);
}
/**
* Gets the elements that can be used.
*/
private void getElements() {
//get the elements
iElements = new HashMap<String, MolecularFormula>();
//get the elements that can be used
try {
BufferedReader br = new BufferedReader(new InputStreamReader(this.getClass().getClassLoader().getResourceAsStream("elements.txt")));
String line;
String[] lHeaderElements = null;
while ((line = br.readLine()) != null) {
if (line.startsWith("#")) {
//do nothing
} else if (line.startsWith("Header")) {
String lTemp = line.substring(line.indexOf("=") + 1);
lHeaderElements = lTemp.split(",");
} else {
String lAa = line.substring(0, line.indexOf("="));
String[] lContribution = line.substring(line.indexOf("=") + 1).split(",");
MolecularFormula lAaFormula = new MolecularFormula();
for (int i = 0; i < lHeaderElements.length; i++) {
for (MolecularElement lMolecularElement : MolecularElement.values()) {
if (lMolecularElement.toString().equalsIgnoreCase(lHeaderElements[i])) {
lAaFormula.addElement(lMolecularElement, Integer.valueOf(lContribution[i]));
}
}
}
iElements.put(lAa, lAaFormula);
}
}
br.close();
} catch (Exception e) {
logger.error(e);
}
}
/**
* Method that checks if a given string is an element we can calculate an isotopic distribution for
*
* @param lElement String with the element to check
* @return boolean that indicates if we can use this element
*/
public boolean isElement(String lElement) {
Object lValue = iElements.get(lElement);
if (lValue == null) {
return false;
}
return true;
}
/**
* This method reads the peaks and their intensities from the specified
* SpectrumFile and stores these internally for drawing. The masses are sorted
* in this step.
*
* @param aSpecFile SpectrumFile from which the peaks and intensities will be copied.
* @param dataPointAndLineColor the color to use for the data points and line
* @param areaUnderCurveColor the color to use for the area under the curve
*/
private void processIsotopicDistribution(SpectrumFile aSpecFile, Color dataPointAndLineColor, Color areaUnderCurveColor) {
if (dataSetCounter == 0) {
iXAxisData = new ArrayList<double[]>();
iYAxisData = new ArrayList<double[]>();
}
iDataPointAndLineColor.add(dataPointAndLineColor);
iAreaUnderCurveColor.add(areaUnderCurveColor);
HashMap peaks = aSpecFile.getPeaks();
iXAxisData.add(new double[peaks.size()]);
iYAxisData.add(new double[peaks.size()]);
iFilename = aSpecFile.getFilename();
// Maximum intensity of the peaks.
double maxInt = 0.0;
// TreeSets are sorted.
TreeSet masses = new TreeSet(peaks.keySet());
Iterator iter = masses.iterator();
int count = 0;
while (iter.hasNext()) {
Double key = (Double) iter.next();
double mass = key.doubleValue();
double intensity = ((Double) peaks.get(key)).doubleValue();
if (intensity > maxInt) {
maxInt = intensity;
}
iXAxisData.get(dataSetCounter)[count] = mass;
iYAxisData.get(dataSetCounter)[count] = intensity;
count++;
}
if (iXAxisStartAtZero) {
this.rescale(0.0, getMaxXAxisValue());
} else {
this.rescale(getMinXAxisValue(), getMaxXAxisValue());
}
this.iPrecursorMZ = aSpecFile.getPrecursorMZ();
int liTemp = aSpecFile.getCharge();
if (liTemp == 0) {
iPrecursorCharge = "?";
} else {
iPrecursorCharge = Integer.toString(liTemp);
iPrecursorCharge += (liTemp > 0 ? "+" : "-");
}
dataSetCounter++;
}
/**
* Get the set of peptide sequences.
*
* @return the peptideSequences
*/
public ArrayList<AASequenceImpl> getPeptideSequences() {
return peptideSequences;
}
}