Browse Source

Improve Item redraw and initial draw performance (#3475)

* Make items redraws return queues

* Parallel initial items redraw

* Seperate read and write actions in items

* Parallel all items redraws

* Remove comments

* Fix linting comments

* Fix redraws on actions

* Add stress example

* Fix example files

* Explain and fix example

* Fix comment issues
jittering-top
Yotam Berkowitz 6 years ago
committed by GitHub
parent
commit
fe49e7e1d1
8 changed files with 555 additions and 182 deletions
  1. +66
    -0
      examples/timeline/other/stressPerformance.html
  2. +21
    -5
      lib/timeline/Timeline.js
  3. +71
    -16
      lib/timeline/component/Group.js
  4. +2
    -1
      lib/timeline/component/ItemSet.js
  5. +80
    -26
      lib/timeline/component/item/BackgroundItem.js
  6. +114
    -53
      lib/timeline/component/item/BoxItem.js
  7. +112
    -48
      lib/timeline/component/item/PointItem.js
  8. +89
    -33
      lib/timeline/component/item/RangeItem.js

+ 66
- 0
examples/timeline/other/stressPerformance.html View File

@ -0,0 +1,66 @@
<!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 | Stress Performance 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 groupCount = 20;
var itemCount = 400;
// create a data set with groups
var groups = new vis.DataSet();
for (var g = 0; g < groupCount; g++) {
groups.add({
id: g,
content: "group " + g
});
}
var types = ['point', 'range', 'box', 'background']
// 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');
var group = Math.floor(Math.random() * groupCount);
items.add({
id: i,
type: types[Math.floor(Math.random() * types.length)],
group: group,
content: '' + i,
start: start,
end: end
});
}
// create visualization
var container = document.getElementById('visualization');
var options = {
width: '100%',
showCurrentTime: false,
stack: true,
selectable: true,
editable: true,
};
var timeline = new vis.Timeline(container);
timeline.setOptions(options);
timeline.setGroups(groups);
timeline.setItems(items);
</script>
</html>

+ 21
- 5
lib/timeline/Timeline.js View File

@ -469,13 +469,30 @@ Timeline.prototype.getItemRange = function () {
}
var factor = interval / this.props.center.width;
// calculate the date of the left side and right side of the items given
util.forEach(this.itemSet.items, function (item) {
var redrawQueue = {};
var redrawQueueLength = 0;
// collect redraw functions
util.forEach(this.itemSet.items, function (item, key) {
if (item.groupShowing) {
item.show();
item.repositionX();
var returnQueue = true;
redrawQueue[key] = item.redraw(returnQueue);
redrawQueueLength = redrawQueue[key].length;
}
})
var needRedraw = redrawQueueLength > 0;
if (needRedraw) {
// redraw all regular items
for (var i = 0; i < redrawQueueLength; i++) {
util.forEach(redrawQueue, function (fns) {
fns[i]();
});
}
}
// calculate the date of the left side and right side of the items given
util.forEach(this.itemSet.items, function (item) {
var start = getStart(item);
var end = getEnd(item);
var startSide;
@ -489,7 +506,6 @@ Timeline.prototype.getItemRange = function () {
endSide = end + (item.getWidthRight() + 10) * factor;
}
if (startSide < min) {
min = startSide;
minItem = item;

+ 71
- 16
lib/timeline/component/Group.js View File

@ -210,19 +210,38 @@ Group.prototype._didMarkerHeightChange = function() {
var markerHeight = this.dom.marker.clientHeight;
if (markerHeight != this.lastMarkerHeight) {
this.lastMarkerHeight = markerHeight;
util.forEach(this.items, function (item) {
var redrawQueue = {};
var redrawQueueLength = 0;
util.forEach(this.items, function (item, key) {
item.dirty = true;
if (item.displayed) item.redraw();
});
if (item.displayed) {
var returnQueue = true;
redrawQueue[key] = item.redraw(returnQueue);
redrawQueueLength = redrawQueue[key].length;
}
})
var needRedraw = redrawQueueLength > 0;
if (needRedraw) {
// redraw all regular items
for (var i = 0; i < redrawQueueLength; i++) {
util.forEach(redrawQueue, function (fns) {
fns[i]();
});
}
}
return true;
}
}
Group.prototype._calculateGroupSizeAndPosition = function() {
var foreground = this.dom.foreground;
this.top = foreground.offsetTop;
this.right = foreground.offsetLeft;
this.width = foreground.offsetWidth;
var offsetTop = this.dom.foreground.offsetTop
var offsetLeft = this.dom.foreground.offsetLeft
var offsetWidth = this.dom.foreground.offsetWidth
this.top = offsetTop;
this.right = offsetLeft;
this.width = offsetWidth;
}
Group.prototype._redrawItems = function(forceRestack, lastIsVisible, margin, range) {
@ -237,13 +256,32 @@ Group.prototype._redrawItems = function(forceRestack, lastIsVisible, margin, ran
// show all items
var me = this;
var limitSize = false;
util.forEach(this.items, function (item) {
var redrawQueue = {};
var redrawQueueLength = 0;
util.forEach(this.items, function (item, key) {
if (!item.displayed) {
item.redraw();
var returnQueue = true;
redrawQueue[key] = item.redraw(returnQueue);
redrawQueueLength = redrawQueue[key].length;
me.visibleItems.push(item);
}
item.repositionX(limitSize);
});
})
var needRedraw = redrawQueueLength > 0;
if (needRedraw) {
// redraw all regular items
for (var i = 0; i < redrawQueueLength; i++) {
util.forEach(redrawQueue, function (fns) {
fns[i]();
});
}
}
for (i = 0; i < this.items.length; i++) {
this.items[i].repositionX(limitSize);
}
// order all items and force a restacking
var customOrderedItems = this.orderedItems.byStart.slice().sort(function (a, b) {
@ -727,14 +765,31 @@ Group.prototype._updateItemsInRange = function(orderedItems, oldVisibleItems, ra
});
}
// finally, we reposition all the visible items.
var redrawQueue = {};
var redrawQueueLength = 0;
for (i = 0; i < visibleItems.length; i++) {
var item = visibleItems[i];
if (!item.displayed) item.show();
// reposition item horizontally
item.repositionX();
if (!item.displayed) {
var returnQueue = true;
redrawQueue[i] = item.redraw(returnQueue);
redrawQueueLength = redrawQueue[i].length;
}
}
var needRedraw = redrawQueueLength > 0;
if (needRedraw) {
// redraw all regular items
for (var j = 0; j < redrawQueueLength; j++) {
util.forEach(redrawQueue, function (fns) {
fns[j]();
});
}
}
for (i = 0; i < visibleItems.length; i++) {
visibleItems[i].repositionX();
}
return visibleItems;
};

+ 2
- 1
lib/timeline/component/ItemSet.js View File

@ -693,7 +693,8 @@ ItemSet.prototype.redraw = function() {
redrawQueueLength = redrawQueue[key].length;
});
if (redrawQueueLength) {
var needRedraw = redrawQueueLength > 0;
if (needRedraw) {
var redrawResults = {};
for (var i = 0; i < redrawQueueLength; i++) {

+ 80
- 26
lib/timeline/component/item/BackgroundItem.js View File

@ -37,6 +37,7 @@ function BackgroundItem (data, conversion, options) {
BackgroundItem.prototype = new Item (null, null, null);
BackgroundItem.prototype.baseClassName = 'vis-item vis-background';
BackgroundItem.prototype.stack = false;
/**
@ -49,51 +50,49 @@ BackgroundItem.prototype.isVisible = function(range) {
return (this.data.start < range.end) && (this.data.end > range.start);
};
/**
* Repaint the item
*/
BackgroundItem.prototype.redraw = function() {
var dom = this.dom;
if (!dom) {
BackgroundItem.prototype._createDomElement = function() {
if (!this.dom) {
// create DOM
this.dom = {};
dom = this.dom;
// background box
dom.box = document.createElement('div');
this.dom.box = document.createElement('div');
// className is updated in redraw()
// frame box (to prevent the item contents from overflowing
dom.frame = document.createElement('div');
dom.frame.className = 'vis-item-overflow';
dom.box.appendChild(dom.frame);
this.dom.frame = document.createElement('div');
this.dom.frame.className = 'vis-item-overflow';
this.dom.box.appendChild(this.dom.frame);
// contents box
dom.content = document.createElement('div');
dom.content.className = 'vis-item-content';
dom.frame.appendChild(dom.content);
this.dom.content = document.createElement('div');
this.dom.content.className = 'vis-item-content';
this.dom.frame.appendChild(this.dom.content);
// Note: we do NOT attach this item as attribute to the DOM,
// such that background items cannot be selected
//dom.box['timeline-item'] = this;
//this.dom.box['timeline-item'] = this;
this.dirty = true;
}
}
// append DOM to parent DOM
BackgroundItem.prototype._appendDomElement = function() {
if (!this.parent) {
throw new Error('Cannot redraw item: no parent attached');
}
if (!dom.box.parentNode) {
if (!this.dom.box.parentNode) {
var background = this.parent.dom.background;
if (!background) {
throw new Error('Cannot redraw item: parent has no background container element');
}
background.appendChild(dom.box);
background.appendChild(this.dom.box);
}
this.displayed = true;
}
// Update DOM when item is marked dirty. An item is marked dirty when:
BackgroundItem.prototype._updateDirtyDomComponents = function() {
// update dirty DOM. An item is marked dirty when:
// - the item is not yet rendered
// - the item's data is changed
// - the item is selected/deselected
@ -105,16 +104,71 @@ BackgroundItem.prototype.redraw = function() {
// update class
var className = (this.data.className ? (' ' + this.data.className) : '') +
(this.selected ? ' vis-selected' : '');
dom.box.className = this.baseClassName + className;
this.dom.box.className = this.baseClassName + className;
}
}
// determine from css whether this box has overflow
this.overflow = window.getComputedStyle(dom.content).overflow !== 'hidden';
BackgroundItem.prototype._getDomComponentsSizes = function() {
// determine from css whether this box has overflow
this.overflow = window.getComputedStyle(this.dom.content).overflow !== 'hidden';
return {
content: {
width: this.dom.content.offsetWidth
}
}
}
// recalculate size
this.props.content.width = this.dom.content.offsetWidth;
this.height = 0; // set height zero, so this item will be ignored when stacking items
BackgroundItem.prototype._updateDomComponentsSizes = function(sizes) {
// recalculate size
this.props.content.width = sizes.content.width;
this.height = 0; // set height zero, so this item will be ignored when stacking items
this.dirty = false;
this.dirty = false;
}
BackgroundItem.prototype._repaintDomAdditionals = function() {
}
/**
* Repaint the item
* @param {boolean} [returnQueue=false] return the queue
* @return {boolean} the redraw result or the redraw queue if returnQueue=true
*/
BackgroundItem.prototype.redraw = function(returnQueue) {
var sizes
var queue = [
// create item DOM
this._createDomElement.bind(this),
// append DOM to parent DOM
this._appendDomElement.bind(this),
this._updateDirtyDomComponents.bind(this),
(function() {
if (this.dirty) {
sizes = this._getDomComponentsSizes.bind(this)();
}
}).bind(this),
(function() {
if (this.dirty) {
this._updateDomComponentsSizes.bind(this)(sizes);
}
}).bind(this),
// repaint DOM additionals
this._repaintDomAdditionals.bind(this)
];
if (returnQueue) {
return queue;
} else {
var result;
queue.forEach(function (fn) {
result = fn();
});
return result;
}
};

+ 114
- 53
lib/timeline/component/item/BoxItem.js View File

@ -58,60 +58,58 @@ BoxItem.prototype.isVisible = function(range) {
return isVisible;
};
/**
* Repaint the item
*/
BoxItem.prototype.redraw = function() {
var dom = this.dom;
if (!dom) {
BoxItem.prototype._createDomElement = function() {
if (!this.dom) {
// create DOM
this.dom = {};
dom = this.dom;
// create main box
dom.box = document.createElement('DIV');
this.dom.box = document.createElement('DIV');
// contents box (inside the background box). used for making margins
dom.content = document.createElement('DIV');
dom.content.className = 'vis-item-content';
dom.box.appendChild(dom.content);
this.dom.content = document.createElement('DIV');
this.dom.content.className = 'vis-item-content';
this.dom.box.appendChild(this.dom.content);
// line to axis
dom.line = document.createElement('DIV');
dom.line.className = 'vis-line';
this.dom.line = document.createElement('DIV');
this.dom.line.className = 'vis-line';
// dot on axis
dom.dot = document.createElement('DIV');
dom.dot.className = 'vis-dot';
this.dom.dot = document.createElement('DIV');
this.dom.dot.className = 'vis-dot';
// attach this item as attribute
dom.box['timeline-item'] = this;
this.dom.box['timeline-item'] = this;
this.dirty = true;
}
}
// append DOM to parent DOM
BoxItem.prototype._appendDomElement = function() {
if (!this.parent) {
throw new Error('Cannot redraw item: no parent attached');
}
if (!dom.box.parentNode) {
if (!this.dom.box.parentNode) {
var foreground = this.parent.dom.foreground;
if (!foreground) throw new Error('Cannot redraw item: parent has no foreground container element');
foreground.appendChild(dom.box);
foreground.appendChild(this.dom.box);
}
if (!dom.line.parentNode) {
if (!this.dom.line.parentNode) {
var background = this.parent.dom.background;
if (!background) throw new Error('Cannot redraw item: parent has no background container element');
background.appendChild(dom.line);
background.appendChild(this.dom.line);
}
if (!dom.dot.parentNode) {
if (!this.dom.dot.parentNode) {
var axis = this.parent.dom.axis;
if (!background) throw new Error('Cannot redraw item: parent has no axis container element');
axis.appendChild(dom.dot);
axis.appendChild(this.dom.dot);
}
this.displayed = true;
}
// Update DOM when item is marked dirty. An item is marked dirty when:
BoxItem.prototype._updateDirtyDomComponents = function() {
// An item is marked dirty when:
// - the item is not yet rendered
// - the item's data is changed
// - the item is selected/deselected
@ -126,41 +124,104 @@ BoxItem.prototype.redraw = function() {
var className = (this.data.className? ' ' + this.data.className : '') +
(this.selected ? ' vis-selected' : '') +
(editable ? ' vis-editable' : ' vis-readonly');
dom.box.className = 'vis-item vis-box' + className;
dom.line.className = 'vis-item vis-line' + className;
dom.dot.className = 'vis-item vis-dot' + className;
// set initial position in the visible range of the grid so that the
// rendered box size can be determinated correctly, even the content
// has a dynamic width (fixes #2032).
var previousRight = dom.box.style.right;
var previousLeft = dom.box.style.left;
if (this.options.rtl) {
dom.box.style.right = "0px";
} else {
dom.box.style.left = "0px";
}
// recalculate size
this.props.dot.height = dom.dot.offsetHeight;
this.props.dot.width = dom.dot.offsetWidth;
this.props.line.width = dom.line.offsetWidth;
this.width = dom.box.offsetWidth;
this.height = dom.box.offsetHeight;
this.dom.box.className = 'vis-item vis-box' + className;
this.dom.line.className = 'vis-item vis-line' + className;
this.dom.dot.className = 'vis-item vis-dot' + className;
}
}
// restore previous position
if (this.options.rtl) {
dom.box.style.right = previousRight;
} else {
dom.box.style.left = previousLeft;
BoxItem.prototype._getDomComponentsSizes = function() {
return {
previous: {
right: this.dom.box.style.right,
left: this.dom.box.style.left
},
dot: {
height: this.dom.dot.offsetHeight,
width: this.dom.dot.offsetWidth
},
line: {
width: this.dom.line.offsetWidth
},
box: {
width: this.dom.box.offsetWidth,
height: this.dom.box.offsetHeight
}
}
}
BoxItem.prototype._updateDomComponentsSizes = function(sizes) {
if (this.options.rtl) {
this.dom.box.style.right = "0px";
} else {
this.dom.box.style.left = "0px";
}
this.dirty = false;
// recalculate size
this.props.dot.height = sizes.dot.height;
this.props.dot.width = sizes.dot.width;
this.props.line.width = sizes.line.width;
this.width = sizes.box.width;
this.height = sizes.box.height;
// restore previous position
if (this.options.rtl) {
this.dom.box.style.right = sizes.previous.right;
} else {
this.dom.box.style.left = sizes.previous.left;
}
this._repaintOnItemUpdateTimeTooltip(dom.box);
this.dirty = false;
}
BoxItem.prototype._repaintDomAdditionals = function() {
this._repaintOnItemUpdateTimeTooltip(this.dom.box);
this._repaintDragCenter();
this._repaintDeleteButton(dom.box);
this._repaintDeleteButton(this.dom.box);
}
/**
* Repaint the item
* @param {boolean} [returnQueue=false] return the queue
* @return {boolean} the redraw queue if returnQueue=true
*/
BoxItem.prototype.redraw = function(returnQueue) {
var sizes
var queue = [
// create item DOM
this._createDomElement.bind(this),
// append DOM to parent DOM
this._appendDomElement.bind(this),
// update dirty DOM
this._updateDirtyDomComponents.bind(this),
(function() {
if (this.dirty) {
sizes = this._getDomComponentsSizes();
}
}).bind(this),
(function() {
if (this.dirty) {
this._updateDomComponentsSizes.bind(this)(sizes);
}
}).bind(this),
// repaint DOM additionals
this._repaintDomAdditionals.bind(this)
];
if (returnQueue) {
return queue;
} else {
var result;
queue.forEach(function (fn) {
result = fn();
});
return result;
}
};
/**

+ 112
- 48
lib/timeline/component/item/PointItem.js View File

@ -48,49 +48,48 @@ PointItem.prototype.isVisible = function(range) {
return (this.data.start.getTime() + widthInMs > range.start ) && (this.data.start < range.end);
};
/**
* Repaint the item
*/
PointItem.prototype.redraw = function() {
var dom = this.dom;
if (!dom) {
PointItem.prototype._createDomElement = function() {
if (!this.dom) {
// create DOM
this.dom = {};
dom = this.dom;
// background box
dom.point = document.createElement('div');
this.dom.point = document.createElement('div');
// className is updated in redraw()
// contents box, right from the dot
dom.content = document.createElement('div');
dom.content.className = 'vis-item-content';
dom.point.appendChild(dom.content);
this.dom.content = document.createElement('div');
this.dom.content.className = 'vis-item-content';
this.dom.point.appendChild(this.dom.content);
// dot at start
dom.dot = document.createElement('div');
dom.point.appendChild(dom.dot);
this.dom.dot = document.createElement('div');
this.dom.point.appendChild(this.dom.dot);
// attach this item as attribute
dom.point['timeline-item'] = this;
this.dom.point['timeline-item'] = this;
this.dirty = true;
}
}
// append DOM to parent DOM
PointItem.prototype._appendDomElement = function() {
if (!this.parent) {
throw new Error('Cannot redraw item: no parent attached');
}
if (!dom.point.parentNode) {
if (!this.dom.point.parentNode) {
var foreground = this.parent.dom.foreground;
if (!foreground) {
throw new Error('Cannot redraw item: parent has no foreground container element');
}
foreground.appendChild(dom.point);
foreground.appendChild(this.dom.point);
}
this.displayed = true;
}
// Update DOM when item is marked dirty. An item is marked dirty when:
PointItem.prototype._updateDirtyDomComponents = function() {
// An item is marked dirty when:
// - the item is not yet rendered
// - the item's data is changed
// - the item is selected/deselected
@ -104,40 +103,105 @@ PointItem.prototype.redraw = function() {
var className = (this.data.className ? ' ' + this.data.className : '') +
(this.selected ? ' vis-selected' : '') +
(editable ? ' vis-editable' : ' vis-readonly');
dom.point.className = 'vis-item vis-point' + className;
dom.dot.className = 'vis-item vis-dot' + className;
// recalculate size of dot and contents
this.props.dot.width = dom.dot.offsetWidth;
this.props.dot.height = dom.dot.offsetHeight;
this.props.content.height = dom.content.offsetHeight;
// resize contents
if (this.options.rtl) {
dom.content.style.marginRight = 2 * this.props.dot.width + 'px';
} else {
dom.content.style.marginLeft = 2 * this.props.dot.width + 'px';
}
//dom.content.style.marginRight = ... + 'px'; // TODO: margin right
// recalculate size
this.width = dom.point.offsetWidth;
this.height = dom.point.offsetHeight;
// reposition the dot
dom.dot.style.top = ((this.height - this.props.dot.height) / 2) + 'px';
if (this.options.rtl) {
dom.dot.style.right = (this.props.dot.width / 2) + 'px';
} else {
dom.dot.style.left = (this.props.dot.width / 2) + 'px';
this.dom.point.className = 'vis-item vis-point' + className;
this.dom.dot.className = 'vis-item vis-dot' + className;
}
}
PointItem.prototype._getDomComponentsSizes = function() {
return {
dot: {
width: this.dom.dot.offsetWidth,
height: this.dom.dot.offsetHeight
},
content: {
width: this.dom.content.offsetWidth,
height: this.dom.content.offsetHeight
},
point: {
width: this.dom.point.offsetWidth,
height: this.dom.point.offsetHeight
}
}
}
this.dirty = false;
PointItem.prototype._updateDomComponentsSizes = function(sizes) {
// recalculate size of dot and contents
this.props.dot.width = sizes.dot.width;
this.props.dot.height = sizes.dot.height;
this.props.content.height = sizes.content.height;
// resize contents
if (this.options.rtl) {
this.dom.content.style.marginRight = 2 * this.props.dot.width + 'px';
} else {
this.dom.content.style.marginLeft = 2 * this.props.dot.width + 'px';
}
this._repaintOnItemUpdateTimeTooltip(dom.point);
//this.dom.content.style.marginRight = ... + 'px'; // TODO: margin right
// recalculate size
this.width = sizes.point.width;
this.height = sizes.point.height;
// reposition the dot
this.dom.dot.style.top = ((this.height - this.props.dot.height) / 2) + 'px';
if (this.options.rtl) {
this.dom.dot.style.right = (this.props.dot.width / 2) + 'px';
} else {
this.dom.dot.style.left = (this.props.dot.width / 2) + 'px';
}
this.dirty = false;
}
PointItem.prototype._repaintDomAdditionals = function() {
this._repaintOnItemUpdateTimeTooltip(this.dom.point);
this._repaintDragCenter();
this._repaintDeleteButton(dom.point);
this._repaintDeleteButton(this.dom.point);
}
/**
* Repaint the item
* @param {boolean} [returnQueue=false] return the queue
* @return {boolean} the redraw queue if returnQueue=true
*/
PointItem.prototype.redraw = function(returnQueue) {
var sizes
var queue = [
// create item DOM
this._createDomElement.bind(this),
// append DOM to parent DOM
this._appendDomElement.bind(this),
// update dirty DOM
this._updateDirtyDomComponents.bind(this),
(function() {
if (this.dirty) {
sizes = this._getDomComponentsSizes();
}
}).bind(this),
(function() {
if (this.dirty) {
this._updateDomComponentsSizes.bind(this)(sizes);
}
}).bind(this),
// repaint DOM additionals
this._repaintDomAdditionals.bind(this)
];
if (returnQueue) {
return queue;
} else {
var result;
queue.forEach(function (fn) {
result = fn();
});
return result;
}
};
/**

+ 89
- 33
lib/timeline/component/item/RangeItem.js View File

@ -46,55 +46,54 @@ RangeItem.prototype.isVisible = function(range) {
return (this.data.start < range.end) && (this.data.end > range.start);
};
/**
* Repaint the item
*/
RangeItem.prototype.redraw = function() {
var dom = this.dom;
if (!dom) {
RangeItem.prototype._createDomElement = function() {
if (!this.dom) {
// create DOM
this.dom = {};
dom = this.dom;
// background box
dom.box = document.createElement('div');
this.dom.box = document.createElement('div');
// className is updated in redraw()
// frame box (to prevent the item contents from overflowing)
dom.frame = document.createElement('div');
dom.frame.className = 'vis-item-overflow';
dom.box.appendChild(dom.frame);
this.dom.frame = document.createElement('div');
this.dom.frame.className = 'vis-item-overflow';
this.dom.box.appendChild(this.dom.frame);
// visible frame box (showing the frame that is always visible)
dom.visibleFrame = document.createElement('div');
dom.visibleFrame.className = 'vis-item-visible-frame';
dom.box.appendChild(dom.visibleFrame);
this.dom.visibleFrame = document.createElement('div');
this.dom.visibleFrame.className = 'vis-item-visible-frame';
this.dom.box.appendChild(this.dom.visibleFrame);
// contents box
dom.content = document.createElement('div');
dom.content.className = 'vis-item-content';
dom.frame.appendChild(dom.content);
this.dom.content = document.createElement('div');
this.dom.content.className = 'vis-item-content';
this.dom.frame.appendChild(this.dom.content);
// attach this item as attribute
dom.box['timeline-item'] = this;
this.dom.box['timeline-item'] = this;
this.dirty = true;
}
// append DOM to parent DOM
}
RangeItem.prototype._appendDomElement = function() {
if (!this.parent) {
throw new Error('Cannot redraw item: no parent attached');
}
if (!dom.box.parentNode) {
if (!this.dom.box.parentNode) {
var foreground = this.parent.dom.foreground;
if (!foreground) {
throw new Error('Cannot redraw item: parent has no foreground container element');
}
foreground.appendChild(dom.box);
foreground.appendChild(this.dom.box);
}
this.displayed = true;
}
// Update DOM when item is marked dirty. An item is marked dirty when:
RangeItem.prototype._updateDirtyDomComponents = function() {
// update dirty DOM. An item is marked dirty when:
// - the item is not yet rendered
// - the item's data is changed
// - the item is selected/deselected
@ -109,27 +108,84 @@ RangeItem.prototype.redraw = function() {
var className = (this.data.className ? (' ' + this.data.className) : '') +
(this.selected ? ' vis-selected' : '') +
(editable ? ' vis-editable' : ' vis-readonly');
dom.box.className = this.baseClassName + className;
this.dom.box.className = this.baseClassName + className;
// determine from css whether this box has overflow
this.overflow = window.getComputedStyle(dom.frame).overflow !== 'hidden';
// recalculate size
// turn off max-width to be able to calculate the real width
// this causes an extra browser repaint/reflow, but so be it
this.dom.content.style.maxWidth = 'none';
this.props.content.width = this.dom.content.offsetWidth;
this.height = this.dom.box.offsetHeight;
this.dom.content.style.maxWidth = '';
}
}
this.dirty = false;
RangeItem.prototype._getDomComponentsSizes = function() {
// determine from css whether this box has overflow
this.overflow = window.getComputedStyle(this.dom.frame).overflow !== 'hidden';
return {
content: {
width: this.dom.content.offsetWidth,
},
box: {
height: this.dom.box.offsetHeight
}
}
}
this._repaintOnItemUpdateTimeTooltip(dom.box);
this._repaintDeleteButton(dom.box);
RangeItem.prototype._updateDomComponentsSizes = function(sizes) {
this.props.content.width = sizes.content.width;
this.height = sizes.box.height;
this.dom.content.style.maxWidth = '';
this.dirty = false;
}
RangeItem.prototype._repaintDomAdditionals = function() {
this._repaintOnItemUpdateTimeTooltip(this.dom.box);
this._repaintDeleteButton(this.dom.box);
this._repaintDragCenter();
this._repaintDragLeft();
this._repaintDragRight();
}
/**
* Repaint the item
* @param {boolean} [returnQueue=false] return the queue
* @return {boolean} the redraw queue if returnQueue=true
*/
RangeItem.prototype.redraw = function(returnQueue) {
var sizes;
var queue = [
// create item DOM
this._createDomElement.bind(this),
// append DOM to parent DOM
this._appendDomElement.bind(this),
// update dirty DOM
this._updateDirtyDomComponents.bind(this),
(function() {
if (this.dirty) {
sizes = this._getDomComponentsSizes.bind(this)();
}
}).bind(this),
(function() {
if (this.dirty) {
this._updateDomComponentsSizes.bind(this)(sizes);
}
}).bind(this),
// repaint DOM additionals
this._repaintDomAdditionals.bind(this)
];
if (returnQueue) {
return queue;
} else {
var result;
queue.forEach(function (fn) {
result = fn();
});
return result;
}
};
/**

Loading…
Cancel
Save