package org.geogebra.web.web.gui.view.data; import java.util.ArrayList; import java.util.List; import org.geogebra.common.euclidian.event.KeyEvent; import org.geogebra.common.euclidian.event.KeyHandler; import org.geogebra.common.gui.view.data.DataAnalysisModel; import org.geogebra.common.gui.view.data.DataDisplayModel; import org.geogebra.common.gui.view.data.DataDisplayModel.IDataDisplayListener; import org.geogebra.common.gui.view.data.DataDisplayModel.PlotType; import org.geogebra.common.kernel.geos.GeoElement; import org.geogebra.common.kernel.geos.GeoNumeric; import org.geogebra.common.kernel.statistics.AlgoFrequencyTable; import org.geogebra.common.main.App; import org.geogebra.common.util.Validation; import org.geogebra.common.util.debug.Log; import org.geogebra.web.html5.awt.GDimensionW; import org.geogebra.web.html5.gui.inputfield.AutoCompleteTextFieldW; import org.geogebra.web.html5.gui.util.LayoutUtilW; import org.geogebra.web.html5.gui.util.NoDragImage; import org.geogebra.web.html5.gui.util.Slider; import org.geogebra.web.html5.main.AppW; import org.geogebra.web.html5.main.DrawEquationW; import org.geogebra.web.html5.main.GlobalKeyDispatcherW; import org.geogebra.web.html5.main.LocalizationW; import org.geogebra.web.web.css.GuiResources; import org.geogebra.web.web.gui.util.MyToggleButtonW; import org.geogebra.web.web.gui.view.algebra.InputPanelW; import com.google.gwt.canvas.client.Canvas; import com.google.gwt.event.dom.client.BlurEvent; import com.google.gwt.event.dom.client.BlurHandler; import com.google.gwt.event.dom.client.ChangeEvent; import com.google.gwt.event.dom.client.ChangeHandler; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.user.client.Command; import com.google.gwt.user.client.ui.DeckPanel; import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.ListBox; import com.google.gwt.user.client.ui.MenuBar; import com.google.gwt.user.client.ui.MenuItem; import com.google.gwt.user.client.ui.RequiresResize; import com.google.gwt.user.client.ui.ScrollPanel; /** * Class to dynamically display plots and statistics in coordination with the * DataAnalysisView. * * @author G.Sturr * */ public class DataDisplayPanelW extends FlowPanel implements StatPanelInterfaceW, RequiresResize, IDataDisplayListener { private static final int NUM_CLASSES_IDX = 0; private static final int MANUAL_CLASSES_IDX = 1; private static final int STEM_IDX = 2; private static final int EMPTY_IDX = 3; private static final int METAPLOT_IDX = 0; private static final int IMAGE_IDX = 1; private static final int PLOTPANEL_MARGIN = 10; private static final int PLOTPANEL_MIN_WIDTH = 400; private static final int PLOTPANEL_MIN_HEIGHT = 150; // ggb fields private AppW app; private final LocalizationW loc; // privateDataAnalysisViewD daView; private DataDisplayModel model; // data view mode // display panels private DeckPanel displayDeckPanel; private FlowPanel metaPlotPanel, plotPanelNorth, plotPanelSouth; private PlotPanelEuclidianViewW plotPanel; private Canvas latexCanvas; private GeoNumeric sample; // control panel private FlowPanel controlPanel; private DeckPanel controlDecks; private boolean hasControlPanel = true; private ListBox lbDisplayType; private List<PlotType> plotTypes; // options button and sidebar panel private OptionsPanelW optionsPanel; private MyToggleButtonW btnOptions; // numClasses panel // private int numClasses = 6; private FlowPanel numClassesPanel; private Slider sliderNumClasses; // manual classes panel private FlowPanel manualClassesPanel; private Label lblStart; private Label lblWidth; private AutoCompleteTextFieldW fldStart; private AutoCompleteTextFieldW fldWidth; // stemplot adjustment panel private FlowPanel stemAdjustPanel; private Label lblAdjust; private MyToggleButtonW minus; private MyToggleButtonW none; private MyToggleButtonW plus; private FlowPanel imagePanel; private Label lblTitleX, lblTitleY; private AutoCompleteTextFieldW fldTitleX, fldTitleY; private FrequencyTablePanelW frequencyTable; private MenuBar btnExport; private AutoCompleteTextFieldW fldNumClasses; private DataAnalysisModel daModel; private ScrollPanel spFrequencyTable; private int oldWidth; private int oldHeight; private DataAnalysisViewW daView; /***************************************** * Constructs a ComboStatPanel * * @param daView * daView */ public DataDisplayPanelW(DataAnalysisViewW daView) { this.app = daView.getApp(); this.loc = (LocalizationW) app.getLocalization(); daModel = daView.getModel(); setModel(new DataDisplayModel(daModel, this)); this.daView = daView; this.sample = new GeoNumeric(app.getKernel().getConstruction()); // create the GUI createGUI(); } /** * Sets the plot to be displayed and the GUI corresponding to the given data * analysis mode * * @param plotIndex * the plot to be displayed * @param mode * the data analysis mode */ public void setPanel(PlotType plotIndex, int mode) { getModel().updatePlot(plotIndex, mode); setLabels(); getModel().updatePlot(true); optionsPanel.setVisible(false); btnOptions.setValue(false); } // ============================================== // GUI // ============================================== private void createGUI() { oldWidth = 0; oldHeight = 0; // create options button btnOptions = new MyToggleButtonW(new NoDragImage(GuiResources.INSTANCE .menu_icon_options().getSafeUri().asString(), 18)); btnOptions.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { actionPerformed(btnOptions); } }); // create export button btnExport = new MenuBar(); // create control panel if (hasControlPanel) { // create sub-control panels createDisplayTypeComboBox(); createNumClassesPanel(); createManualClassesPanel(); createStemPlotAdjustmentPanel(); FlowPanel emptyControl = new FlowPanel(); emptyControl.add(new Label(" ")); // put sub-control panels into a deck panel controlDecks = new DeckPanel(); controlDecks.add(numClassesPanel); controlDecks.add(manualClassesPanel); controlDecks.add(stemAdjustPanel); controlDecks.add(emptyControl); FlowPanel buttonPanel = new FlowPanel(); buttonPanel.setStyleName("daOptionButtons"); buttonPanel.add(LayoutUtilW.panelRow(btnOptions, btnExport)); // control panel controlPanel = new FlowPanel(); controlPanel.add(LayoutUtilW.panelRow(lbDisplayType, controlDecks, buttonPanel)); } plotPanel = new PlotPanelEuclidianViewW(app.getKernel()); // plotPanel.setPreferredSize(PLOTPANEL_WIDTH, PLOTPANEL_HEIGHT); // plotPanel.updateSize(); plotPanelNorth = new FlowPanel(); plotPanelSouth = new FlowPanel(); lblTitleX = new Label(); lblTitleY = new Label(); fldTitleX = (new InputPanelW(app, -1, false)).getTextComponent(); fldTitleY = (new InputPanelW(app, -1, false)).getTextComponent(); fldTitleX.setEditable(false); fldTitleY.setEditable(false); metaPlotPanel = new FlowPanel(); metaPlotPanel.setStyleName("daDotPanel"); metaPlotPanel.add(plotPanel.getComponent()); createImagePanel(); // put display panels into a deck panel displayDeckPanel = new DeckPanel(); displayDeckPanel.add(metaPlotPanel); displayDeckPanel.add(new ScrollPanel(imagePanel)); // create options panel optionsPanel = new OptionsPanelW(app, daModel, getModel()); optionsPanel.setVisible(false); frequencyTable = new FrequencyTablePanelW(); spFrequencyTable = new ScrollPanel(); spFrequencyTable.add(frequencyTable); spFrequencyTable.setStyleName("spFrequencyTable"); // ======================================= // put all the panels together FlowPanel mainPanel = new FlowPanel(); mainPanel.setStyleName("dataDisplayMain"); if (hasControlPanel) { mainPanel.add(controlPanel); } mainPanel.add(LayoutUtilW.panelRow(displayDeckPanel, optionsPanel)); add(mainPanel); createExportMenu(); resize(); } /** * Sets the labels to the current language */ @Override public void setLabels() { createDisplayTypeComboBox(); lblStart.setText(loc.getMenu("Start") + " "); lblWidth.setText(loc.getMenu("Width") + " "); if (daModel.isRegressionMode()) { lblTitleX.setText(loc.getMenu("Column.X") + ": "); lblTitleY.setText(loc.getMenu("Column.Y") + ": "); } lblAdjust.setText(loc.getMenu("Adjustment") + ": "); optionsPanel.setLabels(); btnOptions.setToolTipText(loc.getMenu("Options")); } /** * Creates the ListBox that selects display type */ private void createDisplayTypeComboBox() { if (lbDisplayType == null) { lbDisplayType = new ListBox(); lbDisplayType.addChangeHandler(new ChangeHandler() { @Override public void onChange(ChangeEvent event) { actionPerformed(lbDisplayType); } }); plotTypes = new ArrayList<PlotType>(); } else { plotTypes.clear(); lbDisplayType.clear(); } getModel().fillDisplayTypes(); } /** * Creates a display panel to hold an image, e.g. tabletext */ private void createImagePanel() { imagePanel = new FlowPanel(); latexCanvas = Canvas.createIfSupported(); imagePanel.add(latexCanvas); imagePanel.setStyleName("daImagePanel"); } /** * Creates a control panel for adjusting the number of histogram classes */ private void createNumClassesPanel() { int numClasses = getModel().getSettings().getNumClasses(); fldNumClasses = (new InputPanelW(app, -1, false)).getTextComponent(); fldNumClasses.setEditable(false); fldNumClasses.setOpaque(true); fldNumClasses.setColumns(2); fldNumClasses.setVisible(false); sliderNumClasses = new Slider( 3, 20); sliderNumClasses.setValue(numClasses); sliderNumClasses.setMajorTickSpacing(1); sliderNumClasses.addChangeHandler(new ChangeHandler() { @Override public void onChange(ChangeEvent event) { getModel().getSettings().setNumClasses(sliderNumClasses.getValue()); fldNumClasses.setText(("" + getModel().getSettings() .getNumClasses())); getModel().updatePlot(true); } }); numClassesPanel = new FlowPanel(); numClassesPanel.add(sliderNumClasses); numClassesPanel.add(fldNumClasses); } /** * Creates a control panel to adjust the stem plot */ private void createStemPlotAdjustmentPanel() { lblAdjust = new Label(); minus = new MyToggleButtonW("-1"); none = new MyToggleButtonW("0"); plus = new MyToggleButtonW("+1"); minus.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { actionPerformed(minus); } }); none.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { actionPerformed(none); } }); plus.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { actionPerformed(plus); } }); none.setValue(true); stemAdjustPanel = new FlowPanel(); stemAdjustPanel.add(LayoutUtilW.panelRow(minus, none, plus)); } /** * Creates a control panel for manually setting classes */ private void createManualClassesPanel() { lblStart = new Label(); lblWidth = new Label(); fldStart = new AutoCompleteTextFieldW(4, app); fldStart.setText("" + (int) getModel().getSettings().getClassStart()); fldWidth = new AutoCompleteTextFieldW(4, app); fldStart.setColumns(4); fldWidth.setColumns(4); fldWidth.setText("" + (int) getModel().getSettings().getClassWidth()); manualClassesPanel = new FlowPanel(); manualClassesPanel.add(LayoutUtilW.panelRow(lblStart, fldStart, lblWidth, fldWidth)); fldStart.addBlurHandler(new BlurHandler() { @Override public void onBlur(BlurEvent event) { actionPerformed(fldStart); } }); fldStart.addKeyHandler(new KeyHandler() { @Override public void keyReleased(KeyEvent e) { if (e.isEnterKey()) { actionPerformed(fldStart); } } }); fldWidth.addBlurHandler(new BlurHandler() { @Override public void onBlur(BlurEvent event) { actionPerformed(fldWidth); } }); fldWidth.addKeyHandler(new KeyHandler() { @Override public void keyReleased(KeyEvent e) { if (e.isEnterKey()) { actionPerformed(fldWidth); } } }); } private void createExportMenu() { MenuBar menu = new MenuBar(true); MenuItem miToGraphich = new MenuItem(loc.getMenu("CopyToGraphics"), new Command() { @Override public void execute() { exportToEV(); } }); menu.addItem(miToGraphich); if(app.getLAF().copyToClipboardSupported()){ MenuItem miAsPicture = new MenuItem( loc.getPlain("ExportAsPicture"), new Command() { @Override public void execute() { exportAsPicture(); } }); menu.addItem(miAsPicture); } String image = "<img src=\"" + GuiResources.INSTANCE.menu_icons_file_export().getSafeUri() .asString() + "\" >"; btnExport.addItem(image, true, menu); } // ============================================== // DISPLAY UPDATE // ============================================== protected void exportAsPicture() { app.getSelectionManager().clearSelectedGeos(true, false); app.updateSelection(false); app.setWaitCursor(); app.copyEVtoClipboard(plotPanel); app.setDefaultCursor(); } protected void exportToEV() { // use EV1 unless shift is down, then use EV2 int euclidianViewID = GlobalKeyDispatcherW.getShiftDown() ? app.getEuclidianView2(1).getViewID() : app.getEuclidianView1().getViewID(); // do the export getModel().exportGeosToEV(euclidianViewID); daView.updateOtherDataDisplay(this); } @Override public void showControlPanel() { controlDecks.showWidget(EMPTY_IDX); } @Override public void setOptionsButtonVisible() { btnOptions.setVisible(true); btnExport.setVisible(true); plotPanel.updateSize(); } @Override public void showInvalidDataDisplay() { // imageContainer.setIcon(null); displayDeckPanel.showWidget(IMAGE_IDX); } // ============================================================ // Event Handlers // ============================================================ // public void actionPerformed(Object source) { if (source instanceof AutoCompleteTextFieldW) { doTextFieldActionPerformed(source); } else if (source == minus || source == plus || source == none) { minus.setValue(source == minus); none.setValue(source == none); plus.setValue(source == plus); Log.debug("[Data] - 0 + has pressed"); if (source == minus) { getModel().getSettings().setStemAdjust(-1); } if (source == none) { getModel().getSettings().setStemAdjust(0); } if (source == plus) { getModel().getSettings().setStemAdjust(1); } getModel().updatePlot(true); } else if (source == btnOptions) { optionsPanel.setPanel(getModel().getSelectedPlot()); optionsPanel.setVisible(btnOptions.isSelected()); resize(); } else if (source == btnExport) { } else if (source == lbDisplayType) { int idx = lbDisplayType.getSelectedIndex(); if (idx != -1) { PlotType t = plotTypes.get(idx); getModel().setSelectedPlot(t); getModel().updatePlot(true); } if (optionsPanel.isVisible()) { optionsPanel.setPanel(getModel().getSelectedPlot()); resize(); } } } private void doTextFieldActionPerformed(Object source) { if (source == fldStart) { getModel().getSettings().setClassStart( Validation.validateDouble(fldStart, getModel() .getSettings().getClassStart())); } else if (source == fldWidth) { getModel().getSettings().setClassWidth( Validation.validateDoublePositive(fldWidth, getModel() .getSettings().getClassWidth())); } getModel().updatePlot(true); } public void detachView() { // plotPanel.detachView(); } public void attachView() { plotPanel.attachView(); } @Override public void updatePanel() { // } @Override public void addDisplayTypeItem(PlotType type) { lbDisplayType.addItem(loc.getMenu(type.getKey())); plotTypes.add(type); } @Override public void updateScatterPlot() { if (!daModel.isRegressionMode()) { return; } metaPlotPanel.clear(); plotPanelNorth.clear(); plotPanelSouth.clear(); plotPanelSouth.add(LayoutUtilW.panelRow(lblTitleX, fldTitleX)); plotPanelNorth.add(LayoutUtilW.panelRow(lblTitleY, fldTitleY)); metaPlotPanel.add(plotPanelNorth); metaPlotPanel.add(plotPanel.getComponent()); metaPlotPanel.add(plotPanelSouth); } @Override public void updateFrequencyTable() { plotPanelSouth.add(spFrequencyTable); metaPlotPanel.add(plotPanelSouth); } @Override public void setSelectedType(PlotType type) { lbDisplayType.setSelectedIndex(plotTypes.indexOf(type)); } @Override public void setTableFromGeoFrequencyTable( AlgoFrequencyTable parentAlgorithm, boolean b) { Log.debug("setTableFromGeoFrequencyTable"); frequencyTable.setTableFromGeoFrequencyTable(parentAlgorithm, b); resize(false); } @Override public void removeFrequencyTable() { Log.debug("removeFrequencyTable"); plotPanelSouth.remove(spFrequencyTable); plotPanel.updateSize(); resize(false); } @Override public void updatePlotPanelSettings() { plotPanel.commonFields.updateSettings(plotPanel, getModel() .getSettings()); } @Override public void showManualClassesPanel() { controlDecks.showWidget(MANUAL_CLASSES_IDX); } @Override public void showNumClassesPanel() { controlDecks.showWidget(NUM_CLASSES_IDX); } @Override public void showPlotPanel() { displayDeckPanel.showWidget(METAPLOT_IDX); } @Override public void updateStemPlot(String latex) { btnOptions.setVisible(false); btnExport.setVisible(false); DrawEquationW.paintOnCanvas(sample, latex, latexCanvas, app.getFontSizeWeb()); if (hasControlPanel) { controlDecks.showWidget(STEM_IDX); } displayDeckPanel.showWidget(IMAGE_IDX); } @Override public void updateXYTitles(boolean isPointList, boolean isLeftToRight) { if (isPointList) { fldTitleX.setText(daModel.getDataTitles()[0]); fldTitleY.setText(daModel.getDataTitles()[0]); } else { if (isLeftToRight) { fldTitleX.setText(daModel.getDataTitles()[0]); fldTitleY.setText(daModel.getDataTitles()[1]); } else { fldTitleX.setText(daModel.getDataTitles()[1]); fldTitleY.setText(daModel.getDataTitles()[0]); } } } @Override public void geoToPlotPanel(GeoElement listGeo) { listGeo.addView(plotPanel.getViewID()); plotPanel.add(listGeo); listGeo.removeView(App.VIEW_EUCLIDIAN); app.getEuclidianView1().remove(listGeo); } public DataDisplayModel getModel() { return model; } public void setModel(DataDisplayModel model) { this.model = model; } public void resize(int offsetWidth, int offsetHeight, boolean update) { int w = offsetWidth; int h = offsetHeight; int width = optionsPanel.isVisible() ? w - optionsPanel.getOffsetWidth() - PLOTPANEL_MARGIN : w; int height = (frequencyTable.isVisible() ? h - spFrequencyTable.getOffsetHeight() : h) - lbDisplayType.getOffsetHeight() - PLOTPANEL_MARGIN; if (daModel.isRegressionMode()) { height -= 2*lblTitleX.getOffsetHeight(); height -= lblTitleY.getOffsetHeight(); } if (width < PLOTPANEL_MIN_WIDTH) { width = PLOTPANEL_MIN_WIDTH; } if (height < PLOTPANEL_MIN_HEIGHT) { height = PLOTPANEL_MIN_HEIGHT; } if (oldWidth == width && oldHeight == height) { return; } oldWidth = width; oldHeight = height; plotPanel.setPreferredSize(new GDimensionW(width, height)); if (optionsPanel.isVisible()) { optionsPanel.resize(height); } plotPanel.updateSize(); plotPanel.repaintView(); plotPanel.getEuclidianController().calculateEnvironment(); if (update) { getModel().updatePlot(false); } imagePanel.setPixelSize(width, height); } public void resize(boolean update) { resize(getOffsetWidth(), getOffsetHeight(), update); } @Override public void resize() { resize(true); } @Override public void onResize() { resize(true); } public void update() { model.updatePlot(true); } }