Mercurial > gemma
view client/src/fairway/Fairwayprofile.vue @ 1199:181b8a947ecd
fixed property of null bug
author | Markus Kottlaender <markus@intevation.de> |
---|---|
date | Mon, 19 Nov 2018 08:51:44 +0100 |
parents | 1335e088e7ef |
children | 502e0b960424 |
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())); if (coordinates.length === 4) { this.applyCoordinates([ coordinates[1], coordinates[0], coordinates[3], coordinates[2] ]); } }, applyCoordinates(coordinates) { 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); } }, 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>