Mercurial > gemma
comparison client/src/components/gauge/HydrologicalConditions.vue @ 2791:2b79c0871138
client: spuc8: draw diagrams
The line and area charts from spuc 8 are being drawn but there's a lot missing like tooltips
and toggeling individual charts.
author | Markus Kottlaender <markus@intevation.de> |
---|---|
date | Mon, 25 Mar 2019 12:07:00 +0100 |
parents | 71e7237110ba |
children | 49c1570919ae |
comparison
equal
deleted
inserted
replaced
2790:563bcd8b7d7b | 2791:2b79c0871138 |
---|---|
9 </template> | 9 </template> |
10 | 10 |
11 <style lang="sass" scoped> | 11 <style lang="sass" scoped> |
12 .diagram-container | 12 .diagram-container |
13 /deep/ | 13 /deep/ |
14 .hide | |
15 opacity: 0 | |
14 .line | 16 .line |
15 clip-path: url(#clip) | 17 clip-path: url(#clip) |
18 stroke-width: 2 | |
19 fill: none | |
20 &.mean | |
21 stroke: steelblue | |
22 &.median | |
23 stroke: black | |
24 &.q25 | |
25 stroke: orange | |
26 &.q75 | |
27 stroke: purple | |
28 .area | |
29 clip-path: url(#clip) | |
30 stroke: none | |
31 fill: lightsteelblue | |
16 | 32 |
17 .hdc-line, | 33 .hdc-line, |
18 .ldc-line, | 34 .ldc-line, |
19 .mw-line | 35 .mw-line |
20 stroke-width: 1 | 36 stroke-width: 1 |
62 * Author(s): | 78 * Author(s): |
63 * Markus Kottländer <markus.kottlaender@intevation.de> | 79 * Markus Kottländer <markus.kottlaender@intevation.de> |
64 */ | 80 */ |
65 | 81 |
66 import { mapState, mapGetters } from "vuex"; | 82 import { mapState, mapGetters } from "vuex"; |
67 import * as d3Base from "d3"; | 83 import * as d3 from "d3"; |
68 import debounce from "debounce"; | 84 import debounce from "debounce"; |
69 import { lineChunked } from "d3-line-chunked"; | |
70 import { startOfYear, endOfYear } from "date-fns"; | 85 import { startOfYear, endOfYear } from "date-fns"; |
71 | |
72 // we should load only d3 modules we need but for now we'll go with the lazy way | |
73 // https://www.giacomodebidda.com/how-to-import-d3-plugins-with-webpack/ | |
74 const d3 = Object.assign(d3Base, { lineChunked }); | |
75 | 86 |
76 export default { | 87 export default { |
77 computed: { | 88 computed: { |
78 ...mapState("gauges", ["meanWaterlevels"]), | 89 ...mapState("gauges", ["meanWaterlevels"]), |
79 ...mapGetters("gauges", ["selectedGauge", "minMaxWaterlevelForDay"]) | 90 ...mapGetters("gauges", ["selectedGauge", "minMaxWaterlevelForDay"]) |
156 let xAxis = d3 | 167 let xAxis = d3 |
157 .axisTop(x) | 168 .axisTop(x) |
158 .tickSizeInner(mainHeight) | 169 .tickSizeInner(mainHeight) |
159 .tickSizeOuter(0) | 170 .tickSizeOuter(0) |
160 .tickFormat(date => { | 171 .tickFormat(date => { |
172 // make the x-axis label formats dynamic, based on zoom | |
173 // but never display year numbers since they don't make any sense in | |
174 // this diagram | |
161 return (d3.timeSecond(date) < date | 175 return (d3.timeSecond(date) < date |
162 ? d3.timeFormat(".%L") | 176 ? d3.timeFormat(".%L") |
163 : d3.timeMinute(date) < date | 177 : d3.timeMinute(date) < date |
164 ? d3.timeFormat(":%S") | 178 ? d3.timeFormat(":%S") |
165 : d3.timeHour(date) < date | 179 : d3.timeHour(date) < date |
178 .tickSizeInner(width) | 192 .tickSizeInner(width) |
179 .tickSizeOuter(0); | 193 .tickSizeOuter(0); |
180 | 194 |
181 // PREPARING CHART FUNCTIONS | 195 // PREPARING CHART FUNCTIONS |
182 | 196 |
183 // points are "next to each other" when they are exactly 1 day apart | 197 // waterlevel line charts in big chart |
184 const isNext = (prev, current) => | 198 const lineChart = type => |
185 current.date - prev.date === 24 * 60 * 60 * 1000; | 199 d3 |
186 | 200 .line() |
187 // waterlevel line in big chart | 201 .x(d => x(d.date)) |
188 // d3-line-chunked plugin: https://github.com/pbeshai/d3-line-chunked | 202 .y(d => y(d[type])) |
189 var mainLineChart = d3 | 203 .curve(d3.curveLinear); |
190 .lineChunked() | 204 |
205 // overall min/max area chart | |
206 const areaChart = d3 | |
207 .area() | |
191 .x(d => x(d.date)) | 208 .x(d => x(d.date)) |
192 .y(d => y(d.waterlevel)) | 209 .y0(d => y(d.min)) |
193 .curve(d3.curveLinear) | 210 .y1(d => y(d.max)); |
194 .isNext(isNext) | 211 |
195 .pointAttrs({ r: 2.2 }); | 212 // overall min/max area chart in nav |
196 // waterlevel line in small chart | 213 const areaChartNav = d3 |
197 let navLineChart = d3 | 214 .area() |
198 .lineChunked() | |
199 .x(d => x2(d.date)) | 215 .x(d => x2(d.date)) |
200 .y(d => y2(d.waterlevel)) | 216 .y0(d => y2(d.min)) |
201 .curve(d3.curveMonotoneX) | 217 .y1(d => y2(d.max)); |
202 .isNext(isNext) | 218 |
203 .pointAttrs({ r: 1.7 }); | |
204 // hdc/ldc/mw | 219 // hdc/ldc/mw |
205 let refWaterlevelLine = d3 | 220 let refWaterlevelLine = d3 |
206 .line() | 221 .line() |
207 .x(d => x(d.x)) | 222 .x(d => x(d.x)) |
208 .y(d => y(d.y)); | 223 .y(d => y(d.y)); |
240 mainChart | 255 mainChart |
241 .append("g") | 256 .append("g") |
242 .call(yAxis) | 257 .call(yAxis) |
243 .selectAll(".tick text") | 258 .selectAll(".tick text") |
244 .attr("x", -25); | 259 .attr("x", -25); |
260 | |
261 // overall min/max area chart | |
262 mainChart | |
263 .append("path") | |
264 .datum(this.meanWaterlevels) | |
265 .attr("class", "area") | |
266 .attr("d", areaChart); | |
245 | 267 |
246 // reference waterlevels | 268 // reference waterlevels |
247 // HDC | 269 // HDC |
248 mainChart | 270 mainChart |
249 .append("path") | 271 .append("path") |
288 .text("MW") | 310 .text("MW") |
289 .attr("class", "ref-waterlevel-label") | 311 .attr("class", "ref-waterlevel-label") |
290 .attr("x", x(yearEnd) - 20) | 312 .attr("x", x(yearEnd) - 20) |
291 .attr("y", y(refWaterLevels.MW) - 3); | 313 .attr("y", y(refWaterLevels.MW) - 3); |
292 | 314 |
293 // waterlevel chart | 315 // mean waterlevel chart |
294 mainChart | 316 mainChart |
295 .append("g") | 317 .append("path") |
296 .attr("class", "line") | 318 .attr("class", "line mean") |
297 .datum([]) | 319 .datum(this.meanWaterlevels) |
298 .call(mainLineChart); | 320 .attr("d", lineChart("mean")); |
321 // median waterlevel chart | |
322 mainChart | |
323 .append("path") | |
324 .attr("class", "line median") | |
325 .datum(this.meanWaterlevels) | |
326 .attr("d", lineChart("median")); | |
327 // q25 waterlevel chart | |
328 mainChart | |
329 .append("path") | |
330 .attr("class", "line q25") | |
331 .datum(this.meanWaterlevels) | |
332 .attr("d", lineChart("q25")); | |
333 // q75 waterlevel chart | |
334 mainChart | |
335 .append("path") | |
336 .attr("class", "line q75") | |
337 .datum(this.meanWaterlevels) | |
338 .attr("d", lineChart("q75")); | |
299 | 339 |
300 // DRAWING NAVCHART | 340 // DRAWING NAVCHART |
301 | 341 |
302 let navChart = svg | 342 let navChart = svg |
303 .append("g") | 343 .append("g") |
309 .append("g") | 349 .append("g") |
310 .attr("class", "axis axis--x") | 350 .attr("class", "axis axis--x") |
311 .attr("transform", `translate(0, ${navHeight})`) | 351 .attr("transform", `translate(0, ${navHeight})`) |
312 .call(xAxis2); | 352 .call(xAxis2); |
313 | 353 |
314 // waterlevel chart | 354 // overall min/max area chart |
315 navChart | 355 navChart |
316 .append("g") | 356 .append("path") |
317 .attr("class", "line") | 357 .datum(this.meanWaterlevels) |
318 .datum([]) | 358 .attr("class", "area") |
319 .call(navLineChart); | 359 .attr("d", areaChartNav); |
320 | 360 |
321 // INTERACTIVITY | 361 // INTERACTIVITY |
362 | |
363 const updateChart = () => { | |
364 mainChart.select(".line.mean").attr("d", lineChart("mean")); | |
365 mainChart.select(".line.median").attr("d", lineChart("median")); | |
366 mainChart.select(".line.q25").attr("d", lineChart("q25")); | |
367 mainChart.select(".line.q75").attr("d", lineChart("q75")); | |
368 mainChart.select(".area").attr("d", areaChart); | |
369 mainChart | |
370 .select(".axis--x") | |
371 .call(xAxis) | |
372 .selectAll(".tick text") | |
373 .attr("y", 15); | |
374 }; | |
322 | 375 |
323 // selecting time period in nav chart | 376 // selecting time period in nav chart |
324 let brush = d3 | 377 let brush = d3 |
325 .brushX() | 378 .brushX() |
326 .handleSize(4) | 379 .handleSize(4) |
328 .on("brush end", () => { | 381 .on("brush end", () => { |
329 if (d3.event.sourceEvent && d3.event.sourceEvent.type === "zoom") | 382 if (d3.event.sourceEvent && d3.event.sourceEvent.type === "zoom") |
330 return; // ignore brush-by-zoom | 383 return; // ignore brush-by-zoom |
331 let s = d3.event.selection || x2.range(); | 384 let s = d3.event.selection || x2.range(); |
332 x.domain(s.map(x2.invert, x2)); | 385 x.domain(s.map(x2.invert, x2)); |
333 mainChart.select(".line").call(mainLineChart); | 386 updateChart(); |
334 mainChart | |
335 .select(".axis--x") | |
336 .call(xAxis) | |
337 .selectAll(".tick text") | |
338 .attr("y", 15); | |
339 svg | 387 svg |
340 .select(".zoom") | 388 .select(".zoom") |
341 .call( | 389 .call( |
342 zoom.transform, | 390 zoom.transform, |
343 d3.zoomIdentity.scale(width / (s[1] - s[0])).translate(-s[0], 0) | 391 d3.zoomIdentity.scale(width / (s[1] - s[0])).translate(-s[0], 0) |
353 .on("zoom", () => { | 401 .on("zoom", () => { |
354 if (d3.event.sourceEvent && d3.event.sourceEvent.type === "brush") | 402 if (d3.event.sourceEvent && d3.event.sourceEvent.type === "brush") |
355 return; // ignore zoom-by-brush | 403 return; // ignore zoom-by-brush |
356 let t = d3.event.transform; | 404 let t = d3.event.transform; |
357 x.domain(t.rescaleX(x2).domain()); | 405 x.domain(t.rescaleX(x2).domain()); |
358 mainChart.select(".line").call(mainLineChart); | 406 updateChart(); |
359 mainChart | |
360 .select(".axis--x") | |
361 .call(xAxis) | |
362 .selectAll(".tick text") | |
363 .attr("y", 15); | |
364 navChart | 407 navChart |
365 .select(".brush") | 408 .select(".brush") |
366 .call(brush.move, x.range().map(t.invertX, t)); | 409 .call(brush.move, x.range().map(t.invertX, t)); |
367 }); | 410 }); |
368 | 411 |