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