Mercurial > gemma
view client/src/fairway/Fairwayprofile.vue @ 1207:70116d392387
close bottleneck list: made searchbar collapse only if it was collapsed before
opening the bottleneck list will expand the searchbar, closing the bottleneck list
was always collapsing the searchbar too. Now it stays open if it was open beforeopening the bottleneck list
author | Markus Kottlaender <markus@intevation.de> |
---|---|
date | Mon, 19 Nov 2018 13:02:48 +0100 |
parents | ddfdf440da24 |
children | ba8cd80d68b6 |
line wrap: on
line source
<template> <div :class="['position-relative', {show: showSplitscreen}]" v-if="Object.keys(currentProfile).length"> <button class="rounded-bottom bg-white border-0 position-absolute splitscreen-toggle shadow-sm" @click="$store.commit('application/showSplitscreen', false)" v-if="showSplitscreen"> <i class="fa fa-angle-down"></i> </button> <button class="rounded-bottom bg-white border-0 position-absolute clear-selection shadow-sm" @click="$store.dispatch('fairwayprofile/clearSelection');" v-if="showSplitscreen"> <i class="fa fa-times text-danger"></i> </button> <div class="profile d-flex flex-column pr-5"> <h5 class="mb-0 mt-2">{{ selectedBottleneck }} ({{ selectedSurvey.date_info }})</h5> <div class="d-flex flex-fill"> <div class="fairwayprofile flex-grow-1"></div> <div class="additionalsurveys d-flex flex-column"> <small> Additional Surveys <select v-model="additionalSurvey" class="form-control form-control-sm"> <option value="">None</option> <option v-for="survey in additionalSurveys" :key="survey.date_info" :value="survey" >{{survey.date_info}}</option> </select> <hr> <div class="d-flex text-left mb-2"> <div class="text-nowrap mr-1"> <b>Start:</b> <br> Lat: {{ startPoint[1] }} <br> Lon: {{ startPoint[0] }} </div> <div class="text-nowrap ml-1"> <b>End:</b> <br> Lat: {{ endPoint[1] }} <br> Lon: {{ endPoint[0] }} </div> <button class="btn btn-outline-secondary btn-sm ml-2 mt-auto" @click="showLabelInput = !showLabelInput"> <i :class="'fa fa-' + (showLabelInput ? 'times' : 'save')"></i> </button> <button v-clipboard:copy="coordinatesForClipboard" v-clipboard:success="onCopyCoordinates" class="btn btn-outline-secondary btn-sm ml-2 mt-auto"> <i class="fa fa-copy"></i> </button> </div> <div v-if="showLabelInput"> Enter label for cross profile: <div class="position-relative"> <input class="form-control form-control-sm pr-5" v-model="cutLabel" /><br> <button class="btn btn-sm btn-outline-secondary position-absolute" @click="saveCut" v-if="cutLabel" style="top: 0; right: 0;"> <i class="fa fa-check"></i> </button> </div> </div> Saved cross profiles: <select class="form-control form-control-sm mb-2" v-model="coordinatesSelect"> <option></option> <option v-for="(cut, index) in previousCuts" :value="cut.coordinates" :key="index"> {{ cut.label }} </option> </select> Enter coordinates manually: <div class="position-relative"> <input class="form-control form-control-sm pr-5" placeholder="Lat,Lon,Lat,Lon" v-model="coordinatesInput" /><br> <button class="btn btn-sm btn-outline-secondary position-absolute" @click="applyManualCoordinates" style="top: 0; right: 0;" v-if="coordinatesInput"> <i class="fa fa-check"></i> </button> </div> </small> </div> </div> </div> </div> </template> <style lang="sass" scoped> .profile background-color: white width: 100vw height: 0 overflow: hidden position: relative z-index: 2 .splitscreen-toggle, .clear-selection right: $icon-width + $offset width: $icon-width height: $icon-height margin-top: 2px z-index: 3 outline: none .clear-selection right: $offset .show .profile height: 50vh .waterlevelselection margin-top: $large-offset margin-right: $large-offset .additionalsurveys margin-top: $large-offset margin-bottom: auto margin-right: $large-offset margin-left: auto max-width: 300px .additionalsurveys input margin-right: $small-offset .fairwayprofile background-color: white margin: $offset margin-top: 0 </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): * Thomas Junk <thomas.junk@intevation.de> */ import * as d3 from "d3"; import { mapState, mapGetters } from "vuex"; import { displayError, displayInfo } from "../application/lib/errors.js"; import Feature from "ol/Feature"; import LineString from "ol/geom/LineString"; const GROUND_COLOR = "#4A2F06"; export default { name: "fairwayprofile", props: [ "width", "height", "xScale", "yScaleLeft", "yScaleRight", "margin", "additionalSurveys" ], data() { return { wait: false, coordinatesInput: "", coordinatesSelect: null, cutLabel: "", showLabelInput: false }; }, computed: { ...mapGetters("map", ["getLayerByName"]), ...mapState("application", ["showSplitscreen"]), ...mapState("fairwayprofile", [ "startPoint", "endPoint", "currentProfile", "minAlt", "maxAlt", "totalLength", "fairwayCoordinates", "waterLevels", "selectedWaterLevel", "previousCuts" ]), ...mapState("bottlenecks", ["selectedBottleneck", "selectedSurvey"]), additionalSurvey: { get() { return this.$store.getters["fairwayprofile/additionalSurvey"]; }, set(value) { this.$store.commit("fairwayprofile/setAdditionalSurvey", value); this.selectAdditionalSurveyData(); } }, currentData() { if ( !this.selectedSurvey || !this.currentProfile.hasOwnProperty(this.selectedSurvey.date_info) ) return []; return this.currentProfile[this.selectedSurvey.date_info]; }, additionalData() { if ( !this.additionalSurvey || !this.currentProfile.hasOwnProperty(this.additionalSurvey.date_info) ) return []; return this.currentProfile[this.additionalSurvey.date_info]; }, waterColor() { const result = this.waterLevels.find( x => x.level === this.selectedWaterLevel ); return result.color; }, coordinatesForClipboard() { return ( this.startPoint[1] + "," + this.startPoint[0] + "," + this.endPoint[1] + "," + this.endPoint[0] ); } }, watch: { showSplitscreen() { this.drawDiagram(); }, currentData() { this.drawDiagram(); }, width() { this.drawDiagram(); }, height() { this.drawDiagram(); }, waterLevels() { this.drawDiagram(); }, selectedWaterLevel() { this.drawDiagram(); }, fairwayCoordinates() { this.drawDiagram(); }, selectedBottleneck() { this.$store.dispatch("fairwayprofile/previousCuts"); this.cutLabel = this.selectedBottleneck + " (" + new Date().toISOString() + ")"; }, coordinatesSelect(newValue) { if (newValue) { this.applyCoordinates(newValue); } } }, methods: { selectAdditionalSurveyData() { if ( !this.additionalSurvey || this.wait || this.currentProfile[this.additionalSurvey.date_info] ) { this.drawDiagram(); return; } this.$store .dispatch("fairwayprofile/loadProfile", this.additionalSurvey) .then(() => { this.wait = false; }) .catch(error => { this.wait = false; let status = "ERROR"; let data = error; const response = error.response; if (response) { status = response.status; data = response.data; } displayError({ title: "Backend Error", message: `${status}: ${data.message || data}` }); }); }, drawDiagram() { this.coordinatesSelect = null; const chartDiv = document.querySelector(".fairwayprofile"); d3.select("svg").remove(); let svg = d3.select(chartDiv).append("svg"); svg.attr("width", this.width); svg.attr("height", this.height); const width = this.width - this.margin.right - 1.5 * this.margin.left; const height = this.height - this.margin.top - 2 * this.margin.bottom; const currentData = this.currentData; const additionalData = this.additionalData; const { xScale, yScaleRight, yScaleLeft, graph } = this.generateCoordinates(svg, height, width); this.drawWaterlevel({ graph, xScale, yScaleRight, height, width }); if (currentData) { this.drawProfile({ graph, xScale, yScaleRight, currentData, height, width, color: GROUND_COLOR, strokeColor: "black", opacity: 1 }); } if (additionalData) { this.drawProfile({ graph, xScale, yScaleRight, currentData: additionalData, height, width, color: GROUND_COLOR, strokeColor: "#943007", opacity: 0.6 }); } this.drawLabels({ graph, xScale, yScaleLeft, currentData, height, width }); this.drawFairway({ graph, xScale, yScaleRight, currentData, height, width }); }, drawFairway({ graph, xScale, yScaleRight }) { for (let coordinates of this.fairwayCoordinates) { const [startPoint, endPoint, depth] = coordinates; let fairwayArea = d3 .area() .x(function(d) { return xScale(d.x); }) .y0(yScaleRight(0)) .y1(function(d) { return yScaleRight(d.y); }); graph .append("path") .datum([{ x: startPoint, y: depth }, { x: endPoint, y: depth }]) .attr("fill", "#002AFF") .attr("stroke-opacity", 0.65) .attr("fill-opacity", 0.65) .attr("stroke", "#FFD20D") .attr("d", fairwayArea); } }, drawLabels({ graph, height }) { graph .append("text") .attr("transform", ["rotate(-90)"]) .attr("y", this.width - 60) .attr("x", -(this.height - this.margin.top - this.margin.bottom) / 2) .attr("dy", "1em") .attr("fill", "black") .style("text-anchor", "middle") .text("Depth [m]"); graph .append("text") .attr("y", 0 - this.margin.left) .attr("x", 0 - height / 4) .attr("dy", "1em") .attr("fill", "black") .style("text-anchor", "middle") .attr("transform", [ "translate(" + this.width / 2 + "," + this.height + ")", "rotate(0)" ]) .text("Width [m]"); }, generateCoordinates(svg, height, width) { let xScale = d3 .scaleLinear() .domain(this.xScale) .rangeRound([0, width]); xScale.ticks(5); let yScaleLeft = d3 .scaleLinear() .domain(this.yScaleLeft) .rangeRound([height, 0]); let yScaleRight = d3 .scaleLinear() .domain(this.yScaleRight) .rangeRound([height, 0]); let xAxis = d3.axisBottom(xScale); let yAxis2 = d3.axisRight(yScaleRight); let graph = svg .append("g") .attr( "transform", "translate(" + this.margin.left + "," + this.margin.top + ")" ); graph .append("g") .attr("transform", "translate(0," + height + ")") .call(xAxis.ticks(5)); graph .append("g") .attr("transform", "translate(" + width + ",0)") .call(yAxis2); return { xScale, yScaleLeft, yScaleRight, graph }; }, drawWaterlevel({ graph, xScale, yScaleRight, height }) { let waterArea = d3 .area() .x(function(d) { return xScale(d.x); }) .y0(height) .y1(function(d) { return yScaleRight(d.y); }); graph .append("path") .datum([{ x: 0, y: 0 }, { x: this.totalLength, y: 0 }]) .attr("fill", this.waterColor) .attr("stroke", this.waterColor) .attr("d", waterArea); }, drawProfile({ graph, xScale, yScaleRight, currentData, height, color, strokeColor, opacity }) { for (let part of currentData) { let profileLine = d3 .line() .x(d => { return xScale(d.x); }) .y(d => { return yScaleRight(d.y); }); let profileArea = d3 .area() .x(function(d) { return xScale(d.x); }) .y0(height) .y1(function(d) { return yScaleRight(d.y); }); graph .append("path") .datum(part) .attr("fill", color) .attr("stroke", color) .attr("stroke-width", 3) .attr("stroke-opacity", opacity) .attr("fill-opacity", opacity) .attr("d", profileArea); graph .append("path") .datum(part) .attr("fill", "none") .attr("stroke", strokeColor) .attr("stroke-linejoin", "round") .attr("stroke-linecap", "round") .attr("stroke-width", 3) .attr("stroke-opacity", opacity) .attr("fill-opacity", opacity) .attr("d", profileLine); } }, onCopyCoordinates() { displayInfo({ title: "Success", message: "Coordinates copied to clipboard!" }); }, applyManualCoordinates() { const coordinates = this.coordinatesInput .split(",") .map(coord => parseFloat(coord.trim())); this.applyCoordinates([ coordinates[1], coordinates[0], coordinates[3], coordinates[2] ]); }, applyCoordinates(coordinates) { // allow only numbers coordinates = coordinates.filter(c => Number(c) === c); if (coordinates.length === 4) { // draw line on map const cutLayer = this.getLayerByName("Cut Tool"); cutLayer.data.getSource().clear(); const cut = new Feature({ geometry: new LineString([ [coordinates[0], coordinates[1]], [coordinates[2], coordinates[3]] ]).transform("EPSG:4326", "EPSG:3857") }); cutLayer.data.getSource().addFeature(cut); // draw diagram this.$store.dispatch("fairwayprofile/cut", cut); } else { displayError({ title: "Invalid input", message: "Please enter correct coordinates in the format: Lat,Lon,Lat,Lon" }); } }, saveCut() { const previousCuts = JSON.parse(localStorage.getItem("previousCuts")) || []; const newEntry = { label: this.cutLabel, bottleneckName: this.selectedBottleneck, coordinates: [...this.startPoint, ...this.endPoint] }; const existingEntry = previousCuts.find(cut => { return JSON.stringify(cut) === JSON.stringify(newEntry); }); if (!existingEntry) previousCuts.push(newEntry); if (previousCuts.length > 100) previousCuts.shift(); localStorage.setItem("previousCuts", JSON.stringify(previousCuts)); this.$store.dispatch("fairwayprofile/previousCuts"); this.showLabelInput = false; displayInfo({ title: "Coordinates saved!", message: 'You can now select these coordinates from the "Saved cross profiles" menu to restore this cross profile.' }); } }, mounted() { this.drawDiagram(); } }; </script>