Mercurial > gemma
view client/src/components/gauge/HydrologicalConditions.vue @ 2813:49c1570919ae
client: spuc8: use new endpoints to fetch year based and longterm waterlevel data
author | Markus Kottlaender <markus@intevation.de> |
---|---|
date | Tue, 26 Mar 2019 14:05:54 +0100 |
parents | 2b79c0871138 |
children | 12f053763be2 |
line wrap: on
line source
<template> <div class="d-flex flex-fill justify-content-center align-items-center diagram-container" > <div v-if="!longtermWaterlevels.length"> <translate>No data available.</translate> </div> </div> </template> <style lang="sass" scoped> .diagram-container /deep/ .hide opacity: 0 .line clip-path: url(#clip) stroke-width: 2 fill: none &.mean stroke: steelblue &.median stroke: black &.q25 stroke: orange &.q75 stroke: purple .area clip-path: url(#clip) stroke: none fill: lightsteelblue .hdc-line, .ldc-line, .mw-line stroke-width: 1 fill: none clip-path: url(#clip) .hdc-line stroke: red .ldc-line stroke: green .mw-line stroke: grey .ref-waterlevel-label font-size: 11px fill: #999 .tick line stroke-dasharray: 5 stroke: #ccc .zoom cursor: move fill: none pointer-events: all .brush .selection stroke: none fill-opacity: 0.2 .handle stroke: rgba($color-info, 0.5) fill: rgba($color-info, 0.5) </style> <script> /* This is Free Software under GNU Affero General Public License v >= 3.0 * without warranty, see README.md and license for details. * * SPDX-License-Identifier: AGPL-3.0-or-later * License-Filename: LICENSES/AGPL-3.0.txt * * Copyright (C) 2018 by via donau * – Österreichische Wasserstraßen-Gesellschaft mbH * Software engineering by Intevation GmbH * * Author(s): * Markus Kottländer <markus.kottlaender@intevation.de> */ import { mapState, mapGetters } from "vuex"; import * as d3 from "d3"; import debounce from "debounce"; import { startOfYear, endOfYear } from "date-fns"; export default { computed: { ...mapState("gauges", ["longtermWaterlevels", "yearWaterlevels"]), ...mapGetters("gauges", ["selectedGauge", "minMaxWaterlevelForDay"]) }, methods: { drawDiagram() { // TODO: Optimize code. I'm pretty sure all of this can be done in a much // more elegant way and with less lines of code. // remove old diagram d3.select(".diagram-container svg").remove(); if (!this.selectedGauge || !this.longtermWaterlevels.length) return; // get HDC/LDC/MW of the gauge let refWaterLevels = JSON.parse( this.selectedGauge.properties.reference_water_levels ); // CREATE SVG AND SET DIMENSIONS/MARGINS let svgWidth = document.querySelector(".diagram-container").clientWidth; let svgHeight = document.querySelector(".diagram-container").clientHeight; let svg = d3 .select(".diagram-container") .append("svg") .attr("width", "100%") .attr("height", "100%"); let mainMargin = { top: 20, right: 20, bottom: 110, left: 80 }, navMargin = { top: svgHeight - mainMargin.top - 65, right: 20, bottom: 30, left: 80 }, width = +svgWidth - mainMargin.left - mainMargin.right, mainHeight = +svgHeight - mainMargin.top - mainMargin.bottom, navHeight = +svgHeight - navMargin.top - navMargin.bottom; // PREPARING AXES/SCALING // scaling helpers to convert real values to pixels // based on the diagrams dimensions let x = d3.scaleTime().range([0, width]), x2 = d3.scaleTime().range([0, width]), y = d3.scaleLinear().range([mainHeight, 0]), y2 = d3.scaleLinear().range([navHeight, 0]); // find min/max values for the waterlevel axis // including HDC/LDC (+/- 1/8 HDC-LDC) let WaterlevelMinMax = d3.extent( [ ...this.longtermWaterlevels, { waterlevel: refWaterLevels.HDC + (refWaterLevels.HDC - refWaterLevels.LDC) / 8 }, { waterlevel: Math.max( refWaterLevels.LDC - (refWaterLevels.HDC - refWaterLevels.LDC) / 8, 0 ) } ], d => d.waterlevel ); // setting the min and max values for the diagram axes let yearStart = startOfYear(new Date()); let yearEnd = endOfYear(new Date()); x.domain(d3.extent([yearStart, yearEnd])); y.domain(WaterlevelMinMax); x2.domain(x.domain()); y2.domain(y.domain()); // creating the axes based on these scales let xAxis = d3 .axisTop(x) .tickSizeInner(mainHeight) .tickSizeOuter(0) .tickFormat(date => { // make the x-axis label formats dynamic, based on zoom // but never display year numbers since they don't make any sense in // this diagram return (d3.timeSecond(date) < date ? d3.timeFormat(".%L") : d3.timeMinute(date) < date ? d3.timeFormat(":%S") : d3.timeHour(date) < date ? d3.timeFormat("%I:%M") : d3.timeDay(date) < date ? d3.timeFormat("%I %p") : d3.timeMonth(date) < date ? d3.timeWeek(date) < date ? d3.timeFormat("%a %d") : d3.timeFormat("%b %d") : d3.timeFormat("%B"))(date); }); let xAxis2 = d3.axisBottom(x2).tickFormat(d3.timeFormat("%B")); let yAxis = d3 .axisRight(y) .tickSizeInner(width) .tickSizeOuter(0); // PREPARING CHART FUNCTIONS // waterlevel line charts in big chart const lineChart = type => d3 .line() .x(d => x(d.date)) .y(d => y(d[type])) .curve(d3.curveLinear); // overall min/max area chart const areaChart = d3 .area() .x(d => x(d.date)) .y0(d => y(d.min)) .y1(d => y(d.max)); // overall min/max area chart in nav const areaChartNav = d3 .area() .x(d => x2(d.date)) .y0(d => y2(d.min)) .y1(d => y2(d.max)); // hdc/ldc/mw let refWaterlevelLine = d3 .line() .x(d => x(d.x)) .y(d => y(d.y)); // DRAWING MAINCHART // define visible chart area // everything outside this area will be hidden (clipped) svg .append("defs") .append("clipPath") .attr("id", "clip") .append("rect") .attr("width", width) .attr("height", mainHeight); let mainChart = svg .append("g") .attr("class", "main") .attr("transform", `translate(${mainMargin.left}, ${mainMargin.top})`); // axes mainChart .append("g") .attr("class", "axis--x") .attr("transform", `translate(0, ${mainHeight})`) .call(xAxis) .selectAll(".tick text") .attr("y", 15); mainChart // label .append("text") .text(this.$gettext("Waterlevel [cm]")) .attr("text-anchor", "middle") .attr("transform", `translate(-45, ${mainHeight / 2}) rotate(-90)`); mainChart .append("g") .call(yAxis) .selectAll(".tick text") .attr("x", -25); // overall min/max area chart mainChart .append("path") .datum(this.longtermWaterlevels) .attr("class", "area") .attr("d", areaChart); // reference waterlevels // HDC mainChart .append("path") .datum([ { x: 0, y: refWaterLevels.HDC }, { x: yearEnd, y: refWaterLevels.HDC } ]) .attr("class", "hdc-line") .attr("d", refWaterlevelLine); mainChart // label .append("text") .text("HDC") .attr("class", "ref-waterlevel-label") .attr("x", x(yearEnd) - 20) .attr("y", y(refWaterLevels.HDC) - 3); // LDC mainChart .append("path") .datum([ { x: 0, y: refWaterLevels.LDC }, { x: yearEnd, y: refWaterLevels.LDC } ]) .attr("class", "ldc-line") .attr("d", refWaterlevelLine); mainChart // label .append("text") .text("LDC") .attr("class", "ref-waterlevel-label") .attr("x", x(yearEnd) - 20) .attr("y", y(refWaterLevels.LDC) - 3); // MW mainChart .append("path") .datum([ { x: 0, y: refWaterLevels.MW }, { x: yearEnd, y: refWaterLevels.MW } ]) .attr("class", "mw-line") .attr("d", refWaterlevelLine); mainChart // label .append("text") .text("MW") .attr("class", "ref-waterlevel-label") .attr("x", x(yearEnd) - 20) .attr("y", y(refWaterLevels.MW) - 3); // mean waterlevel chart mainChart .append("path") .attr("class", "line mean") .datum(this.longtermWaterlevels) .attr("d", lineChart("mean")); // median waterlevel chart mainChart .append("path") .attr("class", "line median") .datum(this.longtermWaterlevels) .attr("d", lineChart("median")); // q25 waterlevel chart mainChart .append("path") .attr("class", "line q25") .datum(this.longtermWaterlevels) .attr("d", lineChart("q25")); // q75 waterlevel chart mainChart .append("path") .attr("class", "line q75") .datum(this.longtermWaterlevels) .attr("d", lineChart("q75")); // DRAWING NAVCHART let navChart = svg .append("g") .attr("class", "nav") .attr("transform", `translate(${navMargin.left}, ${navMargin.top})`); // axis (nav chart only has x-axis) navChart .append("g") .attr("class", "axis axis--x") .attr("transform", `translate(0, ${navHeight})`) .call(xAxis2); // overall min/max area chart navChart .append("path") .datum(this.longtermWaterlevels) .attr("class", "area") .attr("d", areaChartNav); // INTERACTIVITY const updateChart = () => { mainChart.select(".line.mean").attr("d", lineChart("mean")); mainChart.select(".line.median").attr("d", lineChart("median")); mainChart.select(".line.q25").attr("d", lineChart("q25")); mainChart.select(".line.q75").attr("d", lineChart("q75")); mainChart.select(".area").attr("d", areaChart); mainChart .select(".axis--x") .call(xAxis) .selectAll(".tick text") .attr("y", 15); }; // selecting time period in nav chart let brush = d3 .brushX() .handleSize(4) .extent([[0, 0], [width, navHeight]]) .on("brush end", () => { if (d3.event.sourceEvent && d3.event.sourceEvent.type === "zoom") return; // ignore brush-by-zoom let s = d3.event.selection || x2.range(); x.domain(s.map(x2.invert, x2)); updateChart(); svg .select(".zoom") .call( zoom.transform, d3.zoomIdentity.scale(width / (s[1] - s[0])).translate(-s[0], 0) ); }); // zooming with mousewheel in main chart let zoom = d3 .zoom() .scaleExtent([1, Infinity]) .translateExtent([[0, 0], [width, mainHeight]]) .extent([[0, 0], [width, mainHeight]]) .on("zoom", () => { if (d3.event.sourceEvent && d3.event.sourceEvent.type === "brush") return; // ignore zoom-by-brush let t = d3.event.transform; x.domain(t.rescaleX(x2).domain()); updateChart(); navChart .select(".brush") .call(brush.move, x.range().map(t.invertX, t)); }); navChart .append("g") .attr("class", "brush") .call(brush) .call(brush.move, x.range()); svg .append("rect") .attr("class", "zoom") .attr("width", width) .attr("height", mainHeight) .attr("transform", `translate(${mainMargin.left}, ${mainMargin.top})`) .call(zoom); } }, created() { window.addEventListener("resize", debounce(this.drawDiagram), 100); }, mounted() { this.drawDiagram(); }, updated() { this.drawDiagram(); } }; </script>