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.

2637 lines
79 KiB

10 years ago
10 years ago
10 years ago
9 years ago
10 years ago
10 years ago
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 keycharm = require('keycharm');
  4. var util = require('../util');
  5. var hammerUtil = require('../hammerUtil');
  6. var DataSet = require('../DataSet');
  7. var DataView = require('../DataView');
  8. var dotparser = require('./dotparser');
  9. var gephiParser = require('./gephiParser');
  10. var Groups = require('./Groups');
  11. var Images = require('./Images');
  12. var Node = require('./Node');
  13. var Edge = require('./Edge');
  14. var Popup = require('./Popup');
  15. var MixinLoader = require('./mixins/MixinLoader');
  16. var Activator = require('../shared/Activator');
  17. var locales = require('./locales');
  18. // Load custom shapes into CanvasRenderingContext2D
  19. require('./shapes');
  20. /**
  21. * @constructor Network
  22. * Create a network visualization, displaying nodes and edges.
  23. *
  24. * @param {Element} container The DOM element in which the Network will
  25. * be created. Normally a div element.
  26. * @param {Object} data An object containing parameters
  27. * {Array} nodes
  28. * {Array} edges
  29. * @param {Object} options Options
  30. */
  31. function Network (container, data, options) {
  32. if (!(this instanceof Network)) {
  33. throw new SyntaxError('Constructor must be called with the new operator');
  34. }
  35. this._initializeMixinLoaders();
  36. // create variables and set default values
  37. this.containerElement = container;
  38. // render and calculation settings
  39. this.renderRefreshRate = 60; // hz (fps)
  40. this.renderTimestep = 1000 / this.renderRefreshRate; // ms -- saves calculation later on
  41. this.renderTime = 0.5 * this.renderTimestep; // measured time it takes to render a frame
  42. this.maxPhysicsTicksPerRender = 3; // max amount of physics ticks per render step.
  43. this.physicsDiscreteStepsize = 0.50; // discrete stepsize of the simulation
  44. this.initializing = true;
  45. this.triggerFunctions = {add:null,edit:null,editEdge:null,connect:null,del:null};
  46. // set constant values
  47. this.defaultOptions = {
  48. nodes: {
  49. mass: 1,
  50. radiusMin: 10,
  51. radiusMax: 30,
  52. radius: 10,
  53. shape: 'ellipse',
  54. image: undefined,
  55. widthMin: 16, // px
  56. widthMax: 64, // px
  57. fontColor: 'black',
  58. fontSize: 14, // px
  59. fontFace: 'verdana',
  60. fontFill: undefined,
  61. level: -1,
  62. color: {
  63. border: '#2B7CE9',
  64. background: '#97C2FC',
  65. highlight: {
  66. border: '#2B7CE9',
  67. background: '#D2E5FF'
  68. },
  69. hover: {
  70. border: '#2B7CE9',
  71. background: '#D2E5FF'
  72. }
  73. },
  74. group: undefined,
  75. borderWidth: 1,
  76. borderWidthSelected: undefined
  77. },
  78. edges: {
  79. widthMin: 1, //
  80. widthMax: 15,//
  81. width: 1,
  82. widthSelectionMultiplier: 2,
  83. hoverWidth: 1.5,
  84. style: 'line',
  85. color: {
  86. color:'#848484',
  87. highlight:'#848484',
  88. hover: '#848484'
  89. },
  90. fontColor: '#343434',
  91. fontSize: 14, // px
  92. fontFace: 'arial',
  93. fontFill: 'white',
  94. arrowScaleFactor: 1,
  95. dash: {
  96. length: 10,
  97. gap: 5,
  98. altLength: undefined
  99. },
  100. inheritColor: "from" // to, from, false, true (== from)
  101. },
  102. configurePhysics:false,
  103. physics: {
  104. barnesHut: {
  105. enabled: true,
  106. thetaInverted: 1 / 0.5, // inverted to save time during calculation
  107. gravitationalConstant: -2000,
  108. centralGravity: 0.3,
  109. springLength: 95,
  110. springConstant: 0.04,
  111. damping: 0.09
  112. },
  113. repulsion: {
  114. centralGravity: 0.0,
  115. springLength: 200,
  116. springConstant: 0.05,
  117. nodeDistance: 100,
  118. damping: 0.09
  119. },
  120. hierarchicalRepulsion: {
  121. enabled: false,
  122. centralGravity: 0.0,
  123. springLength: 100,
  124. springConstant: 0.01,
  125. nodeDistance: 150,
  126. damping: 0.09
  127. },
  128. damping: null,
  129. centralGravity: null,
  130. springLength: null,
  131. springConstant: null
  132. },
  133. clustering: { // Per Node in Cluster = PNiC
  134. enabled: false, // (Boolean) | global on/off switch for clustering.
  135. initialMaxNodes: 100, // (# nodes) | if the initial amount of nodes is larger than this, we cluster until the total number is less than this threshold.
  136. clusterThreshold:500, // (# nodes) | during calculate forces, we check if the total number of nodes is larger than this. If it is, cluster until reduced to reduceToNodes
  137. reduceToNodes:300, // (# nodes) | during calculate forces, we check if the total number of nodes is larger than clusterThreshold. If it is, cluster until reduced to this
  138. chainThreshold: 0.4, // (% of all drawn nodes)| maximum percentage of allowed chainnodes (long strings of connected nodes) within all nodes. (lower means less chains).
  139. clusterEdgeThreshold: 20, // (px) | edge length threshold. if smaller, this node is clustered.
  140. sectorThreshold: 100, // (# nodes in cluster) | cluster size threshold. If larger, expanding in own sector.
  141. screenSizeThreshold: 0.2, // (% of canvas) | relative size threshold. If the width or height of a clusternode takes up this much of the screen, decluster node.
  142. fontSizeMultiplier: 4.0, // (px PNiC) | how much the cluster font size grows per node in cluster (in px).
  143. maxFontSize: 1000,
  144. forceAmplification: 0.1, // (multiplier PNiC) | factor of increase fo the repulsion force of a cluster (per node in cluster).
  145. distanceAmplification: 0.1, // (multiplier PNiC) | factor how much the repulsion distance of a cluster increases (per node in cluster).
  146. edgeGrowth: 20, // (px PNiC) | amount of clusterSize connected to the edge is multiplied with this and added to edgeLength.
  147. nodeScaling: {width: 1, // (px PNiC) | growth of the width per node in cluster.
  148. height: 1, // (px PNiC) | growth of the height per node in cluster.
  149. radius: 1}, // (px PNiC) | growth of the radius per node in cluster.
  150. maxNodeSizeIncrements: 600, // (# increments) | max growth of the width per node in cluster.
  151. activeAreaBoxSize: 80, // (px) | box area around the curser where clusters are popped open.
  152. clusterLevelDifference: 2
  153. },
  154. navigation: {
  155. enabled: false
  156. },
  157. keyboard: {
  158. enabled: false,
  159. speed: {x: 10, y: 10, zoom: 0.02}
  160. },
  161. dataManipulation: {
  162. enabled: false,
  163. initiallyVisible: false
  164. },
  165. hierarchicalLayout: {
  166. enabled:false,
  167. levelSeparation: 150,
  168. nodeSpacing: 100,
  169. direction: "UD", // UD, DU, LR, RL
  170. layout: "hubsize" // hubsize, directed
  171. },
  172. freezeForStabilization: false,
  173. smoothCurves: {
  174. enabled: true,
  175. dynamic: true,
  176. type: "continuous",
  177. roundness: 0.5
  178. },
  179. maxVelocity: 30,
  180. minVelocity: 0.1, // px/s
  181. stabilize: true, // stabilize before displaying the network
  182. stabilizationIterations: 1000, // maximum number of iteration to stabilize
  183. zoomExtentOnStabilize: true,
  184. locale: 'en',
  185. locales: locales,
  186. tooltip: {
  187. delay: 300,
  188. fontColor: 'black',
  189. fontSize: 14, // px
  190. fontFace: 'verdana',
  191. color: {
  192. border: '#666',
  193. background: '#FFFFC6'
  194. }
  195. },
  196. dragNetwork: true,
  197. dragNodes: true,
  198. zoomable: true,
  199. hover: false,
  200. hideEdgesOnDrag: false,
  201. hideNodesOnDrag: false,
  202. width : '100%',
  203. height : '100%',
  204. selectable: true
  205. };
  206. this.constants = util.extend({}, this.defaultOptions);
  207. this.pixelRatio = 1;
  208. this.hoverObj = {nodes:{},edges:{}};
  209. this.controlNodesActive = false;
  210. this.navigationHammers = {existing:[], _new: []};
  211. // animation properties
  212. this.animationSpeed = 1/this.renderRefreshRate;
  213. this.animationEasingFunction = "easeInOutQuint";
  214. this.easingTime = 0;
  215. this.sourceScale = 0;
  216. this.targetScale = 0;
  217. this.sourceTranslation = 0;
  218. this.targetTranslation = 0;
  219. this.lockedOnNodeId = null;
  220. this.lockedOnNodeOffset = null;
  221. this.touchTime = 0;
  222. // Node variables
  223. var network = this;
  224. this.groups = new Groups(); // object with groups
  225. this.images = new Images(); // object with images
  226. this.images.setOnloadCallback(function () {
  227. network._redraw();
  228. });
  229. // keyboard navigation variables
  230. this.xIncrement = 0;
  231. this.yIncrement = 0;
  232. this.zoomIncrement = 0;
  233. // loading all the mixins:
  234. // load the force calculation functions, grouped under the physics system.
  235. this._loadPhysicsSystem();
  236. // create a frame and canvas
  237. this._create();
  238. // load the sector system. (mandatory, fully integrated with Network)
  239. this._loadSectorSystem();
  240. // load the cluster system. (mandatory, even when not using the cluster system, there are function calls to it)
  241. this._loadClusterSystem();
  242. // load the selection system. (mandatory, required by Network)
  243. this._loadSelectionSystem();
  244. // load the selection system. (mandatory, required by Network)
  245. this._loadHierarchySystem();
  246. // apply options
  247. this._setTranslation(this.frame.clientWidth / 2, this.frame.clientHeight / 2);
  248. this._setScale(1);
  249. this.setOptions(options);
  250. // other vars
  251. this.freezeSimulation = false;// freeze the simulation
  252. this.cachedFunctions = {};
  253. this.startedStabilization = false;
  254. this.stabilized = false;
  255. this.stabilizationIterations = null;
  256. this.draggingNodes = false;
  257. // containers for nodes and edges
  258. this.calculationNodes = {};
  259. this.calculationNodeIndices = [];
  260. this.nodeIndices = []; // array with all the indices of the nodes. Used to speed up forces calculation
  261. this.nodes = {}; // object with Node objects
  262. this.edges = {}; // object with Edge objects
  263. // position and scale variables and objects
  264. this.canvasTopLeft = {"x": 0,"y": 0}; // coordinates of the top left of the canvas. they will be set during _redraw.
  265. this.canvasBottomRight = {"x": 0,"y": 0}; // coordinates of the bottom right of the canvas. they will be set during _redraw
  266. this.pointerPosition = {"x": 0,"y": 0}; // coordinates of the bottom right of the canvas. they will be set during _redraw
  267. this.areaCenter = {}; // object with x and y elements used for determining the center of the zoom action
  268. this.scale = 1; // defining the global scale variable in the constructor
  269. this.previousScale = this.scale; // this is used to check if the zoom operation is zooming in or out
  270. // datasets or dataviews
  271. this.nodesData = null; // A DataSet or DataView
  272. this.edgesData = null; // A DataSet or DataView
  273. // create event listeners used to subscribe on the DataSets of the nodes and edges
  274. this.nodesListeners = {
  275. 'add': function (event, params) {
  276. network._addNodes(params.items);
  277. network.start();
  278. },
  279. 'update': function (event, params) {
  280. network._updateNodes(params.items, params.data);
  281. network.start();
  282. },
  283. 'remove': function (event, params) {
  284. network._removeNodes(params.items);
  285. network.start();
  286. }
  287. };
  288. this.edgesListeners = {
  289. 'add': function (event, params) {
  290. network._addEdges(params.items);
  291. network.start();
  292. },
  293. 'update': function (event, params) {
  294. network._updateEdges(params.items);
  295. network.start();
  296. },
  297. 'remove': function (event, params) {
  298. network._removeEdges(params.items);
  299. network.start();
  300. }
  301. };
  302. // properties for the animation
  303. this.moving = true;
  304. this.timer = undefined; // Scheduling function. Is definded in this.start();
  305. // load data (the disable start variable will be the same as the enabled clustering)
  306. this.setData(data,this.constants.clustering.enabled || this.constants.hierarchicalLayout.enabled);
  307. // hierarchical layout
  308. this.initializing = false;
  309. if (this.constants.hierarchicalLayout.enabled == true) {
  310. this._setupHierarchicalLayout();
  311. }
  312. else {
  313. // zoom so all data will fit on the screen, if clustering is enabled, we do not want start to be called here.
  314. if (this.constants.stabilize == false) {
  315. this.zoomExtent(undefined, true,this.constants.clustering.enabled);
  316. }
  317. }
  318. // if clustering is disabled, the simulation will have started in the setData function
  319. if (this.constants.clustering.enabled) {
  320. this.startWithClustering();
  321. }
  322. }
  323. // Extend Network with an Emitter mixin
  324. Emitter(Network.prototype);
  325. /**
  326. * Get the script path where the vis.js library is located
  327. *
  328. * @returns {string | null} path Path or null when not found. Path does not
  329. * end with a slash.
  330. * @private
  331. */
  332. Network.prototype._getScriptPath = function() {
  333. var scripts = document.getElementsByTagName( 'script' );
  334. // find script named vis.js or vis.min.js
  335. for (var i = 0; i < scripts.length; i++) {
  336. var src = scripts[i].src;
  337. var match = src && /\/?vis(.min)?\.js$/.exec(src);
  338. if (match) {
  339. // return path without the script name
  340. return src.substring(0, src.length - match[0].length);
  341. }
  342. }
  343. return null;
  344. };
  345. /**
  346. * Find the center position of the network
  347. * @private
  348. */
  349. Network.prototype._getRange = function() {
  350. var minY = 1e9, maxY = -1e9, minX = 1e9, maxX = -1e9, node;
  351. for (var nodeId in this.nodes) {
  352. if (this.nodes.hasOwnProperty(nodeId)) {
  353. node = this.nodes[nodeId];
  354. if (minX > (node.boundingBox.left)) {minX = node.boundingBox.left;}
  355. if (maxX < (node.boundingBox.right)) {maxX = node.boundingBox.right;}
  356. if (minY > (node.boundingBox.bottom)) {minY = node.boundingBox.bottom;}
  357. if (maxY < (node.boundingBox.top)) {maxY = node.boundingBox.top;}
  358. }
  359. }
  360. if (minX == 1e9 && maxX == -1e9 && minY == 1e9 && maxY == -1e9) {
  361. minY = 0, maxY = 0, minX = 0, maxX = 0;
  362. }
  363. return {minX: minX, maxX: maxX, minY: minY, maxY: maxY};
  364. };
  365. /**
  366. * @param {object} range = {minX: minX, maxX: maxX, minY: minY, maxY: maxY};
  367. * @returns {{x: number, y: number}}
  368. * @private
  369. */
  370. Network.prototype._findCenter = function(range) {
  371. return {x: (0.5 * (range.maxX + range.minX)),
  372. y: (0.5 * (range.maxY + range.minY))};
  373. };
  374. /**
  375. * This function zooms out to fit all data on screen based on amount of nodes
  376. *
  377. * @param {Boolean} [initialZoom] | zoom based on fitted formula or range, true = fitted, default = false;
  378. * @param {Boolean} [disableStart] | If true, start is not called.
  379. */
  380. Network.prototype.zoomExtent = function(animationOptions, initialZoom, disableStart) {
  381. this._redraw(true);
  382. if (initialZoom === undefined) {
  383. initialZoom = false;
  384. }
  385. if (disableStart === undefined) {
  386. disableStart = false;
  387. }
  388. if (animationOptions === undefined) {
  389. animationOptions = false;
  390. }
  391. var range = this._getRange();
  392. var zoomLevel;
  393. if (initialZoom == true) {
  394. var numberOfNodes = this.nodeIndices.length;
  395. if (this.constants.smoothCurves == true) {
  396. if (this.constants.clustering.enabled == true &&
  397. numberOfNodes >= this.constants.clustering.initialMaxNodes) {
  398. zoomLevel = 49.07548 / (numberOfNodes + 142.05338) + 9.1444e-04; // this is obtained from fitting a dataset from 5 points with scale levels that looked good.
  399. }
  400. else {
  401. zoomLevel = 12.662 / (numberOfNodes + 7.4147) + 0.0964822; // this is obtained from fitting a dataset from 5 points with scale levels that looked good.
  402. }
  403. }
  404. else {
  405. if (this.constants.clustering.enabled == true &&
  406. numberOfNodes >= this.constants.clustering.initialMaxNodes) {
  407. zoomLevel = 77.5271985 / (numberOfNodes + 187.266146) + 4.76710517e-05; // this is obtained from fitting a dataset from 5 points with scale levels that looked good.
  408. }
  409. else {
  410. zoomLevel = 30.5062972 / (numberOfNodes + 19.93597763) + 0.08413486; // this is obtained from fitting a dataset from 5 points with scale levels that looked good.
  411. }
  412. }
  413. // correct for larger canvasses.
  414. var factor = Math.min(this.frame.canvas.clientWidth / 600, this.frame.canvas.clientHeight / 600);
  415. zoomLevel *= factor;
  416. }
  417. else {
  418. var xDistance = Math.abs(range.maxX - range.minX) * 1.1;
  419. var yDistance = Math.abs(range.maxY - range.minY) * 1.1;
  420. var xZoomLevel = this.frame.canvas.clientWidth / xDistance;
  421. var yZoomLevel = this.frame.canvas.clientHeight / yDistance;
  422. zoomLevel = (xZoomLevel <= yZoomLevel) ? xZoomLevel : yZoomLevel;
  423. }
  424. if (zoomLevel > 1.0) {
  425. zoomLevel = 1.0;
  426. }
  427. var center = this._findCenter(range);
  428. if (disableStart == false) {
  429. var options = {position: center, scale: zoomLevel, animation: animationOptions};
  430. this.moveTo(options);
  431. this.moving = true;
  432. this.start();
  433. }
  434. else {
  435. center.x *= zoomLevel;
  436. center.y *= zoomLevel;
  437. center.x -= 0.5 * this.frame.canvas.clientWidth;
  438. center.y -= 0.5 * this.frame.canvas.clientHeight;
  439. this._setScale(zoomLevel);
  440. this._setTranslation(-center.x,-center.y);
  441. }
  442. };
  443. /**
  444. * Update the this.nodeIndices with the most recent node index list
  445. * @private
  446. */
  447. Network.prototype._updateNodeIndexList = function() {
  448. this._clearNodeIndexList();
  449. for (var idx in this.nodes) {
  450. if (this.nodes.hasOwnProperty(idx)) {
  451. this.nodeIndices.push(idx);
  452. }
  453. }
  454. };
  455. /**
  456. * Set nodes and edges, and optionally options as well.
  457. *
  458. * @param {Object} data Object containing parameters:
  459. * {Array | DataSet | DataView} [nodes] Array with nodes
  460. * {Array | DataSet | DataView} [edges] Array with edges
  461. * {String} [dot] String containing data in DOT format
  462. * {String} [gephi] String containing data in gephi JSON format
  463. * {Options} [options] Object with options
  464. * @param {Boolean} [disableStart] | optional: disable the calling of the start function.
  465. */
  466. Network.prototype.setData = function(data, disableStart) {
  467. if (disableStart === undefined) {
  468. disableStart = false;
  469. }
  470. // we set initializing to true to ensure that the hierarchical layout is not performed until both nodes and edges are added.
  471. this.initializing = true;
  472. if (data && data.dot && (data.nodes || data.edges)) {
  473. throw new SyntaxError('Data must contain either parameter "dot" or ' +
  474. ' parameter pair "nodes" and "edges", but not both.');
  475. }
  476. // set options
  477. this.setOptions(data && data.options);
  478. // set all data
  479. if (data && data.dot) {
  480. // parse DOT file
  481. if(data && data.dot) {
  482. var dotData = dotparser.DOTToGraph(data.dot);
  483. this.setData(dotData);
  484. return;
  485. }
  486. }
  487. else if (data && data.gephi) {
  488. // parse DOT file
  489. if(data && data.gephi) {
  490. var gephiData = gephiParser.parseGephi(data.gephi);
  491. this.setData(gephiData);
  492. return;
  493. }
  494. }
  495. else {
  496. this._setNodes(data && data.nodes);
  497. this._setEdges(data && data.edges);
  498. }
  499. this._putDataInSector();
  500. if (disableStart == false) {
  501. if (this.constants.hierarchicalLayout.enabled == true) {
  502. this._resetLevels();
  503. this._setupHierarchicalLayout();
  504. }
  505. else {
  506. // find a stable position or start animating to a stable position
  507. if (this.constants.stabilize) {
  508. this._stabilize();
  509. }
  510. }
  511. this.start();
  512. }
  513. this.initializing = false;
  514. };
  515. /**
  516. * Set options
  517. * @param {Object} options
  518. */
  519. Network.prototype.setOptions = function (options) {
  520. if (options) {
  521. var prop;
  522. var fields = ['nodes','edges','smoothCurves','hierarchicalLayout','clustering','navigation',
  523. 'keyboard','dataManipulation','onAdd','onEdit','onEditEdge','onConnect','onDelete','clickToUse'
  524. ];
  525. // extend all but the values in fields
  526. util.selectiveNotDeepExtend(fields,this.constants, options);
  527. util.selectiveNotDeepExtend(['color'],this.constants.nodes, options.nodes);
  528. util.selectiveNotDeepExtend(['color','length'],this.constants.edges, options.edges);
  529. if (options.physics) {
  530. util.mergeOptions(this.constants.physics, options.physics,'barnesHut');
  531. util.mergeOptions(this.constants.physics, options.physics,'repulsion');
  532. if (options.physics.hierarchicalRepulsion) {
  533. this.constants.hierarchicalLayout.enabled = true;
  534. this.constants.physics.hierarchicalRepulsion.enabled = true;
  535. this.constants.physics.barnesHut.enabled = false;
  536. for (prop in options.physics.hierarchicalRepulsion) {
  537. if (options.physics.hierarchicalRepulsion.hasOwnProperty(prop)) {
  538. this.constants.physics.hierarchicalRepulsion[prop] = options.physics.hierarchicalRepulsion[prop];
  539. }
  540. }
  541. }
  542. }
  543. if (options.onAdd) {this.triggerFunctions.add = options.onAdd;}
  544. if (options.onEdit) {this.triggerFunctions.edit = options.onEdit;}
  545. if (options.onEditEdge) {this.triggerFunctions.editEdge = options.onEditEdge;}
  546. if (options.onConnect) {this.triggerFunctions.connect = options.onConnect;}
  547. if (options.onDelete) {this.triggerFunctions.del = options.onDelete;}
  548. util.mergeOptions(this.constants, options,'smoothCurves');
  549. util.mergeOptions(this.constants, options,'hierarchicalLayout');
  550. util.mergeOptions(this.constants, options,'clustering');
  551. util.mergeOptions(this.constants, options,'navigation');
  552. util.mergeOptions(this.constants, options,'keyboard');
  553. util.mergeOptions(this.constants, options,'dataManipulation');
  554. if (options.dataManipulation) {
  555. this.editMode = this.constants.dataManipulation.initiallyVisible;
  556. }
  557. // TODO: work out these options and document them
  558. if (options.edges) {
  559. if (options.edges.color !== undefined) {
  560. if (util.isString(options.edges.color)) {
  561. this.constants.edges.color = {};
  562. this.constants.edges.color.color = options.edges.color;
  563. this.constants.edges.color.highlight = options.edges.color;
  564. this.constants.edges.color.hover = options.edges.color;
  565. }
  566. else {
  567. if (options.edges.color.color !== undefined) {this.constants.edges.color.color = options.edges.color.color;}
  568. if (options.edges.color.highlight !== undefined) {this.constants.edges.color.highlight = options.edges.color.highlight;}
  569. if (options.edges.color.hover !== undefined) {this.constants.edges.color.hover = options.edges.color.hover;}
  570. }
  571. this.constants.edges.inheritColor = false;
  572. }
  573. if (!options.edges.fontColor) {
  574. if (options.edges.color !== undefined) {
  575. if (util.isString(options.edges.color)) {this.constants.edges.fontColor = options.edges.color;}
  576. else if (options.edges.color.color !== undefined) {this.constants.edges.fontColor = options.edges.color.color;}
  577. }
  578. }
  579. }
  580. if (options.nodes) {
  581. if (options.nodes.color) {
  582. var newColorObj = util.parseColor(options.nodes.color);
  583. this.constants.nodes.color.background = newColorObj.background;
  584. this.constants.nodes.color.border = newColorObj.border;
  585. this.constants.nodes.color.highlight.background = newColorObj.highlight.background;
  586. this.constants.nodes.color.highlight.border = newColorObj.highlight.border;
  587. this.constants.nodes.color.hover.background = newColorObj.hover.background;
  588. this.constants.nodes.color.hover.border = newColorObj.hover.border;
  589. }
  590. }
  591. if (options.groups) {
  592. for (var groupname in options.groups) {
  593. if (options.groups.hasOwnProperty(groupname)) {
  594. var group = options.groups[groupname];
  595. this.groups.add(groupname, group);
  596. }
  597. }
  598. }
  599. if (options.tooltip) {
  600. for (prop in options.tooltip) {
  601. if (options.tooltip.hasOwnProperty(prop)) {
  602. this.constants.tooltip[prop] = options.tooltip[prop];
  603. }
  604. }
  605. if (options.tooltip.color) {
  606. this.constants.tooltip.color = util.parseColor(options.tooltip.color);
  607. }
  608. }
  609. if ('clickToUse' in options) {
  610. if (options.clickToUse) {
  611. if (!this.activator) {
  612. this.activator = new Activator(this.frame);
  613. this.activator.on('change', this._createKeyBinds.bind(this));
  614. }
  615. }
  616. else {
  617. if (this.activator) {
  618. this.activator.destroy();
  619. delete this.activator;
  620. }
  621. }
  622. }
  623. if (options.labels) {
  624. throw new Error('Option "labels" is deprecated. Use options "locale" and "locales" instead.');
  625. }
  626. // (Re)loading the mixins that can be enabled or disabled in the options.
  627. // load the force calculation functions, grouped under the physics system.
  628. this._loadPhysicsSystem();
  629. // load the navigation system.
  630. this._loadNavigationControls();
  631. // load the data manipulation system
  632. this._loadManipulationSystem();
  633. // configure the smooth curves
  634. this._configureSmoothCurves();
  635. // bind keys. If disabled, this will not do anything;
  636. this._createKeyBinds();
  637. this.setSize(this.constants.width, this.constants.height);
  638. this.moving = true;
  639. this.start();
  640. }
  641. };
  642. /**
  643. * Create the main frame for the Network.
  644. * This function is executed once when a Network object is created. The frame
  645. * contains a canvas, and this canvas contains all objects like the axis and
  646. * nodes.
  647. * @private
  648. */
  649. Network.prototype._create = function () {
  650. // remove all elements from the container element.
  651. while (this.containerElement.hasChildNodes()) {
  652. this.containerElement.removeChild(this.containerElement.firstChild);
  653. }
  654. this.frame = document.createElement('div');
  655. this.frame.className = 'vis network-frame';
  656. this.frame.style.position = 'relative';
  657. this.frame.style.overflow = 'hidden';
  658. //////////////////////////////////////////////////////////////////
  659. this.frame.canvas = document.createElement("canvas");
  660. this.frame.canvas.style.position = 'relative';
  661. this.frame.appendChild(this.frame.canvas);
  662. if (!this.frame.canvas.getContext) {
  663. var noCanvas = document.createElement( 'DIV' );
  664. noCanvas.style.color = 'red';
  665. noCanvas.style.fontWeight = 'bold' ;
  666. noCanvas.style.padding = '10px';
  667. noCanvas.innerHTML = 'Error: your browser does not support HTML canvas';
  668. this.frame.canvas.appendChild(noCanvas);
  669. }
  670. else {
  671. var ctx = this.frame.canvas.getContext("2d");
  672. this.pixelRatio = (window.devicePixelRatio || 1) / (ctx.webkitBackingStorePixelRatio ||
  673. ctx.mozBackingStorePixelRatio ||
  674. ctx.msBackingStorePixelRatio ||
  675. ctx.oBackingStorePixelRatio ||
  676. ctx.backingStorePixelRatio || 1);
  677. this.frame.canvas.getContext("2d").setTransform(this.pixelRatio, 0, 0, this.pixelRatio, 0, 0);
  678. }
  679. //////////////////////////////////////////////////////////////////
  680. var me = this;
  681. this.drag = {};
  682. this.pinch = {};
  683. this.hammer = Hammer(this.frame.canvas, {
  684. prevent_default: true
  685. });
  686. this.hammer.on('tap', me._onTap.bind(me) );
  687. this.hammer.on('doubletap', me._onDoubleTap.bind(me) );
  688. this.hammer.on('hold', me._onHold.bind(me) );
  689. this.hammer.on('pinch', me._onPinch.bind(me) );
  690. this.hammer.on('touch', me._onTouch.bind(me) );
  691. this.hammer.on('dragstart', me._onDragStart.bind(me) );
  692. this.hammer.on('drag', me._onDrag.bind(me) );
  693. this.hammer.on('dragend', me._onDragEnd.bind(me) );
  694. this.hammer.on('mousewheel',me._onMouseWheel.bind(me) );
  695. this.hammer.on('DOMMouseScroll',me._onMouseWheel.bind(me) ); // for FF
  696. this.hammer.on('mousemove', me._onMouseMoveTitle.bind(me) );
  697. this.hammerFrame = Hammer(this.frame, {
  698. prevent_default: true
  699. });
  700. this.hammerFrame.on('release', me._onRelease.bind(me) );
  701. // add the frame to the container element
  702. this.containerElement.appendChild(this.frame);
  703. };
  704. /**
  705. * Binding the keys for keyboard navigation. These functions are defined in the NavigationMixin
  706. * @private
  707. */
  708. Network.prototype._createKeyBinds = function() {
  709. var me = this;
  710. if (this.keycharm !== undefined) {
  711. this.keycharm.destroy();
  712. }
  713. this.keycharm = keycharm();
  714. this.keycharm.reset();
  715. if (this.constants.keyboard.enabled && this.isActive()) {
  716. this.keycharm.bind("up", this._moveUp.bind(me) , "keydown");
  717. this.keycharm.bind("up", this._yStopMoving.bind(me), "keyup");
  718. this.keycharm.bind("down", this._moveDown.bind(me) , "keydown");
  719. this.keycharm.bind("down", this._yStopMoving.bind(me), "keyup");
  720. this.keycharm.bind("left", this._moveLeft.bind(me) , "keydown");
  721. this.keycharm.bind("left", this._xStopMoving.bind(me), "keyup");
  722. this.keycharm.bind("right",this._moveRight.bind(me), "keydown");
  723. this.keycharm.bind("right",this._xStopMoving.bind(me), "keyup");
  724. this.keycharm.bind("=", this._zoomIn.bind(me), "keydown");
  725. this.keycharm.bind("=", this._stopZoom.bind(me), "keyup");
  726. this.keycharm.bind("num+", this._zoomIn.bind(me), "keydown");
  727. this.keycharm.bind("num+", this._stopZoom.bind(me), "keyup");
  728. this.keycharm.bind("num-", this._zoomOut.bind(me), "keydown");
  729. this.keycharm.bind("num-", this._stopZoom.bind(me), "keyup");
  730. this.keycharm.bind("-", this._zoomOut.bind(me), "keydown");
  731. this.keycharm.bind("-", this._stopZoom.bind(me), "keyup");
  732. this.keycharm.bind("[", this._zoomIn.bind(me), "keydown");
  733. this.keycharm.bind("[", this._stopZoom.bind(me), "keyup");
  734. this.keycharm.bind("]", this._zoomOut.bind(me), "keydown");
  735. this.keycharm.bind("]", this._stopZoom.bind(me), "keyup");
  736. this.keycharm.bind("pageup",this._zoomIn.bind(me), "keydown");
  737. this.keycharm.bind("pageup",this._stopZoom.bind(me), "keyup");
  738. this.keycharm.bind("pagedown",this._zoomOut.bind(me),"keydown");
  739. this.keycharm.bind("pagedown",this._stopZoom.bind(me), "keyup");
  740. }
  741. if (this.constants.dataManipulation.enabled == true) {
  742. this.keycharm.bind("esc",this._createManipulatorBar.bind(me));
  743. this.keycharm.bind("delete",this._deleteSelected.bind(me));
  744. }
  745. };
  746. /**
  747. * Cleans up all bindings of the network, removing it fully from the memory IF the variable is set to null after calling this function.
  748. * var network = new vis.Network(..);
  749. * network.destroy();
  750. * network = null;
  751. */
  752. Network.prototype.destroy = function() {
  753. this.start = function () {};
  754. this.redraw = function () {};
  755. this.timer = false;
  756. // cleanup physicsConfiguration if it exists
  757. this._cleanupPhysicsConfiguration();
  758. // remove keybindings
  759. this.keycharm.reset();
  760. // clear hammer bindings
  761. this.hammer.dispose();
  762. // clear events
  763. this.off();
  764. // remove all elements from the container element.
  765. while (this.frame.hasChildNodes()) {
  766. this.frame.removeChild(this.frame.firstChild);
  767. }
  768. // remove all elements from the container element.
  769. while (this.containerElement.hasChildNodes()) {
  770. this.containerElement.removeChild(this.containerElement.firstChild);
  771. }
  772. }
  773. /**
  774. * Get the pointer location from a touch location
  775. * @param {{pageX: Number, pageY: Number}} touch
  776. * @return {{x: Number, y: Number}} pointer
  777. * @private
  778. */
  779. Network.prototype._getPointer = function (touch) {
  780. return {
  781. x: touch.pageX - util.getAbsoluteLeft(this.frame.canvas),
  782. y: touch.pageY - util.getAbsoluteTop(this.frame.canvas)
  783. };
  784. };
  785. /**
  786. * On start of a touch gesture, store the pointer
  787. * @param event
  788. * @private
  789. */
  790. Network.prototype._onTouch = function (event) {
  791. if (new Date().valueOf() - this.touchTime > 100) {
  792. this.drag.pointer = this._getPointer(event.gesture.center);
  793. this.drag.pinched = false;
  794. this.pinch.scale = this._getScale();
  795. // to avoid double fireing of this event because we have two hammer instances. (on canvas and on frame)
  796. this.touchTime = new Date().valueOf();
  797. this._handleTouch(this.drag.pointer);
  798. }
  799. };
  800. /**
  801. * handle drag start event
  802. * @private
  803. */
  804. Network.prototype._onDragStart = function () {
  805. this._handleDragStart();
  806. };
  807. /**
  808. * This function is called by _onDragStart.
  809. * It is separated out because we can then overload it for the datamanipulation system.
  810. *
  811. * @private
  812. */
  813. Network.prototype._handleDragStart = function() {
  814. var drag = this.drag;
  815. var node = this._getNodeAt(drag.pointer);
  816. // note: drag.pointer is set in _onTouch to get the initial touch location
  817. drag.dragging = true;
  818. drag.selection = [];
  819. drag.translation = this._getTranslation();
  820. drag.nodeId = null;
  821. this.draggingNodes = false;
  822. if (node != null && this.constants.dragNodes == true) {
  823. this.draggingNodes = true;
  824. drag.nodeId = node.id;
  825. // select the clicked node if not yet selected
  826. if (!node.isSelected()) {
  827. this._selectObject(node,false);
  828. }
  829. this.emit("dragStart",{nodeIds:this.getSelection().nodes});
  830. // create an array with the selected nodes and their original location and status
  831. for (var objectId in this.selectionObj.nodes) {
  832. if (this.selectionObj.nodes.hasOwnProperty(objectId)) {
  833. var object = this.selectionObj.nodes[objectId];
  834. var s = {
  835. id: object.id,
  836. node: object,
  837. // store original x, y, xFixed and yFixed, make the node temporarily Fixed
  838. x: object.x,
  839. y: object.y,
  840. xFixed: object.xFixed,
  841. yFixed: object.yFixed
  842. };
  843. object.xFixed = true;
  844. object.yFixed = true;
  845. drag.selection.push(s);
  846. }
  847. }
  848. }
  849. };
  850. /**
  851. * handle drag event
  852. * @private
  853. */
  854. Network.prototype._onDrag = function (event) {
  855. this._handleOnDrag(event)
  856. };
  857. /**
  858. * This function is called by _onDrag.
  859. * It is separated out because we can then overload it for the datamanipulation system.
  860. *
  861. * @private
  862. */
  863. Network.prototype._handleOnDrag = function(event) {
  864. if (this.drag.pinched) {
  865. return;
  866. }
  867. // remove the focus on node if it is focussed on by the focusOnNode
  868. this.releaseNode();
  869. var pointer = this._getPointer(event.gesture.center);
  870. var me = this;
  871. var drag = this.drag;
  872. var selection = drag.selection;
  873. if (selection && selection.length && this.constants.dragNodes == true) {
  874. // calculate delta's and new location
  875. var deltaX = pointer.x - drag.pointer.x;
  876. var deltaY = pointer.y - drag.pointer.y;
  877. // update position of all selected nodes
  878. selection.forEach(function (s) {
  879. var node = s.node;
  880. if (!s.xFixed) {
  881. node.x = me._XconvertDOMtoCanvas(me._XconvertCanvasToDOM(s.x) + deltaX);
  882. }
  883. if (!s.yFixed) {
  884. node.y = me._YconvertDOMtoCanvas(me._YconvertCanvasToDOM(s.y) + deltaY);
  885. }
  886. });
  887. // start _animationStep if not yet running
  888. if (!this.moving) {
  889. this.moving = true;
  890. this.start();
  891. }
  892. }
  893. else {
  894. if (this.constants.dragNetwork == true) {
  895. // move the network
  896. var diffX = pointer.x - this.drag.pointer.x;
  897. var diffY = pointer.y - this.drag.pointer.y;
  898. this._setTranslation(
  899. this.drag.translation.x + diffX,
  900. this.drag.translation.y + diffY
  901. );
  902. this._redraw();
  903. // this.moving = true;
  904. // this.start();
  905. }
  906. }
  907. };
  908. /**
  909. * handle drag start event
  910. * @private
  911. */
  912. Network.prototype._onDragEnd = function (event) {
  913. this._handleDragEnd(event);
  914. };
  915. Network.prototype._handleDragEnd = function(event) {
  916. this.drag.dragging = false;
  917. var selection = this.drag.selection;
  918. if (selection && selection.length) {
  919. selection.forEach(function (s) {
  920. // restore original xFixed and yFixed
  921. s.node.xFixed = s.xFixed;
  922. s.node.yFixed = s.yFixed;
  923. });
  924. this.moving = true;
  925. this.start();
  926. }
  927. else {
  928. this._redraw();
  929. }
  930. if (this.draggingNodes == false) {
  931. this.emit("dragEnd",{nodeIds:[]});
  932. }
  933. else {
  934. this.emit("dragEnd",{nodeIds:this.getSelection().nodes});
  935. }
  936. }
  937. /**
  938. * handle tap/click event: select/unselect a node
  939. * @private
  940. */
  941. Network.prototype._onTap = function (event) {
  942. var pointer = this._getPointer(event.gesture.center);
  943. this.pointerPosition = pointer;
  944. this._handleTap(pointer);
  945. };
  946. /**
  947. * handle doubletap event
  948. * @private
  949. */
  950. Network.prototype._onDoubleTap = function (event) {
  951. var pointer = this._getPointer(event.gesture.center);
  952. this._handleDoubleTap(pointer);
  953. };
  954. /**
  955. * handle long tap event: multi select nodes
  956. * @private
  957. */
  958. Network.prototype._onHold = function (event) {
  959. var pointer = this._getPointer(event.gesture.center);
  960. this.pointerPosition = pointer;
  961. this._handleOnHold(pointer);
  962. };
  963. /**
  964. * handle the release of the screen
  965. *
  966. * @private
  967. */
  968. Network.prototype._onRelease = function (event) {
  969. var pointer = this._getPointer(event.gesture.center);
  970. this._handleOnRelease(pointer);
  971. };
  972. /**
  973. * Handle pinch event
  974. * @param event
  975. * @private
  976. */
  977. Network.prototype._onPinch = function (event) {
  978. var pointer = this._getPointer(event.gesture.center);
  979. this.drag.pinched = true;
  980. if (!('scale' in this.pinch)) {
  981. this.pinch.scale = 1;
  982. }
  983. // TODO: enabled moving while pinching?
  984. var scale = this.pinch.scale * event.gesture.scale;
  985. this._zoom(scale, pointer)
  986. };
  987. /**
  988. * Zoom the network in or out
  989. * @param {Number} scale a number around 1, and between 0.01 and 10
  990. * @param {{x: Number, y: Number}} pointer Position on screen
  991. * @return {Number} appliedScale scale is limited within the boundaries
  992. * @private
  993. */
  994. Network.prototype._zoom = function(scale, pointer) {
  995. if (this.constants.zoomable == true) {
  996. var scaleOld = this._getScale();
  997. if (scale < 0.00001) {
  998. scale = 0.00001;
  999. }
  1000. if (scale > 10) {
  1001. scale = 10;
  1002. }
  1003. var preScaleDragPointer = null;
  1004. if (this.drag !== undefined) {
  1005. if (this.drag.dragging == true) {
  1006. preScaleDragPointer = this.DOMtoCanvas(this.drag.pointer);
  1007. }
  1008. }
  1009. // + this.frame.canvas.clientHeight / 2
  1010. var translation = this._getTranslation();
  1011. var scaleFrac = scale / scaleOld;
  1012. var tx = (1 - scaleFrac) * pointer.x + translation.x * scaleFrac;
  1013. var ty = (1 - scaleFrac) * pointer.y + translation.y * scaleFrac;
  1014. this.areaCenter = {"x" : this._XconvertDOMtoCanvas(pointer.x),
  1015. "y" : this._YconvertDOMtoCanvas(pointer.y)};
  1016. this._setScale(scale);
  1017. this._setTranslation(tx, ty);
  1018. this.updateClustersDefault();
  1019. if (preScaleDragPointer != null) {
  1020. var postScaleDragPointer = this.canvasToDOM(preScaleDragPointer);
  1021. this.drag.pointer.x = postScaleDragPointer.x;
  1022. this.drag.pointer.y = postScaleDragPointer.y;
  1023. }
  1024. this._redraw();
  1025. if (scaleOld < scale) {
  1026. this.emit("zoom", {direction:"+"});
  1027. }
  1028. else {
  1029. this.emit("zoom", {direction:"-"});
  1030. }
  1031. return scale;
  1032. }
  1033. };
  1034. /**
  1035. * Event handler for mouse wheel event, used to zoom the timeline
  1036. * See http://adomas.org/javascript-mouse-wheel/
  1037. * https://github.com/EightMedia/hammer.js/issues/256
  1038. * @param {MouseEvent} event
  1039. * @private
  1040. */
  1041. Network.prototype._onMouseWheel = function(event) {
  1042. // retrieve delta
  1043. var delta = 0;
  1044. if (event.wheelDelta) { /* IE/Opera. */
  1045. delta = event.wheelDelta/120;
  1046. } else if (event.detail) { /* Mozilla case. */
  1047. // In Mozilla, sign of delta is different than in IE.
  1048. // Also, delta is multiple of 3.
  1049. delta = -event.detail/3;
  1050. }
  1051. // If delta is nonzero, handle it.
  1052. // Basically, delta is now positive if wheel was scrolled up,
  1053. // and negative, if wheel was scrolled down.
  1054. if (delta) {
  1055. // calculate the new scale
  1056. var scale = this._getScale();
  1057. var zoom = delta / 10;
  1058. if (delta < 0) {
  1059. zoom = zoom / (1 - zoom);
  1060. }
  1061. scale *= (1 + zoom);
  1062. // calculate the pointer location
  1063. var gesture = hammerUtil.fakeGesture(this, event);
  1064. var pointer = this._getPointer(gesture.center);
  1065. // apply the new scale
  1066. this._zoom(scale, pointer);
  1067. }
  1068. // Prevent default actions caused by mouse wheel.
  1069. event.preventDefault();
  1070. };
  1071. /**
  1072. * Mouse move handler for checking whether the title moves over a node with a title.
  1073. * @param {Event} event
  1074. * @private
  1075. */
  1076. Network.prototype._onMouseMoveTitle = function (event) {
  1077. var gesture = hammerUtil.fakeGesture(this, event);
  1078. var pointer = this._getPointer(gesture.center);
  1079. // check if the previously selected node is still selected
  1080. if (this.popupObj) {
  1081. this._checkHidePopup(pointer);
  1082. }
  1083. // start a timeout that will check if the mouse is positioned above
  1084. // an element
  1085. var me = this;
  1086. var checkShow = function() {
  1087. me._checkShowPopup(pointer);
  1088. };
  1089. if (this.popupTimer) {
  1090. clearInterval(this.popupTimer); // stop any running calculationTimer
  1091. }
  1092. if (!this.drag.dragging) {
  1093. this.popupTimer = setTimeout(checkShow, this.constants.tooltip.delay);
  1094. }
  1095. /**
  1096. * Adding hover highlights
  1097. */
  1098. if (this.constants.hover == true) {
  1099. // removing all hover highlights
  1100. for (var edgeId in this.hoverObj.edges) {
  1101. if (this.hoverObj.edges.hasOwnProperty(edgeId)) {
  1102. this.hoverObj.edges[edgeId].hover = false;
  1103. delete this.hoverObj.edges[edgeId];
  1104. }
  1105. }
  1106. // adding hover highlights
  1107. var obj = this._getNodeAt(pointer);
  1108. if (obj == null) {
  1109. obj = this._getEdgeAt(pointer);
  1110. }
  1111. if (obj != null) {
  1112. this._hoverObject(obj);
  1113. }
  1114. // removing all node hover highlights except for the selected one.
  1115. for (var nodeId in this.hoverObj.nodes) {
  1116. if (this.hoverObj.nodes.hasOwnProperty(nodeId)) {
  1117. if (obj instanceof Node && obj.id != nodeId || obj instanceof Edge || obj == null) {
  1118. this._blurObject(this.hoverObj.nodes[nodeId]);
  1119. delete this.hoverObj.nodes[nodeId];
  1120. }
  1121. }
  1122. }
  1123. this.redraw();
  1124. }
  1125. };
  1126. /**
  1127. * Check if there is an element on the given position in the network
  1128. * (a node or edge). If so, and if this element has a title,
  1129. * show a popup window with its title.
  1130. *
  1131. * @param {{x:Number, y:Number}} pointer
  1132. * @private
  1133. */
  1134. Network.prototype._checkShowPopup = function (pointer) {
  1135. var obj = {
  1136. left: this._XconvertDOMtoCanvas(pointer.x),
  1137. top: this._YconvertDOMtoCanvas(pointer.y),
  1138. right: this._XconvertDOMtoCanvas(pointer.x),
  1139. bottom: this._YconvertDOMtoCanvas(pointer.y)
  1140. };
  1141. var id;
  1142. var lastPopupNode = this.popupObj;
  1143. if (this.popupObj == undefined) {
  1144. // search the nodes for overlap, select the top one in case of multiple nodes
  1145. var nodes = this.nodes;
  1146. for (id in nodes) {
  1147. if (nodes.hasOwnProperty(id)) {
  1148. var node = nodes[id];
  1149. if (node.getTitle() !== undefined && node.isOverlappingWith(obj)) {
  1150. this.popupObj = node;
  1151. break;
  1152. }
  1153. }
  1154. }
  1155. }
  1156. if (this.popupObj === undefined) {
  1157. // search the edges for overlap
  1158. var edges = this.edges;
  1159. for (id in edges) {
  1160. if (edges.hasOwnProperty(id)) {
  1161. var edge = edges[id];
  1162. if (edge.connected && (edge.getTitle() !== undefined) &&
  1163. edge.isOverlappingWith(obj)) {
  1164. this.popupObj = edge;
  1165. break;
  1166. }
  1167. }
  1168. }
  1169. }
  1170. if (this.popupObj) {
  1171. // show popup message window
  1172. if (this.popupObj != lastPopupNode) {
  1173. var me = this;
  1174. if (!me.popup) {
  1175. me.popup = new Popup(me.frame, me.constants.tooltip);
  1176. }
  1177. // adjust a small offset such that the mouse cursor is located in the
  1178. // bottom left location of the popup, and you can easily move over the
  1179. // popup area
  1180. me.popup.setPosition(pointer.x - 3, pointer.y - 3);
  1181. me.popup.setText(me.popupObj.getTitle());
  1182. me.popup.show();
  1183. }
  1184. }
  1185. else {
  1186. if (this.popup) {
  1187. this.popup.hide();
  1188. }
  1189. }
  1190. };
  1191. /**
  1192. * Check if the popup must be hided, which is the case when the mouse is no
  1193. * longer hovering on the object
  1194. * @param {{x:Number, y:Number}} pointer
  1195. * @private
  1196. */
  1197. Network.prototype._checkHidePopup = function (pointer) {
  1198. if (!this.popupObj || !this._getNodeAt(pointer) ) {
  1199. this.popupObj = undefined;
  1200. if (this.popup) {
  1201. this.popup.hide();
  1202. }
  1203. }
  1204. };
  1205. /**
  1206. * Set a new size for the network
  1207. * @param {string} width Width in pixels or percentage (for example '800px'
  1208. * or '50%')
  1209. * @param {string} height Height in pixels or percentage (for example '400px'
  1210. * or '30%')
  1211. */
  1212. Network.prototype.setSize = function(width, height) {
  1213. var emitEvent = false;
  1214. var oldWidth = this.frame.canvas.width;
  1215. var oldHeight = this.frame.canvas.height;
  1216. if (width != this.constants.width || height != this.constants.height || this.frame.style.width != width || this.frame.style.height != height) {
  1217. this.frame.style.width = width;
  1218. this.frame.style.height = height;
  1219. this.frame.canvas.style.width = '100%';
  1220. this.frame.canvas.style.height = '100%';
  1221. this.frame.canvas.width = this.frame.canvas.clientWidth * this.pixelRatio;
  1222. this.frame.canvas.height = this.frame.canvas.clientHeight * this.pixelRatio;
  1223. this.constants.width = width;
  1224. this.constants.height = height;
  1225. emitEvent = true;
  1226. }
  1227. else {
  1228. // this would adapt the width of the canvas to the width from 100% if and only if
  1229. // there is a change.
  1230. if (this.frame.canvas.width != this.frame.canvas.clientWidth * this.pixelRatio) {
  1231. this.frame.canvas.width = this.frame.canvas.clientWidth * this.pixelRatio;
  1232. emitEvent = true;
  1233. }
  1234. if (this.frame.canvas.height != this.frame.canvas.clientHeight * this.pixelRatio) {
  1235. this.frame.canvas.height = this.frame.canvas.clientHeight * this.pixelRatio;
  1236. emitEvent = true;
  1237. }
  1238. }
  1239. if (emitEvent == true) {
  1240. this.emit('resize', {width:this.frame.canvas.width * this.pixelRatio,height:this.frame.canvas.height * this.pixelRatio, oldWidth: oldWidth * this.pixelRatio, oldHeight: oldHeight * this.pixelRatio});
  1241. }
  1242. };
  1243. /**
  1244. * Set a data set with nodes for the network
  1245. * @param {Array | DataSet | DataView} nodes The data containing the nodes.
  1246. * @private
  1247. */
  1248. Network.prototype._setNodes = function(nodes) {
  1249. var oldNodesData = this.nodesData;
  1250. if (nodes instanceof DataSet || nodes instanceof DataView) {
  1251. this.nodesData = nodes;
  1252. }
  1253. else if (Array.isArray(nodes)) {
  1254. this.nodesData = new DataSet();
  1255. this.nodesData.add(nodes);
  1256. }
  1257. else if (!nodes) {
  1258. this.nodesData = new DataSet();
  1259. }
  1260. else {
  1261. throw new TypeError('Array or DataSet expected');
  1262. }
  1263. if (oldNodesData) {
  1264. // unsubscribe from old dataset
  1265. util.forEach(this.nodesListeners, function (callback, event) {
  1266. oldNodesData.off(event, callback);
  1267. });
  1268. }
  1269. // remove drawn nodes
  1270. this.nodes = {};
  1271. if (this.nodesData) {
  1272. // subscribe to new dataset
  1273. var me = this;
  1274. util.forEach(this.nodesListeners, function (callback, event) {
  1275. me.nodesData.on(event, callback);
  1276. });
  1277. // draw all new nodes
  1278. var ids = this.nodesData.getIds();
  1279. this._addNodes(ids);
  1280. }
  1281. this._updateSelection();
  1282. };
  1283. /**
  1284. * Add nodes
  1285. * @param {Number[] | String[]} ids
  1286. * @private
  1287. */
  1288. Network.prototype._addNodes = function(ids) {
  1289. var id;
  1290. for (var i = 0, len = ids.length; i < len; i++) {
  1291. id = ids[i];
  1292. var data = this.nodesData.get(id);
  1293. var node = new Node(data, this.images, this.groups, this.constants);
  1294. this.nodes[id] = node; // note: this may replace an existing node
  1295. if ((node.xFixed == false || node.yFixed == false) && (node.x === null || node.y === null)) {
  1296. var radius = 10 * 0.1*ids.length + 10;
  1297. var angle = 2 * Math.PI * Math.random();
  1298. if (node.xFixed == false) {node.x = radius * Math.cos(angle);}
  1299. if (node.yFixed == false) {node.y = radius * Math.sin(angle);}
  1300. }
  1301. this.moving = true;
  1302. }
  1303. this._updateNodeIndexList();
  1304. if (this.constants.hierarchicalLayout.enabled == true && this.initializing == false) {
  1305. this._resetLevels();
  1306. this._setupHierarchicalLayout();
  1307. }
  1308. this._updateCalculationNodes();
  1309. this._reconnectEdges();
  1310. this._updateValueRange(this.nodes);
  1311. this.updateLabels();
  1312. };
  1313. /**
  1314. * Update existing nodes, or create them when not yet existing
  1315. * @param {Number[] | String[]} ids
  1316. * @private
  1317. */
  1318. Network.prototype._updateNodes = function(ids,changedData) {
  1319. var nodes = this.nodes;
  1320. for (var i = 0, len = ids.length; i < len; i++) {
  1321. var id = ids[i];
  1322. var node = nodes[id];
  1323. var data = changedData[i];
  1324. if (node) {
  1325. // update node
  1326. node.setProperties(data, this.constants);
  1327. }
  1328. else {
  1329. // create node
  1330. node = new Node(properties, this.images, this.groups, this.constants);
  1331. nodes[id] = node;
  1332. }
  1333. }
  1334. this.moving = true;
  1335. if (this.constants.hierarchicalLayout.enabled == true && this.initializing == false) {
  1336. this._resetLevels();
  1337. this._setupHierarchicalLayout();
  1338. }
  1339. this._updateNodeIndexList();
  1340. this._updateValueRange(nodes);
  1341. };
  1342. /**
  1343. * Remove existing nodes. If nodes do not exist, the method will just ignore it.
  1344. * @param {Number[] | String[]} ids
  1345. * @private
  1346. */
  1347. Network.prototype._removeNodes = function(ids) {
  1348. var nodes = this.nodes;
  1349. for (var i = 0, len = ids.length; i < len; i++) {
  1350. var id = ids[i];
  1351. delete nodes[id];
  1352. }
  1353. this._updateNodeIndexList();
  1354. if (this.constants.hierarchicalLayout.enabled == true && this.initializing == false) {
  1355. this._resetLevels();
  1356. this._setupHierarchicalLayout();
  1357. }
  1358. this._updateCalculationNodes();
  1359. this._reconnectEdges();
  1360. this._updateSelection();
  1361. this._updateValueRange(nodes);
  1362. };
  1363. /**
  1364. * Load edges by reading the data table
  1365. * @param {Array | DataSet | DataView} edges The data containing the edges.
  1366. * @private
  1367. * @private
  1368. */
  1369. Network.prototype._setEdges = function(edges) {
  1370. var oldEdgesData = this.edgesData;
  1371. if (edges instanceof DataSet || edges instanceof DataView) {
  1372. this.edgesData = edges;
  1373. }
  1374. else if (Array.isArray(edges)) {
  1375. this.edgesData = new DataSet();
  1376. this.edgesData.add(edges);
  1377. }
  1378. else if (!edges) {
  1379. this.edgesData = new DataSet();
  1380. }
  1381. else {
  1382. throw new TypeError('Array or DataSet expected');
  1383. }
  1384. if (oldEdgesData) {
  1385. // unsubscribe from old dataset
  1386. util.forEach(this.edgesListeners, function (callback, event) {
  1387. oldEdgesData.off(event, callback);
  1388. });
  1389. }
  1390. // remove drawn edges
  1391. this.edges = {};
  1392. if (this.edgesData) {
  1393. // subscribe to new dataset
  1394. var me = this;
  1395. util.forEach(this.edgesListeners, function (callback, event) {
  1396. me.edgesData.on(event, callback);
  1397. });
  1398. // draw all new nodes
  1399. var ids = this.edgesData.getIds();
  1400. this._addEdges(ids);
  1401. }
  1402. this._reconnectEdges();
  1403. };
  1404. /**
  1405. * Add edges
  1406. * @param {Number[] | String[]} ids
  1407. * @private
  1408. */
  1409. Network.prototype._addEdges = function (ids) {
  1410. var edges = this.edges,
  1411. edgesData = this.edgesData;
  1412. for (var i = 0, len = ids.length; i < len; i++) {
  1413. var id = ids[i];
  1414. var oldEdge = edges[id];
  1415. if (oldEdge) {
  1416. oldEdge.disconnect();
  1417. }
  1418. var data = edgesData.get(id, {"showInternalIds" : true});
  1419. edges[id] = new Edge(data, this, this.constants);
  1420. }
  1421. this.moving = true;
  1422. this._updateValueRange(edges);
  1423. this._createBezierNodes();
  1424. this._updateCalculationNodes();
  1425. if (this.constants.hierarchicalLayout.enabled == true && this.initializing == false) {
  1426. this._resetLevels();
  1427. this._setupHierarchicalLayout();
  1428. }
  1429. };
  1430. /**
  1431. * Update existing edges, or create them when not yet existing
  1432. * @param {Number[] | String[]} ids
  1433. * @private
  1434. */
  1435. Network.prototype._updateEdges = function (ids) {
  1436. var edges = this.edges,
  1437. edgesData = this.edgesData;
  1438. for (var i = 0, len = ids.length; i < len; i++) {
  1439. var id = ids[i];
  1440. var data = edgesData.get(id);
  1441. var edge = edges[id];
  1442. if (edge) {
  1443. // update edge
  1444. edge.disconnect();
  1445. edge.setProperties(data, this.constants);
  1446. edge.connect();
  1447. }
  1448. else {
  1449. // create edge
  1450. edge = new Edge(data, this, this.constants);
  1451. this.edges[id] = edge;
  1452. }
  1453. }
  1454. this._createBezierNodes();
  1455. if (this.constants.hierarchicalLayout.enabled == true && this.initializing == false) {
  1456. this._resetLevels();
  1457. this._setupHierarchicalLayout();
  1458. }
  1459. this.moving = true;
  1460. this._updateValueRange(edges);
  1461. };
  1462. /**
  1463. * Remove existing edges. Non existing ids will be ignored
  1464. * @param {Number[] | String[]} ids
  1465. * @private
  1466. */
  1467. Network.prototype._removeEdges = function (ids) {
  1468. var edges = this.edges;
  1469. for (var i = 0, len = ids.length; i < len; i++) {
  1470. var id = ids[i];
  1471. var edge = edges[id];
  1472. if (edge) {
  1473. if (edge.via != null) {
  1474. delete this.sectors['support']['nodes'][edge.via.id];
  1475. }
  1476. edge.disconnect();
  1477. delete edges[id];
  1478. }
  1479. }
  1480. this.moving = true;
  1481. this._updateValueRange(edges);
  1482. if (this.constants.hierarchicalLayout.enabled == true && this.initializing == false) {
  1483. this._resetLevels();
  1484. this._setupHierarchicalLayout();
  1485. }
  1486. this._updateCalculationNodes();
  1487. };
  1488. /**
  1489. * Reconnect all edges
  1490. * @private
  1491. */
  1492. Network.prototype._reconnectEdges = function() {
  1493. var id,
  1494. nodes = this.nodes,
  1495. edges = this.edges;
  1496. for (id in nodes) {
  1497. if (nodes.hasOwnProperty(id)) {
  1498. nodes[id].edges = [];
  1499. nodes[id].dynamicEdges = [];
  1500. }
  1501. }
  1502. for (id in edges) {
  1503. if (edges.hasOwnProperty(id)) {
  1504. var edge = edges[id];
  1505. edge.from = null;
  1506. edge.to = null;
  1507. edge.connect();
  1508. }
  1509. }
  1510. };
  1511. /**
  1512. * Update the values of all object in the given array according to the current
  1513. * value range of the objects in the array.
  1514. * @param {Object} obj An object containing a set of Edges or Nodes
  1515. * The objects must have a method getValue() and
  1516. * setValueRange(min, max).
  1517. * @private
  1518. */
  1519. Network.prototype._updateValueRange = function(obj) {
  1520. var id;
  1521. // determine the range of the objects
  1522. var valueMin = undefined;
  1523. var valueMax = undefined;
  1524. for (id in obj) {
  1525. if (obj.hasOwnProperty(id)) {
  1526. var value = obj[id].getValue();
  1527. if (value !== undefined) {
  1528. valueMin = (valueMin === undefined) ? value : Math.min(value, valueMin);
  1529. valueMax = (valueMax === undefined) ? value : Math.max(value, valueMax);
  1530. }
  1531. }
  1532. }
  1533. // adjust the range of all objects
  1534. if (valueMin !== undefined && valueMax !== undefined) {
  1535. for (id in obj) {
  1536. if (obj.hasOwnProperty(id)) {
  1537. obj[id].setValueRange(valueMin, valueMax);
  1538. }
  1539. }
  1540. }
  1541. };
  1542. /**
  1543. * Redraw the network with the current data
  1544. * chart will be resized too.
  1545. */
  1546. Network.prototype.redraw = function() {
  1547. this.setSize(this.constants.width, this.constants.height);
  1548. this._redraw();
  1549. };
  1550. /**
  1551. * Redraw the network with the current data
  1552. * @param hidden | used to get the first estimate of the node sizes. only the nodes are drawn after which they are quickly drawn over.
  1553. * @private
  1554. */
  1555. Network.prototype._redraw = function(hidden) {
  1556. var ctx = this.frame.canvas.getContext('2d');
  1557. ctx.setTransform(this.pixelRatio, 0, 0, this.pixelRatio, 0, 0);
  1558. // clear the canvas
  1559. var w = this.frame.canvas.width * this.pixelRatio;
  1560. var h = this.frame.canvas.height * this.pixelRatio;
  1561. ctx.clearRect(0, 0, w, h);
  1562. // set scaling and translation
  1563. ctx.save();
  1564. ctx.translate(this.translation.x, this.translation.y);
  1565. ctx.scale(this.scale, this.scale);
  1566. this.canvasTopLeft = {
  1567. "x": this._XconvertDOMtoCanvas(0),
  1568. "y": this._YconvertDOMtoCanvas(0)
  1569. };
  1570. this.canvasBottomRight = {
  1571. "x": this._XconvertDOMtoCanvas(this.frame.canvas.clientWidth * this.pixelRatio),
  1572. "y": this._YconvertDOMtoCanvas(this.frame.canvas.clientHeight * this.pixelRatio)
  1573. };
  1574. if (!(hidden == true)) {
  1575. this._doInAllSectors("_drawAllSectorNodes", ctx);
  1576. if (this.drag.dragging == false || this.drag.dragging === undefined || this.constants.hideEdgesOnDrag == false) {
  1577. this._doInAllSectors("_drawEdges", ctx);
  1578. }
  1579. }
  1580. if (this.drag.dragging == false || this.drag.dragging === undefined || this.constants.hideNodesOnDrag == false) {
  1581. this._doInAllSectors("_drawNodes",ctx,false);
  1582. }
  1583. if (!(hidden == true)) {
  1584. if (this.controlNodesActive == true) {
  1585. this._doInAllSectors("_drawControlNodes", ctx);
  1586. }
  1587. }
  1588. // this._doInSupportSector("_drawNodes",ctx,true);
  1589. // this._drawTree(ctx,"#F00F0F");
  1590. // restore original scaling and translation
  1591. ctx.restore();
  1592. if (hidden == true) {
  1593. ctx.clearRect(0, 0, w, h);
  1594. }
  1595. };
  1596. /**
  1597. * Set the translation of the network
  1598. * @param {Number} offsetX Horizontal offset
  1599. * @param {Number} offsetY Vertical offset
  1600. * @private
  1601. */
  1602. Network.prototype._setTranslation = function(offsetX, offsetY) {
  1603. if (this.translation === undefined) {
  1604. this.translation = {
  1605. x: 0,
  1606. y: 0
  1607. };
  1608. }
  1609. if (offsetX !== undefined) {
  1610. this.translation.x = offsetX;
  1611. }
  1612. if (offsetY !== undefined) {
  1613. this.translation.y = offsetY;
  1614. }
  1615. this.emit('viewChanged');
  1616. };
  1617. /**
  1618. * Get the translation of the network
  1619. * @return {Object} translation An object with parameters x and y, both a number
  1620. * @private
  1621. */
  1622. Network.prototype._getTranslation = function() {
  1623. return {
  1624. x: this.translation.x,
  1625. y: this.translation.y
  1626. };
  1627. };
  1628. /**
  1629. * Scale the network
  1630. * @param {Number} scale Scaling factor 1.0 is unscaled
  1631. * @private
  1632. */
  1633. Network.prototype._setScale = function(scale) {
  1634. this.scale = scale;
  1635. };
  1636. /**
  1637. * Get the current scale of the network
  1638. * @return {Number} scale Scaling factor 1.0 is unscaled
  1639. * @private
  1640. */
  1641. Network.prototype._getScale = function() {
  1642. return this.scale;
  1643. };
  1644. /**
  1645. * Convert the X coordinate in DOM-space (coordinate point in browser relative to the container div) to
  1646. * the X coordinate in canvas-space (the simulation sandbox, which the camera looks upon)
  1647. * @param {number} x
  1648. * @returns {number}
  1649. * @private
  1650. */
  1651. Network.prototype._XconvertDOMtoCanvas = function(x) {
  1652. return (x - this.translation.x) / this.scale;
  1653. };
  1654. /**
  1655. * Convert the X coordinate in canvas-space (the simulation sandbox, which the camera looks upon) to
  1656. * the X coordinate in DOM-space (coordinate point in browser relative to the container div)
  1657. * @param {number} x
  1658. * @returns {number}
  1659. * @private
  1660. */
  1661. Network.prototype._XconvertCanvasToDOM = function(x) {
  1662. return x * this.scale + this.translation.x;
  1663. };
  1664. /**
  1665. * Convert the Y coordinate in DOM-space (coordinate point in browser relative to the container div) to
  1666. * the Y coordinate in canvas-space (the simulation sandbox, which the camera looks upon)
  1667. * @param {number} y
  1668. * @returns {number}
  1669. * @private
  1670. */
  1671. Network.prototype._YconvertDOMtoCanvas = function(y) {
  1672. return (y - this.translation.y) / this.scale;
  1673. };
  1674. /**
  1675. * Convert the Y coordinate in canvas-space (the simulation sandbox, which the camera looks upon) to
  1676. * the Y coordinate in DOM-space (coordinate point in browser relative to the container div)
  1677. * @param {number} y
  1678. * @returns {number}
  1679. * @private
  1680. */
  1681. Network.prototype._YconvertCanvasToDOM = function(y) {
  1682. return y * this.scale + this.translation.y ;
  1683. };
  1684. /**
  1685. *
  1686. * @param {object} pos = {x: number, y: number}
  1687. * @returns {{x: number, y: number}}
  1688. * @constructor
  1689. */
  1690. Network.prototype.canvasToDOM = function (pos) {
  1691. return {x: this._XconvertCanvasToDOM(pos.x), y: this._YconvertCanvasToDOM(pos.y)};
  1692. };
  1693. /**
  1694. *
  1695. * @param {object} pos = {x: number, y: number}
  1696. * @returns {{x: number, y: number}}
  1697. * @constructor
  1698. */
  1699. Network.prototype.DOMtoCanvas = function (pos) {
  1700. return {x: this._XconvertDOMtoCanvas(pos.x), y: this._YconvertDOMtoCanvas(pos.y)};
  1701. };
  1702. /**
  1703. * Redraw all nodes
  1704. * The 2d context of a HTML canvas can be retrieved by canvas.getContext('2d');
  1705. * @param {CanvasRenderingContext2D} ctx
  1706. * @param {Boolean} [alwaysShow]
  1707. * @private
  1708. */
  1709. Network.prototype._drawNodes = function(ctx,alwaysShow) {
  1710. if (alwaysShow === undefined) {
  1711. alwaysShow = false;
  1712. }
  1713. // first draw the unselected nodes
  1714. var nodes = this.nodes;
  1715. var selected = [];
  1716. for (var id in nodes) {
  1717. if (nodes.hasOwnProperty(id)) {
  1718. nodes[id].setScaleAndPos(this.scale,this.canvasTopLeft,this.canvasBottomRight);
  1719. if (nodes[id].isSelected()) {
  1720. selected.push(id);
  1721. }
  1722. else {
  1723. if (nodes[id].inArea() || alwaysShow) {
  1724. nodes[id].draw(ctx);
  1725. }
  1726. }
  1727. }
  1728. }
  1729. // draw the selected nodes on top
  1730. for (var s = 0, sMax = selected.length; s < sMax; s++) {
  1731. if (nodes[selected[s]].inArea() || alwaysShow) {
  1732. nodes[selected[s]].draw(ctx);
  1733. }
  1734. }
  1735. };
  1736. /**
  1737. * Redraw all edges
  1738. * The 2d context of a HTML canvas can be retrieved by canvas.getContext('2d');
  1739. * @param {CanvasRenderingContext2D} ctx
  1740. * @private
  1741. */
  1742. Network.prototype._drawEdges = function(ctx) {
  1743. var edges = this.edges;
  1744. for (var id in edges) {
  1745. if (edges.hasOwnProperty(id)) {
  1746. var edge = edges[id];
  1747. edge.setScale(this.scale);
  1748. if (edge.connected) {
  1749. edges[id].draw(ctx);
  1750. }
  1751. }
  1752. }
  1753. };
  1754. /**
  1755. * Redraw all edges
  1756. * The 2d context of a HTML canvas can be retrieved by canvas.getContext('2d');
  1757. * @param {CanvasRenderingContext2D} ctx
  1758. * @private
  1759. */
  1760. Network.prototype._drawControlNodes = function(ctx) {
  1761. var edges = this.edges;
  1762. for (var id in edges) {
  1763. if (edges.hasOwnProperty(id)) {
  1764. edges[id]._drawControlNodes(ctx);
  1765. }
  1766. }
  1767. };
  1768. /**
  1769. * Find a stable position for all nodes
  1770. * @private
  1771. */
  1772. Network.prototype._stabilize = function() {
  1773. if (this.constants.freezeForStabilization == true) {
  1774. this._freezeDefinedNodes();
  1775. }
  1776. // find stable position
  1777. var count = 0;
  1778. while (this.moving && count < this.constants.stabilizationIterations) {
  1779. this._physicsTick();
  1780. count++;
  1781. }
  1782. if (this.constants.zoomExtentOnStabilize == true) {
  1783. this.zoomExtent(undefined, false, true);
  1784. }
  1785. if (this.constants.freezeForStabilization == true) {
  1786. this._restoreFrozenNodes();
  1787. }
  1788. };
  1789. /**
  1790. * When initializing and stabilizing, we can freeze nodes with a predefined position. This greatly speeds up stabilization
  1791. * because only the supportnodes for the smoothCurves have to settle.
  1792. *
  1793. * @private
  1794. */
  1795. Network.prototype._freezeDefinedNodes = function() {
  1796. var nodes = this.nodes;
  1797. for (var id in nodes) {
  1798. if (nodes.hasOwnProperty(id)) {
  1799. if (nodes[id].x != null && nodes[id].y != null) {
  1800. nodes[id].fixedData.x = nodes[id].xFixed;
  1801. nodes[id].fixedData.y = nodes[id].yFixed;
  1802. nodes[id].xFixed = true;
  1803. nodes[id].yFixed = true;
  1804. }
  1805. }
  1806. }
  1807. };
  1808. /**
  1809. * Unfreezes the nodes that have been frozen by _freezeDefinedNodes.
  1810. *
  1811. * @private
  1812. */
  1813. Network.prototype._restoreFrozenNodes = function() {
  1814. var nodes = this.nodes;
  1815. for (var id in nodes) {
  1816. if (nodes.hasOwnProperty(id)) {
  1817. if (nodes[id].fixedData.x != null) {
  1818. nodes[id].xFixed = nodes[id].fixedData.x;
  1819. nodes[id].yFixed = nodes[id].fixedData.y;
  1820. }
  1821. }
  1822. }
  1823. };
  1824. /**
  1825. * Check if any of the nodes is still moving
  1826. * @param {number} vmin the minimum velocity considered as 'moving'
  1827. * @return {boolean} true if moving, false if non of the nodes is moving
  1828. * @private
  1829. */
  1830. Network.prototype._isMoving = function(vmin) {
  1831. var nodes = this.nodes;
  1832. for (var id in nodes) {
  1833. if (nodes.hasOwnProperty(id) && nodes[id].isMoving(vmin)) {
  1834. return true;
  1835. }
  1836. }
  1837. return false;
  1838. };
  1839. /**
  1840. * /**
  1841. * Perform one discrete step for all nodes
  1842. *
  1843. * @private
  1844. */
  1845. Network.prototype._discreteStepNodes = function() {
  1846. var interval = this.physicsDiscreteStepsize;
  1847. var nodes = this.nodes;
  1848. var nodeId;
  1849. var nodesPresent = false;
  1850. if (this.constants.maxVelocity > 0) {
  1851. for (nodeId in nodes) {
  1852. if (nodes.hasOwnProperty(nodeId)) {
  1853. nodes[nodeId].discreteStepLimited(interval, this.constants.maxVelocity);
  1854. nodesPresent = true;
  1855. }
  1856. }
  1857. }
  1858. else {
  1859. for (nodeId in nodes) {
  1860. if (nodes.hasOwnProperty(nodeId)) {
  1861. nodes[nodeId].discreteStep(interval);
  1862. nodesPresent = true;
  1863. }
  1864. }
  1865. }
  1866. if (nodesPresent == true) {
  1867. var vminCorrected = this.constants.minVelocity / Math.max(this.scale,0.05);
  1868. if (vminCorrected > 0.5*this.constants.maxVelocity) {
  1869. return true;
  1870. }
  1871. else {
  1872. return this._isMoving(vminCorrected);
  1873. }
  1874. }
  1875. return false;
  1876. };
  1877. /**
  1878. * A single simulation step (or "tick") in the physics simulation
  1879. *
  1880. * @private
  1881. */
  1882. Network.prototype._physicsTick = function() {
  1883. if (!this.freezeSimulation) {
  1884. if (this.moving == true) {
  1885. var mainMovingStatus = false;
  1886. var supportMovingStatus = false;
  1887. this._doInAllActiveSectors("_initializeForceCalculation");
  1888. var mainMoving = this._doInAllActiveSectors("_discreteStepNodes");
  1889. if (this.constants.smoothCurves.enabled == true && this.constants.smoothCurves.dynamic == true) {
  1890. supportMovingStatus = this._doInSupportSector("_discreteStepNodes");
  1891. }
  1892. // gather movement data from all sectors, if one moves, we are NOT stabilzied
  1893. for (var i = 0; i < mainMoving.length; i++) {mainMovingStatus = mainMoving[0] || mainMovingStatus;}
  1894. // determine if the network has stabilzied
  1895. this.moving = mainMovingStatus || supportMovingStatus;
  1896. this.stabilizationIterations++;
  1897. }
  1898. }
  1899. };
  1900. /**
  1901. * This function runs one step of the animation. It calls an x amount of physics ticks and one render tick.
  1902. * It reschedules itself at the beginning of the function
  1903. *
  1904. * @private
  1905. */
  1906. Network.prototype._animationStep = function() {
  1907. // reset the timer so a new scheduled animation step can be set
  1908. this.timer = undefined;
  1909. // handle the keyboad movement
  1910. this._handleNavigation();
  1911. // this schedules a new animation step
  1912. this.start();
  1913. // start the physics simulation
  1914. var calculationTime = Date.now();
  1915. var maxSteps = 1;
  1916. this._physicsTick();
  1917. var timeRequired = Date.now() - calculationTime;
  1918. while (timeRequired < 0.9*(this.renderTimestep - this.renderTime) && maxSteps < this.maxPhysicsTicksPerRender) {
  1919. this._physicsTick();
  1920. timeRequired = Date.now() - calculationTime;
  1921. maxSteps++;
  1922. }
  1923. // start the rendering process
  1924. var renderTime = Date.now();
  1925. this._redraw();
  1926. this.renderTime = Date.now() - renderTime;
  1927. };
  1928. if (typeof window !== 'undefined') {
  1929. window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame ||
  1930. window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
  1931. }
  1932. /**
  1933. * Schedule a animation step with the refreshrate interval.
  1934. */
  1935. Network.prototype.start = function() {
  1936. if (this.moving == true || this.xIncrement != 0 || this.yIncrement != 0 || this.zoomIncrement != 0) {
  1937. if (this.startedStabilization == false) {
  1938. this.emit("startStabilization");
  1939. this.startedStabilization = true;
  1940. }
  1941. if (!this.timer) {
  1942. var ua = navigator.userAgent.toLowerCase();
  1943. var requiresTimeout = false;
  1944. if (ua.indexOf('msie 9.0') != -1) { // IE 9
  1945. requiresTimeout = true;
  1946. }
  1947. else if (ua.indexOf('safari') != -1) { // safari
  1948. if (ua.indexOf('chrome') <= -1) {
  1949. requiresTimeout = true;
  1950. }
  1951. }
  1952. if (requiresTimeout == true) {
  1953. this.timer = window.setTimeout(this._animationStep.bind(this), this.renderTimestep); // wait this.renderTimeStep milliseconds and perform the animation step function
  1954. }
  1955. else{
  1956. this.timer = window.requestAnimationFrame(this._animationStep.bind(this), this.renderTimestep); // wait this.renderTimeStep milliseconds and perform the animation step function
  1957. }
  1958. }
  1959. }
  1960. else {
  1961. this._redraw();
  1962. if (this.stabilizationIterations > 0) {
  1963. // trigger the "stabilized" event.
  1964. // The event is triggered on the next tick, to prevent the case that
  1965. // it is fired while initializing the Network, in which case you would not
  1966. // be able to catch it
  1967. var me = this;
  1968. var params = {
  1969. iterations: me.stabilizationIterations
  1970. };
  1971. me.stabilizationIterations = 0;
  1972. me.startedStabilization = false;
  1973. setTimeout(function () {
  1974. me.emit("stabilized", params);
  1975. }, 0);
  1976. }
  1977. }
  1978. };
  1979. /**
  1980. * Move the network according to the keyboard presses.
  1981. *
  1982. * @private
  1983. */
  1984. Network.prototype._handleNavigation = function() {
  1985. if (this.xIncrement != 0 || this.yIncrement != 0) {
  1986. var translation = this._getTranslation();
  1987. this._setTranslation(translation.x+this.xIncrement, translation.y+this.yIncrement);
  1988. }
  1989. if (this.zoomIncrement != 0) {
  1990. var center = {
  1991. x: this.frame.canvas.clientWidth / 2,
  1992. y: this.frame.canvas.clientHeight / 2
  1993. };
  1994. this._zoom(this.scale*(1 + this.zoomIncrement), center);
  1995. }
  1996. };
  1997. /**
  1998. * Freeze the _animationStep
  1999. */
  2000. Network.prototype.toggleFreeze = function() {
  2001. if (this.freezeSimulation == false) {
  2002. this.freezeSimulation = true;
  2003. }
  2004. else {
  2005. this.freezeSimulation = false;
  2006. this.start();
  2007. }
  2008. };
  2009. /**
  2010. * This function cleans the support nodes if they are not needed and adds them when they are.
  2011. *
  2012. * @param {boolean} [disableStart]
  2013. * @private
  2014. */
  2015. Network.prototype._configureSmoothCurves = function(disableStart) {
  2016. if (disableStart === undefined) {
  2017. disableStart = true;
  2018. }
  2019. if (this.constants.smoothCurves.enabled == true && this.constants.smoothCurves.dynamic == true) {
  2020. this._createBezierNodes();
  2021. // cleanup unused support nodes
  2022. for (var nodeId in this.sectors['support']['nodes']) {
  2023. if (this.sectors['support']['nodes'].hasOwnProperty(nodeId)) {
  2024. if (this.edges[this.sectors['support']['nodes'][nodeId].parentEdgeId] === undefined) {
  2025. delete this.sectors['support']['nodes'][nodeId];
  2026. }
  2027. }
  2028. }
  2029. }
  2030. else {
  2031. // delete the support nodes
  2032. this.sectors['support']['nodes'] = {};
  2033. for (var edgeId in this.edges) {
  2034. if (this.edges.hasOwnProperty(edgeId)) {
  2035. this.edges[edgeId].via = null;
  2036. }
  2037. }
  2038. }
  2039. this._updateCalculationNodes();
  2040. if (!disableStart) {
  2041. this.moving = true;
  2042. this.start();
  2043. }
  2044. };
  2045. /**
  2046. * Bezier curves require an anchor point to calculate the smooth flow. These points are nodes. These nodes are invisible but
  2047. * are used for the force calculation.
  2048. *
  2049. * @private
  2050. */
  2051. Network.prototype._createBezierNodes = function() {
  2052. if (this.constants.smoothCurves.enabled == true && this.constants.smoothCurves.dynamic == true) {
  2053. for (var edgeId in this.edges) {
  2054. if (this.edges.hasOwnProperty(edgeId)) {
  2055. var edge = this.edges[edgeId];
  2056. if (edge.via == null) {
  2057. var nodeId = "edgeId:".concat(edge.id);
  2058. this.sectors['support']['nodes'][nodeId] = new Node(
  2059. {id:nodeId,
  2060. mass:1,
  2061. shape:'circle',
  2062. image:"",
  2063. internalMultiplier:1
  2064. },{},{},this.constants);
  2065. edge.via = this.sectors['support']['nodes'][nodeId];
  2066. edge.via.parentEdgeId = edge.id;
  2067. edge.positionBezierNode();
  2068. }
  2069. }
  2070. }
  2071. }
  2072. };
  2073. /**
  2074. * load the functions that load the mixins into the prototype.
  2075. *
  2076. * @private
  2077. */
  2078. Network.prototype._initializeMixinLoaders = function () {
  2079. for (var mixin in MixinLoader) {
  2080. if (MixinLoader.hasOwnProperty(mixin)) {
  2081. Network.prototype[mixin] = MixinLoader[mixin];
  2082. }
  2083. }
  2084. };
  2085. /**
  2086. * Load the XY positions of the nodes into the dataset.
  2087. */
  2088. Network.prototype.storePosition = function() {
  2089. console.log("storePosition is depricated: use .storePositions() from now on.")
  2090. this.storePositions();
  2091. };
  2092. /**
  2093. * Load the XY positions of the nodes into the dataset.
  2094. */
  2095. Network.prototype.storePositions = function() {
  2096. var dataArray = [];
  2097. for (var nodeId in this.nodes) {
  2098. if (this.nodes.hasOwnProperty(nodeId)) {
  2099. var node = this.nodes[nodeId];
  2100. var allowedToMoveX = !this.nodes.xFixed;
  2101. var allowedToMoveY = !this.nodes.yFixed;
  2102. if (this.nodesData._data[nodeId].x != Math.round(node.x) || this.nodesData._data[nodeId].y != Math.round(node.y)) {
  2103. dataArray.push({id:nodeId,x:Math.round(node.x),y:Math.round(node.y),allowedToMoveX:allowedToMoveX,allowedToMoveY:allowedToMoveY});
  2104. }
  2105. }
  2106. }
  2107. this.nodesData.update(dataArray);
  2108. };
  2109. /**
  2110. * Return the positions of the nodes.
  2111. */
  2112. Network.prototype.getPositions = function(ids) {
  2113. var dataArray = {};
  2114. if (ids !== undefined) {
  2115. if (Array.isArray(ids) == true) {
  2116. for (var i = 0; i < ids.length; i++) {
  2117. if (this.nodes[ids[i]] !== undefined) {
  2118. var node = this.nodes[ids[i]];
  2119. dataArray[ids[i]] = {x: Math.round(node.x), y: Math.round(node.y)};
  2120. }
  2121. }
  2122. }
  2123. else {
  2124. if (this.nodes[ids] !== undefined) {
  2125. var node = this.nodes[ids];
  2126. dataArray[ids] = {x: Math.round(node.x), y: Math.round(node.y)};
  2127. }
  2128. }
  2129. }
  2130. else {
  2131. for (var nodeId in this.nodes) {
  2132. if (this.nodes.hasOwnProperty(nodeId)) {
  2133. var node = this.nodes[nodeId];
  2134. dataArray[nodeId] = {x: Math.round(node.x), y: Math.round(node.y)};
  2135. }
  2136. }
  2137. }
  2138. return dataArray;
  2139. };
  2140. /**
  2141. * Center a node in view.
  2142. *
  2143. * @param {Number} nodeId
  2144. * @param {Number} [options]
  2145. */
  2146. Network.prototype.focusOnNode = function (nodeId, options) {
  2147. if (this.nodes.hasOwnProperty(nodeId)) {
  2148. if (options === undefined) {
  2149. options = {};
  2150. }
  2151. var nodePosition = {x: this.nodes[nodeId].x, y: this.nodes[nodeId].y};
  2152. options.position = nodePosition;
  2153. options.lockedOnNode = nodeId;
  2154. this.moveTo(options)
  2155. }
  2156. else {
  2157. console.log("This nodeId cannot be found.");
  2158. }
  2159. };
  2160. /**
  2161. *
  2162. * @param {Object} options | options.offset = {x:Number, y:Number} // offset from the center in DOM pixels
  2163. * | options.scale = Number // scale to move to
  2164. * | options.position = {x:Number, y:Number} // position to move to
  2165. * | options.animation = {duration:Number, easingFunction:String} || Boolean // position to move to
  2166. */
  2167. Network.prototype.moveTo = function (options) {
  2168. if (options === undefined) {
  2169. options = {};
  2170. return;
  2171. }
  2172. if (options.offset === undefined) {options.offset = {x: 0, y: 0}; }
  2173. if (options.offset.x === undefined) {options.offset.x = 0; }
  2174. if (options.offset.y === undefined) {options.offset.y = 0; }
  2175. if (options.scale === undefined) {options.scale = this._getScale(); }
  2176. if (options.position === undefined) {options.position = this._getTranslation();}
  2177. if (options.animation === undefined) {options.animation = {duration:0}; }
  2178. if (options.animation === false ) {options.animation = {duration:0}; }
  2179. if (options.animation === true ) {options.animation = {}; }
  2180. if (options.animation.duration === undefined) {options.animation.duration = 1000; } // default duration
  2181. if (options.animation.easingFunction === undefined) {options.animation.easingFunction = "easeInOutQuad"; } // default easing function
  2182. this.animateView(options);
  2183. };
  2184. /**
  2185. *
  2186. * @param {Object} options | options.offset = {x:Number, y:Number} // offset from the center in DOM pixels
  2187. * | options.time = Number // animation time in milliseconds
  2188. * | options.scale = Number // scale to animate to
  2189. * | options.position = {x:Number, y:Number} // position to animate to
  2190. * | options.easingFunction = String // linear, easeInQuad, easeOutQuad, easeInOutQuad,
  2191. * // easeInCubic, easeOutCubic, easeInOutCubic,
  2192. * // easeInQuart, easeOutQuart, easeInOutQuart,
  2193. * // easeInQuint, easeOutQuint, easeInOutQuint
  2194. */
  2195. Network.prototype.animateView = function (options) {
  2196. if (options === undefined) {
  2197. options = {};
  2198. return;
  2199. }
  2200. // release if something focussed on the node
  2201. this.releaseNode();
  2202. if (options.locked == true) {
  2203. this.lockedOnNodeId = options.lockedOnNode;
  2204. this.lockedOnNodeOffset = options.offset;
  2205. }
  2206. // forcefully complete the old animation if it was still running
  2207. if (this.easingTime != 0) {
  2208. this._transitionRedraw(1); // by setting easingtime to 1, we finish the animation.
  2209. }
  2210. this.sourceScale = this._getScale();
  2211. this.sourceTranslation = this._getTranslation();
  2212. this.targetScale = options.scale;
  2213. // set the scale so the viewCenter is based on the correct zoom level. This is overridden in the transitionRedraw
  2214. // but at least then we'll have the target transition
  2215. this._setScale(this.targetScale);
  2216. var viewCenter = this.DOMtoCanvas({x: 0.5 * this.frame.canvas.clientWidth, y: 0.5 * this.frame.canvas.clientHeight});
  2217. var distanceFromCenter = { // offset from view, distance view has to change by these x and y to center the node
  2218. x: viewCenter.x - options.position.x,
  2219. y: viewCenter.y - options.position.y
  2220. };
  2221. this.targetTranslation = {
  2222. x: this.sourceTranslation.x + distanceFromCenter.x * this.targetScale + options.offset.x,
  2223. y: this.sourceTranslation.y + distanceFromCenter.y * this.targetScale + options.offset.y
  2224. };
  2225. // if the time is set to 0, don't do an animation
  2226. if (options.animation.duration == 0) {
  2227. if (this.lockedOnNodeId != null) {
  2228. this._classicRedraw = this._redraw;
  2229. this._redraw = this._lockedRedraw;
  2230. }
  2231. else {
  2232. this._setScale(this.targetScale);
  2233. this._setTranslation(this.targetTranslation.x, this.targetTranslation.y);
  2234. this._redraw();
  2235. }
  2236. }
  2237. else {
  2238. this.animationSpeed = 1 / (this.renderRefreshRate * options.animation.duration * 0.001) || 1 / this.renderRefreshRate;
  2239. this.animationEasingFunction = options.animation.easingFunction;
  2240. this._classicRedraw = this._redraw;
  2241. this._redraw = this._transitionRedraw;
  2242. this._redraw();
  2243. this.moving = true;
  2244. this.start();
  2245. }
  2246. };
  2247. /**
  2248. * used to animate smoothly by hijacking the redraw function.
  2249. * @private
  2250. */
  2251. Network.prototype._lockedRedraw = function () {
  2252. var nodePosition = {x: this.nodes[this.lockedOnNodeId].x, y: this.nodes[this.lockedOnNodeId].y};
  2253. var viewCenter = this.DOMtoCanvas({x: 0.5 * this.frame.canvas.clientWidth, y: 0.5 * this.frame.canvas.clientHeight});
  2254. var distanceFromCenter = { // offset from view, distance view has to change by these x and y to center the node
  2255. x: viewCenter.x - nodePosition.x,
  2256. y: viewCenter.y - nodePosition.y
  2257. };
  2258. var sourceTranslation = this._getTranslation();
  2259. var targetTranslation = {
  2260. x: sourceTranslation.x + distanceFromCenter.x * this.scale + this.lockedOnNodeOffset.x,
  2261. y: sourceTranslation.y + distanceFromCenter.y * this.scale + this.lockedOnNodeOffset.y
  2262. };
  2263. this._setTranslation(targetTranslation.x,targetTranslation.y);
  2264. this._classicRedraw();
  2265. }
  2266. Network.prototype.releaseNode = function () {
  2267. if (this.lockedOnNodeId != null) {
  2268. this._redraw = this._classicRedraw;
  2269. this.lockedOnNodeId = null;
  2270. this.lockedOnNodeOffset = null;
  2271. }
  2272. }
  2273. /**
  2274. *
  2275. * @param easingTime
  2276. * @private
  2277. */
  2278. Network.prototype._transitionRedraw = function (easingTime) {
  2279. this.easingTime = easingTime || this.easingTime + this.animationSpeed;
  2280. this.easingTime += this.animationSpeed;
  2281. var progress = util.easingFunctions[this.animationEasingFunction](this.easingTime);
  2282. this._setScale(this.sourceScale + (this.targetScale - this.sourceScale) * progress);
  2283. this._setTranslation(
  2284. this.sourceTranslation.x + (this.targetTranslation.x - this.sourceTranslation.x) * progress,
  2285. this.sourceTranslation.y + (this.targetTranslation.y - this.sourceTranslation.y) * progress
  2286. );
  2287. this._classicRedraw();
  2288. this.moving = true;
  2289. // cleanup
  2290. if (this.easingTime >= 1.0) {
  2291. this.easingTime = 0;
  2292. if (this.lockedOnNodeId != null) {
  2293. this._redraw = this._lockedRedraw;
  2294. }
  2295. else {
  2296. this._redraw = this._classicRedraw;
  2297. }
  2298. this.emit("animationFinished");
  2299. }
  2300. };
  2301. Network.prototype._classicRedraw = function () {
  2302. // placeholder function to be overloaded by animations;
  2303. };
  2304. /**
  2305. * Returns true when the Network is active.
  2306. * @returns {boolean}
  2307. */
  2308. Network.prototype.isActive = function () {
  2309. return !this.activator || this.activator.active;
  2310. };
  2311. /**
  2312. * Sets the scale
  2313. * @returns {Number}
  2314. */
  2315. Network.prototype.setScale = function () {
  2316. return this._setScale();
  2317. };
  2318. /**
  2319. * Returns the scale
  2320. * @returns {Number}
  2321. */
  2322. Network.prototype.getScale = function () {
  2323. return this._getScale();
  2324. };
  2325. /**
  2326. * Returns the scale
  2327. * @returns {Number}
  2328. */
  2329. Network.prototype.getCenterCoordinates = function () {
  2330. return this.DOMtoCanvas({x: 0.5 * this.frame.canvas.clientWidth, y: 0.5 * this.frame.canvas.clientHeight});
  2331. };
  2332. module.exports = Network;