From 1788c67a8925a2495e19f1da5f7f23c5b1b8bf72 Mon Sep 17 00:00:00 2001 From: David Rogers Date: Sun, 4 Nov 2018 10:27:12 -0500 Subject: [PATCH 1/5] Added option hierarchical.layout.userControlsFreeAxis --- docs/network/layout.html | 17 +++++++++++++- lib/network/modules/LayoutEngine.js | 36 ++++++++++++++++++++++++++++- lib/network/options.js | 4 +++- 3 files changed, 54 insertions(+), 3 deletions(-) diff --git a/docs/network/layout.html b/docs/network/layout.html index 2b11a8904..98874895e 100644 --- a/docs/network/layout.html +++ b/docs/network/layout.html @@ -66,7 +66,8 @@

Options

edgeMinimization: true, parentCentralization: true, direction: 'UD', // UD, DU, LR, RL - sortMethod: 'hubsize' // hubsize, directed + sortMethod: 'hubsize', // hubsize, directed + userControlsFreeAxis: false } } } @@ -103,6 +104,20 @@

Options

hierarchical.sortMethodString'hubsize' The algorithm used to ascertain the levels of the nodes based on the data. The possible options are: hubsize, directed.

Hubsize takes the nodes with the most edges and puts them at the top. From that the rest of the hierarchy is evaluated.

Directed adheres to the to and from data of the edges. A --> B so B is a level lower than A. + + hierarchical.userControlsFreeAxis + Boolean + false + Whether or not the value specified by a node's Dataset x or y values will affect a node's layout along its "free" axis.

+ If the hierarchical layout direction is either "DU" or "UD", vis.js will layout the node using the Node's x property, + or if directionis either "LR" or "RL", vis.js will use the node's y property.

+ If the property vis wants to use (e.g., the x or y property on the node within the target Dataset) + is undefined, the default behavior is invoked, as though this option were set to false. This provides the ability to initally utilize Vis's default layout, + then later call Network.storePositions() and have the hierarchical layout engine respect the retreived values.

+ This option is helpful because updating a hierarchically layed-out graph will trigger a redraw, and without this option, if a node has been moved along its free axis, + it will be returned to its default position.

+ This option is useful with physics disabled. + diff --git a/lib/network/modules/LayoutEngine.js b/lib/network/modules/LayoutEngine.js index ad907ebd8..544e74dcc 100644 --- a/lib/network/modules/LayoutEngine.js +++ b/lib/network/modules/LayoutEngine.js @@ -743,11 +743,18 @@ class LayoutEngine { this._determineLevelsCustomCallback(); } } - + + // + // to be iterated over later, in the userControlsFreeAxis option section + // we iterate over nodeIds once to call ensureLevel, so we might as well + // capture them now + // + let nodeIds = []; // fallback for cases where there are nodes but no edges for (let nodeId in this.body.nodes) { if (this.body.nodes.hasOwnProperty(nodeId)) { + nodeIds.push(nodeId); this.hierarchical.ensureLevel(nodeId); } } @@ -765,6 +772,33 @@ class LayoutEngine { // shift to center so gravity does not have to do much this._shiftToCenter(); + + // + // if option hierarchical.userControlsFreeAxis is set (to true) + // we will attempt to set each node's free axis position based on Dataset value + // if the node's free axis position is undefined, we skip it and keep the original LayoutEngine-generated value + // + // the free axis in direction DU/UD is x + // the free axis in direction LR/RL is y + // + if (this.options.hierarchical.userControlsFreeAxis) { + let direction = this.options.hierarchical.direction; + let dataSet = this.body.data.nodes.getDataSet(); + let freeAxis = ""; + if (direction === "UD" || direction === "DU") { + freeAxis = "x"; + } + else { // direction === "LR" || direction == "RL", any other values are caught as errors earlier in the program + freeAxis = "y"; + } + for (let nodeId of nodeIds) { + let targetPosition = dataSet._data[nodeId][freeAxis]; + if (targetPosition !== undefined) { + this.body.nodes[nodeId][freeAxis] = targetPosition; + } + } + } + } } } diff --git a/lib/network/options.js b/lib/network/options.js index ff614a5df..12e0c84f7 100644 --- a/lib/network/options.js +++ b/lib/network/options.js @@ -183,6 +183,7 @@ let allOptions = { parentCentralization: { boolean: bool }, direction: { string: ['UD', 'DU', 'LR', 'RL'] }, // UD, DU, LR, RL sortMethod: { string: ['hubsize', 'directed'] }, // hubsize, directed + userControlsFreeAxis: { boolean: bool }, __type__: { object, boolean: bool } }, __type__: { object } @@ -553,7 +554,8 @@ let configureOptions = { edgeMinimization: true, parentCentralization: true, direction: ['UD', 'DU', 'LR', 'RL'], // UD, DU, LR, RL - sortMethod: ['hubsize', 'directed'] // hubsize, directed + sortMethod: ['hubsize', 'directed'], // hubsize, directed + userControlsFreeAxis: false } }, interaction: { From 8396bdb77498e71322c92b1245eefeda6b304687 Mon Sep 17 00:00:00 2001 From: softwareCobbler Date: Sun, 20 Jan 2019 15:40:32 -0500 Subject: [PATCH 2/5] Update example 'manipulation.html' to allow interaction w/ 'userControlsFreeAxis' feature --- examples/network/other/manipulation.html | 118 ++++++++++++++++++++++- lib/network/Network.js | 15 +++ lib/network/modules/LayoutEngine.js | 17 ++-- 3 files changed, 143 insertions(+), 7 deletions(-) diff --git a/examples/network/other/manipulation.html b/examples/network/other/manipulation.html index f86b6b90c..b1f445f89 100644 --- a/examples/network/other/manipulation.html +++ b/examples/network/other/manipulation.html @@ -64,6 +64,97 @@ // randomly create some nodes and edges var data = getScaleFreeNetwork(25); var seed = 2; + + function bakeLevels() { + let direction = network.layoutEngine.options.hierarchical.direction; + console.log(direction); + let levelAxis = ""; + + // + // direction can only be "UD" | "DU" | "LR" | "RL", other values are caught as errors earlier in the program + // once levels are baked, they should not change even if the user alters the direction of the layout + // + + if (direction == "UD" || direction == "DU") { + levelAxis = "y"; + } + else { + levelAxis = "x"; + } + + let nodes = network.body.nodes; + let nodeIds = Object.getOwnPropertyNames(nodes); + let nodeLevelMap = new Map(); + let uniqueLevelsInCanvasSpace = new Set(); + + for (nodeId of nodeIds) { + node = nodes[nodeId]; + uniqueLevelsInCanvasSpace.add(node[levelAxis]); + if (nodeLevelMap.has(node[levelAxis])) { + nodeLevelMap.get(node[levelAxis]).push(node.id); + } + else { + nodeLevelMap.set(node[levelAxis], [node.id]); + } + } + + orderedUniqueLevelsInCanvasSpace = Array.from(uniqueLevelsInCanvasSpace); + orderedUniqueLevelsInCanvasSpace.sort((a,b) => { return a-b; }); + canvasLevelMap = new Map(); + + for (let i = 0; i < orderedUniqueLevelsInCanvasSpace.length; i++) { + canvasLevelMap.set(orderedUniqueLevelsInCanvasSpace[i], i); + } + + updateList = [] + + for (nodeId of nodeIds) { + node = nodes[nodeId]; + updateList.push({ + "id": nodeId, + "level": canvasLevelMap.get(node[levelAxis]) + }); + } + + console.log(network); + network.body.data.nodes.update(updateList); + + } + + function storeFreeAxisPositions() { + let direction = network.layoutEngine.options.hierarchical.direction; + let freeAxis = ""; + + if (direction == "UD" || direction == "DU") { + freeAxis = "x"; + } + else { + freeAxis = "y"; + } + + let positions = network.getPositions(); + let keys = Object.getOwnPropertyNames(positions); + let updateList = []; + for (key of keys) { + pos = positions[key]; + updateList.push({ + id: key, + [freeAxis]: pos[freeAxis] + }); + } + network.body.data.nodes.update(updateList); + } + + function toggleBakeLevelsButton() { + let target = document.getElementById("bake-levels"); + let isDisabled = target.hasAttribute("disabled"); + if (isDisabled) { + target.removeAttribute("disabled"); + } + else { + target.setAttribute("disabled", "") + } + } function setDefaultLocale() { var defaultLocal = navigator.language; @@ -92,7 +183,10 @@ // create a network var container = document.getElementById('mynetwork'); var options = { - layout: {randomSeed:seed}, // just to make sure the layout is the same when the locale is changed + configure: true, + layout: { + randomSeed:seed + }, // just to make sure the layout is the same when the locale is changed locale: document.getElementById('locale').value, manipulation: { addNode: function (data, callback) { @@ -127,6 +221,21 @@ } }; network = new vis.Network(container, data, options); + network.on("configChange", (e) => { + if (e.layout) { + if (e.layout.hierarchical) { + if (e.layout.hierarchical === true) { + toggleBakeLevelsButton(); + } + if (e.layout.hierarchical.enabled === false) { + toggleBakeLevelsButton(); + } + } + } + }); + network.on("dragEnd", (e) => { + storeFreeAxisPositions(); + }); } function clearPopUp() { @@ -187,6 +296,13 @@

Editing the nodes and edges (localized)


+
diff --git a/lib/network/Network.js b/lib/network/Network.js index f0f3a240c..8943f1b3d 100644 --- a/lib/network/Network.js +++ b/lib/network/Network.js @@ -170,6 +170,21 @@ Network.prototype.setOptions = function (options) { // the hierarchical system can adapt the edges and the physics to it's own options because not all combinations work with the hierarichical system. options = this.layoutEngine.setOptions(options.layout, options); + console.log(this.layoutEngine); + // + // this clears "userControlsFreeAxis" when "hierarchical" is disabled + // without doing this, and performing the following steps: + // enable layout.hierarchical + // enable layout.userControlsFreeAxis + // disable layout.hierarchical + // enable layout.hierarchical + // edges were getting placed into the body.nodes in LayoutEngine::setupHierarchicalLayout + // + // is there a better way to perform this, or better place to put this? It feels like a hack. + // + if (this.layoutEngine.options.hierarchical.enabled == false) { + this.layoutEngine.options.hierarchical["userControlsFreeAxis"] = false; + } this.canvas.setOptions(options); // options for canvas are in globals diff --git a/lib/network/modules/LayoutEngine.js b/lib/network/modules/LayoutEngine.js index 544e74dcc..6eb667a50 100644 --- a/lib/network/modules/LayoutEngine.js +++ b/lib/network/modules/LayoutEngine.js @@ -750,7 +750,6 @@ class LayoutEngine { // capture them now // let nodeIds = []; - // fallback for cases where there are nodes but no edges for (let nodeId in this.body.nodes) { if (this.body.nodes.hasOwnProperty(nodeId)) { @@ -785,17 +784,23 @@ class LayoutEngine { let direction = this.options.hierarchical.direction; let dataSet = this.body.data.nodes.getDataSet(); let freeAxis = ""; - if (direction === "UD" || direction === "DU") { + + if (direction === "UD" || direction === "DU") { freeAxis = "x"; } - else { // direction === "LR" || direction == "RL", any other values are caught as errors earlier in the program + else { // direction == "LR" || direction == "RL", any other values are caught as errors earlier in the program freeAxis = "y"; } - for (let nodeId of nodeIds) { + + for (let nodeId of nodeIds) { + console.log(nodeId); let targetPosition = dataSet._data[nodeId][freeAxis]; if (targetPosition !== undefined) { - this.body.nodes[nodeId][freeAxis] = targetPosition; - } + this.body.nodes[nodeId][freeAxis] = targetPosition; + } + else { // explicitly assign it the freeaxis value as issued by layout engine + dataSet._data[nodeId][freeAxis] = this.body.nodes[nodeId][freeAxis] + } } } From 1ff92b930a7ecac4bdd08f66764c7b05d16b934f Mon Sep 17 00:00:00 2001 From: softwareCobbler Date: Sun, 20 Jan 2019 16:17:05 -0500 Subject: [PATCH 3/5] misc. cleanup --- lib/network/Network.js | 2 +- lib/network/modules/LayoutEngine.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/network/Network.js b/lib/network/Network.js index 8943f1b3d..3b3caed7c 100644 --- a/lib/network/Network.js +++ b/lib/network/Network.js @@ -170,7 +170,7 @@ Network.prototype.setOptions = function (options) { // the hierarchical system can adapt the edges and the physics to it's own options because not all combinations work with the hierarichical system. options = this.layoutEngine.setOptions(options.layout, options); - console.log(this.layoutEngine); + // // this clears "userControlsFreeAxis" when "hierarchical" is disabled // without doing this, and performing the following steps: diff --git a/lib/network/modules/LayoutEngine.js b/lib/network/modules/LayoutEngine.js index 6eb667a50..1f4a2c3dd 100644 --- a/lib/network/modules/LayoutEngine.js +++ b/lib/network/modules/LayoutEngine.js @@ -795,7 +795,7 @@ class LayoutEngine { for (let nodeId of nodeIds) { console.log(nodeId); let targetPosition = dataSet._data[nodeId][freeAxis]; - if (targetPosition !== undefined) { + if (targetPosition != undefined) { this.body.nodes[nodeId][freeAxis] = targetPosition; } else { // explicitly assign it the freeaxis value as issued by layout engine From 4e87c5ae80715403d306e07700d9c13e27a3ec20 Mon Sep 17 00:00:00 2001 From: softwareCobbler Date: Sun, 20 Jan 2019 16:24:35 -0500 Subject: [PATCH 4/5] errant console.log --- lib/network/modules/LayoutEngine.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/network/modules/LayoutEngine.js b/lib/network/modules/LayoutEngine.js index 1f4a2c3dd..17fdc7954 100644 --- a/lib/network/modules/LayoutEngine.js +++ b/lib/network/modules/LayoutEngine.js @@ -793,7 +793,6 @@ class LayoutEngine { } for (let nodeId of nodeIds) { - console.log(nodeId); let targetPosition = dataSet._data[nodeId][freeAxis]; if (targetPosition != undefined) { this.body.nodes[nodeId][freeAxis] = targetPosition; From ce888d3a8999de43d3bba9572b5009fe8a599fc8 Mon Sep 17 00:00:00 2001 From: softwareCobbler Date: Tue, 22 Jan 2019 20:17:03 -0500 Subject: [PATCH 5/5] Clean up variable scopes --- examples/network/other/manipulation.html | 27 +++++++++++------------- lib/network/Network.js | 15 ------------- 2 files changed, 12 insertions(+), 30 deletions(-) diff --git a/examples/network/other/manipulation.html b/examples/network/other/manipulation.html index b1f445f89..5921790a0 100644 --- a/examples/network/other/manipulation.html +++ b/examples/network/other/manipulation.html @@ -67,7 +67,6 @@ function bakeLevels() { let direction = network.layoutEngine.options.hierarchical.direction; - console.log(direction); let levelAxis = ""; // @@ -87,8 +86,8 @@ let nodeLevelMap = new Map(); let uniqueLevelsInCanvasSpace = new Set(); - for (nodeId of nodeIds) { - node = nodes[nodeId]; + for (let nodeId of nodeIds) { + let node = nodes[nodeId]; uniqueLevelsInCanvasSpace.add(node[levelAxis]); if (nodeLevelMap.has(node[levelAxis])) { nodeLevelMap.get(node[levelAxis]).push(node.id); @@ -98,7 +97,7 @@ } } - orderedUniqueLevelsInCanvasSpace = Array.from(uniqueLevelsInCanvasSpace); + let orderedUniqueLevelsInCanvasSpace = Array.from(uniqueLevelsInCanvasSpace); orderedUniqueLevelsInCanvasSpace.sort((a,b) => { return a-b; }); canvasLevelMap = new Map(); @@ -106,17 +105,16 @@ canvasLevelMap.set(orderedUniqueLevelsInCanvasSpace[i], i); } - updateList = [] + let updateList = [] - for (nodeId of nodeIds) { - node = nodes[nodeId]; + for (let nodeId of nodeIds) { + let node = nodes[nodeId]; updateList.push({ "id": nodeId, "level": canvasLevelMap.get(node[levelAxis]) }); } - console.log(network); network.body.data.nodes.update(updateList); } @@ -135,13 +133,15 @@ let positions = network.getPositions(); let keys = Object.getOwnPropertyNames(positions); let updateList = []; - for (key of keys) { - pos = positions[key]; + + for (let key of keys) { + let pos = positions[key]; updateList.push({ id: key, [freeAxis]: pos[freeAxis] }); } + network.body.data.nodes.update(updateList); } @@ -224,10 +224,7 @@ network.on("configChange", (e) => { if (e.layout) { if (e.layout.hierarchical) { - if (e.layout.hierarchical === true) { - toggleBakeLevelsButton(); - } - if (e.layout.hierarchical.enabled === false) { + if (e.layout.hierarchical === true || e.layout.hierarchical.enabled === false) { toggleBakeLevelsButton(); } } @@ -299,7 +296,7 @@

Editing the nodes and edges (localized)

diff --git a/lib/network/Network.js b/lib/network/Network.js index 3b3caed7c..c5ee7226d 100644 --- a/lib/network/Network.js +++ b/lib/network/Network.js @@ -171,21 +171,6 @@ Network.prototype.setOptions = function (options) { // the hierarchical system can adapt the edges and the physics to it's own options because not all combinations work with the hierarichical system. options = this.layoutEngine.setOptions(options.layout, options); - // - // this clears "userControlsFreeAxis" when "hierarchical" is disabled - // without doing this, and performing the following steps: - // enable layout.hierarchical - // enable layout.userControlsFreeAxis - // disable layout.hierarchical - // enable layout.hierarchical - // edges were getting placed into the body.nodes in LayoutEngine::setupHierarchicalLayout - // - // is there a better way to perform this, or better place to put this? It feels like a hack. - // - if (this.layoutEngine.options.hierarchical.enabled == false) { - this.layoutEngine.options.hierarchical["userControlsFreeAxis"] = false; - } - this.canvas.setOptions(options); // options for canvas are in globals // pass the options to the modules