Timeline.js 29 KB

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