Browse Source

Merge remote-tracking branch 'origin/develop' into develop

v3_develop
jos 9 years ago
parent
commit
da10145046
14 changed files with 25492 additions and 25087 deletions
  1. +10
    -0
      HISTORY.md
  2. +25030
    -24874
      dist/vis.js
  3. +1
    -1
      dist/vis.min.css
  4. +102
    -4
      docs/network.html
  5. +1
    -0
      examples/network/25_physics_configuration.html
  6. +10
    -27
      examples/network/29_neighbourhood_highlight.html
  7. +29
    -19
      lib/network/Edge.js
  8. +152
    -39
      lib/network/Network.js
  9. +42
    -23
      lib/network/Node.js
  10. +61
    -91
      lib/network/mixins/ClusterMixin.js
  11. +1
    -1
      lib/network/mixins/HierarchicalLayoutMixin.js
  12. +5
    -5
      lib/network/mixins/ManipulationMixin.js
  13. +6
    -3
      lib/network/mixins/physics/PhysicsMixin.js
  14. +42
    -0
      lib/util.js

+ 10
- 0
HISTORY.md View File

@ -8,6 +8,16 @@ http://visjs.org
- 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.
- 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 freezeSimulation method.
- Added clusterByZoom option.
- Fixed bug when redrawing was not right on zoomed-out browsers.
- Added opacity to edges. Opacity is only used for the unselected state.
### DataSet/DataView

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


+ 1
- 1
dist/vis.min.css
File diff suppressed because it is too large
View File


+ 102
- 4
docs/network.html View File

@ -849,6 +849,13 @@ var options = {
inside an object <code>nodes</code> in the networks options object.</p> All options in green boxes can be defined per-node as well.
All options defined per-node override these global settings.
<table>
<tr>
<th>Name</th>
<th>Type</th>
<th>Default</th>
<th>Description</th>
</tr>
<tr>
<td class="greenField">borderWidth</td>
<td>Number</td>
@ -861,6 +868,31 @@ All options defined per-node override these global settings.
<td>undefined</td>
<td>The width of the border of the node when it is selected. If left at undefined, double the borderWidth will be used.</td>
</tr>
<tr>
<td>customScalingFunction</td>
<td>Function</td>
<td>Function</td>
<td>This is a function you can override to make the nodes scale the way you want them based on their values. The default function is this: <br>
<pre class="prettyprint lang-js">
function (min,max,total,value) {
if (max == min) {
return 0.5;
}
else {
var scale = 1 / (max - min);
return Math.max(0,(value - min)*scale);
}
};
</pre>
The function receives the minimum value of the set, the maximum value, the total sum of all values and finally the value of the node or edge it works on. It has to return a value between 0 and 1.
The nodes and edges then calculate their size as follows:
<pre class="prettyprint lang-js">
var scale = customScalingFunction(min,max,total,value);
var diff = maxSize - minSize;
mySize = minSize + diff * scale;</pre>
</td>
</tr>
<tr>
<td class="greenField">color</td>
<td>String | Object</td>
@ -927,6 +959,38 @@ All options defined per-node override these global settings.
<td>14</td>
<td>Font size in pixels for label in the node.</td>
</tr>
<tr>
<td>scaleFontWithValue</td>
<td>Boolean</td>
<td>false</td>
<td>When using values, you can let the font scale with the size of the nodes if you enable the this option.</td>
</tr>
<tr>
<td>fontSizeMin</td>
<td>Number</td>
<td>14</td>
<td>When using values, you can let the font scale with the size of the nodes if you enable the scaleFontWithValue option. This is the minimum value of the fontSize.</td>
</tr>
<tr>
<td></td>fontSizeMax</td>
<td>Number</td>
<td>30</td>
<td>When using values, you can let the font scale with the size of the nodes if you enable the scaleFontWithValue option. This is the maximum value of the fontSize.</td>
</tr>
<tr>
<td>fontSizeMaxVisible</td>
<td>Number</td>
<td>30</td>
<td>When using values, you can let the font scale with the size of the nodes if you enable the scaleFontWithValue option. If you have a wide distribution of values and have a large max fontSize,
the text will become huge if you zoom in on it. This option limits the percieved fontSize to avoid this. If you set it to 20, no label will be larger than fontsize 20 (at scale = 1) regardless of the scale.</td>
</tr>
<tr>
<td>fontDrawThreshold</td>
<td>Number</td>
<td>3</td>
<td>When zooming out, the text becomes smaller. This option sets the minimum size of the label before not being drawn. Just like the fontSizeMaxVisible option, this is the relative fontSize (fontSize * scale).
You can combine this with the min and max values to have the labels of influential nodes show earlier when zooming in.</td>
</tr>
<tr>
<td class="greenField">fontFill</td>
<td>String</td>
@ -1197,7 +1261,12 @@ var options = {
<td>Possible values: <code>"line-above", "line-center", "line-below"</code>. The alignment of the label when drawn on the edge.
If <code>horizontal</code> it will align the label absolute horizontial.</td>
</tr>
<tr>
<td class="greenField">opacity</td>
<td>Number</td>
<td>1.0</td>
<td>Possible values: <code>[0 .. 1]</code>. This opacity value is added on top of the color information. This only happens for the unselected state.</td>
</tr>
<tr>
<td class="greenField">style</td>
<td>string</td>
@ -1727,7 +1796,8 @@ var options = {
radius: 1},
maxNodeSizeIncrements: 600,
activeAreaBoxSize: 100,
clusterLevelDifference: 2
clusterLevelDifference: 2,
clusterByZoom: true
}
}
// OR to just load the module with default values:
@ -1870,6 +1940,12 @@ var options = {
If the highest level of your network at any given time is 3, nodes that have not clustered or
have clustered only once will join their neighbour with the lowest cluster level.</td>
</tr>
<tr>
<td>clusterByZoom</td>
<td>Boolean</td>
<td>true</td>
<td>You can toggle the clustering by zoom level using this option.</td>
</tr>
</table>
<h3 id="Navigation_controls">Navigation controls</h3>
@ -2209,11 +2285,18 @@ var options = {
</td>
</tr>
<tr>
<td>getBoundingBox()</td>
<td>getBoundingBox(nodeId)</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>
</tr>
<tr>
<td>getConnectedNodes(nodeId)</td>
<td>Array</td>
<td>Returns an array with nodeIds of nodes that are connected to this node. Network keeps track of the connected nodes so this function allows you
to quickly get them without iterating over all edges manually. This is a lot faster for cases with many edges.
</td>
</tr>
<tr>
<td>getSelection()</td>
<td>Array of ids</td>
@ -2240,6 +2323,13 @@ var options = {
easeInQuint, easeOutQuint, easeInOutQuint </code> <br /><br />
</td>
</tr>
<tr>
<td>freezeSimulation(Boolean)</td>
<td>none</td>
<td>Calling freezeSimulation(true) immmediately stops the simulation and triggerst the stabilized event. This does not mean that the network
is physically stabilized but the nodes are not moving anymore. To continue the simulation call freezeSimulation(false).
</td>
</tr>
<tr>
<td>releaseNode()</td>
<td>none</td>
@ -2329,6 +2419,12 @@ var options = {
nodes with id 3 and 5. The highlisghEdges boolean can be used to automatically select the edges connected to the node.
</td>
</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>
<td>selectEdges(selection)</td>
<td>none</td>
@ -2348,7 +2444,7 @@ var options = {
or in percentages.</td>
</tr>
<tr>
<td>getPositions([ids])</td>
<td>getPositions([nodeIds])</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>
@ -2370,6 +2466,7 @@ var options = {
options can just be a boolean. When true, the zoom is animated, when false there is no animation.
Alternatively, you can supply an object.
<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>easingFunction: String</code></b><br /> - the easing function of the animation, available are:<br />
<code>linear, easeInQuad, easeOutQuad, easeInOutQuad, easeInCubic, easeOutCubic, easeInOutCubic,
@ -2378,6 +2475,7 @@ var options = {
</td>
</tr>
</table>
<h2 id="Events">Events</h2>

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

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

+ 10
- 27
examples/network/29_neighbourhood_highlight.html View File

@ -10027,7 +10027,12 @@ function redrawAll() {
radiusMin: 10,
radiusMax: 30,
fontSize: 12,
fontFace: "Tahoma"
fontFace: "Tahoma",
scaleFontWithValue:true,
fontSizeMin:8,
fontSizeMax:20,
fontThreshold:12,
fontSizeMaxVisible:20
},
edges: {
width: 0.15,
@ -10071,8 +10076,6 @@ function onClick(selectedItems) {
}
}
else {
var allEdges = edges.get();
// we clear the level of separation in all nodes.
clearLevelOfSeperation(allNodes);
@ -10083,7 +10086,7 @@ function onClick(selectedItems) {
// any data can be added to a node, this is just stored in the nodeObject.
storeLevelOfSeperation(connectedNodes,0, allNodes);
for (var i = 1; i < degrees + 1; i++) {
appendConnectedNodes(connectedNodes, allEdges);
appendConnectedNodes(connectedNodes);
storeLevelOfSeperation(connectedNodes, i, allNodes);
}
for (nodeId in allNodes) {
@ -10153,7 +10156,7 @@ function clearLevelOfSeperation(allNodes) {
*
*
*/
function appendConnectedNodes(sourceNodes, allEdges) {
function appendConnectedNodes(sourceNodes) {
var tempSourceNodes = [];
// first we make a copy of the nodes so we do not extend the array we loop over.
for (var i = 0; i < sourceNodes.length; i++) {
@ -10165,7 +10168,8 @@ function appendConnectedNodes(sourceNodes, allEdges) {
if (sourceNodes.indexOf(nodeId) == -1) {
sourceNodes.push(nodeId);
}
addUnique(getConnectedNodes(nodeId, allEdges),sourceNodes);
var connectedNodes = network.getConnectedNodes(nodeId);
addUnique(connectedNodes,sourceNodes);
}
tempSourceNodes = null;
}
@ -10183,27 +10187,6 @@ function addUnique(fromArray, toArray) {
}
}
/**
* Get a list of nodes that are connected to the supplied nodeId with edges.
* @param nodeId
* @returns {Array}
*/
function getConnectedNodes(nodeId, allEdges) {
var edgesArray = allEdges;
var connectedNodes = [];
for (var i = 0; i < edgesArray.length; i++) {
var edge = edgesArray[i];
if (edge.to == nodeId) {
connectedNodes.push(edge.from);
}
else if (edge.from == nodeId) {
connectedNodes.push(edge.to)
}
}
return connectedNodes;
}
redrawAll()
</script>

+ 29
- 19
lib/network/Edge.js View File

@ -40,6 +40,7 @@ function Edge (properties, network, networkConstants) {
this.hover = false;
this.labelDimensions = {top:0,left:0,width:0,height:0,yLine:0}; // could be cached
this.dirtyLabel = true;
this.colorDirty = true;
this.from = null; // a node
this.to = null; // a node
@ -71,12 +72,14 @@ function Edge (properties, network, networkConstants) {
* @param {Object} constants and object with default, global properties
*/
Edge.prototype.setProperties = function(properties) {
this.colorDirty = true;
if (!properties) {
return;
}
var fields = ['style','fontSize','fontFace','fontColor','fontFill','fontStrokeWidth','fontStrokeColor','width',
'widthSelectionMultiplier','hoverWidth','arrowScaleFactor','dash','inheritColor','labelAlignment'
'widthSelectionMultiplier','hoverWidth','arrowScaleFactor','dash','inheritColor','labelAlignment', 'opacity',
'customScalingFunction'
];
util.selectiveDeepExtend(fields, this.options, properties);
@ -103,7 +106,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.widthFixed = this.widthFixed || (properties.width !== undefined);
@ -119,9 +124,9 @@ Edge.prototype.setProperties = function(properties) {
case 'dash-line': this.draw = this._drawDashLine; break;
default: this.draw = this._drawLine; break;
}
};
/**
* Connect an edge to its nodes
*/
@ -186,10 +191,11 @@ Edge.prototype.getValue = function() {
* @param {Number} min
* @param {Number} max
*/
Edge.prototype.setValueRange = function(min, max) {
Edge.prototype.setValueRange = function(min, max, total) {
if (!this.widthFixed && this.value !== undefined) {
var scale = (this.options.widthMax - this.options.widthMin) / (max - min);
this.options.width= (this.value - min) * scale + this.options.widthMin;
var scale = this.options.customScalingFunction(min, max, total, this.value);
var widthDiff = this.options.widthMax - this.options.widthMin;
this.options.width = this.options.widthMin + scale * widthDiff;
this.widthSelected = this.options.width* this.options.widthSelectionMultiplier;
}
};
@ -230,19 +236,23 @@ Edge.prototype.isOverlappingWith = function(obj) {
Edge.prototype._getColor = function() {
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;}

+ 152
- 39
lib/network/Network.js View File

@ -53,9 +53,19 @@ function Network (container, data, options) {
this.triggerFunctions = {add:null,edit:null,editEdge:null,connect:null,del:null};
var customScalingFunction = function (min,max,total,value) {
if (max == min) {
return 0.5;
}
else {
var scale = 1 / (max - min);
return Math.max(0,(value - min)*scale);
}
};
// set constant values
this.defaultOptions = {
nodes: {
customScalingFunction: customScalingFunction,
mass: 1,
radiusMin: 10,
radiusMax: 30,
@ -69,7 +79,12 @@ function Network (container, data, options) {
fontFace: 'verdana',
fontFill: undefined,
fontStrokeWidth: 0, // px
fontStrokeColor: 'white',
fontStrokeColor: '#ffffff',
fontDrawThreshold: 3,
scaleFontWithValue: false,
fontSizeMin: 14,
fontSizeMax: 30,
fontSizeMaxVisible: 30,
level: -1,
color: {
border: '#2B7CE9',
@ -88,6 +103,7 @@ function Network (container, data, options) {
borderWidthSelected: undefined
},
edges: {
customScalingFunction: customScalingFunction,
widthMin: 1, //
widthMax: 15,//
width: 1,
@ -99,6 +115,7 @@ function Network (container, data, options) {
highlight:'#848484',
hover: '#848484'
},
opacity:1.0,
fontColor: '#343434',
fontSize: 14, // px
fontFace: 'arial',
@ -163,8 +180,8 @@ function Network (container, data, options) {
height: 1, // (px PNiC) | growth of the height 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.
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
},
navigation: {
@ -193,7 +210,7 @@ function Network (container, data, options) {
type: "continuous",
roundness: 0.5
},
maxVelocity: 30,
maxVelocity: 50,
minVelocity: 0.1, // px/s
stabilize: true, // stabilize before displaying the network
stabilizationIterations: 1000, // maximum number of iteration to stabilize
@ -275,7 +292,7 @@ function Network (container, data, options) {
this.setOptions(options);
// other vars
this.freezeSimulation = false;// freeze the simulation
this.freezeSimulationEnabled = false;// freeze the simulation
this.cachedFunctions = {};
this.startedStabilization = false;
this.stabilized = false;
@ -346,7 +363,7 @@ function Network (container, data, options) {
else {
// 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) {
this.zoomExtent(undefined, true,this.constants.clustering.enabled);
this.zoomExtent({duration:0}, true, this.constants.clustering.enabled);
}
}
@ -406,17 +423,45 @@ Network.prototype._getScriptPath = function() {
* Find the center position of the network
* @private
*/
Network.prototype._getRange = function() {
Network.prototype._getRange = function(specificNodes) {
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) {
minY = 0, maxY = 0, minX = 0, maxX = 0;
}
@ -441,17 +486,37 @@ Network.prototype._findCenter = function(range) {
* @param {Boolean} [initialZoom] | zoom based on fitted formula or range, true = fitted, default = false;
* @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);
if (initialZoom === undefined) {initialZoom = 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;
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;
if (this.constants.smoothCurves == true) {
if (this.constants.clustering.enabled == true &&
@ -477,6 +542,7 @@ Network.prototype.zoomExtent = function(animationOptions, initialZoom, disableSt
zoomLevel *= factor;
}
else {
range = this._getRange(options.nodes);
var xDistance = Math.abs(range.maxX - range.minX) * 1.1;
var yDistance = Math.abs(range.maxY - range.minY) * 1.1;
@ -492,7 +558,7 @@ Network.prototype.zoomExtent = function(animationOptions, initialZoom, disableSt
var center = this._findCenter(range);
if (disableStart == false) {
var options = {position: center, scale: zoomLevel, animation: animationOptions};
var options = {position: center, scale: zoomLevel, animation: options};
this.moveTo(options);
this.moving = true;
this.start();
@ -581,7 +647,7 @@ Network.prototype.setData = function(data, disableStart) {
}
else {
// find a stable position or start animating to a stable position
if (this.constants.stabilize) {
if (this.constants.stabilize == true) {
this._stabilize();
}
}
@ -732,7 +798,7 @@ Network.prototype.setOptions = function (options) {
// bind keys. If disabled, this will not do anything;
this._createKeyBinds();
this._markAllEdgesAsDirty();
this.setSize(this.constants.width, this.constants.height);
this.moving = true;
this.start();
@ -783,6 +849,7 @@ Network.prototype._create = function () {
ctx.oBackingStorePixelRatio ||
ctx.backingStorePixelRatio || 1);
//this.pixelRatio = Math.max(1,this.pixelRatio); // this is to account for browser zooming out. The pixel ratio is ment to switch between 1 and 2 for HD screens.
this.frame.canvas.getContext("2d").setTransform(this.pixelRatio, 0, 0, this.pixelRatio, 0, 0);
}
@ -874,10 +941,11 @@ Network.prototype._createKeyBinds = function() {
this.keycharm.bind("pagedown",this._zoomOut.bind(me),"keydown");
this.keycharm.bind("pagedown",this._stopZoom.bind(me), "keyup");
}
this.keycharm.bind("1",this.increaseClusterLevel.bind(me), "keydown");
this.keycharm.bind("2",this.decreaseClusterLevel.bind(me), "keydown");
this.keycharm.bind("3",this.forceAggregateHubs.bind(me,true),"keydown");
this.keycharm.bind("4",this.normalizeClusterLevels.bind(me), "keydown");
//this.keycharm.bind("1",this.increaseClusterLevel.bind(me), "keydown");
//this.keycharm.bind("2",this.decreaseClusterLevel.bind(me), "keydown");
//this.keycharm.bind("3",this.forceAggregateHubs.bind(me,true),"keydown");
//this.keycharm.bind("4",this.normalizeClusterLevels.bind(me), "keydown");
if (this.constants.dataManipulation.enabled == true) {
this.keycharm.bind("esc",this._createManipulatorBar.bind(me));
this.keycharm.bind("delete",this._deleteSelected.bind(me));
@ -1599,8 +1667,16 @@ Network.prototype._updateNodes = function(ids,changedData) {
}
this._updateNodeIndexList();
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.
* @param {Number[] | String[]} ids
@ -1803,12 +1879,14 @@ Network.prototype._updateValueRange = function(obj) {
// determine the range of the objects
var valueMin = undefined;
var valueMax = undefined;
var valueTotal = 0;
for (id in obj) {
if (obj.hasOwnProperty(id)) {
var value = obj[id].getValue();
if (value !== undefined) {
valueMin = (valueMin === undefined) ? value : Math.min(value, valueMin);
valueMax = (valueMax === undefined) ? value : Math.max(value, valueMax);
valueTotal += value;
}
}
}
@ -1817,7 +1895,7 @@ Network.prototype._updateValueRange = function(obj) {
if (valueMin !== undefined && valueMax !== undefined) {
for (id in obj) {
if (obj.hasOwnProperty(id)) {
obj[id].setValueRange(valueMin, valueMax);
obj[id].setValueRange(valueMin, valueMax, valueTotal);
}
}
}
@ -1843,8 +1921,8 @@ Network.prototype._redraw = function(hidden) {
ctx.setTransform(this.pixelRatio, 0, 0, this.pixelRatio, 0, 0);
// clear the canvas
var w = this.frame.canvas.width * this.pixelRatio;
var h = this.frame.canvas.height * this.pixelRatio;
var w = this.frame.canvas.clientWidth;
var h = this.frame.canvas.clientHeight;
ctx.clearRect(0, 0, w, h);
// set scaling and translation
@ -1857,8 +1935,8 @@ Network.prototype._redraw = function(hidden) {
"y": this._YconvertDOMtoCanvas(0)
};
this.canvasBottomRight = {
"x": this._XconvertDOMtoCanvas(this.frame.canvas.clientWidth * this.pixelRatio),
"y": this._YconvertDOMtoCanvas(this.frame.canvas.clientHeight * this.pixelRatio)
"x": this._XconvertDOMtoCanvas(this.frame.canvas.clientWidth),
"y": this._YconvertDOMtoCanvas(this.frame.canvas.clientHeight)
};
if (!(hidden == true)) {
@ -2093,16 +2171,22 @@ Network.prototype._stabilize = function() {
var count = 0;
while (this.moving && count < this.constants.stabilizationIterations) {
this._physicsTick();
if (count % 100 == 0) {
console.log("stabilizationIterations",count);
}
count++;
}
if (this.constants.zoomExtentOnStabilize == true) {
this.zoomExtent(undefined, false, true);
this.zoomExtent({duration:0}, false, true);
}
if (this.constants.freezeForStabilization == true) {
this._restoreFrozenNodes();
}
this.emit("stabilizationIterationsDone");
};
/**
@ -2152,8 +2236,10 @@ Network.prototype._restoreFrozenNodes = function() {
Network.prototype._isMoving = function(vmin) {
var nodes = this.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;
@ -2224,7 +2310,7 @@ Network.prototype._revertPhysicsTick = function() {
* @private
*/
Network.prototype._physicsTick = function() {
if (!this.freezeSimulation) {
if (!this.freezeSimulationEnabled) {
if (this.moving == true) {
var mainMovingStatus = false;
var supportMovingStatus = false;
@ -2236,11 +2322,12 @@ Network.prototype._physicsTick = function() {
}
// 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
this.moving = mainMovingStatus || supportMovingStatus;
if (this.moving == false) {
this._revertPhysicsTick();
}
@ -2363,12 +2450,14 @@ Network.prototype._handleNavigation = function() {
/**
* Freeze the _animationStep
*/
Network.prototype.toggleFreeze = function() {
if (this.freezeSimulation == false) {
this.freezeSimulation = true;
Network.prototype.freezeSimulation = function(freeze) {
if (freeze == true) {
this.freezeSimulationEnabled = true;
this.moving = false;
}
else {
this.freezeSimulation = false;
this.freezeSimulationEnabled = false;
this.moving = true;
this.start();
}
};
@ -2741,4 +2830,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;

+ 42
- 23
lib/network/Node.js View File

@ -36,8 +36,6 @@ function Node(properties, imagelist, grouplist, networkConstants) {
this.dynamicEdges = [];
this.reroutedEdges = {};
this.fontDrawThreshold = 3;
// set defaults for the properties
this.id = undefined;
this.allowedToMoveX = false;
@ -64,6 +62,7 @@ function Node(properties, imagelist, grouplist, networkConstants) {
this.vy = 0.0; // velocity y
this.x = 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
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
this.resetCluster();
this.dynamicEdgesLength = 0;
this.clusterSession = 0;
this.clusterSizeWidthFactor = networkConstants.clustering.nodeScaling.width;
this.clusterSizeHeightFactor = networkConstants.clustering.nodeScaling.height;
@ -126,7 +124,6 @@ Node.prototype.attachEdge = function(edge) {
if (this.dynamicEdges.indexOf(edge) == -1) {
this.dynamicEdges.push(edge);
}
this.dynamicEdgesLength = this.dynamicEdges.length;
};
/**
@ -142,7 +139,6 @@ Node.prototype.detachEdge = function(edge) {
if (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',
'fontSize','fontFace','fontFill','fontStrokeWidth','fontStrokeColor','group','mass'
'fontSize','fontFace','fontFill','fontStrokeWidth','fontStrokeColor','group','mass','fontDrawThreshold',
'scaleFontWithValue','fontSizeMaxVisible','customScalingFunction'
];
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.label !== undefined) {this.label = properties.label; this.originalLabel = properties.label;}
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.level !== undefined) {this.level = properties.level; this.preassignedLevel = true;}
@ -482,16 +479,17 @@ Node.prototype.getDistance = function(x, y) {
* @param {Number} min
* @param {Number} max
*/
Node.prototype.setValueRange = function(min, max) {
Node.prototype.setValueRange = function(min, max, total) {
if (!this.radiusFixed && this.value !== undefined) {
if (max == min) {
this.options.radius= (this.options.radiusMin + this.options.radiusMax) / 2;
}
else {
var scale = (this.options.radiusMax - this.options.radiusMin) / (max - min);
this.options.radius= (this.value - min) * scale + this.options.radiusMin;
var scale = this.options.customScalingFunction(min, max, total, this.value);
var radiusDiff = this.options.radiusMax - this.options.radiusMin;
if (this.options.scaleFontWithValue == true) {
var fontDiff = this.options.fontSizeMax - this.options.fontSizeMin;
this.options.fontSize = this.options.fontSizeMin + scale * fontDiff;
}
this.options.radius = this.options.radiusMin + scale * radiusDiff;
}
this.baseRadiusValue = this.options.radius;
};
@ -1015,12 +1013,29 @@ Node.prototype._drawText = function (ctx) {
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 lineCount = lines.length;
var fontSize = Number(this.options.fontSize);
var yLine = y + (1 - lineCount) / 2 * fontSize;
if (labelUnderNode == true) {
yLine = y + (1 - lineCount) / (2 * fontSize);
@ -1032,7 +1047,7 @@ Node.prototype._label = function (ctx, text, x, y, align, baseline, labelUnderNo
var lineWidth = ctx.measureText(lines[i]).width;
width = lineWidth > width ? lineWidth : width;
}
var height = this.options.fontSize * lineCount;
var height = fontSize * lineCount;
var left = x - width / 2;
var top = y - height / 2;
if (baseline == "hanging") {
@ -1049,12 +1064,12 @@ Node.prototype._label = function (ctx, text, x, y, align, baseline, labelUnderNo
}
// draw text
ctx.fillStyle = this.options.fontColor || "black";
ctx.fillStyle = fontColor;
ctx.textAlign = align || "center";
ctx.textBaseline = baseline || "middle";
if (this.options.fontStrokeWidth > 0){
ctx.lineWidth = this.options.fontStrokeWidth;
ctx.strokeStyle = this.options.fontStrokeColor;
ctx.strokeStyle = strokecolor;
ctx.lineJoin = 'round';
}
for (var i = 0; i < lineCount; i++) {
@ -1070,10 +1085,14 @@ Node.prototype._label = function (ctx, text, x, y, align, baseline, labelUnderNo
Node.prototype.getTextSize = function(ctx) {
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'),
height = (Number(this.options.fontSize) + 4) * lines.length,
height = (fontSize + 4) * lines.length,
width = 0;
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
// the setData function.
if (this.stabilize) {
if (this.constants.stabilize == true) {
this._stabilize();
}
this.start();
@ -32,24 +32,22 @@ exports.startWithClustering = function() {
exports.clusterToFit = function(maxNumberOfNodes, reposition) {
var numberOfNodes = this.nodeIndices.length;
var maxLevels = 2;
var maxLevels = 50;
var level = 0;
// we first cluster the hubs, then we pull in the outliers, repeat
while (numberOfNodes > maxNumberOfNodes && level < maxLevels) {
console.log("Performing clustering:", level, numberOfNodes, this.clusterSession);
if (level % 3 == 0.0) {
//this.forceAggregateHubs(true);
//this.normalizeClusterLevels();
this.forceAggregateHubs(true);
this.normalizeClusterLevels();
}
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;
level += 1;
}
console.log("finished")
// after the clustering we reposition the nodes to reduce the initial chaos
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.
*
* @param node | Node object: cluster to open.
@ -82,9 +80,8 @@ exports.openCluster = function(node) {
else {
this._expandClusterNode(node,false,true);
// update the index list, dynamic edges and labels
// update the index list and labels
this._updateNodeIndexList();
this._updateDynamicEdges();
this._updateCalculationNodes();
this.updateLabels();
}
@ -142,18 +139,21 @@ exports.updateClusters = function(zoomDirection,recursive,force,doNotStart) {
var isMovingBeforeClustering = this.moving;
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
if (this.previousScale > this.scale && zoomDirection == 0) {
if (detectedZoomingOut == true) {
this._collapseSector();
}
// 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
// outer nodes determines if it is being clustered
this._formClusters(force);
}
else if (this.previousScale < this.scale || zoomDirection == 1) { // zoom in
else if (detectedZoomingIn == true || zoomDirection == 1) { // zoom in
if (force == true) {
// _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
@ -161,27 +161,27 @@ exports.updateClusters = function(zoomDirection,recursive,force,doNotStart) {
}
else {
// if a cluster takes up a set percentage of the active window
this._openClustersBySize();
//this._openClustersBySize();
this._openClusters(recursive, false);
}
}
this._updateNodeIndexList();
// 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._updateNodeIndexList();
}
// we now reduce chains.
if (this.previousScale > this.scale || zoomDirection == -1) { // zoom out
if (detectedZoomingOut == true || zoomDirection == -1) { // zoom out
this.handleChains();
this._updateNodeIndexList();
}
this.previousScale = this.scale;
// rest of the update the index list, dynamic edges and labels
this._updateDynamicEdges();
// update labels
this.updateLabels();
// 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
this._updateNodeIndexList();
this._updateCalculationNodes();
this._updateDynamicEdges();
this.updateLabels();
this._updateCalculationNodes();
// if a cluster was formed, we increase the clusterSession
if (this.nodeIndices.length != amountOfNodes) {
this.clusterSession += 1;
@ -260,13 +260,15 @@ exports.forceAggregateHubs = function(doNotStart) {
* @private
*/
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) {
// first check if node is a cluster
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 (parentNode.formationScale < this.scale || force == true) {
// 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.clusterSize -= childNode.clusterSize;
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
childNode.x = parentNode.x + parentNode.growthIndicator * (0.5 - Math.random());
@ -441,7 +442,9 @@ exports._repositionBezierNodes = function(node) {
*/
exports._formClusters = function(force) {
if (force == false) {
this._formClustersByZoom();
if (this.constants.clustering.clusterByZoom == true) {
this._formClustersByZoom();
}
}
else {
this._forceClustersByZoom();
@ -455,8 +458,8 @@ exports._formClusters = function(force) {
* @private
*/
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
// the clustering favours the node with the larger mass
@ -479,10 +482,10 @@ exports._formClustersByZoom = function() {
childNode = edge.from;
}
if (childNode.dynamicEdgesLength == 1) {
if (childNode.dynamicEdges.length == 1) {
this._addToCluster(parentNode,childNode,false);
}
else if (parentNode.dynamicEdgesLength == 1) {
else if (parentNode.dynamicEdges.length == 1) {
this._addToCluster(childNode,parentNode,false);
}
}
@ -505,8 +508,7 @@ exports._forceClustersByZoom = function() {
var childNode = this.nodes[nodeId];
// 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 parentNode = (edge.toId == childNode.id) ? this.nodes[edge.fromId] : this.nodes[edge.toId];
// group to the largest node
@ -588,14 +590,13 @@ exports._formClusterFromHub = function(hubNode, force, onlyEqual, absorptionSize
if (absorptionSizeOffset === undefined) {
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
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
var dx,dy,length;
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)) &&
(childNode.id != hubNode.id)) {
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
*
@ -774,7 +746,7 @@ exports._updateDynamicEdges = function() {
*/
exports._addToContainedEdges = function(parentNode, childNode, edge) {
// 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] = []
}
// add this edge to the list
@ -813,7 +785,6 @@ exports._connectEdgeToCluster = function(parentNode, childNode, edge) {
edge.toId = parentNode.id;
}
else { // edge connected to other node with the "from" side
edge.originalFromId.push(childNode.id);
edge.from = parentNode;
edge.fromId = parentNode.id;
@ -989,7 +960,7 @@ exports.updateLabels = function() {
// for (nodeId in this.nodes) {
// if (this.nodes.hasOwnProperty(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._updateDynamicEdges();
// if a cluster was formed, we increase the clusterSession
if (this.nodeIndices.length != amountOfNodes) {
this.clusterSession += 1;
@ -1090,11 +1060,11 @@ exports._getHubSize = function() {
for (var i = 0; i < this.nodeIndices.length; 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;
}
average = average / hubCounter;
@ -1128,7 +1098,7 @@ exports._reduceAmountOfChains = function(fraction) {
var reduceAmount = Math.floor(this.nodeIndices.length * fraction);
for (var nodeId in this.nodes) {
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) {
this._formClusterFromHub(this.nodes[nodeId],true,true,1);
reduceAmount -= 1;
@ -1149,7 +1119,7 @@ exports._getChainFraction = function() {
var total = 0;
for (var nodeId in this.nodes) {
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;
}
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 (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.");
this.zoomExtent(undefined,true,this.constants.clustering.enabled);
this.zoomExtent({duration:0},true,this.constants.clustering.enabled);
if (!this.constants.clustering.enabled) {
this.start();
}

+ 5
- 5
lib/network/mixins/ManipulationMixin.js View File

@ -15,7 +15,7 @@ exports._clearManipulatorBar = function() {
delete this.sectors['support']['nodes']['targetNode'];
delete this.sectors['support']['nodes']['targetViaNode'];
this.controlNodesActive = false;
this.freezeSimulation = false;
this.freezeSimulationEnabled = false;
};
/**
@ -84,7 +84,7 @@ exports._createManipulatorBar = function() {
this._restoreOverloadedFunctions();
// resume calculation
this.freezeSimulation = false;
this.freezeSimulationEnabled = false;
// reset global variables
this.blockConnectingEdgeSelection = false;
@ -254,7 +254,7 @@ exports._createAddEdgeToolbar = function() {
// clear the toolbar
this._clearManipulatorBar();
this._unselectAll(true);
this.freezeSimulation = true;
this.freezeSimulationEnabled = true;
if (this.boundFunction) {
this.off('select', this.boundFunction);
@ -385,7 +385,7 @@ exports._selectControlNode = function(pointer) {
this.selectedControlNode = this.edgeBeingEdited._getSelectedControlNode(this._XconvertDOMtoCanvas(pointer.x),this._YconvertDOMtoCanvas(pointer.y));
if (this.selectedControlNode !== null) {
this.selectedControlNode.select();
this.freezeSimulation = true;
this.freezeSimulationEnabled = true;
}
this._redraw();
};
@ -429,7 +429,7 @@ exports._releaseControlNode = function(pointer) {
else {
this.edgeBeingEdited._restoreControlNodes();
}
this.freezeSimulation = false;
this.freezeSimulationEnabled = false;
this._redraw();
};

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

@ -325,6 +325,9 @@ exports._loadPhysicsConfiguration = function () {
this.backupConstants = {};
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"];
this.physicsConfiguration = document.createElement('div');
this.physicsConfiguration.className = "PhysicsConfiguration";
@ -339,16 +342,16 @@ exports._loadPhysicsConfiguration = function () {
'<table id="graph_BH_table" style="display:none">' +
'<tr><td><b>Barnes Hut</b></td></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>' +
'<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>' +
'<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>' +
'<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>' +
'<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>' +

+ 42
- 0
lib/util.js View File

@ -13,6 +13,26 @@ exports.isNumber = function(object) {
return (object instanceof Number || typeof object == 'number');
};
/**
* this function gives you a range between 0 and 1 based on the min and max values in the set, the total sum of all values and the current value.
*
* @param min
* @param max
* @param total
* @param value
* @returns {number}
*/
exports.giveRange = function(min,max,total,value) {
if (max == min) {
return 0.5;
}
else {
var scale = 1 / (max - min);
return Math.max(0,(value - min)*scale);
}
}
/**
* Test whether given object is a string
* @param {*} object
@ -757,6 +777,28 @@ exports.hexToRGB = function(hex) {
} : 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

Loading…
Cancel
Save