Browse Source

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

v3_develop
jos 10 years ago
parent
commit
758d75100c
8 changed files with 25755 additions and 25241 deletions
  1. +3
    -0
      HISTORY.md
  2. +25161
    -25189
      dist/vis.js
  3. +1
    -1
      dist/vis.min.css
  4. +54
    -7
      docs/network.html
  5. +319
    -0
      examples/network/33_animation.html
  6. +2
    -0
      examples/network/index.html
  7. +214
    -43
      lib/network/Network.js
  8. +1
    -1
      lib/network/mixins/HierarchicalLayoutMixin.js

+ 3
- 0
HISTORY.md View File

@ -15,6 +15,9 @@ http://visjs.org
- Implemented support for broken image fallback. Thanks @sfairgrieve. - Implemented support for broken image fallback. Thanks @sfairgrieve.
- Added multiline labels to edges as they are implemented in nodes. Updated - Added multiline labels to edges as they are implemented in nodes. Updated
multiline example to show this. multiline example to show this.
- Added animation and camera controls by the method .moveTo()
- Added new event that fires when the animation is finished.
- Added new example showing the new features of animation.
### Timeline ### Timeline

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


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


+ 54
- 7
docs/network.html View File

@ -1020,7 +1020,7 @@ All options defined per-node override these global settings.
<p> <p>
Edges can be configured with different length and styling. To configure edges, provide an object named <code>edges</code> in the <code>options</code> for the Network. Edges can be configured with different length and styling. To configure edges, provide an object named <code>edges</code> in the <code>options</code> for the Network.
Because the length of an edge is a property of the physics simulation, you can change the length of the edge by changing the springLength in your selected physics solver. Because the length of an edge is a property of the physics simulation, you can change the length of the edge by changing the springLength in your selected physics solver.
To change the edge length of individual edges, you can use the <code>length</code> property in the <a href="Edges">edge definition</a>.
To change the edge length of individual edges, you can use the <code>length</code> property in the <a href="#Edges">edge definition</a>.
</p> </p>
<p> <p>
@ -2146,10 +2146,20 @@ var options: {
</td> </td>
</tr> </tr>
<tr> <tr>
<td>focusOnNode(nodeId, [zoomLevel])</td>
<td>focusOnNode(nodeId, [options])</td>
<td>none</td> <td>none</td>
<td>This function will move the view to center on the specified node. An optional zoomLevel can be passed where 1.0 is 100&#37;, between 0.0 and 1.0 is zooming out and > 1.0 is zooming in. Generally, close to 1.0 is sufficient.
If this argument is not passed the view will only move, not zoom.
<td>This function will move the view to center on the specified node. An optional options object can submitted where you can define the animation properties. <br />
The options that can be defined are:<br />
<b><code>scale:Number</code></b><br /> - to zoom to that scale,<br />
<b><code>offset:{x:Number, y:Number}</code></b><br /> - to offset the position from the center of the canvas (in DOM units),<br />
<b><code>animation: Object || Boolean</code></b><br /> - to define the specifics of the animation. True is animated with default settings, false is not animated.<br />
<br />
The animation object can consist of:<br />
<b><code>duration: Number</code></b><br /> - the duration of the animation in milliseconds,<br />
<b><code>easingFunction: String</code></b><br /> - the easing function of the animation, available are:<br />
<code>linear, easeInQuad, easeOutQuad, easeInOutQuad, easeInCubic, easeOutCubic, easeInOutCubic,
easeInQuart, easeOutQuart, easeInOutQuart,
easeInQuint, easeOutQuint, easeInOutQuint </code> <br /><br />
</td> </td>
</tr> </tr>
<tr> <tr>
@ -2171,7 +2181,27 @@ var options: {
<td>This function converts canvas coordinates to coordinates on the DOM. Input and output are in the form of {x:xpos,y:ypos}. The DOM values are relative to the network container. <td>This function converts canvas coordinates to coordinates on the DOM. Input and output are in the form of {x:xpos,y:ypos}. The DOM values are relative to the network container.
</td> </td>
</tr> </tr>
<tr>
<tr>
<td>moveTo(options)</td>
<td>object</td>
<td>This function allows you to programmatically move the view. The options that can be defined are:<br />
<b><code>position:{x:Number, y:Number}</code></b><br /> - to move to that position (in canvas units), <br />
<b><code>scale:Number</code></b><br /> - to zoom to that scale,<br />
<b><code>offset:{x:Number, y:Number}</code></b><br /> - to offset the position from the center of the canvas (in DOM units),<br />
<b><code>animation: Object || Boolean</code></b><br /> - to define the specifics of the animation.<br />
<br />
The animation object can consist of:<br />
<b><code>duration: Number</code></b><br /> - the duration of the animation in milliseconds,<br />
<b><code>easingFunction: String</code></b><br /> - the easing function of the animation, available are:<br />
<code>linear, easeInQuad, easeOutQuad, easeInOutQuad, easeInCubic, easeOutCubic, easeInOutCubic,
easeInQuart, easeOutQuart, easeInOutQuart,
easeInQuint, easeOutQuint, easeInOutQuint </code> <br /><br />
<i>You will have to define at least a scale or a position. Otherwise, there is nothing to move to.</i>
</td>
</tr>
<tr>
<td>on(event, callback)</td> <td>on(event, callback)</td>
<td>none</td> <td>none</td>
<td>Create an event listener. The callback function is invoked every time the event is triggered. Avialable events: <code>select</code>. The callback function is invoked as <code>callback(properties)</code>, where <code>properties</code> is an object containing event specific properties. See section <a href="#Events">Events</a> for more information.</td> <td>Create an event listener. The callback function is invoked every time the event is triggered. Avialable events: <code>select</code>. The callback function is invoked as <code>callback(properties)</code>, where <code>properties</code> is an object containing event specific properties. See section <a href="#Events">Events</a> for more information.</td>
@ -2248,9 +2278,18 @@ var options: {
</tr> </tr>
<tr> <tr>
<td>zoomExtent()</td>
<td>zoomExtent([options])</td>
<td>none</td> <td>none</td>
<td>Scales the network so all the nodes are in center view.</td>
<td>Scales the network so all the nodes are in center view. Optionally you can supply options for animation. These
options can just be a boolean. When true, the zoom is animated, when false there is no animation.
Alternatively, you can supply an object.
<br /><br /> The object can consist of:<br />
<b><code>duration: Number</code></b><br /> - the duration of the animation in milliseconds,<br />
<b><code>easingFunction: String</code></b><br /> - the easing function of the animation, available are:<br />
<code>linear, easeInQuad, easeOutQuad, easeInOutQuad, easeInCubic, easeOutCubic, easeInOutCubic,
easeInQuart, easeOutQuart, easeInOutQuart,
easeInQuint, easeOutQuint, easeInOutQuint </code>
</td>
</tr> </tr>
</table> </table>
@ -2301,6 +2340,14 @@ network.off('select', onSelect);
<th>Properties</th> <th>Properties</th>
</tr> </tr>
<tr>
<td>animationFinished</td>
<td>Fired after an animation is finished.
</td>
<td>
none
</td>
</tr>
<tr> <tr>
<td>select</td> <td>select</td>
<td>Fired after the user selects or deselects a node by clicking it. <td>Fired after the user selects or deselects a node by clicking it.

+ 319
- 0
examples/network/33_animation.html View File

@ -0,0 +1,319 @@
<!doctype html>
<html>
<head>
<title>Network | Animation</title>
<style type="text/css">
body {
font: 10pt sans;
}
#mynetwork {
width: 600px;
height: 600px;
border: 1px solid lightgray;
}
div.left {
position:relative;
float:left;
width:300px;
border: 1px #c7c7c7 solid;
height:590px;
padding:5px;
}
div.right {
padding-left:10px;
float:left;
width:600px;
}
div.bottom {
position:absolute;
bottom:5px;
}
</style>
<script type="text/javascript" src="../../dist/vis.js"></script>
<link href="../../dist/vis.css" rel="stylesheet" type="text/css" />
<script type="text/javascript">
var nodes = null;
var edges = null;
var network = null;
var doButton, focusButton, showButton;
var statusUpdateSpan;
var finishMessage = "";
function draw() {
statusUpdateSpan = document.getElementById("statusUpdate");
doButton = document.getElementById("btnDo");
focusButton = document.getElementById("btnFocus");
showButton = document.getElementById("btnShow");
nodes = [];
edges = [];
var connectionCount = [];
// randomly create some nodes and edges
var nodeCount = 25;
for (var i = 0; i < nodeCount; i++) {
nodes.push({
id: i,
label: String(i)
});
connectionCount[i] = 0;
// create edges in a scale-free-network way
if (i == 1) {
var from = i;
var to = 0;
edges.push({
from: from,
to: to
});
connectionCount[from]++;
connectionCount[to]++;
}
else if (i > 1) {
var conn = edges.length * 2;
var rand = Math.floor(Math.random() * conn);
var cum = 0;
var j = 0;
while (j < connectionCount.length && cum < rand) {
cum += connectionCount[j];
j++;
}
var from = i;
var to = j;
edges.push({
from: from,
to: to
});
connectionCount[from]++;
connectionCount[to]++;
}
}
// create a network
var container = document.getElementById('mynetwork');
var data = {
nodes: nodes,
edges: edges
};
var options = {stabilizationIterations:1200};
network = new vis.Network(container, data, options);
// add event listeners
network.on('select', function(params) {
document.getElementById('selection').innerHTML = 'Selection: ' + params.nodes;
});
network.on('stabilized', function (params) {
document.getElementById('stabilization').innerHTML = 'Stabilization took ' + params.iterations + ' iterations.';
});
network.on("animationFinished", function() {
statusUpdateSpan.innerHTML = finishMessage;
})
}
function zoomExtentAnimated() {
var offsetx = parseInt(document.getElementById("offsetx").value);
var offsety = parseInt(document.getElementById("offsety").value);
var duration = parseInt(document.getElementById("duration").value);
var easingFunction = document.getElementById("easingFunction").value;
var options = {offset: {x:offsetx,y:offsety},
duration: duration,
easingFunction: easingFunction
}
statusUpdateSpan.innerHTML = "Doing ZoomExtent() Animation.";
finishMessage = "Animation finished."
network.zoomExtent(options);
}
function doDefaultAnimation() {
var offsetx = parseInt(document.getElementById("offsetx").value);
var offsety = parseInt(document.getElementById("offsety").value);
var scale = parseFloat(document.getElementById("scale").value);
var positionx = parseInt(document.getElementById("positionx").value);
var positiony = parseInt(document.getElementById("positiony").value);
var easingFunction = document.getElementById("easingFunction").value;
var options = {
position: {x:positionx,y:positiony},
scale: scale,
offset: {x:offsetx,y:offsety},
animation: true // default duration is 1000ms and default easingFunction is easeInOutQuad.
}
statusUpdateSpan.innerHTML = "Doing Animation.";
finishMessage = "Animation finished."
network.moveTo(options);
}
function doAnimation() {
var offsetx = parseInt(document.getElementById("offsetx").value);
var offsety = parseInt(document.getElementById("offsety").value);
var duration = parseInt(document.getElementById("duration").value);
var scale = parseFloat(document.getElementById("scale").value);
var positionx = parseInt(document.getElementById("positionx").value);
var positiony = parseInt(document.getElementById("positiony").value);
var easingFunction = document.getElementById("easingFunction").value;
var options = {
position: {x:positionx,y:positiony},
scale: scale,
offset: {x:offsetx,y:offsety},
animation: {
duration: duration,
easingFunction: easingFunction
}
}
statusUpdateSpan.innerHTML = "Doing Animation.";
finishMessage = "Animation finished."
network.moveTo(options);
}
function focusRandom() {
var nodeId = Math.floor(Math.random() * 25);
var offsetx = parseInt(document.getElementById("offsetx").value);
var offsety = parseInt(document.getElementById("offsety").value);
var duration = parseInt(document.getElementById("duration").value);
var scale = parseFloat(document.getElementById("scale").value);
var easingFunction = document.getElementById("easingFunction").value;
var options = {
// position: {x:positionx,y:positiony}, // this is not relevant when focusing on nodes
scale: scale,
offset: {x:offsetx,y:offsety},
animation: {
duration: duration,
easingFunction: easingFunction
}
}
statusUpdateSpan.innerHTML = "Focusing on node: " + nodeId;
finishMessage = "Node: " + nodeId + " in focus.";
network.focusOnNode(nodeId, options);
}
var showInterval = false;
var showPhase = 1;
function startShow() {
if (showInterval !== false) {
showInterval = false;
showButton.value = "Start a show!";
network.zoomExtent();
}
else {
showButton.value = "Stop the show!";
var duration = parseInt(document.getElementById("duration").value);
focusRandom();
window.setTimeout(doTheShow, duration);
showInterval = true;
}
}
function doTheShow() {
if (showInterval == true) {
var duration = parseInt(document.getElementById("duration").value);
if (showPhase == 0) {
focusRandom();
showPhase = 1;
}
else {
zoomExtentAnimated();
showPhase = 0;
}
window.setTimeout(doTheShow, duration);
}
}
</script>
</head>
<body onload="draw();">
<h2>Camera animations</h2>
<div style="width:700px; font-size:14px;">
You can move the view around programmatically using the .moveTo(options) function. The options supplied to this function can
also be (partially) supplied to the .zoomExtent() and .focusOnNode() methods. These are explained in the docs.
<br /><br/>
The buttons below take the fields from the table when they can. For instance, the "Animate with default settings." takes the position, scale and offset while using
the default animation values for duration and easing function. The focusOnNode takes everything except the position and the zoomExtent takes only the duration and easing function.
<br/><br/>
Here you can see a full description of the options you can supply to moveTo:
</div>
<pre>
var moveToOptions = {
position: {x:x, y:x}, // position to animate to (Numbers)
scale: 1.0, // scale to animate to (Number)
offset: {x:x, y:y}, // offset from the center in DOM pixels (Numbers)
animation: { // animation object, can also be Boolean
duration: 1000, // animation duration in milliseconds (Number)
easingFunction: "easeInOutQuad" // Animation easing function, available are:
} // linear, easeInQuad, easeOutQuad, easeInOutQuad,
} // easeInCubic, easeOutCubic, easeInOutCubic,
// easeInQuart, easeOutQuart, easeInOutQuart,
// easeInQuint, easeOutQuint, easeInOutQuint
</pre>
<div class="left">
<table>
<tr>
<td>position x</td><td><input type="text" value="300" id="positionx" style="width:170px;"></td>
</tr>
<tr>
<td>position y</td><td><input type="text" value="300" id="positiony" style="width:170px;"></td>
</tr>
<tr>
<td>scale</td><td><input type="text" value="1.0" id="scale" style="width:170px;"></td>
</tr>
<tr>
<td>offset x</td><td><input type="text" value="0" id="offsetx" style="width:170px;"> px</td>
</tr>
<tr>
<td>offset y</td><td><input type="text" value="0" id="offsety" style="width:170px;"> px</td>
</tr>
<tr>
<td>duration</td><td><input type="text" value="2500" id="duration" style="width:170px;"> ms</td>
</tr>
<tr>
<td>easingFunction</td><td>
<select id="easingFunction" style="width:174px;">
<option value="linear">linear</option>
<option value="easeInQuad">easeInQuad</option>
<option value="easeOutQuad">easeOutQuad</option>
<option value="easeInOutQuad" selected="selected">easeInOutQuad</option>
<option value="easeInCubic">easeInCubic</option>
<option value="easeOutCubic">easeOutCubic</option>
<option value="easeInOutCubic">easeInOutCubic</option>
<option value="easeInQuart">easeInQuart</option>
<option value="easeOutQuart">easeOutQuart</option>
<option value="easeInOutQuart">easeInOutQuart</option>
<option value="easeInQuint">easeInQuint</option>
<option value="easeOutQuint">easeOutQuint</option>
<option value="easeInOutQuint">easeInOutQuint</option>
</select>
</td>
</tr>
</table>
<div class="bottom">
<span id="statusUpdate"></span><br />
Examples:
<input type="button" onclick="doAnimation();" value="Animate with above settings." style="width:300px;" id="btnDo"> <br/>
<input type="button" onclick="doDefaultAnimation();" value="Animate with default settings." style="width:300px;" id="btnDoDefault"> <br/>
<input type="button" onclick="zoomExtentAnimated();" value="Animated ZoomExtent()." style="width:300px;" id="btnZoom"> <br/>
<input type="button" onclick="focusRandom();" value="Focus on random node." style="width:300px;" id="btnFocus"><br/>
<input type="button" onclick="startShow();" value="Start a show!" style="width:300px;" id="btnShow"><br/>
</div>
</div>
<div class="right">
<div id="mynetwork"></div>
<p id="selection"></p>
<p id="stabilization"></p>
</div>
</body>
</html>

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

@ -43,6 +43,8 @@
<p><a href="29_neighbourhood_highlight.html">29_neighbourhood_highlight.html</a></p> <p><a href="29_neighbourhood_highlight.html">29_neighbourhood_highlight.html</a></p>
<p><a href="30_importing_from_gephi.html">30_importing_from_gephi.html</a></p> <p><a href="30_importing_from_gephi.html">30_importing_from_gephi.html</a></p>
<p><a href="31_localization.html">31_localization.html</a></p> <p><a href="31_localization.html">31_localization.html</a></p>
<p><a href="32_hierarchicaLayoutMethods.html">32_hierarchicaLayoutMethods.html</a></p>
<p><a href="33_animation.html">33_animation.html</a></p>
<p><a href="graphviz/graphviz_gallery.html">graphviz_gallery.html</a></p> <p><a href="graphviz/graphviz_gallery.html">graphviz_gallery.html</a></p>
</div> </div>

+ 214
- 43
lib/network/Network.js View File

@ -219,6 +219,15 @@ function Network (container, data, options) {
this.hoverObj = {nodes:{},edges:{}}; this.hoverObj = {nodes:{},edges:{}};
this.controlNodesActive = false; this.controlNodesActive = false;
// animation properties
this.animationSpeed = 1/this.renderRefreshRate;
this.animationEasingFunction = "easeInOutQuint";
this.easingTime = 0;
this.sourceScale = 0;
this.targetScale = 0;
this.sourceTranslation = 0;
this.targetTranslation = 0;
// Node variables // Node variables
var network = this; var network = this;
this.groups = new Groups(); // object with groups this.groups = new Groups(); // object with groups
@ -246,6 +255,66 @@ function Network (container, data, options) {
// load the selection system. (mandatory, required by Network) // load the selection system. (mandatory, required by Network)
this._loadHierarchySystem(); this._loadHierarchySystem();
/*
* Easing Functions - inspired from http://gizma.com/easing/
* only considering the t value for the range [0, 1] => [0, 1]
* https://gist.github.com/gre/1650294
*/
this.easingFunctions = {
// no easing, no acceleration
linear: function (t) {
return t
},
// accelerating from zero velocity
easeInQuad: function (t) {
return t * t
},
// decelerating to zero velocity
easeOutQuad: function (t) {
return t * (2 - t)
},
// acceleration until halfway, then deceleration
easeInOutQuad: function (t) {
return t < .5 ? 2 * t * t : -1 + (4 - 2 * t) * t
},
// accelerating from zero velocity
easeInCubic: function (t) {
return t * t * t
},
// decelerating to zero velocity
easeOutCubic: function (t) {
return (--t) * t * t + 1
},
// acceleration until halfway, then deceleration
easeInOutCubic: function (t) {
return t < .5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1
},
// accelerating from zero velocity
easeInQuart: function (t) {
return t * t * t * t
},
// decelerating to zero velocity
easeOutQuart: function (t) {
return 1 - (--t) * t * t * t
},
// acceleration until halfway, then deceleration
easeInOutQuart: function (t) {
return t < .5 ? 8 * t * t * t * t : 1 - 8 * (--t) * t * t * t
},
// accelerating from zero velocity
easeInQuint: function (t) {
return t * t * t * t * t
},
// decelerating to zero velocity
easeOutQuint: function (t) {
return 1 + (--t) * t * t * t * t
},
// acceleration until halfway, then deceleration
easeInOutQuint: function (t) {
return t < .5 ? 16 * t * t * t * t * t : 1 + 16 * (--t) * t * t * t * t
}
};
// apply options // apply options
this._setTranslation(this.frame.clientWidth / 2, this.frame.clientHeight / 2); this._setTranslation(this.frame.clientWidth / 2, this.frame.clientHeight / 2);
this._setScale(1); this._setScale(1);
@ -321,7 +390,7 @@ function Network (container, data, options) {
else { else {
// zoom so all data will fit on the screen, if clustering is enabled, we do not want start to be called here. // zoom so all data will fit on the screen, if clustering is enabled, we do not want start to be called here.
if (this.constants.stabilize == false) { if (this.constants.stabilize == false) {
this.zoomExtent(true,this.constants.clustering.enabled);
this.zoomExtent(undefined, true,this.constants.clustering.enabled);
} }
} }
@ -391,38 +460,25 @@ Network.prototype._findCenter = function(range) {
}; };
/**
* center the network
*
* @param {object} range = {minX: minX, maxX: maxX, minY: minY, maxY: maxY};
*/
Network.prototype._centerNetwork = function(range) {
var center = this._findCenter(range);
center.x *= this.scale;
center.y *= this.scale;
center.x -= 0.5 * this.frame.canvas.clientWidth;
center.y -= 0.5 * this.frame.canvas.clientHeight;
this._setTranslation(-center.x,-center.y); // set at 0,0
};
/** /**
* This function zooms out to fit all data on screen based on amount of nodes * This function zooms out to fit all data on screen based on amount of nodes
* *
* @param {Boolean} [initialZoom] | zoom based on fitted formula or range, true = fitted, default = false; * @param {Boolean} [initialZoom] | zoom based on fitted formula or range, true = fitted, default = false;
* @param {Boolean} [disableStart] | If true, start is not called. * @param {Boolean} [disableStart] | If true, start is not called.
*/ */
Network.prototype.zoomExtent = function(initialZoom, disableStart) {
Network.prototype.zoomExtent = function(animationOptions, initialZoom, disableStart) {
if (initialZoom === undefined) { if (initialZoom === undefined) {
initialZoom = false; initialZoom = false;
} }
if (disableStart === undefined) { if (disableStart === undefined) {
disableStart = false; disableStart = false;
} }
if (animationOptions === undefined) {
animationOptions = false;
}
var range = this._getRange(); var range = this._getRange();
var scale = this._getScale();
var zoomLevel; var zoomLevel;
if (initialZoom == true) { if (initialZoom == true) {
@ -465,12 +521,21 @@ Network.prototype.zoomExtent = function(initialZoom, disableStart) {
} }
this._setScale(zoomLevel);
this._centerNetwork(range);
var center = this._findCenter(range);
if (disableStart == false) { if (disableStart == false) {
var options = {position: center, scale: zoomLevel, animation: animationOptions};
this.moveTo(options);
this.moving = true; this.moving = true;
this.start(); this.start();
} }
else {
center.x *= zoomLevel;
center.y *= zoomLevel;
center.x -= 0.5 * this.frame.canvas.clientWidth;
center.y -= 0.5 * this.frame.canvas.clientHeight;
this._setScale(zoomLevel);
this._setTranslation(-center.x,-center.y);
}
}; };
@ -1804,9 +1869,9 @@ Network.prototype._YconvertCanvasToDOM = function(y) {
* @returns {{x: number, y: number}} * @returns {{x: number, y: number}}
* @constructor * @constructor
*/ */
Network.prototype.canvasToDOM = function(pos) {
return {x:this._XconvertCanvasToDOM(pos.x),y:this._YconvertCanvasToDOM(pos.y)};
}
Network.prototype.canvasToDOM = function (pos) {
return {x: this._XconvertCanvasToDOM(pos.x), y: this._YconvertCanvasToDOM(pos.y)};
};
/** /**
* *
@ -1814,9 +1879,9 @@ Network.prototype.canvasToDOM = function(pos) {
* @returns {{x: number, y: number}} * @returns {{x: number, y: number}}
* @constructor * @constructor
*/ */
Network.prototype.DOMtoCanvas = function(pos) {
return {x:this._XconvertDOMtoCanvas(pos.x),y:this._YconvertDOMtoCanvas(pos.y)};
}
Network.prototype.DOMtoCanvas = function (pos) {
return {x: this._XconvertDOMtoCanvas(pos.x), y: this._YconvertDOMtoCanvas(pos.y)};
};
/** /**
* Redraw all nodes * Redraw all nodes
@ -1905,7 +1970,7 @@ Network.prototype._stabilize = function() {
this._physicsTick(); this._physicsTick();
count++; count++;
} }
this.zoomExtent(false,true);
this.zoomExtent(undefined,false,true);
if (this.constants.freezeForStabilization == true) { if (this.constants.freezeForStabilization == true) {
this._restoreFrozenNodes(); this._restoreFrozenNodes();
} }
@ -2258,33 +2323,137 @@ Network.prototype.storePosition = function() {
* Center a node in view. * Center a node in view.
* *
* @param {Number} nodeId * @param {Number} nodeId
* @param {Number} [zoomLevel]
* @param {Number} [options]
*/ */
Network.prototype.focusOnNode = function (nodeId, zoomLevel) {
Network.prototype.focusOnNode = function (nodeId, options) {
if (this.nodes.hasOwnProperty(nodeId)) { if (this.nodes.hasOwnProperty(nodeId)) {
if (zoomLevel === undefined) {
zoomLevel = this._getScale();
if (options === undefined) {
options = {};
} }
var nodePosition= {x: this.nodes[nodeId].x, y: this.nodes[nodeId].y};
var nodePosition = {x: this.nodes[nodeId].x, y: this.nodes[nodeId].y};
options.position = nodePosition;
var requiredScale = zoomLevel;
this._setScale(requiredScale);
this.moveTo(options)
}
else {
console.log("This nodeId cannot be found.");
}
};
var canvasCenter = this.DOMtoCanvas({x:0.5 * this.frame.canvas.width,y:0.5 * this.frame.canvas.height});
var translation = this._getTranslation();
/**
*
* @param {Object} options | options.offset = {x:Number, y:Number} // offset from the center in DOM pixels
* | options.scale = Number // scale to move to
* | options.position = {x:Number, y:Number} // position to move to
* | options.animation = {duration:Number, easingFunction:String} || Boolean // position to move to
*/
Network.prototype.moveTo = function (options) {
if (options === undefined) {
options = {};
return;
}
if (options.offset === undefined) {options.offset = {x: 0, y: 0}; }
if (options.offset.x === undefined) {options.offset.x = 0; }
if (options.offset.y === undefined) {options.offset.y = 0; }
if (options.scale === undefined) {options.scale = this._getScale(); }
if (options.position === undefined) {options.position = this._getTranslation();}
if (options.animation === undefined) {options.animation = {duration:0}; }
if (options.animation === false ) {options.animation = {duration:0}; }
if (options.animation === true ) {options.animation = {}; }
if (options.animation.duration === undefined) {options.animation.duration = 1000; } // default duration
if (options.animation.easingFunction === undefined) {options.animation.easingFunction = "easeInOutQuad"; } // default easing function
var distanceFromCenter = {x:canvasCenter.x - nodePosition.x,
y:canvasCenter.y - nodePosition.y};
this.animateView(options);
};
this._setTranslation(translation.x + requiredScale * distanceFromCenter.x,
translation.y + requiredScale * distanceFromCenter.y);
this.redraw();
/**
*
* @param {Object} options | options.offset = {x:Number, y:Number} // offset from the center in DOM pixels
* | options.time = Number // animation time in milliseconds
* | options.scale = Number // scale to animate to
* | options.position = {x:Number, y:Number} // position to animate to
* | options.easingFunction = String // linear, easeInQuad, easeOutQuad, easeInOutQuad,
* // easeInCubic, easeOutCubic, easeInOutCubic,
* // easeInQuart, easeOutQuart, easeInOutQuart,
* // easeInQuint, easeOutQuint, easeInOutQuint
*/
Network.prototype.animateView = function (options) {
if (options === undefined) {
options = {};
return;
}
// forcefully complete the old animation if it was still running
if (this.easingTime != 0) {
this._transitionRedraw(1); // by setting easingtime to 1, we finish the animation.
}
this.sourceScale = this._getScale();
this.sourceTranslation = this._getTranslation();
this.targetScale = options.scale;
// set the scale so the viewCenter is based on the correct zoom level. This is overridden in the transitionRedraw
// but at least then we'll have the target transition
this._setScale(this.targetScale);
var viewCenter = this.DOMtoCanvas({x: 0.5 * this.frame.canvas.clientWidth, y: 0.5 * this.frame.canvas.clientHeight});
var distanceFromCenter = { // offset from view, distance view has to change by these x and y to center the node
x: viewCenter.x - options.position.x,
y: viewCenter.y - options.position.y
};
this.targetTranslation = {
x: this.sourceTranslation.x + distanceFromCenter.x * this.targetScale + options.offset.x,
y: this.sourceTranslation.y + distanceFromCenter.y * this.targetScale + options.offset.y
};
// if the time is set to 0, don't do an animation
if (options.animation.duration == 0) {
this._setScale(this.targetScale);
this._setTranslation(this.targetTranslation.x, this.targetTranslation.y);
this._redraw();
} }
else { else {
console.log("This nodeId cannot be found.");
this.animationSpeed = 1 / (this.renderRefreshRate * options.animation.duration * 0.001) || 1 / this.renderRefreshRate;
this.animationEasingFunction = options.animation.easingFunction;
this._classicRedraw = this._redraw;
this._redraw = this._transitionRedraw;
this.moving = true;
this.start();
} }
}; };
/**
*
* @param easingTime
* @private
*/
Network.prototype._transitionRedraw = function (easingTime) {
this.easingTime = easingTime || this.easingTime + this.animationSpeed;
this.easingTime += this.animationSpeed;
var progress = this.easingFunctions[this.animationEasingFunction](this.easingTime);
this._setScale(this.sourceScale + (this.targetScale - this.sourceScale) * progress);
this._setTranslation(
this.sourceTranslation.x + (this.targetTranslation.x - this.sourceTranslation.x) * progress,
this.sourceTranslation.y + (this.targetTranslation.y - this.sourceTranslation.y) * progress
);
this._classicRedraw();
this.moving = true;
// cleanup
if (this.easingTime >= 1.0) {
this.easingTime = 0;
this._redraw = this._classicRedraw;
this.emit("animationFinished");
}
};
Network.prototype._classicRedraw = function () {
// placeholder function to be overloaded by animations;
};
/** /**
* Returns true when the Timeline is active. * Returns true when the Timeline is active.
* @returns {boolean} * @returns {boolean}
@ -2293,4 +2462,6 @@ Network.prototype.isActive = function () {
return !this.activator || this.activator.active; return !this.activator || this.activator.active;
}; };
module.exports = Network; module.exports = Network;

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

@ -59,7 +59,7 @@ exports._setupHierarchicalLayout = function() {
// if the user defined some levels but not all, alert and run without hierarchical layout // if the user defined some levels but not all, alert and run without hierarchical layout
if (undefinedLevel == true && definedLevel == true) { if (undefinedLevel == true && definedLevel == true) {
throw new Error("To use the hierarchical layout, nodes require either no predefined levels or levels have to be defined for all nodes."); throw new Error("To use the hierarchical layout, nodes require either no predefined levels or levels have to be defined for all nodes.");
this.zoomExtent(true,this.constants.clustering.enabled);
this.zoomExtent(undefined,true,this.constants.clustering.enabled);
if (!this.constants.clustering.enabled) { if (!this.constants.clustering.enabled) {
this.start(); this.start();
} }

Loading…
Cancel
Save