/*
* RapidMiner
*
* Copyright (C) 2001-2011 by Rapid-I and the contributors
*
* Complete list of developers available at our web site:
*
* http://rapid-i.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
package com.rapidminer.gui.plotter;
import java.awt.Component;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.ListSelectionModel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import org.freehep.util.export.ExportDialog;
import com.rapidminer.datatable.DataTable;
import com.rapidminer.gui.ApplicationFrame;
import com.rapidminer.gui.plotter.PlotterConfigurationModel.PlotterChangedListener;
import com.rapidminer.gui.plotter.PlotterConfigurationModel.PlotterSettingsChangedListener;
import com.rapidminer.gui.plotter.PlotterPanel.LineStyleCellRenderer;
import com.rapidminer.gui.plotter.settings.ListeningJCheckBox;
import com.rapidminer.gui.plotter.settings.ListeningJComboBox;
import com.rapidminer.gui.plotter.settings.ListeningJSlider;
import com.rapidminer.gui.plotter.settings.ListeningListSelectionModel;
import com.rapidminer.gui.tools.ExtendedJList;
import com.rapidminer.gui.tools.ExtendedJScrollPane;
import com.rapidminer.gui.tools.ExtendedListModel;
import com.rapidminer.gui.tools.ResourceAction;
import com.rapidminer.parameter.ParameterTypeEnumeration;
import com.rapidminer.tools.LogService;
/** Panel containing control elements for a {@link Plotter}. Depending on
* the selected plotter type the options panel part is created or adapted. The
* option panel usually contains selectors for up to three axis and other
* options depending on the plotter like a plot amount slider or option buttons.
*
* @see PlotterPanel
* @author Simon Fischer
*
*/
public class PlotterControlPanel extends JPanel implements PlotterChangedListener {
private static final long serialVersionUID = 1L;
private PlotterConfigurationModel plotterSettings;
/** The plotter selection combo box. */
private final JComboBox plotterCombo = new JComboBox();
private List<PlotterSettingsChangedListener> changeListenerElements = new LinkedList<PlotterSettingsChangedListener>();
private transient final ItemListener plotterComboListener = new ItemListener() {
@Override
public void itemStateChanged(ItemEvent e) {
plotterSettings.setPlotter((plotterCombo.getSelectedItem().toString()));
}
};
public PlotterControlPanel(PlotterConfigurationModel plotterSettings) {
this.plotterSettings = plotterSettings;
this.setLayout(new GridBagLayout());
updatePlotterCombo();
updateControls();
}
private void updateControls() {
final Plotter plotter = plotterSettings.getPlotter();
DataTable dataTable = plotterSettings.getDataTable();
changeListenerElements = new LinkedList<PlotterSettingsChangedListener>();
// 0. Clear GUI
removeAll();
GridBagConstraints c = new GridBagConstraints();
c.fill = GridBagConstraints.BOTH;
c.gridwidth = GridBagConstraints.REMAINDER;
c.insets = new Insets(2, 2, 2, 2);
c.weightx = 1;
// 1. register mouse listener on plotter
final JLabel coordinatesLabel = new JLabel(" ");
PlotterMouseHandler mouseHandler = new PlotterMouseHandler(plotter, plotterSettings.getDataTable(), new CoordinatesHandler() {
@Override
public void updateCoordinates(String coordinateInfo) {
coordinatesLabel.setText(coordinateInfo);
}
});
plotter.addMouseMotionListener(mouseHandler);
plotter.addMouseListener(mouseHandler);
// 2. Construct Plotter list
JLabel label = null;
String toolTip = null;
if (plotterSettings.getAvailablePlotters().size() > 1) {
toolTip = "The plotter which should be used for displaying data.";
label = new JLabel("Plotter");
label.setToolTipText(toolTip);
this.add(label, c);
this.add(plotterCombo, c);
}
List<Integer> plottedDimensionList = new LinkedList<Integer>();
if (plotter != null) {
for (int i = 0; i < dataTable.getNumberOfColumns(); i++) {
if (plotter.getPlotColumn(i)) {
plottedDimensionList.add(i);
}
}
}
// 3b. Setup axes selection panel (main)
final List<JComboBox> axisCombos = new LinkedList<JComboBox>();
for (int axisIndex = 0; axisIndex < plotter.getNumberOfAxes(); axisIndex++) {
toolTip = "Select a column for " + plotter.getAxisName(axisIndex);
label = new JLabel(plotter.getAxisName(axisIndex));
label.setToolTipText(toolTip);
this.add(label, c);
final int finalAxisIndex = axisIndex;
final ListeningJComboBox axisCombo = new ListeningJComboBox(PlotterAdapter.PARAMETER_SUFFIX_AXIS + PlotterAdapter.transformParameterName(plotter.getAxisName(finalAxisIndex)), 200) {
private static final long serialVersionUID = 1L;
@Override
public void settingChanged(String generalKey, String specificKey, String value) {
super.settingChanged(generalKey, specificKey, value);
}
};
axisCombo.setToolTipText(toolTip);
axisCombo.addItem("None");
for (int j = 0; j < dataTable.getNumberOfColumns(); j++) {
axisCombo.addItem(dataTable.getColumnName(j));
}
changeListenerElements.add(axisCombo);
axisCombo.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent e) {
plotterSettings.setParameterAsString(PlotterAdapter.PARAMETER_SUFFIX_AXIS + PlotterAdapter.transformParameterName(plotter.getAxisName(finalAxisIndex)), axisCombo.getSelectedItem().toString());
}
});
this.add(axisCombo, c);
axisCombos.add(axisCombo);
// log scale
if (plotter.isSupportingLogScale(axisIndex)) {
final ListeningJCheckBox logScaleBox = new ListeningJCheckBox(PlotterAdapter.PARAMETER_SUFFIX_AXIS + PlotterAdapter.transformParameterName(plotter.getAxisName(finalAxisIndex))+ PlotterAdapter.PARAMETER_SUFFIX_LOG_SCALE, "Log Scale", false);
changeListenerElements.add(logScaleBox);
logScaleBox.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
plotterSettings.setParameterAsBoolean(PlotterAdapter.PARAMETER_SUFFIX_AXIS + PlotterAdapter.transformParameterName(plotter.getAxisName(finalAxisIndex))+ PlotterAdapter.PARAMETER_SUFFIX_LOG_SCALE, logScaleBox.isSelected());
}
});
this.add(logScaleBox, c);
}
}
// 4. Specific settings (colors, values, etc.)
if (plotter.getValuePlotSelectionType() != Plotter.NO_SELECTION) {
JLabel plotLabel;
if (plotter.getPlotName() == null) {
plotLabel = new JLabel("Plots");
toolTip = "Select the column which should be plotted.";
} else {
plotLabel = new JLabel(plotter.getPlotName());
toolTip = "Select a column for " + plotter.getPlotName();
}
plotLabel.setToolTipText(toolTip);
this.add(plotLabel, c);
}
switch (plotter.getValuePlotSelectionType()) {
case Plotter.MULTIPLE_SELECTION:
final ExtendedListModel model = new ExtendedListModel();
for (String name : dataTable.getColumnNames()) {
model.addElement(name, "Select column '" + name + "' for plotting.");
}
final JList plotList = new ExtendedJList(model, 200);
ListeningListSelectionModel selectionModel = new ListeningListSelectionModel(PlotterAdapter.PARAMETER_PLOT_COLUMNS, plotList);
changeListenerElements.add(selectionModel);
plotList.setSelectionModel(selectionModel);
plotList.setToolTipText(toolTip);
plotList.setBorder(BorderFactory.createLoweredBevelBorder());
plotList.setCellRenderer(new LineStyleCellRenderer(plotter));
plotList.addListSelectionListener(new ListSelectionListener() {
public void valueChanged(ListSelectionEvent e) {
if (!e.getValueIsAdjusting()) {
List<String> list = new LinkedList<String>();
for (int i = 0; i < plotList.getModel().getSize(); i++) {
if (plotList.isSelectedIndex(i)) {
list.add(model.get(i).toString());
}
}
String result = ParameterTypeEnumeration.transformEnumeration2String(list);
plotterSettings.setParameterAsString(PlotterAdapter.PARAMETER_PLOT_COLUMNS, result);
}
}
});
plotList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
JScrollPane listScrollPane = new ExtendedJScrollPane(plotList);
listScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
c.weighty = 1.0;
// c.weightx = 0;
this.add(listScrollPane, c);
c.weighty = 0.0;
// c.weightx = 1;
break;
case Plotter.SINGLE_SELECTION:
final ListeningJComboBox plotCombo = new ListeningJComboBox(PlotterAdapter.PARAMETER_PLOT_COLUMN, 200);
plotCombo.setToolTipText(toolTip);
plotCombo.addItem("None");
changeListenerElements.add(plotCombo);
for (int j = 0; j < dataTable.getNumberOfColumns(); j++) {
plotCombo.addItem(dataTable.getColumnName(j));
}
plotCombo.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent e) {
plotterSettings.setParameterAsString(PlotterAdapter.PARAMETER_PLOT_COLUMN, plotCombo.getSelectedItem().toString());
}
});
this.add(plotCombo, c);
break;
case Plotter.NO_SELECTION:
// do nothing
break;
}
// log scale
if (plotter.isSupportingLogScaleForPlotColumns()) {
final ListeningJCheckBox logScaleBox = new ListeningJCheckBox(PlotterAdapter.PARAMETER_PLOT_COLUMNS + PlotterAdapter.PARAMETER_SUFFIX_LOG_SCALE, "Log Scale", false);
changeListenerElements.add(logScaleBox);
logScaleBox.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
plotterSettings.setParameterAsBoolean(PlotterAdapter.PARAMETER_PLOT_COLUMNS + PlotterAdapter.PARAMETER_SUFFIX_LOG_SCALE, logScaleBox.isSelected());
}
});
this.add(logScaleBox, c);
}
// sorting
if (plotter.isSupportingSorting()) {
final ListeningJCheckBox sortingBox = new ListeningJCheckBox(PlotterAdapter.PARAMETER_SUFFIX_SORTING, "Sorting", false);
changeListenerElements.add(sortingBox);
sortingBox.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
plotterSettings.setParameterAsBoolean(PlotterAdapter.PARAMETER_SUFFIX_SORTING, sortingBox.isSelected());
}
});
this.add(sortingBox, c);
}
// sorting
if (plotter.isSupportingAbsoluteValues()) {
final ListeningJCheckBox absoluteBox = new ListeningJCheckBox(PlotterAdapter.PARAMETER_SUFFIX_ABSOLUTE_VALUES, "Absolute Values", false);
changeListenerElements.add(absoluteBox);
absoluteBox.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
plotterSettings.setParameterAsBoolean(PlotterAdapter.PARAMETER_SUFFIX_ABSOLUTE_VALUES, absoluteBox.isSelected());
}
});
this.add(absoluteBox, c);
}
// zooming
if (plotter.canHandleZooming()) {
label = new JLabel("Zooming");
toolTip = "Set a new zooming factor.";
label.setToolTipText(toolTip);
this.add(label, c);
final ListeningJSlider zoomingSlider = new ListeningJSlider(PlotterAdapter.PARAMETER_SUFFIX_ZOOM_FACTOR, 1, 100, plotter.getInitialZoomFactor());
changeListenerElements.add(zoomingSlider);
zoomingSlider.setToolTipText(toolTip);
this.add(zoomingSlider, c);
zoomingSlider.addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent e) {
plotterSettings.setParameterAsInt(PlotterAdapter.PARAMETER_SUFFIX_ZOOM_FACTOR, zoomingSlider.getValue());
}
});
}
// jitter
if (plotter.canHandleJitter()) {
label = new JLabel("Jitter");
toolTip = "Select the amount of jittering (small perturbation of data points).";
label.setToolTipText(toolTip);
this.add(label, c);
final ListeningJSlider jitterSlider = new ListeningJSlider(PlotterAdapter.PARAMETER_JITTER_AMOUNT, 0, 100, 0);
changeListenerElements.add(jitterSlider);
jitterSlider.setToolTipText(toolTip);
this.add(jitterSlider, c);
jitterSlider.addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent e) {
plotterSettings.setParameterAsInt(PlotterAdapter.PARAMETER_JITTER_AMOUNT, jitterSlider.getValue());
}
});
}
// option dialog
if (plotter.hasOptionsDialog()) {
toolTip = "Opens a dialog with further options for this plotter.";
JButton optionsButton = new JButton("Options");
optionsButton.setToolTipText(toolTip);
this.add(optionsButton, c);
optionsButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
plotter.showOptionsDialog();
}
});
}
// Add the plotter options components for user interaction, if provided
int componentCounter = 0;
while (plotter.getOptionsComponent(componentCounter) != null) {
Component options = plotter.getOptionsComponent(componentCounter);
this.add(options, c);
componentCounter++;
}
// Save image button for the plotter
if (!plotter.hasSaveImageButton()) {
JButton imageButton = new JButton(new ResourceAction(true, "save_image") {
private static final long serialVersionUID = -6568814929011124484L;
@Override
public void actionPerformed(ActionEvent e) {
Component tosave = plotter.getPlotter();
ExportDialog exportDialog = new ExportDialog("RapidMiner");
exportDialog.showExportDialog(ApplicationFrame.getApplicationFrame(), "Save Image...", tosave, "plot");
}
});
this.add(imageButton, c);
}
// check if savable (for data)
if (plotter.isSaveable()) {
toolTip = "Saves the data underlying this plot into a file.";
JButton saveButton = new JButton(new ResourceAction("save_result") {
private static final long serialVersionUID = 1L;
public void actionPerformed(ActionEvent e) {
plotter.save();
}
});
saveButton.setToolTipText(toolTip);
this.add(saveButton, c);
}
// coordinates
if (plotter.isProvidingCoordinates()) {
toolTip = "The current coordinates of the mouese cursor with respect to the data dimensions.";
coordinatesLabel.setToolTipText(toolTip);
coordinatesLabel.setBorder(BorderFactory.createEtchedBorder());
coordinatesLabel.setFont(new Font("Monospaced", Font.PLAIN, coordinatesLabel.getFont().getSize()));
this.add(coordinatesLabel, c);
}
// add fill component if necessary (glue)
if (plotter.getValuePlotSelectionType() != Plotter.MULTIPLE_SELECTION) {
c.weighty = 1.0;
JPanel fillPanel = new JPanel();
this.add(fillPanel, c);
c.weighty = 0.0;
}
this.setAlignmentX(LEFT_ALIGNMENT);
revalidate();
repaint();
}
public void updatePlotterCombo() {
plotterCombo.removeItemListener(plotterComboListener);
plotterCombo.removeAllItems();
Iterator<String> n = plotterSettings.getAvailablePlotters().keySet().iterator();
while (n.hasNext()) {
String plotterName = n.next();
try {
Class<? extends Plotter> plotterClass = plotterSettings.getAvailablePlotters().get(plotterName);
if (plotterClass != null) {
//TODO: Make this more elegant...
plotterCombo.addItem(plotterName);
}
} catch (IllegalArgumentException e) {
LogService.getGlobal().log("Plotter control panel: cannot instantiate plotter '" + plotterName + "'. Skipping...", LogService.WARNING);
} catch (SecurityException e) {
LogService.getGlobal().log("Plotter control panel: cannot instantiate plotter '" + plotterName + "'. Skipping...", LogService.WARNING);
}
}
plotterCombo.setToolTipText("The plotter which should be used for displaying data.");
plotterCombo.addItemListener(plotterComboListener);
}
@Override
public List<PlotterSettingsChangedListener> getListeningObjects() {
return changeListenerElements;
}
@Override
public void plotterChanged(String plotterName) {
plotterCombo.setSelectedItem(plotterName);
updateControls();
}
}