vis.js is a dynamic, browser-based visualization library
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

796 lines
25 KiB

11 years ago
11 years ago
11 years ago
8 years ago
8 years ago
11 years ago
11 years ago
11 years ago
11 years ago
  1. var moment = require('../module/moment');
  2. var util = require('../util');
  3. var DataSet = require('../DataSet');
  4. var DataView = require('../DataView');
  5. var Range = require('./Range');
  6. var Core = require('./Core');
  7. var TimeAxis = require('./component/TimeAxis');
  8. var CurrentTime = require('./component/CurrentTime');
  9. var CustomTime = require('./component/CustomTime');
  10. var ItemSet = require('./component/ItemSet');
  11. var printStyle = require('../shared/Validator').printStyle;
  12. var allOptions = require('./optionsTimeline').allOptions;
  13. var configureOptions = require('./optionsTimeline').configureOptions;
  14. var Configurator = require('../shared/Configurator').default;
  15. var Validator = require('../shared/Validator').default;
  16. /**
  17. * Create a timeline visualization
  18. * @param {HTMLElement} container
  19. * @param {vis.DataSet | vis.DataView | Array} [items]
  20. * @param {vis.DataSet | vis.DataView | Array} [groups]
  21. * @param {Object} [options] See Timeline.setOptions for the available options.
  22. * @constructor Timeline
  23. * @extends Core
  24. */
  25. function Timeline (container, items, groups, options) {
  26. this.initTime = new Date();
  27. this.itemsDone = false;
  28. if (!(this instanceof Timeline)) {
  29. throw new SyntaxError('Constructor must be called with the new operator');
  30. }
  31. // if the third element is options, the forth is groups (optionally);
  32. if (!(Array.isArray(groups) || groups instanceof DataSet || groups instanceof DataView) && groups instanceof Object) {
  33. var forthArgument = options;
  34. options = groups;
  35. groups = forthArgument;
  36. }
  37. // TODO: REMOVE THIS in the next MAJOR release
  38. // see https://github.com/almende/vis/issues/2511
  39. if (options && options.throttleRedraw) {
  40. console.warn("Timeline option \"throttleRedraw\" is DEPRICATED and no longer supported. It will be removed in the next MAJOR release.");
  41. }
  42. var me = this;
  43. this.defaultOptions = {
  44. start: null,
  45. end: null,
  46. autoResize: true,
  47. orientation: {
  48. axis: 'bottom', // axis orientation: 'bottom', 'top', or 'both'
  49. item: 'bottom' // not relevant
  50. },
  51. moment: moment,
  52. width: null,
  53. height: null,
  54. maxHeight: null,
  55. minHeight: null,
  56. };
  57. this.options = util.deepExtend({}, this.defaultOptions);
  58. // Create the DOM, props, and emitter
  59. this._create(container);
  60. if (!options || (options && typeof options.rtl == "undefined")) {
  61. this.dom.root.style.visibility = 'hidden';
  62. var directionFromDom, domNode = this.dom.root;
  63. while (!directionFromDom && domNode) {
  64. directionFromDom = window.getComputedStyle(domNode, null).direction;
  65. domNode = domNode.parentElement;
  66. }
  67. this.options.rtl = (directionFromDom && (directionFromDom.toLowerCase() == "rtl"));
  68. } else {
  69. this.options.rtl = options.rtl;
  70. }
  71. this.options.rollingMode = options && options.rollingMode;
  72. this.options.onInitialDrawComplete = options && options.onInitialDrawComplete;
  73. this.options.onTimeout = options && options.onTimeout;
  74. this.options.loadingScreenTemplate = options && options.loadingScreenTemplate;
  75. // Prepare loading screen
  76. var loadingScreenFragment = document.createElement('div');
  77. if (this.options.loadingScreenTemplate) {
  78. var templateFunction = this.options.loadingScreenTemplate.bind(this);
  79. var loadingScreen = templateFunction(this.dom.loadingScreen);
  80. if ((loadingScreen instanceof Object) && !(loadingScreen instanceof Element)) {
  81. templateFunction(loadingScreenFragment)
  82. } else {
  83. if (loadingScreen instanceof Element) {
  84. loadingScreenFragment.innerHTML = '';
  85. loadingScreenFragment.appendChild(loadingScreen);
  86. }
  87. else if (loadingScreen != undefined) {
  88. loadingScreenFragment.innerHTML = loadingScreen;
  89. }
  90. }
  91. }
  92. this.dom.loadingScreen.appendChild(loadingScreenFragment);
  93. // all components listed here will be repainted automatically
  94. this.components = [];
  95. this.body = {
  96. dom: this.dom,
  97. domProps: this.props,
  98. emitter: {
  99. on: this.on.bind(this),
  100. off: this.off.bind(this),
  101. emit: this.emit.bind(this)
  102. },
  103. hiddenDates: [],
  104. util: {
  105. getScale: function () {
  106. return me.timeAxis.step.scale;
  107. },
  108. getStep: function () {
  109. return me.timeAxis.step.step;
  110. },
  111. toScreen: me._toScreen.bind(me),
  112. toGlobalScreen: me._toGlobalScreen.bind(me), // this refers to the root.width
  113. toTime: me._toTime.bind(me),
  114. toGlobalTime : me._toGlobalTime.bind(me)
  115. }
  116. };
  117. // range
  118. this.range = new Range(this.body, this.options);
  119. this.components.push(this.range);
  120. this.body.range = this.range;
  121. // time axis
  122. this.timeAxis = new TimeAxis(this.body, this.options);
  123. this.timeAxis2 = null; // used in case of orientation option 'both'
  124. this.components.push(this.timeAxis);
  125. // current time bar
  126. this.currentTime = new CurrentTime(this.body, this.options);
  127. this.components.push(this.currentTime);
  128. // item set
  129. this.itemSet = new ItemSet(this.body, this.options);
  130. this.components.push(this.itemSet);
  131. this.itemsData = null; // DataSet
  132. this.groupsData = null; // DataSet
  133. this.dom.root.onclick = function (event) {
  134. me.emit('click', me.getEventProperties(event))
  135. };
  136. this.dom.root.ondblclick = function (event) {
  137. me.emit('doubleClick', me.getEventProperties(event))
  138. };
  139. this.dom.root.oncontextmenu = function (event) {
  140. me.emit('contextmenu', me.getEventProperties(event))
  141. };
  142. this.dom.root.onmouseover = function (event) {
  143. me.emit('mouseOver', me.getEventProperties(event))
  144. };
  145. if(window.PointerEvent) {
  146. this.dom.root.onpointerdown = function (event) {
  147. me.emit('mouseDown', me.getEventProperties(event))
  148. };
  149. this.dom.root.onpointermove = function (event) {
  150. me.emit('mouseMove', me.getEventProperties(event))
  151. };
  152. this.dom.root.onpointerup = function (event) {
  153. me.emit('mouseUp', me.getEventProperties(event))
  154. };
  155. } else {
  156. this.dom.root.onmousemove = function (event) {
  157. me.emit('mouseMove', me.getEventProperties(event))
  158. };
  159. this.dom.root.onmousedown = function (event) {
  160. me.emit('mouseDown', me.getEventProperties(event))
  161. };
  162. this.dom.root.onmouseup = function (event) {
  163. me.emit('mouseUp', me.getEventProperties(event))
  164. };
  165. }
  166. //Single time autoscale/fit
  167. this.initialFitDone = false;
  168. this.on('changed', function (){
  169. if (me.itemsData == null) return;
  170. if (!me.initialFitDone && !me.options.rollingMode) {
  171. me.initialFitDone = true;
  172. if (me.options.start != undefined || me.options.end != undefined) {
  173. if (me.options.start == undefined || me.options.end == undefined) {
  174. var range = me.getItemRange();
  175. }
  176. var start = me.options.start != undefined ? me.options.start : range.min;
  177. var end = me.options.end != undefined ? me.options.end : range.max;
  178. me.setWindow(start, end, {animation: false});
  179. } else {
  180. me.fit({animation: false});
  181. }
  182. }
  183. if (!me.initialDrawDone && (me.initialRangeChangeDone || (!me.options.start && !me.options.end)
  184. || me.options.rollingMode)) {
  185. me.initialDrawDone = true;
  186. me.itemSet.initialDrawDone = true;
  187. me.dom.root.style.visibility = 'visible';
  188. me.dom.loadingScreen.parentNode.removeChild(me.dom.loadingScreen);
  189. if (me.options.onInitialDrawComplete) {
  190. setTimeout(() => {
  191. return me.options.onInitialDrawComplete();
  192. }, 0)
  193. }
  194. }
  195. });
  196. this.on('destroyTimeline', () => {
  197. me.destroy()
  198. });
  199. // apply options
  200. if (options) {
  201. this.setOptions(options);
  202. }
  203. // IMPORTANT: THIS HAPPENS BEFORE SET ITEMS!
  204. if (groups) {
  205. this.setGroups(groups);
  206. }
  207. // create itemset
  208. if (items) {
  209. this.setItems(items);
  210. }
  211. // draw for the first time
  212. this._redraw();
  213. }
  214. // Extend the functionality from Core
  215. Timeline.prototype = new Core();
  216. /**
  217. * Load a configurator
  218. * @return {Object}
  219. * @private
  220. */
  221. Timeline.prototype._createConfigurator = function () {
  222. return new Configurator(this, this.dom.container, configureOptions);
  223. };
  224. /**
  225. * Force a redraw. The size of all items will be recalculated.
  226. * Can be useful to manually redraw when option autoResize=false and the window
  227. * has been resized, or when the items CSS has been changed.
  228. *
  229. * Note: this function will be overridden on construction with a trottled version
  230. */
  231. Timeline.prototype.redraw = function() {
  232. this.itemSet && this.itemSet.markDirty({refreshItems: true});
  233. this._redraw();
  234. };
  235. Timeline.prototype.setOptions = function (options) {
  236. // validate options
  237. let errorFound = Validator.validate(options, allOptions);
  238. if (errorFound === true) {
  239. console.log('%cErrors have been found in the supplied options object.', printStyle);
  240. }
  241. Core.prototype.setOptions.call(this, options);
  242. if ('type' in options) {
  243. if (options.type !== this.options.type) {
  244. this.options.type = options.type;
  245. // force recreation of all items
  246. var itemsData = this.itemsData;
  247. if (itemsData) {
  248. var selection = this.getSelection();
  249. this.setItems(null); // remove all
  250. this.setItems(itemsData); // add all
  251. this.setSelection(selection); // restore selection
  252. }
  253. }
  254. }
  255. };
  256. /**
  257. * Set items
  258. * @param {vis.DataSet | Array | null} items
  259. */
  260. Timeline.prototype.setItems = function(items) {
  261. this.itemsDone = false;
  262. // convert to type DataSet when needed
  263. var newDataSet;
  264. if (!items) {
  265. newDataSet = null;
  266. }
  267. else if (items instanceof DataSet || items instanceof DataView) {
  268. newDataSet = items;
  269. }
  270. else {
  271. // turn an array into a dataset
  272. newDataSet = new DataSet(items, {
  273. type: {
  274. start: 'Date',
  275. end: 'Date'
  276. }
  277. });
  278. }
  279. // set items
  280. this.itemsData = newDataSet;
  281. this.itemSet && this.itemSet.setItems(newDataSet);
  282. };
  283. /**
  284. * Set groups
  285. * @param {vis.DataSet | Array} groups
  286. */
  287. Timeline.prototype.setGroups = function(groups) {
  288. // convert to type DataSet when needed
  289. var newDataSet;
  290. if (!groups) {
  291. newDataSet = null;
  292. }
  293. else {
  294. var filter = function(group) {
  295. return group.visible !== false;
  296. }
  297. if (groups instanceof DataSet || groups instanceof DataView) {
  298. newDataSet = new DataView(groups,{filter: filter});
  299. }
  300. else {
  301. // turn an array into a dataset
  302. newDataSet = new DataSet(groups.filter(filter));
  303. }
  304. }
  305. this.groupsData = newDataSet;
  306. this.itemSet.setGroups(newDataSet);
  307. };
  308. /**
  309. * Set both items and groups in one go
  310. * @param {{items: (Array | vis.DataSet), groups: (Array | vis.DataSet)}} data
  311. */
  312. Timeline.prototype.setData = function (data) {
  313. if (data && data.groups) {
  314. this.setGroups(data.groups);
  315. }
  316. if (data && data.items) {
  317. this.setItems(data.items);
  318. }
  319. };
  320. /**
  321. * Set selected items by their id. Replaces the current selection
  322. * Unknown id's are silently ignored.
  323. * @param {string[] | string} [ids] An array with zero or more id's of the items to be
  324. * selected. If ids is an empty array, all items will be
  325. * unselected.
  326. * @param {Object} [options] Available options:
  327. * `focus: boolean`
  328. * If true, focus will be set to the selected item(s)
  329. * `animation: boolean | {duration: number, easingFunction: string}`
  330. * If true (default), the range is animated
  331. * smoothly to the new window. An object can be
  332. * provided to specify duration and easing function.
  333. * Default duration is 500 ms, and default easing
  334. * function is 'easeInOutQuad'.
  335. * Only applicable when option focus is true.
  336. */
  337. Timeline.prototype.setSelection = function(ids, options) {
  338. this.itemSet && this.itemSet.setSelection(ids);
  339. if (options && options.focus) {
  340. this.focus(ids, options);
  341. }
  342. };
  343. /**
  344. * Get the selected items by their id
  345. * @return {Array} ids The ids of the selected items
  346. */
  347. Timeline.prototype.getSelection = function() {
  348. return this.itemSet && this.itemSet.getSelection() || [];
  349. };
  350. /**
  351. * Adjust the visible window such that the selected item (or multiple items)
  352. * are centered on screen.
  353. * @param {string | String[]} id An item id or array with item ids
  354. * @param {Object} [options] Available options:
  355. * `animation: boolean | {duration: number, easingFunction: string}`
  356. * If true (default), the range is animated
  357. * smoothly to the new window. An object can be
  358. * provided to specify duration and easing function.
  359. * Default duration is 500 ms, and default easing
  360. * function is 'easeInOutQuad'.
  361. */
  362. Timeline.prototype.focus = function(id, options) {
  363. if (!this.itemsData || id == undefined) return;
  364. var ids = Array.isArray(id) ? id : [id];
  365. // get the specified item(s)
  366. var itemsData = this.itemsData.getDataSet().get(ids, {
  367. type: {
  368. start: 'Date',
  369. end: 'Date'
  370. }
  371. });
  372. // calculate minimum start and maximum end of specified items
  373. var start = null;
  374. var end = null;
  375. itemsData.forEach(function (itemData) {
  376. var s = itemData.start.valueOf();
  377. var e = 'end' in itemData ? itemData.end.valueOf() : itemData.start.valueOf();
  378. if (start === null || s < start) {
  379. start = s;
  380. }
  381. if (end === null || e > end) {
  382. end = e;
  383. }
  384. });
  385. if (start !== null && end !== null) {
  386. var me = this;
  387. // Use the first item for the vertical focus
  388. var item = this.itemSet.items[ids[0]];
  389. var startPos = this._getScrollTop() * -1;
  390. var initialVerticalScroll = null;
  391. // Setup a handler for each frame of the vertical scroll
  392. var verticalAnimationFrame = function(ease, willDraw, done) {
  393. var verticalScroll = getItemVerticalScroll(me, item);
  394. if (verticalScroll === false) {
  395. return; // We don't need to scroll, so do nothing
  396. }
  397. if(!initialVerticalScroll) {
  398. initialVerticalScroll = verticalScroll;
  399. }
  400. if(initialVerticalScroll.itemTop == verticalScroll.itemTop && !initialVerticalScroll.shouldScroll) {
  401. return; // We don't need to scroll, so do nothing
  402. }
  403. else if(initialVerticalScroll.itemTop != verticalScroll.itemTop && verticalScroll.shouldScroll) {
  404. // The redraw shifted elements, so reset the animation to correct
  405. initialVerticalScroll = verticalScroll;
  406. startPos = me._getScrollTop() * -1;
  407. }
  408. var from = startPos;
  409. var to = initialVerticalScroll.scrollOffset;
  410. var scrollTop = done ? to : (from + (to - from) * ease);
  411. me._setScrollTop(-scrollTop);
  412. if(!willDraw) {
  413. me._redraw();
  414. }
  415. };
  416. // Enforces the final vertical scroll position
  417. var setFinalVerticalPosition = function() {
  418. var finalVerticalScroll = getItemVerticalScroll(me, item);
  419. if (finalVerticalScroll.shouldScroll && finalVerticalScroll.itemTop != initialVerticalScroll.itemTop) {
  420. me._setScrollTop(-finalVerticalScroll.scrollOffset);
  421. me._redraw();
  422. }
  423. };
  424. // Perform one last check at the end to make sure the final vertical
  425. // position is correct
  426. var finalVerticalCallback = function() {
  427. // Double check we ended at the proper scroll position
  428. setFinalVerticalPosition();
  429. // Let the redraw settle and finalize the position.
  430. setTimeout(setFinalVerticalPosition, 100);
  431. };
  432. // calculate the new middle and interval for the window
  433. var middle = (start + end) / 2;
  434. var interval = Math.max(this.range.end - this.range.start, (end - start) * 1.1);
  435. var animation = options && options.animation !== undefined ? options.animation : true;
  436. if (!animation) {
  437. // We aren't animating so set a default so that the final callback forces the vertical location
  438. initialVerticalScroll = { shouldScroll: false, scrollOffset: -1, itemTop: -1 };
  439. }
  440. this.range.setRange(middle - interval / 2, middle + interval / 2, { animation: animation }, finalVerticalCallback, verticalAnimationFrame);
  441. }
  442. };
  443. /**
  444. * Set Timeline window such that it fits all items
  445. * @param {Object} [options] Available options:
  446. * `animation: boolean | {duration: number, easingFunction: string}`
  447. * If true (default), the range is animated
  448. * smoothly to the new window. An object can be
  449. * provided to specify duration and easing function.
  450. * Default duration is 500 ms, and default easing
  451. * function is 'easeInOutQuad'.
  452. * @param {function} [callback]
  453. */
  454. Timeline.prototype.fit = function (options, callback) {
  455. var animation = (options && options.animation !== undefined) ? options.animation : true;
  456. var range;
  457. var dataset = this.itemsData && this.itemsData.getDataSet();
  458. if (dataset.length === 1 && dataset.get()[0].end === undefined) {
  459. // a single item -> don't fit, just show a range around the item from -4 to +3 days
  460. range = this.getDataRange();
  461. this.moveTo(range.min.valueOf(), {animation}, callback);
  462. }
  463. else {
  464. // exactly fit the items (plus a small margin)
  465. range = this.getItemRange();
  466. this.range.setRange(range.min, range.max, { animation: animation }, callback);
  467. }
  468. };
  469. /**
  470. *
  471. * @param {vis.Item} item
  472. * @returns {number}
  473. */
  474. function getStart(item) {
  475. return util.convert(item.data.start, 'Date').valueOf()
  476. }
  477. /**
  478. *
  479. * @param {vis.Item} item
  480. * @returns {number}
  481. */
  482. function getEnd(item) {
  483. var end = item.data.end != undefined ? item.data.end : item.data.start;
  484. return util.convert(end, 'Date').valueOf();
  485. }
  486. /**
  487. * @param {vis.Timeline} timeline
  488. * @param {vis.Item} item
  489. * @return {{shouldScroll: bool, scrollOffset: number, itemTop: number}}
  490. */
  491. function getItemVerticalScroll(timeline, item) {
  492. if (!item.parent) {
  493. // The item no longer exists, so ignore this focus.
  494. return false;
  495. }
  496. var leftHeight = timeline.props.leftContainer.height;
  497. var contentHeight = timeline.props.left.height;
  498. var group = item.parent;
  499. var offset = group.top;
  500. var shouldScroll = true;
  501. var orientation = timeline.timeAxis.options.orientation.axis;
  502. var itemTop = function () {
  503. if (orientation == "bottom") {
  504. return group.height - item.top - item.height;
  505. }
  506. else {
  507. return item.top;
  508. }
  509. };
  510. var currentScrollHeight = timeline._getScrollTop() * -1;
  511. var targetOffset = offset + itemTop();
  512. var height = item.height;
  513. if (targetOffset < currentScrollHeight) {
  514. if (offset + leftHeight <= offset + itemTop() + height) {
  515. offset += itemTop() - timeline.itemSet.options.margin.item.vertical;
  516. }
  517. }
  518. else if (targetOffset + height > currentScrollHeight + leftHeight) {
  519. offset += itemTop() + height - leftHeight + timeline.itemSet.options.margin.item.vertical;
  520. }
  521. else {
  522. shouldScroll = false;
  523. }
  524. offset = Math.min(offset, contentHeight - leftHeight);
  525. return { shouldScroll: shouldScroll, scrollOffset: offset, itemTop: targetOffset };
  526. }
  527. /**
  528. * Determine the range of the items, taking into account their actual width
  529. * and a margin of 10 pixels on both sides.
  530. *
  531. * @returns {{min: Date, max: Date}}
  532. */
  533. Timeline.prototype.getItemRange = function () {
  534. // get a rough approximation for the range based on the items start and end dates
  535. var range = this.getDataRange();
  536. var min = range.min !== null ? range.min.valueOf() : null;
  537. var max = range.max !== null ? range.max.valueOf() : null;
  538. var minItem = null;
  539. var maxItem = null;
  540. if (min != null && max != null) {
  541. var interval = (max - min); // ms
  542. if (interval <= 0) {
  543. interval = 10;
  544. }
  545. var factor = interval / this.props.center.width;
  546. var redrawQueue = {};
  547. var redrawQueueLength = 0;
  548. // collect redraw functions
  549. util.forEach(this.itemSet.items, function (item, key) {
  550. if (item.groupShowing) {
  551. var returnQueue = true;
  552. redrawQueue[key] = item.redraw(returnQueue);
  553. redrawQueueLength = redrawQueue[key].length;
  554. }
  555. })
  556. var needRedraw = redrawQueueLength > 0;
  557. if (needRedraw) {
  558. // redraw all regular items
  559. for (var i = 0; i < redrawQueueLength; i++) {
  560. util.forEach(redrawQueue, function (fns) {
  561. fns[i]();
  562. });
  563. }
  564. }
  565. // calculate the date of the left side and right side of the items given
  566. util.forEach(this.itemSet.items, function (item) {
  567. var start = getStart(item);
  568. var end = getEnd(item);
  569. var startSide;
  570. var endSide;
  571. if (this.options.rtl) {
  572. startSide = start - (item.getWidthRight() + 10) * factor;
  573. endSide = end + (item.getWidthLeft() + 10) * factor;
  574. } else {
  575. startSide = start - (item.getWidthLeft() + 10) * factor;
  576. endSide = end + (item.getWidthRight() + 10) * factor;
  577. }
  578. if (startSide < min) {
  579. min = startSide;
  580. minItem = item;
  581. }
  582. if (endSide > max) {
  583. max = endSide;
  584. maxItem = item;
  585. }
  586. }.bind(this));
  587. if (minItem && maxItem) {
  588. var lhs = minItem.getWidthLeft() + 10;
  589. var rhs = maxItem.getWidthRight() + 10;
  590. var delta = this.props.center.width - lhs - rhs; // px
  591. if (delta > 0) {
  592. if (this.options.rtl) {
  593. min = getStart(minItem) - rhs * interval / delta; // ms
  594. max = getEnd(maxItem) + lhs * interval / delta; // ms
  595. } else {
  596. min = getStart(minItem) - lhs * interval / delta; // ms
  597. max = getEnd(maxItem) + rhs * interval / delta; // ms
  598. }
  599. }
  600. }
  601. }
  602. return {
  603. min: min != null ? new Date(min) : null,
  604. max: max != null ? new Date(max) : null
  605. }
  606. };
  607. /**
  608. * Calculate the data range of the items start and end dates
  609. * @returns {{min: Date, max: Date}}
  610. */
  611. Timeline.prototype.getDataRange = function() {
  612. var min = null;
  613. var max = null;
  614. var dataset = this.itemsData && this.itemsData.getDataSet();
  615. if (dataset) {
  616. dataset.forEach(function (item) {
  617. var start = util.convert(item.start, 'Date').valueOf();
  618. var end = util.convert(item.end != undefined ? item.end : item.start, 'Date').valueOf();
  619. if (min === null || start < min) {
  620. min = start;
  621. }
  622. if (max === null || end > max) {
  623. max = end;
  624. }
  625. });
  626. }
  627. return {
  628. min: min != null ? new Date(min) : null,
  629. max: max != null ? new Date(max) : null
  630. }
  631. };
  632. /**
  633. * Generate Timeline related information from an event
  634. * @param {Event} event
  635. * @return {Object} An object with related information, like on which area
  636. * The event happened, whether clicked on an item, etc.
  637. */
  638. Timeline.prototype.getEventProperties = function (event) {
  639. var clientX = event.center ? event.center.x : event.clientX;
  640. var clientY = event.center ? event.center.y : event.clientY;
  641. var x;
  642. if (this.options.rtl) {
  643. x = util.getAbsoluteRight(this.dom.centerContainer) - clientX;
  644. } else {
  645. x = clientX - util.getAbsoluteLeft(this.dom.centerContainer);
  646. }
  647. var y = clientY - util.getAbsoluteTop(this.dom.centerContainer);
  648. var item = this.itemSet.itemFromTarget(event);
  649. var group = this.itemSet.groupFromTarget(event);
  650. var customTime = CustomTime.customTimeFromTarget(event);
  651. var snap = this.itemSet.options.snap || null;
  652. var scale = this.body.util.getScale();
  653. var step = this.body.util.getStep();
  654. var time = this._toTime(x);
  655. var snappedTime = snap ? snap(time, scale, step) : time;
  656. var element = util.getTarget(event);
  657. var what = null;
  658. if (item != null) {what = 'item';}
  659. else if (customTime != null) {what = 'custom-time';}
  660. else if (util.hasParent(element, this.timeAxis.dom.foreground)) {what = 'axis';}
  661. else if (this.timeAxis2 && util.hasParent(element, this.timeAxis2.dom.foreground)) {what = 'axis';}
  662. else if (util.hasParent(element, this.itemSet.dom.labelSet)) {what = 'group-label';}
  663. else if (util.hasParent(element, this.currentTime.bar)) {what = 'current-time';}
  664. else if (util.hasParent(element, this.dom.center)) {what = 'background';}
  665. return {
  666. event: event,
  667. item: item ? item.id : null,
  668. group: group ? group.groupId : null,
  669. what: what,
  670. pageX: event.srcEvent ? event.srcEvent.pageX : event.pageX,
  671. pageY: event.srcEvent ? event.srcEvent.pageY : event.pageY,
  672. x: x,
  673. y: y,
  674. time: time,
  675. snappedTime: snappedTime
  676. }
  677. };
  678. /**
  679. * Toggle Timeline rolling mode
  680. */
  681. Timeline.prototype.toggleRollingMode = function () {
  682. if (this.range.rolling) {
  683. this.range.stopRolling();
  684. } else {
  685. if (this.options.rollingMode == undefined) {
  686. this.setOptions(this.options)
  687. }
  688. this.range.startRolling();
  689. }
  690. }
  691. module.exports = Timeline;