/* * Copyright 2006-2020 The MZmine Development Team * * This file is part of MZmine. * * MZmine is free software; you can redistribute it and/or modify it under the terms of the GNU * General Public License as published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * MZmine is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General * Public License for more details. * * You should have received a copy of the GNU General Public License along with MZmine; if not, * write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 * USA */ package io.github.mzmine.modules.visualization.chromatogram; import java.awt.Color; import java.awt.Paint; import java.awt.Shape; import java.awt.event.ActionEvent; import java.awt.event.MouseEvent; import java.awt.event.MouseWheelEvent; import java.awt.geom.Ellipse2D; import java.util.Arrays; import java.util.logging.Level; import java.util.logging.Logger; import org.jfree.chart.ChartFactory; import org.jfree.chart.JFreeChart; import org.jfree.chart.axis.NumberAxis; import org.jfree.chart.event.ChartProgressEvent; import org.jfree.chart.labels.XYItemLabelGenerator; import org.jfree.chart.labels.XYToolTipGenerator; import org.jfree.chart.plot.DatasetRenderingOrder; import org.jfree.chart.plot.PlotOrientation; import org.jfree.chart.plot.XYPlot; import org.jfree.chart.renderer.xy.DefaultXYItemRenderer; import org.jfree.chart.renderer.xy.XYItemRenderer; import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer; import org.jfree.chart.title.LegendTitle; import org.jfree.chart.title.TextTitle; import org.jfree.data.general.DatasetUtils; import org.jfree.data.xy.XYDataset; import io.github.mzmine.gui.chartbasics.chartthemes.EStandardChartTheme; import io.github.mzmine.gui.chartbasics.gui.javafx.EChartViewer; import io.github.mzmine.gui.chartbasics.listener.ZoomHistory; import io.github.mzmine.main.MZmineCore; import javafx.scene.Cursor; import javafx.scene.control.MenuItem; import javafx.stage.Stage; import javafx.stage.Window; /** * TIC plot. * * Added the possibility to switch to TIC plot type from a "non-TICVisualizerWindow" context. */ public class TICPlot extends EChartViewer { // Logger. private static final Logger logger = Logger.getLogger(TICPlot.class.getName()); // Zoom factor. private static final double ZOOM_FACTOR = 1.2; // peak labels color - moved to EStandardChartTheme ~SteffenHeu // data points shape private static final Shape DATA_POINT_SHAPE = new Ellipse2D.Double(-2.0, -2.0, 5.0, 5.0); // Fonts. - moved to EStandardChartTheme ~SteffenHeu // Axis margins. private static final double AXIS_MARGINS = 0.001; // Title margin. // private static final double TITLE_TOP_MARGIN = 5.0; // Plot type. private TICPlotType plotType; private final JFreeChart chart; // The plot. private final XYPlot plot; // TICVisualizerWindow visualizer. // private final ActionListener visualizer; // Titles. private final TextTitle chartTitle; private final TextTitle chartSubTitle; // Renderer. private final TICPlotRenderer defaultRenderer; // Counters. private int numOfDataSets; private int numOfPeaks; private MenuItem RemoveFilePopupMenu; EStandardChartTheme theme; /** * Indicates whether we have a request to show spectra visualizer for selected data point. Since * the selection (cross-hair) is updated with some delay after clicking with mouse, we cannot open * the new visualizer immediately. Therefore we place a request and open the visualizer later in * chartProgress() */ private boolean showSpectrumRequest; // Label visibility: 0 -> none; 1 -> m/z; 2 -> identities private int labelsVisible; private boolean havePeakLabels; public TICPlot() { super(ChartFactory.createXYLineChart("", // title "Retention time", // x-axis label "Y", // y-axis label null, // data set PlotOrientation.VERTICAL, // orientation true, // create legend? true, // generate tooltips? false // generate URLs? )); theme = MZmineCore.getConfiguration().getDefaultChartTheme(); // Initialize. // visualizer = listener; labelsVisible = 1; havePeakLabels = false; numOfDataSets = 0; numOfPeaks = 0; showSpectrumRequest = false; setMinWidth(300.0); setMinHeight(300.0); setPrefWidth(600.0); setPrefHeight(400.0); // Plot type // Y-axis label. final String yAxisLabel = (this.plotType == TICPlotType.BASEPEAK) ? "Base peak intensity" : "Total ion intensity"; // Initialize the chart by default time series chart from factory. chart = getChart(); chart.getXYPlot().getRangeAxis().setLabel(yAxisLabel); // setChart(chart); // Title. chartTitle = chart.getTitle(); // Subtitle. chartSubTitle = new TextTitle(); chart.addSubtitle(chartSubTitle); // Disable maximum size (we don't want scaling). // setMaximumDrawWidth(Integer.MAX_VALUE); // setMaximumDrawHeight(Integer.MAX_VALUE); // Set the plot properties. plot = chart.getXYPlot(); plot.setDatasetRenderingOrder(DatasetRenderingOrder.FORWARD); // Set cross-hair (selection) properties. // if (listener instanceof TICVisualizerWindow) { // Set cursor. setCursor(Cursor.CROSSHAIR); setPlotType(TICPlotType.BASEPEAK); // } // Set the x-axis (retention time) properties. final NumberAxis xAxis = (NumberAxis) plot.getDomainAxis(); xAxis.setNumberFormatOverride(MZmineCore.getConfiguration().getRTFormat()); xAxis.setUpperMargin(AXIS_MARGINS); xAxis.setLowerMargin(AXIS_MARGINS); // Set the y-axis (intensity) properties. final NumberAxis yAxis = (NumberAxis) plot.getRangeAxis(); yAxis.setNumberFormatOverride(MZmineCore.getConfiguration().getIntensityFormat()); // Set default renderer properties. defaultRenderer = new TICPlotRenderer(); defaultRenderer.setDefaultShapesFilled(true); defaultRenderer.setDrawOutlines(false); defaultRenderer.setUseFillPaint(true); // Set label generator final XYItemLabelGenerator labelGenerator = new TICItemLabelGenerator(this); defaultRenderer.setDefaultItemLabelGenerator(labelGenerator); defaultRenderer.setDefaultItemLabelsVisible(true); // Set toolTipGenerator final XYToolTipGenerator toolTipGenerator = new TICToolTipGenerator(); defaultRenderer.setDefaultToolTipGenerator(toolTipGenerator); // Set focus state to receive key events. // setFocusable(true); // Register key handlers. /* * TODO: GUIUtils.registerKeyHandler(this, KeyStroke.getKeyStroke("LEFT"), listener, * "MOVE_CURSOR_LEFT"); * * GUIUtils.registerKeyHandler(this, KeyStroke.getKeyStroke("RIGHT"), listener, * "MOVE_CURSOR_RIGHT"); GUIUtils.registerKeyHandler(this, KeyStroke.getKeyStroke("SPACE"), * listener, "SHOW_SPECTRUM"); GUIUtils.registerKeyHandler(this, KeyStroke.getKeyStroke('+'), * this, "ZOOM_IN"); GUIUtils.registerKeyHandler(this, KeyStroke.getKeyStroke('-'), this, * "ZOOM_OUT"); GUIUtils.registerKeyHandler(this, KeyStroke.getKeyStroke('*'), this, * "ZOOM_AUTO"); */ // Add items to popup menu. // final ContextMenu popupMenu = getContextMenu(); // popupMenu.getItems().add(new MenuItem("gagaga")); // popupMenu.getItems().add(new SeparatorMenuItem()); // if (listener instanceof TICVisualizerWindow) { // popupMenu.add(new ExportPopUpMenu((TICVisualizerWindow) listener)); // popupMenu.addSeparator(); // popupMenu.add(new AddFilePopupMenu((TICVisualizerWindow) listener)); // RemoveFilePopupMenu = popupMenu.add(new RemoveFilePopupMenu((TICVisualizerWindow) listener)); // popupMenu.addSeparator(); // RemoveFilePopupMenu.setEnabled(false); // GUIUtils.addMenuItem(popupMenu, "Toggle showing peak values", this, "SHOW_ANNOTATIONS"); // GUIUtils.addMenuItem(popupMenu, "Toggle showing data points", this, "SHOW_DATA_POINTS"); // if(listener instanceof TICVisualizerWindow) { // popupMenu.addSeparator(); // GUIUtils.addMenuItem(popupMenu, "Show spectrum of selected scan", listener, // "SHOW_SPECTRUM"); } // popupMenu.addSeparator(); // GUIUtils.addMenuItem(popupMenu,"Set axes range",this,"SETUP_AXES"); // if(listener instanceof TICVisualizerWindow) { // GUIUtils.addMenuItem(popupMenu, "Set same range to all windows", this, "SET_SAME_RANGE"); } // Register for mouse-wheel events // addMouseWheelListener(this); chart.addProgressListener(event -> { if (event.getType() == ChartProgressEvent.DRAWING_FINISHED) { Window myWindow = this.getScene().getWindow(); if (myWindow instanceof TICVisualizerWindow) { ((TICVisualizerWindow) myWindow).updateTitle(); } if (showSpectrumRequest) { showSpectrumRequest = false; // visualizer.actionPerformed( // new ActionEvent(event.getSource(), ActionEvent.ACTION_PERFORMED, "SHOW_SPECTRUM")); } } }); // reset zoom history ZoomHistory history = getZoomHistory(); if (history != null) history.clear(); // theme.apply(this.getChart()); } // @Override public void actionPerformed(final ActionEvent event) { // super.actionPerformed(event); final String command = event.getActionCommand(); if ("ZOOM_IN".equals(command)) { getXYPlot().getDomainAxis().resizeRange(1.0 / ZOOM_FACTOR); getXYPlot().getDomainAxis().setAutoTickUnitSelection(true); } // Set tick size to auto when zooming String[] zoomList = new String[] {"ZOOM_IN_BOTH", "ZOOM_IN_DOMAIN", "ZOOM_IN_RANGE", "ZOOM_OUT_BOTH", "ZOOM_DOMAIN_BOTH", "ZOOM_RANGE_BOTH", "ZOOM_RESET_BOTH", "ZOOM_RESET_DOMAIN", "ZOOM_RESET_RANGE"}; if (Arrays.asList(zoomList).contains(command)) { getXYPlot().getDomainAxis().setAutoTickUnitSelection(true); getXYPlot().getRangeAxis().setAutoTickUnitSelection(true); } if ("ZOOM_OUT".equals(command)) { getXYPlot().getDomainAxis().resizeRange(ZOOM_FACTOR); getXYPlot().getDomainAxis().setAutoTickUnitSelection(true); // if (getXYPlot().getDomainAxis().getRange().contains(0.0000001)) { // getXYPlot().getDomainAxis().setAutoRange(true); // getXYPlot().getDomainAxis().setAutoTickUnitSelection(true); // } } if ("ZOOM_AUTO".equals(command)) { getXYPlot().getDomainAxis().setAutoTickUnitSelection(true); getXYPlot().getRangeAxis().setAutoTickUnitSelection(true); // restoreAutoDomainBounds(); // restoreAutoRangeBounds(); } if ("SET_SAME_RANGE".equals(command)) { // Get current axes range. final NumberAxis xAxis = (NumberAxis) getXYPlot().getDomainAxis(); final NumberAxis yAxis = (NumberAxis) getXYPlot().getRangeAxis(); final double xMin = xAxis.getRange().getLowerBound(); final double xMax = xAxis.getRange().getUpperBound(); final double xTick = xAxis.getTickUnit().getSize(); final double yMin = yAxis.getRange().getLowerBound(); final double yMax = yAxis.getRange().getUpperBound(); final double yTick = yAxis.getTickUnit().getSize(); // Set the range of these frames for (final Window frame : Stage.getWindows()) { if (frame instanceof TICVisualizerWindow) { final TICVisualizerWindow ticFrame = (TICVisualizerWindow) frame; ticFrame.setAxesRange(xMin, xMax, xTick, yMin, yMax, yTick); } } } } // @Override public void mouseWheelMoved(MouseWheelEvent event) { int notches = event.getWheelRotation(); if (notches < 0) { getXYPlot().getDomainAxis().resizeRange(1.0 / ZOOM_FACTOR); } else { getXYPlot().getDomainAxis().resizeRange(ZOOM_FACTOR); } } // @Override public void restoreAutoBounds() { getXYPlot().getDomainAxis().setAutoTickUnitSelection(true); getXYPlot().getRangeAxis().setAutoTickUnitSelection(true); // restoreAutoDomainBounds(); // restoreAutoRangeBounds(); } // @Override public void mouseClicked(final MouseEvent event) { // Let the parent handle the event (selection etc.) // super.mouseClicked(event); // Request focus to receive key events. requestFocus(); // Handle mouse click events if (event.getButton() == MouseEvent.BUTTON1) { System.out.println("mouse " + event); if (event.getX() < 70) { // User clicked on Y-axis if (event.getClickCount() == 2) { // Reset zoom on Y-axis XYDataset data = ((XYPlot) getChart().getPlot()).getDataset(); Number maximum = DatasetUtils.findMaximumRangeValue(data); getXYPlot().getRangeAxis().setRange(0, 1.05 * maximum.floatValue()); } else if (event.getClickCount() == 1) { // Auto range on Y-axis getXYPlot().getRangeAxis().setAutoTickUnitSelection(true); getXYPlot().getRangeAxis().setAutoRange(true); } } else if (event.getY() > this.getRenderingInfo().getPlotInfo().getPlotArea().getMaxY() - 41 && event.getClickCount() == 2) { // Reset zoom on X-axis getXYPlot().getDomainAxis().setAutoTickUnitSelection(true); // restoreAutoDomainBounds(); } else if (event.getClickCount() == 2) { // If user double-clicked // left button, place a // request to open a // spectrum. showSpectrumRequest = true; } } } public void switchLegendVisible() { // Toggle legend visibility. final LegendTitle legend = getChart().getLegend(); legend.setVisible(!legend.isVisible()); } public void switchItemLabelsVisible() { // Switch to next mode. Include peaks mode only if peak labels are // present. labelsVisible = (labelsVisible + 1) % (havePeakLabels ? 3 : 2); final int dataSetCount = plot.getDatasetCount(); for (int i = 0; i < dataSetCount; i++) { final XYDataset dataSet = plot.getDataset(i); final XYItemRenderer renderer = plot.getRenderer(i); if (dataSet instanceof TICDataSet) { renderer.setDefaultItemLabelsVisible(labelsVisible == 1); } else if (dataSet instanceof PeakDataSet) { renderer.setDefaultItemLabelsVisible(labelsVisible == 2); } else { renderer.setDefaultItemLabelsVisible(false); } } } public void switchDataPointsVisible() { Boolean dataPointsVisible = null; final int count = plot.getDatasetCount(); for (int i = 0; i < count; i++) { if (plot.getRenderer(i) instanceof XYLineAndShapeRenderer) { final XYLineAndShapeRenderer renderer = (XYLineAndShapeRenderer) plot.getRenderer(i); if (dataPointsVisible == null) { dataPointsVisible = !renderer.getDefaultShapesVisible(); } renderer.setDefaultShapesVisible(dataPointsVisible); } } } public void switchBackground() { // Toggle background color final Paint color = getChart().getPlot().getBackgroundPaint(); Color bgColor, liColor; if (color.equals(Color.lightGray)) { bgColor = Color.white; liColor = Color.lightGray; } else { bgColor = Color.lightGray; liColor = Color.white; } getChart().getPlot().setBackgroundPaint(bgColor); getChart().getXYPlot().setDomainGridlinePaint(liColor); getChart().getXYPlot().setRangeGridlinePaint(liColor); } public XYPlot getXYPlot() { return plot; } public synchronized void addTICDataset(final XYDataset dataSet) { // Check if the dataSet to be added is compatible with the type of plot. if ((dataSet instanceof TICDataSet) && (((TICDataSet) dataSet).getPlotType() != this.plotType)) throw new IllegalArgumentException("Added dataset of class '" + dataSet.getClass() + "' does not have a compatible plotType. Expected '" + this.plotType.toString() + "'"); try { final TICPlotRenderer renderer = (TICPlotRenderer) defaultRenderer.clone(); // SimpleColorPalette palette = MZmineCore.getConfiguration().getDefaultColorPalette(); // final Color rendererColor = palette.getAWT(numOfDataSets % palette.size()); // renderer.setSeriesPaint(0, rendererColor); // renderer.setSeriesFillPaint(0, rendererColor); renderer.setSeriesPaint(0, plot.getDrawingSupplier().getNextPaint()); renderer.setSeriesFillPaint(0, plot.getDrawingSupplier().getNextFillPaint()); renderer.setSeriesShape(0, DATA_POINT_SHAPE); renderer.setDefaultItemLabelsVisible(labelsVisible == 1); addDataSetRenderer(dataSet, renderer); numOfDataSets++; // Enable remove plot menu // if (visualizer instanceof TICVisualizerWindow && numOfDataSets > 1) { // RemoveFilePopupMenu.setEnabled(true); // } } catch (CloneNotSupportedException e) { logger.log(Level.WARNING, "Unable to clone renderer", e); } } public synchronized void addPeakDataset(final XYDataset dataSet) { final PeakTICPlotRenderer renderer = new PeakTICPlotRenderer(); renderer.setDefaultToolTipGenerator(new TICToolTipGenerator()); // renderer.setSeriesPaint(0, PEAK_COLORS[numOfPeaks % PEAK_COLORS.length]); addDataSetRenderer(dataSet, renderer); numOfPeaks++; } // add data set public synchronized void addDataSet(XYDataset dataSet, Color color, boolean transparency) { XYItemRenderer newRenderer = new DefaultXYItemRenderer(); newRenderer.setDefaultFillPaint(color); plot.setDataset(numOfDataSets, dataSet); plot.setRenderer(numOfDataSets, newRenderer); numOfDataSets++; } public synchronized void addLabelledPeakDataset(final XYDataset dataSet, final String label) { // Add standard peak data set. addPeakDataset(dataSet); // Do we have a label? if (label != null && label.length() > 0) { // Add peak label renderer and data set. final XYLineAndShapeRenderer renderer = new XYLineAndShapeRenderer(false, false); renderer.setDefaultItemLabelsVisible(labelsVisible == 2); renderer.setDefaultItemLabelPaint(theme.getItemLabelPaint()); addDataSetRenderer(dataSet, renderer); renderer.setDrawSeriesLineAsPath(true); renderer.setDefaultItemLabelGenerator(new XYItemLabelGenerator() { @Override public String generateLabel(final XYDataset xyDataSet, final int series, final int item) { return ((PeakDataSet) xyDataSet).isPeak(item) ? label : null; } }); havePeakLabels = true; } } public void removeAllTICDataSets() { final int dataSetCount = plot.getDatasetCount(); for (int index = 0; index < dataSetCount; index++) { plot.setDataset(index, null); } numOfPeaks = 0; numOfDataSets = 0; } public void setTitle(final String titleText, final String subTitleText) { chartTitle.setText(titleText); chartSubTitle.setText(subTitleText); } public void setPlotType(final TICPlotType plotType) { if (this.plotType == plotType) return; /* * // Plot type if (visualizer instanceof TICVisualizerWindow) { this.plotType = * ((TICVisualizerWindow) visualizer).getPlotType(); } else { } */ this.plotType = plotType; // Y-axis label. String yAxisLabel = (this.plotType == TICPlotType.BASEPEAK) ? "Base peak intensity" : "Total ion intensity"; getXYPlot().getRangeAxis().setLabel(yAxisLabel); } private void addDataSetRenderer(final XYDataset dataSet, final XYItemRenderer renderer) { final int index = numOfDataSets + numOfPeaks; plot.setRenderer(index, renderer); plot.setDataset(index, dataSet); } }