changeset 1436:29fc2d1dfe9b

Client side of interactive cross section diagrams. flys-client/trunk@3399 c6561f87-3c4e-4783-a992-168aeb5c3f6f
author Felix Wolfsteller <felix.wolfsteller@intevation.de>
date Tue, 13 Dec 2011 11:11:48 +0000
parents f6fbfdc813f0
children 1d82a2c695a6
files flys-client/src/main/java/de/intevation/flys/client/client/FLYSConstants.java flys-client/src/main/java/de/intevation/flys/client/client/FLYSConstants.properties flys-client/src/main/java/de/intevation/flys/client/client/FLYSConstants_de.properties flys-client/src/main/java/de/intevation/flys/client/client/FLYSConstants_en.properties flys-client/src/main/java/de/intevation/flys/client/client/ui/chart/CrossSectionChartThemePanel.java
diffstat 5 files changed, 291 insertions(+), 36 deletions(-) [+]
line wrap: on
line diff
--- a/flys-client/src/main/java/de/intevation/flys/client/client/FLYSConstants.java	Tue Dec 13 09:51:47 2011 +0000
+++ b/flys-client/src/main/java/de/intevation/flys/client/client/FLYSConstants.java	Tue Dec 13 11:11:48 2011 +0000
@@ -378,6 +378,8 @@
 
     String chart_themepanel_header_actions();
 
+    String chart_themepanel_set_master();
+
     String discharge();
 
     // ERRORS
--- a/flys-client/src/main/java/de/intevation/flys/client/client/FLYSConstants.properties	Tue Dec 13 09:51:47 2011 +0000
+++ b/flys-client/src/main/java/de/intevation/flys/client/client/FLYSConstants.properties	Tue Dec 13 11:11:48 2011 +0000
@@ -185,6 +185,7 @@
 computed_dischargecurve_at_export = AT Export
 chart_themepanel_header_themes = Theme
 chart_themepanel_header_actions = Actions
+chart_themepanel_set_master = Reference
 
 ele_window_title = Elevation Node
 ele_window_label = Elevation settings in m \u00fc. NN.
--- a/flys-client/src/main/java/de/intevation/flys/client/client/FLYSConstants_de.properties	Tue Dec 13 09:51:47 2011 +0000
+++ b/flys-client/src/main/java/de/intevation/flys/client/client/FLYSConstants_de.properties	Tue Dec 13 11:11:48 2011 +0000
@@ -185,6 +185,7 @@
 computed_dischargecurve_at_export = AT Export
 chart_themepanel_header_themes = Thema
 chart_themepanel_header_action = Aktionen
+chart_themepanel_set_master = Referenz
 
 ele_window_title = H\u00f6henknoten setzen
 ele_window_label = H\u00f6henangaben in m \u00fc. NN.
--- a/flys-client/src/main/java/de/intevation/flys/client/client/FLYSConstants_en.properties	Tue Dec 13 09:51:47 2011 +0000
+++ b/flys-client/src/main/java/de/intevation/flys/client/client/FLYSConstants_en.properties	Tue Dec 13 11:11:48 2011 +0000
@@ -185,6 +185,7 @@
 computed_dischargecurve_at_export = AT Export
 chart_themepanel_header_themes = Theme
 chart_themepanel_header_actions = Actions
+chart_themepanel_set_master = Reference
 
 ele_window_title = Elevation Node
 ele_window_label = Elevation settings in m \u00fc. NN.
--- a/flys-client/src/main/java/de/intevation/flys/client/client/ui/chart/CrossSectionChartThemePanel.java	Tue Dec 13 09:51:47 2011 +0000
+++ b/flys-client/src/main/java/de/intevation/flys/client/client/ui/chart/CrossSectionChartThemePanel.java	Tue Dec 13 11:11:48 2011 +0000
@@ -1,54 +1,251 @@
 package de.intevation.flys.client.client.ui.chart;
 
+import java.util.HashMap;
+import java.util.Map;
+import java.util.LinkedHashMap;
+
 import com.google.gwt.core.client.GWT;
 
+import com.google.gwt.user.client.rpc.AsyncCallback;
+
+import com.smartgwt.client.util.SC;
+
 import com.smartgwt.client.types.Alignment; 
+import com.smartgwt.client.types.ListGridFieldType;
+
 import com.smartgwt.client.widgets.Canvas;
-import com.google.gwt.user.client.rpc.AsyncCallback;
-import com.smartgwt.client.types.ListGridFieldType;
 import com.smartgwt.client.widgets.grid.ListGrid;
 import com.smartgwt.client.widgets.grid.ListGridField;
 import com.smartgwt.client.widgets.grid.ListGridRecord;
 import com.smartgwt.client.widgets.layout.HLayout;
+import com.smartgwt.client.widgets.layout.VLayout;
 import com.smartgwt.client.widgets.form.fields.SpinnerItem;
 import com.smartgwt.client.widgets.form.DynamicForm;
+
+import com.smartgwt.client.widgets.form.fields.ComboBoxItem;
+import com.smartgwt.client.widgets.form.fields.SelectItem;
+import com.smartgwt.client.widgets.form.fields.events.ChangeEvent;
 import com.smartgwt.client.widgets.form.fields.events.ChangedEvent;
 import com.smartgwt.client.widgets.form.fields.events.ChangedHandler;
+import com.smartgwt.client.widgets.form.fields.events.ChangeHandler;
 
 import de.intevation.flys.client.client.Config;
 import de.intevation.flys.client.shared.model.Artifact;
 import de.intevation.flys.client.shared.model.Collection;
-import de.intevation.flys.client.shared.model.OutputMode;
-
+import de.intevation.flys.client.shared.model.CollectionItem;
 import de.intevation.flys.client.shared.model.Data;
 import de.intevation.flys.client.shared.model.DataItem;
 import de.intevation.flys.client.shared.model.DefaultArtifact;
 import de.intevation.flys.client.shared.model.DefaultData;
 import de.intevation.flys.client.shared.model.DefaultDataItem;
 import de.intevation.flys.client.shared.model.FacetRecord;
+import de.intevation.flys.client.shared.model.OutputMode;
+import de.intevation.flys.client.shared.model.Theme;
+import de.intevation.flys.client.shared.model.ThemeList;
 
 import de.intevation.flys.client.client.services.FeedServiceAsync;
-
+import de.intevation.flys.client.client.services.CrossSectionKMServiceAsync;
 
 /**
  * ThemePanel much like ChartThemePanel, but shows an "Actions" column,
- * needed for interaction in the CrossSection Charts.
+ * needed for interaction in the CrossSection Charts and a selctor to
+ * declare which cross section profile is "master".
  */
 public class CrossSectionChartThemePanel
 extends      ChartThemePanel {
 
+    /** Service to query measurement points of cross sections. */
+    CrossSectionKMServiceAsync kmService = GWT.create(
+        de.intevation.flys.client.client.services.CrossSectionKMService.class);
+
+    /** UUID of the current "master" cross section. */
+    protected String currentMasterUUID;
+
+    /** The layout (used for visual active/inactive feedback). */
+    protected VLayout layout;
+
     /**
      * Trivial constructor.
      */
     public CrossSectionChartThemePanel(
-            Collection collection,
-            OutputMode mode)
+        Collection collection,
+        OutputMode mode)
     {
         super(collection, mode);
     }
 
 
     /**
+     * Feed an artifact to let it know that it is master wrt cross-sections.
+     * @param artifact uuid of an artifact.
+     */
+    public void feedTellMaster(final String artifact) {
+        Data[] feedData = DefaultData.createSimpleStringDataArray(
+            "cross_section.master?", "1");
+
+        feedService.feed(
+            Config.getInstance().getLocale(),
+            new DefaultArtifact(artifact, "TODO:hash"),
+            feedData,
+            new AsyncCallback<Artifact>() {
+                public void onFailure(Throwable caught) {
+                    GWT.log("Could not feed artifact (" + artifact
+                            + ") with master marker: " + caught.getMessage());
+                    SC.warn(MSG.getString(caught.getMessage()));
+                    enable();
+                }
+                public void onSuccess(Artifact artifact) {
+                    GWT.log("Successfully injected master mark to " + artifact);
+                    setCurrentMaster(artifact.getUuid());
+                    requestRedraw();
+                    enable();
+                }
+            });
+    }
+
+
+    /**
+     * Create Layout, add a master selection box beneath.
+     */
+    @Override
+    protected VLayout createLayout() {
+
+        layout            = super.createLayout();
+
+        // Create "set master" combobox.
+        SelectItem cbItem = new SelectItem();
+
+        cbItem.setTitle(MSG.chart_themepanel_set_master());
+        cbItem.setType("comboBox");
+        final LinkedHashMap<String, String> valueMap =
+            getThemeList().toMapArtifactUUIDDescription("cross_section");
+        cbItem.setValueMap(valueMap);
+
+        final DynamicForm form = new DynamicForm();
+        form.setWidth(200);
+        form.setFields(cbItem);
+        layout.addMember(form);
+
+        Config config          = Config.getInstance();
+        final String locale    = config.getLocale();
+
+        // Add Change Handler to first unset the old master and then set the
+        // new master.
+        cbItem.addChangeHandler(new ChangeHandler() {
+            public void onChange(ChangeEvent event) {  
+                String selectedItem   = (String) event.getValue();
+                final String artifact = selectedItem;
+
+                disable();
+
+                // Tell current master that he is not master anymore.
+                if (getCurrentMaster() != null) {
+                    Data[] feedData = DefaultData.createSimpleStringDataArray(
+                            "cross_section.master?", "0");
+                    feedService.feed(
+                        locale,
+                        new DefaultArtifact(getCurrentMaster(), "TODO:hash"),
+                        feedData,
+                        new AsyncCallback<Artifact>() {
+                            public void onFailure(Throwable caught) {
+                                GWT.log("Could not un-master artifact: " +
+                                    caught.getMessage());
+                                SC.warn(MSG.getString(caught.getMessage()));
+                                enable();
+                            }
+                            public void onSuccess(Artifact oldMaster) {
+                                GWT.log("Successfully un-mastered artifact.");
+                                feedTellMaster(artifact);
+                            }
+                        });
+                }
+                else {
+                    feedTellMaster(artifact);
+                }
+            }  
+        });
+
+        return layout;
+    }
+
+
+    /** Disable the UI (becomes gray, inresponsive to user input). */
+    public void disable() {
+        this.layout.setDisabled(true);
+    }
+
+
+    /** DisDisable the UI (becomes ungray, responsive to user input). */
+    public void enable() {
+        this.layout.setDisabled(false);
+    }
+
+
+    /**
+     * Returns a double from the list that has the smallest distance to the
+     * given to value. In case of multiple values with the same difference,
+     * the last one is taken.
+     * @param in possible return values.
+     * @param to the value to be as close to as possible.
+     * @return value from in that is closest to to, -1 if none.
+     */
+    public static double closest(Double[] in, double to) {
+        if (in == null || in.length == 0) {
+            return -1;
+        }
+        if (in[0] == to) {
+            return to;
+        }
+        double minDiff = Math.abs(to - in[0]);
+        double bestMatch = in[0];
+        for (int i = 1; i < in.length; i++) {
+            if (in[i] == to) {
+                return to;
+            }
+            double diff = Math.abs(to - in[0]);
+            if (diff < minDiff) {
+                minDiff = diff;
+                bestMatch = in[i];
+            }
+        }
+        return bestMatch;
+    }
+
+
+    /** Feed a single artifact with the km of the crosssection to display. */
+    public void sendFeed(FacetRecord facetRecord, double kmD) {
+        Config config          = Config.getInstance();
+        final String locale    = config.getLocale();
+
+        Data[] feedData =
+            DefaultData.createSimpleStringDataArray("cross_section.km",
+                Double.valueOf(kmD).toString());
+
+        disable();
+        feedService.feed(
+            locale,
+            new DefaultArtifact(
+                facetRecord.getTheme().getArtifact(),
+                "TODO:hash"),
+            feedData,
+            new AsyncCallback<Artifact>() {
+                @Override
+                public void onFailure(Throwable caught) {
+                    GWT.log("Could not feed artifact " + caught.getMessage());
+                    SC.warn(MSG.getString(caught.getMessage()));
+                    enable();
+                }
+                @Override
+                public void onSuccess(Artifact artifact) {
+                    GWT.log("Successfully fed");
+                    requestRedraw();
+                    enable();
+                }
+        });
+    }
+
+
+    /**
      * Create the handler for ChangeEvents on the Spinner in the
      * facets that control km of cross section.
      *
@@ -66,37 +263,55 @@
         ChangedHandler handler = new ChangedHandler()
             {
                 @Override
-                public void onChanged(ChangedEvent ce) {
+                public void onChanged(final ChangedEvent ce) {
                     if (ce.getValue() == null) {
                         return;
                     }
-                    DefaultDataItem kmItem = new DefaultDataItem(
-                        "cross_section.km",
-                        "cross_section.km",
-                        ce.getValue().toString());
-                    DefaultData km = new DefaultData(
-                        "cross_section.km",
-                        null,
-                        null,
-                        new DataItem[] {kmItem});
-                    Data[] feedData = new Data[] {km};
-                    feedService.feed(
-                        locale,
-                        new DefaultArtifact(
-                            facetRecord.getTheme().getArtifact(),
-                            "TODO:hash"),
-                        feedData,
-                        new AsyncCallback<Artifact>() {
+
+                    // Now, query the value with the kmService.
+                    Map<Integer, Double> map = new HashMap<Integer,Double>();
+                    final double selected_km =
+                        Double.parseDouble(ce.getValue().toString());
+                    
+                    int _dbid = -1;
+                    try {
+                        _dbid = Integer.valueOf(facetRecord.getTheme()
+                            .getCollectionItem()
+                            .getData().get("cross_section.dbid"));
+                    }
+                    catch (NumberFormatException nfe) {
+                        GWT.log("Could not extract cross-section db id from data.");
+                    }
+                    final int dbid = _dbid;
+
+                    map.put(dbid, Double.parseDouble(ce.getValue().toString()));
+
+                    disable();
+    
+                    kmService.getCrossSectionKMs(locale, map, 2,
+                        new AsyncCallback<Map<Integer, Double[]>>() {
+                            @Override
                             public void onFailure(Throwable caught) {
-                                GWT.log("Could not feed artifact " + caught.getMessage());
-                                // TODO SC.warn 
+                                GWT.log("Could not get single km for "
+                                    + dbid + ": "+ caught.getMessage());
+                                SC.warn(MSG.getString(caught.getMessage()));
+                                updateCollection();
+                                updateGrid();
+                                enable();
                             }
-                            public void onSuccess(Artifact artifact) {
-                                GWT.log("Successfully fed");
-                                // TODO: Also update content of spinnerbox
-                                requestRedraw();
+                            @Override
+                            public void onSuccess(Map<Integer, Double[]> obj) {
+                                Double[] kms = obj.get(dbid);
+                                double closest =
+                                    CrossSectionChartThemePanel.closest(kms,
+                                        selected_km);
+                                GWT.log("Got single km for " + dbid + ", it is "
+                                    + closest);
+                                SpinnerItem item = (SpinnerItem) ce.getItem();
+                                item.setValue(closest);
+                                sendFeed(facetRecord, closest);
                             }
-                    });
+                            });
                 }
             };
         return handler;
@@ -104,6 +319,28 @@
 
 
     /**
+     * Get the chosen km for given cross-section.
+     */
+    final public double getKmForCrossSectionIdx(final int index) {
+        double km   = 0d;
+        int collIdx = 0;
+        while (km == 0d) {
+            CollectionItem item = collection.getItem(collIdx++);
+            if (item == null) {
+                break;
+            }
+            if (item.getData() == null) continue;
+            String d = item.getData().get("cross_section" + index + ".km");
+            if (d == null) {
+                continue;
+            }
+            km = Double.valueOf(d);
+        }
+        return km;
+    }
+
+
+    /**
      * Create and configure the Grid to display.
      */
     @Override
@@ -134,8 +371,10 @@
                         spinnerItem.setTitle("Waterlevel-Spinner");
                         spinnerItem.setWidth(45);
                         // TODO actually get the value from artifact
-                        // TODO actually get the range from artifact (or river?)
-                        spinnerItem.setDefaultValue(0);
+                        spinnerItem.setDefaultValue(
+                            getKmForCrossSectionIdx(facetRecord.getTheme().getIndex()));
+
+                        // TODO actually use crosssectionkmservive
                         spinnerItem.setMin(0);
                         spinnerItem.setMax(1000);
                         spinnerItem.setStep(5f);
@@ -177,7 +416,6 @@
         list.setShowRecordComponents(true);
         list.setShowRecordComponentsByCell(true);
         list.setShowHeader(true);
-        //list.setShowHeaderContextMenu(false);
         list.setWidth100();
         list.setHeight100();
 
@@ -195,5 +433,17 @@
 
         list.setFields(active, name, actions);
     }
+
+
+    /** Get Current Cross-section Masters uuid. */
+    public String getCurrentMaster() {
+        return currentMasterUUID;
+    }
+
+
+    /** Set Current Cross-section Masters uuid. */
+    public void setCurrentMaster(String currentMasterUuid) {
+        this.currentMasterUUID = currentMasterUuid;
+    }
 }
 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :

http://dive4elements.wald.intevation.org