Mercurial > gemma
changeset 3782:d36ccff8de5f yworks-svg2pdf
pdf: waterlevel diagram with svg2pdf
author | Thomas Junk <thomas.junk@intevation.de> |
---|---|
date | Tue, 02 Jul 2019 14:53:57 +0200 |
parents | 217fe7b4f909 |
children | 40b962c4190b |
files | client/src/components/gauge/Waterlevel.vue |
diffstat | 1 files changed, 136 insertions(+), 138 deletions(-) [+] |
line wrap: on
line diff
--- a/client/src/components/gauge/Waterlevel.vue Mon Jul 01 17:23:07 2019 +0200 +++ b/client/src/components/gauge/Waterlevel.vue Tue Jul 02 14:53:57 2019 +0200 @@ -59,7 +59,6 @@ <translate>Export as CSV</translate> </a> - <!-- <button @click="downloadSVG" type="button" @@ -68,7 +67,6 @@ > <translate>Export as SVG</translate> </button> - --> </div> </DiagramLegend> <div @@ -82,7 +80,7 @@ </div> <div id="pdfContainer" - style="position: absolute; z-index: -1; top: -9999px;" + style="position: absolute; z-index: -1; top: -9999px; width: 387mm; height: 100mm;" ></div> </div> </template> @@ -110,7 +108,7 @@ import { endOfDay } from "date-fns"; import debounce from "debounce"; import jsPDF from "jspdf-yworks"; -import canvg from "canvg"; +import svg2pdf from "svg2pdf.js"; import { saveAs } from "file-saver"; import { pdfgen } from "@/lib/mixins"; import { HTTP } from "@/lib/http"; @@ -128,7 +126,6 @@ data() { return { containerId: "waterlevel-diagram-container", - resizeListenerFunction: null, svg: null, diagram: null, navigation: null, @@ -253,40 +250,35 @@ downloadPDF() { this.pdf.doc = new jsPDF( "l", - "mm", + "pt", this.templateData.properties.paperSize ); // pdf width and height in millimeter (landscape) this.pdf.width = - this.templateData.properties.paperSize === "a3" ? 420 : 297; + this.templateData.properties.paperSize === "a3" ? 420 : 595.28; this.pdf.height = - this.templateData.properties.paperSize === "a3" ? 297 : 210; + this.templateData.properties.paperSize === "a3" ? 841.89 : 595.28; // check the template elements if (this.templateData) { let defaultFontSize = 11, defaultColor = "black", - defaultWidth = 70, + defaultWidth = 70 * 2.83, defaultTextColor = "black", defaultBorderColor = "white", defaultBgColor = "white", defaultRounding = 2, - defaultPadding = 2, - defaultOffset = { x: 0, y: 0 }; + 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 - ); + this.addDiagram(e.position, this.calculateOffset(e.offset)); break; } case "diagramlegend": { this.addDiagramLegend( e.position, - e.offset || defaultOffset, + this.calculateOffset(e.offset), e.color || defaultColor ); break; @@ -306,7 +298,7 @@ this.dateTo.toLocaleDateString(); this.addDiagramTitle( e.position, - e.offset || defaultOffset, + this.calculateOffset(e.offset), e.fontsize || defaultFontSize, e.color || defaultColor, gaugeInfo @@ -316,8 +308,8 @@ case "text": { this.addText( e.position, - e.offset || defaultOffset, - e.width || defaultWidth, + this.calculateOffset(e.offset), + e.width * 2.83 || defaultWidth, e.fontsize || defaultFontSize, e.color || defaultTextColor, e.text || "" @@ -329,18 +321,18 @@ e.url, e.format || "", e.position, - e.offset || defaultOffset, - e.width || 90, - e.height || 60 + this.calculateOffset(e.offset), + e.width * 2.83 || 90 * 2.83, + e.height * 2.83 || 60 * 2.83 ); break; } case "box": { this.addBox( e.position, - e.offset || defaultOffset, - e.width || 90, - e.height || 60, + this.calculateOffset(e.offset), + e.width * 2.83 || 90 * 2.83, + e.height * 2.83 || 60 * 2.83, e.rounding === 0 || e.rounding ? e.rounding : defaultRounding, e.color || defaultBgColor, e.brcolor || defaultBorderColor @@ -350,9 +342,9 @@ case "textbox": { this.addTextBox( e.position, - e.offset || defaultOffset, - e.width, - e.height, + this.calculateOffset(e.offset), + e.width * 2.83, + e.height * 2.83, e.rounding === 0 || e.rounding ? e.rounding : defaultRounding, e.padding || defaultPadding, e.fontsize || defaultFontSize, @@ -370,6 +362,19 @@ this.selectedGauge.properties.objname + " Waterlevel-Diagram.pdf" ); }, + calculateOffset(offset) { + let tmp; + let defaultOffset = { x: 0, y: 0 }; + if (offset) { + tmp = { + x: offset.x * 2.83, + y: offset.y * 2.83 + }; + } else { + tmp = defaultOffset; + } + return tmp; + }, applyChange() { if (this.form.template.hasOwnProperty("properties")) { this.templateData = this.defaultTemplate; @@ -394,54 +399,36 @@ }); } }, - addDiagram(position, offset, width, height) { + addDiagram(position, offset) { let x = offset.x, y = offset.y; - // check if there are tow diagrams on screen - if ( - ["GAUGE_WATERLEVEL_HYDROLOGICALCONDITIONS"].indexOf(this.paneSetup) !== - -1 - ) { - this.containerId = "pdfContainer"; - // set width and height - document.querySelector("#pdfContainer").style.width = - document.querySelector("#waterlevel-diagram-container").clientWidth * - 2 + - document.querySelector("#diagramlegendId").clientWidth + - "px"; - document.querySelector("#pdfContainer").style.height = - document.querySelector("#waterlevel-diagram-container").clientHeight + - "px"; - this.drawDiagram(); - } - var svg = document.getElementById(this.containerId).innerHTML; - if (svg) { - svg = svg.replace(/\r?\n|\r/g, "").trim(); - } - this.containerId = "waterlevel-diagram-container"; - 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"); + // use default width,height if they are missing in the template definition - 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; + x = + this.pdf.width - + offset.x - + document.getElementById(this.containerId).clientWidth; } if (["bottomright", "bottomleft"].indexOf(position) !== -1) { - y = this.pdf.height - offset.y - height; + y = + this.pdf.height - + offset.y - + document.getElementById(this.containerId).clientHeight; } - this.pdf.doc.addImage(imgData, "PNG", x, y, width, height); + + let svg = document.getElementById(this.containerId).firstElementChild; + svg2pdf(svg, this.pdf.doc, { + xOffset: x, + yOffset: y, + scale: + this.templateData.properties.paperSize === "a3" + ? 2 * (25.4 / 72) + : 1.5 * (25.4 / 72) + }); + + this.containerId = "waterlevel-diagram-container"; }, // Diagram legend addDiagramLegend(position, offset, color) { @@ -461,17 +448,17 @@ this.pdf.doc.setTextColor(color); this.pdf.doc.setDrawColor("white"); this.pdf.doc.setFillColor("steelblue"); - this.pdf.doc.circle(x, y, 2, "FD"); - this.pdf.doc.text(x + 3, y + 1, "Waterlevel"); + this.pdf.doc.circle(x, y, 2 * 2.8, "FD"); + this.pdf.doc.text(x + 3 * 2.83, y + 1 * 2.83, "Waterlevel"); this.pdf.doc.setFillColor("#dae6f0"); - this.pdf.doc.circle(x, y + 5, 2, "FD"); + this.pdf.doc.circle(x, y + 5 * 2.83, 2 * 2.83, "FD"); this.pdf.doc.setFillColor("#e5ffe5"); - this.pdf.doc.circle(x, y + 10, 2, "FD"); - this.pdf.doc.text(x + 3, y + 11, "Navigable Range"); + this.pdf.doc.circle(x, y + 10 * 2.83, 2 * 2.83, "FD"); + this.pdf.doc.text(x + 3 * 2.83, y + 11 * 2.83, "Navigable Range"); this.pdf.doc.setDrawColor("#90b4d2"); this.pdf.doc.setFillColor("#90b4d2"); - this.pdf.doc.circle(x, y + 5, 0.6, "FD"); - this.pdf.doc.text(x + 3, y + 6, "Prediction"); + this.pdf.doc.circle(x, y + 5 * 2.83, 0.6 * 2.83, "FD"); + this.pdf.doc.text(x + 3 * 2.83, y + 6 * 2.83, "Prediction"); }, drawDiagram() { // remove old diagram and exit if necessary data is missing @@ -565,9 +552,7 @@ // static, don't need updater this.drawNavigationChart(); - if (refWaterLevels) { - this.drawRefLines(refWaterLevels); - } + this.drawRefLines(refWaterLevels); updaters.push(this.drawNashSutcliffe(72)); updaters.push(this.drawNashSutcliffe(48)); @@ -675,6 +660,10 @@ this.svg.selectAll(".tick text").attr("fill", "black"); this.svg.selectAll(".domain").attr("stroke", "black"); this.svg + .selectAll(".domain") + .attr("stroke", "black") + .attr("fill", "none"); + this.svg .selectAll(".zoom") .attr("cursor", "move") .attr("fill", "none") @@ -687,6 +676,7 @@ .selectAll(".brush .handle") .attr("stroke", "rgba(23, 162, 184, 0.5)") .attr("fill", "rgba(23, 162, 184, 0.5)"); + this.svg.selectAll(".brush .overlay").attr("fill", "none"); this.svg .selectAll(".chart-dots") .attr("clip-path", "url(#waterlevel-clip)"); @@ -732,23 +722,6 @@ return { width, mainHeight, navHeight, mainMargin, navMargin }; }, getExtent(refWaterLevels) { - let waterlevelValues = [...this.waterlevels.map(wl => wl.waterlevel)]; - if (refWaterLevels) { - waterlevelValues.push( - refWaterLevels.HDC + (refWaterLevels.HDC - refWaterLevels.LDC) / 8, - Math.max( - refWaterLevels.LDC - (refWaterLevels.HDC - refWaterLevels.LDC) / 4, - 0 - ) - ); - } else { - let delta = d3.max(waterlevelValues) - d3.min(waterlevelValues); - waterlevelValues.push( - d3.max(waterlevelValues) + delta * 0.1, - d3.min(waterlevelValues) - delta * 0.1 - ); - } - return { // set min/max values for the date axis date: [ @@ -757,8 +730,24 @@ ], // set min/max values for the waterlevel axis // including HDC (+ 1/8 HDC-LDC) and LDC (- 1/4 HDC-LDC) - // or, if no refWaterlevels exist, +-10% of delta between min and max wl - waterlevel: d3.extent(waterlevelValues) + waterlevel: d3.extent( + [ + ...this.waterlevels, + { + waterlevel: + refWaterLevels.HDC + + (refWaterLevels.HDC - refWaterLevels.LDC) / 8 + }, + { + waterlevel: Math.max( + refWaterLevels.LDC - + (refWaterLevels.HDC - refWaterLevels.LDC) / 4, + 0 + ) + } + ], + d => d.waterlevel + ) }; }, getScale() { @@ -819,19 +808,22 @@ domainLeft.setDate(domainLeft.getDate() - 1); domainRight.setDate(domainRight.getDate() + 1); - return ( - d3 - .lineChunked() - // render only data points that are visible in the current scale - .defined(d => d.date > domainLeft && d.date < domainRight) - .x(d => this.scale.x(d.date)) - .y(d => this.scale.y(d.waterlevel)) - .curve(d3.curveLinear) - .isNext(this.isNext()) - .pointAttrs({ r: 1.7 }) + let lineChunked = d3 + .lineChunked() + // render only data points that are visible in the current scale + .defined(d => d.date > domainLeft && d.date < domainRight) + .x(d => this.scale.x(d.date)) + .y(d => this.scale.y(d.waterlevel)) + .curve(d3.curveLinear) + .isNext(this.isNext()) + .pointAttrs({ r: 1.7 }); + // to avoid creating empty clip-path element + if (this.hasPredictions) { + lineChunked .chunk(d => (d.predicted ? "predicted" : "line")) - .chunkDefinitions({ predicted: {} }) - ); + .chunkDefinitions({ predicted: {} }); + } + return lineChunked; }; this.diagram @@ -845,37 +837,36 @@ }; }, drawNavigationChart() { + let lineChunked = d3 + .lineChunked() + .x(d => this.scale.x2(d.date)) + .y(d => this.scale.y2(d.waterlevel)) + .curve(d3.curveLinear) + .isNext(this.isNext()) + .pointAttrs({ r: 1.7 }); + // to avoid creating empty clip-path element + if (this.hasPredictions) { + lineChunked + .chunk(d => (d.predicted ? "predicted" : "line")) + .chunkDefinitions({ predicted: {} }); + } this.navigation .append("g") .attr("class", "line") .datum(this.waterlevels) - .call( - d3 - .lineChunked() - .x(d => this.scale.x2(d.date)) - .y(d => this.scale.y2(d.waterlevel)) - .curve(d3.curveLinear) - .isNext(this.isNext()) - .pointAttrs({ r: 1.7 }) - .chunk(d => (d.predicted ? "predicted" : "line")) - .chunkDefinitions({ predicted: {} }) - ); + .call(lineChunked); }, drawNowLines() { - const now = new Date(); - const nowCoords = [ - { x: now, y: this.extent.waterlevel[0] }, - { x: now, y: this.extent.waterlevel[1] } - ]; const nowLine = d3 .line() .x(d => this.scale.x(d.x)) .y(d => this.scale.y(d.y)); + const nowLabel = selection => { selection.attr( "transform", - `translate(${this.scale.x(now)}, ${this.scale.y( - this.extent.waterlevel[1] + `translate(${this.scale.x(new Date())}, ${this.scale.y( + this.extent.waterlevel[1] - 16 )})` ); }; @@ -883,7 +874,10 @@ // draw in main this.diagram .append("path") - .datum(nowCoords) + .datum([ + { x: new Date(), y: this.extent.waterlevel[0] }, + { x: new Date(), y: this.extent.waterlevel[1] - 20 } + ]) .attr("class", "now-line") .attr("d", nowLine); this.diagram // label @@ -896,7 +890,10 @@ // draw in nav this.navigation .append("path") - .datum(nowCoords) + .datum([ + { x: new Date(), y: this.extent.waterlevel[0] }, + { x: new Date(), y: this.extent.waterlevel[1] - 20 } + ]) .attr("class", "now-line") .attr( "d", @@ -959,7 +956,7 @@ this.diagram .append("path") .datum([ - { x: this.extent.date[0], y: refWaterLevels[ref] }, + { x: 0, y: refWaterLevels[ref] }, { x: this.extent.date[1], y: refWaterLevels[ref] } ]) .attr("class", ref.toLowerCase() + "-line") @@ -1118,7 +1115,9 @@ tooltip .append("rect") .attr("rx", "0.25em") - .attr("ry", "0.25em"); + .attr("ry", "0.25em") + .attr("width", "0px") + .attr("height", "0px"); // create container for multiple text rows const tooltipText = tooltip.append("text").attr("text-anchor", "middle"); @@ -1261,8 +1260,7 @@ } }, created() { - this.resizeListenerFunction = debounce(this.drawDiagram, 100); - window.addEventListener("resize", this.resizeListenerFunction); + window.addEventListener("resize", debounce(this.drawDiagram), 100); }, mounted() { // Nasty but necessary if we don't want to use the updated hook to re-draw @@ -1272,7 +1270,7 @@ // 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); + setTimeout(this.drawDiagram, 50); this.templates[0] = this.defaultTemplate; this.form.template = this.templates[0]; @@ -1300,7 +1298,7 @@ }); }, destroyed() { - window.removeEventListener("resize", this.resizeListenerFunction); + window.removeEventListener("resize", debounce(this.drawDiagram)); } }; </script>