package org.geogebra.common.gui.view.data;
import java.util.ArrayList;
//import geogebra.common.kernel.geos.GeoList;
//import geogebra.common.kernel.geos.GeoNumeric;
//import geogebra.common.kernel.statistics.AlgoFrequencyTable;
import org.geogebra.common.euclidian.EuclidianView;
import org.geogebra.common.gui.view.data.DataAnalysisModel.Regression;
import org.geogebra.common.gui.view.data.DataVariable.GroupType;
import org.geogebra.common.kernel.Construction;
import org.geogebra.common.kernel.geos.GeoElement;
import org.geogebra.common.kernel.geos.GeoList;
import org.geogebra.common.kernel.geos.GeoNumeric;
import org.geogebra.common.kernel.kernelND.GeoElementND;
import org.geogebra.common.kernel.statistics.AlgoFrequencyTable;
import org.geogebra.common.main.App;
import org.geogebra.common.main.Localization;
import org.geogebra.common.util.debug.Log;
/**
* Class to dynamically display plots and statistics in coordination with the
* DataAnalysisView.
*
* @author G.Sturr
*
*/
public class DataDisplayModel {
public interface IDataDisplayListener {
void addDisplayTypeItem(PlotType histogram);
void updateScatterPlot();
void updateFrequencyTable();
void setSelectedType(PlotType type);
void showControlPanel();
void setOptionsButtonVisible();
void showInvalidDataDisplay();
void setTableFromGeoFrequencyTable(AlgoFrequencyTable parentAlgorithm,
boolean b);
void updatePlotPanelSettings();
void showManualClassesPanel();
void showNumClassesPanel();
void showPlotPanel();
void updateStemPlot(String latex);
void updateXYTitles(boolean isPointList, boolean isLeftToRight);
void geoToPlotPanel(GeoElement listGeo);
void removeFrequencyTable();
void resize();
// void updatePlot(boolean b);
}
//
// // ggb fields
private App app;
private DataAnalysisModel daModel;
private StatGeo statGeo;
//
// // data view mode
// private int mode;
//
@SuppressWarnings("javadoc")
// keys for these need to be in "menu" category of ggbtrans
public enum PlotType {
HISTOGRAM("Histogram"), BOXPLOT("Boxplot"), DOTPLOT(
"DotPlot"), NORMALQUANTILE("NormalQuantilePlot"), STEMPLOT(
"StemPlot"), BARCHART("BarChart"), SCATTERPLOT(
"Scatterplot"), RESIDUAL(
"ResidualPlot"), MULTIBOXPLOT(
"StackedBoxPlots");
/**
* the associated key from menu.properties app.getMenu(key) gives the
* translation (for the menu) in the current locale
*/
final private String key;
PlotType(String key) {
this.key = key;
}
final public String getKey() {
return key;
}
/**
* @param app
* App
* @return translated key for the current locale eg "StemPlot" ->
* "Stem and Leaf Diagram" in en_GB
*/
public String getTranslatedKey(Localization loc) {
return loc.getMenu(key);
}
}
// currently selected plot type
private PlotType selectedPlot;
private StatPanelSettings settings;
private ArrayList<GeoElementND> plotGeoList;
private GeoElement[] boxPlotTitles;
private GeoElementND frequencyPolygon, histogram, barChart, scatterPlotLine;
private GeoElement dotPlot, normalCurve, scatterPlot, residualPlot,
nqPlot, boxPlot, freqTableGeo;
private boolean hasControlPanel = true;
private IDataDisplayListener listener;
/*****************************************
* Constructs a ComboStatPanel
*
* @param daModel
* daModel
*/
public DataDisplayModel(DataAnalysisModel daModel,
IDataDisplayListener listener) {
this.daModel = daModel;
this.app = daModel.getApp();
this.statGeo = daModel.getStatGeo();
this.listener = listener;
plotGeoList = new ArrayList<GeoElementND>();
// create settings
settings = new StatPanelSettings();
settings.setDataSource(daModel.getDataSource());
}
public void updatePlot(PlotType index, int mode) {
daModel.setMode(mode);
this.setSelectedPlot(index);
getSettings().setDataSource(daModel.getDataSource());
}
public void fillDisplayTypes() {
switch (daModel.getMode()) {
default:
case DataAnalysisModel.MODE_ONEVAR:
if (!daModel.isNumericData()) {
listener.addDisplayTypeItem(PlotType.BARCHART);
}
else if (getSettings().groupType() == GroupType.RAWDATA) {
listener.addDisplayTypeItem(PlotType.HISTOGRAM);
listener.addDisplayTypeItem(PlotType.BARCHART);
listener.addDisplayTypeItem(PlotType.BOXPLOT);
listener.addDisplayTypeItem(PlotType.DOTPLOT);
listener.addDisplayTypeItem(PlotType.STEMPLOT);
listener.addDisplayTypeItem(PlotType.NORMALQUANTILE);
}
else if (getSettings().groupType() == GroupType.FREQUENCY) {
listener.addDisplayTypeItem(PlotType.HISTOGRAM);
listener.addDisplayTypeItem(PlotType.BARCHART);
listener.addDisplayTypeItem(PlotType.BOXPLOT);
} else if (getSettings().groupType() == GroupType.CLASS) {
listener.addDisplayTypeItem(PlotType.HISTOGRAM);
}
break;
case DataAnalysisModel.MODE_REGRESSION:
listener.addDisplayTypeItem(PlotType.SCATTERPLOT);
listener.addDisplayTypeItem(PlotType.RESIDUAL);
break;
case DataAnalysisModel.MODE_MULTIVAR:
listener.addDisplayTypeItem(PlotType.MULTIBOXPLOT);
break;
}
listener.setSelectedType(getSelectedPlot());
}
/**
* Updates the plot panel. Adds/removes additional panels as needed for the
* current selected plot.
*/
public void updatePlotPanelLayout() {
if (getSelectedPlot() == PlotType.SCATTERPLOT) {
listener.updateScatterPlot();
}
else if (getSelectedPlot() == PlotType.HISTOGRAM
|| getSelectedPlot() == PlotType.BARCHART) {
if (getSettings().isShowFrequencyTable()) {
listener.updateFrequencyTable();
}
}
}
// ==============================================
// DISPLAY UPDATE
// ==============================================
/**
* Update the plot.
*
* @param doCreate
* if true then the plot GeoElements are redefined
*/
public void updatePlot(boolean doCreate) {
GeoList dataListSelected = daModel.getController().getDataSelected();
if (dataListSelected == null) {
Log.debug("[DDMODEL] dataListSelected is null!");
return;
}
if (hasControlPanel) {
listener.showControlPanel();
}
if (doCreate) {
clearPlotGeoList();
}
listener.setOptionsButtonVisible();
updatePlotPanelLayout();
// if invalid data, show blank plot and exit
if (!daModel.getController().isValidData()) {
listener.showInvalidDataDisplay();
return;
}
try {
switch (selectedPlot) {
case HISTOGRAM:
if (doCreate) {
if (histogram != null) {
histogram.remove();
}
histogram = statGeo.createHistogram(dataListSelected,
settings, false);
plotGeoList.add(histogram);
if (frequencyPolygon != null) {
frequencyPolygon.remove();
}
if (settings.isHasOverlayPolygon()) {
frequencyPolygon = statGeo.createHistogram(
dataListSelected, settings, true);
plotGeoList.add(frequencyPolygon);
}
if (normalCurve != null) {
normalCurve.remove();
}
if (settings.isHasOverlayNormal()) {
normalCurve = statGeo
.createNormalCurveOverlay(dataListSelected);
plotGeoList.add(normalCurve);
}
if (freqTableGeo != null) {
freqTableGeo.remove();
}
if (settings.isShowFrequencyTable()) {
freqTableGeo = statGeo.createFrequencyTableGeo(
(GeoNumeric) histogram, selectedPlot);
plotGeoList.add(freqTableGeo);
}
}
// update the frequency table
if (settings.isShowFrequencyTable()) {
listener.setTableFromGeoFrequencyTable(
(AlgoFrequencyTable) freqTableGeo
.getParentAlgorithm(),
true);
} else {
listener.removeFrequencyTable();
}
// update settings
statGeo.getHistogramSettings(dataListSelected, histogram,
settings);
listener.updatePlotPanelSettings();
if (hasControlPanel && settings.getDataSource()
.getGroupType() != GroupType.CLASS) {
if (settings.isUseManualClasses()) {
listener.showManualClassesPanel();
} else {
listener.showNumClassesPanel();
}
}
listener.showPlotPanel();
break;
case BOXPLOT:
if (doCreate) {
if (boxPlot != null) {
boxPlot.remove();
}
boxPlot = statGeo.createBoxPlot(dataListSelected, settings);
plotGeoList.add(boxPlot);
}
statGeo.getBoxPlotSettings(dataListSelected, settings);
listener.updatePlotPanelSettings();
listener.showPlotPanel();
break;
case BARCHART:
if (doCreate) {
if (barChart != null) {
barChart.remove();
}
if (settings.isNumericData()) {
barChart = statGeo.createBarChartNumeric(
dataListSelected, settings);
plotGeoList.add(barChart);
} else {
barChart = statGeo.createBarChartText(dataListSelected,
settings);
}
plotGeoList.add(barChart);
if (freqTableGeo != null) {
freqTableGeo.remove();
}
if (settings.isShowFrequencyTable()) {
freqTableGeo = statGeo.createFrequencyTableGeo(
(GeoNumeric) barChart, selectedPlot);
plotGeoList.add(freqTableGeo);
}
listener.resize();
}
// update the frequency table
if (settings.isShowFrequencyTable()) {
listener.setTableFromGeoFrequencyTable(
(AlgoFrequencyTable) freqTableGeo
.getParentAlgorithm(),
false);
}
// update settings
statGeo.getBarChartSettings(dataListSelected, settings,
barChart);
listener.updatePlotPanelSettings();
listener.showPlotPanel();
break;
case DOTPLOT:
if (doCreate) {
if (dotPlot != null) {
dotPlot.remove();
}
dotPlot = statGeo.createDotPlot(dataListSelected);
plotGeoList.add(dotPlot);
}
statGeo.updateDotPlot(dataListSelected, dotPlot, settings);
listener.updatePlotPanelSettings();
listener.showPlotPanel();
break;
case STEMPLOT:
String latex = statGeo.getStemPlotLatex(dataListSelected,
settings.getStemAdjust());
listener.updateStemPlot(latex);
break;
case NORMALQUANTILE:
if (doCreate) {
if (nqPlot != null) {
nqPlot.remove();
}
nqPlot = statGeo.createNormalQuantilePlot(dataListSelected);
plotGeoList.add(nqPlot);
}
statGeo.updateNormalQuantilePlot(dataListSelected, settings);
listener.updatePlotPanelSettings();
listener.showPlotPanel();
break;
case SCATTERPLOT:
if (doCreate) {
Log.debug("[DDMODEL] UPDATE SCATTERPLOT");
scatterPlot = statGeo.createScatterPlot(dataListSelected);
plotGeoList.add(scatterPlot);
if (daModel.getRegressionModel() != null && !daModel
.getRegressionMode().equals(Regression.NONE)) {
plotGeoList.add(daModel.getRegressionModel());
}
if (settings.isShowScatterplotLine()) {
scatterPlotLine = statGeo
.createScatterPlotLine((GeoList) scatterPlot);
plotGeoList.add(scatterPlotLine);
}
}
// update xy title fields
listener.updateXYTitles(settings.isPointList(),
daModel.getDaCtrl().isLeftToRight());
// update settings
statGeo.getScatterPlotSettings(dataListSelected, settings);
listener.updatePlotPanelSettings();
listener.showPlotPanel();
break;
case RESIDUAL:
if (doCreate) {
if (!daModel.getRegressionMode().equals(Regression.NONE)) {
residualPlot = statGeo.createRegressionPlot(
dataListSelected, daModel.getRegressionMode(),
daModel.getRegressionOrder(), true);
plotGeoList.add(residualPlot);
statGeo.getResidualPlotSettings(dataListSelected,
residualPlot, settings);
listener.updatePlotPanelSettings();
} else if (residualPlot != null) {
residualPlot.remove();
residualPlot = null;
}
}
listener.showPlotPanel();
break;
case MULTIBOXPLOT:
if (doCreate) {
GeoElement[] boxPlots = statGeo
.createMultipleBoxPlot(dataListSelected, settings);
for (int i = 0; i < boxPlots.length; i++) {
plotGeoList.add(boxPlots[i]);
}
}
statGeo.getMultipleBoxPlotSettings(dataListSelected, settings);
listener.updatePlotPanelSettings();
boxPlotTitles = statGeo.createBoxPlotTitles(daModel, settings);
for (int i = 0; i < boxPlotTitles.length; i++) {
plotGeoList.add(boxPlotTitles[i]);
}
listener.showPlotPanel();
break;
default:
}
// ==============================================
// Prepare Geos for plot panel view
// ==============================================
if (doCreate && statGeo.removeFromConstruction()) {
for (GeoElementND listGeo : plotGeoList) {
// add the geo to our view and remove it from EV
listener.geoToPlotPanel(listGeo.toGeoElement());
}
}
if (freqTableGeo != null) {
freqTableGeo.setEuclidianVisible(false);
freqTableGeo.updateRepaint();
}
if (histogram != null) {
histogram.setEuclidianVisible(settings.isShowHistogram()
&& selectedPlot == PlotType.HISTOGRAM);
histogram.updateRepaint();
}
} catch (Exception e) {
daModel.getDaCtrl().setValidData(false);
listener.showInvalidDataDisplay();
e.printStackTrace();
}
}
public void clearPlotGeoList() {
for (GeoElementND geo : plotGeoList) {
if (geo != null) {
geo.remove();
geo = null;
}
}
plotGeoList.clear();
}
/**
* Exports all GeoElements that are currently displayed in this panel to a
* target EuclidianView.
*
* @param euclidianViewID
* viewID of the target EuclidianView
*/
public void exportGeosToEV(int euclidianViewID) {
// TODO:
// in multivar mode create dynamic boxplots linked to separate lists
app.setWaitCursor();
// app.storeUndoInfo();
GeoElement regressionCopy = null;
EuclidianView targetEV = (EuclidianView) app.getView(euclidianViewID);
try {
// =================================================================
// Step 1:
// Update the plot geos with the reomoveFromConstruction
// flag set to false. This ensures that the display geos have been
// put in the construction list and will be saved to xml.
// =================================================================
daModel.getController().loadDataLists(false); // load actual data
// lists
daModel.getStatGeo().setRemoveFromConstruction(false);
updatePlot(true);
// =================================================================
// Step 2:
// Prepare the geos for display in the currently active EV
// (set labels, make visible, etc).
// =================================================================
// remove the histogram from the plot geo list if it is not showing
if (histogram != null && !settings.isShowHistogram()) {
plotGeoList.remove(histogram);
histogram.remove();
histogram = null;
}
// prepare all display geos to appear in the EV
for (GeoElementND geo : plotGeoList) {
prepareGeoForEV(geo, euclidianViewID);
}
// the regression geo is maintained by the da view, so we create a
// copy and prepare this for the EV
if (daModel.isRegressionMode()
&& !daModel.getRegressionMode().equals(Regression.NONE)) {
regressionCopy = statGeo.createRegressionPlot(
(GeoList) scatterPlot, daModel.getRegressionMode(),
daModel.getRegressionOrder(), false);
prepareGeoForEV(regressionCopy, euclidianViewID);
}
// =================================================================
// Step 3:
// Adjust the target EV window to match the plotPanel dimensions
// =================================================================
targetEV.setRealWorldCoordSystem(settings.xMin, settings.xMax,
settings.yMin, settings.yMax);
targetEV.setAutomaticAxesNumberingDistance(
settings.xAxesIntervalAuto, 0);
targetEV.setAutomaticAxesNumberingDistance(
settings.yAxesIntervalAuto, 1);
Construction cons = app.getKernel().getConstruction();
if (!settings.xAxesIntervalAuto) {
targetEV.setAxesNumberingDistance(
new GeoNumeric(cons, settings.xAxesInterval), 0);
}
if (!settings.yAxesIntervalAuto) {
targetEV.setAxesNumberingDistance(
new GeoNumeric(cons, settings.yAxesInterval), 1);
}
targetEV.updateBackground();
// =================================================================
// Step 4:
// Dereference the geos from fields in this class and the
// DataAnalysisView
// =================================================================
// null the display geos
boxPlotTitles = null;
histogram = null;
dotPlot = null;
frequencyPolygon = null;
normalCurve = null;
scatterPlotLine = null;
scatterPlot = null;
nqPlot = null;
boxPlot = null;
barChart = null;
freqTableGeo = null;
daModel.getController().removeRegressionGeo();
daModel.getController().disposeDataListSelected();
plotGeoList.clear();
// =================================================================
// Step 5:
// Reload the data and create new display geos that are not
// in the construction list.
// =================================================================
daModel.getController().loadDataLists(true);
statGeo.setRemoveFromConstruction(true);
daModel.getController().setRegressionGeo();
daModel.getController().updateRegressionPanel();
updatePlot(true);
} catch (Exception e) {
e.printStackTrace();
app.setDefaultCursor();
}
app.setDefaultCursor();
app.storeUndoInfo();
}
/**
* Prepares the specified GeoElement for visibility in a target
* EuclidianView.
*
* @param geo
* @param euclidianViewID
* viewID of the target EuclidianView
*/
private static void prepareGeoForEV(GeoElementND geo, int euclidianViewID) {
geo.setLabel(null);
geo.setEuclidianVisible(true);
geo.setAuxiliaryObject(false);
if (euclidianViewID == App.VIEW_EUCLIDIAN) {
geo.addView(App.VIEW_EUCLIDIAN);
geo.removeView(App.VIEW_EUCLIDIAN2);
geo.update();
}
if (euclidianViewID == App.VIEW_EUCLIDIAN2) {
geo.addView(App.VIEW_EUCLIDIAN2);
geo.removeView(App.VIEW_EUCLIDIAN);
geo.update();
}
}
public void removeGeos() {
clearPlotGeoList();
}
public StatPanelSettings getSettings() {
return settings;
}
public PlotType getSelectedPlot() {
return selectedPlot;
}
public void setSelectedPlot(PlotType selectedPlot) {
this.selectedPlot = selectedPlot;
}
}