Mercurial > gemma
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);