package org.geogebra.common.gui.view.data; import java.util.ArrayList; import org.geogebra.common.kernel.Construction; import org.geogebra.common.kernel.arithmetic.NumberValue; import org.geogebra.common.kernel.geos.GeoElement; import org.geogebra.common.kernel.geos.GeoFunction; import org.geogebra.common.kernel.geos.GeoList; import org.geogebra.common.main.App; import org.geogebra.common.util.debug.Log; /** * Class to control data management for the DataAnalysisView. * * @author G. Sturr * */ public abstract class DataAnalysisController { private Construction cons; // private DataAnalysisModel view; private StatGeo statGeo; private DataSource dataSource; private ArrayList<GeoElement> dataArray; private GeoList dataSelected; private boolean leftToRight = true; private boolean isValidData = true; private GeoElement geoRegression; private DataAnalysisModel model; /**************************************************** * Constructs a StatDialogController * * @param app * application */ public DataAnalysisController(App app) { this.cons = app.getKernel().getConstruction(); } // ========================================== // Getters/Setters // ========================================== protected int getMode() { return getModel().getMode(); } public ArrayList<GeoElement> getDataArray() { return dataArray; } public GeoList getDataSelected() { return dataSelected; } public boolean isValidData() { return isValidData; } public void setValidData(boolean isValidData) { this.isValidData = isValidData; } public void setLeftToRight(boolean leftToRight) { this.leftToRight = leftToRight; } public boolean isLeftToRight() { return leftToRight; } public GeoElement getRegressionModel() { return geoRegression; } public void setRegressionModel(GeoFunction regressionModel) { this.geoRegression = regressionModel; } // ========================================== // Data source // ========================================== public DataSource getDataSource() { return dataSource; } protected void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } /** * Returns true if the current data source contains the specified GeoElement */ protected boolean isInDataSource(GeoElement geo) { if (dataSource == null) { return false; } return dataSource.isInDataSource(geo); } /** * Loads GeoElements from dataSource into (GeoList) dataListSelected and * (ArrayList) dataArray . * * @param doCopy * if true lists are loaded as copies */ public void loadDataLists(boolean doCopy) { if (dataSelected != null) { dataSelected.remove(); } if (dataSource == null) { setValidData(false); return; } // retrieve data from the data source as a list of GeoLists ArrayList<GeoList> list; switch (getMode()) { default: case DataAnalysisModel.MODE_ONEVAR: case DataAnalysisModel.MODE_REGRESSION: list = dataSource.toGeoList(getMode(), leftToRight, doCopy, 0); break; case DataAnalysisModel.MODE_MULTIVAR: list = dataSource.toGeoListAll(getMode(), leftToRight, doCopy); break; } // validate if (!isValidList(list)) { setValidData(false); return; } setValidData(true); // convert the list to (GeoList) dataSelected // TODO: dataSelected should always be a list of lists if (list.size() == 1) { dataSelected = list.get(0); } else { dataSelected = new GeoList(cons); for (GeoList geoList : list) { // TODO: suppress label creation? dataSelected.add(geoList); } } // add the selected geos to the dataPanel array loadDataPanelArray(); } private static boolean isValidList(ArrayList<GeoList> list) { if (list == null || list.size() == 0) { return false; } // check for empty lists for (int i = 0; i < list.size(); i++) { // System.out.println("data list " + i + " size: " // + list.get(i).size()); if (list.get(i).size() < 1) { // System.out.println("invalid data list"); return false; } } // TODO: add validation checks for different modes, e.g. are data // lengths equal? return true; } /** * Loads the DataPanel array with references to all geos in the DataSource. * This is used by DataPanel to add/remove geos from the dataSelected list * without needing to change the DataSource. * * TODO: use a more efficient method: maintain a map of removed geos as * index/geo pairs */ private void loadDataPanelArray() { // create and update dataArray (list of all geos contained in // dataSelected) if (dataSelected != null) { if (dataArray == null) { dataArray = new ArrayList<GeoElement>(); } dataArray.clear(); for (int i = 0; i < dataSelected.size(); i++) { dataArray.add(i, dataSelected.get(i)); // Log.error(dataSelected.get(i).toOutputValueString( // StringTemplate.defaultTemplate)); } // load dataPanel with dataArray if (!getModel().isMultiVar()) { getModel().getListener().loadDataTable(dataArray); } } else { Log.error("null dataSelected, mode = " + getMode()); } } /** * Add/remove elements from the selected data list. Called by the data panel * on checkbox click. */ public void updateSelectedDataList(int index, boolean doAdd) { GeoElement geo = dataArray.get(index); if (doAdd) { dataSelected.add(geo); } else { dataSelected.remove(geo); } // debugDataSelected(); dataSelected.updateRepaint(); updateAllPanels(false); updateRegressionPanel(); // System.out.println("updateSelectedList: " + index + " " + doAdd); } protected abstract void updateRegressionPanel(); /** * Gets the data titles from the source cells. * * @return String array of data titles */ public String[] getDataTitles() { return dataSource == null ? new String[0] : dataSource.getTitles(); // return dataSource.getDataTitles(mode(), leftToRight); } public void swapXY() { leftToRight = !leftToRight; updateDataAnalysisView(); clearPredictionPanel(); } protected abstract void clearPredictionPanel(); /** * Updates the view to reflect the current values of the GeoElements in the * data source. */ public void updateDataAnalysisView() { updateDataLists(); if (isValidData) { if (getModel().isRegressionMode()) { setRegressionGeo(); updateRegressionPanel(); } // update the panels // TODO: internal geos are all redefined, this is not efficient and // needs optimizing updateAllPanels(true); } getModel().updateGUI(); } /** * Loads the data lists with GeoElements references from the data source. * All internal GeoElements are removed to prevent orphan links to * previously loaded GeoElements. */ public void updateDataLists() { removeStatGeos(); loadDataLists(true); } /** * Updates all panels in the DataAnalysisView. * * @param doRedefine * if true then the internal GeoElements will be redefined. */ public abstract void updateAllPanels(boolean doRedefine); protected void handleRemovedDataGeo(GeoElement geo) { // System.out.println("removed: " + geo.toString()); if (isInDataSource(geo)) { // System.out.println("stat dialog removed: " + geo.toString()); dataSource.clearData(); this.setValidData(false); updateDataAnalysisView(); } } public void setRegressionGeo() { removeRegressionGeo(); geoRegression = statGeo.createRegressionPlot(dataSelected, getModel().getRegressionMode(), getModel().getRegressionOrder(), false); } public void removeRegressionGeo() { if (geoRegression != null) { geoRegression.remove(); geoRegression.doRemove(); geoRegression = null; } } public void disposeDataListSelected() { dataSelected = null; } /** * Removes all geos maintained by this dialog and its child components */ public void removeStatGeos() { if (dataSelected != null) { dataSelected.remove(); dataSelected = null; } removeRegressionGeo(); removeGeos(); } protected abstract void removeGeos(); public double[] getValueArray(GeoList dataList) { ArrayList<Double> list = new ArrayList<Double>(); for (int i = 0; i < dataList.size(); i++) { GeoElement geo = dataList.get(i); if (geo instanceof NumberValue) { NumberValue num = (NumberValue) geo; list.add(num.getDouble()); } } double[] val = new double[list.size()]; for (int i = 0; i < list.size(); i++) { val[i] = list.get(i); } return val; } public DataAnalysisModel getModel() { return model; } public void setModel(DataAnalysisModel model) { this.model = model; this.statGeo = model.getStatGeo(); } }