Commit 8d416485 authored by Romain Loth's avatar Romain Loth

add a "disperse nodes" button for noverlap in zoom/layout controls

parent 2493378f
......@@ -373,6 +373,13 @@
</a>
</li>
<li>
<a href="#" id="noverlapButton" class="zoombarbuttons"
title="Disperse Overlapping Nodes">
<img src="libs/img2/disperse.png" style="width:36px">
</a>
</li>
<li>
<div class="onoffswitch">
<input type="checkbox" name="edges-switch" class="onoffswitch-checkbox" id="edges-switch" checked>
......@@ -617,6 +624,8 @@
the v1.2 stock sigma.(min).js but with a much lighter RAM footprint!
cf. https://github.com/jacomyal/sigma.js/issues/340
-->
<script src="tinawebJS/sigma_v1.2/plugins/sigma.plugins.animate/sigma.plugins.animate.js"></script>
<script src="tinawebJS/sigma_v1.2/plugins/sigma.layout.noverlap/sigma.layout.noverlap.js"></script>
<script src="tinawebJS/sigma_v1.2/plugins/sigma.layout.forceAtlas2/supervisor.js"></script>
<script src="tinawebJS/sigma_v1.2/plugins/sigma.layout.forceAtlas2/worker.js"></script>
<script src="tinawebJS/sigma_v1.2/plugins/sigma.renderers.snapshot/sigma.renderers.snapshot.js"></script>
......
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="210mm"
height="297mm"
viewBox="0 0 744.09448819 1052.3622047"
id="svg2"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="button_bg_disperse.svg">
<defs
id="defs4">
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker10552"
refX="0.0"
refY="0.0"
orient="auto"
inkscape:stockid="TriangleOutM">
<path
transform="scale(0.4)"
style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1"
d="M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z "
id="path10554" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker10542"
refX="0.0"
refY="0.0"
orient="auto"
inkscape:stockid="TriangleOutM">
<path
transform="scale(0.4)"
style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1"
d="M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z "
id="path10544" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker10460"
refX="0.0"
refY="0.0"
orient="auto"
inkscape:stockid="TriangleOutM">
<path
transform="scale(0.4)"
style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1"
d="M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z "
id="path10462" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker10384"
refX="0.0"
refY="0.0"
orient="auto"
inkscape:stockid="TriangleOutM">
<path
transform="scale(0.4)"
style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1"
d="M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z "
id="path10386" />
</marker>
<marker
inkscape:stockid="TriangleOutM"
orient="auto"
refY="0.0"
refX="0.0"
id="TriangleOutM"
style="overflow:visible"
inkscape:isstock="true"
inkscape:collect="always">
<path
id="path4522"
d="M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z "
style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1"
transform="scale(0.4)" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible;"
id="marker10164"
refX="0.0"
refY="0.0"
orient="auto"
inkscape:stockid="Arrow2Mend">
<path
transform="scale(0.6) rotate(180) translate(0,0)"
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
id="path10166" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible;"
id="marker10106"
refX="0.0"
refY="0.0"
orient="auto"
inkscape:stockid="Arrow2Mend">
<path
transform="scale(0.6) rotate(180) translate(0,0)"
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
id="path10108" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible;"
id="marker10054"
refX="0.0"
refY="0.0"
orient="auto"
inkscape:stockid="Arrow2Mend">
<path
transform="scale(0.6) rotate(180) translate(0,0)"
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
id="path10056" />
</marker>
<marker
inkscape:stockid="Arrow2Send"
orient="auto"
refY="0.0"
refX="0.0"
id="Arrow2Send"
style="overflow:visible;"
inkscape:isstock="true">
<path
id="path4410"
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
transform="scale(0.3) rotate(180) translate(-2.3,0)" />
</marker>
<marker
inkscape:stockid="Arrow2Send"
orient="auto"
refY="0"
refX="0"
id="Arrow2Send-3"
style="overflow:visible"
inkscape:isstock="true">
<path
inkscape:connector-curvature="0"
id="path4410-5"
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
transform="matrix(-0.3,0,0,-0.3,0.69,0)" />
</marker>
<marker
inkscape:stockid="Arrow2Send"
orient="auto"
refY="0"
refX="0"
id="Arrow2Send-2"
style="overflow:visible"
inkscape:isstock="true">
<path
inkscape:connector-curvature="0"
id="path4410-9"
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
transform="matrix(-0.3,0,0,-0.3,0.69,0)" />
</marker>
<marker
inkscape:stockid="Arrow2Send"
orient="auto"
refY="0"
refX="0"
id="Arrow2Send-27"
style="overflow:visible"
inkscape:isstock="true">
<path
inkscape:connector-curvature="0"
id="path4410-0"
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
transform="matrix(-0.3,0,0,-0.3,0.69,0)" />
</marker>
<marker
inkscape:stockid="Arrow2Send"
orient="auto"
refY="0"
refX="0"
id="Arrow2Send-36"
style="overflow:visible"
inkscape:isstock="true">
<path
inkscape:connector-curvature="0"
id="path4410-06"
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
transform="matrix(-0.3,0,0,-0.3,0.69,0)" />
</marker>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="2.8"
inkscape:cx="233.39899"
inkscape:cy="585.91057"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="1600"
inkscape:window-height="833"
inkscape:window-x="0"
inkscape:window-y="27"
inkscape:window-maximized="1" />
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<image
y="460.98569"
x="234.47551"
id="image4166"
xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAAABmJLR0QA/wD/AP+gvaeTAAAACXBI
WXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4QUQCykzfvardAAABHZJREFUWMPtmL2OXEUQhb++u6yE
BaElAkxKtBGESE6Rn4HAAU9gR/gJnBCQIBEQIREhIREi5MwZARGSI0SAIwsJyWBrblcdgq7+uT+z
s3ZEwER3Zufe/vrUqdM1C/+xV9r78Nmjzy6QXwLIMyCQI3fAQfFeDnKQlWs32j3j57J+vwx5fvHu
Jz/9ehLo2aMHl6AvkX90PRjr1xXGDsTFCiaDNMK+kPxb5Pdv3X381wbo2aMHt0E/Ir94PRhHlq8L
M6r7RJY/fO/Tn58DTF0ffd5h5l0YeY6HDgstYIR83oexQ7yf+yZkyPL7oHuVYgrP3ET+QYdhCePW
d9dAy0JLmFDI5wVwKWOBZaOokOePK9B5LP7OCFO+6PHwQd5TMFUZQDajuhEEnpE8PLK5760F0HU6
qUGFCqNiC2+0TrKAE3iBA5HeuIHnl+W+oQGWCh2D8fBB/L3sUgGlK2ByAES5LYOK/yy/ZLp4G/eh
AdxZl+w4jEbjll2uS7iAsWrqDOkMuSMZDGrYiz8hTWF8bRU6WqZqwj3jxnfBtzDV1DLAynsUm6Sp
S1t3rdAaxnN5WDPw2isez/W2Q4Vpie9obXqLRjg7h3wAElImpdQbqJdsGXjlwX7cuFLsYzS4lTJR
/p5ShYjykyAlyC+BhHtmmiYkI/eKjV1GC7OFMstULeVJIDfS0G3Nc67WZaplp5dJctxhmtRgvG5w
NPUiY9JZlHENowZT8skW6ilUTdVXPpdNejGvEF6fIWGecAnrFatHxyrwlEnnb5bSyDbKMGTMUiEr
/rPqKSvGVVHHLeMCNzVlzMHFXtsP6ZszcGimlfpZltBGmR4VObruEM8ifOWYqcC4SKnAuFL7bOuh
8TRGyOah6wpEam2/gony4pFbqo1S2n3OZcEKk02QOsyOQtp2FOpxwHLY6r5S6zDVEK0WSCBPmIsp
gV0BM3rofH/C2x4FXAXjJZlTTXMJxaQ1TcKtSFBhsnVVzHdK9uowFh1l/YRvZ1JRh2j3HKJdBbNT
sqOz7z6M1ZYP0w73IIdpQuplmo0NTPWV1RNkUbLrj5txXtUTP4KQAQbHZseZcM9EtRqMFGrtwCwU
2oybq+kxxs2AmXtQthO/wNSdZxeuadHyx2DynofauHmkrQtMPTy7x9r9UjPnbCUmXELiJIy012Un
YOoYuoCpMSHijBIWi7uEDyl8FczW1FfCzP33VkvtwS+mvvsE2RPu3hY6ljcjzH5Sn4KBOJtsxy+Q
zm9gLsz/KWJGl6ldx8Si7qcrkno/8Pr5xDBubv0CkA9/l0W8JLU5uzD9updx6yH54RRMN3Gop74z
99Evrw8zdtkfyEzuZ2uYWjbaoNVLZQaijxApgXlqsNeFcfHbYh66dffxc7l/cwpGMuYsUkpkL1Op
ecztkb610/ZgsmkDU/JKX68GNEB2X55/aQa3OX5XdZg6VM3DYXkqY6p647ExwgBf3Hn49Ifdf8f8
/tXlBeieLN8Gv9l+j2k74dXuOBV4607qcaAnwHd3Hj79nv9fr/D6F7FERJvBF7CeAAAAAElFTkSu
QmCC
"
preserveAspectRatio="none"
height="45"
width="45"
inkscape:export-xdpi="70"
inkscape:export-ydpi="70" />
<circle
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.57319087;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.18817204"
id="path4334"
cx="253.47896"
cy="477.70657"
r="3.3700202" />
<circle
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.51875943;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.18817204"
id="path4334-3"
cx="258.34692"
cy="478.92337"
r="3.0499961" />
<circle
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.54844934;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.18817204"
id="path4334-6"
cx="262.17798"
cy="485.00565"
r="3.2245548" />
<circle
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.55339766;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.18817204"
id="path4334-7"
cx="255.62852"
cy="488.08008"
r="3.2536478" />
<circle
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.51875943;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.18817204"
id="path4334-5"
cx="251.02547"
cy="484.19125"
r="3.0499961" />
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#marker10384)"
d="m 261.56118,476.53247 c 1.34014,-0.96382 3.71488,-3.83031 3.71488,-3.83031"
id="path4371"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#marker10460)"
d="m 267.66168,483.71899 c 1.63338,-0.23883 4.00281,-2.28882 4.00281,-2.28882"
id="path4371-6"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc"
inkscape:transform-center-x="-4.2001496"
inkscape:transform-center-y="-3.6794042" />
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#marker10542)"
d="m 256.9531,492.65511 c 0.48789,1.57699 1.91392,3.56083 1.91392,3.56083"
id="path4371-1"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#TriangleOutM)"
d="m 251.83784,473.07814 c -0.19007,-1.63975 -1.48016,-4.16735 -1.48016,-4.16735"
id="path4371-9"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#marker10552)"
d="m 247.59338,483.77171 c -1.5865,0.456 -4.97441,0.0977 -4.97441,0.0977"
id="path4371-2"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
</g>
</svg>
......@@ -822,6 +822,20 @@ TinaWebJS = function ( sigmacanvas ) {
}
});
$("#noverlapButton").click(function () {
if(! TW.partialGraph.isNoverlapRunning()) {
// show waiting cursor on page and button
theHtml.classList.add('waiting');
$("#noverlapButton").css('cursor', 'wait')
var listener = TW.partialGraph.startNoverlap();
listener.bind('stop', function(event) {
theHtml.classList.remove('waiting');
$("#noverlapButton").css('cursor', 'auto')
});
return;
}
});
$("#edges-switch").click(function () {
sigma_utils.toggleEdges()
......
......@@ -677,6 +677,19 @@ else {
// init FA2 for any future forceAtlas2 calls
TW.partialGraph.configForceAtlas2(TW.FA2Params)
// init noverlap for any future calls
TW.partialGraph.configNoverlap({
nodeMargin: .3,
scaleNodes: 1.2,
gridSize: 400,
speed: 5,
maxIterations: 10,
easing: 'quadraticOut', // animation transition function
duration: 1500 // animation duration
// NB animation happens *after* processing
});
// REFA new sigma.js
TW.partialGraph.camera.goTo({x:0, y:0, ratio:0.5, angle: 0})
......@@ -685,9 +698,11 @@ else {
// run fa2 if settings_explorerjs.fa2enabled == true
if (fa2enabled) {
TW.partialGraph.startForceAtlas2();
$.doTimeout(parseInt(fa2milliseconds) || 5000, function(){
setTimeout(function(){
TW.partialGraph.stopForceAtlas2();
});
},
parseInt(fa2milliseconds) || 5000
);
}
if( TW.categories.length==1 ) {
......
sigma.layout.noverlap
========================
Plugin developed by [Andrew Pitts](https://github.com/apitts) and published under the [MIT](LICENSE) license. Original algorithm by [Mathieu Jacomy](https://github.com/jacomyma) and ported to sigma.js with permission.
---
This plugin runs an algorithm which distributes nodes in the network, ensuring that they do not overlap and providing a margin where specified.
## Methods
**configure**
Changes the layout's configuration.
```js
var listener = s.configNoverlap(config);
```
**start**
Starts the layout. It is possible to pass a configuration if this is the first time you start the layout.
```js
s.startNoverlap();
```
**isRunning**
Returns whether the layout is running.
```js
s.isNoverlapRunning();
```
## Configuration
* **nodes**: *array*: the subset of nodes to apply the layout.
*Algorithm configuration*
* **nodeMargin**: *number* `5.0`: The additional minimum space to apply around each and every node.
* **scaleNodes**: *number* `1.2`: A multiplier to apply to nodes such that larger nodes will have more space around them if this multiplier is greater than zero.
* **gridSize**: *integer* `20`: The number of rows and columns to use when dividing the nodes up into cells which the algorithm is applied to. Use more rows and columns for larger graphs for a more efficient algorithm.
* **permittedExpansion** *number* `1.1`: At every step, this is the maximum ratio to apply to the bounding box, i.e. the maximum by which the network is permitted to expand.
* **rendererIndex** *integer* `0`: The index of the renderer to use to compute overlap and collisions of the nodes.
* **speed** *number* `2`: A larger value increases the speed with which the algorithm will convergence at the cost of precision.
* **maxIterations** *number* `500`: The maximum number of iterations to run the algorithm for before stopping it.
*Easing configuration*
* **easing** *string*: if specified, ease the transition between nodes positions if background is `true`. The duration is specified by the Sigma settings `animationsTime`. See [sigma.utils.easing](../../src/utils/sigma.utils.js#L723) for available values.
* **duration** *number*: duration of the transition for the easing method. Default value is Sigma setting `animationsTime`.
## Events
The plugin dispatches the following events:
- `start`: on layout start.
- `interpolate`: at the beginning of the layout animation if an *easing* function is specified and the layout is ran on background.
- `stop`: on layout stop, will be dispatched after `interpolate`.
Example:
```js
s = new sigma({
graph: g,
container: 'graph-container'
});
var config = {
nodeMargin: 3.0,
scaleNodes: 1.3
};
// Configure the algorithm
var listener = s.configNoverlap(config);
// Bind all events:
listener.bind('start stop interpolate', function(event) {
console.log(event.type);
});
// Start the algorithm:
s.startNoverlap();
```
\ No newline at end of file
;(function(undefined) {
'use strict';
if (typeof sigma === 'undefined')
throw new Error('sigma is not declared');
// Initialize package:
sigma.utils.pkg('sigma.layout.noverlap');
/**
* Noverlap Layout
* ===============================
*
* Author: @apitts / Andrew Pitts
* Algorithm: @jacomyma / Mathieu Jacomy (originally contributed to Gephi and ported to sigma.js under the MIT license by @andpitts with permission)
* Acknowledgement: @sheyman / Sébastien Heymann (some inspiration has been taken from other MIT licensed layout algorithms authored by @sheyman)
* Version: 0.1
*/
var settings = {
speed: 3,
scaleNodes: 1.2,
nodeMargin: 5.0,
gridSize: 20,
permittedExpansion: 1.1,
rendererIndex: 0,
maxIterations: 500
};
var _instance = {};
/**
* Event emitter Object
* ------------------
*/
var _eventEmitter = {};
/**
* Noverlap Object
* ------------------
*/
function Noverlap() {
var self = this;
this.init = function (sigInst, options) {
options = options || {};
// Properties
this.sigInst = sigInst;
this.config = sigma.utils.extend(options, settings);
this.easing = options.easing;
this.duration = options.duration;
if (options.nodes) {
this.nodes = options.nodes;
delete options.nodes;
}
if (!sigma.plugins || typeof sigma.plugins.animate === 'undefined') {
throw new Error('sigma.plugins.animate is not declared');
}
// State
this.running = false;
};
/**
* Single layout iteration.
*/
this.atomicGo = function () {
if (!this.running || this.iterCount < 1) return false;
var nodes = this.nodes || this.sigInst.graph.nodes(),
nodesCount = nodes.length,
i,
n,
n1,
n2,
xmin = Infinity,
xmax = -Infinity,
ymin = Infinity,
ymax = -Infinity,
xwidth,
yheight,
xcenter,
ycenter,
grid,
row,
col,
minXBox,
maxXBox,
minYBox,
maxYBox,
adjacentNodes,
subRow,
subCol,
nxmin,
nxmax,
nymin,
nymax;
this.iterCount--;
this.running = false;
for (i=0; i < nodesCount; i++) {
n = nodes[i];
n.dn.dx = 0;
n.dn.dy = 0;
//Find the min and max for both x and y across all nodes
xmin = Math.min(xmin, n.dn_x - (n.dn_size*self.config.scaleNodes + self.config.nodeMargin) );
xmax = Math.max(xmax, n.dn_x + (n.dn_size*self.config.scaleNodes + self.config.nodeMargin) );
ymin = Math.min(ymin, n.dn_y - (n.dn_size*self.config.scaleNodes + self.config.nodeMargin) );
ymax = Math.max(ymax, n.dn_y + (n.dn_size*self.config.scaleNodes + self.config.nodeMargin) );
}
xwidth = xmax - xmin;
yheight = ymax - ymin;
xcenter = (xmin + xmax) / 2;
ycenter = (ymin + ymax) / 2;
xmin = xcenter - self.config.permittedExpansion*xwidth / 2;
xmax = xcenter + self.config.permittedExpansion*xwidth / 2;
ymin = ycenter - self.config.permittedExpansion*yheight / 2;
ymax = ycenter + self.config.permittedExpansion*yheight / 2;
grid = {}; //An object of objects where grid[row][col] is an array of node ids representing nodes that fall in that grid. Nodes can fall in more than one grid
for(row = 0; row < self.config.gridSize; row++) {
grid[row] = {};
for(col = 0; col < self.config.gridSize; col++) {
grid[row][col] = [];
}
}
//Place nodes in grid
for (i=0; i < nodesCount; i++) {
n = nodes[i];
nxmin = n.dn_x - (n.dn_size*self.config.scaleNodes + self.config.nodeMargin);
nxmax = n.dn_x + (n.dn_size*self.config.scaleNodes + self.config.nodeMargin);
nymin = n.dn_y - (n.dn_size*self.config.scaleNodes + self.config.nodeMargin);
nymax = n.dn_y + (n.dn_size*self.config.scaleNodes + self.config.nodeMargin);
minXBox = Math.floor(self.config.gridSize* (nxmin - xmin) / (xmax - xmin) );
maxXBox = Math.floor(self.config.gridSize* (nxmax - xmin) / (xmax - xmin) );
minYBox = Math.floor(self.config.gridSize* (nymin - ymin) / (ymax - ymin) );
maxYBox = Math.floor(self.config.gridSize* (nymax - ymin) / (ymax - ymin) );
for(col = minXBox; col <= maxXBox; col++) {
for(row = minYBox; row <= maxYBox; row++) {
grid[row][col].push(n.id);
}
}
}
adjacentNodes = {}; //An object that stores the node ids of adjacent nodes (either in same grid box or adjacent grid box) for all nodes
for(row = 0; row < self.config.gridSize; row++) {
for(col = 0; col < self.config.gridSize; col++) {
grid[row][col].forEach(function(nodeId) {
if(!adjacentNodes[nodeId]) {
adjacentNodes[nodeId] = [];
}
for(subRow = Math.max(0, row - 1); subRow <= Math.min(row + 1, self.config.gridSize - 1); subRow++) {
for(subCol = Math.max(0, col - 1); subCol <= Math.min(col + 1, self.config.gridSize - 1); subCol++) {
grid[subRow][subCol].forEach(function(subNodeId) {
if(subNodeId !== nodeId && adjacentNodes[nodeId].indexOf(subNodeId) === -1) {
adjacentNodes[nodeId].push(subNodeId);
}
});
}
}
});
}
}
//If two nodes overlap then repulse them
for (i=0; i < nodesCount; i++) {
n1 = nodes[i];
adjacentNodes[n1.id].forEach(function(nodeId) {
var n2 = self.sigInst.graph.nodes(nodeId);
var xDist = n2.dn_x - n1.dn_x;
var yDist = n2.dn_y - n1.dn_y;
var dist = Math.sqrt(xDist*xDist + yDist*yDist);
var collision = (dist < ((n1.dn_size*self.config.scaleNodes + self.config.nodeMargin) + (n2.dn_size*self.config.scaleNodes + self.config.nodeMargin)));
if(collision) {
self.running = true;
if(dist > 0) {
n2.dn.dx += xDist / dist * (1 + n1.dn_size);
n2.dn.dy += yDist / dist * (1 + n1.dn_size);
} else {
n2.dn.dx += xwidth * 0.01 * (0.5 - Math.random());
n2.dn.dy += yheight * 0.01 * (0.5 - Math.random());
}
}
});
}
for (i=0; i < nodesCount; i++) {
n = nodes[i];
if(!n.fixed) {
n.dn_x = n.dn_x + n.dn.dx * 0.1 * self.config.speed;
n.dn_y = n.dn_y + n.dn.dy * 0.1 * self.config.speed;
}
}
if(this.running && this.iterCount < 1) {
this.running = false;
}
return this.running;
};
this.go = function () {
this.iterCount = this.config.maxIterations;
while (this.running) {
this.atomicGo();
};
this.stop();
};
this.start = function() {
if (this.running) return;
var nodes = this.sigInst.graph.nodes();
var prefix = this.sigInst.renderers[self.config.rendererIndex].options.prefix;
this.running = true;
// Init nodes
for (var i = 0; i < nodes.length; i++) {
nodes[i].dn_x = nodes[i][prefix + 'x'];
nodes[i].dn_y = nodes[i][prefix + 'y'];
nodes[i].dn_size = nodes[i][prefix + 'size'];
nodes[i].dn = {
dx: 0,
dy: 0
};
}
_eventEmitter[self.sigInst.id].dispatchEvent('start');
this.go();
};
this.stop = function() {
var nodes = this.sigInst.graph.nodes();
this.running = false;
if (this.easing) {
_eventEmitter[self.sigInst.id].dispatchEvent('interpolate');
sigma.plugins.animate(
self.sigInst,
{
x: 'dn_x',
y: 'dn_y'
},
{
easing: self.easing,
onComplete: function() {
self.sigInst.refresh();
for (var i = 0; i < nodes.length; i++) {
nodes[i].dn = null;
nodes[i].dn_x = null;
nodes[i].dn_y = null;
}
_eventEmitter[self.sigInst.id].dispatchEvent('stop');
},
duration: self.duration
}
);
}
else {
// Apply changes
for (var i = 0; i < nodes.length; i++) {
nodes[i].x = nodes[i].dn_x;
nodes[i].y = nodes[i].dn_y;
}
this.sigInst.refresh();
for (var i = 0; i < nodes.length; i++) {
nodes[i].dn = null;
nodes[i].dn_x = null;
nodes[i].dn_y = null;
}
_eventEmitter[self.sigInst.id].dispatchEvent('stop');
}
};
this.kill = function() {
this.sigInst = null;
this.config = null;
this.easing = null;
};
};
/**
* Interface
* ----------
*/
/**
* Configure the layout algorithm.
* Recognized options:
* **********************
* Here is the exhaustive list of every accepted parameter in the settings
* object:
*
* {?number} speed A larger value increases the convergence speed at the cost of precision
* {?number} scaleNodes The ratio to scale nodes by - a larger ratio will lead to more space around larger nodes
* {?number} nodeMargin A fixed margin to apply around nodes regardless of size
* {?number} maxIterations The maximum number of iterations to perform before the layout completes.
* {?integer} gridSize The number of rows and columns to use when partioning nodes into a grid for efficient computation
* {?number} permittedExpansion A permitted expansion factor to the overall size of the network applied at each iteration
* {?integer} rendererIndex The index of the renderer to use for node co-ordinates. Defaults to zero.
* {?(function|string)} easing Either the name of an easing in the sigma.utils.easings package or a function. If not specified, the
* quadraticInOut easing from this package will be used instead.
* {?number} duration The duration of the animation. If not specified, the "animationsTime" setting value of the sigma instance will be used instead.
*
*
* @param {object} config The optional configuration object.
*
* @return {sigma.classes.dispatcher} Returns an event emitter.
*/
sigma.prototype.configNoverlap = function(config) {
var sigInst = this;
if (!config) throw new Error('Missing argument: "config"');
// Create instance if undefined
if (!_instance[sigInst.id]) {
_instance[sigInst.id] = new Noverlap();
_eventEmitter[sigInst.id] = {};
sigma.classes.dispatcher.extend(_eventEmitter[sigInst.id]);
// Binding on kill to clear the references
sigInst.bind('kill', function() {
_instance[sigInst.id].kill();
_instance[sigInst.id] = null;
_eventEmitter[sigInst.id] = null;
});
}
_instance[sigInst.id].init(sigInst, config);
return _eventEmitter[sigInst.id];
};
/**
* Start the layout algorithm. It will use the existing configuration if no
* new configuration is passed.
* Recognized options:
* **********************
* Here is the exhaustive list of every accepted parameter in the settings
* object
*
* {?number} speed A larger value increases the convergence speed at the cost of precision
* {?number} scaleNodes The ratio to scale nodes by - a larger ratio will lead to more space around larger nodes
* {?number} nodeMargin A fixed margin to apply around nodes regardless of size
* {?number} maxIterations The maximum number of iterations to perform before the layout completes.
* {?integer} gridSize The number of rows and columns to use when partioning nodes into a grid for efficient computation
* {?number} permittedExpansion A permitted expansion factor to the overall size of the network applied at each iteration
* {?integer} rendererIndex The index of the renderer to use for node co-ordinates. Defaults to zero.
* {?(function|string)} easing Either the name of an easing in the sigma.utils.easings package or a function. If not specified, the
* quadraticInOut easing from this package will be used instead.
* {?number} duration The duration of the animation. If not specified, the "animationsTime" setting value of the sigma instance will be used instead.
*
*
*
* @param {object} config The optional configuration object.
*
* @return {sigma.classes.dispatcher} Returns an event emitter.
*/
sigma.prototype.startNoverlap = function(config) {
var sigInst = this;
if (config) {
this.configNoverlap(sigInst, config);
}
_instance[sigInst.id].start();
return _eventEmitter[sigInst.id];
};
/**
* Returns true if the layout has started and is not completed.
*
* @return {boolean}
*/
sigma.prototype.isNoverlapRunning = function() {
var sigInst = this;
return !!_instance[sigInst.id] && _instance[sigInst.id].running;
};
}).call(this);
\ No newline at end of file
sigma.plugins.animate
=====================
Plugin developed by [Alexis Jacomy](https://github.com/jacomyal).
---
This plugin provides a method to animate a sigma instance by interpolating some node properties. Check the `sigma.plugins.animate` function doc or the `examples/animate.html` code sample to know more.
Interpolate coordinates as follows:
```js
sigma.plugins.animate(
s,
{
x: 'target_x',
y: 'target_y',
}
);
```
Interpolate colors and sizes as follows:
```js
sigma.plugins.animate(
s,
{
size: 'target_size',
color: 'target_color'
}
);
```
Animate a subset of nodes as follows:
```js
sigma.plugins.animate(
s,
{
x: 'to_x',
y: 'to_y',
size: 'to_size',
color: 'to_color'
},
{
nodes: ['n0', 'n1', 'n2']
}
);
```
Example using all options:
```js
sigma.plugins.animate(
s,
{
x: 'to_x',
y: 'to_y',
size: 'to_size',
color: 'to_color'
},
{
nodes: ['n0', 'n1', 'n2'],
easing: 'cubicInOut',
duration: 300,
onComplete: function() {
// do stuff here after animation is complete
}
}
);
```
/**
* This plugin provides a method to animate a sigma instance by interpolating
* some node properties. Check the sigma.plugins.animate function doc or the
* examples/animate.html code sample to know more.
*/
(function() {
'use strict';
if (typeof sigma === 'undefined')
throw 'sigma is not declared';
sigma.utils.pkg('sigma.plugins');
var _id = 0,
_cache = {};
// TOOLING FUNCTIONS:
// ******************
function parseColor(val) {
if (_cache[val])
return _cache[val];
var result = [0, 0, 0];
if (val.match(/^#/)) {
val = (val || '').replace(/^#/, '');
result = (val.length === 3) ?
[
parseInt(val.charAt(0) + val.charAt(0), 16),
parseInt(val.charAt(1) + val.charAt(1), 16),
parseInt(val.charAt(2) + val.charAt(2), 16)
] :
[
parseInt(val.charAt(0) + val.charAt(1), 16),
parseInt(val.charAt(2) + val.charAt(3), 16),
parseInt(val.charAt(4) + val.charAt(5), 16)
];
} else if (val.match(/^ *rgba? *\(/)) {
val = val.match(
/^ *rgba? *\( *([0-9]*) *, *([0-9]*) *, *([0-9]*) *(,.*)?\) *$/
);
result = [
+val[1],
+val[2],
+val[3]
];
}
_cache[val] = {
r: result[0],
g: result[1],
b: result[2]
};
return _cache[val];
}
function interpolateColors(c1, c2, p) {
c1 = parseColor(c1);
c2 = parseColor(c2);
var c = {
r: c1.r * (1 - p) + c2.r * p,
g: c1.g * (1 - p) + c2.g * p,
b: c1.b * (1 - p) + c2.b * p
};
return 'rgb(' + [c.r | 0, c.g | 0, c.b | 0].join(',') + ')';
}
/**
* This function will animate some specified node properties. It will
* basically call requestAnimationFrame, interpolate the values and call the
* refresh method during a specified duration.
*
* Recognized parameters:
* **********************
* Here is the exhaustive list of every accepted parameters in the settings
* object:
*
* {?array} nodes An array of node objects or node ids. If
* not specified, all nodes of the graph
* will be animated.
* {?(function|string)} easing Either the name of an easing in the
* sigma.utils.easings package or a
* function. If not specified, the
* quadraticInOut easing from this package
* will be used instead.
* {?number} duration The duration of the animation. If not
* specified, the "animationsTime" setting
* value of the sigma instance will be used
* instead.
* {?function} onComplete Eventually a function to call when the
* animation is ended.
*
* @param {sigma} s The related sigma instance.
* @param {object} animate An hash with the keys being the node properties
* to interpolate, and the values being the related
* target values.
* @param {?object} options Eventually an object with options.
*/
sigma.plugins.animate = function(s, animate, options) {
var o = options || {},
id = ++_id,
duration = o.duration || s.settings('animationsTime'),
easing = typeof o.easing === 'string' ?
sigma.utils.easings[o.easing] :
typeof o.easing === 'function' ?
o.easing :
sigma.utils.easings.quadraticInOut,
start = sigma.utils.dateNow(),
nodes,
startPositions;
if (o.nodes && o.nodes.length) {
if (typeof o.nodes[0] === 'object')
nodes = o.nodes;
else
nodes = s.graph.nodes(o.nodes); // argument is an array of IDs
}
else
nodes = s.graph.nodes();
// Store initial positions:
startPositions = nodes.reduce(function(res, node) {
var k;
res[node.id] = {};
for (k in animate)
if (k in node)
res[node.id][k] = node[k];
return res;
}, {});
s.animations = s.animations || Object.create({});
sigma.plugins.kill(s);
// Do not refresh edgequadtree during drag:
var k,
c;
for (k in s.cameras) {
c = s.cameras[k];
c.edgequadtree._enabled = false;
}
function step() {
var p = (sigma.utils.dateNow() - start) / duration;
if (p >= 1) {
nodes.forEach(function(node) {
for (var k in animate)
if (k in animate)
node[k] = node[animate[k]];
});
// Allow to refresh edgequadtree:
var k,
c;
for (k in s.cameras) {
c = s.cameras[k];
c.edgequadtree._enabled = true;
}
s.refresh();
if (typeof o.onComplete === 'function')
o.onComplete();
} else {
p = easing(p);
nodes.forEach(function(node) {
for (var k in animate)
if (k in animate) {
if (k.match(/color$/))
node[k] = interpolateColors(
startPositions[node.id][k],
node[animate[k]],
p
);
else
node[k] =
node[animate[k]] * p +
startPositions[node.id][k] * (1 - p);
}
});
s.refresh();
s.animations[id] = requestAnimationFrame(step);
}
}
step();
};
sigma.plugins.kill = function(s) {
for (var k in (s.animations || {}))
cancelAnimationFrame(s.animations[k]);
// Allow to refresh edgequadtree:
var k,
c;
for (k in s.cameras) {
c = s.cameras[k];
c.edgequadtree._enabled = true;
}
};
}).call(window);
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment