Mercurial > gemma
view client/src/components/fairway/Fairwayprofile.vue @ 5095:e21cbb9768a2
Prevent duplicate fairway areas
In principal, there can be only one or no fairway area at each point
on the map. Since polygons from real data will often be topologically
inexact, just disallow equal geometries. This will also help to
avoid importing duplicates with concurrent imports, once the history
of fairway dimensions will be preserved.
author | Tom Gottfried <tom@intevation.de> |
---|---|
date | Wed, 25 Mar 2020 18:10:02 +0100 |
parents | b65898de11ad |
children | de86a96d55c3 |
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" v-if="openLayersMap()"> <DiagramLegend> <div class="legend"> <span style="background-color: #5995ff; width: 20px; height: 20px;" ></span> <span class="fix-trans-space" style="display:inline;" v-translate >Water</span > </div> <div class="legend"> <span :style=" 'width: 16px; height: 16px; background:' + this.getLayerStyle(1).fillColor + '; border: dotted 2px ' + this.getLayerStyle(1).strokeColor + '; background-clip: padding-box; box-sizing: content-box;' " ></span> <span class="fix-trans-space" style="display:inline;" v-translate >Fairway (LOS 1)</span > </div> <div class="legend"> <span :style=" 'width: 16px; height: 16px; background:' + this.getLayerStyle(2).fillColor + '; border: dashed 2px ' + this.getLayerStyle(2).strokeColor + '; background-clip: padding-box; box-sizing: content-box;' " ></span> <span class="fix-trans-space" style="display:inline;" v-translate >Fairway (LOS 2)</span > </div> <div class="legend"> <span :style=" 'width: 16px; height: 16px; background:' + this.getLayerStyle(3).fillColor + '; border: solid 2px ' + this.getLayerStyle(3).strokeColor + '; background-clip: padding-box; box-sizing: content-box;' " ></span> <span class="fix-trans-space" style="display:inline;" v-translate >Fairway (LOS 3)</span > </div> <div class="legend"> <span style="width: 14px; height: 14px; background-color: #4a2f06; border: solid 3px black; background-clip: padding-box; box-sizing: content-box;" ></span> <span class="fix-trans-space" style="display:inline;" v-translate >Sediment</span > </div> <div class="legend"> <span style="width: 14px; height: 14px; background-color: rgba(74, 47, 6, 0.6); border: solid 3px #943007; background-clip: padding-box; box-sizing: content-box;" ></span> <span class="fix-trans-space" style="display:inline;" v-translate >Sediment (Compare)</span > </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> <a @click="downloadImage('fairwaypng', title)" id="fairwaypng" class="btn btn-sm btn-info text-white d-block w-100 mt-2" :download="`${fileName}.png`" > <translate>Export as Image</translate> </a> </div> </DiagramLegend> <div id="pdfContainer" class="d-flex flex-fill justify-content-center align-items-center diagram-container position-relative" > <div class="direction-indicator"></div> <div v-if="!fairwayData"> <translate>No data available.</translate> </div> <div v-if="!refWaterlevelValid"> <translate>No valid reference waterlevel data available.</translate> </div> <div v-if="!waterlevelValid"> <translate>No valid waterlevel data available.</translate> </div> </div> </div> </div> </template> <style lang="sass" scoped> .direction-indicator width: 70px height: 0 border-top: dashed 2px #333 position: absolute bottom: 50px left: 115px margin-left: -35px &::after content: "" width: 0 height: 0 border-width: 10px border-top-width: 5px border-bottom-width: 5px border-style: solid border-color: transparent border-left-color: #333 position: absolute right: -20px top: -6px </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, 2019 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 { mapState, mapGetters } from "vuex"; import debounce from "debounce"; import { diagram, pdfgen, templateLoader } from "@/lib/mixins"; import { HTTP } from "@/lib/http"; import { displayError } from "@/lib/errors"; import { defaultDiagramTemplate } from "@/lib/DefaultDiagramTemplate"; const GROUND_COLOR = "#4A2F06"; const WATER_COLOR = "#005DFF"; const isNumber = value => { if (typeof value !== "number") { return false; } if (value !== Number(value)) { return false; } if (Number.isFinite(value) === false) { return false; } return true; }; export default { mixins: [diagram, pdfgen, templateLoader], name: "fairwayprofile", components: { DiagramLegend: () => import("@/components/DiagramLegend") }, data() { return { resizeListenerFunction: null, width: null, height: null, form: { template: null }, templates: [], defaultTemplate: defaultDiagramTemplate, pdf: { doc: null, width: 32, height: 297 }, templateData: null }; }, computed: { ...mapGetters("map", ["openLayersMap"]), ...mapGetters("fairwayprofile", ["totalLength"]), ...mapState("fairwayprofile", [ "additionalSurvey", "currentProfile", "startPoint", "endPoint", "fairwayData", "minAlt", "maxAlt", "selectedWaterLevel", "depth", "useCustomDepth" ]), ...mapState("bottlenecks", ["selectedSurvey", "selectedBottleneck"]), ...mapState("application", ["paneSetup"]), title() { let dates = [this.selectedSurvey.date_info]; let waterlevelLabel = this.selectedWaterLevel === "ref" ? this.selectedSurvey.depth_reference : "Current"; if (this.additionalSurvey) dates.push(this.additionalSurvey.date_info); dates.map(d => this.$options.filters.dateTime(d, true)); const waterlevelMeasurement = this.waterlevelValid && this.refWaterlevelValid ? `${this.$options.filters.waterlevel(this.waterlevel)} m` : this.$gettext("No valid value available"); return `${this.$gettext("Fairwayprofile")}: ${ this.selectedBottleneck } (${dates.join( ", " )}) WL: ${waterlevelLabel} ( ${waterlevelMeasurement} )`; }, 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; }, bottleneck() { return this.openLayersMap() .getLayer("BOTTLENECKS") .getSource() .getFeatures() .find(f => f.get("objnam") === this.selectedBottleneck); }, waterlevel() { return this.selectedWaterLevel === "ref" ? this.refWaterlevel : this.bottleneck.get("gm_waterlevel"); }, refWaterlevel() { if (!this.selectedSurvey) return 0; return this.selectedSurvey.waterlevel_value; }, waterlevelValid() { return isNumber(this.waterlevel); }, refWaterlevelValid() { return isNumber(this.refWaterlevel); }, fileName() { return this.downloadFilename( this.$gettext("Fairwayprofile"), this.selectedBottleneck ); } }, watch: { depth() { if (!this.useCustomDepth) return; this.drawDiagram(); }, useCustomDepth() { this.drawDiagram(); }, 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: { addLegendToCanvas(ctx, { width, height }) { let x = width / 12, y = height - 55; ctx.font = "12px sans-serif"; ctx.textAlign = "start"; ctx.beginPath(); ctx.fillStyle = "#5995ff"; ctx.strokeStyle = "#5995ff"; ctx.arc(x, y, 8, 0, 2 * Math.PI); ctx.fill(); ctx.stroke(); ctx.fillStyle = "black"; ctx.fillText(this.$gettext("Water"), x + 14, y + 5); ctx.closePath(); ctx.beginPath(); ctx.fillStyle = this.getLayerStyle(1).fillColor; ctx.strokeStyle = this.getLayerStyle(1).strokeColor; ctx.arc(x, (y += 20), 8, 0, 2 * Math.PI); ctx.setLineDash([0.8], 0); ctx.fill(); ctx.stroke(); ctx.fillStyle = "black"; ctx.fillText(this.$gettext("Fairway (LOS 1)"), x + 14, y + 5); ctx.closePath(); ctx.beginPath(); ctx.fillStyle = this.getLayerStyle(2).fillColor; ctx.strokeStyle = this.getLayerStyle(2).strokeColor; ctx.arc(x, (y += 20), 8, 0, 2 * Math.PI); ctx.setLineDash([1.8], 0); ctx.fill(); ctx.stroke(); ctx.fillStyle = "black"; ctx.fillText(this.$gettext("Fairway (LOS 2)"), x + 14, y + 5); ctx.closePath(); ctx.beginPath(); ctx.fillStyle = this.getLayerStyle(3).fillColor; ctx.strokeStyle = this.getLayerStyle(3).strokeColor; ctx.arc(x, (y += 20), 8, 0, 2 * Math.PI); ctx.setLineDash([]); ctx.fill(); ctx.stroke(); ctx.fillStyle = "black"; ctx.fillText(this.$gettext("Fairway (LOS 3)"), x + 14, y + 5); ctx.closePath(); ctx.beginPath(); ctx.fillStyle = "#4a2e06"; ctx.strokeStyle = "black"; ctx.arc(x, (y += 20), 8, 0, 2 * Math.PI); ctx.setLineDash([]); ctx.fill(); ctx.stroke(); ctx.fillStyle = "black"; ctx.fillText(this.$gettext("Sediment"), x + 14, y + 5); ctx.closePath(); ctx.beginPath(); ctx.fillStyle = "rgba(74, 47, 6, 0.6)"; ctx.strokeStyle = "#943007"; ctx.arc(x, (y += 20), 8, 0, 2 * Math.PI); ctx.setLineDash([]); ctx.fill(); ctx.stroke(); ctx.fillStyle = "black"; ctx.fillText(this.$gettext("Sediment (compare)"), x + 14, y + 5); ctx.closePath(); }, close() { this.$store.commit( "application/paneSetup", this.paneSetup === "COMPARESURVEYS_FAIRWAYPROFILE" ? "COMPARESURVEYS" : "DEFAULT" ); this.$store.dispatch("fairwayprofile/clearCurrentProfile"); }, getLayerStyle(los) { let style = this.openLayersMap() .getLayer("FAIRWAYDIMENSIONSLOS" + los) .getStyle()()[0]; // use spread operator to clone arrays let fillColor = style.getFill().getColor(); let strokeColor = style.getStroke().getColor(); let strokeDash = style.getStroke().getLineDash(); return { fillColor, strokeColor, strokeDash }; }, applyChange() { if (this.form.template.hasOwnProperty("properties")) { this.templateData = this.defaultTemplate; return; } if (this.form.template) { this.loadTemplates("/templates/diagram/" + this.form.template.name) .then(response => { this.prepareImages(response.data.template_data.elements).then( values => { values.forEach(v => { response.data.template_data.elements[v.index].url = v.url; }); this.templateData = response.data.template_data; } ); }) .catch(error => { let message = "Backend not reachable"; if (error.response) { const { status, data } = error.response; message = `${status}: ${data.message || data}`; } displayError({ title: this.$gettext("Backend Error"), message: message }); }); } }, downloadPDF() { let fairwayInfo = this.selectedBottleneck + " (" + this.selectedSurvey.date_info + ")"; this.generatePDF({ templateData: this.templateData, diagramTitle: fairwayInfo }); this.pdf.doc.save(this.fileName + ".pdf"); }, // Diagram legend addDiagramLegend(position, offset, color) { let x = offset.x, y = offset.y; this.pdf.doc.setFontSize(10); let width = (this.pdf.doc.getStringUnitWidth("Sediment (Compare)") * 10) / (72 / 25.6) + 4; 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(8); } this.pdf.doc.setTextColor(color); this.pdf.doc.setDrawColor("#5995ff"); this.pdf.doc.setFillColor("#5995ff"); this.pdf.doc.circle(x, y, 2, "FD"); this.pdf.doc.text(x + 3, y + 1, this.$gettext("Water")); const toRGB = s => { let [, r, g, b] = s.match(/.*?(\d+), (\d+), (\d+), .*/); const toHex = n => { let val = parseInt(n).toString(16); if (val.length === 1) return `0${val}`; return val; }; return `#${toHex(r)}${toHex(g)}${toHex(b)}`; }; const los1Color = toRGB(this.getLayerStyle(1).strokeColor); const los1Fill = toRGB(this.getLayerStyle(1).fillColor); const los2Color = toRGB(this.getLayerStyle(2).strokeColor); const los2Fill = toRGB(this.getLayerStyle(2).fillColor); const los3Color = toRGB(this.getLayerStyle(3).strokeColor); const los3Fill = toRGB(this.getLayerStyle(3).fillColor); this.pdf.doc.setLineDashPattern([0.8], 0); this.pdf.doc.setDrawColor(los1Color); this.pdf.doc.setFillColor(los1Fill); this.pdf.doc.circle(x, y + 5, 2, "FD"); this.pdf.doc.text(x + 3, y + 6, this.$gettext("Fairway (LOS 1)")); this.pdf.doc.setLineDashPattern([1.8], 0); this.pdf.doc.setFillColor(los2Fill); this.pdf.doc.setDrawColor(los2Color); this.pdf.doc.circle(x, y + 10, 2, "FD"); this.pdf.doc.text(x + 3, y + 11, this.$gettext("Fairway (LOS 2)")); this.pdf.doc.setLineDashPattern([], 0); this.pdf.doc.setFillColor(los3Fill); this.pdf.doc.setDrawColor(los3Color); this.pdf.doc.circle(x, y + 15, 2, "FD"); this.pdf.doc.text(x + 3, y + 16, this.$gettext("Fairway (LOS 3)")); this.pdf.doc.setDrawColor("black"); this.pdf.doc.setFillColor("#4a2e06"); this.pdf.doc.circle(x, y + 20, 2, "FD"); this.pdf.doc.text(x + 3, y + 21, this.$gettext("Sediment")); this.pdf.doc.setDrawColor("#943007"); this.pdf.doc.setFillColor("#928269"); this.pdf.doc.circle(x, y + 25, 2, "FD"); this.pdf.doc.text(x + 3, y + 26, this.$gettext("Sediment (Compare)")); }, getPrintLayout(svgHeight, svgWidth) { return { main: { top: Math.floor(0.08 * svgHeight), right: Math.floor(0.08 * svgWidth), bottom: Math.floor(0.2 * svgHeight), left: Math.floor(0.08 * svgWidth) } }; }, drawDiagram() { d3.select(".diagram-container svg").remove(); this.scaleFairwayProfile(); if (!this.height || !this.width) return; // do not try to render when height and width are unknown const layout = this.getPrintLayout(this.height, this.width); this.renderTo({ element: ".diagram-container", dimensions: this.getDimensions({ svgWidth: this.width, svgHeight: this.height, ...layout }) }); }, renderTo({ element, dimensions }) { if (!this.waterlevelValid || !this.refWaterlevelValid) return; let svg = d3.select(element).append("svg"); svg.attr("width", "100%"); svg.attr("height", "100%"); svg .append("g") .append("rect") .attr("width", "100%") .attr("height", "100%") .attr("fill", "#ffffff"); const width = dimensions.width; const height = dimensions.mainHeight; const offsetY = 15; const currentData = this.currentData; const additionalData = this.additionalData; const { xScale, yScaleRight, graph } = this.generateScalesAndGraph({ svg, height, width, dimensions, offsetY }); this.drawWaterlevel({ graph, xScale, yScaleRight, height, offsetY }); this.drawLabels({ graph, dimensions }); if (currentData) { this.drawProfile({ graph, xScale, yScaleRight, currentData, height, color: GROUND_COLOR, strokeColor: "black", opacity: 1, offsetY }); } if (additionalData) { this.drawProfile({ graph, xScale, yScaleRight, currentData: additionalData, height, color: GROUND_COLOR, strokeColor: "#943007", opacity: 0.6, offsetY }); } this.drawFairway({ graph, xScale, yScaleRight, offsetY }); }, /** * Draws Fairway rectangle * * start end * ____________ * [___________] customDepth | referenceDepth * * Starting point is the 0 line of the diagram * */ drawFairway({ graph, xScale, yScaleRight, offsetY }) { if (this.fairwayData === undefined) { return; } for (let data of this.fairwayData) { data.coordinates.forEach(coordinates => { const [startPoint, endPoint, depth] = coordinates; const referenceDepth = this.maxAlt * 1.1 + (this.waterlevel - this.refWaterlevel) / 100; let customdepth = this.depth < referenceDepth ? this.depth : referenceDepth; let fairwayArea = d3 .area() .x(function(d) { return xScale(d.x); }) .y0(yScaleRight(0)) .y1(function(d) { return yScaleRight(d.y); }); let strokColor = this.getLayerStyle(data.los).strokeColor; // Convert stroke value to rgb() and opacity to pass them separately let [r, g, b, opacity] = strokColor .substring(5, strokColor.length - 1) .split(","); let rgb = `rgb(${r}, ${g}, ${b})`; graph .append("path") .datum([ { x: startPoint, y: this.useCustomDepth ? customdepth : depth }, { x: endPoint, y: this.useCustomDepth ? customdepth : depth } ]) .attr("fill", `${this.getLayerStyle(data.los).fillColor}`) .attr("stroke", rgb) .attr("stroke-opacity", opacity) .attr("stroke-dasharray", this.getLayerStyle(data.los).strokeDash) .attr("d", fairwayArea) .attr("transform", `translate(0 ${-offsetY})`); }); } }, drawLabels({ graph, dimensions }) { graph .append("text") .attr("transform", ["rotate(-90)"]) .attr("y", dimensions.width + Math.floor(0.06 * dimensions.width)) .attr("x", -dimensions.mainHeight / 2) .attr("fill", "black") .style("text-anchor", "middle") .text(this.$gettext("Depth [m]")); graph .append("text") .attr("transform", ["rotate(-90)"]) .attr("y", -1 * Math.floor(0.065 * dimensions.width)) .attr("x", -dimensions.mainHeight / 2) .attr("fill", "black") .style("text-anchor", "middle") .text(this.$gettext("Waterlevel [m]")); graph .append("text") .attr("y", 0) .attr("x", 0) .attr("dy", "1em") .attr("fill", "black") .style("text-anchor", "middle") .attr("transform", [ `translate(${dimensions.width / 2} ${dimensions.mainHeight})`, "rotate(0)" ]) .text(this.$gettext("Width [m]")); }, generateScalesAndGraph({ svg, height, width, dimensions, offsetY }) { let xScale = d3 .scaleLinear() .domain([0, this.totalLength]) .rangeRound([0, width]); // Upper limit is relevant for the extension of the y-Axis // If there is no measure above the waterlevel we choose a cosmetic value of this.maxAlt *0.1 // to get a bit of space above the waterlevel. // Otherwise we take the maximum measured value const upperLimit = this.minAlt > 0 ? this.maxAlt * 0.1 : Math.abs(this.minAlt); // In order to draw positive values downwards, we switch both values in the // domain definition [min, max] => [max, min] has the desired effect. let yScaleRight = d3 .scaleLinear() .domain([ this.maxAlt * 1.1 + (this.waterlevel - this.refWaterlevel) / 100, -upperLimit ]) .rangeRound([height, 0]); // This definition is accordingly but uses values * 100 for the tickformatter let yScaleLeft = d3 .scaleLinear() .domain([ this.waterlevel - (this.maxAlt * 110 + (this.waterlevel - this.refWaterlevel)), this.waterlevel + upperLimit * 100 ]) .rangeRound([height, 0]); let xAxis = d3 .axisBottom(xScale) .tickSizeOuter(0) .ticks(5); let yAxisRight = d3 .axisRight() .tickSizeOuter(0) .tickSizeInner(5) .scale(yScaleRight); let yAxisLeft = d3 .axisLeft() .tickSizeOuter(0) .tickSizeInner(5) .scale(yScaleLeft) .tickFormat(d => this.$options.filters.waterlevel(d)); let graph = svg .append("g") .attr( "transform", "translate(" + dimensions.mainMargin.left + "," + dimensions.mainMargin.top + ")" ); graph .append("g") .attr("transform", `translate(0 ${height - offsetY})`) .call(xAxis) .selectAll(".tick text") .attr("fill", "black") .select(function() { return this.parentNode; }) .selectAll(".tick line") .attr("stroke", "black"); graph .append("g") .attr("transform", `translate(${width} ${-offsetY})`) .call(yAxisRight) .selectAll(".tick text") .attr("fill", "black") .select(function() { return this.parentNode; }) .selectAll(".tick line") .attr("stroke", "black"); graph .append("g") .attr("transform", `translate(0 ${-offsetY})`) .call(yAxisLeft) .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 }; }, /** * Draws a rectangle for the waterlevel * (0,0) (totalLength,0) * ____________ * [____________] * (0,height) (totalLength, height) */ drawWaterlevel({ graph, xScale, yScaleRight, height, offsetY }) { 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", "transparent") .attr("d", waterArea) .attr("transform", `translate(0 ${-offsetY})`); }, /** * * Draws the ground level * * (x, y + ΔWaterlevel) * * ΔWaterlevel is the difference between the current waterlevel and the reference level * * Is the current level higher as the reference level ΔWaterlevel is positive, which in * turn means that the distance between the surface and the ground is increased. * * Is the current level below the reference level ΔWaterlevel is negative, which in turn means * that the distance between the surface and the ground is decreased. * */ drawProfile({ graph, xScale, yScaleRight, currentData, height, color, strokeColor, opacity, offsetY }) { for (let part of currentData) { let profileLine = d3 .line() .x(d => { return xScale(d.x); }) .y(d => yScaleRight(d.y + (this.waterlevel - this.refWaterlevel) / 100) ); let profileArea = d3 .area() .x(function(d) { return xScale(d.x); }) .y0(height) .y1(d => yScaleRight(d.y + (this.waterlevel - this.refWaterlevel) / 100) ); graph .append("path") .datum(part) .attr("fill", color) .attr("stroke", "transparent") .attr("fill-opacity", opacity) .attr("d", profileArea) .attr("transform", `translate(0 ${-offsetY})`); 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) .attr("transform", `translate(0 ${-offsetY})`); } }, 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() { this.resizeListenerFunction = debounce(this.drawDiagram, 100); window.addEventListener("resize", this.resizeListenerFunction); }, mounted() { // Nasty but necessary if we don't want to use the updated hook to re-draw // the diagram because this would re-draw it also for irrelevant reasons. // In this case we need to wait for the child component (DiagramLegend) to // render. According to the docs (https://vuejs.org/v2/api/#mounted) this // should be possible with $nextTick() but it doesn't work because it does // not guarantee that the DOM is not only updated but also re-painted on the // screen. setTimeout(this.drawDiagram, 150); 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(error => { let message = "Backend not reachable"; if (error.response) { const { status, data } = error.response; message = `${status}: ${data.message || data}`; } displayError({ title: this.$gettext("Backend Error"), message: message }); }); }, updated() { this.drawDiagram(); }, destroyed() { window.removeEventListener("resize", this.resizeListenerFunction); } }; </script>