Mercurial > gemma
comparison client/src/components/gauge/HydrologicalConditions.vue @ 3197:9d38df37c1f8
client: implemnt pdf-template for hydrologicalconditions diagram
author | Fadi Abbud <fadi.abbud@intevation.de> |
---|---|
date | Wed, 08 May 2019 15:08:27 +0200 |
parents | cdfb0093b7b1 |
children | 5b8916b78cea |
comparison
equal
deleted
inserted
replaced
3196:0d76a0476a5b | 3197:9d38df37c1f8 |
---|---|
23 </div> | 23 </div> |
24 <div class="legend"> | 24 <div class="legend"> |
25 <span style="background-color: lightsteelblue"></span> | 25 <span style="background-color: lightsteelblue"></span> |
26 Long-term Amplitude | 26 Long-term Amplitude |
27 </div> | 27 </div> |
28 <select | |
29 @change="applyChange" | |
30 v-model="form.template" | |
31 class="form-control d-block custom-select-sm w-100" | |
32 > | |
33 <option | |
34 v-for="template in templates" | |
35 :value="template" | |
36 :key="template.name" | |
37 > | |
38 {{ template.name }} | |
39 </option> | |
40 </select> | |
28 <div> | 41 <div> |
29 <button | 42 <button |
30 @click="downloadPDF" | 43 @click="downloadPDF" |
31 type="button" | 44 type="button" |
32 class="btn btn-sm btn-info d-block w-100 mt-2" | 45 class="btn btn-sm btn-info d-block w-100 mt-2" |
67 import * as d3 from "d3"; | 80 import * as d3 from "d3"; |
68 import debounce from "debounce"; | 81 import debounce from "debounce"; |
69 import { startOfYear, endOfYear } from "date-fns"; | 82 import { startOfYear, endOfYear } from "date-fns"; |
70 import jsPDF from "jspdf"; | 83 import jsPDF from "jspdf"; |
71 import canvg from "canvg"; | 84 import canvg from "canvg"; |
85 import { pdfgen } from "@/lib/mixins"; | |
86 import { HTTP } from "@/lib/http"; | |
87 import { displayError } from "@/lib/errors"; | |
72 | 88 |
73 export default { | 89 export default { |
90 mixins: [pdfgen], | |
74 components: { | 91 components: { |
75 DiagramLegend: () => import("@/components/DiagramLegend") | 92 DiagramLegend: () => import("@/components/DiagramLegend") |
76 }, | 93 }, |
77 data() { | 94 data() { |
78 return { | 95 return { |
81 diagram: null, | 98 diagram: null, |
82 navigation: null, | 99 navigation: null, |
83 dimensions: null, | 100 dimensions: null, |
84 extent: null, | 101 extent: null, |
85 scale: null, | 102 scale: null, |
86 axes: null | 103 axes: null, |
104 templateData: null, | |
105 form: { | |
106 template: null, | |
107 form: null | |
108 }, | |
109 templates: [], | |
110 defaultTemplate: { | |
111 name: "Default", | |
112 properties: { | |
113 paperSize: "a4", | |
114 resolution: 80 | |
115 }, | |
116 elements: [ | |
117 { | |
118 type: "diagram", | |
119 position: "topleft", | |
120 offset: { x: 15, y: 50 }, | |
121 width: 290, | |
122 height: 100 | |
123 }, | |
124 { | |
125 type: "diagramlegend", | |
126 position: "topleft", | |
127 offset: { x: 30, y: 150 }, | |
128 colot: "black" | |
129 }, | |
130 { | |
131 type: "diagramtitle", | |
132 position: "topleft", | |
133 offset: { x: 50, y: 26 }, | |
134 fontsize: 22, | |
135 color: "steelblue" | |
136 } | |
137 ] | |
138 }, | |
139 pdf: { | |
140 doc: null, | |
141 width: 420, | |
142 height: 297 | |
143 } | |
87 }; | 144 }; |
88 }, | 145 }, |
89 computed: { | 146 computed: { |
90 ...mapState("application", ["paneSetup"]), | 147 ...mapState("application", ["paneSetup"]), |
91 ...mapState("gauges", [ | 148 ...mapState("gauges", [ |
120 ? "GAUGE_WATERLEVEL" | 177 ? "GAUGE_WATERLEVEL" |
121 : "DEFAULT" | 178 : "DEFAULT" |
122 ); | 179 ); |
123 }, | 180 }, |
124 downloadPDF() { | 181 downloadPDF() { |
182 if (this.templateData) { | |
183 this.pdf.doc = new jsPDF( | |
184 "l", | |
185 "mm", | |
186 this.templateData.properties.paperSize | |
187 ); | |
188 // pdf width and height in millimeter (landscape) | |
189 this.pdf.width = | |
190 this.templateData.properties.paperSize === "a3" ? 420 : 297; | |
191 this.pdf.height = | |
192 this.templateData.properties.paperSize === "a3" ? 297 : 210; | |
193 // default values if some are missing in template | |
194 let defaultFontSize = 11, | |
195 defaultColor = "black", | |
196 defaultWidth = 70, | |
197 defaultTextColor = "black", | |
198 defaultBorderColor = "white", | |
199 defaultBgColor = "white", | |
200 defaultRounding = 2, | |
201 defaultPadding = 2, | |
202 defaultOffset = { x: 0, y: 0 }; | |
203 this.templateData.elements.forEach(e => { | |
204 switch (e.type) { | |
205 case "diagram": { | |
206 this.addDiagram( | |
207 e.position, | |
208 e.offset || defaultOffset, | |
209 e.width, | |
210 e.height | |
211 ); | |
212 break; | |
213 } | |
214 case "diagramtitle": { | |
215 this.addDiagramTitle(e.position, e.offset, e.fontsize, e.color); | |
216 break; | |
217 } | |
218 case "diagramlegend": { | |
219 this.addDiagramLegend( | |
220 e.position, | |
221 e.offset || defaultOffset, | |
222 e.color || defaultColor | |
223 ); | |
224 break; | |
225 } | |
226 case "image": { | |
227 this.addImage( | |
228 e.url, | |
229 e.format, | |
230 e.position, | |
231 e.offset || defaultOffset, | |
232 e.width, | |
233 e.height | |
234 ); | |
235 break; | |
236 } | |
237 case "text": { | |
238 this.addText( | |
239 e.position, | |
240 e.offset || defaultOffset, | |
241 e.width || defaultWidth, | |
242 e.fontSize || defaultFontSize, | |
243 e.color || defaultTextColor, | |
244 e.text | |
245 ); | |
246 break; | |
247 } | |
248 case "box": { | |
249 this.addBox( | |
250 e.position, | |
251 e.offset, | |
252 e.width, | |
253 e.height, | |
254 e.rounding === 0 || e.rounding ? e.rounding : defaultRounding, | |
255 e.color || defaultBgColor, | |
256 e.brcolor || defaultBorderColor | |
257 ); | |
258 break; | |
259 } | |
260 case "textbox": { | |
261 this.addTextBox( | |
262 e.position, | |
263 e.offset || defaultOffset, | |
264 e.width, | |
265 e.height, | |
266 e.rounding === 0 || e.rounding ? e.rounding : defaultRounding, | |
267 e.padding || defaultPadding, | |
268 e.fontSize || defaultFontSize, | |
269 e.color || defaultTextColor, | |
270 e.background || defaultBgColor, | |
271 e.text, | |
272 e.brcolor || defaultBorderColor | |
273 ); | |
274 break; | |
275 } | |
276 } | |
277 }); | |
278 } | |
279 this.pdf.doc.save( | |
280 this.selectedGauge.properties.objname + | |
281 " Hydrological-condition Diagram.pdf" | |
282 ); | |
283 }, | |
284 addDiagram(position, offset, width, height) { | |
285 let x = offset.x, | |
286 y = offset.y; | |
125 var svg = document.getElementById(this.containerId).innerHTML; | 287 var svg = document.getElementById(this.containerId).innerHTML; |
126 if (svg) { | 288 if (svg) { |
127 svg = svg.replace(/\r?\n|\r/g, "").trim(); | 289 svg = svg.replace(/\r?\n|\r/g, "").trim(); |
128 } | 290 } |
129 var pdf = new jsPDF("l", "mm", "a3"); | |
130 var canvas = document.createElement("canvas"); | 291 var canvas = document.createElement("canvas"); |
131 canvas.width = window.innerWidth; | 292 canvas.width = window.innerWidth; |
132 canvas.height = window.innerHeight / 2; | 293 canvas.height = window.innerHeight / 2; |
133 canvg(canvas, svg, { | 294 canvg(canvas, svg, { |
134 ignoreMouse: true, | 295 ignoreMouse: true, |
135 ignoreAnimation: true, | 296 ignoreAnimation: true, |
136 ignoreDimensions: true | 297 ignoreDimensions: true |
137 }); | 298 }); |
138 var imgData = canvas.toDataURL("image/png"); | 299 var imgData = canvas.toDataURL("image/png"); |
139 pdf.addImage(imgData, "PNG", 40, 60, 380, 130); | 300 // landscape format is used for both a3,a4 papersize |
140 this.addDiagramTitle(pdf, 108, 30, 22, "steelblue"); | 301 if (!width) { |
141 this.addDiagramLegend(pdf, 60, 190, "black"); | 302 width = this.templateData.properties.paperSize === "a3" ? 380 : 290; |
142 pdf.save( | 303 } |
143 this.selectedGauge.properties.objname + | 304 if (!height) { |
144 " Hydrological-condition Diagram.pdf" | 305 height = this.templateData.properties.paperSize === "a3" ? 130 : 100; |
145 ); | 306 } |
307 if (["topright", "bottomright"].indexOf(position) !== -1) { | |
308 x = this.pdf.width - offset.x - width; | |
309 } | |
310 if (["bottomright", "bottomleft"].indexOf(position) !== -1) { | |
311 y = this.pdf.height - offset.y - height; | |
312 } | |
313 this.pdf.doc.addImage(imgData, "PNG", x, y, width, height); | |
314 }, | |
315 applyChange() { | |
316 if (this.form.template.hasOwnProperty("properties")) { | |
317 this.templateData = this.defaultTemplate; | |
318 return; | |
319 } | |
320 if (this.form.template) { | |
321 HTTP.get("/templates/print/" + this.form.template.name, { | |
322 headers: { | |
323 "X-Gemma-Auth": localStorage.getItem("token"), | |
324 "Content-type": "text/xml; charset=UTF-8" | |
325 } | |
326 }) | |
327 .then(response => { | |
328 this.templateData = response.data.template_data; | |
329 this.form.paperSize = this.templateData.properties.paperSize; | |
330 }) | |
331 .catch(e => { | |
332 const { status, data } = e.response; | |
333 displayError({ | |
334 title: this.$gettext("Backend Error"), | |
335 message: `${status}: ${data.message || data}` | |
336 }); | |
337 }); | |
338 } | |
146 }, | 339 }, |
147 // Gauge info as title | 340 // Gauge info as title |
148 addDiagramTitle(pdf, x, y, size, color) { | 341 addDiagramTitle(position, offset, size, color) { |
342 let x = offset.x, | |
343 y = offset.y; | |
149 let gaugeInfo = | 344 let gaugeInfo = |
150 this.selectedGauge.properties.objname + | 345 this.selectedGauge.properties.objname + |
151 " (" + | 346 " (" + |
152 this.selectedGauge.id | 347 this.selectedGauge.id |
153 .split(".")[1] | 348 .split(".")[1] |
154 .replace(/[()]/g, "") | 349 .replace(/[()]/g, "") |
155 .split(",")[3] + | 350 .split(",")[3] + |
156 "): Hydrological Conditions " + | 351 "): Hydrological Conditions " + |
157 this.longtermInterval.join(" - "); | 352 this.longtermInterval.join(" - "); |
158 pdf.setTextColor(color); | 353 let width = |
159 pdf.setFontSize(22); | 354 (this.pdf.doc.getStringUnitWidth(gaugeInfo) * size) / (72 / 25.6) + |
160 pdf.setFontStyle("bold"); | 355 size / 2; |
161 pdf.text(gaugeInfo, 108, 30); | 356 // if position is on the right, x needs to be calculate with pdf width and |
357 // the size of the element | |
358 if (["topright", "bottomright"].indexOf(position) !== -1) { | |
359 x = this.pdf.width - offset.x - width; | |
360 } | |
361 if (["bottomright", "bottomleft"].indexOf(position) !== -1) { | |
362 y = this.pdf.height - offset.y - this.getTextHeight(1); | |
363 } | |
364 this.pdf.doc.setTextColor(color); | |
365 this.pdf.doc.setFontSize(size); | |
366 this.pdf.doc.setFontStyle("bold"); | |
367 this.pdf.doc.text(gaugeInfo, x, y, { baseline: "hanging" }); | |
368 }, | |
369 getTextHeight(numberOfLines) { | |
370 return ( | |
371 numberOfLines * | |
372 ((this.pdf.doc.getFontSize() * 25.4) / 80) * | |
373 this.pdf.doc.getLineHeightFactor() | |
374 ); | |
162 }, | 375 }, |
163 // Diagram legend | 376 // Diagram legend |
164 addDiagramLegend(pdf, x, y, color) { | 377 addDiagramLegend(position, offset, color) { |
165 pdf.setFontSize(10); | 378 let x = offset.x, |
166 pdf.setTextColor(color); | 379 y = offset.y; |
167 pdf.setDrawColor("white"); | 380 let width = |
168 pdf.setFillColor("red"); | 381 (this.pdf.doc.getStringUnitWidth("Long-term Amplitude") * 10) / |
169 pdf.circle(x, y, 2, "FD"); | 382 (72 / 25.6) + |
170 pdf.text(x + 3, y + 1, "" + this.yearCompare); | 383 5; |
171 pdf.setFillColor("orange"); | 384 // if position is on the right, x needs to be calculate with pdf width and |
172 pdf.circle(x, y + 5, 2, "FD"); | 385 // the size of the element |
173 pdf.text(x + 3, y + 6, "Q25%"); | 386 if (["topright", "bottomright"].indexOf(position) !== -1) { |
174 pdf.setFillColor("black"); | 387 x = this.pdf.width - offset.x - width; |
175 pdf.circle(x, y + 10, 2, "FD"); | 388 } |
176 pdf.text(x + 3, y + 11, "Median "); | 389 if (["bottomright", "bottomleft"].indexOf(position) !== -1) { |
177 pdf.setFillColor("purple"); | 390 y = this.pdf.height - offset.y - this.getTextHeight(4); |
178 pdf.circle(x, y + 15, 2, "FD"); | 391 } |
179 pdf.text(x + 3, y + 16, "Q75%"); | 392 this.pdf.doc.setFontSize(10); |
180 pdf.setFillColor("lightsteelblue"); | 393 this.pdf.doc.setTextColor(color); |
181 pdf.circle(x, y + 20, 2, "FD"); | 394 this.pdf.doc.setDrawColor("white"); |
182 pdf.text(x + 3, y + 21, "Long-term Amplitude"); | 395 this.pdf.doc.setFillColor("red"); |
396 this.pdf.doc.circle(x, y, 2, "FD"); | |
397 this.pdf.doc.text(x + 3, y + 1, "" + this.yearCompare); | |
398 this.pdf.doc.setFillColor("orange"); | |
399 this.pdf.doc.circle(x, y + 5, 2, "FD"); | |
400 this.pdf.doc.text(x + 3, y + 6, "Q25%"); | |
401 this.pdf.doc.setFillColor("black"); | |
402 this.pdf.doc.circle(x, y + 10, 2, "FD"); | |
403 this.pdf.doc.text(x + 3, y + 11, "Median "); | |
404 this.pdf.doc.setFillColor("purple"); | |
405 this.pdf.doc.circle(x, y + 15, 2, "FD"); | |
406 this.pdf.doc.text(x + 3, y + 16, "Q75%"); | |
407 this.pdf.doc.setFillColor("lightsteelblue"); | |
408 this.pdf.doc.circle(x, y + 20, 2, "FD"); | |
409 this.pdf.doc.text(x + 3, y + 21, "Long-term Amplitude"); | |
183 }, | 410 }, |
184 drawDiagram() { | 411 drawDiagram() { |
185 // remove old diagram | 412 // remove old diagram |
186 d3.select("#" + this.containerId + " svg").remove(); | 413 d3.select("#" + this.containerId + " svg").remove(); |
187 if (!this.selectedGauge || !this.longtermWaterlevels.length) return; | 414 if (!this.selectedGauge || !this.longtermWaterlevels.length) return; |
845 created() { | 1072 created() { |
846 window.addEventListener("resize", debounce(this.drawDiagram), 100); | 1073 window.addEventListener("resize", debounce(this.drawDiagram), 100); |
847 }, | 1074 }, |
848 mounted() { | 1075 mounted() { |
849 this.drawDiagram(); | 1076 this.drawDiagram(); |
1077 this.templates[0] = this.defaultTemplate; | |
1078 this.form.template = this.templates[0]; | |
1079 this.templateData = this.form.template; | |
1080 HTTP.get("/templates/print", { | |
1081 headers: { | |
1082 "X-Gemma-Auth": localStorage.getItem("token"), | |
1083 "Content-type": "text/xml; charset=UTF-8" | |
1084 } | |
1085 }) | |
1086 .then(response => { | |
1087 if (response.data.length) { | |
1088 this.templates = response.data; | |
1089 this.form.template = this.templates[0]; | |
1090 this.templates[this.templates.length] = this.defaultTemplate; | |
1091 this.applyChange(); | |
1092 } | |
1093 }) | |
1094 .catch(e => { | |
1095 const { status, data } = e.response; | |
1096 displayError({ | |
1097 title: this.$gettext("Backend Error"), | |
1098 message: `${status}: ${data.message || data}` | |
1099 }); | |
1100 }); | |
850 }, | 1101 }, |
851 updated() { | 1102 updated() { |
852 this.drawDiagram(); | 1103 this.drawDiagram(); |
853 }, | 1104 }, |
854 destroyed() { | 1105 destroyed() { |