Mercurial > gemma
changeset 3991:06096a7ce1c5
merge with diagram-cleanup
author | Thomas Junk <thomas.junk@intevation.de> |
---|---|
date | Wed, 17 Jul 2019 14:18:43 +0200 |
parents | beaa75082c09 (current diff) d76321bc22d8 (diff) |
children | 2f024d6189ca |
files | |
diffstat | 7 files changed, 216 insertions(+), 652 deletions(-) [+] |
line wrap: on
line diff
--- a/client/src/components/fairway/AvailableFairwayDepth.vue Wed Jul 17 14:13:51 2019 +0200 +++ b/client/src/components/fairway/AvailableFairwayDepth.vue Wed Jul 17 14:18:43 2019 +0200 @@ -1,7 +1,6 @@ <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"> @@ -44,16 +43,17 @@ </div> </DiagramLegend> <div - ref="diagramContainer" + class="d-flex flex-fill justify-content-center align-items-center" :id="containerId" - class="diagram-container flex-fill" - ></div> + > + <div v-if="!fwData.length"> + <translate>No data available.</translate> + </div> + </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. @@ -71,86 +71,30 @@ * * Fadi Abbud <fadi.abbud@intevation.de> */ import * as d3 from "d3"; -import app from "@/main"; -import debounce from "debounce"; import { mapState } from "vuex"; -import filters from "@/lib/filters.js"; import { diagram, pdfgen, templateLoader } from "@/lib/mixins"; -import { HTTP } from "@/lib/http"; -import { displayError } from "@/lib/errors"; import { FREQUENCIES } from "@/store/fairwayavailability"; -import { defaultDiagramTemplate } from "@/lib/DefaultDiagramTemplate"; +import filters from "@/lib/filters.js"; +// hoursInDays is a const instead of component method because it's used where +// the component context is not available const hoursInDays = x => x / 24; +const COLORS = { + LDC: "#cdcdcd", + HIGHEST: "#3675ff", + REST: ["#782121", "#ff6c6c", "#ffaaaa"] +}; export default { mixins: [diagram, pdfgen, templateLoader], - components: { - DiagramLegend: () => import("@/components/DiagramLegend") - }, data() { return { containerId: "availablefairwaydepth-diagram-container", - resizeListenerFunction: null, - loading: false, scalePaddingLeft: 60, scalePaddingRight: 0, - paddingTop: 25, - pdf: { - doc: null, - width: null, - height: null - }, - form: { - template: null - }, - templateData: null, - templates: [], - defaultTemplate: defaultDiagramTemplate + paddingTop: 25 }; }, - created() { - this.resizeListenerFunction = debounce(this.drawDiagram, 100); - window.addEventListener("resize", this.resizeListenerFunction); - }, - destroyed() { - window.removeEventListener("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(e => { - const { status, data } = e.response; - displayError({ - title: this.$gettext("Backend Error"), - message: `${status}: ${data.message || data}` - }); - }); - }, computed: { ...mapState("fairwayavailability", [ "selectedFairwayAvailabilityFeature", @@ -216,40 +160,6 @@ } }, methods: { - 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(e => { - const { status, data } = e.response; - displayError({ - title: this.$gettext("Backend Error"), - message: `${status}: ${data.message || data}` - }); - }); - } - }, - downloadPDF() { - let title = `Available Fairway Depth: ${this.featureName}`; - this.generatePDF({ - templateData: this.templateData, - diagramTitle: title - }); - this.pdf.doc.save(`Available Fairway Depth: ${this.featureName}`); - }, addDiagramLegend(position, offset, color) { let x = offset.x, y = offset.y; @@ -266,60 +176,54 @@ } this.pdf.doc.setTextColor(color); - this.pdf.doc.setDrawColor(this.$options.COLORS.LDC); - this.pdf.doc.setFillColor(this.$options.COLORS.LDC); + this.pdf.doc.setDrawColor(COLORS.LDC); + this.pdf.doc.setFillColor(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.setDrawColor(COLORS.REST[0]); + this.pdf.doc.setFillColor(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.setDrawColor(COLORS.REST[1]); + this.pdf.doc.setFillColor(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.setDrawColor(COLORS.HIGHEST); + this.pdf.doc.setFillColor(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) { return [ - `background-color: ${this.$options.COLORS.LDC};`, - `background-color: ${this.$options.COLORS.HIGHEST};`, - `background-color: ${this.$options.COLORS.REST[1]};`, - `background-color: ${this.$options.COLORS.REST[0]};` + `background-color: ${COLORS.LDC};`, + `background-color: ${COLORS.HIGHEST};`, + `background-color: ${COLORS.REST[1]};`, + `background-color: ${COLORS.REST[0]};` ][index]; }, close() { this.$store.commit("application/paneSetup", "DEFAULT"); }, - getPrintLayout(svgHeight) { + getPrintLayout() { return { - main: { top: 0, right: 20, bottom: 50, left: 20 }, - nav: { - top: svgHeight - 65, - right: 20, - bottom: 30, - left: 80 - } + main: { top: 0, right: 20, bottom: 50, left: 20 } }; }, drawDiagram() { const elem = document.querySelector("#" + this.containerId); const svgWidth = elem != null ? elem.clientWidth : 0; const svgHeight = elem != null ? elem.clientHeight : 0; - const layout = this.getPrintLayout(svgHeight); + const layout = this.getPrintLayout(); const dimensions = this.getDimensions({ svgHeight, svgWidth, ...layout }); - d3.select(".diagram-container svg").remove(); - this.renderTo({ element: ".diagram-container", dimensions }); + d3.select("#" + this.containerId + " svg").remove(); + this.renderTo({ element: "#" + this.containerId, dimensions }); }, renderTo({ element, dimensions }) { const diagram = d3 @@ -440,7 +344,7 @@ .attr("x", ldcOffset + spaceBetween / 2) .attr("width", widthPerItem - ldcOffset - spaceBetween) .attr("fill", (d, i) => { - return this.$options.COLORS.REST[i]; + return COLORS.REST[i]; }); }, fnheight({ name, yScale }) { @@ -479,7 +383,7 @@ "transform", d => `translate(0 ${this.paddingTop + -1 * height(d)})` ) - .attr("fill", this.$options.COLORS.LDC) + .attr("fill", COLORS.LDC) .attr("id", "ldc"); }, drawHighestLevel({ @@ -523,7 +427,7 @@ "transform", d => `translate(0 ${this.paddingTop + -1 * height(d)})` ) - .attr("fill", this.$options.COLORS.HIGHEST); + .attr("fill", COLORS.HIGHEST); }, drawLabelPerBar({ everyBar, dimensions, widthPerItem }) { everyBar @@ -537,7 +441,7 @@ drawScaleLabel({ diagram, dimensions }) { diagram .append("text") - .text(this.$options.LEGEND) + .text(this.$gettext("Sum of days")) .attr("text-anchor", "middle") .attr("x", 0) .attr("y", 0) @@ -615,12 +519,6 @@ fwData() { this.drawDiagram(); } - }, - LEGEND: app.$gettext("Sum of days"), - COLORS: { - LDC: "#cdcdcd", - HIGHEST: "#3675ff", - REST: ["#782121", "#ff6c6c", "#ffaaaa"] } }; </script>
--- a/client/src/components/fairway/AvailableFairwayDepthLNWL.vue Wed Jul 17 14:13:51 2019 +0200 +++ b/client/src/components/fairway/AvailableFairwayDepthLNWL.vue Wed Jul 17 14:18:43 2019 +0200 @@ -1,7 +1,6 @@ <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 legendLNWL" :key="index" class="legend"> @@ -44,16 +43,17 @@ </div> </DiagramLegend> <div - ref="diagramContainer" + class="d-flex flex-fill justify-content-center align-items-center" :id="containerId" - class="diagram-container flex-fill" - ></div> + > + <div v-if="!fwLNWLData.length"> + <translate>No data available.</translate> + </div> + </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. @@ -71,83 +71,24 @@ * * Fadi Abbud <fadi.abbud@intevation.de> */ import * as d3 from "d3"; -import app from "@/main"; -import debounce from "debounce"; import { mapState } from "vuex"; +import { diagram, pdfgen, templateLoader } from "@/lib/mixins"; import filters from "@/lib/filters.js"; -import { diagram, pdfgen, templateLoader } from "@/lib/mixins"; -import { HTTP } from "@/lib/http"; -import { displayError } from "@/lib/errors"; -import { defaultDiagramTemplate } from "@/lib/DefaultDiagramTemplate"; export default { mixins: [diagram, pdfgen, templateLoader], - components: { - DiagramLegend: () => import("@/components/DiagramLegend") - }, data() { return { containerId: "availablefairwaydepthlnwl-diagram-container", - resizeListenerFunction: null, - loading: false, scalePaddingLeft: 60, scalePaddingRight: 0, paddingTop: 25, - pdf: { - doc: null, - width: null, - height: null - }, - form: { - template: null - }, - templateData: null, - templates: [], - defaultTemplate: defaultDiagramTemplate + colors: { + afd: ["#3636ff", "#f49b7f", "#e15472"], + lnwl: "#97ddf3" + } }; }, - created() { - this.resizeListenerFunction = debounce(this.drawDiagram, 100); - window.addEventListener("resize", this.resizeListenerFunction); - }, - destroyed() { - window.removeEventListener("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(e => { - const { status, data } = e.response; - displayError({ - title: this.$gettext("Backend Error"), - message: `${status}: ${data.message || data}` - }); - }); - }, computed: { ...mapState("fairwayavailability", [ "selectedFairwayAvailabilityFeature", @@ -207,47 +148,13 @@ methods: { legendStyle(index) { const style = { - 0: `background-color: ${this.$options.LWNLCOLORS.LDC};`, - 1: `background-color: ${this.$options.AFDCOLORS[2]};`, - 2: `background-color: ${this.$options.AFDCOLORS[1]};`, - 3: `background-color: ${this.$options.AFDCOLORS[0]};` + 0: `background-color: ${this.colors.lnwl};`, + 1: `background-color: ${this.colors.afd[2]};`, + 2: `background-color: ${this.colors.afd[1]};`, + 3: `background-color: ${this.colors.afd[0]};` }; return style[index]; }, - 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(e => { - const { status, data } = e.response; - displayError({ - title: this.$gettext("Backend Error"), - message: `${status}: ${data.message || data}` - }); - }); - } - }, - downloadPDF() { - let title = `Available Fairway Depth vs LNWL: ${this.featureName}`; - this.generatePDF({ - templateData: this.templateData, - diagramTitle: title - }); - this.pdf.doc.save(`Available Fairway Depth LNWL: ${this.featureName}`); - }, addDiagramLegend(position, offset, color) { let x = offset.x, y = offset.y; @@ -263,52 +170,46 @@ y = this.pdf.height - offset.y - this.getTextHeight(6); } this.pdf.doc.setTextColor(color); - this.pdf.doc.setDrawColor(this.$options.LWNLCOLORS.LDC); - this.pdf.doc.setFillColor(this.$options.LWNLCOLORS.LDC); + this.pdf.doc.setDrawColor(this.colors.lnwl); + this.pdf.doc.setFillColor(this.colors.lnwl); this.pdf.doc.roundedRect(x, y, 10, 4, 1.5, 1.5, "FD"); this.pdf.doc.text(this.legendLNWL[0], x + 12, y + 3); - this.pdf.doc.setDrawColor(this.$options.AFDCOLORS[2]); - this.pdf.doc.setFillColor(this.$options.AFDCOLORS[2]); + this.pdf.doc.setDrawColor(this.colors.afd[2]); + this.pdf.doc.setFillColor(this.colors.afd[2]); this.pdf.doc.roundedRect(x, y + 5, 10, 4, 1.5, 1.5, "FD"); this.pdf.doc.text(this.legendLNWL[1], x + 12, y + 8); - this.pdf.doc.setDrawColor(this.$options.AFDCOLORS[1]); - this.pdf.doc.setFillColor(this.$options.AFDCOLORS[1]); + this.pdf.doc.setDrawColor(this.colors.afd[1]); + this.pdf.doc.setFillColor(this.colors.afd[1]); this.pdf.doc.roundedRect(x, y + 10, 10, 4, 1.5, 1.5, "FD"); this.pdf.doc.text(this.legendLNWL[2], x + 12, y + 13); - this.pdf.doc.setDrawColor(this.$options.AFDCOLORS[0]); - this.pdf.doc.setFillColor(this.$options.AFDCOLORS[0]); + this.pdf.doc.setDrawColor(this.colors.afd[0]); + this.pdf.doc.setFillColor(this.colors.afd[0]); this.pdf.doc.roundedRect(x, y + 15, 10, 4, 1.5, 1.5, "FD"); this.pdf.doc.text(this.legendLNWL[3], x + 12, y + 18); }, close() { this.$store.commit("application/paneSetup", "DEFAULT"); }, - getPrintLayout(svgHeight) { + getPrintLayout() { return { - main: { top: 0, right: 20, bottom: 50, left: 20 }, - nav: { - top: svgHeight - 65, - right: 20, - bottom: 30, - left: 80 - } + main: { top: 0, right: 20, bottom: 50, left: 20 } }; }, drawDiagram() { const elem = document.querySelector("#" + this.containerId); const svgWidth = elem != null ? elem.clientWidth : 0; const svgHeight = elem != null ? elem.clientHeight : 0; - const layout = this.getPrintLayout(svgHeight); + const layout = this.getPrintLayout(); const dimensions = this.getDimensions({ svgHeight, svgWidth, ...layout }); - d3.select(".diagram-container svg").remove(); - this.renderTo({ element: ".diagram-container", dimensions }); + d3.select("#" + this.containerId + " svg").remove(); + this.renderTo({ element: "#" + this.containerId, dimensions }); }, drawTooltip(diagram) { diagram @@ -442,7 +343,7 @@ .attr("transform", `translate(0 ${this.paddingTop})`) .attr("width", afdWidth) .attr("fill", (d, i) => { - return this.$options.AFDCOLORS[i]; + return this.colors.afd[i]; }); }, drawLNWL(data, i, diagram, spaceBetween, widthPerItem, ldcWidth, yScale) { @@ -486,13 +387,13 @@ .attr("transform", `translate(0 ${this.paddingTop})`) .attr("width", ldcWidth) .attr("fill", () => { - return this.$options.LWNLCOLORS.LDC; + return this.colors.lnwl; }); }, drawScaleLabel({ diagram, dimensions }) { diagram .append("text") - .text(this.$options.LEGEND) + .text(this.$gettext("Percent")) .attr("text-anchor", "middle") .attr("x", 0) .attr("y", 0) @@ -571,12 +472,6 @@ fwLNWLData() { this.drawDiagram(); } - }, - LEGEND: app.$gettext("Percent"), - AFDCOLORS: ["#3636ff", "#f49b7f", "#e15472"], - LWNLCOLORS: { - LDC: "#97ddf3", - HDC: "#43FFE1" } }; </script>
--- a/client/src/components/fairway/Fairwayprofile.vue Wed Jul 17 14:13:51 2019 +0200 +++ b/client/src/components/fairway/Fairwayprofile.vue Wed Jul 17 14:18:43 2019 +0200 @@ -93,12 +93,11 @@ </div> </DiagramLegend> <div - ref="pdfContainer" - id="pdfContainer" - class="d-flex flex-fill justify-content-center align-items-center diagram-container position-relative" + class="d-flex flex-fill justify-content-center align-items-center position-relative" + :id="containerId" > <div class="direction-indicator"></div> - <div v-if="!fairwayData"> + <div v-if="!fairwayData.length"> <translate>No data available.</translate> </div> </div> @@ -148,37 +147,16 @@ */ 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"; 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 + containerId: "fairwayprofile-diagram-container" }; }, computed: { @@ -293,46 +271,6 @@ return { fillColor, fillOpacity, strokeColor, strokeOpacity, 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(e => { - const { status, data } = e.response; - displayError({ - title: this.$gettext("Backend Error"), - message: `${status}: ${data.message || data}` - }); - }); - } - }, - downloadPDF() { - let fairwayInfo = - this.selectedBottleneck + " (" + this.selectedSurvey.date_info + ")"; - - this.generatePDF({ - templateData: this.templateData, - diagramTitle: fairwayInfo - }); - - this.pdf.doc.save( - this.title.replace(/\s/g, "_").replace(/[():,]/g, "") + ".pdf" - ); - }, - // Diagram legend addDiagramLegend(position, offset, color) { let x = offset.x, @@ -383,24 +321,21 @@ }, getPrintLayout() { return { - main: { - top: 20, - right: 80, - bottom: 60, - left: 80 - } + main: { top: 20, right: 80, bottom: 60, left: 80 } }; }, 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); + // remove old diagram and exit if necessary data is missing + d3.select("#" + this.containerId + " svg").remove(); + const elem = document.querySelector("#" + this.containerId); + const svgWidth = elem.clientWidth; + const svgHeight = elem.clientHeight; + const layout = this.getPrintLayout(); this.renderTo({ - element: ".diagram-container", + element: "#" + this.containerId, dimensions: this.getDimensions({ - svgWidth: this.width, - svgHeight: this.height, + svgWidth: svgWidth, + svgHeight: svgHeight, ...layout }) }); @@ -672,62 +607,7 @@ .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() { - 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(e => { - const { status, data } = e.response; - displayError({ - title: this.$gettext("Backend Error"), - message: `${status}: ${data.message || data}` - }); - }); - }, - updated() { - this.drawDiagram(); - }, - destroyed() { - window.removeEventListener("resize", this.resizeListenerFunction); } }; </script>
--- a/client/src/components/gauge/HydrologicalConditions.vue Wed Jul 17 14:13:51 2019 +0200 +++ b/client/src/components/gauge/HydrologicalConditions.vue Wed Jul 17 14:18:43 2019 +0200 @@ -1,13 +1,8 @@ <template> <div class="d-flex flex-column flex-fill"> - <UIBoxHeader - icon="ruler-vertical" - :title="title" - :closeCallback="close" - class="rounded-0" - /> + <UIBoxHeader icon="ruler-vertical" :title="title" :closeCallback="close" /> <div class="d-flex flex-fill"> - <DiagramLegend id="diagramlegendId"> + <DiagramLegend> <div class="legend"> <span style="background-color: red; width: 20px; height: 20px;" @@ -102,34 +97,14 @@ import { mapState, mapGetters } from "vuex"; import * as d3 from "d3"; -import debounce from "debounce"; import { startOfYear, endOfYear } from "date-fns"; import { diagram, pdfgen, templateLoader } from "@/lib/mixins"; -import { HTTP } from "@/lib/http"; -import { displayError } from "@/lib/errors"; -import { defaultDiagramTemplate } from "@/lib/DefaultDiagramTemplate"; export default { mixins: [diagram, pdfgen, templateLoader], - components: { - DiagramLegend: () => import("@/components/DiagramLegend") - }, data() { return { - containerId: "hydrologicalconditions-diagram-container", - resizeListenerFunction: null, - templateData: null, - form: { - template: null, - form: null - }, - templates: [], - defaultTemplate: defaultDiagramTemplate, - pdf: { - doc: null, - width: 420, - height: 297 - } + containerId: "hydrologicalconditions-diagram-container" }; }, computed: { @@ -197,48 +172,6 @@ : "DEFAULT" ); }, - downloadPDF() { - let diagramTitle = - this.gaugeInfo(this.selectedGauge) + - ": Hydrological Conditions " + - this.longtermInterval.join(" - "); - - this.generatePDF({ - templateData: this.templateData, - diagramTitle: diagramTitle - }); - - this.pdf.doc.save( - this.selectedGauge.properties.objname + - " Hydrological-condition Diagram.pdf" - ); - }, - 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(e => { - const { status, data } = e.response; - displayError({ - title: this.$gettext("Backend Error"), - message: `${status}: ${data.message || data}` - }); - }); - } - }, // Diagram legend addDiagramLegend(position, offset, color) { let x = offset.x, @@ -969,48 +902,6 @@ .attr("height", textBBox.height + tooltipPadding * 2); }); } - }, - 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(e => { - const { status, data } = e.response; - displayError({ - title: this.$gettext("Backend Error"), - message: `${status}: ${data.message || data}` - }); - }); - }, - destroyed() { - window.removeEventListener("resize", this.resizeListenerFunction); } }; </script>
--- a/client/src/components/gauge/Waterlevel.vue Wed Jul 17 14:13:51 2019 +0200 +++ b/client/src/components/gauge/Waterlevel.vue Wed Jul 17 14:18:43 2019 +0200 @@ -1,13 +1,8 @@ <template> <div class="d-flex flex-column flex-fill"> - <UIBoxHeader - icon="ruler-vertical" - :title="title" - :closeCallback="close" - class="rounded-0" - /> + <UIBoxHeader icon="ruler-vertical" :title="title" :closeCallback="close" /> <div class="d-flex flex-fill"> - <DiagramLegend id="diagramlegendId"> + <DiagramLegend> <div class="legend"> <span style="background-color: steelblue; width: 20px; height: 20px;" @@ -99,17 +94,12 @@ * * Markus Kottländer <markus.kottlaender@intevation.de> * * Fadi Abbud <fadi.abbud@intevation.de> */ - import { mapState, mapGetters } from "vuex"; import * as d3Base from "d3"; import { lineChunked } from "d3-line-chunked"; import { endOfDay } from "date-fns"; -import debounce from "debounce"; import { saveAs } from "file-saver"; import { diagram, pdfgen, templateLoader } from "@/lib/mixins"; -import { HTTP } from "@/lib/http"; -import { displayError } from "@/lib/errors"; -import { defaultDiagramTemplate } from "@/lib/DefaultDiagramTemplate"; // we should load only d3 modules we need but for now we'll go with the lazy way // https://www.giacomodebidda.com/how-to-import-d3-plugins-with-webpack/ // d3-line-chunked plugin: https://github.com/pbeshai/d3-line-chunked @@ -117,24 +107,9 @@ export default { mixins: [diagram, pdfgen, templateLoader], - components: { - DiagramLegend: () => import("@/components/DiagramLegend") - }, data() { return { - containerId: "waterlevel-diagram-container", - resizeListenerFunction: null, - form: { - template: null - }, - templates: [], - defaultTemplate: defaultDiagramTemplate, - pdf: { - doc: null, - width: 420, - height: 297 - }, - templateData: null + containerId: "waterlevel-diagram-container" }; }, computed: { @@ -202,49 +177,6 @@ this.selectedGauge.properties.objname + "-waterlevel-diagram.svg"; saveAs(blog, filename); }, - downloadPDF() { - let diagramTitle = - this.gaugeInfo(this.selectedGauge) + - ": Waterlevel " + - this.dateFrom.toLocaleDateString() + - " - " + - this.dateTo.toLocaleDateString(); - - this.generatePDF({ - templateData: this.templateData, - diagramTitle: diagramTitle - }); - - this.pdf.doc.save( - this.selectedGauge.properties.objname + " Waterlevel-Diagram.pdf" - ); - }, - 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(e => { - const { status, data } = e.response; - displayError({ - title: this.$gettext("Backend Error"), - message: `${status}: ${data.message || data}` - }); - }); - } - }, // Diagram legend addDiagramLegend(position, offset, color) { let x = offset.x; @@ -950,8 +882,8 @@ .append("rect") .attr("rx", "0.25em") .attr("ry", "0.25em") - .attr("width", "0px") - .attr("height", "0px"); + .attr("width", "0") + .attr("height", "0"); // create container for multiple text rows const tooltipText = tooltip.append("text").attr("text-anchor", "middle"); @@ -1089,48 +1021,6 @@ return difference === 900; }; } - }, - 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(e => { - const { status, data } = e.response; - displayError({ - title: this.$gettext("Backend Error"), - message: `${status}: ${data.message || data}` - }); - }); - }, - destroyed() { - window.removeEventListener("resize", this.resizeListenerFunction); } }; </script>
--- a/client/src/lib/DefaultDiagramTemplate.js Wed Jul 17 14:13:51 2019 +0200 +++ b/client/src/lib/DefaultDiagramTemplate.js Wed Jul 17 14:18:43 2019 +0200 @@ -1,4 +1,4 @@ -const defaultDiagramTemplate = { +export default { name: "Default", properties: { paperSize: "a4" @@ -26,5 +26,3 @@ } ] }; - -export { defaultDiagramTemplate };
--- a/client/src/lib/mixins.js Wed Jul 17 14:13:51 2019 +0200 +++ b/client/src/lib/mixins.js Wed Jul 17 14:18:43 2019 +0200 @@ -16,8 +16,11 @@ import jsPDF from "jspdf-yworks"; import svg2pdf from "svg2pdf.js"; import locale2 from "locale2"; +import debounce from "debounce"; import { mapState } from "vuex"; import { HTTP } from "@/lib/http"; +import { displayError } from "@/lib/errors"; +import defaultDiagramTemplate from "@/lib/DefaultDiagramTemplate"; export const sortTable = { data() { @@ -37,25 +40,58 @@ }; export const diagram = { + components: { + DiagramLegend: () => import("@/components/DiagramLegend") + }, + data() { + return { + resizeListenerFunction: null + }; + }, methods: { - getDimensions({ svgWidth, svgHeight, main, nav }) { - const mainMargin = main || { + getDimensions({ svgWidth, svgHeight, main, nav, DPI }) { + const mainMargin = { top: 20, right: 80, bottom: 60, - left: 80 + left: 80, + ...main }; - const navMargin = nav || { + const navMargin = { top: svgHeight - mainMargin.top - 65, right: 20, bottom: 30, - left: 80 + left: 80, + ...nav }; + if (DPI) { + ["top", "right", "bottom", "left"].forEach(x => { + mainMargin[x] = this.millimeter2pixels(mainMargin[x], DPI); + navMargin[x] = this.millimeter2pixels(navMargin[x], DPI); + }); + } const width = Number(svgWidth) - mainMargin.left - mainMargin.right; const mainHeight = Number(svgHeight) - mainMargin.top - mainMargin.bottom; const navHeight = Number(svgHeight) - navMargin.top - navMargin.bottom; return { width, mainHeight, navHeight, mainMargin, navMargin }; } + }, + 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); + }, + destroyed() { + window.removeEventListener("resize", this.resizeListenerFunction); } }; @@ -129,16 +165,39 @@ }; export const pdfgen = { + data() { + return { + pdf: { + doc: null, + width: null, + height: null + }, + templates: [], + defaultTemplate: defaultDiagramTemplate, + templateData: null, + form: { + template: null + } + }; + }, computed: { ...mapState("application", ["logoForPDF"]), ...mapState("user", ["user"]) }, methods: { + downloadPDF() { + this.generatePDF(); + + this.pdf.doc.save( + this.title.replace(/\s/g, "_").replace(/[():,]/g, "") + ".pdf" + ); + }, addDiagram(position, offset, width, height) { let x = offset.x, y = offset.y; - const svgWidth = this.millimeter2pixels(width, 80); - const svgHeight = this.millimeter2pixels(height, 80); + const DPI = this.templateData.properties.resoltion || 80; + const svgWidth = this.millimeter2pixels(width, DPI); + const svgHeight = this.millimeter2pixels(height, DPI); // draw the diagram in a separated html element to get the full size const offScreen = document.querySelector("#offScreen"); offScreen.style.width = `${svgWidth}px`; @@ -149,7 +208,8 @@ dimensions: this.getDimensions({ svgWidth: svgWidth, svgHeight: svgHeight, - ...layout + ...layout, + DPI: DPI }) }); var svg = offScreen.querySelector("svg"); @@ -162,7 +222,7 @@ svg2pdf(svg, this.pdf.doc, { xOffset: x, yOffset: y, - scale: 0.354 + scale: this.pixel2millimeter(1, DPI) }); offScreen.removeChild(svg); }, @@ -197,12 +257,12 @@ ")" ); }, - generatePDF(params) { + generatePDF() { // creates a new jsPDF object into this.pdf.doc // will call functions that the calling context has to provide // as specified in the templateData - let templateData = params["templateData"]; - let diagramTitle = params["diagramTitle"]; + const templateData = this.templateData; + const diagramTitle = this.title; this.pdf.doc = new jsPDF("l", "mm", templateData.properties.paperSize); // pdf width and height in millimeter (landscape) @@ -233,9 +293,9 @@ e.offset || defaultOffset, // use default width,height if they are missing in template definition e.width || - (this.templateData.properties.paperSize === "a3" ? 318 : 230), + (templateData.properties.paperSize === "a3" ? 318 : 230), e.height || - (this.templateData.properties.paperSize === "a3" ? 104 : 110) + (templateData.properties.paperSize === "a3" ? 104 : 110) ); break; } @@ -468,6 +528,58 @@ color, text ); + }, + 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(e => { + const { status, data } = e.response; + displayError({ + title: this.$gettext("Backend Error"), + message: `${status}: ${data.message || data}` + }); + }); + } } + }, + mounted() { + 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}` + }); + }); } };