/*global define*/ define([ '../../Core/ClockRange', '../../Core/defined', '../../Core/destroyObject', '../../Core/DeveloperError', '../../Core/JulianDate', '../getElement', './TimelineHighlightRange', './TimelineTrack' ], function( ClockRange, defined, destroyObject, DeveloperError, JulianDate, getElement, TimelineHighlightRange, TimelineTrack) { "use strict"; 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. */ var Timeline = function(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); /** * Gets the parent container. * @type {Element} */ this.container = container; var topDiv = document.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 = '
' + '' + '
'; 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; document.addEventListener('mouseup', this._onMouseUp, false); document.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); 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); document.removeEventListener('mouseup', this._onMouseUp, false); document.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); 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; if (duration > 315360000) { // 3650+ days visible, epoch is start of the first visible century. epochJulian = JulianDate.fromIso8601(JulianDate.toDate(startJulian).toISOString().substring(0, 2) + '00-01-01T00:00:00Z'); } else if (duration > 31536000) { // 365+ days visible, epoch is start of the first visible decade. epochJulian = JulianDate.fromIso8601(JulianDate.toDate(startJulian).toISOString().substring(0, 3) + '0-01-01T00:00:00Z'); } else if (duration > 86400) { // 1+ day(s) visible, epoch is start of the year. epochJulian = JulianDate.fromIso8601(JulianDate.toDate(startJulian).toISOString().substring(0, 4) + '-01-01T00:00:00Z'); } else { // Less than a day on timeline, epoch is midnight of the visible day. epochJulian = JulianDate.fromIso8601(JulianDate.toDate(startJulian).toISOString().substring(0, 10) + 'T00:00:00Z'); } // 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 += ''; } } if ((timeBarWidth * (subTic / this._timeBarSecondsSpan)) >= 3.0) { for (tic = getStartTic(subTic); tic <= endTime; tic = getNextTic(tic, subTic)) { tics += ''; } } 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 += '' + '' + ticLabel + ''; } else { tics += ''; } tic = getNextTic(tic, mainTic); } } else { this._mainTicSpan = -1; } tics += ''; 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 = '0px 0px'; } 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._handleTouchMove(e); } else if (timeline._touchMode === timelineTouchMode.scrub) { timeline._handleTouchMove(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._lastWidth = width; this._lastHeight = height; }; return Timeline; });