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.

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