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.

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