Mercurial > gemma
comparison client/src/components/fairway/AvailableFairwayDepth.vue @ 3963:feb53713bc2f diagram-cleanup
client: moved duplicated code to mixins and unified code patterns in diagram components [WIP]
author | Markus Kottlaender <markus@intevation.de> |
---|---|
date | Fri, 12 Jul 2019 15:14:16 +0200 |
parents | 7b3935a8d9ee |
children | afc7bca44df4 |
comparison
equal
deleted
inserted
replaced
3944:0ed8af02d5a9 | 3963:feb53713bc2f |
---|---|
1 <template> | 1 <template> |
2 <div class="d-flex flex-column flex-fill"> | 2 <div class="d-flex flex-column flex-fill"> |
3 <UIBoxHeader icon="chart-area" :title="title" :closeCallback="close" /> | 3 <UIBoxHeader icon="chart-area" :title="title" :closeCallback="close" /> |
4 <UISpinnerOverlay v-if="loading" /> | |
5 <div class="d-flex flex-fill"> | 4 <div class="d-flex flex-fill"> |
6 <DiagramLegend> | 5 <DiagramLegend> |
7 <div v-for="(entry, index) in legend" :key="index" class="legend"> | 6 <div v-for="(entry, index) in legend" :key="index" class="legend"> |
8 <span | 7 <span |
9 :style=" | 8 :style=" |
42 >Download CSV</a | 41 >Download CSV</a |
43 > | 42 > |
44 </div> | 43 </div> |
45 </DiagramLegend> | 44 </DiagramLegend> |
46 <div | 45 <div |
47 ref="diagramContainer" | 46 class="d-flex flex-fill justify-content-center align-items-center" |
48 :id="containerId" | 47 :id="containerId" |
49 class="diagram-container flex-fill" | 48 > |
50 ></div> | 49 <div v-if="!fwData.length"> |
50 <translate>No data available.</translate> | |
51 </div> | |
52 </div> | |
51 </div> | 53 </div> |
52 </div> | 54 </div> |
53 </template> | 55 </template> |
54 | |
55 <style></style> | |
56 | 56 |
57 <script> | 57 <script> |
58 /* This is Free Software under GNU Affero General Public License v >= 3.0 | 58 /* This is Free Software under GNU Affero General Public License v >= 3.0 |
59 * without warranty, see README.md and license for details. | 59 * without warranty, see README.md and license for details. |
60 * | 60 * |
69 * * Thomas Junk <thomas.junk@intevation.de> | 69 * * Thomas Junk <thomas.junk@intevation.de> |
70 * * Markus Kottländer <markus.kottlaender@intevation.de> | 70 * * Markus Kottländer <markus.kottlaender@intevation.de> |
71 * * Fadi Abbud <fadi.abbud@intevation.de> | 71 * * Fadi Abbud <fadi.abbud@intevation.de> |
72 */ | 72 */ |
73 import * as d3 from "d3"; | 73 import * as d3 from "d3"; |
74 import app from "@/main"; | |
75 import debounce from "debounce"; | |
76 import { mapState } from "vuex"; | 74 import { mapState } from "vuex"; |
75 import { diagram, pdfgen, templateLoader } from "@/lib/mixins"; | |
76 import { FREQUENCIES } from "@/store/fairwayavailability"; | |
77 import filters from "@/lib/filters.js"; | 77 import filters from "@/lib/filters.js"; |
78 import { diagram, pdfgen, templateLoader } from "@/lib/mixins"; | 78 |
79 import { HTTP } from "@/lib/http"; | 79 // hoursInDays is a const instead of component method because it's used where |
80 import { displayError } from "@/lib/errors"; | 80 // the component context is not available |
81 import { FREQUENCIES } from "@/store/fairwayavailability"; | |
82 import { defaultDiagramTemplate } from "@/lib/DefaultDiagramTemplate"; | |
83 | |
84 const hoursInDays = x => x / 24; | 81 const hoursInDays = x => x / 24; |
82 const COLORS = { | |
83 LDC: "#cdcdcd", | |
84 HIGHEST: "#3675ff", | |
85 REST: ["#782121", "#ff6c6c", "#ffaaaa"] | |
86 }; | |
85 | 87 |
86 export default { | 88 export default { |
87 mixins: [diagram, pdfgen, templateLoader], | 89 mixins: [diagram, pdfgen, templateLoader], |
88 components: { | 90 components: { |
89 DiagramLegend: () => import("@/components/DiagramLegend") | 91 DiagramLegend: () => import("@/components/DiagramLegend") |
90 }, | 92 }, |
91 data() { | 93 data() { |
92 return { | 94 return { |
93 containerId: "availablefairwaydepth-diagram-container", | 95 containerId: "availablefairwaydepth-diagram-container", |
94 resizeListenerFunction: null, | |
95 loading: false, | |
96 scalePaddingLeft: 60, | 96 scalePaddingLeft: 60, |
97 scalePaddingRight: 0, | 97 scalePaddingRight: 0, |
98 paddingTop: 25, | 98 paddingTop: 25 |
99 pdf: { | |
100 doc: null, | |
101 width: null, | |
102 height: null | |
103 }, | |
104 form: { | |
105 template: null | |
106 }, | |
107 templateData: null, | |
108 templates: [], | |
109 defaultTemplate: defaultDiagramTemplate | |
110 }; | 99 }; |
111 }, | |
112 created() { | |
113 this.resizeListenerFunction = debounce(this.drawDiagram, 100); | |
114 window.addEventListener("resize", this.resizeListenerFunction); | |
115 }, | |
116 destroyed() { | |
117 window.removeEventListener("resize", this.resizeListenerFunction); | |
118 }, | |
119 mounted() { | |
120 // Nasty but necessary if we don't want to use the updated hook to re-draw | |
121 // the diagram because this would re-draw it also for irrelevant reasons. | |
122 // In this case we need to wait for the child component (DiagramLegend) to | |
123 // render. According to the docs (https://vuejs.org/v2/api/#mounted) this | |
124 // should be possible with $nextTick() but it doesn't work because it does | |
125 // not guarantee that the DOM is not only updated but also re-painted on the | |
126 // screen. | |
127 setTimeout(this.drawDiagram, 150); | |
128 | |
129 this.templates[0] = this.defaultTemplate; | |
130 this.form.template = this.templates[0]; | |
131 this.templateData = this.form.template; | |
132 HTTP.get("/templates/diagram", { | |
133 headers: { | |
134 "X-Gemma-Auth": localStorage.getItem("token"), | |
135 "Content-type": "text/xml; charset=UTF-8" | |
136 } | |
137 }) | |
138 .then(response => { | |
139 if (response.data.length) { | |
140 this.templates = response.data; | |
141 this.form.template = this.templates[0]; | |
142 this.templates[this.templates.length] = this.defaultTemplate; | |
143 this.applyChange(); | |
144 } | |
145 }) | |
146 .catch(e => { | |
147 const { status, data } = e.response; | |
148 displayError({ | |
149 title: this.$gettext("Backend Error"), | |
150 message: `${status}: ${data.message || data}` | |
151 }); | |
152 }); | |
153 }, | 100 }, |
154 computed: { | 101 computed: { |
155 ...mapState("fairwayavailability", [ | 102 ...mapState("fairwayavailability", [ |
156 "selectedFairwayAvailabilityFeature", | 103 "selectedFairwayAvailabilityFeature", |
157 "fwData", | 104 "fwData", |
214 if (this.selectedFairwayAvailabilityFeature == null) return ""; | 161 if (this.selectedFairwayAvailabilityFeature == null) return ""; |
215 return this.selectedFairwayAvailabilityFeature.properties.name; | 162 return this.selectedFairwayAvailabilityFeature.properties.name; |
216 } | 163 } |
217 }, | 164 }, |
218 methods: { | 165 methods: { |
219 applyChange() { | |
220 if (this.form.template.hasOwnProperty("properties")) { | |
221 this.templateData = this.defaultTemplate; | |
222 return; | |
223 } | |
224 if (this.form.template) { | |
225 this.loadTemplates("/templates/diagram/" + this.form.template.name) | |
226 .then(response => { | |
227 this.prepareImages(response.data.template_data.elements).then( | |
228 values => { | |
229 values.forEach(v => { | |
230 response.data.template_data.elements[v.index].url = v.url; | |
231 }); | |
232 this.templateData = response.data.template_data; | |
233 } | |
234 ); | |
235 }) | |
236 .catch(e => { | |
237 const { status, data } = e.response; | |
238 displayError({ | |
239 title: this.$gettext("Backend Error"), | |
240 message: `${status}: ${data.message || data}` | |
241 }); | |
242 }); | |
243 } | |
244 }, | |
245 downloadPDF() { | 166 downloadPDF() { |
246 let title = `Available Fairway Depth: ${this.featureName}`; | 167 let title = `Available Fairway Depth: ${this.featureName}`; |
247 this.generatePDF({ | 168 this.generatePDF({ |
248 templateData: this.templateData, | 169 templateData: this.templateData, |
249 diagramTitle: title | 170 diagramTitle: title |
264 if (["bottomright", "bottomleft"].indexOf(position) !== -1) { | 185 if (["bottomright", "bottomleft"].indexOf(position) !== -1) { |
265 y = this.pdf.height - offset.y - this.getTextHeight(6); | 186 y = this.pdf.height - offset.y - this.getTextHeight(6); |
266 } | 187 } |
267 | 188 |
268 this.pdf.doc.setTextColor(color); | 189 this.pdf.doc.setTextColor(color); |
269 this.pdf.doc.setDrawColor(this.$options.COLORS.LDC); | 190 this.pdf.doc.setDrawColor(COLORS.LDC); |
270 this.pdf.doc.setFillColor(this.$options.COLORS.LDC); | 191 this.pdf.doc.setFillColor(COLORS.LDC); |
271 this.pdf.doc.roundedRect(x, y, 10, 4, 1.5, 1.5, "FD"); | 192 this.pdf.doc.roundedRect(x, y, 10, 4, 1.5, 1.5, "FD"); |
272 this.pdf.doc.text(this.legend[0], x + 12, y + 3); | 193 this.pdf.doc.text(this.legend[0], x + 12, y + 3); |
273 | 194 |
274 this.pdf.doc.setDrawColor(this.$options.COLORS.REST[0]); | 195 this.pdf.doc.setDrawColor(COLORS.REST[0]); |
275 this.pdf.doc.setFillColor(this.$options.COLORS.REST[0]); | 196 this.pdf.doc.setFillColor(COLORS.REST[0]); |
276 this.pdf.doc.roundedRect(x, y + 5, 10, 4, 1.5, 1.5, "FD"); | 197 this.pdf.doc.roundedRect(x, y + 5, 10, 4, 1.5, 1.5, "FD"); |
277 this.pdf.doc.text(this.legend[1], x + 12, y + 8); | 198 this.pdf.doc.text(this.legend[1], x + 12, y + 8); |
278 | 199 |
279 this.pdf.doc.setDrawColor(this.$options.COLORS.REST[1]); | 200 this.pdf.doc.setDrawColor(COLORS.REST[1]); |
280 this.pdf.doc.setFillColor(this.$options.COLORS.REST[1]); | 201 this.pdf.doc.setFillColor(COLORS.REST[1]); |
281 this.pdf.doc.roundedRect(x, y + 10, 10, 4, 1.5, 1.5, "FD"); | 202 this.pdf.doc.roundedRect(x, y + 10, 10, 4, 1.5, 1.5, "FD"); |
282 this.pdf.doc.text(this.legend[2], x + 12, y + 13); | 203 this.pdf.doc.text(this.legend[2], x + 12, y + 13); |
283 | 204 |
284 this.pdf.doc.setDrawColor(this.$options.COLORS.HIGHEST); | 205 this.pdf.doc.setDrawColor(COLORS.HIGHEST); |
285 this.pdf.doc.setFillColor(this.$options.COLORS.HIGHEST); | 206 this.pdf.doc.setFillColor(COLORS.HIGHEST); |
286 this.pdf.doc.roundedRect(x, y + 15, 10, 4, 1.5, 1.5, "FD"); | 207 this.pdf.doc.roundedRect(x, y + 15, 10, 4, 1.5, 1.5, "FD"); |
287 this.pdf.doc.text(this.legend[3], x + 12, y + 18); | 208 this.pdf.doc.text(this.legend[3], x + 12, y + 18); |
288 }, | 209 }, |
289 legendStyle(index) { | 210 legendStyle(index) { |
290 return [ | 211 return [ |
291 `background-color: ${this.$options.COLORS.LDC};`, | 212 `background-color: ${COLORS.LDC};`, |
292 `background-color: ${this.$options.COLORS.HIGHEST};`, | 213 `background-color: ${COLORS.HIGHEST};`, |
293 `background-color: ${this.$options.COLORS.REST[1]};`, | 214 `background-color: ${COLORS.REST[1]};`, |
294 `background-color: ${this.$options.COLORS.REST[0]};` | 215 `background-color: ${COLORS.REST[0]};` |
295 ][index]; | 216 ][index]; |
296 }, | 217 }, |
297 close() { | 218 close() { |
298 this.$store.commit("application/paneSetup", "DEFAULT"); | 219 this.$store.commit("application/paneSetup", "DEFAULT"); |
299 }, | 220 }, |
300 getPrintLayout(svgHeight) { | 221 getPrintLayout() { |
301 return { | 222 return { |
302 main: { top: 0, right: 20, bottom: 50, left: 20 }, | 223 main: { top: 0, right: 20, bottom: 50, left: 20 } |
303 nav: { | |
304 top: svgHeight - 65, | |
305 right: 20, | |
306 bottom: 30, | |
307 left: 80 | |
308 } | |
309 }; | 224 }; |
310 }, | 225 }, |
311 drawDiagram() { | 226 drawDiagram() { |
312 const elem = document.querySelector("#" + this.containerId); | 227 const elem = document.querySelector("#" + this.containerId); |
313 const svgWidth = elem != null ? elem.clientWidth : 0; | 228 const svgWidth = elem != null ? elem.clientWidth : 0; |
314 const svgHeight = elem != null ? elem.clientHeight : 0; | 229 const svgHeight = elem != null ? elem.clientHeight : 0; |
315 const layout = this.getPrintLayout(svgHeight); | 230 const layout = this.getPrintLayout(); |
316 const dimensions = this.getDimensions({ | 231 const dimensions = this.getDimensions({ |
317 svgHeight, | 232 svgHeight, |
318 svgWidth, | 233 svgWidth, |
319 ...layout | 234 ...layout |
320 }); | 235 }); |
321 d3.select(".diagram-container svg").remove(); | 236 d3.select("#" + this.containerId + " svg").remove(); |
322 this.renderTo({ element: ".diagram-container", dimensions }); | 237 this.renderTo({ element: "#" + this.containerId, dimensions }); |
323 }, | 238 }, |
324 renderTo({ element, dimensions }) { | 239 renderTo({ element, dimensions }) { |
325 const diagram = d3 | 240 const diagram = d3 |
326 .select(element) | 241 .select(element) |
327 .append("svg") | 242 .append("svg") |
438 return yScale(0) - yScale(hoursInDays(d.height)); | 353 return yScale(0) - yScale(hoursInDays(d.height)); |
439 }) | 354 }) |
440 .attr("x", ldcOffset + spaceBetween / 2) | 355 .attr("x", ldcOffset + spaceBetween / 2) |
441 .attr("width", widthPerItem - ldcOffset - spaceBetween) | 356 .attr("width", widthPerItem - ldcOffset - spaceBetween) |
442 .attr("fill", (d, i) => { | 357 .attr("fill", (d, i) => { |
443 return this.$options.COLORS.REST[i]; | 358 return COLORS.REST[i]; |
444 }); | 359 }); |
445 }, | 360 }, |
446 fnheight({ name, yScale }) { | 361 fnheight({ name, yScale }) { |
447 return d => yScale(0) - yScale(hoursInDays(d[name])); | 362 return d => yScale(0) - yScale(hoursInDays(d[name])); |
448 }, | 363 }, |
477 .attr("width", widthPerItem - ldcOffset - spaceBetween) | 392 .attr("width", widthPerItem - ldcOffset - spaceBetween) |
478 .attr( | 393 .attr( |
479 "transform", | 394 "transform", |
480 d => `translate(0 ${this.paddingTop + -1 * height(d)})` | 395 d => `translate(0 ${this.paddingTop + -1 * height(d)})` |
481 ) | 396 ) |
482 .attr("fill", this.$options.COLORS.LDC) | 397 .attr("fill", COLORS.LDC) |
483 .attr("id", "ldc"); | 398 .attr("id", "ldc"); |
484 }, | 399 }, |
485 drawHighestLevel({ | 400 drawHighestLevel({ |
486 everyBar, | 401 everyBar, |
487 yScale, | 402 yScale, |
521 .attr("width", widthPerItem - ldcOffset - spaceBetween) | 436 .attr("width", widthPerItem - ldcOffset - spaceBetween) |
522 .attr( | 437 .attr( |
523 "transform", | 438 "transform", |
524 d => `translate(0 ${this.paddingTop + -1 * height(d)})` | 439 d => `translate(0 ${this.paddingTop + -1 * height(d)})` |
525 ) | 440 ) |
526 .attr("fill", this.$options.COLORS.HIGHEST); | 441 .attr("fill", COLORS.HIGHEST); |
527 }, | 442 }, |
528 drawLabelPerBar({ everyBar, dimensions, widthPerItem }) { | 443 drawLabelPerBar({ everyBar, dimensions, widthPerItem }) { |
529 everyBar | 444 everyBar |
530 .append("text") | 445 .append("text") |
531 .text(d => d.label) | 446 .text(d => d.label) |
535 .attr("font-size", 10); | 450 .attr("font-size", 10); |
536 }, | 451 }, |
537 drawScaleLabel({ diagram, dimensions }) { | 452 drawScaleLabel({ diagram, dimensions }) { |
538 diagram | 453 diagram |
539 .append("text") | 454 .append("text") |
540 .text(this.$options.LEGEND) | 455 .text(this.$gettext("Sum of days")) |
541 .attr("text-anchor", "middle") | 456 .attr("text-anchor", "middle") |
542 .attr("x", 0) | 457 .attr("x", 0) |
543 .attr("y", 0) | 458 .attr("y", 0) |
544 .attr("dy", "20") | 459 .attr("dy", "20") |
545 .attr( | 460 .attr( |
613 }, | 528 }, |
614 watch: { | 529 watch: { |
615 fwData() { | 530 fwData() { |
616 this.drawDiagram(); | 531 this.drawDiagram(); |
617 } | 532 } |
618 }, | |
619 LEGEND: app.$gettext("Sum of days"), | |
620 COLORS: { | |
621 LDC: "#cdcdcd", | |
622 HIGHEST: "#3675ff", | |
623 REST: ["#782121", "#ff6c6c", "#ffaaaa"] | |
624 } | 533 } |
625 }; | 534 }; |
626 </script> | 535 </script> |