Browse Source

fixed bug in clustering! woohoo! added event on off once to docs. more examples

flowchartTest
Alex de Mulder 9 years ago
parent
commit
50f6dbc0e7
9 changed files with 295 additions and 77 deletions
  1. +63
    -35
      dist/vis.js
  2. +31
    -4
      docs/network/index.html
  3. +1
    -1
      examples/network/categories/events/interactionEvents.html
  4. +4
    -4
      examples/network/categories/rest/clustering.html
  5. +135
    -0
      examples/network/categories/rest/clusteringByZoom.html
  6. +1
    -1
      lib/network/Network.js
  7. +57
    -29
      lib/network/modules/Clustering.js
  8. +2
    -2
      lib/network/modules/InteractionHandler.js
  9. +1
    -1
      lib/network/modules/View.js

+ 63
- 35
dist/vis.js View File

@ -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 });
}
}
}

+ 31
- 4
docs/network/index.html View File

@ -466,6 +466,33 @@ var locales = {
container with the module name to contain its options.
</td>
</tr>
<tr class="collapsible toggle" onclick="toggleTable('methodTable','onEvent', this);">
<td colspan="2"><span parent="onEvent" class="right-caret"></span> on(<code>String event name, Function callback</code>)
</td>
</tr>
<tr class="hidden" parent="onEvent">
<td class="midMethods">Returns: none</td>
<td>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.
</td>
</tr>
<tr class="collapsible toggle" onclick="toggleTable('methodTable','offEvent', this);">
<td colspan="2"><span parent="offEvent" class="right-caret"></span> off(<code>String event name, Function callback</code>)
</td>
</tr>
<tr class="hidden" parent="offEvent">
<td class="midMethods">Returns: none</td>
<td>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.
</td>
</tr>
<tr class="collapsible toggle" onclick="toggleTable('methodTable','onceEvent', this);">
<td colspan="2"><span parent="onceEvent" class="right-caret"></span> once(<code>String event name, Function callback</code>)
</td>
</tr>
<tr class="hidden" parent="onceEvent">
<td class="midMethods">Returns: none</td>
<td>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.
</td>
</tr>
<tr class="subHeader">
@ -914,11 +941,11 @@ var locales = {
</td>
</tr>
<tr class="collapsible toggle" onclick="toggleTable('methodTable','clusterByHubsize', this);">
<td colspan="2"><span parent="clusterByHubsize" class="right-caret"></span> getPosition()</td>
<td colspan="2"><span parent="clusterByHubsize" class="right-caret"></span> getViewPosition()</td>
</tr>
<tr class="hidden" parent="clusterByHubsize">
<td class="midMethods">Returns: Number</td>
<td>Returns the current central focus point of the camera.</td>
<td>Returns the current central focus point of the view.</td>
</tr>
<tr class="collapsible toggle" onclick="toggleTable('methodTable','fit', this);">
@ -1255,8 +1282,8 @@ var options = {
</tr>
<tr class="">
<td>zoom</td>
<td><code>{direction:'+'/'-'}</code></td>
<td>Fired when the user zooms in or out. The properties tell you which direction the zoom is in.</td>
<td><code>{direction:'+'/'-', scale: Number}</code></td>
<td>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().</td>
</tr>
<tr class="">
<td>showPopup</td>

examples/network/categories/events/clickEvents.html → examples/network/categories/events/interactionEvents.html View File

@ -1,7 +1,7 @@
<!doctype html>
<html>
<head>
<title>Network | Basic usage</title>
<title>Network | Interaction events</title>
<script type="text/javascript" src="../../../../dist/vis.js"></script>
<link href="../../../../dist/vis.css" rel="stylesheet" type="text/css"/>

+ 4
- 4
examples/network/categories/rest/clustering.html View File

@ -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) {

+ 135
- 0
examples/network/categories/rest/clusteringByZoom.html View File

@ -0,0 +1,135 @@
<!doctype html>
<html>
<head>
<title>Network | Clustering</title>
<script type="text/javascript" src="../../../../dist/vis.js"></script>
<link href="../../../../dist/vis.css" rel="stylesheet" type="text/css"/>
<style type="text/css">
#mynetwork {
width: 600px;
height: 600px;
border: 1px solid lightgray;
}
p {
max-width: 600px;
}
h4 {
margin-bottom: 3px;
}
</style>
<script src="../../../googleAnalytics.js"></script>
</head>
<body>
<p>
You can zoom in and out to cluster/decluster.
</p>
<div id="mynetwork"></div>
<script type="text/javascript">
var clusterIndex = 0;
var clusters = [];
var lastClusterZoomLevel = 0;
var clusterFactor = 0.9;
// create an array with nodes
var nodes = [
{id: 1, label: 'Node 1'},
{id: 2, label: 'Node 2'},
{id: 3, label: 'Node 3'},
{id: 4, label: 'Node 4'},
{id: 5, label: 'Node 5'},
{id: 6, label: 'Node 6'},
{id: 7, label: 'Node 7'},
{id: 8, label: 'Node 8'},
{id: 9, label: 'Node 9'},
{id: 10, label: 'Node 10'}
];
// create an array with edges
var edges = [
{from: 1, to: 2},
{from: 1, to: 3},
{from: 10, to: 4},
{from: 2, to: 5},
{from: 6, to: 2},
{from: 7, to: 5},
{from: 8, to: 6},
{from: 9, to: 7},
{from: 10, to: 9}
];
// create a network
var container = document.getElementById('mynetwork');
var data = {
nodes: nodes,
edges: edges
};
var options = {layout: {randomSeed: 8}};
var network = new vis.Network(container, data, options);
// set the first initial zoom level
network.once('initRedraw', function() {
if (lastClusterZoomLevel === 0) {
lastClusterZoomLevel = network.getScale();
}
});
network.on('zoom', function (params) {
if (params.direction == '-') {
if (params.scale < lastClusterZoomLevel*clusterFactor) {
makeClusters(params.scale);
lastClusterZoomLevel = params.scale;
}
}
else {
openClusters(params.scale);
}
});
network.on("selectNode", function (params) {
if (params.nodes.length == 1) {
if (network.isCluster(params.nodes[0]) == true) {
network.openCluster(params.nodes[0])
}
}
});
function makeClusters(scale) {
var clusterOptionsByData = {
processProperties: function (clusterOptions) {
clusterIndex = clusterIndex + 1;
clusterOptions.label = clusterIndex;
clusterOptions.id = 'cluster:' + clusterIndex;
clusters.push({id:'cluster:' + clusterIndex, scale:scale});
return clusterOptions;
},
clusterNodeProperties: {borderWidth: 3, shape: 'box', font: {size: 30}}
}
network.clusterOutliers(clusterOptionsByData);
}
function openClusters(scale) {
var newClusters = [];
for (var i = 0; i < clusters.length; i++) {
if (clusters[i].scale < scale) {
network.openCluster(clusters[i].id);
lastClusterZoomLevel = scale;
}
else {
newClusters.push(clusters[i])
}
}
clusters = newClusters;
}
</script>
</body>
</html>

+ 1
- 1
lib/network/Network.js View File

@ -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);};

+ 57
- 29
lib/network/modules/Clustering.js View File

@ -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;

+ 2
- 2
lib/network/modules/InteractionHandler.js View File

@ -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});
}
}
}

+ 1
- 1
lib/network/modules/View.js View File

@ -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};
}

Loading…
Cancel
Save