/* * Copyright 2006-2016 The MZmine 3 Development Team * * This file is part of MZmine 3. * * MZmine 3 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 3 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 3; if not, * write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 * USA */ package io.github.mzmine.modules.plots.chromatogram; import java.awt.BasicStroke; import java.awt.Font; import java.awt.Stroke; import java.awt.geom.Ellipse2D; import java.io.File; import java.net.URL; import java.text.DecimalFormat; import org.jfree.chart.JFreeChart; import org.jfree.chart.axis.NumberAxis; import org.jfree.chart.block.BlockBorder; import org.jfree.chart.labels.XYItemLabelGenerator; import org.jfree.chart.plot.DatasetRenderingOrder; import org.jfree.chart.plot.XYPlot; 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.RangeType; import org.jfree.ui.RectangleInsets; import com.sun.javafx.tk.Toolkit; import io.github.msdk.datamodel.chromatograms.Chromatogram; import io.github.mzmine.main.MZmineCore; import io.github.mzmine.modules.plots.chromatogram.datasets.ChromatogramDataSet; import io.github.mzmine.util.JavaFXUtil; import io.github.mzmine.util.jfreechart.ChartNodeJFreeChart; import io.github.mzmine.util.jfreechart.IntelligentItemLabelGenerator; import io.github.mzmine.util.jfreechart.JFreeChartUtils; import io.github.mzmine.util.jfreechart.JFreeChartUtils.ImgFileType; import io.github.mzmine.util.jfreechart.ManualZoomDialog; import javafx.application.Platform; import javafx.beans.property.BooleanProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.collections.FXCollections; import javafx.collections.ListChangeListener.Change; import javafx.collections.ObservableList; import javafx.event.Event; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.scene.Cursor; import javafx.scene.control.Menu; import javafx.scene.control.MenuItem; import javafx.scene.layout.BorderPane; import javafx.scene.paint.Color; import javafx.stage.Modality; import javafx.stage.Stage; import javafx.stage.Window; /** * Chromatogram plot window */ public class ChromatogramPlotWindowController { private final ObservableList<ChromatogramPlotDataSet> datasets = FXCollections.observableArrayList(); private int numberOfDataSets = 0; // Colors private static final Color gridColor = Color.rgb(220, 220, 220, 0.5); private static final Color crossHairColor = Color.GRAY; private static final Color labelsColor = Color.BLACK; private static final Color backgroundColor = Color.WHITE; private static final Color[] plotColors = {Color.rgb(0, 0, 192), // blue Color.rgb(192, 0, 0), // red Color.rgb(0, 192, 0), // green Color.MAGENTA, Color.CYAN, Color.ORANGE}; private static final Font titleFont = new Font("SansSerif", Font.BOLD, 12); private static final String LAYERS_DIALOG_FXML = "ChromatogramLayersDialog.fxml"; private final BooleanProperty itemLabelsVisible = new SimpleBooleanProperty(this, "itemLabelsVisible", true); private final BooleanProperty legendVisible = new SimpleBooleanProperty(this, "legendVisible", true); private File lastSaveDirectory; private TextTitle chartTitle, chartSubTitle; @FXML private BorderPane chartPane; @FXML private ChartNodeJFreeChart chartNode; @FXML private MenuItem showSpectrumMenuItem; @FXML private Menu removeDatasetMenu; @FXML public void initialize() { final JFreeChart chart = chartNode.getChart(); final XYPlot plot = chart.getXYPlot(); // Do not set colors and strokes dynamically. They are instead provided // by the dataset and configured in configureRenderer() plot.setDrawingSupplier(null); plot.setDomainGridlinePaint(JavaFXUtil.convertColorToAWT(gridColor)); plot.setRangeGridlinePaint(JavaFXUtil.convertColorToAWT(gridColor)); plot.setBackgroundPaint(JavaFXUtil.convertColorToAWT(backgroundColor)); plot.setAxisOffset(new RectangleInsets(5.0, 5.0, 5.0, 5.0)); plot.setDatasetRenderingOrder(DatasetRenderingOrder.FORWARD); plot.setDomainCrosshairPaint(JavaFXUtil.convertColorToAWT(crossHairColor)); plot.setRangeCrosshairPaint(JavaFXUtil.convertColorToAWT(crossHairColor)); plot.setDomainCrosshairVisible(true); plot.setRangeCrosshairVisible(true); // chart properties chart.setBackgroundPaint(JavaFXUtil.convertColorToAWT(backgroundColor)); // legend properties LegendTitle legend = chart.getLegend(); // legend.setItemFont(legendFont); legend.setFrame(BlockBorder.NONE); // set the X axis (retention time) properties NumberAxis xAxis = (NumberAxis) plot.getDomainAxis(); xAxis.setLabel("Retention time (min)"); xAxis.setUpperMargin(0.03); xAxis.setLowerMargin(0.03); xAxis.setRangeType(RangeType.POSITIVE); xAxis.setTickLabelInsets(new RectangleInsets(0, 0, 20, 20)); // set the Y axis (intensity) properties NumberAxis yAxis = (NumberAxis) plot.getRangeAxis(); yAxis.setLabel("Intensity"); yAxis.setRangeType(RangeType.POSITIVE); yAxis.setAutoRangeIncludesZero(true); // set the fixed number formats, because otherwise JFreeChart sometimes // shows exponent, sometimes it doesn't DecimalFormat mzFormat = MZmineCore.getConfiguration().getMZFormat(); xAxis.setNumberFormatOverride(mzFormat); DecimalFormat intensityFormat = MZmineCore.getConfiguration().getIntensityFormat(); yAxis.setNumberFormatOverride(intensityFormat); chartTitle = chartNode.getChart().getTitle(); chartTitle.setMargin(5, 0, 0, 0); chartTitle.setFont(titleFont); chartTitle.setText("Chromatogram"); chartNode.setCursor(Cursor.CROSSHAIR); // Remove the dataset if it is removed from the list datasets.addListener((Change<? extends ChromatogramPlotDataSet> c) -> { while (c.next()) { if (c.wasRemoved()) { for (ChromatogramPlotDataSet ds : c.getRemoved()) { int index = plot.indexOf(ds); plot.setDataset(index, null); } } } }); itemLabelsVisible.addListener((prop, oldVal, newVal) -> { for (ChromatogramPlotDataSet dataset : datasets) { int datasetIndex = plot.indexOf(dataset); XYItemRenderer renderer = plot.getRenderer(datasetIndex); renderer.setBaseItemLabelsVisible(newVal); } }); legendVisible.addListener((prop, oldVal, newVal) -> { legend.setVisible(newVal); }); } void addChromatogram(Chromatogram chromatogram, String title) { if (!Platform.isFxApplicationThread()) { throw new IllegalStateException( "Not on FX application thread; currentThread = " + Thread.currentThread().getName()); } final int datasetIndex = numberOfDataSets; numberOfDataSets++; ChromatogramDataSet newDataSet = new ChromatogramDataSet(chromatogram, title); datasets.add(newDataSet); final XYPlot plot = chartNode.getChart().getXYPlot(); final Color newColor = plotColors[datasetIndex % plotColors.length]; newDataSet.setColor(newColor); configureRenderer(newDataSet, datasetIndex); newDataSet.colorProperty().addListener(e -> { Platform.runLater(() -> configureRenderer(newDataSet, datasetIndex)); }); newDataSet.lineThicknessProperty().addListener(e -> { Platform.runLater(() -> configureRenderer(newDataSet, datasetIndex)); }); newDataSet.showDataPointsProperty().addListener(e -> { Platform.runLater(() -> configureRenderer(newDataSet, datasetIndex)); }); // Once everything is configured, add the dataset to the plot plot.setDataset(datasetIndex, newDataSet); } private void configureRenderer(ChromatogramPlotDataSet dataset, int datasetIndex) { final XYPlot plot = chartNode.getChart().getXYPlot(); XYLineAndShapeRenderer newRenderer = new XYLineAndShapeRenderer(); final int lineThickness = dataset.getLineThickness(); newRenderer.setBaseShape(new Ellipse2D.Double(-2 * lineThickness, -2 * lineThickness, 4 * lineThickness + 1, 4 * lineThickness + 1)); newRenderer.setBaseShapesFilled(true); newRenderer.setBaseShapesVisible(dataset.getShowDataPoints()); newRenderer.setDrawOutlines(false); Stroke baseStroke = new BasicStroke(lineThickness); newRenderer.setBaseStroke(baseStroke); // Set tooltips for legend newRenderer.setLegendItemToolTipGenerator((ds, series) -> { if (ds instanceof ChromatogramPlotDataSet) { return ((ChromatogramPlotDataSet) ds).getDescription(); } else return null; }); // Set color Color baseColor = dataset.getColor(); newRenderer.setBasePaint(JavaFXUtil.convertColorToAWT(baseColor)); // Set label generator XYItemLabelGenerator intelligentLabelGenerator = new IntelligentItemLabelGenerator(chartNode, 100, dataset); newRenderer.setBaseItemLabelGenerator(intelligentLabelGenerator); newRenderer.setBaseItemLabelPaint(JavaFXUtil.convertColorToAWT(labelsColor)); newRenderer.setBaseItemLabelsVisible(itemLabelsVisible.get()); newRenderer.setBaseItemLabelsVisible(true); // Set tooltip generator newRenderer.setBaseToolTipGenerator(dataset); plot.setRenderer(datasetIndex, newRenderer); } public void handleShowSpectrum(Event e) { } public void handleNormalizeIntensityScale(Event e) { } public void handleResetIntensityScale(Event e) { } public void handleSetupLayers(Event e) { try { URL layersDialogFXML = getClass().getResource(LAYERS_DIALOG_FXML); FXMLLoader loader = new FXMLLoader(layersDialogFXML); Stage layersDialog = loader.load(); ChromatogramLayersDialogController controller = loader.getController(); controller.configure(datasets, this); layersDialog.initModality(Modality.APPLICATION_MODAL); layersDialog.show(); } catch (Exception ex) { ex.printStackTrace(); } } public void handleContextMenuShowing(Event e) { } public void handleChartMousePressed(Event e) { } public void handleChartKeyPressed(Event e) { } public void handleAddChromatogramFromFile(Event e) { } public void handleAddChromatogramFromText(Event e) { } public void handleCopyImage(Event e) { JFreeChartUtils.exportToClipboard(chartNode); } public void handleCopyChromatogram(Event e) { } public void handleExportJPG(Event event) { JFreeChartUtils.showSaveDialog(chartNode, ImgFileType.JPG); } public void handleExportPNG(Event event) { JFreeChartUtils.showSaveDialog(chartNode, ImgFileType.PNG); } public void handleExportPDF(Event event) { JFreeChartUtils.showSaveDialog(chartNode, ImgFileType.PDF); } public void handleExportSVG(Event event) { JFreeChartUtils.showSaveDialog(chartNode, ImgFileType.SVG); } public void handleExportEMF(Event event) { JFreeChartUtils.showSaveDialog(chartNode, ImgFileType.EMF); } public void handleExportEPS(Event event) { JFreeChartUtils.showSaveDialog(chartNode, ImgFileType.EPS); } public void handleExportMzML(Event e) {} public void handleExportCSV(Event e) {} public void handleExportTXT(Event e) {} public void handleManualZoom(Event e) { XYPlot plot = chartNode.getChart().getXYPlot(); Window parent = chartNode.getScene().getWindow(); ManualZoomDialog dialog = new ManualZoomDialog(parent, plot); dialog.show(); } public void handleZoomOut(Event e) { XYPlot plot = chartNode.getChart().getXYPlot(); plot.getDomainAxis().setAutoRange(true); plot.getRangeAxis().setAutoRange(true); } public void handleToggleLabels(Event event) { itemLabelsVisible.set(!itemLabelsVisible.get()); } public void handleToggleLegend(Event event) { legendVisible.set(!legendVisible.get()); } public void handlePrint(Event event) { JFreeChartUtils.printChart(chartNode); } }