org.simbrain.plot.projection.ProjectionGui.java Source code

Java tutorial

Introduction

Here is the source code for org.simbrain.plot.projection.ProjectionGui.java

Source

/*
 * Part of Simbrain--a java-based neural network kit
 * Copyright (C) 2005,2007 The Authors.  See http://www.simbrain.net/credits
 *
 * This program 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.
 *
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
package org.simbrain.plot.projection;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Paint;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.geom.Ellipse2D;
import java.util.Map.Entry;
import java.util.concurrent.Executors;
import javax.swing.Action;
import javax.swing.Box;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.JToolBar;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.labels.CustomXYToolTipGenerator;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.data.xy.XYDataset;
import org.simbrain.plot.ChartListener;
import org.simbrain.plot.actions.PlotActionManager;
import org.simbrain.resource.ResourceManager;
import org.simbrain.util.SimbrainPreferences;
import org.simbrain.util.SimbrainPreferences.PropertyNotFoundException;
import org.simbrain.util.Utils;
import org.simbrain.util.genericframe.GenericFrame;
import org.simbrain.util.projection.DataPoint;
import org.simbrain.util.projection.DataPointColored;
import org.simbrain.util.projection.IterableProjectionMethod;
import org.simbrain.util.projection.ProjectCoordinate;
import org.simbrain.util.projection.ProjectSammon;
import org.simbrain.util.projection.ProjectionMethod;
import org.simbrain.util.projection.Projector;
import org.simbrain.util.projection.ProjectorListener;
import org.simbrain.util.widgets.ShowHelpAction;
import org.simbrain.workspace.component_actions.CloseAction;
import org.simbrain.workspace.gui.GuiComponent;

/**
 * Gui Component to display a high dimensional projection object.
 */
public class ProjectionGui extends GuiComponent<ProjectionComponent> {

    /** Open button. */
    private JButton openBtn = new JButton(ResourceManager.getImageIcon("Open.png"));

    /** Save button. */
    private JButton saveBtn = new JButton(ResourceManager.getImageIcon("Save.png"));

    /** Iterate once. */
    protected JButton iterateBtn = new JButton(ResourceManager.getImageIcon("Step.png"));

    /** Play button. */
    private JButton playBtn = new JButton(ResourceManager.getImageIcon("Play.png"));

    /** Preferences button. */
    private JButton prefsBtn = new JButton(ResourceManager.getImageIcon("Prefs.gif"));

    /** Clear button. */
    private JButton clearBtn = new JButton(ResourceManager.getImageIcon("Eraser.png"));

    /** Random button. */
    private JButton randomBtn = new JButton(ResourceManager.getImageIcon("Rand.png"));

    /** List of projector types. */
    private JComboBox<String> projectionList = new JComboBox<String>();

    /** Bottom panel. */
    private Box bottomPanel = Box.createVerticalBox();

    /** Toolbar for bottom panel. */
    private JToolBar theToolBar = new JToolBar();

    /** Status toolbar. */
    private JToolBar statusBar = new JToolBar();

    /** Error bar. */
    private JToolBar errorBar = new JToolBar();

    /** Points indicator. */
    private JLabel pointsLabel = new JLabel();

    /** Dimension indicator. */
    private JLabel dimsLabel = new JLabel(" Dimensions:");

    /** Error indicator. */
    private JLabel errorLabel = new JLabel();

    /** Show error option. */
    private boolean showError = true;

    /** Help action used in menu and button. */
    private ShowHelpAction helpAction = new ShowHelpAction("Pages/Plot/projection.html");

    /** Warning label. */
    private JLabel warningLabel = new JLabel(ResourceManager.getImageIcon("Warning.png"));

    /** Panel for showing Sammon step size and label, both with tooltip. */
    private Box sammonStepSizePanel = Box.createHorizontalBox();

    /** Shows the step size for Sammon map. */
    private JTextField sammonStepSize;

    /** Combo box for first dimension of coordinate projection. */
    private JComboBox<Integer> adjustDimension1 = new JComboBox<Integer>();

    /** Model for adjustDimension1. */
    private DefaultComboBoxModel<Integer> adjustDimension1Model = new DefaultComboBoxModel<Integer>();

    /** Combo box for first dimension of coordinate projection. */
    private JComboBox<Integer> adjustDimension2 = new JComboBox<Integer>();

    /** Model for adjustDimension2. */
    private DefaultComboBoxModel<Integer> adjustDimension2Model = new DefaultComboBoxModel<Integer>();

    /** Plot Action Manager. */
    private PlotActionManager actionManager;

    /** The JFreeChart chart. */
    private JFreeChart chart;

    /** The JFreeChart panel specialized for displaying JFreeCharts. */
    private ChartPanel panel;

    /**
     * Custom rendering of scatter plot points
     */
    private class CustomRenderer extends XYLineAndShapeRenderer {

        @Override
        public Paint getItemPaint(int row, int column) {
            Projector projector = getWorkspaceComponent().getProjectionModel().getProjector();
            DataPointColored point = ((DataPointColored) projector.getUpstairs().getPoint(column));
            if (point != null) {
                return point.getColor();
            } else {
                return Color.green;
            }
        }

    }

    /**
     * Datapoints return a tooltip showing the high dimensional point being
     * Represented by a given point in the plot.
     */
    private class CustomToolTipGenerator extends CustomXYToolTipGenerator {
        @Override
        public String generateToolTip(XYDataset data, int series, int item) {
            DataPoint point = getWorkspaceComponent().getProjector().getUpstairs().getPoint(item);
            if (point != null) {
                return Utils.doubleArrayToString(point.getVector());
            } else {
                return "null";
            }
        }
    }

    /**
     * Construct the Projection GUI.
     */
    public ProjectionGui(final GenericFrame frame, final ProjectionComponent component) {
        super(frame, component);
        setPreferredSize(new Dimension(500, 400));
        actionManager = new PlotActionManager(this);
        setLayout(new BorderLayout());

        // Generate the graph
        chart = ChartFactory.createScatterPlot("High Dimensional Projection", "Projection X", "Projection Y",
                getWorkspaceComponent().getProjectionModel().getDataset(), PlotOrientation.VERTICAL, false, true,
                false);
        // chart.getXYPlot().getDomainAxis().setRange(-100, 100);
        // chart.getXYPlot().getRangeAxis().setRange(-100, 100);
        chart.getXYPlot().setBackgroundPaint(Color.white);
        chart.getXYPlot().setDomainGridlinePaint(Color.gray);
        chart.getXYPlot().setRangeGridlinePaint(Color.gray);
        chart.getXYPlot().getDomainAxis().setAutoRange(true);
        chart.getXYPlot().getRangeAxis().setAutoRange(true);
        panel = new ChartPanel(chart);

        // Custom render points as dots (not squares) and use custom tooltips
        // that show high-d point
        CustomRenderer renderer = new CustomRenderer();
        chart.getXYPlot().setRenderer(renderer);
        renderer.setSeriesLinesVisible(0, false);
        renderer.setSeriesShape(0, new Ellipse2D.Double(-5, -5, 5, 5));
        CustomToolTipGenerator generator = new CustomToolTipGenerator();
        renderer.setSeriesToolTipGenerator(0, generator);

        // Toolbar
        openBtn.setToolTipText("Open high-dimensional data");
        saveBtn.setToolTipText("Save data");
        projectionList.setMaximumSize(new java.awt.Dimension(200, 100));
        iterateBtn.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                getWorkspaceComponent().getProjector().iterate();
                update();
            }
        });
        clearBtn.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                getWorkspaceComponent().getWorkspace().stop();
                Executors.newSingleThreadExecutor().execute(new Runnable() {
                    @Override
                    public void run() {
                        getWorkspaceComponent().clearData();
                    }
                });
            }
        });
        playBtn.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                if (getWorkspaceComponent().getProjectionModel().isRunning()) {
                    playBtn.setIcon(ResourceManager.getImageIcon("Stop.png"));
                    playBtn.setToolTipText("Stop iterating projection algorithm");
                    getWorkspaceComponent().getProjectionModel().setRunning(false);
                    Executors.newSingleThreadExecutor().execute(new ProjectionUpdater(getWorkspaceComponent()));
                } else {
                    playBtn.setIcon(ResourceManager.getImageIcon("Play.png"));
                    playBtn.setToolTipText("Start iterating projection algorithm");
                    getWorkspaceComponent().getProjectionModel().setRunning(true);
                }
            }
        });
        prefsBtn.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                // TODO (Still working out overall dialog structure).
            }
        });
        randomBtn.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                getWorkspaceComponent().getProjector().randomize(100);
            }
        });
        theToolBar.add(projectionList);
        playBtn.setToolTipText("Iterate projection algorithm");
        theToolBar.add(playBtn);
        iterateBtn.setToolTipText("Step projection algorithm");
        theToolBar.add(iterateBtn);
        clearBtn.setToolTipText("Clear current data");
        theToolBar.add(clearBtn);
        randomBtn.setToolTipText("Randomize datapoints");
        theToolBar.add(randomBtn);
        theToolBar.addSeparator();
        warningLabel.setPreferredSize(new Dimension(16, 16));
        warningLabel.setToolTipText("This method works best with more " + "datapoints already added");
        theToolBar.add(warningLabel);
        String stepSizeToolTip = "Scales the amount points are moved on each iteration";
        JLabel stepSizeLabel = new JLabel("Step Size");
        stepSizeLabel.setToolTipText(stepSizeToolTip);
        sammonStepSizePanel.add(stepSizeLabel);
        try {
            sammonStepSize = new JTextField("" + SimbrainPreferences.getDouble("projectorSammonEpsilon"));
        } catch (PropertyNotFoundException e1) {
            e1.printStackTrace();
        }
        sammonStepSize.setColumns(3);
        sammonStepSize.setToolTipText(stepSizeToolTip);
        sammonStepSizePanel.add(sammonStepSize);
        theToolBar.add(sammonStepSizePanel);
        adjustDimension1.setToolTipText("Dimension 1");
        adjustDimension2.setToolTipText("Dimension 2");
        theToolBar.add(adjustDimension1);
        theToolBar.add(adjustDimension2);

        // Help button
        JButton helpButton = new JButton();
        helpButton.setAction(helpAction);

        // Add/Remove dimension buttons
        JButton addButton = new JButton("Add Dimension");
        addButton.setActionCommand("Add");
        addButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                getWorkspaceComponent().getProjectionModel().addSource();
            }
        });
        JButton deleteButton = new JButton("Remove Dimension");
        deleteButton.setActionCommand("Delete");
        deleteButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                getWorkspaceComponent().getProjectionModel().removeSource();
            }
        });

        // Button Panel
        JPanel buttonPanel = new JPanel();
        buttonPanel.add(helpButton);
        buttonPanel.add(addButton);
        buttonPanel.add(deleteButton);

        // Setup Menu Bar
        createAttachMenuBar();

        // Status Bar
        statusBar.add(pointsLabel);
        statusBar.add(dimsLabel);
        errorBar.add(errorLabel);

        // Bottom panel
        bottomPanel.add("North", buttonPanel);
        JPanel southPanel = new JPanel();
        southPanel.add(errorBar);
        southPanel.add(statusBar);
        bottomPanel.add("South", southPanel);

        // Put all panels together
        add("North", theToolBar);
        add("Center", panel);
        add("South", bottomPanel);

        // Other initialization
        initializeComboBoxes();
        addListeners();
        updateToolBar();
        update();

    }

    /**
     * Initialize all the combo boxes.
     */
    private void initializeComboBoxes() {

        // Populate projection list combo box
        for (Entry<Class<?>, String> projMethod : getWorkspaceComponent().getProjector().getProjectionMethods()
                .entrySet()) {
            projectionList.addItem(projMethod.getValue());
        }
        projectionList.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                String selectedMethod = (String) projectionList.getSelectedItem();
                getWorkspaceComponent().getProjector().setProjectionMethod(selectedMethod);
                updateToolBar();
            }
        });
        projectionList.getModel().setSelectedItem(getWorkspaceComponent().getProjector().getCurrentMethodString());

        // Init the adjust dimension combo boxes
        updateCoordinateProjectionComboBoxes();
        adjustDimension1.setModel(adjustDimension1Model);
        try {
            adjustDimension1.setSelectedIndex(SimbrainPreferences.getInt("projectorHiD1"));
        } catch (PropertyNotFoundException e) {
            e.printStackTrace();
        }
        adjustDimension1.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                ProjectionMethod proj = getWorkspaceComponent().getProjector().getProjectionMethod();
                if (proj != null) {
                    if (proj instanceof ProjectCoordinate) {
                        ((ProjectCoordinate) proj).setHiD1(adjustDimension1.getSelectedIndex());
                        ((ProjectCoordinate) proj).project();
                        getWorkspaceComponent().getProjector().fireProjectorDataChanged();
                    }
                }
            }

        });
        adjustDimension2.setModel(adjustDimension2Model);
        try {
            adjustDimension2.setSelectedIndex(SimbrainPreferences.getInt("projectorHiD2"));
        } catch (PropertyNotFoundException e) {
            e.printStackTrace();
        }
        adjustDimension2.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                ProjectionMethod proj = getWorkspaceComponent().getProjector().getProjectionMethod();
                if (proj != null) {
                    if (proj instanceof ProjectCoordinate) {
                        ((ProjectCoordinate) proj).setHiD2(adjustDimension2.getSelectedIndex());
                        ((ProjectCoordinate) proj).project();
                        getWorkspaceComponent().getProjector().fireProjectorDataChanged();
                    }
                }
            }

        });
    }

    /**
     * Update the toolbar based on current projection method.
     */
    private void updateToolBar() {
        ProjectionMethod proj = getWorkspaceComponent().getProjector().getProjectionMethod();
        if (proj == null) {
            return;
        }

        // Clear unused toolbar items
        if (!(proj instanceof ProjectSammon)) {
            sammonStepSizePanel.setVisible(false);
        }
        if (!(proj instanceof ProjectCoordinate)) {
            adjustDimension1.setVisible(false);
            adjustDimension2.setVisible(false);
        } else {
            // Set correct dimensions
            adjustDimension1.setSelectedIndex(((ProjectCoordinate) proj).getHiD1());
            adjustDimension2.setSelectedIndex(((ProjectCoordinate) proj).getHiD2());
        }

        // Handle error bar
        if ((proj.isIterable()) && (showError)) {
            errorBar.setVisible(true);
        } else {
            errorBar.setVisible(false);
        }

        // Handle warning
        if (getWorkspaceComponent().getProjector().getNumPoints() < proj.suggestedMinPoints()) {
            warningLabel.setVisible(true);
        } else {
            warningLabel.setVisible(false);
        }

        // Handle new toolbar items
        if (proj instanceof ProjectSammon) {
            sammonStepSizePanel.setVisible(true);
        } else if (proj instanceof ProjectCoordinate) {
            adjustDimension1.setVisible(true);
            adjustDimension2.setVisible(true);
        }

        // Handle iterable
        setToolbarIterable(proj.isIterable());

    }

    /**
     * Add listeners. The chart listener mainly concerns workspace and gui level
     * stuff. The projector listener concerns the underlying projection model.
     */
    private void addListeners() {
        getWorkspaceComponent().getProjectionModel().addListener(new ChartListener() {

            /**
             * Update bottom stats when a data source is added.
             */
            public void dataSourceAdded(int index) {
                update();
                updateCoordinateProjectionComboBoxes();
            }

            /**
             * Update bottom stats when a data source is removed
             */
            public void dataSourceRemoved(int index) {
                update();
                updateCoordinateProjectionComboBoxes();
            }

            /**
             * {@inheritDoc}
             */
            public void chartInitialized(int numSources) {
                update();
            }

        });

        // Listen to events from the underlying projector model.
        // Currently the main action is to just update the labels at the bottom.
        getWorkspaceComponent().getProjectionModel().getProjector().addListener(new ProjectorListener() {

            @Override
            public void projectionMethodChanged() {
                // System.out.println("ProjectionGui: In method changed");
                update();
            }

            @Override
            public void projectorDataChanged() {
                // System.out.println("ProjectionGui: In data changed");
                update();
            }

            @Override
            public void datapointAdded() {
                // System.out.println("ProjectionGui: In data added");
            }

            @Override
            public void projectorColorsChanged() {
                // System.out.println("ProjectionGui: In colors changed");
                getWorkspaceComponent().getProjectionModel().getProjector().resetColors();
                update();
            }

        });

        // Epsilon field should update the model whenever a user clicks out of
        // it.
        sammonStepSize.addFocusListener(new FocusAdapter() {
            @Override
            public void focusLost(FocusEvent e) {
                ProjectionMethod proj = getWorkspaceComponent().getProjector().getProjectionMethod();
                if (proj != null) {
                    if (proj instanceof ProjectSammon) {
                        ((ProjectSammon) proj).setEpsilon(Double.parseDouble(sammonStepSize.getText()));
                    }
                }
            }
        });

    }

    /**
     * Update the Coordinate projection combo boxes.
     */
    private void updateCoordinateProjectionComboBoxes() {

        adjustDimension1Model.removeAllElements();
        adjustDimension2Model.removeAllElements();
        int dims = getWorkspaceComponent().getProjector().getDimensions();
        for (int i = 0; i < dims; i++) {
            adjustDimension1Model.addElement(i + 1);
            adjustDimension2Model.addElement(i + 1);
        }
    }

    /**
     * Initializes frame.
     */
    @Override
    public void postAddInit() {
    }

    /**
     * Creates the menu bar.
     */
    private void createAttachMenuBar() {

        final JMenuBar bar = new JMenuBar();
        final JMenu fileMenu = new JMenu("File");

        for (Action action : actionManager.getOpenSavePlotActions()) {
            fileMenu.add(action);
        }
        fileMenu.addSeparator();
        final JMenu exportImport = new JMenu("Export/Import...");
        fileMenu.add(exportImport);
        exportImport.add(ProjectionPlotActions.getImportData(getWorkspaceComponent().getProjectionModel()));
        exportImport.addSeparator();
        exportImport.add(ProjectionPlotActions.getExportDataHi(getWorkspaceComponent().getProjectionModel()));
        exportImport.add(ProjectionPlotActions.getExportDataLow(getWorkspaceComponent().getProjectionModel()));
        fileMenu.addSeparator();
        fileMenu.add(new CloseAction(this.getWorkspaceComponent()));

        final JMenu editMenu = new JMenu("Edit");
        final JMenuItem preferencesGeneral = new JMenuItem("General Preferences...");
        preferencesGeneral.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent arg0) {
                ProjectionPreferencesDialog dialog = new ProjectionPreferencesDialog(
                        getWorkspaceComponent().getProjectionModel().getProjector());
                dialog.pack();
                dialog.setLocationRelativeTo(null);
                dialog.setVisible(true);
            }

        });
        editMenu.add(preferencesGeneral);

        final JMenuItem colorPrefs = new JMenuItem("Datapoint Coloring...");
        colorPrefs.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent arg0) {
                DataPointColoringDialog dialog = new DataPointColoringDialog(
                        getWorkspaceComponent().getProjectionModel());
                dialog.pack();
                dialog.setLocationRelativeTo(null);
                dialog.setVisible(true);

            }

        });
        editMenu.add(colorPrefs);

        final JMenuItem dims = new JMenuItem("Set dimensions...");
        dims.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent arg0) {
                String dimensions = JOptionPane.showInputDialog("Dimensions:");
                if (dimensions != null) {
                    getWorkspaceComponent().getProjectionModel().getProjector().init(Integer.parseInt(dimensions));
                }

            }

        });
        // editMenu.add(dims);

        JMenu helpMenu = new JMenu("Help");
        JMenuItem helpItem = new JMenuItem(helpAction);
        helpMenu.add(helpItem);

        bar.add(fileMenu);
        bar.add(editMenu);
        bar.add(helpMenu);

        getParentFrame().setJMenuBar(bar);
    }

    @Override
    public void closing() {
    }

    /**
     * Update labels at bottom of component.
     */
    @Override
    protected void update() {
        super.update();
        chart.fireChartChanged();
        updateToolBar();
        dimsLabel.setText(
                "     Dimensions: " + getWorkspaceComponent().getProjector().getUpstairs().getDimensions());
        pointsLabel
                .setText("  Datapoints: " + getWorkspaceComponent().getProjector().getDownstairs().getNumPoints());
        if (getWorkspaceComponent().getProjector().getProjectionMethod().isIterable()) {
            errorLabel.setText(" Error:"
                    + ((IterableProjectionMethod) getWorkspaceComponent().getProjector().getProjectionMethod())
                            .getError());
        }
    }

    /**
     * Enable or disable buttons depending on whether the current projection
     * algorithm allows for iterations or not.
     *
     * @param b whether the current projection algorithm can be iterated
     */
    private void setToolbarIterable(final boolean b) {
        if (b) {
            playBtn.setEnabled(true);
            iterateBtn.setEnabled(true);
        } else {
            playBtn.setEnabled(false);
            iterateBtn.setEnabled(false);
        }
    }
}