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.

513 lines
16 KiB

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