view client/src/fairway/Fairwayprofile.vue @ 1146:74e180ad3d6b

fairway profile UI improvements splitscreen button position at top of profile container bottleneck name and survey date as headline in profile container moved logout button to sidebar menu to avoid unnecessary overlapping
author Markus Kottlaender <markus@intevation.de>
date Tue, 13 Nov 2018 11:12:12 +0100
parents dc3f0277628a
children fb5c83d4ea1d
line wrap: on
line source

<template>
    <div :class="['position-relative', {show: showSplitscreen}]" v-if="Object.keys(currentProfile).length">
        <button
            class="rounded bg-white border-0 position-absolute splitscreen-toggle shadow"
            @click="$store.commit('application/showSplitscreen', false)"
            v-if="showSplitscreen">
            <i class="fa fa-angle-down"></i>
        </button>
        <button
            class="rounded bg-white border-0 position-absolute clear-selection shadow"
            @click="$store.dispatch('fairwayprofile/clearSelection');"
            v-if="showSplitscreen">
            <i class="fa fa-times text-danger"></i>
        </button>
        <div class="profile d-flex flex-column">
            <h5 class="mb-0 mt-2">{{ selectedBottleneck }} ({{ selectedSurvey.date_info }})</h5>
            <div class="d-flex flex-fill">
                <div class="fairwayprofile flex-fill"></div>
                <div class="additionalsurveys d-flex flex-column">
                    <small class="label">Available Additional Surveys</small>
                    <select v-model="additionalSurvey">
                        <option value="">None</option>
                        <option
                            v-for="survey in additionalSurveys"
                            :key="survey.date_info"
                            :value="survey"
                        >{{survey.date_info}}</option>
                    </select>
                    <small class="mt-2">
                        <b>Start:</b>
                        <br>
                        Lat: {{ startPoint[1] }}
                        <br>
                        Lon: {{ startPoint[0] }}
                        <br>
                        <b>End:</b>
                        <br>
                        Lat: {{ endPoint[1] }}
                        <br>
                        Lon: {{ endPoint[0] }}
                        <br>
                    </small>
                </div>
            </div>
        </div>
    </div>
</template>

<style lang="sass" scoped>
.profile
  background-color: white
  width: 100vw
  height: 0
  overflow: hidden
  position: relative
  z-index: 2

.splitscreen-toggle,
.clear-selection
  top: -$icon-height
  right: $icon-width + $offset + $offset
  width: $icon-width
  height: $icon-height
  margin-top: 2px
  z-index: 1
  outline: none

.clear-selection
  right: $offset

.show
  .profile
    height: 50vh

.label
  margin-bottom: $small-offset

.waterlevelselection
  margin-top: $large-offset
  margin-right: $large-offset

.additionalsurveys
  width: 200px
  margin-top: $large-offset
  margin-bottom: auto
  margin-right: $large-offset
  margin-left: auto

.additionalsurveys input
  margin-right: $small-offset

.fairwayprofile
  background-color: white
  margin: $offset
  margin-top: 0
</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):
 * Thomas Junk <thomas.junk@intevation.de>
 */
import * as d3 from "d3";
import { mapState } from "vuex";
import { displayError } from "../application/lib/errors.js";

const GROUND_COLOR = "#4A2F06";

export default {
  name: "fairwayprofile",
  props: [
    "width",
    "height",
    "xScale",
    "yScaleLeft",
    "yScaleRight",
    "margin",
    "additionalSurveys"
  ],
  computed: {
    ...mapState("application", ["showSplitscreen"]),
    ...mapState("fairwayprofile", [
      "startPoint",
      "endPoint",
      "currentProfile",
      "minAlt",
      "maxAlt",
      "totalLength",
      "fairwayCoordinates",
      "waterLevels",
      "selectedWaterLevel"
    ]),
    ...mapState("bottlenecks", ["selectedBottleneck", "selectedSurvey"]),
    additionalSurvey: {
      get() {
        return this.$store.getters["fairwayprofile/additionalSurvey"];
      },
      set(value) {
        this.$store.commit("fairwayprofile/setAdditionalSurvey", value);
        this.selectAdditionalSurveyData();
      }
    },
    currentData() {
      if (
        !this.selectedSurvey ||
        !this.currentProfile.hasOwnProperty(this.selectedSurvey.date_info)
      )
        return [];
      return this.currentProfile[this.selectedSurvey.date_info];
    },
    additionalData() {
      if (
        !this.additionalSurvey ||
        !this.currentProfile.hasOwnProperty(this.additionalSurvey.date_info)
      )
        return [];
      return this.currentProfile[this.additionalSurvey.date_info];
    },
    waterColor() {
      const result = this.waterLevels.find(
        x => x.level === this.selectedWaterLevel
      );
      return result.color;
    }
  },
  data() {
    return {
      wait: false
    };
  },
  watch: {
    showSplitscreen() {
      this.drawDiagram();
    },
    currentData() {
      this.drawDiagram();
    },
    width() {
      this.drawDiagram();
    },
    height() {
      this.drawDiagram();
    },
    waterLevels() {
      this.drawDiagram();
    },
    selectedWaterLevel() {
      this.drawDiagram();
    },
    fairwayCoordinates() {
      this.drawDiagram();
    }
  },
  methods: {
    selectAdditionalSurveyData() {
      if (
        !this.additionalSurvey ||
        this.wait ||
        this.currentProfile[this.additionalSurvey.date_info]
      ) {
        this.drawDiagram();
        return;
      }
      this.$store
        .dispatch("fairwayprofile/loadProfile", this.additionalSurvey)
        .then(() => {
          this.wait = false;
        })
        .catch(error => {
          this.wait = false;
          let status = "ERROR";
          let data = error;
          const response = error.response;
          if (response) {
            status = response.status;
            data = response.data;
          }
          displayError({
            title: "Backend Error",
            message: `${status}: ${data.message || data}`
          });
        });
    },
    drawDiagram() {
      const chartDiv = document.querySelector(".fairwayprofile");
      d3.select("svg").remove();
      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,
        yScaleLeft,
        graph
      } = this.generateCoordinates(svg, height, width);
      this.drawWaterlevel({
        graph,
        xScale,
        yScaleRight,
        height,
        width
      });
      if (currentData) {
        this.drawProfile({
          graph,
          xScale,
          yScaleRight,
          currentData,
          height,
          width,
          color: GROUND_COLOR,
          strokeColor: "black",
          opacity: 1
        });
      }
      if (additionalData) {
        this.drawProfile({
          graph,
          xScale,
          yScaleRight,
          currentData: additionalData,
          height,
          width,
          color: GROUND_COLOR,
          strokeColor: "#943007",
          opacity: 0.6
        });
      }
      this.drawLabels({
        graph,
        xScale,
        yScaleLeft,
        currentData,
        height,
        width
      });
      this.drawFairway({
        graph,
        xScale,
        yScaleRight,
        currentData,
        height,
        width
      });
    },
    drawFairway({ graph, xScale, yScaleRight }) {
      for (let coordinates of this.fairwayCoordinates) {
        const [startPoint, endPoint, depth] = coordinates;
        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("stroke-opacity", 0.65)
          .attr("fill-opacity", 0.65)
          .attr("stroke", "#FFD20D")
          .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 yScaleLeft = d3
        .scaleLinear()
        .domain(this.yScaleLeft)
        .rangeRound([height, 0]);

      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, yScaleLeft, 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", 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);
      }
    }
  },
  mounted() {
    this.drawDiagram();
  }
};
</script>