Mercurial > gemma
view client/src/components/TimeSlider.vue @ 5043:bae2548dc484 time-sliding
client: improve the axes labeling for time-slider
author | Fadi Abbud <fadi.abbud@intevation.de> |
---|---|
date | Fri, 28 Feb 2020 16:38:53 +0100 |
parents | abe9b75686ba |
children | ca5b1b5a553a |
line wrap: on
line source
<template> <div id="slider" :class="[ 'd-flex box ui-element rounded bg-white flex-row', { expanded: showTimeSlider } ]" > <div class="d-flex m-1"> <input class="form-control form-control-sm" type="date" v-model="timeSelection" min="2015-01-01" max="2020-03-01" /> </div> <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" scoped> #slider position: absolute bottom: 0 min-width: 100vw; &.expanded 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"; import app from "@/main"; let zoom = null; export default { name: "timeslider", data() { return { newX: null }; }, watch: { ongoingRefresh() { if (this.ongoingRefresh) return; this.$store.commit("application/setSelectedTime", new Date()); this.$nextTick(this.redrawSlider); } }, computed: { ...mapState("application", ["showTimeSlider"]), ...mapState("map", ["ongoingRefresh"]), timeSelection: { get() { const date = this.$store.state.application.selectedTime; return date.toISOString().split("T")[0]; }, set(value) { const date = new Date(value); this.$store.commit("application/setSelectedTime", date); this.$nextTick(this.redrawSlider); } }, selectedTime: { get() { return this.$store.state.application.selectedTime; }, set(value) { this.$store.commit("application/setSelectedTime", value); } } }, methods: { close() { this.$store.commit("application/showTimeSlider", false); }, redrawSlider() { this.createSlider(); zoom.translateTo( d3.select(".line"), this.newX(d3.isoParse(this.selectedTime.toISOString())), 1 ); }, createSlider() { const element = document.getElementById("sliderContainer"); const svgWidth = element ? element.clientWidth : 0, svgHeight = 40, marginTop = 20, marginLeft = 0; this.newX = this.getScale(); d3.select(".sliderContainer svg").remove(); let svg = d3 .select(".sliderContainer") .append("svg") .attr("width", svgWidth) .attr("height", svgHeight); const wheelDelta = () => { return (-d3.event.deltaY * (d3.event.deltaMode ? 120 : 1)) / 15; }; // zoom event zoom = d3 .zoom() .scaleExtent([0.8, 102000]) .translateExtent([[0, 0], [svgWidth, svgHeight]]) .extent([[0, 0], [(svgWidth, svgHeight)]]) .on("zoom", this.zoomed); zoom.wheelDelta(wheelDelta); svg .append("g") .attr("class", "axis--x") .attr("transform", `translate(${marginLeft}, ${marginTop})`) .call(this.getAxes()); // 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("2015-01-01")), d3.isoParse(new Date())]); }, getAxes() { const axesFormat = date => { return (d3.timeSecond(date) < date ? d3.timeFormat(".%L") : d3.timeMinute(date) < date ? d3.timeFormat(":%S") : d3.timeHour(date) < date ? d3.timeFormat("%H:%M") : d3.timeDay(date) < date ? d3.timeFormat("%H:%M") : d3.timeMonth(date) < date ? d3.timeWeek(date) < date ? d3.timeFormat(app.$gettext("%a %d")) : d3.timeFormat(app.$gettext("%b %d")) : d3.timeYear(date) < date ? d3.timeFormat("%B") : d3.timeFormat("%Y"))(date); }; return d3 .axisBottom(this.newX) .ticks(12) .tickFormat(axesFormat); }, zoomed() { let scale = this.getScale(); this.newX = d3.event.transform.rescaleX(scale); d3.select(".axis--x").call(this.getAxes()); 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>