Browse Source

Merge branch 'clusterRewrite' into v4

Conflicts:
	HISTORY.md
	dist/vis.js
	dist/vis.map
	dist/vis.min.css
	dist/vis.min.js
flowchartTest
Alex de Mulder 9 years ago
parent
commit
dc977a7386
35 changed files with 2503 additions and 1281 deletions
  1. +26
    -1
      HISTORY.md
  2. +6
    -0
      docs/graph2d.html
  3. +25
    -2
      docs/network.html
  4. +65
    -0
      examples/graph2d/19_labels.html
  5. +11
    -7
      examples/network/06_groups.html
  6. +0
    -1
      examples/network/25_physics_configuration.html
  7. +17
    -4
      examples/network/26_staticSmoothCurves.html
  8. +2
    -0
      examples/network/27_world_cup_network.html
  9. +166
    -0
      examples/network/38_node_as_icon.html
  10. +103
    -0
      examples/network/39_newClustering.html
  11. +1
    -0
      examples/network/index.html
  12. +24
    -1
      lib/DOMutil.js
  13. +65
    -21
      lib/network/Edge.js
  14. +46
    -19
      lib/network/Groups.js
  15. +182
    -98
      lib/network/Network.js
  16. +67
    -151
      lib/network/Node.js
  17. +4
    -1
      lib/network/Popup.js
  18. +432
    -933
      lib/network/mixins/ClusterMixin.js
  19. +1
    -4
      lib/network/mixins/HierarchicalLayoutMixin.js
  20. +1
    -2
      lib/network/mixins/MixinLoader.js
  21. +8
    -8
      lib/network/mixins/SelectionMixin.js
  22. +2
    -2
      lib/network/mixins/physics/BarnesHutMixin.js
  23. +1
    -1
      lib/network/mixins/physics/HierarchialRepulsionMixin.js
  24. +3
    -14
      lib/network/mixins/physics/PhysicsMixin.js
  25. +1
    -1
      lib/network/mixins/physics/RepulsionMixin.js
  26. +647
    -0
      lib/network/modules/ClusterEngine.js
  27. +33
    -0
      lib/network/modules/PhysicsEngine.js
  28. +409
    -0
      lib/network/modules/components/BarnesHutSolver.js
  29. +32
    -0
      lib/network/modules/components/CentralGravitySolver.js
  30. +101
    -0
      lib/network/modules/components/SpringSolver.js
  31. +3
    -3
      lib/timeline/Timeline.js
  32. +9
    -1
      lib/timeline/component/LineGraph.js
  33. +4
    -2
      lib/timeline/component/graph2d_types/bar.js
  34. +1
    -1
      lib/timeline/component/graph2d_types/points.js
  35. +5
    -3
      lib/util.js

+ 26
- 1
HISTORY.md View File

@ -22,8 +22,33 @@ http://visjs.org
- Fixed invalid css names for time axis grid, renamed hours class names from
`4-8h` to `h4-h8`.
### Network
- Rebuilt the cluster system
## not yet released, version 3.10.1-SNAPSHOT
### Network
- (added gradient coloring for lines, but set for release in 4.0 due to required refactoring of options)
- Fixed bug where a network that has frozen physics would resume redrawing after setData, setOptions etc.
- (add docs) Added option to bypass default groups. If more groups are specified in the nodes than there are in the groups, loop over supplied groups instead of default.
- (add docs) Added two new static smooth curves modes: curveCW and curve CCW.
- Added request redraw for certain internal processes to reduce number of draw calls.
- Added pull request for usage of Icons. Thanks @Dude9177!
- Allow hierarchical view to be set in setOptions.
### Graph2d
### Timeline
- Fixed not property initializing with a DataView for groups.
## not yet released, version 3.9.2-SNAPSHOT
## 2015-02-11, version 3.10.0
### Network

+ 6
- 0
docs/graph2d.html View File

@ -175,6 +175,12 @@ var items = [
<td>no</td>
<td>The ID of the group this point belongs to.</td>
</tr>
<tr>
<td>label</td>
<td>object</td>
<td>no</td>
<td>A label object which will be displayed near to the item. A label object has one requirement - a <b> content </b> property. In addition you can set the <b> xOffset, yOffset and className </b> for further appearance customisations </td>
</tr>
</table>
<h3 id="groups">Groups</h3>

+ 25
- 2
docs/network.html View File

@ -1009,7 +1009,6 @@ mySize = minSize + diff * scale;
<td>'white'</td>
<td>The color of the label stroke.</td>
</tr>
<tr>
<td class="greenField">shape</td>
<td>string</td>
@ -1018,7 +1017,7 @@ mySize = minSize + diff * scale;
Choose from
<code>ellipse</code> (default), <code>circle</code>, <code>box</code>,
<code>database</code>, <code>image</code>, <code>circularImage</code>, <code>label</code>, <code>dot</code>,
<code>star</code>, <code>triangle</code>, <code>triangleDown</code>, and <code>square</code>.
<code>star</code>, <code>triangle</code>, <code>triangleDown</code>, <code>square</code> and <code>icon</code>.
<br><br>
In case of <code>image</code> and <code>circularImage</code>, a property with name <code>image</code> must
@ -1095,6 +1094,30 @@ mySize = minSize + diff * scale;
<td>The maximum radius for a scaled node. Only applicable to shapes <code>dot</code>,
<code>star</code>, <code>triangle</code>, <code>triangleDown</code>, and <code>square</code>. This only does something if you supply a value.</td>
</tr>
<tr>
<td class="greenField">iconFontFace</td>
<td>String</td>
<td>undefined</td>
<td>Font face for icons, for example <code>FontAwesome</code> or <code>Ionicon</code>.<br /><em>You have to link to the css defining the font by yourself (see Examples)</em></td>
</tr>
<tr>
<td class="greenField">icon</td>
<td>String</td>
<td>undefined</td>
<td>Unicode of the icon f.e. <code>\uf0c0</code> (user-icon in FontAwesome)</td>
</tr>
<tr>
<td class="greenField">iconSize</td>
<td>Number</td>
<td>50</td>
<td>Size of the icon</td>
</tr>
<tr>
<td class="greenField">color</td>
<td>String</td>
<td>black</td>
<td>Color of the icon</td>
</tr>
</table>

+ 65
- 0
examples/graph2d/19_labels.html View File

@ -0,0 +1,65 @@
<!DOCTYPE HTML>
<html>
<head>
<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
<meta content="utf-8" http-equiv="encoding">
<title>Graph2d | Basic Example</title>
<style type="text/css">
body, html {
font-family: sans-serif;
}
.red {
fill:red;
}
</style>
<script src="../../dist/vis.js"></script>
<link href="../../dist/vis.css" rel="stylesheet" type="text/css" />
</head>
<body>
<h2>Graph2d | Label Example</h2>
<div style="width:700px; font-size:14px; text-align: justify;">
This example shows the how to add a label to each point in Graph2d. Each item can have a label object which contains the content and CSS class.In addition, xOffset and yOffset will adjust the location of the label relative to the point being labelled.
<br /><br />
</div>
<br />
<div id="visualization"></div>
<script type="text/javascript">
var container = document.getElementById('visualization');
var label1 = {
content: "offset label",
xOffset: 20,
yOffset: 20
}
var label2 = {
content: "Label2",
className: "red"
}
var items = [
{x: '2014-06-11', y: 10,label:label1},
{x: '2014-06-12', y: 25,label:label2},
{x: '2014-06-13', y: 30},
{x: '2014-06-14', y: 10},
{x: '2014-06-15', y: 15},
{x: '2014-06-16', y: 30}
];
var dataset = new vis.DataSet(items);
var options = {
start: '2014-06-10',
end: '2014-06-18',
style:'bar'
};
var graph2d = new vis.Graph2d(container, dataset, options);
</script>
</body>
</html>

+ 11
- 7
examples/network/06_groups.html View File

@ -8,9 +8,10 @@
font: 10pt arial;
}
#mynetwork {
width: 600px;
height: 600px;
width: 1900px;
height: 900px;
border: 1px solid lightgray;
background-color:#222222;
}
</style>
@ -139,11 +140,14 @@
edges: edges
};
var options = {
stabilize: false,
stabilize: true,
nodes: {
shape: 'dot'
shape: 'dot',
radius:30,
fontColor:'#ffffff',
borderWidth:2
},
physics: {barnesHut:{springLength: 200}}
physics: {barnesHut:{springLength: 100}}
};
network = new vis.Network(container, data, options);
}
@ -154,9 +158,9 @@
<body onload="draw()">
<form onsubmit= "javascript: draw(); return false;">
Number of groups:
<input type="text" value="6" id="groupCount" style="width: 50px;">
<input type="text" value="20" id="groupCount" style="width: 50px;">
Number of nodes per group:
<input type="text" value="7" id="nodeCount" style="width: 50px;">
<input type="text" value="1" id="nodeCount" style="width: 50px;">
<input type="submit" value="Go">
</form>
<br>

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

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

+ 17
- 4
examples/network/26_staticSmoothCurves.html View File

@ -32,19 +32,26 @@
Smooth curve type:
<select id="dropdownID">
<option value="continuous">continuous</option>
<option value="continuous" selected="selected">continuous</option>
<option value="discrete">discrete</option>
<option value="diagonalCross">diagonalCross</option>
<option value="straightCross">straightCross</option>
<option value="horizontal">horizontal</option>
<option value="vertical">vertical</option>
</select>
<option value="curvedCW">curvedCW</option>
<option value="curvedCCW">curvedCCW</option>
</select><br/>
Roundness (0..1): <input type="range" min="0" max="1" value="0.5" step="0.05" style="width:200px" id="roundnessSlider"> <input id="roundnessScreen" value="0.5"> (0.5 is max roundness for continuous, 1.0 for the others)
<div id="mynetwork"></div>
<script type="text/javascript">
var dropdown = document.getElementById("dropdownID");
dropdown.onchange = update;
var roundnessSlider = document.getElementById("roundnessSlider");
roundnessSlider.onchange = update;
var roundnessScreen = document.getElementById("roundnessScreen");
// create an array with nodes
var nodes = [
{id: 1, label: 'Node 1'},
@ -53,7 +60,7 @@ dropdown.onchange = update;
// create an array with edges
var edges = [
{from: 1, to: 2}
{from: 1, to: 2, style:"arrow"}
];
// create a network
@ -68,8 +75,14 @@ dropdown.onchange = update;
function update() {
var type = dropdown.value;
network.setOptions({smoothCurves:{type:type}});
var roundness = roundnessSlider.value;
roundnessScreen.value = roundness;
var options = {smoothCurves:{type:type, roundness:roundness}}
network.setOptions(options);
}
update();
</script>
</body>

+ 2
- 0
examples/network/27_world_cup_network.html View File

@ -39,6 +39,8 @@ Smooth curve type:
<option value="straightCross">straightCross</option>
<option value="horizontal">horizontal</option>
<option value="vertical">vertical</option>
<option value="curvedCW">curvedCW</option>
<option value="curvedCCW">curvedCCW</option>
</select><br/>
inheritColor option:
<select id="inheritColor">

+ 166
- 0
examples/network/38_node_as_icon.html View File

@ -0,0 +1,166 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Network | node as icon</title>
<script type="text/javascript" src="../../dist/vis.js"></script>
<link href="../../dist/vis.css" rel="stylesheet" type="text/css" />
<link rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css">
<link rel="stylesheet" href="http://code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css">
<script language="JavaScript">
function draw() {
/*
* Example for FontAwesome
*/
var optionsFA = {
height: '300px',
groups: {
usergroups: {
shape: 'icon',
iconFontFace: 'FontAwesome',
icon: '\uf0c0',
iconSize: 50,
iconColor: '#57169a'
},
users: {
shape: 'icon',
iconFontFace: 'FontAwesome',
icon: '\uf007',
iconSize: 50,
iconColor: '#aa00ff'
}
}
};
// create an array with nodes
var nodesFA = [{
id: 1,
label: 'User 1',
group: 'users'
}, {
id: 2,
label: 'User 2',
group: 'users'
}, {
id: 3,
label: 'Usergroup 1',
group: 'usergroups'
}, {
id: 4,
label: 'Usergroup 2',
group: 'usergroups'
}, {
id: 5,
label: 'Organisation 1',
shape: 'icon',
iconFontFace: 'FontAwesome',
icon: '\uf1ad',
iconSize: 50,
iconColor: '#f0a30a'
}];
// create an array with edges
var edges = [{
from: 1,
to: 3
}, {
from: 1,
to: 4
}, {
from: 2,
to: 4
}, {
from: 3,
to: 5
}, {
from: 4,
to: 5
}];
// create a network
var containerFA = document.getElementById('mynetworkFA');
var dataFA = {
nodes: nodesFA,
edges: edges
};
var networkFA = new vis.Network(containerFA, dataFA, optionsFA);
/*
* Example for Ionicons
*/
var optionsIO = {
height: '300px',
groups: {
usergroups: {
shape: 'icon',
iconFontFace: 'Ionicons',
icon: '\uf47c',
iconSize: 50,
iconColor: '#57169a'
},
users: {
shape: 'icon',
iconFontFace: 'Ionicons',
icon: '\uf47e',
iconSize: 50,
iconColor: '#aa00ff'
}
}
};
// create an array with nodes
var nodesIO = [{
id: 1,
label: 'User 1',
group: 'users'
}, {
id: 2,
label: 'User 2',
group: 'users'
}, {
id: 3,
label: 'Usergroup 1',
group: 'usergroups'
}, {
id: 4,
label: 'Usergroup 2',
group: 'usergroups'
}, {
id: 5,
label: 'Organisation 1',
shape: 'icon',
iconFontFace: 'Ionicons',
icon: '\uf276',
iconSize: 50,
iconColor: '#f0a30a'
}];
// create a network
var containerIO = document.getElementById('mynetworkIO');
var dataIO = {
nodes: nodesIO,
edges: edges
};
var networkIO = new vis.Network(containerIO, dataIO, optionsIO);
}
</script>
</head>
<body onload="draw()">
<h2>
<i class="fa fa-flag"></i> Use FontAwesome-icons for node</h2>
<div id="mynetworkFA"></div>
<h2>
<i class="ion ion-ionic"></i> Use Ionicons-icons for node</h2>
<div id="mynetworkIO"></div>
</body>
</html>

+ 103
- 0
examples/network/39_newClustering.html View File

@ -0,0 +1,103 @@
<!doctype html>
<html>
<head>
<title>Network | Basic usage</title>
<script type="text/javascript" src="../../dist/vis.js"></script>
<link href="../../dist/vis.css" rel="stylesheet" type="text/css" />
<style type="text/css">
#mynetwork {
width: 400px;
height: 400px;
border: 1px solid lightgray;
}
</style>
</head>
<body>
<div id="mynetwork"></div>
<script type="text/javascript">
// create an array with nodes
var nodes = [
{id: 1, label: 'Node 1'},
{id: 2, label: 'Node 2'},
{id: 3, label: 'Node 3'},
{id: 4, label: 'Node 4'},
{id: 5, label: 'Node 5'},
{id: 6, label: 'Node 6', cid:1},
{id: 7, label: 'Node 7', cid:1},
{id: 8, label: 'Node 8', cid:1},
{id: 9, label: 'Node 9', cid:1},
{id: 10, label: 'Node 10', cid:1}
];
// create an array with edges
var edges = [
{from: 1, to: 2},
{from: 1, to: 3},
{from: 10, to: 4},
{from: 2, to: 5},
{from: 6, to: 2},
{from: 7, to: 5},
{from: 8, to: 6},
{from: 9, to: 7},
{from: 10, to: 9}
];
// create a network
var container = document.getElementById('mynetwork');
var data = {
nodes: nodes,
edges: edges
};
var options = {};
var network = new vis.Network(container, data, options);
var clusterOptions = {
joinCondition:function(parentOptions,childOptions) {
return true;
},
processClusterProperties: function (properties, childNodes, childEdges) {
return properties;
},
clusterNodeProperties: {id:'bla', borderWidth:8},
}
var clusterOptionsByData = {
joinCondition:function(childOptions) {
return childOptions.cid == 1;
},
processClusterProperties: function (properties, childNodes, childEdges) {
return properties;
},
clusterNodeProperties: {id:'bla', borderWidth:8}
}
// network.clusterByNodeData(clusterOptionsByData)
// network.clusterOutliers({clusterNodeProperties: {borderWidth:8}})
network.clusterByConnection(2, clusterOptions);
// network.clusterByConnection(9, {
// joinCondition:function(parentOptions,childOptions) {return true;},
// processProperties:function (properties, childNodes, childEdges) {
// return properties;
// },
// clusterNodeProperties: {id:'bla2', label:"bla2", borderWidth:8}
// });
network.on("select", function(params) {
if (params.nodes.length == 1) {
if (network.isCluster(params.nodes[0]) == true) {
network.openCluster(params.nodes[0])
}
}
})
// network.openCluster('bla');
// network.openCluster('bla2');
</script>
</body>
</html>

+ 1
- 0
examples/network/index.html View File

@ -49,6 +49,7 @@
<p><a href="35_label_stroke.html">35_label_stroke.html</a></p>
<p><a href="36_HTML_in_Nodes.html">36_HTML_in_Nodes.html</a></p>
<p><a href="37_label_alignment.html">37_label_alignment.html</a></p>
<p><a href="38_node_as_icon.html">38_node_as_icon.html</a></p>
<p><a href="graphviz/graphviz_gallery.html">graphviz_gallery.html</a></p>
</div>

+ 24
- 1
lib/DOMutil.js View File

@ -130,9 +130,10 @@ exports.getDOMElement = function (elementType, JSONcontainer, DOMContainer, inse
* @param group
* @param JSONcontainer
* @param svgContainer
* @param labelObj
* @returns {*}
*/
exports.drawPoint = function(x, y, group, JSONcontainer, svgContainer) {
exports.drawPoint = function(x, y, group, JSONcontainer, svgContainer, labelObj) {
var point;
if (group.options.drawPoints.style == 'circle') {
point = exports.getSVGElement('circle',JSONcontainer,svgContainer);
@ -152,6 +153,28 @@ exports.drawPoint = function(x, y, group, JSONcontainer, svgContainer) {
point.setAttributeNS(null, "style", group.group.options.drawPoints.styles);
}
point.setAttributeNS(null, "class", group.className + " point");
//handle label
var label = exports.getSVGElement('text',JSONcontainer,svgContainer);
if (labelObj){
if (labelObj.xOffset) {
x = x + labelObj.xOffset;
}
if (labelObj.yOffset) {
y = y + labelObj.yOffset;
}
if (labelObj.content) {
label.textContent = labelObj.content;
}
if (labelObj.className) {
label.setAttributeNS(null, "class", labelObj.className + " label");
}
}
label.setAttributeNS(null, "x", x);
label.setAttributeNS(null, "y", y);
return point;
};

+ 65
- 21
lib/network/Edge.js View File

@ -23,6 +23,7 @@ function Edge (properties, network, networkConstants) {
var fields = ['edges','physics'];
var constants = util.selectiveBridgeObject(fields,networkConstants);
this.options = constants.edges;
this.physics = constants.physics;
this.options['smoothCurves'] = networkConstants['smoothCurves'];
@ -46,13 +47,13 @@ function Edge (properties, network, networkConstants) {
this.to = null; // a node
this.via = null; // a temp node
this.fromBackup = null; // used to clean up after reconnect
this.toBackup = null;; // used to clean up after reconnect
this.fromBackup = null; // used to clean up after reconnect (used for manipulation)
this.toBackup = null; // used to clean up after reconnect (used for manipulation)
// we use this to be able to reconnect the edge to a cluster if its node is put into a cluster
// by storing the original information we can revert to the original connection when the cluser is opened.
this.originalFromId = [];
this.originalToId = [];
this.fromArray = [];
this.toArray = [];
this.connected = false;
@ -76,10 +77,11 @@ Edge.prototype.setProperties = function(properties) {
if (!properties) {
return;
}
this.properties = properties;
var fields = ['style','fontSize','fontFace','fontColor','fontFill','fontStrokeWidth','fontStrokeColor','width',
'widthSelectionMultiplier','hoverWidth','arrowScaleFactor','dash','inheritColor','labelAlignment', 'opacity',
'customScalingFunction'
'customScalingFunction','useGradients','value'
];
util.selectiveDeepExtend(fields, this.options, properties);
@ -135,9 +137,9 @@ Edge.prototype.connect = function () {
this.from = this.network.nodes[this.fromId] || null;
this.to = this.network.nodes[this.toId] || null;
this.connected = (this.from && this.to);
this.connected = (this.from !== null && this.to !== null);
if (this.connected) {
if (this.connected === true) {
this.from.attachEdge(this);
this.to.attachEdge(this);
}
@ -234,9 +236,32 @@ Edge.prototype.isOverlappingWith = function(obj) {
}
};
Edge.prototype._getColor = function() {
Edge.prototype._getColor = function(ctx) {
var colorObj = this.options.color;
if (this.options.useGradients == true) {
var grd = ctx.createLinearGradient(this.from.x, this.from.y, this.to.x, this.to.y);
var fromColor, toColor;
fromColor = this.from.options.color.highlight.border;
toColor = this.to.options.color.highlight.border;
if (this.from.selected == false && this.to.selected == false) {
fromColor = util.overrideOpacity(this.from.options.color.border, this.options.opacity);
toColor = util.overrideOpacity(this.to.options.color.border, this.options.opacity);
}
else if (this.from.selected == true && this.to.selected == false) {
toColor = this.to.options.color.border;
}
else if (this.from.selected == false && this.to.selected == true) {
fromColor = this.from.options.color.border;
}
grd.addColorStop(0, fromColor);
grd.addColorStop(1, toColor);
return grd;
}
if (this.colorDirty === true) {
if (this.options.inheritColor == "to") {
colorObj = {
highlight: this.to.options.color.highlight.border,
@ -255,6 +280,8 @@ Edge.prototype._getColor = function() {
this.colorDirty = false;
}
if (this.selected == true) {return colorObj.highlight;}
else if (this.hover == true) {return colorObj.hover;}
else {return colorObj.color;}
@ -270,7 +297,7 @@ Edge.prototype._getColor = function() {
*/
Edge.prototype._drawLine = function(ctx) {
// set style
ctx.strokeStyle = this._getColor();
ctx.strokeStyle = this._getColor(ctx);
ctx.lineWidth = this._getLineWidth();
if (this.from != this.to) {
@ -344,7 +371,6 @@ Edge.prototype._getViaCoordinates = function () {
var yVia = null;
var factor = this.options.smoothCurves.roundness;
var type = this.options.smoothCurves.type;
var dx = Math.abs(this.from.x - this.to.x);
var dy = Math.abs(this.from.y - this.to.y);
if (type == 'discrete' || type == 'diagonalCross') {
@ -437,17 +463,39 @@ Edge.prototype._getViaCoordinates = function () {
yVia = this.to.y + (1 - factor) * dy;
}
}
else if (type == 'curvedCW') {
var dx = this.to.x - this.from.x;
var dy = this.from.y - this.to.y;
var radius = Math.sqrt(dx*dx + dy*dy);
var pi = Math.PI;
var originalAngle = Math.atan2(dy,dx);
var myAngle = (originalAngle + ((factor * 0.5) + 0.5) * pi) % (2 * pi);
xVia = this.from.x + (factor*0.5 + 0.5)*radius*Math.sin(myAngle);
yVia = this.from.y + (factor*0.5 + 0.5)*radius*Math.cos(myAngle);
}
else if (type == 'curvedCCW') {
var dx = this.to.x - this.from.x;
var dy = this.from.y - this.to.y;
var radius = Math.sqrt(dx*dx + dy*dy);
var pi = Math.PI;
var originalAngle = Math.atan2(dy,dx);
var myAngle = (originalAngle + ((-factor * 0.5) + 0.5) * pi) % (2 * pi);
xVia = this.from.x + (factor*0.5 + 0.5)*radius*Math.sin(myAngle);
yVia = this.from.y + (factor*0.5 + 0.5)*radius*Math.cos(myAngle);
}
else { // continuous
if (Math.abs(this.from.x - this.to.x) < Math.abs(this.from.y - this.to.y)) {
if (this.from.y > this.to.y) {
if (this.from.x < this.to.x) {
// console.log(1)
xVia = this.from.x + factor * dy;
yVia = this.from.y - factor * dy;
xVia = this.to.x < xVia ? this.to.x : xVia;
}
else if (this.from.x > this.to.x) {
// console.log(2)
xVia = this.from.x - factor * dy;
yVia = this.from.y - factor * dy;
xVia = this.to.x > xVia ? this.to.x : xVia;
@ -455,13 +503,11 @@ Edge.prototype._getViaCoordinates = function () {
}
else if (this.from.y < this.to.y) {
if (this.from.x < this.to.x) {
// console.log(3)
xVia = this.from.x + factor * dy;
yVia = this.from.y + factor * dy;
xVia = this.to.x < xVia ? this.to.x : xVia;
}
else if (this.from.x > this.to.x) {
// console.log(4, this.from.x, this.to.x)
xVia = this.from.x - factor * dy;
yVia = this.from.y + factor * dy;
xVia = this.to.x > xVia ? this.to.x : xVia;
@ -471,13 +517,11 @@ Edge.prototype._getViaCoordinates = function () {
else if (Math.abs(this.from.x - this.to.x) > Math.abs(this.from.y - this.to.y)) {
if (this.from.y > this.to.y) {
if (this.from.x < this.to.x) {
// console.log(5)
xVia = this.from.x + factor * dx;
yVia = this.from.y - factor * dx;
yVia = this.to.y > yVia ? this.to.y : yVia;
}
else if (this.from.x > this.to.x) {
// console.log(6)
xVia = this.from.x - factor * dx;
yVia = this.from.y - factor * dx;
yVia = this.to.y > yVia ? this.to.y : yVia;
@ -485,13 +529,11 @@ Edge.prototype._getViaCoordinates = function () {
}
else if (this.from.y < this.to.y) {
if (this.from.x < this.to.x) {
// console.log(7)
xVia = this.from.x + factor * dx;
yVia = this.from.y + factor * dx;
yVia = this.to.y < yVia ? this.to.y : yVia;
}
else if (this.from.x > this.to.x) {
// console.log(8)
xVia = this.from.x - factor * dx;
yVia = this.from.y + factor * dx;
yVia = this.to.y < yVia ? this.to.y : yVia;
@ -527,6 +569,8 @@ Edge.prototype._line = function (ctx) {
// this.via.y = via.y;
ctx.quadraticCurveTo(via.x,via.y,this.to.x, this.to.y);
ctx.stroke();
//ctx.circle(via.x,via.y,2)
//ctx.stroke();
return via;
}
}
@ -715,7 +759,7 @@ Edge.prototype._drawLabelText = function(ctx, x, yLine, lines, lineCount, fontSi
*/
Edge.prototype._drawDashLine = function(ctx) {
// set style
ctx.strokeStyle = this._getColor();
ctx.strokeStyle = this._getColor(ctx);
ctx.lineWidth = this._getLineWidth();
var via = null;
@ -820,7 +864,7 @@ Edge.prototype._pointOnCircle = function (x, y, radius, percentage) {
Edge.prototype._drawArrowCenter = function(ctx) {
var point;
// set style
ctx.strokeStyle = this._getColor();
ctx.strokeStyle = this._getColor(ctx);
ctx.fillStyle = ctx.strokeStyle;
ctx.lineWidth = this._getLineWidth();
@ -956,7 +1000,7 @@ Edge.prototype._findBorderPosition = function(from,ctx) {
*/
Edge.prototype._drawArrow = function(ctx) {
// set style
ctx.strokeStyle = this._getColor();
ctx.strokeStyle = this._getColor(ctx);
ctx.fillStyle = ctx.strokeStyle;
ctx.lineWidth = this._getLineWidth();

+ 46
- 19
lib/network/Groups.js View File

@ -7,6 +7,9 @@ var util = require('../util');
function Groups() {
this.clear();
this.defaultIndex = 0;
this.groupsArray = [];
this.groupIndex = 0;
this.useDefaultGroups = true;
}
@ -14,16 +17,29 @@ function Groups() {
* default constants for group colors
*/
Groups.DEFAULT = [
{border: "#2B7CE9", background: "#97C2FC", highlight: {border: "#2B7CE9", background: "#D2E5FF"}, hover: {border: "#2B7CE9", background: "#D2E5FF"}}, // blue
{border: "#FFA500", background: "#FFFF00", highlight: {border: "#FFA500", background: "#FFFFA3"}, hover: {border: "#FFA500", background: "#FFFFA3"}}, // yellow
{border: "#FA0A10", background: "#FB7E81", highlight: {border: "#FA0A10", background: "#FFAFB1"}, hover: {border: "#FA0A10", background: "#FFAFB1"}}, // red
{border: "#41A906", background: "#7BE141", highlight: {border: "#41A906", background: "#A1EC76"}, hover: {border: "#41A906", background: "#A1EC76"}}, // green
{border: "#E129F0", background: "#EB7DF4", highlight: {border: "#E129F0", background: "#F0B3F5"}, hover: {border: "#E129F0", background: "#F0B3F5"}}, // magenta
{border: "#7C29F0", background: "#AD85E4", highlight: {border: "#7C29F0", background: "#D3BDF0"}, hover: {border: "#7C29F0", background: "#D3BDF0"}}, // purple
{border: "#C37F00", background: "#FFA807", highlight: {border: "#C37F00", background: "#FFCA66"}, hover: {border: "#C37F00", background: "#FFCA66"}}, // orange
{border: "#4220FB", background: "#6E6EFD", highlight: {border: "#4220FB", background: "#9B9BFD"}, hover: {border: "#4220FB", background: "#9B9BFD"}}, // darkblue
{border: "#FD5A77", background: "#FFC0CB", highlight: {border: "#FD5A77", background: "#FFD1D9"}, hover: {border: "#FD5A77", background: "#FFD1D9"}}, // pink
{border: "#4AD63A", background: "#C2FABC", highlight: {border: "#4AD63A", background: "#E6FFE3"}, hover: {border: "#4AD63A", background: "#E6FFE3"}} // mint
{border: "#2B7CE9", background: "#97C2FC", highlight: {border: "#2B7CE9", background: "#D2E5FF"}, hover: {border: "#2B7CE9", background: "#D2E5FF"}}, // 0: blue
{border: "#FFA500", background: "#FFFF00", highlight: {border: "#FFA500", background: "#FFFFA3"}, hover: {border: "#FFA500", background: "#FFFFA3"}}, // 1: yellow
{border: "#FA0A10", background: "#FB7E81", highlight: {border: "#FA0A10", background: "#FFAFB1"}, hover: {border: "#FA0A10", background: "#FFAFB1"}}, // 2: red
{border: "#41A906", background: "#7BE141", highlight: {border: "#41A906", background: "#A1EC76"}, hover: {border: "#41A906", background: "#A1EC76"}}, // 3: green
{border: "#E129F0", background: "#EB7DF4", highlight: {border: "#E129F0", background: "#F0B3F5"}, hover: {border: "#E129F0", background: "#F0B3F5"}}, // 4: magenta
{border: "#7C29F0", background: "#AD85E4", highlight: {border: "#7C29F0", background: "#D3BDF0"}, hover: {border: "#7C29F0", background: "#D3BDF0"}}, // 5: purple
{border: "#C37F00", background: "#FFA807", highlight: {border: "#C37F00", background: "#FFCA66"}, hover: {border: "#C37F00", background: "#FFCA66"}}, // 6: orange
{border: "#4220FB", background: "#6E6EFD", highlight: {border: "#4220FB", background: "#9B9BFD"}, hover: {border: "#4220FB", background: "#9B9BFD"}}, // 7: darkblue
{border: "#FD5A77", background: "#FFC0CB", highlight: {border: "#FD5A77", background: "#FFD1D9"}, hover: {border: "#FD5A77", background: "#FFD1D9"}}, // 8: pink
{border: "#4AD63A", background: "#C2FABC", highlight: {border: "#4AD63A", background: "#E6FFE3"}, hover: {border: "#4AD63A", background: "#E6FFE3"}}, // 9: mint
{border: "#990000", background: "#EE0000", highlight: {border: "#BB0000", background: "#FF3333"}, hover: {border: "#BB0000", background: "#FF3333"}}, // 10:bright red
{border: "#FF6000", background: "#FF6000", highlight: {border: "#FF6000", background: "#FF6000"}, hover: {border: "#FF6000", background: "#FF6000"}}, // 12: real orange
{border: "#97C2FC", background: "#2B7CE9", highlight: {border: "#D2E5FF", background: "#2B7CE9"}, hover: {border: "#D2E5FF", background: "#2B7CE9"}}, // 13: blue
{border: "#399605", background: "#255C03", highlight: {border: "#399605", background: "#255C03"}, hover: {border: "#399605", background: "#255C03"}}, // 14: green
{border: "#B70054", background: "#FF007E", highlight: {border: "#B70054", background: "#FF007E"}, hover: {border: "#B70054", background: "#FF007E"}}, // 15: magenta
{border: "#AD85E4", background: "#7C29F0", highlight: {border: "#D3BDF0", background: "#7C29F0"}, hover: {border: "#D3BDF0", background: "#7C29F0"}}, // 16: purple
{border: "#4557FA", background: "#000EA1", highlight: {border: "#6E6EFD", background: "#000EA1"}, hover: {border: "#6E6EFD", background: "#000EA1"}}, // 17: darkblue
{border: "#FFC0CB", background: "#FD5A77", highlight: {border: "#FFD1D9", background: "#FD5A77"}, hover: {border: "#FFD1D9", background: "#FD5A77"}}, // 18: pink
{border: "#C2FABC", background: "#74D66A", highlight: {border: "#E6FFE3", background: "#74D66A"}, hover: {border: "#E6FFE3", background: "#74D66A"}}, // 19: mint
{border: "#EE0000", background: "#990000", highlight: {border: "#FF3333", background: "#BB0000"}, hover: {border: "#FF3333", background: "#BB0000"}}, // 20:bright red
];
@ -54,12 +70,22 @@ Groups.prototype.clear = function () {
Groups.prototype.get = function (groupname) {
var group = this.groups[groupname];
if (group == undefined) {
// create new group
var index = this.defaultIndex % Groups.DEFAULT.length;
this.defaultIndex++;
group = {};
group.color = Groups.DEFAULT[index];
this.groups[groupname] = group;
if (this.useDefaultGroups === false && this.groupsArray.length > 0) {
// create new group
var index = this.groupIndex % this.groupsArray.length;
this.groupIndex++;
group = {};
group.color = this.groups[this.groupsArray[index]];
this.groups[groupname] = group;
}
else {
// create new group
var index = this.defaultIndex % Groups.DEFAULT.length;
this.defaultIndex++;
group = {};
group.color = Groups.DEFAULT[index];
this.groups[groupname] = group;
}
}
return group;
@ -67,13 +93,14 @@ Groups.prototype.get = function (groupname) {
/**
* Add a custom group style
* @param {String} groupname
* @param {String} groupName
* @param {Object} style An object containing borderColor,
* backgroundColor, etc.
* @return {Object} group The created group object
*/
Groups.prototype.add = function (groupname, style) {
this.groups[groupname] = style;
Groups.prototype.add = function (groupName, style) {
this.groups[groupName] = style;
this.groupsArray.push(groupName);
return style;
};

+ 182
- 98
lib/network/Network.js View File

@ -45,7 +45,6 @@ function Network (container, data, options) {
this.renderRefreshRate = 60; // hz (fps)
this.renderTimestep = 1000 / this.renderRefreshRate; // ms -- saves calculation later on
this.renderTime = 0; // measured time it takes to render a frame
this.physicsTime = 0; // measured time it takes to render a frame
this.runDoubleSpeed = false;
this.physicsDiscreteStepsize = 0.50; // discrete stepsize of the simulation
@ -85,6 +84,7 @@ function Network (container, data, options) {
fontSizeMin: 14,
fontSizeMax: 30,
fontSizeMaxVisible: 30,
value: 1,
level: -1,
color: {
border: '#2B7CE9',
@ -109,6 +109,7 @@ function Network (container, data, options) {
width: 1,
widthSelectionMultiplier: 2,
hoverWidth: 1.5,
value:1,
style: 'line',
color: {
color:'#848484',
@ -129,7 +130,8 @@ function Network (container, data, options) {
gap: 5,
altLength: undefined
},
inheritColor: "from" // to, from, false, true (== from)
inheritColor: "from", // to, from, false, true (== from)
useGradients: false // release in 4.0
},
configurePhysics:false,
physics: {
@ -163,26 +165,7 @@ function Network (container, data, options) {
springConstant: null
},
clustering: { // Per Node in Cluster = PNiC
enabled: false, // (Boolean) | global on/off switch for clustering.
initialMaxNodes: 100, // (# nodes) | if the initial amount of nodes is larger than this, we cluster until the total number is less than this threshold.
clusterThreshold:500, // (# nodes) | during calculate forces, we check if the total number of nodes is larger than this. If it is, cluster until reduced to reduceToNodes
reduceToNodes:300, // (# nodes) | during calculate forces, we check if the total number of nodes is larger than clusterThreshold. If it is, cluster until reduced to this
chainThreshold: 0.4, // (% of all drawn nodes)| maximum percentage of allowed chainnodes (long strings of connected nodes) within all nodes. (lower means less chains).
clusterEdgeThreshold: 20, // (px) | edge length threshold. if smaller, this node is clustered.
sectorThreshold: 100, // (# nodes in cluster) | cluster size threshold. If larger, expanding in own sector.
screenSizeThreshold: 0.2, // (% of canvas) | relative size threshold. If the width or height of a clusternode takes up this much of the screen, decluster node.
fontSizeMultiplier: 4.0, // (px PNiC) | how much the cluster font size grows per node in cluster (in px).
maxFontSize: 1000,
forceAmplification: 0.1, // (multiplier PNiC) | factor of increase fo the repulsion force of a cluster (per node in cluster).
distanceAmplification: 0.1, // (multiplier PNiC) | factor how much the repulsion distance of a cluster increases (per node in cluster).
edgeGrowth: 20, // (px PNiC) | amount of clusterSize connected to the edge is multiplied with this and added to edgeLength.
nodeScaling: {width: 1, // (px PNiC) | growth of the width 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.
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, // used for normalization of the cluster levels
clusterByZoom: true // enable clustering through zooming in and out
enabled: false // (Boolean) | global on/off switch for clustering.
},
navigation: {
enabled: false
@ -214,6 +197,7 @@ function Network (container, data, options) {
minVelocity: 0.1, // px/s
stabilize: true, // stabilize before displaying the network
stabilizationIterations: 1000, // maximum number of iteration to stabilize
stabilizationStepsize: 100,
zoomExtentOnStabilize: true,
locale: 'en',
locales: locales,
@ -235,7 +219,8 @@ function Network (container, data, options) {
hideNodesOnDrag: false,
width : '100%',
height : '100%',
selectable: true
selectable: true,
useDefaultGroups: true
};
this.constants = util.extend({}, this.defaultOptions);
this.pixelRatio = 1;
@ -257,13 +242,14 @@ function Network (container, data, options) {
this.lockedOnNodeId = null;
this.lockedOnNodeOffset = null;
this.touchTime = 0;
this.redrawRequested = false;
// Node variables
var network = this;
this.groups = new Groups(); // object with groups
this.images = new Images(); // object with images
this.images.setOnloadCallback(function (status) {
network._redraw();
network._requestRedraw();
});
// keyboard navigation variables
@ -300,6 +286,14 @@ function Network (container, data, options) {
this.draggingNodes = false;
// containers for nodes and edges
this.body = {
calculationNodes: {},
calculationNodeIndices: {},
nodeIndices: {},
nodes: {},
edges: {}
}
this.calculationNodes = {};
this.calculationNodeIndices = [];
this.nodeIndices = []; // array with all the indices of the nodes. Used to speed up forces calculation
@ -310,9 +304,7 @@ function Network (container, data, options) {
this.canvasTopLeft = {"x": 0,"y": 0}; // coordinates of the top left of the canvas. they will be set during _redraw.
this.canvasBottomRight = {"x": 0,"y": 0}; // coordinates of the bottom right of the canvas. they will be set during _redraw
this.pointerPosition = {"x": 0,"y": 0}; // coordinates of the bottom right of the canvas. they will be set during _redraw
this.areaCenter = {}; // object with x and y elements used for determining the center of the zoom action
this.scale = 1; // defining the global scale variable in the constructor
this.previousScale = this.scale; // this is used to check if the zoom operation is zooming in or out
// datasets or dataviews
this.nodesData = null; // A DataSet or DataView
@ -353,10 +345,9 @@ function Network (container, data, options) {
this.timer = undefined; // Scheduling function. Is definded in this.start();
// load data (the disable start variable will be the same as the enabled clustering)
this.setData(data,this.constants.clustering.enabled || this.constants.hierarchicalLayout.enabled);
this.setData(data, this.constants.hierarchicalLayout.enabled);
// hierarchical layout
this.initializing = false;
if (this.constants.hierarchicalLayout.enabled == true) {
this._setupHierarchicalLayout();
}
@ -367,10 +358,11 @@ function Network (container, data, options) {
}
}
// if clustering is disabled, the simulation will have started in the setData function
if (this.constants.clustering.enabled) {
this.startWithClustering();
if (this.constants.stabilize == false) {
this.initializing = false;
}
this.on("stabilizationIterationsDone", function () {this.initializing = false; this.start();}.bind(this));
}
// Extend Network with an Emitter mixin
@ -580,11 +572,7 @@ Network.prototype.zoomExtent = function(options, initialZoom, disableStart) {
*/
Network.prototype._updateNodeIndexList = function() {
this._clearNodeIndexList();
for (var idx in this.nodes) {
if (this.nodes.hasOwnProperty(idx)) {
this.nodeIndices.push(idx);
}
}
this.nodeIndices = Object.keys(this.nodes);
};
@ -644,6 +632,7 @@ Network.prototype.setData = function(data, disableStart) {
this._setEdges(data && data.edges);
}
this._putDataInSector();
if (disableStart == false) {
if (this.constants.hierarchicalLayout.enabled == true) {
this._resetLevels();
@ -654,10 +643,15 @@ Network.prototype.setData = function(data, disableStart) {
if (this.constants.stabilize == true) {
this._stabilize();
}
else {
this.moving = true;
this.start();
}
}
this.start();
}
this.initializing = false;
else {
this.initializing = false;
}
};
/**
@ -675,6 +669,7 @@ Network.prototype.setOptions = function (options) {
util.selectiveNotDeepExtend(['color'],this.constants.nodes, options.nodes);
util.selectiveNotDeepExtend(['color','length'],this.constants.edges, options.edges);
this.groups.useDefaultGroups = this.constants.useDefaultGroups;
if (options.physics) {
util.mergeOptions(this.constants.physics, options.physics,'barnesHut');
util.mergeOptions(this.constants.physics, options.physics,'repulsion');
@ -785,7 +780,6 @@ Network.prototype.setOptions = function (options) {
throw new Error('Option "labels" is deprecated. Use options "locale" and "locales" instead.');
}
// (Re)loading the mixins that can be enabled or disabled in the options.
// load the force calculation functions, grouped under the physics system.
this._loadPhysicsSystem();
@ -804,8 +798,15 @@ Network.prototype.setOptions = function (options) {
this._markAllEdgesAsDirty();
this.setSize(this.constants.width, this.constants.height);
this.moving = true;
this.start();
if (this.constants.hierarchicalLayout.enabled == true && this.initializing == false) {
this._resetLevels();
this._setupHierarchicalLayout();
}
if (this.initializing !== true) {
this.moving = true;
this.start();
}
}
};
@ -830,7 +831,6 @@ Network.prototype._create = function () {
this.frame.style.overflow = 'hidden';
this.frame.tabIndex = 900;
//////////////////////////////////////////////////////////////////
this.frame.canvas = document.createElement("canvas");
@ -945,10 +945,6 @@ 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");
if (this.constants.dataManipulation.enabled == true) {
this.keycharm.bind("esc",this._createManipulatorBar.bind(me));
@ -1284,7 +1280,6 @@ Network.prototype._zoom = function(scale, pointer) {
this._setScale(scale);
this._setTranslation(tx, ty);
this.updateClustersDefault();
if (preScaleDragPointer != null) {
var postScaleDragPointer = this.canvasToDOM(preScaleDragPointer);
@ -1358,10 +1353,20 @@ Network.prototype._onMouseWheel = function(event) {
Network.prototype._onMouseMoveTitle = function (event) {
var gesture = hammerUtil.fakeGesture(this, event);
var pointer = this._getPointer(gesture.center);
var popupVisible = false;
// check if the previously selected node is still selected
if (this.popupObj) {
this._checkHidePopup(pointer);
if (this.popup !== undefined) {
if (this.popup.hidden === false) {
this._checkHidePopup(pointer);
}
// if the popup was not hidden above
if (this.popup.hidden === false) {
popupVisible = true;
this.popup.setPosition(pointer.x + 3,pointer.y - 5)
this.popup.show();
}
}
// if we bind the keyboard to the div, we have to highlight it to use it. This highlights it on mouse over
@ -1369,20 +1374,20 @@ Network.prototype._onMouseMoveTitle = function (event) {
this.frame.focus();
}
// start a timeout that will check if the mouse is positioned above
// an element
var me = this;
var checkShow = function() {
me._checkShowPopup(pointer);
};
if (this.popupTimer) {
clearInterval(this.popupTimer); // stop any running calculationTimer
}
if (!this.drag.dragging) {
this.popupTimer = setTimeout(checkShow, this.constants.tooltip.delay);
// start a timeout that will check if the mouse is positioned above an element
if (popupVisible === false) {
var me = this;
var checkShow = function () {
me._checkShowPopup(pointer);
};
if (this.popupTimer) {
clearInterval(this.popupTimer); // stop any running calculationTimer
}
if (!this.drag.dragging) {
this.popupTimer = setTimeout(checkShow, this.constants.tooltip.delay);
}
}
/**
* Adding hover highlights
*/
@ -1434,8 +1439,9 @@ Network.prototype._checkShowPopup = function (pointer) {
};
var id;
var lastPopupNode = this.popupObj;
var previousPopupObjId = this.popupObj === undefined ? "" : this.popupObj.id;
var nodeUnderCursor = false;
var popupType = "node";
if (this.popupObj == undefined) {
// search the nodes for overlap, select the top one in case of multiple nodes
@ -1468,7 +1474,7 @@ Network.prototype._checkShowPopup = function (pointer) {
for (id in edges) {
if (edges.hasOwnProperty(id)) {
var edge = edges[id];
if (edge.connected && (edge.getTitle() !== undefined) &&
if (edge.connected === true && (edge.getTitle() !== undefined) &&
edge.isOverlappingWith(obj)) {
overlappingEdges.push(id);
}
@ -1477,23 +1483,26 @@ Network.prototype._checkShowPopup = function (pointer) {
if (overlappingEdges.length > 0) {
this.popupObj = this.edges[overlappingEdges[overlappingEdges.length - 1]];
popupType = "edge";
}
}
if (this.popupObj) {
// show popup message window
if (this.popupObj != lastPopupNode) {
var me = this;
if (!me.popup) {
me.popup = new Popup(me.frame, me.constants.tooltip);
if (this.popupObj.id != previousPopupObjId) {
if (this.popup === undefined) {
this.popup = new Popup(this.frame, this.constants.tooltip);
}
this.popup.popupTargetType = popupType;
this.popup.popupTargetId = this.popupObj.id;
// adjust a small offset such that the mouse cursor is located in the
// bottom left location of the popup, and you can easily move over the
// popup area
me.popup.setPosition(pointer.x - 3, pointer.y - 3);
me.popup.setText(me.popupObj.getTitle());
me.popup.show();
this.popup.setPosition(pointer.x + 3, pointer.y - 5);
this.popup.setText(this.popupObj.getTitle());
this.popup.show();
}
}
else {
@ -1505,18 +1514,38 @@ Network.prototype._checkShowPopup = function (pointer) {
/**
* Check if the popup must be hided, which is the case when the mouse is no
* Check if the popup must be hidden, which is the case when the mouse is no
* longer hovering on the object
* @param {{x:Number, y:Number}} pointer
* @private
*/
Network.prototype._checkHidePopup = function (pointer) {
if (!this.popupObj || !this._getNodeAt(pointer) ) {
this.popupObj = undefined;
if (this.popup) {
this.popup.hide();
var pointerObj = {
left: this._XconvertDOMtoCanvas(pointer.x),
top: this._YconvertDOMtoCanvas(pointer.y),
right: this._XconvertDOMtoCanvas(pointer.x),
bottom: this._YconvertDOMtoCanvas(pointer.y)
};
var stillOnObj = false;
if (this.popup.popupTargetType == 'node') {
stillOnObj = this.nodes[this.popup.popupTargetId].isOverlappingWith(pointerObj);
if (stillOnObj === true) {
var overNode = this._getNodeAt(pointer);
stillOnObj = overNode.id == this.popup.popupTargetId;
}
}
else {
if (this._getNodeAt(pointer) === null) {
stillOnObj = this.edges[this.popup.popupTargetId].isOverlappingWith(pointerObj);
}
}
if (stillOnObj === false) {
this.popupObj = undefined;
this.popup.hide();
}
};
@ -1640,7 +1669,6 @@ Network.prototype._addNodes = function(ids) {
this._updateCalculationNodes();
this._reconnectEdges();
this._updateValueRange(this.nodes);
this.updateLabels();
};
/**
@ -1876,7 +1904,6 @@ Network.prototype._reconnectEdges = function() {
for (id in nodes) {
if (nodes.hasOwnProperty(id)) {
nodes[id].edges = [];
nodes[id].dynamicEdges = [];
}
}
@ -1940,7 +1967,23 @@ Network.prototype.redraw = function() {
* @param hidden | used to get the first estimate of the node sizes. only the nodes are drawn after which they are quickly drawn over.
* @private
*/
Network.prototype._redraw = function(hidden) {
Network.prototype._requestRedraw = function(hidden) {
if (this.redrawRequested !== true) {
this.redrawRequested = true;
if (this.requiresTimeout === true) {
window.setTimeout(this._redraw.bind(this, hidden),0);
}
else {
window.requestAnimationFrame(this._redraw.bind(this, hidden, true));
}
}
};
Network.prototype._redraw = function(hidden, requested) {
if (hidden === undefined) {
hidden = false;
}
this.redrawRequested = false;
var ctx = this.frame.canvas.getContext('2d');
ctx.setTransform(this.pixelRatio, 0, 0, this.pixelRatio, 0, 0);
@ -1964,7 +2007,7 @@ Network.prototype._redraw = function(hidden) {
"y": this._YconvertDOMtoCanvas(this.frame.canvas.clientHeight)
};
if (!(hidden == true)) {
if (hidden === false) {
this._doInAllSectors("_drawAllSectorNodes", ctx);
if (this.drag.dragging == false || this.drag.dragging === undefined || this.constants.hideEdgesOnDrag == false) {
this._doInAllSectors("_drawEdges", ctx);
@ -1975,22 +2018,22 @@ Network.prototype._redraw = function(hidden) {
this._doInAllSectors("_drawNodes",ctx,false);
}
if (!(hidden == true)) {
if (hidden === false) {
if (this.controlNodesActive == true) {
this._doInAllSectors("_drawControlNodes", ctx);
}
}
// this._doInSupportSector("_drawNodes",ctx,true);
//this._doInSupportSector("_drawNodes",ctx,true);
// this._drawTree(ctx,"#F00F0F");
// restore original scaling and translation
ctx.restore();
if (hidden == true) {
if (hidden === true) {
ctx.clearRect(0, 0, w, h);
}
};
}
/**
* Set the translation of the network
@ -2161,7 +2204,7 @@ Network.prototype._drawEdges = function(ctx) {
if (edges.hasOwnProperty(id)) {
var edge = edges[id];
edge.setScale(this.scale);
if (edge.connected) {
if (edge.connected === true) {
edges[id].draw(ctx);
}
}
@ -2191,19 +2234,29 @@ Network.prototype._stabilize = function() {
if (this.constants.freezeForStabilization == true) {
this._freezeDefinedNodes();
}
this.stabilizationSteps = 0;
// find stable position
setTimeout(this._stabilizationBatch.bind(this),0);
};
Network.prototype._stabilizationBatch = function() {
var count = 0;
while (this.moving && count < this.constants.stabilizationIterations) {
while (this.moving && count < this.constants.stabilizationStepsize && this.stabilizationSteps < this.constants.stabilizationIterations) {
this._physicsTick();
// TODO: cleanup
//if (count % 100 == 0) {
// console.log("stabilizationIterations",count);
//}
this.stabilizationSteps++;
count++;
}
if (this.moving && this.stabilizationSteps < this.constants.stabilizationIterations) {
this.emit("stabilizationProgress", {steps: this.stabilizationSteps, total: this.constants.stabilizationIterations});
setTimeout(this._stabilizationBatch.bind(this),0);
}
else {
this._finalizeStabilization();
}
}
Network.prototype._finalizeStabilization = function() {
if (this.constants.zoomExtentOnStabilize == true) {
this.zoomExtent({duration:0}, false, true);
}
@ -2213,7 +2266,7 @@ Network.prototype._stabilize = function() {
}
this.emit("stabilizationIterationsDone");
};
}
/**
* When initializing and stabilizing, we can freeze nodes with a predefined position. This greatly speeds up stabilization
@ -2381,12 +2434,18 @@ Network.prototype._animationStep = function() {
// reset the timer so a new scheduled animation step can be set
this.timer = undefined;
if (this.requiresTimeout == true) {
// this schedules a new animation step
this.start();
}
// handle the keyboad movement
this._handleNavigation();
// check if the physics have settled
if (this.moving == true) {
var startTime = Date.now();
this._physicsTick();
var physicsTime = Date.now() - startTime;
@ -2405,8 +2464,10 @@ Network.prototype._animationStep = function() {
this._redraw();
this.renderTime = Date.now() - renderStartTime;
// this schedules a new animation step
this.start();
if (this.requiresTimeout == false) {
// this schedules a new animation step
this.start();
}
};
if (typeof window !== 'undefined') {
@ -2418,6 +2479,9 @@ if (typeof window !== 'undefined') {
* Schedule a animation step with the refreshrate interval.
*/
Network.prototype.start = function() {
if (this.freezeSimulationEnabled == true) {
this.moving = false;
}
if (this.moving == true || this.xIncrement != 0 || this.yIncrement != 0 || this.zoomIncrement != 0 || this.animating == true) {
if (!this.timer) {
if (this.requiresTimeout == true) {
@ -2429,7 +2493,7 @@ Network.prototype.start = function() {
}
}
else {
this._redraw();
this._requestRedraw();
// this check is to ensure that the network does not emit these events if it was already stabilized and setOptions is called (setting moving to true and calling start())
if (this.stabilizationIterations > 1) {
// trigger the "stabilized" event.
@ -2535,11 +2599,14 @@ Network.prototype._configureSmoothCurves = function(disableStart) {
*
* @private
*/
Network.prototype._createBezierNodes = function() {
Network.prototype._createBezierNodes = function(specificEdges) {
if (specificEdges === undefined) {
specificEdges = this.edges;
}
if (this.constants.smoothCurves.enabled == true && this.constants.smoothCurves.dynamic == true) {
for (var edgeId in this.edges) {
if (this.edges.hasOwnProperty(edgeId)) {
var edge = this.edges[edgeId];
for (var edgeId in specificEdges) {
if (specificEdges.hasOwnProperty(edgeId)) {
var edge = specificEdges[edgeId];
if (edge.via == null) {
var nodeId = "edgeId:".concat(edge.id);
this.sectors['support']['nodes'][nodeId] = new Node(
@ -2880,4 +2947,21 @@ Network.prototype.getConnectedNodes = function(nodeId) {
return nodeList;
}
Network.prototype.getEdgesFromNode = function(nodeId) {
var edgesList = [];
if (this.nodes[nodeId] !== undefined) {
var node = this.nodes[nodeId];
for (var i = 0; i < node.edges.length; i++) {
edgesList.push(node.edges[i].id);
}
}
return edgesList;
}
Network.prototype.generateColorObject = function(color) {
return util.parseColor(color);
}
module.exports = Network;

+ 67
- 151
lib/network/Node.js View File

@ -13,7 +13,7 @@ var util = require('../util');
* "database", "circle", "ellipse",
* "box", "image", "text", "dot",
* "star", "triangle", "triangleDown",
* "square"
* "square", "icon"
* {string} image An image url
* {string} title An title text, can be HTML
* {anytype} group A group name or number
@ -33,8 +33,6 @@ function Node(properties, imagelist, grouplist, networkConstants) {
this.hover = false;
this.edges = []; // all edges connected to this node
this.dynamicEdges = [];
this.reroutedEdges = {};
// set defaults for the properties
this.id = undefined;
@ -72,15 +70,6 @@ function Node(properties, imagelist, grouplist, networkConstants) {
this.setProperties(properties, constants);
// creating the variables for clustering
this.resetCluster();
this.clusterSession = 0;
this.clusterSizeWidthFactor = networkConstants.clustering.nodeScaling.width;
this.clusterSizeHeightFactor = networkConstants.clustering.nodeScaling.height;
this.clusterSizeRadiusFactor = networkConstants.clustering.nodeScaling.radius;
this.maxNodeSizeIncrements = networkConstants.clustering.maxNodeSizeIncrements;
this.growthIndicator = 0;
// variables to tell the node about the network.
this.networkScaleInv = 1;
this.networkScale = 1;
@ -101,18 +90,6 @@ Node.prototype.revertPosition = function() {
}
/**
* (re)setting the clustering variables and objects
*/
Node.prototype.resetCluster = function() {
// clustering variables
this.formationScale = undefined; // this is used to determine when to open the cluster
this.clusterSize = 1; // this signifies the total amount of nodes in this cluster
this.containedNodes = {};
this.containedEdges = {};
this.clusterSessions = [];
};
/**
* Attach a edge to the node
* @param {Edge} edge
@ -121,9 +98,6 @@ Node.prototype.attachEdge = function(edge) {
if (this.edges.indexOf(edge) == -1) {
this.edges.push(edge);
}
if (this.dynamicEdges.indexOf(edge) == -1) {
this.dynamicEdges.push(edge);
}
};
/**
@ -135,10 +109,6 @@ Node.prototype.detachEdge = function(edge) {
if (index != -1) {
this.edges.splice(index, 1);
}
index = this.dynamicEdges.indexOf(edge);
if (index != -1) {
this.dynamicEdges.splice(index, 1);
}
};
@ -151,10 +121,12 @@ Node.prototype.setProperties = function(properties, constants) {
if (!properties) {
return;
}
this.properties = properties;
var fields = ['borderWidth','borderWidthSelected','shape','image','brokenImage','radius','fontColor',
'fontSize','fontFace','fontFill','fontStrokeWidth','fontStrokeColor','group','mass','fontDrawThreshold',
'scaleFontWithValue','fontSizeMaxVisible','customScalingFunction'
var fields = ['borderWidth', 'borderWidthSelected', 'shape', 'image', 'brokenImage', 'radius', 'fontColor',
'fontSize', 'fontFace', 'fontFill', 'fontStrokeWidth', 'fontStrokeColor', 'group', 'mass', 'fontDrawThreshold',
'scaleFontWithValue', 'fontSizeMaxVisible', 'customScalingFunction', 'iconFontFace', 'icon', 'iconColor', 'iconSize',
'value'
];
util.selectiveDeepExtend(fields, this.options, properties);
@ -235,6 +207,7 @@ Node.prototype.setProperties = function(properties, constants) {
case 'triangle': this.draw = this._drawTriangle; this.resize = this._resizeShape; break;
case 'triangleDown': this.draw = this._drawTriangleDown; this.resize = this._resizeShape; break;
case 'star': this.draw = this._drawStar; this.resize = this._resizeShape; break;
case 'icon': this.draw = this._drawIcon; this.resize = this._resizeIcon; break;
default: this.draw = this._drawEllipse; this.resize = this._resizeEllipse; break;
}
// reset the size of the node, this can be changed
@ -423,6 +396,7 @@ Node.prototype.discreteStepLimited = function(interval, maxVelocity) {
this.fy = 0;
this.vy = 0;
}
};
/**
@ -546,29 +520,11 @@ Node.prototype._resizeImage = function (ctx) {
}
this.width = width;
this.height = height;
this.growthIndicator = 0;
if (this.width > 0 && this.height > 0) {
this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeWidthFactor;
this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeHeightFactor;
this.options.radius+= Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeRadiusFactor;
this.growthIndicator = this.width - width;
}
}
};
Node.prototype._drawImageAtPosition = function (ctx) {
if (this.imageObj.width != 0 ) {
// draw the shade
if (this.clusterSize > 1) {
var lineWidth = ((this.clusterSize > 1) ? 10 : 0.0);
lineWidth *= this.networkScaleInv;
lineWidth = Math.min(0.2 * this.width,lineWidth);
ctx.globalAlpha = 0.5;
ctx.drawImage(this.imageObj, this.left - lineWidth, this.top - lineWidth, this.width + 2*lineWidth, this.height + 2*lineWidth);
}
// draw the image
ctx.globalAlpha = 1.0;
ctx.drawImage(this.imageObj, this.left, this.top, this.width, this.height);
@ -618,12 +574,6 @@ Node.prototype._resizeCircularImage = function (ctx) {
var diameter = this.options.radius * 2;
this.width = diameter;
this.height = diameter;
// scaling used for clustering
//this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeWidthFactor;
//this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeHeightFactor;
this.options.radius += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeRadiusFactor;
this.growthIndicator = this.options.radius- 0.5*diameter;
this._swapToImageResizeWhenImageLoaded = true;
}
}
@ -677,12 +627,6 @@ Node.prototype._resizeBox = function (ctx) {
var textSize = this.getTextSize(ctx);
this.width = textSize.width + 2 * margin;
this.height = textSize.height + 2 * margin;
this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeWidthFactor;
this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeHeightFactor;
this.growthIndicator = this.width - (textSize.width + 2 * margin);
// this.options.radius+= Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeRadiusFactor;
}
};
@ -692,22 +636,11 @@ Node.prototype._drawBox = function (ctx) {
this.left = this.x - this.width / 2;
this.top = this.y - this.height / 2;
var clusterLineWidth = 2.5;
var borderWidth = this.options.borderWidth;
var selectionLineWidth = this.options.borderWidthSelected || 2 * this.options.borderWidth;
ctx.strokeStyle = this.selected ? this.options.color.highlight.border : this.hover ? this.options.color.hover.border : this.options.color.border;
// draw the outer border
if (this.clusterSize > 1) {
ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
ctx.lineWidth *= this.networkScaleInv;
ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
ctx.roundRect(this.left-2*ctx.lineWidth, this.top-2*ctx.lineWidth, this.width+4*ctx.lineWidth, this.height+4*ctx.lineWidth, this.options.radius);
ctx.stroke();
}
ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth);
ctx.lineWidth *= this.networkScaleInv;
ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
@ -733,12 +666,6 @@ Node.prototype._resizeDatabase = function (ctx) {
var size = textSize.width + 2 * margin;
this.width = size;
this.height = size;
// scaling used for clustering
this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeWidthFactor;
this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeHeightFactor;
this.options.radius+= Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeRadiusFactor;
this.growthIndicator = this.width - size;
}
};
@ -747,22 +674,11 @@ Node.prototype._drawDatabase = function (ctx) {
this.left = this.x - this.width / 2;
this.top = this.y - this.height / 2;
var clusterLineWidth = 2.5;
var borderWidth = this.options.borderWidth;
var selectionLineWidth = this.options.borderWidthSelected || 2 * this.options.borderWidth;
ctx.strokeStyle = this.selected ? this.options.color.highlight.border : this.hover ? this.options.color.hover.border : this.options.color.border;
// draw the outer border
if (this.clusterSize > 1) {
ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
ctx.lineWidth *= this.networkScaleInv;
ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
ctx.database(this.x - this.width/2 - 2*ctx.lineWidth, this.y - this.height*0.5 - 2*ctx.lineWidth, this.width + 4*ctx.lineWidth, this.height + 4*ctx.lineWidth);
ctx.stroke();
}
ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth);
ctx.lineWidth *= this.networkScaleInv;
ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
@ -789,32 +705,16 @@ Node.prototype._resizeCircle = function (ctx) {
this.width = diameter;
this.height = diameter;
// scaling used for clustering
// this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeWidthFactor;
// this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeHeightFactor;
this.options.radius += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeRadiusFactor;
this.growthIndicator = this.options.radius- 0.5*diameter;
}
};
Node.prototype._drawRawCircle = function (ctx, x, y, radius) {
var clusterLineWidth = 2.5;
var borderWidth = this.options.borderWidth;
var selectionLineWidth = this.options.borderWidthSelected || 2 * this.options.borderWidth;
ctx.strokeStyle = this.selected ? this.options.color.highlight.border : this.hover ? this.options.color.hover.border : this.options.color.border;
// draw the outer border
if (this.clusterSize > 1) {
ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
ctx.lineWidth *= this.networkScaleInv;
ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
ctx.circle(x, y, radius+2*ctx.lineWidth);
ctx.stroke();
}
ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth);
ctx.lineWidth *= this.networkScaleInv;
ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
@ -849,12 +749,6 @@ Node.prototype._resizeEllipse = function (ctx) {
this.width = this.height;
}
var defaultSize = this.width;
// scaling used for clustering
this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeWidthFactor;
this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeHeightFactor;
this.options.radius += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeRadiusFactor;
this.growthIndicator = this.width - defaultSize;
}
};
@ -863,22 +757,12 @@ Node.prototype._drawEllipse = function (ctx) {
this.left = this.x - this.width / 2;
this.top = this.y - this.height / 2;
var clusterLineWidth = 2.5;
var borderWidth = this.options.borderWidth;
var selectionLineWidth = this.options.borderWidthSelected || 2 * this.options.borderWidth;
ctx.strokeStyle = this.selected ? this.options.color.highlight.border : this.hover ? this.options.color.hover.border : this.options.color.border;
// draw the outer border
if (this.clusterSize > 1) {
ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
ctx.lineWidth *= this.networkScaleInv;
ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
ctx.ellipse(this.left-2*ctx.lineWidth, this.top-2*ctx.lineWidth, this.width+4*ctx.lineWidth, this.height+4*ctx.lineWidth);
ctx.stroke();
}
ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth);
ctx.lineWidth *= this.networkScaleInv;
ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
@ -922,12 +806,6 @@ Node.prototype._resizeShape = function (ctx) {
var size = 2 * this.options.radius;
this.width = size;
this.height = size;
// scaling used for clustering
this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeWidthFactor;
this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeHeightFactor;
this.options.radius+= Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeRadiusFactor;
this.growthIndicator = this.width - size;
}
};
@ -937,7 +815,6 @@ Node.prototype._drawShape = function (ctx, shape) {
this.left = this.x - this.width / 2;
this.top = this.y - this.height / 2;
var clusterLineWidth = 2.5;
var borderWidth = this.options.borderWidth;
var selectionLineWidth = this.options.borderWidthSelected || 2 * this.options.borderWidth;
var radiusMultiplier = 2;
@ -952,16 +829,7 @@ Node.prototype._drawShape = function (ctx, shape) {
}
ctx.strokeStyle = this.selected ? this.options.color.highlight.border : this.hover ? this.options.color.hover.border : this.options.color.border;
// draw the outer border
if (this.clusterSize > 1) {
ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
ctx.lineWidth *= this.networkScaleInv;
ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
ctx[shape](this.x, this.y, this.options.radius+ radiusMultiplier * ctx.lineWidth);
ctx.stroke();
}
ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth);
ctx.lineWidth *= this.networkScaleInv;
ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
@ -989,12 +857,6 @@ Node.prototype._resizeText = function (ctx) {
var textSize = this.getTextSize(ctx);
this.width = textSize.width + 2 * margin;
this.height = textSize.height + 2 * margin;
// scaling used for clustering
this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeWidthFactor;
this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeHeightFactor;
this.options.radius+= Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeRadiusFactor;
this.growthIndicator = this.width - (textSize.width + 2 * margin);
}
};
@ -1011,7 +873,61 @@ Node.prototype._drawText = function (ctx) {
this.boundingBox.bottom = this.top + this.height;
};
Node.prototype._resizeIcon = function (ctx) {
if (!this.width) {
var margin = 5;
var iconSize =
{
width: Number(this.options.iconSize),
height: Number(this.options.iconSize)
};
this.width = iconSize.width + 2 * margin;
this.height = iconSize.height + 2 * margin;
}
};
Node.prototype._drawIcon = function (ctx) {
this._resizeIcon(ctx);
this.options.iconSize = this.options.iconSize || 50;
this.left = this.x - this.width / 2;
this.top = this.y - this.height / 2;
this._icon(ctx);
this.boundingBox.top = this.y - this.options.iconSize/2;
this.boundingBox.left = this.x - this.options.iconSize/2;
this.boundingBox.right = this.x + this.options.iconSize/2;
this.boundingBox.bottom = this.y + this.options.iconSize/2;
if (this.label) {
var iconTextSpacing = 5;
this._label(ctx, this.label, this.x, this.y + this.height / 2 + iconTextSpacing, 'top', true);
this.boundingBox.left = Math.min(this.boundingBox.left, this.labelDimensions.left);
this.boundingBox.right = Math.max(this.boundingBox.right, this.labelDimensions.left + this.labelDimensions.width);
this.boundingBox.bottom = Math.max(this.boundingBox.bottom, this.boundingBox.bottom + this.labelDimensions.height);
}
};
Node.prototype._icon = function (ctx) {
var relativeIconSize = Number(this.options.iconSize) * this.networkScale;
if (this.options.icon && relativeIconSize > this.options.fontDrawThreshold - 1) {
var iconSize = Number(this.options.iconSize);
ctx.font = (this.selected ? "bold " : "") + iconSize + "px " + this.options.iconFontFace;
// draw icon
ctx.fillStyle = this.options.iconColor || "black";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText(this.options.icon, this.x, this.y);
}
};
Node.prototype._label = function (ctx, text, x, y, align, baseline, labelUnderNode) {
var relativeFontSize = Number(this.options.fontSize) * this.networkScale;
if (text && relativeFontSize >= this.options.fontDrawThreshold - 1) {

+ 4
- 1
lib/network/Popup.js View File

@ -40,8 +40,9 @@ function Popup(container, x, y, text, style) {
this.x = 0;
this.y = 0;
this.padding = 5;
this.hidden = false;
if (x !== undefined && y !== undefined ) {
if (x !== undefined && y !== undefined) {
this.setPosition(x, y);
}
if (text !== undefined) {
@ -116,6 +117,7 @@ Popup.prototype.show = function (show) {
this.frame.style.left = left + "px";
this.frame.style.top = top + "px";
this.frame.style.visibility = "visible";
this.hidden = false;
}
else {
this.hide();
@ -126,6 +128,7 @@ Popup.prototype.show = function (show) {
* Hide the popup window
*/
Popup.prototype.hide = function () {
this.hidden = true;
this.frame.style.visibility = "hidden";
};

+ 432
- 933
lib/network/mixins/ClusterMixin.js
File diff suppressed because it is too large
View File


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

@ -64,11 +64,8 @@ exports._setupHierarchicalLayout = function() {
// check the distribution of the nodes per level.
var distribution = this._getDistribution();
// place the nodes on the canvas. This also stablilizes the system.
// place the nodes on the canvas. This also stablilizes the system. Redraw in started automatically after stabilize.
this._placeNodesByHierarchy(distribution);
// start the simulation.
this.start();
}
}
};

+ 1
- 2
lib/network/mixins/MixinLoader.js View File

@ -59,8 +59,7 @@ exports._loadPhysicsSystem = function () {
* @private
*/
exports._loadClusterSystem = function () {
this.clusterSession = 0;
this.hubThreshold = 5;
this.clusteredNodes = {};
this._loadMixin(ClusterMixin);
};

+ 8
- 8
lib/network/mixins/SelectionMixin.js View File

@ -355,8 +355,8 @@ exports._clusterInSelection = function() {
* @private
*/
exports._selectConnectedEdges = function(node) {
for (var i = 0; i < node.dynamicEdges.length; i++) {
var edge = node.dynamicEdges[i];
for (var i = 0; i < node.edges.length; i++) {
var edge = node.edges[i];
edge.select();
this._addToSelection(edge);
}
@ -369,8 +369,8 @@ exports._selectConnectedEdges = function(node) {
* @private
*/
exports._hoverConnectedEdges = function(node) {
for (var i = 0; i < node.dynamicEdges.length; i++) {
var edge = node.dynamicEdges[i];
for (var i = 0; i < node.edges.length; i++) {
var edge = node.edges[i];
edge.hover = true;
this._addToHover(edge);
}
@ -384,8 +384,8 @@ exports._hoverConnectedEdges = function(node) {
* @private
*/
exports._unselectConnectedEdges = function(node) {
for (var i = 0; i < node.dynamicEdges.length; i++) {
var edge = node.dynamicEdges[i];
for (var i = 0; i < node.edges.length; i++) {
var edge = node.edges[i];
edge.unselect();
this._removeFromSelection(edge);
}
@ -512,7 +512,7 @@ exports._handleTap = function(pointer) {
canvas: {x: this._XconvertDOMtoCanvas(pointer.x), y: this._YconvertDOMtoCanvas(pointer.y)}
}
this.emit("click", properties);
this._redraw();
this._requestRedraw();
};
@ -556,7 +556,7 @@ exports._handleOnHold = function(pointer) {
this._selectObject(edge,true);
}
}
this._redraw();
this._requestRedraw();
};

+ 2
- 2
lib/network/mixins/physics/BarnesHutMixin.js View File

@ -19,7 +19,7 @@ exports._calculateNodeForces = function() {
for (var i = 0; i < nodeCount; i++) {
node = nodes[nodeIndices[i]];
if (node.options.mass > 0) {
// starting with root is irrelevant, it never passes the BarnesHut condition
// starting with root is irrelevant, it never passes the BarnesHutSolver condition
this._getForceContribution(barnesHutTree.root.children.NW,node);
this._getForceContribution(barnesHutTree.root.children.NE,node);
this._getForceContribution(barnesHutTree.root.children.SW,node);
@ -48,7 +48,7 @@ exports._getForceContribution = function(parentBranch,node) {
dy = parentBranch.centerOfMass.y - node.y;
distance = Math.sqrt(dx * dx + dy * dy);
// BarnesHut condition
// BarnesHutSolver condition
// original condition : s/d < thetaInverted = passed === d/s > 1/theta = passed
// calcSize = 1/s --> d * 1/s > 1/theta = passed
if (distance * parentBranch.calcSize > this.constants.physics.barnesHut.thetaInverted) {

+ 1
- 1
lib/network/mixins/physics/HierarchialRepulsionMixin.js View File

@ -81,7 +81,7 @@ exports._calculateHierarchicalSpringForces = function () {
for (edgeId in edges) {
if (edges.hasOwnProperty(edgeId)) {
edge = edges[edgeId];
if (edge.connected) {
if (edge.connected === true) {
// only calculate forces if nodes are in the same sector
if (this.nodes.hasOwnProperty(edge.toId) && this.nodes.hasOwnProperty(edge.fromId)) {
edgeLength = edge.physics.springLength;

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

@ -71,11 +71,6 @@ exports._initializeForceCalculation = function () {
this.nodes[this.nodeIndices[0]]._setForce(0, 0);
}
else {
// if there are too many nodes on screen, we cluster without repositioning
if (this.nodeIndices.length > this.constants.clustering.clusterThreshold && this.constants.clustering.enabled == true) {
this.clusterToFit(this.constants.clustering.reduceToNodes, false);
}
// we now start the force calculation
this._calculateForces();
}
@ -202,12 +197,10 @@ exports._calculateSpringForces = function () {
for (edgeId in edges) {
if (edges.hasOwnProperty(edgeId)) {
edge = edges[edgeId];
if (edge.connected) {
if (edge.connected === true) {
// only calculate forces if nodes are in the same sector
if (this.nodes.hasOwnProperty(edge.toId) && this.nodes.hasOwnProperty(edge.fromId)) {
edgeLength = edge.physics.springLength;
// this implies that the edges between big clusters are longer
edgeLength += (edge.to.clusterSize + edge.from.clusterSize - 2) * this.constants.clustering.edgeGrowth;
dx = (edge.from.x - edge.to.x);
dy = (edge.from.y - edge.to.y);
@ -242,14 +235,14 @@ exports._calculateSpringForces = function () {
* @private
*/
exports._calculateSpringForcesWithSupport = function () {
var edgeLength, edge, edgeId, combinedClusterSize;
var edgeLength, edge, edgeId;
var edges = this.edges;
// forces caused by the edges, modelled as springs
for (edgeId in edges) {
if (edges.hasOwnProperty(edgeId)) {
edge = edges[edgeId];
if (edge.connected) {
if (edge.connected === true) {
// only calculate forces if nodes are in the same sector
if (this.nodes.hasOwnProperty(edge.toId) && this.nodes.hasOwnProperty(edge.fromId)) {
if (edge.via != null) {
@ -259,10 +252,6 @@ exports._calculateSpringForcesWithSupport = function () {
edgeLength = edge.physics.springLength;
combinedClusterSize = node1.clusterSize + node3.clusterSize - 2;
// this implies that the edges between big clusters are longer
edgeLength += combinedClusterSize * this.constants.clustering.edgeGrowth;
this._calculateSpringForce(node1, node2, 0.5 * edgeLength);
this._calculateSpringForce(node2, node3, 0.5 * edgeLength);
}

+ 1
- 1
lib/network/mixins/physics/RepulsionMixin.js View File

@ -31,7 +31,7 @@ exports._calculateNodeForces = function () {
dy = node2.y - node1.y;
distance = Math.sqrt(dx * dx + dy * dy);
// same condition as BarnesHut, making sure nodes are never 100% overlapping.
// same condition as BarnesHutSolver, making sure nodes are never 100% overlapping.
if (distance == 0) {
distance = 0.1*Math.random();
dx = distance;

+ 647
- 0
lib/network/modules/ClusterEngine.js View File

@ -0,0 +1,647 @@
/**
* Created by Alex on 2/20/2015.
*/
var Node = require('../Node');
var Edge = require('../Edge');
var util = require('../../util');
function ClusterEngine(data,options) {
this.nodes = data.nodes;
this.edges = data.edges;
this.nodeIndices = data.nodeIndices;
this.emitter = data.emitter;
this.clusteredNodes = {};
}
/**
*
* @param hubsize
* @param options
*/
ClusterEngine.prototype.clusterByConnectionCount = function(hubsize, options) {
if (hubsize === undefined) {
hubsize = this._getHubSize();
}
else if (tyepof(hubsize) == "object") {
options = this._checkOptions(hubsize);
hubsize = this._getHubSize();
}
var nodesToCluster = [];
for (var i = 0; i < this.nodeIndices.length; i++) {
var node = this.nodes[this.nodeIndices[i]];
if (node.edges.length >= hubsize) {
nodesToCluster.push(node.id);
}
}
for (var i = 0; i < nodesToCluster.length; i++) {
var node = this.nodes[nodesToCluster[i]];
this.clusterByConnection(node,options,{},{},true);
}
this.emitter.emit('dataChanged');
}
/**
* loop over all nodes, check if they adhere to the condition and cluster if needed.
* @param options
* @param doNotUpdateCalculationNodes
*/
ClusterEngine.prototype.clusterByNodeData = function(options, doNotUpdateCalculationNodes) {
if (options === undefined) {throw new Error("Cannot call clusterByNodeData without options.");}
if (options.joinCondition === undefined) {throw new Error("Cannot call clusterByNodeData without a joinCondition function in the options.");}
// check if the options object is fine, append if needed
options = this._checkOptions(options);
var childNodesObj = {};
var childEdgesObj = {}
// collect the nodes that will be in the cluster
for (var i = 0; i < this.nodeIndices.length; i++) {
var nodeId = this.nodeIndices[i];
var clonedOptions = this._cloneOptions(nodeId);
if (options.joinCondition(clonedOptions) == true) {
childNodesObj[nodeId] = this.nodes[nodeId];
}
}
this._cluster(childNodesObj, childEdgesObj, options, doNotUpdateCalculationNodes);
}
/**
* Cluster all nodes in the network that have only 1 edge
* @param options
* @param doNotUpdateCalculationNodes
*/
ClusterEngine.prototype.clusterOutliers = function(options, doNotUpdateCalculationNodes) {
options = this._checkOptions(options);
var clusters = []
// collect the nodes that will be in the cluster
for (var i = 0; i < this.nodeIndices.length; i++) {
var childNodesObj = {};
var childEdgesObj = {};
var nodeId = this.nodeIndices[i];
if (this.nodes[nodeId].edges.length == 1) {
var edge = this.nodes[nodeId].edges[0];
var childNodeId = this._getConnectedId(edge, nodeId);
if (childNodeId != nodeId) {
if (options.joinCondition === undefined) {
childNodesObj[nodeId] = this.nodes[nodeId];
childNodesObj[childNodeId] = this.nodes[childNodeId];
}
else {
var clonedOptions = this._cloneOptions(nodeId);
if (options.joinCondition(clonedOptions) == true) {
childNodesObj[nodeId] = this.nodes[nodeId];
}
clonedOptions = this._cloneOptions(childNodeId);
if (options.joinCondition(clonedOptions) == true) {
childNodesObj[childNodeId] = this.nodes[childNodeId];
}
}
clusters.push({nodes:childNodesObj, edges:childEdgesObj})
}
}
}
for (var i = 0; i < clusters.length; i++) {
this._cluster(clusters[i].nodes, clusters[i].edges, options, true)
}
if (doNotUpdateCalculationNodes !== true) {
this.emitter.emit('dataChanged');
}
}
/**
*
* @param nodeId
* @param options
* @param doNotUpdateCalculationNodes
*/
ClusterEngine.prototype.clusterByConnection = function(nodeId, options, doNotUpdateCalculationNodes) {
// kill conditions
if (nodeId === undefined) {throw new Error("No nodeId supplied to clusterByConnection!");}
if (this.nodes[nodeId] === undefined) {throw new Error("The nodeId given to clusterByConnection does not exist!");}
var node = this.nodes[nodeId];
options = this._checkOptions(options, node);
if (options.clusterNodeProperties.x === undefined) {options.clusterNodeProperties.x = node.x; options.clusterNodeProperties.allowedToMoveX = !node.xFixed;}
if (options.clusterNodeProperties.y === undefined) {options.clusterNodeProperties.y = node.y; options.clusterNodeProperties.allowedToMoveY = !node.yFixed;}
var childNodesObj = {};
var childEdgesObj = {}
var parentNodeId = node.id;
var parentClonedOptions = this._cloneOptions(parentNodeId);
childNodesObj[parentNodeId] = node;
// collect the nodes that will be in the cluster
for (var i = 0; i < node.edges.length; i++) {
var edge = node.edges[i];
var childNodeId = this._getConnectedId(edge, parentNodeId);
if (childNodeId !== parentNodeId) {
if (options.joinCondition === undefined) {
childEdgesObj[edge.id] = edge;
childNodesObj[childNodeId] = this.nodes[childNodeId];
}
else {
// clone the options and insert some additional parameters that could be interesting.
var childClonedOptions = this._cloneOptions(childNodeId);
if (options.joinCondition(parentClonedOptions, childClonedOptions) == true) {
childEdgesObj[edge.id] = edge;
childNodesObj[childNodeId] = this.nodes[childNodeId];
}
}
}
else {
childEdgesObj[edge.id] = edge;
}
}
this._cluster(childNodesObj, childEdgesObj, options, doNotUpdateCalculationNodes);
}
/**
* This returns a clone of the options or properties of the edge or node to be used for construction of new edges or check functions for new nodes.
* @param objId
* @param type
* @returns {{}}
* @private
*/
ClusterEngine.prototype._cloneOptions = function(objId, type) {
var clonedOptions = {};
if (type === undefined || type == 'node') {
util.deepExtend(clonedOptions, this.nodes[objId].options, true);
util.deepExtend(clonedOptions, this.nodes[objId].properties, true);
clonedOptions.amountOfConnections = this.nodes[objId].edges.length;
}
else {
util.deepExtend(clonedOptions, this.edges[objId].properties, true);
}
return clonedOptions;
}
/**
* This function creates the edges that will be attached to the cluster.
*
* @param childNodesObj
* @param childEdgesObj
* @param newEdges
* @param options
* @private
*/
ClusterEngine.prototype._createClusterEdges = function (childNodesObj, childEdgesObj, newEdges, options) {
var edge, childNodeId, childNode;
var childKeys = Object.keys(childNodesObj);
for (var i = 0; i < childKeys.length; i++) {
childNodeId = childKeys[i];
childNode = childNodesObj[childNodeId];
// mark all edges for removal from global and construct new edges from the cluster to others
for (var j = 0; j < childNode.edges.length; j++) {
edge = childNode.edges[j];
childEdgesObj[edge.id] = edge;
var otherNodeId = edge.toId;
var otherOnTo = true;
if (edge.toId != childNodeId) {
otherNodeId = edge.toId;
otherOnTo = true;
}
else if (edge.fromId != childNodeId) {
otherNodeId = edge.fromId;
otherOnTo = false;
}
if (childNodesObj[otherNodeId] === undefined) {
var clonedOptions = this._cloneOptions(edge.id, 'edge');
util.deepExtend(clonedOptions, options.clusterEdgeProperties);
// avoid forcing the default color on edges that inherit color
if (edge.properties.color === undefined) {
delete clonedOptions.color;
}
if (otherOnTo === true) {
clonedOptions.from = options.clusterNodeProperties.id;
clonedOptions.to = otherNodeId;
}
else {
clonedOptions.from = otherNodeId;
clonedOptions.to = options.clusterNodeProperties.id;
}
clonedOptions.id = 'clusterEdge:' + util.randomUUID();
newEdges.push(new Edge(clonedOptions,this,this.constants))
}
}
}
}
/**
* This function checks the options that can be supplied to the different cluster functions
* for certain fields and inserts defaults if needed
* @param options
* @returns {*}
* @private
*/
ClusterEngine.prototype._checkOptions = function(options) {
if (options === undefined) {options = {};}
if (options.clusterEdgeProperties === undefined) {options.clusterEdgeProperties = {};}
if (options.clusterNodeProperties === undefined) {options.clusterNodeProperties = {};}
return options;
}
/**
*
* @param {Object} childNodesObj | object with node objects, id as keys, same as childNodes except it also contains a source node
* @param {Object} childEdgesObj | object with edge objects, id as keys
* @param {Array} options | object with {clusterNodeProperties, clusterEdgeProperties, processProperties}
* @param {Boolean} doNotUpdateCalculationNodes | when true, do not wrap up
* @private
*/
ClusterEngine.prototype._cluster = function(childNodesObj, childEdgesObj, options, doNotUpdateCalculationNodes) {
// kill condition: no children so cant cluster
if (Object.keys(childNodesObj).length == 0) {return;}
// check if we have an unique id;
if (options.clusterNodeProperties.id === undefined) {options.clusterNodeProperties.id = 'cluster:' + util.randomUUID();}
var clusterId = options.clusterNodeProperties.id;
// create the new edges that will connect to the cluster
var newEdges = [];
this._createClusterEdges(childNodesObj, childEdgesObj, newEdges, options);
// construct the clusterNodeProperties
var clusterNodeProperties = options.clusterNodeProperties;
if (options.processProperties !== undefined) {
// get the childNode options
var childNodesOptions = [];
for (var nodeId in childNodesObj) {
var clonedOptions = this._cloneOptions(nodeId);
childNodesOptions.push(clonedOptions);
}
// get clusterproperties based on childNodes
var childEdgesOptions = [];
for (var edgeId in childEdgesObj) {
var clonedOptions = this._cloneOptions(edgeId, 'edge');
childEdgesOptions.push(clonedOptions);
}
clusterNodeProperties = options.processProperties(clusterNodeProperties, childNodesOptions, childEdgesOptions);
if (!clusterNodeProperties) {
throw new Error("The processClusterProperties function does not return properties!");
}
}
if (clusterNodeProperties.label === undefined) {
clusterNodeProperties.label = 'cluster';
}
// give the clusterNode a postion if it does not have one.
var pos = undefined
if (clusterNodeProperties.x === undefined) {
pos = this._getClusterPosition(childNodesObj);
clusterNodeProperties.x = pos.x;
clusterNodeProperties.allowedToMoveX = true;
}
if (clusterNodeProperties.x === undefined) {
if (pos === undefined) {
pos = this._getClusterPosition(childNodesObj);
}
clusterNodeProperties.y = pos.y;
clusterNodeProperties.allowedToMoveY = true;
}
// force the ID to remain the same
clusterNodeProperties.id = clusterId;
// create the clusterNode
var clusterNode = new Node(clusterNodeProperties, this.images, this.groups, this.constants);
clusterNode.isCluster = true;
clusterNode.containedNodes = childNodesObj;
clusterNode.containedEdges = childEdgesObj;
// delete contained edges from global
for (var edgeId in childEdgesObj) {
if (childEdgesObj.hasOwnProperty(edgeId)) {
if (this.edges[edgeId] !== undefined) {
if (this.edges[edgeId].via !== null) {
var viaId = this.edges[edgeId].via.id;
if (viaId) {
this.edges[edgeId].via = null
delete this.sectors['support']['nodes'][viaId];
}
}
this.edges[edgeId].disconnect();
delete this.edges[edgeId];
}
}
}
// remove contained nodes from global
for (var nodeId in childNodesObj) {
if (childNodesObj.hasOwnProperty(nodeId)) {
this.clusteredNodes[nodeId] = {clusterId:clusterNodeProperties.id, node: this.nodes[nodeId]};
delete this.nodes[nodeId];
}
}
// finally put the cluster node into global
this.nodes[clusterNodeProperties.id] = clusterNode;
// push new edges to global
for (var i = 0; i < newEdges.length; i++) {
this.edges[newEdges[i].id] = newEdges[i];
this.edges[newEdges[i].id].connect();
}
// create bezier nodes for smooth curves if needed
this._createBezierNodes(newEdges);
// set ID to undefined so no duplicates arise
clusterNodeProperties.id = undefined;
// wrap up
if (doNotUpdateCalculationNodes !== true) {
this.emitter.emit('dataChanged');
}
}
/**
* Check if a node is a cluster.
* @param nodeId
* @returns {*}
*/
ClusterEngine.prototype.isCluster = function(nodeId) {
if (this.nodes[nodeId] !== undefined) {
return this.nodes[nodeId].isCluster;
}
else {
console.log("Node does not exist.")
return false;
}
}
/**
* get the position of the cluster node based on what's inside
* @param {object} childNodesObj | object with node objects, id as keys
* @returns {{x: number, y: number}}
* @private
*/
ClusterEngine.prototype._getClusterPosition = function(childNodesObj) {
var childKeys = Object.keys(childNodesObj);
var minX = childNodesObj[childKeys[0]].x;
var maxX = childNodesObj[childKeys[0]].x;
var minY = childNodesObj[childKeys[0]].y;
var maxY = childNodesObj[childKeys[0]].y;
var node;
for (var i = 0; i < childKeys.lenght; i++) {
node = childNodesObj[childKeys[0]];
minX = node.x < minX ? node.x : minX;
maxX = node.x > maxX ? node.x : maxX;
minY = node.y < minY ? node.y : minY;
maxY = node.y > maxY ? node.y : maxY;
}
return {x: 0.5*(minX + maxX), y: 0.5*(minY + maxY)};
}
/**
* Open a cluster by calling this function.
* @param {String} clusterNodeId | the ID of the cluster node
* @param {Boolean} doNotUpdateCalculationNodes | wrap up afterwards if not true
*/
ClusterEngine.prototype.openCluster = function(clusterNodeId, doNotUpdateCalculationNodes) {
// kill conditions
if (clusterNodeId === undefined) {throw new Error("No clusterNodeId supplied to openCluster.");}
if (this.nodes[clusterNodeId] === undefined) {throw new Error("The clusterNodeId supplied to openCluster does not exist.");}
if (this.nodes[clusterNodeId].containedNodes === undefined) {console.log("The node:" + clusterNodeId + " is not a cluster."); return};
var node = this.nodes[clusterNodeId];
var containedNodes = node.containedNodes;
var containedEdges = node.containedEdges;
// release nodes
for (var nodeId in containedNodes) {
if (containedNodes.hasOwnProperty(nodeId)) {
this.nodes[nodeId] = containedNodes[nodeId];
// inherit position
this.nodes[nodeId].x = node.x;
this.nodes[nodeId].y = node.y;
// inherit speed
this.nodes[nodeId].vx = node.vx;
this.nodes[nodeId].vy = node.vy;
delete this.clusteredNodes[nodeId];
}
}
// release edges
for (var edgeId in containedEdges) {
if (containedEdges.hasOwnProperty(edgeId)) {
this.edges[edgeId] = containedEdges[edgeId];
this.edges[edgeId].connect();
var edge = this.edges[edgeId];
if (edge.connected === false) {
if (this.clusteredNodes[edge.fromId] !== undefined) {
this._connectEdge(edge, edge.fromId, true);
}
if (this.clusteredNodes[edge.toId] !== undefined) {
this._connectEdge(edge, edge.toId, false);
}
}
}
}
this._createBezierNodes(containedEdges);
var edgeIds = [];
for (var i = 0; i < node.edges.length; i++) {
edgeIds.push(node.edges[i].id);
}
// remove edges in clusterNode
for (var i = 0; i < edgeIds.length; i++) {
var edge = this.edges[edgeIds[i]];
// if the edge should have been connected to a contained node
if (edge.fromArray.length > 0 && edge.fromId == clusterNodeId) {
// the node in the from array was contained in the cluster
if (this.nodes[edge.fromArray[0].id] !== undefined) {
this._connectEdge(edge, edge.fromArray[0].id, true);
}
}
else if (edge.toArray.length > 0 && edge.toId == clusterNodeId) {
// the node in the to array was contained in the cluster
if (this.nodes[edge.toArray[0].id] !== undefined) {
this._connectEdge(edge, edge.toArray[0].id, false);
}
}
else {
var edgeId = edgeIds[i];
var viaId = this.edges[edgeId].via.id;
if (viaId) {
this.edges[edgeId].via = null
delete this.sectors['support']['nodes'][viaId];
}
// this removes the edge from node.edges, which is why edgeIds is formed
this.edges[edgeId].disconnect();
delete this.edges[edgeId];
}
}
// remove clusterNode
delete this.nodes[clusterNodeId];
if (doNotUpdateCalculationNodes !== true) {
this.emitter.emit('dataChanged');
}
}
/**
* Recalculate navigation nodes, color edges dirty, update nodes list etc.
* @private
*/
ClusterEngine.prototype._wrapUp = function() {
this._updateNodeIndexList();
this._updateCalculationNodes();
this._markAllEdgesAsDirty();
if (this.initializing !== true) {
this.moving = true;
this.start();
}
}
/**
* Connect an edge that was previously contained from cluster A to cluster B if the node that it was originally connected to
* is currently residing in cluster B
* @param edge
* @param nodeId
* @param from
* @private
*/
ClusterEngine.prototype._connectEdge = function(edge, nodeId, from) {
var clusterStack = this._getClusterStack(nodeId);
if (from == true) {
edge.from = clusterStack[clusterStack.length - 1];
edge.fromId = clusterStack[clusterStack.length - 1].id;
clusterStack.pop()
edge.fromArray = clusterStack;
}
else {
edge.to = clusterStack[clusterStack.length - 1];
edge.toId = clusterStack[clusterStack.length - 1].id;
clusterStack.pop();
edge.toArray = clusterStack;
}
edge.connect();
}
/**
* Get the stack clusterId's that a certain node resides in. cluster A -> cluster B -> cluster C -> node
* @param nodeId
* @returns {Array}
* @private
*/
ClusterEngine.prototype._getClusterStack = function(nodeId) {
var stack = [];
var max = 100;
var counter = 0;
while (this.clusteredNodes[nodeId] !== undefined && counter < max) {
stack.push(this.clusteredNodes[nodeId].node);
nodeId = this.clusteredNodes[nodeId].clusterId;
counter++;
}
stack.push(this.nodes[nodeId]);
return stack;
}
/**
* Get the Id the node is connected to
* @param edge
* @param nodeId
* @returns {*}
* @private
*/
ClusterEngine.prototype._getConnectedId = function(edge, nodeId) {
if (edge.toId != nodeId) {
return edge.toId;
}
else if (edge.fromId != nodeId) {
return edge.fromId;
}
else {
return edge.fromId;
}
}
/**
* We determine how many connections denote an important hub.
* We take the mean + 2*std as the important hub size. (Assuming a normal distribution of data, ~2.2%)
*
* @private
*/
ClusterEngine.prototype._getHubSize = function() {
var average = 0;
var averageSquared = 0;
var hubCounter = 0;
var largestHub = 0;
for (var i = 0; i < this.nodeIndices.length; i++) {
var node = this.nodes[this.nodeIndices[i]];
if (node.edges.length > largestHub) {
largestHub = node.edges.length;
}
average += node.edges.length;
averageSquared += Math.pow(node.edges.length,2);
hubCounter += 1;
}
average = average / hubCounter;
averageSquared = averageSquared / hubCounter;
var variance = averageSquared - Math.pow(average,2);
var standardDeviation = Math.sqrt(variance);
var hubThreshold = Math.floor(average + 2*standardDeviation);
// always have at least one to cluster
if (hubThreshold > largestHub) {
hubThreshold = largestHub;
}
return hubThreshold;
};
module.exports = clusterEngine

+ 33
- 0
lib/network/modules/PhysicsEngine.js View File

@ -0,0 +1,33 @@
/**
* Created by Alex on 2/23/2015.
*/
var BarnesHut = require("./compontents/BarnesHutSolver")
var SpringSolver = require("./compontents/SpringSolver")
var CentralGravitySolver = require("./compontents/CentralGravitySolver")
function PhysicsEngine(body, options) {
this.body = body;
this.nodesSolver = new BarnesHut(body, options);
this.edgesSolver = new SpringSolver(body, options);
this.gravitySolver = new CentralGravitySolver(body, options);
}
PhysicsEngine.prototype.calculateField = function () {
this.nodesSolver.solve();
};
PhysicsEngine.prototype.calculateSprings = function () {
this.edgesSolver.solve();
};
PhysicsEngine.prototype.calculateCentralGravity = function () {
this.gravitySolver.solve();
};
PhysicsEngine.prototype.calculate = function () {
this.calculateCentralGravity();
this.calculateField();
this.calculateSprings();
};

+ 409
- 0
lib/network/modules/components/BarnesHutSolver.js View File

@ -0,0 +1,409 @@
/**
* Created by Alex on 2/23/2015.
*/
function BarnesHutSolver(body, options) {
this.body = body;
this.options = options;
}
/**
* This function calculates the forces the nodes apply on eachother based on a gravitational model.
* The Barnes Hut method is used to speed up this N-body simulation.
*
* @private
*/
BarnesHutSolver.prototype.solve = function() {
if (this.options.gravitationalConstant != 0) {
var node;
var nodes = this.body.calculationNodes;
var nodeIndices = this.body.calculationNodeIndices;
var nodeCount = nodeIndices.length;
var barnesHutTree = this._formBarnesHutTree(nodes,nodeIndices);
// place the nodes one by one recursively
for (var i = 0; i < nodeCount; i++) {
node = nodes[nodeIndices[i]];
if (node.options.mass > 0) {
// starting with root is irrelevant, it never passes the BarnesHutSolver condition
this._getForceContribution(barnesHutTree.root.children.NW,node);
this._getForceContribution(barnesHutTree.root.children.NE,node);
this._getForceContribution(barnesHutTree.root.children.SW,node);
this._getForceContribution(barnesHutTree.root.children.SE,node);
}
}
}
};
/**
* This function traverses the barnesHutTree. It checks when it can approximate distant nodes with their center of mass.
* If a region contains a single node, we check if it is not itself, then we apply the force.
*
* @param parentBranch
* @param node
* @private
*/
BarnesHutSolver.prototype._getForceContribution = function(parentBranch,node) {
// we get no force contribution from an empty region
if (parentBranch.childrenCount > 0) {
var dx,dy,distance;
// get the distance from the center of mass to the node.
dx = parentBranch.centerOfMass.x - node.x;
dy = parentBranch.centerOfMass.y - node.y;
distance = Math.sqrt(dx * dx + dy * dy);
// BarnesHutSolver condition
// original condition : s/d < thetaInverted = passed === d/s > 1/theta = passed
// calcSize = 1/s --> d * 1/s > 1/theta = passed
if (distance * parentBranch.calcSize > this.options.thetaInverted) {
// duplicate code to reduce function calls to speed up program
if (distance == 0) {
distance = 0.1*Math.random();
dx = distance;
}
var gravityForce = this.options.gravitationalConstant * parentBranch.mass * node.options.mass / (distance * distance * distance);
var fx = dx * gravityForce;
var fy = dy * gravityForce;
node.fx += fx;
node.fy += fy;
}
else {
// Did not pass the condition, go into children if available
if (parentBranch.childrenCount == 4) {
this._getForceContribution(parentBranch.children.NW,node);
this._getForceContribution(parentBranch.children.NE,node);
this._getForceContribution(parentBranch.children.SW,node);
this._getForceContribution(parentBranch.children.SE,node);
}
else { // parentBranch must have only one node, if it was empty we wouldnt be here
if (parentBranch.children.data.id != node.id) { // if it is not self
// duplicate code to reduce function calls to speed up program
if (distance == 0) {
distance = 0.5*Math.random();
dx = distance;
}
var gravityForce = this.options.gravitationalConstant * parentBranch.mass * node.options.mass / (distance * distance * distance);
var fx = dx * gravityForce;
var fy = dy * gravityForce;
node.fx += fx;
node.fy += fy;
}
}
}
}
};
/**
* This function constructs the barnesHut tree recursively. It creates the root, splits it and starts placing the nodes.
*
* @param nodes
* @param nodeIndices
* @private
*/
BarnesHutSolver.prototype._formBarnesHutTree = function(nodes,nodeIndices) {
var node;
var nodeCount = nodeIndices.length;
var minX = Number.MAX_VALUE,
minY = Number.MAX_VALUE,
maxX =-Number.MAX_VALUE,
maxY =-Number.MAX_VALUE;
// get the range of the nodes
for (var i = 0; i < nodeCount; i++) {
var x = nodes[nodeIndices[i]].x;
var y = nodes[nodeIndices[i]].y;
if (nodes[nodeIndices[i]].options.mass > 0) {
if (x < minX) { minX = x; }
if (x > maxX) { maxX = x; }
if (y < minY) { minY = y; }
if (y > maxY) { maxY = y; }
}
}
// make the range a square
var sizeDiff = Math.abs(maxX - minX) - Math.abs(maxY - minY); // difference between X and Y
if (sizeDiff > 0) {minY -= 0.5 * sizeDiff; maxY += 0.5 * sizeDiff;} // xSize > ySize
else {minX += 0.5 * sizeDiff; maxX -= 0.5 * sizeDiff;} // xSize < ySize
var minimumTreeSize = 1e-5;
var rootSize = Math.max(minimumTreeSize,Math.abs(maxX - minX));
var halfRootSize = 0.5 * rootSize;
var centerX = 0.5 * (minX + maxX), centerY = 0.5 * (minY + maxY);
// construct the barnesHutTree
var barnesHutTree = {
root:{
centerOfMass: {x:0, y:0},
mass:0,
range: {
minX: centerX-halfRootSize,maxX:centerX+halfRootSize,
minY: centerY-halfRootSize,maxY:centerY+halfRootSize
},
size: rootSize,
calcSize: 1 / rootSize,
children: { data:null},
maxWidth: 0,
level: 0,
childrenCount: 4
}
};
this._splitBranch(barnesHutTree.root);
// place the nodes one by one recursively
for (i = 0; i < nodeCount; i++) {
node = nodes[nodeIndices[i]];
if (node.options.mass > 0) {
this._placeInTree(barnesHutTree.root,node);
}
}
// make global
return barnesHutTree
};
/**
* this updates the mass of a branch. this is increased by adding a node.
*
* @param parentBranch
* @param node
* @private
*/
BarnesHutSolver.prototype._updateBranchMass = function(parentBranch, node) {
var totalMass = parentBranch.mass + node.options.mass;
var totalMassInv = 1/totalMass;
parentBranch.centerOfMass.x = parentBranch.centerOfMass.x * parentBranch.mass + node.x * node.options.mass;
parentBranch.centerOfMass.x *= totalMassInv;
parentBranch.centerOfMass.y = parentBranch.centerOfMass.y * parentBranch.mass + node.y * node.options.mass;
parentBranch.centerOfMass.y *= totalMassInv;
parentBranch.mass = totalMass;
var biggestSize = Math.max(Math.max(node.height,node.radius),node.width);
parentBranch.maxWidth = (parentBranch.maxWidth < biggestSize) ? biggestSize : parentBranch.maxWidth;
};
/**
* determine in which branch the node will be placed.
*
* @param parentBranch
* @param node
* @param skipMassUpdate
* @private
*/
BarnesHutSolver.prototype._placeInTree = function(parentBranch,node,skipMassUpdate) {
if (skipMassUpdate != true || skipMassUpdate === undefined) {
// update the mass of the branch.
this._updateBranchMass(parentBranch,node);
}
if (parentBranch.children.NW.range.maxX > node.x) { // in NW or SW
if (parentBranch.children.NW.range.maxY > node.y) { // in NW
this._placeInRegion(parentBranch,node,"NW");
}
else { // in SW
this._placeInRegion(parentBranch,node,"SW");
}
}
else { // in NE or SE
if (parentBranch.children.NW.range.maxY > node.y) { // in NE
this._placeInRegion(parentBranch,node,"NE");
}
else { // in SE
this._placeInRegion(parentBranch,node,"SE");
}
}
};
/**
* actually place the node in a region (or branch)
*
* @param parentBranch
* @param node
* @param region
* @private
*/
BarnesHutSolver.prototype._placeInRegion = function(parentBranch,node,region) {
switch (parentBranch.children[region].childrenCount) {
case 0: // place node here
parentBranch.children[region].children.data = node;
parentBranch.children[region].childrenCount = 1;
this._updateBranchMass(parentBranch.children[region],node);
break;
case 1: // convert into children
// if there are two nodes exactly overlapping (on init, on opening of cluster etc.)
// we move one node a pixel and we do not put it in the tree.
if (parentBranch.children[region].children.data.x == node.x &&
parentBranch.children[region].children.data.y == node.y) {
node.x += Math.random();
node.y += Math.random();
}
else {
this._splitBranch(parentBranch.children[region]);
this._placeInTree(parentBranch.children[region],node);
}
break;
case 4: // place in branch
this._placeInTree(parentBranch.children[region],node);
break;
}
};
/**
* this function splits a branch into 4 sub branches. If the branch contained a node, we place it in the subbranch
* after the split is complete.
*
* @param parentBranch
* @private
*/
BarnesHutSolver.prototype._splitBranch = function(parentBranch) {
// if the branch is shaded with a node, replace the node in the new subset.
var containedNode = null;
if (parentBranch.childrenCount == 1) {
containedNode = parentBranch.children.data;
parentBranch.mass = 0; parentBranch.centerOfMass.x = 0; parentBranch.centerOfMass.y = 0;
}
parentBranch.childrenCount = 4;
parentBranch.children.data = null;
this._insertRegion(parentBranch,"NW");
this._insertRegion(parentBranch,"NE");
this._insertRegion(parentBranch,"SW");
this._insertRegion(parentBranch,"SE");
if (containedNode != null) {
this._placeInTree(parentBranch,containedNode);
}
};
/**
* This function subdivides the region into four new segments.
* Specifically, this inserts a single new segment.
* It fills the children section of the parentBranch
*
* @param parentBranch
* @param region
* @param parentRange
* @private
*/
BarnesHutSolver.prototype._insertRegion = function(parentBranch, region) {
var minX,maxX,minY,maxY;
var childSize = 0.5 * parentBranch.size;
switch (region) {
case "NW":
minX = parentBranch.range.minX;
maxX = parentBranch.range.minX + childSize;
minY = parentBranch.range.minY;
maxY = parentBranch.range.minY + childSize;
break;
case "NE":
minX = parentBranch.range.minX + childSize;
maxX = parentBranch.range.maxX;
minY = parentBranch.range.minY;
maxY = parentBranch.range.minY + childSize;
break;
case "SW":
minX = parentBranch.range.minX;
maxX = parentBranch.range.minX + childSize;
minY = parentBranch.range.minY + childSize;
maxY = parentBranch.range.maxY;
break;
case "SE":
minX = parentBranch.range.minX + childSize;
maxX = parentBranch.range.maxX;
minY = parentBranch.range.minY + childSize;
maxY = parentBranch.range.maxY;
break;
}
parentBranch.children[region] = {
centerOfMass:{x:0,y:0},
mass:0,
range:{minX:minX,maxX:maxX,minY:minY,maxY:maxY},
size: 0.5 * parentBranch.size,
calcSize: 2 * parentBranch.calcSize,
children: {data:null},
maxWidth: 0,
level: parentBranch.level+1,
childrenCount: 0
};
};
/**
* This function is for debugging purposed, it draws the tree.
*
* @param ctx
* @param color
* @private
*/
BarnesHutSolver.prototype._drawTree = function(ctx,color) {
if (this.barnesHutTree !== undefined) {
ctx.lineWidth = 1;
this._drawBranch(this.barnesHutTree.root,ctx,color);
}
};
/**
* This function is for debugging purposes. It draws the branches recursively.
*
* @param branch
* @param ctx
* @param color
* @private
*/
BarnesHutSolver.prototype._drawBranch = function(branch,ctx,color) {
if (color === undefined) {
color = "#FF0000";
}
if (branch.childrenCount == 4) {
this._drawBranch(branch.children.NW,ctx);
this._drawBranch(branch.children.NE,ctx);
this._drawBranch(branch.children.SE,ctx);
this._drawBranch(branch.children.SW,ctx);
}
ctx.strokeStyle = color;
ctx.beginPath();
ctx.moveTo(branch.range.minX,branch.range.minY);
ctx.lineTo(branch.range.maxX,branch.range.minY);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(branch.range.maxX,branch.range.minY);
ctx.lineTo(branch.range.maxX,branch.range.maxY);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(branch.range.maxX,branch.range.maxY);
ctx.lineTo(branch.range.minX,branch.range.maxY);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(branch.range.minX,branch.range.maxY);
ctx.lineTo(branch.range.minX,branch.range.minY);
ctx.stroke();
/*
if (branch.mass > 0) {
ctx.circle(branch.centerOfMass.x, branch.centerOfMass.y, 3*branch.mass);
ctx.stroke();
}
*/
};
module.exports = BarnesHutSolver;

+ 32
- 0
lib/network/modules/components/CentralGravitySolver.js View File

@ -0,0 +1,32 @@
/**
* Created by Alex on 2/23/2015.
*/
function CentralGravitySolver(body, options) {
this.body = body;
this.options = options;
}
CentralGravitySolver.prototype.solve = function () {
var dx, dy, distance, node, i;
var nodes = this.body.calculationNodes;
var gravity = this.options.centralGravity;
var gravityForce = 0;
for (i = 0; i < this.body.calculationNodeIndices.length; i++) {
node = nodes[this.body.calculationNodeIndices[i]];
node.damping = this.options.damping; // possibly add function to alter damping properties of clusters.
dx = -node.x;
dy = -node.y;
distance = Math.sqrt(dx * dx + dy * dy);
gravityForce = (distance == 0) ? 0 : (gravity / distance);
node.fx = dx * gravityForce;
node.fy = dy * gravityForce;
}
};
module.exports = CentralGravitySolver;

+ 101
- 0
lib/network/modules/components/SpringSolver.js View File

@ -0,0 +1,101 @@
/**
* Created by Alex on 2/23/2015.
*/
function SpringSolver(body, options) {
this.body = body;
this.options = options;
}
/**
* this function calculates the effects of the springs in the case of unsmooth curves.
*
* @private
*/
SpringSolver.prototype._calculateSpringForces = function () {
var edgeLength, edge, edgeId;
var edges = this.edges;
// forces caused by the edges, modelled as springs
for (edgeId in edges) {
if (edges.hasOwnProperty(edgeId)) {
edge = edges[edgeId];
if (edge.connected === true) {
// only calculate forces if nodes are in the same sector
if (this.nodes.hasOwnProperty(edge.toId) && this.nodes.hasOwnProperty(edge.fromId)) {
edgeLength = edge.physics.springLength;
this._calculateSpringForce(edge.from, edge.to, edgeLength);
}
}
}
}
};
/**
* This function calculates the springforces on the nodes, accounting for the support nodes.
*
* @private
*/
SpringSolver.prototype._calculateSpringForcesWithSupport = function () {
var edgeLength, edge, edgeId;
var edges = this.edges;
// forces caused by the edges, modelled as springs
for (edgeId in edges) {
if (edges.hasOwnProperty(edgeId)) {
edge = edges[edgeId];
if (edge.connected === true) {
// only calculate forces if nodes are in the same sector
if (this.nodes.hasOwnProperty(edge.toId) && this.nodes.hasOwnProperty(edge.fromId)) {
if (edge.via != null) {
var node1 = edge.to;
var node2 = edge.via;
var node3 = edge.from;
edgeLength = edge.physics.springLength;
this._calculateSpringForce(node1, node2, 0.5 * edgeLength);
this._calculateSpringForce(node2, node3, 0.5 * edgeLength);
}
}
}
}
}
};
/**
* This is the code actually performing the calculation for the function above. It is split out to avoid repetition.
*
* @param node1
* @param node2
* @param edgeLength
* @private
*/
SpringSolver.prototype._calculateSpringForce = function (node1, node2, edgeLength) {
var dx, dy, fx, fy, springForce, distance;
dx = (node1.x - node2.x);
dy = (node1.y - node2.y);
distance = Math.sqrt(dx * dx + dy * dy);
distance = distance == 0 ? 0.01 : distance;
// the 1/distance is so the fx and fy can be calculated without sine or cosine.
springForce = this.options.springConstant * (edgeLength - distance) / distance;
fx = dx * springForce;
fy = dy * springForce;
node1.fx += fx;
node1.fy += fy;
node2.fx -= fx;
node2.fy -= fy;
};
module.exports = SpringSolver;

+ 3
- 3
lib/timeline/Timeline.js View File

@ -13,8 +13,8 @@ var ItemSet = require('./component/ItemSet');
/**
* Create a timeline visualization
* @param {HTMLElement} container
* @param {vis.DataSet | Array | google.visualization.DataTable} [items]
* @param {vis.DataSet | Array | google.visualization.DataTable} [groups]
* @param {vis.DataSet | vis.DataView | Array | google.visualization.DataTable} [items]
* @param {vis.DataSet | vis.DataView | Array | google.visualization.DataTable} [groups]
* @param {Object} [options] See Timeline.setOptions for the available options.
* @constructor
* @extends Core
@ -25,7 +25,7 @@ function Timeline (container, items, groups, options) {
}
// if the third element is options, the forth is groups (optionally);
if (!(Array.isArray(groups) || groups instanceof DataSet) && groups instanceof Object) {
if (!(Array.isArray(groups) || groups instanceof DataSet || groups instanceof DataView) && groups instanceof Object) {
var forthArgument = options;
options = groups;
groups = forthArgument;

+ 9
- 1
lib/timeline/component/LineGraph.js View File

@ -981,9 +981,17 @@ LineGraph.prototype._convertYcoordinates = function (datapoints, group) {
}
for (var i = 0; i < datapoints.length; i++) {
var labelValue;
//if (datapoints[i].label) {
// labelValue = datapoints[i].label;
//}
//else {
// labelValue = null;
//}
labelValue = datapoints[i].label ? datapoints[i].label : null;
xValue = toScreen(datapoints[i].x) + this.props.width;
yValue = Math.round(axis.convertValue(datapoints[i].y));
extractedData.push({x: xValue, y: yValue});
extractedData.push({x: xValue, y: yValue, label:labelValue});
}
group.setZeroPosition(Math.min(svgHeight, axis.convertValue(0)));

+ 4
- 2
lib/timeline/component/graph2d_types/bar.js View File

@ -58,7 +58,8 @@ Bargraph.draw = function (groupIds, processedGroupData, framework) {
combinedData.push({
x: processedGroupData[groupIds[i]][j].x,
y: processedGroupData[groupIds[i]][j].y,
groupId: groupIds[i]
groupId: groupIds[i],
label: processedGroupData[groupIds[i]][j].label
});
barPoints += 1;
}
@ -114,7 +115,8 @@ Bargraph.draw = function (groupIds, processedGroupData, framework) {
DOMutil.drawBar(combinedData[i].x + drawData.offset, combinedData[i].y - heightOffset, drawData.width, group.zeroPosition - combinedData[i].y, group.className + ' bar', framework.svgElements, framework.svg);
// draw points
if (group.options.drawPoints.enabled == true) {
DOMutil.drawPoint(combinedData[i].x + drawData.offset, combinedData[i].y, group, framework.svgElements, framework.svg);
Points.draw([combinedData[i]], group, framework, drawData.offset);
//DOMutil.drawPoint(combinedData[i].x + drawData.offset, combinedData[i].y, group, framework.svgElements, framework.svg);
}
}
};

+ 1
- 1
lib/timeline/component/graph2d_types/points.js View File

@ -35,7 +35,7 @@ Points.prototype.draw = function(dataset, group, framework, offset) {
Points.draw = function (dataset, group, framework, offset) {
if (offset === undefined) {offset = 0;}
for (var i = 0; i < dataset.length; i++) {
DOMutil.drawPoint(dataset[i].x + offset, dataset[i].y, group, framework.svgElements, framework.svg);
DOMutil.drawPoint(dataset[i].x + offset, dataset[i].y, group, framework.svgElements, framework.svg, dataset[i].label);
}
};

+ 5
- 3
lib/util.js View File

@ -225,22 +225,24 @@ exports.selectiveNotDeepExtend = function (props, a, b) {
* Deep extend an object a with the properties of object b
* @param {Object} a
* @param {Object} b
* @param {Boolean} protoExtend --> optional parameter. If true, the prototype values will also be extended.
* (ie. the options objects that inherit from others will also get the inherited options)
* @returns {Object}
*/
exports.deepExtend = function(a, b) {
exports.deepExtend = function(a, b, protoExtend) {
// TODO: add support for Arrays to deepExtend
if (Array.isArray(b)) {
throw new TypeError('Arrays are not supported by deepExtend');
}
for (var prop in b) {
if (b.hasOwnProperty(prop)) {
if (b.hasOwnProperty(prop) || protoExtend === true) {
if (b[prop] && b[prop].constructor === Object) {
if (a[prop] === undefined) {
a[prop] = {};
}
if (a[prop].constructor === Object) {
exports.deepExtend(a[prop], b[prop]);
exports.deepExtend(a[prop], b[prop], protoExtend);
}
else {
a[prop] = b[prop];

Loading…
Cancel
Save