view client/src/components/gauge/Waterlevel.vue @ 2624:9dbaf69c7a66

Improve geoserver config to better calculate bounding boxes * Disable the use of estimated extents for the postgis storage configuration for geoserver, which is set via the gemma middleware. This way we are able to get better bounding boxes for many layers where the postgis function `ST_EstimatedExtent()` would be far off.
author Bernhard Reiter <bernhard@intevation.de>
date Wed, 13 Mar 2019 16:18:39 +0100
parents e8c97481438f
children a735119e4f5c
line wrap: on
line source

<template>
  <div class="flex-fill diagram-container"></div>
</template>

<style lang="sass" scoped>
.diagram-container
  /deep/
    .line
      stroke: steelblue
      stroke-width: 2
      fill: transparent
      clip-path: url(#clip)

    .zoom
      cursor: move
      fill: none
      pointer-events: all

    .hdc-line,
    .ldc-line,
    .mw-line
      stroke-width: 1
      fill: transparent
      clip-path: url(#clip)
    .hdc-line
      stroke: red
    .ldc-line
      stroke: green
    .mw-line
      stroke: grey
    .ref-waterlevel-label
      font-size: 11px
      fill: #999
    .brush
      .selection
        stroke: transparent
        fill-opacity: 0.2
      .handle
        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";

export default {
  computed: {
    ...mapState("gauges", ["waterlevels"]),
    ...mapGetters("gauges", ["selectedGauge"])
  },
  watch: {
    waterlevels() {
      this.drawDiagram();
    }
  },
  methods: {
    drawDiagram() {
      if (!this.selectedGauge || !this.waterlevels.length) return;

      // remove old diagram
      d3.select(".diagram-container svg").remove();

      // 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: 50, right: 20, bottom: 110, left: 40 },
        navMargin = {
          top: svgHeight - mainMargin.top - 35,
          right: 20,
          bottom: 30,
          left: 40
        },
        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 (+/- 100 cm)
      let WaterlevelMinMax = d3.extent(
        [
          ...this.waterlevels,
          { waterlevel: refWaterLevels.HDC + 100 },
          { waterlevel: Math.max(refWaterLevels.LDC - 100, 0) }
        ],
        d => d.waterlevel
      );
      // setting the min and max values for the diagram axes
      x.domain(d3.extent(this.waterlevels, d => d.date));
      y.domain(WaterlevelMinMax);
      x2.domain(x.domain());
      y2.domain(y.domain());
      // creating the axes based on these scales
      let xAxis = d3.axisBottom(x),
        xAxis2 = d3.axisBottom(x2),
        yAxis = d3.axisLeft(y);

      // PREPARING CHART FUNCTIONS

      // waterlevel line in big chart
      let mainLineChart = d3
        .line()
        .curve(d3.curveMonotoneX)
        .x(d => x(d.date))
        .y(d => y(d.waterlevel));
      // waterlevel line in small chart
      let navLineChart = d3
        .line()
        .curve(d3.curveMonotoneX)
        .x(d => x2(d.date))
        .y(d => y2(d.waterlevel));
      // hdc/ldc/mw
      let refWaterlevelLine = d3
        .line()
        .x(d => x(d.x))
        .y(d => y(d.y));

      // DRAWING MAINCHART

      // define visible 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("transform", `translate(${mainMargin.left}, ${mainMargin.top})`);

      // axes
      mainChart
        .append("g")
        .attr("transform", `translate(0, ${mainHeight})`)
        .call(xAxis);
      mainChart.append("g").call(yAxis);

      // waterlevel chart
      mainChart
        .append("path")
        .datum(this.waterlevels)
        .attr("class", "line")
        .attr("d", mainLineChart);

      // reference waterlevels
      let lastDate = this.waterlevels[this.waterlevels.length - 1].date;
      // HDC
      mainChart
        .append("path")
        .datum([
          { x: 0, y: refWaterLevels.HDC },
          { x: lastDate, y: refWaterLevels.HDC }
        ])
        .attr("class", "hdc-line")
        .attr("d", refWaterlevelLine);
      mainChart // label
        .append("text")
        .text("HDC")
        .attr("class", "ref-waterlevel-label")
        .attr("x", x(lastDate) - 20)
        .attr("y", y(refWaterLevels.HDC) - 3);
      // LDC
      mainChart
        .append("path")
        .datum([
          { x: 0, y: refWaterLevels.LDC },
          { x: lastDate, y: refWaterLevels.LDC }
        ])
        .attr("class", "ldc-line")
        .attr("d", refWaterlevelLine);
      mainChart // label
        .append("text")
        .text("LDC")
        .attr("class", "ref-waterlevel-label")
        .attr("x", x(lastDate) - 20)
        .attr("y", y(refWaterLevels.LDC) - 3);
      // MW
      mainChart
        .append("path")
        .datum([
          { x: 0, y: refWaterLevels.MW },
          { x: lastDate, y: refWaterLevels.MW }
        ])
        .attr("class", "mw-line")
        .attr("d", refWaterlevelLine);
      mainChart // label
        .append("text")
        .text("MW")
        .attr("class", "ref-waterlevel-label")
        .attr("x", x(lastDate) - 20)
        .attr("y", y(refWaterLevels.MW) - 3);

      // DRAWING NAVCHART

      let navChart = svg
        .append("g")
        .attr("transform", `translate(${navMargin.left}, ${navMargin.top})`);

      // axis (nav chart only has y-axis)
      navChart
        .append("g")
        .attr("class", "axis axis--x")
        .attr("transform", `translate(0, ${navHeight})`)
        .call(xAxis2);

      // waterlevel chart
      navChart
        .append("path")
        .datum(this.waterlevels)
        .attr("class", "line")
        .attr("d", navLineChart);

      // INTERACTIVITY

      // 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));
          mainChart.select(".line").attr("d", mainLineChart);
          mainChart.select(".axis--x").call(xAxis);
          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());
          mainChart.select(".line").attr("d", mainLineChart);
          mainChart.select(".axis--x").call(xAxis);
          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>