From 4f97bc871c7803f5ab182f761715d0c876570842 Mon Sep 17 00:00:00 2001 From: guvial Date: Sat, 18 Mar 2017 13:25:52 +0100 Subject: [PATCH 01/21] Prevent redirect to blank after drag and drop (FF) (#2871) Fix proposal for issue #2842 More details here : #2842 --- lib/timeline/Core.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/timeline/Core.js b/lib/timeline/Core.js index 01bf3993..8fbf0e97 100644 --- a/lib/timeline/Core.js +++ b/lib/timeline/Core.js @@ -245,6 +245,9 @@ Core.prototype._create = function (container) { } function handleDrop(event) { + // prevent redirect to blank page - Firefox + if(event.preventDefault) { event.preventDefault(); } + if(event.stopPropagation) { event.stopPropagation(); } // return when dropping non-vis items try { var itemData = JSON.parse(event.dataTransfer.getData("text")) From 2d4491e55716788364648a6e457b6113e639c1a0 Mon Sep 17 00:00:00 2001 From: Alexander Wunschik Date: Sat, 18 Mar 2017 15:30:18 +0100 Subject: [PATCH 02/21] Release v4.19.0 (#2874) fixes #2784 --- HISTORY.md | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 4 ++-- 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index 98831743..bb032ebe 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -2,6 +2,65 @@ http://visjs.org +## 2017-02-25, version 4.19.0 + +### General + +- FIX: Fix eslint problem on Travis. (#2744) +- added support for eslint (#2695) +- Trivial typo fix in how_to_help doc. (#2714) +- add link to a mentioned example (#2709) +- FEAT: use babel preset2015 for custom builds (#2678) +- FIX: use babel version compatible with webpack@1.14 (#2693) +- FEAT: run mocha tests in travis ci (#2687) +- Add note that PRs should be submitted against the `develop` branch (#2623) +- FIX: Fixes instanceof Object statements for objects from other windows and iFrames. (#2631) +- removed google-analytics from all examples (#2670) +- do not ignore test folder (#2648) +- updated dependencies and devDependencies (#2649) +- general improvements (#2652) + +### Network + +- FEAT: Improve the performance of the network layout engine (#2729) +- FEAT: Allow for image nodes to have a selected or broken image (#2601) + +### Timeline / Graph2D + +- FIX #2842: Prevent redirect to blank after drag and drop in FF (#2871) +- FIX #2810: Nested groups do not use "groupOrder" (#2817) +- FIX #2795: fix date for custom format function (#2826) +- FIX #2689: Add animation options for zoomIn/zoomOut funtions (#2830) +- FIX #2800: Removed all "Object.assign" from examples (#2829) +- FIX #2725: Background items positioning when orientation: top (#2831) +- FEAT: Added data as argument to the template function (#2802) +- FIX #2827: Update "progress bar" example to reflect values (#2828) +- FIX #2672: Item events original event (#2704) +- FIX #2696: Update serialization example to use ISOString dates (#2789) +- FIX #2790: Update examples to use ISOString format (#2791) +- FEAT: Added support to supply an end-time to bar charts to have them scale (#2760) +- FIX #1982, #1417: Modify redraw logic to treat scroll as needing restack (#2774) +- FEAT: Initial tests for timeline ItemSet (#2750) +- FIX #2720: Problems with option editable (#2743, #2796, #2806) +- FIX: Range.js "event" is undeclared (#2749) +- FEAT: added new locales for french and espanol (#2723) +- FIX: fixes timestep next issue (#2732) +- FEAT: #2647 Dynamic rolling mode option (#2705) +- FIX #2679: TypeError: Cannot read property 'hasOwnProperty' of null (#2735) +- Add initial tests for Timeline PointItem (#2716) +- FIX #778: Tooltip does not work with background items in timeline (#2703) +- FIX #2598: Flickering onUpdateTimeTooltip (#2702) +- FEAT: refactor tooltip to only use one dom-element (#2662) +- FEAT: Change setCustomTimeTitle title parameter to be a string or a function (#2611) + +### Graph3D + +- FEAT #2769: Graph3d tooltip styling (#2780) +- FEAT #2540: Adjusted graph3d doc for autoscaling (#2812) +- FIX #2536: 3d bar graph data array unsorted (#2803) +- FEAT: Added showX(YZ)Axis options to Graph3d (#2686) + + ## 2017-01-29, version 4.18.1 ### General diff --git a/package.json b/package.json index f7698cd3..8b68b32b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vis", - "version": "4.18.1-SNAPSHOT", + "version": "4.19.0", "description": "A dynamic, browser-based visualization library.", "homepage": "http://visjs.org/", "license": "(Apache-2.0 OR MIT)", @@ -28,7 +28,7 @@ "lint": "eslint lib", "watch": "gulp watch", "watch-dev": "gulp watch --bundle" - }, + }, "dependencies": { "babel-core": "^6.6.5", "babel-loader": "^6.2.4", From c18a39285c16b5e8411d7c4361b47a16bd0824d3 Mon Sep 17 00:00:00 2001 From: Alexander Wunschik Date: Sat, 18 Mar 2017 16:17:20 +0100 Subject: [PATCH 03/21] changed to v4.19.0-SNAPSHOT --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8b68b32b..51625b6f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vis", - "version": "4.19.0", + "version": "4.19.0-SNAPSHOT", "description": "A dynamic, browser-based visualization library.", "homepage": "http://visjs.org/", "license": "(Apache-2.0 OR MIT)", From 2a1ce025e6b808222ea637737bd6f7c00e41697e Mon Sep 17 00:00:00 2001 From: Alexander Wunschik Date: Sat, 18 Mar 2017 21:26:16 +0100 Subject: [PATCH 04/21] fix(chore): Moved babel to devDependencies (#2875) fixes #2685 --- package.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 51625b6f..383c4c07 100644 --- a/package.json +++ b/package.json @@ -30,14 +30,6 @@ "watch-dev": "gulp watch --bundle" }, "dependencies": { - "babel-core": "^6.6.5", - "babel-loader": "^6.2.4", - "babel-polyfill": "^6.22.0", - "babel-plugin-transform-es3-member-expression-literals": "^6.22.0", - "babel-plugin-transform-es3-property-literals": "^6.8.0", - "babel-plugin-transform-runtime": "^6.22.0", - "babel-preset-es2015": "^6.6.0", - "babel-runtime": "^6.22.0", "emitter-component": "^1.1.1", "moment": "^2.17.1", "propagating-hammerjs": "^1.4.6", @@ -46,7 +38,15 @@ }, "devDependencies": { "async": "^2.1.4", + "babel-core": "^6.6.5", "babel-eslint": "^7.1.1", + "babel-loader": "^6.2.4", + "babel-polyfill": "^6.22.0", + "babel-plugin-transform-es3-member-expression-literals": "^6.22.0", + "babel-plugin-transform-es3-property-literals": "^6.8.0", + "babel-plugin-transform-runtime": "^6.22.0", + "babel-preset-es2015": "^6.6.0", + "babel-runtime": "^6.22.0", "babelify": "^7.3.0", "clean-css": "^4.0.2", "eslint": "^3.15.0", From a6a7ee232e6aac2e7ef78b351a345177a61a77fe Mon Sep 17 00:00:00 2001 From: Ben Morton Date: Sat, 18 Mar 2017 20:49:51 +0000 Subject: [PATCH 05/21] Allow nested groups to be removed by nulling the nestedGroups content. (#2852) Don't update the showNested property if nothing has been set in the data object, unless it is undefined. Verify group object exists during `onGroupClick` --- lib/timeline/component/Group.js | 25 +++++++++++++++++++------ lib/timeline/component/ItemSet.js | 2 +- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/lib/timeline/component/Group.js b/lib/timeline/component/Group.js index 883d8580..42c2eddb 100644 --- a/lib/timeline/component/Group.js +++ b/lib/timeline/component/Group.js @@ -133,21 +133,34 @@ Group.prototype.setData = function(data) { } if (data && data.nestedGroups) { - if (data.showNested == false) { - this.showNested = false; - } else { - this.showNested = true; + if (!this.nestedGroups || this.nestedGroups != data.nestedGroups) { + this.nestedGroups = data.nestedGroups; + } + + if (data.showNested !== undefined || this.showNested === undefined) { + if (data.showNested == false) { + this.showNested = false; + } else { + this.showNested = true; + } } util.addClassName(this.dom.label, 'vis-nesting-group'); + var collapsedDirClassName = this.itemSet.options.rtl ? 'collapsed-rtl' : 'collapsed' if (this.showNested) { - util.removeClassName(this.dom.label, 'collapsed'); + util.removeClassName(this.dom.label, collapsedDirClassName); util.addClassName(this.dom.label, 'expanded'); } else { util.removeClassName(this.dom.label, 'expanded'); - var collapsedDirClassName = this.itemSet.options.rtl ? 'collapsed-rtl' : 'collapsed' util.addClassName(this.dom.label, collapsedDirClassName); } + } else if (this.nestedGroups) { + this.nestedGroups = null; + + var collapsedDirClassName = this.itemSet.options.rtl ? 'collapsed-rtl' : 'collapsed' + util.removeClassName(this.dom.label, collapsedDirClassName); + util.removeClassName(this.dom.label, 'expanded'); + util.removeClassName(this.dom.label, 'vis-nesting-group'); } if (data && data.nestedInGroup) { diff --git a/lib/timeline/component/ItemSet.js b/lib/timeline/component/ItemSet.js index d3f670fe..46129a6a 100644 --- a/lib/timeline/component/ItemSet.js +++ b/lib/timeline/component/ItemSet.js @@ -1644,7 +1644,7 @@ ItemSet.prototype._onDragEnd = function (event) { ItemSet.prototype._onGroupClick = function (event) { var group = this.groupFromTarget(event); - if (!group.nestedGroups) return; + if (!group || !group.nestedGroups) return; var groupsData = this.groupsData; if (this.groupsData instanceof DataView) { From a67a8e2287716dff1919de205aed72b4064a153c Mon Sep 17 00:00:00 2001 From: Ben Morton Date: Sat, 18 Mar 2017 21:05:47 +0000 Subject: [PATCH 06/21] Fixes for removing and adding items to subgroups (#2821) * Add methods to remove and add items to a subgroup. Add method to change the subgroup an item is in. No longer decrement subgroup ID when removing a subgroup. Fixes #2594 * Moved the orderSubgroups call outside of the add/remove methods. Added the ability to call the add/remove methods without a subgroupId parameter (takes from item data instead). --- lib/timeline/component/Group.js | 85 +++++++++++++++++++------------ lib/timeline/component/ItemSet.js | 21 +++++--- 2 files changed, 66 insertions(+), 40 deletions(-) diff --git a/lib/timeline/component/Group.js b/lib/timeline/component/Group.js index 42c2eddb..58920dfa 100644 --- a/lib/timeline/component/Group.js +++ b/lib/timeline/component/Group.js @@ -438,30 +438,9 @@ Group.prototype.add = function(item) { // add to if (item.data.subgroup !== undefined) { - if (this.subgroups[item.data.subgroup] === undefined) { - this.subgroups[item.data.subgroup] = { - height:0, - top: 0, - start: item.data.start, - end: item.data.end, - visible: false, - index:this.subgroupIndex, - items: [] - }; - this.subgroupIndex++; - } - - - if (new Date(item.data.start) < new Date(this.subgroups[item.data.subgroup].start)) { - this.subgroups[item.data.subgroup].start = item.data.start; - } - if (new Date(item.data.end) > new Date(this.subgroups[item.data.subgroup].end)) { - this.subgroups[item.data.subgroup].end = item.data.end; - } - - this.subgroups[item.data.subgroup].items.push(item); + this._addToSubgroup(item); + this.orderSubgroups(); } - this.orderSubgroups(); if (this.visibleItems.indexOf(item) == -1) { var range = this.itemSet.body.range; // TODO: not nice accessing the range like this @@ -469,6 +448,34 @@ Group.prototype.add = function(item) { } }; + +Group.prototype._addToSubgroup = function(item, subgroupId) { + subgroupId = subgroupId || item.data.subgroup; + if (subgroupId != undefined && this.subgroups[subgroupId] === undefined) { + this.subgroups[subgroupId] = { + height:0, + top: 0, + start: item.data.start, + end: item.data.end, + visible: false, + index:this.subgroupIndex, + items: [] + }; + this.subgroupIndex++; + } + + + if (new Date(item.data.start) < new Date(this.subgroups[subgroupId].start)) { + this.subgroups[subgroupId].start = item.data.start; + } + if (new Date(item.data.end) > new Date(this.subgroups[subgroupId].end)) { + this.subgroups[subgroupId].end = item.data.end; + } + + this.subgroups[subgroupId].items.push(item); + +}; + Group.prototype._updateSubgroupsSizes = function () { var me = this; if (me.subgroups) { @@ -539,22 +546,30 @@ Group.prototype.remove = function(item) { if (index != -1) this.visibleItems.splice(index, 1); if(item.data.subgroup !== undefined){ - var subgroup = this.subgroups[item.data.subgroup]; + this._removeFromSubgroup(item); + this.orderSubgroups(); + } +}; + +Group.prototype._removeFromSubgroup = function(item, subgroupId) { + subgroupId = subgroupId || item.data.subgroup; + if (subgroupId != undefined) { + var subgroup = this.subgroups[subgroupId]; if (subgroup){ var itemIndex = subgroup.items.indexOf(item); - subgroup.items.splice(itemIndex,1); - if (!subgroup.items.length){ - delete this.subgroups[item.data.subgroup]; - this.subgroupIndex--; - } else { - this._updateSubgroupsSizes(); + // Check the item is actually in this subgroup. How should items not in the group be handled? + if (itemIndex >= 0) { + subgroup.items.splice(itemIndex,1); + if (!subgroup.items.length){ + delete this.subgroups[subgroupId]; + } else { + this._updateSubgroupsSizes(); + } } - this.orderSubgroups(); } } }; - /** * Remove an item from the corresponding DataSet * @param {Item} item @@ -735,6 +750,10 @@ Group.prototype._checkIfVisibleWithReference = function(item, visibleItems, visi } }; - +Group.prototype.changeSubgroup = function(item, oldSubgroup, newSubgroup) { + this._removeFromSubgroup(item, oldSubgroup); + this._addToSubgroup(item, newSubgroup); + this.orderSubgroups(); +}; module.exports = Group; diff --git a/lib/timeline/component/ItemSet.js b/lib/timeline/component/ItemSet.js index 46129a6a..a278fba9 100644 --- a/lib/timeline/component/ItemSet.js +++ b/lib/timeline/component/ItemSet.js @@ -1199,21 +1199,28 @@ ItemSet.prototype._updateItem = function(item, itemData) { var oldGroupId = item.data.group; var oldSubGroupId = item.data.subgroup; + if (oldGroupId != itemData.group) { + var oldGroup = this.groups[oldGroupId]; + if (oldGroup) oldGroup.remove(item); + } + // update the items data (will redraw the item when displayed) item.setData(itemData); var groupId = this._getGroupId(item.data); - var group = this.groups[groupId]; + var group = this.groups[groupId]; if (!group) { - item.groupShowing = false; + item.groupShowing = false; } else if (group && group.data && group.data.showNested) { - item.groupShowing = true; + item.groupShowing = true; } // update group - if (oldGroupId != item.data.group || oldSubGroupId != item.data.subgroup) { - var oldGroup = this.groups[oldGroupId]; - if (oldGroup) oldGroup.remove(item); - if (group) group.add(item); + if (group) { + if (oldGroupId != item.data.group) { + group.add(item); + } else if (oldSubGroupId != item.data.subgroup) { + group.changeSubgroup(item, oldSubGroupId); + } } }; From 0906f0a49313e6c5a3d848f9dad674e9ca45113c Mon Sep 17 00:00:00 2001 From: yotamberk Date: Sun, 19 Mar 2017 11:29:31 +0200 Subject: [PATCH 07/21] fix(timeline): #2809 Fix docs typo in `showNested` (#2879) --- docs/timeline/index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/timeline/index.html b/docs/timeline/index.html index b69d05bd..3bcd44fe 100644 --- a/docs/timeline/index.html +++ b/docs/timeline/index.html @@ -468,10 +468,10 @@ var groups = [ Array of group ids nested in the group. Nested groups will appear under this nesting group. - showNestedGroups + showNested Boolean no - Assuming the group has nested groups, this will set the initial state of the group - shown or collapsed. The showNestedGroups is defaulted to true. + Assuming the group has nested groups, this will set the initial state of the group - shown or collapsed. The showNested is defaulted to true. From d6f63589c8f7bde4170f66ca2898b224420762be Mon Sep 17 00:00:00 2001 From: Alexander Wunschik Date: Sun, 19 Mar 2017 11:03:52 +0100 Subject: [PATCH 08/21] Release v4.19.1 --- HISTORY.md | 14 +++++++++++++- package.json | 2 +- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index bb032ebe..23e8c72f 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,8 +1,20 @@ # vis.js history http://visjs.org +## 2017-03-19, version 4.19.1 -## 2017-02-25, version 4.19.0 +### General + +* FIX: #2685 Fixed babel dependencies (#2875) + +### Timeline / Graph2D + +* FIX #2809: Fix docs typo in "showNested" (#2879) +* FIX #2594: Fixes for removing and adding items to subgroups (#2821) +* FIX: Allow nested groups to be removed (#2852) + + +## 2017-03-18, version 4.19.0 ### General diff --git a/package.json b/package.json index 383c4c07..b46c66a4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vis", - "version": "4.19.0-SNAPSHOT", + "version": "4.19.1", "description": "A dynamic, browser-based visualization library.", "homepage": "http://visjs.org/", "license": "(Apache-2.0 OR MIT)", From 0944445a1a939791728429753eeb285e38f7a30a Mon Sep 17 00:00:00 2001 From: Alexander Wunschik Date: Sun, 19 Mar 2017 11:15:39 +0100 Subject: [PATCH 09/21] changed version to v4.19.1-SNAPSHOT --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b46c66a4..1c1b30e7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vis", - "version": "4.19.1", + "version": "4.19.1-SNAPSHOT", "description": "A dynamic, browser-based visualization library.", "homepage": "http://visjs.org/", "license": "(Apache-2.0 OR MIT)", From 92088be21bf3711d91bcfd802860eb3bd931b850 Mon Sep 17 00:00:00 2001 From: Alexander Wunschik Date: Tue, 21 Mar 2017 20:28:46 +0100 Subject: [PATCH 10/21] added @wimrijnders to the support team (#2886) --- misc/we_need_help.md | 1 + 1 file changed, 1 insertion(+) diff --git a/misc/we_need_help.md b/misc/we_need_help.md index 7eb941bc..bba818a2 100644 --- a/misc/we_need_help.md +++ b/misc/we_need_help.md @@ -16,3 +16,4 @@ If you have shown some commitment to the project you can ask [@ludost](//github. * [@Tooa](//github.com/Tooa) * [@eymiha](//github.com/eymiha) * [@bradh](//github.com/bradh) +* [@wimrijnders](//github.com/wimrijnders) From 1901a3ec9d8c9ca8acb1119c86734fae4afa78b8 Mon Sep 17 00:00:00 2001 From: yotamberk Date: Tue, 21 Mar 2017 22:13:31 +0200 Subject: [PATCH 11/21] Visible items bug (#2878) * Fix redraw order * Fix error when option is not defined * Allow template labels * Add .travis.yml file * Add experiment travis code * Fix react example * Fix getVisibleItems method * Remove console log --- lib/timeline/component/ItemSet.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/timeline/component/ItemSet.js b/lib/timeline/component/ItemSet.js index a278fba9..23798ecf 100644 --- a/lib/timeline/component/ItemSet.js +++ b/lib/timeline/component/ItemSet.js @@ -546,7 +546,7 @@ ItemSet.prototype.getVisibleItems = function() { for (var groupId in this.groups) { if (this.groups.hasOwnProperty(groupId)) { var group = this.groups[groupId]; - var rawVisibleItems = group.visibleItems; + var rawVisibleItems = group.isVisible ? group.visibleItems : []; // filter the "raw" set with visibleItems into a set which is really // visible by pixels From ec201296aa9396318d3ae0438a7323248405b326 Mon Sep 17 00:00:00 2001 From: Ludo Stellingwerff Date: Thu, 23 Mar 2017 22:34:56 +0100 Subject: [PATCH 12/21] Reduce the time-complexity of the network initial positioning. (#2759) * Reduce the time-complexity of the network initial positioning. Very substantial performance gain for large graphs. * Remove unwanted console messages, extended comment. --- lib/network/modules/KamadaKawai.js | 118 ++++++++++++++---- lib/network/modules/LayoutEngine.js | 26 ++-- .../components/algorithms/FloydWarshall.js | 27 ++-- 3 files changed, 130 insertions(+), 41 deletions(-) diff --git a/lib/network/modules/KamadaKawai.js b/lib/network/modules/KamadaKawai.js index 63d620ff..50e84c83 100644 --- a/lib/network/modules/KamadaKawai.js +++ b/lib/network/modules/KamadaKawai.js @@ -49,11 +49,14 @@ class KamadaKawai { // get the K Matrix this._createK_matrix(D_matrix); + // initial E Matrix + this._createE_matrix(); + // calculate positions let threshold = 0.01; let innerThreshold = 1; let iterations = 0; - let maxIterations = Math.max(1000,Math.min(10*this.body.nodeIndices.length,6000)); + let maxIterations = Math.max(1000, Math.min(10 * this.body.nodeIndices.length, 6000)); let maxInnerIterations = 5; let maxEnergy = 1e9; @@ -64,10 +67,10 @@ class KamadaKawai { [highE_nodeId, maxEnergy, dE_dx, dE_dy] = this._getHighestEnergyNode(ignoreClusters); delta_m = maxEnergy; subIterations = 0; - while(delta_m > innerThreshold && subIterations < maxInnerIterations) { + while (delta_m > innerThreshold && subIterations < maxInnerIterations) { subIterations += 1; this._moveNode(highE_nodeId, dE_dx, dE_dy); - [delta_m,dE_dx,dE_dy] = this._getEnergy(highE_nodeId); + [delta_m, dE_dx, dE_dy] = this._getEnergy(highE_nodeId); } } } @@ -87,7 +90,7 @@ class KamadaKawai { for (let nodeIdx = 0; nodeIdx < nodesArray.length; nodeIdx++) { let m = nodesArray[nodeIdx]; // by not evaluating nodes with predefined positions we should only move nodes that have no positions. - if ((nodes[m].predefinedPosition === false || nodes[m].isCluster === true && ignoreClusters === true) || nodes[m].options.fixed.x === true || nodes[m].options.fixed.y === true) { + if ((nodes[m].predefinedPosition === false || nodes[m].isCluster === true && ignoreClusters === true) || nodes[m].options.fixed.x === true || nodes[m].options.fixed.y === true) { let [delta_m,dE_dx,dE_dy] = this._getEnergy(m); if (maxEnergy < delta_m) { maxEnergy = delta_m; @@ -108,24 +111,7 @@ class KamadaKawai { * @private */ _getEnergy(m) { - let nodesArray = this.body.nodeIndices; - let nodes = this.body.nodes; - - let x_m = nodes[m].x; - let y_m = nodes[m].y; - let dE_dx = 0; - let dE_dy = 0; - for (let iIdx = 0; iIdx < nodesArray.length; iIdx++) { - let i = nodesArray[iIdx]; - if (i !== m) { - let x_i = nodes[i].x; - let y_i = nodes[i].y; - let denominator = 1.0 / Math.sqrt(Math.pow(x_m - x_i, 2) + Math.pow(y_m - y_i, 2)); - dE_dx += this.K_matrix[m][i] * ((x_m - x_i) - this.L_matrix[m][i] * (x_m - x_i) * denominator); - dE_dy += this.K_matrix[m][i] * ((y_m - y_i) - this.L_matrix[m][i] * (y_m - y_i) * denominator); - } - } - + let [dE_dx,dE_dy] = this.E_sums[m]; let delta_m = Math.sqrt(Math.pow(dE_dx, 2) + Math.pow(dE_dy, 2)); return [delta_m, dE_dx, dE_dy]; } @@ -147,15 +133,20 @@ class KamadaKawai { let x_m = nodes[m].x; let y_m = nodes[m].y; + let km = this.K_matrix[m]; + let lm = this.L_matrix[m]; + for (let iIdx = 0; iIdx < nodesArray.length; iIdx++) { let i = nodesArray[iIdx]; if (i !== m) { let x_i = nodes[i].x; let y_i = nodes[i].y; + let kmat = km[i]; + let lmat = lm[i]; let denominator = 1.0 / Math.pow(Math.pow(x_m - x_i, 2) + Math.pow(y_m - y_i, 2), 1.5); - d2E_dx2 += this.K_matrix[m][i] * (1 - this.L_matrix[m][i] * Math.pow(y_m - y_i, 2) * denominator); - d2E_dxdy += this.K_matrix[m][i] * (this.L_matrix[m][i] * (x_m - x_i) * (y_m - y_i) * denominator); - d2E_dy2 += this.K_matrix[m][i] * (1 - this.L_matrix[m][i] * Math.pow(x_m - x_i, 2) * denominator); + d2E_dx2 += kmat * (1 - lmat * Math.pow(y_m - y_i, 2) * denominator); + d2E_dxdy += kmat * (lmat * (x_m - x_i) * (y_m - y_i) * denominator); + d2E_dy2 += kmat * (1 - lmat * Math.pow(x_m - x_i, 2) * denominator); } } // make the variable names easier to make the solving of the linear system easier to read @@ -168,6 +159,9 @@ class KamadaKawai { // move the node nodes[m].x += dx; nodes[m].y += dy; + + // Recalculate E_matrix (should be incremental) + this._updateE_matrix(m); } @@ -208,8 +202,82 @@ class KamadaKawai { } } + /** + * Create matrix with all energies between nodes + * @private + */ + _createE_matrix() { + let nodesArray = this.body.nodeIndices; + let nodes = this.body.nodes; + this.E_matrix = {}; + this.E_sums = {}; + for (let mIdx = 0; mIdx < nodesArray.length; mIdx++) { + this.E_matrix[nodesArray[mIdx]] = []; + } + for (let mIdx = 0; mIdx < nodesArray.length; mIdx++) { + let m = nodesArray[mIdx]; + let x_m = nodes[m].x; + let y_m = nodes[m].y; + let dE_dx = 0; + let dE_dy = 0; + for (let iIdx = mIdx; iIdx < nodesArray.length; iIdx++) { + let i = nodesArray[iIdx]; + if (i !== m) { + let x_i = nodes[i].x; + let y_i = nodes[i].y; + let denominator = 1.0 / Math.sqrt(Math.pow(x_m - x_i, 2) + Math.pow(y_m - y_i, 2)); + this.E_matrix[m][iIdx] = [ + this.K_matrix[m][i] * ((x_m - x_i) - this.L_matrix[m][i] * (x_m - x_i) * denominator), + this.K_matrix[m][i] * ((y_m - y_i) - this.L_matrix[m][i] * (y_m - y_i) * denominator) + ]; + this.E_matrix[i][mIdx] = this.E_matrix[m][iIdx]; + dE_dx += this.E_matrix[m][iIdx][0]; + dE_dy += this.E_matrix[m][iIdx][1]; + } + } + //Store sum + this.E_sums[m] = [dE_dx, dE_dy]; + } + } + + //Update method, just doing single column (rows are auto-updated) (update all sums) + _updateE_matrix(m) { + let nodesArray = this.body.nodeIndices; + let nodes = this.body.nodes; + let colm = this.E_matrix[m]; + let kcolm = this.K_matrix[m]; + let lcolm = this.L_matrix[m]; + let x_m = nodes[m].x; + let y_m = nodes[m].y; + let dE_dx = 0; + let dE_dy = 0; + for (let iIdx = 0; iIdx < nodesArray.length; iIdx++) { + let i = nodesArray[iIdx]; + if (i !== m) { + //Keep old energy value for sum modification below + let cell = colm[iIdx]; + let oldDx = cell[0]; + let oldDy = cell[1]; + //Calc new energy: + let x_i = nodes[i].x; + let y_i = nodes[i].y; + let denominator = 1.0 / Math.sqrt(Math.pow(x_m - x_i, 2) + Math.pow(y_m - y_i, 2)); + let dx = kcolm[i] * ((x_m - x_i) - lcolm[i] * (x_m - x_i) * denominator); + let dy = kcolm[i] * ((y_m - y_i) - lcolm[i] * (y_m - y_i) * denominator); + colm[iIdx] = [dx, dy]; + dE_dx += dx; + dE_dy += dy; + //add new energy to sum of each column + let sum = this.E_sums[i]; + sum[0] += (dx-oldDx); + sum[1] += (dy-oldDy); + } + } + //Store sum at -1 index + this.E_sums[m] = [dE_dx, dE_dy]; + } } export default KamadaKawai; \ No newline at end of file diff --git a/lib/network/modules/LayoutEngine.js b/lib/network/modules/LayoutEngine.js index 166781e1..09468955 100644 --- a/lib/network/modules/LayoutEngine.js +++ b/lib/network/modules/LayoutEngine.js @@ -161,9 +161,9 @@ class LayoutEngine { positionInitially(nodesArray) { if (this.options.hierarchical.enabled !== true) { this.randomSeed = this.initialRandomSeed; + let radius = nodesArray.length + 50; for (let i = 0; i < nodesArray.length; i++) { let node = nodesArray[i]; - let radius = 10 * 0.1 * nodesArray.length + 10; let angle = 2 * Math.PI * this.seededRandom(); if (node.x === undefined) { node.x = radius * Math.cos(angle); @@ -196,34 +196,46 @@ class LayoutEngine { if (positionDefined < 0.5 * this.body.nodeIndices.length) { let MAX_LEVELS = 10; let level = 0; - let clusterThreshold = 100; + let clusterThreshold = 150; + //Performance enhancement, during clustering edges need only be simple straight lines. These options don't propagate outside the clustering phase. + let clusterOptions = { + clusterEdgeProperties:{ + smooth: { + enabled: false + } + } + }; + // if there are a lot of nodes, we cluster before we run the algorithm. if (this.body.nodeIndices.length > clusterThreshold) { let startLength = this.body.nodeIndices.length; - while (this.body.nodeIndices.length > clusterThreshold) { + while (this.body.nodeIndices.length > clusterThreshold && level <= MAX_LEVELS) { //console.time("clustering") level += 1; let before = this.body.nodeIndices.length; // if there are many nodes we do a hubsize cluster if (level % 3 === 0) { - this.body.modules.clustering.clusterBridges(); + this.body.modules.clustering.clusterBridges(clusterOptions); } else { - this.body.modules.clustering.clusterOutliers(); + this.body.modules.clustering.clusterOutliers(clusterOptions); } let after = this.body.nodeIndices.length; - if ((before == after && level % 3 !== 0) || level > MAX_LEVELS) { + if (before == after && level % 3 !== 0) { this._declusterAll(); this.body.emitter.emit("_layoutFailed"); console.info("This network could not be positioned by this version of the improved layout algorithm. Please disable improvedLayout for better performance."); return; } //console.timeEnd("clustering") - //console.log(level,after) + //console.log(before,level,after); } // increase the size of the edges this.body.modules.kamadaKawai.setOptions({springLength: Math.max(150, 2 * startLength)}) } + if (level > MAX_LEVELS){ + console.info("The clustering didn't succeed within the amount of interations allowed, progressing with partial result."); + } // position the system for these nodes and edges this.body.modules.kamadaKawai.solve(this.body.nodeIndices, this.body.edgeIndices, true); diff --git a/lib/network/modules/components/algorithms/FloydWarshall.js b/lib/network/modules/components/algorithms/FloydWarshall.js index e0d3f92d..42983755 100644 --- a/lib/network/modules/components/algorithms/FloydWarshall.js +++ b/lib/network/modules/components/algorithms/FloydWarshall.js @@ -4,7 +4,8 @@ class FloydWarshall { - constructor(){} + constructor() { + } getDistances(body, nodesArray, edgesArray) { let D_matrix = {}; @@ -12,11 +13,11 @@ class FloydWarshall { // prepare matrix with large numbers for (let i = 0; i < nodesArray.length; i++) { - D_matrix[nodesArray[i]] = {}; - D_matrix[nodesArray[i]] = {}; + let node = nodesArray[i]; + let cell = {}; + D_matrix[node] = cell; for (let j = 0; j < nodesArray.length; j++) { - D_matrix[nodesArray[i]][nodesArray[j]] = (i == j ? 0 : 1e9); - D_matrix[nodesArray[i]][nodesArray[j]] = (i == j ? 0 : 1e9); + cell[nodesArray[j]] = (i == j ? 0 : 1e9); } } @@ -34,10 +35,18 @@ class FloydWarshall { // Adapted FloydWarshall based on unidirectionality to greatly reduce complexity. for (let k = 0; k < nodeCount; k++) { - for (let i = 0; i < nodeCount-1; i++) { - for (let j = i+1; j < nodeCount; j++) { - D_matrix[nodesArray[i]][nodesArray[j]] = Math.min(D_matrix[nodesArray[i]][nodesArray[j]],D_matrix[nodesArray[i]][nodesArray[k]] + D_matrix[nodesArray[k]][nodesArray[j]]) - D_matrix[nodesArray[j]][nodesArray[i]] = D_matrix[nodesArray[i]][nodesArray[j]]; + let knode = nodesArray[k]; + let kcolm = D_matrix[knode]; + for (let i = 0; i < nodeCount - 1; i++) { + let inode = nodesArray[i]; + let icolm = D_matrix[inode]; + for (let j = i + 1; j < nodeCount; j++) { + let jnode = nodesArray[j]; + let jcolm = D_matrix[jnode]; + + let val = Math.min(icolm[jnode], icolm[knode] + kcolm[jnode]); + icolm[jnode] = val; + jcolm[inode] = val; } } } From fd46ec13c8e4f9791a0b2e2b6cbe080f17d5939b Mon Sep 17 00:00:00 2001 From: Alexander Wunschik Date: Tue, 28 Mar 2017 11:48:16 +0200 Subject: [PATCH 13/21] updated moment.js (#2925) see https://github.com/moment/moment/pull/3853 fixes #2912 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1c1b30e7..2695c51f 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ }, "dependencies": { "emitter-component": "^1.1.1", - "moment": "^2.17.1", + "moment": "^2.18.1", "propagating-hammerjs": "^1.4.6", "hammerjs": "^2.0.8", "keycharm": "^0.2.0" From 23e96c3000f325d75314dc4f34513678f9c72aaa Mon Sep 17 00:00:00 2001 From: Brad Hards Date: Wed, 29 Mar 2017 16:22:30 +1100 Subject: [PATCH 14/21] (timeline docs #2888) Fix error in class names. (#2911) --- docs/timeline/index.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/timeline/index.html b/docs/timeline/index.html index 3bcd44fe..90552d55 100644 --- a/docs/timeline/index.html +++ b/docs/timeline/index.html @@ -1055,11 +1055,11 @@ function (option, path) { This would be used as an additional way to add content that is constant in size with the visible frame of the item and does not get visibly hidden with the item's internal container: vis-item-overflow which is overflow:hidden. - + throttleRedraw number 0 - This option is DEPRICATED and no longer supported. It will be removed in the next MAJOR release. + This option is DEPRECATED and no longer supported. It will be removed in the next MAJOR release. @@ -1991,7 +1991,7 @@ var options = { Weekdayvis-monday, vis-tuesday, vis-wednesday, vis-thursday, vis-friday, vis-saturday, vis-sunday - Daysvis-date1, vis-date2, ..., vis-date31 + Daysvis-day1, vis-day2, ..., vis-day31 Monthsvis-january, vis-february, vis-march, vis-april, vis-may, vis-june, vis-july, vis-august, vis-september, vis-october, vis-november, vis-december From 752da28b3cbaece1d81d4140668a213fe6820816 Mon Sep 17 00:00:00 2001 From: Brad Hards Date: Sat, 1 Apr 2017 00:24:03 +1100 Subject: [PATCH 15/21] Fix typo in PR template. (#2908) --- .github/PULL_REQUEST_TEMPLATE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 1a43ae70..9f9ae19b 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -3,7 +3,7 @@ Please make sure to check the following requirements before creating a pull request: * [ ] All pull requests must be to the [develop branch](https://github.com/almende/vis/tree/develop). Pull requests to the `master` branch will be closed! -* [ ] Make sure your changes are based on the latest version of the [develop branch](https://github.com/almende/vis/tree/develop). (Use e.g. `git fetch && git rebase origin develop` to update you feature branch). +* [ ] Make sure your changes are based on the latest version of the [develop branch](https://github.com/almende/vis/tree/develop). (Use e.g. `git fetch && git rebase origin develop` to update your feature branch). * [ ] Provide an additional or update an example to demonstrate your changes or new features. * [ ] Update the documentation if you introduced new behavior or changed existing behavior. * [ ] Reference issue numbers of issues that your pull request addresses. (If you write something like `fixes #1781` in your git commit message this issue gets closed automatically by merging your pull request). From 3ecc98fffc36dfd2ad49d8d21ad9fcaa0c67b57e Mon Sep 17 00:00:00 2001 From: Brad Hards Date: Sun, 2 Apr 2017 06:33:06 +1000 Subject: [PATCH 16/21] [Timeline fix #2814] Do not corrupt class names at high zoom levels. (#2909) This takes a more robust approach to produce the list of class names. --- lib/timeline/TimeStep.js | 73 ++++++++++++++++++++-------------------- test/TimeStep.test.js | 44 ++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 37 deletions(-) create mode 100644 test/TimeStep.test.js diff --git a/lib/timeline/TimeStep.js b/lib/timeline/TimeStep.js index d0f230f6..e3f6ff7b 100644 --- a/lib/timeline/TimeStep.js +++ b/lib/timeline/TimeStep.js @@ -560,6 +560,7 @@ TimeStep.prototype.getClassName = function() { var m = this.moment(this.current); var current = m.locale ? m.locale('en') : m.lang('en'); // old versions of moment have .lang() function var step = this.step; + var classNames = []; function even(value) { return (value / step % 2 == 0) ? ' vis-even' : ' vis-odd'; @@ -592,51 +593,49 @@ TimeStep.prototype.getClassName = function() { switch (this.scale) { case 'millisecond': - return today(current) + - even(current.milliseconds()).trim(); - + classNames.push(today(current)); + classNames.push(even(current.milliseconds())); + break; case 'second': - return today(current) + - even(current.seconds()).trim(); - + classNames.push(today(current)); + classNames.push(even(current.seconds())); + break; case 'minute': - return today(current) + - even(current.minutes()).trim(); - + classNames.push(today(current)); + classNames.push(even(current.minutes())); + break; case 'hour': - return 'vis-h' + current.hours() + - (this.step == 4 ? '-h' + (current.hours() + 4) : '') + - today(current) + - even(current.hours()); - + classNames.push('vis-h' + current.hours() + this.step == 4 ? '-h' + (current.hours() + 4) : ''); + classNames.push(today(current)); + classNames.push(even(current.hours())); + break; case 'weekday': - return 'vis-' + current.format('dddd').toLowerCase() + - today(current) + - currentWeek(current) + - even(current.date()); - + classNames.push('vis-' + current.format('dddd').toLowerCase()); + classNames.push(today(current)); + classNames.push(currentWeek(current)); + classNames.push(even(current.date())); + break; case 'day': - return 'vis-day' + current.date() + - ' vis-' + current.format('MMMM').toLowerCase() + - today(current) + - currentMonth(current) + - (this.step <= 2 ? today(current) : '') + - (this.step <= 2 ? ' vis-' + current.format('dddd').toLowerCase() : '' + even(current.date() - 1)); - + classNames.push('vis-day' + current.date()); + classNames.push('vis-' + current.format('MMMM').toLowerCase()); + classNames.push(today(current)); + classNames.push(currentMonth(current)); + classNames.push(this.step <= 2 ? today(current) : ''); + classNames.push(this.step <= 2 ? 'vis-' + current.format('dddd').toLowerCase() : ''); + classNames.push(even(current.date() - 1)); + break; case 'month': - return 'vis-' + current.format('MMMM').toLowerCase() + - currentMonth(current) + - even(current.month()); - + classNames.push('vis-' + current.format('MMMM').toLowerCase()); + classNames.push(currentMonth(current)); + classNames.push(even(current.month())); + break; case 'year': - var year = current.year(); - return 'vis-year' + year + - currentYear(current) + - even(year); - - default: - return ''; + classNames.push('vis-year' + current.year()); + classNames.push(currentYear(current)); + classNames.push(even(current.year())); + break; } + return classNames.filter(String).join(" "); }; module.exports = TimeStep; diff --git a/test/TimeStep.test.js b/test/TimeStep.test.js new file mode 100644 index 00000000..4ab3b2ca --- /dev/null +++ b/test/TimeStep.test.js @@ -0,0 +1,44 @@ +var assert = require('assert'); +var vis = require('../dist/vis'); +var jsdom = require('mocha-jsdom') +var moment = vis.moment; +var timeline = vis.timeline; +var TimeStep = timeline.TimeStep; +var TestSupport = require('./TestSupport'); + +describe('TimeStep', function () { + + jsdom(); + + it('should work with just start and end dates', function () { + var timestep = new TimeStep(new Date(2017, 3, 3), new Date(2017, 3, 5)); + assert.equal(timestep.autoScale, true, "should autoscale if scale not specified"); + assert.equal(timestep.scale, "day", "should default to day scale if scale not specified"); + assert.equal(timestep.step, 1, "should default to 1 day step if scale not specified"); + }); + + it('should work with specified scale (just under 1 second)', function () { + var timestep = new TimeStep(new Date(2017, 3, 3), new Date(2017, 3, 5), 999); + assert.equal(timestep.scale, "second", "should have right scale"); + assert.equal(timestep.step, 1, "should have right step size"); + }); + + // TODO: check this - maybe should work for 1000? + it('should work with specified scale (1 second)', function () { + var timestep = new TimeStep(new Date(2017, 3, 3), new Date(2017, 3, 5), 1001); + assert.equal(timestep.scale, "second", "should have right scale"); + assert.equal(timestep.step, 5, "should have right step size"); + }); + + it('should work with specified scale (2 seconds)', function () { + var timestep = new TimeStep(new Date(2017, 3, 3), new Date(2017, 3, 5), 2000); + assert.equal(timestep.scale, "second", "should have right scale"); + assert.equal(timestep.step, 5, "should have right step size"); + }); + + it('should work with specified scale (5 seconds)', function () { + var timestep = new TimeStep(new Date(2017, 3, 3), new Date(2017, 3, 5), 5001); + assert.equal(timestep.scale, "second", "should have right scale"); + assert.equal(timestep.step, 10, "should have right step size"); + }); +}); \ No newline at end of file From 3d1e8a3296293417c8e0a9882908906f01cfe3ca Mon Sep 17 00:00:00 2001 From: yotamberk Date: Sun, 2 Apr 2017 01:30:26 +0300 Subject: [PATCH 17/21] Add callback functions to moveTo, zoomIn, zoomOut and setWindow (#2870) * Fix redraw order * Fix error when option is not defined * Allow template labels * Add .travis.yml file * Add experiment travis code * Fix react example * Add callback functions to moveTo, zoomIn, zoomOut and setWindow --- docs/timeline/index.html | 12 ++++--- lib/timeline/Core.js | 49 ++++++++++++++++++++++------- lib/timeline/Range.js | 68 +++++++++++++++++++++++++++++----------- lib/timeline/Timeline.js | 4 +-- 4 files changed, 98 insertions(+), 35 deletions(-) diff --git a/docs/timeline/index.html b/docs/timeline/index.html index 90552d55..c5ed6640 100644 --- a/docs/timeline/index.html +++ b/docs/timeline/index.html @@ -1308,12 +1308,13 @@ document.getElementById('myTimeline').onclick = function (event) { - moveTo(time [, options]) + moveTo(time [, options, callback]) none Move the window such that given time is centered on screen. Parameter time can be a Date, Number, or String. Available options:
  • animation: boolean or {duration: number, easingFunction: string}
    If true (default) or an Object, the range is animated smoothly to the new window. An object can be provided to specify duration and easing function. Default duration is 500 ms, and default easing function is 'easeInOutQuad'. Available easing functions: "linear", "easeInQuad", "easeOutQuad", "easeInOutQuad", "easeInCubic", "easeOutCubic", "easeInOutCubic", "easeInQuart", "easeOutQuart", "easeInOutQuart", "easeInQuint", "easeOutQuint", "easeInOutQuint".
+ A callback function can be passed as an optional parameter. This function will be called at the end of moveTo function. @@ -1422,12 +1423,13 @@ document.getElementById('myTimeline').onclick = function (event) { - setWindow(start, end [, options]) + setWindow(start, end [, options, callback]) none Set the current visible window. The parameters start and end can be a Date, Number, or String. If the parameter value of start or end is null, the parameter will be left unchanged. Available options:
  • animation: boolean or {duration: number, easingFunction: string}
    If true (default) or an Object, the range is animated smoothly to the new window. An object can be provided to specify duration and easing function. Default duration is 500 ms, and default easing function is 'easeInOutQuad'. Available easing functions: "linear", "easeInQuad", "easeOutQuad", "easeInOutQuad", "easeInCubic", "easeOutCubic", "easeInOutCubic", "easeInQuart", "easeOutQuart", "easeInOutQuart", "easeInQuint", "easeOutQuint", "easeInOutQuint".
+ A callback function can be passed as an optional parameter. This function will be called at the end of setWindow function. @@ -1439,21 +1441,23 @@ document.getElementById('myTimeline').onclick = function (event) { - zoomIn(percentage [, options]) + zoomIn(percentage [, options, callback]) none Zoom in the current visible window. The parameter percentage can be a Number and must be between 0 and 1. If the parameter value of percentage is null, the window will be left unchanged. Available options:
  • animation: boolean or {duration: number, easingFunction: string}
    If true (default) or an Object, the range is animated smoothly to the new window. An object can be provided to specify duration and easing function. Default duration is 500 ms, and default easing function is 'easeInOutQuad'. Available easing functions: "linear", "easeInQuad", "easeOutQuad", "easeInOutQuad", "easeInCubic", "easeOutCubic", "easeInOutCubic", "easeInQuart", "easeOutQuart", "easeInOutQuart", "easeInQuint", "easeOutQuint", "easeInOutQuint".
+ A callback function can be passed as an optional parameter. This function will be called at the end of zoomIn function. - zoomOut(percentage [, options]) + zoomOut(percentage [, options, callback]) none Zoom out the current visible window. The parameter percentage can be a Number and must be between 0 and 1. If the parameter value of percentage is null, the window will be left unchanged. Available options:
  • animation: boolean or {duration: number, easingFunction: string}
    If true (default) or an Object, the range is animated smoothly to the new window. An object can be provided to specify duration and easing function. Default duration is 500 ms, and default easing function is 'easeInOutQuad'. Available easing functions: "linear", "easeInQuad", "easeOutQuad", "easeInOutQuad", "easeInCubic", "easeOutCubic", "easeInOutCubic", "easeInQuart", "easeOutQuart", "easeInOutQuart", "easeInQuint", "easeOutQuint", "easeInOutQuint".
+ A callback function can be passed as an optional parameter. This function will be called at the end of zoomOut function. diff --git a/lib/timeline/Core.js b/lib/timeline/Core.js index 8fbf0e97..4a6533f7 100644 --- a/lib/timeline/Core.js +++ b/lib/timeline/Core.js @@ -613,8 +613,9 @@ Core.prototype.getVisibleItems = function() { * provided to specify duration and easing function. * Default duration is 500 ms, and default easing * function is 'easeInOutQuad'. + * @param {Function} a callback funtion to be executed at the end of this function */ -Core.prototype.fit = function(options) { +Core.prototype.fit = function(options, callback) { var range = this.getDataRange(); // skip range set if there is no min and max date @@ -627,7 +628,7 @@ Core.prototype.fit = function(options) { var min = new Date(range.min.valueOf() - interval * 0.01); var max = new Date(range.max.valueOf() + interval * 0.01); var animation = (options && options.animation !== undefined) ? options.animation : true; - this.range.setRange(min, max, animation); + this.range.setRange(min, max, { animation: animation }, callback); }; /** @@ -660,17 +661,28 @@ Core.prototype.getDataRange = function() { * provided to specify duration and easing function. * Default duration is 500 ms, and default easing * function is 'easeInOutQuad'. + * @param {Function} a callback funtion to be executed at the end of this function */ -Core.prototype.setWindow = function(start, end, options) { +Core.prototype.setWindow = function(start, end, options, callback) { + if (typeof arguments[2] == "function") { + callback = arguments[2] + options = {}; + } var animation; if (arguments.length == 1) { var range = arguments[0]; animation = (range.animation !== undefined) ? range.animation : true; - this.range.setRange(range.start, range.end, animation); + this.range.setRange(range.start, range.end, { animation: animation }); + } + else if (arguments.length == 2 && typeof arguments[1] == "function") { + var range = arguments[0]; + callback = arguments[1]; + animation = (range.animation !== undefined) ? range.animation : true; + this.range.setRange(range.start, range.end, { animation: animation }, callback); } else { animation = (options && options.animation !== undefined) ? options.animation : true; - this.range.setRange(start, end, animation); + this.range.setRange(start, end, { animation: animation }, callback); } }; @@ -684,8 +696,13 @@ Core.prototype.setWindow = function(start, end, options) { * provided to specify duration and easing function. * Default duration is 500 ms, and default easing * function is 'easeInOutQuad'. + * @param {Function} a callback funtion to be executed at the end of this function */ -Core.prototype.moveTo = function(time, options) { +Core.prototype.moveTo = function(time, options, callback) { + if (typeof arguments[1] == "function") { + callback = arguments[1] + options = {}; + } var interval = this.range.end - this.range.start; var t = util.convert(time, 'Date').valueOf(); @@ -693,7 +710,7 @@ Core.prototype.moveTo = function(time, options) { var end = t + interval / 2; var animation = (options && options.animation !== undefined) ? options.animation : true; - this.range.setRange(start, end, animation); + this.range.setRange(start, end, { animation: animation }, callback); }; /** @@ -718,9 +735,14 @@ Core.prototype.getWindow = function() { * provided to specify duration and easing function. * Default duration is 500 ms, and default easing * function is 'easeInOutQuad'. + * @param {Function} a callback funtion to be executed at the end of this function */ -Core.prototype.zoomIn = function(percentage, options) { +Core.prototype.zoomIn = function(percentage, options, callback) { if (!percentage || percentage < 0 || percentage > 1) return + if (typeof arguments[1] == "function") { + callback = arguments[1] + options = {}; + } var range = this.getWindow(); var start = range.start.valueOf(); var end = range.end.valueOf(); @@ -730,7 +752,7 @@ Core.prototype.zoomIn = function(percentage, options) { var newStart = start + distance; var newEnd = end - distance; - this.setWindow(newStart, newEnd, options); + this.setWindow(newStart, newEnd, options, callback); }; /** @@ -743,9 +765,14 @@ Core.prototype.zoomIn = function(percentage, options) { * provided to specify duration and easing function. * Default duration is 500 ms, and default easing * function is 'easeInOutQuad'. + * @param {Function} a callback funtion to be executed at the end of this function */ -Core.prototype.zoomOut = function(percentage, options) { +Core.prototype.zoomOut = function(percentage, options, callback) { if (!percentage || percentage < 0 || percentage > 1) return + if (typeof arguments[1] == "function") { + callback = arguments[1] + options = {}; + } var range = this.getWindow(); var start = range.start.valueOf(); var end = range.end.valueOf(); @@ -753,7 +780,7 @@ Core.prototype.zoomOut = function(percentage, options) { var newStart = start - interval * percentage / 2; var newEnd = end + interval * percentage / 2; - this.setWindow(newStart, newEnd, options); + this.setWindow(newStart, newEnd, options, callback); }; /** diff --git a/lib/timeline/Range.js b/lib/timeline/Range.js index ced39db8..a00435ad 100644 --- a/lib/timeline/Range.js +++ b/lib/timeline/Range.js @@ -138,7 +138,10 @@ Range.prototype.startRolling = function() { var end = t + interval / 2; var animation = (me.options && me.options.animation !== undefined) ? me.options.animation : true; - me.setRange(start, end, false); + var options = { + animation: false + }; + me.setRange(start, end, options); // determine interval to refresh var scale = me.conversion(me.body.domProps.center.width).scale; @@ -169,29 +172,36 @@ Range.prototype.stopRolling = function() { * Set a new start and end range * @param {Date | Number | String} [start] * @param {Date | Number | String} [end] - * @param {boolean | {duration: number, easingFunction: string}} [animation=false] - * If true (default), the range is animated + * @param {Object} options Available options: + * {Boolean | {duration: number, easingFunction: string}} [animation=false] + * If true, the range is animated * smoothly to the new window. An object can be * provided to specify duration and easing function. * Default duration is 500 ms, and default easing * function is 'easeInOutQuad'. - * @param {Boolean} [byUser=false] + * {Boolean} [byUser=false] + * {Event} event Mouse event + * {Function} a callback funtion to be executed at the end of this function * */ -Range.prototype.setRange = function(start, end, animation, byUser, event) { - if (byUser !== true) { - byUser = false; + +Range.prototype.setRange = function(start, end, options, callback) { + if (!options) { + options = {}; + } + if (options.byUser !== true) { + options.byUser = false; } var finalStart = start != undefined ? util.convert(start, 'Date').valueOf() : null; var finalEnd = end != undefined ? util.convert(end, 'Date').valueOf() : null; this._cancelAnimation(); - if (animation) { // true or an Object + if (options.animation) { // true or an Object var me = this; var initStart = this.start; var initEnd = this.end; - var duration = (typeof animation === 'object' && 'duration' in animation) ? animation.duration : 500; - var easingName = (typeof animation === 'object' && 'easingFunction' in animation) ? animation.easingFunction : 'easeInOutQuad'; + var duration = (typeof options.animation === 'object' && 'duration' in options.animation) ? options.animation.duration : 500; + var easingName = (typeof options.animation === 'object' && 'easingFunction' in options.animation) ? options.animation.easingFunction : 'easeInOutQuad'; var easingFunction = util.easingFunctions[easingName]; if (!easingFunction) { throw new Error('Unknown easing function ' + JSON.stringify(easingName) + '. ' + @@ -217,8 +227,8 @@ Range.prototype.setRange = function(start, end, animation, byUser, event) { var params = { start: new Date(me.start), end: new Date(me.end), - byUser:byUser, - event: event + byUser: options.byUser, + event: options.event } if (changed) { @@ -228,6 +238,7 @@ Range.prototype.setRange = function(start, end, animation, byUser, event) { if (done) { if (anyChanged) { me.body.emitter.emit('rangechanged', params); + if (callback) { return callback() } } } else { @@ -247,11 +258,12 @@ Range.prototype.setRange = function(start, end, animation, byUser, event) { var params = { start: new Date(this.start), end: new Date(this.end), - byUser:byUser, - event: event + byUser: options.byUser, + event: options.event }; this.body.emitter.emit('rangechange', params); this.body.emitter.emit('rangechanged', params); + if (callback) { return callback() } } } }; @@ -600,7 +612,12 @@ Range.prototype._onMouseWheel = function(event) { var newStart = this.start - diff; var newEnd = this.end - diff; - this.setRange(newStart, newEnd, false, true, event); + var options = { + animation: false, + byUser: true, + event: event + } + this.setRange(newStart, newEnd, options); } return; } @@ -698,7 +715,12 @@ Range.prototype._onPinch = function (event) { newEnd = safeEnd; } - this.setRange(newStart, newEnd, false, true, event); + var options = { + animation: false, + byUser: true, + event: event + } + this.setRange(newStart, newEnd, options); this.startToFront = false; // revert to default this.endToFront = true; // revert to default @@ -802,7 +824,12 @@ Range.prototype.zoom = function(scale, center, delta, event) { newEnd = safeEnd; } - this.setRange(newStart, newEnd, false, true, event); + var options = { + animation: false, + byUser: true, + event: event + } + this.setRange(newStart, newEnd, options); this.startToFront = false; // revert to default this.endToFront = true; // revert to default @@ -843,7 +870,12 @@ Range.prototype.moveTo = function(moveTo) { var newStart = this.start - diff; var newEnd = this.end - diff; - this.setRange(newStart, newEnd, false, true, null); + var options = { + animation: false, + byUser: true, + event: null + } + this.setRange(newStart, newEnd, options); }; module.exports = Range; diff --git a/lib/timeline/Timeline.js b/lib/timeline/Timeline.js index 6cc4b068..02d633a4 100644 --- a/lib/timeline/Timeline.js +++ b/lib/timeline/Timeline.js @@ -382,7 +382,7 @@ Timeline.prototype.focus = function(id, options) { var interval = Math.max((this.range.end - this.range.start), (end - start) * 1.1); var animation = (options && options.animation !== undefined) ? options.animation : true; - this.range.setRange(middle - interval / 2, middle + interval / 2, animation); + this.range.setRange(middle - interval / 2, middle + interval / 2, { animation: animation }); } }; @@ -409,7 +409,7 @@ Timeline.prototype.fit = function (options) { else { // exactly fit the items (plus a small margin) range = this.getItemRange(); - this.range.setRange(range.min, range.max, animation); + this.range.setRange(range.min, range.max, { animation: animation }); } }; From 1cd3b054b60a8d85eaa44303185883d067932f19 Mon Sep 17 00:00:00 2001 From: Tom Woudenberg Date: Wed, 5 Apr 2017 01:39:03 -0400 Subject: [PATCH 18/21] Rolling mode offset (#2950) --- docs/timeline/index.html | 20 +++++++++++++++++-- .../timeline/interaction/rollingMode.html | 5 ++++- lib/timeline/Range.js | 16 +++++++++------ lib/timeline/optionsTimeline.js | 6 +++++- 4 files changed, 37 insertions(+), 10 deletions(-) diff --git a/docs/timeline/index.html b/docs/timeline/index.html index c5ed6640..4fb318ec 100644 --- a/docs/timeline/index.html +++ b/docs/timeline/index.html @@ -955,13 +955,29 @@ function (option, path) { Orientation of the timeline items: 'top' or 'bottom' (default). Determines whether items are aligned to the top or bottom of the Timeline. - - rollingMode + + rollingMode + Object + Object + Specify how the timeline implements rolling mode. + + + rollingMode.follow boolean false If true, the timeline will initial in a rolling mode - the current time will always be centered. I the user drags the timeline, the timeline will go out of rolling mode and a toggle button will appear. Clicking that button will go back to rolling mode. Zooming in rolling mode will zoom in to the center without consideration of the mouse position. + + rollingMode.offset + Number + '0.5' + + Set how far from the left the rolling mode is implemented from. A percentage (i.e. a decimal between 0 and 1) + Defaults to the middle or 0.5 (50%) + + + rtl boolean diff --git a/examples/timeline/interaction/rollingMode.html b/examples/timeline/interaction/rollingMode.html index ebe3fcfb..199d3e39 100644 --- a/examples/timeline/interaction/rollingMode.html +++ b/examples/timeline/interaction/rollingMode.html @@ -33,7 +33,10 @@ var options = { start: new Date(), end: new Date(new Date().getTime() + 1000000), - rollingMode: true + rollingMode: { + follow: true, + offset: 0.5 + } }; // create a Timeline diff --git a/lib/timeline/Range.js b/lib/timeline/Range.js index a00435ad..f568bc58 100644 --- a/lib/timeline/Range.js +++ b/lib/timeline/Range.js @@ -45,7 +45,11 @@ function Range(body, options) { min: null, max: null, zoomMin: 10, // milliseconds - zoomMax: 1000 * 60 * 60 * 24 * 365 * 10000 // milliseconds + zoomMax: 1000 * 60 * 60 * 24 * 365 * 10000, // milliseconds + rollingMode: { + follow: false, + offset: 0.5 + } }; this.options = util.extend({}, this.defaultOptions); this.props = { @@ -94,11 +98,11 @@ Range.prototype.setOptions = function (options) { // copy the options that we know var fields = [ 'animation', 'direction', 'min', 'max', 'zoomMin', 'zoomMax', 'moveable', 'zoomable', - 'moment', 'activate', 'hiddenDates', 'zoomKey', 'rtl', 'showCurrentTime', 'rollMode', 'horizontalScroll' + 'moment', 'activate', 'hiddenDates', 'zoomKey', 'rtl', 'showCurrentTime', 'rollingMode', 'horizontalScroll' ]; util.selectiveExtend(fields, this.options, options); - if (options.rollingMode) { + if (options.rollingMode && options.rollingMode.follow) { this.startRolling(); } if ('start' in options || 'end' in options) { @@ -134,8 +138,8 @@ Range.prototype.startRolling = function() { var interval = me.end - me.start; var t = util.convert(new Date(), 'Date').valueOf(); - var start = t - interval / 2; - var end = t + interval / 2; + var start = t - interval * (me.options.rollingMode.offset); + var end = t + interval * (1 - me.options.rollingMode.offset); var animation = (me.options && me.options.animation !== undefined) ? me.options.animation : true; var options = { @@ -647,7 +651,7 @@ Range.prototype._onMouseWheel = function(event) { // calculate center, the date to zoom around var pointerDate if (this.rolling) { - pointerDate = (this.start + this.end) / 2; + pointerDate = this.start + ((this.end - this.start) * this.options.rollingMode.offset); } else { var pointer = this.getPointer({x: event.clientX, y: event.clientY}, this.body.dom.center); pointerDate = this._pointerToDate(pointer); diff --git a/lib/timeline/optionsTimeline.js b/lib/timeline/optionsTimeline.js index 6251bcac..41ac8d0b 100644 --- a/lib/timeline/optionsTimeline.js +++ b/lib/timeline/optionsTimeline.js @@ -26,7 +26,11 @@ let allOptions = { //globals : align: {string}, rtl: { 'boolean': bool, 'undefined': 'undefined'}, - rollingMode: { 'boolean': bool, 'undefined': 'undefined'}, + rollingMode: { + follow: { 'boolean': bool }, + offset: {number,'undefined': 'undefined'}, + __type__: {object} + }, verticalScroll: { 'boolean': bool, 'undefined': 'undefined'}, horizontalScroll: { 'boolean': bool, 'undefined': 'undefined'}, autoResize: { 'boolean': bool}, From fc082d3334117ece95ad4cc8f8c027f0d0ca282b Mon Sep 17 00:00:00 2001 From: Steven Jones Date: Wed, 5 Apr 2017 20:45:23 +0100 Subject: [PATCH 19/21] Fixes #2918: Remove usages of elementsCensor. (#2947) --- lib/timeline/component/CustomTime.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/timeline/component/CustomTime.js b/lib/timeline/component/CustomTime.js index 6aa6502a..e3e60dc1 100644 --- a/lib/timeline/component/CustomTime.js +++ b/lib/timeline/component/CustomTime.js @@ -217,7 +217,7 @@ CustomTime.prototype._onDrag = function (event) { this.body.emitter.emit('timechange', { id: this.options.id, time: new Date(this.customTime.valueOf()), - event: util.elementsCensor(event) + event: event }); event.stopPropagation(); @@ -235,7 +235,7 @@ CustomTime.prototype._onDragEnd = function (event) { this.body.emitter.emit('timechanged', { id: this.options.id, time: new Date(this.customTime.valueOf()), - event: util.elementsCensor(event) + event: event }); event.stopPropagation(); From 420f8a3bbf317a10e6c1c78c94b2cf1564b28860 Mon Sep 17 00:00:00 2001 From: wimrijnders Date: Tue, 11 Apr 2017 22:07:16 +0200 Subject: [PATCH 20/21] Fix check for nodes not present in EdgesHandler (#2963) --- lib/network/modules/EdgesHandler.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/network/modules/EdgesHandler.js b/lib/network/modules/EdgesHandler.js index 549cf598..f5afc13b 100644 --- a/lib/network/modules/EdgesHandler.js +++ b/lib/network/modules/EdgesHandler.js @@ -385,8 +385,8 @@ class EdgesHandler { let nodeList = []; if (this.body.edges[edgeId] !== undefined) { let edge = this.body.edges[edgeId]; - if (edge.fromId) {nodeList.push(edge.fromId);} - if (edge.toId) {nodeList.push(edge.toId);} + if (edge.fromId !== undefined) {nodeList.push(edge.fromId);} + if (edge.toId !== undefined) {nodeList.push(edge.toId);} } return nodeList; } From 3d79ad56319d213568a4e654c03e32d06ac2ae24 Mon Sep 17 00:00:00 2001 From: wimrijnders Date: Tue, 11 Apr 2017 22:13:27 +0200 Subject: [PATCH 21/21] Fix #2940 Gephi consolidate double assignment of node title. (#2962) --- lib/network/gephiParser.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/network/gephiParser.js b/lib/network/gephiParser.js index 124d6b45..66da1eb6 100644 --- a/lib/network/gephiParser.js +++ b/lib/network/gephiParser.js @@ -45,11 +45,10 @@ function parseGephi(gephiJSON, optionsObj) { var gNode = gNodes[i]; node['id'] = gNode.id; node['attributes'] = gNode.attributes; - node['title'] = gNode.title; node['x'] = gNode.x; node['y'] = gNode.y; node['label'] = gNode.label; - node['title'] = gNode.attributes !== undefined ? gNode.attributes.title : undefined; + node['title'] = gNode.attributes !== undefined ? gNode.attributes.title : gNode.title; if (options.nodes.parseColor === true) { node['color'] = gNode.color; } @@ -64,4 +63,4 @@ function parseGephi(gephiJSON, optionsObj) { return {nodes:nodes, edges:edges}; } -exports.parseGephi = parseGephi; \ No newline at end of file +exports.parseGephi = parseGephi;