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.Container;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.print.PageFormat;
import java.awt.print.Printable;
import java.util.ArrayList;
import java.util.List;
import javax.swing.BorderFactory;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JSplitPane;
import org.geogebra.common.awt.GColor;
import org.geogebra.common.gui.SetLabels;
import org.geogebra.common.gui.view.data.DataAnalysisModel;
import org.geogebra.common.gui.view.data.DataAnalysisModel.IDataAnalysisListener;
import org.geogebra.common.gui.view.data.DataDisplayModel;
import org.geogebra.common.gui.view.data.DataDisplayModel.PlotType;
import org.geogebra.common.gui.view.data.DataSource;
import org.geogebra.common.gui.view.data.DataVariable.GroupType;
import org.geogebra.common.kernel.Construction;
import org.geogebra.common.kernel.Kernel;
import org.geogebra.common.kernel.ModeSetter;
import org.geogebra.common.kernel.View;
import org.geogebra.common.kernel.geos.GProperty;
import org.geogebra.common.kernel.geos.GeoElement;
import org.geogebra.common.main.App;
import org.geogebra.common.main.GeoGebraColorConstants;
import org.geogebra.desktop.export.PrintPreviewD;
import org.geogebra.desktop.gui.util.FullWidthLayout;
import org.geogebra.desktop.main.AppD;
/**
* View to display plots and statistical analysis of data.
*
* @author G. Sturr
*
*/
public class DataAnalysisViewD extends JPanel
implements View, Printable, SetLabels, IDataAnalysisListener {
private static final long serialVersionUID = 1L;
// ggb
private AppD app;
private Kernel kernel;
private DataAnalysisModel model;
protected DataAnalysisControllerD daCtrl;
private DataAnalysisStyleBar stylebar;
// colors
public static final Color TABLE_GRID_COLOR = org.geogebra.desktop.awt.GColorD
.getAwtColor(GeoGebraColorConstants.TABLE_GRID_COLOR);
public static final Color TABLE_HEADER_COLOR = new Color(240, 240, 240);
public static final Color HISTOGRAM_COLOR = org.geogebra.desktop.awt.GColorD
.getAwtColor(GColor.BLUE);
public static final Color BOXPLOT_COLOR = org.geogebra.desktop.awt.GColorD
.getAwtColor(GeoGebraColorConstants.GGB_RED);
public static final Color BARCHART_COLOR = org.geogebra.desktop.awt.GColorD
.getAwtColor(GeoGebraColorConstants.GGB_GREEN);
public static final Color DOTPLOT_COLOR = org.geogebra.desktop.awt.GColorD
.getAwtColor(GeoGebraColorConstants.GRAY5);
public static final Color NQPLOT_COLOR = org.geogebra.desktop.awt.GColorD
.getAwtColor(GeoGebraColorConstants.GRAY5);
public static final Color REGRESSION_COLOR = Color.RED;
public static final Color OVERLAY_COLOR = org.geogebra.desktop.awt.GColorD
.getAwtColor(GeoGebraColorConstants.DARKBLUE);
private Color[] colors = { TABLE_GRID_COLOR, TABLE_HEADER_COLOR,
HISTOGRAM_COLOR, BOXPLOT_COLOR, BARCHART_COLOR, DOTPLOT_COLOR,
NQPLOT_COLOR, REGRESSION_COLOR, OVERLAY_COLOR, Color.BLACK,
Color.WHITE };
// main GUI panels
private DataPanelD dataPanel;
private StatisticsPanel statisticsPanel;
private RegressionPanel regressionPanel;
private DataDisplayPanelD dataDisplayPanel1, dataDisplayPanel2;
private JSplitPane statDataPanel, displayPanel, comboPanelSplit;
private JPanel mainPanel;
private int defaultDividerSize;
private static final String MainCard = "Card with main panel";
private static final String SourceCard = "Card with data type options";
/*************************************************
* Constructs the view.
*
* @param app
* @param mode
*/
public DataAnalysisViewD(AppD app, int mode) {
this.app = app;
this.kernel = app.getKernel();
daCtrl = new DataAnalysisControllerD(app, this);
model = new DataAnalysisModel(app, this, daCtrl);
dataDisplayPanel1 = new DataDisplayPanelD(this);
dataDisplayPanel2 = new DataDisplayPanelD(this);
setView(null, mode, true);
model.setIniting(false);
}
/*************************************************
* END constructor
*/
protected void setView(DataSource dataSource, int mode,
boolean forceModeUpdate) {
model.setView(dataSource, mode, forceModeUpdate);
updateFonts();
updateGUI();
revalidate();
}
public JComponent getStyleBar() {
if (stylebar == null) {
stylebar = new DataAnalysisStyleBar(app, this);
}
return stylebar;
}
private void buildStatisticsPanel() {
if (statisticsPanel != null) {
// TODO handle any orphaned geo children of stat panel
statisticsPanel = null;
}
statisticsPanel = new StatisticsPanel(app, this);
statisticsPanel.setBorder(BorderFactory.createEmptyBorder(4, 2, 2, 2));
}
@Override
public void setPlotPanelOVNotNumeric(int mode) {
dataDisplayPanel1.setPanel(PlotType.BARCHART, mode);
dataDisplayPanel2.setPanel(PlotType.BARCHART, mode);
}
@Override
public void setPlotPanelOVRawData(int mode) {
dataDisplayPanel1.setPanel(PlotType.HISTOGRAM, mode);
dataDisplayPanel2.setPanel(PlotType.BOXPLOT, mode);
}
@Override
public void setPlotPanelOVFrequency(int mode) {
dataDisplayPanel1.setPanel(PlotType.BARCHART, mode);
dataDisplayPanel2.setPanel(PlotType.BOXPLOT, mode);
}
@Override
public void setPlotPanelOVClass(int mode) {
dataDisplayPanel1.setPanel(PlotType.HISTOGRAM, mode);
dataDisplayPanel2.setPanel(PlotType.HISTOGRAM, mode);
}
@Override
public void setPlotPanelRegression(int mode) {
dataDisplayPanel1.setPanel(PlotType.SCATTERPLOT, mode);
dataDisplayPanel2.setPanel(PlotType.RESIDUAL, mode);
}
@Override
public void setPlotPanelMultiVar(int mode) {
dataDisplayPanel1.setPanel(PlotType.MULTIBOXPLOT, mode);
}
/**
* set the data plot panels with default plots
*/
public void setDataPlotPanels() {
model.setDataPlotPanels();
}
// Create DataPanel to display the current data set(s) and allow
// temporary editing.
protected DataPanelD buildDataPanel() {
if (dataPanel != null) {
// TODO handle any orphaned data panel geos
dataPanel = null;
}
if (!model.isMultiVar()) {
dataPanel = new DataPanelD(app, this);
dataPanel.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
}
return dataPanel;
}
@Override
public void loadDataTable(ArrayList<GeoElement> dataArray) {
if (dataPanel == null) {
buildDataPanel();
}
dataPanel.loadDataTable(dataArray);
}
protected DataPanelD getDataPanel() {
return dataPanel;
}
// =================================================
// GUI
// =================================================
private void updateLayout() {
this.removeAll();
// ===========================================
// statData panel
if (!model.isMultiVar()) {
statDataPanel = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
statisticsPanel, null);
statDataPanel.setResizeWeight(0.5);
statDataPanel.setBorder(BorderFactory.createEmptyBorder());
}
if (model.isMultiVar()) {
statDataPanel = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
statisticsPanel, null);
statDataPanel.setDividerSize(0);
statDataPanel.setBorder(BorderFactory.createEmptyBorder());
}
// ===========================================
// regression panel
if (model.isRegressionMode()) {
regressionPanel = new RegressionPanel(app, this);
}
// ===========================================
// plotComboPanel panel
// create a splitPane to hold the two plotComboPanels
comboPanelSplit = new JSplitPane(JSplitPane.VERTICAL_SPLIT,
dataDisplayPanel1, dataDisplayPanel2);
comboPanelSplit.setDividerLocation(0.5);
comboPanelSplit.setBorder(BorderFactory.createEmptyBorder());
// grab the default divider size
defaultDividerSize = comboPanelSplit.getDividerSize();
JPanel plotComboPanel = new JPanel(new BorderLayout());
plotComboPanel.add(comboPanelSplit, BorderLayout.CENTER);
// display panel
// ============================================
if (!model.isMultiVar()) {
displayPanel = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
statDataPanel, plotComboPanel);
displayPanel.setResizeWeight(0.5);
} else {
displayPanel = new JSplitPane(JSplitPane.VERTICAL_SPLIT,
plotComboPanel, statDataPanel);
displayPanel.setResizeWeight(1);
}
displayPanel.setBorder(BorderFactory.createEmptyBorder());
// main panel
// ============================================
mainPanel = new JPanel(new BorderLayout());
// mainPanel.add(getStyleBar(), BorderLayout.NORTH);
mainPanel.add(displayPanel, BorderLayout.CENTER);
if (model.isRegressionMode()) {
mainPanel.add(regressionPanel, BorderLayout.SOUTH);
}
// dataTypePanel = new DataViewSettingsPanel(app,
// StatDialog.MODE_ONEVAR);
JPanel p = new JPanel(new FullWidthLayout());
// p.add(dataTypePanel);
this.setLayout(new CardLayout());
add(mainPanel, MainCard);
add(p, SourceCard);
showMainPanel();
model.setShowComboPanel2(model.showDataDisplayPanel2());
updateStatDataPanelVisibility();
}
public void showSourcePanel() {
CardLayout c = (CardLayout) this.getLayout();
c.show(this, SourceCard);
}
public void showMainPanel() {
CardLayout c = (CardLayout) this.getLayout();
c.show(this, MainCard);
}
// ======================================
// Getters/setters
// ======================================
public DataAnalysisControllerD getDaCtrl() {
return daCtrl;
}
public DataSource getDataSource() {
return model.getDataSource();
}
public GroupType groupType() {
return daCtrl.getDataSource().getGroupType();
}
public DataDisplayPanelD getDataDisplayPanel1() {
return dataDisplayPanel1;
}
public DataDisplayPanelD getDataDisplayPanel2() {
return dataDisplayPanel2;
}
public RegressionPanel getRegressionPanel() {
return regressionPanel;
}
public StatisticsPanel getStatisticsPanel() {
return statisticsPanel;
}
/**
* Component representation of this view
*
* @return reference to self
*/
public JComponent getDataAnalysisViewComponent() {
return this;
}
@Override
public DataAnalysisControllerD getController() {
return daCtrl;
}
public GeoElement getRegressionModel() {
return daCtrl.getRegressionModel();
}
public AppD getApp() {
return app;
}
// public int getMode() {
// return mode;
// }
// =================================================
// Handlers for Component Visibility
// =================================================
@Override
public void updateStatDataPanelVisibility() {
if (statDataPanel == null) {
return;
}
if (!model.isMultiVar()) {
if (model.showDataPanel()) {
if (statDataPanel.getRightComponent() == null) {
statDataPanel.setRightComponent(dataPanel);
statDataPanel.resetToPreferredSizes();
}
} else {
if (statDataPanel.getRightComponent() != null) {
statDataPanel.setRightComponent(null);
statDataPanel.resetToPreferredSizes();
}
}
if (model.showStatPanel()) {
if (statDataPanel.getLeftComponent() == null) {
statDataPanel.setLeftComponent(statisticsPanel);
statDataPanel.resetToPreferredSizes();
}
} else {
if (statDataPanel.getLeftComponent() != null) {
statDataPanel.setLeftComponent(null);
statDataPanel.resetToPreferredSizes();
}
}
// hide/show divider
if (model.showDataPanel() && model.showStatPanel()) {
statDataPanel.setDividerSize(defaultDividerSize);
} else {
statDataPanel.setDividerSize(0);
}
// hide/show statData panel
if (model.showDataPanel() || model.showStatPanel()) {
if (displayPanel.getLeftComponent() == null) {
displayPanel.setLeftComponent(statDataPanel);
// displayPanel.resetToPreferredSizes();
displayPanel.setDividerLocation(
displayPanel.getLastDividerLocation());
displayPanel.setDividerSize(defaultDividerSize);
}
} else { // statData panel is empty, so hide it
displayPanel.setLastDividerLocation(
displayPanel.getDividerLocation());
displayPanel.setLeftComponent(null);
displayPanel.setDividerSize(0);
}
} else { // handle multi-variable case
if (model.showStatPanel()) {
if (displayPanel.getBottomComponent() == null) {
displayPanel.setBottomComponent(statDataPanel);
// displayPanel.resetToPreferredSizes();
displayPanel.setDividerLocation(
displayPanel.getLastDividerLocation());
displayPanel.setDividerSize(defaultDividerSize);
}
} else {
displayPanel.setLastDividerLocation(
displayPanel.getDividerLocation());
displayPanel.setBottomComponent(null);
displayPanel.setDividerSize(0);
}
}
updateFonts();
displayPanel.resetToPreferredSizes();
}
public void doPrint() {
List<Printable> l = new ArrayList<Printable>();
l.add(this);
PrintPreviewD.get(app, App.VIEW_DATA_ANALYSIS, PageFormat.LANDSCAPE)
.setVisible(true);
}
// =================================================
// Event Handlers and Updates
// =================================================
@Override
public void updateGUI() {
if (stylebar != null) {
stylebar.updateGUI();
}
revalidate();
repaint();
}
public void updateFonts() {
Font font = app.getPlainFont();
setFont(font);
setFontRecursive(this, font);
if (stylebar != null) {
stylebar.reinit();
}
dataDisplayPanel1.updateFonts(font);
dataDisplayPanel2.updateFonts(font);
if (dataPanel != null) {
dataPanel.updateFonts(font);
}
if (statisticsPanel != null) {
statisticsPanel.updateFonts(font);
}
if (regressionPanel != null) {
regressionPanel.updateFonts(font);
}
setLabels();
}
public void setFontRecursive(Container c, Font font) {
Component[] components = c.getComponents();
for (Component com : components) {
com.setFont(font);
if (com instanceof StatPanelInterface) {
((StatPanelInterface) com).updateFonts(font);
}
if (com instanceof Container) {
setFontRecursive((Container) com, font);
}
}
}
@Override
public void setLabels() {
if (model.isIniting()) {
return;
}
// setTitle(app.getMenu("OneVariableStatistics"));
if (model.isRegressionMode() && regressionPanel != null) {
regressionPanel.setLabels();
}
if (stylebar != null) {
stylebar.setLabels();
}
// call setLabels() for all child panels
setLabelsRecursive(this);
}
public void setLabelsRecursive(Container c) {
Component[] components = c.getComponents();
for (Component com : components) {
if (com instanceof StatPanelInterface) {
// System.out.println(c.getClass().getSimpleName());
((StatPanelInterface) com).setLabels();
} else if (com instanceof Container) {
setLabelsRecursive((Container) com);
}
}
}
// =================================================
// Number Format
//
// (use GeoGebra rounding settings unless decimals < 4)
// =================================================
// =================================================
// View Implementation
// =================================================
@Override
public void remove(GeoElement geo) {
model.remove(geo);
}
@Override
public void update(GeoElement geo) {
model.update(geo);
}
@Override
final public void updateVisualStyle(GeoElement geo, GProperty prop) {
update(geo);
}
@Override
public void updatePreviewFromInputBar(GeoElement[] geos) {
// TODO
}
@Override
public void add(GeoElement geo) {
// do nothing
}
@Override
public void clearView() {
// do nothing
}
@Override
public void rename(GeoElement geo) {
// do nothing
}
@Override
public void repaintView() {
// do nothing
}
@Override
public void updateAuxiliaryObject(GeoElement geo) {
// do nothing
}
@Override
public void reset() {
// do nothing
}
@Override
public void setMode(int mode, ModeSetter m) {
// do nothing
}
public void attachView() {
// clearView();
// kernel.notifyAddAll(this);
model.updateFromSettings();
kernel.attach(this);
// attachView to plot panels
dataDisplayPanel1.attachView();
if (dataDisplayPanel2 != null) {
dataDisplayPanel2.attachView();
}
}
public void detachView() {
dataDisplayPanel1.detachView();
if (dataDisplayPanel2 != null) {
dataDisplayPanel2.detachView();
}
daCtrl.removeStatGeos();
kernel.detach(this);
// clearView();
// kernel.notifyRemoveAll(this);
}
public String[] getDataTitles() {
return daCtrl.getDataTitles();
}
public void updateSelection() {
// updateDialog(true);
}
// =================================================
// Printing
// =================================================
@Override
public int print(Graphics g, PageFormat pageFormat, int pageIndex0) {
int pageIndex = ((AppD)kernel.getApplication()).getPrintPreview().adjustIndex(pageIndex0);
if (pageIndex > 0) {
return (NO_SUCH_PAGE);
}
Graphics2D g2d = (Graphics2D) g;
g2d.translate(pageFormat.getImageableX(), pageFormat.getImageableY());
// construction title
int y = 0;
Construction cons = kernel.getConstruction();
String title = cons.getTitle();
if (!"".equals(title)) {
Font titleFont = app.getBoldFont().deriveFont(Font.BOLD,
app.getBoldFont().getSize() + 2);
g2d.setFont(titleFont);
g2d.setColor(Color.black);
// Font fn = g2d.getFont();
FontMetrics fm = g2d.getFontMetrics();
y += fm.getAscent();
g2d.drawString(title, 0, y);
}
// construction author and date
String author = cons.getAuthor();
String date = cons.getDate();
String line = null;
if (!"".equals(author)) {
line = author;
}
if (!"".equals(date)) {
if (line == null) {
line = date;
} else {
line = line + " - " + date;
}
}
if (line != null) {
g2d.setFont(app.getPlainFont());
g2d.setColor(Color.black);
// Font fn = g2d.getFont();
FontMetrics fm = g2d.getFontMetrics();
y += fm.getHeight();
g2d.drawString(line, 0, y);
}
if (y > 0) {
g2d.translate(0, y + 20); // space between title and drawing
}
// scale the dialog so that it fits on one page.
double xScale = pageFormat.getImageableWidth() / this.getWidth();
double yScale = (pageFormat.getImageableHeight() - (y + 20))
/ this.getHeight();
double scale = Math.min(xScale, yScale);
this.paint(g2d, scale);
return (PAGE_EXISTS);
}
/**
* Paint the dialog with given scale factor (used for printing).
*/
public void paint(Graphics graphics, double scale) {
Graphics2D g2 = (Graphics2D) graphics;
g2.scale(scale, scale);
super.paint(graphics);
}
@Override
public int getViewID() {
return App.VIEW_DATA_ANALYSIS;
}
public JPopupMenu getExportMenu() {
return dataDisplayPanel1.getExportMenu();
}
@Override
public void startBatchUpdate() {
// TODO Auto-generated method stub
}
@Override
public void endBatchUpdate() {
// TODO Auto-generated method stub
}
@Override
public boolean suggestRepaint() {
return false;
// only for web
}
@Override
public DataAnalysisModel getModel() {
return model;
}
public void setModel(DataAnalysisModel model) {
this.model = model;
}
@Override
public void onModeChange() {
dataPanel = null;
buildStatisticsPanel();
setDataPlotPanels();
updateLayout();
}
@Override
public void showComboPanel2(boolean show) {
if (show) {
if (comboPanelSplit == null) {
// Application.debug("splitpane null");
}
comboPanelSplit.setBottomComponent(dataDisplayPanel2);
comboPanelSplit.setDividerLocation(200);
comboPanelSplit.setDividerSize(4);
} else {
comboPanelSplit.setBottomComponent(null);
comboPanelSplit.setLastDividerLocation(
comboPanelSplit.getDividerLocation());
comboPanelSplit.setDividerLocation(0);
comboPanelSplit.setDividerSize(0);
}
}
public String format(double value) {
return model.format(value);
}
@Override
public GColor createColor(int idx) {
Color c = colors[idx];
return GColor.newColor(c.getRed(), c.getGreen(), c.getBlue());
}
public void updateOtherDataDisplay(DataDisplayPanelD display) {
if (!model.showDataDisplayPanel2()) {
return;
}
if (display == dataDisplayPanel1) {
dataDisplayPanel2.update();
} else {
dataDisplayPanel1.update();
}
}
@Override
public DataDisplayModel getDisplayModel(int index) {
return index == 0 ? this.dataDisplayPanel1.getModel()
: this.dataDisplayPanel2.getModel();
}
}