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.

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