Browse Source

onTimeout function (#3602)

* initial trial

* Add onInitialDrawComplete

* Add docs

* Add to eventListeners examples

* Keeping things DRY

* Remove callback insertion

* Remove call

* Add onTimeout

* Prepare bailing in stack

* Initial bail function

* Better written bail function

* Clean up

* Fix and cleanup examples

* Add onTimeout docs

* Remove core.js changes

* Readd newline in corejs

* Fix example title

* Fix review comments
develop
Yotam Berkowitz 6 years ago
committed by GitHub
parent
commit
80af85e36d
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 151 additions and 10 deletions
  1. +23
    -0
      docs/timeline/index.html
  2. +55
    -0
      examples/timeline/other/onTimeout.html
  3. +1
    -1
      lib/timeline/Core.js
  4. +10
    -1
      lib/timeline/Stack.js
  5. +11
    -0
      lib/timeline/Timeline.js
  6. +36
    -2
      lib/timeline/component/Group.js
  7. +10
    -6
      lib/timeline/component/ItemSet.js
  8. +5
    -0
      lib/timeline/optionsTimeline.js

+ 23
- 0
docs/timeline/index.html View File

@ -946,6 +946,29 @@ function (option, path) {
<td>Callback function triggered when a group is about to be removed. The signature and semantics are the same as for <code>onRemove</code>. <td>Callback function triggered when a group is about to be removed. The signature and semantics are the same as for <code>onRemove</code>.
</td> </td>
</tr> </tr>
<tr class='toggle collapsible' onclick="toggleTable('optionTable','onTimeout', this);">
<td><span parent="onTimeout" class="right-caret"></span> onTimeout</td>
<td>Object</td>
<td><code>Object</code></td>
<td>Specify timeline bailing options when a specified timeout is reached.</td>
</tr>
<tr parent="onTimeout" class="hidden">
<td class="indent">onTimeout.timeoutMs</td>
<td>number</td>
<td>none</td>
<td>Number of milliseconds until the callback function should be called.
The callback will not be called if the timeline gets drawn completely before the timeoutMs limit.</td>
</tr>
<tr parent="onTimeout" class="hidden">
<td class="indent">onTimeout.callback</td>
<td>function</td>
<td>none</td>
<td>
A callback function called when <code>timeoutMs</code> milliseconds pass and the timeline has yet to be fully drawn initially.
</td>
</tr>
<tr> <tr>
<td>onUpdate</td> <td>onUpdate</td>

+ 55
- 0
examples/timeline/other/onTimeout.html View File

@ -0,0 +1,55 @@
<!DOCTYPE HTML>
<!--
This example is used mainly for developers to test performance issues by controlling number of groups,
number of items and their types.
-->
<html>
<head>
<title>Timeline | onTimeout example</title>
<script src="http://cdnjs.cloudflare.com/ajax/libs/moment.js/2.8.4/moment.min.js"></script>
<script src="../../../dist/vis.js"></script>
<link href="../../../dist/vis-timeline-graph2d.min.css" rel="stylesheet" type="text/css" />
</head>
<body>
<div id="visualization"></div>
</body>
<script>
var now = moment();
var itemCount = 300;
var types = ['point', 'range', 'box']
// create a dataset with items
var items = new vis.DataSet();
for (var i = 0; i < itemCount; i++) {
var start = now.clone().add(Math.random() * 180, 'days');
var end = start.clone().add(Math.random() * 30, 'days');
items.add({
id: i,
type: types[Math.floor(Math.random() * types.length)],
content: '' + i,
start: start,
end: end
});
}
// create visualization
var container = document.getElementById('visualization');
var options = {
stack: true,
onTimeout: {
timeoutMs: 1000,
callback: function(callback) {
var didUserCancel;
var didUserCancel = confirm("Too many items loaded! Would you like to continue rendering (this might take a while)?");
callback(didUserCancel)
},
}
};
var timeline = new vis.Timeline(container, items, options);
</script>
</html>

+ 1
- 1
lib/timeline/Core.js View File

@ -969,7 +969,7 @@ Core.prototype._redraw = function() {
props.border.top - props.border.bottom, 0); props.border.top - props.border.bottom, 0);
} }
dom.center.style.top = offset + 'px'; dom.center.style.top = offset + 'px';
// show shadows when vertical scrolling is available // show shadows when vertical scrolling is available
var visibilityTop = props.scrollTop == 0 ? 'hidden' : ''; var visibilityTop = props.scrollTop == 0 ? 'hidden' : '';
var visibilityBottom = props.scrollTop == props.scrollTopMin ? 'hidden' : ''; var visibilityBottom = props.scrollTop == props.scrollTopMin ? 'hidden' : '';

+ 10
- 1
lib/timeline/Stack.js View File

@ -35,8 +35,11 @@ exports.orderByEnd = function(items) {
* @param {boolean} [force=false] * @param {boolean} [force=false]
* If true, all items will be repositioned. If false (default), only * If true, all items will be repositioned. If false (default), only
* items having a top===null will be re-stacked * items having a top===null will be re-stacked
* @param {function} shouldBailItemsRedrawFunction
* bailing function
* @return {boolean} shouldBail
*/ */
exports.stack = function(items, margin, force) {
exports.stack = function(items, margin, force, shouldBailItemsRedrawFunction) {
if (force) { if (force) {
// reset top position of all items // reset top position of all items
for (var i = 0; i < items.length; i++) { for (var i = 0; i < items.length; i++) {
@ -50,6 +53,7 @@ exports.stack = function(items, margin, force) {
if (item.stack && item.top === null) { if (item.stack && item.top === null) {
// initialize top position // initialize top position
item.top = margin.axis; item.top = margin.axis;
var shouldBail = false;
do { do {
// TODO: optimize checking for overlap. when there is a gap without items, // TODO: optimize checking for overlap. when there is a gap without items,
@ -57,6 +61,10 @@ exports.stack = function(items, margin, force) {
var collidingItem = null; var collidingItem = null;
for (var j = 0, jj = items.length; j < jj; j++) { for (var j = 0, jj = items.length; j < jj; j++) {
var other = items[j]; var other = items[j];
shouldBail = shouldBailItemsRedrawFunction() || false;
if (shouldBail) { return true; }
if (other.top !== null && other !== item && other.stack && exports.collision(item, other, margin.item, other.options.rtl)) { if (other.top !== null && other !== item && other.stack && exports.collision(item, other, margin.item, other.options.rtl)) {
collidingItem = other; collidingItem = other;
break; break;
@ -70,6 +78,7 @@ exports.stack = function(items, margin, force) {
} while (collidingItem); } while (collidingItem);
} }
} }
return shouldBail;
}; };
/** /**

+ 11
- 0
lib/timeline/Timeline.js View File

@ -28,6 +28,9 @@ var Validator = require('../shared/Validator').default;
*/ */
function Timeline (container, items, groups, options) { function Timeline (container, items, groups, options) {
this.initTime = new Date();
this.itemsDone = false;
if (!(this instanceof Timeline)) { if (!(this instanceof Timeline)) {
throw new SyntaxError('Constructor must be called with the new operator'); throw new SyntaxError('Constructor must be called with the new operator');
} }
@ -78,6 +81,7 @@ function Timeline (container, items, groups, options) {
this.options.rollingMode = options && options.rollingMode; this.options.rollingMode = options && options.rollingMode;
this.options.onInitialDrawComplete = options && options.onInitialDrawComplete; this.options.onInitialDrawComplete = options && options.onInitialDrawComplete;
this.options.onTimeout = options && options.onTimeout;
this.options.loadingScreenTemplate = options && options.loadingScreenTemplate; this.options.loadingScreenTemplate = options && options.loadingScreenTemplate;
// Prepare loading screen // Prepare loading screen
@ -202,6 +206,7 @@ function Timeline (container, items, groups, options) {
if (!me.initialDrawDone && (me.initialRangeChangeDone || (!me.options.start && !me.options.end) || me.options.rollingMode)) { if (!me.initialDrawDone && (me.initialRangeChangeDone || (!me.options.start && !me.options.end) || me.options.rollingMode)) {
me.initialDrawDone = true; me.initialDrawDone = true;
me.itemSet.initialDrawDone = true;
me.dom.root.style.visibility = 'visible'; me.dom.root.style.visibility = 'visible';
me.dom.loadingScreen.parentNode.removeChild(me.dom.loadingScreen); me.dom.loadingScreen.parentNode.removeChild(me.dom.loadingScreen);
if (me.options.onInitialDrawComplete) { if (me.options.onInitialDrawComplete) {
@ -212,6 +217,10 @@ function Timeline (container, items, groups, options) {
} }
}); });
this.on('destroyTimeline', () => {
me.destroy()
});
// apply options // apply options
if (options) { if (options) {
this.setOptions(options); this.setOptions(options);
@ -285,6 +294,8 @@ Timeline.prototype.setOptions = function (options) {
* @param {vis.DataSet | Array | null} items * @param {vis.DataSet | Array | null} items
*/ */
Timeline.prototype.setItems = function(items) { Timeline.prototype.setItems = function(items) {
this.itemsDone = false;
// convert to type DataSet when needed // convert to type DataSet when needed
var newDataSet; var newDataSet;
if (!items) { if (!items) {

+ 36
- 2
lib/timeline/component/Group.js View File

@ -13,6 +13,7 @@ function Group (groupId, data, itemSet) {
this.subgroupStack = {}; this.subgroupStack = {};
this.subgroupStackAll = false; this.subgroupStackAll = false;
this.doInnerStack = false; this.doInnerStack = false;
this.shouldBailStackItems = false;
this.subgroupIndex = 0; this.subgroupIndex = 0;
this.subgroupOrderer = data && data.subgroupOrder; this.subgroupOrderer = data && data.subgroupOrder;
this.itemSet = itemSet; this.itemSet = itemSet;
@ -262,6 +263,35 @@ Group.prototype._calculateGroupSizeAndPosition = function() {
this.width = offsetWidth; this.width = offsetWidth;
} }
Group.prototype._shouldBailItemsRedraw = function() {
var me = this;
var timeoutOptions = this.itemSet.options.onTimeout;
var bailOptions = {
relativeBailingTime: this.itemSet.itemsSettingTime,
bailTimeMs: timeoutOptions && timeoutOptions.timeoutMs,
userBailFunction: timeoutOptions && timeoutOptions.callback,
shouldBailStackItems: this.shouldBailStackItems
};
var bail = null;
if (!this.itemSet.initialDrawDone) {
if (bailOptions.shouldBailStackItems) { return true; }
if (Math.abs(new Date() - new Date(bailOptions.relativeBailingTime)) > bailOptions.bailTimeMs) {
if (bailOptions.userBailFunction && this.itemSet.userContinueNotBail == null) {
bailOptions.userBailFunction(function(didUserContinue) {
me.itemSet.userContinueNotBail = didUserContinue;
bail = !didUserContinue;
})
} else if (me.itemSet.userContinueNotBail == false) {
bail = true;
} else {
bail = false;
}
}
}
return bail;
}
Group.prototype._redrawItems = function(forceRestack, lastIsVisible, margin, range) { Group.prototype._redrawItems = function(forceRestack, lastIsVisible, margin, range) {
var restack = forceRestack || this.stackDirty || this.isVisible && !lastIsVisible; var restack = forceRestack || this.stackDirty || this.isVisible && !lastIsVisible;
@ -270,6 +300,7 @@ Group.prototype._redrawItems = function(forceRestack, lastIsVisible, margin, ran
var visibleSubgroups = {}; var visibleSubgroups = {};
var subgroup = null; var subgroup = null;
if (typeof this.itemSet.options.order === 'function') { if (typeof this.itemSet.options.order === 'function') {
// a custom order function // a custom order function
// brute force restack of all items // brute force restack of all items
@ -321,7 +352,7 @@ Group.prototype._redrawItems = function(forceRestack, lastIsVisible, margin, ran
var customOrderedItems = this.orderedItems.byStart.slice().sort(function (a, b) { var customOrderedItems = this.orderedItems.byStart.slice().sort(function (a, b) {
return me.itemSet.options.order(a.data, b.data); return me.itemSet.options.order(a.data, b.data);
}); });
stack.stack(customOrderedItems, margin, true /* restack=true */);
this.shouldBailStackItems = stack.stack(customOrderedItems, margin, true, this._shouldBailItemsRedraw.bind(this));
} }
this.visibleItems = this._updateItemsInRange(this.orderedItems, this.visibleItems, range); this.visibleItems = this._updateItemsInRange(this.orderedItems, this.visibleItems, range);
@ -339,7 +370,7 @@ Group.prototype._redrawItems = function(forceRestack, lastIsVisible, margin, ran
} }
else { else {
// TODO: ugly way to access options... // TODO: ugly way to access options...
stack.stack(this.visibleItems, margin, true /* restack=true */);
this.shouldBailStackItems = stack.stack(this.visibleItems, margin, true, this._shouldBailItemsRedraw.bind(this));
} }
} else { } else {
// no stacking // no stacking
@ -347,6 +378,9 @@ Group.prototype._redrawItems = function(forceRestack, lastIsVisible, margin, ran
} }
} }
if (this.shouldBailStackItems) {
this.itemSet.body.emitter.emit('destroyTimeline')
}
this.stackDirty = false; this.stackDirty = false;
} }
} }

+ 10
- 6
lib/timeline/component/ItemSet.js View File

@ -115,12 +115,13 @@ function ItemSet(body, options) {
// options is shared by this ItemSet and all its items // options is shared by this ItemSet and all its items
this.options = util.extend({}, this.defaultOptions); this.options = util.extend({}, this.defaultOptions);
this.options.rtl = options.rtl; this.options.rtl = options.rtl;
this.options.onTimeout = options.onTimeout;
// options for getting items from the DataSet with the correct type // options for getting items from the DataSet with the correct type
this.itemOptions = { this.itemOptions = {
type: {start: 'Date', end: 'Date'} type: {start: 'Date', end: 'Date'}
}; };
this.conversion = { this.conversion = {
toScreen: body.util.toScreen, toScreen: body.util.toScreen,
toTime: body.util.toTime toTime: body.util.toTime
@ -128,11 +129,14 @@ function ItemSet(body, options) {
this.dom = {}; this.dom = {};
this.props = {}; this.props = {};
this.hammer = null; this.hammer = null;
var me = this; var me = this;
this.itemsData = null; // DataSet this.itemsData = null; // DataSet
this.groupsData = null; // DataSet this.groupsData = null; // DataSet
this.itemsSettingTime = null;
this.initialItemSetDrawn = false;
this.userContinueNotBail = null;
// listeners for the DataSet of the items // listeners for the DataSet of the items
this.itemListeners = { this.itemListeners = {
'add': function (event, params, senderId) { // eslint-disable-line no-unused-vars 'add': function (event, params, senderId) { // eslint-disable-line no-unused-vars
@ -367,7 +371,7 @@ ItemSet.prototype.setOptions = function(options) {
var fields = [ var fields = [
'type', 'rtl', 'align', 'order', 'stack', 'stackSubgroups', 'selectable', 'multiselect', 'type', 'rtl', 'align', 'order', 'stack', 'stackSubgroups', 'selectable', 'multiselect',
'multiselectPerGroup', 'groupOrder', 'dataAttributes', 'template', 'groupTemplate', 'visibleFrameTemplate', 'multiselectPerGroup', 'groupOrder', 'dataAttributes', 'template', 'groupTemplate', 'visibleFrameTemplate',
'hide', 'snap', 'groupOrderSwap', 'showTooltips', 'tooltip', 'tooltipOnItemUpdateTime'
'hide', 'snap', 'groupOrderSwap', 'showTooltips', 'tooltip', 'tooltipOnItemUpdateTime', 'onTimeout'
]; ];
util.selectiveExtend(fields, this.options, options); util.selectiveExtend(fields, this.options, options);
@ -688,7 +692,6 @@ ItemSet.prototype.redraw = function() {
this.lastStackSubgroups = options.stackSubgroups; this.lastStackSubgroups = options.stackSubgroups;
this.props.lastWidth = this.props.width; this.props.lastWidth = this.props.width;
var firstGroup = this._firstGroup(); var firstGroup = this._firstGroup();
var firstMargin = { var firstMargin = {
item: margin.item, item: margin.item,
@ -834,6 +837,7 @@ ItemSet.prototype.getLabelSet = function() {
* @param {vis.DataSet | null} items * @param {vis.DataSet | null} items
*/ */
ItemSet.prototype.setItems = function(items) { ItemSet.prototype.setItems = function(items) {
this.itemsSettingTime = new Date();
var me = this, var me = this,
ids, ids,
oldItemsData = this.itemsData; oldItemsData = this.itemsData;

+ 5
- 0
lib/timeline/optionsTimeline.js View File

@ -31,6 +31,11 @@ let allOptions = {
offset: {number,'undefined': 'undefined'}, offset: {number,'undefined': 'undefined'},
__type__: {object} __type__: {object}
}, },
onTimeout: {
timeoutMs: {number},
callback: {'function': 'function'},
__type__: {object}
},
verticalScroll: { 'boolean': bool, 'undefined': 'undefined'}, verticalScroll: { 'boolean': bool, 'undefined': 'undefined'},
horizontalScroll: { 'boolean': bool, 'undefined': 'undefined'}, horizontalScroll: { 'boolean': bool, 'undefined': 'undefined'},
autoResize: { 'boolean': bool}, autoResize: { 'boolean': bool},

Loading…
Cancel
Save