Timeline.js 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792
  1. /*global define*/
  2. define([
  3. '../../Core/ClockRange',
  4. '../../Core/defined',
  5. '../../Core/destroyObject',
  6. '../../Core/DeveloperError',
  7. '../../Core/JulianDate',
  8. '../getElement',
  9. './TimelineHighlightRange',
  10. './TimelineTrack'
  11. ], function(
  12. ClockRange,
  13. defined,
  14. destroyObject,
  15. DeveloperError,
  16. JulianDate,
  17. getElement,
  18. TimelineHighlightRange,
  19. TimelineTrack) {
  20. "use strict";
  21. var timelineWheelDelta = 1e12;
  22. var timelineMouseMode = {
  23. none : 0,
  24. scrub : 1,
  25. slide : 2,
  26. zoom : 3,
  27. touchOnly : 4
  28. };
  29. var timelineTouchMode = {
  30. none : 0,
  31. scrub : 1,
  32. slideZoom : 2,
  33. singleTap : 3,
  34. ignore : 4
  35. };
  36. 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
  37. 120.0, // 2min
  38. 300.0, // 5min
  39. 600.0, // 10min
  40. 900.0, // 15min
  41. 1800.0, // 30min
  42. 3600.0, // 1hr
  43. 7200.0, // 2hr
  44. 14400.0, // 4hr
  45. 21600.0, // 6hr
  46. 43200.0, // 12hr
  47. 86400.0, // 24hr
  48. 172800.0, // 2days
  49. 345600.0, // 4days
  50. 604800.0, // 7days
  51. 1296000.0, // 15days
  52. 2592000.0, // 30days
  53. 5184000.0, // 60days
  54. 7776000.0, // 90days
  55. 15552000.0, // 180days
  56. 31536000.0, // 365days
  57. 63072000.0, // 2years
  58. 126144000.0, // 4years
  59. 157680000.0, // 5years
  60. 315360000.0, // 10years
  61. 630720000.0, // 20years
  62. 1261440000.0, // 40years
  63. 1576800000.0, // 50years
  64. 3153600000.0, // 100years
  65. 6307200000.0, // 200years
  66. 12614400000.0, // 400years
  67. 15768000000.0, // 500years
  68. 31536000000.0 // 1000years
  69. ];
  70. var timelineMonthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
  71. /**
  72. * The Timeline is a widget for displaying and controlling the current scene time.
  73. * @alias Timeline
  74. * @constructor
  75. *
  76. * @param {Element} container The parent HTML container node for this widget.
  77. * @param {Clock} clock The clock to use.
  78. */
  79. var Timeline = function(container, clock) {
  80. //>>includeStart('debug', pragmas.debug);
  81. if (!defined(container)) {
  82. throw new DeveloperError('container is required.');
  83. }
  84. if (!defined(clock)) {
  85. throw new DeveloperError('clock is required.');
  86. }
  87. //>>includeEnd('debug');
  88. container = getElement(container);
  89. /**
  90. * Gets the parent container.
  91. * @type {Element}
  92. */
  93. this.container = container;
  94. var topDiv = document.createElement('div');
  95. topDiv.className = 'cesium-timeline-main';
  96. container.appendChild(topDiv);
  97. this._topDiv = topDiv;
  98. this._endJulian = undefined;
  99. this._epochJulian = undefined;
  100. this._lastXPos = undefined;
  101. this._scrubElement = undefined;
  102. this._startJulian = undefined;
  103. this._timeBarSecondsSpan = undefined;
  104. this._clock = clock;
  105. this._scrubJulian = clock.currentTime;
  106. this._mainTicSpan = -1;
  107. this._mouseMode = timelineMouseMode.none;
  108. this._touchMode = timelineTouchMode.none;
  109. this._touchState = {
  110. centerX : 0,
  111. spanX : 0
  112. };
  113. this._mouseX = 0;
  114. this._timelineDrag = 0;
  115. this._timelineDragLocation = undefined;
  116. this._lastHeight = undefined;
  117. this._lastWidth = undefined;
  118. this._topDiv.innerHTML = '<div class="cesium-timeline-bar"></div><div class="cesium-timeline-trackContainer">' +
  119. '<canvas class="cesium-timeline-tracks" width="10" height="1">' +
  120. '</canvas></div><div class="cesium-timeline-needle"></div><span class="cesium-timeline-ruler"></span>';
  121. this._timeBarEle = this._topDiv.childNodes[0];
  122. this._trackContainer = this._topDiv.childNodes[1];
  123. this._trackListEle = this._topDiv.childNodes[1].childNodes[0];
  124. this._needleEle = this._topDiv.childNodes[2];
  125. this._rulerEle = this._topDiv.childNodes[3];
  126. this._context = this._trackListEle.getContext('2d');
  127. this._trackList = [];
  128. this._highlightRanges = [];
  129. this.zoomTo(clock.startTime, clock.stopTime);
  130. this._onMouseDown = createMouseDownCallback(this);
  131. this._onMouseUp = createMouseUpCallback(this);
  132. this._onMouseMove = createMouseMoveCallback(this);
  133. this._onMouseWheel = createMouseWheelCallback(this);
  134. this._onTouchStart = createTouchStartCallback(this);
  135. this._onTouchMove = createTouchMoveCallback(this);
  136. this._onTouchEnd = createTouchEndCallback(this);
  137. var timeBarEle = this._timeBarEle;
  138. document.addEventListener('mouseup', this._onMouseUp, false);
  139. document.addEventListener('mousemove', this._onMouseMove, false);
  140. timeBarEle.addEventListener('mousedown', this._onMouseDown, false);
  141. timeBarEle.addEventListener('DOMMouseScroll', this._onMouseWheel, false); // Mozilla mouse wheel
  142. timeBarEle.addEventListener('mousewheel', this._onMouseWheel, false);
  143. timeBarEle.addEventListener('touchstart', this._onTouchStart, false);
  144. timeBarEle.addEventListener('touchmove', this._onTouchMove, false);
  145. timeBarEle.addEventListener('touchend', this._onTouchEnd, false);
  146. this._topDiv.oncontextmenu = function() {
  147. return false;
  148. };
  149. clock.onTick.addEventListener(this.updateFromClock, this);
  150. this.updateFromClock();
  151. };
  152. /**
  153. * @private
  154. */
  155. Timeline.prototype.addEventListener = function(type, listener, useCapture) {
  156. this._topDiv.addEventListener(type, listener, useCapture);
  157. };
  158. /**
  159. * @private
  160. */
  161. Timeline.prototype.removeEventListener = function(type, listener, useCapture) {
  162. this._topDiv.removeEventListener(type, listener, useCapture);
  163. };
  164. /**
  165. * @returns {Boolean} true if the object has been destroyed, false otherwise.
  166. */
  167. Timeline.prototype.isDestroyed = function() {
  168. return false;
  169. };
  170. /**
  171. * Destroys the widget. Should be called if permanently
  172. * removing the widget from layout.
  173. */
  174. Timeline.prototype.destroy = function() {
  175. this._clock.onTick.removeEventListener(this.updateFromClock, this);
  176. document.removeEventListener('mouseup', this._onMouseUp, false);
  177. document.removeEventListener('mousemove', this._onMouseMove, false);
  178. var timeBarEle = this._timeBarEle;
  179. timeBarEle.removeEventListener('mousedown', this._onMouseDown, false);
  180. timeBarEle.removeEventListener('DOMMouseScroll', this._onMouseWheel, false); // Mozilla mouse wheel
  181. timeBarEle.removeEventListener('mousewheel', this._onMouseWheel, false);
  182. timeBarEle.removeEventListener('touchstart', this._onTouchStart, false);
  183. timeBarEle.removeEventListener('touchmove', this._onTouchMove, false);
  184. timeBarEle.removeEventListener('touchend', this._onTouchEnd, false);
  185. this.container.removeChild(this._topDiv);
  186. destroyObject(this);
  187. };
  188. /**
  189. * @private
  190. */
  191. Timeline.prototype.addHighlightRange = function(color, heightInPx, base) {
  192. var newHighlightRange = new TimelineHighlightRange(color, heightInPx, base);
  193. this._highlightRanges.push(newHighlightRange);
  194. this.resize();
  195. return newHighlightRange;
  196. };
  197. /**
  198. * @private
  199. */
  200. Timeline.prototype.addTrack = function(interval, heightInPx, color, backgroundColor) {
  201. var newTrack = new TimelineTrack(interval, heightInPx, color, backgroundColor);
  202. this._trackList.push(newTrack);
  203. this._lastHeight = undefined;
  204. this.resize();
  205. return newTrack;
  206. };
  207. /**
  208. * Sets the view to the provided times.
  209. *
  210. * @param {JulianDate} startTime The start time.
  211. * @param {JulianDate} stopTime The stop time.
  212. */
  213. Timeline.prototype.zoomTo = function(startTime, stopTime) {
  214. //>>includeStart('debug', pragmas.debug);
  215. if (!defined(startTime)) {
  216. throw new DeveloperError('startTime is required.');
  217. }
  218. if (!defined(stopTime)) {
  219. throw new DeveloperError('stopTime is required');
  220. }
  221. if (JulianDate.lessThanOrEquals(stopTime, startTime)) {
  222. throw new DeveloperError('Start time must come before end time.');
  223. }
  224. //>>includeEnd('debug');
  225. this._startJulian = startTime;
  226. this._endJulian = stopTime;
  227. this._timeBarSecondsSpan = JulianDate.secondsDifference(stopTime, startTime);
  228. // If clock is not unbounded, clamp timeline range to clock.
  229. if (this._clock && (this._clock.clockRange !== ClockRange.UNBOUNDED)) {
  230. var clockStart = this._clock.startTime;
  231. var clockEnd = this._clock.stopTime;
  232. var clockSpan = JulianDate.secondsDifference(clockEnd, clockStart);
  233. var startOffset = JulianDate.secondsDifference(clockStart, this._startJulian);
  234. var endOffset = JulianDate.secondsDifference(clockEnd, this._endJulian);
  235. if (this._timeBarSecondsSpan >= clockSpan) {
  236. // if new duration longer than clock range duration, clamp to full range.
  237. this._timeBarSecondsSpan = clockSpan;
  238. this._startJulian = this._clock.startTime;
  239. this._endJulian = this._clock.stopTime;
  240. } else if (startOffset > 0) {
  241. // if timeline start is before clock start, shift right
  242. this._endJulian = JulianDate.addSeconds(this._endJulian, startOffset, new JulianDate());
  243. this._startJulian = clockStart;
  244. this._timeBarSecondsSpan = JulianDate.secondsDifference(this._endJulian, this._startJulian);
  245. } else if (endOffset < 0) {
  246. // if timeline end is after clock end, shift left
  247. this._startJulian = JulianDate.addSeconds(this._startJulian, endOffset, new JulianDate());
  248. this._endJulian = clockEnd;
  249. this._timeBarSecondsSpan = JulianDate.secondsDifference(this._endJulian, this._startJulian);
  250. }
  251. }
  252. this._makeTics();
  253. var evt = document.createEvent('Event');
  254. evt.initEvent('setzoom', true, true);
  255. evt.startJulian = this._startJulian;
  256. evt.endJulian = this._endJulian;
  257. evt.epochJulian = this._epochJulian;
  258. evt.totalSpan = this._timeBarSecondsSpan;
  259. evt.mainTicSpan = this._mainTicSpan;
  260. this._topDiv.dispatchEvent(evt);
  261. };
  262. /**
  263. * @private
  264. */
  265. Timeline.prototype.zoomFrom = function(amount) {
  266. var centerSec = JulianDate.secondsDifference(this._scrubJulian, this._startJulian);
  267. if ((amount > 1) || (centerSec < 0) || (centerSec > this._timeBarSecondsSpan)) {
  268. centerSec = this._timeBarSecondsSpan * 0.5;
  269. } else {
  270. centerSec += (centerSec - this._timeBarSecondsSpan * 0.5);
  271. }
  272. var centerSecFlip = this._timeBarSecondsSpan - centerSec;
  273. this.zoomTo(JulianDate.addSeconds(this._startJulian, centerSec - (centerSec * amount), new JulianDate()), JulianDate.addSeconds(this._endJulian, (centerSecFlip * amount) - centerSecFlip, new JulianDate()));
  274. };
  275. function twoDigits(num) {
  276. return ((num < 10) ? ('0' + num.toString()) : num.toString());
  277. }
  278. /**
  279. * @private
  280. */
  281. Timeline.prototype.makeLabel = function(time) {
  282. var gregorian = JulianDate.toGregorianDate(time);
  283. var millisecond = gregorian.millisecond, millisecondString = ' UTC';
  284. if ((millisecond > 0) && (this._timeBarSecondsSpan < 3600)) {
  285. millisecondString = Math.floor(millisecond).toString();
  286. while (millisecondString.length < 3) {
  287. millisecondString = '0' + millisecondString;
  288. }
  289. millisecondString = '.' + millisecondString;
  290. }
  291. return timelineMonthNames[gregorian.month - 1] + ' ' + gregorian.day + ' ' + gregorian.year + ' ' + twoDigits(gregorian.hour) +
  292. ':' + twoDigits(gregorian.minute) + ':' + twoDigits(gregorian.second) + millisecondString;
  293. };
  294. /**
  295. * @private
  296. */
  297. Timeline.prototype.smallestTicInPixels = 7.0;
  298. /**
  299. * @private
  300. */
  301. Timeline.prototype._makeTics = function() {
  302. var timeBar = this._timeBarEle;
  303. var seconds = JulianDate.secondsDifference(this._scrubJulian, this._startJulian);
  304. var xPos = Math.round(seconds * this._topDiv.clientWidth / this._timeBarSecondsSpan);
  305. var scrubX = xPos - 8, tic;
  306. var widget = this;
  307. this._needleEle.style.left = xPos.toString() + 'px';
  308. var tics = '';
  309. var minimumDuration = 0.01;
  310. var maximumDuration = 31536000000.0; // ~1000 years
  311. var epsilon = 1e-10;
  312. // If time step size is known, enter it here...
  313. var minSize = 0;
  314. var duration = this._timeBarSecondsSpan;
  315. if (duration < minimumDuration) {
  316. duration = minimumDuration;
  317. this._timeBarSecondsSpan = minimumDuration;
  318. this._endJulian = JulianDate.addSeconds(this._startJulian, minimumDuration, new JulianDate());
  319. } else if (duration > maximumDuration) {
  320. duration = maximumDuration;
  321. this._timeBarSecondsSpan = maximumDuration;
  322. this._endJulian = JulianDate.addSeconds(this._startJulian, maximumDuration, new JulianDate());
  323. }
  324. var timeBarWidth = this._timeBarEle.clientWidth;
  325. if (timeBarWidth < 10) {
  326. timeBarWidth = 10;
  327. }
  328. var startJulian = this._startJulian;
  329. // epsilonTime: a small fraction of one pixel width of the timeline, measured in seconds.
  330. var epsilonTime = Math.min((duration / timeBarWidth) * 1e-5, 0.4);
  331. // epochJulian: a nearby time to be considered "zero seconds", should be a round-ish number by human standards.
  332. var epochJulian;
  333. if (duration > 315360000) { // 3650+ days visible, epoch is start of the first visible century.
  334. epochJulian = JulianDate.fromIso8601(JulianDate.toDate(startJulian).toISOString().substring(0, 2) + '00-01-01T00:00:00Z');
  335. } else if (duration > 31536000) { // 365+ days visible, epoch is start of the first visible decade.
  336. epochJulian = JulianDate.fromIso8601(JulianDate.toDate(startJulian).toISOString().substring(0, 3) + '0-01-01T00:00:00Z');
  337. } else if (duration > 86400) { // 1+ day(s) visible, epoch is start of the year.
  338. epochJulian = JulianDate.fromIso8601(JulianDate.toDate(startJulian).toISOString().substring(0, 4) + '-01-01T00:00:00Z');
  339. } else { // Less than a day on timeline, epoch is midnight of the visible day.
  340. epochJulian = JulianDate.fromIso8601(JulianDate.toDate(startJulian).toISOString().substring(0, 10) + 'T00:00:00Z');
  341. }
  342. // startTime: Seconds offset of the left side of the timeline from epochJulian.
  343. var startTime = JulianDate.secondsDifference(this._startJulian, JulianDate.addSeconds(epochJulian, epsilonTime, new JulianDate()));
  344. // endTime: Seconds offset of the right side of the timeline from epochJulian.
  345. var endTime = startTime + duration;
  346. this._epochJulian = epochJulian;
  347. function getStartTic(ticScale) {
  348. return Math.floor(startTime / ticScale) * ticScale;
  349. }
  350. function getNextTic(tic, ticScale) {
  351. return Math.ceil((tic / ticScale) + 0.5) * ticScale;
  352. }
  353. function getAlpha(time) {
  354. return (time - startTime) / duration;
  355. }
  356. function remainder(x, y) {
  357. //return x % y;
  358. return x - (y * Math.round(x / y));
  359. }
  360. // Width in pixels of a typical label, plus padding
  361. this._rulerEle.innerHTML = this.makeLabel(JulianDate.addSeconds(this._endJulian, -minimumDuration, new JulianDate()));
  362. var sampleWidth = this._rulerEle.offsetWidth + 20;
  363. if (sampleWidth < 30) {
  364. // Workaround an apparent IE bug with measuring the width after going full-screen from inside an iframe.
  365. sampleWidth = 180;
  366. }
  367. var origMinSize = minSize;
  368. minSize -= epsilon;
  369. var renderState = {
  370. startTime : startTime,
  371. startJulian : startJulian,
  372. epochJulian : epochJulian,
  373. duration : duration,
  374. timeBarWidth : timeBarWidth,
  375. getAlpha : getAlpha
  376. };
  377. this._highlightRanges.forEach(function(highlightRange) {
  378. tics += highlightRange.render(renderState);
  379. });
  380. // Calculate tic mark label spacing in the TimeBar.
  381. var mainTic = 0.0, subTic = 0.0, tinyTic = 0.0;
  382. // Ideal labeled tic as percentage of zoom interval
  383. var idealTic = sampleWidth / timeBarWidth;
  384. if (idealTic > 1.0) {
  385. // Clamp to width of window, for thin windows.
  386. idealTic = 1.0;
  387. }
  388. // Ideal labeled tic size in seconds
  389. idealTic *= this._timeBarSecondsSpan;
  390. var ticIndex = -1, smallestIndex = -1;
  391. var i, ticScaleLen = timelineTicScales.length;
  392. for (i = 0; i < ticScaleLen; ++i) {
  393. var sc = timelineTicScales[i];
  394. ++ticIndex;
  395. mainTic = sc;
  396. // Find acceptable main tic size not smaller than ideal size.
  397. if ((sc > idealTic) && (sc > minSize)) {
  398. break;
  399. }
  400. if ((smallestIndex < 0) && ((timeBarWidth * (sc / this._timeBarSecondsSpan)) >= this.smallestTicInPixels)) {
  401. smallestIndex = ticIndex;
  402. }
  403. }
  404. if (ticIndex > 0) {
  405. while (ticIndex > 0) // Compute sub-tic size that evenly divides main tic.
  406. {
  407. --ticIndex;
  408. if (Math.abs(remainder(mainTic, timelineTicScales[ticIndex])) < 0.00001) {
  409. if (timelineTicScales[ticIndex] >= minSize) {
  410. subTic = timelineTicScales[ticIndex];
  411. }
  412. break;
  413. }
  414. }
  415. if (smallestIndex >= 0) {
  416. while (smallestIndex < ticIndex) // Compute tiny tic size that evenly divides sub-tic.
  417. {
  418. if ((Math.abs(remainder(subTic, timelineTicScales[smallestIndex])) < 0.00001) && (timelineTicScales[smallestIndex] >= minSize)) {
  419. tinyTic = timelineTicScales[smallestIndex];
  420. break;
  421. }
  422. ++smallestIndex;
  423. }
  424. }
  425. }
  426. minSize = origMinSize;
  427. if ((minSize > epsilon) && (tinyTic < 0.00001) && (Math.abs(minSize - mainTic) > epsilon)) {
  428. tinyTic = minSize;
  429. if (minSize <= (mainTic + epsilon)) {
  430. subTic = 0.0;
  431. }
  432. }
  433. var lastTextLeft = -999999, textWidth;
  434. if ((timeBarWidth * (tinyTic / this._timeBarSecondsSpan)) >= 3.0) {
  435. for (tic = getStartTic(tinyTic); tic <= endTime; tic = getNextTic(tic, tinyTic)) {
  436. tics += '<span class="cesium-timeline-ticTiny" style="left: ' + Math.round(timeBarWidth * getAlpha(tic)).toString() + 'px;"></span>';
  437. }
  438. }
  439. if ((timeBarWidth * (subTic / this._timeBarSecondsSpan)) >= 3.0) {
  440. for (tic = getStartTic(subTic); tic <= endTime; tic = getNextTic(tic, subTic)) {
  441. tics += '<span class="cesium-timeline-ticSub" style="left: ' + Math.round(timeBarWidth * getAlpha(tic)).toString() + 'px;"></span>';
  442. }
  443. }
  444. if ((timeBarWidth * (mainTic / this._timeBarSecondsSpan)) >= 2.0) {
  445. this._mainTicSpan = mainTic;
  446. endTime += mainTic;
  447. tic = getStartTic(mainTic);
  448. var leapSecond = JulianDate.computeTaiMinusUtc(epochJulian);
  449. while (tic <= endTime) {
  450. var ticTime = JulianDate.addSeconds(startJulian, tic - startTime, new JulianDate());
  451. if (mainTic > 2.1) {
  452. var ticLeap = JulianDate.computeTaiMinusUtc(ticTime);
  453. if (Math.abs(ticLeap - leapSecond) > 0.1) {
  454. tic += (ticLeap - leapSecond);
  455. ticTime = JulianDate.addSeconds(startJulian, tic - startTime, new JulianDate());
  456. }
  457. }
  458. var ticLeft = Math.round(timeBarWidth * getAlpha(tic));
  459. var ticLabel = this.makeLabel(ticTime);
  460. this._rulerEle.innerHTML = ticLabel;
  461. textWidth = this._rulerEle.offsetWidth;
  462. if (textWidth < 10) {
  463. // IE iframe fullscreen sampleWidth workaround, continued.
  464. textWidth = sampleWidth;
  465. }
  466. var labelLeft = ticLeft - ((textWidth / 2) - 1);
  467. if (labelLeft > lastTextLeft) {
  468. lastTextLeft = labelLeft + textWidth + 5;
  469. tics += '<span class="cesium-timeline-ticMain" style="left: ' + ticLeft.toString() + 'px;"></span>' + '<span class="cesium-timeline-ticLabel" style="left: ' + labelLeft.toString() +
  470. 'px;">' + ticLabel + '</span>';
  471. } else {
  472. tics += '<span class="cesium-timeline-ticSub" style="left: ' + ticLeft.toString() + 'px;"></span>';
  473. }
  474. tic = getNextTic(tic, mainTic);
  475. }
  476. } else {
  477. this._mainTicSpan = -1;
  478. }
  479. tics += '<span class="cesium-timeline-icon16" style="left:' + scrubX + 'px;bottom:0;background-position: 0px 0px;"></span>';
  480. timeBar.innerHTML = tics;
  481. this._scrubElement = timeBar.lastChild;
  482. // Clear track canvas.
  483. this._context.clearRect(0, 0, this._trackListEle.width, this._trackListEle.height);
  484. renderState.y = 0;
  485. this._trackList.forEach(function(track) {
  486. track.render(widget._context, renderState);
  487. renderState.y += track.height;
  488. });
  489. };
  490. /**
  491. * @private
  492. */
  493. Timeline.prototype.updateFromClock = function() {
  494. this._scrubJulian = this._clock.currentTime;
  495. var scrubElement = this._scrubElement;
  496. if (defined(this._scrubElement)) {
  497. var seconds = JulianDate.secondsDifference(this._scrubJulian, this._startJulian);
  498. var xPos = Math.round(seconds * this._topDiv.clientWidth / this._timeBarSecondsSpan);
  499. if (this._lastXPos !== xPos) {
  500. this._lastXPos = xPos;
  501. scrubElement.style.left = (xPos - 8) + 'px';
  502. this._needleEle.style.left = xPos + 'px';
  503. }
  504. }
  505. if (defined(this._timelineDragLocation)) {
  506. this._setTimeBarTime(this._timelineDragLocation, this._timelineDragLocation * this._timeBarSecondsSpan / this._topDiv.clientWidth);
  507. this.zoomTo(JulianDate.addSeconds(this._startJulian, this._timelineDrag, new JulianDate()), JulianDate.addSeconds(this._endJulian, this._timelineDrag, new JulianDate()));
  508. }
  509. };
  510. /**
  511. * @private
  512. */
  513. Timeline.prototype._setTimeBarTime = function(xPos, seconds) {
  514. xPos = Math.round(xPos);
  515. this._scrubJulian = JulianDate.addSeconds(this._startJulian, seconds, new JulianDate());
  516. if (this._scrubElement) {
  517. var scrubX = xPos - 8;
  518. this._scrubElement.style.left = scrubX.toString() + 'px';
  519. this._needleEle.style.left = xPos.toString() + 'px';
  520. }
  521. var evt = document.createEvent('Event');
  522. evt.initEvent('settime', true, true);
  523. evt.clientX = xPos;
  524. evt.timeSeconds = seconds;
  525. evt.timeJulian = this._scrubJulian;
  526. evt.clock = this._clock;
  527. this._topDiv.dispatchEvent(evt);
  528. };
  529. function createMouseDownCallback(timeline) {
  530. return function(e) {
  531. if (timeline._mouseMode !== timelineMouseMode.touchOnly) {
  532. if (e.button === 0) {
  533. timeline._mouseMode = timelineMouseMode.scrub;
  534. if (timeline._scrubElement) {
  535. timeline._scrubElement.style.backgroundPosition = '-16px 0';
  536. }
  537. timeline._onMouseMove(e);
  538. } else {
  539. timeline._mouseX = e.clientX;
  540. if (e.button === 2) {
  541. timeline._mouseMode = timelineMouseMode.zoom;
  542. } else {
  543. timeline._mouseMode = timelineMouseMode.slide;
  544. }
  545. }
  546. }
  547. e.preventDefault();
  548. };
  549. }
  550. function createMouseUpCallback(timeline) {
  551. return function(e) {
  552. timeline._mouseMode = timelineMouseMode.none;
  553. if (timeline._scrubElement) {
  554. timeline._scrubElement.style.backgroundPosition = '0px 0px';
  555. }
  556. timeline._timelineDrag = 0;
  557. timeline._timelineDragLocation = undefined;
  558. };
  559. }
  560. function createMouseMoveCallback(timeline) {
  561. return function(e) {
  562. var dx;
  563. if (timeline._mouseMode === timelineMouseMode.scrub) {
  564. e.preventDefault();
  565. var x = e.clientX - timeline._topDiv.getBoundingClientRect().left;
  566. if (x < 0) {
  567. timeline._timelineDragLocation = 0;
  568. timeline._timelineDrag = -0.01 * timeline._timeBarSecondsSpan;
  569. } else if (x > timeline._topDiv.clientWidth) {
  570. timeline._timelineDragLocation = timeline._topDiv.clientWidth;
  571. timeline._timelineDrag = 0.01 * timeline._timeBarSecondsSpan;
  572. } else {
  573. timeline._timelineDragLocation = undefined;
  574. timeline._setTimeBarTime(x, x * timeline._timeBarSecondsSpan / timeline._topDiv.clientWidth);
  575. }
  576. } else if (timeline._mouseMode === timelineMouseMode.slide) {
  577. dx = timeline._mouseX - e.clientX;
  578. timeline._mouseX = e.clientX;
  579. if (dx !== 0) {
  580. var dsec = dx * timeline._timeBarSecondsSpan / timeline._topDiv.clientWidth;
  581. timeline.zoomTo(JulianDate.addSeconds(timeline._startJulian, dsec, new JulianDate()), JulianDate.addSeconds(timeline._endJulian, dsec, new JulianDate()));
  582. }
  583. } else if (timeline._mouseMode === timelineMouseMode.zoom) {
  584. dx = timeline._mouseX - e.clientX;
  585. timeline._mouseX = e.clientX;
  586. if (dx !== 0) {
  587. timeline.zoomFrom(Math.pow(1.01, dx));
  588. }
  589. }
  590. };
  591. }
  592. function createMouseWheelCallback(timeline) {
  593. return function(e) {
  594. var dy = e.wheelDeltaY || e.wheelDelta || (-e.detail);
  595. timelineWheelDelta = Math.max(Math.min(Math.abs(dy), timelineWheelDelta), 1);
  596. dy /= timelineWheelDelta;
  597. timeline.zoomFrom(Math.pow(1.05, -dy));
  598. };
  599. }
  600. function createTouchStartCallback(timeline) {
  601. return function(e) {
  602. var len = e.touches.length, seconds, xPos, leftX = timeline._topDiv.getBoundingClientRect().left;
  603. e.preventDefault();
  604. timeline._mouseMode = timelineMouseMode.touchOnly;
  605. if (len === 1) {
  606. seconds = JulianDate.secondsDifference(timeline._scrubJulian, timeline._startJulian);
  607. xPos = Math.round(seconds * timeline._topDiv.clientWidth / timeline._timeBarSecondsSpan + leftX);
  608. if (Math.abs(e.touches[0].clientX - xPos) < 50) {
  609. timeline._touchMode = timelineTouchMode.scrub;
  610. if (timeline._scrubElement) {
  611. timeline._scrubElement.style.backgroundPosition = (len === 1) ? '-16px 0' : '0 0';
  612. }
  613. } else {
  614. timeline._touchMode = timelineTouchMode.singleTap;
  615. timeline._touchState.centerX = e.touches[0].clientX - leftX;
  616. }
  617. } else if (len === 2) {
  618. timeline._touchMode = timelineTouchMode.slideZoom;
  619. timeline._touchState.centerX = (e.touches[0].clientX + e.touches[1].clientX) * 0.5 - leftX;
  620. timeline._touchState.spanX = Math.abs(e.touches[0].clientX - e.touches[1].clientX);
  621. } else {
  622. timeline._touchMode = timelineTouchMode.ignore;
  623. }
  624. };
  625. }
  626. function createTouchEndCallback(timeline) {
  627. return function(e) {
  628. var len = e.touches.length, leftX = timeline._topDiv.getBoundingClientRect().left;
  629. if (timeline._touchMode === timelineTouchMode.singleTap) {
  630. timeline._touchMode = timelineTouchMode.scrub;
  631. timeline._handleTouchMove(e);
  632. } else if (timeline._touchMode === timelineTouchMode.scrub) {
  633. timeline._handleTouchMove(e);
  634. }
  635. timeline._mouseMode = timelineMouseMode.touchOnly;
  636. if (len !== 1) {
  637. timeline._touchMode = (len > 0) ? timelineTouchMode.ignore : timelineTouchMode.none;
  638. } else if (timeline._touchMode === timelineTouchMode.slideZoom) {
  639. timeline._touchState.centerX = e.touches[0].clientX - leftX;
  640. }
  641. if (timeline._scrubElement) {
  642. timeline._scrubElement.style.backgroundPosition = '0 0';
  643. }
  644. };
  645. }
  646. function createTouchMoveCallback(timeline) {
  647. return function(e) {
  648. var dx, x, len, newCenter, newSpan, newStartTime, zoom = 1, leftX = timeline._topDiv.getBoundingClientRect().left;
  649. if (timeline._touchMode === timelineTouchMode.singleTap) {
  650. timeline._touchMode = timelineTouchMode.slideZoom;
  651. }
  652. timeline._mouseMode = timelineMouseMode.touchOnly;
  653. if (timeline._touchMode === timelineTouchMode.scrub) {
  654. e.preventDefault();
  655. if (e.changedTouches.length === 1) {
  656. x = e.changedTouches[0].clientX - leftX;
  657. if ((x >= 0) && (x <= timeline._topDiv.clientWidth)) {
  658. timeline._setTimeBarTime(x, x * timeline._timeBarSecondsSpan / timeline._topDiv.clientWidth);
  659. }
  660. }
  661. } else if (timeline._touchMode === timelineTouchMode.slideZoom) {
  662. len = e.touches.length;
  663. if (len === 2) {
  664. newCenter = (e.touches[0].clientX + e.touches[1].clientX) * 0.5 - leftX;
  665. newSpan = Math.abs(e.touches[0].clientX - e.touches[1].clientX);
  666. } else if (len === 1) {
  667. newCenter = e.touches[0].clientX - leftX;
  668. newSpan = 0;
  669. }
  670. if (defined(newCenter)) {
  671. if ((newSpan > 0) && (timeline._touchState.spanX > 0)) {
  672. // Zoom and slide
  673. zoom = (timeline._touchState.spanX / newSpan);
  674. newStartTime = JulianDate.addSeconds(timeline._startJulian, ((timeline._touchState.centerX * timeline._timeBarSecondsSpan) - (newCenter * timeline._timeBarSecondsSpan * zoom)) / timeline._topDiv.clientWidth, new JulianDate());
  675. } else {
  676. // Slide to newCenter
  677. dx = timeline._touchState.centerX - newCenter;
  678. newStartTime = JulianDate.addSeconds(timeline._startJulian, dx * timeline._timeBarSecondsSpan / timeline._topDiv.clientWidth, new JulianDate());
  679. }
  680. timeline.zoomTo(newStartTime, JulianDate.addSeconds(newStartTime, timeline._timeBarSecondsSpan * zoom, new JulianDate()));
  681. timeline._touchState.centerX = newCenter;
  682. timeline._touchState.spanX = newSpan;
  683. }
  684. }
  685. };
  686. }
  687. /**
  688. * Resizes the widget to match the container size.
  689. */
  690. Timeline.prototype.resize = function() {
  691. var width = this.container.clientWidth;
  692. var height = this.container.clientHeight;
  693. if (width === this._lastWidth && height === this._lastHeight) {
  694. return;
  695. }
  696. this._trackContainer.style.height = height + 'px';
  697. var trackListHeight = 1;
  698. this._trackList.forEach(function(track) {
  699. trackListHeight += track.height;
  700. });
  701. this._trackListEle.style.height = trackListHeight.toString() + 'px';
  702. this._trackListEle.width = this._trackListEle.clientWidth;
  703. this._trackListEle.height = trackListHeight;
  704. this._makeTics();
  705. this._lastWidth = width;
  706. this._lastHeight = height;
  707. };
  708. return Timeline;
  709. });