From 9830616875a647e962aab4c0516c4bb40af8e42c Mon Sep 17 00:00:00 2001 From: Yotam Berkowitz Date: Mon, 18 Sep 2017 11:30:53 +0300 Subject: [PATCH] Group redraw performance (#3409) * Implement group redraw @grimalschi performance enhancement * Fix JSDoc for redraw * Remove commented out in itemset * Remove fasdom * Clean up queue functions in group redraw * Fix mistake in comments * Remove extra read-write from _didResize * Resolve review comments --- lib/timeline/component/Group.js | 159 ++++++++++++++++++++---------- lib/timeline/component/ItemSet.js | 36 +++++-- 2 files changed, 138 insertions(+), 57 deletions(-) diff --git a/lib/timeline/component/Group.js b/lib/timeline/component/Group.js index 306d0f4c..6a26b27b 100644 --- a/lib/timeline/component/Group.js +++ b/lib/timeline/component/Group.js @@ -206,19 +206,7 @@ Group.prototype.getLabelWidth = function() { return this.props.label.width; }; - -/** - * Repaint this group - * @param {{start: number, end: number}} range - * @param {{item: {horizontal: number, vertical: number}, axis: number}} margin - * @param {boolean} [forceRestack=false] Force restacking of all items - * @return {boolean} Returns true if the group is resized - */ -Group.prototype.redraw = function(range, margin, forceRestack) { - var resized = false; - - // force recalculation of the height of the items when the marker height changed - // (due to the Timeline being attached to the DOM or changed from display:none to visible) +Group.prototype._didMarkerHeightChange = function() { var markerHeight = this.dom.marker.clientHeight; if (markerHeight != this.lastMarkerHeight) { this.lastMarkerHeight = markerHeight; @@ -226,27 +214,22 @@ Group.prototype.redraw = function(range, margin, forceRestack) { item.dirty = true; if (item.displayed) item.redraw(); }); + return true; + } +} - forceRestack = true; - } - - // recalculate the height of the subgroups - this._calculateSubGroupHeights(margin); - - // calculate actual size and position +Group.prototype._calculateGroupSizeAndPosition = function() { var foreground = this.dom.foreground; this.top = foreground.offsetTop; this.right = foreground.offsetLeft; this.width = foreground.offsetWidth; +} - var lastIsVisible = this.isVisible; - this.isVisible = this._isGroupVisible(range, margin); - - var restack = forceRestack || this.stackDirty || (this.isVisible && !lastIsVisible); +Group.prototype._redrawItems = function(forceRestack, lastIsVisible, margin, range) { + var restack = forceRestack || this.stackDirty || this.isVisible && !lastIsVisible; - this._updateSubgroupsSizes(); - // if restacking, reposition visible items vertically - if(restack) { + // if restacking, reposition visible items vertically + if (restack) { if (typeof this.itemSet.options.order === 'function') { // a custom order function // brute force restack of all items @@ -255,7 +238,7 @@ Group.prototype.redraw = function(range, margin, forceRestack) { var me = this; var limitSize = false; util.forEach(this.items, function (item) { - if (!item.dom) { // If this item has never been displayed then the dom property will not be defined, this means we need to measure the size + if (!item.displayed) { item.redraw(); me.visibleItems.push(item); } @@ -268,41 +251,41 @@ Group.prototype.redraw = function(range, margin, forceRestack) { }); stack.stack(customOrderedItems, margin, true /* restack=true */); this.visibleItems = this._updateItemsInRange(this.orderedItems, this.visibleItems, range); - - } - else { + } else { // no custom order function, lazy stacking this.visibleItems = this._updateItemsInRange(this.orderedItems, this.visibleItems, range); - if (this.itemSet.options.stack) { // TODO: ugly way to access options... - stack.stack(this.visibleItems, margin, true /* restack=true */); - } - else { // no stacking + if (this.itemSet.options.stack) { + // TODO: ugly way to access options... + stack.stack(this.visibleItems, margin, true /* restack=true */); + } else { + // no stacking stack.nostack(this.visibleItems, margin, this.subgroups, this.itemSet.options.stackSubgroups); } } - + this.stackDirty = false; } - // recalculate the height of the group - var height = this._calculateHeight(margin); +} - // calculate actual size and position - foreground = this.dom.foreground; - this.top = foreground.offsetTop; - this.right = foreground.offsetLeft; - this.width = foreground.offsetWidth; +Group.prototype._didResize = function(resized, height) { resized = util.updateProperty(this, 'height', height) || resized; // recalculate size of label - resized = util.updateProperty(this.props.label, 'width', this.dom.inner.clientWidth) || resized; - resized = util.updateProperty(this.props.label, 'height', this.dom.inner.clientHeight) || resized; + var labelWidth = this.dom.inner.clientWidth; + var labelHeight = this.dom.inner.clientHeight; + resized = util.updateProperty(this.props.label, 'width', labelWidth) || resized; + resized = util.updateProperty(this.props.label, 'height', labelHeight) || resized; + return resized; +} - // apply new height - this.dom.background.style.height = height + 'px'; - this.dom.foreground.style.height = height + 'px'; +Group.prototype._applyGroupHeight = function(height) { + this.dom.background.style.height = height + 'px'; + this.dom.foreground.style.height = height + 'px'; this.dom.label.style.height = height + 'px'; +} - // update vertical position of items after they are re-stacked and the height of the group is calculated +// update vertical position of items after they are re-stacked and the height of the group is calculated +Group.prototype._updateItemsVerticalPosition = function(resized, margin) { for (var i = 0, ii = this.visibleItems.length; i < ii; i++) { var item = this.visibleItems[i]; item.repositionY(margin); @@ -312,10 +295,84 @@ Group.prototype.redraw = function(range, margin, forceRestack) { } if (!this.isVisible && this.height) { - return resized = false; + return false; } return resized; +} + +/** + * Repaint this group + * @param {{start: number, end: number}} range + * @param {{item: {horizontal: number, vertical: number}, axis: number}} margin + * @param {boolean} [forceRestack=false] Force restacking of all items + * @param {boolean} [returnQueue=false] return the queue or if the group resized + * @return {boolean} Returns true if the group is resized or the redraw queue if returnQueue=true + */ +Group.prototype.redraw = function(range, margin, forceRestack, returnQueue) { + var resized = false; + var lastIsVisible = this.isVisible; + var height; + + var queue = [ + // force recalculation of the height of the items when the marker height changed + // (due to the Timeline being attached to the DOM or changed from display:none to visible) + (function () { + forceRestack = this._didMarkerHeightChange.bind(this); + }).bind(this), + + // recalculate the height of the subgroups + this._updateSubGroupHeights.bind(this, margin), + + // calculate actual size and position + this._calculateGroupSizeAndPosition.bind(this), + + // check if group is visible + (function() { + this.isVisible = this._isGroupVisible.bind(this)(range, margin); + }).bind(this), + + // redraw Items if needed + (function() { + this._redrawItems.bind(this)(forceRestack, lastIsVisible, margin, range) + }).bind(this), + + // update subgroups + this._updateSubgroupsSizes.bind(this), + + // recalculate the height of the group + (function() { + height = this._calculateHeight.bind(this)(margin); + }).bind(this), + + // calculate actual size and position again + this._calculateGroupSizeAndPosition.bind(this), + + // check if resized + (function() { + resized = this._didResize.bind(this)(resized, height) + }).bind(this), + + // apply group height + (function() { + this._applyGroupHeight.bind(this)(height) + }).bind(this), + + // update vertical position of items after they are re-stacked and the height of the group is calculated + (function() { + return resized = this._updateItemsVerticalPosition.bind(this)(resized, margin) + }).bind(this) + ] + + if (returnQueue) { + return queue; + } else { + var result; + queue.forEach(function (fn) { + result = fn(); + }); + return result; + } }; /** @@ -324,7 +381,7 @@ Group.prototype.redraw = function(range, margin, forceRestack) { * @param {{item: vis.Item}} margin * @private */ -Group.prototype._calculateSubGroupHeights = function (margin) { +Group.prototype._updateSubGroupHeights = function (margin) { if (Object.keys(this.subgroups).length > 0) { var me = this; diff --git a/lib/timeline/component/ItemSet.js b/lib/timeline/component/ItemSet.js index 51f9a02a..5bd9bb41 100644 --- a/lib/timeline/component/ItemSet.js +++ b/lib/timeline/component/ItemSet.js @@ -681,13 +681,37 @@ ItemSet.prototype.redraw = function() { // redraw the background group this.groups[BACKGROUND].redraw(range, nonFirstMargin, forceRestack); - // redraw all regular groups - util.forEach(this.groups, function (group) { - var groupMargin = (group == firstGroup) ? firstMargin : nonFirstMargin; - var groupResized = group.redraw(range, groupMargin, forceRestack); - resized = groupResized || resized; - height += group.height; + var redrawQueue = {}; + var redrawQueueLength = 0; + + // collect redraw functions + util.forEach(this.groups, function (group, key) { + if (key === BACKGROUND) return; + var groupMargin = group == firstGroup ? firstMargin : nonFirstMargin; + var returnQueue = true; + redrawQueue[key] = group.redraw(range, groupMargin, forceRestack, returnQueue); + redrawQueueLength = redrawQueue[key].length; }); + + if (redrawQueueLength) { + var redrawResults = {}; + + for (var i = 0; i < redrawQueueLength; i++) { + util.forEach(redrawQueue, function (fns, key) { + redrawResults[key] = fns[i](); + }); + } + + // redraw all regular groups + util.forEach(this.groups, function (group, key) { + if (key === BACKGROUND) return; + var groupResized = redrawResults[key]; + resized = groupResized || resized; + height += group.height; + }); + height = Math.max(height, minHeight); + } + height = Math.max(height, minHeight); // update frame height