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.

884 lines
26 KiB

10 years ago
10 years ago
10 years ago
10 years ago
  1. var Emitter = require('emitter-component');
  2. var Hammer = require('../module/hammer');
  3. var util = require('../util');
  4. var DataSet = require('../DataSet');
  5. var DataView = require('../DataView');
  6. var dotparser = require('./dotparser');
  7. var gephiParser = require('./gephiParser');
  8. var Groups = require('./Groups');
  9. var Images = require('./Images');
  10. var Node = require('./modules/components/nodes/NodeMain');
  11. var Edge = require('./modules/components/edges/EdgeMain');
  12. var Popup = require('./Popup');
  13. var Activator = require('../shared/Activator');
  14. var locales = require('./locales');
  15. // Load custom shapes into CanvasRenderingContext2D
  16. require('./shapes');
  17. import NodesHandler from './modules/NodesHandler';
  18. import EdgesHandler from './modules/EdgesHandler';
  19. import PhysicsEngine from './modules/PhysicsEngine';
  20. import ClusterEngine from './modules/Clustering';
  21. import CanvasRenderer from './modules/CanvasRenderer';
  22. import Canvas from './modules/Canvas';
  23. import View from './modules/View';
  24. import InteractionHandler from './modules/InteractionHandler';
  25. import SelectionHandler from "./modules/SelectionHandler";
  26. import LayoutEngine from "./modules/LayoutEngine";
  27. /**
  28. * @constructor Network
  29. * Create a network visualization, displaying nodes and edges.
  30. *
  31. * @param {Element} container The DOM element in which the Network will
  32. * be created. Normally a div element.
  33. * @param {Object} data An object containing parameters
  34. * {Array} nodes
  35. * {Array} edges
  36. * @param {Object} options Options
  37. */
  38. function Network (container, data, options) {
  39. if (!(this instanceof Network)) {
  40. throw new SyntaxError('Constructor must be called with the new operator');
  41. }
  42. // set constant values
  43. this.remainingOptions = {
  44. dataManipulation: {
  45. enabled: false,
  46. initiallyVisible: false
  47. },
  48. hierarchicalLayout: {
  49. enabled:false,
  50. levelSeparation: 150,
  51. nodeSpacing: 100,
  52. direction: "UD", // UD, DU, LR, RL
  53. layout: "hubsize" // hubsize, directed
  54. },
  55. locale: 'en',
  56. locales: locales,
  57. useDefaultGroups: true
  58. };
  59. // containers for nodes and edges
  60. this.body = {
  61. nodes: {},
  62. nodeIndices: [],
  63. supportNodes: {},
  64. supportNodeIndices: [],
  65. edges: {},
  66. data: {
  67. nodes: null, // A DataSet or DataView
  68. edges: null // A DataSet or DataView
  69. },
  70. functions:{
  71. createNode: this._createNode.bind(this),
  72. createEdge: this._createEdge.bind(this)
  73. },
  74. emitter: {
  75. on: this.on.bind(this),
  76. off: this.off.bind(this),
  77. emit: this.emit.bind(this),
  78. once: this.once.bind(this)
  79. },
  80. eventListeners: {
  81. onTap: function() {},
  82. onTouch: function() {},
  83. onDoubleTap: function() {},
  84. onHold: function() {},
  85. onDragStart: function() {},
  86. onDrag: function() {},
  87. onDragEnd: function() {},
  88. onMouseWheel: function() {},
  89. onPinch: function() {},
  90. onMouseMove: function() {},
  91. onRelease: function() {}
  92. },
  93. container: container,
  94. view: {
  95. scale:1,
  96. translation:{x:0,y:0}
  97. }
  98. };
  99. // todo think of good comment for this set
  100. var groups = new Groups(); // object with groups
  101. var images = new Images(() => this.body.emitter.emit("_requestRedraw")); // object with images
  102. // data handling modules
  103. this.canvas = new Canvas(this.body); // DOM handler
  104. this.selectionHandler = new SelectionHandler(this.body, this.canvas); // Selection handler
  105. this.interactionHandler = new InteractionHandler(this.body, this.canvas, this.selectionHandler); // Interaction handler handles all the hammer bindings (that are bound by canvas), key
  106. this.view = new View(this.body, this.canvas); // camera handler, does animations and zooms
  107. this.renderer = new CanvasRenderer(this.body, this.canvas); // renderer, starts renderloop, has events that modules can hook into
  108. this.physics = new PhysicsEngine(this.body); // physics engine, does all the simulations
  109. this.layoutEngine = new LayoutEngine(this.body); // TODO: layout engine for initial positioning and hierarchical positioning
  110. this.clustering = new ClusterEngine(this.body); // clustering api
  111. this.nodesHandler = new NodesHandler(this.body, images, groups, this.layoutEngine); // Handle adding, deleting and updating of nodes as well as global options
  112. this.edgesHandler = new EdgesHandler(this.body, images, groups); // Handle adding, deleting and updating of edges as well as global options
  113. // this event will trigger a rebuilding of the cache everything. Used when nodes or edges have been added or removed.
  114. this.body.emitter.on("_dataChanged", (params) => {
  115. var t0 = new Date().valueOf();
  116. // update shortcut lists
  117. this._updateNodeIndexList();
  118. this.physics._updateCalculationNodes();
  119. // update values
  120. this._updateValueRange(this.body.nodes);
  121. this._updateValueRange(this.body.edges);
  122. // update edges
  123. this._reconnectEdges();
  124. this.edgesHandler.createBezierNodes(params);
  125. this._markAllEdgesAsDirty();
  126. // start simulation (can be called safely, even if already running)
  127. this.body.emitter.emit("startSimulation");
  128. console.log("_dataChanged took:", new Date().valueOf() - t0);
  129. })
  130. // this is called when options of EXISTING nodes or edges have changed.
  131. this.body.emitter.on("_dataUpdated", () => {
  132. var t0 = new Date().valueOf();
  133. // update values
  134. this._updateValueRange(this.body.nodes);
  135. this._updateValueRange(this.body.edges);
  136. // update edges
  137. this._reconnectEdges();
  138. this.edgesHandler.createBezierNodes(params);
  139. this._markAllEdgesAsDirty();
  140. // start simulation (can be called safely, even if already running)
  141. this.body.emitter.emit("startSimulation");
  142. console.log("_dataUpdated took:", new Date().valueOf() - t0);
  143. });
  144. // create the DOM elements
  145. this.canvas.create();
  146. // apply options
  147. this.setOptions(options);
  148. // load data (the disable start variable will be the same as the enabled clustering)
  149. this.setData(data);
  150. }
  151. // Extend Network with an Emitter mixin
  152. Emitter(Network.prototype);
  153. Network.prototype._createNode = function(properties) {
  154. return new Node(properties, this.images, this.groups, this.constants)
  155. }
  156. Network.prototype._createEdge = function(properties) {
  157. return new Edge(properties, this.body, this.constants)
  158. }
  159. /**
  160. * Update the this.body.nodeIndices with the most recent node index list
  161. * @private
  162. */
  163. Network.prototype._updateNodeIndexList = function() {
  164. this.body.supportNodeIndices = Object.keys(this.body.supportNodes)
  165. this.body.nodeIndices = Object.keys(this.body.nodes);
  166. };
  167. /**
  168. * Set nodes and edges, and optionally options as well.
  169. *
  170. * @param {Object} data Object containing parameters:
  171. * {Array | DataSet | DataView} [nodes] Array with nodes
  172. * {Array | DataSet | DataView} [edges] Array with edges
  173. * {String} [dot] String containing data in DOT format
  174. * {String} [gephi] String containing data in gephi JSON format
  175. * {Options} [options] Object with options
  176. * @param {Boolean} [disableStart] | optional: disable the calling of the start function.
  177. */
  178. Network.prototype.setData = function(data) {
  179. // reset the physics engine.
  180. this.body.emitter.emit("resetPhysics");
  181. this.body.emitter.emit("_resetData");
  182. // unselect all to ensure no selections from old data are carried over.
  183. this.selectionHandler.unselectAll();
  184. // we set initializing to true to ensure that the hierarchical layout is not performed until both nodes and edges are added.
  185. this.initializing = true;
  186. if (data && data.dot && (data.nodes || data.edges)) {
  187. throw new SyntaxError('Data must contain either parameter "dot" or ' +
  188. ' parameter pair "nodes" and "edges", but not both.');
  189. }
  190. // clean up in case there is anyone in an active mode of the manipulation. This is the same option as bound to the escape button.
  191. //if (this.constants.dataManipulation.enabled == true) {
  192. // this._createManipulatorBar();
  193. //}
  194. // set options
  195. this.setOptions(data && data.options);
  196. // set all data
  197. if (data && data.dot) {
  198. // parse DOT file
  199. if(data && data.dot) {
  200. var dotData = dotparser.DOTToGraph(data.dot);
  201. this.setData(dotData);
  202. return;
  203. }
  204. }
  205. else if (data && data.gephi) {
  206. // parse DOT file
  207. if(data && data.gephi) {
  208. var gephiData = gephiParser.parseGephi(data.gephi);
  209. this.setData(gephiData);
  210. return;
  211. }
  212. }
  213. else {
  214. this.nodesHandler.setData(data && data.nodes);
  215. this.edgesHandler.setData(data && data.edges);
  216. }
  217. // find a stable position or start animating to a stable position
  218. this.body.emitter.emit("initPhysics");
  219. };
  220. /**
  221. * Set options
  222. * @param {Object} options
  223. */
  224. Network.prototype.setOptions = function (options) {
  225. if (options) {
  226. //var fields = ['nodes','edges','smoothCurves','hierarchicalLayout','navigation',
  227. // 'keyboard','dataManipulation','onAdd','onEdit','onEditEdge','onConnect','onDelete','clickToUse'
  228. //];
  229. // extend all but the values in fields
  230. //util.selectiveNotDeepExtend(fields,this.constants, options);
  231. //util.selectiveNotDeepExtend(['color'],this.constants.nodes, options.nodes);
  232. //util.selectiveNotDeepExtend(['color','length'],this.constants.edges, options.edges);
  233. //this.groups.useDefaultGroups = this.constants.useDefaultGroups;
  234. this.nodesHandler.setOptions(options.nodes);
  235. this.edgesHandler.setOptions(options.edges);
  236. this.physics.setOptions(options.physics);
  237. this.canvas.setOptions(options.canvas);
  238. this.renderer.setOptions(options.rendering);
  239. this.view.setOptions(options.view);
  240. this.interactionHandler.setOptions(options.interaction);
  241. this.selectionHandler.setOptions(options.selection);
  242. this.layoutEngine.setOptions(options.layout);
  243. //this.clustering.setOptions(options.clustering);
  244. //util.mergeOptions(this.constants, options,'smoothCurves');
  245. //util.mergeOptions(this.constants, options,'hierarchicalLayout');
  246. //util.mergeOptions(this.constants, options,'clustering');
  247. //util.mergeOptions(this.constants, options,'navigation');
  248. //util.mergeOptions(this.constants, options,'keyboard');
  249. //util.mergeOptions(this.constants, options,'dataManipulation');
  250. //if (options.dataManipulation) {
  251. // this.editMode = this.constants.dataManipulation.initiallyVisible;
  252. //}
  253. // TODO: work out these options and document them
  254. if (options.edges) {
  255. if (options.edges.color !== undefined) {
  256. if (util.isString(options.edges.color)) {
  257. this.constants.edges.color = {};
  258. this.constants.edges.color.color = options.edges.color;
  259. this.constants.edges.color.highlight = options.edges.color;
  260. this.constants.edges.color.hover = options.edges.color;
  261. }
  262. else {
  263. if (options.edges.color.color !== undefined) {this.constants.edges.color.color = options.edges.color.color;}
  264. if (options.edges.color.highlight !== undefined) {this.constants.edges.color.highlight = options.edges.color.highlight;}
  265. if (options.edges.color.hover !== undefined) {this.constants.edges.color.hover = options.edges.color.hover;}
  266. }
  267. this.constants.edges.inheritColor = false;
  268. }
  269. if (!options.edges.fontColor) {
  270. if (options.edges.color !== undefined) {
  271. if (util.isString(options.edges.color)) {this.constants.edges.fontColor = options.edges.color;}
  272. else if (options.edges.color.color !== undefined) {this.constants.edges.fontColor = options.edges.color.color;}
  273. }
  274. }
  275. }
  276. if (options.nodes) {
  277. if (options.nodes.color) {
  278. var newColorObj = util.parseColor(options.nodes.color);
  279. this.constants.nodes.color.background = newColorObj.background;
  280. this.constants.nodes.color.border = newColorObj.border;
  281. this.constants.nodes.color.highlight.background = newColorObj.highlight.background;
  282. this.constants.nodes.color.highlight.border = newColorObj.highlight.border;
  283. this.constants.nodes.color.hover.background = newColorObj.hover.background;
  284. this.constants.nodes.color.hover.border = newColorObj.hover.border;
  285. }
  286. }
  287. if (options.groups) {
  288. for (var groupname in options.groups) {
  289. if (options.groups.hasOwnProperty(groupname)) {
  290. var group = options.groups[groupname];
  291. this.groups.add(groupname, group);
  292. }
  293. }
  294. }
  295. if (options.tooltip) {
  296. for (prop in options.tooltip) {
  297. if (options.tooltip.hasOwnProperty(prop)) {
  298. this.constants.tooltip[prop] = options.tooltip[prop];
  299. }
  300. }
  301. if (options.tooltip.color) {
  302. this.constants.tooltip.color = util.parseColor(options.tooltip.color);
  303. }
  304. }
  305. if ('clickToUse' in options) {
  306. if (options.clickToUse === true) {
  307. if (this.activator === undefined) {
  308. this.activator = new Activator(this.frame);
  309. this.activator.on('change', this._createKeyBinds.bind(this));
  310. }
  311. }
  312. else {
  313. if (this.activator !== undefined) {
  314. this.activator.destroy();
  315. delete this.activator;
  316. }
  317. this.body.emitter.emit("activate");
  318. }
  319. }
  320. else {
  321. this.body.emitter.emit("activate");
  322. }
  323. this.canvas.setSize();
  324. }
  325. };
  326. /**
  327. * Cleans up all bindings of the network, removing it fully from the memory IF the variable is set to null after calling this function.
  328. * var network = new vis.Network(..);
  329. * network.destroy();
  330. * network = null;
  331. */
  332. Network.prototype.destroy = function() {
  333. this.body.emitter.emit("destroy");
  334. // clear events
  335. this.body.emitter.off();
  336. // remove the container and everything inside it recursively
  337. this.util.recursiveDOMDelete(this.body.container);
  338. };
  339. /**
  340. * Check if there is an element on the given position in the network
  341. * (a node or edge). If so, and if this element has a title,
  342. * show a popup window with its title.
  343. *
  344. * @param {{x:Number, y:Number}} pointer
  345. * @private
  346. */
  347. Network.prototype._checkShowPopup = function (pointer) {
  348. var obj = {
  349. left: this._XconvertDOMtoCanvas(pointer.x),
  350. top: this._YconvertDOMtoCanvas(pointer.y),
  351. right: this._XconvertDOMtoCanvas(pointer.x),
  352. bottom: this._YconvertDOMtoCanvas(pointer.y)
  353. };
  354. var id;
  355. var previousPopupObjId = this.popupObj === undefined ? "" : this.popupObj.id;
  356. var nodeUnderCursor = false;
  357. var popupType = "node";
  358. if (this.popupObj == undefined) {
  359. // search the nodes for overlap, select the top one in case of multiple nodes
  360. var nodes = this.body.nodes;
  361. var overlappingNodes = [];
  362. for (id in nodes) {
  363. if (nodes.hasOwnProperty(id)) {
  364. var node = nodes[id];
  365. if (node.isOverlappingWith(obj)) {
  366. if (node.getTitle() !== undefined) {
  367. overlappingNodes.push(id);
  368. }
  369. }
  370. }
  371. }
  372. if (overlappingNodes.length > 0) {
  373. // if there are overlapping nodes, select the last one, this is the
  374. // one which is drawn on top of the others
  375. this.popupObj = this.body.nodes[overlappingNodes[overlappingNodes.length - 1]];
  376. // if you hover over a node, the title of the edge is not supposed to be shown.
  377. nodeUnderCursor = true;
  378. }
  379. }
  380. if (this.popupObj === undefined && nodeUnderCursor == false) {
  381. // search the edges for overlap
  382. var edges = this.body.edges;
  383. var overlappingEdges = [];
  384. for (id in edges) {
  385. if (edges.hasOwnProperty(id)) {
  386. var edge = edges[id];
  387. if (edge.connected === true && (edge.getTitle() !== undefined) &&
  388. edge.isOverlappingWith(obj)) {
  389. overlappingEdges.push(id);
  390. }
  391. }
  392. }
  393. if (overlappingEdges.length > 0) {
  394. this.popupObj = this.body.edges[overlappingEdges[overlappingEdges.length - 1]];
  395. popupType = "edge";
  396. }
  397. }
  398. if (this.popupObj) {
  399. // show popup message window
  400. if (this.popupObj.id != previousPopupObjId) {
  401. if (this.popup === undefined) {
  402. this.popup = new Popup(this.frame, this.constants.tooltip);
  403. }
  404. this.popup.popupTargetType = popupType;
  405. this.popup.popupTargetId = this.popupObj.id;
  406. // adjust a small offset such that the mouse cursor is located in the
  407. // bottom left location of the popup, and you can easily move over the
  408. // popup area
  409. this.popup.setPosition(pointer.x + 3, pointer.y - 5);
  410. this.popup.setText(this.popupObj.getTitle());
  411. this.popup.show();
  412. }
  413. }
  414. else {
  415. if (this.popup) {
  416. this.popup.hide();
  417. }
  418. }
  419. };
  420. /**
  421. * Check if the popup must be hidden, which is the case when the mouse is no
  422. * longer hovering on the object
  423. * @param {{x:Number, y:Number}} pointer
  424. * @private
  425. */
  426. Network.prototype._checkHidePopup = function (pointer) {
  427. var pointerObj = {
  428. left: this._XconvertDOMtoCanvas(pointer.x),
  429. top: this._YconvertDOMtoCanvas(pointer.y),
  430. right: this._XconvertDOMtoCanvas(pointer.x),
  431. bottom: this._YconvertDOMtoCanvas(pointer.y)
  432. };
  433. var stillOnObj = false;
  434. if (this.popup.popupTargetType == 'node') {
  435. stillOnObj = this.body.nodes[this.popup.popupTargetId].isOverlappingWith(pointerObj);
  436. if (stillOnObj === true) {
  437. var overNode = this.getNodeAt(pointer);
  438. stillOnObj = overNode.id == this.popup.popupTargetId;
  439. }
  440. }
  441. else {
  442. if (this.getNodeAt(pointer) === null) {
  443. stillOnObj = this.body.edges[this.popup.popupTargetId].isOverlappingWith(pointerObj);
  444. }
  445. }
  446. if (stillOnObj === false) {
  447. this.popupObj = undefined;
  448. this.popup.hide();
  449. }
  450. };
  451. Network.prototype._markAllEdgesAsDirty = function() {
  452. for (var edgeId in this.body.edges) {
  453. this.body.edges[edgeId].colorDirty = true;
  454. }
  455. }
  456. /**
  457. * Reconnect all edges
  458. * @private
  459. */
  460. Network.prototype._reconnectEdges = function() {
  461. var id,
  462. nodes = this.body.nodes,
  463. edges = this.body.edges;
  464. for (id in nodes) {
  465. if (nodes.hasOwnProperty(id)) {
  466. nodes[id].edges = [];
  467. }
  468. }
  469. for (id in edges) {
  470. if (edges.hasOwnProperty(id)) {
  471. var edge = edges[id];
  472. edge.from = null;
  473. edge.to = null;
  474. edge.connect();
  475. }
  476. }
  477. };
  478. /**
  479. * Update the values of all object in the given array according to the current
  480. * value range of the objects in the array.
  481. * @param {Object} obj An object containing a set of Edges or Nodes
  482. * The objects must have a method getValue() and
  483. * setValueRange(min, max).
  484. * @private
  485. */
  486. Network.prototype._updateValueRange = function(obj) {
  487. var id;
  488. // determine the range of the objects
  489. var valueMin = undefined;
  490. var valueMax = undefined;
  491. var valueTotal = 0;
  492. for (id in obj) {
  493. if (obj.hasOwnProperty(id)) {
  494. var value = obj[id].getValue();
  495. if (value !== undefined) {
  496. valueMin = (valueMin === undefined) ? value : Math.min(value, valueMin);
  497. valueMax = (valueMax === undefined) ? value : Math.max(value, valueMax);
  498. valueTotal += value;
  499. }
  500. }
  501. }
  502. // adjust the range of all objects
  503. if (valueMin !== undefined && valueMax !== undefined) {
  504. for (id in obj) {
  505. if (obj.hasOwnProperty(id)) {
  506. obj[id].setValueRange(valueMin, valueMax, valueTotal);
  507. }
  508. }
  509. }
  510. };
  511. /**
  512. * Set the translation of the network
  513. * @param {Number} offsetX Horizontal offset
  514. * @param {Number} offsetY Vertical offset
  515. * @private
  516. */
  517. Network.prototype._setTranslation = function(offsetX, offsetY) {
  518. if (this.translation === undefined) {
  519. this.translation = {
  520. x: 0,
  521. y: 0
  522. };
  523. }
  524. if (offsetX !== undefined) {
  525. this.translation.x = offsetX;
  526. }
  527. if (offsetY !== undefined) {
  528. this.translation.y = offsetY;
  529. }
  530. this.emit('viewChanged');
  531. };
  532. /**
  533. * Get the translation of the network
  534. * @return {Object} translation An object with parameters x and y, both a number
  535. * @private
  536. */
  537. Network.prototype._getTranslation = function() {
  538. return {
  539. x: this.translation.x,
  540. y: this.translation.y
  541. };
  542. };
  543. /**
  544. * Scale the network
  545. * @param {Number} scale Scaling factor 1.0 is unscaled
  546. * @private
  547. */
  548. Network.prototype._setScale = function(scale) {
  549. this.scale = scale;
  550. };
  551. /**
  552. * Get the current scale of the network
  553. * @return {Number} scale Scaling factor 1.0 is unscaled
  554. * @private
  555. */
  556. Network.prototype._getScale = function() {
  557. return this.scale;
  558. };
  559. /**
  560. * Move the network according to the keyboard presses.
  561. *
  562. * @private
  563. */
  564. Network.prototype._handleNavigation = function() {
  565. if (this.xIncrement != 0 || this.yIncrement != 0) {
  566. var translation = this._getTranslation();
  567. this._setTranslation(translation.x+this.xIncrement, translation.y+this.yIncrement);
  568. }
  569. if (this.zoomIncrement != 0) {
  570. var center = {
  571. x: this.frame.canvas.clientWidth / 2,
  572. y: this.frame.canvas.clientHeight / 2
  573. };
  574. this.zoom(this.scale*(1 + this.zoomIncrement), center);
  575. }
  576. };
  577. /**
  578. * Freeze the _animationStep
  579. */
  580. Network.prototype.freezeSimulation = function(freeze) {
  581. if (freeze == true) {
  582. this.freezeSimulationEnabled = true;
  583. this.moving = false;
  584. }
  585. else {
  586. this.freezeSimulationEnabled = false;
  587. this.moving = true;
  588. this.start();
  589. }
  590. };
  591. /**
  592. * This function cleans the support nodes if they are not needed and adds them when they are.
  593. *
  594. * @param {boolean} [disableStart]
  595. * @private
  596. */
  597. Network.prototype._configureSmoothCurves = function(disableStart = true) {
  598. if (this.constants.smoothCurves.enabled == true && this.constants.smoothCurves.dynamic == true) {
  599. this._createBezierNodes();
  600. // cleanup unused support nodes
  601. for (let i = 0; i < this.body.supportNodeIndices.length; i++) {
  602. let nodeId = this.body.supportNodeIndices[i];
  603. // delete support nodes for edges that have been deleted
  604. if (this.body.edges[this.body.supportNodes[nodeId].parentEdgeId] === undefined) {
  605. delete this.body.supportNodes[nodeId];
  606. }
  607. }
  608. }
  609. else {
  610. // delete the support nodes
  611. this.body.supportNodes = {};
  612. for (var edgeId in this.body.edges) {
  613. if (this.body.edges.hasOwnProperty(edgeId)) {
  614. this.body.edges[edgeId].via = null;
  615. }
  616. }
  617. }
  618. this._updateNodeIndexList();
  619. this.physics._updateCalculationNodes();
  620. if (!disableStart) {
  621. this.moving = true;
  622. this.start();
  623. }
  624. };
  625. /**
  626. * load the functions that load the mixins into the prototype.
  627. *
  628. * @private
  629. */
  630. Network.prototype._initializeMixinLoaders = function () {
  631. for (var mixin in MixinLoader) {
  632. if (MixinLoader.hasOwnProperty(mixin)) {
  633. Network.prototype[mixin] = MixinLoader[mixin];
  634. }
  635. }
  636. };
  637. /**
  638. * Load the XY positions of the nodes into the dataset.
  639. */
  640. Network.prototype.storePosition = function() {
  641. console.log("storePosition is depricated: use .storePositions() from now on.")
  642. this.storePositions();
  643. };
  644. /**
  645. * Load the XY positions of the nodes into the dataset.
  646. */
  647. Network.prototype.storePositions = function() {
  648. var dataArray = [];
  649. for (var nodeId in this.body.nodes) {
  650. if (this.body.nodes.hasOwnProperty(nodeId)) {
  651. var node = this.body.nodes[nodeId];
  652. var allowedToMoveX = !this.body.nodes.xFixed;
  653. var allowedToMoveY = !this.body.nodes.yFixed;
  654. if (this.body.data.nodes._data[nodeId].x != Math.round(node.x) || this.body.data.nodes._data[nodeId].y != Math.round(node.y)) {
  655. dataArray.push({id:nodeId,x:Math.round(node.x),y:Math.round(node.y),allowedToMoveX:allowedToMoveX,allowedToMoveY:allowedToMoveY});
  656. }
  657. }
  658. }
  659. this.body.data.nodes.update(dataArray);
  660. };
  661. /**
  662. * Return the positions of the nodes.
  663. */
  664. Network.prototype.getPositions = function(ids) {
  665. var dataArray = {};
  666. if (ids !== undefined) {
  667. if (Array.isArray(ids) == true) {
  668. for (var i = 0; i < ids.length; i++) {
  669. if (this.body.nodes[ids[i]] !== undefined) {
  670. var node = this.body.nodes[ids[i]];
  671. dataArray[ids[i]] = {x: Math.round(node.x), y: Math.round(node.y)};
  672. }
  673. }
  674. }
  675. else {
  676. if (this.body.nodes[ids] !== undefined) {
  677. var node = this.body.nodes[ids];
  678. dataArray[ids] = {x: Math.round(node.x), y: Math.round(node.y)};
  679. }
  680. }
  681. }
  682. else {
  683. for (var nodeId in this.body.nodes) {
  684. if (this.body.nodes.hasOwnProperty(nodeId)) {
  685. var node = this.body.nodes[nodeId];
  686. dataArray[nodeId] = {x: Math.round(node.x), y: Math.round(node.y)};
  687. }
  688. }
  689. }
  690. return dataArray;
  691. };
  692. /**
  693. * Returns true when the Network is active.
  694. * @returns {boolean}
  695. */
  696. Network.prototype.isActive = function () {
  697. return !this.activator || this.activator.active;
  698. };
  699. /**
  700. * Sets the scale
  701. * @returns {Number}
  702. */
  703. Network.prototype.setScale = function () {
  704. return this._setScale();
  705. };
  706. /**
  707. * Returns the scale
  708. * @returns {Number}
  709. */
  710. Network.prototype.getScale = function () {
  711. return this._getScale();
  712. };
  713. /**
  714. * Check if a node is a cluster.
  715. * @param nodeId
  716. * @returns {*}
  717. */
  718. Network.prototype.isCluster = function(nodeId) {
  719. if (this.body.nodes[nodeId] !== undefined) {
  720. return this.body.nodes[nodeId].isCluster;
  721. }
  722. else {
  723. console.log("Node does not exist.")
  724. return false;
  725. }
  726. };
  727. /**
  728. * Returns the scale
  729. * @returns {Number}
  730. */
  731. Network.prototype.getCenterCoordinates = function () {
  732. return this.DOMtoCanvas({x: 0.5 * this.frame.canvas.clientWidth, y: 0.5 * this.frame.canvas.clientHeight});
  733. };
  734. Network.prototype.getBoundingBox = function(nodeId) {
  735. if (this.body.nodes[nodeId] !== undefined) {
  736. return this.body.nodes[nodeId].boundingBox;
  737. }
  738. }
  739. Network.prototype.getConnectedNodes = function(nodeId) {
  740. var nodeList = [];
  741. if (this.body.nodes[nodeId] !== undefined) {
  742. var node = this.body.nodes[nodeId];
  743. var nodeObj = {nodeId : true}; // used to quickly check if node already exists
  744. for (var i = 0; i < node.edges.length; i++) {
  745. var edge = node.edges[i];
  746. if (edge.toId == nodeId) {
  747. if (nodeObj[edge.fromId] === undefined) {
  748. nodeList.push(edge.fromId);
  749. nodeObj[edge.fromId] = true;
  750. }
  751. }
  752. else if (edge.fromId == nodeId) {
  753. if (nodeObj[edge.toId] === undefined) {
  754. nodeList.push(edge.toId)
  755. nodeObj[edge.toId] = true;
  756. }
  757. }
  758. }
  759. }
  760. return nodeList;
  761. }
  762. Network.prototype.getEdgesFromNode = function(nodeId) {
  763. var edgesList = [];
  764. if (this.body.nodes[nodeId] !== undefined) {
  765. var node = this.body.nodes[nodeId];
  766. for (var i = 0; i < node.edges.length; i++) {
  767. edgesList.push(node.edges[i].id);
  768. }
  769. }
  770. return edgesList;
  771. }
  772. Network.prototype.generateColorObject = function(color) {
  773. return util.parseColor(color);
  774. }
  775. module.exports = Network;