Browse Source

Graph is robust now against edges connected to non existing nodes

css_transitions
josdejong 11 years ago
parent
commit
7ad93c9ff7
6 changed files with 227 additions and 103 deletions
  1. +1
    -1
      examples/graph/16_dynamic_data.html
  2. +51
    -10
      src/graph/Edge.js
  3. +58
    -39
      src/graph/Graph.js
  4. +3
    -1
      src/graph/Node.js
  5. +109
    -47
      vis.js
  6. +5
    -5
      vis.min.js

+ 1
- 1
examples/graph/16_dynamic_data.html View File

@ -173,7 +173,7 @@
<p> <p>
This example demonstrates dynamically adding, updating and removing nodes This example demonstrates dynamically adding, updating and removing nodes
using a DataSet.
and edges using a DataSet.
</p> </p>
<h1>Adjust</h1> <h1>Adjust</h1>

+ 51
- 10
src/graph/Edge.js View File

@ -25,12 +25,18 @@ function Edge (properties, graph, constants) {
// initialize variables // initialize variables
this.id = undefined; this.id = undefined;
this.fromId = undefined;
this.toId = undefined;
this.style = constants.edges.style; this.style = constants.edges.style;
this.title = undefined; this.title = undefined;
this.width = constants.edges.width; this.width = constants.edges.width;
this.value = undefined; this.value = undefined;
this.length = constants.edges.length; this.length = constants.edges.length;
this.from = null; // a node
this.to = null; // a node
this.connected = false;
// Added to support dashed lines // Added to support dashed lines
// David Jordan // David Jordan
// 2012-08-08 // 2012-08-08
@ -42,7 +48,7 @@ function Edge (properties, graph, constants) {
this.lengthFixed = false; this.lengthFixed = false;
this.setProperties(properties, constants); this.setProperties(properties, constants);
};
}
/** /**
* Set or overwrite properties for the edge * Set or overwrite properties for the edge
@ -54,8 +60,8 @@ Edge.prototype.setProperties = function(properties, constants) {
return; return;
} }
if (properties.from != undefined) {this.from = this.graph.nodes[properties.from];}
if (properties.to != undefined) {this.to = this.graph.nodes[properties.to];}
if (properties.from != undefined) {this.fromId = properties.from;}
if (properties.to != undefined) {this.toId = properties.to;}
if (properties.id != undefined) {this.id = properties.id;} if (properties.id != undefined) {this.id = properties.id;}
if (properties.style != undefined) {this.style = properties.style;} if (properties.style != undefined) {this.style = properties.style;}
@ -84,16 +90,11 @@ Edge.prototype.setProperties = function(properties, constants) {
if (properties.color != undefined) {this.color = properties.color;} if (properties.color != undefined) {this.color = properties.color;}
if (!this.from) {
throw "Node with id " + properties.from + " not found";
}
if (!this.to) {
throw "Node with id " + properties.to + " not found";
}
// A node is connected when it has a from and to node.
this.connect();
this.widthFixed = this.widthFixed || (properties.width != undefined); this.widthFixed = this.widthFixed || (properties.width != undefined);
this.lengthFixed = this.lengthFixed || (properties.length != undefined); this.lengthFixed = this.lengthFixed || (properties.length != undefined);
this.stiffness = 1 / this.length; this.stiffness = 1 / this.length;
// set draw method based on style // set draw method based on style
@ -106,6 +107,46 @@ Edge.prototype.setProperties = function(properties, constants) {
} }
}; };
/**
* Connect an edge to its nodes
*/
Edge.prototype.connect = function () {
this.disconnect();
this.from = this.graph.nodes[this.fromId] || null;
this.to = this.graph.nodes[this.toId] || null;
this.connected = (this.from && this.to);
if (this.connected) {
this.from.attachEdge(this);
this.to.attachEdge(this);
}
else {
if (this.from) {
this.from.detachEdge(this);
}
if (this.to) {
this.to.detachEdge(this);
}
}
};
/**
* Disconnect an edge from its nodes
*/
Edge.prototype.disconnect = function () {
if (this.from) {
this.from.detachEdge(this);
this.from = null;
}
if (this.to) {
this.to.detachEdge(this);
this.to = null;
}
this.connected = false;
};
/** /**
* get the title of this edge. * get the title of this edge.
* @return {string} title The title of the edge, or undefined when no title * @return {string} title The title of the edge, or undefined when no title

+ 58
- 39
src/graph/Graph.js View File

@ -669,7 +669,8 @@ Graph.prototype._checkShowPopup = function (x, y) {
for (id in edges) { for (id in edges) {
if (edges.hasOwnProperty(id)) { if (edges.hasOwnProperty(id)) {
var edge = edges[id]; var edge = edges[id];
if (edge.getTitle() != undefined && edge.isOverlappingWith(obj)) {
if (edge.connected && (edge.getTitle() != undefined) &&
edge.isOverlappingWith(obj)) {
this.popupNode = edge; this.popupNode = edge;
break; break;
} }
@ -1126,7 +1127,6 @@ Graph.prototype._addNodes = function(ids) {
id = ids[i]; id = ids[i];
var data = this.nodesData.get(id); var data = this.nodesData.get(id);
var node = new Node(data, this.images, this.groups, this.constants); var node = new Node(data, this.images, this.groups, this.constants);
// TODO: detach any old node from the edges it is attached to
this.nodes[id] = node; // note: this may replace an existing node this.nodes[id] = node; // note: this may replace an existing node
if (!node.isFixed()) { if (!node.isFixed()) {
@ -1143,6 +1143,7 @@ Graph.prototype._addNodes = function(ids) {
} }
} }
this._reconnectEdges();
this._updateValueRange(this.nodes); this._updateValueRange(this.nodes);
}; };
@ -1173,6 +1174,7 @@ Graph.prototype._updateNodes = function(ids) {
} }
} }
this._reconnectEdges();
this._updateValueRange(nodes); this._updateValueRange(nodes);
}; };
@ -1185,10 +1187,10 @@ Graph.prototype._removeNodes = function(ids) {
var nodes = this.nodes; var nodes = this.nodes;
for (var i = 0, len = ids.length; i < len; i++) { for (var i = 0, len = ids.length; i < len; i++) {
var id = ids[i]; var id = ids[i];
// TODO: detach the node from the edges it is attached to
delete nodes[id]; delete nodes[id];
} }
this._reconnectEdges();
this._updateSelection(); this._updateSelection();
this._updateValueRange(nodes); this._updateValueRange(nodes);
}; };
@ -1224,7 +1226,6 @@ Graph.prototype._setEdges = function(edges) {
} }
// remove drawn edges // remove drawn edges
// TODO: detach all existing edges
this.edges = {}; this.edges = {};
if (this.edgesData) { if (this.edgesData) {
@ -1238,6 +1239,8 @@ Graph.prototype._setEdges = function(edges) {
var ids = this.edgesData.getIds(); var ids = this.edgesData.getIds();
this._addEdges(ids); this._addEdges(ids);
} }
this._reconnectEdges();
}; };
/** /**
@ -1253,16 +1256,11 @@ Graph.prototype._addEdges = function (ids) {
var oldEdge = edges[id]; var oldEdge = edges[id];
if (oldEdge) { if (oldEdge) {
oldEdge.from.detachEdge(oldEdge);
oldEdge.to.detachEdge(oldEdge);
oldEdge.disconnect();
} }
var data = edgesData.get(id); var data = edgesData.get(id);
var edge = new Edge(data, this, this.constants);
edges[id] = edge;
// TODO: test whether from and to are provided
edge.from.attachEdge(edge);
edge.to.attachEdge(edge);
edges[id] = new Edge(data, this, this.constants);
} }
this.moving = true; this.moving = true;
@ -1284,19 +1282,13 @@ Graph.prototype._updateEdges = function (ids) {
var edge = edges[id]; var edge = edges[id];
if (edge) { if (edge) {
// update edge // update edge
edge.from.detachEdge(edge);
edge.to.detachEdge(edge);
edge.disconnect();
edge.setProperties(data, this.constants); edge.setProperties(data, this.constants);
edge.from.attachEdge(edge);
edge.to.attachEdge(edge);
edge.connect();
} }
else { else {
// create edge // create edge
edge = new Edge(data, this, this.constants); edge = new Edge(data, this, this.constants);
edge.from.attachEdge(edge);
edge.to.attachEdge(edge);
this.edges[id] = edge; this.edges[id] = edge;
} }
} }
@ -1316,8 +1308,7 @@ Graph.prototype._removeEdges = function (ids) {
var id = ids[i]; var id = ids[i];
var edge = edges[id]; var edge = edges[id];
if (edge) { if (edge) {
edge.from.detachEdge(edge);
edge.to.detachEdge(edge);
edge.disconnect();
delete edges[id]; delete edges[id];
} }
} }
@ -1326,6 +1317,30 @@ Graph.prototype._removeEdges = function (ids) {
this._updateValueRange(edges); this._updateValueRange(edges);
}; };
/**
* Reconnect all edges
* @private
*/
Graph.prototype._reconnectEdges = function() {
var id,
nodes = this.nodes,
edges = this.edges;
for (id in nodes) {
if (nodes.hasOwnProperty(id)) {
nodes[id].edges = [];
}
}
for (id in edges) {
if (edges.hasOwnProperty(id)) {
var edge = edges[id];
edge.from = null;
edge.to = null;
edge.connect();
}
}
};
/** /**
* Update the values of all object in the given array according to the current * Update the values of all object in the given array according to the current
* value range of the objects in the array. * value range of the objects in the array.
@ -1498,7 +1513,10 @@ Graph.prototype._drawEdges = function(ctx) {
var edges = this.edges; var edges = this.edges;
for (var id in edges) { for (var id in edges) {
if (edges.hasOwnProperty(id)) { if (edges.hasOwnProperty(id)) {
edges[id].draw(ctx);
var edge = edges[id];
if (edge.connected) {
edges[id].draw(ctx);
}
} }
} }
}; };
@ -1618,23 +1636,24 @@ Graph.prototype._calculateForces = function() {
for (id in edges) { for (id in edges) {
if (edges.hasOwnProperty(id)) { if (edges.hasOwnProperty(id)) {
var edge = edges[id]; var edge = edges[id];
dx = (edge.to.x - edge.from.x);
dy = (edge.to.y - edge.from.y);
//edgeLength = (edge.from.width + edge.from.height + edge.to.width + edge.to.height)/2 || edge.length; // TODO: dmin
//edgeLength = (edge.from.width + edge.to.width)/2 || edge.length; // TODO: dmin
//edgeLength = 20 + ((edge.from.width + edge.to.width) || 0) / 2;
edgeLength = edge.length;
length = Math.sqrt(dx * dx + dy * dy);
angle = Math.atan2(dy, dx);
springForce = edge.stiffness * (edgeLength - length);
fx = Math.cos(angle) * springForce;
fy = Math.sin(angle) * springForce;
edge.from._addForce(-fx, -fy);
edge.to._addForce(fx, fy);
if (edge.connected) {
dx = (edge.to.x - edge.from.x);
dy = (edge.to.y - edge.from.y);
//edgeLength = (edge.from.width + edge.from.height + edge.to.width + edge.to.height)/2 || edge.length; // TODO: dmin
//edgeLength = (edge.from.width + edge.to.width)/2 || edge.length; // TODO: dmin
//edgeLength = 20 + ((edge.from.width + edge.to.width) || 0) / 2;
edgeLength = edge.length;
length = Math.sqrt(dx * dx + dy * dy);
angle = Math.atan2(dy, dx);
springForce = edge.stiffness * (edgeLength - length);
fx = Math.cos(angle) * springForce;
fy = Math.sin(angle) * springForce;
edge.from._addForce(-fx, -fy);
edge.to._addForce(fx, fy);
}
} }
} }

+ 3
- 1
src/graph/Node.js View File

@ -67,7 +67,9 @@ function Node(properties, imagelist, grouplist, constants) {
* @param {Edge} edge * @param {Edge} edge
*/ */
Node.prototype.attachEdge = function(edge) { Node.prototype.attachEdge = function(edge) {
this.edges.push(edge);
if (this.edges.indexOf(edge) == -1) {
this.edges.push(edge);
}
this._updateMass(); this._updateMass();
}; };

+ 109
- 47
vis.js View File

@ -7932,7 +7932,9 @@ function Node(properties, imagelist, grouplist, constants) {
* @param {Edge} edge * @param {Edge} edge
*/ */
Node.prototype.attachEdge = function(edge) { Node.prototype.attachEdge = function(edge) {
this.edges.push(edge);
if (this.edges.indexOf(edge) == -1) {
this.edges.push(edge);
}
this._updateMass(); this._updateMass();
}; };
@ -8570,12 +8572,18 @@ function Edge (properties, graph, constants) {
// initialize variables // initialize variables
this.id = undefined; this.id = undefined;
this.fromId = undefined;
this.toId = undefined;
this.style = constants.edges.style; this.style = constants.edges.style;
this.title = undefined; this.title = undefined;
this.width = constants.edges.width; this.width = constants.edges.width;
this.value = undefined; this.value = undefined;
this.length = constants.edges.length; this.length = constants.edges.length;
this.from = null; // a node
this.to = null; // a node
this.connected = false;
// Added to support dashed lines // Added to support dashed lines
// David Jordan // David Jordan
// 2012-08-08 // 2012-08-08
@ -8587,7 +8595,7 @@ function Edge (properties, graph, constants) {
this.lengthFixed = false; this.lengthFixed = false;
this.setProperties(properties, constants); this.setProperties(properties, constants);
};
}
/** /**
* Set or overwrite properties for the edge * Set or overwrite properties for the edge
@ -8599,8 +8607,8 @@ Edge.prototype.setProperties = function(properties, constants) {
return; return;
} }
if (properties.from != undefined) {this.from = this.graph.nodes[properties.from];}
if (properties.to != undefined) {this.to = this.graph.nodes[properties.to];}
if (properties.from != undefined) {this.fromId = properties.from;}
if (properties.to != undefined) {this.toId = properties.to;}
if (properties.id != undefined) {this.id = properties.id;} if (properties.id != undefined) {this.id = properties.id;}
if (properties.style != undefined) {this.style = properties.style;} if (properties.style != undefined) {this.style = properties.style;}
@ -8629,16 +8637,11 @@ Edge.prototype.setProperties = function(properties, constants) {
if (properties.color != undefined) {this.color = properties.color;} if (properties.color != undefined) {this.color = properties.color;}
if (!this.from) {
throw "Node with id " + properties.from + " not found";
}
if (!this.to) {
throw "Node with id " + properties.to + " not found";
}
// A node is connected when it has a from and to node.
this.connect();
this.widthFixed = this.widthFixed || (properties.width != undefined); this.widthFixed = this.widthFixed || (properties.width != undefined);
this.lengthFixed = this.lengthFixed || (properties.length != undefined); this.lengthFixed = this.lengthFixed || (properties.length != undefined);
this.stiffness = 1 / this.length; this.stiffness = 1 / this.length;
// set draw method based on style // set draw method based on style
@ -8651,6 +8654,46 @@ Edge.prototype.setProperties = function(properties, constants) {
} }
}; };
/**
* Connect an edge to its nodes
*/
Edge.prototype.connect = function () {
this.disconnect();
this.from = this.graph.nodes[this.fromId] || null;
this.to = this.graph.nodes[this.toId] || null;
this.connected = (this.from && this.to);
if (this.connected) {
this.from.attachEdge(this);
this.to.attachEdge(this);
}
else {
if (this.from) {
this.from.detachEdge(this);
}
if (this.to) {
this.to.detachEdge(this);
}
}
};
/**
* Disconnect an edge from its nodes
*/
Edge.prototype.disconnect = function () {
if (this.from) {
this.from.detachEdge(this);
this.from = null;
}
if (this.to) {
this.to.detachEdge(this);
this.to = null;
}
this.connected = false;
};
/** /**
* get the title of this edge. * get the title of this edge.
* @return {string} title The title of the edge, or undefined when no title * @return {string} title The title of the edge, or undefined when no title
@ -10005,7 +10048,8 @@ Graph.prototype._checkShowPopup = function (x, y) {
for (id in edges) { for (id in edges) {
if (edges.hasOwnProperty(id)) { if (edges.hasOwnProperty(id)) {
var edge = edges[id]; var edge = edges[id];
if (edge.getTitle() != undefined && edge.isOverlappingWith(obj)) {
if (edge.connected && (edge.getTitle() != undefined) &&
edge.isOverlappingWith(obj)) {
this.popupNode = edge; this.popupNode = edge;
break; break;
} }
@ -10462,7 +10506,6 @@ Graph.prototype._addNodes = function(ids) {
id = ids[i]; id = ids[i];
var data = this.nodesData.get(id); var data = this.nodesData.get(id);
var node = new Node(data, this.images, this.groups, this.constants); var node = new Node(data, this.images, this.groups, this.constants);
// TODO: detach any old node from the edges it is attached to
this.nodes[id] = node; // note: this may replace an existing node this.nodes[id] = node; // note: this may replace an existing node
if (!node.isFixed()) { if (!node.isFixed()) {
@ -10479,6 +10522,7 @@ Graph.prototype._addNodes = function(ids) {
} }
} }
this._reconnectEdges();
this._updateValueRange(this.nodes); this._updateValueRange(this.nodes);
}; };
@ -10509,6 +10553,7 @@ Graph.prototype._updateNodes = function(ids) {
} }
} }
this._reconnectEdges();
this._updateValueRange(nodes); this._updateValueRange(nodes);
}; };
@ -10521,10 +10566,10 @@ Graph.prototype._removeNodes = function(ids) {
var nodes = this.nodes; var nodes = this.nodes;
for (var i = 0, len = ids.length; i < len; i++) { for (var i = 0, len = ids.length; i < len; i++) {
var id = ids[i]; var id = ids[i];
// TODO: detach the node from the edges it is attached to
delete nodes[id]; delete nodes[id];
} }
this._reconnectEdges();
this._updateSelection(); this._updateSelection();
this._updateValueRange(nodes); this._updateValueRange(nodes);
}; };
@ -10560,7 +10605,6 @@ Graph.prototype._setEdges = function(edges) {
} }
// remove drawn edges // remove drawn edges
// TODO: detach all existing edges
this.edges = {}; this.edges = {};
if (this.edgesData) { if (this.edgesData) {
@ -10574,6 +10618,8 @@ Graph.prototype._setEdges = function(edges) {
var ids = this.edgesData.getIds(); var ids = this.edgesData.getIds();
this._addEdges(ids); this._addEdges(ids);
} }
this._reconnectEdges();
}; };
/** /**
@ -10589,16 +10635,11 @@ Graph.prototype._addEdges = function (ids) {
var oldEdge = edges[id]; var oldEdge = edges[id];
if (oldEdge) { if (oldEdge) {
oldEdge.from.detachEdge(oldEdge);
oldEdge.to.detachEdge(oldEdge);
oldEdge.disconnect();
} }
var data = edgesData.get(id); var data = edgesData.get(id);
var edge = new Edge(data, this, this.constants);
edges[id] = edge;
// TODO: test whether from and to are provided
edge.from.attachEdge(edge);
edge.to.attachEdge(edge);
edges[id] = new Edge(data, this, this.constants);
} }
this.moving = true; this.moving = true;
@ -10620,19 +10661,13 @@ Graph.prototype._updateEdges = function (ids) {
var edge = edges[id]; var edge = edges[id];
if (edge) { if (edge) {
// update edge // update edge
edge.from.detachEdge(edge);
edge.to.detachEdge(edge);
edge.disconnect();
edge.setProperties(data, this.constants); edge.setProperties(data, this.constants);
edge.from.attachEdge(edge);
edge.to.attachEdge(edge);
edge.connect();
} }
else { else {
// create edge // create edge
edge = new Edge(data, this, this.constants); edge = new Edge(data, this, this.constants);
edge.from.attachEdge(edge);
edge.to.attachEdge(edge);
this.edges[id] = edge; this.edges[id] = edge;
} }
} }
@ -10652,8 +10687,7 @@ Graph.prototype._removeEdges = function (ids) {
var id = ids[i]; var id = ids[i];
var edge = edges[id]; var edge = edges[id];
if (edge) { if (edge) {
edge.from.detachEdge(edge);
edge.to.detachEdge(edge);
edge.disconnect();
delete edges[id]; delete edges[id];
} }
} }
@ -10662,6 +10696,30 @@ Graph.prototype._removeEdges = function (ids) {
this._updateValueRange(edges); this._updateValueRange(edges);
}; };
/**
* Reconnect all edges
* @private
*/
Graph.prototype._reconnectEdges = function() {
var id,
nodes = this.nodes,
edges = this.edges;
for (id in nodes) {
if (nodes.hasOwnProperty(id)) {
nodes[id].edges = [];
}
}
for (id in edges) {
if (edges.hasOwnProperty(id)) {
var edge = edges[id];
edge.from = null;
edge.to = null;
edge.connect();
}
}
};
/** /**
* Update the values of all object in the given array according to the current * Update the values of all object in the given array according to the current
* value range of the objects in the array. * value range of the objects in the array.
@ -10834,7 +10892,10 @@ Graph.prototype._drawEdges = function(ctx) {
var edges = this.edges; var edges = this.edges;
for (var id in edges) { for (var id in edges) {
if (edges.hasOwnProperty(id)) { if (edges.hasOwnProperty(id)) {
edges[id].draw(ctx);
var edge = edges[id];
if (edge.connected) {
edges[id].draw(ctx);
}
} }
} }
}; };
@ -10954,23 +11015,24 @@ Graph.prototype._calculateForces = function() {
for (id in edges) { for (id in edges) {
if (edges.hasOwnProperty(id)) { if (edges.hasOwnProperty(id)) {
var edge = edges[id]; var edge = edges[id];
if (edge.connected) {
dx = (edge.to.x - edge.from.x);
dy = (edge.to.y - edge.from.y);
//edgeLength = (edge.from.width + edge.from.height + edge.to.width + edge.to.height)/2 || edge.length; // TODO: dmin
//edgeLength = (edge.from.width + edge.to.width)/2 || edge.length; // TODO: dmin
//edgeLength = 20 + ((edge.from.width + edge.to.width) || 0) / 2;
edgeLength = edge.length;
length = Math.sqrt(dx * dx + dy * dy);
angle = Math.atan2(dy, dx);
dx = (edge.to.x - edge.from.x);
dy = (edge.to.y - edge.from.y);
//edgeLength = (edge.from.width + edge.from.height + edge.to.width + edge.to.height)/2 || edge.length; // TODO: dmin
//edgeLength = (edge.from.width + edge.to.width)/2 || edge.length; // TODO: dmin
//edgeLength = 20 + ((edge.from.width + edge.to.width) || 0) / 2;
edgeLength = edge.length;
length = Math.sqrt(dx * dx + dy * dy);
angle = Math.atan2(dy, dx);
springForce = edge.stiffness * (edgeLength - length);
springForce = edge.stiffness * (edgeLength - length);
fx = Math.cos(angle) * springForce;
fy = Math.sin(angle) * springForce;
fx = Math.cos(angle) * springForce;
fy = Math.sin(angle) * springForce;
edge.from._addForce(-fx, -fy);
edge.to._addForce(fx, fy);
edge.from._addForce(-fx, -fy);
edge.to._addForce(fx, fy);
}
} }
} }

+ 5
- 5
vis.min.js
File diff suppressed because it is too large
View File


Loading…
Cancel
Save