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.

557 lines
17 KiB

10 years ago
10 years ago
10 years ago
10 years ago
  1. // Load custom shapes into CanvasRenderingContext2D
  2. require('./shapes');
  3. var Emitter = require('emitter-component');
  4. var Hammer = require('../module/hammer');
  5. var util = require('../util');
  6. var DataSet = require('../DataSet');
  7. var DataView = require('../DataView');
  8. var dotparser = require('./dotparser');
  9. var gephiParser = require('./gephiParser');
  10. var Groups = require('./Groups');
  11. var Images = require('./Images');
  12. var Activator = require('../shared/Activator');
  13. var locales = require('./locales');
  14. import NodesHandler from './modules/NodesHandler';
  15. import EdgesHandler from './modules/EdgesHandler';
  16. import PhysicsEngine from './modules/PhysicsEngine';
  17. import ClusterEngine from './modules/Clustering';
  18. import CanvasRenderer from './modules/CanvasRenderer';
  19. import Canvas from './modules/Canvas';
  20. import View from './modules/View';
  21. import InteractionHandler from './modules/InteractionHandler';
  22. import SelectionHandler from "./modules/SelectionHandler";
  23. import LayoutEngine from "./modules/LayoutEngine";
  24. /**
  25. * @constructor Network
  26. * Create a network visualization, displaying nodes and edges.
  27. *
  28. * @param {Element} container The DOM element in which the Network will
  29. * be created. Normally a div element.
  30. * @param {Object} data An object containing parameters
  31. * {Array} nodes
  32. * {Array} edges
  33. * @param {Object} options Options
  34. */
  35. function Network (container, data, options) {
  36. if (!(this instanceof Network)) {
  37. throw new SyntaxError('Constructor must be called with the new operator');
  38. }
  39. // set constant values
  40. this.remainingOptions = {
  41. dataManipulation: {
  42. enabled: false,
  43. initiallyVisible: false
  44. },
  45. locale: 'en',
  46. locales: locales,
  47. useDefaultGroups: true
  48. };
  49. // containers for nodes and edges
  50. this.body = {
  51. nodes: {},
  52. nodeIndices: [],
  53. edges: {},
  54. edgeIndices: [],
  55. data: {
  56. nodes: null, // A DataSet or DataView
  57. edges: null // A DataSet or DataView
  58. },
  59. functions:{
  60. createNode: () => {},
  61. createEdge: () => {}
  62. },
  63. emitter: {
  64. on: this.on.bind(this),
  65. off: this.off.bind(this),
  66. emit: this.emit.bind(this),
  67. once: this.once.bind(this)
  68. },
  69. eventListeners: {
  70. onTap: function() {},
  71. onTouch: function() {},
  72. onDoubleTap: function() {},
  73. onHold: function() {},
  74. onDragStart: function() {},
  75. onDrag: function() {},
  76. onDragEnd: function() {},
  77. onMouseWheel: function() {},
  78. onPinch: function() {},
  79. onMouseMove: function() {},
  80. onRelease: function() {}
  81. },
  82. container: container,
  83. view: {
  84. scale:1,
  85. translation:{x:0,y:0}
  86. }
  87. };
  88. // bind the event listeners
  89. this.bindEventListeners();
  90. // setting up all modules
  91. var groups = new Groups(); // object with groups
  92. var images = new Images(() => this.body.emitter.emit("_requestRedraw")); // object with images
  93. this.canvas = new Canvas(this.body); // DOM handler
  94. this.selectionHandler = new SelectionHandler(this.body, this.canvas); // Selection handler
  95. this.interactionHandler = new InteractionHandler(this.body, this.canvas, this.selectionHandler); // Interaction handler handles all the hammer bindings (that are bound by canvas), key
  96. this.view = new View(this.body, this.canvas); // camera handler, does animations and zooms
  97. this.renderer = new CanvasRenderer(this.body, this.canvas); // renderer, starts renderloop, has events that modules can hook into
  98. this.physics = new PhysicsEngine(this.body); // physics engine, does all the simulations
  99. this.layoutEngine = new LayoutEngine(this.body);
  100. this.clustering = new ClusterEngine(this.body); // clustering api
  101. this.nodesHandler = new NodesHandler(this.body, images, groups, this.layoutEngine); // Handle adding, deleting and updating of nodes as well as global options
  102. this.edgesHandler = new EdgesHandler(this.body, images, groups); // Handle adding, deleting and updating of edges as well as global options
  103. // create the DOM elements
  104. this.canvas.create();
  105. // apply options
  106. this.setOptions(options);
  107. // load data (the disable start variable will be the same as the enabled clustering)
  108. this.setData(data);
  109. }
  110. // Extend Network with an Emitter mixin
  111. Emitter(Network.prototype);
  112. /**
  113. * Update the this.body.nodeIndices with the most recent node index list
  114. * @private
  115. */
  116. Network.prototype._updateVisibleIndices = function() {
  117. let nodes = this.body.nodes;
  118. let edges = this.body.edges;
  119. this.body.nodeIndices = [];
  120. this.body.edgeIndices = [];
  121. for (let nodeId in nodes) {
  122. if (nodes.hasOwnProperty(nodeId)) {
  123. if (nodes[nodeId].options.hidden === false) {
  124. this.body.nodeIndices.push(nodeId);
  125. }
  126. }
  127. }
  128. for (let edgeId in edges) {
  129. if (edges.hasOwnProperty(edgeId)) {
  130. if (edges[edgeId].options.hidden === false) {
  131. this.body.edgeIndices.push(edgeId);
  132. }
  133. }
  134. }
  135. };
  136. Network.prototype.bindEventListeners = function() {
  137. // this event will trigger a rebuilding of the cache everything. Used when nodes or edges have been added or removed.
  138. this.body.emitter.on("_dataChanged", (params) => {
  139. var t0 = new Date().valueOf();
  140. // update shortcut lists
  141. this._updateVisibleIndices();
  142. this.physics.updatePhysicsIndices();
  143. // call the dataUpdated event because the only difference between the two is the updating of the indices
  144. this.body.emitter.emit("_dataUpdated");
  145. // start simulation (can be called safely, even if already running)
  146. this.body.emitter.emit("startSimulation");
  147. console.log("_dataChanged took:", new Date().valueOf() - t0);
  148. });
  149. // this is called when options of EXISTING nodes or edges have changed.
  150. this.body.emitter.on("_dataUpdated", () => {
  151. var t0 = new Date().valueOf();
  152. // update values
  153. this._updateValueRange(this.body.nodes);
  154. this._updateValueRange(this.body.edges);
  155. // update edges
  156. this._reconnectEdges();
  157. this._markAllEdgesAsDirty();
  158. // start simulation (can be called safely, even if already running)
  159. this.body.emitter.emit("startSimulation");
  160. console.log("_dataUpdated took:", new Date().valueOf() - t0);
  161. });
  162. }
  163. /**
  164. * Set nodes and edges, and optionally options as well.
  165. *
  166. * @param {Object} data Object containing parameters:
  167. * {Array | DataSet | DataView} [nodes] Array with nodes
  168. * {Array | DataSet | DataView} [edges] Array with edges
  169. * {String} [dot] String containing data in DOT format
  170. * {String} [gephi] String containing data in gephi JSON format
  171. * {Options} [options] Object with options
  172. * @param {Boolean} [disableStart] | optional: disable the calling of the start function.
  173. */
  174. Network.prototype.setData = function(data) {
  175. // reset the physics engine.
  176. this.body.emitter.emit("resetPhysics");
  177. this.body.emitter.emit("_resetData");
  178. // unselect all to ensure no selections from old data are carried over.
  179. this.selectionHandler.unselectAll();
  180. if (data && data.dot && (data.nodes || data.edges)) {
  181. throw new SyntaxError('Data must contain either parameter "dot" or ' +
  182. ' parameter pair "nodes" and "edges", but not both.');
  183. }
  184. // set options
  185. this.setOptions(data && data.options);
  186. // set all data
  187. if (data && data.dot) {
  188. // parse DOT file
  189. if(data && data.dot) {
  190. var dotData = dotparser.DOTToGraph(data.dot);
  191. this.setData(dotData);
  192. return;
  193. }
  194. }
  195. else if (data && data.gephi) {
  196. // parse DOT file
  197. if(data && data.gephi) {
  198. var gephiData = gephiParser.parseGephi(data.gephi);
  199. this.setData(gephiData);
  200. return;
  201. }
  202. }
  203. else {
  204. this.nodesHandler.setData(data && data.nodes, true);
  205. this.edgesHandler.setData(data && data.edges, true);
  206. }
  207. // emit change in data
  208. this.body.emitter.emit("_dataChanged");
  209. // find a stable position or start animating to a stable position
  210. this.body.emitter.emit("initPhysics");
  211. };
  212. /**
  213. * Set options
  214. * @param {Object} options
  215. */
  216. Network.prototype.setOptions = function (options) {
  217. if (options !== undefined) {
  218. //var fields = ['nodes','edges','smoothCurves','hierarchicalLayout','navigation',
  219. // 'keyboard','dataManipulation','onAdd','onEdit','onEditEdge','onConnect','onDelete','clickToUse'
  220. //];
  221. // extend all but the values in fields
  222. //util.selectiveNotDeepExtend(fields,this.constants, options);
  223. //util.selectiveNotDeepExtend(['color'],this.constants.nodes, options.nodes);
  224. //util.selectiveNotDeepExtend(['color','length'],this.constants.edges, options.edges);
  225. //this.groups.useDefaultGroups = this.constants.useDefaultGroups;
  226. // the hierarchical system can adapt the edges and the physics to it's own options because not all combinations work with the hierarichical system.
  227. options = this.layoutEngine.setOptions(options.layout, options);
  228. this.nodesHandler.setOptions(options.nodes);
  229. this.edgesHandler.setOptions(options.edges);
  230. this.physics.setOptions(options.physics);
  231. this.canvas.setOptions(options.canvas);
  232. this.renderer.setOptions(options.rendering);
  233. this.view.setOptions(options.view);
  234. this.interactionHandler.setOptions(options.interaction);
  235. this.selectionHandler.setOptions(options.selection);
  236. this.clustering.setOptions(options.clustering);
  237. //util.mergeOptions(this.constants, options,'smoothCurves');
  238. //util.mergeOptions(this.constants, options,'hierarchicalLayout');
  239. //util.mergeOptions(this.constants, options,'clustering');
  240. //util.mergeOptions(this.constants, options,'navigation');
  241. //util.mergeOptions(this.constants, options,'keyboard');
  242. //util.mergeOptions(this.constants, options,'dataManipulation');
  243. //if (options.dataManipulation) {
  244. // this.editMode = this.constants.dataManipulation.initiallyVisible;
  245. //}
  246. //// TODO: work out these options and document them
  247. //
  248. //
  249. //
  250. //if (options.groups) {
  251. // for (var groupname in options.groups) {
  252. // if (options.groups.hasOwnProperty(groupname)) {
  253. // var group = options.groups[groupname];
  254. // this.groups.add(groupname, group);
  255. // }
  256. // }
  257. //}
  258. //
  259. //if (options.tooltip) {
  260. // for (prop in options.tooltip) {
  261. // if (options.tooltip.hasOwnProperty(prop)) {
  262. // this.constants.tooltip[prop] = options.tooltip[prop];
  263. // }
  264. // }
  265. // if (options.tooltip.color) {
  266. // this.constants.tooltip.color = util.parseColor(options.tooltip.color);
  267. // }
  268. //}
  269. if ('clickToUse' in options) {
  270. if (options.clickToUse === true) {
  271. if (this.activator === undefined) {
  272. this.activator = new Activator(this.frame);
  273. this.activator.on('change', this._createKeyBinds.bind(this));
  274. }
  275. }
  276. else {
  277. if (this.activator !== undefined) {
  278. this.activator.destroy();
  279. delete this.activator;
  280. }
  281. this.body.emitter.emit("activate");
  282. }
  283. }
  284. else {
  285. this.body.emitter.emit("activate");
  286. }
  287. this.canvas.setSize();
  288. }
  289. };
  290. /**
  291. * Cleans up all bindings of the network, removing it fully from the memory IF the variable is set to null after calling this function.
  292. * var network = new vis.Network(..);
  293. * network.destroy();
  294. * network = null;
  295. */
  296. Network.prototype.destroy = function() {
  297. this.body.emitter.emit("destroy");
  298. // clear events
  299. this.body.emitter.off();
  300. // remove the container and everything inside it recursively
  301. util.recursiveDOMDelete(this.body.container);
  302. };
  303. /**
  304. * Update the values of all object in the given array according to the current
  305. * value range of the objects in the array.
  306. * @param {Object} obj An object containing a set of Edges or Nodes
  307. * The objects must have a method getValue() and
  308. * setValueRange(min, max).
  309. * @private
  310. */
  311. Network.prototype._updateValueRange = function(obj) {
  312. var id;
  313. // determine the range of the objects
  314. var valueMin = undefined;
  315. var valueMax = undefined;
  316. var valueTotal = 0;
  317. for (id in obj) {
  318. if (obj.hasOwnProperty(id)) {
  319. var value = obj[id].getValue();
  320. if (value !== undefined) {
  321. valueMin = (valueMin === undefined) ? value : Math.min(value, valueMin);
  322. valueMax = (valueMax === undefined) ? value : Math.max(value, valueMax);
  323. valueTotal += value;
  324. }
  325. }
  326. }
  327. // adjust the range of all objects
  328. if (valueMin !== undefined && valueMax !== undefined) {
  329. for (id in obj) {
  330. if (obj.hasOwnProperty(id)) {
  331. obj[id].setValueRange(valueMin, valueMax, valueTotal);
  332. }
  333. }
  334. }
  335. };
  336. /**
  337. * Scale the network
  338. * @param {Number} scale Scaling factor 1.0 is unscaled
  339. * @private
  340. */
  341. Network.prototype._setScale = function(scale) {
  342. this.body.view.scale = scale;
  343. };
  344. /**
  345. * Get the current scale of the network
  346. * @return {Number} scale Scaling factor 1.0 is unscaled
  347. * @private
  348. */
  349. Network.prototype._getScale = function() {
  350. return this.body.view.scale;
  351. };
  352. /**
  353. * Load the XY positions of the nodes into the dataset.
  354. */
  355. Network.prototype.storePositions = function() {
  356. // todo: incorporate fixed instead of allowedtomove, add support for clusters and hierarchical.
  357. var dataArray = [];
  358. for (var nodeId in this.body.nodes) {
  359. if (this.body.nodes.hasOwnProperty(nodeId)) {
  360. var node = this.body.nodes[nodeId];
  361. var allowedToMoveX = !this.body.nodes.xFixed;
  362. var allowedToMoveY = !this.body.nodes.yFixed;
  363. if (this.body.data.nodes._data[nodeId].x != Math.round(node.x) || this.body.data.nodes._data[nodeId].y != Math.round(node.y)) {
  364. dataArray.push({id:nodeId,x:Math.round(node.x),y:Math.round(node.y),allowedToMoveX:allowedToMoveX,allowedToMoveY:allowedToMoveY});
  365. }
  366. }
  367. }
  368. this.body.data.nodes.update(dataArray);
  369. };
  370. /**
  371. * Return the positions of the nodes.
  372. */
  373. Network.prototype.getPositions = function(ids) {
  374. var dataArray = {};
  375. if (ids !== undefined) {
  376. if (Array.isArray(ids) == true) {
  377. for (var i = 0; i < ids.length; i++) {
  378. if (this.body.nodes[ids[i]] !== undefined) {
  379. var node = this.body.nodes[ids[i]];
  380. dataArray[ids[i]] = {x: Math.round(node.x), y: Math.round(node.y)};
  381. }
  382. }
  383. }
  384. else {
  385. if (this.body.nodes[ids] !== undefined) {
  386. var node = this.body.nodes[ids];
  387. dataArray[ids] = {x: Math.round(node.x), y: Math.round(node.y)};
  388. }
  389. }
  390. }
  391. else {
  392. for (var nodeId in this.body.nodes) {
  393. if (this.body.nodes.hasOwnProperty(nodeId)) {
  394. var node = this.body.nodes[nodeId];
  395. dataArray[nodeId] = {x: Math.round(node.x), y: Math.round(node.y)};
  396. }
  397. }
  398. }
  399. return dataArray;
  400. };
  401. /**
  402. * Returns true when the Network is active.
  403. * @returns {boolean}
  404. */
  405. Network.prototype.isActive = function () {
  406. return !this.activator || this.activator.active;
  407. };
  408. /**
  409. * Sets the scale
  410. * @returns {Number}
  411. */
  412. Network.prototype.setScale = function () {
  413. return this._setScale();
  414. };
  415. /**
  416. * Returns the scale
  417. * @returns {Number}
  418. */
  419. Network.prototype.getScale = function () {
  420. return this._getScale();
  421. };
  422. /**
  423. * Check if a node is a cluster.
  424. * @param nodeId
  425. * @returns {*}
  426. */
  427. Network.prototype.isCluster = function(nodeId) {
  428. if (this.body.nodes[nodeId] !== undefined) {
  429. return this.body.nodes[nodeId].isCluster;
  430. }
  431. else {
  432. console.log("Node does not exist.")
  433. return false;
  434. }
  435. };
  436. /**
  437. * Returns the scale
  438. * @returns {Number}
  439. */
  440. Network.prototype.getCenterCoordinates = function () {
  441. return this.DOMtoCanvas({x: 0.5 * this.frame.canvas.clientWidth, y: 0.5 * this.frame.canvas.clientHeight});
  442. };
  443. Network.prototype.getBoundingBox = function(nodeId) {
  444. if (this.body.nodes[nodeId] !== undefined) {
  445. return this.body.nodes[nodeId].boundingBox;
  446. }
  447. }
  448. Network.prototype.getConnectedNodes = function(nodeId) {
  449. var nodeList = [];
  450. if (this.body.nodes[nodeId] !== undefined) {
  451. var node = this.body.nodes[nodeId];
  452. var nodeObj = {nodeId : true}; // used to quickly check if node already exists
  453. for (var i = 0; i < node.edges.length; i++) {
  454. var edge = node.edges[i];
  455. if (edge.toId == nodeId) {
  456. if (nodeObj[edge.fromId] === undefined) {
  457. nodeList.push(edge.fromId);
  458. nodeObj[edge.fromId] = true;
  459. }
  460. }
  461. else if (edge.fromId == nodeId) {
  462. if (nodeObj[edge.toId] === undefined) {
  463. nodeList.push(edge.toId)
  464. nodeObj[edge.toId] = true;
  465. }
  466. }
  467. }
  468. }
  469. return nodeList;
  470. }
  471. Network.prototype.getEdgesFromNode = function(nodeId) {
  472. var edgesList = [];
  473. if (this.body.nodes[nodeId] !== undefined) {
  474. var node = this.body.nodes[nodeId];
  475. for (var i = 0; i < node.edges.length; i++) {
  476. edgesList.push(node.edges[i].id);
  477. }
  478. }
  479. return edgesList;
  480. }
  481. Network.prototype.generateColorObject = function(color) {
  482. return util.parseColor(color);
  483. }
  484. module.exports = Network;