Java tutorial
/******************************************************************************* * 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); } } }