Browse Source

- Added getConnectedNodes method.

- Added fontSizeMin, fontSizeMax, fontSizeMaxVisible, scaleFontWithValue, fontDrawThreshold to Nodes.
- Added fade in of labels (on nodes) near the fontDrawThreshold.
- Added nodes option to zoomExtent to zoom in on specific set of nodes.
- Added stabilizationIterationsDone event which fires at the end of the internal stabilization run. Does not imply that the network is stabilized.
- Added setFreezeSimulation method.
- Added clusterByZoom option and increaseClusterLevel and decreaseClusterLevel methods.

Still clustering bug and need more documentation on new features
v3_develop
Alex de Mulder 9 years ago
parent
commit
2c28c42371
13 changed files with 14878 additions and 14317 deletions
  1. +8
    -0
      HISTORY.md
  2. +14286
    -14160
      dist/vis.js
  3. +10
    -2
      docs/network.html
  4. +1
    -0
      examples/network/25_physics_configuration.html
  5. +1
    -1
      examples/network/29_neighbourhood_highlight.html
  6. +24
    -16
      lib/network/Edge.js
  7. +124
    -26
      lib/network/Network.js
  8. +43
    -17
      lib/network/Node.js
  9. +61
    -91
      lib/network/mixins/ClusterMixin.js
  10. +1
    -1
      lib/network/mixins/HierarchicalLayoutMixin.js
  11. +6
    -3
      lib/network/mixins/physics/PhysicsMixin.js
  12. +22
    -0
      lib/util.js
  13. +291
    -0
      test/EU/js/init_precalc.js

+ 8
- 0
HISTORY.md View File

@ -8,6 +8,14 @@ http://visjs.org
- Added option bindToWindow (default true) to choose whether the keyboard binds are global or to the network div. - Added option bindToWindow (default true) to choose whether the keyboard binds are global or to the network div.
- Improved images handling so broken images are shown on all references of images that are broken. - Improved images handling so broken images are shown on all references of images that are broken.
- Added getConnectedNodes method.
- Added fontSizeMin, fontSizeMax, fontSizeMaxVisible, scaleFontWithValue, fontDrawThreshold to Nodes.
- Added fade in of labels (on nodes) near the fontDrawThreshold.
- Added nodes option to zoomExtent to zoom in on specific set of nodes.
- Added stabilizationIterationsDone event which fires at the end of the internal stabilization run. Does not imply that the network is stabilized.
- Added setFreezeSimulation method.
- Added clusterByZoom option and increaseClusterLevel and decreaseClusterLevel methods.
### DataSet/DataView ### DataSet/DataView

+ 14286
- 14160
dist/vis.js
File diff suppressed because it is too large
View File


+ 10
- 2
docs/network.html View File

@ -2209,7 +2209,7 @@ var options = {
</td> </td>
</tr> </tr>
<tr> <tr>
<td>getBoundingBox()</td>
<td>getBoundingBox(nodeId)</td>
<td>Object</td> <td>Object</td>
<td>Returns a bounding box for the node including label in the format: {top:Number,left:Number,right:Number,bottom:Number}. These values are in canvas space. <td>Returns a bounding box for the node including label in the format: {top:Number,left:Number,right:Number,bottom:Number}. These values are in canvas space.
</td> </td>
@ -2329,6 +2329,12 @@ var options = {
nodes with id 3 and 5. The highlisghEdges boolean can be used to automatically select the edges connected to the node. nodes with id 3 and 5. The highlisghEdges boolean can be used to automatically select the edges connected to the node.
</td> </td>
</tr> </tr>
<tr>
<td>getConnectedNodes(nodeId)</td>
<td>Array</td>
<td>Get an array of (unique) nodeIds that are directly connected to this node.
</td>
</tr>
<tr> <tr>
<td>selectEdges(selection)</td> <td>selectEdges(selection)</td>
<td>none</td> <td>none</td>
@ -2348,7 +2354,7 @@ var options = {
or in percentages.</td> or in percentages.</td>
</tr> </tr>
<tr> <tr>
<td>getPositions([ids])</td>
<td>getPositions([nodeIds])</td>
<td>Object</td> <td>Object</td>
<td>This will return an object of all nodes' positions. Data can be accessed with object[nodeId].x and .y. You can optionally supply an id as string or number or an array of ids. If no id or array of ids have been supplied, all positions are returned. <td>This will return an object of all nodes' positions. Data can be accessed with object[nodeId].x and .y. You can optionally supply an id as string or number or an array of ids. If no id or array of ids have been supplied, all positions are returned.
</td> </td>
@ -2370,6 +2376,7 @@ var options = {
options can just be a boolean. When true, the zoom is animated, when false there is no animation. options can just be a boolean. When true, the zoom is animated, when false there is no animation.
Alternatively, you can supply an object. Alternatively, you can supply an object.
<br /><br /> The object can consist of:<br /> <br /><br /> The object can consist of:<br />
<b><code>nodes: [nodeIds]</code></b><br /> - an optional subset of nodes to zoom in on,<br />
<b><code>duration: Number</code></b><br /> - the duration of the animation in milliseconds,<br /> <b><code>duration: Number</code></b><br /> - the duration of the animation in milliseconds,<br />
<b><code>easingFunction: String</code></b><br /> - the easing function of the animation, available are:<br /> <b><code>easingFunction: String</code></b><br /> - the easing function of the animation, available are:<br />
<code>linear, easeInQuad, easeOutQuad, easeInOutQuad, easeInCubic, easeOutCubic, easeInOutCubic, <code>linear, easeInQuad, easeOutQuad, easeInOutQuad, easeInCubic, easeOutCubic, easeInOutCubic,
@ -2378,6 +2385,7 @@ var options = {
</td> </td>
</tr> </tr>
</table> </table>
<h2 id="Events">Events</h2> <h2 id="Events">Events</h2>

+ 1
- 0
examples/network/25_physics_configuration.html View File

@ -78,6 +78,7 @@
}; };
var options = { var options = {
edges:{opacity:0.2},
stabilize: false, stabilize: false,
configurePhysics:true configurePhysics:true
}; };

+ 1
- 1
examples/network/29_neighbourhood_highlight.html View File

@ -10047,7 +10047,7 @@ function redrawAll() {
}; };
network = new vis.Network(container, data, options); network = new vis.Network(container, data, options);
network.on("click",onClick);
// network.on("click",onClick);
} }

+ 24
- 16
lib/network/Edge.js View File

@ -40,6 +40,7 @@ function Edge (properties, network, networkConstants) {
this.hover = false; this.hover = false;
this.labelDimensions = {top:0,left:0,width:0,height:0,yLine:0}; // could be cached this.labelDimensions = {top:0,left:0,width:0,height:0,yLine:0}; // could be cached
this.dirtyLabel = true; this.dirtyLabel = true;
this.colorDirty = true;
this.from = null; // a node this.from = null; // a node
this.to = null; // a node this.to = null; // a node
@ -71,12 +72,13 @@ function Edge (properties, network, networkConstants) {
* @param {Object} constants and object with default, global properties * @param {Object} constants and object with default, global properties
*/ */
Edge.prototype.setProperties = function(properties) { Edge.prototype.setProperties = function(properties) {
this.colorDirty = true;
if (!properties) { if (!properties) {
return; return;
} }
var fields = ['style','fontSize','fontFace','fontColor','fontFill','fontStrokeWidth','fontStrokeColor','width', var fields = ['style','fontSize','fontFace','fontColor','fontFill','fontStrokeWidth','fontStrokeColor','width',
'widthSelectionMultiplier','hoverWidth','arrowScaleFactor','dash','inheritColor','labelAlignment'
'widthSelectionMultiplier','hoverWidth','arrowScaleFactor','dash','inheritColor','labelAlignment', 'opacity'
]; ];
util.selectiveDeepExtend(fields, this.options, properties); util.selectiveDeepExtend(fields, this.options, properties);
@ -103,7 +105,9 @@ Edge.prototype.setProperties = function(properties) {
} }
} }
// A node is connected when it has a from and to node.
// A node is connected when it has a from and to node.
this.connect(); this.connect();
this.widthFixed = this.widthFixed || (properties.width !== undefined); this.widthFixed = this.widthFixed || (properties.width !== undefined);
@ -119,9 +123,9 @@ Edge.prototype.setProperties = function(properties) {
case 'dash-line': this.draw = this._drawDashLine; break; case 'dash-line': this.draw = this._drawDashLine; break;
default: this.draw = this._drawLine; break; default: this.draw = this._drawLine; break;
} }
}; };
/** /**
* Connect an edge to its nodes * Connect an edge to its nodes
*/ */
@ -230,19 +234,23 @@ Edge.prototype.isOverlappingWith = function(obj) {
Edge.prototype._getColor = function() { Edge.prototype._getColor = function() {
var colorObj = this.options.color; var colorObj = this.options.color;
if (this.options.inheritColor == "to") {
colorObj = {
highlight: this.to.options.color.highlight.border,
hover: this.to.options.color.hover.border,
color: this.to.options.color.border
};
}
else if (this.options.inheritColor == "from" || this.options.inheritColor == true) {
colorObj = {
highlight: this.from.options.color.highlight.border,
hover: this.from.options.color.hover.border,
color: this.from.options.color.border
};
if (this.colorDirty === true) {
if (this.options.inheritColor == "to") {
colorObj = {
highlight: this.to.options.color.highlight.border,
hover: this.to.options.color.hover.border,
color: util.overrideOpacity(this.from.options.color.border, this.options.opacity)
};
}
else if (this.options.inheritColor == "from" || this.options.inheritColor == true) {
colorObj = {
highlight: this.from.options.color.highlight.border,
hover: this.from.options.color.hover.border,
color: util.overrideOpacity(this.from.options.color.border, this.options.opacity)
};
}
this.options.color = colorObj;
this.colorDirty = false;
} }
if (this.selected == true) {return colorObj.highlight;} if (this.selected == true) {return colorObj.highlight;}

+ 124
- 26
lib/network/Network.js View File

@ -69,7 +69,12 @@ function Network (container, data, options) {
fontFace: 'verdana', fontFace: 'verdana',
fontFill: undefined, fontFill: undefined,
fontStrokeWidth: 0, // px fontStrokeWidth: 0, // px
fontStrokeColor: 'white',
fontStrokeColor: '#ffffff',
fontDrawThreshold: 3,
scaleFontWithValue: false,
fontSizeMin: 14,
fontSizeMax: 30,
fontSizeMaxVisible: 30,
level: -1, level: -1,
color: { color: {
border: '#2B7CE9', border: '#2B7CE9',
@ -99,6 +104,7 @@ function Network (container, data, options) {
highlight:'#848484', highlight:'#848484',
hover: '#848484' hover: '#848484'
}, },
opacity:1.0,
fontColor: '#343434', fontColor: '#343434',
fontSize: 14, // px fontSize: 14, // px
fontFace: 'arial', fontFace: 'arial',
@ -163,8 +169,8 @@ function Network (container, data, options) {
height: 1, // (px PNiC) | growth of the height per node in cluster. height: 1, // (px PNiC) | growth of the height per node in cluster.
radius: 1}, // (px PNiC) | growth of the radius per node in cluster. radius: 1}, // (px PNiC) | growth of the radius per node in cluster.
maxNodeSizeIncrements: 600, // (# increments) | max growth of the width per node in cluster. maxNodeSizeIncrements: 600, // (# increments) | max growth of the width per node in cluster.
activeAreaBoxSize: 80, // (px) | box area around the curser where clusters are popped open.
clusterLevelDifference: 2,
activeAreaBoxSize: 80, // (px) | box area around the curser where clusters are popped open.
clusterLevelDifference: 2, // used for normalization of the cluster levels
clusterByZoom: true // enable clustering through zooming in and out clusterByZoom: true // enable clustering through zooming in and out
}, },
navigation: { navigation: {
@ -193,7 +199,7 @@ function Network (container, data, options) {
type: "continuous", type: "continuous",
roundness: 0.5 roundness: 0.5
}, },
maxVelocity: 30,
maxVelocity: 50,
minVelocity: 0.1, // px/s minVelocity: 0.1, // px/s
stabilize: true, // stabilize before displaying the network stabilize: true, // stabilize before displaying the network
stabilizationIterations: 1000, // maximum number of iteration to stabilize stabilizationIterations: 1000, // maximum number of iteration to stabilize
@ -346,7 +352,7 @@ function Network (container, data, options) {
else { else {
// zoom so all data will fit on the screen, if clustering is enabled, we do not want start to be called here. // zoom so all data will fit on the screen, if clustering is enabled, we do not want start to be called here.
if (this.constants.stabilize == false) { if (this.constants.stabilize == false) {
this.zoomExtent(undefined, true,this.constants.clustering.enabled);
this.zoomExtent({duration:0}, true, this.constants.clustering.enabled);
} }
} }
@ -406,17 +412,45 @@ Network.prototype._getScriptPath = function() {
* Find the center position of the network * Find the center position of the network
* @private * @private
*/ */
Network.prototype._getRange = function() {
Network.prototype._getRange = function(specificNodes) {
var minY = 1e9, maxY = -1e9, minX = 1e9, maxX = -1e9, node; var minY = 1e9, maxY = -1e9, minX = 1e9, maxX = -1e9, node;
for (var nodeId in this.nodes) {
if (this.nodes.hasOwnProperty(nodeId)) {
node = this.nodes[nodeId];
if (minX > (node.boundingBox.left)) {minX = node.boundingBox.left;}
if (maxX < (node.boundingBox.right)) {maxX = node.boundingBox.right;}
if (minY > (node.boundingBox.bottom)) {minY = node.boundingBox.top;} // top is negative, bottom is positive
if (maxY < (node.boundingBox.top)) {maxY = node.boundingBox.bottom;} // top is negative, bottom is positive
if (specificNodes.length > 0) {
for (var i = 0; i < specificNodes.length; i++) {
node = this.nodes[specificNodes[i]];
if (minX > (node.boundingBox.left)) {
minX = node.boundingBox.left;
}
if (maxX < (node.boundingBox.right)) {
maxX = node.boundingBox.right;
}
if (minY > (node.boundingBox.bottom)) {
minY = node.boundingBox.top;
} // top is negative, bottom is positive
if (maxY < (node.boundingBox.top)) {
maxY = node.boundingBox.bottom;
} // top is negative, bottom is positive
}
}
else {
for (var nodeId in this.nodes) {
if (this.nodes.hasOwnProperty(nodeId)) {
node = this.nodes[nodeId];
if (minX > (node.boundingBox.left)) {
minX = node.boundingBox.left;
}
if (maxX < (node.boundingBox.right)) {
maxX = node.boundingBox.right;
}
if (minY > (node.boundingBox.bottom)) {
minY = node.boundingBox.top;
} // top is negative, bottom is positive
if (maxY < (node.boundingBox.top)) {
maxY = node.boundingBox.bottom;
} // top is negative, bottom is positive
}
} }
} }
if (minX == 1e9 && maxX == -1e9 && minY == 1e9 && maxY == -1e9) { if (minX == 1e9 && maxX == -1e9 && minY == 1e9 && maxY == -1e9) {
minY = 0, maxY = 0, minX = 0, maxX = 0; minY = 0, maxY = 0, minX = 0, maxX = 0;
} }
@ -441,17 +475,37 @@ Network.prototype._findCenter = function(range) {
* @param {Boolean} [initialZoom] | zoom based on fitted formula or range, true = fitted, default = false; * @param {Boolean} [initialZoom] | zoom based on fitted formula or range, true = fitted, default = false;
* @param {Boolean} [disableStart] | If true, start is not called. * @param {Boolean} [disableStart] | If true, start is not called.
*/ */
Network.prototype.zoomExtent = function(animationOptions, initialZoom, disableStart) {
Network.prototype.zoomExtent = function(options, initialZoom, disableStart) {
this._redraw(true); this._redraw(true);
if (initialZoom === undefined) {initialZoom = false;} if (initialZoom === undefined) {initialZoom = false;}
if (disableStart === undefined) {disableStart = false;} if (disableStart === undefined) {disableStart = false;}
if (animationOptions === undefined) {animationOptions = false;}
if (options === undefined) {options = {nodes:[]};}
if (options.nodes === undefined) {
options.nodes = [];
}
var range = this._getRange();
var range;
var zoomLevel; var zoomLevel;
if (initialZoom == true) { if (initialZoom == true) {
// check if more than half of the nodes have a predefined position. If so, we use the range, not the approximation.
var positionDefined = 0;
for (var nodeId in this.nodes) {
if (this.nodes.hasOwnProperty(nodeId)) {
var node = this.nodes[nodeId];
if (node.predefinedPosition == true) {
positionDefined += 1;
}
}
}
if (positionDefined > 0.5 * this.nodeIndices.length) {
this.zoomExtent(options,false,disableStart);
return;
}
range = this._getRange(options.nodes);
var numberOfNodes = this.nodeIndices.length; var numberOfNodes = this.nodeIndices.length;
if (this.constants.smoothCurves == true) { if (this.constants.smoothCurves == true) {
if (this.constants.clustering.enabled == true && if (this.constants.clustering.enabled == true &&
@ -477,6 +531,7 @@ Network.prototype.zoomExtent = function(animationOptions, initialZoom, disableSt
zoomLevel *= factor; zoomLevel *= factor;
} }
else { else {
range = this._getRange(options.nodes);
var xDistance = Math.abs(range.maxX - range.minX) * 1.1; var xDistance = Math.abs(range.maxX - range.minX) * 1.1;
var yDistance = Math.abs(range.maxY - range.minY) * 1.1; var yDistance = Math.abs(range.maxY - range.minY) * 1.1;
@ -492,7 +547,7 @@ Network.prototype.zoomExtent = function(animationOptions, initialZoom, disableSt
var center = this._findCenter(range); var center = this._findCenter(range);
if (disableStart == false) { if (disableStart == false) {
var options = {position: center, scale: zoomLevel, animation: animationOptions};
var options = {position: center, scale: zoomLevel, animation: options};
this.moveTo(options); this.moveTo(options);
this.moving = true; this.moving = true;
this.start(); this.start();
@ -581,7 +636,7 @@ Network.prototype.setData = function(data, disableStart) {
} }
else { else {
// find a stable position or start animating to a stable position // find a stable position or start animating to a stable position
if (this.constants.stabilize) {
if (this.constants.stabilize == true) {
this._stabilize(); this._stabilize();
} }
} }
@ -732,7 +787,7 @@ Network.prototype.setOptions = function (options) {
// bind keys. If disabled, this will not do anything; // bind keys. If disabled, this will not do anything;
this._createKeyBinds(); this._createKeyBinds();
this._markAllEdgesAsDirty();
this.setSize(this.constants.width, this.constants.height); this.setSize(this.constants.width, this.constants.height);
this.moving = true; this.moving = true;
this.start(); this.start();
@ -1599,8 +1654,16 @@ Network.prototype._updateNodes = function(ids,changedData) {
} }
this._updateNodeIndexList(); this._updateNodeIndexList();
this._updateValueRange(nodes); this._updateValueRange(nodes);
this._markAllEdgesAsDirty();
}; };
Network.prototype._markAllEdgesAsDirty = function() {
for (var edgeId in this.edges) {
this.edges[edgeId].colorDirty = true;
}
}
/** /**
* Remove existing nodes. If nodes do not exist, the method will just ignore it. * Remove existing nodes. If nodes do not exist, the method will just ignore it.
* @param {Number[] | String[]} ids * @param {Number[] | String[]} ids
@ -2093,16 +2156,22 @@ Network.prototype._stabilize = function() {
var count = 0; var count = 0;
while (this.moving && count < this.constants.stabilizationIterations) { while (this.moving && count < this.constants.stabilizationIterations) {
this._physicsTick(); this._physicsTick();
if (count % 100 == 0) {
console.log("stabilizationIterations",count);
}
count++; count++;
} }
if (this.constants.zoomExtentOnStabilize == true) { if (this.constants.zoomExtentOnStabilize == true) {
this.zoomExtent(undefined, false, true);
this.zoomExtent({duration:0}, false, true);
} }
if (this.constants.freezeForStabilization == true) { if (this.constants.freezeForStabilization == true) {
this._restoreFrozenNodes(); this._restoreFrozenNodes();
} }
this.emit("stabilizationIterationsDone");
}; };
/** /**
@ -2152,8 +2221,10 @@ Network.prototype._restoreFrozenNodes = function() {
Network.prototype._isMoving = function(vmin) { Network.prototype._isMoving = function(vmin) {
var nodes = this.nodes; var nodes = this.nodes;
for (var id in nodes) { for (var id in nodes) {
if (nodes.hasOwnProperty(id) && nodes[id].isMoving(vmin)) {
return true;
if (nodes[id] !== undefined) {
if (nodes[id].isMoving(vmin) == true) {
return true;
}
} }
} }
return false; return false;
@ -2236,11 +2307,12 @@ Network.prototype._physicsTick = function() {
} }
// gather movement data from all sectors, if one moves, we are NOT stabilzied // gather movement data from all sectors, if one moves, we are NOT stabilzied
for (var i = 0; i < mainMoving.length; i++) {mainMovingStatus = mainMoving[0] || mainMovingStatus;}
for (var i = 0; i < mainMoving.length; i++) {
mainMovingStatus = mainMoving[i] || mainMovingStatus;
}
// determine if the network has stabilzied // determine if the network has stabilzied
this.moving = mainMovingStatus || supportMovingStatus; this.moving = mainMovingStatus || supportMovingStatus;
if (this.moving == false) { if (this.moving == false) {
this._revertPhysicsTick(); this._revertPhysicsTick();
} }
@ -2363,12 +2435,14 @@ Network.prototype._handleNavigation = function() {
/** /**
* Freeze the _animationStep * Freeze the _animationStep
*/ */
Network.prototype.toggleFreeze = function() {
if (this.freezeSimulation == false) {
Network.prototype.setFreezeSimulation = function(freeze) {
if (freeze == true) {
this.freezeSimulation = true; this.freezeSimulation = true;
this.moving = false;
} }
else { else {
this.freezeSimulation = false; this.freezeSimulation = false;
this.moving = true;
this.start(); this.start();
} }
}; };
@ -2741,4 +2815,28 @@ Network.prototype.getBoundingBox = function(nodeId) {
} }
} }
Network.prototype.getConnectedNodes = function(nodeId) {
var nodeList = [];
if (this.nodes[nodeId] !== undefined) {
var node = this.nodes[nodeId];
var nodeObj = {nodeId : true}; // used to quickly check if node already exists
for (var i = 0; i < node.edges.length; i++) {
var edge = node.edges[i];
if (edge.toId == nodeId) {
if (nodeObj[edge.fromId] === undefined) {
nodeList.push(edge.fromId);
nodeObj[edge.fromId] = true;
}
}
else if (edge.fromId == nodeId) {
if (nodeObj[edge.toId] === undefined) {
nodeList.push(edge.toId)
nodeObj[edge.toId] = true;
}
}
}
}
return nodeList;
}
module.exports = Network; module.exports = Network;

+ 43
- 17
lib/network/Node.js View File

@ -36,8 +36,6 @@ function Node(properties, imagelist, grouplist, networkConstants) {
this.dynamicEdges = []; this.dynamicEdges = [];
this.reroutedEdges = {}; this.reroutedEdges = {};
this.fontDrawThreshold = 3;
// set defaults for the properties // set defaults for the properties
this.id = undefined; this.id = undefined;
this.allowedToMoveX = false; this.allowedToMoveX = false;
@ -64,6 +62,7 @@ function Node(properties, imagelist, grouplist, networkConstants) {
this.vy = 0.0; // velocity y this.vy = 0.0; // velocity y
this.x = null; this.x = null;
this.y = null; this.y = null;
this.predefinedPosition = false; // used to check if initial zoomExtent should just take the range or approximate
// used for reverting to previous position on stabilization // used for reverting to previous position on stabilization
this.previousState = {vx:0,vy:0,x:0,y:0}; this.previousState = {vx:0,vy:0,x:0,y:0};
@ -75,7 +74,6 @@ function Node(properties, imagelist, grouplist, networkConstants) {
// creating the variables for clustering // creating the variables for clustering
this.resetCluster(); this.resetCluster();
this.dynamicEdgesLength = 0;
this.clusterSession = 0; this.clusterSession = 0;
this.clusterSizeWidthFactor = networkConstants.clustering.nodeScaling.width; this.clusterSizeWidthFactor = networkConstants.clustering.nodeScaling.width;
this.clusterSizeHeightFactor = networkConstants.clustering.nodeScaling.height; this.clusterSizeHeightFactor = networkConstants.clustering.nodeScaling.height;
@ -126,7 +124,6 @@ Node.prototype.attachEdge = function(edge) {
if (this.dynamicEdges.indexOf(edge) == -1) { if (this.dynamicEdges.indexOf(edge) == -1) {
this.dynamicEdges.push(edge); this.dynamicEdges.push(edge);
} }
this.dynamicEdgesLength = this.dynamicEdges.length;
}; };
/** /**
@ -142,7 +139,6 @@ Node.prototype.detachEdge = function(edge) {
if (index != -1) { if (index != -1) {
this.dynamicEdges.splice(index, 1); this.dynamicEdges.splice(index, 1);
} }
this.dynamicEdgesLength = this.dynamicEdges.length;
}; };
@ -157,7 +153,8 @@ Node.prototype.setProperties = function(properties, constants) {
} }
var fields = ['borderWidth','borderWidthSelected','shape','image','brokenImage','radius','fontColor', var fields = ['borderWidth','borderWidthSelected','shape','image','brokenImage','radius','fontColor',
'fontSize','fontFace','fontFill','fontStrokeWidth','fontStrokeColor','group','mass'
'fontSize','fontFace','fontFill','fontStrokeWidth','fontStrokeColor','group','mass','fontDrawThreshold',
'scaleFontWithValue','fontSizeMaxVisible'
]; ];
util.selectiveDeepExtend(fields, this.options, properties); util.selectiveDeepExtend(fields, this.options, properties);
@ -165,8 +162,8 @@ Node.prototype.setProperties = function(properties, constants) {
if (properties.id !== undefined) {this.id = properties.id;} if (properties.id !== undefined) {this.id = properties.id;}
if (properties.label !== undefined) {this.label = properties.label; this.originalLabel = properties.label;} if (properties.label !== undefined) {this.label = properties.label; this.originalLabel = properties.label;}
if (properties.title !== undefined) {this.title = properties.title;} if (properties.title !== undefined) {this.title = properties.title;}
if (properties.x !== undefined) {this.x = properties.x;}
if (properties.y !== undefined) {this.y = properties.y;}
if (properties.x !== undefined) {this.x = properties.x; this.predefinedPosition = true;}
if (properties.y !== undefined) {this.y = properties.y; this.predefinedPosition = true;}
if (properties.value !== undefined) {this.value = properties.value;} if (properties.value !== undefined) {this.value = properties.value;}
if (properties.level !== undefined) {this.level = properties.level; this.preassignedLevel = true;} if (properties.level !== undefined) {this.level = properties.level; this.preassignedLevel = true;}
@ -486,12 +483,20 @@ Node.prototype.setValueRange = function(min, max) {
if (!this.radiusFixed && this.value !== undefined) { if (!this.radiusFixed && this.value !== undefined) {
if (max == min) { if (max == min) {
this.options.radius= (this.options.radiusMin + this.options.radiusMax) / 2; this.options.radius= (this.options.radiusMin + this.options.radiusMax) / 2;
if (this.options.scaleFontWithValue == true) {
this.options.fontSize = (this.options.fontSizeMin + this.options.fontSizeMax) / 2;
}
} }
else { else {
var scale = (this.options.radiusMax - this.options.radiusMin) / (max - min); var scale = (this.options.radiusMax - this.options.radiusMin) / (max - min);
this.options.radius= (this.value - min) * scale + this.options.radiusMin;
var fontScale = (this.options.fontSizeMax - this.options.fontSizeMin) / (max - min);
if (this.options.scaleFontWithValue == true) {
this.options.fontSize = Math.max(this.options.fontSizeMin ,(this.value - min) * fontScale + this.options.fontSizeMin);
}
this.options.radius= Math.max(this.options.radiusMin,(this.value - min) * scale + this.options.radiusMin); // max function is protection against value = 0
} }
} }
this.baseRadiusValue = this.options.radius; this.baseRadiusValue = this.options.radius;
}; };
@ -1015,12 +1020,29 @@ Node.prototype._drawText = function (ctx) {
Node.prototype._label = function (ctx, text, x, y, align, baseline, labelUnderNode) { Node.prototype._label = function (ctx, text, x, y, align, baseline, labelUnderNode) {
if (text && Number(this.options.fontSize) * this.networkScale > this.fontDrawThreshold) {
ctx.font = (this.selected ? "bold " : "") + this.options.fontSize + "px " + this.options.fontFace;
var relativeFontSize = Number(this.options.fontSize) * this.networkScale;
if (text && relativeFontSize >= this.options.fontDrawThreshold - 1) {
var fontSize = Number(this.options.fontSize);
// this ensures that there will not be HUGE letters on screen by setting an upper limit on the visible text size (regardless of zoomLevel)
if (relativeFontSize >= this.options.fontSizeMaxVisible) {
fontSize = Number(this.options.fontSizeMaxVisible) * this.networkScaleInv;
}
// fade in when relative scale is between threshold and threshold - 1
var fontColor = this.options.fontColor || "#000000";
var strokecolor = this.options.fontStrokeColor;
if (relativeFontSize <= this.options.fontDrawThreshold) {
var opacity = Math.max(0,Math.min(1,1 - (this.options.fontDrawThreshold - relativeFontSize)));
fontColor = util.overrideOpacity(fontColor, opacity);
strokecolor = util.overrideOpacity(strokecolor, opacity);
}
ctx.font = (this.selected ? "bold " : "") + fontSize + "px " + this.options.fontFace;
var lines = text.split('\n'); var lines = text.split('\n');
var lineCount = lines.length; var lineCount = lines.length;
var fontSize = Number(this.options.fontSize);
var yLine = y + (1 - lineCount) / 2 * fontSize; var yLine = y + (1 - lineCount) / 2 * fontSize;
if (labelUnderNode == true) { if (labelUnderNode == true) {
yLine = y + (1 - lineCount) / (2 * fontSize); yLine = y + (1 - lineCount) / (2 * fontSize);
@ -1032,7 +1054,7 @@ Node.prototype._label = function (ctx, text, x, y, align, baseline, labelUnderNo
var lineWidth = ctx.measureText(lines[i]).width; var lineWidth = ctx.measureText(lines[i]).width;
width = lineWidth > width ? lineWidth : width; width = lineWidth > width ? lineWidth : width;
} }
var height = this.options.fontSize * lineCount;
var height = fontSize * lineCount;
var left = x - width / 2; var left = x - width / 2;
var top = y - height / 2; var top = y - height / 2;
if (baseline == "hanging") { if (baseline == "hanging") {
@ -1049,12 +1071,12 @@ Node.prototype._label = function (ctx, text, x, y, align, baseline, labelUnderNo
} }
// draw text // draw text
ctx.fillStyle = this.options.fontColor || "black";
ctx.fillStyle = fontColor;
ctx.textAlign = align || "center"; ctx.textAlign = align || "center";
ctx.textBaseline = baseline || "middle"; ctx.textBaseline = baseline || "middle";
if (this.options.fontStrokeWidth > 0){ if (this.options.fontStrokeWidth > 0){
ctx.lineWidth = this.options.fontStrokeWidth; ctx.lineWidth = this.options.fontStrokeWidth;
ctx.strokeStyle = this.options.fontStrokeColor;
ctx.strokeStyle = strokecolor;
ctx.lineJoin = 'round'; ctx.lineJoin = 'round';
} }
for (var i = 0; i < lineCount; i++) { for (var i = 0; i < lineCount; i++) {
@ -1070,10 +1092,14 @@ Node.prototype._label = function (ctx, text, x, y, align, baseline, labelUnderNo
Node.prototype.getTextSize = function(ctx) { Node.prototype.getTextSize = function(ctx) {
if (this.label !== undefined) { if (this.label !== undefined) {
ctx.font = (this.selected ? "bold " : "") + this.options.fontSize + "px " + this.options.fontFace;
var fontSize = Number(this.options.fontSize);
if (fontSize * this.networkScale > this.options.fontSizeMaxVisible) {
fontSize = Number(this.options.fontSizeMaxVisible) * this.networkScaleInv;
}
ctx.font = (this.selected ? "bold " : "") + fontSize + "px " + this.options.fontFace;
var lines = this.label.split('\n'), var lines = this.label.split('\n'),
height = (Number(this.options.fontSize) + 4) * lines.length,
height = (fontSize + 4) * lines.length,
width = 0; width = 0;
for (var i = 0, iMax = lines.length; i < iMax; i++) { for (var i = 0, iMax = lines.length; i < iMax; i++) {

+ 61
- 91
lib/network/mixins/ClusterMixin.js View File

@ -17,7 +17,7 @@ exports.startWithClustering = function() {
// this is called here because if clusterin is disabled, the start and stabilize are called in // this is called here because if clusterin is disabled, the start and stabilize are called in
// the setData function. // the setData function.
if (this.stabilize) {
if (this.constants.stabilize == true) {
this._stabilize(); this._stabilize();
} }
this.start(); this.start();
@ -32,24 +32,22 @@ exports.startWithClustering = function() {
exports.clusterToFit = function(maxNumberOfNodes, reposition) { exports.clusterToFit = function(maxNumberOfNodes, reposition) {
var numberOfNodes = this.nodeIndices.length; var numberOfNodes = this.nodeIndices.length;
var maxLevels = 2;
var maxLevels = 50;
var level = 0; var level = 0;
// we first cluster the hubs, then we pull in the outliers, repeat // we first cluster the hubs, then we pull in the outliers, repeat
while (numberOfNodes > maxNumberOfNodes && level < maxLevels) { while (numberOfNodes > maxNumberOfNodes && level < maxLevels) {
console.log("Performing clustering:", level, numberOfNodes, this.clusterSession);
if (level % 3 == 0.0) { if (level % 3 == 0.0) {
//this.forceAggregateHubs(true);
//this.normalizeClusterLevels();
this.forceAggregateHubs(true);
this.normalizeClusterLevels();
} }
else { else {
//this.increaseClusterLevel(); // this also includes a cluster normalization
this.increaseClusterLevel(); // this also includes a cluster normalization
} }
//this.forceAggregateHubs(true);
this.forceAggregateHubs(true);
numberOfNodes = this.nodeIndices.length; numberOfNodes = this.nodeIndices.length;
level += 1; level += 1;
} }
console.log("finished")
// after the clustering we reposition the nodes to reduce the initial chaos // after the clustering we reposition the nodes to reduce the initial chaos
if (level > 0 && reposition == true) { if (level > 0 && reposition == true) {
@ -59,7 +57,7 @@ exports.clusterToFit = function(maxNumberOfNodes, reposition) {
}; };
/** /**
* This function can be called to open up a specific cluster. It is only called by
* This function can be called to open up a specific cluster.
* It will unpack the cluster back one level. * It will unpack the cluster back one level.
* *
* @param node | Node object: cluster to open. * @param node | Node object: cluster to open.
@ -82,9 +80,8 @@ exports.openCluster = function(node) {
else { else {
this._expandClusterNode(node,false,true); this._expandClusterNode(node,false,true);
// update the index list, dynamic edges and labels
// update the index list and labels
this._updateNodeIndexList(); this._updateNodeIndexList();
this._updateDynamicEdges();
this._updateCalculationNodes(); this._updateCalculationNodes();
this.updateLabels(); this.updateLabels();
} }
@ -142,18 +139,21 @@ exports.updateClusters = function(zoomDirection,recursive,force,doNotStart) {
var isMovingBeforeClustering = this.moving; var isMovingBeforeClustering = this.moving;
var amountOfNodes = this.nodeIndices.length; var amountOfNodes = this.nodeIndices.length;
var detectedZoomingIn = (this.previousScale < this.scale && zoomDirection == 0);
var detectedZoomingOut = (this.previousScale > this.scale && zoomDirection == 0);
// on zoom out collapse the sector if the scale is at the level the sector was made // on zoom out collapse the sector if the scale is at the level the sector was made
if (this.previousScale > this.scale && zoomDirection == 0) {
if (detectedZoomingOut == true) {
this._collapseSector(); this._collapseSector();
} }
// check if we zoom in or out // check if we zoom in or out
if (this.previousScale > this.scale || zoomDirection == -1) { // zoom out
if (detectedZoomingOut == true || zoomDirection == -1) { // zoom out
// forming clusters when forced pulls outliers in. When not forced, the edge length of the // forming clusters when forced pulls outliers in. When not forced, the edge length of the
// outer nodes determines if it is being clustered // outer nodes determines if it is being clustered
this._formClusters(force); this._formClusters(force);
} }
else if (this.previousScale < this.scale || zoomDirection == 1) { // zoom in
else if (detectedZoomingIn == true || zoomDirection == 1) { // zoom in
if (force == true) { if (force == true) {
// _openClusters checks for each node if the formationScale of the cluster is smaller than // _openClusters checks for each node if the formationScale of the cluster is smaller than
// the current scale and if so, declusters. When forced, all clusters are reduced by one step // the current scale and if so, declusters. When forced, all clusters are reduced by one step
@ -161,27 +161,27 @@ exports.updateClusters = function(zoomDirection,recursive,force,doNotStart) {
} }
else { else {
// if a cluster takes up a set percentage of the active window // if a cluster takes up a set percentage of the active window
this._openClustersBySize();
//this._openClustersBySize();
this._openClusters(recursive, false);
} }
} }
this._updateNodeIndexList(); this._updateNodeIndexList();
// if a cluster was NOT formed and the user zoomed out, we try clustering by hubs // if a cluster was NOT formed and the user zoomed out, we try clustering by hubs
if (this.nodeIndices.length == amountOfNodes && (this.previousScale > this.scale || zoomDirection == -1)) {
if (this.nodeIndices.length == amountOfNodes && (detectedZoomingOut == true || zoomDirection == -1)) {
this._aggregateHubs(force); this._aggregateHubs(force);
this._updateNodeIndexList(); this._updateNodeIndexList();
} }
// we now reduce chains. // we now reduce chains.
if (this.previousScale > this.scale || zoomDirection == -1) { // zoom out
if (detectedZoomingOut == true || zoomDirection == -1) { // zoom out
this.handleChains(); this.handleChains();
this._updateNodeIndexList(); this._updateNodeIndexList();
} }
this.previousScale = this.scale; this.previousScale = this.scale;
// rest of the update the index list, dynamic edges and labels
this._updateDynamicEdges();
// update labels
this.updateLabels(); this.updateLabels();
// if a cluster was formed, we increase the clusterSession // if a cluster was formed, we increase the clusterSession
@ -237,10 +237,10 @@ exports.forceAggregateHubs = function(doNotStart) {
// update the index list, dynamic edges and labels // update the index list, dynamic edges and labels
this._updateNodeIndexList(); this._updateNodeIndexList();
this._updateCalculationNodes();
this._updateDynamicEdges();
this.updateLabels(); this.updateLabels();
this._updateCalculationNodes();
// if a cluster was formed, we increase the clusterSession // if a cluster was formed, we increase the clusterSession
if (this.nodeIndices.length != amountOfNodes) { if (this.nodeIndices.length != amountOfNodes) {
this.clusterSession += 1; this.clusterSession += 1;
@ -260,13 +260,15 @@ exports.forceAggregateHubs = function(doNotStart) {
* @private * @private
*/ */
exports._openClustersBySize = function() { exports._openClustersBySize = function() {
for (var nodeId in this.nodes) {
if (this.nodes.hasOwnProperty(nodeId)) {
var node = this.nodes[nodeId];
if (node.inView() == true) {
if ((node.width*this.scale > this.constants.clustering.screenSizeThreshold * this.frame.canvas.clientWidth) ||
(node.height*this.scale > this.constants.clustering.screenSizeThreshold * this.frame.canvas.clientHeight)) {
this.openCluster(node);
if (this.constants.clustering.clusterByZoom == true) {
for (var nodeId in this.nodes) {
if (this.nodes.hasOwnProperty(nodeId)) {
var node = this.nodes[nodeId];
if (node.inView() == true) {
if ((node.width * this.scale > this.constants.clustering.screenSizeThreshold * this.frame.canvas.clientWidth) ||
(node.height * this.scale > this.constants.clustering.screenSizeThreshold * this.frame.canvas.clientHeight)) {
this.openCluster(node);
}
} }
} }
} }
@ -302,12 +304,12 @@ exports._openClusters = function(recursive,force) {
exports._expandClusterNode = function(parentNode, recursive, force, openAll) { exports._expandClusterNode = function(parentNode, recursive, force, openAll) {
// first check if node is a cluster // first check if node is a cluster
if (parentNode.clusterSize > 1) { if (parentNode.clusterSize > 1) {
// this means that on a double tap event or a zoom event, the cluster fully unpacks if it is smaller than 20
if (parentNode.clusterSize < this.constants.clustering.sectorThreshold) {
openAll = true;
if (openAll === undefined) {
openAll = false;
} }
recursive = openAll ? true : recursive;
// this means that on a double tap event or a zoom event, the cluster fully unpacks if it is smaller than 20
recursive = openAll || recursive;
// if the last child has been added on a smaller scale than current scale decluster // if the last child has been added on a smaller scale than current scale decluster
if (parentNode.formationScale < this.scale || force == true) { if (parentNode.formationScale < this.scale || force == true) {
// we will check if any of the contained child nodes should be removed from the cluster // we will check if any of the contained child nodes should be removed from the cluster
@ -373,7 +375,6 @@ exports._expelChildFromParent = function(parentNode, containedNodeId, recursive,
parentNode.options.mass -= childNode.options.mass; parentNode.options.mass -= childNode.options.mass;
parentNode.clusterSize -= childNode.clusterSize; parentNode.clusterSize -= childNode.clusterSize;
parentNode.options.fontSize = Math.min(this.constants.clustering.maxFontSize, this.constants.nodes.fontSize + this.constants.clustering.fontSizeMultiplier*(parentNode.clusterSize-1)); parentNode.options.fontSize = Math.min(this.constants.clustering.maxFontSize, this.constants.nodes.fontSize + this.constants.clustering.fontSizeMultiplier*(parentNode.clusterSize-1));
parentNode.dynamicEdgesLength = parentNode.dynamicEdges.length;
// place the child node near the parent, not at the exact same location to avoid chaos in the system // place the child node near the parent, not at the exact same location to avoid chaos in the system
childNode.x = parentNode.x + parentNode.growthIndicator * (0.5 - Math.random()); childNode.x = parentNode.x + parentNode.growthIndicator * (0.5 - Math.random());
@ -441,7 +442,9 @@ exports._repositionBezierNodes = function(node) {
*/ */
exports._formClusters = function(force) { exports._formClusters = function(force) {
if (force == false) { if (force == false) {
this._formClustersByZoom();
if (this.constants.clustering.clusterByZoom == true) {
this._formClustersByZoom();
}
} }
else { else {
this._forceClustersByZoom(); this._forceClustersByZoom();
@ -455,8 +458,8 @@ exports._formClusters = function(force) {
* @private * @private
*/ */
exports._formClustersByZoom = function() { exports._formClustersByZoom = function() {
var dx,dy,length,
minLength = this.constants.clustering.clusterEdgeThreshold/this.scale;
var dx,dy,length;
var minLength = this.constants.clustering.clusterEdgeThreshold/this.scale;
// check if any edges are shorter than minLength and start the clustering // check if any edges are shorter than minLength and start the clustering
// the clustering favours the node with the larger mass // the clustering favours the node with the larger mass
@ -479,10 +482,10 @@ exports._formClustersByZoom = function() {
childNode = edge.from; childNode = edge.from;
} }
if (childNode.dynamicEdgesLength == 1) {
if (childNode.dynamicEdges.length == 1) {
this._addToCluster(parentNode,childNode,false); this._addToCluster(parentNode,childNode,false);
} }
else if (parentNode.dynamicEdgesLength == 1) {
else if (parentNode.dynamicEdges.length == 1) {
this._addToCluster(childNode,parentNode,false); this._addToCluster(childNode,parentNode,false);
} }
} }
@ -505,8 +508,7 @@ exports._forceClustersByZoom = function() {
var childNode = this.nodes[nodeId]; var childNode = this.nodes[nodeId];
// the edges can be swallowed by another decrease // the edges can be swallowed by another decrease
if (childNode.dynamicEdgesLength == 1 && childNode.dynamicEdges.length != 0) {
if (childNode.dynamicEdges.length == 1) {
var edge = childNode.dynamicEdges[0]; var edge = childNode.dynamicEdges[0];
var parentNode = (edge.toId == childNode.id) ? this.nodes[edge.fromId] : this.nodes[edge.toId]; var parentNode = (edge.toId == childNode.id) ? this.nodes[edge.fromId] : this.nodes[edge.toId];
// group to the largest node // group to the largest node
@ -588,14 +590,13 @@ exports._formClusterFromHub = function(hubNode, force, onlyEqual, absorptionSize
if (absorptionSizeOffset === undefined) { if (absorptionSizeOffset === undefined) {
absorptionSizeOffset = 0; absorptionSizeOffset = 0;
} }
if (hubNode.dynamicEdgesLength < 0) {
console.error(hubNode.dynamicEdgesLength, this.hubThreshold, onlyEqual)
}
//this.hubThreshold = 43
//if (hubNode.dynamicEdgesLength < 0) {
// console.error(hubNode.dynamicEdgesLength, this.hubThreshold, onlyEqual)
//}
// we decide if the node is a hub // we decide if the node is a hub
if ((hubNode.dynamicEdgesLength >= this.hubThreshold && onlyEqual == false) ||
(hubNode.dynamicEdgesLength == this.hubThreshold && onlyEqual == true)) {
if ((hubNode.dynamicEdges.length >= this.hubThreshold && onlyEqual == false) ||
(hubNode.dynamicEdges.length == this.hubThreshold && onlyEqual == true)) {
// initialize variables // initialize variables
var dx,dy,length; var dx,dy,length;
var minLength = this.constants.clustering.clusterEdgeThreshold/this.scale; var minLength = this.constants.clustering.clusterEdgeThreshold/this.scale;
@ -651,8 +652,13 @@ exports._formClusterFromHub = function(hubNode, force, onlyEqual, absorptionSize
if ((childNode.dynamicEdges.length <= (this.hubThreshold + absorptionSizeOffset)) && if ((childNode.dynamicEdges.length <= (this.hubThreshold + absorptionSizeOffset)) &&
(childNode.id != hubNode.id)) { (childNode.id != hubNode.id)) {
this._addToCluster(hubNode,childNode,force); this._addToCluster(hubNode,childNode,force);
}
else {
//console.log("WILL NOT MERGE:",childNode.dynamicEdges.length , (this.hubThreshold + absorptionSizeOffset))
} }
} }
} }
} }
}; };
@ -730,40 +736,6 @@ exports._addToCluster = function(parentNode, childNode, force) {
}; };
/**
* This function will apply the changes made to the remainingEdges during the formation of the clusters.
* This is a seperate function to allow for level-wise collapsing of the node barnesHutTree.
* It has to be called if a level is collapsed. It is called by _formClusters().
* @private
*/
exports._updateDynamicEdges = function() {
for (var i = 0; i < this.nodeIndices.length; i++) {
var node = this.nodes[this.nodeIndices[i]];
node.dynamicEdgesLength = node.dynamicEdges.length;
// this corrects for multiple edges pointing at the same other node
var correction = 0;
if (node.dynamicEdgesLength > 1) {
for (var j = 0; j < node.dynamicEdgesLength - 1; j++) {
var edgeToId = node.dynamicEdges[j].toId;
var edgeFromId = node.dynamicEdges[j].fromId;
for (var k = j+1; k < node.dynamicEdgesLength; k++) {
if ((node.dynamicEdges[k].toId == edgeToId && node.dynamicEdges[k].fromId == edgeFromId) ||
(node.dynamicEdges[k].fromId == edgeToId && node.dynamicEdges[k].toId == edgeFromId)) {
correction += 1;
}
}
}
}
if (node.dynamicEdgesLength < correction) {
console.error("overshoot", node.dynamicEdgesLength, correction)
}
node.dynamicEdgesLength -= correction;
}
};
/** /**
* This adds an edge from the childNode to the contained edges of the parent node * This adds an edge from the childNode to the contained edges of the parent node
* *
@ -774,7 +746,7 @@ exports._updateDynamicEdges = function() {
*/ */
exports._addToContainedEdges = function(parentNode, childNode, edge) { exports._addToContainedEdges = function(parentNode, childNode, edge) {
// create an array object if it does not yet exist for this childNode // create an array object if it does not yet exist for this childNode
if (!(parentNode.containedEdges.hasOwnProperty(childNode.id))) {
if (parentNode.containedEdges[childNode.id] === undefined) {
parentNode.containedEdges[childNode.id] = [] parentNode.containedEdges[childNode.id] = []
} }
// add this edge to the list // add this edge to the list
@ -813,7 +785,6 @@ exports._connectEdgeToCluster = function(parentNode, childNode, edge) {
edge.toId = parentNode.id; edge.toId = parentNode.id;
} }
else { // edge connected to other node with the "from" side else { // edge connected to other node with the "from" side
edge.originalFromId.push(childNode.id); edge.originalFromId.push(childNode.id);
edge.from = parentNode; edge.from = parentNode;
edge.fromId = parentNode.id; edge.fromId = parentNode.id;
@ -989,7 +960,7 @@ exports.updateLabels = function() {
// for (nodeId in this.nodes) { // for (nodeId in this.nodes) {
// if (this.nodes.hasOwnProperty(nodeId)) { // if (this.nodes.hasOwnProperty(nodeId)) {
// node = this.nodes[nodeId]; // node = this.nodes[nodeId];
// node.label = String(node.level);
// node.label = String(node.clusterSize + ":" + node.dynamicEdges.length);
// } // }
// } // }
@ -1029,7 +1000,6 @@ exports.normalizeClusterLevels = function() {
} }
} }
this._updateNodeIndexList(); this._updateNodeIndexList();
this._updateDynamicEdges();
// if a cluster was formed, we increase the clusterSession // if a cluster was formed, we increase the clusterSession
if (this.nodeIndices.length != amountOfNodes) { if (this.nodeIndices.length != amountOfNodes) {
this.clusterSession += 1; this.clusterSession += 1;
@ -1090,11 +1060,11 @@ exports._getHubSize = function() {
for (var i = 0; i < this.nodeIndices.length; i++) { for (var i = 0; i < this.nodeIndices.length; i++) {
var node = this.nodes[this.nodeIndices[i]]; var node = this.nodes[this.nodeIndices[i]];
if (node.dynamicEdgesLength > largestHub) {
largestHub = node.dynamicEdgesLength;
if (node.dynamicEdges.length > largestHub) {
largestHub = node.dynamicEdges.length;
} }
average += node.dynamicEdgesLength;
averageSquared += Math.pow(node.dynamicEdgesLength,2);
average += node.dynamicEdges.length;
averageSquared += Math.pow(node.dynamicEdges.length,2);
hubCounter += 1; hubCounter += 1;
} }
average = average / hubCounter; average = average / hubCounter;
@ -1128,7 +1098,7 @@ exports._reduceAmountOfChains = function(fraction) {
var reduceAmount = Math.floor(this.nodeIndices.length * fraction); var reduceAmount = Math.floor(this.nodeIndices.length * fraction);
for (var nodeId in this.nodes) { for (var nodeId in this.nodes) {
if (this.nodes.hasOwnProperty(nodeId)) { if (this.nodes.hasOwnProperty(nodeId)) {
if (this.nodes[nodeId].dynamicEdgesLength == 2 && this.nodes[nodeId].dynamicEdges.length >= 2) {
if (this.nodes[nodeId].dynamicEdges.length == 2) {
if (reduceAmount > 0) { if (reduceAmount > 0) {
this._formClusterFromHub(this.nodes[nodeId],true,true,1); this._formClusterFromHub(this.nodes[nodeId],true,true,1);
reduceAmount -= 1; reduceAmount -= 1;
@ -1149,7 +1119,7 @@ exports._getChainFraction = function() {
var total = 0; var total = 0;
for (var nodeId in this.nodes) { for (var nodeId in this.nodes) {
if (this.nodes.hasOwnProperty(nodeId)) { if (this.nodes.hasOwnProperty(nodeId)) {
if (this.nodes[nodeId].dynamicEdgesLength == 2 && this.nodes[nodeId].dynamicEdges.length >= 2) {
if (this.nodes[nodeId].dynamicEdges.length == 2) {
chains += 1; chains += 1;
} }
total += 1; total += 1;

+ 1
- 1
lib/network/mixins/HierarchicalLayoutMixin.js View File

@ -42,7 +42,7 @@ exports._setupHierarchicalLayout = function() {
// if the user defined some levels but not all, alert and run without hierarchical layout // if the user defined some levels but not all, alert and run without hierarchical layout
if (undefinedLevel == true && definedLevel == true) { if (undefinedLevel == true && definedLevel == true) {
throw new Error("To use the hierarchical layout, nodes require either no predefined levels or levels have to be defined for all nodes."); throw new Error("To use the hierarchical layout, nodes require either no predefined levels or levels have to be defined for all nodes.");
this.zoomExtent(undefined,true,this.constants.clustering.enabled);
this.zoomExtent({duration:0},true,this.constants.clustering.enabled);
if (!this.constants.clustering.enabled) { if (!this.constants.clustering.enabled) {
this.start(); this.start();
} }

+ 6
- 3
lib/network/mixins/physics/PhysicsMixin.js View File

@ -325,6 +325,9 @@ exports._loadPhysicsConfiguration = function () {
this.backupConstants = {}; this.backupConstants = {};
util.deepExtend(this.backupConstants,this.constants); util.deepExtend(this.backupConstants,this.constants);
var maxGravitational = Math.max(20000, (-1 * this.constants.physics.barnesHut.gravitationalConstant) * 10);
var maxSpring = Math.min(0.05, this.constants.physics.barnesHut.springConstant * 10)
var hierarchicalLayoutDirections = ["LR", "RL", "UD", "DU"]; var hierarchicalLayoutDirections = ["LR", "RL", "UD", "DU"];
this.physicsConfiguration = document.createElement('div'); this.physicsConfiguration = document.createElement('div');
this.physicsConfiguration.className = "PhysicsConfiguration"; this.physicsConfiguration.className = "PhysicsConfiguration";
@ -339,16 +342,16 @@ exports._loadPhysicsConfiguration = function () {
'<table id="graph_BH_table" style="display:none">' + '<table id="graph_BH_table" style="display:none">' +
'<tr><td><b>Barnes Hut</b></td></tr>' + '<tr><td><b>Barnes Hut</b></td></tr>' +
'<tr>' + '<tr>' +
'<td width="150px">gravitationalConstant</td><td>0</td><td><input type="range" min="0" max="20000" value="' + (-1 * this.constants.physics.barnesHut.gravitationalConstant) + '" step="25" style="width:300px" id="graph_BH_gc"></td><td width="50px">-20000</td><td><input value="' + (-1 * this.constants.physics.barnesHut.gravitationalConstant) + '" id="graph_BH_gc_value" style="width:60px"></td>' +
'<td width="150px">gravitationalConstant</td><td>0</td><td><input type="range" min="0" max="'+maxGravitational+'" value="' + (-1 * this.constants.physics.barnesHut.gravitationalConstant) + '" step="25" style="width:300px" id="graph_BH_gc"></td><td width="50px">-'+maxGravitational+'</td><td><input value="' + (this.constants.physics.barnesHut.gravitationalConstant) + '" id="graph_BH_gc_value" style="width:60px"></td>' +
'</tr>' + '</tr>' +
'<tr>' + '<tr>' +
'<td width="150px">centralGravity</td><td>0</td><td><input type="range" min="0" max="3" value="' + this.constants.physics.barnesHut.centralGravity + '" step="0.05" style="width:300px" id="graph_BH_cg"></td><td>3</td><td><input value="' + this.constants.physics.barnesHut.centralGravity + '" id="graph_BH_cg_value" style="width:60px"></td>' +
'<td width="150px">centralGravity</td><td>0</td><td><input type="range" min="0" max="6" value="' + this.constants.physics.barnesHut.centralGravity + '" step="0.05" style="width:300px" id="graph_BH_cg"></td><td>3</td><td><input value="' + this.constants.physics.barnesHut.centralGravity + '" id="graph_BH_cg_value" style="width:60px"></td>' +
'</tr>' + '</tr>' +
'<tr>' + '<tr>' +
'<td width="150px">springLength</td><td>0</td><td><input type="range" min="0" max="500" value="' + this.constants.physics.barnesHut.springLength + '" step="1" style="width:300px" id="graph_BH_sl"></td><td>500</td><td><input value="' + this.constants.physics.barnesHut.springLength + '" id="graph_BH_sl_value" style="width:60px"></td>' + '<td width="150px">springLength</td><td>0</td><td><input type="range" min="0" max="500" value="' + this.constants.physics.barnesHut.springLength + '" step="1" style="width:300px" id="graph_BH_sl"></td><td>500</td><td><input value="' + this.constants.physics.barnesHut.springLength + '" id="graph_BH_sl_value" style="width:60px"></td>' +
'</tr>' + '</tr>' +
'<tr>' + '<tr>' +
'<td width="150px">springConstant</td><td>0</td><td><input type="range" min="0" max="0.5" value="' + this.constants.physics.barnesHut.springConstant + '" step="0.001" style="width:300px" id="graph_BH_sc"></td><td>0.5</td><td><input value="' + this.constants.physics.barnesHut.springConstant + '" id="graph_BH_sc_value" style="width:60px"></td>' +
'<td width="150px">springConstant</td><td>0</td><td><input type="range" min="0" max="'+maxSpring+'" value="' + this.constants.physics.barnesHut.springConstant + '" step="0.0001" style="width:300px" id="graph_BH_sc"></td><td>'+maxSpring+'</td><td><input value="' + this.constants.physics.barnesHut.springConstant + '" id="graph_BH_sc_value" style="width:60px"></td>' +
'</tr>' + '</tr>' +
'<tr>' + '<tr>' +
'<td width="150px">damping</td><td>0</td><td><input type="range" min="0" max="0.3" value="' + this.constants.physics.barnesHut.damping + '" step="0.005" style="width:300px" id="graph_BH_damp"></td><td>0.3</td><td><input value="' + this.constants.physics.barnesHut.damping + '" id="graph_BH_damp_value" style="width:60px"></td>' + '<td width="150px">damping</td><td>0</td><td><input type="range" min="0" max="0.3" value="' + this.constants.physics.barnesHut.damping + '" step="0.005" style="width:300px" id="graph_BH_damp"></td><td>0.3</td><td><input value="' + this.constants.physics.barnesHut.damping + '" id="graph_BH_damp_value" style="width:60px"></td>' +

+ 22
- 0
lib/util.js View File

@ -757,6 +757,28 @@ exports.hexToRGB = function(hex) {
} : null; } : null;
}; };
/**
* This function takes color in hex format or rgb() or rgba() format and overrides the opacity. Returns rgba() string.
* @param color
* @param opacity
* @returns {*}
*/
exports.overrideOpacity = function(color,opacity) {
if (color.indexOf("rgb") != -1) {
var rgb = color.substr(color.indexOf("(")+1).replace(")","").split(",");
return "rgba(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + "," + opacity + ")"
}
else {
var rgb = exports.hexToRGB(color);
if (rgb == null) {
return color;
}
else {
return "rgba(" + rgb.r + "," + rgb.g + "," + rgb.b + "," + opacity + ")"
}
}
}
/** /**
* *
* @param red 0 -- 255 * @param red 0 -- 255

+ 291
- 0
test/EU/js/init_precalc.js View File

@ -0,0 +1,291 @@
function focusOn(event) {
var nodeId = event.value;
network.selectNodes([nodeId]);
highlightConnections({nodes:[nodeId]})
network.focusOnNode(nodeId,{animation:false, scale:0.2})
}
function viewAllNeighbours() {
network.zoomExtent({nodes:connectedNodes, duration:0})
}
function doSteps(amount) {
network.setOptions({stabilizationIterations:amount})
network._stabilize()
}
var network;
var nodes;
var edges;
var edgeOpacity = 0.15;
function drawAll(dataJSON, file) {
// create an array with nodes
nodes = new vis.DataSet(dataJSON.nodes);
var totalMass = 0;
for (var i = 0; i < dataJSON.nodes.length; i++) {
totalMass += dataJSON.nodes[i].mass;
//console.log(dataJSON.nodes[i].mass)
}
var gravityConstant = -20000;
if (totalMass < 2000) {
gravityConstant = -2000;
}
var edgeNodeRatio = Math.max(1,dataJSON.edges.length) / Math.max(1,dataJSON.nodes.length);
var nodeEdgeRatio = Math.max(1,dataJSON.nodes.length) / Math.max(1,dataJSON.edges.length);
var centralGravity = Math.min(5,Math.max(0.1,edgeNodeRatio));
opacity = Math.min(1.0,Math.max(0.15,nodeEdgeRatio));
// create an array with edges
edges = new vis.DataSet(dataJSON.edges);
// console.log(edgesArray.length,edgesArray)
// create a network
var container = document.getElementById('mynetwork');
var data = {
nodes: nodes,
edges: edges
};
var amountOfNodes = dataJSON.nodes.length;
var options = {
stabilize: true,
stabilizationIterations: 15000,
smoothCurves: {
enabled: false,
dynamic: false
},
configurePhysics: false,
physics: {barnesHut: {gravitationalConstant: gravityConstant, centralGravity: centralGravity, springLength: 100, springConstant: 0.001}},
//physics: {barnesHut: {gravitationalConstant: 0, centralGravity: 0.0, springConstant: 0}},
nodes: {
shape: 'dot',
radiusMax: amountOfNodes * 0.5,
fontColor: '#ffffff',
fontDrawThreshold: 8,
scaleFontWithValue: true,
fontSizeMin: 14,
fontSizeMax: amountOfNodes * 0.25,
fontSizeMaxVisible: 20,
fontStrokeWidth: 1, // px
fontStrokeColor: '#222222'
},
edges: {
opacity: edgeOpacity
},
hideEdgesOnDrag: true,
maxVelocity: 100,
tooltip: {
delay: 200,
fontSize: 12,
color: {
background: "#fff"
}
}
};
network = new vis.Network(container, {nodes:[],edges:[]}, options);
network.on("stabilizationIterationsDone", function() {
this.setFreezeSimulation(true);
});
network.on("stabilized", function() {
console.log('downloading')
network.storePositions();
download(file);
setTimeout(getNewTask(file),3000);
})
network.setData(data);
if (dataJSON.nodes.length < 2) {
console.log('downloading because few nodes')
network.storePositions();
download(file);
setTimeout(getNewTask(file),3000);
}
//network.on("click", highlightConnections);
//window.onresize = function () {
// network.redraw()
//};
//network.on("stabilized", function() {
// network.setOptions({physics: {barnesHut: {gravitationalConstant: 0, centralGravity: 0, springConstant: 0}}});
// console.log('downlaoding')
// download();
// setTimeout(next,2000);
//});
//populateCompanyDropdown();
}
// marked is used so we don't do heavy stuff on each click
var marked = false;
var connectedNodes = [];
function highlightConnections(selectedItems) {
var nodeId;
var requireUpdate = false;
// we get all data from the dataset once to avoid updating multiple times.
if (selectedItems.nodes.length == 0 && marked === true) {
connectedNodes = [];
requireUpdate = true;
var allNodes = nodes.get({returnType:"Object"});
// restore on unselect
for (nodeId in allNodes) {
allNodes[nodeId].color = undefined;
if (allNodes[nodeId].oldLabel !== undefined) {
allNodes[nodeId].label = allNodes[nodeId].oldLabel;
allNodes[nodeId].oldLabel = undefined;
}
}
marked = false;
network.setOptions({nodes:{fontSizeMin:14},edges:{opacity:0.15}})
}
else if (selectedItems.nodes.length > 0) {
var allNodes = nodes.get({returnType:"Object"});
marked = true;
requireUpdate = true;
var mainNode = selectedItems.nodes[0]; // this is an ID
connectedNodes = network.getConnectedNodes(mainNode);
connectedNodes.push(mainNode);
for (nodeId in allNodes) {
allNodes[nodeId].color = 'rgba(150,150,150,0.1)';
if (allNodes[nodeId].oldLabel === undefined) {
allNodes[nodeId].oldLabel = allNodes[nodeId].label;
allNodes[nodeId].label = "";
}
}
for (var i = 0; i < connectedNodes.length; i++) {
allNodes[connectedNodes[i]].color = undefined;
if (allNodes[connectedNodes[i]].oldLabel !== undefined) {
allNodes[connectedNodes[i]].label = allNodes[connectedNodes[i]].oldLabel;
allNodes[connectedNodes[i]].oldLabel = undefined;
}
}
network.setOptions({nodes:{fontSizeMin:150},edges:{opacity:0.025}})
}
if (requireUpdate === true) {
var updateArray = [];
for (nodeId in allNodes) {
updateArray.push(allNodes[nodeId]);
}
nodes.update(updateArray);
}
}
function loadJSON(path, success, error) {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
success(JSON.parse(xhr.responseText), path);
}
else {
error();
}
}
};
xhr.open("GET", path, true);
xhr.send();
}
function populateDropdown() {
var nodesAr = nodes.get();
var nodeIds = [];
for (var i = 0; i < nodesAr.length;i++) {
nodeIds.push(nodesAr[i].id);
}
nodeIds.sort()
var dropdown = document.getElementById("companyDropdown");
var option = document.createElement('option');
option.text = option.value = 'pick a company to focus on.';
dropdown.add(option);
for (i = 0; i < nodeIds.length; i++) {
option = document.createElement('option');
option.text = option.value = nodeIds[i];
dropdown.add(option);
}
}
function download(path) {
var file = path.replace("./data/data/","");
var nodesAr = nodes.get();
var edgesAr = edges.get();
var obj = {nodes: nodesAr, edges: edgesAr};
var json = JSON.stringify(obj);
var blob = new Blob([json], {type: "text/plain;charset=utf-8"});
saveAs(blob, "processed_" + file);
}
function startTask(path, success) {
// if we find the file already processed, we go to the next one
loadJSON("./data/processedData/processed_" + path,
function() {
getNewTask(path);
},
function() {
loadJSON("./data/data/" + path, success, function() {
console.log("could not find file ", path);
})
});
}
function askAgent(to, message) {
return new Promise(function (resolve, reject) {
if (typeof message == 'object') {
message = JSON.stringify(message);
}
// create XMLHttpRequest object to send the POST request
var http = new XMLHttpRequest();
// insert the callback function. This is called when the message has been delivered and a response has been received
http.onreadystatechange = function () {
if (http.readyState == 4 && http.status == 200) {
var response = "";
if (http.responseText.length > 0) {
response = JSON.parse(http.responseText);
}
resolve(response);
}
else if (http.readyState == 4) {
reject(new Error("http.status:" + http.status));
}
};
// open an asynchronous POST connection
http.open("POST", to, true);
// include header so the receiving code knows its a JSON object
http.setRequestHeader("Content-Type", "text/plain");
// send
http.send(message);
});
}
function getNewTask(path) {
var file = undefined;
if (path !== undefined) {
file = path.replace("./data/data/", "");
}
askAgent("http://127.0.0.1:3000/agents/proxy", {jsonrpc:"2.0",method:"getAssignment", params:{finishedFile:file}}).then(function(reply) {
console.log(reply, reply.result);
if (reply.result != 'none') {
startTask(reply.result, function(data,path) {document.getElementById("FILENAME").innerHTML = path; setTimeout(function() {drawAll(data,path);},200)});
}
})
}
getNewTask()

Loading…
Cancel
Save