diff client/src/components/TimeSlider.vue @ 5036:8f421cd3c746 time-sliding

client: Implemented first version of time-sliding
author Fadi Abbud <fadi.abbud@intevation.de>
date Thu, 27 Feb 2020 09:18:17 +0100
parents
children 9d288d9b851b
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/TimeSlider.vue	Thu Feb 27 09:18:17 2020 +0100
@@ -0,0 +1,172 @@
+<template>
+  <div
+    id="slider"
+    :class="[
+      'd-flex box ui-element rounded bg-white mw-100 flex-row',
+      { expand: showTimeSlider }
+    ]"
+  >
+    <div
+      id="sliderContainer"
+      class="d-flex sliderContainer"
+      style="width: 98%;"
+    ></div>
+    <div @click="close" class="d-flex box-control mr-0" style="width: 2%;">
+      <font-awesome-icon icon="times"></font-awesome-icon>
+    </div>
+  </div>
+</template>
+<style lang="sass">
+#slider
+  position: absolute
+  bottom: 0
+  width: 100%
+  &.expand
+    max-height: 100%;
+    max-width: 100%;
+    margin: 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) 2020 by via donau
+ *   – Österreichische Wasserstraßen-Gesellschaft mbH
+ * Software engineering by Intevation GmbH
+ *
+ * Author(s):
+ * Fadi Abbud <fadiabbud@intevation.de>
+ */
+import { mapState } from "vuex";
+import * as d3 from "d3";
+export default {
+  name: "timeslider",
+  data() {
+    return {
+      selectedTime: new Date("2020-01-04"),
+      newX: null
+    };
+  },
+  computed: {
+    ...mapState("application", ["showTimeSlider"])
+  },
+  methods: {
+    close() {
+      this.$store.commit("application/showTimeSlider", false);
+    },
+    createSlider() {
+      const element = document.getElementById("sliderContainer");
+      const svgWidth = element ? element.clientWidth : 0,
+        svgHeight = 40,
+        marginTop = 20,
+        marginLeft = 0;
+
+      this.newX = this.getScale();
+      let svg = d3
+        .select(".sliderContainer")
+        .append("svg")
+        .attr("width", svgWidth)
+        .attr("height", svgHeight);
+
+      // zoom event
+      let zoom = d3
+        .zoom()
+        .scaleExtent([0, Infinity])
+        .translateExtent([[0, 0], [svgWidth, svgHeight]])
+        .extent([[0, 0], [(svgWidth, svgHeight)]])
+        .on("zoom", this.zoomed);
+
+      svg
+        .append("g")
+        .attr("class", "axis--x")
+        .attr("transform", `translate(${marginLeft}, ${marginTop})`)
+        .call(
+          d3.axisBottom(this.newX).ticks(12)
+          //.tickFormat(dFormat)
+        );
+
+      // create rectanlge on the slider area to capture mouse events
+      const eventRect = svg
+        .append("rect")
+        .attr("id", "zoom")
+        .attr("class", "zoom")
+        .attr("width", svgWidth)
+        .attr("height", svgHeight)
+        .attr("fill", "white")
+        .attr("opacity", 0.2)
+        .on("mouseover", () => {
+          svg.select(".zoom").attr("cursor", "move");
+        });
+      eventRect.call(zoom).on("click", this.onClick);
+
+      const toIsoDate = d => {
+        return d.toISOString();
+      };
+
+      let drag = d3
+        .drag()
+        .on("start", () => {
+          d3.select(".line")
+            .raise()
+            .classed("active", true);
+        })
+        .on("drag", this.onDrag)
+        .on("end", () => {
+          d3.select(".line").classed("active", false);
+        });
+
+      // Create cursor to indicate to the selected time
+      svg
+        .append("rect")
+        .attr("class", "line")
+        .attr("id", "scrubber")
+        .attr("x", this.newX(d3.isoParse(toIsoDate(this.selectedTime))))
+        .attr("y", 0)
+        .attr("width", 2)
+        .attr("height", svgHeight)
+        .attr("stroke", "#17a2b8")
+        .attr("stroke-width", 2)
+        .attr("opacity", 0.6)
+        .on("mouseover", () => {
+          svg.select(".line").attr("cursor", "e-resize");
+        })
+        .call(drag);
+    },
+    getScale() {
+      return d3
+        .scaleTime()
+        .range([0, document.getElementById("sliderContainer").clientWidth || 0])
+        .domain([
+          d3.isoParse(new Date("2020-01-01")),
+          d3.isoParse(new Date("2020-03-01"))
+        ]);
+    },
+    zoomed() {
+      let scale = this.getScale();
+      this.newX = d3.event.transform.rescaleX(scale);
+      d3.select(".axis--x").call(
+        d3.axisBottom(this.newX).ticks(12)
+        //.tickFormat(dFormat)
+      );
+      d3.select(".line").attr("x", this.newX(d3.isoParse(this.selectedTime)));
+    },
+    onClick() {
+      // Extract the click location
+      let point = d3.mouse(document.getElementById("zoom")),
+        p = { x: point[0], y: point[1] };
+      d3.select(".line").attr("x", p.x);
+      this.selectedTime = d3.isoParse(this.newX.invert(p.x + 2));
+    },
+    onDrag() {
+      this.selectedTime = d3.isoParse(this.newX.invert(d3.event.x + 2));
+      d3.select(".line").attr("x", d3.event.x);
+    }
+  },
+  mounted() {
+    setTimeout(this.createSlider, 150);
+  }
+};
+</script>