package com.compomics.util.gui.spectrum; import com.compomics.util.XYZDataPoint; import com.compomics.util.experiment.identification.matches.IonMatch; import com.compomics.util.experiment.massspectrometry.MSnSpectrum; import java.awt.Color; import java.awt.Font; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import javax.swing.JPanel; import org.jfree.chart.ChartFactory; import org.jfree.chart.ChartPanel; import org.jfree.chart.JFreeChart; import org.jfree.chart.plot.IntervalMarker; import org.jfree.chart.plot.PlotOrientation; import org.jfree.chart.plot.XYPlot; import org.jfree.data.xy.DefaultXYZDataset; import org.jfree.ui.Layer; import org.jfree.ui.RectangleEdge; import org.jfree.ui.RectangleInsets; import org.jfree.ui.TextAnchor; /** * Creates a MassErrorBubblePlot displaying the mz values vs the mass error with * the intensity as the size of the bubbles. * * @author Harald Barsnes */ public class MassErrorBubblePlot extends JPanel { /** * If true the relative error (ppm) is used instead of the absolute error * (Da). */ private boolean useRelativeError = false; /** * The default fragment ion marker color. */ private static Color defaultMarkerColor = new Color(0, 0, 255, 25); // light blue /** * The default b ion marker color. */ private static Color bFragmentIonColor = new Color(0, 0, 255, 25); // light blue /** * The default y ion marker color. */ private static Color yFragmentIonColor = new Color(0, 255, 0, 25); // light green /** * The default other marker color. */ private static Color otherFragmentIonColor = new Color(255, 0, 0, 25); // light red /** * The default visible alpha level. */ public static final float DEFAULT_VISIBLE_MARKER_ALPHA = 1.0f; /** * The default non-visible alpha level. */ public static final float DEFAULT_NON_VISIBLE_MARKER_ALPHA = 0.0f; /** * The list of currently used ions. */ private ArrayList<IonMatch> currentlyUsedIonMatches; /** * The chart panel. */ private ChartPanel chartPanel; /** * The data series fragment ion colors. */ private ArrayList<Color> dataSeriesfragmentIonColors = new ArrayList<Color>(); /** * Creates a new MassErrorBubblePlot. * * @param dataIndexes the data set indexes/labels * @param annotations the full list of spectrum annotations * @param currentSpectra the current spectra * @param massTolerance the mass error tolerance * @param fragmentIonLabels if true, the fragment ion type is used as the * data series key, otherwise the psm index is used * @param addMarkers if true interval markers for the fragment ions will be * shown * * @throws java.lang.InterruptedException exception thrown if the thread is * interrupted */ public MassErrorBubblePlot( ArrayList<String> dataIndexes, ArrayList<ArrayList<IonMatch>> annotations, ArrayList<MSnSpectrum> currentSpectra, double massTolerance, boolean fragmentIonLabels, boolean addMarkers) throws InterruptedException { this(dataIndexes, annotations, currentSpectra, massTolerance, 1, fragmentIonLabels, addMarkers, false); } /** * Creates a new MassErrorBubblePlot. * * @param dataIndexes the data set indexes/labels * @param annotations the full list of spectrum annotations * @param currentSpectra the current spectra * @param massTolerance the mass error tolerance * @param fragmentIonLabels if true, the fragment ion type is used as the * data series key, otherwise the psm index is used * @param addMarkers if true interval markers for the fragment ions will be * shown * @param useRelativeError if true the relative error (ppm) is used instead * of the absolute error (Da) * * @throws java.lang.InterruptedException exception thrown if the thread is * interrupted */ public MassErrorBubblePlot( ArrayList<String> dataIndexes, ArrayList<ArrayList<IonMatch>> annotations, ArrayList<MSnSpectrum> currentSpectra, double massTolerance, boolean fragmentIonLabels, boolean addMarkers, boolean useRelativeError) throws InterruptedException { this(dataIndexes, annotations, currentSpectra, massTolerance, 1, fragmentIonLabels, addMarkers, useRelativeError); } /** * Creates a new MassErrorBubblePlot. * * @param dataIndexes the data set indexes/labels * @param annotations the full list of spectrum annotations * @param currentSpectra the current spectra * @param massTolerance the mass error tolerance * @param bubbleScale the bubble scale value * @param fragmentIonLabels if true, the fragment ion type is used as the * data series key, otherwise the psm index is used * @param addMarkers if true interval markers for the fragment ions will be * shown * @param useRelativeError if true the relative error (ppm) is used instead * of the absolute error (Da) * * @throws java.lang.InterruptedException exception thrown if the thread is * interrupted */ public MassErrorBubblePlot( ArrayList<String> dataIndexes, ArrayList<ArrayList<IonMatch>> annotations, ArrayList<MSnSpectrum> currentSpectra, double massTolerance, double bubbleScale, boolean fragmentIonLabels, boolean addMarkers, boolean useRelativeError) throws InterruptedException { super(); setOpaque(false); setLayout(new javax.swing.BoxLayout(this, javax.swing.BoxLayout.LINE_AXIS)); currentlyUsedIonMatches = new ArrayList<IonMatch>(); DefaultXYZDataset xyzDataset = new DefaultXYZDataset(); HashMap<IonMatch, ArrayList<XYZDataPoint>> fragmentIonDataset = new HashMap<IonMatch, ArrayList<XYZDataPoint>>(); double maxError = 0.0; for (int j = 0; j < annotations.size(); j++) { ArrayList<IonMatch> currentAnnotations = annotations.get(j); MSnSpectrum currentSpectrum = currentSpectra.get(j); // the annotated ion matches currentlyUsedIonMatches = currentAnnotations; if (currentlyUsedIonMatches.size() > 0) { // find the most intense annotated peak double maxAnnotatedIntensity = 0.0; for (int i = 0; i < currentlyUsedIonMatches.size(); i++) { IonMatch ionMatch = (IonMatch) currentlyUsedIonMatches.get(i); if (ionMatch.peak.intensity > maxAnnotatedIntensity) { maxAnnotatedIntensity = ionMatch.peak.intensity; } } double totalIntensity = currentSpectrum.getTotalIntensity(); if (fragmentIonLabels) { for (int i = 0; i < currentlyUsedIonMatches.size(); i++) { IonMatch ionMatch = (IonMatch) currentlyUsedIonMatches.get(i); double error; if (useRelativeError) { error = ionMatch.getRelativeError(); } else { error = ionMatch.getAbsoluteError(); } if (Math.abs(error) > maxError) { maxError = Math.abs(error); } if (fragmentIonDataset.get(ionMatch) != null) { fragmentIonDataset.get(ionMatch).add( new XYZDataPoint(ionMatch.peak.mz, error, (ionMatch.peak.intensity / totalIntensity) * bubbleScale)); } else { ArrayList<XYZDataPoint> temp = new ArrayList<XYZDataPoint>(); temp.add(new XYZDataPoint(ionMatch.peak.mz, error, (ionMatch.peak.intensity / totalIntensity) * bubbleScale)); fragmentIonDataset.put(ionMatch, temp); } // The code below ought to be used if fragmentIonLabels are used and more than one spectrum is to be displayed. // As this is currently not used the below code is not used either // if (fragmentIonDataset.get(ionMatch.getPeakAnnotation() + " (" + (j + 1) + ")") != null) { // fragmentIonDataset.get(ionMatch.getPeakAnnotation() + " (" + (j + 1) + ")").add( // new XYZDataPoint(ionMatch.peak.mz, ionMatch.getError(), (ionMatch.peak.intensity / totalIntensity) * bubbleScale)); // } else { // ArrayList<XYZDataPoint> temp = new ArrayList<XYZDataPoint>(); // temp.add(new XYZDataPoint(ionMatch.peak.mz, ionMatch.getError(), (ionMatch.peak.intensity / totalIntensity) * bubbleScale)); // fragmentIonDataset.put(ionMatch.getPeakAnnotation() + " (" + (j + 1) + ")", temp); // } } xyzDataset = addXYZDataSeries(fragmentIonDataset); } else { double[][] dataXYZ = new double[3][currentlyUsedIonMatches.size()]; for (int i = 0; i < currentlyUsedIonMatches.size(); i++) { IonMatch ionMatch = (IonMatch) currentlyUsedIonMatches.get(i); double error; if (useRelativeError) { error = ionMatch.getRelativeError(); } else { error = ionMatch.getAbsoluteError(); } if (Math.abs(error) > maxError) { maxError = Math.abs(error); } dataXYZ[0][i] = ionMatch.peak.mz; dataXYZ[1][i] = error; dataXYZ[2][i] = (ionMatch.peak.intensity / totalIntensity) * bubbleScale; if (fragmentIonDataset.get(ionMatch) != null) { fragmentIonDataset.get(ionMatch).add( new XYZDataPoint(ionMatch.peak.mz, error, ionMatch.peak.intensity / totalIntensity)); } else { ArrayList<XYZDataPoint> temp = new ArrayList<XYZDataPoint>(); temp.add(new XYZDataPoint(ionMatch.peak.mz, error, ionMatch.peak.intensity / totalIntensity)); fragmentIonDataset.put(ionMatch, temp); } } xyzDataset.addSeries(dataIndexes.get(j), dataXYZ); } } } String yAxisLabel; if (useRelativeError) { yAxisLabel = "m/z error (ppm)"; } else { yAxisLabel = "m/z error (Da)"; } JFreeChart chart = ChartFactory.createBubbleChart(null, "m/z", yAxisLabel, xyzDataset, PlotOrientation.VERTICAL, !fragmentIonLabels, true, false); if (chart.getLegend() != null) { chart.getLegend().setPosition(RectangleEdge.RIGHT); } // add fragment ion bar highlighters if (addMarkers) { addFragmentIonTypeMarkers(fragmentIonDataset, chart, true); } // fine tune the chart properites XYPlot plot = chart.getXYPlot(); // set the data series colors if fragment ion label type is currently used if (fragmentIonLabels) { for (int i = 0; i < xyzDataset.getSeriesCount(); i++) { plot.getRenderer().setSeriesPaint(i, dataSeriesfragmentIonColors.get(i)); } } // set the mass error range plot.getRangeAxis().setLowerBound(-massTolerance); plot.getRangeAxis().setUpperBound(massTolerance); plot.getDomainAxis().setLowerBound(0); plot.getDomainAxis().setUpperBound(plot.getDomainAxis().getUpperBound() + 100); // remove space before/after the domain axis plot.getDomainAxis().setUpperMargin(0); plot.getDomainAxis().setLowerMargin(0); plot.setRangeGridlinePaint(Color.black); // make semi see through plot.setForegroundAlpha(0.5f); // set background color chart.getPlot().setBackgroundPaint(Color.WHITE); chart.setBackgroundPaint(Color.WHITE); chartPanel = new ChartPanel(chart); chartPanel.setBackground(Color.WHITE); this.add(chartPanel); } /** * Adds interval markers for all the fragment ion types. * * @param data the data to get the interval markers from * @param chart the chart to add the markers to * @param showMarkers if true interval markers for the fragment ions will be * added */ public static void addFragmentIonTypeMarkers(HashMap<IonMatch, ArrayList<XYZDataPoint>> data, JFreeChart chart, boolean showMarkers) { int horizontalFontPadding = 13; Iterator<IonMatch> iterator = data.keySet().iterator(); // iterate the data and add one interval marker for each fragment ion type while (iterator.hasNext()) { IonMatch fragmentIonType = iterator.next(); // get the mz value of the current fragment ion type ArrayList<XYZDataPoint> dataPoints = data.get(fragmentIonType); XYZDataPoint currentDataPoint = dataPoints.get(0); double currentXValue = currentDataPoint.getX(); // create the interval marker IntervalMarker intervalMarker = new IntervalMarker(currentXValue - 5, currentXValue + 5, defaultMarkerColor); IonMatch ionMatch = fragmentIonType; String tempKey = ionMatch.getPeakAnnotation(); intervalMarker.setLabel(tempKey); intervalMarker.setLabelFont(new Font("SansSerif", Font.PLAIN, 10)); intervalMarker.setLabelPaint(Color.GRAY); intervalMarker.setLabelTextAnchor(TextAnchor.TOP_LEFT); // set the fragment ion marker color if (tempKey.startsWith("b")) { intervalMarker.setPaint(bFragmentIonColor); } else if (tempKey.startsWith("y")) { intervalMarker.setPaint(yFragmentIonColor); } else { intervalMarker.setPaint(otherFragmentIonColor); } // make the marker visible or not if (showMarkers) { intervalMarker.setAlpha(DEFAULT_VISIBLE_MARKER_ALPHA); } else { intervalMarker.setAlpha(DEFAULT_NON_VISIBLE_MARKER_ALPHA); } // set the horizontal location of the markers label // this is need so that not all labels appear on top of each other if (tempKey.startsWith("y")) { intervalMarker.setLabelOffset(new RectangleInsets(horizontalFontPadding, 0, horizontalFontPadding, 0)); } if (tempKey.lastIndexOf("H2O") != -1) { intervalMarker.setLabelOffset(new RectangleInsets(horizontalFontPadding * 2, 0, horizontalFontPadding * 2, 0)); } if (tempKey.lastIndexOf("NH3") != -1) { intervalMarker.setLabelOffset(new RectangleInsets(horizontalFontPadding * 3, 0, horizontalFontPadding * 3, 0)); } if (tempKey.lastIndexOf("Prec") != -1) { intervalMarker.setLabelOffset(new RectangleInsets(horizontalFontPadding * 4, 0, horizontalFontPadding * 4, 0)); if (tempKey.lastIndexOf("H2O") != -1) { intervalMarker.setLabelOffset(new RectangleInsets(horizontalFontPadding * 5, 0, horizontalFontPadding * 5, 0)); } if (tempKey.lastIndexOf("NH3") != -1) { intervalMarker.setLabelOffset(new RectangleInsets(horizontalFontPadding * 6, 0, horizontalFontPadding * 6, 0)); } } if (tempKey.startsWith("i")) { intervalMarker.setLabelOffset(new RectangleInsets(horizontalFontPadding * 5, 0, horizontalFontPadding * 5, 0)); } if (tempKey.lastIndexOf("++") != -1) { intervalMarker.setLabelOffset(new RectangleInsets(horizontalFontPadding * 7, 0, horizontalFontPadding * 7, 0)); if (tempKey.lastIndexOf("H2O") != -1) { intervalMarker.setLabelOffset(new RectangleInsets(horizontalFontPadding * 8, 0, horizontalFontPadding * 8, 0)); } if (tempKey.lastIndexOf("NH3") != -1) { intervalMarker.setLabelOffset(new RectangleInsets(horizontalFontPadding * 9, 0, horizontalFontPadding * 9, 0)); } } // add the interval marker to the plot ((XYPlot) chart.getPlot()).addDomainMarker(intervalMarker, Layer.BACKGROUND); } } /** * Returns the current number of data points in the mass error plot. * * @return the current number of data points */ public int getNumberOfDataPointsInPlot() { return currentlyUsedIonMatches.size(); } /** * Adds the provided data series to an XYZ data set. * * @param data the data to add * @return the created data set */ public DefaultXYZDataset addXYZDataSeries(HashMap<IonMatch, ArrayList<XYZDataPoint>> data) { // sort the keys ArrayList<String> sortedKeys = new ArrayList<String>(); HashMap<String, IonMatch> ionNameTypeMap = new HashMap<String, IonMatch>(); Iterator<IonMatch> iterator = data.keySet().iterator(); while (iterator.hasNext()) { IonMatch ionMatch = iterator.next(); ionNameTypeMap.put(ionMatch.getPeakAnnotation(), ionMatch); sortedKeys.add(ionMatch.getPeakAnnotation()); } java.util.Collections.sort(sortedKeys); DefaultXYZDataset dataset = new DefaultXYZDataset(); for (int j = 0; j < sortedKeys.size(); j++) { IonMatch ionMatch = ionNameTypeMap.get(sortedKeys.get(j)); ArrayList<XYZDataPoint> currentData = data.get(ionMatch); double[][] tempXYZData = new double[3][currentData.size()]; for (int i = 0; i < currentData.size(); i++) { tempXYZData[0][i] = currentData.get(i).getX(); tempXYZData[1][i] = currentData.get(i).getY(); tempXYZData[2][i] = currentData.get(i).getZ(); } dataset.addSeries(ionMatch.getPeakAnnotation(), tempXYZData); dataSeriesfragmentIonColors.add(SpectrumPanel.determineFragmentIonColor(ionMatch.ion, false)); } return dataset; } /** * Returns the chart panel. * * @return the chart panel */ public ChartPanel getChartPanel() { return chartPanel; } }