pipeline.GUI_utils.XYScatterPlotView.java Source code

Java tutorial

Introduction

Here is the source code for pipeline.GUI_utils.XYScatterPlotView.java

Source

/*******************************************************************************
 * Parismi v0.1
 * Copyright (c) 2009-2015 Cinquin Lab.
 * All rights reserved. This code is made available under a dual license:
 * the two-clause BSD license or the GNU Public License v2.
 ******************************************************************************/
package pipeline.GUI_utils;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.HashMap;

import javax.swing.DefaultComboBoxModel;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTable;
import javax.swing.WindowConstants;
import javax.swing.table.DefaultTableModel;

import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.plot.DatasetRenderingOrder;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.StandardXYItemRenderer;
import org.jfree.data.general.AbstractSeriesDataset;
import org.jfree.data.statistics.HistogramDataset;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
import org.jfree.ui.RectangleInsets;

import pipeline.data.IPluginIO;
import pipeline.misc_util.Utils;
import pipeline.misc_util.Utils.LogLevel;
import pipeline.parameter_cell_views.FloatSlider;
import pipeline.parameters.AbstractParameter;
import pipeline.parameters.FloatParameter;
import pipeline.parameters.ParameterListener;

/**
 * The type parameterization could be improved.
 *
 * @param <CollectionType>
 *            The type of series collection to be plotted. At this point, this is meant to specify whether we're dealing
 *            with collections of histograms or collections of X,Y pairs for scatter plots.
 * @param <SeriesWithTrendline>
 *            The type of the series stored in the collection. At this point both histograms and XY scatter plots
 *            are stored as XYSeriesReflection ; we are using this even for histograms because that makes it possible
 *            to reuse the code from that class that automatically reads values from a PluginIOCells generated by a
 *            pipeline plugin.
 */
public class XYScatterPlotView<CollectionType extends AbstractSeriesDataset & FreeChartSeriesCollectionManipulator, SeriesWithTrendline extends XYSeriesE>
        extends PluginIOView {

    private HashMap<String, XYSeriesE> scatterSeries;

    private transient JFrame windowWithGraph;
    private String windowTitle = "SkeletonGraph display";

    private transient plotGUIDisplay contentPanel;

    private transient JTable statisticsTable;
    private transient DefaultTableModel statData;

    private static final int meanColumn = 0;
    private static final int medianColumn = 1;
    private static final int stdDevColumn = 2;
    private static final int conf95Column = 3;
    private static final int conf84Column = 4;
    private static final int nDataPointsColumn = 5;
    private static final int sumColumn = 6;
    private static final int nStatColumns = 7;
    private static final String[] statColumnNames = { "Mean", "Median", "Std dev", "Conf 95", "Conf 84", "N",
            "Sum" };

    private String currentSelectedChannelName;
    @SuppressWarnings("rawtypes")
    private transient DefaultComboBoxModel channelBoxModel;

    private transient CollectionType freeChartSeriesCollection;
    private transient CollectionType trendLineCollection;
    private transient XYSeriesE currentSeries;

    private class plotGUIDisplay extends JPanel implements ActionListener {
        private static final long serialVersionUID = 1L;
        private transient ChartPanel chartPanel;
        private transient JPanel controlPanel;
        private transient JFreeChart chart;

        @SuppressWarnings("rawtypes")
        JComboBox channelSelectionBox;
        JCheckBox drawLines;
        JCheckBox flipAxes;

        public void recreateChart() {
            if (chartPanel != null)
                remove(chartPanel);

            statData.setRowCount(1);

            if (freeChartSeriesCollection.getSeries().size() == 0)
                return;

            final XYPlot plot;

            if (chart != null && chart.getXYPlot().getDataset() != null) {
                chart.getXYPlot().getDataset(0).removeChangeListener(chart.getXYPlot());
            }

            if (freeChartSeriesCollection.getSeries().get(0) instanceof XYSeries) {
                PlotOrientation orientation = flipAxes.isSelected() ? PlotOrientation.HORIZONTAL
                        : PlotOrientation.VERTICAL;
                if (drawLines.isSelected())
                    chart = ChartFactory.createXYLineChart(null, // chart title
                            currentSeries.displayNameForXSeries, // "Category", // domain axis label
                            currentSeries.displayNameForYSeries, // "Value", // range axis label
                            (XYSeriesCollection) freeChartSeriesCollection, // data
                            orientation, false, // include legend
                            true, false);
                else
                    chart = ChartFactory.createScatterPlot(null, // chart title
                            currentSeries.displayNameForXSeries, // "Category", // domain axis label
                            currentSeries.displayNameForYSeries, // "Value", // range axis label
                            (XYSeriesCollection) freeChartSeriesCollection, // data
                            orientation, false, // include legend
                            true, false);

                plot = chart.getXYPlot();
                plot.setDataset(1, (XYSeriesCollection) trendLineCollection);

                try {
                    double[] values = ((XYSeriesReflection) (freeChartSeriesCollection).getSeries().get(0))
                            .getXValues();
                    double mean = Utils.mean(values);
                    double stdDev = Utils.stdDev(values, mean);
                    double median = Utils.median(values);
                    double conf95 = Utils.confidence(0.05, values.length, stdDev);
                    double conf84 = Utils.confidence(0.16, values.length, stdDev);
                    int nDataPoints = values.length;

                    statData.addRow((Object[]) null);
                    statData.setValueAt(mean, 1, meanColumn);
                    statData.setValueAt(stdDev, 1, stdDevColumn);
                    statData.setValueAt(median, 1, medianColumn);
                    statData.setValueAt(conf95, 1, conf95Column);
                    statData.setValueAt(conf84, 1, conf84Column);
                    statData.setValueAt(mean * nDataPoints, 1, sumColumn);
                    statData.setValueAt(nDataPoints, 1, nDataPointsColumn);
                } catch (Exception e) {
                    Utils.log("Error computing statistics for scatterplot", LogLevel.ERROR);
                    Utils.printStack(e);
                }
            } else {
                chart = ChartFactory.createHistogram(null, // chart title
                        null, // "Category", // domain axis label
                        currentSeries.displayNameForXSeries, // "Value", // range axis label
                        (HistogramDataset) freeChartSeriesCollection, // data
                        PlotOrientation.VERTICAL, false, // include legend
                        true, false);
                plot = chart.getXYPlot();
                plot.setDataset(1, (HistogramDataset) trendLineCollection);

                try {
                    double[] values = ((XYSeriesE) ((HashMap<?, ?>) (freeChartSeriesCollection).getSeries().get(0))
                            .get("XYSeries")).getXValues();
                    double mean = Utils.mean(values);
                    double stdDev = Utils.stdDev(values, mean);
                    double median = Utils.median(values);
                    double conf95 = Utils.confidence(0.05, values.length, stdDev);
                    double conf84 = Utils.confidence(0.16, values.length, stdDev);
                    int nDataPoints = values.length;

                    statData.addRow((Object[]) null);
                    statData.setValueAt(mean, 1, meanColumn);
                    statData.setValueAt(stdDev, 1, stdDevColumn);
                    statData.setValueAt(median, 1, medianColumn);
                    statData.setValueAt(conf95, 1, conf95Column);
                    statData.setValueAt(conf84, 1, conf84Column);
                    statData.setValueAt(mean * nDataPoints, 1, sumColumn);
                    statData.setValueAt(nDataPoints, 1, nDataPointsColumn);
                } catch (Exception e) {
                    Utils.log("Error computing statistics for histogram", LogLevel.ERROR);
                    Utils.printStack(e);
                }

            }

            final NumberAxis domainAxis = new NumberAxis(currentSeries.displayNameForXSeries);
            domainAxis.setAutoRangeIncludesZero(false);
            domainAxis.setTickLabelFont(new Font("Times", 0, 20));

            final NumberAxis rangeAxis = new NumberAxis(currentSeries.displayNameForYSeries);
            rangeAxis.setAutoRange(true);
            rangeAxis.setVisible(true);
            plot.setDomainAxis(domainAxis);
            plot.setRangeAxis(rangeAxis);
            chart.setBackgroundPaint(Color.white);
            chart.setPadding(new RectangleInsets(0, 0, 0, 0));

            plot.setBackgroundImage(null);
            plot.setBackgroundPaint(Color.white);
            plot.setOutlinePaint(Color.black);

            if (drawLines.isSelected())
                plot.setRenderer(1, new StandardXYItemRenderer(StandardXYItemRenderer.LINES));
            else
                plot.setRenderer(1, new StandardXYItemRenderer(StandardXYItemRenderer.SHAPES));

            plot.setDatasetRenderingOrder(DatasetRenderingOrder.FORWARD);

            chartPanel = new ChartPanel(chart);

            int constrainedHeight;
            int constrainedWidth;

            constrainedHeight = 150;
            constrainedWidth = 400;

            chartPanel.setPreferredSize(new java.awt.Dimension(constrainedWidth, constrainedHeight));
            chartPanel.setMaximumDrawHeight(constrainedHeight);
            chartPanel.setMaximumDrawWidth(constrainedWidth);
            chartPanel.setMaximumDrawHeight(constrainedHeight);
            chartPanel.setMaximumDrawWidth(constrainedWidth);
            chartPanel.setMouseWheelEnabled(true);
            chartPanel.setMouseZoomable(true);
            chartPanel.setRangeZoomable(true);

            ((XYPlot) chart.getPlot()).getRenderer().setSeriesStroke(0, new BasicStroke(0.5f));
            ((XYPlot) chart.getPlot()).getRenderer().setSeriesShape(0, new java.awt.Rectangle(0, 0, 1, 1));

            // chartPanel.addMouseListener(this);
            // chartPanel.addMouseMotionListener(this);

            GridBagConstraints c = new GridBagConstraints();
            c.fill = GridBagConstraints.BOTH;
            c.gridx = 0;
            c.gridy = 0;
            c.weighty = 1.0;
            c.weightx = 1.0;

            add(chartPanel, c);
            if (windowWithGraph != null) {
                windowWithGraph.pack();
                windowWithGraph.repaint();
            }
        }

        private class boxListener implements ActionListener {
            @Override
            public void actionPerformed(ActionEvent e) {
                if (!EventQueue.isDispatchThread()) {
                    Utils.log("We're in comboBox listener, but thread is not the Swing one.", LogLevel.ERROR);
                }

                @SuppressWarnings("rawtypes")
                JComboBox source = channelSelectionBox;
                if (source != null) {
                    String s = (String) source.getSelectedItem();
                    if (s != null) {
                        currentSelectedChannelName = s;

                        selectChannel(currentSelectedChannelName);
                    } else {
                    }
                }

            }
        }

        private class smoothingScaleListener implements ParameterListener {

            @Override
            public void buttonPressed(String commandName, AbstractParameter parameter, ActionEvent event) {
            }

            @Override
            public void parameterValueChanged(boolean stillChanging, AbstractParameter parameterWhoseValueChanged,
                    boolean keepQuiet) {
                if (currentSeries != null) {
                    currentSeries.setSmoothingScale(((FloatParameter) parameterWhoseValueChanged).getFloatValue(),
                            true);
                    trendLineCollection.removeAllSeries();
                    trendLineCollection.addSeries(currentSeries.trendLine);
                }
            }

            @Override
            public void parameterPropertiesChanged(AbstractParameter parameterWhosePropertiesChanged) {
            }

            @Override
            public boolean alwaysNotify() {
                return false;
            }

            @Override
            public String getParameterName() {
                throw new RuntimeException("Unimplemented");
            }

            @Override
            public void setParameterName(String name) {
                throw new RuntimeException("Unimplemented");
            }
        }

        @SuppressWarnings({ "rawtypes", "unchecked" })
        public plotGUIDisplay() {
            setLayout(new GridBagLayout());
            GridBagConstraints c = new GridBagConstraints();
            c.fill = GridBagConstraints.BOTH;

            controlPanel = new JPanel();

            DefaultTableModel smoothingTableModel = new DefaultTableModel(1, 1);
            JXTableBetterFocus smoothingParameterTable = new JXTableBetterFocus(smoothingTableModel);
            smoothingParameterTable.setFillsViewportHeight(false);
            smoothingParameterTable.getColumn(0).setCellEditor(new FloatSlider());
            smoothingParameterTable.getColumn(0).setCellRenderer(new FloatSlider());
            smoothingTableModel.setValueAt(new FloatParameter("Averaging window",
                    "Smoothing curve is created by averaging in a sliding window of this length", 0.0f, 0.0f,
                    100.0f, true, true, true, new smoothingScaleListener()), 0, 0);

            smoothingParameterTable.setPreferredSize(new Dimension(300, 75));
            controlPanel.add(smoothingParameterTable);

            JButton save = new JButton("Save source data");
            save.setActionCommand("Save source data");
            save.addActionListener(this);
            controlPanel.add(save);

            JButton split = new JButton("Split");
            split.setActionCommand("Split");
            split.addActionListener(this);
            controlPanel.add(split);

            Object[] empty_array = {};
            channelSelectionBox = new javax.swing.JComboBox(empty_array);
            channelSelectionBox.addActionListener(new boxListener());
            channelBoxModel = (DefaultComboBoxModel) channelSelectionBox.getModel();
            controlPanel.add(channelSelectionBox);

            drawLines = new JCheckBox("Draw lines");
            drawLines.setSelected(true);
            drawLines.setActionCommand("Draw lines");
            drawLines.addActionListener(this);
            controlPanel.add(drawLines);

            flipAxes = new JCheckBox("Flip axes");
            flipAxes.setSelected(false);
            flipAxes.setActionCommand("Flip axes");
            flipAxes.addActionListener(this);
            controlPanel.add(flipAxes);

            controlPanel.setMaximumSize(new Dimension(200, 20));

            c.gridx = 0;
            c.gridy = 1;
            c.weighty = 0.1;
            c.weightx = 1.0;

            statData = new DefaultTableModel(2, nStatColumns);
            for (int i = 0; i < statColumnNames.length; i++) {
                statData.setValueAt(statColumnNames[i], 0, i);
            }
            statisticsTable = new JTable(statData);
            add(statisticsTable, c);

            c.gridx = 0;
            c.gridy = 2;
            c.weighty = 0.1;
            c.weightx = 1.0;
            add(controlPanel, c);

        }

        @Override
        public void actionPerformed(ActionEvent e) {
            // TODO Auto-generated method stub
            if (e.getActionCommand().equals("Draw lines") || e.getActionCommand().equals("Flip axes")) {
                currentSeries.setFlipAxes(contentPanel.flipAxes.isSelected());
                contentPanel.recreateChart();
            }
            Utils.log("ACTION", LogLevel.DEBUG);
        }
    }

    public void addSeries(String name, XYSeriesE series) {
        initializeSeries();
        scatterSeries.put(name, series);
        updateChannelChoices();
    }

    public CollectionType getCollection() {
        return freeChartSeriesCollection;
    }

    @SuppressWarnings("unchecked")
    private void selectChannel(String channelName) {
        XYSeriesE newSelection = scatterSeries.get(channelName);
        if (newSelection == null)
            throw new IllegalArgumentException("Channel " + channelName + " not present in "
                    + Utils.printStringArray(scatterSeries.keySet().toArray(new String[] {})));

        if (currentSeries != null)
            freeChartSeriesCollection.removeAllSeries();
        // Utils.log("Switching to series "+newSelection,LogLevel.VERBOSE_DEBUG);
        if (freeChartSeriesCollection == null) {
            // if (newSelection instanceof XYSeriesReflection){
            freeChartSeriesCollection = (CollectionType) new XYSeriesCollectionGenericManipulator();
            trendLineCollection = (CollectionType) new XYSeriesCollectionGenericManipulator();
            /*
             * } else {
             * freeChartSeriesCollection=(CollectionType) new BetterHistogramDataset();
             * trendLineCollection=(CollectionType) new BetterHistogramDataset();
             * }
             */
        }
        freeChartSeriesCollection.addSeries(newSelection);
        trendLineCollection.removeAllSeries();
        trendLineCollection.addSeries(newSelection.trendLine);
        currentSeries = newSelection;
        contentPanel.recreateChart();
    }

    @SuppressWarnings("unchecked")
    private void updateChannelChoices() {

        channelBoxModel.removeAllElements();

        scatterSeries.keySet().forEach(channelBoxModel::addElement);

        // If there was no channel to display before, display the first one in the list
        if ((currentSelectedChannelName == null) && scatterSeries.size() > 0) {
            selectChannel((String) channelBoxModel.getSelectedItem());
        }
        currentSelectedChannelName = (String) channelBoxModel.getSelectedItem();
    }

    private void initializeSeries() {
        if (scatterSeries == null)
            scatterSeries = new HashMap<>();
    }

    @Override
    public void close() {
        // Utils.log("Closing plot view",LogLevel.DEBUG);
        if (contentPanel != null) {
            if (contentPanel.chart != null && contentPanel.chart.getXYPlot().getDataset() != null) {
                contentPanel.chart.getXYPlot().getDataset(0).removeChangeListener(contentPanel.chart.getXYPlot());
            }
        }
        if (windowWithGraph != null) {
            windowWithGraph.setVisible(false);
            windowWithGraph.dispose();
            windowWithGraph = null;
        }
    }

    @SuppressWarnings("unchecked")
    public XYScatterPlotView(int seriesType) {
        if (seriesType == 0) {
            freeChartSeriesCollection = (CollectionType) new XYSeriesCollectionGenericManipulator();
            trendLineCollection = (CollectionType) new XYSeriesCollectionGenericManipulator();
        } else if (seriesType == 1) {
            freeChartSeriesCollection = (CollectionType) new BetterHistogramDataset();
            trendLineCollection = (CollectionType) new BetterHistogramDataset();
        }
        initializeSeries();
        contentPanel = new plotGUIDisplay();
    }

    @Override
    public void show() {
        initializeSeries();
        if (windowWithGraph == null) {
            try {
                Runnable r = () -> {
                    windowWithGraph = new JFrame(windowTitle);
                    windowWithGraph.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);

                    if (contentPanel == null)
                        contentPanel = new plotGUIDisplay();

                    contentPanel.setOpaque(true);
                    windowWithGraph.setContentPane(contentPanel);

                    // Display the window.
                    windowWithGraph.pack();
                    windowWithGraph.setVisible(true);
                };
                if (java.awt.EventQueue.isDispatchThread())
                    r.run();
                else
                    javax.swing.SwingUtilities.invokeAndWait(r);
            } catch (Throwable e) {
                Utils.printStack(e);
            }
        } else
            windowWithGraph.setVisible(true);

        if ((currentSelectedChannelName == null) && freeChartSeriesCollection.getSeries().size() > 0) {// scatterSeries.size()>0
            selectChannel((String) channelBoxModel.getSelectedItem());
        }

    }

    @Override
    public void setData(IPluginIO data) {
        throw new RuntimeException("Unimplemented");
    }

    public void setWindowTitle(String title) {
        this.windowTitle = title;
        if (windowWithGraph != null) {
            Runnable r = () -> windowWithGraph.setTitle(title);
            if (java.awt.EventQueue.isDispatchThread())
                r.run();
            else
                javax.swing.SwingUtilities.invokeLater(r);
        }
    }

}