package com.compomics.util.gui.spectrum; import com.compomics.util.Util; import com.compomics.util.gui.events.RescalingEvent; import com.compomics.util.gui.interfaces.SpectrumAnnotation; import com.compomics.util.gui.interfaces.SpectrumPanelListener; import javax.swing.*; import java.awt.*; import java.awt.event.*; import java.awt.geom.Line2D; import java.io.InputStream; import java.math.BigDecimal; import java.text.DecimalFormat; import java.util.*; import java.util.ArrayList; import java.util.List; /** * This class presents a JPanel that will hold and display a mass spectrum or a * chromatogram. * * @author Lennart Martens * @author Harald Barsnes */ public abstract class GraphicsPanel extends JPanel { /** * The width used for the annotated peaks. */ private float peakWidth = 1.0f; /** * The width used for the background peaks. */ private float backgroundPeakWidth = 1.0f; /** * If true, all numbers in peak annotations are subscripted. */ private boolean subscriptAnnotationNumbers = true; /** * The color to use for the non-annotated peaks when only the annotated * peaks are to be shown. */ private Color peakWaterMarkColor = new Color(100, 100, 100, 50); /** * If true the x-axis will be drawn using the scientific annotation. The * pattern i set in the "scientificPattern" field. */ private boolean scientificXAxis = false; /** * If true the y-axis will be drawn using the scientific annotation. The * pattern i set in the "scientificPattern" field. */ private boolean scientificYAxis = false; /** * The number format pattern for scientific annotation. */ private String scientificPattern = "##0.#####E0"; /** * A hashmap of the current x-axis reference areas. Key is the name of the * reference area. */ private HashMap<String, ReferenceArea> referenceAreasXAxis = new HashMap<String, ReferenceArea>(); /** * A hashmap of the current y-axis reference areas. Key is the name of the * reference area. */ private HashMap<String, ReferenceArea> referenceAreasYAxis = new HashMap<String, ReferenceArea>(); /** * If set to true, the y-axis is removed, the y- and x-axis tags are * removed, and any annotations are hidden. All to make the graphics panel * look better in a smaller version, e.g., when put into a table cell. When * turning miniature mode one it is also recommended to reduce the max * padding. * * Note that miniature and reduced max padding is set automatically by the * GraphicsPanelTableCellRenderer. */ protected boolean miniature = false; /** * If set to true, all y data is assumed to be positive. This adds a white * polygon under the y-axis hiding any polygon data lines that crosses * (slightly) below the y-axis. */ protected boolean yDataIsPositive = true; /** * The opacity of the spectra. 0 means completely see-through, 1 means * opaque. */ protected float alphaLevel = 0.3f; /** * The number of datasets currently displayed in the panel. */ protected int dataSetCounter = 0; /** * The number of mirrored datasets currently displayed in the panel. */ protected int dataSetCounterMirroredSpectra = 0; /** * This status indicates that no annotation will be displayed, but the user * will have a fully functional interface (point clicking, selecting, * sequencing etc.) */ public final int INTERACTIVE_STATUS = 0; /** * This status indicates that annotation (if present) will be displayed, * while limiting the user to zooming in/out. */ public final int ANNOTATED_STATUS = 1; /** * This HashMap instance holds all the known mass deltas (if any). The keys * are the Doubles with the massdelta, the values are the descriptions. */ protected static HashMap<Double, String> iKnownMassDeltas = null; // @TODO: should not be static! /** * If true, pairs of delta mass annotations are used when doing de novo * sequencing. If false, only single delta masses are annotated. */ private boolean useMassDeltaCombinations = true; // Static init block takes care of reading the 'SpectrumPanel.properties' file if // it hasn't already been done. static { try { if (iKnownMassDeltas == null) { iKnownMassDeltas = new HashMap(); Properties temp = new Properties(); InputStream is = SpectrumPanel.class.getClassLoader().getResourceAsStream("SpectrumPanel.properties"); if (is != null) { temp.load(is); is.close(); Iterator iter = temp.keySet().iterator(); while (iter.hasNext()) { String key = (String) iter.next(); iKnownMassDeltas.put(new Double(key), temp.getProperty(key)); } } } } catch (Exception e) { // Do nothing. So now masses will be known. } } /** * The size of the window to use when searching for matches in the known * masses list when the user hovers over a second data point after clicking * a previous data point. */ protected double deltaMassWindow = 0.2; /** * The label (and unit between brackets, if available) for the x-axis. * Defaults to "m/z". */ protected String iXAxisLabel = "m/z"; /** * The label (and unit between brackets, if available) for the y-axis. * Defaults to "Int". */ protected String iYAxisLabel = "Int"; /** * This is the color the filename should be presented in. */ protected Color iFilenameColor = null; /** * Colors in which the data points and peaks are rendered. Indexed by * dataset. */ protected ArrayList<Color> iDataPointAndLineColor = new ArrayList<Color>(); /** * Colors in which the data points and peaks are rendered for the mirrored * spectra. Indexed by dataset. */ protected ArrayList<Color> iDataPointAndLineColorMirroredSpectra = new ArrayList<Color>(); /** * Colors in which the chromatogram polyline is rendered. Indexed by * dataset. */ protected ArrayList<Color> iAreaUnderCurveColor = new ArrayList<Color>(); /** * Colors in which the chromatogram polyline is rendered for the mirrored * spectra. Indexed by dataset. */ protected ArrayList<Color> iAreaUnderCurveColorMirroredSpectra = new ArrayList<Color>(); /** * Size for the point on a polygon. */ protected Integer iPointSize = 0; /** * The spectrum or chromatogram filename. */ protected String iFilename = null; /** * The spectrum or chromatogram filename for the mirrored spectrum or * chromatogram. */ protected String iFilenameMirrorredSpectrum = null; /** * The list of SpectrumPanelListeners. */ protected ArrayList iSpecPanelListeners = new ArrayList(); /** * The deviation (both left and right) allowed for point highlighting * detection. */ protected int iPointDetectionTolerance = 5; /** * When the mouse is dragged, this represents the X-coordinate of the * starting location. */ protected int iStartXLoc = 0; /** * When the mouse is dragged, this represents the Y-coordinate of the * starting location. */ protected int iStartYLoc = 0; /** * When the mouse is dragged, this represents the X-coordinate of the ending * location. */ protected int iEndXLoc = 0; /** * The lower range for the current zoom range. */ protected double xAxisZoomRangeLowerValue = 0; /** * The upper range for the current zoom range. */ protected double xAxisZoomRangeUpperValue = 0; /** * The current dragging location. */ protected int iDragXLoc = 0; /** * Scale unit for the X axis. */ protected double iXScaleUnit = 0.0; /** * Scale unit for the Y axis. */ protected double iYScaleUnit = 0.0; /** * Graphical unit for the X axis. */ protected int iXUnit = 0; /** * Graphical unit for the Y axis. */ protected int iYUnit = 0; /** * Effective distance from the x-axis to the panel border. */ protected int iXPadding = 0; /** * Effective distance from the panel top border to 5 pixels above the top of * the highest point (or y-tick mark). */ protected int iTopPadding = 0; /** * This boolean is set to 'true' if the x-axis should start at zero. */ protected boolean iXAxisStartAtZero = true; /** * This boolean is set to 'true' when dragging is performed. */ protected boolean iDragged = false; /** * The number of X-axis tags. */ protected int xTagCount = 10; /** * The number of Y-axis tags. */ protected int yTagCount = 10; /** * The padding (distance between the axes and the border of the panel). */ protected int padding = 20; /** * The current padding (distance between the axes and the border of the * panel). */ protected int currentPadding = 20; /** * The maximum padding (distance between the axes and the border of the * panel). Increase if font size on the y-axis becomes too small. */ protected int maxPadding = 50; /** * The boolean is set to 'true' if the file name is to be shown in the * panel. */ protected boolean showFileName = true; /** * The boolean is set to 'true' if the precursor details is to be shown in * the panel. */ protected boolean showPrecursorDetails = true; /** * The boolean is set to 'true' if the resolution is to be shown in the * panel. */ protected boolean showResolution = true; /** * All the x-axis data points. Indexed by dataset (one double[] per * dataset). First dataset is the first double[], second dataset is the * second double[] etc.Should at all times be sorted from high to low. */ protected ArrayList<double[]> iXAxisData = null; /** * All the x-axis data points for the mirrored spectrum. Indexed by dataset * (one double[] per dataset). First dataset is the first double[], second * dataset is the second double[] etc.Should at all times be sorted from * high to low. */ protected ArrayList<double[]> iXAxisDataMirroredSpectrum = null; /** * The minimum x-axis value to display. */ protected double iXAxisMin = 0.0; /** * The maximum x-axis value to display. */ protected double iXAxisMax = 0.0; /** * The minimum y-axis value to display. */ protected double iYAxisMin = 0.0; /** * The maximum y-axis value to display. */ protected double iYAxisMax = 0.0; /** * The percent non-inclusive, minimal y-axis value (compared to the highest * point in the spectrum) a point should have before being eligible for * annotation. Default is '0.0'. */ protected double iAnnotationYAxisThreshold = 0.0; /** * All the y-axis values. Indexed by dataset (one double[] per dataset). * First dataset is the first double[], second dataset is the second * double[] etc. Y-axis values are related to the x-axis values by the table * index. So the first y-axis value of the first dataset is the value for * the first x-axis value in the first dataset etc. */ protected ArrayList<double[]> iYAxisData = null; /** * All the y-axis values for the mirrored spectra. Indexed by dataset (one * double[] per dataset). First dataset is the first double[], second * dataset is the second double[] etc. Y-axis values are related to the * x-axis values by the table index. So the first y-axis value of the first * dataset is the value for the first x-axis value in the first dataset etc. */ protected ArrayList<double[]> iYAxisDataMirroredSpectrum = null; /** * This variable holds the precursor M/Z. */ protected double iPrecursorMZ = 0.0; /** * This variable holds the precursor M/Z for the mirrored spectrum. */ protected double iPrecursorMZMirroredSpectrum = 0.0; /** * This String holds the charge for the precursor. */ protected String iPrecursorCharge = null; /** * This String holds the charge for the precursor for the mirrored spectrum. */ protected String iPrecursorChargeMirorredSpectrum = null; /** * This array will hold the x-coordinates in pixels for all the x-axis * values. Link is through index. Again indexed by dataset (one double[] per * dataset). */ protected ArrayList<int[]> iXAxisDataInPixels = null; /** * This array will hold the y-coordinates in pixels for all the y-axis * values. Link is through index. Again indexed by dataset (one double[] per * dataset). */ protected ArrayList<int[]> iYAxisDataInPixels = null; /** * This array will hold the x-coordinates in pixels for all the x-axis * values of the mirrored spectrum. Link is through index. Again indexed by * dataset (one double[] per dataset). */ protected ArrayList<int[]> iXAxisDataInPixelsMirroredSpectrum = null; /** * This array will hold the y-coordinates in pixels for all the y-axis * values of the mirrored spectrum. Link is through index. Again indexed by * dataset (one double[] per dataset). */ protected ArrayList<int[]> iYAxisDataInPixelsMirroredSpectrum = null; /** * Boolean that will be 'true' when a point needs highlighting. */ protected boolean iHighLight = false; /** * Boolean that will be 'true' when a point needs highlighting in the * mirrored spectra. */ protected boolean iHighLightMirrored = false; /** * Index of the point that needs to be highlighted. */ protected int iHighLightIndex = 0; /** * Index of the point that needs to be highlighted in the mirrored spectra. */ protected int iHighLightIndexMirrored = 0; /** * Index of the dataset containing the point that needs to be highlighted. */ protected int iHighLightDatasetIndex = 0; /** * Index of the dataset containing the point that needs to be highlighted in * the mirrored spectra. */ protected int iHighLightDatasetIndexMirrored = 0; /** * Boolean that indicates whether a point has been marked by clicking. */ protected boolean iClicked = false; /** * Boolean that indicates whether a point has been marked by clicking in the * mirrored spectra. */ protected boolean iClickedMirrored = false; /** * Int that indicates which point was clicked. */ protected int iClickedIndex = 0; /** * Int that indicates which point was clicked in the mirrored spectra. */ protected int iClickedIndexMirrored = 0; /** * Int that indicates which dataset contains the clicked point. */ protected int iClickedDataSetIndex = 0; /** * Int that indicates which dataset contains the clicked point in the * mirrored spectra. */ protected int iClickedDataSetIndexMirrored = 0; /** * The Vector that holds all points clicked up to now. */ protected Vector iClickedList = new Vector(15, 5); /** * The Vector that holds all points clicked up to now in the mirrored * spectra. */ protected Vector iClickedListMirrored = new Vector(15, 5); /** * The Vector that holds the dataset indices of all points clicked up to * now. */ protected Vector iClickedListDatasetIndices = new Vector(15, 5); /** * The Vector that holds the dataset indices of all points clicked up to now * in the mirrored spectra. */ protected Vector iClickedListDatasetIndicesMirrored = new Vector(15, 5); /** * The Vector that holds a set of stored points from a previously * established list. */ protected Vector iStoredSequence = new Vector(15, 5); /** * The Vector that holds a set of stored points from a previously * established list in the mirrored spectra. */ protected Vector iStoredSequenceMirrored = new Vector(15, 5); /** * The Vector that holds the dataset indices of stored points from a * previously established list. */ protected Vector iStoredSequenceDatasetIndices = new Vector(15, 5); /** * The Vector that holds the dataset indices of stored points from a * previously established list in the mirrored spectra. */ protected Vector iStoredSequenceDatasetIndicesMirrored = new Vector(15, 5); /** * The Vector that holds a set of Annotation instances. */ protected Vector iAnnotations = new Vector(50, 20); /** * The Vector that holds a set of Annotation instances for the mirrored * spectra. */ protected Vector iAnnotationsMirroredSpectra = new Vector(50, 20); /** * Minimal dragging distance in pixels. */ protected int iMinDrag = 15; /** * This variable holds the current drawing style. */ protected DrawingStyle iCurrentDrawStyle = DrawingStyle.LINES; /** * This variable holds the dot radius; only used when drawing style is DOTS * style. */ protected int iDotRadius = 2; /** * An enumerator of the possible GraphicsPanel types. */ public enum DrawingStyle { /** * Draw lines connecting the X-axis with the measurement */ LINES, /** * Draw a dot at the measurement height. */ DOTS } /** * The ms level of the current spectrum. O is assumed to mean no ms level * given. */ protected int iMSLevel = 0; /** * If false, only the annotated peaks will be shown. Note that this setting * is ignored in profile mode! */ protected boolean showAllPeaks = true; /** * If true, the automatic y-axis zoom excludes the background peaks. False * includes all peaks in the zoom. */ protected boolean yAxisZoomExcludesBackgroundPeaks = false; /** * If more than one peak is within the accuracy range of an annotation * setting this to true will always select the most intense of possible * peaks. Setting this variable to false will instead select the most * accurate peak. */ protected boolean annotateHighestPeak = true; /** * Returns true if the most intense of possible peaks to annotate is to be * selected, false if the most accurate is to be selected. * * @return true if the most intense of possible peaks to annotate is to be * selected, false if the most accurate is to be selected */ public boolean isAnnotateHighestPeak() { return annotateHighestPeak; } /** * Set if the most intense of possible peaks to annotate is to be selected, * false if the most accurate is to be selected. * * @param annotateHighestPeak if the most intense of possible peaks to * annotate is to be selected, false if the most accurate is to be selected */ public void setAnnotateHighestPeak(boolean annotateHighestPeak) { this.annotateHighestPeak = annotateHighestPeak; } /** * Returns true if the numbers in the peak annotations are to be * subscripted. * * @return true if the numbers in the peak annotations are to be subscripted */ public boolean isSubscriptAnnotationNumbers() { return subscriptAnnotationNumbers; } /** * Set if the numbers in the peak annotations are to be subscripted. * * @param subscriptAnnotationNumbers set if the numbers in the peak * annotations are to be subscripted */ public void setSubscriptAnnotationNumbers(boolean subscriptAnnotationNumbers) { this.subscriptAnnotationNumbers = subscriptAnnotationNumbers; } /** * Returns true if the automatic y-axis zoom excludes background peaks. * False if includes all peaks. * * @return true if the automatic y-axis zoom only excludes background peaks */ public boolean yAxisZoomOnlyExcludesBackgroundPeaks() { return yAxisZoomExcludesBackgroundPeaks; } /** * Set if the automatic y-axis zoom only considers the annotated peaks. * * @param yAxisZoomExcludesBackgroundPeaks if the automatic y-axis zoom only * considers the annotated peaks */ public void setYAxisZoomExcludesBackgroundPeaks(boolean yAxisZoomExcludesBackgroundPeaks) { this.yAxisZoomExcludesBackgroundPeaks = yAxisZoomExcludesBackgroundPeaks; } /** * If true, pairs of delta mass annotations are used when doing de novo * sequencing. If false, only single delta masses are annotated. * * @return the useMassDeltaCombinations if pairs of delta mass annotations * are used when doing de novo sequencing */ public boolean useMassDeltaCombinations() { return useMassDeltaCombinations; } /** * If true, pairs of delta mass annotations are used when doing de novo * sequencing. If false, only single delta masses are annotated. * * @param useMassDeltaCombinations the useMassDeltaCombinations to set */ public void setUseMassDeltaCombinations(boolean useMassDeltaCombinations) { this.useMassDeltaCombinations = useMassDeltaCombinations; } /** * Returns true of the precursor details are to be shown. * * @return true of the precursor details are to be show */ public boolean showPrecursorDetails() { return showPrecursorDetails; } /** * Set whether the precursor details are to be shown. * * @param showPrecursorDetails the showPrecursorDetails to set */ public void setShowPrecursorDetails(boolean showPrecursorDetails) { this.showPrecursorDetails = showPrecursorDetails; } /** * Returns true if the resolution is to be shown. * * @return true of the resolution is to be shown */ public boolean showResolution() { return showResolution; } /** * Set whether the resolution is to be shown. * * @param showResolution the showResolution to set */ public void setShowResolution(boolean showResolution) { this.showResolution = showResolution; } /** * An enumerator of the possible GraphicsPanel types. */ protected enum GraphicsPanelType { profileSpectrum, centroidSpectrum, profileChromatogram, centroidChromatogram, isotopicDistributionCentroid, isotopicDistributionProfile } /** * Sets the current GraphicsPanel type, default to centroid spectrum. */ protected GraphicsPanelType currentGraphicsPanelType = GraphicsPanelType.centroidSpectrum; /** * Returns true of all the y-data is to be assumed as positive. * * @return true of all the y-data is to be assumed as positive */ public boolean yDataIsPositive() { return yDataIsPositive; } /** * Set to true of all y data values can be assumed to be positive. * * @param yDataIsPositive true of all y data values can be assumed to be * positive */ public void setYDataIsPositive(boolean yDataIsPositive) { this.yDataIsPositive = yDataIsPositive; } /** * Returns true if the graphics panel is to be drawn in a miniature form. * * @return true if the graphics panel is to be drawn in a miniature form */ public boolean isMiniature() { return miniature; } /** * Set if the graphics panel is to be drawn in a miniature form. * * @param miniature if the spectrum is to be drawn in a miniature form */ public void setMiniature(boolean miniature) { this.miniature = miniature; } /** * Returns the lower range for the current zoom range. * * @return the lower range for the current zoom range */ public double getXAxisZoomRangeLowerValue() { return xAxisZoomRangeLowerValue; } /** * Returns the upper range for the current zoom range. * * @return the upper range for the current zoom range */ public double getXAxisZoomRangeUpperValue() { return xAxisZoomRangeUpperValue; } /** * Get the size of the window to use when searching for matches in the known * masses list when the user hovers over a second data point after clicking * a previous data point. * * @return the size of the delta mass window */ public double getDeltaMassWindow() { return deltaMassWindow; } /** * Set the size of the window to use when searching for matches in the known * masses list when the user hovers over a second data point after clicking * a previous data point. * * @param deltaMassWindow the new size of the delta mass window */ public void setDeltaMassWindow(double deltaMassWindow) { this.deltaMassWindow = deltaMassWindow; } /** * Get all the known mass deltas (if any). The keys are the Doubles with the * massdelta, the values are the descriptions. * * @return HashMap of the known mass deltas */ public static HashMap<Double, String> getKnownMassDeltas() { return iKnownMassDeltas; } /** * Set all the known mass deltas (if any). The keys are the Doubles with the * massdelta, the values are the descriptions. * * @param aiKnownMassDeltas the HasMap of the known mass deltas */ public static void setKnownMassDeltas(HashMap<Double, String> aiKnownMassDeltas) { iKnownMassDeltas = aiKnownMassDeltas; } /** * Returns the x-axis data. * * @return the x-axis data */ public ArrayList<double[]> getXAxisData() { return iXAxisData; } /** * Returns the y-axis data. * * @return the y-axis data */ public ArrayList<double[]> getYAxisData() { return iYAxisData; } /** * Returns the alpha level. * * @return the alphaLevel */ public float getAlphaLevel() { return alphaLevel; } /** * Sets the alpha level * * @param alphaLevel the alphaLevel to set */ public void setAlphaLevel(float alphaLevel) { this.alphaLevel = alphaLevel; } /** * This method sets the start value of the x-axis to zero. * * @param aXAxisStartAtZero if true the x axis starts at zero */ public void setXAxisStartAtZero(boolean aXAxisStartAtZero) { iXAxisStartAtZero = aXAxisStartAtZero; } /** * Set the max padding (distance between the axes and the border of the * panel). * * @param aMaxPadding the new max padding */ public void setMaxPadding(int aMaxPadding) { maxPadding = aMaxPadding; } /** * Returns the max padding (distance between the axes and the border of the * panel). * * @return the max padding */ public int getMaxPadding() { return maxPadding; } /** * Returns the current width of the peaks. * * @return the peak width */ public float getPeakWidth() { return peakWidth; } /** * Set the peak width. * * @param peakWidth the new peak width */ public void setPeakWidth(float peakWidth) { this.peakWidth = peakWidth; } /** * Returns the current width of the background peaks. * * @return the peak width */ public float getBackgroundPeakWidth() { return backgroundPeakWidth; } /** * Set the backgroundPeakWidth peak width. * * @param backgroundPeakWidth the new backgroundPeakWidth peak width */ public void setBackgroundPeakWidth(float backgroundPeakWidth) { this.backgroundPeakWidth = backgroundPeakWidth; } /** * This method sets all the annotations on this instance. Passing a 'null' * value for the Vector will result in simply removing all annotations. Do * note that this method will attempt to remove duplicate annotations on a * point by deleting any annotation for which the combination of annotation * label and annotation x-axis value has been seen before! * * @param aAnnotations Vector with SpectrumAnnotation instances. * @param annotateHighestPeak boolean indicating if the most intense peak * within the accuracy range (true) or the most accurate peak (false) is to * be annotated */ public void setAnnotations(List<SpectrumAnnotation> aAnnotations, boolean annotateHighestPeak) { this.annotateHighestPeak = annotateHighestPeak; setAnnotations(aAnnotations); } /** * This method sets all the annotations on this instance. Passing a 'null' * value for the Vector will result in simply removing all annotations. Do * note that this method will attempt to remove duplicate annotations on a * point by deleting any annotation for which the combination of annotation * label and annotation x-axis value has been seen before! * * @param aAnnotations Vector with SpectrumAnnotation instances. */ public void setAnnotations(List<SpectrumAnnotation> aAnnotations) { this.iAnnotations = new Vector(50, 25); if (aAnnotations != null) { // Attempt to remove duplicates. HashSet removeDupes = new HashSet(aAnnotations.size()); for (SpectrumAnnotation annotation : aAnnotations) { String key = annotation.getLabel() + annotation.getMZ(); if (removeDupes.contains(key)) { // Duplicate, ignore! } else { removeDupes.add(key); this.iAnnotations.add(annotation); } } } } /** * This method sets all the annotations for the mirrored spectra. Passing a * 'null' value for the Vector will result in simply removing all * annotations. Do note that this method will attempt to remove duplicate * annotations on a point by deleting any annotation for which the * combination of annotation label and annotation x-axis value has been seen * before! * * @param aAnnotations Vector with SpectrumAnnotation instances * @param annotateHighestPeak boolean indicating if the most intense peak * within the accuracy range (true) or the most accurate peak (false) is to * be annotated */ public void setAnnotationsMirrored(List<SpectrumAnnotation> aAnnotations, boolean annotateHighestPeak) { this.annotateHighestPeak = annotateHighestPeak; setAnnotationsMirrored(aAnnotations); } /** * This method sets all the annotations for the mirrored spectra. Passing a * 'null' value for the Vector will result in simply removing all * annotations. Do note that this method will attempt to remove duplicate * annotations on a point by deleting any annotation for which the * combination of annotation label and annotation x-axis value has been seen * before! * * @param aAnnotations Vector with SpectrumAnnotation instances */ public void setAnnotationsMirrored(List<SpectrumAnnotation> aAnnotations) { this.iAnnotationsMirroredSpectra = new Vector(50, 25); if (aAnnotations != null) { // Attempt to remove duplicates. HashSet removeDupes = new HashSet(aAnnotations.size()); for (SpectrumAnnotation annotation : aAnnotations) { String key = annotation.getLabel() + annotation.getMZ(); if (removeDupes.contains(key)) { // Duplicate, ignore! } else { removeDupes.add(key); this.iAnnotationsMirroredSpectra.add(annotation); } } } } /** * This method allows the caller to set the minimal non-inclusive y-axis * value threshold in percent (compared to the highest point in the spectrum * or chromatogram) a point must pass before being eligible for annotation. * * @param aThreshold double with the minimal y-axis value in percent (as * compared to the highest point in the spectrum or chromatogram) cutoff * threshold for annotation. */ public void setAnnotationYAxisThreshold(double aThreshold) { this.iAnnotationYAxisThreshold = (aThreshold / 100) * iYAxisMax; } /** * This method sets the display color for the filename on the panel. Can be * 'null' for default coloring. * * @param aFilenameColor Color to render the filename in on the panel. Can * be 'null' for default coloring. */ public void setFilenameColor(Color aFilenameColor) { iFilenameColor = aFilenameColor; } /** * Returns the minimum value of the x-axis. * * @return the minimum the axis value */ public double getiXAxisMin() { return iXAxisMin; } /** * Sets the minimum value for the x-axis. * * @param aXAxisMin double with the minimum x-axis value */ public void setiXAxisMin(double aXAxisMin) { this.iXAxisMin = aXAxisMin; } /** * Returns the maximum value of the x-axis. * * @return the maximum x axis value */ public double getiXAxisMax() { return iXAxisMax; } /** * Sets the maximum value for the x-axis. * * @param aXAxisMax double with the maximum x-axis value */ public void setiXAxisMax(double aXAxisMax) { this.iXAxisMax = aXAxisMax; } /** * Returns the minimum value of the y-axis. * * @return the minimum y axis value */ public double getiYAxisMin() { return iYAxisMin; } /** * Sets the minimum value for the y-axis. * * @param aYAxisMin double with the minimum y-axis value */ public void setiYAxisMin(double aYAxisMin) { this.iYAxisMin = aYAxisMin; } /** * Returns the maximum value of the y-axis. * * @return the maximum y axis value */ public double getiYAxisMax() { return iYAxisMax; } /** * Sets the maximum value for the y-axis. * * @param aYAxisMax double with the maximum y-axis value */ public void setiYAxisMax(double aYAxisMax) { this.iYAxisMax = aYAxisMax; } /** * Invoked by Swing to draw components. Applications should not invoke * <code>paint</code> directly, but should instead use the * <code>repaint</code> method to schedule the component for redrawing. * <p> * This method actually delegates the work of painting to three protected * methods: <code>paintComponent</code>, <code>paintBorder</code>, and * <code>paintChildren</code>. They're called in the order listed to ensure * that children appear on top of component itself. Generally speaking, the * component and its children should not paint in the insets area allocated * to the border. Subclasses can just override this method, as always. A * subclass that just wants to specialize the UI (look and feel) delegate's * <code>paint</code> method should just override * <code>paintComponent</code>. * * @param g the <code>Graphics</code> context in which to paint * @see #paintComponent * @see #paintBorder * @see #paintChildren * @see #getComponentGraphics * @see #repaint */ public void paint(Graphics g) { super.paint(g); if (iXAxisData != null) { if (iDragged && iDragXLoc > 0) { g.drawLine(iStartXLoc, iStartYLoc, iDragXLoc, iStartYLoc); g.drawLine(iStartXLoc, iStartYLoc - 2, iStartXLoc, iStartYLoc + 2); g.drawLine(iDragXLoc, iStartYLoc - 2, iDragXLoc, iStartYLoc + 2); } // round the range of the x- and y-axis to integer values iXAxisMin = (int) Math.floor(iXAxisMin); // @TODO: increase the zooming resolution!!! iXAxisMax = (int) Math.ceil(iXAxisMax); iYAxisMin = (int) Math.floor(iYAxisMin); iYAxisMax = (int) Math.ceil(iYAxisMax); // draw the x and y axis drawAxes(g, iXAxisMin, iXAxisMax, 2, iYAxisMin, iYAxisMax);// @TODO: scale? // add reference areas that are to be drawn in the back, if any drawYAxisReferenceAreas(g, false); drawXAxisReferenceAreas(g, false); // draw the peaks if (currentGraphicsPanelType.equals(GraphicsPanelType.profileChromatogram) || currentGraphicsPanelType.equals(GraphicsPanelType.profileSpectrum) || currentGraphicsPanelType.equals(GraphicsPanelType.isotopicDistributionProfile)) { drawFilledPolygon(g); } else { drawPeaks(g); if (dataSetCounterMirroredSpectra > 0) { drawMirroredPeaks(g); } } // draw any measurement lines in the normal spectra if (iClicked && iHighLight && iClickedIndex != iHighLightIndex) { // Now we should calculate the distance based on the real values and draw a line to show this. this.drawMeasurementLine(iClickedIndex, iClickedDataSetIndex, iHighLightIndex, iHighLightDatasetIndex, g, Color.blue, 0, false); } // draw any measurement lines in the mirrored spectra if (iClickedMirrored && iHighLightMirrored && iClickedIndexMirrored != iHighLightIndexMirrored) { // Now we should calculate the distance based on the real values and draw a line to show this. this.drawMeasurementLine(iClickedIndexMirrored, iClickedDataSetIndexMirrored, iHighLightIndexMirrored, iHighLightDatasetIndexMirrored, g, Color.blue, 0, true); } // hihlight peaks if (iHighLight) { this.highLightPeak(iHighLightIndex, iHighLightDatasetIndex, g, false); iHighLight = false; } // highlight mirrored peaks if (iHighLightMirrored) { this.highLightPeak(iHighLightIndexMirrored, iHighLightDatasetIndexMirrored, g, true); iHighLightMirrored = false; } // highlight clicked peaks if (iClicked) { this.highlightClicked(iClickedIndex, iHighLightDatasetIndex, g, false); } // highlight clicked mirrored peaks if (iClickedMirrored) { this.highlightClicked(iClickedIndexMirrored, iHighLightDatasetIndexMirrored, g, true); } // see if there are daisychain to display drawDaisyChain(g, iClickedList, iClickedListDatasetIndices, iClickedIndex, iClickedDataSetIndex, iStoredSequence, iStoredSequenceDatasetIndices, false); // see if there are daisychains to display for the mirrored spectra drawDaisyChain(g, iClickedListMirrored, iClickedListDatasetIndicesMirrored, iClickedIndexMirrored, iClickedDataSetIndexMirrored, iStoredSequenceMirrored, iStoredSequenceDatasetIndicesMirrored, true); // annotate peaks annotatePeaks(g, iAnnotations, false); // annotate mirrored peaks annotatePeaks(g, iAnnotationsMirroredSpectra, true); // add reference areas that are to be drawn on top of the data, if any{ drawYAxisReferenceAreas(g, true); drawXAxisReferenceAreas(g, true); // (re-)draw the axes to have them appear in front of the data points drawAxes(g, iXAxisMin, iXAxisMax, 2, iYAxisMin, iYAxisMax); // @TODO scale. } } /** * Annotate the annotated peaks. * * @param g the <code>Graphics</code> context in which to paint * @param annotations the annotations * @param mirrored if the annotations are for the mirrored spectra */ protected void annotatePeaks(Graphics g, Vector annotations, boolean mirrored) { if (annotations != null && annotations.size() > 0 && !miniature) { // This HashMap will contain the indices of the points that already carry an annotation // as keys (datasetIndex_peakIndex), and the number of annotations as values. HashMap<String, Integer> annotatedPeaks = new HashMap<String, Integer>(); for (Object o : annotations) { if (o instanceof SpectrumAnnotation) { SpectrumAnnotation sa = (SpectrumAnnotation) o; this.annotate(sa, g, annotatedPeaks, mirrored); } } } } /** * Draw daisy chains. * * @param g the <code>Graphics</code> context in which to paint * @param clickedList vector that holds all points clicked up to now * @param clickedListDatasetIndices vector that holds the dataset indices of * all points clicked up to now * @param clickedIndex int that indicates which point was clicked * @param clickedDataSetIndex int that indicates which dataset contains the * clicked point * @param storedSequence vector that holds a set of stored points from a * previously established list * @param storedSequenceDatasetIndices vector that holds the dataset indices * of stored points from a previously established list. * @param mirrored if the daisy chains are for the mirrored spectra */ protected void drawDaisyChain(Graphics g, Vector clickedList, Vector clickedListDatasetIndices, int clickedIndex, int clickedDataSetIndex, Vector storedSequence, Vector storedSequenceDatasetIndices, boolean mirrored) { int clickedSize = clickedList.size(); if (clickedSize > 0) { for (int i = 0; i < clickedSize; i++) { // The last one should be connected to iClicked. int first = ((Integer) clickedList.get(i)).intValue(); int firstDataSetIndex = ((Integer) clickedListDatasetIndices.get(i)).intValue(); int second, secondDataSetIndex; if ((i + 1) == clickedSize) { second = clickedIndex; secondDataSetIndex = clickedDataSetIndex; } else { second = ((Integer) clickedList.get(i + 1)).intValue(); secondDataSetIndex = ((Integer) clickedListDatasetIndices.get(i + 1)); } this.drawMeasurementLine(first, firstDataSetIndex, second, secondDataSetIndex, g, Color.LIGHT_GRAY, 0, mirrored); } } // See if there are secondary daisychains to display. if (storedSequence.size() > 0) { for (int i = 1; i < storedSequence.size(); i++) { int first = ((Integer) storedSequence.get(i - 1)).intValue(); int second = ((Integer) storedSequence.get(i)).intValue(); int firstDataSetIndex = ((Integer) storedSequenceDatasetIndices.get(i - 1)).intValue(); int secondDataSetIndex = ((Integer) storedSequenceDatasetIndices.get(i)).intValue(); this.drawMeasurementLine(first, firstDataSetIndex, second, secondDataSetIndex, g, Color.red, g.getFontMetrics().getAscent() + 15, mirrored); } } } /** * Draws the x-axis reference areas if any. * * @param g Graphics object to draw on. * @param drawOnTop if true the areas to be drawn on top of the data are * drawn, if false the areas that are to be drawn behind the data are drawn */ protected void drawXAxisReferenceAreas(Graphics g, boolean drawOnTop) { // used to find the location of the label FontMetrics fm = g.getFontMetrics(); Font oldFont = this.getFont(); // switch to 2D graphics Graphics2D g2d = (Graphics2D) g; // store the original color Color originalColor = g2d.getColor(); Composite originalComposite = g2d.getComposite(); Stroke originalStroke = g2d.getStroke(); Iterator<String> allReferenceAreas = referenceAreasXAxis.keySet().iterator(); while (allReferenceAreas.hasNext()) { ReferenceArea currentReferenceArea = referenceAreasXAxis.get(allReferenceAreas.next()); if (drawOnTop == currentReferenceArea.drawOnTop()) { // set the color and opacity level g.setColor(currentReferenceArea.getAreaColor()); g2d.setComposite(makeComposite(currentReferenceArea.getAlpha())); // set up the data tables for the polygon int[] xTemp = new int[4]; int[] yTemp = new int[4]; // x range start double tempDouble = (currentReferenceArea.getStart() - iXAxisMin) / iXScaleUnit; int start = (int) tempDouble; if ((tempDouble - start) >= 0.5) { start++; } // x range end tempDouble = (currentReferenceArea.getEnd() - iXAxisMin) / iXScaleUnit; int end = (int) tempDouble; if ((tempDouble - end) >= 0.5) { end++; } // x-axis locations xTemp[0] = start + iXPadding; xTemp[1] = end + iXPadding; xTemp[2] = end + iXPadding; xTemp[3] = start + iXPadding; // y-axis locations int xAxisYLocation; if (dataSetCounterMirroredSpectra > 0) { xAxisYLocation = (this.getHeight() - currentPadding) / 2; } else { xAxisYLocation = this.getHeight() - currentPadding; } double areaHeight = xAxisYLocation - currentPadding; int y = (int) (areaHeight - (areaHeight * currentReferenceArea.getPercentLength())) + currentPadding; if (currentReferenceArea.isAboveXAxis()) { yTemp[0] = y; yTemp[1] = y; if (dataSetCounterMirroredSpectra > 0) { yTemp[2] = this.getHeight() - xAxisYLocation - currentPadding; yTemp[3] = this.getHeight() - xAxisYLocation - currentPadding; } else { yTemp[2] = this.getHeight() - currentPadding; yTemp[3] = this.getHeight() - currentPadding; } } else { y = this.getHeight() - y - currentPadding; yTemp[0] = this.getHeight() - xAxisYLocation - currentPadding; yTemp[1] = this.getHeight() - xAxisYLocation - currentPadding; yTemp[2] = y; yTemp[3] = y; } // draw the polygon g2d.fillPolygon(xTemp, yTemp, xTemp.length); // draw the thin line around the polygon g2d.setComposite(originalComposite); g2d.setColor(currentReferenceArea.getBorderColor()); g2d.setStroke(new BasicStroke(currentReferenceArea.getBorderWidth())); g2d.drawRect(xTemp[0], yTemp[0], xTemp[1] - xTemp[0], yTemp[2] - yTemp[0]); g2d.setStroke(originalStroke); g2d.setColor(originalColor); // draw the label if (currentReferenceArea.drawLabel() && !miniature) { // set the color and opacity level for the label g2d.setColor(currentReferenceArea.getLabelColor()); // set the font to use for the labels if (currentReferenceArea.useBoldFont()) { g.setFont(new Font(oldFont.getName(), oldFont.getStyle() | Font.BOLD, oldFont.getSize())); } else { g.setFont(new Font(oldFont.getName(), oldFont.getStyle(), oldFont.getSize())); } // insert the label String label = currentReferenceArea.getLabel(); int width = g.getFontMetrics().stringWidth(label); int xPosText = xTemp[0] + (Math.abs(xTemp[1] - xTemp[0]) / 2) - (width / 2); if (currentReferenceArea.isAboveXAxis()) { g2d.drawString(label, xPosText, y + (int) fm.getStringBounds(label, g).getHeight() + 8); } else { g2d.drawString(label, xPosText, y - (int) fm.getStringBounds(label, g).getHeight()); } } } } // Change the color and alpha level back to its original setting. g2d.setColor(originalColor); g2d.setComposite(originalComposite); g.setFont(oldFont); } /** * Draws the y-axis reference areas if any. * * @param g Graphics object to draw on. * @param drawOnTop if true the areas to be drawn on top of the data are * drawn, if false the areas that are to be drawn behind the data are drawn */ protected void drawYAxisReferenceAreas(Graphics g, boolean drawOnTop) { // used to find the location of the label FontMetrics fm = g.getFontMetrics(); Font oldFont = this.getFont(); // switch to 2D graphics Graphics2D g2d = (Graphics2D) g; Stroke originalStroke = g2d.getStroke(); // store the original color Color originalColor = g2d.getColor(); Composite originalComposite = g2d.getComposite(); Iterator<String> allReferenceAreas = referenceAreasYAxis.keySet().iterator(); while (allReferenceAreas.hasNext()) { ReferenceArea currentReferenceArea = referenceAreasYAxis.get(allReferenceAreas.next()); if (drawOnTop == currentReferenceArea.drawOnTop()) { // set the color and opacity level g.setColor(currentReferenceArea.getAreaColor()); g2d.setComposite(makeComposite(currentReferenceArea.getAlpha())); // set up the data tables for the polygon int[] xTemp = new int[4]; int[] yTemp = new int[4]; // y range start double tempDouble = (currentReferenceArea.getStart() - iYAxisMin) / iYScaleUnit; int start = (int) tempDouble; if ((tempDouble - start) >= 0.5) { start++; } // y range end tempDouble = (currentReferenceArea.getEnd() - iYAxisMin) / iYScaleUnit; int end = (int) tempDouble; if ((tempDouble - end) >= 0.5) { end++; } int areaLength = this.getWidth() - currentPadding; int xMax = (int) (areaLength * currentReferenceArea.getPercentLength()); xTemp[0] = currentPadding; yTemp[0] = this.getHeight() - start - currentPadding; xTemp[1] = currentPadding; yTemp[1] = this.getHeight() - end - currentPadding; xTemp[2] = xMax; yTemp[2] = this.getHeight() - end - currentPadding; xTemp[3] = xMax; yTemp[3] = this.getHeight() - start - currentPadding; g2d.fillPolygon(xTemp, yTemp, xTemp.length); // draw the thin line around the polygon g2d.setComposite(originalComposite); g2d.setColor(currentReferenceArea.getBorderColor()); g2d.setStroke(new BasicStroke(currentReferenceArea.getBorderWidth())); g2d.drawRect(xTemp[0], yTemp[2], xTemp[2] - xTemp[0], yTemp[0] - yTemp[2]); g2d.setStroke(originalStroke); g2d.setColor(originalColor); // draw the label if (currentReferenceArea.drawLabel() && !miniature) { // set the color and opacity level for the label g2d.setColor(currentReferenceArea.getLabelColor()); // set the font to use for the labels if (currentReferenceArea.useBoldFont()) { g.setFont(new Font(oldFont.getName(), oldFont.getStyle() | Font.BOLD, oldFont.getSize())); } else { g.setFont(new Font(oldFont.getName(), oldFont.getStyle(), oldFont.getSize())); } // insert the label String label = currentReferenceArea.getLabel(); g2d.drawString(label, currentPadding + 5, this.getHeight() - end - currentPadding + (int) fm.getStringBounds(label, g).getHeight()); } } } // Change the color and alpha level back to its original setting. g2d.setColor(originalColor); g2d.setComposite(originalComposite); } /** * This method reports on the largest x-axis value in the point collection * across all datasets. * * @return double with the largest x-axis value in the point collection. */ public double getMaxXAxisValue() { double maxValue = Double.MIN_VALUE; for (double[] iXAxisData1 : iXAxisData) { if (iXAxisData1.length > 0 && iXAxisData1[iXAxisData1.length - 1] > maxValue) { maxValue = iXAxisData1[iXAxisData1.length - 1]; } } if (dataSetCounterMirroredSpectra > 0) { for (double[] iXAxisDataCurrentMirroredSpectrum : iXAxisDataMirroredSpectrum) { for (int i = 0; i < iXAxisDataCurrentMirroredSpectrum.length; i++) { double lMass = iXAxisDataCurrentMirroredSpectrum[i]; if (lMass > maxValue) { maxValue = lMass; } } } } return maxValue; } /** * This method reports on the smallest x-axis value in the point collection * across all datasets. * * @return double with the smallest x-axis value in the point collection. */ public double getMinXAxisValue() { double minValue = Double.MAX_VALUE; for (double[] iXAxisData1 : iXAxisData) { if (iXAxisData1[0] < minValue) { minValue = iXAxisData1[0]; } } if (dataSetCounterMirroredSpectra > 0) { for (double[] iXAxisDataCurrentMirroredSpectrum : iXAxisDataMirroredSpectrum) { for (int i = 0; i < iXAxisDataCurrentMirroredSpectrum.length; i++) { double lMass = iXAxisDataCurrentMirroredSpectrum[i]; if (lMass < minValue) { minValue = lMass; } } } } return minValue; } /** * This method registers the specified SpectrumPanelListener with this * instance and notifies it of all future events. The Listeners will be * notified in order of addition (first addition is notified first). * * @param aListener SpectrumPanelListener to register on this instance. */ public void addSpectrumPanelListener(SpectrumPanelListener aListener) { this.iSpecPanelListeners.add(aListener); } /** * This method adds the event listeners to the panel. */ protected void addListeners() { this.addMouseListener(new MouseAdapter() { /** * Invoked when a mouse button has been released on a component. */ public void mouseReleased(MouseEvent e) { if (iXAxisData != null) { if (e.getButton() == MouseEvent.BUTTON3 || e.getButton() == MouseEvent.BUTTON2) { if (iXAxisStartAtZero) { rescale(0.0, getMaxXAxisValue()); } else { double tempMinXValue = getMinXAxisValue(); // if isotopic distribution add a little padding on the left side // to make sure that the first peak is not too close to the y-axis if (currentGraphicsPanelType.equals(GraphicsPanelType.isotopicDistributionProfile) || currentGraphicsPanelType.equals(GraphicsPanelType.isotopicDistributionCentroid)) { tempMinXValue -= 1; if (tempMinXValue < 0) { tempMinXValue = 0; } } rescale(tempMinXValue, getMaxXAxisValue()); } iDragged = false; repaint(); } else if (e.getButton() == MouseEvent.BUTTON1) { iEndXLoc = e.getX(); int min = Math.min(iEndXLoc, iStartXLoc); int max = Math.max(iEndXLoc, iStartXLoc); double start = iXAxisMin + ((min - iXPadding) * iXScaleUnit); double end = iXAxisMin + ((max - iXPadding) * iXScaleUnit); if (iDragged) { iDragged = false; // Rescale. if ((max - min) > iMinDrag) { rescale(start, end); } iDragXLoc = 0; repaint(); } } } } /** * Invoked when the mouse has been clicked on a component. */ public void mouseClicked(MouseEvent e) { // see if we're above or below the x-axis int xAxisYLocation = (getHeight() - currentPadding) / 2; boolean aboveXAxis = e.getY() < xAxisYLocation; if (dataSetCounterMirroredSpectra == 0) { aboveXAxis = true; } if (aboveXAxis) { // @TODO: merge the above/below code if (iXAxisData != null) { if (e.getButton() == MouseEvent.BUTTON1 && e.getModifiersEx() == (MouseEvent.CTRL_DOWN_MASK | MouseEvent.ALT_DOWN_MASK)) { iStoredSequence = new Vector(15, 5); iStoredSequenceDatasetIndices = new Vector(15, 5); repaint(); } else if (e.getButton() == MouseEvent.BUTTON1 && e.getModifiersEx() == MouseEvent.CTRL_DOWN_MASK) { iClicked = false; iClickedList = new Vector(15, 5); iClickedListDatasetIndices = new Vector(15, 5); repaint(); } else if (e.getButton() == MouseEvent.BUTTON1 && e.getModifiersEx() == MouseEvent.SHIFT_DOWN_MASK) { // If the clicked point is the last one in the list of previously clicked points, // remove it from the list! if (iClickedList != null && iClickedList.size() > 0 && iHighLightIndex == iClickedIndex) { // Retrieve the previously clicked index from the list and set the currently clicked // one to that value. iClickedIndex = ((Integer) iClickedList.get(iClickedList.size() - 1)).intValue(); iClickedDataSetIndex = ((Integer) iClickedListDatasetIndices.get(iClickedListDatasetIndices.size() - 1)).intValue(); // Remove the previously clicked index from the list. iClickedList.remove(iClickedList.size() - 1); iClickedListDatasetIndices.remove(iClickedListDatasetIndices.size() - 1); // Repaint. repaint(); } } else if (e.getButton() == MouseEvent.BUTTON1 && e.getModifiersEx() == MouseEvent.ALT_DOWN_MASK) { // See if there is a clicked list and if it contains any values. if (iClickedList != null && iClickedList.size() > 0) { // Copy the current clickedlist into the stored sequence. iStoredSequence = (Vector) iClickedList.clone(); iStoredSequence.add(Integer.valueOf(iClickedIndex)); iStoredSequenceDatasetIndices = (Vector) iClickedListDatasetIndices.clone(); iStoredSequenceDatasetIndices.add(Integer.valueOf(iClickedDataSetIndex)); iClicked = false; // Reset the clicked list. iClickedList = new Vector(15, 5); iClickedListDatasetIndices = new Vector(15, 5); repaint(); } } else if (e.getButton() == MouseEvent.BUTTON1) { if (iClicked && iClickedIndex != iHighLightIndex) { // We need the current point to be stored in the previously clicked // Vector and set the current one as clicked. iClickedList.add(Integer.valueOf(iClickedIndex)); iClickedListDatasetIndices.add(Integer.valueOf(iClickedDataSetIndex)); } iClicked = true; iClickedIndex = iHighLightIndex; iClickedDataSetIndex = iHighLightDatasetIndex; repaint(); } } } else { if (iXAxisDataMirroredSpectrum != null) { if (e.getButton() == MouseEvent.BUTTON1 && e.getModifiersEx() == (MouseEvent.CTRL_DOWN_MASK | MouseEvent.ALT_DOWN_MASK)) { iStoredSequenceMirrored = new Vector(15, 5); iStoredSequenceDatasetIndicesMirrored = new Vector(15, 5); repaint(); } else if (e.getButton() == MouseEvent.BUTTON1 && e.getModifiersEx() == MouseEvent.CTRL_DOWN_MASK) { iClickedMirrored = false; iClickedListMirrored = new Vector(15, 5); iClickedListDatasetIndicesMirrored = new Vector(15, 5); repaint(); } else if (e.getButton() == MouseEvent.BUTTON1 && e.getModifiersEx() == MouseEvent.SHIFT_DOWN_MASK) { // If the clicked point is the last one in the list of previously clicked points, // remove it from the list! if (iClickedListMirrored != null && iClickedListMirrored.size() > 0 && iHighLightIndexMirrored == iClickedIndexMirrored) { // Retrieve the previously clicked index from the list and set the currently clicked // one to that value. iClickedIndexMirrored = ((Integer) iClickedListMirrored.get(iClickedListMirrored.size() - 1)).intValue(); iClickedDataSetIndexMirrored = ((Integer) iClickedListDatasetIndicesMirrored.get(iClickedListDatasetIndicesMirrored.size() - 1)).intValue(); // Remove the previously clicked index from the list. iClickedListMirrored.remove(iClickedListMirrored.size() - 1); iClickedListDatasetIndicesMirrored.remove(iClickedListDatasetIndicesMirrored.size() - 1); // Repaint. repaint(); } } else if (e.getButton() == MouseEvent.BUTTON1 && e.getModifiersEx() == MouseEvent.ALT_DOWN_MASK) { // See if there is a clicked list and if it contains any values. if (iClickedListMirrored != null && iClickedListMirrored.size() > 0) { // Copy the current clickedlist into the stored sequence. iStoredSequenceMirrored = (Vector) iClickedListMirrored.clone(); iStoredSequenceMirrored.add(Integer.valueOf(iClickedIndexMirrored)); iStoredSequenceDatasetIndicesMirrored = (Vector) iClickedListDatasetIndicesMirrored.clone(); iStoredSequenceDatasetIndicesMirrored.add(Integer.valueOf(iClickedDataSetIndexMirrored)); iClicked = false; // Reset the clicked list. iClickedListMirrored = new Vector(15, 5); iClickedListDatasetIndicesMirrored = new Vector(15, 5); repaint(); } } else if (e.getButton() == MouseEvent.BUTTON1) { if (iClickedMirrored && iClickedIndexMirrored != iHighLightIndexMirrored) { // We need the current point to be stored in the previously clicked // Vector and set the current one as clicked. iClickedListMirrored.add(Integer.valueOf(iClickedIndexMirrored)); iClickedListDatasetIndicesMirrored.add(Integer.valueOf(iClickedDataSetIndexMirrored)); } iClickedMirrored = true; iClickedIndexMirrored = iHighLightIndexMirrored; iClickedDataSetIndexMirrored = iHighLightDatasetIndexMirrored; repaint(); } } } } /** * Invoked when a mouse button has been pressed on a component. */ public void mousePressed(MouseEvent e) { if (e.getButton() == MouseEvent.BUTTON1) { iStartXLoc = e.getX(); iStartYLoc = e.getY(); } } }); this.addMouseMotionListener(new MouseMotionAdapter() { /** * Invoked when a mouse button is pressed on a component and then * dragged. Mouse drag events will continue to be delivered to the * component where they first originated until the mouse button is * released (regardless of whether the mouse position is within the * bounds of the component). */ public void mouseDragged(MouseEvent e) { if (SwingUtilities.isLeftMouseButton(e)) { iDragged = true; iDragXLoc = e.getX(); repaint(); } } /** * Invoked when the mouse button has been moved on a component (with * no buttons no down). */ public void mouseMoved(MouseEvent e) { // see if we're above or below the x-axis int y = e.getY(); int xAxisYLocation = (getHeight() - currentPadding) / 2; boolean aboveXAxis = y < xAxisYLocation; if (dataSetCounterMirroredSpectra == 0) { aboveXAxis = true; } ArrayList<double[]> xAxisData; ArrayList<int[]> xAxisDataInPixels; ArrayList<int[]> yAxisDataInPixels; if (aboveXAxis) { xAxisData = iXAxisData; xAxisDataInPixels = iXAxisDataInPixels; yAxisDataInPixels = iYAxisDataInPixels; } else { xAxisData = iXAxisDataMirroredSpectrum; xAxisDataInPixels = iXAxisDataInPixelsMirroredSpectrum; yAxisDataInPixels = iYAxisDataInPixelsMirroredSpectrum; } if (xAxisData != null && xAxisDataInPixels != null) { int x = e.getX(); // this variable is used to make sure that the most intense peak within range is highlighted int highestPeakInRange = 0; for (int j = 0; j < xAxisDataInPixels.size(); j++) { for (int i = 0; i < xAxisDataInPixels.get(j).length; i++) { int delta = xAxisDataInPixels.get(j)[i] - x; if (Math.abs(delta) < iPointDetectionTolerance) { if (aboveXAxis) { int deltaYPixels = y - yAxisDataInPixels.get(j)[i]; if (deltaYPixels < 0 && Math.abs(deltaYPixels) < (getHeight() - yAxisDataInPixels.get(j)[i]) && highestPeakInRange < (getHeight() - yAxisDataInPixels.get(j)[i])) { iHighLight = true; iHighLightIndex = i; iHighLightDatasetIndex = j; highestPeakInRange = (getHeight() - yAxisDataInPixels.get(j)[i]); repaint(); } } else { int deltaYPixels = yAxisDataInPixels.get(j)[i] - y; if (deltaYPixels < 0 && Math.abs(deltaYPixels) < yAxisDataInPixels.get(j)[i] && highestPeakInRange < yAxisDataInPixels.get(j)[i]) { iHighLightMirrored = true; iHighLightIndexMirrored = i; iHighLightDatasetIndexMirrored = j; highestPeakInRange = yAxisDataInPixels.get(j)[i]; repaint(); } } } else if (delta >= iPointDetectionTolerance) { break; } } } repaint(); } } }); } /** * This method rescales the x-axis while notifying the observers. * * @param aMinXAxisValue double with the new minimum x-axis value to * display. * @param aMaxXAxisValue double with the new maximum x-axis value to * display. */ public void rescale(double aMinXAxisValue, double aMaxXAxisValue) { this.rescale(aMinXAxisValue, aMaxXAxisValue, true); } /** * Add a x-axis reference area. * * @param referenceArea the reference area to add */ public void addReferenceAreaXAxis(ReferenceArea referenceArea) { referenceAreasXAxis.put(referenceArea.getIdentifier(), referenceArea); } /** * Removes the x-axis reference area with the given identifier. Does nothing * if no reference with the given identifier is found. * * @param identifier the identifier of the reference to remove */ public void removeReferenceAreaXAxis(String identifier) { referenceAreasXAxis.remove(identifier); } /** * Removes all the x-axis reference areas. */ public void removeAllReferenceAreasXAxis() { referenceAreasXAxis = new HashMap<String, ReferenceArea>(); } /** * Returns all the x-axis references areas as a hashmap, with the labels as * the keys. * * @return hashmap of all reference areas */ public HashMap<String, ReferenceArea> getAllReferenceAreasXAxis() { return referenceAreasXAxis; } /** * Add a y-axis reference area. * * @param referenceArea the reference area to add */ public void addReferenceAreaYAxis(ReferenceArea referenceArea) { referenceAreasYAxis.put(referenceArea.getIdentifier(), referenceArea); } /** * Removes the y-axis reference area with the given identifier. Does nothing * if no reference with the given identifier is found. * * @param identifier the identifier of the reference to remove */ public void removeReferenceAreaYAxis(String identifier) { referenceAreasYAxis.remove(identifier); } /** * Removes all the y-axis reference areas. */ public void removeAllReferenceAreasYAxis() { referenceAreasYAxis = new HashMap<String, ReferenceArea>(); } /** * Returns all the y-axis references areas as a hashmap, with the labels as * the keys. * * @return hashmap of all reference areas */ public HashMap<String, ReferenceArea> getAllReferenceAreasYAxis() { return referenceAreasYAxis; } /** * Sets the color of data points and line for the dataset with the given * dataset index. Index starts at 0. * * @param aColor the color to use * @param index the index of the dataset */ public void setDataPointAndLineColor(Color aColor, int index) { if (index < iDataPointAndLineColor.size() && index >= 0) { iDataPointAndLineColor.set(index, aColor); } } /** * Sets the color of the area under the curve for profile chromatograms and * spectra for the dataset with the given dataset index. Index starts at 0. * * @param aColor the color to use * @param index the index of the dataset */ public void setAreaUnderCurveColor(Color aColor, int index) { if (index < iAreaUnderCurveColor.size() && index >= 0) { iAreaUnderCurveColor.set(index, aColor); } } /** * Returns the list of colors used for the datasets. Index on dataset. Index * starts at 0. * * @return the the list of colors used for the datasets */ public ArrayList<Color> getAreaUnderCurveColors() { return iAreaUnderCurveColor; } /** * This method rescales the x-axis, allowing the caller to specify whether * the observers need be notified. * * @param aMinXAxisValue double with the new minimum x-axis value to * display. * @param aMaxXAxisValue double with the new maximum x-axis value to * display. * @param aNotifyListeners boolean to indicate whether the observers should * be notified. */ public void rescale(double aMinXAxisValue, double aMaxXAxisValue, boolean aNotifyListeners) { xAxisZoomRangeLowerValue = aMinXAxisValue; xAxisZoomRangeUpperValue = aMaxXAxisValue; // Calculate the new max x-axis value. double maxInt = 1.0; for (int j = 0; j < iXAxisData.size(); j++) { for (int i = 0; i < iXAxisData.get(j).length; i++) { double lMass = iXAxisData.get(j)[i]; if (lMass < aMinXAxisValue) { continue; } else if (lMass > aMaxXAxisValue) { break; } else { if (iYAxisData.get(j)[i] > maxInt) { boolean annotatedPeak = false; // exclude background peaks? if (yAxisZoomExcludesBackgroundPeaks) { annotatedPeak = isPeakAnnotated(iXAxisData.get(j)[i], false); } if (!yAxisZoomExcludesBackgroundPeaks || (yAxisZoomExcludesBackgroundPeaks && annotatedPeak) || showAllPeaks) { maxInt = iYAxisData.get(j)[i]; } } } } } if (dataSetCounterMirroredSpectra > 0) { for (int j = 0; j < iXAxisDataMirroredSpectrum.size(); j++) { for (int i = 0; i < iXAxisDataMirroredSpectrum.get(j).length; i++) { double lMass = iXAxisDataMirroredSpectrum.get(j)[i]; if (lMass < aMinXAxisValue) { continue; } else if (lMass > aMaxXAxisValue) { break; } else { if (iYAxisDataMirroredSpectrum.get(j)[i] > maxInt) { boolean annotatedPeak = false; // exclude background peaks? if (yAxisZoomExcludesBackgroundPeaks) { annotatedPeak = isPeakAnnotated(iXAxisDataMirroredSpectrum.get(j)[i], true); } if (!yAxisZoomExcludesBackgroundPeaks || (yAxisZoomExcludesBackgroundPeaks && annotatedPeak) || showAllPeaks) { maxInt = iYAxisDataMirroredSpectrum.get(j)[i]; } } } } } } // Init the new params. double delta = aMaxXAxisValue - aMinXAxisValue; // Round to nearest order of 10, based on displayed delta. double tempOoM = (Math.log(delta) / Math.log(10)) - 1; if (tempOoM < 0) { tempOoM--; } int orderOfMagnitude = (int) tempOoM; double power = Math.pow(10, orderOfMagnitude); iXAxisMin = aMinXAxisValue - (aMinXAxisValue % power); iXAxisMax = aMaxXAxisValue + (power - (aMaxXAxisValue % power)); // @TODO: just some helpful printouts for when this is refined further. //logger.info(" - Delta: " + delta + "\tAdj. delta: " + (iMassMax-iMassMin) + "\tMinMass: " + iMassMin + "\tMaxMass: " + iMassMax + "\tScale: " + power); iYAxisMax = maxInt + (maxInt / 10); int liSize = iSpecPanelListeners.size(); RescalingEvent re = new RescalingEvent(this, aMinXAxisValue, aMaxXAxisValue); if (aNotifyListeners) { for (int i = 0; i < liSize; i++) { ((SpectrumPanelListener) iSpecPanelListeners.get(i)).rescaled(re); } } } /** * This method reads the x and y values from the specified arrays and stores * these internally for drawing. The x-axis values are sorted in this step. * * @param aXAxisData double[] with the x-axis values. * @param aYAxisData double[] with the corresponding y-axis values. * @param dataPointAndLineColor the color to use for the data points and * line * @param areaUnderCurveColor the color to use for the area under the curve */ protected void processXAndYData(double[] aXAxisData, double[] aYAxisData, Color dataPointAndLineColor, Color areaUnderCurveColor) { // if first dataset, create the dataset array lists if (dataSetCounter == 0) { iXAxisData = new ArrayList<double[]>(); iYAxisData = new ArrayList<double[]>(); } // set the data colors iDataPointAndLineColor.add(dataPointAndLineColor); iAreaUnderCurveColor.add(areaUnderCurveColor); HashMap peaks = new HashMap(aXAxisData.length); // add the peaks to the dataset for (int i = 0; i < aXAxisData.length; i++) { peaks.put(new Double(aXAxisData[i]), new Double(aYAxisData[i])); } // add the new dataset iXAxisData.add(new double[peaks.size()]); iYAxisData.add(new double[peaks.size()]); // Maximum y-axis value. double maxYAxisValue = 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 xValue = key.doubleValue(); double yValue = ((Double) peaks.get(key)).doubleValue(); if (yValue > maxYAxisValue) { maxYAxisValue = yValue; } iXAxisData.get(dataSetCounter)[count] = xValue; iYAxisData.get(dataSetCounter)[count] = yValue; count++; } // rescale the added dataset if (iXAxisStartAtZero) { this.rescale(0.0, getMaxXAxisValue()); } else { this.rescale(getMinXAxisValue(), getMaxXAxisValue()); } dataSetCounter++; } /** * This method reads the x and y values from the specified arrays and stores * these internally for drawing. The x-axis values are sorted in this step. * * @param aXAxisData double[] with the x-axis values. * @param aYAxisData double[] with the corresponding y-axis values. * @param dataPointAndLineColor the color to use for the data points and * line * @param areaUnderCurveColor the color to use for the area under the curve */ protected void processMirroredXAndYData(double[] aXAxisData, double[] aYAxisData, Color dataPointAndLineColor, Color areaUnderCurveColor) { // create the dataset array lists // if first mirrored dataset, create the dataset array lists if (dataSetCounterMirroredSpectra == 0) { iXAxisDataMirroredSpectrum = new ArrayList<double[]>(); iYAxisDataMirroredSpectrum = new ArrayList<double[]>(); } // set the data colors iDataPointAndLineColorMirroredSpectra.add(dataPointAndLineColor); iAreaUnderCurveColorMirroredSpectra.add(areaUnderCurveColor); HashMap peaks = new HashMap(aXAxisData.length); // add the peaks to the dataset for (int i = 0; i < aXAxisData.length; i++) { peaks.put(new Double(aXAxisData[i]), new Double(aYAxisData[i])); } // add the new dataset iXAxisDataMirroredSpectrum.add(new double[peaks.size()]); iYAxisDataMirroredSpectrum.add(new double[peaks.size()]); // Maximum y-axis value. double maxYAxisValue = 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 xValue = key.doubleValue(); double yValue = ((Double) peaks.get(key)).doubleValue(); if (yValue > maxYAxisValue) { maxYAxisValue = yValue; } iXAxisDataMirroredSpectrum.get(dataSetCounterMirroredSpectra)[count] = xValue; iYAxisDataMirroredSpectrum.get(dataSetCounterMirroredSpectra)[count] = yValue; count++; } dataSetCounterMirroredSpectra++; // rescale the added dataset if (iXAxisStartAtZero) { this.rescale(0.0, getMaxXAxisValue()); } else { this.rescale(getMinXAxisValue(), getMaxXAxisValue()); } } /** * This method draws the axes and their labels on the specified Graphics * object, taking into account the padding. * * @param g Graphics object to draw on. * @param aXMin double with the minimal x value. * @param aXMax double with the maximum x value. * @param aXScale int with the scale to display for the X-axis labels (as * used in BigDecimal's setScale). * @param aYMin double with the minimal y value. * @param aYMax double with the maximum y value. * @return int[] with the length of the X axis and Y axis respectively. */ protected int[] drawAxes(Graphics g, double aXMin, double aXMax, int aXScale, double aYMin, double aYMax) { // Recalibrate padding so that it holds the axis labels. FontMetrics fm = g.getFontMetrics(); int xAxisLabelWidth = fm.stringWidth(iXAxisLabel); int yAxisLabelWidth = fm.stringWidth(iYAxisLabel); int minWidth = fm.stringWidth(Double.toString(aYMin)); int maxWidth = fm.stringWidth(Double.toString(aYMax)); int max = Math.max(Math.max(xAxisLabelWidth, yAxisLabelWidth), Math.max(minWidth, maxWidth)); currentPadding = padding; if ((padding - max) < 0) { currentPadding += max; if (currentPadding > maxPadding) { currentPadding = maxPadding; } } else { currentPadding *= 2; } // X-axis. int xAxis = (this.getWidth() - (2 * currentPadding)); int xAxisYLocation = this.getHeight() - currentPadding; if (dataSetCounterMirroredSpectra > 0) { xAxisYLocation = (this.getHeight() - currentPadding) / 2; } // hide any data going slightly below the y-axis if (yDataIsPositive) { Color currentColor = g.getColor(); g.setColor(this.getBackground()); if (miniature) { g.fillRect(currentPadding, xAxisYLocation, this.getWidth() - currentPadding - 2, 2); } else { g.fillRect(currentPadding, xAxisYLocation, this.getWidth() - currentPadding - 2, 20); } g.setColor(currentColor); } g.drawLine(currentPadding, xAxisYLocation, this.getWidth() - currentPadding, xAxisYLocation); if (!miniature) { // Arrowhead on X-axis. g.fillPolygon(new int[]{this.getWidth() - currentPadding - 3, this.getWidth() - currentPadding - 3, this.getWidth() - currentPadding + 2}, new int[]{xAxisYLocation + 5, xAxisYLocation - 5, xAxisYLocation}, 3); // X-axis label if (iXAxisLabel.equalsIgnoreCase("m/z")) { g.drawString(iXAxisLabel, this.getWidth() - (currentPadding - (padding / 2)), xAxisYLocation + 4); } else { g.drawString(iXAxisLabel, this.getWidth() - (xAxisLabelWidth + 5), xAxisYLocation - (currentPadding / 2)); } // Y-axis. g.drawLine(currentPadding, this.getHeight() - currentPadding, currentPadding, currentPadding / 2); } iXPadding = currentPadding; int yAxis = xAxisYLocation - (currentPadding / 2); if (!miniature) { // Arrowhead on Y axis. g.fillPolygon(new int[]{currentPadding - 5, currentPadding + 5, currentPadding}, new int[]{(currentPadding / 2) + 3, (currentPadding / 2) + 3, currentPadding / 2 - 2}, 3); // Y-axis label if (iYAxisLabel.equalsIgnoreCase("Int")) { g.drawString(iYAxisLabel, currentPadding - yAxisLabelWidth, (currentPadding / 2) - 4); } else { g.drawString(iYAxisLabel, currentPadding - (yAxisLabelWidth / 5), (currentPadding / 2) - 4); } } // Now the tags along the axes. this.drawXTags(g, (int) Math.floor(aXMin), (int) Math.ceil(aXMax), aXScale, xAxis, currentPadding); // @TODO: increase the zooming resolution!!! int yTemp = yAxis; if (iAnnotations != null && iAnnotations.size() > 0 && !miniature) { yTemp -= 20; } iTopPadding = this.getHeight() - yTemp - 5; this.drawYTags(g, (int) Math.floor(aYMin), (int) Math.ceil(aYMax), yTemp, currentPadding); return new int[]{xAxis, yAxis}; } /** * This method draws tags on the X axis. * * @param aMin double with the minimum value for the axis. * @param aMax double with the maximum value for the axis. * @param aXScale int with the scale to display for the X-axis labels (as * used in BigDecimal's setScale). * @param g Graphics object to draw on. * @param aXAxisWidth int with the axis width in pixels. * @param aPadding int with the amount of padding to take into account. */ protected void drawXTags(Graphics g, int aMin, int aMax, int aXScale, int aXAxisWidth, int aPadding) { // @TODO: increase the zooming resolution!!! // Font Metrics. We'll be needing these. FontMetrics fm = g.getFontMetrics(); //this.setFont(new Font(this.getFont().getName(), this.getFont().getStyle(), 18)); // find the scale unit double delta = aMax - aMin; iXScaleUnit = delta / aXAxisWidth; // note: do not alter! also used when drawing the peaks // The next section will only be drawn for spectra. if (currentGraphicsPanelType.equals(GraphicsPanelType.centroidSpectrum) || currentGraphicsPanelType.equals(GraphicsPanelType.profileSpectrum)) { if (!miniature) { // Since we know the scale unit, we also know the resolution. // This will be displayed on the bottom line. String resolution = ""; if (showResolution) { resolution = "Resolution: " + new BigDecimal(iXScaleUnit).setScale(2, BigDecimal.ROUND_HALF_UP).toString(); } // Also print the MS level and precursor MZ and charge (if known, '?' otherwise). String msLevel_and_optional_precursor = ""; if (showPrecursorDetails) { msLevel_and_optional_precursor = "MS level: " + iMSLevel; if (iMSLevel > 0) { // Also print the precursor MZ and charge (if known, '?' otherwise). msLevel_and_optional_precursor += " Precursor M/Z: " + this.iPrecursorMZ + " (" + this.iPrecursorCharge + ")"; } else { msLevel_and_optional_precursor = "Precursor M/Z: " + this.iPrecursorMZ + " (" + this.iPrecursorCharge + ")"; } } // Finally, we also want the filename. String filename = ""; if (showFileName) { filename = "Filename: " + iFilename; } int precLength = fm.stringWidth(msLevel_and_optional_precursor); int resLength = fm.stringWidth(resolution); int xDistance = ((this.getWidth() - (iXPadding * 2)) / 4) - (precLength / 2); int fromBottom = fm.getAscent() / 2; Font oldFont = this.getFont(); int smallFontCorrection = 0; int yHeight = this.getHeight() - fromBottom; int xAdditionForResolution = precLength + 15; int xAdditionForFilename = xAdditionForResolution + resLength + 15; if (precLength + resLength + 45 + fm.stringWidth(filename) > aXAxisWidth) { g.setFont(new Font(oldFont.getName(), oldFont.getStyle(), oldFont.getSize() - 2)); smallFontCorrection = g.getFontMetrics().getAscent(); xAdditionForFilename = g.getFontMetrics().stringWidth(msLevel_and_optional_precursor) + 5; xAdditionForResolution = g.getFontMetrics().stringWidth(msLevel_and_optional_precursor) / 2; xDistance = aPadding; } g.drawString(msLevel_and_optional_precursor, xDistance, yHeight - smallFontCorrection); g.drawString(resolution, xDistance + xAdditionForResolution, yHeight); Color foreground = null; if (iFilenameColor != null) { foreground = g.getColor(); g.setColor(iFilenameColor); } g.drawString(filename, xDistance + xAdditionForFilename, yHeight - smallFontCorrection); if (foreground != null) { g.setColor(foreground); } // Restore original font. g.setFont(oldFont); } } if (!miniature) { int labelHeight = fm.getAscent() + 5; // Find out how many tags we have room for int tagWidthEstimate = fm.stringWidth("1545") + 15; int numberTimes = (aXAxisWidth / tagWidthEstimate); // find the scale unit for the x tags double scaleUnitXTags = aXAxisWidth / delta; // try to find the optimal distance to use between the tags int distanceBetweenTags = findOptimalTagDistance(numberTimes, delta); // set up the number formatting DecimalFormat numberFormat = new DecimalFormat(); numberFormat.setGroupingSize(3); numberFormat.setGroupingUsed(true); if (scientificXAxis) { numberFormat = new DecimalFormat(scientificPattern); } int xAxisYLocation = this.getHeight(); if (dataSetCounterMirroredSpectra > 0) { xAxisYLocation = (this.getHeight() + currentPadding) / 2; } // add the tags and values if (delta > 1) { long lTagValue = 0; // now we can mark the first unit for (int i = 0; i < aMax; i++) { if ((aMin + i) % distanceBetweenTags == 0) { int xLoc = (int) (aPadding + (i * scaleUnitXTags)); if (xLoc < (aPadding + aXAxisWidth)) { g.drawLine(xLoc, xAxisYLocation - aPadding, xLoc, xAxisYLocation - aPadding + 3); int labelAsInt = aMin + i; String label = numberFormat.format(labelAsInt); int labelWidth = fm.stringWidth(label); boolean drawValue = true; if (labelAsInt == 0 && dataSetCounterMirroredSpectra > 0) { drawValue = false; } if (drawValue) { g.drawString(label, xLoc - (labelWidth / 2), xAxisYLocation - aPadding + labelHeight); } lTagValue = i; break; } } } // now we can mark the rest of the units while (lTagValue < aMax && distanceBetweenTags > 0) { int xLoc = (int) (aPadding + (lTagValue * scaleUnitXTags)); if (xLoc < (aPadding + aXAxisWidth)) { g.drawLine(xLoc, xAxisYLocation - aPadding, xLoc, xAxisYLocation - aPadding + 3); long labelAsInt = aMin + lTagValue; String label = numberFormat.format(labelAsInt); int labelWidth = fm.stringWidth(label); boolean drawValue = true; if (labelAsInt == 0 && dataSetCounterMirroredSpectra > 0) { drawValue = false; } if (drawValue) { g.drawString(label, xLoc - (labelWidth / 2), xAxisYLocation - aPadding + labelHeight); } } lTagValue += distanceBetweenTags; } } else { // special case for when zoom size is 1Da. we then need to use float values, e.g., 155.1, 155.2 etc. // first find how many tags we have room for: 10, 5, 2 or 1 int numberOfTags = 10; // find the scale unit for the x tags if (numberTimes >= numberOfTags) { scaleUnitXTags = aXAxisWidth / numberOfTags; } else { numberOfTags = 5; if (numberTimes >= numberOfTags) { scaleUnitXTags = aXAxisWidth / numberOfTags; } else { numberOfTags = 2; if (numberTimes >= numberOfTags) { numberOfTags = 1; } } } // add the tags for (int i = 0; i < numberOfTags; i++) { int xLoc = (int) (aPadding + (i * scaleUnitXTags)); if (xLoc < (aPadding + aXAxisWidth)) { g.drawLine(xLoc, this.getHeight() - aPadding, xLoc, this.getHeight() - aPadding + 3); double labelAsdouble = aMin + ((1 / (double) numberOfTags) * i); String label = numberFormat.format(labelAsdouble); int labelWidth = fm.stringWidth(label); g.drawString(label, xLoc - (labelWidth / 2), this.getHeight() - aPadding + labelHeight); } } } } } /** * This method draws tags on the Y axis. * * @param aMin double with the minimum value for the axis. * @param aMax double with the maximum value for the axis. * @param g Graphics object to draw on. * @param aYAxisHeight int with the axis height in pixels. * @param aPadding int with the amount of padding to take into account. */ protected void drawYTags(Graphics g, int aMin, int aMax, int aYAxisHeight, int aPadding) { // Font Metrics. We'll be needing these. FontMetrics fm = g.getFontMetrics(); int labelHeight = fm.getAscent(); // Find out how many tags we have room for int tagHeightEstimate = labelHeight + 10; int numberTimes = (aYAxisHeight / tagHeightEstimate); // find the scale unit double delta = aMax - aMin; iYScaleUnit = delta / aYAxisHeight; // note: do not alter! also used when drawing the peaks if (!miniature) { // find the scale unit for the x tags double scaleUnitYTags = aYAxisHeight / delta; // find the optimal distance to use between the tags int distanceBetweenTags = findOptimalTagDistance(numberTimes, delta); // set up the number formatting DecimalFormat numberFormat = new DecimalFormat(); numberFormat.setGroupingSize(3); numberFormat.setGroupingUsed(true); if (scientificYAxis) { numberFormat = new DecimalFormat(scientificPattern); } // max y-value to display String largestLabel = numberFormat.format((int) aMax); // old font storage Font oldFont = g.getFont(); // find the required scaling level for the y-axis tags int sizeCounter = 0; int margin = aPadding - 10; while (g.getFontMetrics().stringWidth(largestLabel) >= margin) { sizeCounter++; g.setFont(new Font(oldFont.getName(), oldFont.getStyle(), oldFont.getSize() - sizeCounter)); } // set the font to use for the y-axis tags if (oldFont.getSize() - sizeCounter > 0) { g.setFont(new Font(oldFont.getName(), oldFont.getStyle(), oldFont.getSize() - sizeCounter)); } else { // have to make sure that the font at least has a size of 1 g.setFont(new Font(oldFont.getName(), oldFont.getStyle(), 1)); } int height = this.getHeight(); if (dataSetCounterMirroredSpectra > 0) { height = this.getHeight() - (((this.getHeight() - currentPadding)) / 2); } long lTagValue = 0; while (lTagValue < aMax) { int yLoc = (int) (aPadding + (lTagValue * scaleUnitYTags)); g.drawLine(aPadding, height - yLoc, aPadding - 3, height - yLoc); long labelAsInt = aMin + lTagValue; String label = numberFormat.format(labelAsInt); int labelWidth = g.getFontMetrics().stringWidth(label) + 5; g.drawString(label, aPadding - labelWidth, height - yLoc + (g.getFontMetrics().getAscent() / 2) - 1); lTagValue = lTagValue + distanceBetweenTags; } if (dataSetCounterMirroredSpectra > 0) { height = (this.getHeight() - currentPadding * 3) / 2; lTagValue = 0; while (lTagValue < aMax) { int yLoc = (int) (aPadding + (lTagValue * scaleUnitYTags)); long labelAsInt = aMin + lTagValue; if (labelAsInt != 0) { g.drawLine(aPadding, height + yLoc, aPadding - 3, height + yLoc); String label = numberFormat.format(labelAsInt); int labelWidth = g.getFontMetrics().stringWidth(label) + 5; g.drawString(label, aPadding - labelWidth, height + yLoc + (g.getFontMetrics().getAscent() / 2) - 1); } lTagValue = lTagValue + distanceBetweenTags; } } // restore original font g.setFont(oldFont); } } /** * Try to find the optimal distance between the tags. The most detailed * option is always used, i.e., the option containing the most tags within * the current boundaries. Note always returns a round number, e.g., 1, 5, * 10, 25, 50, etc. * * @param maxNumberOfTags the maximum number of tags there is room for * @param delta the difference between the max and the min value * @return the optimal distance between the tags */ private int findOptimalTagDistance(int maxNumberOfTags, double delta) { // the two smallest tag options int[] distanceAlternatives = {1, 5}; int optimalDistance = 1; boolean optimalDistanceFound = false; // check if the two minimum tag options can be used for (int i = 0; i < distanceAlternatives.length && !optimalDistanceFound; i++) { if (delta / distanceAlternatives[i] <= maxNumberOfTags) { optimalDistance = distanceAlternatives[i]; optimalDistanceFound = true; } } int tempOptimalDistance = 10; // find the optimal tag distance while (!optimalDistanceFound) { if (delta / (tempOptimalDistance * 2.5) <= maxNumberOfTags) { optimalDistance = Double.valueOf(tempOptimalDistance * 2.5).intValue(); optimalDistanceFound = true; } if (delta / (tempOptimalDistance * 5) <= maxNumberOfTags && !optimalDistanceFound) { optimalDistance = Double.valueOf(tempOptimalDistance * 5).intValue(); optimalDistanceFound = true; } if (delta / (tempOptimalDistance * 10) <= maxNumberOfTags && !optimalDistanceFound) { optimalDistance = Double.valueOf(tempOptimalDistance * 10).intValue(); optimalDistanceFound = true; } tempOptimalDistance *= 10; } return optimalDistance; } /** * This method will draw a highlighting triangle + x-value on top of the * marked point. * * @param aIndex int with the index of the point to highlight * @param dataSetIndex the index of the dataset * @param g Graphics object to draw the highlighting on * @param mirrored if true the highlighted peak is from a mirrored spectrum */ protected void highLightPeak(int aIndex, int dataSetIndex, Graphics g, boolean mirrored) { if (!mirrored) { this.highLight(aIndex, dataSetIndex, g, Color.blue, null, 0, true, 1, mirrored); } else { this.highLight(aIndex, dataSetIndex, g, Color.blue, null, 20, true, 1, mirrored); } } /** * This method will draw a highlighting triangle + x-value on top of the * clicked marked point. * * @param aIndex int with the index of the clicked point to highlight * @param dataSetIndex the index of the dataset * @param g Graphics object to draw the highlighting on * @param mirrored if true the highlighted peak is from a mirrored spectrum */ protected void highlightClicked(int aIndex, int dataSetIndex, Graphics g, boolean mirrored) { this.highLight(aIndex, dataSetIndex, g, Color.BLACK, null, 0, true, 1, mirrored); } /** * This method will highlight the specified point in the specified color by * drawing a floating triangle + x-value above it. * * @param aIndex int with the index. * @param dataSetIndex the index of the dataset * @param g Graphics object to draw on * @param aColor Color to draw the highlighting in. * @param aComment String with an optional comment. Can be 'null' in which * case it will be omitted. * @param aPixelsSpacer int that gives the vertical spacer in pixels for the * highlighting. * @param aShowArrow boolean that indicates whether a downward-pointing * arrow and dotted line should be drawn over the point. * @param aAnnotationCounter the number of annotation of the given peak * @param mirrored if true the highlighting is done on the mirrored spectra */ protected void highLight(int aIndex, int dataSetIndex, Graphics g, Color aColor, String aComment, int aPixelsSpacer, boolean aShowArrow, int aAnnotationCounter, boolean mirrored) { int x; if (!mirrored) { x = iXAxisDataInPixels.get(dataSetIndex)[aIndex]; } else { x = iXAxisDataInPixelsMirroredSpectrum.get(dataSetIndex)[aIndex]; } int y; if (aPixelsSpacer < 0) { if (!mirrored) { y = iTopPadding; } else { y = this.getHeight() - iTopPadding; } } else { if (!mirrored) { y = iYAxisDataInPixels.get(dataSetIndex)[aIndex] - aPixelsSpacer; } else { y = iYAxisDataInPixelsMirroredSpectrum.get(dataSetIndex)[aIndex] + (aPixelsSpacer / 2); // @TODO: what is the correct aPixelsSpacer value? } // special case for when top peak is annotated with multiple annotations if (y < 0) { y = (iTopPadding / 3) - (aAnnotationCounter - 3) * (g.getFontMetrics().getAscent() + 4); // @TODO: what about mirrored spectra? } } // Correct for absurd heights. if (!mirrored) { if (dataSetCounterMirroredSpectra == 0) { if (y < iTopPadding / 3) { y = (iTopPadding / 3) - (aAnnotationCounter - 3) * (g.getFontMetrics().getAscent() + 4); } } else { y = iYAxisDataInPixels.get(dataSetIndex)[aIndex] - aPixelsSpacer; if (y < iTopPadding / 3) { y = maxPadding - aAnnotationCounter * (g.getFontMetrics().getAscent() + 4); // @TODO: check if this makes sense... } } } else { if (y > this.getHeight() - iXPadding) { y = this.getHeight() - iXPadding; // @TODO: check... and what about multiple annotations? } } // Temporarily change the color Color originalColor = g.getColor(); g.setColor(aColor); // Draw the triangle first, if appropriate. int arrowSpacer = 10; if (aShowArrow) { if (!mirrored) { g.fillPolygon(new int[]{x - 3, x + 3, x}, new int[]{y - 6, y - 6, y - 3}, 3); arrowSpacer = 13; } else { g.fillPolygon(new int[]{x - 4, x + 4, x}, new int[]{y - 3, y - 3, y - 8}, 3); arrowSpacer = -10; } } // Now the x-value. // If there is any, print the comment instead of the x-value. if (aComment != null && !aComment.trim().equals("")) { aComment = aComment.trim(); if (subscriptAnnotationNumbers) { int commentXStart = x - g.getFontMetrics().stringWidth(aComment) / 2; Font oldFont = g.getFont(); for (int i = 0; i < aComment.length(); i++) { int tempX = commentXStart + g.getFontMetrics().stringWidth(aComment.substring(0, i)); if (Character.isDigit(aComment.charAt(i))) { g.setFont(new Font(oldFont.getName(), oldFont.getStyle(), oldFont.getSize() - 2)); g.drawString("" + aComment.charAt(i), tempX, y - arrowSpacer + 3); } else { g.drawString("" + aComment.charAt(i), tempX, y - arrowSpacer); } g.setFont(oldFont); } } else { g.drawString(aComment, x - g.getFontMetrics().stringWidth(aComment) / 2, y - arrowSpacer); } } else { // No comment, so print the x- and y-value. Note: both are rounded to four decimals String xValue; String yValue; if (!mirrored) { xValue = Double.toString(Util.roundDouble(iXAxisData.get(dataSetIndex)[aIndex], 4)); yValue = Double.toString(Util.roundDouble(iYAxisData.get(dataSetIndex)[aIndex], 4)); } else { xValue = Double.toString(Util.roundDouble(iXAxisDataMirroredSpectrum.get(dataSetIndex)[aIndex], 4)); yValue = Double.toString(Util.roundDouble(iYAxisDataMirroredSpectrum.get(dataSetIndex)[aIndex], 4)); } String label = "(" + xValue + ", " + yValue + ")"; int halfWayMass = g.getFontMetrics().stringWidth(label) / 2; g.drawString(label, x - halfWayMass, y - arrowSpacer); } // If we drew above/below the point, drop a dotted line. if (aPixelsSpacer != 0 && aShowArrow) { if (!mirrored) { dropDottedLine(aIndex, dataSetIndex, y + 2, g, mirrored); } else { dropDottedLine(aIndex, dataSetIndex, y - 4, g, mirrored); } } // Restore original color. g.setColor(originalColor); } /** * This method draws a line, measuring the distance between two points in * real mass units. * * @param aFirstIndex int with the first point index to draw from. * @param aFirstDatasetIndex the dataset index of the first data point * @param aSecondIndex int with the second point index to draw to. * @param aSecondDatasetIndex the dataset index of the second data point * @param g Graphics object on which to draw. * @param aColor Color object with the color for all the drawing. * @param aExtraPadding int with an optional amount of extra padding (lower * on the graph if positive, higher on the graph if negative) * @param mirrored if true the line is drawn for the mirrored spectra */ protected void drawMeasurementLine(int aFirstIndex, int aFirstDatasetIndex, int aSecondIndex, int aSecondDatasetIndex, Graphics g, Color aColor, int aExtraPadding, boolean mirrored) { // First get the x coordinates of the two points. int x1; int x2; ArrayList<double[]> xAxisData; if (!mirrored) { x1 = iXAxisDataInPixels.get(aFirstDatasetIndex)[aFirstIndex]; x2 = iXAxisDataInPixels.get(aSecondDatasetIndex)[aSecondIndex]; xAxisData = iXAxisData; } else { x1 = iXAxisDataInPixelsMirroredSpectrum.get(aFirstDatasetIndex)[aFirstIndex]; x2 = iXAxisDataInPixelsMirroredSpectrum.get(aSecondDatasetIndex)[aSecondIndex]; xAxisData = iXAxisDataMirroredSpectrum; } if (x1 == 0 && x2 == 0) { return; } else if (x1 == 0) { if (xAxisData.get(aFirstDatasetIndex)[aFirstIndex] < iXAxisMin) { x1 = iXPadding + 1; } else { x1 = this.getWidth() - iXPadding - 1; } } else if (x2 == 0) { if (xAxisData.get(aSecondDatasetIndex)[aSecondIndex] < iXAxisMin) { x2 = iXPadding + 1; } else { x2 = this.getWidth() - iXPadding - 1; } } // Now the real x-value difference as a String. double delta = Math.abs(xAxisData.get(aFirstDatasetIndex)[aFirstIndex] - xAxisData.get(aSecondDatasetIndex)[aSecondIndex]); String deltaMass = new BigDecimal(delta).setScale(2, BigDecimal.ROUND_HALF_UP).toString(); String deNovoTag = this.findDeltaMassMatches(delta, deltaMassWindow); int deNovoTagWidth = g.getFontMetrics().stringWidth(deNovoTag); int deltaMassWidth = g.getFontMetrics().stringWidth(deltaMass); // Vertical position of the bar with the position of the highest point + a margin. int y; if (!mirrored) { y = (int) (iYScaleUnit / iYAxisMax + (iXPadding / 2)) + aExtraPadding; } else { y = this.getHeight() - iXPadding - aExtraPadding; } // Draw the line. Color originalColor = g.getColor(); g.setColor(aColor); g.drawLine(x1, y, x2, y); g.drawLine(x1, y - 3, x1, y + 3); g.drawLine(x2, y - 3, x2, y + 3); // Drop a dotted line down to the peaks. dropDottedLine(aFirstIndex, aFirstDatasetIndex, y - 3, g, mirrored); dropDottedLine(aSecondIndex, aSecondDatasetIndex, y - 3, g, mirrored); // Draw the de novo tag. int xPosText = Math.min(x1, x2) + (Math.abs(x1 - x2) / 2) - (deNovoTagWidth / 2); g.drawString(deNovoTag, xPosText, y - 5); // Draw the mass. xPosText = Math.min(x1, x2) + (Math.abs(x1 - x2) / 2) - (deltaMassWidth / 2); g.drawString(deltaMass, xPosText, y + 15); // Return original color. g.setColor(originalColor); } /** * This method drops a dotted line from the specified total height to the * top of the indicated point. * * @param aPeakIndex int with the index of the point to draw the dotted line * for. * @param aDatasetIndex the index of the dataset * @param aTotalHeight int with the height (in pixels) to drop the dotted * line from. * @param g Graphics object to draw the dotted line on. * @param mirrored if true the line is drawn on the mirrored spectra */ protected void dropDottedLine(int aPeakIndex, int aDatasetIndex, int aTotalHeight, Graphics g, boolean mirrored) { int x; int y; if (!mirrored) { x = iXAxisDataInPixels.get(aDatasetIndex)[aPeakIndex]; y = iYAxisDataInPixels.get(aDatasetIndex)[aPeakIndex]; } else { x = iXAxisDataInPixelsMirroredSpectrum.get(aDatasetIndex)[aPeakIndex]; y = iYAxisDataInPixelsMirroredSpectrum.get(aDatasetIndex)[aPeakIndex]; } if (!mirrored) { // Draw the dotted line. if ((y - aTotalHeight) > 10) { int start = aTotalHeight; while (start < y) { g.drawLine(x, start, x, start + 2); start += 7; } } } else { // Draw the dotted line. if ((aTotalHeight - y) > 10) { int start = aTotalHeight; while (start > y) { g.drawLine(x, start, x, start - 2); start -= 7; } } } } /** * This method attempts to find a list of known mass deltas, corresponding * with the specified x value in the given window. * * @param aDelta the delta mass to search for * @param aWindow the window used for the search * @return String with the description of the matching mass delta or empty * String if none was found. */ protected String findDeltaMassMatches(double aDelta, double aWindow) { StringBuilder result = new StringBuilder(""); boolean appended = false; if (iKnownMassDeltas != null) { for (Double mass : iKnownMassDeltas.keySet()) { if (Math.abs(mass.doubleValue() - aDelta) < aWindow) { if (appended) { result.append(" "); } else { appended = true; } result.append(iKnownMassDeltas.get(mass)); } } // add combinations of two mass deltas if (useMassDeltaCombinations) { // look for combinations of two mass deltas Iterator iter1 = iKnownMassDeltas.keySet().iterator(); ArrayList<String> addedMassDeltas = new ArrayList<String>(); while (iter1.hasNext()) { Double mass1 = (Double) iter1.next(); for (Double mass2 : iKnownMassDeltas.keySet()) { Double mass = mass1 + mass2; if (Math.abs(mass.doubleValue() - aDelta) < aWindow) { if (!addedMassDeltas.contains(iKnownMassDeltas.get(mass2) + "" + iKnownMassDeltas.get(mass1))) { if (appended) { result.append(" "); } else { appended = true; } result.append(iKnownMassDeltas.get(mass1)).append("").append(iKnownMassDeltas.get(mass2)); addedMassDeltas.add(iKnownMassDeltas.get(mass1) + "" + iKnownMassDeltas.get(mass2)); } } } } } } return result.toString(); } /** * This method attempts to find the specified SpectrumAnnotation in the * current peak list and if so, annotates it correspondingly on the screen. * * @param aSA SpectrumAnnotation with the annotation to find. * @param g Graphics instance to annotate on. * @param aAlreadyAnnotated HashMap with the index of a point as key, and * the number of times it has been annotated as value (or 'null' if not yet * annotated). * @param mirrored if true the annotation is for the mirrored spectra */ protected void annotate(SpectrumAnnotation aSA, Graphics g, HashMap<String, Integer> aAlreadyAnnotated, boolean mirrored) { double xValue = aSA.getMZ(); double error = Math.abs(aSA.getErrorMargin()); // Only do those that fall within the current visual range. if (!(xValue < iXAxisMin || xValue > iXAxisMax)) { ArrayList<double[]> xAxisData; ArrayList<double[]> yAxisData; if (!mirrored) { xAxisData = iXAxisData; yAxisData = iYAxisData; } else { xAxisData = iXAxisDataMirroredSpectrum; yAxisData = iYAxisDataMirroredSpectrum; } // See if any match is to be found. boolean foundMatch = false; int peakIndex = -1; int dataSetIndex = -1; double smallestAbsError = Double.MAX_VALUE; for (int j = 0; j < xAxisData.size(); j++) { for (int i = 0; i < xAxisData.get(j).length; i++) { double delta = xAxisData.get(j)[i] - xValue; if (Math.abs(delta) <= error) { if (!foundMatch) { foundMatch = true; peakIndex = i; dataSetIndex = j; smallestAbsError = Math.abs(delta); } else { // we already had one. take the one with the largest intensity or the most accurate if (annotateHighestPeak) { if (yAxisData.get(j)[i] > yAxisData.get(dataSetIndex)[peakIndex]) { peakIndex = i; dataSetIndex = j; } } else { if (Math.abs(delta) < smallestAbsError) { peakIndex = i; dataSetIndex = j; } } } } else if (delta > error) { break; } } } // If a match was found and it qualifies against the minimal intensity, // we now have a peak index so we can annotate. if (foundMatch && yAxisData.get(dataSetIndex)[peakIndex] > iAnnotationYAxisThreshold) { //String label = aSA.getLabel() + " (" + new BigDecimal(mz-iMasses[peakIndex]).setScale(2, BigDecimal.ROUND_HALF_UP).toString() + ")"; String label = aSA.getLabel(); int spacer = (int) ((yAxisData.get(dataSetIndex)[peakIndex] - iYAxisMin) / iYScaleUnit) / 2; // @TODO: should this be different if mirrored? boolean showArrow = true; String key = dataSetIndex + "_" + peakIndex; if (aAlreadyAnnotated.containsKey(key)) { int count = ((Integer) aAlreadyAnnotated.get(key)).intValue(); spacer += count * (g.getFontMetrics().getAscent() + 4); aAlreadyAnnotated.put(key, Integer.valueOf(count + 1)); showArrow = false; } else { aAlreadyAnnotated.put(key, Integer.valueOf(1)); } this.highLight(peakIndex, dataSetIndex, g, aSA.getColor(), label, spacer, showArrow, aAlreadyAnnotated.get(key), mirrored); } } } /** * This method draws all of the peaks for all datasets in the current x-axis * range on the panel. * * @param g Graphics object to draw on. */ protected void drawMirroredPeaks(Graphics g) { // @TODO: should be merged with the drawPeaks method Color originalColor = g.getColor(); // Init an array that holds pixel coordinates for each peak. iXAxisDataInPixelsMirroredSpectrum = new ArrayList<int[]>(); iYAxisDataInPixelsMirroredSpectrum = new ArrayList<int[]>(); // cycle the datasets for (int j = 0; j < iXAxisDataMirroredSpectrum.size(); j++) { // set the color g.setColor(iDataPointAndLineColorMirroredSpectra.get(j)); iXAxisDataInPixelsMirroredSpectrum.add(new int[iXAxisDataMirroredSpectrum.get(j).length]); iYAxisDataInPixelsMirroredSpectrum.add(new int[iYAxisDataMirroredSpectrum.get(j).length]); // cycle the peaks for the dataset for (int i = 0; i < iXAxisDataMirroredSpectrum.get(j).length; i++) { double lXAxisValue = iXAxisDataMirroredSpectrum.get(j)[i]; // Only draw those x values within the ('low x value', 'high x value') window. if (lXAxisValue < iXAxisMin) { continue; } else if (lXAxisValue > iXAxisMax) { break; } else { // is the peak annotated? boolean annotatedPeak = true; if (!showAllPeaks) { annotatedPeak = isPeakAnnotated(lXAxisValue, true); } double lYAxisValue = iYAxisDataMirroredSpectrum.get(j)[i]; // Calculate pixel coordinates for x and y values. // X value first. double tempDouble = (lXAxisValue - iXAxisMin) / iXScaleUnit; int temp = (int) tempDouble; if ((tempDouble - temp) >= 0.5) { temp++; } int xAxisPxl = temp + iXPadding; if (annotatedPeak || showAllPeaks) { iXAxisDataInPixelsMirroredSpectrum.get(j)[i] = xAxisPxl; } // Now intensity. tempDouble = (lYAxisValue - iYAxisMin) / iYScaleUnit; temp = (int) tempDouble; if ((tempDouble - temp) >= 0.5) { temp++; } int xAxisYLocation = (this.getHeight() + currentPadding) / 2; int yValuePxl = xAxisYLocation + (temp - iXPadding); if (annotatedPeak || showAllPeaks) { iYAxisDataInPixelsMirroredSpectrum.get(j)[i] = yValuePxl; } // store the current peak color Color currentColor = g.getColor(); // set the width of the peaks Graphics2D g2 = (Graphics2D) g; Stroke tempStroke = g2.getStroke(); // change the peak color if the peak is to be drawn in the background if (!annotatedPeak && !showAllPeaks) { g.setColor(peakWaterMarkColor); BasicStroke stroke = new BasicStroke(backgroundPeakWidth); g2.setStroke(stroke); } else { BasicStroke stroke = new BasicStroke(peakWidth); g2.setStroke(stroke); } // draw the peak if (iCurrentDrawStyle == DrawingStyle.LINES) { // Draw the line. g2.draw(new Line2D.Double(xAxisPxl, xAxisYLocation - iXPadding, xAxisPxl, yValuePxl)); } else if (iCurrentDrawStyle == DrawingStyle.DOTS) { // Draw the dot. g.fillOval(xAxisPxl - iDotRadius, yValuePxl - iDotRadius, iDotRadius * 2, iDotRadius * 2); } // reset the width of lines to the previous width g2.setStroke(tempStroke); g.setColor(currentColor); } } } // Change the color back to its original setting. g.setColor(originalColor); } /** * This method draws all of the peaks for all datasets in the current x-axis * range on the panel. * * @param g Graphics object to draw on. */ protected void drawPeaks(Graphics g) { Color originalColor = g.getColor(); // Init an array that holds pixel coordinates for each peak. iXAxisDataInPixels = new ArrayList<int[]>(); iYAxisDataInPixels = new ArrayList<int[]>(); // cycle the datasets for (int j = 0; j < iXAxisData.size(); j++) { // set the color g.setColor(iDataPointAndLineColor.get(j)); iXAxisDataInPixels.add(new int[iXAxisData.get(j).length]); iYAxisDataInPixels.add(new int[iYAxisData.get(j).length]); // cycle the peaks for the dataset for (int i = 0; i < iXAxisData.get(j).length; i++) { double lXAxisValue = iXAxisData.get(j)[i]; // Only draw those x values within the ('low x value', 'high x value') window. if (lXAxisValue < iXAxisMin) { continue; } else if (lXAxisValue > iXAxisMax) { break; } else { // is the peak annotated? boolean annotatedPeak = true; if (!showAllPeaks) { annotatedPeak = isPeakAnnotated(lXAxisValue, false); } double lYAxisValue = iYAxisData.get(j)[i]; // Calculate pixel coordinates for x and y values. // X value first. double tempDouble = (lXAxisValue - iXAxisMin) / iXScaleUnit; int temp = (int) tempDouble; if ((tempDouble - temp) >= 0.5) { temp++; } int xAxisPxl = temp + iXPadding; if (annotatedPeak || showAllPeaks) { iXAxisDataInPixels.get(j)[i] = xAxisPxl; } // Now intensity. tempDouble = (lYAxisValue - iYAxisMin) / iYScaleUnit; temp = (int) tempDouble; if ((tempDouble - temp) >= 0.5) { temp++; } int xAxisYLocation = this.getHeight(); if (dataSetCounterMirroredSpectra > 0) { xAxisYLocation = (this.getHeight() + currentPadding) / 2; } int yValuePxl = xAxisYLocation - (temp + iXPadding); if (annotatedPeak || showAllPeaks) { iYAxisDataInPixels.get(j)[i] = yValuePxl; } // store the current peak color Color currentColor = g.getColor(); // set the width of the peaks Graphics2D g2 = (Graphics2D) g; Stroke tempStroke = g2.getStroke(); // change the peak color if the peak is to be drawn in the background if (!annotatedPeak && !showAllPeaks) { g.setColor(peakWaterMarkColor); BasicStroke stroke = new BasicStroke(backgroundPeakWidth); g2.setStroke(stroke); } else { BasicStroke stroke = new BasicStroke(peakWidth); g2.setStroke(stroke); } // draw the peak if (iCurrentDrawStyle == DrawingStyle.LINES) { // Draw the line. g2.draw(new Line2D.Double(xAxisPxl, xAxisYLocation - iXPadding, xAxisPxl, yValuePxl)); } else if (iCurrentDrawStyle == DrawingStyle.DOTS) { // Draw the dot. g.fillOval(xAxisPxl - iDotRadius, yValuePxl - iDotRadius, iDotRadius * 2, iDotRadius * 2); } // reset the width of lines to the previous width g2.setStroke(tempStroke); g.setColor(currentColor); } } } // Change the color back to its original setting. g.setColor(originalColor); } /** * This method draws filled polygons for all of the peaks for all datasets * in the current x-axis range on the panel. * * @param g Graphics object to draw on. */ protected void drawFilledPolygon(Graphics g) { // switch to 2D graphics Graphics2D g2d = (Graphics2D) g; // store the original color Color originalColor = g2d.getColor(); Composite originalComposite = g2d.getComposite(); // init an array that holds pixel coordinates for each point. iXAxisDataInPixels = new ArrayList<int[]>(); iYAxisDataInPixels = new ArrayList<int[]>(); // cycle the datasets for (int j = 0; j < iXAxisData.size(); j++) { iXAxisDataInPixels.add(new int[iXAxisData.get(j).length]); iYAxisDataInPixels.add(new int[iYAxisData.get(j).length]); // These arrays only contain the visible points. ArrayList<Integer> xAxisPointsShown = new ArrayList<Integer>(); ArrayList<Integer> yAxisPointsShown = new ArrayList<Integer>(); // cycle the datapoints for (int i = 0; i < iXAxisData.get(j).length; i++) { double xMeasurement = iXAxisData.get(j)[i]; // Only draw those x-axis measurements within the ('low x', 'high x') window. if (xMeasurement < iXAxisMin) { continue; } else if (xMeasurement > iXAxisMax) { break; } else { // See if we need to initialize the start index. double yMeasurement = iYAxisData.get(j)[i]; // Calculate pixel coordinates for X and Y. // X first. double tempDouble = (xMeasurement - iXAxisMin) / iXScaleUnit; int temp = (int) tempDouble; if ((tempDouble - temp) >= 0.5) { temp++; } int xAxisPxl = temp + iXPadding; iXAxisDataInPixels.get(j)[i] = xAxisPxl; // Now intensity. tempDouble = (yMeasurement - iYAxisMin) / iYScaleUnit; temp = (int) tempDouble; if ((tempDouble - temp) >= 0.5) { temp++; } int yAxisPxl = this.getHeight() - (temp + iXPadding); iYAxisDataInPixels.get(j)[i] = yAxisPxl; // Add to the list of points shwon. xAxisPointsShown.add(xAxisPxl); yAxisPointsShown.add(yAxisPxl); } } // check if there are any data points to draw if (!xAxisPointsShown.isEmpty()) { // set the color and opacity level g.setColor(iAreaUnderCurveColor.get(j)); if (j != 0) { g2d.setComposite(makeComposite(alphaLevel)); } // First draw the filled polygon. int[] xTemp = new int[xAxisPointsShown.size() + 2]; int[] yTemp = new int[yAxisPointsShown.size() + 2]; xTemp[0] = xAxisPointsShown.get(0).intValue(); yTemp[0] = this.getHeight() - iXPadding; for (int i = 0; i < xAxisPointsShown.size(); i++) { xTemp[i + 1] = xAxisPointsShown.get(i).intValue(); yTemp[i + 1] = yAxisPointsShown.get(i).intValue(); } xTemp[xTemp.length - 1] = xAxisPointsShown.get(xAxisPointsShown.size() - 1).intValue(); yTemp[xTemp.length - 1] = this.getHeight() - iXPadding; // Fill out the chromatogram. g2d.fillPolygon(xTemp, yTemp, xTemp.length); // set the color g.setColor(iDataPointAndLineColor.get(j)); // Now draw the points, and a line connecting them. g2d.drawPolyline(xTemp, yTemp, xTemp.length); // Skip the point for the first and last element; // these are just there to nicely fill the polygon. for (int i = 1; i < xTemp.length - 1; i++) { int x = xTemp[i] - (iPointSize / 2); int y = yTemp[i] - (iPointSize / 2); g2d.fillOval(x, y, iPointSize, iPointSize); } g2d.setComposite(originalComposite); } } // Change the color back to its original setting. g2d.setColor(originalColor); } /** * Helper method for setting the opacity. * * @param alpha the opacity value, 0 means completely see-through, 1 means * opaque. * @return an AlphaComposite object */ private AlphaComposite makeComposite(float alpha) { int type = AlphaComposite.SRC_OVER; return (AlphaComposite.getInstance(type, alpha)); } /** * Set if the x-axis tags are to be drawn using scientific annotation. The * default is false. The default pattern is "##0.#####E0". * * @param scientificXAxis if the x-axis tags is to be drawn using scientific * annotation */ public void setScientificXAxis(boolean scientificXAxis) { this.scientificXAxis = scientificXAxis; } /** * Set if the x-axis tags are to be drawn using scientific annotation. The * default is false. For pattern details see java.text.DecimalFormat. The * default pattern is "##0.#####E0". * * @param pattern the number format pattern to use */ public void setScientificXAxis(String pattern) { this.scientificXAxis = true; this.scientificPattern = pattern; } /** * Set if the y-axis tags are to be drawn using scientific annotation. The * default is false. The default pattern is "##0.#####E0". * * @param scientificYAxis if the y-axis tags is to be drawn using scientific * annotation */ public void setScientificYAxis(boolean scientificYAxis) { this.scientificYAxis = scientificYAxis; } /** * Set if the y-axis tags are to be drawn using scientific annotation. The * default is false. For pattern details see java.text.DecimalFormat. The * default pattern is "##0.#####E0". * * @param pattern the number format pattern to use */ public void setScientificYAxis(String pattern) { this.scientificYAxis = true; this.scientificPattern = pattern; } /** * Get the peak water mark color. * * @return the peak water mark color */ public Color getPeakWaterMarkColor() { return peakWaterMarkColor; } /** * Set the peak water mark color. * * @param peakWaterMarkColor the color to set */ public void setPeakWaterMarkColor(Color peakWaterMarkColor) { this.peakWaterMarkColor = peakWaterMarkColor; } /** * Returns true of the given x-axis value is annotated with at least one * annotation. * * @param xAxisValue the x-axis value * @param mirrored if true checks for the mirrored peaks, false checks the * normal peaks * @return true of the given x-axis value is annotated with at least one * annotation */ private boolean isPeakAnnotated(double xAxisValue, boolean mirrored) { Vector annotations; if (!mirrored) { annotations = iAnnotations; } else { annotations = iAnnotationsMirroredSpectra; } boolean annotatedPeak = false; for (int m = 0; m < annotations.size() && !annotatedPeak; m++) { Object o = annotations.get(m); if (o instanceof SpectrumAnnotation) { SpectrumAnnotation sa = (SpectrumAnnotation) o; double xValue = sa.getMZ(); double error = Math.abs(sa.getErrorMargin()); // @TODO: make sure that there is only one annotated peak per annotation! double delta = xAxisValue - xValue; if (Math.abs(delta) <= error) { annotatedPeak = true; } } } return annotatedPeak; } }