UNPKG

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