Skip to content

Commit

Permalink
feat(physics): add wind (#334)
Browse files Browse the repository at this point in the history
“Wind”: a physics force that pushes all node in a given direction.
  • Loading branch information
ylarom authored and Thomaash committed Jan 19, 2020
1 parent f56518d commit f198749
Show file tree
Hide file tree
Showing 8 changed files with 144 additions and 4 deletions.
3 changes: 3 additions & 0 deletions docs-kr/network/physics.html
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,9 @@ <h3>옵션</h3>
<tr parent="stabilization" class="hidden"><td class="indent">stabilization.fit</td> <td>Boolean</td> <td><code>true</code></td> <td>안정화가 완료되면 모든 Node에 맞게 확대/축소할지 여부로 전환됩니다.</td></tr>
<tr><td>timestep</td> <td>Number</td> <td><code>0.5</code></td> <td>physics 시뮬레이션은 별개입니다. 즉, 시간이 지나면서 힘을 계산하고, Node를 이동하고, 다른 단계를 밟습니다. 이 수를 늘리면 단계가 많아져서 Network가 불안정해질 수 있습니다. Network에서 jittery가 많이 이동하면 이 값을 조금 줄일 수 있습니다.</td></tr>
<tr><td>adaptiveTimestep</td> <td>Boolean</td> <td><code>true</code></td> <td>만약 활성화되면 <b>(안정화가 활성화되고, 안정화가 진행되는 동안!)</b> 안정화 시간을 크게 줄일 수 있습니다. 위에 구성된 timestep은 최소 timestep으로 간주됩니다. <a href="layout.html#layout" target="_blank">이 기능은 개선된 Layout 알고리즘을 사용하여 더욱 개선할 수 있습니다.</a>.</td></tr>
<tr class='toggle collapsible' onclick="toggleTable('optionTable','wind', this);"><td><span parent="wind" class="right-caret"></span> wind</td> <td>Object</td><td><code>Object</code></td> <td>TODO</td></tr>
<tr parent="wind" class="hidden"><td class="indent">wind.x</td> <td>Number</td> <td><code>0</code></td> <td>TODO</td></tr>
<tr parent="wind" class="hidden"><td class="indent">wind.y</td> <td>Number</td> <td><code>0</code></td> <td>TODO</td></tr>
</table>

<div class="flagbar">
Expand Down
8 changes: 7 additions & 1 deletion docs/network/physics.html
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,8 @@ <h3>Options</h3>
fit: true
},
timestep: 0.5,
adaptiveTimestep: true
adaptiveTimestep: true,
wind: { x: 0, y: 0 }
}
}

Expand Down Expand Up @@ -197,6 +198,11 @@ <h3>Options</h3>
<tr parent="stabilization" class="hidden"><td class="indent">stabilization.fit</td> <td>Boolean</td> <td><code>true</code></td> <td>Toggle whether or not you want the view to zoom to fit all nodes when the stabilization is finished.</td></tr>
<tr><td>timestep</td> <td>Number</td> <td><code>0.5</code></td> <td>The physics simulation is discrete. This means we take a step in time, calculate the forces, move the nodes and take another step. If you increase this number the steps will be too large and the network can get unstable. If you see a lot of jittery movement in the network, you may want to reduce this value a little.</td></tr>
<tr><td>adaptiveTimestep</td> <td>Boolean</td> <td><code>true</code></td> <td>If this is enabled, the timestep will intelligently be adapted <b>(only during the stabilization stage if stabilization is enabled!)</b> to greatly decrease stabilization times. The timestep configured above is taken as the minimum timestep. <a href="layout.html#layout" target="_blank">This can be further improved by using the improvedLayout algorithm</a>.</td></tr>

<tr class='toggle collapsible' onclick="toggleTable('optionTable','wind', this);"><td><span parent="wind" class="right-caret"></span> wind</td> <td>Object</td><td><code>Object</code></td> <td>A force that pushes all non-fixed nodes in the given direction.<br/>Requires all nodes are connected to nodes which are `fixed`, otherwise non-attached nodes will keep moving indefinitely.</td></tr>
<tr parent="wind" class="hidden"><td class="indent">wind.x</td> <td>Number</td> <td><code>0</code></td> <td>The amount of force to be applied pushing non-fixed nodes to the right (positive value) or to the left (negative value).</td></tr>
<tr parent="wind" class="hidden"><td class="indent">wind.y</td> <td>Number</td> <td><code>0</code></td> <td>The amount of force to be applied pushing non-fixed nodes downwards (positive value) or upwards (negative value).</td></tr>

</table>

<div class="flagbar">
Expand Down
3 changes: 2 additions & 1 deletion examples/network/physics/physicsConfiguration.html
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@

var options = {
physics: {
stabilization: false
stabilization: false,
wind: { x: 0, y: 0 }
},
configure: {
filter:function (option, path) {
Expand Down
55 changes: 55 additions & 0 deletions examples/network/physics/wind.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<!doctype html>
<html>
<head>
<title>Vis Network | Physics | Wind</title>

<script type="text/javascript" src="../../../standalone/umd/vis-network.min.js"></script>

<style type="text/css">
#mynetwork {
width: 600px;
height: 400px;
border: 1px solid lightgray;
}
</style>
</head>
<body>

<p>
Node 1 is fixed, while a "wind" force pushes other nodes to the right.
</p>

<div id="mynetwork"></div>

<script type="text/javascript">
// create an array with nodes
var nodes = new vis.DataSet([
{id: 1, label: 'Node 1', fixed: true },
{id: 2, label: 'Node 2'},
{id: 3, label: 'Node 3'},
{id: 4, label: 'Node 4'},
{id: 5, label: 'Node 5'}
]);

// create an array with edges
var edges = new vis.DataSet([
{from: 1, to: 3},
{from: 1, to: 2},
{from: 2, to: 4},
{from: 2, to: 5},
{from: 3, to: 3}
]);

// create a network
var container = document.getElementById('mynetwork');
var data = {
nodes: nodes,
edges: edges
};
var options = { physics: { enabled: true, wind: { x: 1, y: 0 } } };
var network = new vis.Network(container, data, options);
</script>


</body>
</html>
19 changes: 18 additions & 1 deletion lib/network/modules/PhysicsEngine.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ class PhysicsEngine {
fit: true
},
timestep: 0.5,
adaptiveTimestep: true
adaptiveTimestep: true,
wind: { x: 0, y: 0 }
};
util.extend(this.options, this.defaultOptions);
this.timestep = 0.5;
Expand Down Expand Up @@ -161,6 +162,16 @@ class PhysicsEngine {
this.stopSimulation();
}

const wind = this.options.wind;
if (wind) {
if (typeof wind.x !== 'number' || Number.isNaN(wind.x)) {
wind.x = 0;
}
if (typeof wind.y !== 'number' || Number.isNaN(wind.y)) {
wind.y = 0;
}
}

// set the timestep
this.timestep = this.options.timestep;
}
Expand Down Expand Up @@ -561,6 +572,12 @@ class PhysicsEngine {
_performStep(nodeId) {
let node = this.body.nodes[nodeId];
let force = this.physicsBody.forces[nodeId];

if (this.options.wind) {
force.x += this.options.wind.x;
force.y += this.options.wind.y;
}

let velocity = this.physicsBody.velocities[nodeId];

// store the state so we can revert
Expand Down
9 changes: 9 additions & 0 deletions lib/network/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,11 @@ let allOptions = {
},
timestep: { number },
adaptiveTimestep: { boolean: bool },
wind: {
x: { number },
y: { number },
__type__: { object }
},
__type__: { object, boolean: bool }
},

Expand Down Expand Up @@ -670,6 +675,10 @@ let configureOptions = {
minVelocity: [0.1, 0.01, 0.5, 0.01],
solver: ['barnesHut', 'forceAtlas2Based', 'repulsion', 'hierarchicalRepulsion'],
timestep: [0.5, 0.01, 1, 0.01],
wind: {
x: [0, -10, 10, 0.1],
y: [0, -10, 10, 0.1]
},
//adaptiveTimestep: true
}
};
Expand Down
2 changes: 1 addition & 1 deletion lib/shared/Configurator.js
Original file line number Diff line number Diff line change
Expand Up @@ -614,7 +614,7 @@ class Configurator {
// collapse the physics options that are not enabled
let draw = true;
if (path.indexOf('physics') !== -1) {
if (this.moduleOptions.physics.solver !== subObj) {
if (this.moduleOptions.physics.solver !== subObj && subObj !== 'wind') {
draw = false;
}
}
Expand Down
49 changes: 49 additions & 0 deletions test/physics/wind.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { expect } from "chai";
import Network from "../../lib/network/Network";
import canvasMockify from "../canvas-mock";

describe("wind", function(): void {
beforeEach(function() {
this.clearJSDOM = canvasMockify("<div id='mynetwork'></div>");
this.container = document.getElementById("mynetwork");
});

afterEach(function() {
this.clearJSDOM();

delete this.clearJSDOM;
delete this.container;
});

it("All nodes are on the correct side of the fixed node", async function(): Promise<
void
> {
const network = new Network(
this.container,
{
nodes: [
{ id: 1, fixed: true, x: 0, y: 0 },
{ id: 2 },
{ id: 3 },
{ id: 4 }
],
edges: [
{ from: 1, to: 2 },
{ from: 2, to: 3 },
{ from: 3, to: 4 }
]
},
{ physics: { wind: { x: 10, y: 0 }, stabilization: { iterations: 10 } } }
);

// Wait for the physics to stabilize.
await new Promise((resolve): void => {
network.on("stabilized", resolve);
});

const positions = network.getPositions();
expect(positions[2].x).to.be.greaterThan(0);
expect(positions[3].x).to.be.greaterThan(0);
expect(positions[4].x).to.be.greaterThan(0);
});
});

0 comments on commit f198749

Please sign in to comment.