Mercurial > gemma
view client/src/components/TimeSlider.vue @ 5687:8ff842858434 sr-v2
More stubs for serializing v2.
author | Sascha L. Teichmann <sascha.teichmann@intevation.de> |
---|---|
date | Mon, 12 Feb 2024 01:31:48 +0100 |
parents | 84d01a536bec |
children |
line wrap: on
line source
<template> <div id="slider" :class="[ 'd-flex box ui-element rounded bg-white flex-row', { expanded: showTimeSlider }, { reposition: ['DEFAULT', 'COMPARESURVEYS'].indexOf(this.paneSetup) === -1 } ]" > <div id="timeselection" class="d-flex mt-1 mr-1"> <input class="form-control-sm mr-1" type="date" v-model="dateSelection" min="2015-01-01" :max="new Date().toISOString().split('T')[0]" required /> <input type="time" min="00:00" max="23:59" v-model="timeSelection" class="form-control-sm" required /> </div> <div id="sliderContainer" class="d-flex sliderContainer" style="width: 98%;" ></div> <div id="closebutton" @click="close" class="d-flex box-control mr-0" style="width: 2%;" > <font-awesome-icon icon="times"></font-awesome-icon> </div> </div> </template> <style scoped> #slider { position: absolute; bottom: 0; min-width: 100vw; } /* reposition time slider in case of opened diagram */ #slider.reposition { bottom: 50%; } #slider.expanded { max-height: 100%; max-width: 100%; margin: 0; } input::-webkit-clear-button { display: none; } /* hide clear button on IE */ input::-ms-clear { display: none; } </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 <fadi.abbud@intevation.de> * Thomas Junk <thomas.junk@intevation.de> */ import { mapState } from "vuex"; import * as d3 from "d3"; import app from "@/main"; import { localeDateString } from "@/lib/datelocalization"; import { format, setHours, setMinutes, compareAsc } from "date-fns"; import debounce from "debounce"; let zoom = null; let xScale = null; let xAxis = null; let currentScaleFactor = 1; export default { name: "timeslider", data() { return { isSelectedTimeHourly: false, resizeListenerFunction: null }; }, watch: { ongoingRefresh() { if (this.ongoingRefresh) return; this.$store.commit("application/setSelectedTime", new Date()); this.$nextTick(this.rescaleSlider(1)); }, ongoingTimeSlide() { if (this.ongoingTimeSlide) return; this.$store.commit( "application/setCurrentVisibleTime", this.refreshLayersTime ); }, selectedTime() { this.triggerMapReload(); }, sourcesLoading() { // initiate refresh layers request if the time for the finished request // differs from the selected time on time slider if (this.sourcesLoading !== 0) return; if (compareAsc(this.selectedTime, this.currentVisibleTime) === 0) return; this.triggerMapReload(); } }, computed: { ...mapState("application", [ "showTimeSlider", "paneSetup", "currentVisibleTime", "refreshLayersTime" ]), ...mapState("map", [ "ongoingRefresh", "ongoingTimeSlide", "openLayersMaps" ]), dateSelection: { get() { const date = this.$store.state.application.selectedTime; return format(date, "YYYY-MM-DD"); }, set(value) { if (!value) return; let date = new Date(value); const [hours, minutes] = this.timeSelection.split(":"); date = setHours(date, hours); date = setMinutes(date, minutes); const now = new Date(); if (date > now) { date = now; } this.$store.commit("application/setSelectedTime", date); this.rescaleSlider(50); } }, timeSelection: { get() { const time = this.$store.state.application.selectedTime; return format(time, "HH:mm"); }, set(value) { if (!value) return; let date = this.selectedTime; date = setHours(date, value.split(":")[0]); date = setMinutes(date, value.split(":")[1]); const now = new Date(); if (date > now) { date = now; } this.$store.commit("application/setSelectedTime", date); this.rescaleSlider(800); } }, selectedTime: { get() { return this.$store.state.application.selectedTime; }, set(value) { if (!this.isSelectedTimeHourly) { value = setHours(value, 12); value = setMinutes(value, 0); } this.$store.commit("application/setSelectedTime", value); } }, sourcesLoading() { const layers = [ "BOTTLENECKS", "GAUGES", "FAIRWAYDIMENSIONSLOS1", "FAIRWAYDIMENSIONSLOS2", "FAIRWAYDIMENSIONSLOS3", "WATERWAYAXIS", "FAIRWAYMARKS" ]; let counter = 0; this.openLayersMaps.forEach(map => { for (let i = 0; i < layers.length; i++) { let layer = map.getLayer(layers[i]); if (layer.getSource().loading) counter++; } }); return counter; } }, methods: { close() { this.$store.commit("application/showTimeSlider", false); this.$store.commit("application/setStoredTime", this.currentVisibleTime); this.$store.commit("application/setSelectedTime", new Date()); }, triggerMapReload() { // trigger refresh layers only when last loading of layers was ended if (this.sourcesLoading) { return; } this.$store.commit( "application/setLayerRefreshedTime", this.selectedTime ); this.$store.commit("map/startTimeSlide"); this.$store.dispatch("map/refreshTimebasedLayers"); this.$nextTick(() => { this.$store.commit("map/finishTimeSlide"); }); }, rescaleSlider(scaleFactor) { const tx = -scaleFactor * this.getScale()(d3.isoParse(this.selectedTime.toISOString())) + document.getElementById("sliderContainer").clientWidth / 2; var t = d3.zoomIdentity.translate(tx, 0).scale(scaleFactor); this.getScale().domain(t.rescaleX(this.getScale())); d3.select(".zoom").call(zoom.transform, t); }, createSlider() { const element = document.getElementById("sliderContainer"); const svgWidth = element ? element.clientWidth : 0, svgHeight = 40, marginTop = 20, marginLeft = 0; d3.timeFormatDefaultLocale(localeDateString); const toIsoDate = d => { return d.toISOString(); }; xScale = this.getScale(); xAxis = this.getAxes(); let svg = d3 .select(".sliderContainer") .append("svg") .attr("width", svgWidth) .attr("height", svgHeight); zoom = d3 .zoom() .scaleExtent([0.8, 102000]) .translateExtent([ [0, 0], [svgWidth, svgHeight] ]) .extent([[0, 0], [(svgWidth, svgHeight)]]) .on("zoom", this.zoomed); const drag = d3 .drag() .on("start", () => { d3.select(".line") .raise() .classed("active", true); }) .on("drag", this.onDrag) .on("end", () => { d3.select(".line").classed("active", false); }); const main = svg .append("g") .attr("id", "zoom") .attr("class", "zoom") .attr("width", svgWidth) .attr("height", svgHeight) .on("mouseover", () => { svg.select(".zoom").attr("cursor", "move"); }); main .append("rect") .attr("class", "main") .attr("width", svgWidth) .attr("height", svgHeight) .attr("opacity", 0) .on("mouseover", () => { svg.select(".main").attr("cursor", "move"); }); main .append("g") .attr("class", "axis--x") .attr("id", "zoom") .attr("transform", `translate(${marginLeft}, ${marginTop})`) .call(xAxis); svg .append("rect") .attr("class", "future") .attr("x", xAxis.scale()(d3.isoParse(toIsoDate(new Date())))) .attr("y", 0) .attr("width", svgWidth) .attr("height", svgHeight) .attr("fill", "#333333") .attr("opacity", 0.3); // Create cursor to indicate to the selected time main .append("rect") .attr("class", "line") .attr("id", "scrubber") .attr("x", xAxis.scale()(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); main.call(zoom).on("click", this.onClick); }, 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(xScale) .ticks(12) .tickFormat(axesFormat); }, zoomed() { let newX = d3.event.transform.rescaleX(xScale); currentScaleFactor = d3.event.transform.k; this.checkSelectedTimeHourly(); xAxis.scale(newX); d3.select(".axis--x").call(xAxis); d3.select(".line").attr("x", newX(d3.isoParse(this.selectedTime))); d3.select(".future").attr("x", newX(d3.isoParse(new Date()))); }, checkSelectedTimeHourly() { const isHourly = currentScaleFactor > 400; if (this.isSelectedTimeHourly != isHourly) this.isSelectedTimeHourly = isHourly; }, 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); const xTime = d3.isoParse(xAxis.scale().invert(p.x + 2)).toDateString(); const now = new Date(); // Avoid moving the cursor to future area if the "now" value is earlier than 12:00 if ( xTime === now.toDateString() && !this.isSelectedTimeHourly && now.getHours() < 12 ) { this.isSelectedTimeHourly = true; this.selectedTime = d3.isoParse(new Date()); } else { this.selectedTime = d3.isoParse(xAxis.scale().invert(p.x + 2)); } this.checkSelectedTimeHourly(); }, onDrag() { this.selectedTime = d3.isoParse(xAxis.scale().invert(d3.event.x + 2)); const now = new Date(); const startTimeOnSlider = d3.isoParse(xAxis.scale().invert(0)); const endTimeOnSlider = d3.isoParse( xAxis.scale().invert(xScale.range()[1]) ); // Prevent dragging outside the visible area on slider if (endTimeOnSlider < this.selectedTime) { this.selectedTime = endTimeOnSlider; d3.select(".line").attr( "x", xAxis.scale()(d3.isoParse(endTimeOnSlider)) ); } else { if (startTimeOnSlider > this.selectedTime) { this.selectedTime = startTimeOnSlider; d3.select(".line").attr( "x", xAxis.scale()(d3.isoParse(startTimeOnSlider)) ); } else { if (this.selectedTime > now) { this.isSelectedTimeHourly = true; this.selectedTime = now; d3.select(".line").attr("x", xAxis.scale()(d3.isoParse(now))); this.checkSelectedTimeHourly(); } else { d3.select(".line").attr("x", d3.event.x); } } } }, redrawTimeSlider() { const bodyWidth = document.querySelector("body").clientWidth; const timeSelectionWidth = document.querySelector("#timeselection") .clientWidth; const closeButton = document.querySelector("#closebutton").clientWidth; const svgWidth = bodyWidth - timeSelectionWidth - closeButton; document .querySelector(".sliderContainer svg") .setAttribute("width", svgWidth); xScale.range([0, svgWidth]); xAxis.scale(xScale); d3.select(".axis--x").call(xAxis); d3.select(".line").attr( "x", xAxis.scale()(d3.isoParse(this.selectedTime)) ); this.rescaleSlider(currentScaleFactor); } }, created() { this.resizeListenerFunction = debounce(this.redrawTimeSlider, 100); window.addEventListener("resize", this.resizeListenerFunction); }, destroyed() { window.removeEventListener("resize", this.resizeListenerFunction); }, mounted() { setTimeout(this.createSlider, 150); } }; </script>