Browse Source

Removed history/animation support from Graph

css_transitions
josdejong 11 years ago
parent
commit
0241a46f68
4 changed files with 49 additions and 1626 deletions
  1. +0
    -99
      docs/graph.html
  2. +22
    -761
      src/graph/graph.js
  3. +22
    -761
      vis.js
  4. +5
    -5
      vis.min.js

+ 0
- 99
docs/graph.html View File

@ -194,19 +194,6 @@ var nodes = [
<th>Description</th> <th>Description</th>
</tr> </tr>
<tr>
<td>action</td>
<td>string</td>
<td>no</td>
<td>By specifying <code>action</code>, a node can be created, updated,
or deleted. This is useful in combination with <code>timestamp</code>
to animate history, or for dynamically updating the graph.
Available values are: <code>create</code>,
<code>update</code> (default), or <code>delete</code>.
</td>
</tr>
<tr> <tr>
<td>backgroundColor</td> <td>backgroundColor</td>
<td>String</td> <td>String</td>
@ -323,14 +310,6 @@ var nodes = [
Multiple lines can be separated by a newline character <code>\n</code> .</td> Multiple lines can be separated by a newline character <code>\n</code> .</td>
</tr> </tr>
<tr>
<td>timestamp</td>
<td>Date | Number</td>
<td>no</td>
<td>Timestamp used for filtering nodes when animating.
See section <a href="#Animation">Animation</a> for more information.</td>
</tr>
<tr> <tr>
<td>title</td> <td>title</td>
<td>string</td> <td>string</td>
@ -405,22 +384,6 @@ var edges= [
<th>Description</th> <th>Description</th>
</tr> </tr>
<tr>
<td>action</td>
<td>string</td>
<td>no</td>
<td>By specifying <code>action</code>, a link can be created, updated,
or deleted. This is useful in combination with <code>timestamp</code>
to animate history, or for dynamically updating the graph.
An action on a link can only be used when the link has an id.
Available values are: <code>create</code> (default),
<code>update</code>, or <code>delete</code>.
</td>
</tr>
<tr> <tr>
<td>altdashlength</td> <td>altdashlength</td>
<td>number</td> <td>number</td>
@ -514,14 +477,6 @@ var edges= [
<td>Text to be displayed halfway the link.</td> <td>Text to be displayed halfway the link.</td>
</tr> </tr>
<tr>
<td>timestamp</td>
<td>Date | Number</td>
<td>no</td>
<td>Timestamp used for filtering edges when animating.
See section <a href="#Animation">Animation</a> for more information.</td>
</tr>
<tr> <tr>
<td>title</td> <td>title</td>
<td>string</td> <td>string</td>
@ -986,45 +941,6 @@ var nodes = [
<th>Description</th> <th>Description</th>
</tr> </tr>
<tr>
<td>animationSetAcceleration(acceleration)</td>
<td>none</td>
<td>Set the time acceleration for animation of history.
Only applicable when packages with a timestamp are available.
When acceleration is 1, history is played in real time.
And for example acceleration of 10 will play history then times the speed of
real time.</td>
</tr>
<tr>
<td>animationSetDuration(duration)</td>
<td>none</td>
<td>Duration of the animation in seconds.
Only applicable when packages with a timestamp are available.
The animation speed is scaled such that the total duration of playing the
history equals the set duration.</td>
</tr>
<tr>
<td>animationSetFramerate(framerate)</td>
<td>none</td>
<td>Set the framerate in frames per second for animation of history.
Only applicable when packages with a timestamp are available.
</td>
</tr>
<tr>
<td>animationStart()</td>
<td>none</td>
<td>Start animation of history.
Only applicable when packages with a timestamp are available.
</td>
</tr>
<tr>
<td>animationStop()</td>
<td>none</td>
<td>Stop animation of history.
Only applicable when packages with a timestamp are available.
</td>
</tr>
<tr> <tr>
<td>setData(data)</td> <td>setData(data)</td>
<td>none</td> <td>none</td>
@ -1075,21 +991,6 @@ var nodes = [
or in percentages.</td> or in percentages.</td>
</tr> </tr>
<tr>
<td>start()</td>
<td>none</td>
<td>Start animation.
Only applicable when there are animated edges or nodes,
or when the nodes are not yet moved to a stable position.</td>
</tr>
<tr>
<td>stop()</td>
<td>none</td>
<td>Stop animation.
Only applicable when there are animated edges or nodes.</td>
</tr>
</table> </table>
<h2><a name="Events"></a>Events</h2> <h2><a name="Events"></a>Events</h2>

+ 22
- 761
src/graph/graph.js View File

@ -1,6 +1,7 @@
/** /**
* @constructor Graph * @constructor Graph
* Create a graph visualization connecting nodes via edges.
* Create a graph visualization, displaying nodes and edges.
*
* @param {Element} container The DOM element in which the Graph will * @param {Element} container The DOM element in which the Graph will
* be created. Normally a div element. * be created. Normally a div element.
* @param {Object} data An object containing parameters * @param {Object} data An object containing parameters
@ -14,7 +15,7 @@ function Graph (container, data, options) {
this.width = "100%"; this.width = "100%";
this.height = "100%"; this.height = "100%";
this.refreshRate = 50; // milliseconds this.refreshRate = 50; // milliseconds
this.stabilize = true; // stabilize before displaying the network
this.stabilize = true; // stabilize before displaying the graph
this.selectable = true; this.selectable = true;
// set constant values // set constant values
@ -62,8 +63,7 @@ function Graph (container, data, options) {
this.groups = new Graph.Groups(); // object with groups this.groups = new Graph.Groups(); // object with groups
// properties of the data // 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.moving = false; // True if any of the nodes have an undefined position
this.selection = []; this.selection = [];
this.timer = undefined; this.timer = undefined;
@ -80,7 +80,7 @@ function Graph (container, data, options) {
/** /**
* Main drawing logic. This is the function that needs to be called * Main drawing logic. This is the function that needs to be called
* in the html page, to draw the Network.
* in the html page, to draw the Graph.
* *
* A data table with the events must be provided, and an options table. * A data table with the events must be provided, and an options table.
* @param {Object} data Object containing parameters: * @param {Object} data Object containing parameters:
@ -94,7 +94,6 @@ Graph.prototype.setData = function(data) {
} }
// set all data // set all data
this.hasTimestamps = false;
this.setNodes(data.nodes); this.setNodes(data.nodes);
this.setEdges(data.edges); this.setEdges(data.edges);
@ -105,9 +104,9 @@ Graph.prototype.setData = function(data) {
this.start(); this.start();
// create an onload callback method for the images // create an onload callback method for the images
var network = this;
var graph = this;
var callback = function () { var callback = function () {
network._redraw();
graph._redraw();
}; };
this.images.setOnloadCallback(callback); this.images.setOnloadCallback(callback);
@ -206,8 +205,8 @@ Graph.prototype.trigger = function (event, params) {
/** /**
* Create the main frame for the Network.
* This function is executed once when a Network object is created. The frame
* Create the main frame for the Graph.
* This function is executed once when a Graph object is created. The frame
* contains a canvas, and this canvas contains all objects like the axis and * contains a canvas, and this canvas contains all objects like the axis and
* nodes. * nodes.
*/ */
@ -218,7 +217,7 @@ Graph.prototype._create = function () {
} }
this.frame = document.createElement("div"); this.frame = document.createElement("div");
this.frame.className = "network-frame";
this.frame.className = "graph-frame";
this.frame.style.position = "relative"; this.frame.style.position = "relative";
this.frame.style.overflow = "hidden"; this.frame.style.overflow = "hidden";
@ -361,7 +360,7 @@ Graph.prototype._onMouseDown = function (event) {
this._unselectNodes([this.startClickedObj]); this._unselectNodes([this.startClickedObj]);
} }
if (!this.hasMovingNodes) {
if (!this.moving) {
this._redraw(); this._redraw();
} }
} }
@ -399,8 +398,8 @@ Graph.prototype._onMouseMove = function (event) {
node.y = this._yToCanvas(mouseY - this.startFrameTop); node.y = this._yToCanvas(mouseY - this.startFrameTop);
// start animation if not yet running // start animation if not yet running
if (!this.hasMovingNodes) {
this.hasMovingNodes = true;
if (!this.moving) {
this.moving = true;
this.start(); this.start();
} }
} }
@ -425,7 +424,7 @@ Graph.prototype._onMouseMove = function (event) {
this.frame.selRect.style.height = (bottom - top) + "px"; this.frame.selRect.style.height = (bottom - top) + "px";
} }
else { else {
// move the network
// move the graph
var diffX = mouseX - this.startMouseX; var diffX = mouseX - this.startMouseX;
var diffY = mouseY - this.startMouseY; var diffY = mouseY - this.startMouseY;
@ -600,7 +599,7 @@ Graph.prototype._onMouseMoveTitle = function (event) {
}; };
/** /**
* Check if there is an element on the given position in the network
* Check if there is an element on the given position in the graph
* (a node or edge). If so, and if this element has a title, * (a node or edge). If so, and if this element has a title,
* show a popup window with its title. * show a popup window with its title.
* *
@ -1003,7 +1002,7 @@ Graph.prototype._getConnectionCount = function(level) {
/** /**
* Set a new size for the network
* Set a new size for the graph
* @param {string} width Width in pixels or percentage (for example "800px" * @param {string} width Width in pixels or percentage (for example "800px"
* or "50%") * or "50%")
* @param {string} height Height in pixels or percentage (for example "400px" * @param {string} height Height in pixels or percentage (for example "400px"
@ -1018,10 +1017,6 @@ Graph.prototype._setSize = function(width, height) {
this.frame.canvas.width = this.frame.canvas.clientWidth; this.frame.canvas.width = this.frame.canvas.clientWidth;
this.frame.canvas.height = this.frame.canvas.clientHeight; this.frame.canvas.height = this.frame.canvas.clientHeight;
if (this.slider) {
this.slider.redraw();
}
}; };
/** /**
@ -1031,7 +1026,7 @@ Graph.prototype._setSize = function(width, height) {
Graph.prototype.setNodes = function(nodes) { Graph.prototype.setNodes = function(nodes) {
this.selection = []; this.selection = [];
this.nodes = []; this.nodes = [];
this.hasMovingNodes = false;
this.moving = false;
if (!nodes) { if (!nodes) {
return; return;
} }
@ -1045,9 +1040,6 @@ Graph.prototype.setNodes = function(nodes) {
if (properties.value != undefined) { if (properties.value != undefined) {
hasValues = true; hasValues = true;
} }
if (properties.timestamp) {
this.hasTimestamps = this.hasTimestamps || properties.timestamp;
}
if (properties.id == undefined) { if (properties.id == undefined) {
throw "Column 'id' missing in table with nodes (row " + i + ")"; throw "Column 'id' missing in table with nodes (row " + i + ")";
} }
@ -1060,60 +1052,6 @@ Graph.prototype.setNodes = function(nodes) {
} }
}; };
/**
* Filter the current nodes table for nodes with a timestamp older than given
* timestamp.
* @param {*} [timestamp] If timestamp is undefined, all nodes are shown
*/
Graph.prototype._filterNodes = function(timestamp) {
if (this.nodesTable == undefined) {
return;
}
// remove existing nodes with a too new timestamp
if (timestamp !== undefined) {
var ns = this.nodes;
var n = 0;
while (n < ns.length) {
var t = ns[n].timestamp;
if (t !== undefined && t > timestamp) {
// remove this node
ns.splice(n, 1);
}
else {
n++;
}
}
}
// add all nodes with an old enough timestamp
var table = this.nodesTable;
var rowCount = table.length;
for (var i = 0; i < rowCount; i++) {
// copy all properties
var properties = table[i];
if (properties.id === undefined) {
throw "Column 'id' missing in table with nodes (row " + i + ")";
}
// check what the timestamp is
var ts = properties.timestamp ? properties.timestamp : undefined;
var visible = true;
if (ts !== undefined && timestamp !== undefined && ts > timestamp) {
visible = false;
}
if (visible) {
// create or update the node
this._createNode(properties);
}
}
this.start();
};
/** /**
* Create a node with the given properties * Create a node with the given properties
* If the new node has an id identical to an existing node, the existing * If the new node has an id identical to an existing node, the existing
@ -1164,7 +1102,7 @@ Graph.prototype._createNode = function(properties) {
if (!newNode.isFixed()) { if (!newNode.isFixed()) {
// note: no not use node.isMoving() here, as that gives the current // note: no not use node.isMoving() here, as that gives the current
// velocity of the node, which is zero after creation of the node. // velocity of the node, which is zero after creation of the node.
this.hasMovingNodes = true;
this.moving = true;
} }
} }
else if (action === "update") { else if (action === "update") {
@ -1187,7 +1125,7 @@ Graph.prototype._createNode = function(properties) {
if (!newNode.isFixed()) { if (!newNode.isFixed()) {
// note: no not use node.isMoving() here, as that gives the current // note: no not use node.isMoving() here, as that gives the current
// velocity of the node, which is zero after creation of the node. // velocity of the node, which is zero after creation of the node.
this.hasMovingNodes = true;
this.moving = true;
} }
} }
} }
@ -1249,7 +1187,6 @@ Graph.prototype._findNodeByRow = function (row) {
*/ */
Graph.prototype.setEdges = function(edges) { Graph.prototype.setEdges = function(edges) {
this.edges = []; this.edges = [];
this.hasMovingEdges = false;
if (!edges) { if (!edges) {
return; return;
} }
@ -1266,9 +1203,6 @@ Graph.prototype.setEdges = function(edges) {
if (properties.to === undefined) { if (properties.to === undefined) {
throw "Column 'to' missing in table with edges (row " + i + ")"; throw "Column 'to' missing in table with edges (row " + i + ")";
} }
if (properties.timestamp != undefined) {
this.hasTimestamps = this.hasTimestamps || properties.timestamp;
}
if (properties.value != undefined) { if (properties.value != undefined) {
hasValues = true; hasValues = true;
} }
@ -1282,62 +1216,6 @@ Graph.prototype.setEdges = function(edges) {
} }
}; };
/**
* Filter the current edges table for edges with a timestamp below given
* timestamp.
* @param {*} [timestamp] If timestamp is undefined, all edges are shown
*/
Graph.prototype._filterEdges = function(timestamp) {
if (this.edgesTable == undefined) {
return;
}
// remove existing edges with a too new timestamp
if (timestamp !== undefined) {
var ls = this.edges;
var l = 0;
while (l < ls.length) {
var t = ls[l].timestamp;
if (t !== undefined && t > timestamp) {
// remove this edge
ls.splice(l, 1);
}
else {
l++;
}
}
}
// add all edges with an old enough timestamp
var table = this.edgesTable;
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 edges (row " + i + ")";
}
if (properties.to === undefined) {
throw "Column 'to' missing in table with edges (row " + i + ")";
}
// check what the timestamp is
var ts = properties.timestamp ? properties.timestamp : undefined;
var visible = true;
if (ts !== undefined && timestamp !== undefined && ts > timestamp) {
visible = false;
}
if (visible) {
// create or update the edge
this._createEdge(properties);
}
}
this.start();
};
/** /**
* Create a edge with the given properties * Create a edge with the given properties
* If the new edge has an id identical to an existing edge, the existing * If the new edge has an id identical to an existing edge, the existing
@ -1369,10 +1247,6 @@ Graph.prototype._createEdge = function(properties) {
} }
edge.from.attachEdge(edge); edge.from.attachEdge(edge);
edge.to.attachEdge(edge); edge.to.attachEdge(edge);
if (edge.isMoving()) {
this.hasMovingEdges = true;
}
} }
else if (action === "update") { else if (action === "update") {
// update existing edge, or create the edge if not existing // update existing edge, or create the edge if not existing
@ -1398,9 +1272,6 @@ Graph.prototype._createEdge = function(properties) {
edge.from.attachEdge(edge); edge.from.attachEdge(edge);
edge.to.attachEdge(edge); edge.to.attachEdge(edge);
this.edges.push(edge); this.edges.push(edge);
if (edge.isMoving()) {
this.hasMovingEdges = true;
}
} }
} }
else if (action === "delete") { else if (action === "delete") {
@ -1501,115 +1372,6 @@ Graph.prototype._updateValueRange = function(array) {
} }
}; };
/**
* 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);
};
/**
* 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() {
// range is stored as number. at the end of the method, it is converted to
// Date when needed.
var range = {
"start": undefined,
"end": undefined
};
var tables = [this.nodesTable, this.edgesTable];
for (var t = 0, tMax = tables.length; t < tMax; t++) {
var table = tables[t];
if (table !== undefined) {
for (var i = 0, iMax = table.length; i < iMax; i++) {
var timestamp = table[i].timestamp;
if (timestamp) {
// to long
if (timestamp instanceof Date) {
timestamp = timestamp.getTime();
}
// calculate new range
range.start = range.start ? Math.min(timestamp, range.start) : timestamp;
range.end = range.end ? Math.max(timestamp, range.end) : timestamp;
}
}
}
}
// convert to the right type: number or date
var rangeFormat = {
"start": new Date(range.start),
"end": new Date(range.end)
};
return rangeFormat;
};
/**
* Start animation.
* Only applicable when packages with a timestamp are available
*/
Graph.prototype.animationStart = function() {
if (this.slider) {
this.slider.play();
}
};
/**
* Start animation.
* Only applicable when packages with a timestamp are available
*/
Graph.prototype.animationStop = function() {
if (this.slider) {
this.slider.stop();
}
};
/**
* Set framerate for the animation.
* Only applicable when packages with a timestamp are available
* @param {number} framerate The framerate in frames per second
*/
Graph.prototype.setAnimationFramerate = function(framerate) {
if (this.slider) {
this.slider.setFramerate(framerate);
}
}
/**
* Set the duration of playing the whole package history
* Only applicable when packages with a timestamp are available
* @param {number} duration The duration in seconds
*/
Graph.prototype.setAnimationDuration = function(duration) {
if (this.slider) {
this.slider.setDuration(duration);
}
};
/**
* Set the time acceleration for playing the history.
* Only applicable when packages with a timestamp are available
* @param {number} acceleration Acceleration, for example 10 means play
* ten times as fast as real time. A value
* of 1 will play the history in real time.
*/
Graph.prototype.setAnimationAcceleration = function(acceleration) {
if (this.slider) {
this.slider.setAcceleration(acceleration);
}
};
/** /**
* Redraw the graph with the current data * Redraw the graph with the current data
* chart will be resized too. * chart will be resized too.
@ -1638,7 +1400,6 @@ Graph.prototype._redraw = function() {
this._drawEdges(ctx); this._drawEdges(ctx);
this._drawNodes(ctx); this._drawNodes(ctx);
this._drawSlider();
// restore original scaling and translation // restore original scaling and translation
ctx.restore(); ctx.restore();
@ -1759,54 +1520,6 @@ Graph.prototype._drawEdges = function(ctx) {
} }
}; };
/**
* Redraw the filter
*/
Graph.prototype._drawSlider = function() {
var sliderNode;
if (this.hasTimestamps) {
sliderNode = this.frame.slider;
if (sliderNode === undefined) {
sliderNode = document.createElement( "div" );
sliderNode.style.position = "absolute";
sliderNode.style.bottom = "0px";
sliderNode.style.left = "0px";
sliderNode.style.right = "0px";
sliderNode.style.backgroundColor = "rgba(255, 255, 255, 0.7)";
this.frame.slider = sliderNode;
this.frame.slider.style.padding = "10px";
//this.frame.filter.style.backgroundColor = "#EFEFEF";
this.frame.appendChild(sliderNode);
var range = this._getRange();
this.slider = new Graph.Slider(sliderNode);
this.slider.setLoop(false);
this.slider.setRange(range.start, range.end);
// create an event handler
var me = this;
var onchange = function () {
var timestamp = me.slider.getValue();
me.setTimestamp(timestamp);
// TODO: do only a redraw when the graph is not still moving
me.redraw();
};
this.slider.setOnChangeCallback(onchange);
onchange(); // perform the first update by hand.
}
}
else {
sliderNode = this.frame.slider;
if (sliderNode !== undefined) {
this.frame.removeChild(sliderNode);
this.frame.slider = undefined;
this.slider = undefined;
}
}
};
/** /**
* Recalculate the best positions for all nodes * Recalculate the best positions for all nodes
*/ */
@ -2058,15 +1771,15 @@ Graph.prototype._discreteStepNodes = function() {
* Start animating nodes and edges * Start animating nodes and edges
*/ */
Graph.prototype.start = function() { Graph.prototype.start = function() {
if (this.hasMovingNodes) {
if (this.moving) {
this._calculateForces(); this._calculateForces();
this._discreteStepNodes(); this._discreteStepNodes();
var vmin = this.constants.minVelocity; var vmin = this.constants.minVelocity;
this.hasMovingNodes = this.isMoving(vmin);
this.moving = this.isMoving(vmin);
} }
if (this.hasMovingNodes || this.hasMovingEdges) {
if (this.moving) {
// start animation. only start timer if it is not already running // start animation. only start timer if it is not already running
if (!this.timer) { if (!this.timer) {
var graph = this; var graph = this;
@ -2335,7 +2048,6 @@ Graph.Node.prototype.setProperties = function(properties, constants) {
if (properties.x != undefined) {this.x = properties.x;} if (properties.x != undefined) {this.x = properties.x;}
if (properties.y != undefined) {this.y = properties.y;} if (properties.y != undefined) {this.y = properties.y;}
if (properties.value != undefined) {this.value = properties.value;} if (properties.value != undefined) {this.value = properties.value;}
if (properties.timestamp != undefined) {this.timestamp = properties.timestamp;}
if (this.id === undefined) { if (this.id === undefined) {
throw "Node must have an id"; throw "Node must have an id";
@ -2879,7 +2591,6 @@ Graph.Edge = function (properties, graph, constants) {
this.stiffness = undefined; // depends on the length of the edge this.stiffness = undefined; // depends on the length of the edge
this.color = constants.edges.color; this.color = constants.edges.color;
this.timestamp = undefined;
this.widthFixed = false; this.widthFixed = false;
this.lengthFixed = false; this.lengthFixed = false;
@ -2923,7 +2634,6 @@ Graph.Edge.prototype.setProperties = function(properties, constants) {
if (properties.altdashlength != undefined) {this.altdashlength = properties.altdashlength;} if (properties.altdashlength != undefined) {this.altdashlength = properties.altdashlength;}
if (properties.color != undefined) {this.color = properties.color;} if (properties.color != undefined) {this.color = properties.color;}
if (properties.timestamp != undefined) {this.timestamp = properties.timestamp;}
if (!this.from) { if (!this.from) {
throw "Node with id " + properties.from + " not found"; throw "Node with id " + properties.from + " not found";
@ -2940,13 +2650,6 @@ Graph.Edge.prototype.setProperties = function(properties, constants) {
// initialize animation // initialize animation
if (this.style === 'arrow') { if (this.style === 'arrow') {
this.arrows = [0.5]; this.arrows = [0.5];
this.animation = false;
}
else if (this.style === 'arrow-end') {
this.animation = false;
}
else {
this.animation = false;
} }
// set draw method based on style // set draw method based on style
@ -2959,19 +2662,6 @@ Graph.Edge.prototype.setProperties = function(properties, constants) {
} }
}; };
/**
* Check if a node has an animating contents. If so, the graph needs to be
* redrawn regularly
* @return {boolean} true if this edge needs animation, else false
*/
Graph.Edge.prototype.isMoving = function() {
// TODO: be able to set the interval somehow
return this.animation;
};
/** /**
* 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
@ -3693,435 +3383,6 @@ Graph.isArray = function (obj) {
/**--------------------------------------------------------------------------**/
/**
* @class Slider
*
* An html slider control with start/stop/prev/next buttons
* @param {Element} container The element where the slider will be created
*/
Graph.Slider = function(container) {
if (container === undefined) throw "Error: No container element defined";
this.container = container;
this.frame = document.createElement("DIV");
//this.frame.style.backgroundColor = "#E5E5E5";
this.frame.style.width = "100%";
this.frame.style.position = "relative";
this.title = document.createElement("DIV");
this.title.style.margin = "2px";
this.title.style.marginBottom = "5px";
this.title.innerHTML = "";
this.container.appendChild(this.title);
this.frame.prev = document.createElement("INPUT");
this.frame.prev.type = "BUTTON";
this.frame.prev.value = "Prev";
this.frame.appendChild(this.frame.prev);
this.frame.play = document.createElement("INPUT");
this.frame.play.type = "BUTTON";
this.frame.play.value = "Play";
this.frame.appendChild(this.frame.play);
this.frame.next = document.createElement("INPUT");
this.frame.next.type = "BUTTON";
this.frame.next.value = "Next";
this.frame.appendChild(this.frame.next);
this.frame.bar = document.createElement("INPUT");
this.frame.bar.type = "BUTTON";
this.frame.bar.style.position = "absolute";
this.frame.bar.style.border = "1px solid red";
this.frame.bar.style.width = "100px";
this.frame.bar.style.height = "6px";
this.frame.bar.style.borderRadius = "2px";
this.frame.bar.style.MozBorderRadius = "2px";
this.frame.bar.style.border = "1px solid #7F7F7F";
this.frame.bar.style.backgroundColor = "#E5E5E5";
this.frame.appendChild(this.frame.bar);
this.frame.slide = document.createElement("INPUT");
this.frame.slide.type = "BUTTON";
this.frame.slide.style.margin = "0px";
this.frame.slide.value = " ";
this.frame.slide.style.position = "relative";
this.frame.slide.style.left = "-100px";
this.frame.appendChild(this.frame.slide);
// create events
var me = this;
this.frame.slide.onmousedown = function (event) {me._onMouseDown(event);};
this.frame.prev.onclick = function (event) {me.prev(event);};
this.frame.play.onclick = function (event) {me.togglePlay(event);};
this.frame.next.onclick = function (event) {me.next(event);};
this.container.appendChild(this.frame);
this.onChangeCallback = undefined;
this.playTimeout = undefined;
this.framerate = 20; // frames per second
this.duration = 10; // seconds
this.doLoop = true;
this.start = 0;
this.end = 0;
this.value = 0;
this.step = 0;
this.rangeIsDate = false;
this.redraw();
};
/**
* Retrieve the step size, depending on the range, framerate, and duration
*/
Graph.Slider.prototype._updateStep = function() {
var range = (this.end - this.start);
var frameCount = this.duration * this.framerate;
this.step = range / frameCount;
};
/**
* Select the previous index
*/
Graph.Slider.prototype.prev = function() {
this._setValue(this.value - this.step);
};
/**
* Select the next index
*/
Graph.Slider.prototype.next = function() {
this._setValue(this.value + this.step);
};
/**
* Select the next index
*/
Graph.Slider.prototype.playNext = function() {
var start = new Date();
if (!this.leftButtonDown) {
if (this.value + this.step < this.end) {
this._setValue(this.value + this.step);
}
else {
if (this.doLoop) {
this._setValue(this.start);
}
else {
this._setValue(this.end);
this.stop();
return;
}
}
}
var end = new Date();
var diff = (end - start);
// calculate how much time it to to set the index and to execute the callback
// function.
var interval = Math.max(1000 / this.framerate - diff, 0);
var me = this;
this.playTimeout = setTimeout(function() {me.playNext();}, interval);
};
/**
* Toggle start or stop playing
*/
Graph.Slider.prototype.togglePlay = function() {
if (this.playTimeout === undefined) {
this.play();
} else {
this.stop();
}
};
/**
* Start playing
*/
Graph.Slider.prototype.play = function() {
this.frame.play.value = "Stop";
this.playNext();
};
/**
* Stop playing
*/
Graph.Slider.prototype.stop = function() {
this.frame.play.value = "Play";
clearInterval(this.playTimeout);
this.playTimeout = undefined;
};
/**
* Set a callback function which will be triggered when the value of the
* slider bar has changed.
*/
Graph.Slider.prototype.setOnChangeCallback = function(callback) {
this.onChangeCallback = callback;
};
/**
* Set the interval for playing the list
* @param {number} framerate Framerate in frames per second
*/
Graph.Slider.prototype.setFramerate = function(framerate) {
this.framerate = framerate;
this._updateStep();
};
/**
* Retrieve the current framerate
* @return {number} framerate in frames per second
*/
Graph.Slider.prototype.getFramerate = function() {
return this.framerate;
};
/**
* Set the duration for playing
* @param {number} duration Duration in seconds
*/
Graph.Slider.prototype.setDuration = function(duration) {
this.duration = duration;
this._updateStep();
};
/**
* Set the time acceleration for playing the history. Only applicable when
* the values are of type Date.
* @param {number} acceleration Acceleration, for example 10 means play
* ten times as fast as real time. A value
* of 1 will play the history in real time.
*/
Graph.Slider.prototype.setAcceleration = function(acceleration) {
var durationRealtime = (this.end - this.start) / 1000; // in seconds
this.duration = durationRealtime / acceleration;
this._updateStep();
};
/**
* Set looping on or off
* @param {boolean} doLoop If true, the slider will jump to the start when
* the end is passed, and will jump to the end
* when the start is passed.
*/
Graph.Slider.prototype.setLoop = function(doLoop) {
this.doLoop = doLoop;
};
/**
* Retrieve the current value of loop
* @return {boolean} doLoop If true, the slider will jump to the start when
* the end is passed, and will jump to the end
* when the start is passed.
*/
Graph.Slider.prototype.getLoop = function() {
return this.doLoop;
};
/**
* Execute the onchange callback function
*/
Graph.Slider.prototype.onChange = function() {
if (this.onChangeCallback !== undefined) {
this.onChangeCallback();
}
};
/**
* redraw the slider on the correct place
*/
Graph.Slider.prototype.redraw = function() {
// resize the bar
var barTop = (this.frame.clientHeight/2 -
this.frame.bar.offsetHeight/2);
var barWidth = (this.frame.clientWidth -
this.frame.prev.clientWidth -
this.frame.play.clientWidth -
this.frame.next.clientWidth - 30);
this.frame.bar.style.top = barTop + "px";
this.frame.bar.style.width = barWidth + "px";
// position the slider button
this.frame.slide.title = this.getValue();
this.frame.slide.style.left = this._valueToLeft(this.value) + "px";
// set the title
this.title.innerHTML = this.getValue();
};
/**
* Set the range for the slider
* @param {Date | Number} start Start of the range
* @param {Date | Number} end End of the range
*/
Graph.Slider.prototype.setRange = function(start, end) {
if (start === undefined || start === null || start === NaN) {
this.start = 0;
this.rangeIsDate = false;
}
else if (start instanceof Date) {
this.start = start.getTime();
this.rangeIsDate = true;
}
else {
this.start = start;
this.rangeIsDate = false;
}
if (end === undefined || end === null || end === NaN) {
if (this.start instanceof Date) {
this.end = new Date(this.start);
}
else {
this.end = this.start;
}
}
else if (end instanceof Date) {
this.end = end.getTime();
}
else {
this.end = end;
}
this.value = this.start;
this._updateStep();
this.redraw();
};
/**
* Set a value for the slider. The value must be between start and end
* When the range are Dates, the value will be translated to a date
* @param {Number} value
*/
Graph.Slider.prototype._setValue = function(value) {
this.value = this._limitValue(value);
this.redraw();
this.onChange();
};
/**
* retrieve the current value in the correct type, Number or Date
* @return {Date | Number} value
*/
Graph.Slider.prototype.getValue = function() {
if (this.rangeIsDate) {
return new Date(this.value);
}
else {
return this.value;
}
};
Graph.Slider.prototype.offset = 3;
Graph.Slider.prototype._leftToValue = function (left) {
var width = parseFloat(this.frame.bar.style.width) -
this.frame.slide.clientWidth - 10;
var x = left - this.offset;
var range = this.end - this.start;
var value = this._limitValue(x / width * range + this.start);
return value;
};
Graph.Slider.prototype._valueToLeft = function (value) {
var width = parseFloat(this.frame.bar.style.width) -
this.frame.slide.clientWidth - 10;
var x;
if (this.end > this.start) {
x = (value - this.start) / (this.end - this.start) * width;
}
else {
x = 0;
}
var left = x + this.offset;
return left;
};
Graph.Slider.prototype._limitValue = function(value) {
if (value < this.start) {
value = this.start
}
if (value > this.end) {
value = this.end;
}
return value;
};
Graph.Slider.prototype._onMouseDown = function(event) {
// only react on left mouse button down
this.leftButtonDown = event.which ? (event.which === 1) : (event.button === 1);
if (!this.leftButtonDown) return;
this.startClientX = event.clientX;
this.startSlideX = parseFloat(this.frame.slide.style.left);
this.frame.style.cursor = 'move';
// add event listeners to handle moving the contents
// we store the function onmousemove and onmouseup in the graph, so we can
// remove the eventlisteners lateron in the function mouseUp()
var me = this;
this.onmousemove = function (event) {me._onMouseMove(event);};
this.onmouseup = function (event) {me._onMouseUp(event);};
Graph.addEventListener(document, "mousemove", this.onmousemove);
Graph.addEventListener(document, "mouseup", this.onmouseup);
Graph.preventDefault(event);
};
Graph.Slider.prototype._onMouseMove = function (event) {
var diff = event.clientX - this.startClientX;
var x = this.startSlideX + diff;
var value = this._leftToValue(x);
this._setValue(value);
Graph.preventDefault(event);
};
Graph.Slider.prototype._onMouseUp = function (event) {
this.frame.style.cursor = 'auto';
this.leftButtonDown = false;
// remove event listeners
Graph.removeEventListener(document, "mousemove", this.onmousemove);
Graph.removeEventListener(document, "mouseup", this.onmouseup);
Graph.preventDefault(event);
};
/**--------------------------------------------------------------------------**/ /**--------------------------------------------------------------------------**/

+ 22
- 761
vis.js View File

@ -6807,7 +6807,8 @@ Timeline.prototype.getItemRange = function getItemRange() {
/** /**
* @constructor Graph * @constructor Graph
* Create a graph visualization connecting nodes via edges.
* Create a graph visualization, displaying nodes and edges.
*
* @param {Element} container The DOM element in which the Graph will * @param {Element} container The DOM element in which the Graph will
* be created. Normally a div element. * be created. Normally a div element.
* @param {Object} data An object containing parameters * @param {Object} data An object containing parameters
@ -6821,7 +6822,7 @@ function Graph (container, data, options) {
this.width = "100%"; this.width = "100%";
this.height = "100%"; this.height = "100%";
this.refreshRate = 50; // milliseconds this.refreshRate = 50; // milliseconds
this.stabilize = true; // stabilize before displaying the network
this.stabilize = true; // stabilize before displaying the graph
this.selectable = true; this.selectable = true;
// set constant values // set constant values
@ -6869,8 +6870,7 @@ function Graph (container, data, options) {
this.groups = new Graph.Groups(); // object with groups this.groups = new Graph.Groups(); // object with groups
// properties of the data // 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.moving = false; // True if any of the nodes have an undefined position
this.selection = []; this.selection = [];
this.timer = undefined; this.timer = undefined;
@ -6887,7 +6887,7 @@ function Graph (container, data, options) {
/** /**
* Main drawing logic. This is the function that needs to be called * Main drawing logic. This is the function that needs to be called
* in the html page, to draw the Network.
* in the html page, to draw the Graph.
* *
* A data table with the events must be provided, and an options table. * A data table with the events must be provided, and an options table.
* @param {Object} data Object containing parameters: * @param {Object} data Object containing parameters:
@ -6901,7 +6901,6 @@ Graph.prototype.setData = function(data) {
} }
// set all data // set all data
this.hasTimestamps = false;
this.setNodes(data.nodes); this.setNodes(data.nodes);
this.setEdges(data.edges); this.setEdges(data.edges);
@ -6912,9 +6911,9 @@ Graph.prototype.setData = function(data) {
this.start(); this.start();
// create an onload callback method for the images // create an onload callback method for the images
var network = this;
var graph = this;
var callback = function () { var callback = function () {
network._redraw();
graph._redraw();
}; };
this.images.setOnloadCallback(callback); this.images.setOnloadCallback(callback);
@ -7013,8 +7012,8 @@ Graph.prototype.trigger = function (event, params) {
/** /**
* Create the main frame for the Network.
* This function is executed once when a Network object is created. The frame
* Create the main frame for the Graph.
* This function is executed once when a Graph object is created. The frame
* contains a canvas, and this canvas contains all objects like the axis and * contains a canvas, and this canvas contains all objects like the axis and
* nodes. * nodes.
*/ */
@ -7025,7 +7024,7 @@ Graph.prototype._create = function () {
} }
this.frame = document.createElement("div"); this.frame = document.createElement("div");
this.frame.className = "network-frame";
this.frame.className = "graph-frame";
this.frame.style.position = "relative"; this.frame.style.position = "relative";
this.frame.style.overflow = "hidden"; this.frame.style.overflow = "hidden";
@ -7168,7 +7167,7 @@ Graph.prototype._onMouseDown = function (event) {
this._unselectNodes([this.startClickedObj]); this._unselectNodes([this.startClickedObj]);
} }
if (!this.hasMovingNodes) {
if (!this.moving) {
this._redraw(); this._redraw();
} }
} }
@ -7206,8 +7205,8 @@ Graph.prototype._onMouseMove = function (event) {
node.y = this._yToCanvas(mouseY - this.startFrameTop); node.y = this._yToCanvas(mouseY - this.startFrameTop);
// start animation if not yet running // start animation if not yet running
if (!this.hasMovingNodes) {
this.hasMovingNodes = true;
if (!this.moving) {
this.moving = true;
this.start(); this.start();
} }
} }
@ -7232,7 +7231,7 @@ Graph.prototype._onMouseMove = function (event) {
this.frame.selRect.style.height = (bottom - top) + "px"; this.frame.selRect.style.height = (bottom - top) + "px";
} }
else { else {
// move the network
// move the graph
var diffX = mouseX - this.startMouseX; var diffX = mouseX - this.startMouseX;
var diffY = mouseY - this.startMouseY; var diffY = mouseY - this.startMouseY;
@ -7407,7 +7406,7 @@ Graph.prototype._onMouseMoveTitle = function (event) {
}; };
/** /**
* Check if there is an element on the given position in the network
* Check if there is an element on the given position in the graph
* (a node or edge). If so, and if this element has a title, * (a node or edge). If so, and if this element has a title,
* show a popup window with its title. * show a popup window with its title.
* *
@ -7810,7 +7809,7 @@ Graph.prototype._getConnectionCount = function(level) {
/** /**
* Set a new size for the network
* Set a new size for the graph
* @param {string} width Width in pixels or percentage (for example "800px" * @param {string} width Width in pixels or percentage (for example "800px"
* or "50%") * or "50%")
* @param {string} height Height in pixels or percentage (for example "400px" * @param {string} height Height in pixels or percentage (for example "400px"
@ -7825,10 +7824,6 @@ Graph.prototype._setSize = function(width, height) {
this.frame.canvas.width = this.frame.canvas.clientWidth; this.frame.canvas.width = this.frame.canvas.clientWidth;
this.frame.canvas.height = this.frame.canvas.clientHeight; this.frame.canvas.height = this.frame.canvas.clientHeight;
if (this.slider) {
this.slider.redraw();
}
}; };
/** /**
@ -7838,7 +7833,7 @@ Graph.prototype._setSize = function(width, height) {
Graph.prototype.setNodes = function(nodes) { Graph.prototype.setNodes = function(nodes) {
this.selection = []; this.selection = [];
this.nodes = []; this.nodes = [];
this.hasMovingNodes = false;
this.moving = false;
if (!nodes) { if (!nodes) {
return; return;
} }
@ -7852,9 +7847,6 @@ Graph.prototype.setNodes = function(nodes) {
if (properties.value != undefined) { if (properties.value != undefined) {
hasValues = true; hasValues = true;
} }
if (properties.timestamp) {
this.hasTimestamps = this.hasTimestamps || properties.timestamp;
}
if (properties.id == undefined) { if (properties.id == undefined) {
throw "Column 'id' missing in table with nodes (row " + i + ")"; throw "Column 'id' missing in table with nodes (row " + i + ")";
} }
@ -7867,60 +7859,6 @@ Graph.prototype.setNodes = function(nodes) {
} }
}; };
/**
* Filter the current nodes table for nodes with a timestamp older than given
* timestamp.
* @param {*} [timestamp] If timestamp is undefined, all nodes are shown
*/
Graph.prototype._filterNodes = function(timestamp) {
if (this.nodesTable == undefined) {
return;
}
// remove existing nodes with a too new timestamp
if (timestamp !== undefined) {
var ns = this.nodes;
var n = 0;
while (n < ns.length) {
var t = ns[n].timestamp;
if (t !== undefined && t > timestamp) {
// remove this node
ns.splice(n, 1);
}
else {
n++;
}
}
}
// add all nodes with an old enough timestamp
var table = this.nodesTable;
var rowCount = table.length;
for (var i = 0; i < rowCount; i++) {
// copy all properties
var properties = table[i];
if (properties.id === undefined) {
throw "Column 'id' missing in table with nodes (row " + i + ")";
}
// check what the timestamp is
var ts = properties.timestamp ? properties.timestamp : undefined;
var visible = true;
if (ts !== undefined && timestamp !== undefined && ts > timestamp) {
visible = false;
}
if (visible) {
// create or update the node
this._createNode(properties);
}
}
this.start();
};
/** /**
* Create a node with the given properties * Create a node with the given properties
* If the new node has an id identical to an existing node, the existing * If the new node has an id identical to an existing node, the existing
@ -7971,7 +7909,7 @@ Graph.prototype._createNode = function(properties) {
if (!newNode.isFixed()) { if (!newNode.isFixed()) {
// note: no not use node.isMoving() here, as that gives the current // note: no not use node.isMoving() here, as that gives the current
// velocity of the node, which is zero after creation of the node. // velocity of the node, which is zero after creation of the node.
this.hasMovingNodes = true;
this.moving = true;
} }
} }
else if (action === "update") { else if (action === "update") {
@ -7994,7 +7932,7 @@ Graph.prototype._createNode = function(properties) {
if (!newNode.isFixed()) { if (!newNode.isFixed()) {
// note: no not use node.isMoving() here, as that gives the current // note: no not use node.isMoving() here, as that gives the current
// velocity of the node, which is zero after creation of the node. // velocity of the node, which is zero after creation of the node.
this.hasMovingNodes = true;
this.moving = true;
} }
} }
} }
@ -8056,7 +7994,6 @@ Graph.prototype._findNodeByRow = function (row) {
*/ */
Graph.prototype.setEdges = function(edges) { Graph.prototype.setEdges = function(edges) {
this.edges = []; this.edges = [];
this.hasMovingEdges = false;
if (!edges) { if (!edges) {
return; return;
} }
@ -8073,9 +8010,6 @@ Graph.prototype.setEdges = function(edges) {
if (properties.to === undefined) { if (properties.to === undefined) {
throw "Column 'to' missing in table with edges (row " + i + ")"; throw "Column 'to' missing in table with edges (row " + i + ")";
} }
if (properties.timestamp != undefined) {
this.hasTimestamps = this.hasTimestamps || properties.timestamp;
}
if (properties.value != undefined) { if (properties.value != undefined) {
hasValues = true; hasValues = true;
} }
@ -8089,62 +8023,6 @@ Graph.prototype.setEdges = function(edges) {
} }
}; };
/**
* Filter the current edges table for edges with a timestamp below given
* timestamp.
* @param {*} [timestamp] If timestamp is undefined, all edges are shown
*/
Graph.prototype._filterEdges = function(timestamp) {
if (this.edgesTable == undefined) {
return;
}
// remove existing edges with a too new timestamp
if (timestamp !== undefined) {
var ls = this.edges;
var l = 0;
while (l < ls.length) {
var t = ls[l].timestamp;
if (t !== undefined && t > timestamp) {
// remove this edge
ls.splice(l, 1);
}
else {
l++;
}
}
}
// add all edges with an old enough timestamp
var table = this.edgesTable;
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 edges (row " + i + ")";
}
if (properties.to === undefined) {
throw "Column 'to' missing in table with edges (row " + i + ")";
}
// check what the timestamp is
var ts = properties.timestamp ? properties.timestamp : undefined;
var visible = true;
if (ts !== undefined && timestamp !== undefined && ts > timestamp) {
visible = false;
}
if (visible) {
// create or update the edge
this._createEdge(properties);
}
}
this.start();
};
/** /**
* Create a edge with the given properties * Create a edge with the given properties
* If the new edge has an id identical to an existing edge, the existing * If the new edge has an id identical to an existing edge, the existing
@ -8176,10 +8054,6 @@ Graph.prototype._createEdge = function(properties) {
} }
edge.from.attachEdge(edge); edge.from.attachEdge(edge);
edge.to.attachEdge(edge); edge.to.attachEdge(edge);
if (edge.isMoving()) {
this.hasMovingEdges = true;
}
} }
else if (action === "update") { else if (action === "update") {
// update existing edge, or create the edge if not existing // update existing edge, or create the edge if not existing
@ -8205,9 +8079,6 @@ Graph.prototype._createEdge = function(properties) {
edge.from.attachEdge(edge); edge.from.attachEdge(edge);
edge.to.attachEdge(edge); edge.to.attachEdge(edge);
this.edges.push(edge); this.edges.push(edge);
if (edge.isMoving()) {
this.hasMovingEdges = true;
}
} }
} }
else if (action === "delete") { else if (action === "delete") {
@ -8308,115 +8179,6 @@ Graph.prototype._updateValueRange = function(array) {
} }
}; };
/**
* 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);
};
/**
* 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() {
// range is stored as number. at the end of the method, it is converted to
// Date when needed.
var range = {
"start": undefined,
"end": undefined
};
var tables = [this.nodesTable, this.edgesTable];
for (var t = 0, tMax = tables.length; t < tMax; t++) {
var table = tables[t];
if (table !== undefined) {
for (var i = 0, iMax = table.length; i < iMax; i++) {
var timestamp = table[i].timestamp;
if (timestamp) {
// to long
if (timestamp instanceof Date) {
timestamp = timestamp.getTime();
}
// calculate new range
range.start = range.start ? Math.min(timestamp, range.start) : timestamp;
range.end = range.end ? Math.max(timestamp, range.end) : timestamp;
}
}
}
}
// convert to the right type: number or date
var rangeFormat = {
"start": new Date(range.start),
"end": new Date(range.end)
};
return rangeFormat;
};
/**
* Start animation.
* Only applicable when packages with a timestamp are available
*/
Graph.prototype.animationStart = function() {
if (this.slider) {
this.slider.play();
}
};
/**
* Start animation.
* Only applicable when packages with a timestamp are available
*/
Graph.prototype.animationStop = function() {
if (this.slider) {
this.slider.stop();
}
};
/**
* Set framerate for the animation.
* Only applicable when packages with a timestamp are available
* @param {number} framerate The framerate in frames per second
*/
Graph.prototype.setAnimationFramerate = function(framerate) {
if (this.slider) {
this.slider.setFramerate(framerate);
}
}
/**
* Set the duration of playing the whole package history
* Only applicable when packages with a timestamp are available
* @param {number} duration The duration in seconds
*/
Graph.prototype.setAnimationDuration = function(duration) {
if (this.slider) {
this.slider.setDuration(duration);
}
};
/**
* Set the time acceleration for playing the history.
* Only applicable when packages with a timestamp are available
* @param {number} acceleration Acceleration, for example 10 means play
* ten times as fast as real time. A value
* of 1 will play the history in real time.
*/
Graph.prototype.setAnimationAcceleration = function(acceleration) {
if (this.slider) {
this.slider.setAcceleration(acceleration);
}
};
/** /**
* Redraw the graph with the current data * Redraw the graph with the current data
* chart will be resized too. * chart will be resized too.
@ -8445,7 +8207,6 @@ Graph.prototype._redraw = function() {
this._drawEdges(ctx); this._drawEdges(ctx);
this._drawNodes(ctx); this._drawNodes(ctx);
this._drawSlider();
// restore original scaling and translation // restore original scaling and translation
ctx.restore(); ctx.restore();
@ -8566,54 +8327,6 @@ Graph.prototype._drawEdges = function(ctx) {
} }
}; };
/**
* Redraw the filter
*/
Graph.prototype._drawSlider = function() {
var sliderNode;
if (this.hasTimestamps) {
sliderNode = this.frame.slider;
if (sliderNode === undefined) {
sliderNode = document.createElement( "div" );
sliderNode.style.position = "absolute";
sliderNode.style.bottom = "0px";
sliderNode.style.left = "0px";
sliderNode.style.right = "0px";
sliderNode.style.backgroundColor = "rgba(255, 255, 255, 0.7)";
this.frame.slider = sliderNode;
this.frame.slider.style.padding = "10px";
//this.frame.filter.style.backgroundColor = "#EFEFEF";
this.frame.appendChild(sliderNode);
var range = this._getRange();
this.slider = new Graph.Slider(sliderNode);
this.slider.setLoop(false);
this.slider.setRange(range.start, range.end);
// create an event handler
var me = this;
var onchange = function () {
var timestamp = me.slider.getValue();
me.setTimestamp(timestamp);
// TODO: do only a redraw when the graph is not still moving
me.redraw();
};
this.slider.setOnChangeCallback(onchange);
onchange(); // perform the first update by hand.
}
}
else {
sliderNode = this.frame.slider;
if (sliderNode !== undefined) {
this.frame.removeChild(sliderNode);
this.frame.slider = undefined;
this.slider = undefined;
}
}
};
/** /**
* Recalculate the best positions for all nodes * Recalculate the best positions for all nodes
*/ */
@ -8865,15 +8578,15 @@ Graph.prototype._discreteStepNodes = function() {
* Start animating nodes and edges * Start animating nodes and edges
*/ */
Graph.prototype.start = function() { Graph.prototype.start = function() {
if (this.hasMovingNodes) {
if (this.moving) {
this._calculateForces(); this._calculateForces();
this._discreteStepNodes(); this._discreteStepNodes();
var vmin = this.constants.minVelocity; var vmin = this.constants.minVelocity;
this.hasMovingNodes = this.isMoving(vmin);
this.moving = this.isMoving(vmin);
} }
if (this.hasMovingNodes || this.hasMovingEdges) {
if (this.moving) {
// start animation. only start timer if it is not already running // start animation. only start timer if it is not already running
if (!this.timer) { if (!this.timer) {
var graph = this; var graph = this;
@ -9142,7 +8855,6 @@ Graph.Node.prototype.setProperties = function(properties, constants) {
if (properties.x != undefined) {this.x = properties.x;} if (properties.x != undefined) {this.x = properties.x;}
if (properties.y != undefined) {this.y = properties.y;} if (properties.y != undefined) {this.y = properties.y;}
if (properties.value != undefined) {this.value = properties.value;} if (properties.value != undefined) {this.value = properties.value;}
if (properties.timestamp != undefined) {this.timestamp = properties.timestamp;}
if (this.id === undefined) { if (this.id === undefined) {
throw "Node must have an id"; throw "Node must have an id";
@ -9686,7 +9398,6 @@ Graph.Edge = function (properties, graph, constants) {
this.stiffness = undefined; // depends on the length of the edge this.stiffness = undefined; // depends on the length of the edge
this.color = constants.edges.color; this.color = constants.edges.color;
this.timestamp = undefined;
this.widthFixed = false; this.widthFixed = false;
this.lengthFixed = false; this.lengthFixed = false;
@ -9730,7 +9441,6 @@ Graph.Edge.prototype.setProperties = function(properties, constants) {
if (properties.altdashlength != undefined) {this.altdashlength = properties.altdashlength;} if (properties.altdashlength != undefined) {this.altdashlength = properties.altdashlength;}
if (properties.color != undefined) {this.color = properties.color;} if (properties.color != undefined) {this.color = properties.color;}
if (properties.timestamp != undefined) {this.timestamp = properties.timestamp;}
if (!this.from) { if (!this.from) {
throw "Node with id " + properties.from + " not found"; throw "Node with id " + properties.from + " not found";
@ -9747,13 +9457,6 @@ Graph.Edge.prototype.setProperties = function(properties, constants) {
// initialize animation // initialize animation
if (this.style === 'arrow') { if (this.style === 'arrow') {
this.arrows = [0.5]; this.arrows = [0.5];
this.animation = false;
}
else if (this.style === 'arrow-end') {
this.animation = false;
}
else {
this.animation = false;
} }
// set draw method based on style // set draw method based on style
@ -9766,19 +9469,6 @@ Graph.Edge.prototype.setProperties = function(properties, constants) {
} }
}; };
/**
* Check if a node has an animating contents. If so, the graph needs to be
* redrawn regularly
* @return {boolean} true if this edge needs animation, else false
*/
Graph.Edge.prototype.isMoving = function() {
// TODO: be able to set the interval somehow
return this.animation;
};
/** /**
* 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
@ -10500,435 +10190,6 @@ Graph.isArray = function (obj) {
/**--------------------------------------------------------------------------**/
/**
* @class Slider
*
* An html slider control with start/stop/prev/next buttons
* @param {Element} container The element where the slider will be created
*/
Graph.Slider = function(container) {
if (container === undefined) throw "Error: No container element defined";
this.container = container;
this.frame = document.createElement("DIV");
//this.frame.style.backgroundColor = "#E5E5E5";
this.frame.style.width = "100%";
this.frame.style.position = "relative";
this.title = document.createElement("DIV");
this.title.style.margin = "2px";
this.title.style.marginBottom = "5px";
this.title.innerHTML = "";
this.container.appendChild(this.title);
this.frame.prev = document.createElement("INPUT");
this.frame.prev.type = "BUTTON";
this.frame.prev.value = "Prev";
this.frame.appendChild(this.frame.prev);
this.frame.play = document.createElement("INPUT");
this.frame.play.type = "BUTTON";
this.frame.play.value = "Play";
this.frame.appendChild(this.frame.play);
this.frame.next = document.createElement("INPUT");
this.frame.next.type = "BUTTON";
this.frame.next.value = "Next";
this.frame.appendChild(this.frame.next);
this.frame.bar = document.createElement("INPUT");
this.frame.bar.type = "BUTTON";
this.frame.bar.style.position = "absolute";
this.frame.bar.style.border = "1px solid red";
this.frame.bar.style.width = "100px";
this.frame.bar.style.height = "6px";
this.frame.bar.style.borderRadius = "2px";
this.frame.bar.style.MozBorderRadius = "2px";
this.frame.bar.style.border = "1px solid #7F7F7F";
this.frame.bar.style.backgroundColor = "#E5E5E5";
this.frame.appendChild(this.frame.bar);
this.frame.slide = document.createElement("INPUT");
this.frame.slide.type = "BUTTON";
this.frame.slide.style.margin = "0px";
this.frame.slide.value = " ";
this.frame.slide.style.position = "relative";
this.frame.slide.style.left = "-100px";
this.frame.appendChild(this.frame.slide);
// create events
var me = this;
this.frame.slide.onmousedown = function (event) {me._onMouseDown(event);};
this.frame.prev.onclick = function (event) {me.prev(event);};
this.frame.play.onclick = function (event) {me.togglePlay(event);};
this.frame.next.onclick = function (event) {me.next(event);};
this.container.appendChild(this.frame);
this.onChangeCallback = undefined;
this.playTimeout = undefined;
this.framerate = 20; // frames per second
this.duration = 10; // seconds
this.doLoop = true;
this.start = 0;
this.end = 0;
this.value = 0;
this.step = 0;
this.rangeIsDate = false;
this.redraw();
};
/**
* Retrieve the step size, depending on the range, framerate, and duration
*/
Graph.Slider.prototype._updateStep = function() {
var range = (this.end - this.start);
var frameCount = this.duration * this.framerate;
this.step = range / frameCount;
};
/**
* Select the previous index
*/
Graph.Slider.prototype.prev = function() {
this._setValue(this.value - this.step);
};
/**
* Select the next index
*/
Graph.Slider.prototype.next = function() {
this._setValue(this.value + this.step);
};
/**
* Select the next index
*/
Graph.Slider.prototype.playNext = function() {
var start = new Date();
if (!this.leftButtonDown) {
if (this.value + this.step < this.end) {
this._setValue(this.value + this.step);
}
else {
if (this.doLoop) {
this._setValue(this.start);
}
else {
this._setValue(this.end);
this.stop();
return;
}
}
}
var end = new Date();
var diff = (end - start);
// calculate how much time it to to set the index and to execute the callback
// function.
var interval = Math.max(1000 / this.framerate - diff, 0);
var me = this;
this.playTimeout = setTimeout(function() {me.playNext();}, interval);
};
/**
* Toggle start or stop playing
*/
Graph.Slider.prototype.togglePlay = function() {
if (this.playTimeout === undefined) {
this.play();
} else {
this.stop();
}
};
/**
* Start playing
*/
Graph.Slider.prototype.play = function() {
this.frame.play.value = "Stop";
this.playNext();
};
/**
* Stop playing
*/
Graph.Slider.prototype.stop = function() {
this.frame.play.value = "Play";
clearInterval(this.playTimeout);
this.playTimeout = undefined;
};
/**
* Set a callback function which will be triggered when the value of the
* slider bar has changed.
*/
Graph.Slider.prototype.setOnChangeCallback = function(callback) {
this.onChangeCallback = callback;
};
/**
* Set the interval for playing the list
* @param {number} framerate Framerate in frames per second
*/
Graph.Slider.prototype.setFramerate = function(framerate) {
this.framerate = framerate;
this._updateStep();
};
/**
* Retrieve the current framerate
* @return {number} framerate in frames per second
*/
Graph.Slider.prototype.getFramerate = function() {
return this.framerate;
};
/**
* Set the duration for playing
* @param {number} duration Duration in seconds
*/
Graph.Slider.prototype.setDuration = function(duration) {
this.duration = duration;
this._updateStep();
};
/**
* Set the time acceleration for playing the history. Only applicable when
* the values are of type Date.
* @param {number} acceleration Acceleration, for example 10 means play
* ten times as fast as real time. A value
* of 1 will play the history in real time.
*/
Graph.Slider.prototype.setAcceleration = function(acceleration) {
var durationRealtime = (this.end - this.start) / 1000; // in seconds
this.duration = durationRealtime / acceleration;
this._updateStep();
};
/**
* Set looping on or off
* @param {boolean} doLoop If true, the slider will jump to the start when
* the end is passed, and will jump to the end
* when the start is passed.
*/
Graph.Slider.prototype.setLoop = function(doLoop) {
this.doLoop = doLoop;
};
/**
* Retrieve the current value of loop
* @return {boolean} doLoop If true, the slider will jump to the start when
* the end is passed, and will jump to the end
* when the start is passed.
*/
Graph.Slider.prototype.getLoop = function() {
return this.doLoop;
};
/**
* Execute the onchange callback function
*/
Graph.Slider.prototype.onChange = function() {
if (this.onChangeCallback !== undefined) {
this.onChangeCallback();
}
};
/**
* redraw the slider on the correct place
*/
Graph.Slider.prototype.redraw = function() {
// resize the bar
var barTop = (this.frame.clientHeight/2 -
this.frame.bar.offsetHeight/2);
var barWidth = (this.frame.clientWidth -
this.frame.prev.clientWidth -
this.frame.play.clientWidth -
this.frame.next.clientWidth - 30);
this.frame.bar.style.top = barTop + "px";
this.frame.bar.style.width = barWidth + "px";
// position the slider button
this.frame.slide.title = this.getValue();
this.frame.slide.style.left = this._valueToLeft(this.value) + "px";
// set the title
this.title.innerHTML = this.getValue();
};
/**
* Set the range for the slider
* @param {Date | Number} start Start of the range
* @param {Date | Number} end End of the range
*/
Graph.Slider.prototype.setRange = function(start, end) {
if (start === undefined || start === null || start === NaN) {
this.start = 0;
this.rangeIsDate = false;
}
else if (start instanceof Date) {
this.start = start.getTime();
this.rangeIsDate = true;
}
else {
this.start = start;
this.rangeIsDate = false;
}
if (end === undefined || end === null || end === NaN) {
if (this.start instanceof Date) {
this.end = new Date(this.start);
}
else {
this.end = this.start;
}
}
else if (end instanceof Date) {
this.end = end.getTime();
}
else {
this.end = end;
}
this.value = this.start;
this._updateStep();
this.redraw();
};
/**
* Set a value for the slider. The value must be between start and end
* When the range are Dates, the value will be translated to a date
* @param {Number} value
*/
Graph.Slider.prototype._setValue = function(value) {
this.value = this._limitValue(value);
this.redraw();
this.onChange();
};
/**
* retrieve the current value in the correct type, Number or Date
* @return {Date | Number} value
*/
Graph.Slider.prototype.getValue = function() {
if (this.rangeIsDate) {
return new Date(this.value);
}
else {
return this.value;
}
};
Graph.Slider.prototype.offset = 3;
Graph.Slider.prototype._leftToValue = function (left) {
var width = parseFloat(this.frame.bar.style.width) -
this.frame.slide.clientWidth - 10;
var x = left - this.offset;
var range = this.end - this.start;
var value = this._limitValue(x / width * range + this.start);
return value;
};
Graph.Slider.prototype._valueToLeft = function (value) {
var width = parseFloat(this.frame.bar.style.width) -
this.frame.slide.clientWidth - 10;
var x;
if (this.end > this.start) {
x = (value - this.start) / (this.end - this.start) * width;
}
else {
x = 0;
}
var left = x + this.offset;
return left;
};
Graph.Slider.prototype._limitValue = function(value) {
if (value < this.start) {
value = this.start
}
if (value > this.end) {
value = this.end;
}
return value;
};
Graph.Slider.prototype._onMouseDown = function(event) {
// only react on left mouse button down
this.leftButtonDown = event.which ? (event.which === 1) : (event.button === 1);
if (!this.leftButtonDown) return;
this.startClientX = event.clientX;
this.startSlideX = parseFloat(this.frame.slide.style.left);
this.frame.style.cursor = 'move';
// add event listeners to handle moving the contents
// we store the function onmousemove and onmouseup in the graph, so we can
// remove the eventlisteners lateron in the function mouseUp()
var me = this;
this.onmousemove = function (event) {me._onMouseMove(event);};
this.onmouseup = function (event) {me._onMouseUp(event);};
Graph.addEventListener(document, "mousemove", this.onmousemove);
Graph.addEventListener(document, "mouseup", this.onmouseup);
Graph.preventDefault(event);
};
Graph.Slider.prototype._onMouseMove = function (event) {
var diff = event.clientX - this.startClientX;
var x = this.startSlideX + diff;
var value = this._leftToValue(x);
this._setValue(value);
Graph.preventDefault(event);
};
Graph.Slider.prototype._onMouseUp = function (event) {
this.frame.style.cursor = 'auto';
this.leftButtonDown = false;
// remove event listeners
Graph.removeEventListener(document, "mousemove", this.onmousemove);
Graph.removeEventListener(document, "mouseup", this.onmouseup);
Graph.preventDefault(event);
};
/**--------------------------------------------------------------------------**/ /**--------------------------------------------------------------------------**/

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


Loading…
Cancel
Save