From 876e0ab83e3d378aefa3005a49d0641e3f60f6ca Mon Sep 17 00:00:00 2001 From: Ludo Stellingwerff Date: Tue, 8 Nov 2016 13:54:47 +0100 Subject: [PATCH] Graph2d performance enhancement (#2281) * Important performance enhancement supporting incremental changes to large graphs, these were too slow due to doing a full item conversion on each add(). Note: the data handling side of graph2d is currently somewhat over-engineered (=too complex) It can use some simplification.... * Fixed style based on feedback. --- lib/DataSet.js | 5 ++++ lib/timeline/component/LineGraph.js | 41 ++++++++++++++++++++++++++--- 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/lib/DataSet.js b/lib/DataSet.js index 8f3b910f..84d3f8c6 100644 --- a/lib/DataSet.js +++ b/lib/DataSet.js @@ -910,6 +910,11 @@ DataSet.prototype._getItem = function (id, types) { converted[field] = value; } } + + if (!converted[this._fieldId]) { + converted[this._fieldId] = id; + } + return converted; }; diff --git a/lib/timeline/component/LineGraph.js b/lib/timeline/component/LineGraph.js index 961a293d..fa0934e2 100644 --- a/lib/timeline/component/LineGraph.js +++ b/lib/timeline/component/LineGraph.js @@ -332,7 +332,7 @@ LineGraph.prototype.setGroups = function (groups) { }; LineGraph.prototype._onUpdate = function (ids) { - this._updateAllGroupData(); + this._updateAllGroupData(ids); }; LineGraph.prototype._onAdd = function (ids) { this._onUpdate(ids); @@ -341,7 +341,7 @@ LineGraph.prototype._onRemove = function (ids) { this._onUpdate(ids); }; LineGraph.prototype._onUpdateGroups = function (groupIds) { - this._updateAllGroupData(); + this._updateAllGroupData(null, groupIds); }; LineGraph.prototype._onAddGroups = function (groupIds) { this._onUpdateGroups(groupIds); @@ -425,12 +425,22 @@ LineGraph.prototype._updateGroup = function (group, groupId) { /** * this updates all groups, it is used when there is an update the the itemset. * + * @param {Array} ids + * @param {Array} groupIds * @private */ -LineGraph.prototype._updateAllGroupData = function () { +LineGraph.prototype._updateAllGroupData = function (ids, groupIds) { if (this.itemsData != null) { var groupsContent = {}; var items = this.itemsData.get(); + var fieldId = this.itemsData._fieldId; + var idMap = {}; + if (ids){ + ids.map(function (id) { + idMap[id] = id; + }); + } + //pre-Determine array sizes, for more efficient memory claim var groupCounts = {}; for (var i = 0; i < items.length; i++) { @@ -441,6 +451,26 @@ LineGraph.prototype._updateAllGroupData = function () { } groupCounts.hasOwnProperty(groupId) ? groupCounts[groupId]++ : groupCounts[groupId] = 1; } + + //Pre-load arrays from existing groups if items are not changed (not in ids) + if (!groupIds && ids) { + for (var groupId in this.groups) { + if (this.groups.hasOwnProperty(groupId)) { + var group = this.groups[groupId]; + var existing_items = group.getItems(); + + groupsContent[groupId] = existing_items.filter(function (item) { + return (item[fieldId] !== idMap[item[fieldId]]); + }); + var newLength = groupCounts[groupId]; + groupCounts[groupId] -= groupsContent[groupId].length; + if (groupsContent[groupId].length < newLength) { + groupsContent[groupId][newLength - 1] = {}; + } + } + } + } + //Now insert data into the arrays. for (var i = 0; i < items.length; i++) { var item = items[i]; @@ -448,6 +478,9 @@ LineGraph.prototype._updateAllGroupData = function () { if (groupId === null || groupId === undefined) { groupId = UNGROUPED; } + if (!groupIds && ids && (item[fieldId] !== idMap[item[fieldId]])) { + continue; + } if (!groupsContent.hasOwnProperty(groupId)) { groupsContent[groupId] = new Array(groupCounts[groupId]); } @@ -456,6 +489,7 @@ LineGraph.prototype._updateAllGroupData = function () { extended.x = util.convert(item.x, 'Date'); extended.orginalY = item.y; //real Y extended.y = Number(item.y); + extended[fieldId] = item[fieldId]; var index= groupsContent[groupId].length - groupCounts[groupId]--; groupsContent[groupId][index] = extended; @@ -834,6 +868,7 @@ LineGraph.prototype._applySampling = function (groupIds, groupsData) { // the global screen is used because changing the width of the yAxis may affect the increment, resulting in an endless loop // of width changing of the yAxis. + //TODO: This assumes sorted data, but that's not guaranteed! var xDistance = this.body.util.toGlobalScreen(dataContainer[dataContainer.length - 1].x) - this.body.util.toGlobalScreen(dataContainer[0].x); var pointsPerPixel = amountOfPoints / xDistance; increment = Math.min(Math.ceil(0.2 * amountOfPoints), Math.max(1, Math.round(pointsPerPixel)));