Mercurial > gemma
changeset 3296:fa7dc3f31ef3
available_fairwaydepth_vs_lnwl: POC etd.
author | Thomas Junk <thomas.junk@intevation.de> |
---|---|
date | Thu, 16 May 2019 16:23:19 +0200 |
parents | a409608dd6b3 |
children | 0358bf723769 |
files | client/src/components/Main.vue client/src/components/Pane.vue client/src/components/fairway/AvailableFairwayDepthDialogue.vue client/src/components/fairway/AvailableFairwayDepthLNWL.vue client/src/components/paneSetups.js client/src/store/application.js |
diffstat | 6 files changed, 590 insertions(+), 1 deletions(-) [+] |
line wrap: on
line diff
--- a/client/src/components/Main.vue Thu May 16 15:36:11 2019 +0200 +++ b/client/src/components/Main.vue Thu May 16 16:23:19 2019 +0200 @@ -97,6 +97,12 @@ : ["w-50 h-100", "w-50 h-100"]; } + if (this.paneSetup === "AVAILABLEFAIRWAYDEPTHLNWL") { + return [1, 3].includes(this.paneRotate) + ? ["w-100 h-50", "w-100 h-50"] + : ["w-50 h-100", "w-50 h-100"]; + } + if (this.paneSetup === "COMPARESURVEYS_FAIRWAYPROFILE") { return [1, 3].includes(this.paneRotate) ? ["wh-50", "wh-50", "w-100 h-50"]
--- a/client/src/components/Pane.vue Thu May 16 15:36:11 2019 +0200 +++ b/client/src/components/Pane.vue Thu May 16 16:23:19 2019 +0200 @@ -26,6 +26,8 @@ Map: () => import("./map/Map"), Fairwayprofile: () => import("./fairway/Fairwayprofile"), AvailableFairwayDepth: () => import("./fairway/AvailableFairwayDepth"), + AvailableFairwayDepthLNWL: () => + import("./fairway/AvailableFairwayDepthLNWL"), Waterlevel: () => import("./gauge/Waterlevel"), HydrologicalConditions: () => import("./gauge/HydrologicalConditions") }
--- a/client/src/components/fairway/AvailableFairwayDepthDialogue.vue Thu May 16 15:36:11 2019 +0200 +++ b/client/src/components/fairway/AvailableFairwayDepthDialogue.vue Thu May 16 16:23:19 2019 +0200 @@ -128,7 +128,16 @@ :disabled="isComplete" class="btn btn-info btn-sm w-100" > - <translate>Available Fairway Depth</translate> + <translate>Available fairway depth</translate> + </button> + </div> + <div class="mt-3"> + <button + @click="openFairwaydepthLNWL" + :disabled="isComplete" + class="btn btn-info btn-sm w-100" + > + <translate>Available fairway depth vs LNWL</translate> </button> </div> </div> @@ -170,6 +179,34 @@ }; }, methods: { + openFairwaydepthLNWL() { + this.loading = true; + this.$store + .dispatch("fairwayavailability/loadAvailableFairwayDepth", { + feature: this.selectedFairwayAvailabilityFeature, + from: this.from, + to: this.to, + frequency: this.frequency, + LOS: this.los + }) + .then(() => { + this.$store.commit( + "application/paneSetup", + "AVAILABLEFAIRWAYDEPTHLNWL" + ); + }) + .catch(error => { + console.log(error); + const { status, data } = error.response; + displayError({ + title: this.$gettext("Backend Error"), + message: `${status}: ${data.message || data}` + }); + }) + .finally(() => { + this.loading = false; + }); + }, openFairwaydepth() { this.loading = true; this.$store @@ -197,6 +234,7 @@ }, close() { this.$store.commit("application/showFairwayDepth", false); + this.$store.commit("application/showFairwayDepthLNWL", false); }, entrySelected() { if (this.type === this.$options.BOTTLENECKS) {
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/client/src/components/fairway/AvailableFairwayDepthLNWL.vue Thu May 16 16:23:19 2019 +0200 @@ -0,0 +1,531 @@ +<template> + <div class="d-flex flex-column flex-fill"> + <UIBoxHeader icon="chart-area" :title="title" :closeCallback="close" /> + <UISpinnerOverlay v-if="loading" /> + <div class="d-flex flex-fill"> + <DiagramLegend> + <div v-for="(entry, index) in legend" :key="index" class="legend"> + <span + :style=" + `${legendStyle( + index + )}; border-radius: 0.25rem; width: 40px; height: 20px;` + " + ></span> + {{ entry }} + </div> + <div> + <select + @change="applyChange" + v-model="form.template" + class="form-control d-block custom-select-sm w-100 mt-1" + > + <option + v-for="template in templates" + :value="template" + :key="template.name" + > + {{ template.name }} + </option> + </select> + <button + @click="downloadPDF" + type="button" + class="btn btn-sm btn-info d-block w-100 mt-1" + > + <translate>Export to PDF</translate> + </button> + </div> + </DiagramLegend> + <div + ref="diagramContainer" + :id="containerId" + class="mx-auto my-auto diagram-container" + ></div> + </div> + </div> +</template> + +<style></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> + * Markus Kottländer <markus.kottlaender@intevation.de> + * Fadi Abbud <fadi.abbud@intevation.de> + */ +import * as d3 from "d3"; +import app from "@/main"; +import debounce from "debounce"; +import { diagram } from "@/lib/mixins"; +import { mapState } from "vuex"; +import filters from "@/lib/filters.js"; +import jsPDF from "jspdf"; +import canvg from "canvg"; +import { pdfgen } from "@/lib/mixins"; +import { HTTP } from "@/lib/http"; +import { displayError } from "@/lib/errors"; + +const hoursInDays = x => x / 24; + +export default { + mixins: [diagram, pdfgen], + components: { + DiagramLegend: () => import("@/components/DiagramLegend") + }, + data() { + return { + containerId: "availablefairwaydepth", + loading: false, + width: 1000, + height: 600, + paddingRight: 100, + spaceBetween: 80, + labelPaddingTop: 15, + scalePaddingLeft: 50, + paddingTop: 10, + diagram: null, + yScale: null, + barsWidth: 60, + dimensions: null, + pdf: { + doc: null, + width: null, + height: null + }, + form: { + template: null + }, + templateData: null, + templates: [], + defaultTemplate: { + name: "Default", + properties: { + paperSize: "a4" + }, + elements: [ + { + type: "diagram", + position: "topleft", + offset: { x: 20, y: 60 }, + width: 290, + height: 100 + }, + { + type: "diagramtitle", + position: "topleft", + offset: { x: 70, y: 20 }, + fontsize: 20, + color: "steelblue" + }, + { + type: "diagramlegend", + position: "topleft", + offset: { x: 30, y: 160 }, + color: "black" + } + ] + } + }; + }, + created() { + window.addEventListener("resize", debounce(this.drawDiagram), 100); + }, + mounted() { + this.drawDiagram(); + this.templates[0] = this.defaultTemplate; + this.form.template = this.templates[0]; + this.templateData = this.form.template; + HTTP.get("/templates/diagram", { + headers: { + "X-Gemma-Auth": localStorage.getItem("token"), + "Content-type": "text/xml; charset=UTF-8" + } + }) + .then(response => { + if (response.data.length) { + this.templates = response.data; + this.form.template = this.templates[0]; + this.templates[this.templates.length] = this.defaultTemplate; + this.applyChange(); + } + }) + .catch(e => { + const { status, data } = e.response; + displayError({ + title: this.$gettext("Backend Error"), + message: `${status}: ${data.message || data}` + }); + }); + }, + computed: { + ...mapState("fairwayavailability", [ + "selectedFairwayAvailabilityFeature", + "fwData", + "from", + "to", + "frequency", + "legend" + ]), + ...mapState("user", ["user"]), + fromDate() { + return this.from; + }, + toDate() { + return this.to; + }, + availability() { + return this.plainAvailability; + }, + title() { + return `Available Fairway Depth vs LNWL: ${ + this.featureName + } (${filters.surveyDate(this.fromDate)} - ${filters.surveyDate( + this.toDate + )}) ${this.$gettext(this.frequency)}`; + }, + featureName() { + return this.selectedFairwayAvailabilityFeature.properties.name; + } + }, + methods: { + applyChange() { + if (this.form.template.hasOwnProperty("properties")) { + this.templateData = this.defaultTemplate; + return; + } + if (this.form.template) { + HTTP.get("/templates/diagram/" + this.form.template.name, { + headers: { + "X-Gemma-Auth": localStorage.getItem("token"), + "Content-type": "text/xml; charset=UTF-8" + } + }) + .then(response => { + this.templateData = response.data.template_data; + }) + .catch(e => { + const { status, data } = e.response; + displayError({ + title: this.$gettext("Backend Error"), + message: `${status}: ${data.message || data}` + }); + }); + } + }, + downloadPDF() { + this.pdf.doc = new jsPDF( + "l", + "mm", + this.templateData.properties.paperSize + ); + // pdf width and height in millimeter (landscape) + this.pdf.width = + this.templateData.properties.paperSize === "a3" ? 420 : 297; + this.pdf.height = + this.templateData.properties.paperSize === "a3" ? 297 : 210; + if (this.templateData) { + // default values if some are missing in template + let defaultFontSize = 11, + defaultColor = "black", + defaultWidth = 70, + defaultTextColor = "black", + defaultBorderColor = "white", + defaultBgColor = "white", + defaultRounding = 2, + defaultPadding = 2, + defaultOffset = { x: 0, y: 0 }; + this.templateData.elements.forEach(e => { + switch (e.type) { + case "diagram": { + this.addDiagram( + e.position, + e.offset || defaultOffset, + e.width, + e.height + ); + break; + } + case "diagramtitle": { + let title = `Available Fairway Depth vs LNWL: ${ + this.featureName + }`; + this.addDiagramTitle( + e.position, + e.offset || defaultOffset, + e.fontsize || defaultFontSize, + e.color || defaultColor, + title + ); + break; + } + case "diagramlegend": { + this.addDiagramLegend( + e.position, + e.offset || defaultOffset, + e.color || defaultColor + ); + break; + } + case "text": { + this.addText( + e.position, + e.offset || defaultOffset, + e.width || defaultWidth, + e.fontsize || defaultFontSize, + e.color || defaultTextColor, + e.text + ); + break; + } + case "image": { + this.addImage( + e.url, + e.format, + e.position, + e.offset || defaultOffset, + e.width, + e.height + ); + break; + } + case "box": { + this.addBox( + e.position, + e.offset, + e.width, + e.height, + e.rounding === 0 || e.rounding ? e.rounding : defaultRounding, + e.color || defaultBgColor, + e.brcolor || defaultBorderColor + ); + break; + } + case "textbox": { + this.addTextBox( + e.position, + e.offset || defaultOffset, + e.width, + e.height, + e.rounding === 0 || e.rounding ? e.rounding : defaultRounding, + e.padding || defaultPadding, + e.fontsize || defaultFontSize, + e.color || defaultTextColor, + e.background || defaultBgColor, + e.text, + e.brcolor || defaultBorderColor + ); + break; + } + } + }); + } + this.pdf.doc.save(`Available Fairway Depth LNWL: ${this.featureName}`); + }, + addDiagram(position, offset, width, height) { + let x = offset.x, + y = offset.y; + var svg = this.$refs.diagramContainer.innerHTML; + if (svg) { + svg = svg.replace(/\r?\n|\r/g, "").trim(); + } + if (!width) { + width = this.templateData.properties.paperSize === "a3" ? 380 : 290; + } + if (!height) { + height = this.templateData.properties.paperSize === "a3" ? 130 : 100; + } + if (["topright", "bottomright"].indexOf(position) !== -1) { + x = this.pdf.width - offset.x - width; + } + if (["bottomright", "bottomleft"].indexOf(position) !== -1) { + y = this.pdf.height - offset.y - height; + } + var canvas = document.createElement("canvas"); + canvas.width = window.innerWidth; + canvas.height = window.innerHeight / 2; + canvg(canvas, svg, { + ignoreMouse: true, + ignoreAnimation: true, + ignoreDimensions: true + }); + var imgData = canvas.toDataURL("image/png"); + this.pdf.doc.addImage(imgData, "PNG", x, y, width, height); + }, + addDiagramLegend(position, offset, color) { + let x = offset.x, + y = offset.y; + + this.pdf.doc.setFontSize(10); + this.pdf.doc.setTextColor(color); + this.pdf.doc.setDrawColor("rgb(255, 133, 94)"); + this.pdf.doc.setFillColor("rgb(255, 133, 94)"); + this.pdf.doc.rect(x, y, 8, 4, "FD"); + this.pdf.doc.text(">= LDC [h]", x + 10, y + 3); + + this.pdf.doc.setDrawColor("rgb(255, 66, 79)"); + this.pdf.doc.setFillColor("rgb(255, 66, 79)"); + this.pdf.doc.rect(x, y + 5, 8, 4, "FD"); + this.pdf.doc.text("< 200.00 [h]", x + 10, y + 8); + + this.pdf.doc.setDrawColor("rgb(255, 115, 124)"); + this.pdf.doc.setFillColor("rgb(255, 115, 124)"); + this.pdf.doc.rect(x, y + 10, 8, 4, "FD"); + this.pdf.doc.text(">= 200.00 [h]", x + 10, y + 13); + + this.pdf.doc.setDrawColor("rgb(255, 153, 160)"); + this.pdf.doc.setFillColor("rgb(255, 153, 160)"); + this.pdf.doc.rect(x, y + 15, 8, 4, "FD"); + this.pdf.doc.text(">= 230.00 [h]", x + 10, y + 18); + + this.pdf.doc.setDrawColor("rgb(45, 132, 179)"); + this.pdf.doc.setFillColor("rgb(45, 132, 179)"); + this.pdf.doc.rect(x, y + 20, 8, 4, "FD"); + this.pdf.doc.text(">= 250.00 [h]", x + 10, y + 23); + }, + legendStyle(index) { + if (index == 0) return `background-color: ${this.$options.COLORS.LDC};`; + if (index < 4) + return `background-color: ${this.$options.COLORS.REST[index - 1]};`; + return `background-color: ${this.$options.COLORS.HIGHEST};`; + }, + close() { + this.$store.commit("application/paneSetup", "DEFAULT"); + }, + drawDiagram() { + this.dimensions = this.getDimensions({ + main: { top: 20, right: 20, bottom: 110, left: 200 } + }); + this.yScale = d3 + .scaleLinear() + .domain([-33, 33]) + .range([this.dimensions.mainHeight - 30, 0]); + d3.select(".diagram-container svg").remove(); + this.generateDiagramContainer(); + this.drawBars(); + this.drawScaleLabel(); + this.drawScale(); + }, + generateDiagramContainer() { + const diagram = d3 + .select(".diagram-container") + .append("svg") + .attr("width", this.dimensions.width) + .attr("height", this.dimensions.mainHeight); + this.diagram = diagram + .append("g") + .attr("transform", `translate(0 ${this.paddingTop})`); + }, + drawBars() { + const everyBar = this.diagram + .selectAll("g") + .data(this.fwData) + .enter() + .append("g") + .attr("transform", (d, i) => { + const dx = this.paddingRight + i * this.spaceBetween; + return `translate(${dx})`; + }); + this.drawSingleBars(everyBar); + this.drawLabelPerBar(everyBar); + }, + drawSingleBars(everyBar) { + this.drawLDC(everyBar); + this.drawHighestLevel(everyBar); + this.drawLowerLevels(everyBar); + }, + drawLowerLevels(everyBar) { + everyBar + .selectAll("g") + .data(d => d.lowerLevels.reverse()) + .enter() + .append("rect") + .attr("y", this.yScale(0)) + .attr("height", d => { + return this.yScale(0) - this.yScale(hoursInDays(d)); + }) + .attr("width", this.barsWidth) + .attr("fill", (d, i) => { + return this.$options.COLORS.REST[i]; + }); + }, + fnheight(name) { + return d => this.yScale(0) - this.yScale(hoursInDays(d[name])); + }, + drawLDC(everyBar) { + const height = this.fnheight("ldc"); + everyBar + .append("rect") + .attr("y", this.yScale(0)) + .attr("height", height) + .attr("width", this.barsWidth) + .attr("transform", d => `translate(0 ${-1 * height(d)})`) + .attr("fill", this.$options.COLORS.LDC) + .attr("id", "ldc"); + }, + drawHighestLevel(everyBar) { + const height = this.fnheight("highestLevel"); + everyBar + .append("rect") + .attr("y", this.yScale(0)) + .attr("height", height) + .attr("width", this.barsWidth - 5) + .attr("transform", d => `translate(0 ${-1 * height(d)})`) + .attr("fill", this.$options.COLORS.HIGHEST); + }, + drawLabelPerBar(everyBar) { + everyBar + .append("text") + .text(d => d.label) + .attr("y", this.yScale(0) + this.labelPaddingTop); + }, + drawScaleLabel() { + const center = this.dimensions.mainHeight / 2; + this.diagram + .append("text") + .text(this.$options.LEGEND) + .attr("text-anchor", "middle") + .attr("x", 0) + .attr("y", 0) + .attr("dy", "1em") + .attr("transform", `translate(0, ${center}), rotate(-90)`); + }, + drawScale() { + const yAxis = d3.axisLeft().scale(this.yScale); + this.diagram + .append("g") + .attr("transform", `translate(${this.scalePaddingLeft})`) + .call(yAxis) + .selectAll(".tick text") + .attr("fill", "black") + .select(function() { + return this.parentNode; + }) + .selectAll(".tick line") + .attr("stroke", "black"); + this.diagram.selectAll(".domain").attr("stroke", "black"); + } + }, + watch: { + fwData() { + this.drawDiagram(); + } + }, + LEGEND: app.$gettext("Sum of days"), + COLORS: { + LDC: "#cdcdcd", + HIGHEST: "#3675ff", + REST: ["#782121", "#ff6c6c", "#ffaaaa"] + } +}; +</script>
--- a/client/src/components/paneSetups.js Thu May 16 15:36:11 2019 +0200 +++ b/client/src/components/paneSetups.js Thu May 16 16:23:19 2019 +0200 @@ -18,6 +18,14 @@ } }; +export const AVAILABLEFAIRWAYDEPTHLNWL = { + main, + availablefairwaydepth: { + id: "availablefairwaydepthlnwl", + component: "AvailableFairwayDepthLNWL" + } +}; + export const COMPARESURVEYS_FAIRWAYPROFILE = { main, compare: { id: "compare-survey", component: "Map" },
--- a/client/src/store/application.js Thu May 16 15:36:11 2019 +0200 +++ b/client/src/store/application.js Thu May 16 16:23:19 2019 +0200 @@ -36,6 +36,7 @@ showProfiles: false, showGauges: false, showFairwayDepth: false, + showFairwayDepthLNWL: false, contextBoxContent: null, // bottlenecks, imports, staging expandToolbar: false, countries: ["AT", "SK", "HU", "HR", "RS", "BiH", "BG", "RO", "UA"], @@ -117,6 +118,9 @@ showFairwayDepth: (state, show) => { state.showFairwayDepth = show; }, + showFairwayDepthLNWL: (state, show) => { + state.showFairwayDepthLNWL = show; + }, contextBoxContent: (state, context) => { state.contextBoxContent = context; if (context) {