package org.geogebra.common.gui.view.data; import java.util.ArrayList; import org.geogebra.common.awt.GColor; import org.geogebra.common.euclidian.EuclidianConstants; import org.geogebra.common.gui.view.data.DataVariable.GroupType; import org.geogebra.common.kernel.Kernel; import org.geogebra.common.kernel.StringTemplate; import org.geogebra.common.kernel.arithmetic.ExpressionNodeConstants.StringType; import org.geogebra.common.kernel.geos.GeoElement; import org.geogebra.common.main.App; import org.geogebra.common.main.settings.DataAnalysisSettings; import org.geogebra.common.util.debug.Log; /** * View to display plots and statistical analysis of data. * * @author G. Sturr * */ public class DataAnalysisModel { public interface IDataAnalysisListener extends ICreateColor { void onModeChange(); void setPlotPanelOVNotNumeric(int mode); void setPlotPanelOVRawData(int mode); void setPlotPanelOVFrequency(int mode); void setPlotPanelOVClass(int mode); void setPlotPanelRegression(int mode); void setPlotPanelMultiVar(int mode); void loadDataTable(ArrayList<GeoElement> dataArray); void updateStatDataPanelVisibility(); DataAnalysisController getController(); void showComboPanel2(boolean show); void updateGUI(); DataAnalysisModel getModel(); DataDisplayModel getDisplayModel(int zeroBasedIndex); } public interface ICreateColor { GColor createColor(int idx); } // ggb private App app; private Kernel kernel; private StatGeo statGeo; public static final int MODE_ONEVAR = EuclidianConstants.MODE_SPREADSHEET_ONEVARSTATS; public static final int MODE_REGRESSION = EuclidianConstants.MODE_SPREADSHEET_TWOVARSTATS; public static final int MODE_MULTIVAR = EuclidianConstants.MODE_SPREADSHEET_MULTIVARSTATS; // colors public static final int TABLE_GRID_COLOR_IDX = 0; public static final int TABLE_HEADER_COLOR_IDX = 1; public static final int HISTOGRAM_COLOR_IDX = 2; public static final int BOXPLOT_COLOR_IDX = 3; public static final int BARCHART_COLOR_IDX = 4; public static final int DOTPLOT_COLOR_IDX = 5; public static final int NQPLOT_COLOR_IDX = 6; public static final int REGRESSION_COLOR_IDX = 7; public static final int OVERLAY_COLOR_IDX = 8; public static final int BLACK_COLOR_IDX = 9; public static final int WHITE_COLOR_IDX = 10; // flags private boolean showDataPanel = false; private boolean showStatPanel = false; private boolean showDataDisplayPanel2 = false; private boolean isIniting = true; public static final float opacityBarChart = 0.3f; public static final int thicknessCurve = 4; public static final int thicknessBarChart = 3; /** * @author mrb * * Order determines order in Two Variable Regression Analysis menu * For each String, getMenu(s) must be defined */ public enum Regression { NONE("None"), LINEAR("Linear"), LOG("Log"), POLY("Polynomial"), POW( "Power"), EXP("Exponential"), GROWTH( "Growth"), SIN("Sin"), LOGISTIC("Logistic"); // getMenu(label) must be defined private String label; Regression(String s) { this.label = s; } public String getLabel() { return label; } } // rounding constants for local number format private int printDecimals = 4, printFigures = -1; // public static final int regressionTypes = 9; private int regressionOrder = 2; private DataAnalysisController ctrl; private IDataAnalysisListener listener; /************************************************* * Constructs the view. * * @param app * @param listener * dialog listening to this */ public DataAnalysisModel(App app, IDataAnalysisListener listener, DataAnalysisController ctrl) { setIniting(true); this.app = app; this.kernel = app.getKernel(); this.setListener(listener); this.ctrl = ctrl; ctrl.setModel(this); setIniting(false); } /************************************************* * END constructor */ public void setView(DataSource dataSource, int mode, boolean forceModeUpdate) { ctrl.setDataSource(dataSource); if (dataSource == null) { ctrl.setValidData(false); } else { ctrl.setValidData(true); } if (mode == MODE_ONEVAR) { if (showDataPanel && dataSource.getGroupType() != GroupType.RAWDATA) { setShowDataPanel(false); } } // reinit the GUI if mode is changed if (this.getMode() != mode || forceModeUpdate) { this.setMode(mode); // first update the lists to make sure onModeChange // does not fail on one var -> two var mode change ctrl.updateDataLists(); getListener().onModeChange(); // TODO: why do this here? ctrl.updateDataAnalysisView(); } else { // just update data source ctrl.updateDataAnalysisView(); } // TODO is this needed? ctrl.setLeftToRight(true); } /** * set the data plot panels with default plots */ public void setDataPlotPanels() { switch (getMode()) { default: case MODE_ONEVAR: if (!isNumericData()) { getListener().setPlotPanelOVNotNumeric(getMode()); } else if (groupType() == GroupType.RAWDATA) { getListener().setPlotPanelOVRawData(getMode()); } else if (groupType() == GroupType.FREQUENCY) { getListener().setPlotPanelOVFrequency(getMode()); } else if (groupType() == GroupType.CLASS) { getListener().setPlotPanelOVClass(getMode()); } break; case MODE_REGRESSION: getListener().setPlotPanelRegression(getMode()); break; case MODE_MULTIVAR: getListener().setPlotPanelMultiVar(getMode()); showDataDisplayPanel2 = false; break; } } // ====================================== // Getters/setters // ====================================== public DataAnalysisController getDaCtrl() { return ctrl; } public DataSource getDataSource() { return ctrl.getDataSource(); } public void setDataSource(DataSource dataSource) { ctrl.setDataSource(dataSource); } public GroupType groupType() { return ctrl.getDataSource().getGroupType(); } public boolean showDataDisplayPanel2() { return showDataDisplayPanel2; } public boolean showDataPanel() { return showDataPanel; } public void setShowDataPanel(boolean isVisible) { if (showDataPanel == isVisible) { return; } showDataPanel = isVisible; getListener().updateStatDataPanelVisibility(); } public void setShowStatistics(boolean isVisible) { if (showStatPanel == isVisible) { return; } showStatPanel = isVisible; getListener().updateStatDataPanelVisibility(); } public boolean showStatPanel() { return showStatPanel; } public DataAnalysisController getController() { return ctrl; } public GeoElement getRegressionModel() { return ctrl.getRegressionModel(); } public StatGeo getStatGeo() { if (statGeo == null) { statGeo = new StatGeo(app, getListener()); } return statGeo; } public int getRegressionOrder() { return regressionOrder; } public void setRegressionMode(int regressionMode) { for (Regression l : Regression.values()) { if (l.ordinal() == regressionMode) { app.getSettings().getDataAnalysis().setRegression(l); ctrl.setRegressionGeo(); ctrl.updateAllPanels(true); return; } } Log.warn("no mode set in setRegressionMode()"); // this.regressionMode = Regression.NONE; } public Regression getRegressionMode() { return app.getSettings().getDataAnalysis().getRegression(); } public void setRegressionOrder(int regressionOrder) { this.regressionOrder = regressionOrder; } public App getApp() { return app; } public int getMode() { return app.getSettings().getDataAnalysis().getMode(); } public void setShowDataOptionsDialog(boolean showDialog) { // if (showDialog) { // showSourcePanel(); // this.dataTypePanel; // } else { // showMainPanel(); // } app.getDialogManager().showDataSourceDialog(getMode(), false); } public boolean isNumericData() { if (ctrl.getDataSource() == null) { return false; } return ctrl.getDataSource().isNumericData(); } // ================================================= // Handlers for Component Visibility // ================================================= public void setShowComboPanel2(boolean showComboPanel2) { this.showDataDisplayPanel2 = showComboPanel2; getListener().showComboPanel2(showComboPanel2); } // ================================================= // Number Format // // (use GeoGebra rounding settings unless decimals < 4) // ================================================= /** * Converts a double numeric value to formatted String * * @param x * number to be converted * @return formatted number string */ public String format(double x) { StringTemplate highPrecision; // override the default decimal place setting if less than 4 decimals if (printDecimals >= 0) { int d = printDecimals < 4 ? 4 : printDecimals; highPrecision = StringTemplate.printDecimals(StringType.GEOGEBRA, d, false); } else { highPrecision = StringTemplate.printFigures(StringType.GEOGEBRA, printFigures, false); } // get the formatted string String result = kernel.format(x, highPrecision); return result; } /** * Adjust local rounding constants to match global rounding constants and * update GUI when needed */ private void updateRounding() { if (kernel.useSignificantFigures) { if (printFigures != kernel.getPrintFigures()) { printFigures = kernel.getPrintFigures(); printDecimals = -1; getListener().updateGUI(); } } else if (printDecimals != kernel.getPrintDecimals()) { printDecimals = kernel.getPrintDecimals(); getListener().updateGUI(); } } public int getPrintDecimals() { return printDecimals; } public int getPrintFigures() { return printFigures; } public void remove(GeoElement geo) { // Application.debug("removed geo: " + geo.toString()); ctrl.handleRemovedDataGeo(geo); } public void update(GeoElement geo) { updateRounding(); // update the view if the geo is in the data source if (!isIniting() && ctrl.isInDataSource(geo)) { // use a runnable to allow spreadsheet table model to update app.invokeLater(new Runnable() { @Override public void run() { ctrl.updateDataAnalysisView(); } }); } } public String[] getDataTitles() { return ctrl.getDataTitles(); } public void updateSelection() { // updateDialog(true); } public boolean isIniting() { return isIniting; } public void setIniting(boolean isIniting) { this.isIniting = isIniting; } public boolean isMultiVar() { return getMode() == MODE_MULTIVAR; } public boolean isRegressionMode() { return getMode() == MODE_REGRESSION; } public void updateGUI() { getListener().updateGUI(); } public IDataAnalysisListener getListener() { return listener; } public void setListener(IDataAnalysisListener listener) { this.listener = listener; } public void setMode(int mode) { app.getSettings().getDataAnalysis().setMode(mode); } public void updateFromSettings() { DataAnalysisSettings settings = app.getSettings().getDataAnalysis(); if (settings.getItems().size() > 0) { DataSource source = new DataSource(app); source.setDataListFromSettings(settings.getItems(), settings.getMode()); this.setView(source, settings.getMode(), true); settings.getItems().clear(); } } public void getXML(StringBuilder sb) { sb.append("<dataAnalysis mode=\""); sb.append(getMode()); if (this.getListener().getDisplayModel(0).getSelectedPlot() != null) { sb.append("\" plot1=\""); sb.append(this.getListener().getDisplayModel(0).getSelectedPlot()); } if (this.getListener().getDisplayModel(1).getSelectedPlot() != null) { sb.append("\" plot2=\""); sb.append(this.getListener().getDisplayModel(1).getSelectedPlot()); } if (getRegressionMode() != null) { sb.append("\" regression=\""); sb.append(getRegressionMode()); } sb.append("\">\n"); getDataSource().getXMLDescription(sb); sb.append("</dataAnalysis>"); } }