diff --git a/dist/vis.js b/dist/vis.js index 722f07f1..66eeadf7 100644 --- a/dist/vis.js +++ b/dist/vis.js @@ -16193,8 +16193,8 @@ return /******/ (function(modules) { // webpackBootstrap Network.prototype.getScale = function () { return this.view.getScale.apply(this.view, arguments); }; - Network.prototype.getPosition = function () { - return this.view.getPosition.apply(this.view, arguments); + Network.prototype.getViewPosition = function () { + return this.view.getViewPosition.apply(this.view, arguments); }; Network.prototype.fit = function () { return this.view.fit.apply(this.view, arguments); @@ -22575,28 +22575,41 @@ return /******/ (function(modules) { // webpackBootstrap var childNodesObj = {}; var childEdgesObj = {}; var nodeId = this.body.nodeIndices[i]; - if (this.body.nodes[nodeId].edges.length === 1) { + var visibleEdges = 0; + var edge = undefined; + for (var j = 0; j < this.body.nodes[nodeId].edges.length; j++) { + if (this.body.nodes[nodeId].edges[j].options.hidden === false) { + visibleEdges++; + edge = this.body.nodes[nodeId].edges[j]; + } + } + + if (visibleEdges === 1) { // this is an outlier - var edge = this.body.nodes[nodeId].edges[0]; var childNodeId = this._getConnectedId(edge, nodeId); if (childNodeId !== nodeId) { if (options.joinCondition === undefined) { - childEdgesObj[edge.id] = edge; - childNodesObj[nodeId] = this.body.nodes[nodeId]; - childNodesObj[childNodeId] = this.body.nodes[childNodeId]; + if (this._checkIfUsed(clusters, nodeId, edge.id) === false && this._checkIfUsed(clusters, childNodeId, edge.id) === false) { + childEdgesObj[edge.id] = edge; + childNodesObj[nodeId] = this.body.nodes[nodeId]; + childNodesObj[childNodeId] = this.body.nodes[childNodeId]; + } } else { var clonedOptions = this._cloneOptions(this.body.nodes[nodeId]); - if (options.joinCondition(clonedOptions) === true) { + if (options.joinCondition(clonedOptions) === true && this._checkIfUsed(clusters, nodeId, edge.id) === false) { childEdgesObj[edge.id] = edge; childNodesObj[nodeId] = this.body.nodes[nodeId]; } clonedOptions = this._cloneOptions(this.body.nodes[childNodeId]); - if (options.joinCondition(clonedOptions) === true) { + if (options.joinCondition(clonedOptions) === true && this._checkIfUsed(clusters, nodeId, edge.id) === false) { childEdgesObj[edge.id] = edge; childNodesObj[childNodeId] = this.body.nodes[childNodeId]; } } - clusters.push({ nodes: childNodesObj, edges: childEdgesObj }); + + if (Object.keys(childNodesObj).length > 0 && Object.keys(childEdgesObj).length > 0) { + clusters.push({ nodes: childNodesObj, edges: childEdgesObj }); + } } } } @@ -22609,6 +22622,17 @@ return /******/ (function(modules) { // webpackBootstrap this.body.emitter.emit('_dataChanged'); } } + }, { + key: '_checkIfUsed', + value: function _checkIfUsed(clusters, nodeId, edgeId) { + for (var i = 0; i < clusters.length; i++) { + var cluster = clusters[i]; + if (cluster.nodes[nodeId] !== undefined || cluster.edges[edgeId] !== undefined) { + return true; + } + } + return false; + } }, { key: 'clusterByConnection', @@ -22792,12 +22816,6 @@ return /******/ (function(modules) { // webpackBootstrap var clusterNodeProperties = util.deepExtend({}, options.clusterNodeProperties); - // check if we have an unique id; - if (clusterNodeProperties.id === undefined) { - clusterNodeProperties.id = 'cluster:' + util.randomUUID(); - } - var clusterId = clusterNodeProperties.id; - // construct the clusterNodeProperties if (options.processProperties !== undefined) { // get the childNode options @@ -22820,6 +22838,12 @@ return /******/ (function(modules) { // webpackBootstrap } } + // check if we have an unique id; + if (clusterNodeProperties.id === undefined) { + clusterNodeProperties.id = 'cluster:' + util.randomUUID(); + } + var clusterId = clusterNodeProperties.id; + if (clusterNodeProperties.label === undefined) { clusterNodeProperties.label = 'cluster'; } @@ -22988,7 +23012,8 @@ return /******/ (function(modules) { // webpackBootstrap edge.disconnect(); delete this.body.edges[edgeId]; } else { - // one of the nodes connected to this edge is in a cluster. We give the edge to that cluster and make a new temporary edge. + + // one of the nodes connected to this edge is in a cluster. We give the edge to that cluster so it will be released when that cluster is opened. if (this.clusteredNodes[edge.fromId] !== undefined || this.clusteredNodes[edge.toId] !== undefined) { var fromId = undefined, toId = undefined; @@ -22997,22 +23022,25 @@ return /******/ (function(modules) { // webpackBootstrap var _clusterNode = this.body.nodes[clusterId]; _clusterNode.containedEdges[edgeId] = edge; - if (this.clusteredNodes[edge.fromId] !== undefined) { - fromId = clusterId; - toId = edge.toId; - } else { - fromId = edge.fromId; - toId = clusterId; - } + // if both from and to nodes are visible, we create a new temporary edge + if (edge.from.options.hidden !== true && edge.to.options.hidden !== true) { + if (this.clusteredNodes[edge.fromId] !== undefined) { + fromId = clusterId; + toId = edge.toId; + } else { + fromId = edge.fromId; + toId = clusterId; + } - var clonedOptions = this._cloneOptions(edge, 'edge'); - var id = 'clusterEdge:' + util.randomUUID(); - util.deepExtend(clonedOptions, _clusterNode.clusterEdgeProperties); - util.deepExtend(clonedOptions, { from: fromId, to: toId, hidden: false, physics: true, id: id }); - var newEdge = this.body.functions.createEdge(clonedOptions); + var clonedOptions = this._cloneOptions(edge, 'edge'); + var id = 'clusterEdge:' + util.randomUUID(); + util.deepExtend(clonedOptions, _clusterNode.clusterEdgeProperties); + util.deepExtend(clonedOptions, { from: fromId, to: toId, hidden: false, physics: true, id: id }); + var newEdge = this.body.functions.createEdge(clonedOptions); - this.body.edges[id] = newEdge; - this.body.edges[id].connect(); + this.body.edges[id] = newEdge; + this.body.edges[id].connect(); + } } else { edge.options.hidden = false; edge.togglePhysics(true); @@ -24289,8 +24317,8 @@ return /******/ (function(modules) { // webpackBootstrap return this.body.view.scale; } }, { - key: "getPosition", - value: function getPosition() { + key: "getViewPosition", + value: function getViewPosition() { return { x: this.body.view.translation.x, y: this.body.view.translation.y }; } }]); @@ -24756,9 +24784,9 @@ return /******/ (function(modules) { // webpackBootstrap this.body.emitter.emit('_requestRedraw'); if (scaleOld < scale) { - this.body.emitter.emit('zoom', { direction: '+' }); + this.body.emitter.emit('zoom', { direction: '+', scale: this.body.view.scale }); } else { - this.body.emitter.emit('zoom', { direction: '-' }); + this.body.emitter.emit('zoom', { direction: '-', scale: this.body.view.scale }); } } } diff --git a/docs/network/index.html b/docs/network/index.html index 1ea95686..a00277f8 100644 --- a/docs/network/index.html +++ b/docs/network/index.html @@ -466,6 +466,33 @@ var locales = { container with the module name to contain its options. + + on(String event name, Function callback) + + + + Returns: none + Set an event listener. Depending on the type of event you get different parameters for the callback function. Look at the event section of the documentation for more information. + + + + off(String event name, Function callback) + + + + Returns: none + Remove an event listener. The function you supply has to be the exact same as the one you used in the on function. If no function is supplied, all listeners will be removed. Look at the event section of the documentation for more information. + + + + once(String event name, Function callback) + + + + Returns: none + Set an event listener only once. After it has taken place, the event listener will be removed. Depending on the type of event you get different parameters for the callback function. Look at the event section of the documentation for more information. + + @@ -914,11 +941,11 @@ var locales = { - getPosition() + getViewPosition() Returns: Number - Returns the current central focus point of the camera. + Returns the current central focus point of the view. @@ -1255,8 +1282,8 @@ var options = { zoom - {direction:'+'/'-'} - Fired when the user zooms in or out. The properties tell you which direction the zoom is in. + {direction:'+'/'-', scale: Number} + Fired when the user zooms in or out. The properties tell you which direction the zoom is in. The scale is a number greater than 0, which is the same that you get with network.getScale(). showPopup diff --git a/examples/network/categories/events/clickEvents.html b/examples/network/categories/events/interactionEvents.html similarity index 98% rename from examples/network/categories/events/clickEvents.html rename to examples/network/categories/events/interactionEvents.html index 3b276059..a1030f15 100644 --- a/examples/network/categories/events/clickEvents.html +++ b/examples/network/categories/events/interactionEvents.html @@ -1,7 +1,7 @@ - Network | Basic usage + Network | Interaction events diff --git a/examples/network/categories/rest/clustering.html b/examples/network/categories/rest/clustering.html index 0e304245..7c81f4a5 100644 --- a/examples/network/categories/rest/clustering.html +++ b/examples/network/categories/rest/clustering.html @@ -42,12 +42,12 @@ Click any of the buttons below to cluster the network. On every push the network // create an array with nodes var nodes = [ {id: 1, label: 'Node 1', color:'orange'}, - {id: 2, label: 'Node 2', color:'DarkViolet'}, + {id: 2, label: 'Node 2', color:'DarkViolet', font:{color:'white'}}, {id: 3, label: 'Node 3', color:'orange'}, - {id: 4, label: 'Node 4', color:'DarkViolet'}, + {id: 4, label: 'Node 4', color:'DarkViolet', font:{color:'white'}}, {id: 5, label: 'Node 5', color:'orange'}, {id: 6, label: 'cid = 1', cid:1, color:'orange'}, - {id: 7, label: 'cid = 1', cid:1, color:'DarkViolet'}, + {id: 7, label: 'cid = 1', cid:1, color:'DarkViolet', font:{color:'white'}}, {id: 8, label: 'cid = 1', cid:1, color:'lime'}, {id: 9, label: 'cid = 1', cid:1, color:'orange'}, {id: 10, label: 'cid = 1', cid:1, color:'lime'} @@ -72,7 +72,7 @@ Click any of the buttons below to cluster the network. On every push the network nodes: nodes, edges: edges }; - var options = {layout:{randomSeed:8},nodes:{font:{strokeWidth:5}}}; + var options = {layout:{randomSeed:8}}; var network = new vis.Network(container, data, options); network.on("selectNode", function(params) { if (params.nodes.length == 1) { diff --git a/examples/network/categories/rest/clusteringByZoom.html b/examples/network/categories/rest/clusteringByZoom.html new file mode 100644 index 00000000..6a2f1ee7 --- /dev/null +++ b/examples/network/categories/rest/clusteringByZoom.html @@ -0,0 +1,135 @@ + + + + Network | Clustering + + + + + + + + + + + +

+ You can zoom in and out to cluster/decluster. +

+ +
+ + + + + diff --git a/lib/network/Network.js b/lib/network/Network.js index 00adb0e6..f0bae773 100644 --- a/lib/network/Network.js +++ b/lib/network/Network.js @@ -464,7 +464,7 @@ Network.prototype.selectEdges = function() {return this.selectionHandler Network.prototype.unselectAll = function() {return this.selectionHandler.unselectAll.apply(this.selectionHandler,arguments);}; Network.prototype.redraw = function() {return this.renderer.redraw.apply(this.renderer,arguments);}; Network.prototype.getScale = function() {return this.view.getScale.apply(this.view,arguments);}; -Network.prototype.getPosition = function() {return this.view.getPosition.apply(this.view,arguments);}; +Network.prototype.getViewPosition = function() {return this.view.getViewPosition.apply(this.view,arguments);}; Network.prototype.fit = function() {return this.view.fit.apply(this.view,arguments);}; Network.prototype.moveTo = function() {return this.view.moveTo.apply(this.view,arguments);}; Network.prototype.focus = function() {return this.view.focus.apply(this.view,arguments);}; diff --git a/lib/network/modules/Clustering.js b/lib/network/modules/Clustering.js index 031d48db..e68234cc 100644 --- a/lib/network/modules/Clustering.js +++ b/lib/network/modules/Clustering.js @@ -96,29 +96,42 @@ class ClusterEngine { let childNodesObj = {}; let childEdgesObj = {}; let nodeId = this.body.nodeIndices[i]; - if (this.body.nodes[nodeId].edges.length === 1) { + let visibleEdges = 0; + let edge; + for (let j = 0; j < this.body.nodes[nodeId].edges.length; j++) { + if (this.body.nodes[nodeId].edges[j].options.hidden === false) { + visibleEdges++; + edge = this.body.nodes[nodeId].edges[j]; + } + } + + if (visibleEdges === 1) { // this is an outlier - let edge = this.body.nodes[nodeId].edges[0]; let childNodeId = this._getConnectedId(edge, nodeId); if (childNodeId !== nodeId) { if (options.joinCondition === undefined) { - childEdgesObj[edge.id] = edge; - childNodesObj[nodeId] = this.body.nodes[nodeId]; - childNodesObj[childNodeId] = this.body.nodes[childNodeId]; + if (this._checkIfUsed(clusters,nodeId,edge.id) === false && this._checkIfUsed(clusters,childNodeId,edge.id) === false) { + childEdgesObj[edge.id] = edge; + childNodesObj[nodeId] = this.body.nodes[nodeId]; + childNodesObj[childNodeId] = this.body.nodes[childNodeId]; + } } else { let clonedOptions = this._cloneOptions(this.body.nodes[nodeId]); - if (options.joinCondition(clonedOptions) === true) { + if (options.joinCondition(clonedOptions) === true && this._checkIfUsed(clusters,nodeId,edge.id) === false) { childEdgesObj[edge.id] = edge; childNodesObj[nodeId] = this.body.nodes[nodeId]; } clonedOptions = this._cloneOptions(this.body.nodes[childNodeId]); - if (options.joinCondition(clonedOptions) === true) { + if (options.joinCondition(clonedOptions) === true && this._checkIfUsed(clusters,nodeId,edge.id) === false) { childEdgesObj[edge.id] = edge; childNodesObj[childNodeId] = this.body.nodes[childNodeId]; } } - clusters.push({nodes:childNodesObj, edges:childEdgesObj}) + + if (Object.keys(childNodesObj).length > 0 && Object.keys(childEdgesObj).length > 0) { + clusters.push({nodes: childNodesObj, edges: childEdgesObj}) + } } } } @@ -132,6 +145,17 @@ class ClusterEngine { } } + + _checkIfUsed(clusters, nodeId, edgeId) { + for (let i = 0; i < clusters.length; i++) { + let cluster = clusters[i]; + if (cluster.nodes[nodeId] !== undefined || cluster.edges[edgeId] !== undefined) { + return true; + } + } + return false; + } + /** * suck all connected nodes of a node into the node. * @param nodeId @@ -285,10 +309,6 @@ class ClusterEngine { let clusterNodeProperties = util.deepExtend({},options.clusterNodeProperties); - // check if we have an unique id; - if (clusterNodeProperties.id === undefined) {clusterNodeProperties.id = 'cluster:' + util.randomUUID();} - let clusterId = clusterNodeProperties.id; - // construct the clusterNodeProperties if (options.processProperties !== undefined) { // get the childNode options @@ -311,6 +331,10 @@ class ClusterEngine { } } + // check if we have an unique id; + if (clusterNodeProperties.id === undefined) {clusterNodeProperties.id = 'cluster:' + util.randomUUID();} + let clusterId = clusterNodeProperties.id; + if (clusterNodeProperties.label === undefined) { clusterNodeProperties.label = 'cluster'; } @@ -474,7 +498,8 @@ class ClusterEngine { delete this.body.edges[edgeId]; } else { - // one of the nodes connected to this edge is in a cluster. We give the edge to that cluster and make a new temporary edge. + + // one of the nodes connected to this edge is in a cluster. We give the edge to that cluster so it will be released when that cluster is opened. if (this.clusteredNodes[edge.fromId] !== undefined || this.clusteredNodes[edge.toId] !== undefined) { let fromId, toId; let clusteredNode = this.clusteredNodes[edge.fromId] || this.clusteredNodes[edge.toId]; @@ -482,23 +507,26 @@ class ClusterEngine { let clusterNode = this.body.nodes[clusterId]; clusterNode.containedEdges[edgeId] = edge; - if (this.clusteredNodes[edge.fromId] !== undefined) { - fromId = clusterId; - toId = edge.toId; + // if both from and to nodes are visible, we create a new temporary edge + if (edge.from.options.hidden !== true && edge.to.options.hidden !== true) { + if (this.clusteredNodes[edge.fromId] !== undefined) { + fromId = clusterId; + toId = edge.toId; + } + else { + fromId = edge.fromId; + toId = clusterId; + } + + let clonedOptions = this._cloneOptions(edge, 'edge'); + let id = 'clusterEdge:' + util.randomUUID(); + util.deepExtend(clonedOptions, clusterNode.clusterEdgeProperties); + util.deepExtend(clonedOptions, {from: fromId, to: toId, hidden: false, physics: true, id: id}); + let newEdge = this.body.functions.createEdge(clonedOptions); + + this.body.edges[id] = newEdge; + this.body.edges[id].connect(); } - else { - fromId = edge.fromId; - toId = clusterId; - } - - let clonedOptions = this._cloneOptions(edge, 'edge'); - let id = 'clusterEdge:' + util.randomUUID(); - util.deepExtend(clonedOptions, clusterNode.clusterEdgeProperties); - util.deepExtend(clonedOptions, {from:fromId, to:toId, hidden:false, physics:true, id: id}); - let newEdge = this.body.functions.createEdge(clonedOptions); - - this.body.edges[id] = newEdge; - this.body.edges[id].connect(); } else { edge.options.hidden = false; diff --git a/lib/network/modules/InteractionHandler.js b/lib/network/modules/InteractionHandler.js index bf610b14..f79ae7c5 100644 --- a/lib/network/modules/InteractionHandler.js +++ b/lib/network/modules/InteractionHandler.js @@ -407,10 +407,10 @@ class InteractionHandler { this.body.emitter.emit('_requestRedraw'); if (scaleOld < scale) { - this.body.emitter.emit('zoom', {direction: '+'}); + this.body.emitter.emit('zoom', {direction: '+', scale: this.body.view.scale}); } else { - this.body.emitter.emit('zoom', {direction: '-'}); + this.body.emitter.emit('zoom', {direction: '-', scale: this.body.view.scale}); } } } diff --git a/lib/network/modules/View.js b/lib/network/modules/View.js index 68464b40..f7f301c2 100644 --- a/lib/network/modules/View.js +++ b/lib/network/modules/View.js @@ -325,7 +325,7 @@ class View { return this.body.view.scale; } - getPosition() { + getViewPosition() { return {x:this.body.view.translation.x, y:this.body.view.translation.y}; }