|
|
@ -6858,17 +6858,6 @@ function Graph (container, data, options) { |
|
|
|
"dashlength": 10, |
|
|
|
"dashgap": 5 |
|
|
|
}, |
|
|
|
"packages": { |
|
|
|
"radius": 5, |
|
|
|
"radiusMin": 5, |
|
|
|
"radiusMax": 10, |
|
|
|
"style": "dot", |
|
|
|
"color": "#2B7CE9", |
|
|
|
"image": undefined, |
|
|
|
"widthMin": 16, // px
|
|
|
|
"widthMax": 64, // px
|
|
|
|
"duration": 1.0 // seconds
|
|
|
|
}, |
|
|
|
"minForce": 0.05, |
|
|
|
"minVelocity": 0.02, // px/s
|
|
|
|
"maxIterations": 1000 // maximum number of iteration to stabilize
|
|
|
@ -6876,14 +6865,12 @@ function Graph (container, data, options) { |
|
|
|
|
|
|
|
this.nodes = []; // array with Node objects
|
|
|
|
this.edges = []; // array with Edge objects
|
|
|
|
this.packages = []; // array with all Package packages
|
|
|
|
this.images = new Graph.Images(); // object with images
|
|
|
|
this.groups = new Graph.Groups(); // object with groups
|
|
|
|
|
|
|
|
// properties of the data
|
|
|
|
this.hasMovingEdges = false; // True if one or more of the edges or nodes have an animation
|
|
|
|
this.hasMovingNodes = false; // True if any of the nodes have an undefined position
|
|
|
|
this.hasMovingPackages = false; // True if there are one or more packages
|
|
|
|
|
|
|
|
this.selection = []; |
|
|
|
this.timer = undefined; |
|
|
@ -6917,7 +6904,6 @@ Graph.prototype.setData = function(data) { |
|
|
|
this.hasTimestamps = false; |
|
|
|
this.setNodes(data.nodes); |
|
|
|
this.setEdges(data.edges); |
|
|
|
this.setPackages(data.packages); |
|
|
|
|
|
|
|
this._reposition(); // TODO: bad solution
|
|
|
|
if (this.stabilize) { |
|
|
@ -6979,6 +6965,7 @@ Graph.prototype.setOptions = function (options) { |
|
|
|
this.constants.edges.altdashlength = options.edges.altdashlength; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (options.nodes) { |
|
|
|
for (prop in options.nodes) { |
|
|
|
if (options.nodes.hasOwnProperty(prop)) { |
|
|
@ -6991,18 +6978,6 @@ Graph.prototype.setOptions = function (options) { |
|
|
|
if (options.nodes.widthMax) this.constants.nodes.radiusMax = options.nodes.widthMax; |
|
|
|
*/ |
|
|
|
} |
|
|
|
if (options.packages) { |
|
|
|
for (prop in options.packages) { |
|
|
|
if (options.packages.hasOwnProperty(prop)) { |
|
|
|
this.constants.packages[prop] = options.packages[prop]; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/* |
|
|
|
if (options.packages.widthMin) this.constants.packages.radiusMin = options.packages.widthMin; |
|
|
|
if (options.packages.widthMax) this.constants.packages.radiusMax = options.packages.widthMax; |
|
|
|
*/ |
|
|
|
} |
|
|
|
|
|
|
|
if (options.groups) { |
|
|
|
for (var groupname in options.groups) { |
|
|
@ -7399,8 +7374,7 @@ Graph.prototype._onMouseWheel = function(event) { |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* Mouse move handler for checking whether the title moves over a node or |
|
|
|
* package with a title. |
|
|
|
* Mouse move handler for checking whether the title moves over a node with a title. |
|
|
|
*/ |
|
|
|
Graph.prototype._onMouseMoveTitle = function (event) { |
|
|
|
event = event || window.event; |
|
|
@ -7433,8 +7407,8 @@ Graph.prototype._onMouseMoveTitle = function (event) { |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Check if there is an element on the given position in the network ( |
|
|
|
* (a node, package, or edge). If so, and if this element has a title, |
|
|
|
* Check if there is an element on the given position in the network |
|
|
|
* (a node or edge). If so, and if this element has a title, |
|
|
|
* show a popup window with its title. |
|
|
|
* |
|
|
|
* @param {number} x |
|
|
@ -7451,18 +7425,6 @@ Graph.prototype._checkShowPopup = function (x, y) { |
|
|
|
var i, len; |
|
|
|
var lastPopupNode = this.popupNode; |
|
|
|
|
|
|
|
if (this.popupNode == undefined) { |
|
|
|
// search the packages for overlap
|
|
|
|
|
|
|
|
for (i = 0, len = this.packages.length; i < len; i++) { |
|
|
|
var p = this.packages[i]; |
|
|
|
if (p.getTitle() != undefined && p.isOverlappingWith(obj)) { |
|
|
|
this.popupNode = p; |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (this.popupNode == undefined) { |
|
|
|
// search the nodes for overlap, select the top one in case of multiple nodes
|
|
|
|
var nodes = this.nodes; |
|
|
@ -7961,7 +7923,7 @@ Graph.prototype._filterNodes = function(timestamp) { |
|
|
|
|
|
|
|
/** |
|
|
|
* Create a node with the given properties |
|
|
|
* If the new node has an id identical to an existing package, the existing |
|
|
|
* If the new node has an id identical to an existing node, the existing |
|
|
|
* node will be overwritten. |
|
|
|
* The properties can contain a property "action", which can have values |
|
|
|
* "create", "update", or "delete" |
|
|
@ -7987,7 +7949,7 @@ Graph.prototype._createNode = function(properties) { |
|
|
|
this._unselectNodes([{'row': index}], false); |
|
|
|
} |
|
|
|
|
|
|
|
/* TODO: implement this? -> will give performance issues, searching all edges and node... |
|
|
|
/* TODO: implement this? -> will give performance issues, searching all edges and nodes... |
|
|
|
// update edges linking to this node
|
|
|
|
var edgesTable = this.edges; |
|
|
|
for (var i = 0, iMax = edgesTable.length; i < iMax; i++) { |
|
|
@ -7999,18 +7961,6 @@ Graph.prototype._createNode = function(properties) { |
|
|
|
edge.to = newNode; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// update packages linking to this node
|
|
|
|
var packagesTable = this.packages; |
|
|
|
for (var i = 0, iMax = packagesTable.length; i < iMax; i++) { |
|
|
|
var package = packagesTable[i]; |
|
|
|
if (package.from == oldNode) { |
|
|
|
package.from = newNode; |
|
|
|
} |
|
|
|
if (package.to == oldNode) { |
|
|
|
package.to = newNode; |
|
|
|
} |
|
|
|
} |
|
|
|
*/ |
|
|
|
} |
|
|
|
else { |
|
|
@ -8149,7 +8099,7 @@ Graph.prototype._filterEdges = function(timestamp) { |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
// remove existing packages with a too new timestamp
|
|
|
|
// remove existing edges with a too new timestamp
|
|
|
|
if (timestamp !== undefined) { |
|
|
|
var ls = this.edges; |
|
|
|
var l = 0; |
|
|
@ -8284,22 +8234,20 @@ Graph.prototype._createEdge = function(properties) { |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Update the edge to oldNode in all edges and packages. |
|
|
|
* Update the references to oldNode in all edges. |
|
|
|
* @param {Node} oldNode |
|
|
|
* @param {Node} newNode |
|
|
|
*/ |
|
|
|
// TODO: start utilizing this method _updateNodeReferences
|
|
|
|
Graph.prototype._updateNodeReferences = function(oldNode, newNode) { |
|
|
|
var arrays = [this.edges, this.packages]; |
|
|
|
for (var a = 0, aMax = arrays.length; a < aMax; a++) { |
|
|
|
var array = arrays[a]; |
|
|
|
for (var i = 0, iMax = array.length; i < iMax; i++) { |
|
|
|
if (array.from === oldNode) { |
|
|
|
array.from = newNode; |
|
|
|
} |
|
|
|
if (array.to === oldNode) { |
|
|
|
array.to = newNode; |
|
|
|
} |
|
|
|
var edges = this.edges; |
|
|
|
for (var i = 0, iMax = edges.length; i < iMax; i++) { |
|
|
|
var edge = edges[i]; |
|
|
|
if (edge.from === oldNode) { |
|
|
|
edge.from = newNode; |
|
|
|
} |
|
|
|
if (edge.to === oldNode) { |
|
|
|
edge.to = newNode; |
|
|
|
} |
|
|
|
} |
|
|
|
}; |
|
|
@ -8330,232 +8278,10 @@ Graph.prototype._findEdgeByRow = function (row) { |
|
|
|
return this.edges[row]; |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Set a new packages table |
|
|
|
* Packages with a duplicate id will be replaced |
|
|
|
* @param {Array} packages The data containing the packages. |
|
|
|
*/ |
|
|
|
Graph.prototype.setPackages = function(packages) { |
|
|
|
this.packages = []; |
|
|
|
if (!packages) { |
|
|
|
return; |
|
|
|
} |
|
|
|
this.packagesTable = packages; |
|
|
|
|
|
|
|
var rowCount = packages.length; |
|
|
|
for (var i = 0; i < rowCount; i++) { |
|
|
|
var properties = packages[i]; |
|
|
|
|
|
|
|
if (properties.from === undefined) { |
|
|
|
throw "Column 'from' missing in table with packages (row " + i + ")"; |
|
|
|
} |
|
|
|
if (properties.to === undefined) { |
|
|
|
throw "Column 'to' missing in table with packages (row " + i + ")"; |
|
|
|
} |
|
|
|
if (properties.timestamp) { |
|
|
|
this.hasTimestamps = this.hasTimestamps || properties.timestamp; |
|
|
|
} |
|
|
|
|
|
|
|
this._createPackage(properties); |
|
|
|
} |
|
|
|
|
|
|
|
// calculate scaling function when value is provided
|
|
|
|
this._updateValueRange(this.packages); |
|
|
|
|
|
|
|
/* TODO: adjust examples and documentation for this? |
|
|
|
this.start(); |
|
|
|
*/ |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Filter the current package table for packages with a timestamp below given |
|
|
|
* timestamp. |
|
|
|
* @param {*} [timestamp] If timestamp is undefined, all packages are shown |
|
|
|
*/ |
|
|
|
Graph.prototype._filterPackages = function(timestamp) { |
|
|
|
if (this.packagesTable == undefined) { |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
// remove all current packages
|
|
|
|
this.packages = []; |
|
|
|
|
|
|
|
/* TODO: cleanup |
|
|
|
// remove existing packages with a too new timestamp
|
|
|
|
if (timestamp !== undefined) { |
|
|
|
var packages = this.packages; |
|
|
|
var p = 0; |
|
|
|
while (p < packages.length) { |
|
|
|
var package = packages[p]; |
|
|
|
var t = package.timestamp; |
|
|
|
|
|
|
|
if (t !== undefined && t > timestamp ) { |
|
|
|
// remove this package
|
|
|
|
packages.splice(p, 1); |
|
|
|
} |
|
|
|
else { |
|
|
|
p++; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
*/ |
|
|
|
|
|
|
|
// add all packages with an old enough timestamp
|
|
|
|
var table = this.packagesTable; |
|
|
|
var rowCount = table.length; |
|
|
|
for (var i = 0; i < rowCount; i++) { |
|
|
|
var properties = table[i]; |
|
|
|
|
|
|
|
if (properties.from === undefined) { |
|
|
|
throw "Column 'from' missing in table with packages (row " + i + ")"; |
|
|
|
} |
|
|
|
if (properties.to === undefined) { |
|
|
|
throw "Column 'to' missing in table with packages (row " + i + ")"; |
|
|
|
} |
|
|
|
// check what the timestamp is
|
|
|
|
var pTimestamp = properties.timestamp ? properties.timestamp : undefined; |
|
|
|
|
|
|
|
var visible = true; |
|
|
|
if (pTimestamp !== undefined && timestamp !== undefined && pTimestamp > timestamp) { |
|
|
|
visible = false; |
|
|
|
} |
|
|
|
|
|
|
|
if (visible === true) { |
|
|
|
if (properties.progress == undefined) { |
|
|
|
// when no progress is provided, we need to add our own progress
|
|
|
|
var duration = properties.duration || this.constants.packages.duration; // seconds
|
|
|
|
|
|
|
|
var diff = (timestamp.getTime() - pTimestamp.getTime()) / 1000; // seconds
|
|
|
|
if (diff < duration) { |
|
|
|
// copy the properties, and fill in the current progress based on the
|
|
|
|
// timestamp and the duration
|
|
|
|
var original = properties; |
|
|
|
properties = {}; |
|
|
|
for (var j in original) { |
|
|
|
if (original.hasOwnProperty(j)) { |
|
|
|
properties[j] = original[j]; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
properties.progress = diff / duration; // scale 0-1
|
|
|
|
} |
|
|
|
else { |
|
|
|
visible = false; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (visible === true) { |
|
|
|
// create or update the package
|
|
|
|
this._createPackage(properties); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
this.start(); |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Create a package with the given properties |
|
|
|
* If the new package has an id identical to an existing package, the existing |
|
|
|
* package will be overwritten. |
|
|
|
* The properties can contain a property "action", which can have values |
|
|
|
* "create", "update", or "delete" |
|
|
|
* @param {Object} properties An object with properties |
|
|
|
*/ |
|
|
|
Graph.prototype._createPackage = function(properties) { |
|
|
|
var action = properties.action ? properties.action : "create"; |
|
|
|
var id, index, newPackage; |
|
|
|
|
|
|
|
if (action === "create") { |
|
|
|
// create the package
|
|
|
|
id = properties.id; |
|
|
|
index = (id !== undefined) ? this._findPackage(id) : undefined; |
|
|
|
newPackage = new Graph.Package(properties, this, this.images, this.constants); |
|
|
|
|
|
|
|
if (index !== undefined) { |
|
|
|
// replace existing package
|
|
|
|
this.packages[index] = newPackage; |
|
|
|
} |
|
|
|
else { |
|
|
|
// add new package
|
|
|
|
this.packages.push(newPackage); |
|
|
|
} |
|
|
|
|
|
|
|
if (newPackage.isMoving()) { |
|
|
|
this.hasMovingPackages = true; |
|
|
|
} |
|
|
|
} |
|
|
|
else if (action === "update") { |
|
|
|
// update a package, or create it when not existing
|
|
|
|
id = properties.id; |
|
|
|
if (id === undefined) { |
|
|
|
throw "Cannot update a edge without id"; |
|
|
|
} |
|
|
|
|
|
|
|
index = this._findPackage(id); |
|
|
|
if (index !== undefined) { |
|
|
|
// update existing package
|
|
|
|
this.packages[index].setProperties(properties, this.constants); |
|
|
|
} |
|
|
|
else { |
|
|
|
// add new package
|
|
|
|
newPackage = new Graph.Package(properties, this, this.images, this.constants); |
|
|
|
this.packages.push(newPackage); |
|
|
|
if (newPackage.isMoving()) { |
|
|
|
this.hasMovingPackages = true; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
else if (action === "delete") { |
|
|
|
// delete existing package
|
|
|
|
id = properties.id; |
|
|
|
if (id === undefined) { |
|
|
|
throw "Cannot delete package without its id"; |
|
|
|
} |
|
|
|
|
|
|
|
index = this._findPackage(id); |
|
|
|
if (index !== undefined) { |
|
|
|
this.packages.splice(index, 1); |
|
|
|
} |
|
|
|
else { |
|
|
|
throw "Package with id " + id + " not found"; |
|
|
|
} |
|
|
|
} |
|
|
|
else { |
|
|
|
throw "Unknown action " + action + ". Choose 'create', 'update', or 'delete'."; |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Find a package by its id. |
|
|
|
* @param {Number} id |
|
|
|
* @return {Number} index Index of the package in the array this.packages, |
|
|
|
* or undefined when not found |
|
|
|
*/ |
|
|
|
Graph.prototype._findPackage = function (id) { |
|
|
|
var packages = this.packages; |
|
|
|
for (var n = 0, len = packages.length; n < len; n++) { |
|
|
|
if (packages[n].id === id) { |
|
|
|
return n; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return undefined; |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Find a package by its row |
|
|
|
* @param {Number} row Row of the package |
|
|
|
* @return {Graph.Package} the found package, or undefined when not found |
|
|
|
*/ |
|
|
|
Graph.prototype._findPackageByRow = function (row) { |
|
|
|
return this.packages[row]; |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Update the values of all object in the given array according to the current |
|
|
|
* value range of the objects in the array. |
|
|
|
* @param {Array} array. An array with objects like Edges, Nodes, or Packages |
|
|
|
* @param {Array} array. An array with objects like Edges or Nodes |
|
|
|
* The objects must have a method getValue() and |
|
|
|
* setValueRange(min, max). |
|
|
|
*/ |
|
|
@ -8584,19 +8310,18 @@ Graph.prototype._updateValueRange = function(array) { |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* Set the current timestamp. All packages with a timestamp smaller or equal |
|
|
|
* Set the current timestamp. All nodes and edges with a timestamp smaller or equal |
|
|
|
* than the given timestamp will be drawn. |
|
|
|
* @param {Date | Number} timestamp |
|
|
|
*/ |
|
|
|
Graph.prototype.setTimestamp = function(timestamp) { |
|
|
|
this._filterNodes(timestamp); |
|
|
|
this._filterEdges(timestamp); |
|
|
|
this._filterPackages(timestamp); |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* Get the range of all timestamps defined in the nodes, edges and packages |
|
|
|
* Get the range of all timestamps defined in the nodes and edges |
|
|
|
* @return {Object} A range object, containing parameters start and end. |
|
|
|
*/ |
|
|
|
Graph.prototype._getRange = function() { |
|
|
@ -8628,31 +8353,6 @@ Graph.prototype._getRange = function() { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// calculate the range for the packagesTable by hand. In case of packages
|
|
|
|
// without a progress provided, we need to calculate the end time by hand.
|
|
|
|
if (this.packagesTable) { |
|
|
|
var packagesTable = this.packagesTable; |
|
|
|
for (var row = 0, len = packagesTable.length; row < len; row ++) { |
|
|
|
var pkg = packagesTable[row], |
|
|
|
timestamp = pkg.timestamp, |
|
|
|
progress = pkg.progress, |
|
|
|
duration = pkg.duration || this.constants.packages.duration; |
|
|
|
|
|
|
|
// convert to number
|
|
|
|
if (timestamp instanceof Date) { |
|
|
|
timestamp = timestamp.getTime(); |
|
|
|
} |
|
|
|
|
|
|
|
if (timestamp != undefined) { |
|
|
|
var start = timestamp, |
|
|
|
end = progress ? timestamp : (timestamp + duration * 1000); |
|
|
|
|
|
|
|
range.start = range.start ? Math.min(start, range.start) : start; |
|
|
|
range.end = range.end ? Math.max(end, range.end) : end; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// convert to the right type: number or date
|
|
|
|
var rangeFormat = { |
|
|
|
"start": new Date(range.start), |
|
|
@ -8745,7 +8445,6 @@ Graph.prototype._redraw = function() { |
|
|
|
|
|
|
|
this._drawEdges(ctx); |
|
|
|
this._drawNodes(ctx); |
|
|
|
this._drawPackages(ctx); |
|
|
|
this._drawSlider(); |
|
|
|
|
|
|
|
// restore original scaling and translation
|
|
|
@ -8867,19 +8566,6 @@ Graph.prototype._drawEdges = function(ctx) { |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Redraw all packages |
|
|
|
* The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d"); |
|
|
|
* @param {CanvasRenderingContext2D} ctx |
|
|
|
*/ |
|
|
|
Graph.prototype._drawPackages = function(ctx) { |
|
|
|
var packages = this.packages; |
|
|
|
for (var i = 0, iMax = packages.length; i < iMax; i++) { |
|
|
|
packages[i].draw(ctx); |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* Redraw the filter |
|
|
|
*/ |
|
|
@ -9175,42 +8861,8 @@ Graph.prototype._discreteStepNodes = function() { |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* Perform one discrete step for all packages |
|
|
|
*/ |
|
|
|
Graph.prototype._discreteStepPackages = function() { |
|
|
|
var interval = this.refreshRate / 1000.0; // in seconds
|
|
|
|
var packages = this.packages; |
|
|
|
for (var n = 0, nMax = packages.length; n < nMax; n++) { |
|
|
|
packages[n].discreteStep(interval); |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* Cleanup finished packages. |
|
|
|
* also checks if there are moving packages |
|
|
|
*/ |
|
|
|
Graph.prototype._deleteFinishedPackages = function() { |
|
|
|
var n = 0; |
|
|
|
var hasMovingPackages = false; |
|
|
|
while (n < this.packages.length) { |
|
|
|
if (this.packages[n].isFinished()) { |
|
|
|
this.packages.splice(n, 1); |
|
|
|
n--; |
|
|
|
} |
|
|
|
else if (this.packages[n].isMoving()) { |
|
|
|
hasMovingPackages = true; |
|
|
|
} |
|
|
|
n++; |
|
|
|
} |
|
|
|
|
|
|
|
this.hasMovingPackages = hasMovingPackages; |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Start animating nodes, edges, and packages. |
|
|
|
* Start animating nodes and edges |
|
|
|
*/ |
|
|
|
Graph.prototype.start = function() { |
|
|
|
if (this.hasMovingNodes) { |
|
|
@ -9221,12 +8873,7 @@ Graph.prototype.start = function() { |
|
|
|
this.hasMovingNodes = this.isMoving(vmin); |
|
|
|
} |
|
|
|
|
|
|
|
if (this.hasMovingPackages) { |
|
|
|
this._discreteStepPackages(); |
|
|
|
this._deleteFinishedPackages(); |
|
|
|
} |
|
|
|
|
|
|
|
if (this.hasMovingNodes || this.hasMovingEdges || this.hasMovingPackages) { |
|
|
|
if (this.hasMovingNodes || this.hasMovingEdges) { |
|
|
|
// start animation. only start timer if it is not already running
|
|
|
|
if (!this.timer) { |
|
|
|
var graph = this; |
|
|
@ -9243,7 +8890,7 @@ Graph.prototype.start = function() { |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Stop animating nodes, edges, and packages. |
|
|
|
* Stop animating nodes and edges. |
|
|
|
*/ |
|
|
|
Graph.prototype.stop = function () { |
|
|
|
if (this.timer) { |
|
|
@ -10825,334 +10472,6 @@ Graph.Images.prototype.load = function(url) { |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
/**--------------------------------------------------------------------------**/ |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* @class Package |
|
|
|
* This class contains one package |
|
|
|
* |
|
|
|
* @param {number} properties Properties for the package. Optional. Available |
|
|
|
* properties are: id {number}, title {string}, |
|
|
|
* style {string} with available values "dot" and |
|
|
|
* "image", radius {number}, image {string}, |
|
|
|
* color {string}, progress {number} with a value |
|
|
|
* between 0-1, duration {number}, timestamp {number |
|
|
|
* or Date}. |
|
|
|
* @param {Graph} graph The graph object, used to find |
|
|
|
* and edge to nodes. |
|
|
|
* @param {Graph.Images} imagelist An Images object. Only needed |
|
|
|
* when the package has style 'image' |
|
|
|
* @param {Object} constants An object with default values for |
|
|
|
* example for the color |
|
|
|
*/ |
|
|
|
Graph.Package = function (properties, graph, imagelist, constants) { |
|
|
|
if (graph == undefined) { |
|
|
|
throw "No graph provided"; |
|
|
|
} |
|
|
|
|
|
|
|
// constants
|
|
|
|
this.radiusMin = constants.packages.radiusMin; |
|
|
|
this.radiusMax = constants.packages.radiusMax; |
|
|
|
this.imagelist = imagelist; |
|
|
|
this.graph = graph; |
|
|
|
|
|
|
|
// initialize variables
|
|
|
|
this.id = undefined; |
|
|
|
this.from = undefined; |
|
|
|
this.to = undefined; |
|
|
|
this.title = undefined; |
|
|
|
this.style = constants.packages.style; |
|
|
|
this.radius = constants.packages.radius; |
|
|
|
this.color = constants.packages.color; |
|
|
|
this.image = constants.packages.image; |
|
|
|
this.value = undefined; |
|
|
|
this.progress = 0.0; |
|
|
|
this.timestamp = undefined; |
|
|
|
this.duration = constants.packages.duration; |
|
|
|
this.autoProgress = true; |
|
|
|
this.radiusFixed = false; |
|
|
|
|
|
|
|
// set properties
|
|
|
|
this.setProperties(properties, constants); |
|
|
|
}; |
|
|
|
|
|
|
|
Graph.Package.DEFAULT_DURATION = 1.0; // seconds
|
|
|
|
|
|
|
|
/** |
|
|
|
* Set or overwrite properties for the package |
|
|
|
* @param {Object} properties an object with properties |
|
|
|
* @param {Object} constants and object with default, global properties |
|
|
|
*/ |
|
|
|
Graph.Package.prototype.setProperties = function(properties, constants) { |
|
|
|
if (!properties) { |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
// note that the provided properties can also be null
|
|
|
|
if (properties.from != undefined) {this.from = this.graph._getNode(properties.from);} |
|
|
|
if (properties.to != undefined) {this.to = this.graph._getNode(properties.to);} |
|
|
|
|
|
|
|
if (!this.from) { |
|
|
|
throw "Node with id " + properties.from + " not found"; |
|
|
|
} |
|
|
|
if (!this.to) { |
|
|
|
throw "Node with id " + properties.to + " not found"; |
|
|
|
} |
|
|
|
|
|
|
|
if (properties.id != undefined) {this.id = properties.id;} |
|
|
|
if (properties.title != undefined) {this.title = properties.title;} |
|
|
|
if (properties.style != undefined) {this.style = properties.style;} |
|
|
|
if (properties.radius != undefined) {this.radius = properties.radius;} |
|
|
|
if (properties.value != undefined) {this.value = properties.value;} |
|
|
|
if (properties.image != undefined) {this.image = properties.image;} |
|
|
|
if (properties.color != undefined) {this.color = properties.color;} |
|
|
|
if (properties.dashlength != undefined) {this.dashlength = properties.dashlength;} |
|
|
|
if (properties.dashgap != undefined) {this.dashgap = properties.dashgap;} |
|
|
|
if (properties.altdashlength != undefined) {this.altdashlength = properties.altdashlength;} |
|
|
|
if (properties.progress != undefined) {this.progress = properties.progress;} |
|
|
|
if (properties.timestamp != undefined) {this.timestamp = properties.timestamp;} |
|
|
|
if (properties.duration != undefined) {this.duration = properties.duration;} |
|
|
|
|
|
|
|
this.radiusFixed = this.radiusFixed || (properties.radius != undefined); |
|
|
|
this.autoProgress = (this.autoProgress == true) ? (properties.progress == undefined) : false; |
|
|
|
|
|
|
|
if (this.style == 'image') { |
|
|
|
this.radiusMin = constants.packages.widthMin; |
|
|
|
this.radiusMax = constants.packages.widthMax; |
|
|
|
} |
|
|
|
|
|
|
|
// handle progress
|
|
|
|
if (this.progress < 0.0) {this.progress = 0.0;} |
|
|
|
if (this.progress > 1.0) {this.progress = 1.0;} |
|
|
|
|
|
|
|
// handle image
|
|
|
|
if (this.image != undefined) { |
|
|
|
if (this.imagelist) { |
|
|
|
this.imageObj = this.imagelist.load(this.image); |
|
|
|
} |
|
|
|
else { |
|
|
|
throw "No imagelist provided"; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// choose draw method depending on the style
|
|
|
|
switch (this.style) { |
|
|
|
// TODO: add more styles
|
|
|
|
case 'dot': this.draw = this._drawDot; break; |
|
|
|
case 'square': this.draw = this._drawSquare; break; |
|
|
|
case 'triangle': this.draw = this._drawTriangle; break; |
|
|
|
case 'triangleDown':this.draw = this._drawTriangleDown; break; |
|
|
|
case 'star': this.draw = this._drawStar; break; |
|
|
|
case 'image': this.draw = this._drawImage; break; |
|
|
|
default: this.draw = this._drawDot; break; |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Set a new value for the progress of the package |
|
|
|
* @param {number} progress A value between 0 and 1 |
|
|
|
*/ |
|
|
|
Graph.Package.prototype.setProgress = function (progress) { |
|
|
|
this.progress = progress; |
|
|
|
this.autoProgress = false; |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Check if a package is finished, if it has reached its destination. |
|
|
|
* If so, the package can be removed. |
|
|
|
* Only packages with automatically animated progress can be finished |
|
|
|
* @return {boolean} true if finished, else false. |
|
|
|
*/ |
|
|
|
Graph.Package.prototype.isFinished = function () { |
|
|
|
return (this.autoProgress == true && this.progress >= 1.0); |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Check if this package is moving. |
|
|
|
* A packages moves when it has automatic progress and not yet reached its |
|
|
|
* destination. |
|
|
|
* @return {boolean} true if moving, else false. |
|
|
|
*/ |
|
|
|
Graph.Package.prototype.isMoving = function () { |
|
|
|
return (this.autoProgress || this.isFinished()); |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* Perform one discrete step for the package. Only applicable when the |
|
|
|
* package has no manually set, fixed progress. |
|
|
|
* @param {number} interval Time interval in seconds |
|
|
|
*/ |
|
|
|
Graph.Package.prototype.discreteStep = function(interval) { |
|
|
|
if (this.autoProgress == true) { |
|
|
|
this.progress += (parseFloat(interval) / this.duration); |
|
|
|
|
|
|
|
if (this.progress > 1.0) |
|
|
|
this.progress = 1.0; |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* Draw this package in the given canvas |
|
|
|
* The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d"); |
|
|
|
* @param {CanvasRenderingContext2D} ctx |
|
|
|
*/ |
|
|
|
Graph.Package.prototype.draw = function(ctx) { |
|
|
|
throw "Draw method not initialized for package"; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* Check if this object is overlapping with the provided object |
|
|
|
* @param {Object} obj an object with parameters left, top, right, bottom |
|
|
|
* @return {boolean} True if location is located on node |
|
|
|
*/ |
|
|
|
Graph.Package.prototype.isOverlappingWith = function(obj) { |
|
|
|
// radius minimum 10px else it is too hard to get your mouse at the exact right position
|
|
|
|
var radius = Math.max(this.radius, 10); |
|
|
|
var pos = this._getPosition(); |
|
|
|
|
|
|
|
return (pos.x - radius < obj.right && |
|
|
|
pos.x + radius > obj.left && |
|
|
|
pos.y - radius < obj.bottom && |
|
|
|
pos.y + radius > obj.top); |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Calculate the current position of the package |
|
|
|
* @return {Object} position The object has parameters x and y. |
|
|
|
*/ |
|
|
|
Graph.Package.prototype._getPosition = function() { |
|
|
|
return { |
|
|
|
"x" : (1 - this.progress) * this.from.x + this.progress * this.to.x, |
|
|
|
"y" : (1 - this.progress) * this.from.y + this.progress * this.to.y |
|
|
|
}; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* get the title of this package. |
|
|
|
* @return {string} title The title of the package, or undefined when no |
|
|
|
* title has been set. |
|
|
|
*/ |
|
|
|
Graph.Package.prototype.getTitle = function() { |
|
|
|
return this.title; |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Retrieve the value of the package. Can be undefined |
|
|
|
* @return {Number} value |
|
|
|
*/ |
|
|
|
Graph.Package.prototype.getValue = function() { |
|
|
|
return this.value; |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Calculate the distance from the packages location to the given location (x,y) |
|
|
|
* @param {Number} x |
|
|
|
* @param {Number} y |
|
|
|
* @return {Number} value |
|
|
|
*/ |
|
|
|
Graph.Package.prototype.getDistance = function(x, y) { |
|
|
|
var pos = this._getPosition(), |
|
|
|
dx = pos.x - x, |
|
|
|
dy = pos.y - y; |
|
|
|
return Math.sqrt(dx * dx + dy * dy); |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Adjust the value range of the package. The package will adjust it's radius |
|
|
|
* based on its value. |
|
|
|
* @param {Number} min |
|
|
|
* @param {Number} max |
|
|
|
*/ |
|
|
|
Graph.Package.prototype.setValueRange = function(min, max) { |
|
|
|
if (!this.radiusFixed && this.value !== undefined) { |
|
|
|
var factor = (this.radiusMax - this.radiusMin) / (max - min); |
|
|
|
this.radius = (this.value - min) * factor + this.radiusMin; |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* Redraw a package as a dot |
|
|
|
* Draw this edge in the given canvas |
|
|
|
* The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d"); |
|
|
|
* @param {CanvasRenderingContext2D} ctx |
|
|
|
*/ |
|
|
|
/* TODO: cleanup |
|
|
|
Graph.Package.prototype._drawDot = function(ctx) { |
|
|
|
// set style
|
|
|
|
ctx.fillStyle = this.color; |
|
|
|
// draw dot
|
|
|
|
var pos = this._getPosition(); |
|
|
|
ctx.circle(pos.x, pos.y, this.radius); |
|
|
|
ctx.fill(); |
|
|
|
} |
|
|
|
*/ |
|
|
|
|
|
|
|
Graph.Package.prototype._drawDot = function (ctx) { |
|
|
|
this._drawShape(ctx, 'circle'); |
|
|
|
}; |
|
|
|
|
|
|
|
Graph.Package.prototype._drawTriangle = function (ctx) { |
|
|
|
this._drawShape(ctx, 'triangle'); |
|
|
|
}; |
|
|
|
|
|
|
|
Graph.Package.prototype._drawTriangleDown = function (ctx) { |
|
|
|
this._drawShape(ctx, 'triangleDown'); |
|
|
|
}; |
|
|
|
|
|
|
|
Graph.Package.prototype._drawSquare = function (ctx) { |
|
|
|
this._drawShape(ctx, 'square'); |
|
|
|
}; |
|
|
|
|
|
|
|
Graph.Package.prototype._drawStar = function (ctx) { |
|
|
|
this._drawShape(ctx, 'star'); |
|
|
|
}; |
|
|
|
|
|
|
|
Graph.Package.prototype._drawShape = function (ctx, shape) { |
|
|
|
// set style
|
|
|
|
ctx.fillStyle = this.color; |
|
|
|
|
|
|
|
// draw shape
|
|
|
|
var pos = this._getPosition(); |
|
|
|
ctx[shape](pos.x, pos.y, this.radius); |
|
|
|
ctx.fill(); |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Redraw a package as an image |
|
|
|
* Draw this edge in the given canvas |
|
|
|
* The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d"); |
|
|
|
* @param {CanvasRenderingContext2D} ctx |
|
|
|
*/ |
|
|
|
Graph.Package.prototype._drawImage = function (ctx) { |
|
|
|
if (this.imageObj) { |
|
|
|
var width, height; |
|
|
|
if (this.value) { |
|
|
|
var scale = this.imageObj.height / this.imageObj.width; |
|
|
|
width = this.radius || this.imageObj.width; |
|
|
|
height = this.radius * scale || this.imageObj.height; |
|
|
|
} |
|
|
|
else { |
|
|
|
width = this.imageObj.width; |
|
|
|
height = this.imageObj.height; |
|
|
|
} |
|
|
|
var pos = this._getPosition(); |
|
|
|
|
|
|
|
ctx.drawImage(this.imageObj, pos.x - width / 2, pos.y - height / 2, width, height); |
|
|
|
} |
|
|
|
else { |
|
|
|
console.log("image still loading..."); |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**--------------------------------------------------------------------------**/ |
|
|
|
|
|
|
|
|
|
|
|