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.

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