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.

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