view client/src/components/fairway/Fairwayprofile.vue @ 2549:9bf6b767a56a

client: refactored and improved splitscreen for diagrams To make different diagrams possible, the splitscreen view needed to be decoupled from the cross profiles. Also the style has changed to make it more consistent with the rest of the app. The standard box header is now used and there are collapse and expand animations.
author Markus Kottlaender <markus@intevation.de>
date Fri, 08 Mar 2019 08:50:47 +0100
parents 3c17d401fbd4
children 83b938bf4da9
line wrap: on
line source

<template>
  <div class="fairwayprofile m-3 mt-0 bg-white flex-grow-1"></div>
</template>

<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):
 * Thomas Junk <thomas.junk@intevation.de>
 * Markus Kottländer <markus.kottlaender@intevation.de>
 */
import * as d3 from "d3";
import { mapState, mapGetters } from "vuex";
import debounce from "debounce";

const GROUND_COLOR = "#4A2F06";

export default {
  name: "fairwayprofile",
  data() {
    return {
      coordinatesInput: "",
      coordinatesSelect: null,
      cutLabel: "",
      showLabelInput: false,
      width: null,
      height: null,
      margin: {
        top: 20,
        right: 40,
        bottom: 30,
        left: 40
      }
    };
  },
  computed: {
    ...mapGetters("fairwayprofile", ["totalLength"]),
    ...mapState("fairwayprofile", [
      "additionalSurvey",
      "currentProfile",
      "endPoint",
      "fairwayData",
      "minAlt",
      "maxAlt",
      "referenceWaterLevel",
      "selectedWaterLevel",
      "startPoint",
      "waterLevels"
    ]),
    ...mapState("bottlenecks", ["selectedSurvey"]),
    relativeWaterLevelDelta() {
      return this.selectedWaterLevel.value - this.referenceWaterLevel;
    },
    currentData() {
      if (
        !this.selectedSurvey ||
        !this.currentProfile.hasOwnProperty(this.selectedSurvey.date_info)
      )
        return [];
      return this.currentProfile[this.selectedSurvey.date_info].points;
    },
    additionalData() {
      if (
        !this.additionalSurvey ||
        !this.currentProfile.hasOwnProperty(this.additionalSurvey.date_info)
      )
        return [];
      return this.currentProfile[this.additionalSurvey.date_info].points;
    },
    waterColor() {
      return "#005DFF";
    },
    xScale() {
      return [0, this.totalLength];
    },
    yScaleRight() {
      //ToDO calcReleativeDepth(this.maxAlt) to get the
      // maximum depth according to the actual waterlevel
      // additionally: take the one which is higher reference or current waterlevel
      const DELTA = this.maxAlt * 1.1 - this.maxAlt;
      return [this.maxAlt * 1 + DELTA, -DELTA];
    }
  },
  watch: {
    currentData() {
      this.drawDiagram();
    },
    additionalData() {
      this.drawDiagram();
    },
    width() {
      this.drawDiagram();
    },
    height() {
      this.drawDiagram();
    },
    waterLevels() {
      this.drawDiagram();
    },
    selectedWaterLevel() {
      this.drawDiagram();
    },
    fairwayData() {
      this.drawDiagram();
    }
  },
  methods: {
    calcRelativeDepth(depth) {
      /* takes a depth value and substracts the delta of the relative waterlevel
       * say the reference level is above the current level, the ground is nearer,
       * thus, the depth is lower.
       *
       * E.g.:
       *
       * Reference waterlevel 5m, current 4m => delta = -1m
       * If the distance to the ground was 3m from the 5m mark
       * it is now only 2m from the current waterlevel.
       *
       *  Vice versa:
       *
       *  If the reference level is 5m and the current 6m => delta = +1m
       *  The ground is one meter farer away from the current waterlevel
       *
       */
      return depth - this.relativeWaterLevelDelta;
    },
    drawDiagram() {
      this.coordinatesSelect = null;
      const chartDiv = document.querySelector(".fairwayprofile");
      d3.select(".fairwayprofile svg").remove();
      this.scaleFairwayProfile();
      let svg = d3.select(chartDiv).append("svg");
      svg.attr("width", this.width);
      svg.attr("height", this.height);
      const width = this.width - this.margin.right - 1.5 * this.margin.left;
      const height = this.height - this.margin.top - 2 * this.margin.bottom;
      const currentData = this.currentData;
      const additionalData = this.additionalData;
      const { xScale, yScaleRight, graph } = this.generateCoordinates(
        svg,
        height,
        width
      );
      if (!this.height || !this.width) return; // do not try to render when height and width are unknown
      this.drawWaterlevel({ graph, xScale, yScaleRight, height });
      this.drawLabels({ graph, height });
      if (currentData) {
        this.drawProfile({
          graph,
          xScale,
          yScaleRight,
          currentData,
          height,
          color: GROUND_COLOR,
          strokeColor: "black",
          opacity: 1
        });
      }
      if (additionalData) {
        this.drawProfile({
          graph,
          xScale,
          yScaleRight,
          currentData: additionalData,
          height,
          color: GROUND_COLOR,
          strokeColor: "#943007",
          opacity: 0.6
        });
      }
      this.drawFairway({ graph, xScale, yScaleRight });
    },
    drawFairway({ graph, xScale, yScaleRight }) {
      if (this.fairwayData === undefined) {
        return;
      }
      for (let data of this.fairwayData) {
        const [startPoint, endPoint, depth] = data.coordinates[0];
        const style = data.style();
        let fairwayArea = d3
          .area()
          .x(function(d) {
            return xScale(d.x);
          })
          .y0(yScaleRight(0))
          .y1(function(d) {
            return yScaleRight(d.y);
          });
        graph
          .append("path")
          .datum([{ x: startPoint, y: depth }, { x: endPoint, y: depth }])
          .attr("fill", "#002AFF")
          .attr("fill-opacity", 0.65)
          .attr("stroke", style[0].getStroke().getColor())
          .attr("d", fairwayArea);
      }
    },
    drawLabels({ graph, height }) {
      graph
        .append("text")
        .attr("transform", ["rotate(-90)"])
        .attr("y", this.width - 60)
        .attr("x", -(this.height - this.margin.top - this.margin.bottom) / 2)
        .attr("dy", "1em")
        .attr("fill", "black")
        .style("text-anchor", "middle")
        .text("Depth [m]");
      graph
        .append("text")
        .attr("y", 0 - this.margin.left)
        .attr("x", 0 - height / 4)
        .attr("dy", "1em")
        .attr("fill", "black")
        .style("text-anchor", "middle")
        .attr("transform", [
          "translate(" + this.width / 2 + "," + this.height + ")",
          "rotate(0)"
        ])
        .text("Width [m]");
    },
    generateCoordinates(svg, height, width) {
      let xScale = d3
        .scaleLinear()
        .domain(this.xScale)
        .rangeRound([0, width]);

      xScale.ticks(5);

      let yScaleRight = d3
        .scaleLinear()
        .domain(this.yScaleRight)
        .rangeRound([height, 0]);

      let xAxis = d3.axisBottom(xScale);
      let yAxis2 = d3.axisRight(yScaleRight);
      let graph = svg
        .append("g")
        .attr(
          "transform",
          "translate(" + this.margin.left + "," + this.margin.top + ")"
        );
      graph
        .append("g")
        .attr("transform", "translate(0," + height + ")")
        .call(xAxis.ticks(5));
      graph
        .append("g")
        .attr("transform", "translate(" + width + ",0)")
        .call(yAxis2);
      return { xScale, yScaleRight, graph };
    },
    drawWaterlevel({ graph, xScale, yScaleRight, height }) {
      let waterArea = d3
        .area()
        .x(function(d) {
          return xScale(d.x);
        })
        .y0(height)
        .y1(function(d) {
          return yScaleRight(d.y);
        });
      graph
        .append("path")
        .datum([{ x: 0, y: 0 }, { x: this.totalLength, y: 0 }])
        .attr("fill-opacity", 0.65)
        .attr("fill", this.waterColor)
        .attr("stroke", this.waterColor)
        .attr("d", waterArea);
    },
    drawProfile({
      graph,
      xScale,
      yScaleRight,
      currentData,
      height,
      color,
      strokeColor,
      opacity
    }) {
      for (let part of currentData) {
        let profileLine = d3
          .line()
          .x(d => {
            return xScale(d.x);
          })
          .y(d => {
            return yScaleRight(d.y);
          });
        let profileArea = d3
          .area()
          .x(function(d) {
            return xScale(d.x);
          })
          .y0(height)
          .y1(function(d) {
            return yScaleRight(d.y);
          });
        graph
          .append("path")
          .datum(part)
          .attr("fill", color)
          .attr("stroke", color)
          .attr("stroke-width", 3)
          .attr("stroke-opacity", opacity)
          .attr("fill-opacity", opacity)
          .attr("d", profileArea);
        graph
          .append("path")
          .datum(part)
          .attr("fill", "none")
          .attr("stroke", strokeColor)
          .attr("stroke-linejoin", "round")
          .attr("stroke-linecap", "round")
          .attr("stroke-width", 3)
          .attr("stroke-opacity", opacity)
          .attr("fill-opacity", opacity)
          .attr("d", profileLine);
      }
    },
    scaleFairwayProfile() {
      if (!document.querySelector(".fairwayprofile")) return;
      const clientHeight = document.querySelector(".fairwayprofile")
        .clientHeight;
      const clientWidth = document.querySelector(".fairwayprofile").clientWidth;
      if (!clientHeight || !clientWidth) return;
      this.height = clientHeight;
      this.width = clientWidth;
    }
  },
  created() {
    window.addEventListener("resize", debounce(this.drawDiagram), 100);
  },
  mounted() {
    this.drawDiagram();
  },
  updated() {
    this.scaleFairwayProfile();
  },
  destroyed() {
    window.removeEventListener("resize", debounce(this.drawDiagram));
  }
};
</script>