import ClockRange from "../../Core/ClockRange.js"; import defined from "../../Core/defined.js"; import destroyObject from "../../Core/destroyObject.js"; import DeveloperError from "../../Core/DeveloperError.js"; import JulianDate from "../../Core/JulianDate.js"; import getElement from "../getElement.js"; import TimelineHighlightRange from "./TimelineHighlightRange.js"; import TimelineTrack from "./TimelineTrack.js"; var timelineWheelDelta = 1e12; var timelineMouseMode = { none: 0, scrub: 1, slide: 2, zoom: 3, touchOnly: 4, }; var timelineTouchMode = { none: 0, scrub: 1, slideZoom: 2, singleTap: 3, ignore: 4, }; var timelineTicScales = [ 0.001, 0.002, 0.005, 0.01, 0.02, 0.05, 0.1, 0.25, 0.5, 1.0, 2.0, 5.0, 10.0, 15.0, 30.0, 60.0, // 1min 120.0, // 2min 300.0, // 5min 600.0, // 10min 900.0, // 15min 1800.0, // 30min 3600.0, // 1hr 7200.0, // 2hr 14400.0, // 4hr 21600.0, // 6hr 43200.0, // 12hr 86400.0, // 24hr 172800.0, // 2days 345600.0, // 4days 604800.0, // 7days 1296000.0, // 15days 2592000.0, // 30days 5184000.0, // 60days 7776000.0, // 90days 15552000.0, // 180days 31536000.0, // 365days 63072000.0, // 2years 126144000.0, // 4years 157680000.0, // 5years 315360000.0, // 10years 630720000.0, // 20years 1261440000.0, // 40years 1576800000.0, // 50years 3153600000.0, // 100years 6307200000.0, // 200years 12614400000.0, // 400years 15768000000.0, // 500years 31536000000.0, // 1000years ]; var timelineMonthNames = [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", ]; /** * The Timeline is a widget for displaying and controlling the current scene time. * @alias Timeline * @constructor * * @param {Element} container The parent HTML container node for this widget. * @param {Clock} clock The clock to use. */ function Timeline(container, clock) { //>>includeStart('debug', pragmas.debug); if (!defined(container)) { throw new DeveloperError("container is required."); } if (!defined(clock)) { throw new DeveloperError("clock is required."); } //>>includeEnd('debug'); container = getElement(container); var ownerDocument = container.ownerDocument; /** * Gets the parent container. * @type {Element} */ this.container = container; var topDiv = ownerDocument.createElement("div"); topDiv.className = "cesium-timeline-main"; container.appendChild(topDiv); this._topDiv = topDiv; this._endJulian = undefined; this._epochJulian = undefined; this._lastXPos = undefined; this._scrubElement = undefined; this._startJulian = undefined; this._timeBarSecondsSpan = undefined; this._clock = clock; this._scrubJulian = clock.currentTime; this._mainTicSpan = -1; this._mouseMode = timelineMouseMode.none; this._touchMode = timelineTouchMode.none; this._touchState = { centerX: 0, spanX: 0, }; this._mouseX = 0; this._timelineDrag = 0; this._timelineDragLocation = undefined; this._lastHeight = undefined; this._lastWidth = undefined; this._topDiv.innerHTML = '<div class="cesium-timeline-bar"></div><div class="cesium-timeline-trackContainer">' + '<canvas class="cesium-timeline-tracks" width="10" height="1">' + '</canvas></div><div class="cesium-timeline-needle"></div><span class="cesium-timeline-ruler"></span>'; this._timeBarEle = this._topDiv.childNodes[0]; this._trackContainer = this._topDiv.childNodes[1]; this._trackListEle = this._topDiv.childNodes[1].childNodes[0]; this._needleEle = this._topDiv.childNodes[2]; this._rulerEle = this._topDiv.childNodes[3]; this._context = this._trackListEle.getContext("2d"); this._trackList = []; this._highlightRanges = []; this.zoomTo(clock.startTime, clock.stopTime); this._onMouseDown = createMouseDownCallback(this); this._onMouseUp = createMouseUpCallback(this); this._onMouseMove = createMouseMoveCallback(this); this._onMouseWheel = createMouseWheelCallback(this); this._onTouchStart = createTouchStartCallback(this); this._onTouchMove = createTouchMoveCallback(this); this._onTouchEnd = createTouchEndCallback(this); var timeBarEle = this._timeBarEle; ownerDocument.addEventListener("mouseup", this._onMouseUp, false); ownerDocument.addEventListener("mousemove", this._onMouseMove, false); timeBarEle.addEventListener("mousedown", this._onMouseDown, false); timeBarEle.addEventListener("DOMMouseScroll", this._onMouseWheel, false); // Mozilla mouse wheel timeBarEle.addEventListener("mousewheel", this._onMouseWheel, false); timeBarEle.addEventListener("touchstart", this._onTouchStart, false); timeBarEle.addEventListener("touchmove", this._onTouchMove, false); timeBarEle.addEventListener("touchend", this._onTouchEnd, false); timeBarEle.addEventListener("touchcancel", this._onTouchEnd, false); this._topDiv.oncontextmenu = function () { return false; }; clock.onTick.addEventListener(this.updateFromClock, this); this.updateFromClock(); } /** * @private */ Timeline.prototype.addEventListener = function (type, listener, useCapture) { this._topDiv.addEventListener(type, listener, useCapture); }; /** * @private */ Timeline.prototype.removeEventListener = function (type, listener, useCapture) { this._topDiv.removeEventListener(type, listener, useCapture); }; /** * @returns {Boolean} true if the object has been destroyed, false otherwise. */ Timeline.prototype.isDestroyed = function () { return false; }; /** * Destroys the widget. Should be called if permanently * removing the widget from layout. */ Timeline.prototype.destroy = function () { this._clock.onTick.removeEventListener(this.updateFromClock, this); var doc = this.container.ownerDocument; doc.removeEventListener("mouseup", this._onMouseUp, false); doc.removeEventListener("mousemove", this._onMouseMove, false); var timeBarEle = this._timeBarEle; timeBarEle.removeEventListener("mousedown", this._onMouseDown, false); timeBarEle.removeEventListener("DOMMouseScroll", this._onMouseWheel, false); // Mozilla mouse wheel timeBarEle.removeEventListener("mousewheel", this._onMouseWheel, false); timeBarEle.removeEventListener("touchstart", this._onTouchStart, false); timeBarEle.removeEventListener("touchmove", this._onTouchMove, false); timeBarEle.removeEventListener("touchend", this._onTouchEnd, false); timeBarEle.removeEventListener("touchcancel", this._onTouchEnd, false); this.container.removeChild(this._topDiv); destroyObject(this); }; /** * @private */ Timeline.prototype.addHighlightRange = function (color, heightInPx, base) { var newHighlightRange = new TimelineHighlightRange(color, heightInPx, base); this._highlightRanges.push(newHighlightRange); this.resize(); return newHighlightRange; }; /** * @private */ Timeline.prototype.addTrack = function ( interval, heightInPx, color, backgroundColor ) { var newTrack = new TimelineTrack( interval, heightInPx, color, backgroundColor ); this._trackList.push(newTrack); this._lastHeight = undefined; this.resize(); return newTrack; }; /** * Sets the view to the provided times. * * @param {JulianDate} startTime The start time. * @param {JulianDate} stopTime The stop time. */ Timeline.prototype.zoomTo = function (startTime, stopTime) { //>>includeStart('debug', pragmas.debug); if (!defined(startTime)) { throw new DeveloperError("startTime is required."); } if (!defined(stopTime)) { throw new DeveloperError("stopTime is required"); } if (JulianDate.lessThanOrEquals(stopTime, startTime)) { throw new DeveloperError("Start time must come before end time."); } //>>includeEnd('debug'); this._startJulian = startTime; this._endJulian = stopTime; this._timeBarSecondsSpan = JulianDate.secondsDifference(stopTime, startTime); // If clock is not unbounded, clamp timeline range to clock. if (this._clock && this._clock.clockRange !== ClockRange.UNBOUNDED) { var clockStart = this._clock.startTime; var clockEnd = this._clock.stopTime; var clockSpan = JulianDate.secondsDifference(clockEnd, clockStart); var startOffset = JulianDate.secondsDifference( clockStart, this._startJulian ); var endOffset = JulianDate.secondsDifference(clockEnd, this._endJulian); if (this._timeBarSecondsSpan >= clockSpan) { // if new duration longer than clock range duration, clamp to full range. this._timeBarSecondsSpan = clockSpan; this._startJulian = this._clock.startTime; this._endJulian = this._clock.stopTime; } else if (startOffset > 0) { // if timeline start is before clock start, shift right this._endJulian = JulianDate.addSeconds( this._endJulian, startOffset, new JulianDate() ); this._startJulian = clockStart; this._timeBarSecondsSpan = JulianDate.secondsDifference( this._endJulian, this._startJulian ); } else if (endOffset < 0) { // if timeline end is after clock end, shift left this._startJulian = JulianDate.addSeconds( this._startJulian, endOffset, new JulianDate() ); this._endJulian = clockEnd; this._timeBarSecondsSpan = JulianDate.secondsDifference( this._endJulian, this._startJulian ); } } this._makeTics(); var evt = document.createEvent("Event"); evt.initEvent("setzoom", true, true); evt.startJulian = this._startJulian; evt.endJulian = this._endJulian; evt.epochJulian = this._epochJulian; evt.totalSpan = this._timeBarSecondsSpan; evt.mainTicSpan = this._mainTicSpan; this._topDiv.dispatchEvent(evt); }; /** * @private */ Timeline.prototype.zoomFrom = function (amount) { var centerSec = JulianDate.secondsDifference( this._scrubJulian, this._startJulian ); if (amount > 1 || centerSec < 0 || centerSec > this._timeBarSecondsSpan) { centerSec = this._timeBarSecondsSpan * 0.5; } else { centerSec += centerSec - this._timeBarSecondsSpan * 0.5; } var centerSecFlip = this._timeBarSecondsSpan - centerSec; this.zoomTo( JulianDate.addSeconds( this._startJulian, centerSec - centerSec * amount, new JulianDate() ), JulianDate.addSeconds( this._endJulian, centerSecFlip * amount - centerSecFlip, new JulianDate() ) ); }; function twoDigits(num) { return num < 10 ? "0" + num.toString() : num.toString(); } /** * @private */ Timeline.prototype.makeLabel = function (time) { var gregorian = JulianDate.toGregorianDate(time); var millisecond = gregorian.millisecond, millisecondString = " UTC"; if (millisecond > 0 && this._timeBarSecondsSpan < 3600) { millisecondString = Math.floor(millisecond).toString(); while (millisecondString.length < 3) { millisecondString = "0" + millisecondString; } millisecondString = "." + millisecondString; } return ( timelineMonthNames[gregorian.month - 1] + " " + gregorian.day + " " + gregorian.year + " " + twoDigits(gregorian.hour) + ":" + twoDigits(gregorian.minute) + ":" + twoDigits(gregorian.second) + millisecondString ); }; /** * @private */ Timeline.prototype.smallestTicInPixels = 7.0; /** * @private */ Timeline.prototype._makeTics = function () { var timeBar = this._timeBarEle; var seconds = JulianDate.secondsDifference( this._scrubJulian, this._startJulian ); var xPos = Math.round( (seconds * this._topDiv.clientWidth) / this._timeBarSecondsSpan ); var scrubX = xPos - 8, tic; var widget = this; this._needleEle.style.left = xPos.toString() + "px"; var tics = ""; var minimumDuration = 0.01; var maximumDuration = 31536000000.0; // ~1000 years var epsilon = 1e-10; // If time step size is known, enter it here... var minSize = 0; var duration = this._timeBarSecondsSpan; if (duration < minimumDuration) { duration = minimumDuration; this._timeBarSecondsSpan = minimumDuration; this._endJulian = JulianDate.addSeconds( this._startJulian, minimumDuration, new JulianDate() ); } else if (duration > maximumDuration) { duration = maximumDuration; this._timeBarSecondsSpan = maximumDuration; this._endJulian = JulianDate.addSeconds( this._startJulian, maximumDuration, new JulianDate() ); } var timeBarWidth = this._timeBarEle.clientWidth; if (timeBarWidth < 10) { timeBarWidth = 10; } var startJulian = this._startJulian; // epsilonTime: a small fraction of one pixel width of the timeline, measured in seconds. var epsilonTime = Math.min((duration / timeBarWidth) * 1e-5, 0.4); // epochJulian: a nearby time to be considered "zero seconds", should be a round-ish number by human standards. var epochJulian; var gregorianDate = JulianDate.toGregorianDate(startJulian); if (duration > 315360000) { // 3650+ days visible, epoch is start of the first visible century. epochJulian = JulianDate.fromDate( new Date(Date.UTC(Math.floor(gregorianDate.year / 100) * 100, 0)) ); } else if (duration > 31536000) { // 365+ days visible, epoch is start of the first visible decade. epochJulian = JulianDate.fromDate( new Date(Date.UTC(Math.floor(gregorianDate.year / 10) * 10, 0)) ); } else if (duration > 86400) { // 1+ day(s) visible, epoch is start of the year. epochJulian = JulianDate.fromDate( new Date(Date.UTC(gregorianDate.year, 0)) ); } else { // Less than a day on timeline, epoch is midnight of the visible day. epochJulian = JulianDate.fromDate( new Date( Date.UTC(gregorianDate.year, gregorianDate.month, gregorianDate.day) ) ); } // startTime: Seconds offset of the left side of the timeline from epochJulian. var startTime = JulianDate.secondsDifference( this._startJulian, JulianDate.addSeconds(epochJulian, epsilonTime, new JulianDate()) ); // endTime: Seconds offset of the right side of the timeline from epochJulian. var endTime = startTime + duration; this._epochJulian = epochJulian; function getStartTic(ticScale) { return Math.floor(startTime / ticScale) * ticScale; } function getNextTic(tic, ticScale) { return Math.ceil(tic / ticScale + 0.5) * ticScale; } function getAlpha(time) { return (time - startTime) / duration; } function remainder(x, y) { //return x % y; return x - y * Math.round(x / y); } // Width in pixels of a typical label, plus padding this._rulerEle.innerHTML = this.makeLabel( JulianDate.addSeconds(this._endJulian, -minimumDuration, new JulianDate()) ); var sampleWidth = this._rulerEle.offsetWidth + 20; if (sampleWidth < 30) { // Workaround an apparent IE bug with measuring the width after going full-screen from inside an iframe. sampleWidth = 180; } var origMinSize = minSize; minSize -= epsilon; var renderState = { startTime: startTime, startJulian: startJulian, epochJulian: epochJulian, duration: duration, timeBarWidth: timeBarWidth, getAlpha: getAlpha, }; this._highlightRanges.forEach(function (highlightRange) { tics += highlightRange.render(renderState); }); // Calculate tic mark label spacing in the TimeBar. var mainTic = 0.0, subTic = 0.0, tinyTic = 0.0; // Ideal labeled tic as percentage of zoom interval var idealTic = sampleWidth / timeBarWidth; if (idealTic > 1.0) { // Clamp to width of window, for thin windows. idealTic = 1.0; } // Ideal labeled tic size in seconds idealTic *= this._timeBarSecondsSpan; var ticIndex = -1, smallestIndex = -1; var i, ticScaleLen = timelineTicScales.length; for (i = 0; i < ticScaleLen; ++i) { var sc = timelineTicScales[i]; ++ticIndex; mainTic = sc; // Find acceptable main tic size not smaller than ideal size. if (sc > idealTic && sc > minSize) { break; } if ( smallestIndex < 0 && timeBarWidth * (sc / this._timeBarSecondsSpan) >= this.smallestTicInPixels ) { smallestIndex = ticIndex; } } if (ticIndex > 0) { while (ticIndex > 0) { // Compute sub-tic size that evenly divides main tic. --ticIndex; if (Math.abs(remainder(mainTic, timelineTicScales[ticIndex])) < 0.00001) { if (timelineTicScales[ticIndex] >= minSize) { subTic = timelineTicScales[ticIndex]; } break; } } if (smallestIndex >= 0) { while (smallestIndex < ticIndex) { // Compute tiny tic size that evenly divides sub-tic. if ( Math.abs(remainder(subTic, timelineTicScales[smallestIndex])) < 0.00001 && timelineTicScales[smallestIndex] >= minSize ) { tinyTic = timelineTicScales[smallestIndex]; break; } ++smallestIndex; } } } minSize = origMinSize; if ( minSize > epsilon && tinyTic < 0.00001 && Math.abs(minSize - mainTic) > epsilon ) { tinyTic = minSize; if (minSize <= mainTic + epsilon) { subTic = 0.0; } } var lastTextLeft = -999999, textWidth; if (timeBarWidth * (tinyTic / this._timeBarSecondsSpan) >= 3.0) { for ( tic = getStartTic(tinyTic); tic <= endTime; tic = getNextTic(tic, tinyTic) ) { tics += '<span class="cesium-timeline-ticTiny" style="left: ' + Math.round(timeBarWidth * getAlpha(tic)).toString() + 'px;"></span>'; } } if (timeBarWidth * (subTic / this._timeBarSecondsSpan) >= 3.0) { for ( tic = getStartTic(subTic); tic <= endTime; tic = getNextTic(tic, subTic) ) { tics += '<span class="cesium-timeline-ticSub" style="left: ' + Math.round(timeBarWidth * getAlpha(tic)).toString() + 'px;"></span>'; } } if (timeBarWidth * (mainTic / this._timeBarSecondsSpan) >= 2.0) { this._mainTicSpan = mainTic; endTime += mainTic; tic = getStartTic(mainTic); var leapSecond = JulianDate.computeTaiMinusUtc(epochJulian); while (tic <= endTime) { var ticTime = JulianDate.addSeconds( startJulian, tic - startTime, new JulianDate() ); if (mainTic > 2.1) { var ticLeap = JulianDate.computeTaiMinusUtc(ticTime); if (Math.abs(ticLeap - leapSecond) > 0.1) { tic += ticLeap - leapSecond; ticTime = JulianDate.addSeconds( startJulian, tic - startTime, new JulianDate() ); } } var ticLeft = Math.round(timeBarWidth * getAlpha(tic)); var ticLabel = this.makeLabel(ticTime); this._rulerEle.innerHTML = ticLabel; textWidth = this._rulerEle.offsetWidth; if (textWidth < 10) { // IE iframe fullscreen sampleWidth workaround, continued. textWidth = sampleWidth; } var labelLeft = ticLeft - (textWidth / 2 - 1); if (labelLeft > lastTextLeft) { lastTextLeft = labelLeft + textWidth + 5; tics += '<span class="cesium-timeline-ticMain" style="left: ' + ticLeft.toString() + 'px;"></span>' + '<span class="cesium-timeline-ticLabel" style="left: ' + labelLeft.toString() + 'px;">' + ticLabel + "</span>"; } else { tics += '<span class="cesium-timeline-ticSub" style="left: ' + ticLeft.toString() + 'px;"></span>'; } tic = getNextTic(tic, mainTic); } } else { this._mainTicSpan = -1; } tics += '<span class="cesium-timeline-icon16" style="left:' + scrubX + 'px;bottom:0;background-position: 0 0;"></span>'; timeBar.innerHTML = tics; this._scrubElement = timeBar.lastChild; // Clear track canvas. this._context.clearRect( 0, 0, this._trackListEle.width, this._trackListEle.height ); renderState.y = 0; this._trackList.forEach(function (track) { track.render(widget._context, renderState); renderState.y += track.height; }); }; /** * @private */ Timeline.prototype.updateFromClock = function () { this._scrubJulian = this._clock.currentTime; var scrubElement = this._scrubElement; if (defined(this._scrubElement)) { var seconds = JulianDate.secondsDifference( this._scrubJulian, this._startJulian ); var xPos = Math.round( (seconds * this._topDiv.clientWidth) / this._timeBarSecondsSpan ); if (this._lastXPos !== xPos) { this._lastXPos = xPos; scrubElement.style.left = xPos - 8 + "px"; this._needleEle.style.left = xPos + "px"; } } if (defined(this._timelineDragLocation)) { this._setTimeBarTime( this._timelineDragLocation, (this._timelineDragLocation * this._timeBarSecondsSpan) / this._topDiv.clientWidth ); this.zoomTo( JulianDate.addSeconds( this._startJulian, this._timelineDrag, new JulianDate() ), JulianDate.addSeconds( this._endJulian, this._timelineDrag, new JulianDate() ) ); } }; /** * @private */ Timeline.prototype._setTimeBarTime = function (xPos, seconds) { xPos = Math.round(xPos); this._scrubJulian = JulianDate.addSeconds( this._startJulian, seconds, new JulianDate() ); if (this._scrubElement) { var scrubX = xPos - 8; this._scrubElement.style.left = scrubX.toString() + "px"; this._needleEle.style.left = xPos.toString() + "px"; } var evt = document.createEvent("Event"); evt.initEvent("settime", true, true); evt.clientX = xPos; evt.timeSeconds = seconds; evt.timeJulian = this._scrubJulian; evt.clock = this._clock; this._topDiv.dispatchEvent(evt); }; function createMouseDownCallback(timeline) { return function (e) { if (timeline._mouseMode !== timelineMouseMode.touchOnly) { if (e.button === 0) { timeline._mouseMode = timelineMouseMode.scrub; if (timeline._scrubElement) { timeline._scrubElement.style.backgroundPosition = "-16px 0"; } timeline._onMouseMove(e); } else { timeline._mouseX = e.clientX; if (e.button === 2) { timeline._mouseMode = timelineMouseMode.zoom; } else { timeline._mouseMode = timelineMouseMode.slide; } } } e.preventDefault(); }; } function createMouseUpCallback(timeline) { return function (e) { timeline._mouseMode = timelineMouseMode.none; if (timeline._scrubElement) { timeline._scrubElement.style.backgroundPosition = "0 0"; } timeline._timelineDrag = 0; timeline._timelineDragLocation = undefined; }; } function createMouseMoveCallback(timeline) { return function (e) { var dx; if (timeline._mouseMode === timelineMouseMode.scrub) { e.preventDefault(); var x = e.clientX - timeline._topDiv.getBoundingClientRect().left; if (x < 0) { timeline._timelineDragLocation = 0; timeline._timelineDrag = -0.01 * timeline._timeBarSecondsSpan; } else if (x > timeline._topDiv.clientWidth) { timeline._timelineDragLocation = timeline._topDiv.clientWidth; timeline._timelineDrag = 0.01 * timeline._timeBarSecondsSpan; } else { timeline._timelineDragLocation = undefined; timeline._setTimeBarTime( x, (x * timeline._timeBarSecondsSpan) / timeline._topDiv.clientWidth ); } } else if (timeline._mouseMode === timelineMouseMode.slide) { dx = timeline._mouseX - e.clientX; timeline._mouseX = e.clientX; if (dx !== 0) { var dsec = (dx * timeline._timeBarSecondsSpan) / timeline._topDiv.clientWidth; timeline.zoomTo( JulianDate.addSeconds(timeline._startJulian, dsec, new JulianDate()), JulianDate.addSeconds(timeline._endJulian, dsec, new JulianDate()) ); } } else if (timeline._mouseMode === timelineMouseMode.zoom) { dx = timeline._mouseX - e.clientX; timeline._mouseX = e.clientX; if (dx !== 0) { timeline.zoomFrom(Math.pow(1.01, dx)); } } }; } function createMouseWheelCallback(timeline) { return function (e) { var dy = e.wheelDeltaY || e.wheelDelta || -e.detail; timelineWheelDelta = Math.max( Math.min(Math.abs(dy), timelineWheelDelta), 1 ); dy /= timelineWheelDelta; timeline.zoomFrom(Math.pow(1.05, -dy)); }; } function createTouchStartCallback(timeline) { return function (e) { var len = e.touches.length, seconds, xPos, leftX = timeline._topDiv.getBoundingClientRect().left; e.preventDefault(); timeline._mouseMode = timelineMouseMode.touchOnly; if (len === 1) { seconds = JulianDate.secondsDifference( timeline._scrubJulian, timeline._startJulian ); xPos = Math.round( (seconds * timeline._topDiv.clientWidth) / timeline._timeBarSecondsSpan + leftX ); if (Math.abs(e.touches[0].clientX - xPos) < 50) { timeline._touchMode = timelineTouchMode.scrub; if (timeline._scrubElement) { timeline._scrubElement.style.backgroundPosition = len === 1 ? "-16px 0" : "0 0"; } } else { timeline._touchMode = timelineTouchMode.singleTap; timeline._touchState.centerX = e.touches[0].clientX - leftX; } } else if (len === 2) { timeline._touchMode = timelineTouchMode.slideZoom; timeline._touchState.centerX = (e.touches[0].clientX + e.touches[1].clientX) * 0.5 - leftX; timeline._touchState.spanX = Math.abs( e.touches[0].clientX - e.touches[1].clientX ); } else { timeline._touchMode = timelineTouchMode.ignore; } }; } function createTouchEndCallback(timeline) { return function (e) { var len = e.touches.length, leftX = timeline._topDiv.getBoundingClientRect().left; if (timeline._touchMode === timelineTouchMode.singleTap) { timeline._touchMode = timelineTouchMode.scrub; timeline._onTouchMove(e); } else if (timeline._touchMode === timelineTouchMode.scrub) { timeline._onTouchMove(e); } timeline._mouseMode = timelineMouseMode.touchOnly; if (len !== 1) { timeline._touchMode = len > 0 ? timelineTouchMode.ignore : timelineTouchMode.none; } else if (timeline._touchMode === timelineTouchMode.slideZoom) { timeline._touchState.centerX = e.touches[0].clientX - leftX; } if (timeline._scrubElement) { timeline._scrubElement.style.backgroundPosition = "0 0"; } }; } function createTouchMoveCallback(timeline) { return function (e) { var dx, x, len, newCenter, newSpan, newStartTime, zoom = 1, leftX = timeline._topDiv.getBoundingClientRect().left; if (timeline._touchMode === timelineTouchMode.singleTap) { timeline._touchMode = timelineTouchMode.slideZoom; } timeline._mouseMode = timelineMouseMode.touchOnly; if (timeline._touchMode === timelineTouchMode.scrub) { e.preventDefault(); if (e.changedTouches.length === 1) { x = e.changedTouches[0].clientX - leftX; if (x >= 0 && x <= timeline._topDiv.clientWidth) { timeline._setTimeBarTime( x, (x * timeline._timeBarSecondsSpan) / timeline._topDiv.clientWidth ); } } } else if (timeline._touchMode === timelineTouchMode.slideZoom) { len = e.touches.length; if (len === 2) { newCenter = (e.touches[0].clientX + e.touches[1].clientX) * 0.5 - leftX; newSpan = Math.abs(e.touches[0].clientX - e.touches[1].clientX); } else if (len === 1) { newCenter = e.touches[0].clientX - leftX; newSpan = 0; } if (defined(newCenter)) { if (newSpan > 0 && timeline._touchState.spanX > 0) { // Zoom and slide zoom = timeline._touchState.spanX / newSpan; newStartTime = JulianDate.addSeconds( timeline._startJulian, (timeline._touchState.centerX * timeline._timeBarSecondsSpan - newCenter * timeline._timeBarSecondsSpan * zoom) / timeline._topDiv.clientWidth, new JulianDate() ); } else { // Slide to newCenter dx = timeline._touchState.centerX - newCenter; newStartTime = JulianDate.addSeconds( timeline._startJulian, (dx * timeline._timeBarSecondsSpan) / timeline._topDiv.clientWidth, new JulianDate() ); } timeline.zoomTo( newStartTime, JulianDate.addSeconds( newStartTime, timeline._timeBarSecondsSpan * zoom, new JulianDate() ) ); timeline._touchState.centerX = newCenter; timeline._touchState.spanX = newSpan; } } }; } /** * Resizes the widget to match the container size. */ Timeline.prototype.resize = function () { var width = this.container.clientWidth; var height = this.container.clientHeight; if (width === this._lastWidth && height === this._lastHeight) { return; } this._trackContainer.style.height = height + "px"; var trackListHeight = 1; this._trackList.forEach(function (track) { trackListHeight += track.height; }); this._trackListEle.style.height = trackListHeight.toString() + "px"; this._trackListEle.width = this._trackListEle.clientWidth; this._trackListEle.height = trackListHeight; this._makeTics(); this._lastXPos = undefined; this._lastWidth = width; this._lastHeight = height; }; export default Timeline;