123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449 |
- /* eslint-disable no-shadow */
- var test = require('tap').test,
- createGraph = require('ngraph.graph'),
- createLayout = require('..');
- test('it exposes simulator', function(t) {
- t.ok(typeof createLayout.simulator === 'function', 'Simulator is exposed');
- t.end();
- });
- test('it returns spring', function(t) {
- var g = createGraph();
- var layout = createLayout(g);
- var link = g.addLink(1, 2);
- var springForLink = layout.getSpring(link);
- var springForLinkId = layout.getSpring(link.id);
- var springForFromTo = layout.getSpring(1, 2);
- t.ok(springForLink, 'spring is here');
- t.ok(springForLinkId === springForLink, 'Spring is the same');
- t.ok(springForFromTo === springForLink, 'Spring is the same');
- t.end();
- });
- test('it returns same position', function(t) {
- var g = createGraph();
- var layout = createLayout(g);
- g.addLink(1, 2);
- var firstNodePos = layout.getNodePosition(1);
- layout.step();
- t.ok(firstNodePos === layout.getNodePosition(1), 'Position is the same object');
- layout.step();
- t.ok(firstNodePos === layout.getNodePosition(1), 'Position is the same object after multiple steps');
- t.end();
- });
- test('it returns body', function(t) {
- var g = createGraph();
- var layout = createLayout(g);
- g.addLink(1, 2);
- t.ok(layout.getBody(1), 'node 1 has body');
- t.ok(layout.getBody(2), 'node 2 has body');
- t.notOk(layout.getBody(4), 'there is no node 4');
- var body = layout.getBody(1);
- t.ok(body.pos.x && body.pos.y, 'Body has a position');
- t.ok(body.mass, 'Body has a mass');
- t.end();
- });
- test('it can set node mass', function(t) {
- var g = createGraph();
- g.addNode('anvaka');
- var layout = createLayout(g, {
- nodeMass: function (nodeId) {
- t.equal(nodeId, 'anvaka', 'correct node is called');
- return 84; // my mass in kilograms :P
- }
- });
- var body = layout.getBody('anvaka');
- t.equal(body.mass, 84, 'Mass is okay');
- t.end();
- });
- test('does not tolerate bad input', function (t) {
- t.throws(missingGraph);
- t.throws(invalidNodeId);
- t.end();
- function missingGraph() {
- // graph is missing:
- createLayout();
- }
- function invalidNodeId() {
- var graph = createGraph();
- var layout = createLayout(graph);
- // we don't have nodes in the graph. This should throw:
- layout.getNodePosition(1);
- }
- });
- test('it fires stable on empty graph', function(t) {
- var graph = createGraph();
- var layout = createLayout(graph);
- layout.on('stable', endTest);
- layout.step();
- function endTest() {
- t.end();
- }
- });
- test('can add bodies which are standard prototype names', function (t) {
- var graph = createGraph();
- graph.addLink('constructor', 'watch');
- var layout = createLayout(graph);
- layout.step();
- graph.forEachNode(function (node) {
- var pos = layout.getNodePosition(node.id);
- t.ok(pos && typeof pos.x === 'number' &&
- typeof pos.y === 'number', 'Position is defined');
- });
- t.end();
- });
- test('it can step when no links present', function (t) {
- var graph = createGraph();
- graph.addNode('constructor');
- graph.addNode('watch');
- var layout = createLayout(graph);
- layout.step();
- graph.forEachNode(function (node) {
- var pos = layout.getNodePosition(node.id);
- t.ok(pos && typeof pos.x === 'number' &&
- typeof pos.y === 'number', 'Position is defined');
- });
- t.end();
- });
- test('layout initializes nodes positions', function (t) {
- var graph = createGraph();
- graph.addLink(1, 2);
- var layout = createLayout(graph);
- // perform one iteration of layout:
- layout.step();
- graph.forEachNode(function (node) {
- var pos = layout.getNodePosition(node.id);
- t.ok(pos && typeof pos.x === 'number' &&
- typeof pos.y === 'number', 'Position is defined');
- });
- graph.forEachLink(function (link) {
- var linkPos = layout.getLinkPosition(link.id);
- t.ok(linkPos && linkPos.from && linkPos.to, 'Link position is defined');
- var fromPos = layout.getNodePosition(link.fromId);
- t.ok(linkPos.from === fromPos, '"From" should be identical to getNodePosition');
- var toPos = layout.getNodePosition(link.toId);
- t.ok(linkPos.to === toPos, '"To" should be identical to getNodePosition');
- });
- t.end();
- });
- test('Layout can set node position', function (t) {
- var graph = createGraph();
- graph.addLink(1, 2);
- var layout = createLayout(graph);
- layout.pinNode(graph.getNode(1), true);
- layout.setNodePosition(1, 42, 42);
- // perform one iteration of layout:
- layout.step();
- // and make sure node 1 was not moved:
- var actualPosition = layout.getNodePosition(1);
- t.equal(actualPosition.x, 42, 'X has not changed');
- t.equal(actualPosition.y, 42, 'Y has not changed');
- t.end();
- });
- test('Layout updates bounding box when it sets node position', function (t) {
- var graph = createGraph();
- graph.addLink(1, 2);
- var layout = createLayout(graph);
- layout.setNodePosition(1, 42, 42);
- layout.setNodePosition(2, 40, 40);
- var rect = layout.getGraphRect();
- t.ok(rect.max_x <= 42); t.ok(rect.max_y <= 42);
- t.ok(rect.min_x >= 40); t.ok(rect.min_y >= 40);
- t.end();
- });
- test('layout initializes links', function (t) {
- var graph = createGraph();
- var node1 = graph.addNode(1); node1.position = {x : -1000, y: 0};
- var node2 = graph.addNode(2); node2.position = {x : 1000, y: 0};
- graph.addLink(1, 2);
- var layout = createLayout(graph);
- // perform one iteration of layout:
- layout.step();
- // since both nodes are connected by spring and distance is too large between
- // them, they should start attracting each other
- var pos1 = layout.getNodePosition(1);
- var pos2 = layout.getNodePosition(2);
- t.ok(pos1.x > -1000, 'Node 1 moves towards node 2');
- t.ok(pos2.x < 1000, 'Node 1 moves towards node 2');
- t.end();
- });
- test('layout respects proposed original position', function (t) {
- var graph = createGraph();
- var node = graph.addNode(1);
- var initialPosition = {x: 100, y: 100};
- node.position = copy(initialPosition);
- var layout = createLayout(graph);
- layout.step();
- t.same(layout.getNodePosition(node.id), initialPosition, 'original position preserved');
- t.end();
- });
- test('layout has defined graph rectangle', function (t) {
- t.test('empty graph', function (t) {
- var graph = createGraph();
- var layout = createLayout(graph);
- var rect = layout.getGraphRect();
- var expectedProperties = ['min_x', 'min_y', 'max_x', 'max_y'];
- t.ok(rect && expectedProperties.reduce(hasProperties, true), 'Values are present before step()');
- layout.step();
- t.ok(rect && expectedProperties.reduce(hasProperties, true), 'Values are present after step()');
- t.end();
- function hasProperties(result, key) {
- return result && typeof rect[key] === 'number';
- }
- });
- t.test('two nodes', function (t) {
- var graph = createGraph();
- graph.addLink(1, 2);
- var layout = createLayout(graph);
- layout.step();
- var rect = layout.getGraphRect();
- t.ok(!rectangleIsEmpty(rect), 'Graph rectangle is not empty');
- t.end();
- });
- t.end();
- });
- test('it does not move pinned nodes', function (t) {
- t.test('respects original data.isPinned attribute', function (t) {
- var graph = createGraph();
- var testNode = graph.addNode(1, { isPinned: true });
- var layout = createLayout(graph);
- t.ok(layout.isNodePinned(testNode), 'Node is pinned');
- t.end();
- });
- t.test('respects node.isPinned attribute', function (t) {
- var graph = createGraph();
- var testNode = graph.addNode(1);
- // this was possible in vivagraph. Port it over to ngraph:
- testNode.isPinned = true;
- var layout = createLayout(graph);
- t.ok(layout.isNodePinned(testNode), 'Node is pinned');
- t.end();
- });
- t.test('can pin nodes after graph is initialized', function (t) {
- var graph = createGraph();
- graph.addLink(1, 2);
- var layout = createLayout(graph);
- layout.pinNode(graph.getNode(1), true);
- layout.step();
- var pos1 = copy(layout.getNodePosition(1));
- var pos2 = copy(layout.getNodePosition(2));
- // make one more step and make sure node 1 did not move:
- layout.step();
- t.ok(!positionChanged(pos1, layout.getNodePosition(1)), 'Node 1 was not moved');
- t.ok(positionChanged(pos2, layout.getNodePosition(2)), 'Node 2 has moved');
- t.end();
- });
- t.end();
- });
- test('it listens to graph events', function (t) {
- // we first initialize with empty graph:
- var graph = createGraph();
- var layout = createLayout(graph);
- // and only then add nodes:
- graph.addLink(1, 2);
- // make two iterations
- layout.step();
- var pos1 = copy(layout.getNodePosition(1));
- var pos2 = copy(layout.getNodePosition(2));
- layout.step();
- t.ok(positionChanged(pos1, layout.getNodePosition(1)), 'Node 1 has moved');
- t.ok(positionChanged(pos2, layout.getNodePosition(2)), 'Node 2 has moved');
- t.end();
- });
- test('can stop listen to events', function (t) {
- // we first initialize with empty graph:
- var graph = createGraph();
- var layout = createLayout(graph);
- layout.dispose();
- graph.addLink(1, 2);
- layout.step();
- t.ok(layout.simulator.bodies.length === 0, 'No bodies in the simulator');
- t.end();
- });
- test('physics simulator', function (t) {
- t.test('has default simulator', function (t) {
- var graph = createGraph();
- var layout = createLayout(graph);
- t.ok(layout.simulator, 'physics simulator is present');
- t.end();
- });
- t.test('can override default settings', function (t) {
- var graph = createGraph();
- var layout = createLayout(graph, {
- theta: 1.5
- });
- t.equal(layout.simulator.theta(), 1.5, 'Simulator settings are overridden');
- t.end();
- });
- t.end();
- });
- test('it removes removed nodes', function (t) {
- var graph = createGraph();
- var layout = createLayout(graph);
- graph.addLink(1, 2);
- layout.step();
- graph.clear();
- // since we removed everything from graph rect should be empty:
- var rect = layout.getGraphRect();
- t.ok(rectangleIsEmpty(rect), 'Graph rect is empty');
- t.end();
- });
- test('it can iterate over bodies', function(t) {
- var graph = createGraph();
- var layout = createLayout(graph);
- graph.addLink(1, 2);
- var calledCount = 0;
- layout.forEachBody(function(body, bodyId) {
- t.ok(body.pos, bodyId + ' has position');
- t.ok(graph.getNode(bodyId), bodyId + ' matches a graph node');
- calledCount += 1;
- });
- t.equal(calledCount, 2, 'Both bodies are visited');
- t.end();
- });
- test('it handles large graphs', function (t) {
- var graph = createGraph();
- var layout = createLayout(graph);
- var count = 60000;
- var i = count;
- while (i--) {
- graph.addNode(i);
- }
- // link each node to 2 other random nodes
- i = count;
- while (i--) {
- graph.addLink(i, Math.ceil(Math.random() * count));
- graph.addLink(i, Math.ceil(Math.random() * count));
- }
- layout.step();
- t.ok(layout.simulator.bodies.length !== 0, 'Bodies in the simulator');
- t.end();
- });
- test('it can create high dimensional layout', function(t) {
- var graph = createGraph();
- graph.addLink(1, 2);
- var layout = createLayout(graph, {dimensions: 6});
- layout.step();
- var pos = layout.getNodePosition(1);
- t.ok(pos.x !== undefined, 'Position has x');
- t.ok(pos.y !== undefined, 'Position has y');
- t.ok(pos.z !== undefined, 'Position has z');
- t.ok(pos.c4 !== undefined, 'Position has c4');
- t.ok(pos.c5 !== undefined, 'Position has c5');
- t.ok(pos.c6 !== undefined, 'Position has c6');
- t.end();
- });
- function positionChanged(pos1, pos2) {
- return (pos1.x !== pos2.x) || (pos1.y !== pos2.y);
- }
- function copy(obj) {
- return JSON.parse(JSON.stringify(obj));
- }
- function rectangleIsEmpty(rect) {
- return rect.min_x === 0 && rect.min_y === 0 && rect.max_x === 0 && rect.max_y === 0;
- }
|