changeset 2590:1686ec185155

client: added gauge waterlevel example diagram
author Markus Kottlaender <markus@intevation.de>
date Tue, 12 Mar 2019 08:37:09 +0100
parents f4c399a496cb
children eb69c6d27ae5
files client/src/components/gauge/Waterlevel.vue client/src/components/splitscreen/MinimizedSplitscreens.vue client/src/components/splitscreen/Splitscreen.vue client/src/store/application.js client/src/store/bottlenecks.js client/src/store/fairway.js client/src/store/gauges.js client/src/store/index.js client/src/store/map.js
diffstat 9 files changed, 351 insertions(+), 28 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/gauge/Waterlevel.vue	Tue Mar 12 08:37:09 2019 +0100
@@ -0,0 +1,222 @@
+<template>
+  <div class="flex-fill diagram-container"></div>
+</template>
+
+<style lang="sass" scoped>
+.diagram-container
+  /deep/ .area
+    stroke: steelblue
+    stroke-width: 2
+    fill: transparent
+    clip-path: url(#clip)
+
+  /deep/ .zoom
+    cursor: move
+    fill: none
+    pointer-events: all
+</style>
+
+<script>
+/* This is Free Software under GNU Affero General Public License v >= 3.0
+ * without warranty, see README.md and license for details.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ * License-Filename: LICENSES/AGPL-3.0.txt
+ *
+ * Copyright (C) 2018 by via donau
+ *   – Österreichische Wasserstraßen-Gesellschaft mbH
+ * Software engineering by Intevation GmbH
+ *
+ * Author(s):
+ * Markus Kottländer <markus.kottlaender@intevation.de>
+ */
+
+import { mapState } from "vuex";
+import * as d3 from "d3";
+import debounce from "debounce";
+
+export default {
+  computed: {
+    ...mapState("gauges", ["selectedGauge"]),
+    waterlevels() {
+      let data = [];
+      let waterlevel = 2.5;
+      for (let i = 1; i <= 365; i++) {
+        let date = new Date();
+        date.setFullYear(2018);
+        date.setDate(date.getDate() + i);
+        waterlevel *= Math.random() * (1.02 - 0.98) + 0.98;
+        data.push({ date, waterlevel });
+      }
+      return data;
+    }
+  },
+  methods: {
+    drawDiagram() {
+      var svgWidth = document.querySelector(".diagram-container").clientWidth;
+      var svgHeight = document.querySelector(".diagram-container").clientHeight;
+      d3.select(".diagram-container svg").remove();
+      var svg = d3.select(".diagram-container").append("svg");
+      svg.attr("width", "100%").attr("height", "100%");
+      let margin = { top: 20, right: 20, bottom: 110, left: 40 },
+        margin2 = {
+          top: svgHeight - margin.top - 50,
+          right: 20,
+          bottom: 30,
+          left: 40
+        },
+        width = +svgWidth - margin.left - margin.right,
+        height = +svgHeight - margin.top - margin.bottom,
+        height2 = +svgHeight - margin2.top - margin2.bottom;
+
+      var x = d3.scaleTime().range([0, width]),
+        x2 = d3.scaleTime().range([0, width]),
+        y = d3.scaleLinear().range([height, 0]),
+        y2 = d3.scaleLinear().range([height2, 0]);
+
+      var xAxis = d3.axisBottom(x),
+        xAxis2 = d3.axisBottom(x2),
+        yAxis = d3.axisLeft(y);
+
+      var brush = d3
+        .brushX()
+        .extent([[0, 0], [width, height2]])
+        .on("brush end", brushed);
+
+      var zoom = d3
+        .zoom()
+        .scaleExtent([1, Infinity])
+        .translateExtent([[0, 0], [width, height]])
+        .extent([[0, 0], [width, height]])
+        .on("zoom", zoomed);
+
+      var area = d3
+        .line()
+        .curve(d3.curveMonotoneX)
+        .x(function(d) {
+          return x(d.date);
+        })
+        .y(function(d) {
+          return y(d.waterlevel);
+        });
+
+      var area2 = d3
+        .line()
+        .curve(d3.curveMonotoneX)
+        .x(function(d) {
+          return x2(d.date);
+        })
+        .y(function(d) {
+          return y2(d.waterlevel);
+        });
+
+      svg
+        .append("defs")
+        .append("clipPath")
+        .attr("id", "clip")
+        .append("rect")
+        .attr("width", width)
+        .attr("height", height);
+
+      var focus = svg
+        .append("g")
+        .attr("class", "focus")
+        .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
+
+      var context = svg
+        .append("g")
+        .attr("class", "context")
+        .attr(
+          "transform",
+          "translate(" + margin2.left + "," + margin2.top + ")"
+        );
+
+      x.domain(
+        d3.extent(this.waterlevels, function(d) {
+          return d.date;
+        })
+      );
+      y.domain([0, 5]);
+      x2.domain(x.domain());
+      y2.domain(y.domain());
+
+      focus
+        .append("path")
+        .datum(this.waterlevels)
+        .attr("class", "area")
+        .attr("d", area);
+
+      focus
+        .append("g")
+        .attr("class", "axis axis--x")
+        .attr("transform", "translate(0," + height + ")")
+        .call(xAxis);
+
+      focus
+        .append("g")
+        .attr("class", "axis axis--y")
+        .call(yAxis);
+
+      context
+        .append("path")
+        .datum(this.waterlevels)
+        .attr("class", "area")
+        .attr("d", area2);
+
+      context
+        .append("g")
+        .attr("class", "axis axis--x")
+        .attr("transform", "translate(0," + height2 + ")")
+        .call(xAxis2);
+
+      context
+        .append("g")
+        .attr("class", "brush")
+        .call(brush)
+        .call(brush.move, x.range());
+
+      svg
+        .append("rect")
+        .attr("class", "zoom")
+        .attr("width", width)
+        .attr("height", height)
+        .attr("transform", "translate(" + margin.left + "," + margin.top + ")")
+        .call(zoom);
+
+      function brushed() {
+        if (d3.event.sourceEvent && d3.event.sourceEvent.type === "zoom")
+          return; // ignore brush-by-zoom
+        var s = d3.event.selection || x2.range();
+        x.domain(s.map(x2.invert, x2));
+        focus.select(".area").attr("d", area);
+        focus.select(".axis--x").call(xAxis);
+        svg
+          .select(".zoom")
+          .call(
+            zoom.transform,
+            d3.zoomIdentity.scale(width / (s[1] - s[0])).translate(-s[0], 0)
+          );
+      }
+
+      function zoomed() {
+        if (d3.event.sourceEvent && d3.event.sourceEvent.type === "brush")
+          return; // ignore zoom-by-brush
+        var t = d3.event.transform;
+        x.domain(t.rescaleX(x2).domain());
+        focus.select(".area").attr("d", area);
+        focus.select(".axis--x").call(xAxis);
+        context.select(".brush").call(brush.move, x.range().map(t.invertX, t));
+      }
+    }
+  },
+  created() {
+    window.addEventListener("resize", debounce(this.drawDiagram), 100);
+  },
+  mounted() {
+    this.drawDiagram();
+  },
+  updated() {
+    this.drawDiagram();
+  }
+};
+</script>
--- a/client/src/components/splitscreen/MinimizedSplitscreens.vue	Mon Mar 11 18:46:09 2019 +0100
+++ b/client/src/components/splitscreen/MinimizedSplitscreens.vue	Tue Mar 12 08:37:09 2019 +0100
@@ -1,17 +1,20 @@
 <template>
-  <div class="minimizedSplitscreens ui-element">
-    <transition name="fade">
-      <UIBoxHeader
-        v-for="splitscreen in splitscreens"
-        :key="splitscreen.id"
-        :icon="splitscreen.icon"
-        :title="splitscreen.title"
-        :closeCallback="close(splitscreen)"
-        :expandCallback="expand(splitscreen)"
-        :collapsed="true"
-      />
-    </transition>
-  </div>
+  <transition-group
+    name="fade"
+    tag="div"
+    class="minimizedSplitscreens ui-element"
+  >
+    <UIBoxHeader
+      v-for="splitscreen in splitscreens"
+      :key="splitscreen.id"
+      :icon="splitscreen.icon"
+      :title="splitscreen.title"
+      :closeCallback="close(splitscreen)"
+      :expandCallback="expand(splitscreen)"
+      :collapsed="true"
+      class="mt-2"
+    />
+  </transition-group>
 </template>
 
 <style lang="sass" scoped>
@@ -52,7 +55,7 @@
     expand(splitscreen) {
       return () => {
         if (splitscreen.expandCallback) splitscreen.expandCallback();
-        this.$store.commit("application/activeSplitscreen", splitscreen.id);
+        this.$store.commit("application/activeSplitscreenId", splitscreen.id);
         this.$store.commit("application/showSplitscreen", true);
       };
     }
--- a/client/src/components/splitscreen/Splitscreen.vue	Mon Mar 11 18:46:09 2019 +0100
+++ b/client/src/components/splitscreen/Splitscreen.vue	Tue Mar 12 08:37:09 2019 +0100
@@ -71,7 +71,8 @@
 
 export default {
   components: {
-    Fairwayprofile: () => import("@/components/fairway/Fairwayprofile")
+    Fairwayprofile: () => import("@/components/fairway/Fairwayprofile"),
+    Waterlevel: () => import("@/components/gauge/Waterlevel")
   },
   computed: {
     ...mapState("application", ["showSplitscreen", "splitscreenLoading"]),
@@ -84,15 +85,13 @@
       this.$store.commit("application/showSplitscreen", false);
     },
     close() {
-      if (this.activeSplitscreen.closeCallback)
-        this.activeSplitscreen.closeCallback();
       this.$store.commit("application/showSplitscreen", false);
       setTimeout(() => {
-        this.$store.commit(
-          "application/removeSplitscreen",
-          this.activeSplitscreen.id
-        );
-        this.$store.commit("application/activeSplitscreen", null);
+        let removeId = this.activeSplitscreen.id;
+        let callback = this.activeSplitscreen.closeCallback;
+        this.$store.commit("application/activeSplitscreenId", null);
+        if (callback) callback();
+        this.$store.commit("application/removeSplitscreen", removeId);
       }, 350);
     }
   }
--- a/client/src/store/application.js	Mon Mar 11 18:46:09 2019 +0100
+++ b/client/src/store/application.js	Tue Mar 12 08:37:09 2019 +0100
@@ -25,6 +25,7 @@
     popup: null,
     splitscreens: [],
     splitscreenLoading: false,
+    activeSplitscreenId: null,
     showSplitscreen: false,
     showSidebar: false,
     showUsermenu: false,
@@ -67,7 +68,7 @@
       return versionStr;
     },
     activeSplitscreen: state => {
-      return state.splitscreens.find(s => s.id === state.activeSplitscreen);
+      return state.splitscreens.find(s => s.id === state.activeSplitscreenId);
     }
   },
   mutations: {
@@ -83,8 +84,8 @@
     splitscreenLoading: (state, loading) => {
       state.splitscreenLoading = loading;
     },
-    activeSplitscreen: (state, id) => {
-      state.activeSplitscreen = id;
+    activeSplitscreenId: (state, id) => {
+      state.activeSplitscreenId = id;
     },
     addSplitscreen: (state, config) => {
       let index = state.splitscreens.findIndex(s => s.id === config.id);
--- a/client/src/store/bottlenecks.js	Mon Mar 11 18:46:09 2019 +0100
+++ b/client/src/store/bottlenecks.js	Tue Mar 12 08:37:09 2019 +0100
@@ -72,6 +72,7 @@
           });
           setTimeout(() => {
             commit("fairwayprofile/clearCurrentProfile", null, { root: true });
+            commit("application/splitscreenLoading", false, { root: true });
           }, 350);
           rootState.map.cutTool.setActive(false);
           rootGetters["map/getVSourceByName"](LAYERS.CUTTOOL).clear();
--- a/client/src/store/fairway.js	Mon Mar 11 18:46:09 2019 +0100
+++ b/client/src/store/fairway.js	Tue Mar 12 08:37:09 2019 +0100
@@ -319,7 +319,7 @@
               commit("application/addSplitscreen", splitscreenConf, {
                 root: true
               });
-              commit("application/activeSplitscreen", "fairwayprofile", {
+              commit("application/activeSplitscreenId", "fairwayprofile", {
                 root: true
               });
               commit("application/splitscreenLoading", false, { root: true });
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/store/gauges.js	Tue Mar 12 08:37:09 2019 +0100
@@ -0,0 +1,73 @@
+/* This is Free Software under GNU Affero General Public License v >= 3.0
+ * without warranty, see README.md and license for details.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ * License-Filename: LICENSES/AGPL-3.0.txt
+ *
+ * Copyright (C) 2018 by via donau
+ *   – Österreichische Wasserstraßen-Gesellschaft mbH
+ * Software engineering by Intevation GmbH
+ *
+ * Author(s):
+ * Markus Kottländer <markus@intevation.de>
+ */
+
+import { getCenter } from "ol/extent";
+
+const init = () => {
+  return {
+    selectedGauge: null
+  };
+};
+
+export default {
+  init,
+  namespaced: true,
+  state: init(),
+  mutations: {
+    selectedGauge: (state, gauge) => {
+      state.selectedGauge = gauge;
+    }
+  },
+  actions: {
+    selectedGauge: ({ commit }, gauge) => {
+      commit("selectedGauge", gauge);
+
+      // configure splitscreen
+      let splitscreenConf = {
+        id: "gauge-waterlevel",
+        component: "waterlevel",
+        title: gauge.get("objname"),
+        icon: "ruler-vertical",
+        closeCallback: () => {
+          commit("selectedGauge", null);
+        },
+        expandCallback: () => {
+          commit(
+            "map/moveMap",
+            {
+              coordinates: getCenter(
+                gauge
+                  .getGeometry()
+                  .clone()
+                  .transform("EPSG:3857", "EPSG:4326")
+                  .getExtent()
+              ),
+              zoom: 17,
+              preventZoomOut: true
+            },
+            { root: true }
+          );
+        }
+      };
+      commit("application/addSplitscreen", splitscreenConf, {
+        root: true
+      });
+      commit("application/activeSplitscreenId", "gauge-waterlevel", {
+        root: true
+      });
+      commit("application/splitscreenLoading", false, { root: true });
+      commit("application/showSplitscreen", true, { root: true });
+    }
+  }
+};
--- a/client/src/store/index.js	Mon Mar 11 18:46:09 2019 +0100
+++ b/client/src/store/index.js	Tue Mar 12 08:37:09 2019 +0100
@@ -23,6 +23,7 @@
 import bottlenecks from "./bottlenecks";
 import { imports } from "./imports";
 import { importschedule } from "./importschedule";
+import gauges from "./gauges";
 
 Vue.use(Vuex);
 
@@ -37,7 +38,8 @@
         bottlenecks: bottlenecks.init(),
         map: map.init(),
         user: user.init(),
-        usermanagement: usermanagement.init()
+        usermanagement: usermanagement.init(),
+        gauges: gauges.init()
       });
     }
   },
@@ -49,6 +51,7 @@
     bottlenecks,
     map,
     user,
-    usermanagement
+    usermanagement,
+    gauges
   }
 });
--- a/client/src/store/map.js	Mon Mar 11 18:46:09 2019 +0100
+++ b/client/src/store/map.js	Tue Mar 12 08:37:09 2019 +0100
@@ -790,6 +790,27 @@
                     });
                   }
                 }
+
+                // get selected gauge
+                if (/^gauges/.test(id)) {
+                  if (rootState.gauges.selectedGauge !== feature) {
+                    dispatch("gauges/selectedGauge", feature, {
+                      root: true
+                    });
+                    commit("moveMap", {
+                      coordinates: getCenter(
+                        feature
+                          .getGeometry()
+                          .clone()
+                          .transform("EPSG:3857", "EPSG:4326")
+                          .getExtent()
+                      ),
+                      zoom: 17,
+                      preventZoomOut: true
+                    });
+                    commit("application/showSplitscreen", true, { root: true });
+                  }
+                }
               }
 
               commit("setIdentifiedFeatures", identifiedFeatures);