Mercurial > gemma
view client/src/components/fairway/Fairwayprofile.vue @ 3241:ecfa09241437
client: pdf-template: add a set of template-elements (fairwayprofile)
author | Fadi Abbud <fadi.abbud@intevation.de> |
---|---|
date | Fri, 10 May 2019 13:11:24 +0200 |
parents | 5240f5440b62 |
children | 246754028bf4 |
line wrap: on
line source
<template> <div class="d-flex flex-column flex-fill"> <UIBoxHeader icon="chart-area" :title="title" :closeCallback="close" /> <div class="d-flex flex-fill"> <DiagramLegend> <div class="legend"> <span style="background-color: #5995ff"></span> Water </div> <div class="legend"> <span style="background-color: #1f4fff"></span> Fairway </div> <div class="legend"> <span style="background-color: #4a2f06"></span> Ground </div> <div> <select v-model="form.template" @change="applyChange" class="form-control d-block custom-select-sm w-100" > <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> </div> </DiagramLegend> <div ref="diagramContainer" class="d-flex flex-fill justify-content-center align-items-center diagram-container" > <div v-if="!fairwayData"> <translate>No data available.</translate> </div> </div> </div> </div> </template> <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> */ import * as d3 from "d3"; import { mapState, mapGetters } from "vuex"; import debounce from "debounce"; import jsPDF from "jspdf"; import canvg from "canvg"; import { pdfgen } from "@/lib/mixins"; import { HTTP } from "@/lib/http"; import { displayError } from "@/lib/errors"; const GROUND_COLOR = "#4A2F06"; const WATER_COLOR = "#005DFF"; export default { mixins: [pdfgen], name: "fairwayprofile", components: { DiagramLegend: () => import("@/components/DiagramLegend") }, data() { return { width: null, height: null, margin: { top: 20, right: 40, bottom: 30, left: 40 }, form: { template: null }, templates: [], defaultTemplate: { name: "default", properties: { paperSize: "a4", format: "lanscape" }, elements: [ { type: "diagram", position: "topleft", offset: { x: 20, y: 60 }, width: 290, height: 100 }, { type: "diagramtitle", position: "topleft", offset: { x: 90, y: 30 }, fontsize: 22, color: "steelblue" }, { type: "diagramlegend", position: "topleft", offset: { x: 30, y: 160 }, color: "black" } ] }, pdf: { doc: null, width: 32, height: 297 }, templateData: null }; }, computed: { ...mapGetters("fairwayprofile", ["totalLength"]), ...mapState("fairwayprofile", [ "additionalSurvey", "currentProfile", "startPoint", "endPoint", "fairwayData", "maxAlt", "referenceWaterLevel", "selectedWaterLevel", "waterLevels" ]), ...mapState("application", ["paneSetup"]), ...mapState("user", ["user"]), title() { let dates = [this.selectedSurvey.date_info]; if (this.additionalSurvey) dates.push(this.additionalSurvey.date_info); dates.map(d => this.$options.filters.dateTime(d, true)); return `${this.$gettext("Fairwayprofile")}: ${ this.selectedBottleneck } (${dates.join(", ")})`; }, selectedSurvey: { get() { return this.$store.state.bottlenecks.selectedSurvey; } }, selectedBottleneck: { get() { return this.$store.state.bottlenecks.selectedBottleneck; } }, ...mapState("bottlenecks", ["selectedSurvey"]), relativeWaterLevelDelta() { return this.selectedWaterLevel.value - this.referenceWaterLevel; }, currentData() { if ( !this.selectedSurvey || !this.currentProfile.hasOwnProperty(this.selectedSurvey.date_info) ) return []; return this.currentProfile[this.selectedSurvey.date_info].points; }, additionalData() { if ( !this.additionalSurvey || !this.currentProfile.hasOwnProperty(this.additionalSurvey.date_info) ) return []; return this.currentProfile[this.additionalSurvey.date_info].points; }, xScale() { return [0, this.totalLength]; }, yScaleRight() { //ToDO calcReleativeDepth(this.maxAlt) to get the // maximum depth according to the actual waterlevel // additionally: take the one which is higher reference or current waterlevel const DELTA = this.maxAlt * 1.1 - this.maxAlt; return [this.maxAlt * 1 + DELTA, -DELTA]; } }, watch: { currentData() { this.drawDiagram(); }, additionalData() { this.drawDiagram(); }, width() { this.drawDiagram(); }, height() { this.drawDiagram(); }, waterLevels() { this.drawDiagram(); }, selectedWaterLevel() { this.drawDiagram(); }, fairwayData() { this.drawDiagram(); }, selectedBottleneck() { this.$store.commit("application/paneSetup", "DEFAULT"); } }, methods: { close() { this.$store.commit( "application/paneSetup", this.paneSetup === "COMPARESURVEYS_FAIRWAYPROFILE" ? "COMPARESURVEYS" : "DEFAULT" ); this.$store.dispatch("fairwayprofile/clearCurrentProfile"); }, 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() { if (this.templateData) { 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; // default values if some are missing in template let defaultFontSize = 11, 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 "diagramlegend": { this.addDiagramLegend( e.position, e.offset || defaultOffset, e.color || defaultTextColor ); break; } case "diagramtitle": { this.addDiagramTitle(e.position, e.offset, e.fontsize, e.color); break; } case "image": { this.addImage( e.url, e.format, e.position, e.offset || defaultOffset, e.width, e.height ); break; } case "text": { this.addText( e.position, e.offset || defaultOffset, e.width || defaultWidth, e.fontsize || defaultFontSize, e.color || defaultTextColor, e.text ); 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("Fairwayprofile diagram"); }, 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(); } // landscape format is used for both a3,a4 papersize if (!width) { width = this.form.paperSize === "a3" ? 380 : 290; } if (!height) { height = this.form.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); }, addDiagramTitle(position, offset, size, color) { let x = offset.x, y = offset.y; let fairwayInfo = this.selectedBottleneck + " (" + this.selectedSurvey.date_info + ")"; let width = (this.pdf.doc.getStringUnitWidth(fairwayInfo) * size) / (72 / 25.6) + size / 2; // 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(1); } this.pdf.doc.setTextColor(color); this.pdf.doc.setFontSize(size); this.pdf.doc.setFontStyle("bold"); this.pdf.doc.text(fairwayInfo, x, y, { baseline: "hanging" }); }, // Diagram legend addDiagramLegend(position, offset, color) { let x = offset.x, y = offset.y; let width = (this.pdf.doc.getStringUnitWidth("Ground") * 10) / (72 / 25.6) + 5; 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(3); } this.pdf.doc.setFontSize(10); this.pdf.doc.setTextColor(color); this.pdf.doc.setDrawColor("white"); this.pdf.doc.setFillColor("#5995ff"); this.pdf.doc.circle(x, y, 2, "FD"); this.pdf.doc.text(x + 3, y + 1, "Water"); this.pdf.doc.setFillColor("#1f4fff"); this.pdf.doc.circle(x, y + 5, 2, "FD"); this.pdf.doc.text(x + 3, y + 6, "Fairway"); this.pdf.doc.setFillColor("#4a2f06"); this.pdf.doc.circle(x, y + 10, 2, "FD"); this.pdf.doc.text(x + 3, y + 11, "Ground"); }, getTextHeight(numberOfLines) { return ( numberOfLines * ((this.pdf.doc.getFontSize() * 25.4) / 80) * this.pdf.doc.getLineHeightFactor() ); }, calcRelativeDepth(depth) { /* takes a depth value and substracts the delta of the relative waterlevel * say the reference level is above the current level, the ground is nearer, * thus, the depth is lower. * * E.g.: * * Reference waterlevel 5m, current 4m => delta = -1m * If the distance to the ground was 3m from the 5m mark * it is now only 2m from the current waterlevel. * * Vice versa: * * If the reference level is 5m and the current 6m => delta = +1m * The ground is one meter farer away from the current waterlevel * */ return depth - this.relativeWaterLevelDelta; }, drawDiagram() { d3.select(".diagram-container svg").remove(); this.scaleFairwayProfile(); let svg = d3.select(".diagram-container").append("svg"); svg.attr("width", "100%"); svg.attr("height", "100%"); 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, graph } = this.generateCoordinates( svg, height, width ); if (!this.height || !this.width) return; // do not try to render when height and width are unknown this.drawWaterlevel({ graph, xScale, yScaleRight, height }); this.drawLabels({ graph, height }); if (currentData) { this.drawProfile({ graph, xScale, yScaleRight, currentData, height, color: GROUND_COLOR, strokeColor: "black", opacity: 1 }); } if (additionalData) { this.drawProfile({ graph, xScale, yScaleRight, currentData: additionalData, height, color: GROUND_COLOR, strokeColor: "#943007", opacity: 0.6 }); } this.drawFairway({ graph, xScale, yScaleRight }); }, drawFairway({ graph, xScale, yScaleRight }) { if (this.fairwayData === undefined) { return; } for (let data of this.fairwayData) { const [startPoint, endPoint, depth] = data.coordinates[0]; const style = data.style(); 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("fill-opacity", 0.65) .attr("stroke", style[0].getStroke().getColor()) .attr("d", fairwayArea); } }, drawLabels({ graph, height }) { graph .append("text") .attr("transform", ["rotate(-90)"]) .attr("y", this.width - 70) .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", -50) .attr("x", -(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 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)) .selectAll(".tick text") .attr("fill", "black") .select(function() { return this.parentNode; }) .selectAll(".tick line") .attr("stroke", "black"); graph .append("g") .attr("transform", "translate(" + width + ",0)") .call(yAxis2) .selectAll(".tick text") .attr("fill", "black") .select(function() { return this.parentNode; }) .selectAll(".tick line") .attr("stroke", "black"); graph.selectAll(".domain").attr("stroke", "black"); return { xScale, 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-opacity", 0.65) .attr("fill", WATER_COLOR) .attr("stroke", WATER_COLOR) .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", 0) .attr("d", profileLine); } }, scaleFairwayProfile() { if (!document.querySelector(".diagram-container")) return; const clientHeight = document.querySelector(".diagram-container") .clientHeight; const clientWidth = document.querySelector(".diagram-container") .clientWidth; if (!clientHeight || !clientWidth) return; this.height = clientHeight; this.width = clientWidth; } }, 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}` }); }); }, updated() { this.drawDiagram(); }, destroyed() { window.removeEventListener("resize", debounce(this.drawDiagram)); } }; </script>