PageSourceSearch

https://www.vesi.fi/karttapalvelu/js/TimeSlider.js

js vesi.fi collected 2026-09-24 18:33:41 UTC 21,642 bytes, 606 lines download raw bytes

1const PLAYING_CLASS = 'playing';
2const CLICKABLE_CLASS = 'mapcontainer-timeslider-clickable-container';
3const PRE_MARGIN_CLASS = 'mapcontainer-timeslider-pre-margin';
4const PRE_TOOLS_CLASS = 'mapcontainer-timeslider-pre-tools';
5const FRAMES_CONTAINER_CLASS = 'mapcontainer-timeslider-frames-container';
6const PLAY_BUTTON_CLASS = 'mapcontainer-timeslider-play-button';
7const POST_TOOLS_CLASS = 'mapcontainer-timeslider-post-tools';
8const POST_BUTTON_CLASS = 'mapcontainer-timeslider-step-button';
9const POST_MARGIN_CLASS = 'mapcontainer-timeslider-post-margin';
10const TIMEZONE_LABEL_CLASS = 'mapcontainer-timeslider-timezone';
11const FRAME_TICK_CLASS = 'mapcontainer-timeslider-frame-tick';
12const FRAME_TEXT_WRAPPER_CLASS = 'mapcontainer-timeslider-frame-text-wrapper';
13const FRAME_TEXT_CLASS = 'mapcontainer-timeslider-frame-text';
14const POINTER_CLASS = 'mapcontainer-timeslider-pointer';
15const POINTER_WRAPPER_CLASS = 'mapcontainer-timeslider-pointer-wrapper';
16const POINTER_TEXT_CLASS = 'mapcontainer-timeslider-pointer-text';
17const POINTER_HANDLE_CLASS = 'mapcontainer-timeslider-pointer-handle';
18const INDICATOR_CLASS = 'mapcontainer-timeslider-indicator';
19const HIDDEN_CLASS = 'mapcontainer-timeslider-hidden';
20const MENU_CLASS = 'mapcontainer-timeslider-menu';
21const BEGIN_TIME_CLASS = 'mapcontainer-timeslider-begintime';
22const END_TIME_CLASS = 'mapcontainer-timeslider-endtime';
23const TIMESTEP_CLASS = 'mapcontainer-timeslider-timestep';
24const TIMESTEP_BUTTON_CLASS = 'mapcontainer-timeslider-timestep-button';
25const TIMESTEP_BUTTON_ACTIVE_CLASS = 'mapcontainer-timeslider-timestep-active-button';
26const NO_SELECT_CLASS = 'noselect';
27const BACKWARDS = -1;
28const FORWARDS = 1;
29
30let timeSliderContainer;
31let timeSliderVisible = false;
32let frames_ = [];
33let mouseListeners_ = [];
34// let resizeDetector;
35let paper_ = null;
36let visualPointer_ = null;
37let frameWidth_ = 0;
38let playButton_ = null;
39let frameStatusRects_ = [];
40let timeSlider_animationTime = null;
41let animationPlay_ = false;
42let animationTimer_;
43let previousTickTextTop_ = Number.POSITIVE_INFINITY;
44let previousTickTextRight_ = Number.NEGATIVE_INFINITY;
45let previousTickTextBottom_ = Number.NEGATIVE_INFINITY;
46let previousTickTextLeft_ = Number.POSITIVE_INFINITY;
47let previousTickValue_ = null;
48let previousTickIndex_ = -1;
49let config_;
50
51function timeSlider_initiate(controlConfig) {
52    timeSliderContainer = document.createElement('div');
53    timeSliderContainer.className = 'mapcontainer-timeslider';
54    jQuery(timeSliderContainer).appendTo(controlConfig.mapDiv);
55    timeSlider_animationTime = controlConfig.defaultAnimationTime;
56    config_ = controlConfig;
57}
58
59function timeSlider_showTimeSlider() {
60    if (!timeSliderVisible && config_.showTimeSlider) {
61        timeSliderVisible = true;
62        timeSliderContainer.innerHTML = '';
63        createContainers();
64        let moments = [];
65        let currentMoment = config_.beginTime;
66        while (currentMoment <= config_.endTime) {
67            moments.push(currentMoment);
68            currentMoment = currentMoment + config_.resolutionTime;
69        }
70        createFrames(moments);
71        createIndicators();
72        timeSlider_createTicks();
73        createPointer();
74        timeSlider_setAnimationTime(timeSlider_animationTime);
75        document.addEventListener('keydown', arrowListener);
76    }
77}
78
79function timeSlider_hideTimeSlider() {
80    timeSliderVisible = false;
81    timeSliderContainer.innerHTML = '';
82    document.removeEventListener('keydown', arrowListener);
83    mouseListeners_.forEach(mouseListener => {
84        mouseListener.destroy();
85    });
86}
87
88function createContainers() {
89    let clickableContainer = document.createElement('div');
90    clickableContainer.classList.add(CLICKABLE_CLASS);
91
92    clickableContainer.appendChild(createPreMargin());
93    clickableContainer.appendChild(createPreTools());
94
95    let momentsContainer = document.createElement('div');
96    momentsContainer.classList.add(FRAMES_CONTAINER_CLASS);
97    // this.mouseListeners_.push(listen(momentsContainer, 'wheel', event => {
98    //   self.step(event.deltaY)
99    //   event.preventDefault()
100    // }))
101    clickableContainer.appendChild(momentsContainer);
102
103    // clickableContainer.appendChild(this.createPostTools());
104
105    let postMargin = document.createElement('div');
106    postMargin.classList.add(POST_MARGIN_CLASS);
107    // this.mouseListeners_.push(listen(postMargin, 'click', event => {
108    //   self.step(TimeSlider.FORWARDS)
109    // }))
110    clickableContainer.appendChild(postMargin);
111
112    timeSliderContainer.appendChild(clickableContainer);
113
114    timeSliderContainer.classList.add('noselect');
115
116    // this.resizeDetector.listenTo(this.container_, function (element) {
117    //   self.createTicks()
118    // })
119}
120
121function createPreMargin() {
122    let preMargin = document.createElement('div');
123    preMargin.classList.add(PRE_MARGIN_CLASS);
124    // this.mouseListeners_.push(listen(preMargin, 'click', () => {
125    //   self.step(BACKWARDS)
126    // }))
127    return preMargin;
128}
129
130function createFrames(moments) {
131    let i;
132    let timePeriod;
133    const numMoments = moments.length;
134    const currentTime = Date.now();
135    let beginTime;
136    let endTime;
137    let type;
138    let weight;
139    let timeFrame;
140    let framesContainer = timeSliderContainer.getElementsByClassName(FRAMES_CONTAINER_CLASS)[0];
141    framesContainer.innerHTML = '';
142    if (numMoments < 2) {
143        return;
144    }
145    timePeriod = moments[numMoments - 1] - moments[0];
146
147    frames_ = [];
148    for (i = 0; i < numMoments; i++) {
149        beginTime = i === 0 ? 2 * moments[0] - moments[1] : moments[i - 1];
150        endTime = moments[i];
151        type = moments[i] <= currentTime ? 'history' : 'future';
152        weight = (100 * (endTime - beginTime)) / timePeriod;
153        timeFrame = createFrame(beginTime, endTime, type, weight);
154        framesContainer.appendChild(timeFrame['element']);
155        frames_.push(timeFrame);
156    }
157}
158
159function createFrame(beginTime, endTime, type, weight) {
160    // DOM element
161    const element = document.createElement('div');
162    element.style['flex-grow'] = weight;
163    element.classList.add(type);
164    element.dataset.time = endTime;
165    const dragListenerElement = document.createElement('div');
166    dragListenerElement.classList.add('mapcontainer-timeslider-drag-listener');
167    element.appendChild(dragListenerElement);
168    element.classList.add('mapcontainer-timeslider-frame');
169
170    // click listener
171    function clickListener(event) {
172        timeSlider_setAnimationTime(endTime);
173        symbology_refreshTimeLayers();
174    }
175    element.addEventListener('click', clickListener);
176    mouseListeners_.push({
177        destroy: function () {
178            element.removeEventListener('click', clickListener);
179        }
180    });
181
182    return {
183        beginTime: beginTime,
184        endTime: endTime,
185        useDateFormat: false,
186        element: element
187    };
188}
189
190function createIndicators() {
191    frames_.forEach((frame, index, array) => {
192        let indicator = document.createElement('div');
193        indicator.classList.add(INDICATOR_CLASS);
194        indicator.setAttribute('id', 'timeSliderIndicator_' + frame['endTime'].valueOf());
195        if (index === 0) {
196            indicator.classList.add('first');
197        } else if (index === array.length - 1) {
198            indicator.classList.add('last');
199        }
200        frame.element.appendChild(indicator);
201    });
202}
203
204function timeSlider_createTicks() {
205    let self = this;
206    let tick;
207    let maxTextWidth = 0;
208    let newTextWidth;
209    let useTimeStep = false;
210    let timeStep;
211    let framesContainer;
212    let previousTickTextRight_ = Number.NEGATIVE_INFINITY;
213
214    let clearFrame = frame => {
215        let removeChildrenByClass = className => {
216            Array.from(frame.element.getElementsByClassName(className)).forEach(element => {
217                element.parentElement.removeChild(element);
218            });
219        };
220        removeChildrenByClass(FRAME_TEXT_WRAPPER_CLASS);
221        removeChildrenByClass(FRAME_TICK_CLASS);
222    };
223
224    frames_.forEach((frame, index, frames) => {
225        let tickText;
226        let textWrapperElement;
227        let textElement;
228        let textDayElement;
229
230        clearFrame(frame);
231
232        textWrapperElement = document.createElement('div');
233        textWrapperElement.classList.add(FRAME_TEXT_WRAPPER_CLASS);
234
235        tickText = getTickText(frame['endTime']);
236
237        /*  TODO: päivä yläpuolelle
238      textDayElement = document.createElement('span')
239      textDayElement.classList.add(TimeSlider.FRAME_TEXT_CLASS)
240      textDayElement.classList.add(constants.NO_SELECT_CLASS)
241      textDayElement.textContent = tickText['day']
242      textWrapperElement.appendChild(textDayElement)
243
244      textWrapperElement.appendChild(document.createElement('br'))
245      */
246
247        textElement = document.createElement('span');
248        textElement.setAttribute('id', 'tickLabel_' + frame['endTime'].valueOf());
249        textElement.classList.add(FRAME_TEXT_CLASS);
250        textElement.classList.add(NO_SELECT_CLASS);
251        let tickTextArray = tickText['content'].split(' ');
252        textElement.setAttribute('data-args', tickText['weekDay'] + ',' + tickTextArray[1]);
253        textElement.classList.add('translate');
254        textElement.textContent = tickText['content'];
255        textWrapperElement.appendChild(textElement);
256
257        // click on label?
258        // let self = this;
259        // this.mouseListeners_.push(listen(textElement, 'click', (event) => {
260        //   self.variableEvents.emitEvent('animationTime', [frame['endTime']]
260)
261        // }))
262
263        frame['useDateFormat'] = tickText['useDateFormat'];
264
265        frame.element.appendChild(textWrapperElement);
266        frame.element.style.display = 'none';
267    });
268
269    // Separate loops to prevent accessing textElement width before it is available
270    frames_.forEach((frame, index, frames) => {
271        let clientRect;
272        let localTimeStep;
273        let textElement;
274        let nextIndex = index + 1;
275        frame.element.style.display = '';
276        if (nextIndex === frames.length) {
277            return;
278        }
279        textElement = frame.element.querySelector('span.' + FRAME_TEXT_CLASS);
280        clientRect = textElement.getBoundingClientRect();
281        if (maxTextWidth < clientRect['width']) {
282            maxTextWidth = clientRect['width'];
283        }
284
285        localTimeStep = frames[nextIndex]['endTime'] - frame['endTime'];
286        if (timeStep == null) {
287            useTimeStep = true;
288            timeStep = localTimeStep;
289        } else if (useTimeStep && localTimeStep !== timeStep) {
290            useTimeStep = false;
291        }
292    });
293    // Prevent common tick asynchrony
294    if (useTimeStep) {
295        timeStep *= 2;
296    }
297
298    newTextWidth = Math.round(maxTextWidth) + 'px';
299    Array.from(document.getElementsByClassName(FRAME_TEXT_WRAPPER_CLASS)).forEach(element => {
300        element.style.width = newTextWidth;
301    });
302
303    let createTick = (frame, index, rect, endTime) => {
304        previousTickTextTop_ = rect.top;
305        previousTickTextRight_ = rect.right;
306        previousTickTextBottom_ = rect.bottom;
307        previousTickTextLeft_ = rect.left;
308        previousTickValue_ = endTime;
309        previousTickIndex_ = index;
310        tick = document.createElement('div');
311        tick.classList.add(FRAME_TICK_CLASS);
312        tick.classList.add(HIDDEN_CLASS);
313        frame.element.appendChild(tick);
314    };
315
316    framesContainer = Array.from(document.getElementsByClassName(FRAMES_CONTAINER_CLASS));
317    if (framesContainer.length > 0) {
318        framesContainer = framesContainer[0].getBoundingClientRect();
319    }
320
321    frames_.forEach((frame, index, frames) => {
322        let textWrapper;
323        let clientRect;
324        let textElementArray = Array.from(frame.element.getElementsByClassName(FRAME_TEXT_WRAPPER_CLASS));
325        if (textElementArray.length === 0) {
326            return;
327        }
328        textWrapper = textElementArray.shift();
329        clientRect = textWrapper.getBoundingClientRect();
330
331        // Prevent text overlapping, simply place those that fit
332        if (
333            framesContainer.length === 0 ||
334            (framesContainer.left <= clientRect.left &&
335                framesContainer.right >= clientRect.right &&
336                framesContainer.top <= clientRect.top &&
337                framesContainer.bottom >= clientRect.bottom)
338        ) {
339            if (
340                previousTickTextRight_ < clientRect.left ||
341                previousTickTextLeft_ > clientRect.right ||
342                previousTickTextBottom_ < clientRect.top ||
343                previousTickTextTop_ > clientRect.bottom
344            ) {
345                createTick(frame, index, clientRect, frame['endTime']);
346            } else {
347                frame.element.removeChild(textWrapper);
348                // console.debug('Time slider tick measures:')
349                // console.debug(`${previousTickTextRight_} < ${clientRect.left}`);
350                // console.debug(`${previousTickTextLeft_} > ${clientRect.right}`);
351                // console.debug(`${previousTickTextBottom_} < ${clientRect.top}`);
352                // console.debug(`${previousTickTextTop_} > ${clientRect.bottom}`);
353            }
354        } else {
355            frame.element.removeChild(textWrapper);
356        }
357    });
358
359    Array.from(document.getElementsByClassName(FRAME_TICK_CLASS)).forEach(element => {
360        element.classList.remove(HIDDEN_CLASS);
361    });
362}
363
364/**
365 * Generate text presentation of the given time.
366 * @param {number} tickTime Time value.
367 * @param {boolean} showDate Show date information.
368 * @return {string} Generated text presentation.
369 */
370function getTickText(tickTime, showDate = true, showWeekDay = true) {
371    let zTime;
372    let numFrames;
373    let i;
374    let frameTime;
375    let prevTime;
376    let zPrevTime;
377    let day;
378    let year;
379    let currentMoment;
380    let format = 'HH:mm';
381    let dateFormat = 'D.M.';
382    let dateFormatDay = 'dd';
383    if (showWeekDay) {
384        dateFormat = 'dd D.M.';
385    }
386    /*
387    if (showWeekDay) {
388      dateFormat = 'dd'
389    }
390    */
391    let useDateFormat = false;
392    let beginTime = frames_.length > 0 ? frames_[0]['endTime'] : Number.NEGATIVE_INFINITY;
393    if (beginTime == null) {
394        return '';
395    }
396    if (tickTime < beginTime) {
397        tickTime = beginTime;
398    }
399    //ei käytetä moment-kirjastoa
400    // moment.locale(jQuery.i18n().locale)
401    // zTime = moment(tickTime).tz(this.timeZone_)
402    // day = zTime.dayOfYear()
403    // year = zTime.year()
404    const tickDate = new Date(tickTime);
405
406    // JÄTETTY POIS, TARKOITUS EPÄSELVÄ (S.NEUVONEN/SYKE)
407    // if (showDate) {
408    //   numFrames = frames_.length
409    //   for (i = 0; i < numFrames; i++) {
410    //     frameTime = frames_[i]['endTime']
411    //     if (frameTime >= tickTime) {
412    //       break
413    //     }
414    //     if (Array.from(frames_[i].element.getElementsByClassName(TimeSlider.FRAME_TEXT_WRAPPER_CLASS)).length > 0) {
415    //       prevTime = frameTime
416    //     }
417    //   }
418    //   currentMoment = moment().tz(this.timeZone_)
419    //   if (prevTime != null) {
420    //     zPrevTime = moment(prevTime).tz(this.timeZone_)
421    //     if ((day !== zPrevTime.dayOfYear()) || (year !== zPrevTime.year())) {
422    //       useDateFormat = true
423    //     }
424    //   } else if ((tickTime === beginTime) && ((day !== currentMoment.tz(this.timeZone_).dayOfYear()) || (year !== currentMoment.tz(this.timeZone_).year()))) {
425    //     useDateFormat = true
426    //   }
427    // }
428    useDateFormat = true;
429    let weekDayArray = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'];
430    switch (jQuery.i18n().locale) {
431        case 'fi':
432            weekDayArray = ['Su', 'Ma', 'Ti', 'Ke', 'To', 'Pe', 'La'];
433            break;
434        case 'sv':
435            weekDayArray = ['Sö', 'Må', 'Ti', 'On', 'Th', 'Fr', 'Lö'];
436            break;
437        default:
438            break;
439    }
440    return {
441        day: tickDate.getDate(),
442        content: showWeekDay
443            ? `${weekDayArray[tickDate.getDay()]} ${tickDate.getDate()}.${tickDate.getMonth() + 1}.`
444            : `${tickDate.getDate()}.${tickDate.getMonth() + 1}.`,
445        useDateFormat: useDateFormat,
446        weekDay: weekDayArray[tickDate.getDay()]
447    };
448}
449
450function createPointer() {
451    let pointerContainer = document.createElement('div');
452    pointerContainer.classList.add('pointerContainerDiv');
453
454    let pointer = document.createElement('div');
455    pointer.classList.add(POINTER_CLASS);
456
457    let textContainer = document.createElement('div');
458    textContainer.classList.add(POINTER_WRAPPER_CLASS);
459
460    let textItem = document.createElement('span');
461    textItem.classList.add(POINTER_TEXT_CLASS);
462    textItem.classList.add('noselect');
463    textItem.innerHTML = '';
464
465    textContainer.appendChild(textItem);
466    pointer.appendChild(textContainer);
467
468    let handle = document.createElement('div');
469    handle.classList.add(POINTER_HANDLE_CLASS);
470    pointer.appendChild(handle);
471    // this.mouseListeners_.push(listen(pointer, 'mousedown', e => {
472    //   self.dragging_ = true
473    // }))
474    // this.mouseListeners_.push(listen(pointer, 'touchstart', e => {
475    //   self.dragging_ = true
476    // }))
477
478    pointerContainer.appendChild(pointer);
479
480    visualPointer_ = pointerContainer;
481}
482
483function createPreTools() {
484    let preTools = document.createElement('div');
485    preTools.classList.add(PRE_TOOLS_CLASS);
486
487    let playButton = document.createElement('div');
488    playButton.classList.add(PLAY_BUTTON_CLASS);
489    if (animationPlay_) {
490        playButton.classList.add(PLAYING_CLASS);
491    }
492
493    function clickListener(event) {
494        timeSlider_setAnimationPlay(!animationPlay_);
495    }
496    playButton.addEventListener('click', clickListener);
497    mouseListeners_.push({
498        destroy: function () {
499            playButton.removeEventListener('click', clickListener);
500        }
501    });
502
503    playButton_ = playButton;
504    preTools.appendChild(playButton);
505    return preTools;
506}
507
508function timeSlider_setAnimationTime(animationTime) {
509    timeSlider_animationTime = animationTime;
510    updatePointer(animationTime);
511}
512
513function updatePointer(animationTime) {
514    if (visualPointer_ == null) {
515        return;
516    }
517    let numFrames = frames_.length;
518    let i;
519    let index;
520    let needsUpdate;
521    for (i = 0; i < numFrames; i++) {
522        if (animationTime <= frames_[i]['endTime']) {
523            index = i;
524            break;
525        }
526    }
527    if (index != null) {
528        if (visualPointer_.parentElement == null) {
529            needsUpdate = true;
530        } else if (Number.parseInt(visualPointer_.parentElement.dataset['time']) !== animationTime) {
531            visualPointer_.parentElement.removeChild(visualPointer_);
532            needsUpdate = true;
533        }
534        if (needsUpdate) {
535            frames_[index].element.appendChild(visualPointer_);
536            Array.from(visualPointer_.getElementsByClassName(POINTER_TEXT_CLASS)).forEach(textElement => {
537                //textElement.innerHTML = this.getTickText(frames_[index]['endTime'], false)['content']
538                textElement.innerHTML = getTickText(frames_[index]['endTime'], controlConfig['showTickAsDate'], false)['content'];
539                console.debug(`timeslider pointer set to ${textElement.innerHTML}`);
540            });
541        }
542    }
543}
544
545function step(direction) {
546    // get current frame index
547    let numFrames = frames_.length;
548    let i;
549    let index;
550    for (i = 0; i < numFrames; i++) {
551        if (timeSlider_animationTime <= frames_[i]['endTime']) {
552            index = i;
553            break;
554        }
555    }
556    if (index === null) {
557        for (i = 0; i < numFrames; i++) {
558            if (config_.defaultAnimationTime <= frames_[i]['endTime']) {
559                index = i;
560                break;
561            }
562        }
563    }
564    // step animation time
565    if (index !== null) {
566        if (direction > 0) {
567            index++;
568            index = index > numFrames - 1 ? 0 : index; // loop
569        } else if (direction < 0) {
570            index--;
571            index = index < 0 ? 0 : index;
572        }
573        timeSlider_setAnimationTime(frames_[index]['endTime']);
574        symbology_refreshTimeLayers();
575    }
576}
577
578function arrowListener(event) {
579    let keyCode = event.which || event.keyCode;
580    switch (keyCode) {
581        case 39: //right arrow
582            step(1);
583            break;
584        case 37: //left arrow
585            step(-1);
586            break;
587    }
588}
589
590function timeSlider_setAnimationPlay(animationPlay) {
591    if (animationPlay_ === animationPlay) return;
592    try {
593        animationPlay_ = animationPlay;
594        if (animationPlay_) {
595            playButton_.classList.add(PLAYING_CLASS);
596            animationTimer_ = setInterval(function stepForward() {
597                step(1);
598            }, config_.frameRate);
599        } else {
600            playButton_.classList.remove(PLAYING_CLASS);
601            clearInterval(animationTimer_);
602        }
603    } catch (error) {
604        console.warn(`[timeSlider_setAnimationPlay] ${error.toString()}`);
605    }
606}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.