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.

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