Mercurial > gemma
view client/src/components/fairway/AvailableFairwayDepth.vue @ 3515:c64c47ff2ab1 password
import: fix for passwordleakage. No passwords are received/sent if configuration is edited.
author | Thomas Junk <thomas.junk@intevation.de> |
---|---|
date | Wed, 29 May 2019 09:56:42 +0200 |
parents | da58cf951342 |
children | 067ad32fba69 |
line wrap: on
line source
<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-2" > <translate>Export to PDF</translate> </button> <a :href="dataLink" :download="csvFileName" class="mt-2 btn btn-sm btn-info w-100" >Download CSV</a > </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"; import { FREQUENCIES } from "@/store/fairwayavailability"; 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, ldcoffset: 3, 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), 200); }, 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", "csv", "depthlimit1", "depthlimit2", "widthlimit1", "widthlimit2" ]), legend() { const d = [this.depthlimit1, this.depthlimit2].sort(); const w = [this.widthlimit1, this.widthlimit2].sort(); const lowerBound = [d[0], w[0]].filter(x => x).join(", "); const upperBound = [d[1], w[1]].filter(x => x).join(", "); return [ `> LDC`, `< ${lowerBound}`, `< ${upperBound}`, `>= ${upperBound}` ]; }, dataLink() { return `data:text/csv;charset=utf-8, ${encodeURIComponent(this.csv)}`; }, csvFileName() { return `${this.$gettext("fairwayavailability")}-${ this.featureName }-${filters.surveyDate(this.fromDate)}-${filters.surveyDate( this.toDate )}-${this.$gettext(this.frequency)}-.csv`; }, frequencyToRange() { const frequencies = { [FREQUENCIES.MONTHLY]: [-33, 33], [FREQUENCIES.QUARTERLY]: [-93, 93], [FREQUENCIES.YEARLY]: [-370, 370] }; return frequencies[this.frequency]; }, fromDate() { return this.from; }, toDate() { return this.to; }, availability() { return this.plainAvailability; }, title() { return `Available Fairway Depth: ${ this.featureName } (${filters.surveyDate(this.fromDate)} - ${filters.surveyDate( this.toDate )}) ${this.$gettext(this.frequency)}`; }, featureName() { if (this.selectedFairwayAvailabilityFeature == null) return ""; 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: ${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: ${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); let width = (this.pdf.doc.getStringUnitWidth(">= LDC") * 10) / (72 / 25.6) + 15; // if position is on the right, x needs to be calculate with pdf width and // the size of the element 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 - this.getTextHeight(6); } this.pdf.doc.setTextColor(color); this.pdf.doc.setDrawColor(this.$options.COLORS.LDC); this.pdf.doc.setFillColor(this.$options.COLORS.LDC); this.pdf.doc.roundedRect(x, y, 10, 4, 1.5, 1.5, "FD"); this.pdf.doc.text(this.legend[0], x + 12, y + 3); this.pdf.doc.setDrawColor(this.$options.COLORS.REST[0]); this.pdf.doc.setFillColor(this.$options.COLORS.REST[0]); this.pdf.doc.roundedRect(x, y + 5, 10, 4, 1.5, 1.5, "FD"); this.pdf.doc.text(this.legend[1], x + 12, y + 8); this.pdf.doc.setDrawColor(this.$options.COLORS.REST[1]); this.pdf.doc.setFillColor(this.$options.COLORS.REST[1]); this.pdf.doc.roundedRect(x, y + 10, 10, 4, 1.5, 1.5, "FD"); this.pdf.doc.text(this.legend[2], x + 12, y + 13); this.pdf.doc.setDrawColor(this.$options.COLORS.HIGHEST); this.pdf.doc.setFillColor(this.$options.COLORS.HIGHEST); this.pdf.doc.roundedRect(x, y + 15, 10, 4, 1.5, 1.5, "FD"); this.pdf.doc.text(this.legend[3], x + 12, y + 18); }, legendStyle(index) { const style = { 0: `background-color: ${this.$options.COLORS.LDC};`, 1: `background-color: ${this.$options.COLORS.REST[0]};`, 2: `background-color: ${this.$options.COLORS.REST[1]};`, 3: `background-color: ${this.$options.COLORS.HIGHEST};` }; return style[index]; }, 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(this.frequencyToRange) .range([this.dimensions.mainHeight - 30, 0]); d3.select(".diagram-container svg").remove(); this.generateDiagramContainer(); this.drawBars(); this.drawScaleLabel(); this.drawScale(); this.drawTooltip(); }, 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})`); }, drawTooltip() { this.diagram .append("text") .text("banane") .attr("font-size", "0.8em") .attr("opacity", 0) .attr("id", "tooltip"); }, 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) .enter() .append("rect") .on("mouseover", function() { d3.select(this).attr("opacity", "0.8"); d3.select("#tooltip").attr("opacity", 1); }) .on("mouseout", function() { d3.select(this).attr("opacity", 1); d3.select("#tooltip").attr("opacity", 0); }) .on("mousemove", function(d) { let y = d3.mouse(this)[1]; const dy = document .querySelector(".diagram-container") .getBoundingClientRect().left; const value = Number.parseFloat(hoursInDays(d.height)).toFixed(2); d3.select("#tooltip") .text(value) .attr("y", y - 10) .attr("x", d3.event.pageX - dy); //d3.event.pageX gives coordinates relative to SVG //dy gives offset of svg on page }) .attr("y", d => { return 2 * this.yScale(0) - this.yScale(hoursInDays(d.translateY)); }) .attr("height", d => { return this.yScale(0) - this.yScale(hoursInDays(d.height)); }) .attr("x", this.ldcoffset) .attr("width", this.barsWidth - this.ldcoffset) .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") .on("mouseover", function() { d3.select(this).attr("opacity", "0.8"); d3.select("#tooltip").attr("opacity", 1); }) .on("mouseout", function() { d3.select(this).attr("opacity", 1); d3.select("#tooltip").attr("opacity", 0); }) .on("mousemove", function(d) { let y = d3.mouse(this)[1]; const dy = document .querySelector(".diagram-container") .getBoundingClientRect().left; const value = Number.parseFloat(hoursInDays(d.ldc)).toFixed(2); d3.select("#tooltip") .text(value) .attr("y", y - 50) .attr("x", d3.event.pageX - dy); //d3.event.pageX gives coordinates relative to SVG //dy gives offset of svg on page }) .attr("y", this.yScale(0)) .attr("height", height) .attr("x", -this.ldcoffset) .attr("width", this.barsWidth - this.ldcoffset) .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") .on("mouseover", function() { d3.select(this).attr("opacity", "0.8"); d3.select("#tooltip").attr("opacity", 1); }) .on("mouseout", function() { d3.select(this).attr("opacity", 1); d3.select("#tooltip").attr("opacity", 0); }) .on("mousemove", function(d) { let y = d3.mouse(this)[1]; const dy = document .querySelector(".diagram-container") .getBoundingClientRect().left; const value = Number.parseFloat(hoursInDays(d.highestLevel)).toFixed( 2 ); d3.select("#tooltip") .text(value) .attr("y", y - 50) .attr("x", d3.event.pageX - dy); //d3.event.pageX gives coordinates relative to SVG //dy gives offset of svg on page }) .attr("y", this.yScale(0)) .attr("x", this.ldcoffset) .attr("height", height) .attr("width", this.barsWidth - this.ldcoffset) .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.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>