/**
* Copyright (C) 2001-2017 by RapidMiner and the contributors
*
* Complete list of developers available at our web site:
*
* http://rapidminer.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.Dimension;
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 java.util.logging.Level;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
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.ScrollPaneConstants;
import javax.swing.SwingConstants;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import com.rapidminer.datatable.DataTable;
import com.rapidminer.gui.look.Colors;
import com.rapidminer.gui.look.RapidLookTools;
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.properties.PropertyPanel;
import com.rapidminer.gui.tools.ExtendedJList;
import com.rapidminer.gui.tools.ExtendedJScrollPane;
import com.rapidminer.gui.tools.ExtendedListModel;
import com.rapidminer.gui.tools.SwingTools;
import com.rapidminer.parameter.ParameterTypeEnumeration;
import com.rapidminer.tools.I18N;
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, Michael Knopf
*
*/
public class PlotterControlPanel extends JPanel implements PlotterChangedListener {
private static final long serialVersionUID = 1L;
private PlotterConfigurationModel plotterSettings;
/** The plotter selection combo box. */
private final PlotterChooser plotterCombo = new PlotterChooser();
private List<PlotterSettingsChangedListener> changeListenerElements = new LinkedList<>();
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.plotterCombo.setSettings(plotterSettings);
this.setLayout(new GridBagLayout());
updatePlotterCombo();
updateControls();
}
private void updateControls() {
final Plotter plotter = plotterSettings.getPlotter();
DataTable dataTable = plotterSettings.getDataTable();
changeListenerElements = new LinkedList<>();
// 0. Clear GUI
removeAll();
GridBagConstraints c = new GridBagConstraints();
c.fill = GridBagConstraints.BOTH;
c.gridwidth = GridBagConstraints.REMAINDER;
c.insets = new Insets(6, 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) {
label = new JLabel(I18N.getGUILabel("plotter_panel.selection.label") + ":");
this.add(label, c);
ImageIcon buttonIcon = SwingTools.createImage("icons/chartPreview/32/"
+ plotter.getPlotterName().replace(' ', '_') + ".png");
plotterCombo.setIcon(buttonIcon);
plotterCombo.setIconTextGap(6);
plotterCombo.setVerticalAlignment(SwingConstants.CENTER);
plotterCombo.setHorizontalAlignment(SwingConstants.LEFT);
plotterCombo.setHorizontalTextPosition(SwingConstants.RIGHT);
plotterCombo.setVerticalTextPosition(SwingConstants.CENTER);
plotterCombo.setPreferredSize(new Dimension(200, 40));
this.add(plotterCombo, c);
this.add(createFiller(20), c);
}
List<Integer> plottedDimensionList = new LinkedList<>();
for (int i = 0; i < dataTable.getNumberOfColumns(); i++) {
if (plotter.getPlotColumn(i)) {
plottedDimensionList.add(i);
}
}
// 3b. Setup axes selection panel (main)
final List<JComboBox<String>> axisCombos = new LinkedList<>();
for (int axisIndex = 0; axisIndex < plotter.getNumberOfAxes(); axisIndex++) {
toolTip = I18N.getMessage(I18N.getGUIBundle(), "gui.action.plotter_panel.select_column_axis.tip",
plotter.getAxisName(axisIndex));
label = new JLabel(plotter.getAxisName(axisIndex) + ":");
label.setToolTipText(toolTip);
this.add(label, c);
final int finalAxisIndex = axisIndex;
final ListeningJComboBox<String> axisCombo = new ListeningJComboBox<String>(PlotterAdapter.PARAMETER_SUFFIX_AXIS
+ PlotterAdapter.transformParameterName(plotter.getAxisName(finalAxisIndex)), 200);
axisCombo.setToolTipText(toolTip);
axisCombo.setPreferredSize(
new Dimension(axisCombo.getPreferredSize().width, PropertyPanel.VALUE_CELL_EDITOR_HEIGHT));
axisCombo.putClientProperty(RapidLookTools.PROPERTY_INPUT_BACKGROUND_DARK, true);
axisCombo.addItem(I18N.getMessage(I18N.getGUIBundle(), "gui.label.plotter_panel.no_selection.label"));
for (int j = 0; j < dataTable.getNumberOfColumns(); j++) {
axisCombo.addItem(dataTable.getColumnName(j));
}
changeListenerElements.add(axisCombo);
axisCombo.addItemListener(new ItemListener() {
@Override
public void itemStateChanged(ItemEvent e) {
String value = PlotterAdapter.PARAMETER_SUFFIX_AXIS
+ PlotterAdapter.transformParameterName(plotter.getAxisName(finalAxisIndex));
String key = axisCombo.getSelectedItem().toString();
plotterSettings.setParameterAsString(value, key);
}
});
this.add(axisCombo, c);
if (!plotter.isSupportingLogScale(axisIndex)) {
this.add(createFiller(10), 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, I18N.getMessage(I18N.getGUIBundle(),
"gui.label.plotter_panel.log_scale.label"), false);
changeListenerElements.add(logScaleBox);
logScaleBox.addActionListener(new ActionListener() {
@Override
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);
this.add(createFiller(10), c);
}
}
// 4. Specific settings (colors, values, etc.)
if (plotter.getValuePlotSelectionType() != Plotter.NO_SELECTION) {
JLabel plotLabel;
if (plotter.getPlotName() == null) {
plotLabel = new JLabel(I18N.getMessage(I18N.getGUIBundle(), "gui.label.plotter_panel.plots.label") + ":");
toolTip = I18N.getMessage(I18N.getGUIBundle(), "gui.action.plotter_panel.select_column.tip");
} else {
plotLabel = new JLabel(plotter.getPlotName() + ":");
toolTip = I18N.getMessage(I18N.getGUIBundle(), "gui.action.plotter_panel.select_column_axis.tip",
plotter.getPlotName());
}
plotLabel.setToolTipText(toolTip);
this.add(plotLabel, c);
}
switch (plotter.getValuePlotSelectionType()) {
case Plotter.MULTIPLE_SELECTION:
final ExtendedListModel<String> model = new ExtendedListModel<>();
for (String name : dataTable.getColumnNames()) {
model.addElement(name,
I18N.getMessage(I18N.getGUIBundle(), "gui.action.plotter_panel.select_column_name", name));
}
final JList<String> plotList = new ExtendedJList<>(model, 200);
ListeningListSelectionModel selectionModel = new ListeningListSelectionModel(
PlotterAdapter.PARAMETER_PLOT_COLUMNS, plotList);
changeListenerElements.add(selectionModel);
plotList.setSelectionModel(selectionModel);
plotList.setToolTipText(toolTip);
plotList.setCellRenderer(new LineStyleCellRenderer<>(plotter));
plotList.addListSelectionListener(new ListSelectionListener() {
@Override
public void valueChanged(ListSelectionEvent e) {
if (!e.getValueIsAdjusting()) {
List<String> list = new LinkedList<>();
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.setBorder(BorderFactory.createMatteBorder(1, 1, 1, 1, Colors.TEXTFIELD_BORDER));
listScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
c.weighty = 1.0;
this.add(listScrollPane, c);
c.weighty = 0.0;
break;
case Plotter.SINGLE_SELECTION:
final ListeningJComboBox<String> plotCombo = new ListeningJComboBox<>(PlotterAdapter.PARAMETER_PLOT_COLUMN,
200);
plotCombo.setToolTipText(toolTip);
plotCombo.setPreferredSize(
new Dimension(plotCombo.getPreferredSize().width, PropertyPanel.VALUE_CELL_EDITOR_HEIGHT));
plotCombo.putClientProperty(RapidLookTools.PROPERTY_INPUT_BACKGROUND_DARK, true);
plotCombo.addItem(I18N.getMessage(I18N.getGUIBundle(), "gui.label.plotter_panel.no_selection.label"));
changeListenerElements.add(plotCombo);
for (int j = 0; j < dataTable.getNumberOfColumns(); j++) {
plotCombo.addItem(dataTable.getColumnName(j));
}
plotCombo.addItemListener(new ItemListener() {
@Override
public void itemStateChanged(ItemEvent e) {
plotterSettings.setParameterAsString(PlotterAdapter.PARAMETER_PLOT_COLUMN, plotCombo
.getSelectedItem().toString());
}
});
this.add(plotCombo, c);
break;
case Plotter.NO_SELECTION:
default:
// do nothing
break;
}
// log scale
if (plotter.isSupportingLogScaleForPlotColumns()) {
final ListeningJCheckBox logScaleBox = new ListeningJCheckBox(PlotterAdapter.PARAMETER_PLOT_COLUMNS
+ PlotterAdapter.PARAMETER_SUFFIX_LOG_SCALE, I18N.getMessage(I18N.getGUIBundle(),
"gui.label.plotter_panel.log_scale.label"), false);
changeListenerElements.add(logScaleBox);
logScaleBox.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
plotterSettings.setParameterAsBoolean(PlotterAdapter.PARAMETER_PLOT_COLUMNS
+ PlotterAdapter.PARAMETER_SUFFIX_LOG_SCALE, logScaleBox.isSelected());
}
});
this.add(logScaleBox, c);
this.add(createFiller(10), c);
}
// sorting
if (plotter.isSupportingSorting()) {
final ListeningJCheckBox sortingBox = new ListeningJCheckBox(PlotterAdapter.PARAMETER_SUFFIX_SORTING,
I18N.getMessage(I18N.getGUIBundle(), "gui.label.plotter_panel.sorting.label"), false);
changeListenerElements.add(sortingBox);
sortingBox.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
plotterSettings.setParameterAsBoolean(PlotterAdapter.PARAMETER_SUFFIX_SORTING, sortingBox.isSelected());
}
});
this.add(sortingBox, c);
this.add(createFiller(10), c);
}
// sorting
if (plotter.isSupportingAbsoluteValues()) {
final ListeningJCheckBox absoluteBox = new ListeningJCheckBox(PlotterAdapter.PARAMETER_SUFFIX_ABSOLUTE_VALUES,
I18N.getMessage(I18N.getGUIBundle(), "gui.label.plotter_panel.abs_values.label"), false);
changeListenerElements.add(absoluteBox);
absoluteBox.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
plotterSettings.setParameterAsBoolean(PlotterAdapter.PARAMETER_SUFFIX_ABSOLUTE_VALUES,
absoluteBox.isSelected());
}
});
this.add(absoluteBox, c);
this.add(createFiller(10), c);
}
// zooming
if (plotter.canHandleZooming()) {
label = new JLabel(I18N.getMessage(I18N.getGUIBundle(), "gui.action.plotter_panel.set_zooming_factor.label")
+ ":");
toolTip = I18N.getMessage(I18N.getGUIBundle(), "gui.action.plotter_panel.set_zooming_factor.tip");
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);
this.add(createFiller(10), c);
zoomingSlider.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
plotterSettings.setParameterAsInt(PlotterAdapter.PARAMETER_SUFFIX_ZOOM_FACTOR, zoomingSlider.getValue());
}
});
}
// jitter
if (plotter.canHandleJitter()) {
label = new JLabel(I18N.getMessage(I18N.getGUIBundle(), "gui.action.plotter_panel.set_jittering_amount.label")
+ ":");
toolTip = I18N.getMessage(I18N.getGUIBundle(), "gui.action.plotter_panel.set_jittering_amount.tip");
label.setToolTipText(toolTip);
this.add(label, c);
final ListeningJSlider jitterSlider = new ListeningJSlider(PlotterAdapter.PARAMETER_JITTER_AMOUNT, 0, 10, 0);
changeListenerElements.add(jitterSlider);
jitterSlider.setToolTipText(toolTip);
jitterSlider.setPaintLabels(false);
jitterSlider.setMajorTickSpacing(10);
this.add(jitterSlider, c);
this.add(createFiller(10), c);
jitterSlider.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
plotterSettings.setParameterAsInt(PlotterAdapter.PARAMETER_JITTER_AMOUNT, jitterSlider.getValue());
}
});
}
// option dialog
if (plotter.hasOptionsDialog()) {
toolTip = I18N.getMessage(I18N.getGUIBundle(), "gui.action.plotter_panel.open_options_dialog.tip");
JButton optionsButton = new JButton(I18N.getMessage(I18N.getGUIBundle(),
"gui.action.plotter_panel.open_options_dialog.label"));
optionsButton.setToolTipText(toolTip);
this.add(optionsButton, c);
this.add(createFiller(10), c);
optionsButton.addActionListener(new ActionListener() {
@Override
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);
if (options instanceof JComboBox<?>) {
((JComboBox<?>) options).putClientProperty(RapidLookTools.PROPERTY_INPUT_BACKGROUND_DARK, true);
} else {
options.setBackground(Colors.WHITE);
}
this.add(options, c);
componentCounter++;
}
// coordinates
if (plotter.isProvidingCoordinates()) {
toolTip = I18N.getMessage(I18N.getGUIBundle(), "gui.label.plotter_panel.coordinates.label");
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;
this.add(new JLabel(), 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) {
plotterCombo.addItem(plotterName);
}
} catch (IllegalArgumentException e) {
LogService.getRoot().log(Level.WARNING,
"com.rapidminer.gui.plotter.PlotterControlPanel.instatiating_plotter_error", plotterName);
} catch (SecurityException e) {
LogService.getRoot().log(Level.WARNING,
"com.rapidminer.gui.plotter.PlotterControlPanel.instatiating_plotter_error", plotterName);
}
}
plotterCombo.setToolTipText(I18N.getMessage(I18N.getGUIBundle(), "gui.action.plotter_panel.select_chart.tip"));
plotterCombo.addItemListener(plotterComboListener);
}
@Override
public List<PlotterSettingsChangedListener> getListeningObjects() {
return changeListenerElements;
}
@Override
public void plotterChanged(String plotterName) {
plotterCombo.setSelectedItem(plotterName);
updateControls();
}
/**
* Creates a {@link JLabel} which has a preferred height of the specified value to create some
* spacing
*
* @param height
* pref height
* @return the filler label
*/
private static JLabel createFiller(final int height) {
JLabel label = new JLabel() {
private static final long serialVersionUID = 1L;
@Override
public Dimension getPreferredSize() {
return new Dimension(super.getPreferredSize().width, height);
}
};
return label;
}
}