vis.js is a dynamic, browser-based visualization library
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1358 lines
44 KiB

  1. /**
  2. *
  3. * Useful during debugging
  4. * =======================
  5. *
  6. * console.log(JSON.stringify(output, null, 2));
  7. *
  8. * for (let i in network.body.edges) {
  9. * let edge = network.body.edges[i];
  10. * console.log("" + i + ": from: " + edge.fromId + ", to: " + edge.toId);
  11. * }
  12. */
  13. var fs = require('fs');
  14. var assert = require('assert');
  15. var DataSet = require('../lib/DataSet');
  16. var Network = require('../lib/network/Network');
  17. var stdout = require('test-console').stdout;
  18. var Validator = require("./../lib/shared/Validator").default;
  19. var canvasMockify = require('./canvas-mock');
  20. var {allOptions, configureOptions} = require('./../lib/network/options.js');
  21. //var {printStyle} = require('./../lib/shared/Validator');
  22. /**
  23. * Merge all options of object b into object b
  24. * @param {Object} a
  25. * @param {Object} b
  26. * @return {Object} a
  27. *
  28. * Adapted merge() in dotparser.js
  29. */
  30. function merge (a, b) {
  31. if (!a) {
  32. a = {};
  33. }
  34. if (b) {
  35. for (var name in b) {
  36. if (b.hasOwnProperty(name)) {
  37. if (typeof b[name] === 'object') {
  38. a[name] = merge(a[name], b[name]);
  39. } else {
  40. a[name] = b[name];
  41. }
  42. }
  43. }
  44. }
  45. return a;
  46. }
  47. /**
  48. * Load legacy-style (i.e. not module) javascript files into the given context.
  49. */
  50. function include(list, context) {
  51. if (!(list instanceof Array)) {
  52. list = [list];
  53. }
  54. for (var n in list) {
  55. var path = list[n];
  56. var arr = [fs.readFileSync(path) + ''];
  57. eval.apply(context, arr);
  58. }
  59. }
  60. /**
  61. * Defined network consists of two sub-networks:
  62. *
  63. * - 1-2-3-4
  64. * - 11-12-13-14
  65. *
  66. * For reference, this is the sample network of issue #1218
  67. */
  68. function createSampleNetwork(options) {
  69. var NumInitialNodes = 8;
  70. var NumInitialEdges = 6;
  71. var nodes = new DataSet([
  72. {id: 1, label: '1'},
  73. {id: 2, label: '2'},
  74. {id: 3, label: '3'},
  75. {id: 4, label: '4'},
  76. {id: 11, label: '11'},
  77. {id: 12, label: '12'},
  78. {id: 13, label: '13'},
  79. {id: 14, label: '14'},
  80. ]);
  81. var edges = new DataSet([
  82. {from: 1, to: 2},
  83. {from: 2, to: 3},
  84. {from: 3, to: 4},
  85. {from: 11, to: 12},
  86. {from: 12, to: 13},
  87. {from: 13, to: 14},
  88. ]);
  89. // create a network
  90. var container = document.getElementById('mynetwork');
  91. var data = {
  92. nodes: nodes,
  93. edges: edges
  94. };
  95. var defaultOptions = {
  96. layout: {
  97. randomSeed: 8
  98. },
  99. edges: {
  100. smooth: {
  101. type: 'continuous' // avoid dynamic here, it adds extra hidden nodes
  102. }
  103. }
  104. };
  105. options = merge(defaultOptions, options);
  106. var network = new Network(container, data, options);
  107. assertNumNodes(network, NumInitialNodes);
  108. assertNumEdges(network, NumInitialEdges);
  109. return [network, data, NumInitialNodes, NumInitialEdges];
  110. };
  111. /**
  112. * Create a cluster for the dynamic data change cases.
  113. *
  114. * Works on the network created by createSampleNetwork().
  115. *
  116. * This is actually a pathological case; there are two separate sub-networks and
  117. * a cluster is made of two nodes, each from one of the sub-networks.
  118. */
  119. function createCluster(network) {
  120. var clusterOptionsByData = {
  121. joinCondition: function(node) {
  122. if (node.id == 1 || node.id == 11) return true;
  123. return false;
  124. },
  125. clusterNodeProperties: {id:"c1", label:'c1'}
  126. }
  127. network.cluster(clusterOptionsByData);
  128. }
  129. /**
  130. * Display node/edge state, useful during debugging
  131. */
  132. function log(network) {
  133. console.log(Object.keys(network.body.nodes));
  134. console.log(network.body.nodeIndices);
  135. console.log(Object.keys(network.body.edges));
  136. console.log(network.body.edgeIndices);
  137. };
  138. /**
  139. * Note that only the node and edges counts are asserted.
  140. * This might be done more thoroughly by explicitly checking the id's
  141. */
  142. function assertNumNodes(network, expectedPresent, expectedVisible) {
  143. if (expectedVisible === undefined) expectedVisible = expectedPresent;
  144. assert.equal(Object.keys(network.body.nodes).length, expectedPresent, "Total number of nodes does not match");
  145. assert.equal(network.body.nodeIndices.length, expectedVisible, "Number of visible nodes does not match");
  146. };
  147. /**
  148. * Comment at assertNumNodes() also applies.
  149. */
  150. function assertNumEdges(network, expectedPresent, expectedVisible) {
  151. if (expectedVisible === undefined) expectedVisible = expectedPresent;
  152. assert.equal(Object.keys(network.body.edges).length, expectedPresent, "Total number of edges does not match");
  153. assert.equal(network.body.edgeIndices.length, expectedVisible, "Number of visible edges does not match");
  154. };
  155. /**
  156. * Check if the font options haven't changed.
  157. *
  158. * This is to guard against future code changes; a lot of the code deals with particular properties of
  159. * the font options.
  160. * If any assertion fails here, all code in Network handling fonts should be checked.
  161. */
  162. function checkFontProperties(fontItem, checkStrict = true) {
  163. var knownProperties = [
  164. 'color',
  165. 'size',
  166. 'face',
  167. 'background',
  168. 'strokeWidth',
  169. 'strokeColor',
  170. 'align',
  171. 'multi',
  172. 'vadjust',
  173. 'bold',
  174. 'boldital',
  175. 'ital',
  176. 'mono',
  177. ];
  178. // All properties in fontItem should be known
  179. for (var prop in fontItem) {
  180. if (prop === '__type__') continue; // Skip special field in options definition
  181. if (!fontItem.hasOwnProperty(prop)) continue;
  182. assert(knownProperties.indexOf(prop) !== -1, "Unknown font option '" + prop + "'");
  183. }
  184. if (!checkStrict) return;
  185. // All known properties should be present
  186. var keys = Object.keys(fontItem);
  187. for (var n in knownProperties) {
  188. var prop = knownProperties[n];
  189. assert(keys.indexOf(prop) !== -1, "Missing known font option '" + prop + "'");
  190. }
  191. }
  192. describe('Network', function () {
  193. before(function() {
  194. this.jsdom_global = canvasMockify("<div id='mynetwork'></div>");
  195. this.container = document.getElementById('mynetwork');
  196. });
  197. after(function() {
  198. this.jsdom_global();
  199. });
  200. /////////////////////////////////////////////////////
  201. // Local helper methods for Edge and Node testing
  202. /////////////////////////////////////////////////////
  203. /**
  204. * Simplify network creation for local tests
  205. */
  206. function createNetwork(options) {
  207. var [network, data, numNodes, numEdges] = createSampleNetwork(options);
  208. return network;
  209. }
  210. function firstNode(network) {
  211. for (var id in network.body.nodes) {
  212. return network.body.nodes[id];
  213. }
  214. return undefined;
  215. }
  216. function firstEdge(network) {
  217. for (var id in network.body.edges) {
  218. return network.body.edges[id];
  219. }
  220. return undefined;
  221. }
  222. function checkChooserValues(item, chooser, labelChooser) {
  223. if (chooser === 'function') {
  224. assert.equal(typeof item.chooser, 'function');
  225. } else {
  226. assert.equal(item.chooser, chooser);
  227. }
  228. if (labelChooser === 'function') {
  229. assert.equal(typeof item.labelModule.fontOptions.chooser, 'function');
  230. } else {
  231. assert.equal(item.labelModule.fontOptions.chooser, labelChooser);
  232. }
  233. }
  234. /////////////////////////////////////////////////////
  235. // End Local helper methods for Edge and Node testing
  236. /////////////////////////////////////////////////////
  237. /**
  238. * Helper function for clustering
  239. */
  240. function clusterTo(network, clusterId, nodeList, allowSingle) {
  241. var options = {
  242. joinCondition: function(node) {
  243. return nodeList.indexOf(node.id) !== -1;
  244. },
  245. clusterNodeProperties: {
  246. id: clusterId,
  247. label: clusterId
  248. }
  249. }
  250. if (allowSingle === true) {
  251. options.clusterNodeProperties.allowSingleNodeCluster = true
  252. }
  253. network.cluster(options);
  254. }
  255. /**
  256. * At time of writing, this test detected 22 out of 33 'illegal' loops.
  257. * The real deterrent is eslint rule 'guard-for-in`.
  258. */
  259. it('can deal with added fields in Array.prototype', function (done) {
  260. var canvas = window.document.createElement('canvas');
  261. Array.prototype.foo = 1; // Just add anything to the prototype
  262. Object.prototype.bar = 2; // Let's screw up hashes as well
  263. // The network should just run without throwing errors
  264. try {
  265. var [network, data, numNodes, numEdges] = createSampleNetwork({});
  266. // Do some stuff to trigger more errors
  267. clusterTo(network, 'c1', [1,2,3]);
  268. data.nodes.remove(1);
  269. network.openCluster('c1');
  270. clusterTo(network, 'c1', [4], true);
  271. clusterTo(network, 'c2', ['c1'], true);
  272. clusterTo(network, 'c3', ['c2'], true);
  273. data.nodes.remove(4);
  274. } catch(e) {
  275. delete Array.prototype.foo; // Remove it again so as not to confuse other tests.
  276. delete Object.prototype.bar;
  277. assert(false, "Got exception:\n" + e.stack);
  278. }
  279. delete Array.prototype.foo; // Remove it again so as not to confuse other tests.
  280. delete Object.prototype.bar;
  281. done();
  282. });
  283. /**
  284. * This is a fix on one issue (#3543), but in fact **all* options for all API calls should
  285. * remain unchanged.
  286. * TODO: extend test for all API calls with options, see #3548
  287. */
  288. it('does not change the options object passed to fit()', function() {
  289. var [network, data, numNodes, numEdges] = createSampleNetwork({});
  290. var options = {};
  291. network.fit(options);
  292. // options should still be empty
  293. for (var prop in options) {
  294. assert(!options.hasOwnProperty(prop), 'No properties should be present in options, detected property: ' + prop);
  295. }
  296. });
  297. it('does not crash when dataChanged is triggered when setting options on first initialization ', function() {
  298. // The init should succeed without an error thrown.
  299. var options = {
  300. nodes: {
  301. physics: false // any value here triggered the error
  302. }
  303. };
  304. createSampleNetwork(options);
  305. // Do the other values as well that can cause this./
  306. // 'any values' applies here as well, expecting no throw
  307. options = {edges: {physics: false}};
  308. createSampleNetwork(options);
  309. options = {nodes: {hidden: false}};
  310. createSampleNetwork(options);
  311. options = {edges: {hidden: false}};
  312. createSampleNetwork(options);
  313. });
  314. it('can deal with null data', function() {
  315. // While we're at it, try out other silly values as well
  316. // All the following are wrong, but none should lead to a crash
  317. var awkwardData = [
  318. null,
  319. [1,2,3],
  320. 42,
  321. 'meow'
  322. ];
  323. var container = document.getElementById('mynetwork');
  324. for (var n = 0; n < awkwardData.length; ++n) {
  325. var network = new Network(container, awkwardData[n], {}); // Should not throw
  326. }
  327. });
  328. describe('Node', function () {
  329. it('has known font options', function () {
  330. var network = createNetwork({});
  331. checkFontProperties(network.nodesHandler.defaultOptions.font);
  332. checkFontProperties(allOptions.nodes.font);
  333. checkFontProperties(configureOptions.nodes.font, false);
  334. });
  335. /**
  336. * NOTE: choosify tests of Node and Edge are parallel
  337. * TODO: consolidate this is necessary
  338. */
  339. it('properly handles choosify input', function () {
  340. // check defaults
  341. var options = {};
  342. var network = createNetwork(options);
  343. checkChooserValues(firstNode(network), true, true);
  344. // There's no point in checking invalid values here; these are detected by the options parser
  345. // and subsequently handled as missing input, thus assigned defaults
  346. // check various combinations of valid input
  347. options = {nodes: {chosen: false}};
  348. network = createNetwork(options);
  349. checkChooserValues(firstNode(network), false, false);
  350. options = {nodes: {chosen: { node:true, label:false}}};
  351. network = createNetwork(options);
  352. checkChooserValues(firstNode(network), true, false);
  353. options = {nodes: {chosen: {
  354. node:true,
  355. label: function(value, id, selected, hovering) {}
  356. }}};
  357. network = createNetwork(options);
  358. checkChooserValues(firstNode(network), true, 'function');
  359. options = {nodes: {chosen: {
  360. node: function(value, id, selected, hovering) {},
  361. label:false,
  362. }}};
  363. network = createNetwork(options);
  364. checkChooserValues(firstNode(network), 'function', false);
  365. });
  366. }); // Node
  367. describe('Edge', function () {
  368. it('has known font options', function () {
  369. var network = createNetwork({});
  370. checkFontProperties(network.edgesHandler.defaultOptions.font);
  371. checkFontProperties(allOptions.edges.font);
  372. checkFontProperties(configureOptions.edges.font, false);
  373. });
  374. /**
  375. * NOTE: choosify tests of Node and Edge are parallel
  376. * TODO: consolidate this is necessary
  377. */
  378. it('properly handles choosify input', function () {
  379. // check defaults
  380. var options = {};
  381. var network = createNetwork(options);
  382. checkChooserValues(firstEdge(network), true, true);
  383. // There's no point in checking invalid values here; these are detected by the options parser
  384. // and subsequently handled as missing input, thus assigned defaults
  385. // check various combinations of valid input
  386. options = {edges: {chosen: false}};
  387. network = createNetwork(options);
  388. checkChooserValues(firstEdge(network), false, false);
  389. options = {edges: {chosen: { edge:true, label:false}}};
  390. network = createNetwork(options);
  391. checkChooserValues(firstEdge(network), true, false);
  392. options = {edges: {chosen: {
  393. edge:true,
  394. label: function(value, id, selected, hovering) {}
  395. }}};
  396. network = createNetwork(options);
  397. checkChooserValues(firstEdge(network), true, 'function');
  398. options = {edges: {chosen: {
  399. edge: function(value, id, selected, hovering) {},
  400. label:false,
  401. }}};
  402. network = createNetwork(options);
  403. checkChooserValues(firstEdge(network), 'function', false);
  404. });
  405. /**
  406. * Support routine for next unit test
  407. */
  408. function createDataforColorChange() {
  409. var nodes = new DataSet([
  410. {id: 1, label: 'Node 1' }, // group:'Group1'},
  411. {id: 2, label: 'Node 2', group:'Group2'},
  412. {id: 3, label: 'Node 3'},
  413. ]);
  414. // create an array with edges
  415. var edges = new DataSet([
  416. {id: 1, from: 1, to: 2},
  417. {id: 2, from: 1, to: 3, color: { inherit: 'to'}},
  418. {id: 3, from: 3, to: 3, color: { color: '#00FF00'}},
  419. {id: 4, from: 2, to: 3, color: { inherit: 'from'}},
  420. ]);
  421. var data = {
  422. nodes: nodes,
  423. edges: edges
  424. };
  425. return data;
  426. }
  427. /**
  428. * Unit test for fix of #3350
  429. *
  430. * The issue is that changing color options is not registered in the nodes.
  431. * We test the updates the color options in the general edges options here.
  432. */
  433. it('sets inherit color option for edges on call to Network.setOptions()', function () {
  434. var container = document.getElementById('mynetwork');
  435. var data = createDataforColorChange();
  436. var options = {
  437. "edges" : { "color" : { "inherit" : "to" } },
  438. };
  439. // Test passing options on init.
  440. var network = new Network(container, data, options);
  441. var edges = network.body.edges;
  442. assert.equal(edges[1].options.color.inherit, 'to'); // new default
  443. assert.equal(edges[2].options.color.inherit, 'to'); // set in edge
  444. assert.equal(edges[3].options.color.inherit, false); // has explicit color
  445. assert.equal(edges[4].options.color.inherit, 'from'); // set in edge
  446. // Sanity check: colors should still be defaults
  447. assert.equal(edges[1].options.color.color, network.edgesHandler.options.color.color);
  448. // Override the color value - inherit returns to default
  449. network.setOptions({ edges:{color: {}}});
  450. assert.equal(edges[1].options.color.inherit, 'from'); // default
  451. assert.equal(edges[2].options.color.inherit, 'to'); // set in edge
  452. assert.equal(edges[3].options.color.inherit, false); // has explicit color
  453. assert.equal(edges[4].options.color.inherit, 'from'); // set in edge
  454. // Check no options
  455. network = new Network(container, data, {});
  456. edges = network.body.edges;
  457. assert.equal(edges[1].options.color.inherit, 'from'); // default
  458. assert.equal(edges[2].options.color.inherit, 'to'); // set in edge
  459. assert.equal(edges[3].options.color.inherit, false); // has explicit color
  460. assert.equal(edges[4].options.color.inherit, 'from'); // set in edge
  461. // Set new value
  462. network.setOptions(options);
  463. assert.equal(edges[1].options.color.inherit, 'to');
  464. assert.equal(edges[2].options.color.inherit, 'to'); // set in edge
  465. assert.equal(edges[3].options.color.inherit, false); // has explicit color
  466. assert.equal(edges[4].options.color.inherit, 'from'); // set in edge
  467. /*
  468. // Useful for debugging
  469. console.log('===================================');
  470. console.log(edges[1].options.color);
  471. console.log(edges[1].options.color.__proto__);
  472. console.log(edges[1].options);
  473. console.log(edges[1].options.__proto__);
  474. console.log(edges[1].edgeOptions);
  475. */
  476. });
  477. it('sets inherit color option for specific edge', function () {
  478. var container = document.getElementById('mynetwork');
  479. var data = createDataforColorChange();
  480. // Check no options
  481. var network = new Network(container, data, {});
  482. var edges = network.body.edges;
  483. assert.equal(edges[1].options.color.inherit, 'from'); // default
  484. assert.equal(edges[2].options.color.inherit, 'to'); // set in edge
  485. assert.equal(edges[3].options.color.inherit, false); // has explicit color
  486. assert.equal(edges[4].options.color.inherit, 'from'); // set in edge
  487. // Set new value
  488. data.edges.update({id: 1, color: { inherit: 'to'}});
  489. assert.equal(edges[1].options.color.inherit, 'to'); // Only this changed
  490. assert.equal(edges[2].options.color.inherit, 'to');
  491. assert.equal(edges[3].options.color.inherit, false); // has explicit color
  492. assert.equal(edges[4].options.color.inherit, 'from');
  493. });
  494. /**
  495. * Perhaps TODO: add unit test for passing string value for color option
  496. */
  497. it('sets color value for edges on call to Network.setOptions()', function () {
  498. var container = document.getElementById('mynetwork');
  499. var data = createDataforColorChange();
  500. var defaultColor = '#848484'; // From defaults
  501. var color = '#FF0000';
  502. var options = {
  503. "edges" : { "color" : { "color" : color } },
  504. };
  505. // Test passing options on init.
  506. var network = new Network(container, data, options);
  507. var edges = network.body.edges;
  508. assert.equal(edges[1].options.color.color, color);
  509. assert.equal(edges[1].options.color.inherit, false); // Explicit color, so no inherit
  510. assert.equal(edges[2].options.color.color, color);
  511. assert.equal(edges[2].options.color.inherit, 'to'); // Local value overrides! (bug according to docs)
  512. assert.notEqual(edges[3].options.color.color, color); // Has own value
  513. assert.equal(edges[3].options.color.inherit, false); // Explicit color, so no inherit
  514. assert.equal(edges[4].options.color.color, color);
  515. // Override the color value - all should return to default
  516. network.setOptions({ edges:{color: {}}});
  517. assert.equal(edges[1].options.color.color, defaultColor);
  518. assert.equal(edges[1].options.color.inherit, 'from');
  519. assert.equal(edges[2].options.color.color, defaultColor);
  520. assert.notEqual(edges[3].options.color.color, color); // Has own value
  521. assert.equal(edges[4].options.color.color, defaultColor);
  522. // Check no options
  523. network = new Network(container, data, {});
  524. edges = network.body.edges;
  525. // At this point, color has not changed yet
  526. assert.equal(edges[1].options.color.color, defaultColor);
  527. assert.equal(edges[1].options.color.highlight, defaultColor);
  528. assert.equal(edges[1].options.color.inherit, 'from');
  529. assert.notEqual(edges[3].options.color.color, color); // Has own value
  530. // Set new Value
  531. network.setOptions(options);
  532. assert.equal(edges[1].options.color.color, color);
  533. assert.equal(edges[1].options.color.highlight, defaultColor); // Should not be changed
  534. assert.equal(edges[1].options.color.inherit, false); // Explicit color, so no inherit
  535. assert.equal(edges[2].options.color.color, color);
  536. assert.notEqual(edges[3].options.color.color, color); // Has own value
  537. assert.equal(edges[4].options.color.color, color);
  538. });
  539. /**
  540. * Unit test for fix of #3500
  541. * Checking to make sure edges that become unconnected due to node removal get reconnected
  542. */
  543. it('has reconnected edges', function () {
  544. var node1 = {id:1, label:"test1"};
  545. var node2 = {id:2, label:"test2"};
  546. var nodes = new DataSet([node1, node2]);
  547. var edge = {id:1, from: 1, to:2};
  548. var edges = new DataSet([edge]);
  549. var data = {
  550. nodes: nodes,
  551. edges: edges
  552. };
  553. var container = document.getElementById('mynetwork');
  554. var network = new Network(container, data);
  555. //remove node causing edge to become disconnected
  556. nodes.remove(node2.id);
  557. var foundEdge = network.body.edges[edge.id];
  558. assert.ok(foundEdge===undefined, "edge is still in state cache");
  559. //add node back reconnecting edge
  560. nodes.add(node2);
  561. foundEdge = network.body.edges[edge.id];
  562. assert.ok(foundEdge!==undefined, "edge is missing from state cache");
  563. });
  564. }); // Edge
  565. describe('Clustering', function () {
  566. it('properly handles options allowSingleNodeCluster', function() {
  567. var [network, data, numNodes, numEdges] = createSampleNetwork();
  568. data.edges.update({from: 1, to: 11,});
  569. numEdges += 1;
  570. assertNumNodes(network, numNodes);
  571. assertNumEdges(network, numEdges);
  572. clusterTo(network, 'c1', [3,4]);
  573. numNodes += 1; // A clustering node is now hiding two nodes
  574. numEdges += 1; // One clustering edges now hiding two edges
  575. assertNumNodes(network, numNodes, numNodes - 2);
  576. assertNumEdges(network, numEdges, numEdges - 2);
  577. // Cluster of single node should fail, because by default allowSingleNodeCluster == false
  578. clusterTo(network, 'c2', [14]);
  579. assertNumNodes(network, numNodes, numNodes - 2); // Nothing changed
  580. assertNumEdges(network, numEdges, numEdges - 2);
  581. assert(network.body.nodes['c2'] === undefined); // Cluster not created
  582. // Redo with allowSingleNodeCluster == true
  583. clusterTo(network, 'c2', [14], true);
  584. numNodes += 1;
  585. numEdges += 1;
  586. assertNumNodes(network, numNodes, numNodes - 3);
  587. assertNumEdges(network, numEdges, numEdges - 3);
  588. assert(network.body.nodes['c2'] !== undefined); // Cluster created
  589. // allowSingleNodeCluster: true with two nodes
  590. // removing one clustered node should retain cluster
  591. clusterTo(network, 'c3', [11, 12], true);
  592. numNodes += 1; // Added cluster
  593. numEdges += 2;
  594. assertNumNodes(network, numNodes, 6);
  595. assertNumEdges(network, numEdges, 5);
  596. data.nodes.remove(12);
  597. assert(network.body.nodes['c3'] !== undefined); // Cluster should still be present
  598. numNodes -= 1; // removed node
  599. numEdges -= 3; // cluster edge C3-13 should be removed
  600. assertNumNodes(network, numNodes, 6);
  601. assertNumEdges(network, numEdges, 4);
  602. });
  603. it('removes nested clusters with allowSingleNodeCluster === true', function() {
  604. var [network, data, numNodes, numEdges] = createSampleNetwork();
  605. // Create a chain of nested clusters, three deep
  606. clusterTo(network, 'c1', [4], true);
  607. clusterTo(network, 'c2', ['c1'], true);
  608. clusterTo(network, 'c3', ['c2'], true);
  609. numNodes += 3;
  610. numEdges += 3;
  611. assertNumNodes(network, numNodes, numNodes - 3);
  612. assertNumEdges(network, numEdges, numEdges - 3);
  613. assert(network.body.nodes['c1'] !== undefined);
  614. assert(network.body.nodes['c2'] !== undefined);
  615. assert(network.body.nodes['c3'] !== undefined);
  616. // The whole chain should be removed when the bottom-most node is deleted
  617. data.nodes.remove(4);
  618. numNodes -= 4;
  619. numEdges -= 4;
  620. assertNumNodes(network, numNodes);
  621. assertNumEdges(network, numEdges);
  622. assert(network.body.nodes['c1'] === undefined);
  623. assert(network.body.nodes['c2'] === undefined);
  624. assert(network.body.nodes['c3'] === undefined);
  625. });
  626. /**
  627. * Check on fix for #1218
  628. */
  629. it('connects a new edge to a clustering node instead of the clustered node', function () {
  630. var [network, data, numNodes, numEdges] = createSampleNetwork();
  631. createCluster(network);
  632. numNodes += 1; // A clustering node is now hiding two nodes
  633. numEdges += 2; // Two clustering edges now hide two edges
  634. assertNumNodes(network, numNodes, numNodes - 2);
  635. assertNumEdges(network, numEdges, numEdges - 2);
  636. //console.log("Creating node 21")
  637. data.nodes.update([{id: 21, label: '21'}]);
  638. numNodes += 1; // New unconnected node added
  639. assertNumNodes(network, numNodes, numNodes - 2);
  640. assertNumEdges(network, numEdges, numEdges - 2); // edges unchanged
  641. //console.log("Creating edge 21 pointing to 1");
  642. // '1' is part of the cluster so should
  643. // connect to cluster instead
  644. data.edges.update([{from: 21, to: 1}]);
  645. numEdges += 2; // A new clustering edge is hiding a new edge
  646. assertNumNodes(network, numNodes, numNodes - 2); // nodes unchanged
  647. assertNumEdges(network, numEdges, numEdges - 3);
  648. });
  649. /**
  650. * Check on fix for #1315
  651. */
  652. it('can uncluster a clustered node when a node is removed that has an edge to that cluster', function () {
  653. // NOTE: this block is same as previous test
  654. var [network, data, numNodes, numEdges] = createSampleNetwork();
  655. createCluster(network);
  656. numNodes += 1; // A clustering node is now hiding two nodes
  657. numEdges += 2; // Two clustering edges now hide two edges
  658. assertNumNodes(network, numNodes, numNodes - 2);
  659. assertNumEdges(network, numEdges, numEdges - 2);
  660. // End block same as previous test
  661. //console.log("removing 12");
  662. data.nodes.remove(12);
  663. // NOTE:
  664. // At this particular point, there are still the two edges for node 12 in the edges DataSet.
  665. // If you want to do the delete correctly, these should also be deleted explictly from
  666. // the edges DataSet. In the Network instance, however, this.body.nodes and this.body.edges
  667. // should be correct, with the edges of 12 all cleared out.
  668. // 12 was connected to 11, which is clustered
  669. numNodes -= 1; // 12 removed, one less node
  670. numEdges -= 3; // clustering edge c1-12 and 2 edges of 12 gone
  671. assertNumNodes(network, numNodes, numNodes - 2);
  672. assertNumEdges(network, numEdges, numEdges - 1);
  673. //console.log("Unclustering c1");
  674. network.openCluster("c1");
  675. numNodes -= 1; // cluster node removed, one less node
  676. numEdges -= 1; // clustering edge gone, regular edge visible
  677. assertNumNodes(network, numNodes, numNodes); // all are visible again
  678. assertNumEdges(network, numEdges, numEdges); // all are visible again
  679. });
  680. /**
  681. * Check on fix for #1291
  682. */
  683. it('can remove a node inside a cluster and then open that cluster', function () {
  684. var [network, data, numNodes, numEdges] = createSampleNetwork();
  685. var clusterOptionsByData = {
  686. joinCondition: function(node) {
  687. if (node.id == 1 || node.id == 2 || node.id == 3) return true;
  688. return false;
  689. },
  690. clusterNodeProperties: {id:"c1", label:'c1'}
  691. }
  692. network.cluster(clusterOptionsByData);
  693. numNodes += 1; // new cluster node
  694. numEdges += 1; // 1 cluster edge expected
  695. assertNumNodes(network, numNodes, numNodes - 3); // 3 clustered nodes
  696. assertNumEdges(network, numEdges, numEdges - 3); // 3 edges hidden
  697. //console.log("removing node 2, which is inside the cluster");
  698. data.nodes.remove(2);
  699. numNodes -= 1; // clustered node removed
  700. numEdges -= 2; // edges removed hidden in cluster
  701. assertNumNodes(network, numNodes, numNodes - 2); // view doesn't change
  702. assertNumEdges(network, numEdges, numEdges - 1); // view doesn't change
  703. //console.log("Unclustering c1");
  704. network.openCluster("c1")
  705. numNodes -= 1; // cluster node gone
  706. numEdges -= 1; // cluster edge gone
  707. assertNumNodes(network, numNodes, numNodes); // all visible
  708. assertNumEdges(network, numEdges, numEdges); // all visible
  709. //log(network);
  710. });
  711. /**
  712. * Helper function for setting up a graph for testing clusterByEdgeCount()
  713. */
  714. function createOutlierGraph() {
  715. // create an array with nodes
  716. var nodes = new DataSet([
  717. {id: 1, label: '1', group:'Group1'},
  718. {id: 2, label: '2', group:'Group2'},
  719. {id: 3, label: '3', group:'Group3'},
  720. {id: 4, label: '4', group:'Group4'},
  721. {id: 5, label: '5', group:'Group4'}
  722. ]);
  723. // create an array with edges
  724. var edges = new DataSet([
  725. {from: 1, to: 3},
  726. {from: 1, to: 2},
  727. {from: 2, to: 4},
  728. {from: 2, to: 5}
  729. ]);
  730. // create a network
  731. var container = document.getElementById('mynetwork');
  732. var data = {
  733. nodes: nodes,
  734. edges: edges
  735. };
  736. var options = {
  737. "groups" : {
  738. "Group1" : { level:1 },
  739. "Group2" : { level:2 },
  740. "Group3" : { level:3 },
  741. "Group4" : { level:4 }
  742. }
  743. };
  744. var network = new Network (container, data, options);
  745. return network;
  746. }
  747. /**
  748. * Check on fix for #3367
  749. */
  750. it('correctly handles edge cases of clusterByEdgeCount()', function () {
  751. /**
  752. * Collect clustered id's
  753. *
  754. * All node id's in clustering nodes are collected into an array;
  755. * The results for all clusters are returned as an array.
  756. *
  757. * Ordering of output depends on the order in which they are defined
  758. * within nodes.clustering; strictly, speaking, the array and its items
  759. * are collections, so order should not matter.
  760. */
  761. var collectClusters = function(network) {
  762. var clusters = [];
  763. for(var n in network.body.nodes) {
  764. var node = network.body.nodes[n];
  765. if (node.containedNodes === undefined) continue; // clusters only
  766. // Collect id's of nodes in the cluster
  767. var nodes = [];
  768. for(var m in node.containedNodes) {
  769. nodes.push(m);
  770. }
  771. clusters.push(nodes);
  772. }
  773. return clusters;
  774. }
  775. /**
  776. * Compare cluster data
  777. *
  778. * params are arrays of arrays of id's, e.g:
  779. *
  780. * [[1,3],[2,4]]
  781. *
  782. * Item arrays are the id's of nodes in a given cluster
  783. *
  784. * This comparison depends on the ordering; better
  785. * would be to treat the items and values as collections.
  786. */
  787. var compareClusterInfo = function(recieved, expected) {
  788. if (recieved.length !== expected.length) return false;
  789. for (var n = 0; n < recieved.length; ++n) {
  790. var itema = recieved[n];
  791. var itemb = expected[n];
  792. if (itema.length !== itemb.length) return false;
  793. for (var m = 0; m < itema.length; ++m) {
  794. if (itema[m] != itemb[m]) return false; // != because values can be string or number
  795. }
  796. }
  797. return true;
  798. }
  799. var assertJoinCondition = function(joinCondition, expected) {
  800. var network = createOutlierGraph();
  801. network.clusterOutliers({joinCondition: joinCondition});
  802. var recieved = collectClusters(network);
  803. //console.log(recieved);
  804. assert(compareClusterInfo(recieved, expected),
  805. 'recieved:' + JSON.stringify(recieved) + '; '
  806. + 'expected: ' + JSON.stringify(expected));
  807. };
  808. // Should cluster 3,4,5:
  809. var joinAll_ = function(n) { return true ; }
  810. // Should cluster none:
  811. var joinNone_ = function(n) { return false ; }
  812. // Should cluster 4 & 5:
  813. var joinLevel_ = function(n) { return n.level > 3 ; }
  814. assertJoinCondition(undefined , [[1,3],[2,4,5]]);
  815. assertJoinCondition(null , [[1,3],[2,4,5]]);
  816. assertJoinCondition(joinNone_ , []);
  817. assertJoinCondition(joinLevel_ , [[2,4,5]]);
  818. });
  819. ///////////////////////////////////////////////////////////////
  820. // Automatic opening of clusters due to dynamic data change
  821. ///////////////////////////////////////////////////////////////
  822. /**
  823. * Helper function, created nested clusters, three deep
  824. */
  825. function createNetwork1() {
  826. var [network, data, numNodes, numEdges] = createSampleNetwork();
  827. clusterTo(network, 'c1', [3,4]);
  828. numNodes += 1; // new cluster node
  829. numEdges += 1; // 1 cluster edge expected
  830. assertNumNodes(network, numNodes, numNodes - 2); // 2 clustered nodes
  831. assertNumEdges(network, numEdges, numEdges - 2); // 2 edges hidden
  832. clusterTo(network, 'c2', [2,'c1']);
  833. numNodes += 1; // new cluster node
  834. numEdges += 1; // 2 cluster edges expected
  835. assertNumNodes(network, numNodes, numNodes - 4); // 4 clustered nodes, including c1
  836. assertNumEdges(network, numEdges, numEdges - 4); // 4 edges hidden, including edge for c1
  837. clusterTo(network, 'c3', [1,'c2']);
  838. // Attempt at visualization: parentheses belong to the cluster one level above
  839. // c3
  840. // ( -c2 )
  841. // ( -c1 )
  842. // 14-13-12-11 1 -2 (-3-4)
  843. numNodes += 1; // new cluster node
  844. numEdges += 0; // No new cluster edge expected
  845. assertNumNodes(network, numNodes, numNodes - 6); // 6 clustered nodes, including c1 and c2
  846. assertNumEdges(network, numEdges, numEdges - 5); // 5 edges hidden, including edges for c1 and c2
  847. return [network, data, numNodes, numEdges];
  848. }
  849. it('opens clusters automatically when nodes deleted', function () {
  850. var [network, data, numNodes, numEdges] = createSampleNetwork();
  851. // Simple case: cluster of two nodes, delete one node
  852. clusterTo(network, 'c1', [3,4]);
  853. numNodes += 1; // new cluster node
  854. numEdges += 1; // 1 cluster edge expected
  855. assertNumNodes(network, numNodes, numNodes - 2); // 2 clustered nodes
  856. assertNumEdges(network, numEdges, numEdges - 2); // 2 edges hidden
  857. data.nodes.remove(4);
  858. numNodes -= 2; // deleting clustered node also removes cluster node
  859. numEdges -= 2; // cluster edge should also be removed
  860. assertNumNodes(network, numNodes, numNodes);
  861. assertNumEdges(network, numEdges, numEdges);
  862. // Extended case: nested nodes, three deep
  863. [network, data, numNodes, numEdges] = createNetwork1();
  864. data.nodes.remove(4);
  865. // c3
  866. // ( -c2 )
  867. // 14-13-12-11 1 (-2 -3)
  868. numNodes -= 2; // node removed, c1 also gone
  869. numEdges -= 2;
  870. assertNumNodes(network, numNodes, numNodes - 4);
  871. assertNumEdges(network, numEdges, numEdges - 3);
  872. data.nodes.remove(1);
  873. // c2
  874. // 14-13-12-11 (2 -3)
  875. numNodes -= 2; // node removed, c3 also gone
  876. numEdges -= 2;
  877. assertNumNodes(network, numNodes, numNodes - 2);
  878. assertNumEdges(network, numEdges, numEdges - 1);
  879. data.nodes.remove(2);
  880. // 14-13-12-11 3
  881. numNodes -= 2; // node removed, c2 also gone
  882. numEdges -= 1;
  883. assertNumNodes(network, numNodes); // All visible again
  884. assertNumEdges(network, numEdges);
  885. // Same as previous step, but remove all the given nodes in one go
  886. // The result should be the same.
  887. [network, data, numNodes, numEdges] = createNetwork1(); // nested nodes, three deep
  888. data.nodes.remove([1,2,4]);
  889. // 14-13-12-11 3
  890. assertNumNodes(network, 5);
  891. assertNumEdges(network, 3);
  892. });
  893. ///////////////////////////////////////////////////////////////
  894. // Opening of clusters at various clustering depths
  895. ///////////////////////////////////////////////////////////////
  896. /**
  897. * Check correct opening of a single cluster.
  898. * This is the 'simple' case.
  899. */
  900. it('properly opens 1-level clusters', function () {
  901. var [network, data, numNodes, numEdges] = createSampleNetwork();
  902. // Pedantic: make a cluster of everything
  903. clusterTo(network, 'c1', [1,2,3,4,11, 12, 13, 14]);
  904. // c1(14-13-12-11 1-2-3-4)
  905. numNodes += 1;
  906. assertNumNodes(network, numNodes, 1); // Just the clustering node visible
  907. assertNumEdges(network, numEdges, 0); // No extra edges!
  908. network.clustering.openCluster('c1', {});
  909. numNodes -= 1;
  910. assertNumNodes(network, numNodes, numNodes); // Expecting same as original
  911. assertNumEdges(network, numEdges, numEdges);
  912. // One external connection
  913. [network, data, numNodes, numEdges] = createSampleNetwork();
  914. // 14-13-12-11 1-2-3-4
  915. clusterTo(network, 'c1', [3,4]);
  916. network.clustering.openCluster('c1', {});
  917. assertNumNodes(network, numNodes, numNodes); // Expecting same as original
  918. assertNumEdges(network, numEdges, numEdges);
  919. // Two external connections
  920. clusterTo(network, 'c1', [2,3]);
  921. network.clustering.openCluster('c1', {});
  922. assertNumNodes(network, numNodes, numNodes); // Expecting same as original
  923. assertNumEdges(network, numEdges, numEdges);
  924. // One external connection to cluster
  925. clusterTo(network, 'c1', [1,2]);
  926. clusterTo(network, 'c2', [3,4]);
  927. // 14-13-12-11 c1(1-2-)-c2(-3-4)
  928. network.clustering.openCluster('c1', {});
  929. // 14-13-12-11 1-2-c2(-3-4)
  930. numNodes += 1;
  931. numEdges += 1;
  932. assertNumNodes(network, numNodes, numNodes - 2);
  933. assertNumEdges(network, numEdges, numEdges - 2);
  934. // two external connections to clusters
  935. [network, data, numNodes, numEdges] = createSampleNetwork();
  936. data.edges.update({
  937. from: 1,
  938. to: 11,
  939. });
  940. numEdges += 1;
  941. assertNumNodes(network, numNodes, numNodes);
  942. assertNumEdges(network, numEdges, numEdges);
  943. clusterTo(network, 'c1', [1,2]);
  944. // 14-13-12-11-c1(-1-2-)-3-4
  945. numNodes += 1;
  946. numEdges += 2;
  947. clusterTo(network, 'c2', [3,4]);
  948. // 14-13-12-11-c1(-1-2-)-c2(-3-4)
  949. // NOTE: clustering edges are hidden by clustering here!
  950. numNodes += 1;
  951. numEdges += 1;
  952. clusterTo(network, 'c3', [11,12]);
  953. // 14-13-c3(-12-11-)-c1(-1-2-)-c2(-3-4)
  954. numNodes += 1;
  955. numEdges += 2;
  956. assertNumNodes(network, numNodes, numNodes - 6);
  957. assertNumEdges(network, numEdges, numEdges - 8); // 6 regular edges hidden; also 2 clustering!!!!!
  958. network.clustering.openCluster('c1', {});
  959. numNodes -= 1;
  960. numEdges -= 2;
  961. // 14-13-c3(-12-11-)-1-2-c2(-3-4)
  962. assertNumNodes(network, numNodes, numNodes - 4);
  963. assertNumEdges(network, numEdges, numEdges - 5);
  964. });
  965. /**
  966. * Check correct opening of nested clusters.
  967. * The test uses clustering three levels deep and opens the middle one.
  968. */
  969. it('properly opens clustered clusters', function () {
  970. var [network, data, numNodes, numEdges] = createSampleNetwork();
  971. data.edges.update({from: 1, to: 11,});
  972. numEdges += 1;
  973. clusterTo(network, 'c1', [3,4]);
  974. clusterTo(network, 'c2', [2,'c1']);
  975. clusterTo(network, 'c3', [1,'c2']);
  976. // Attempt at visualization: parentheses belong to the cluster one level above
  977. // -c3
  978. // ( -c2 )
  979. // ( -c1 )
  980. // 14-13-12-11 -1 -2 (-3-4)
  981. numNodes += 3;
  982. numEdges += 3;
  983. //console.log("numNodes: " + numNodes + "; numEdges: " + numEdges);
  984. assertNumNodes(network, numNodes, numNodes - 6);
  985. assertNumEdges(network, numEdges, numEdges - 6);
  986. // Open the middle cluster
  987. network.clustering.openCluster('c2', {});
  988. // -c3
  989. // ( -c1 )
  990. // 14-13-12-11 -1 -2 (-3-4)
  991. numNodes -= 1;
  992. numEdges -= 1;
  993. assertNumNodes(network, numNodes, numNodes - 5);
  994. assertNumEdges(network, numEdges, numEdges - 5);
  995. //
  996. // Same, with one external connection to cluster
  997. //
  998. var [network, data, numNodes, numEdges] = createSampleNetwork();
  999. data.edges.update({from: 1, to: 11,});
  1000. data.edges.update({from: 2, to: 12,});
  1001. numEdges += 2;
  1002. // 14-13-12-11-1-2-3-4
  1003. // |------|
  1004. assertNumNodes(network, numNodes);
  1005. assertNumEdges(network, numEdges);
  1006. clusterTo(network, 'c0', [11,12]);
  1007. clusterTo(network, 'c1', [3,4]);
  1008. clusterTo(network, 'c2', [2,'c1']);
  1009. clusterTo(network, 'c3', [1,'c2']);
  1010. // +----------------+
  1011. // | c3 |
  1012. // | +----------+ |
  1013. // | | c2 | |
  1014. // +-------+ | | +----+ | |
  1015. // | c0 | | | | c1 | | |
  1016. // 14-13-|-12-11-|-|-1-|-2-|-3-4| | |
  1017. // | | | | | | +----+ | |
  1018. // +-------+ | | | | |
  1019. // | | +----------+ |
  1020. // | | | |
  1021. // | +----------------+
  1022. // |------------|
  1023. // (I)
  1024. numNodes += 4;
  1025. numEdges = 15;
  1026. assertNumNodes(network, numNodes, 4);
  1027. assertNumEdges(network, numEdges, 3); // (I) link 2-12 is combined into cluster edge for 11-1
  1028. // Open the middle cluster
  1029. network.clustering.openCluster('c2', {});
  1030. // +--------------+
  1031. // | c3 |
  1032. // | |
  1033. // +-------+ | +----+ |
  1034. // | c0 | | | c1 | |
  1035. // 14-13-|-12-11-|-|-1--2-|-3-4| |
  1036. // | | | | | +----+ |
  1037. // +-------+ | | |
  1038. // | | | |
  1039. // | +--------------+
  1040. // |-----------|
  1041. // (I)
  1042. numNodes -= 1;
  1043. numEdges -= 2;
  1044. assertNumNodes(network, numNodes, 4); // visibility doesn't change, cluster opened within cluster
  1045. assertNumEdges(network, numEdges, 3); // (I)
  1046. // Open the top cluster
  1047. network.clustering.openCluster('c3', {});
  1048. //
  1049. // +-------+ +----+
  1050. // | c0 | | c1 |
  1051. // 14-13-|-12-11-|-1-2-|-3-4|
  1052. // | | | | +----+
  1053. // +-------+ |
  1054. // | |
  1055. // |--------|
  1056. // (II)
  1057. numNodes -= 1;
  1058. numEdges = 12;
  1059. assertNumNodes(network, numNodes, 6); // visibility doesn't change, cluster opened within cluster
  1060. assertNumEdges(network, numEdges, 6); // (II) link 2-12 visible again
  1061. });
  1062. }); // Clustering
  1063. describe('on node.js', function () {
  1064. it('should be running', function () {
  1065. assert(this.container !== null, 'Container div not found');
  1066. // The following should now just plain succeed
  1067. var [network, data] = createSampleNetwork();
  1068. assert.equal(Object.keys(network.body.nodes).length, 8);
  1069. assert.equal(Object.keys(network.body.edges).length, 6);
  1070. });
  1071. describe('runs example ', function () {
  1072. function loadExample(path, noPhysics) {
  1073. include(path, this);
  1074. var container = document.getElementById('mynetwork');
  1075. // create a network
  1076. var data = {
  1077. nodes: new DataSet(nodes),
  1078. edges: new DataSet(edges)
  1079. };
  1080. if (noPhysics) {
  1081. // Avoid excessive processor time due to load.
  1082. // We're just interested that the load itself is good
  1083. options.physics = false;
  1084. }
  1085. var network = new Network(container, data, options);
  1086. return network;
  1087. };
  1088. it('basicUsage', function () {
  1089. var network = loadExample('./test/network/basicUsage.js');
  1090. //console.log(Object.keys(network.body.edges));
  1091. // Count in following also contains the helper nodes for dynamic edges
  1092. assert.equal(Object.keys(network.body.nodes).length, 10);
  1093. assert.equal(Object.keys(network.body.edges).length, 5);
  1094. });
  1095. it('WorlCup2014', function (done) {
  1096. // This is a huge example (which is why it's tested here!), so it takes a long time to load.
  1097. this.timeout(15000);
  1098. var network = loadExample('./examples/network/datasources/WorldCup2014.js', true);
  1099. // Count in following also contains the helper nodes for dynamic edges
  1100. assert.equal(Object.keys(network.body.nodes).length, 9964);
  1101. assert.equal(Object.keys(network.body.edges).length, 9228);
  1102. done();
  1103. });
  1104. // This actually failed to load, added for this reason
  1105. it('disassemblerExample', function () {
  1106. var network = loadExample('./examples/network/exampleApplications/disassemblerExample.js');
  1107. // console.log(Object.keys(network.body.nodes));
  1108. // console.log(Object.keys(network.body.edges));
  1109. // Count in following also contains the helper nodes for dynamic edges
  1110. assert.equal(Object.keys(network.body.nodes).length, 9);
  1111. assert.equal(Object.keys(network.body.edges).length, 14 - 3); // NB 3 edges in data not displayed
  1112. });
  1113. }); // runs example
  1114. }); // on node.js
  1115. }); // Network