package org.geogebra.common.gui.view.data; import java.util.ArrayList; import org.geogebra.common.awt.GPoint; import org.geogebra.common.gui.view.data.DataVariable.GroupType; import org.geogebra.common.gui.view.spreadsheet.CellRange; import org.geogebra.common.gui.view.spreadsheet.CellRangeProcessor; import org.geogebra.common.gui.view.spreadsheet.MyTable; import org.geogebra.common.gui.view.spreadsheet.SpreadsheetViewInterface; import org.geogebra.common.kernel.geos.GeoElement; import org.geogebra.common.kernel.geos.GeoElementSpreadsheet; import org.geogebra.common.kernel.geos.GeoList; import org.geogebra.common.main.App; import org.geogebra.common.main.Feature; import org.geogebra.common.main.SelectionManager; import org.geogebra.common.plugin.GeoClass; import org.geogebra.common.util.debug.Log; /** * Manages a list of DataVariables for the DataAnalysisView. * * @author G. Sturr * */ public class DataSource { private final App app; private final SelectionManager selection; private ArrayList<DataVariable> dataList; private int selectedIndex; private boolean frequencyFromColumn = false; // ==================================== // Constructor // ==================================== /** * @param app */ public DataSource(App app) { this.app = app; this.selection = app.getSelectionManager(); dataList = new ArrayList<DataVariable>(); selectedIndex = 0; } // ==================================== // Add/Remove // ==================================== public boolean isEmpty() { return dataList.size() == 0; } public void clearData() { // TODO dereference geos from all DataItems dataList.clear(); } // ==================================== // Getters/Setters // ==================================== public int getSelectedIndex() { return selectedIndex; } public void setSelectedIndex(int selectedIndex) { this.selectedIndex = selectedIndex; } public boolean enableHeader() { return getSelectedDataVariable().enableHeader(); } public void setEnableHeader(boolean enableHeader) { getSelectedDataVariable().setEnableHeader(enableHeader); } public boolean isNumericData() { if (getSelectedDataVariable() == null) { return false; } return getSelectedDataVariable().getGeoClass() == GeoClass.NUMERIC; } public void setNumericData(boolean isNumericData) { getSelectedDataVariable().setGeoClass(GeoClass.NUMERIC); } public GeoClass getGeoClass() { return getSelectedDataVariable().getGeoClass(); } public void setGeoClass(GeoClass geoClass) { getSelectedDataVariable().setGeoClass(geoClass); } public boolean isPointData() { return getSelectedDataVariable().getGeoClass() == GeoClass.POINT; } public DataVariable getDataVariable(int index) { return dataList.get(index); } public DataVariable getSelectedDataVariable() { if (selectedIndex >= dataList.size()) { return null; } return dataList.get(selectedIndex); } public GroupType getGroupType() { if (isEmpty()) { return GroupType.RAWDATA; // default } return getSelectedDataVariable().getGroupType(); } public GroupType getGroupType(int varIndex) { if (varIndex >= dataList.size()) { return GroupType.RAWDATA; // default } return dataList.get(varIndex).getGroupType(); } public void setGroupType(GroupType groupType, int varIndex) { dataList.get(varIndex).setGroupType(groupType); } public double getClassStart() { return getSelectedDataVariable().getClassStart(); } public void setClassStart(double classStart) { getSelectedDataVariable().setClassStart(classStart); } public double getClassWidth() { return getSelectedDataVariable().getClassWidth(); } public void setClassWidth(double classWidth) { getSelectedDataVariable().setClassWidth(classWidth); } protected CellRangeProcessor crProcessor(App app) { return spreadsheetTable().getCellRangeProcessor(); } private MyTable spreadsheetTable() { SpreadsheetViewInterface spvi = app .getGuiManager().getSpreadsheetView(); return (MyTable) spvi.getSpreadsheetTable(); } /** * Sets the DataItem at a given location to reference the currently selected * GeoElements * * @param dataIndex * index of a DataVariable in dataList * @param itemIndex * index of a DataItem in the given DataVariable * */ public void setDataItemToGeoSelection(int dataIndex, int itemIndex) { if (dataList.get(dataIndex) == null) { return; } dataList.get(dataIndex).setDataItem(itemIndex, createDataItemFromGeoSelection()); } /** * Returns a DataItem that references data from the currently selected geos. * * @return Either a spreadsheet cell range, a GeoList or null if the * selected geos cannot form a DataItem */ private DataItem createDataItemFromGeoSelection() { if (selection.getSelectedGeos() == null || selection.getSelectedGeos().size() == 0) { return null; } GeoElement geo = selection.getSelectedGeos().get(0); if (geo.isGeoList()) { return new DataItem((GeoList) geo); } else if (geo.getSpreadsheetCoords() != null) { return new DataItem(CellRangeProcessor .clone(spreadsheetTable().getSelectedCellRanges())); } return null; } // ===================================== // Getters for the source dialog table // ===================================== /** * @return 2D array of data from the currently selected DataVariable */ public String[][] getTableData() { return getTableData(getSelectedIndex()); } /** * @param dataIndex * @return 2D array of data from the DataVariable at the given index * position */ public String[][] getTableData(int dataIndex) { if (dataIndex >= dataList.size()) { return null; } ArrayList<String[]> list = new ArrayList<String[]>(); list.addAll(dataList.get(dataIndex).getStringData()); // get maximum row count int rowCount = 0; for (String[] s : list) { rowCount = Math.max(rowCount, s.length); } // create data array String[][] data = new String[rowCount][list.size()]; for (int c = 0; c < list.size(); c++) { for (int r = 0; r < list.get(c).length; r++) { data[r][c] = list.get(c)[r]; } } return data; } /** * @return data titles from the currently selected DataVariable */ public String[] getTitles() { return getTitles(getSelectedIndex()); } /** * @param dataIndex * @return data titles from the DataVariable at the given index position */ public String[] getTitles(int dataIndex) { if (dataIndex >= dataList.size()) { return null; } ArrayList<String> list = new ArrayList<String>(); list.addAll(dataList.get(dataIndex).getTitles(app)); String[] s = list.toArray(new String[list.size()]); return s; } /** * @return descriptions (e.g. "Data", "Frequency" etc.) of the DataItems in * the currently selected DataVariable */ public String[] getDescriptions() { ArrayList<String> list = getSelectedDataVariable().getColumnNames(); return list.toArray(new String[list.size()]); } /** * @return descriptions (e.g. "Data", "Frequency" etc.) of the DataItems in * the DataVariable at the given index position */ public String[] getDescriptions(int dataIndex) { if (dataIndex >= dataList.size()) { return null; } ArrayList<String> list = dataList.get(dataIndex).getColumnNames(); return list.toArray(new String[list.size()]); } // ========================================= // GeoLists for DataAnalysisView // ========================================= /** * Converts the currently selected DataVariable to a list of GeoLists * * @param mode * @param leftToRight * @param doCopy * @return arrayList of GeoLists corresponding to data stored in the given * DataVariable */ public ArrayList<GeoList> toGeoList(int mode, boolean leftToRight, boolean doCopy) { return toGeoList(mode, leftToRight, doCopy, getSelectedIndex()); } /** * Converts a DataVariable at a given index position in dataList to a list * of GeoLists * * @param mode * @param leftToRight * @param doCopy * @param dataIndex * @return arrayList of GeoLists corresponding to data stored in the * DataVariable at the given index position */ public ArrayList<GeoList> toGeoList(int mode, boolean leftToRight, boolean doCopy, int dataIndex) { if (dataList == null || dataList.size() == 0) { return null; } return dataList.get(dataIndex).getGeoListData(app, mode, leftToRight, doCopy); } /** * @param mode * @param leftToRight * @param doCopy * @return */ public ArrayList<GeoList> toGeoListAll(int mode, boolean leftToRight, boolean doCopy) { if (dataList == null || dataList.size() == 0) { return null; } ArrayList<GeoList> list = new ArrayList<GeoList>(); for (DataVariable var : dataList) { list.addAll(var.getGeoListData(app, mode, leftToRight, doCopy)); } return list; } // ==================================== // Automatic Source Generation // ==================================== /** * Sets this DataSource to the currently selected GeoElements. * * @param mode * Data analysis mode */ public void setDataListFromSelection(int mode) { dataList.clear(); if (selection.getSelectedGeos() == null || selection.getSelectedGeos().size() == 0) { return; } try { // if the first selected geo is a spreadsheet cell then use the // spreadsheet's selected cell range list if (selection.getSelectedGeos().get(0) .getSpreadsheetCoords() != null) { setDataListFromSpreadsheet(mode); } else { // otherwise add all selected GeoLists setDataListFromGeoList(mode); } return; } catch (Exception e) { e.printStackTrace(); } } public void setDataListFromSettings(ArrayList<String> items, int mode) { dataList.clear(); ArrayList<CellRange> ranges = new ArrayList<CellRange>(); for (int i = 0; i < items.size(); i++) { String range = items.get(i); GPoint start = GeoElementSpreadsheet.getSpreadsheetCoordsForLabel( range.substring(0, range.indexOf(':'))); GPoint end = GeoElementSpreadsheet.getSpreadsheetCoordsForLabel( range.substring(range.indexOf(':') + 1)); CellRange cr = new CellRange(app, start.x, start.y, end.x, end.y); ranges.add(cr); } setDataListFromSpreadsheet(mode, ranges); } /** * Creates a new list of DataVariables from the currently selected GeoLists */ private void setDataListFromGeoList(int mode) { // create a list of GeoLists from the selected elements ArrayList<GeoList> list = new ArrayList<GeoList>(); for (GeoElement geo : selection.getSelectedGeos()) { if (geo.isGeoList() && !((GeoList) geo).isMatrix()) { list.add((GeoList) geo); } } if (list.size() == 0) { return; } ArrayList<DataItem> itemList = new ArrayList<DataItem>(); DataVariable var = new DataVariable(app); switch (mode) { default: case DataAnalysisModel.MODE_ONEVAR: itemList.add(new DataItem(list.get(0))); var.setDataVariableAsRawData(GeoClass.NUMERIC, itemList); break; case DataAnalysisModel.MODE_REGRESSION: if (list.get(0).getElementType() == GeoClass.POINT) { itemList.add(new DataItem(list.get(0))); var.setDataVariableAsRawData(GeoClass.POINT, itemList); } else { itemList.add(new DataItem(list.get(0))); if (list.size() == 1) { itemList.add(new DataItem()); } var.setDataVariableAsRawData(GeoClass.NUMERIC, itemList); } break; case DataAnalysisModel.MODE_MULTIVAR: for (GeoList geo : list) { itemList.add(new DataItem(geo)); } var.setDataVariableAsRawData(GeoClass.NUMERIC, itemList); break; } dataList.add(var); } /** * Creates a new list of DataVariables from the current spreadsheet * selection. */ private void setDataListFromSpreadsheet(int mode) { // The cell range list returned by the spreadsheet can change // dynamically, so we need to use a copy. ArrayList<CellRange> rangeList = CellRangeProcessor .clone(spreadsheetTable().getSelectedCellRanges()); setDataListFromSpreadsheet(mode, rangeList); } private void setDataListFromSpreadsheet(int mode, ArrayList<CellRange> rangeList) { DataVariable var = new DataVariable(app); ArrayList<DataItem> itemList = new ArrayList<DataItem>(); switch (mode) { default: case DataAnalysisModel.MODE_ONEVAR: if (isFrequencyFromColumn()) { CellRange cr = rangeList.get(0); cr.debug(); if (cr.is2D()) { var.setGroupType(GroupType.FREQUENCY); add1DCellRanges(rangeList, itemList); ArrayList<DataItem> values = new ArrayList<DataItem>(); values.add(itemList.get(0)); var.setDataVariable(GroupType.FREQUENCY, GeoClass.NUMERIC, values, itemList.get(1), null, null); break; } } itemList.add(new DataItem(rangeList)); var.setDataVariableAsRawData(GeoClass.NUMERIC, itemList); break; case DataAnalysisModel.MODE_REGRESSION: // test if there is at least one GeoPoint in the selection boolean hasPoint = crProcessor(app).containsGeoClass(rangeList, GeoClass.POINT); if (hasPoint) { // single list of points itemList.add(new DataItem(rangeList)); var.setDataVariableAsRawData(GeoClass.POINT, itemList); } else { // separate x, y lists add1DCellRanges(rangeList, itemList); if (itemList.size() < 2) { itemList.add(new DataItem()); } var.setDataVariableAsRawData(GeoClass.NUMERIC, itemList); } break; case DataAnalysisModel.MODE_MULTIVAR: ArrayList<CellRange> r; for (CellRange cr : rangeList) { if (cr.isRow() || cr.isPartialRow()) { r = cr.toPartialRowList(); for (CellRange cr2 : r) { itemList.add(new DataItem(cr2)); } } else { r = cr.toPartialColumnList(); for (CellRange cr2 : r) { itemList.add(new DataItem(cr2)); } } } var.setDataVariableAsRawData(GeoClass.NUMERIC, itemList); break; } dataList.add(var); } /** * Attempts to extract two 1D cell ranges from the given cell range list and * then add these as DataItems to the given DataItem list. The orientation * of the 1D cell ranges (vertical or horizontal) is determined from the * shape of the given cell ranges. */ private static void add1DCellRanges(ArrayList<CellRange> rangeList, ArrayList<DataItem> itemList) { ArrayList<CellRange> r = null; boolean scanByColumn = rangeList.get(0).getActualDimensions()[1] <= 2; if (rangeList.size() == 1) { // single cell range if (scanByColumn) { // list of vertical cell ranges r = rangeList.get(0).toPartialColumnList(); } else { // list of horizontal cell ranges r = rangeList.get(0).toPartialRowList(); } if (r != null) { if (r.size() > 0) { itemList.add(new DataItem(r.get(0))); } if (r.size() > 1) { itemList.add(new DataItem(r.get(1))); } } } else if (rangeList.size() == 2) { // two separate cell ranges if (scanByColumn) { // extract vertical cell ranges itemList.add(new DataItem( rangeList.get(0).toPartialColumnList().get(0))); itemList.add(new DataItem( rangeList.get(1).toPartialColumnList().get(0))); } else { // extract horizontal cell range itemList.add(new DataItem( rangeList.get(0).toPartialRowList().get(0))); itemList.add(new DataItem( rangeList.get(1).toPartialRowList().get(0))); } } } // ==================================== // Utility methods // ==================================== /** * Returns true if the current data source contains the specified GeoElement */ protected boolean isInDataSource(GeoElement geo) { for (DataVariable var : dataList) { if (var.isInDataSource(geo)) { return true; } } return false; } public void getXMLDescription(StringBuilder sb) { for (DataVariable var : dataList) { var.getXML(sb); } Log.debug(sb.toString()); } /** * * @return if frequency data comes from column. */ public boolean isFrequencyFromColumn() { if (!app.has(Feature.ONE_VAR_FREQUENCY_TABLE)) { return false; } return frequencyFromColumn; } /** * When set to true, spreadsheet 2nd column of selected cells are treated as * frequency data for One-variable analysis. * * @param value * to set */ public void setFrequencyFromColumn(boolean value) { this.frequencyFromColumn = value; } }