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.

471 lines
19 KiB

9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
10 years ago
  1. // Load custom shapes into CanvasRenderingContext2D
  2. require('./shapes');
  3. let Emitter = require('emitter-component');
  4. let Hammer = require('../module/hammer');
  5. let util = require('../util');
  6. let DataSet = require('../DataSet');
  7. let DataView = require('../DataView');
  8. let dotparser = require('./dotparser');
  9. let gephiParser = require('./gephiParser');
  10. let Images = require('./Images');
  11. let Activator = require('../shared/Activator');
  12. let 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. import ManipulationSystem from "./modules/ManipulationSystem";
  25. import ConfigurationSystem from "./modules/ConfigurationSystem";
  26. import Validator from "./modules/Validator";
  27. import {printStyle} from "./modules/Validator";
  28. import {allOptions, configureOptions} from './modules/components/AllOptions.js';
  29. /**
  30. * @constructor Network
  31. * Create a network visualization, displaying nodes and edges.
  32. *
  33. * @param {Element} container The DOM element in which the Network will
  34. * be created. Normally a div element.
  35. * @param {Object} data An object containing parameters
  36. * {Array} nodes
  37. * {Array} edges
  38. * @param {Object} options Options
  39. */
  40. function Network(container, data, options) {
  41. if (!(this instanceof Network)) {
  42. throw new SyntaxError('Constructor must be called with the new operator');
  43. }
  44. // set constant values
  45. this.options = {};
  46. this.defaultOptions = {
  47. locale: 'en',
  48. locales: locales,
  49. clickToUse: false
  50. }
  51. util.extend(this.options, this.defaultOptions);
  52. // containers for nodes and edges
  53. this.body = {
  54. nodes: {},
  55. nodeIndices: [],
  56. edges: {},
  57. edgeIndices: [],
  58. data: {
  59. nodes: null, // A DataSet or DataView
  60. edges: null // A DataSet or DataView
  61. },
  62. functions: {
  63. createNode: function() {},
  64. createEdge: function() {},
  65. getPointer: function() {}
  66. },
  67. emitter: {
  68. on: this.on.bind(this),
  69. off: this.off.bind(this),
  70. emit: this.emit.bind(this),
  71. once: this.once.bind(this)
  72. },
  73. eventListeners: {
  74. onTap: function() {},
  75. onTouch: function() {},
  76. onDoubleTap: function() {},
  77. onHold: function() {},
  78. onDragStart: function() {},
  79. onDrag: function() {},
  80. onDragEnd: function() {},
  81. onMouseWheel: function() {},
  82. onPinch: function() {},
  83. onMouseMove: function() {},
  84. onRelease: function() {},
  85. onContext: function() {}
  86. },
  87. container: container,
  88. view: {
  89. scale: 1,
  90. translation: {x: 0, y: 0}
  91. }
  92. };
  93. // bind the event listeners
  94. this.bindEventListeners();
  95. // setting up all modules
  96. this.images = new Images(() => this.body.emitter.emit("_requestRedraw")); // object with images
  97. this.groups = new Groups(); // object with groups
  98. this.canvas = new Canvas(this.body); // DOM handler
  99. this.selectionHandler = new SelectionHandler(this.body, this.canvas); // Selection handler
  100. this.interactionHandler = new InteractionHandler(this.body, this.canvas, this.selectionHandler); // Interaction handler handles all the hammer bindings (that are bound by canvas), key
  101. this.view = new View(this.body, this.canvas); // camera handler, does animations and zooms
  102. this.renderer = new CanvasRenderer(this.body, this.canvas); // renderer, starts renderloop, has events that modules can hook into
  103. this.physics = new PhysicsEngine(this.body); // physics engine, does all the simulations
  104. this.layoutEngine = new LayoutEngine(this.body); // layout engine for inital layout and hierarchical layout
  105. this.clustering = new ClusterEngine(this.body); // clustering api
  106. this.manipulation = new ManipulationSystem(this.body, this.canvas, this.selectionHandler); // data manipulation system
  107. this.nodesHandler = new NodesHandler(this.body, this.images, this.groups, this.layoutEngine); // Handle adding, deleting and updating of nodes as well as global options
  108. this.edgesHandler = new EdgesHandler(this.body, this.images, this.groups); // Handle adding, deleting and updating of edges as well as global options
  109. // create the DOM elements
  110. this.canvas._create();
  111. // setup configuration system
  112. this.configurationSystem = new ConfigurationSystem(this, this.body.container, configureOptions, this.canvas.pixelRatio);
  113. // apply options
  114. this.setOptions(options);
  115. // load data (the disable start variable will be the same as the enabled clustering)
  116. this.setData(data);
  117. }
  118. // Extend Network with an Emitter mixin
  119. Emitter(Network.prototype);
  120. /**
  121. * Set options
  122. * @param {Object} options
  123. */
  124. Network.prototype.setOptions = function (options) {
  125. if (options !== undefined) {
  126. let errorFound = Validator.validate(options, allOptions);
  127. if (errorFound === true) {
  128. options = {};
  129. console.log('%cErrors have been found in the supplied options object. None of the options will be used.', printStyle);
  130. }
  131. // copy the global fields over
  132. let fields = ['locale','locales','clickToUse'];
  133. util.selectiveDeepExtend(fields,this.options, options);
  134. // the hierarchical system can adapt the edges and the physics to it's own options because not all combinations work with the hierarichical system.
  135. options = this.layoutEngine.setOptions(options.layout, options);
  136. this.canvas.setOptions(options); // options for canvas are in globals
  137. // pass the options to the modules
  138. this.groups.setOptions(options.groups);
  139. this.nodesHandler.setOptions(options.nodes);
  140. this.edgesHandler.setOptions(options.edges);
  141. this.physics.setOptions(options.physics);
  142. this.manipulation.setOptions(options.manipulation,options); // manipulation uses the locales in the globals
  143. this.interactionHandler.setOptions(options.interaction);
  144. this.renderer.setOptions(options.interaction); // options for rendering are in interaction
  145. this.selectionHandler.setOptions(options.interaction); // options for selection are in interaction
  146. // these two do not have options at the moment, here for completeness
  147. //this.view.setOptions(options.view);
  148. //this.clustering.setOptions(options.clustering);
  149. this.configurationSystem.setOptions(options.configure);
  150. // if the configuration system is enabled, copy all options and put them into the config system
  151. if (this.configurationSystem.options.enabled === true) {
  152. let networkOptions = {nodes:{},edges:{},layout:{},interaction:{},manipulation:{},physics:{},global:{}};
  153. util.deepExtend(networkOptions.nodes, this.nodesHandler.options);
  154. util.deepExtend(networkOptions.edges, this.edgesHandler.options);
  155. util.deepExtend(networkOptions.layout, this.layoutEngine.options);
  156. // load the selectionHandler and rendere default options in to the interaction group
  157. util.deepExtend(networkOptions.interaction, this.selectionHandler.options);
  158. util.deepExtend(networkOptions.interaction, this.renderer.options);
  159. util.deepExtend(networkOptions.interaction, this.interactionHandler.options);
  160. util.deepExtend(networkOptions.manipulation, this.manipulation.options);
  161. util.deepExtend(networkOptions.physics, this.physics.options);
  162. // load globals into the global object
  163. util.deepExtend(networkOptions.global, this.canvas.options);
  164. util.deepExtend(networkOptions.global, this.options);
  165. this.configurationSystem.setModuleOptions(networkOptions);
  166. }
  167. // handle network global options
  168. if (options.clickToUse !== undefined) {
  169. if (options.clickToUse === true) {
  170. if (this.activator === undefined) {
  171. this.activator = new Activator(this.frame);
  172. this.activator.on('change', this._createKeyBinds.bind(this));
  173. }
  174. }
  175. else {
  176. if (this.activator !== undefined) {
  177. this.activator.destroy();
  178. delete this.activator;
  179. }
  180. this.body.emitter.emit("activate");
  181. }
  182. }
  183. else {
  184. this.body.emitter.emit("activate");
  185. }
  186. this.canvas.setSize();
  187. // start the physics simulation. Can be safely called multiple times.
  188. this.body.emitter.emit("startSimulation");
  189. }
  190. };
  191. /**
  192. * Update the this.body.nodeIndices with the most recent node index list
  193. * @private
  194. */
  195. Network.prototype._updateVisibleIndices = function () {
  196. let nodes = this.body.nodes;
  197. let edges = this.body.edges;
  198. this.body.nodeIndices = [];
  199. this.body.edgeIndices = [];
  200. for (let nodeId in nodes) {
  201. if (nodes.hasOwnProperty(nodeId)) {
  202. if (nodes[nodeId].options.hidden === false) {
  203. this.body.nodeIndices.push(nodeId);
  204. }
  205. }
  206. }
  207. for (let edgeId in edges) {
  208. if (edges.hasOwnProperty(edgeId)) {
  209. if (edges[edgeId].options.hidden === false) {
  210. this.body.edgeIndices.push(edgeId);
  211. }
  212. }
  213. }
  214. };
  215. /**
  216. * Bind all events
  217. */
  218. Network.prototype.bindEventListeners = function () {
  219. // this event will trigger a rebuilding of the cache everything. Used when nodes or edges have been added or removed.
  220. this.body.emitter.on("_dataChanged", () => {
  221. // update shortcut lists
  222. this._updateVisibleIndices();
  223. this.physics.updatePhysicsIndices();
  224. // call the dataUpdated event because the only difference between the two is the updating of the indices
  225. this.body.emitter.emit("_dataUpdated");
  226. });
  227. // this is called when options of EXISTING nodes or edges have changed.
  228. this.body.emitter.on("_dataUpdated", () => {
  229. // update values
  230. this._updateValueRange(this.body.nodes);
  231. this._updateValueRange(this.body.edges);
  232. // start simulation (can be called safely, even if already running)
  233. this.body.emitter.emit("startSimulation");
  234. });
  235. };
  236. /**
  237. * Set nodes and edges, and optionally options as well.
  238. *
  239. * @param {Object} data Object containing parameters:
  240. * {Array | DataSet | DataView} [nodes] Array with nodes
  241. * {Array | DataSet | DataView} [edges] Array with edges
  242. * {String} [dot] String containing data in DOT format
  243. * {String} [gephi] String containing data in gephi JSON format
  244. * {Options} [options] Object with options
  245. */
  246. Network.prototype.setData = function (data) {
  247. // reset the physics engine.
  248. this.body.emitter.emit("resetPhysics");
  249. this.body.emitter.emit("_resetData");
  250. // unselect all to ensure no selections from old data are carried over.
  251. this.selectionHandler.unselectAll();
  252. if (data && data.dot && (data.nodes || data.edges)) {
  253. throw new SyntaxError('Data must contain either parameter "dot" or ' +
  254. ' parameter pair "nodes" and "edges", but not both.');
  255. }
  256. // set options
  257. this.setOptions(data && data.options);
  258. // set all data
  259. if (data && data.dot) {
  260. // parse DOT file
  261. if (data && data.dot) {
  262. var dotData = dotparser.DOTToGraph(data.dot);
  263. this.setData(dotData);
  264. return;
  265. }
  266. }
  267. else if (data && data.gephi) {
  268. // parse DOT file
  269. if (data && data.gephi) {
  270. var gephiData = gephiParser.parseGephi(data.gephi);
  271. this.setData(gephiData);
  272. return;
  273. }
  274. }
  275. else {
  276. this.nodesHandler.setData(data && data.nodes, true);
  277. this.edgesHandler.setData(data && data.edges, true);
  278. }
  279. // emit change in data
  280. this.body.emitter.emit("_dataChanged");
  281. // find a stable position or start animating to a stable position
  282. this.body.emitter.emit("initPhysics");
  283. };
  284. /**
  285. * Cleans up all bindings of the network, removing it fully from the memory IF the variable is set to null after calling this function.
  286. * var network = new vis.Network(..);
  287. * network.destroy();
  288. * network = null;
  289. */
  290. Network.prototype.destroy = function () {
  291. this.body.emitter.emit("destroy");
  292. // clear events
  293. this.body.emitter.off();
  294. this.off();
  295. // delete modules
  296. delete this.groups;
  297. delete this.canvas;
  298. delete this.selectionHandler;
  299. delete this.interactionHandler;
  300. delete this.view;
  301. delete this.renderer;
  302. delete this.physics;
  303. delete this.layoutEngine;
  304. delete this.clustering;
  305. delete this.manipulation;
  306. delete this.nodesHandler;
  307. delete this.edgesHandler;
  308. delete this.configurationSystem;
  309. delete this.images;
  310. // delete emitter bindings
  311. delete this.body.emitter.emit;
  312. delete this.body.emitter.on;
  313. delete this.body.emitter.off;
  314. delete this.body.emitter.once;
  315. delete this.body.emitter;
  316. for (var nodeId in this.body.nodes) {
  317. delete this.body.nodes[nodeId];
  318. }
  319. for (var edgeId in this.body.edges) {
  320. delete this.body.edges[edgeId];
  321. }
  322. // remove the container and everything inside it recursively
  323. util.recursiveDOMDelete(this.body.container);
  324. };
  325. /**
  326. * Update the values of all object in the given array according to the current
  327. * value range of the objects in the array.
  328. * @param {Object} obj An object containing a set of Edges or Nodes
  329. * The objects must have a method getValue() and
  330. * setValueRange(min, max).
  331. * @private
  332. */
  333. Network.prototype._updateValueRange = function (obj) {
  334. var id;
  335. // determine the range of the objects
  336. var valueMin = undefined;
  337. var valueMax = undefined;
  338. var valueTotal = 0;
  339. for (id in obj) {
  340. if (obj.hasOwnProperty(id)) {
  341. var value = obj[id].getValue();
  342. if (value !== undefined) {
  343. valueMin = (valueMin === undefined) ? value : Math.min(value, valueMin);
  344. valueMax = (valueMax === undefined) ? value : Math.max(value, valueMax);
  345. valueTotal += value;
  346. }
  347. }
  348. }
  349. // adjust the range of all objects
  350. if (valueMin !== undefined && valueMax !== undefined) {
  351. for (id in obj) {
  352. if (obj.hasOwnProperty(id)) {
  353. obj[id].setValueRange(valueMin, valueMax, valueTotal);
  354. }
  355. }
  356. }
  357. };
  358. /**
  359. * Returns true when the Network is active.
  360. * @returns {boolean}
  361. */
  362. Network.prototype.isActive = function () {
  363. return !this.activator || this.activator.active;
  364. };
  365. Network.prototype.setSize = function() {this.canvas.setSize.apply(this.canvas,arguments);};
  366. Network.prototype.canvasToDOM = function() {this.canvas.canvasToDOM.apply(this.canvas,arguments);};
  367. Network.prototype.DOMtoCanvas = function() {this.canvas.setSize.DOMtoCanvas(this.canvas,arguments);};
  368. Network.prototype.findNode = function() {this.clustering.findNode.apply(this.clustering,arguments);};
  369. Network.prototype.isCluster = function() {this.clustering.isCluster.apply(this.clustering,arguments);};
  370. Network.prototype.openCluster = function() {this.clustering.openCluster.apply(this.clustering,arguments);};
  371. Network.prototype.cluster = function() {this.clustering.cluster.apply(this.clustering,arguments);};
  372. Network.prototype.clusterByConnection = function() {this.clustering.clusterByConnection.apply(this.clustering,arguments);};
  373. Network.prototype.clusterByHubsize = function() {this.clustering.clusterByHubsize.apply(this.clustering,arguments);};
  374. Network.prototype.clusterOutliers = function() {this.clustering.clusterOutliers.apply(this.clustering,arguments);};
  375. Network.prototype.getSeed = function() {this.layoutEngine.getSeed.apply(this.layoutEngine,arguments);};
  376. Network.prototype.enableEditMode = function() {this.manipulation.enableEditMode.apply(this.manipulation,arguments);};
  377. Network.prototype.disableEditMode = function() {this.manipulation.disableEditMode.apply(this.manipulation,arguments);};
  378. Network.prototype.addNodeMode = function() {this.manipulation.addNodeMode.apply(this.manipulation,arguments);};
  379. Network.prototype.editNodeMode = function() {this.manipulation.editNodeMode.apply(this.manipulation,arguments);};
  380. Network.prototype.addEdgeMode = function() {this.manipulation.addEdgeMode.apply(this.manipulation,arguments);};
  381. Network.prototype.editEdgeMode = function() {this.manipulation.editEdgeMode.apply(this.manipulation,arguments);};
  382. Network.prototype.deleteSelected = function() {this.manipulation.deleteSelected.apply(this.manipulation,arguments);};
  383. Network.prototype.getPositions = function() {this.nodesHandler.getPositions.apply(this.nodesHandler,arguments);};
  384. Network.prototype.storePositions = function() {this.nodesHandler.storePositions.apply(this.nodesHandler,arguments);};
  385. Network.prototype.getBoundingBox = function() {this.nodesHandler.getBoundingBox.apply(this.nodesHandler,arguments);};
  386. Network.prototype.getConnectedNodes = function() {this.nodesHandler.getConnectedNodes.apply(this.nodesHandler,arguments);};
  387. Network.prototype.getEdges = function() {this.nodesHandler.getEdges.apply(this.nodesHandler,arguments);};
  388. Network.prototype.startSimulation = function() {this.physics.startSimulation.apply(this.physics,arguments);};
  389. Network.prototype.stopSimulation = function() {this.physics.stopSimulation.apply(this.physics,arguments);};
  390. Network.prototype.stabilize = function() {this.physics.stabilize.apply(this.physics,arguments);};
  391. Network.prototype.getSelection = function() {this.selectionHandler.getSelection.apply(this.selectionHandler,arguments);};
  392. Network.prototype.getSelectedNodes = function() {this.selectionHandler.getSelectedNodes.apply(this.selectionHandler,arguments);};
  393. Network.prototype.getSelectedEdges = function() {this.selectionHandler.getSelectedEdges.apply(this.selectionHandler,arguments);};
  394. Network.prototype.getNodeAt = function() {this.selectionHandler.getNodeAt.apply(this.selectionHandler,arguments);};
  395. Network.prototype.getEdgeAt = function() {this.selectionHandler.getEdgeAt.apply(this.selectionHandler,arguments);};
  396. Network.prototype.selectNodes = function() {this.selectionHandler.selectNodes.apply(this.selectionHandler,arguments);};
  397. Network.prototype.selectEdges = function() {this.selectionHandler.selectEdges.apply(this.selectionHandler,arguments);};
  398. Network.prototype.unselectAll = function() {this.selectionHandler.unselectAll.apply(this.selectionHandler,arguments);};
  399. Network.prototype.getScale = function() {this.view.getScale.apply(this.view,arguments);};
  400. Network.prototype.getPosition = function() {this.view.getPosition.apply(this.view,arguments);};
  401. Network.prototype.fit = function() {this.view.fit.apply(this.view,arguments);};
  402. Network.prototype.moveTo = function() {this.view.moveTo.apply(this.view,arguments);};
  403. Network.prototype.focus = function() {this.view.focus.apply(this.view,arguments);};
  404. Network.prototype.releaseNode = function() {this.view.releaseNode.apply(this.view,arguments);};
  405. module.exports = Network;