package org.geogebra.desktop.gui.view.data;
import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.Insets;
import java.awt.SystemColor;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.AbstractAction;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JSeparator;
import javax.swing.JSlider;
import javax.swing.JTextField;
import javax.swing.JToggleButton;
import javax.swing.JToolBar;
import javax.swing.ListCellRenderer;
import javax.swing.SwingConstants;
import javax.swing.border.EmptyBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.geogebra.common.gui.view.data.DataAnalysisModel;
import org.geogebra.common.gui.view.data.DataDisplayModel;
import org.geogebra.common.gui.view.data.DataDisplayModel.IDataDisplayListener;
import org.geogebra.common.gui.view.data.DataDisplayModel.PlotType;
import org.geogebra.common.kernel.geos.GeoElement;
import org.geogebra.common.kernel.statistics.AlgoFrequencyTable;
import org.geogebra.common.main.App;
import org.geogebra.common.util.Validation;
import org.geogebra.common.util.debug.Log;
import org.geogebra.desktop.gui.inputfield.AutoCompleteTextFieldD;
import org.geogebra.desktop.gui.inputfield.MyTextFieldD;
import org.geogebra.desktop.gui.util.GeoGebraIconD;
import org.geogebra.desktop.main.AppD;
import org.geogebra.desktop.main.LocalizationD;
import org.geogebra.desktop.util.GuiResourcesD;
/**
* Class to dynamically display plots and statistics in coordination with the
* DataAnalysisView.
*
* @author G.Sturr
*
*/
public class DataDisplayPanelD extends JPanel implements ActionListener,
FocusListener, StatPanelInterface, IDataDisplayListener {
private static final long serialVersionUID = 1L;
// ggb fields
private AppD app;
private final LocalizationD loc;
// privateDataAnalysisViewD daView;
private DataDisplayModel model;
// data view mode
// display panels
private JPanel displayCardPanel;
private JPanel metaPlotPanel, plotPanelNorth, plotPanelSouth;
private PlotPanelEuclidianViewD plotPanel;
private JLabel imageContainer;
// control panel
private JPanel controlPanel;
private JPanel controlCards;
private boolean hasControlPanel = true;
private JComboBox cbDisplayType;
// options button and sidebar panel
private OptionsPanelD optionsPanel;
private JToggleButton btnOptions;
// numClasses panel
// private int numClasses = 6;
private JPanel numClassesPanel;
private JSlider sliderNumClasses;
// manual classes panel
private JToolBar manualClassesPanel;
private JLabel lblStart;
private JLabel lblWidth;
private AutoCompleteTextFieldD fldStart;
private AutoCompleteTextFieldD fldWidth;
// stemplot adjustment panel
private JToolBar stemAdjustPanel;
private JLabel lblAdjust;
private JButton minus;
private JButton none;
private JButton plus;
private JPanel imagePanel;
private JLabel lblTitleX, lblTitleY;
private MyTextFieldD fldTitleX, fldTitleY;
private FrequencyTablePanel frequencyTable;
private JToggleButton btnExport;
private JTextField fldNumClasses;
private DataAnalysisModel daModel;
private DataAnalysisViewD daView;
/*****************************************
* Constructs a ComboStatPanel
*
* @param daView
* daView
*/
public DataDisplayPanelD(DataAnalysisViewD daView) {
this.app = daView.getApp();
this.loc = app.getLocalization();
daModel = daView.getModel();
setModel(new DataDisplayModel(daModel, this));
// create the GUI
this.daView = daView;
createGUI();
}
/**
* Sets the plot to be displayed and the GUI corresponding to the given data
* analysis mode
*
* @param plotIndex
* the plot to be displayed
* @param mode
* the data analysis mode
*/
public void setPanel(PlotType plotIndex, int mode) {
getModel().updatePlot(plotIndex, mode);
setLabels();
getModel().updatePlot(true);
optionsPanel.setVisible(false);
btnOptions.setSelected(false);
btnOptions.setSelectedIcon(
app.getScaledIcon(GuiResourcesD.INPUTHELP_RIGHT_18x18));
}
public void updateIcons() {
btnOptions
.setIcon(app.getScaledIcon(GuiResourcesD.INPUTHELP_LEFT_18x18));
btnOptions.setSelectedIcon(
app.getScaledIcon(GuiResourcesD.INPUTHELP_RIGHT_18x18));
btnExport.setIcon(app.getScaledIcon(GuiResourcesD.EXPORT16));
}
// ==============================================
// GUI
// ==============================================
private void createGUI() {
// create options button
btnOptions = new JToggleButton();
btnOptions.setBorderPainted(false);
btnOptions.setFocusPainted(false);
btnOptions.setContentAreaFilled(false);
// optionsButton.setPreferredSize(new
// Dimension(optionsButton.getIcon().getIconWidth(),18));
btnOptions.setMargin(new Insets(0, 0, 0, 0));
btnOptions.addActionListener(this);
// create export button
btnExport = new JToggleButton();
btnExport.setBorderPainted(false);
btnExport.setFocusPainted(false);
btnExport.setContentAreaFilled(false);
// btnExport.setPreferredSize(new
// Dimension(btnExport.getIcon().getIconWidth(),18));
btnExport.setMargin(new Insets(0, 0, 0, 0));
btnExport.addActionListener(this);
updateIcons();
// create control panel
if (hasControlPanel) {
// create sub-control panels
createDisplayTypeComboBox();
createNumClassesPanel();
createManualClassesPanel();
createStemPlotAdjustmentPanel();
JPanel emptyControl = new JPanel(new BorderLayout());
emptyControl.add(new JLabel(" "));
// put sub-control panels into a card layout
controlCards = new JPanel(new CardLayout());
controlCards.add("numClassesPanel", numClassesPanel);
controlCards.add("manualClassesPanel", manualClassesPanel);
controlCards.add("stemAdjustPanel", stemAdjustPanel);
controlCards.add("blankPanel", emptyControl);
// control panel
controlPanel = new JPanel(new BorderLayout(0, 0));
controlPanel.add(flowPanel(cbDisplayType), loc.borderWest());
controlPanel.add(controlCards, BorderLayout.CENTER);
controlPanel.add(flowPanelRight(btnOptions, btnExport),
loc.borderEast());
}
plotPanel = new PlotPanelEuclidianViewD(app.getKernel(),
exportToEVAction);
plotPanelNorth = new JPanel();
plotPanelSouth = new JPanel();
Color bgColor = org.geogebra.desktop.awt.GColorD
.getAwtColor(plotPanel.getBackgroundCommon());
plotPanelNorth.setBackground(bgColor);
plotPanelSouth.setBackground(bgColor);
lblTitleX = new JLabel();
lblTitleY = new JLabel();
fldTitleX = new MyTextFieldD(app, 20);
fldTitleY = new MyTextFieldD(app, 20);
fldTitleX.setEditable(false);
fldTitleX.setBorder(BorderFactory.createEmptyBorder());
fldTitleY.setEditable(false);
fldTitleY.setBorder(BorderFactory.createEmptyBorder());
fldTitleX.setBackground(Color.white);
fldTitleY.setBackground(Color.white);
metaPlotPanel = new JPanel(new BorderLayout());
metaPlotPanel.add(plotPanel.getJPanel(), BorderLayout.CENTER);
createImagePanel();
// put display panels into a card layout
displayCardPanel = new JPanel(new CardLayout());
displayCardPanel.setBackground(bgColor);
displayCardPanel.add("plotPanel", metaPlotPanel);
displayCardPanel.add("imagePanel", new JScrollPane(imagePanel));
// create options panel
optionsPanel = new OptionsPanelD(app, daModel,
getModel().getSettings());
optionsPanel.addPropertyChangeListener("settings",
new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
getModel().updatePlot(true);
}
});
optionsPanel.setVisible(false);
frequencyTable = new FrequencyTablePanel(app);
// =======================================
// put all the panels together
JPanel mainPanel = new JPanel(new BorderLayout(0, 0));
if (hasControlPanel) {
mainPanel.add(controlPanel, BorderLayout.NORTH);
}
mainPanel.add(displayCardPanel, BorderLayout.CENTER);
mainPanel.add(optionsPanel, loc.borderEast());
this.setLayout(new BorderLayout(0, 0));
this.add(mainPanel, BorderLayout.CENTER);
this.setBorder(BorderFactory.createEmptyBorder(5, 0, 0, 0));
controlPanel.setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0,
SystemColor.controlShadow));
}
/**
* Sets the labels to the current language
*/
@Override
public void setLabels() {
createDisplayTypeComboBox();
sliderNumClasses.setToolTipText(loc.getMenu("Classes"));
fldNumClasses.setToolTipText(loc.getMenu("Classes"));
lblStart.setText(loc.getMenu("Start") + " ");
lblWidth.setText(loc.getMenu("Width") + " ");
if (daModel.isRegressionMode()) {
lblTitleX.setText(loc.getMenu("Column.X") + ": ");
lblTitleY.setText(loc.getMenu("Column.Y") + ": ");
}
lblAdjust.setText(loc.getMenu("Adjustment") + ": ");
optionsPanel.setLabels();
btnOptions.setToolTipText(loc.getMenu("Options"));
}
/**
* Creates the JComboBox that selects display type
*/
private void createDisplayTypeComboBox() {
if (cbDisplayType == null) {
cbDisplayType = new JComboBox();
cbDisplayType.setRenderer(new MyRenderer(app));
} else {
cbDisplayType.removeActionListener(this);
cbDisplayType.removeAllItems();
}
getModel().fillDisplayTypes();
cbDisplayType.setFocusable(false);
cbDisplayType.addActionListener(this);
cbDisplayType.setMaximumRowCount(cbDisplayType.getItemCount());
}
/**
* Creates a display panel to hold an image, e.g. tabletext
*/
private void createImagePanel() {
imagePanel = new JPanel(new BorderLayout());
imagePanel.setBorder(BorderFactory.createEmptyBorder());
imagePanel.setBackground(Color.WHITE);
imageContainer = new JLabel();
imagePanel.setAlignmentX(SwingConstants.CENTER);
imagePanel.setAlignmentY(SwingConstants.CENTER);
imageContainer.setHorizontalAlignment(SwingConstants.CENTER);
imagePanel.add(imageContainer, BorderLayout.CENTER);
}
/**
* commonFields Creates a control panel for adjusting the number of
* histogram classes
*/
private void createNumClassesPanel() {
int numClasses = getModel().getSettings().getNumClasses();
fldNumClasses = new JTextField("" + numClasses);
fldNumClasses.setEditable(false);
fldNumClasses.setOpaque(true);
fldNumClasses.setColumns(2);
fldNumClasses.setHorizontalAlignment(SwingConstants.CENTER);
fldNumClasses.setBackground(null);
fldNumClasses.setBorder(BorderFactory.createEmptyBorder());
fldNumClasses.setVisible(false);
sliderNumClasses = new JSlider(SwingConstants.HORIZONTAL, 3, 20,
numClasses);
Dimension d = sliderNumClasses.getPreferredSize();
d.width = 80;
sliderNumClasses.setPreferredSize(d);
sliderNumClasses.setMinimumSize(new Dimension(50, d.height));
sliderNumClasses.setMajorTickSpacing(1);
sliderNumClasses.setSnapToTicks(true);
sliderNumClasses.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent evt) {
JSlider slider = (JSlider) evt.getSource();
getModel().getSettings().setNumClasses(slider.getValue());
fldNumClasses.setText(
("" + getModel().getSettings().getNumClasses()));
getModel().updatePlot(true);
}
});
sliderNumClasses.addMouseListener(new MouseAdapter() {
@Override
public void mouseEntered(MouseEvent arg0) {
fldNumClasses.setVisible(true);
fldNumClasses.revalidate();
}
@Override
public void mouseExited(MouseEvent arg0) {
fldNumClasses.setVisible(false);
fldNumClasses.revalidate();
}
});
numClassesPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0));
numClassesPanel.add(sliderNumClasses);
// numClassesPanel.add(lblNumClasses);
numClassesPanel.add(fldNumClasses);
}
/**
* Creates a control panel to adjust the stem plot
*/
private void createStemPlotAdjustmentPanel() {
lblAdjust = new JLabel();
minus = new JButton("-1");
none = new JButton("0");
plus = new JButton("+1");
minus.addActionListener(this);
none.addActionListener(this);
plus.addActionListener(this);
none.setSelected(true);
stemAdjustPanel = new JToolBar();
stemAdjustPanel.setFloatable(false);
stemAdjustPanel.add(minus);
stemAdjustPanel.add(none);
stemAdjustPanel.add(plus);
}
/**
* Creates a control panel for manually setting classes
*/
private void createManualClassesPanel() {
lblStart = new JLabel();
lblWidth = new JLabel();
fldStart = new AutoCompleteTextFieldD(4, app);
Dimension d = fldStart.getMaximumSize();
d.height = fldStart.getPreferredSize().height;
fldStart.setMaximumSize(d);
fldStart.addActionListener(this);
fldStart.setText("" + (int) getModel().getSettings().getClassStart());
fldStart.addFocusListener(this);
fldWidth = new AutoCompleteTextFieldD(4, app);
fldWidth.setMaximumSize(d);
fldStart.setColumns(4);
fldWidth.setColumns(4);
fldWidth.addActionListener(this);
fldWidth.setText("" + (int) getModel().getSettings().getClassWidth());
fldWidth.addFocusListener(this);
manualClassesPanel = new JToolBar();
manualClassesPanel.setFloatable(false);
manualClassesPanel.add(lblStart);
manualClassesPanel.add(fldStart);
manualClassesPanel.add(Box.createHorizontalStrut(4));
manualClassesPanel.add(lblWidth);
manualClassesPanel.add(fldWidth);
}
public JPopupMenu getExportMenu() {
return plotPanel.getContextMenu();
}
// ==============================================
// DISPLAY UPDATE
// ==============================================
@Override
public void showControlPanel() {
((CardLayout) controlCards.getLayout()).show(controlCards,
"blankPanel");
}
@Override
public void setOptionsButtonVisible() {
btnOptions.setVisible(true);
}
@Override
public void showInvalidDataDisplay() {
imageContainer.setIcon(null);
((CardLayout) displayCardPanel.getLayout()).show(displayCardPanel,
"imagePanel");
}
// ============================================================
// Event Handlers
// ============================================================
@Override
public void actionPerformed(ActionEvent e) {
Object source = e.getSource();
if (source instanceof JTextField) {
doTextFieldActionPerformed(source);
} else if (source == minus || source == plus || source == none) {
minus.setSelected(source == minus);
none.setSelected(source == none);
plus.setSelected(source == plus);
if (source == minus) {
getModel().getSettings().setStemAdjust(-1);
}
if (source == none) {
getModel().getSettings().setStemAdjust(0);
}
if (source == plus) {
getModel().getSettings().setStemAdjust(1);
}
getModel().updatePlot(true);
}
else if (source == btnOptions) {
optionsPanel.setPanel(getModel().getSelectedPlot());
optionsPanel.setVisible(btnOptions.isSelected());
}
else if (source == btnExport) {
JPopupMenu menu = plotPanel.getContextMenu();
menu.show(btnExport,
-menu.getPreferredSize().width + btnExport.getWidth(),
btnExport.getHeight());
}
else if (source == cbDisplayType) {
if (cbDisplayType.getSelectedItem().equals(MyRenderer.SEPARATOR)) {
cbDisplayType.setSelectedItem(getModel().getSelectedPlot());
} else {
getModel().setSelectedPlot(
(PlotType) cbDisplayType.getSelectedItem());
getModel().updatePlot(true);
}
if (optionsPanel.isVisible()) {
optionsPanel.setPanel(getModel().getSelectedPlot());
}
}
}
private void doTextFieldActionPerformed(Object source) {
if (source == fldStart) {
getModel().getSettings().setClassStart(Validation.validateDouble(
fldStart, getModel().getSettings().getClassStart()));
} else if (source == fldWidth) {
getModel().getSettings()
.setClassWidth(Validation.validateDoublePositive(fldWidth,
getModel().getSettings().getClassWidth()));
}
getModel().updatePlot(true);
}
@Override
public void focusLost(FocusEvent e) {
Object source = e.getSource();
if (source instanceof JTextField) {
this.doTextFieldActionPerformed(source);
}
}
@Override
public void focusGained(FocusEvent e) {
//
}
public void detachView() {
// plotPanel.detachView();
}
public void attachView() {
plotPanel.attachView();
}
// ============================================================
// Utilities
// ============================================================
private static JPanel flowPanel(JComponent... comp) {
JPanel p = new JPanel(new FlowLayout(FlowLayout.LEFT));
for (int i = 0; i < comp.length; i++) {
p.add(comp[i]);
}
// p.setBackground(Color.white);
return p;
}
private static JPanel flowPanelRight(JComponent... comp) {
JPanel p = new JPanel(new FlowLayout(FlowLayout.RIGHT, 0, 0));
for (int i = 0; i < comp.length; i++) {
p.add(comp[i]);
}
// p.setBackground(Color.white);
return p;
}
@Override
public void updateFonts(Font font) {
plotPanel.updateFonts();
updateIcons();
optionsPanel.updateFonts(font);
setLabels();
}
@Override
public void updatePanel() {
//
}
// ============================================================
// ComboBox Renderer with SEPARATOR
// ============================================================
private static class MyRenderer extends JLabel implements ListCellRenderer {
private static final long serialVersionUID = 1L;
public static final String SEPARATOR = "SEPARATOR";
JSeparator separator;
private AppD app;
public MyRenderer(AppD app) {
this.app = app;
setOpaque(true);
setBorder(new EmptyBorder(1, 1, 1, 1));
separator = new JSeparator(SwingConstants.HORIZONTAL);
}
@Override
public Component getListCellRendererComponent(JList list, Object value,
int index, boolean isSelected, boolean cellHasFocus) {
String str = "";
if (value instanceof PlotType) {
str = app.getLocalization()
.getMenu(((PlotType) value).getKey());
} else {
Log.error("wrong class" + value);
}
if (SEPARATOR.equals(str)) {
return separator;
}
if (isSelected) {
setBackground(list.getSelectionBackground());
setForeground(list.getSelectionForeground());
} else {
setBackground(list.getBackground());
setForeground(list.getForeground());
}
setFont(list.getFont());
setText(str);
return this;
}
}
// **********************************************
// Export
// **********************************************
/**
* Action to export all GeoElements that are currently displayed in this
* panel to a EuclidianView. The viewID for the target EuclidianView is
* stored as a property with key "euclidianViewID".
*
* This action is passed as a parameter to plotPanel where it is used in the
* plotPanel context menu and the EuclidianView transfer handler when the
* plot panel is dragged into an EV.
*/
AbstractAction exportToEVAction = new AbstractAction() {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(ActionEvent event) {
Integer euclidianViewID = (Integer) this
.getValue("euclidianViewID");
// if null ID then use EV1 unless shift is down, then use EV2
if (euclidianViewID == null) {
euclidianViewID = app.getShiftDown()
? app.getEuclidianView2(1).getViewID()
: app.getEuclidianView1().getViewID();
}
// do the export
getModel().exportGeosToEV(euclidianViewID);
updateOtherDataDisplay();
// null out the ID property
this.putValue("euclidianViewID", null);
}
};
@Override
public void addDisplayTypeItem(PlotType type) {
cbDisplayType.addItem(type);
}
protected void updateOtherDataDisplay() {
daView.updateOtherDataDisplay(this);
}
@Override
public void updateScatterPlot() {
plotPanelNorth.setLayout(new FlowLayout(FlowLayout.LEFT));
plotPanelSouth.setLayout(new FlowLayout(FlowLayout.CENTER));
plotPanelSouth.add(lblTitleX);
plotPanelSouth.add(fldTitleX);
plotPanelNorth.add(lblTitleY);
plotPanelNorth.add(fldTitleY);
metaPlotPanel.add(plotPanelNorth, BorderLayout.NORTH);
metaPlotPanel.add(plotPanelSouth, BorderLayout.SOUTH);
}
@Override
public void updateFrequencyTable() {
plotPanelSouth.setLayout(new BorderLayout());
plotPanelSouth.add(frequencyTable, BorderLayout.CENTER);
metaPlotPanel.add(plotPanelSouth, BorderLayout.SOUTH);
}
@Override
public void removeFrequencyTable() {
metaPlotPanel.remove(plotPanelSouth);
}
@Override
public void setSelectedType(PlotType type) {
cbDisplayType.setSelectedItem(type);
}
@Override
public void setTableFromGeoFrequencyTable(
AlgoFrequencyTable parentAlgorithm, boolean b) {
frequencyTable.setTableFromGeoFrequencyTable(parentAlgorithm, b);
}
@Override
public void updatePlotPanelSettings() {
plotPanel.commonFields.updateSettings(plotPanel,
getModel().getSettings());
}
private static void showCardPanel(JPanel panel, String id) {
((CardLayout) panel.getLayout()).show(panel, id);
}
@Override
public void showManualClassesPanel() {
showCardPanel(controlCards, "manualClassesPanel");
}
@Override
public void showNumClassesPanel() {
showCardPanel(controlCards, "numClassesPanel");
}
@Override
public void showPlotPanel() {
showCardPanel(displayCardPanel, "plotPanel");
}
@Override
public void updateStemPlot(String latex) {
imageContainer.setIcon(GeoGebraIconD.createLatexIcon(app, latex,
app.getPlainFont(), Color.BLACK, null));
btnOptions.setVisible(false);
if (hasControlPanel) {
showCardPanel(controlCards, "stemAdjustPanel");
}
((CardLayout) displayCardPanel.getLayout()).show(displayCardPanel,
"imagePanel");
}
@Override
public void updateXYTitles(boolean isPointList, boolean isLeftToRight) {
if (isPointList) {
fldTitleX.setText(daModel.getDataTitles()[0]);
fldTitleY.setText(daModel.getDataTitles()[0]);
} else {
if (isLeftToRight) {
fldTitleX.setText(daModel.getDataTitles()[0]);
fldTitleY.setText(daModel.getDataTitles()[1]);
} else {
fldTitleX.setText(daModel.getDataTitles()[1]);
fldTitleY.setText(daModel.getDataTitles()[0]);
}
}
}
@Override
public void geoToPlotPanel(GeoElement listGeo) {
listGeo.addView(plotPanel.getViewID());
plotPanel.add(listGeo);
listGeo.removeView(App.VIEW_EUCLIDIAN);
app.getEuclidianView1().remove(listGeo);
}
public DataDisplayModel getModel() {
return model;
}
public void setModel(DataDisplayModel model) {
this.model = model;
}
@Override
public void resize() {
// TODO Auto-generated method stub
}
public void update() {
model.updatePlot(true);
}
}