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.

1163 lines
35 KiB

10 years ago
10 years ago
  1. var util = require('../util');
  2. var Node = require('./Node');
  3. /**
  4. * @class Edge
  5. *
  6. * A edge connects two nodes
  7. * @param {Object} properties Object with properties. Must contain
  8. * At least properties from and to.
  9. * Available properties: from (number),
  10. * to (number), label (string, color (string),
  11. * width (number), style (string),
  12. * length (number), title (string)
  13. * @param {Network} network A Network object, used to find and edge to
  14. * nodes.
  15. * @param {Object} constants An object with default values for
  16. * example for the color
  17. */
  18. function Edge (properties, network, networkConstants) {
  19. if (!network) {
  20. throw "No network provided";
  21. }
  22. var fields = ['edges','physics'];
  23. var constants = util.selectiveBridgeObject(fields,networkConstants);
  24. this.options = constants.edges;
  25. this.physics = constants.physics;
  26. this.options['smoothCurves'] = networkConstants['smoothCurves'];
  27. this.network = network;
  28. // initialize variables
  29. this.id = undefined;
  30. this.fromId = undefined;
  31. this.toId = undefined;
  32. this.title = undefined;
  33. this.widthSelected = this.options.width * this.options.widthSelectionMultiplier;
  34. this.value = undefined;
  35. this.selected = false;
  36. this.hover = false;
  37. this.from = null; // a node
  38. this.to = null; // a node
  39. this.via = null; // a temp node
  40. // we use this to be able to reconnect the edge to a cluster if its node is put into a cluster
  41. // by storing the original information we can revert to the original connection when the cluser is opened.
  42. this.originalFromId = [];
  43. this.originalToId = [];
  44. this.connected = false;
  45. this.widthFixed = false;
  46. this.lengthFixed = false;
  47. this.setProperties(properties);
  48. this.controlNodesEnabled = false;
  49. this.controlNodes = {from:null, to:null, positions:{}};
  50. this.connectedNode = null;
  51. }
  52. /**
  53. * Set or overwrite properties for the edge
  54. * @param {Object} properties an object with properties
  55. * @param {Object} constants and object with default, global properties
  56. */
  57. Edge.prototype.setProperties = function(properties) {
  58. if (!properties) {
  59. return;
  60. }
  61. var fields = ['style','fontSize','fontFace','fontColor','fontFill','width',
  62. 'widthSelectionMultiplier','hoverWidth','arrowScaleFactor','dash'
  63. ];
  64. util.selectiveDeepExtend(fields, this.options, properties);
  65. if (properties.from !== undefined) {this.fromId = properties.from;}
  66. if (properties.to !== undefined) {this.toId = properties.to;}
  67. if (properties.id !== undefined) {this.id = properties.id;}
  68. if (properties.label !== undefined) {this.label = properties.label;}
  69. if (properties.title !== undefined) {this.title = properties.title;}
  70. if (properties.value !== undefined) {this.value = properties.value;}
  71. if (properties.length !== undefined) {this.physics.springLength = properties.length;}
  72. // scale the arrow
  73. if (properties.arrowScaleFactor !== undefined) {this.options.arrowScaleFactor = properties.arrowScaleFactor;}
  74. if (properties.inheritColor !== undefined) {this.options.inheritColor = properties.inheritColor;}
  75. if (properties.color !== undefined) {
  76. this.options.inheritColor = false;
  77. if (util.isString(properties.color)) {
  78. this.options.color.color = properties.color;
  79. this.options.color.highlight = properties.color;
  80. }
  81. else {
  82. if (properties.color.color !== undefined) {this.options.color.color = properties.color.color;}
  83. if (properties.color.highlight !== undefined) {this.options.color.highlight = properties.color.highlight;}
  84. if (properties.color.hover !== undefined) {this.options.color.hover = properties.color.hover;}
  85. }
  86. }
  87. // A node is connected when it has a from and to node.
  88. this.connect();
  89. this.widthFixed = this.widthFixed || (properties.width !== undefined);
  90. this.lengthFixed = this.lengthFixed || (properties.length !== undefined);
  91. this.widthSelected = this.options.width* this.options.widthSelectionMultiplier;
  92. // set draw method based on style
  93. switch (this.options.style) {
  94. case 'line': this.draw = this._drawLine; break;
  95. case 'arrow': this.draw = this._drawArrow; break;
  96. case 'arrow-center': this.draw = this._drawArrowCenter; break;
  97. case 'dash-line': this.draw = this._drawDashLine; break;
  98. default: this.draw = this._drawLine; break;
  99. }
  100. };
  101. /**
  102. * Connect an edge to its nodes
  103. */
  104. Edge.prototype.connect = function () {
  105. this.disconnect();
  106. this.from = this.network.nodes[this.fromId] || null;
  107. this.to = this.network.nodes[this.toId] || null;
  108. this.connected = (this.from && this.to);
  109. if (this.connected) {
  110. this.from.attachEdge(this);
  111. this.to.attachEdge(this);
  112. }
  113. else {
  114. if (this.from) {
  115. this.from.detachEdge(this);
  116. }
  117. if (this.to) {
  118. this.to.detachEdge(this);
  119. }
  120. }
  121. };
  122. /**
  123. * Disconnect an edge from its nodes
  124. */
  125. Edge.prototype.disconnect = function () {
  126. if (this.from) {
  127. this.from.detachEdge(this);
  128. this.from = null;
  129. }
  130. if (this.to) {
  131. this.to.detachEdge(this);
  132. this.to = null;
  133. }
  134. this.connected = false;
  135. };
  136. /**
  137. * get the title of this edge.
  138. * @return {string} title The title of the edge, or undefined when no title
  139. * has been set.
  140. */
  141. Edge.prototype.getTitle = function() {
  142. return typeof this.title === "function" ? this.title() : this.title;
  143. };
  144. /**
  145. * Retrieve the value of the edge. Can be undefined
  146. * @return {Number} value
  147. */
  148. Edge.prototype.getValue = function() {
  149. return this.value;
  150. };
  151. /**
  152. * Adjust the value range of the edge. The edge will adjust it's width
  153. * based on its value.
  154. * @param {Number} min
  155. * @param {Number} max
  156. */
  157. Edge.prototype.setValueRange = function(min, max) {
  158. if (!this.widthFixed && this.value !== undefined) {
  159. var scale = (this.options.widthMax - this.options.widthMin) / (max - min);
  160. this.options.width= (this.value - min) * scale + this.options.widthMin;
  161. this.widthSelected = this.options.width* this.options.widthSelectionMultiplier;
  162. }
  163. };
  164. /**
  165. * Redraw a edge
  166. * Draw this edge in the given canvas
  167. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  168. * @param {CanvasRenderingContext2D} ctx
  169. */
  170. Edge.prototype.draw = function(ctx) {
  171. throw "Method draw not initialized in edge";
  172. };
  173. /**
  174. * Check if this object is overlapping with the provided object
  175. * @param {Object} obj an object with parameters left, top
  176. * @return {boolean} True if location is located on the edge
  177. */
  178. Edge.prototype.isOverlappingWith = function(obj) {
  179. if (this.connected) {
  180. var distMax = 10;
  181. var xFrom = this.from.x;
  182. var yFrom = this.from.y;
  183. var xTo = this.to.x;
  184. var yTo = this.to.y;
  185. var xObj = obj.left;
  186. var yObj = obj.top;
  187. var dist = this._getDistanceToEdge(xFrom, yFrom, xTo, yTo, xObj, yObj);
  188. return (dist < distMax);
  189. }
  190. else {
  191. return false
  192. }
  193. };
  194. Edge.prototype._getColor = function() {
  195. var colorObj = this.options.color;
  196. if (this.options.inheritColor == "to") {
  197. colorObj = {
  198. highlight: this.to.options.color.highlight.border,
  199. hover: this.to.options.color.hover.border,
  200. color: this.to.options.color.border
  201. };
  202. }
  203. else if (this.options.inheritColor == "from" || this.options.inheritColor == true) {
  204. colorObj = {
  205. highlight: this.from.options.color.highlight.border,
  206. hover: this.from.options.color.hover.border,
  207. color: this.from.options.color.border
  208. };
  209. }
  210. if (this.selected == true) {return colorObj.highlight;}
  211. else if (this.hover == true) {return colorObj.hover;}
  212. else {return colorObj.color;}
  213. }
  214. /**
  215. * Redraw a edge as a line
  216. * Draw this edge in the given canvas
  217. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  218. * @param {CanvasRenderingContext2D} ctx
  219. * @private
  220. */
  221. Edge.prototype._drawLine = function(ctx) {
  222. // set style
  223. ctx.strokeStyle = this._getColor();
  224. ctx.lineWidth = this._getLineWidth();
  225. if (this.from != this.to) {
  226. // draw line
  227. var via = this._line(ctx);
  228. // draw label
  229. var point;
  230. if (this.label) {
  231. if (this.options.smoothCurves.enabled == true && via != null) {
  232. var midpointX = 0.5*(0.5*(this.from.x + via.x) + 0.5*(this.to.x + via.x));
  233. var midpointY = 0.5*(0.5*(this.from.y + via.y) + 0.5*(this.to.y + via.y));
  234. point = {x:midpointX, y:midpointY};
  235. }
  236. else {
  237. point = this._pointOnLine(0.5);
  238. }
  239. this._label(ctx, this.label, point.x, point.y);
  240. }
  241. }
  242. else {
  243. var x, y;
  244. var radius = this.physics.springLength / 4;
  245. var node = this.from;
  246. if (!node.width) {
  247. node.resize(ctx);
  248. }
  249. if (node.width > node.height) {
  250. x = node.x + node.width / 2;
  251. y = node.y - radius;
  252. }
  253. else {
  254. x = node.x + radius;
  255. y = node.y - node.height / 2;
  256. }
  257. this._circle(ctx, x, y, radius);
  258. point = this._pointOnCircle(x, y, radius, 0.5);
  259. this._label(ctx, this.label, point.x, point.y);
  260. }
  261. };
  262. /**
  263. * Get the line width of the edge. Depends on width and whether one of the
  264. * connected nodes is selected.
  265. * @return {Number} width
  266. * @private
  267. */
  268. Edge.prototype._getLineWidth = function() {
  269. if (this.selected == true) {
  270. return Math.min(this.widthSelected, this.options.widthMax)*this.networkScaleInv;
  271. }
  272. else {
  273. if (this.hover == true) {
  274. return Math.min(this.options.hoverWidth, this.options.widthMax)*this.networkScaleInv;
  275. }
  276. else {
  277. return this.options.width*this.networkScaleInv;
  278. }
  279. }
  280. };
  281. Edge.prototype._getViaCoordinates = function () {
  282. var xVia = null;
  283. var yVia = null;
  284. var factor = this.options.smoothCurves.roundness;
  285. var type = this.options.smoothCurves.type;
  286. var dx = Math.abs(this.from.x - this.to.x);
  287. var dy = Math.abs(this.from.y - this.to.y);
  288. if (type == 'discrete' || type == 'diagonalCross') {
  289. if (Math.abs(this.from.x - this.to.x) < Math.abs(this.from.y - this.to.y)) {
  290. if (this.from.y > this.to.y) {
  291. if (this.from.x < this.to.x) {
  292. xVia = this.from.x + factor * dy;
  293. yVia = this.from.y - factor * dy;
  294. }
  295. else if (this.from.x > this.to.x) {
  296. xVia = this.from.x - factor * dy;
  297. yVia = this.from.y - factor * dy;
  298. }
  299. }
  300. else if (this.from.y < this.to.y) {
  301. if (this.from.x < this.to.x) {
  302. xVia = this.from.x + factor * dy;
  303. yVia = this.from.y + factor * dy;
  304. }
  305. else if (this.from.x > this.to.x) {
  306. xVia = this.from.x - factor * dy;
  307. yVia = this.from.y + factor * dy;
  308. }
  309. }
  310. if (type == "discrete") {
  311. xVia = dx < factor * dy ? this.from.x : xVia;
  312. }
  313. }
  314. else if (Math.abs(this.from.x - this.to.x) > Math.abs(this.from.y - this.to.y)) {
  315. if (this.from.y > this.to.y) {
  316. if (this.from.x < this.to.x) {
  317. xVia = this.from.x + factor * dx;
  318. yVia = this.from.y - factor * dx;
  319. }
  320. else if (this.from.x > this.to.x) {
  321. xVia = this.from.x - factor * dx;
  322. yVia = this.from.y - factor * dx;
  323. }
  324. }
  325. else if (this.from.y < this.to.y) {
  326. if (this.from.x < this.to.x) {
  327. xVia = this.from.x + factor * dx;
  328. yVia = this.from.y + factor * dx;
  329. }
  330. else if (this.from.x > this.to.x) {
  331. xVia = this.from.x - factor * dx;
  332. yVia = this.from.y + factor * dx;
  333. }
  334. }
  335. if (type == "discrete") {
  336. yVia = dy < factor * dx ? this.from.y : yVia;
  337. }
  338. }
  339. }
  340. else if (type == "straightCross") {
  341. if (Math.abs(this.from.x - this.to.x) < Math.abs(this.from.y - this.to.y)) { // up - down
  342. xVia = this.from.x;
  343. if (this.from.y < this.to.y) {
  344. yVia = this.to.y - (1-factor) * dy;
  345. }
  346. else {
  347. yVia = this.to.y + (1-factor) * dy;
  348. }
  349. }
  350. else if (Math.abs(this.from.x - this.to.x) > Math.abs(this.from.y - this.to.y)) { // left - right
  351. if (this.from.x < this.to.x) {
  352. xVia = this.to.x - (1-factor) * dx;
  353. }
  354. else {
  355. xVia = this.to.x + (1-factor) * dx;
  356. }
  357. yVia = this.from.y;
  358. }
  359. }
  360. else if (type == 'horizontal') {
  361. if (this.from.x < this.to.x) {
  362. xVia = this.to.x - (1-factor) * dx;
  363. }
  364. else {
  365. xVia = this.to.x + (1-factor) * dx;
  366. }
  367. yVia = this.from.y;
  368. }
  369. else if (type == 'vertical') {
  370. xVia = this.from.x;
  371. if (this.from.y < this.to.y) {
  372. yVia = this.to.y - (1-factor) * dy;
  373. }
  374. else {
  375. yVia = this.to.y + (1-factor) * dy;
  376. }
  377. }
  378. else { // continuous
  379. if (Math.abs(this.from.x - this.to.x) < Math.abs(this.from.y - this.to.y)) {
  380. if (this.from.y > this.to.y) {
  381. if (this.from.x < this.to.x) {
  382. // console.log(1)
  383. xVia = this.from.x + factor * dy;
  384. yVia = this.from.y - factor * dy;
  385. xVia = this.to.x < xVia ? this.to.x : xVia;
  386. }
  387. else if (this.from.x > this.to.x) {
  388. // console.log(2)
  389. xVia = this.from.x - factor * dy;
  390. yVia = this.from.y - factor * dy;
  391. xVia = this.to.x > xVia ? this.to.x :xVia;
  392. }
  393. }
  394. else if (this.from.y < this.to.y) {
  395. if (this.from.x < this.to.x) {
  396. // console.log(3)
  397. xVia = this.from.x + factor * dy;
  398. yVia = this.from.y + factor * dy;
  399. xVia = this.to.x < xVia ? this.to.x : xVia;
  400. }
  401. else if (this.from.x > this.to.x) {
  402. // console.log(4, this.from.x, this.to.x)
  403. xVia = this.from.x - factor * dy;
  404. yVia = this.from.y + factor * dy;
  405. xVia = this.to.x > xVia ? this.to.x : xVia;
  406. }
  407. }
  408. }
  409. else if (Math.abs(this.from.x - this.to.x) > Math.abs(this.from.y - this.to.y)) {
  410. if (this.from.y > this.to.y) {
  411. if (this.from.x < this.to.x) {
  412. // console.log(5)
  413. xVia = this.from.x + factor * dx;
  414. yVia = this.from.y - factor * dx;
  415. yVia = this.to.y > yVia ? this.to.y : yVia;
  416. }
  417. else if (this.from.x > this.to.x) {
  418. // console.log(6)
  419. xVia = this.from.x - factor * dx;
  420. yVia = this.from.y - factor * dx;
  421. yVia = this.to.y > yVia ? this.to.y : yVia;
  422. }
  423. }
  424. else if (this.from.y < this.to.y) {
  425. if (this.from.x < this.to.x) {
  426. // console.log(7)
  427. xVia = this.from.x + factor * dx;
  428. yVia = this.from.y + factor * dx;
  429. yVia = this.to.y < yVia ? this.to.y : yVia;
  430. }
  431. else if (this.from.x > this.to.x) {
  432. // console.log(8)
  433. xVia = this.from.x - factor * dx;
  434. yVia = this.from.y + factor * dx;
  435. yVia = this.to.y < yVia ? this.to.y : yVia;
  436. }
  437. }
  438. }
  439. }
  440. return {x:xVia, y:yVia};
  441. }
  442. /**
  443. * Draw a line between two nodes
  444. * @param {CanvasRenderingContext2D} ctx
  445. * @private
  446. */
  447. Edge.prototype._line = function (ctx) {
  448. // draw a straight line
  449. ctx.beginPath();
  450. ctx.moveTo(this.from.x, this.from.y);
  451. if (this.options.smoothCurves.enabled == true) {
  452. if (this.options.smoothCurves.dynamic == false) {
  453. var via = this._getViaCoordinates();
  454. if (via.x == null) {
  455. ctx.lineTo(this.to.x, this.to.y);
  456. ctx.stroke();
  457. return null;
  458. }
  459. else {
  460. // this.via.x = via.x;
  461. // this.via.y = via.y;
  462. ctx.quadraticCurveTo(via.x,via.y,this.to.x, this.to.y);
  463. ctx.stroke();
  464. return via;
  465. }
  466. }
  467. else {
  468. ctx.quadraticCurveTo(this.via.x,this.via.y,this.to.x, this.to.y);
  469. ctx.stroke();
  470. return this.via;
  471. }
  472. }
  473. else {
  474. ctx.lineTo(this.to.x, this.to.y);
  475. ctx.stroke();
  476. return null;
  477. }
  478. };
  479. /**
  480. * Draw a line from a node to itself, a circle
  481. * @param {CanvasRenderingContext2D} ctx
  482. * @param {Number} x
  483. * @param {Number} y
  484. * @param {Number} radius
  485. * @private
  486. */
  487. Edge.prototype._circle = function (ctx, x, y, radius) {
  488. // draw a circle
  489. ctx.beginPath();
  490. ctx.arc(x, y, radius, 0, 2 * Math.PI, false);
  491. ctx.stroke();
  492. };
  493. /**
  494. * Draw label with white background and with the middle at (x, y)
  495. * @param {CanvasRenderingContext2D} ctx
  496. * @param {String} text
  497. * @param {Number} x
  498. * @param {Number} y
  499. * @private
  500. */
  501. Edge.prototype._label = function (ctx, text, x, y) {
  502. if (text) {
  503. // TODO: cache the calculated size
  504. ctx.font = ((this.from.selected || this.to.selected) ? "bold " : "") +
  505. this.options.fontSize + "px " + this.options.fontFace;
  506. ctx.fillStyle = this.options.fontFill;
  507. var width = ctx.measureText(text).width;
  508. var height = this.options.fontSize;
  509. var left = x - width / 2;
  510. var top = y - height / 2;
  511. ctx.fillRect(left, top, width, height);
  512. // draw text
  513. ctx.fillStyle = this.options.fontColor || "black";
  514. ctx.textAlign = "left";
  515. ctx.textBaseline = "top";
  516. ctx.fillText(text, left, top);
  517. }
  518. };
  519. /**
  520. * Redraw a edge as a dashed line
  521. * Draw this edge in the given canvas
  522. * @author David Jordan
  523. * @date 2012-08-08
  524. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  525. * @param {CanvasRenderingContext2D} ctx
  526. * @private
  527. */
  528. Edge.prototype._drawDashLine = function(ctx) {
  529. // set style
  530. if (this.selected == true) {ctx.strokeStyle = this.options.color.highlight;}
  531. else if (this.hover == true) {ctx.strokeStyle = this.options.color.hover;}
  532. else {ctx.strokeStyle = this.options.color.color;}
  533. ctx.lineWidth = this._getLineWidth();
  534. var via = null;
  535. // only firefox and chrome support this method, else we use the legacy one.
  536. if (ctx.mozDash !== undefined || ctx.setLineDash !== undefined) {
  537. // configure the dash pattern
  538. var pattern = [0];
  539. if (this.options.dash.length !== undefined && this.options.dash.gap !== undefined) {
  540. pattern = [this.options.dash.length,this.options.dash.gap];
  541. }
  542. else {
  543. pattern = [5,5];
  544. }
  545. // set dash settings for chrome or firefox
  546. if (typeof ctx.setLineDash !== 'undefined') { //Chrome
  547. ctx.setLineDash(pattern);
  548. ctx.lineDashOffset = 0;
  549. } else { //Firefox
  550. ctx.mozDash = pattern;
  551. ctx.mozDashOffset = 0;
  552. }
  553. // draw the line
  554. via = this._line(ctx);
  555. // restore the dash settings.
  556. if (typeof ctx.setLineDash !== 'undefined') { //Chrome
  557. ctx.setLineDash([0]);
  558. ctx.lineDashOffset = 0;
  559. } else { //Firefox
  560. ctx.mozDash = [0];
  561. ctx.mozDashOffset = 0;
  562. }
  563. }
  564. else { // unsupporting smooth lines
  565. // draw dashed line
  566. ctx.beginPath();
  567. ctx.lineCap = 'round';
  568. if (this.options.dash.altLength !== undefined) //If an alt dash value has been set add to the array this value
  569. {
  570. ctx.dashedLine(this.from.x,this.from.y,this.to.x,this.to.y,
  571. [this.options.dash.length,this.options.dash.gap,this.options.dash.altLength,this.options.dash.gap]);
  572. }
  573. else if (this.options.dash.length !== undefined && this.options.dash.gap !== undefined) //If a dash and gap value has been set add to the array this value
  574. {
  575. ctx.dashedLine(this.from.x,this.from.y,this.to.x,this.to.y,
  576. [this.options.dash.length,this.options.dash.gap]);
  577. }
  578. else //If all else fails draw a line
  579. {
  580. ctx.moveTo(this.from.x, this.from.y);
  581. ctx.lineTo(this.to.x, this.to.y);
  582. }
  583. ctx.stroke();
  584. }
  585. // draw label
  586. if (this.label) {
  587. var point;
  588. if (this.options.smoothCurves.enabled == true && via != null) {
  589. var midpointX = 0.5*(0.5*(this.from.x + via.x) + 0.5*(this.to.x + via.x));
  590. var midpointY = 0.5*(0.5*(this.from.y + via.y) + 0.5*(this.to.y + via.y));
  591. point = {x:midpointX, y:midpointY};
  592. }
  593. else {
  594. point = this._pointOnLine(0.5);
  595. }
  596. this._label(ctx, this.label, point.x, point.y);
  597. }
  598. };
  599. /**
  600. * Get a point on a line
  601. * @param {Number} percentage. Value between 0 (line start) and 1 (line end)
  602. * @return {Object} point
  603. * @private
  604. */
  605. Edge.prototype._pointOnLine = function (percentage) {
  606. return {
  607. x: (1 - percentage) * this.from.x + percentage * this.to.x,
  608. y: (1 - percentage) * this.from.y + percentage * this.to.y
  609. }
  610. };
  611. /**
  612. * Get a point on a circle
  613. * @param {Number} x
  614. * @param {Number} y
  615. * @param {Number} radius
  616. * @param {Number} percentage. Value between 0 (line start) and 1 (line end)
  617. * @return {Object} point
  618. * @private
  619. */
  620. Edge.prototype._pointOnCircle = function (x, y, radius, percentage) {
  621. var angle = (percentage - 3/8) * 2 * Math.PI;
  622. return {
  623. x: x + radius * Math.cos(angle),
  624. y: y - radius * Math.sin(angle)
  625. }
  626. };
  627. /**
  628. * Redraw a edge as a line with an arrow halfway the line
  629. * Draw this edge in the given canvas
  630. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  631. * @param {CanvasRenderingContext2D} ctx
  632. * @private
  633. */
  634. Edge.prototype._drawArrowCenter = function(ctx) {
  635. var point;
  636. // set style
  637. if (this.selected == true) {ctx.strokeStyle = this.options.color.highlight; ctx.fillStyle = this.options.color.highlight;}
  638. else if (this.hover == true) {ctx.strokeStyle = this.options.color.hover; ctx.fillStyle = this.options.color.hover;}
  639. else {ctx.strokeStyle = this.options.color.color; ctx.fillStyle = this.options.color.color;}
  640. ctx.lineWidth = this._getLineWidth();
  641. if (this.from != this.to) {
  642. // draw line
  643. var via = this._line(ctx);
  644. var angle = Math.atan2((this.to.y - this.from.y), (this.to.x - this.from.x));
  645. var length = (10 + 5 * this.options.width) * this.options.arrowScaleFactor;
  646. // draw an arrow halfway the line
  647. if (this.options.smoothCurves.enabled == true && via != null) {
  648. var midpointX = 0.5*(0.5*(this.from.x + via.x) + 0.5*(this.to.x + via.x));
  649. var midpointY = 0.5*(0.5*(this.from.y + via.y) + 0.5*(this.to.y + via.y));
  650. point = {x:midpointX, y:midpointY};
  651. }
  652. else {
  653. point = this._pointOnLine(0.5);
  654. }
  655. ctx.arrow(point.x, point.y, angle, length);
  656. ctx.fill();
  657. ctx.stroke();
  658. // draw label
  659. if (this.label) {
  660. this._label(ctx, this.label, point.x, point.y);
  661. }
  662. }
  663. else {
  664. // draw circle
  665. var x, y;
  666. var radius = 0.25 * Math.max(100,this.physics.springLength);
  667. var node = this.from;
  668. if (!node.width) {
  669. node.resize(ctx);
  670. }
  671. if (node.width > node.height) {
  672. x = node.x + node.width * 0.5;
  673. y = node.y - radius;
  674. }
  675. else {
  676. x = node.x + radius;
  677. y = node.y - node.height * 0.5;
  678. }
  679. this._circle(ctx, x, y, radius);
  680. // draw all arrows
  681. var angle = 0.2 * Math.PI;
  682. var length = (10 + 5 * this.options.width) * this.options.arrowScaleFactor;
  683. point = this._pointOnCircle(x, y, radius, 0.5);
  684. ctx.arrow(point.x, point.y, angle, length);
  685. ctx.fill();
  686. ctx.stroke();
  687. // draw label
  688. if (this.label) {
  689. point = this._pointOnCircle(x, y, radius, 0.5);
  690. this._label(ctx, this.label, point.x, point.y);
  691. }
  692. }
  693. };
  694. /**
  695. * Redraw a edge as a line with an arrow
  696. * Draw this edge in the given canvas
  697. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  698. * @param {CanvasRenderingContext2D} ctx
  699. * @private
  700. */
  701. Edge.prototype._drawArrow = function(ctx) {
  702. // set style
  703. if (this.selected == true) {ctx.strokeStyle = this.options.color.highlight; ctx.fillStyle = this.options.color.highlight;}
  704. else if (this.hover == true) {ctx.strokeStyle = this.options.color.hover; ctx.fillStyle = this.options.color.hover;}
  705. else {ctx.strokeStyle = this.options.color.color; ctx.fillStyle = this.options.color.color;}
  706. ctx.lineWidth = this._getLineWidth();
  707. var angle, length;
  708. //draw a line
  709. if (this.from != this.to) {
  710. angle = Math.atan2((this.to.y - this.from.y), (this.to.x - this.from.x));
  711. var dx = (this.to.x - this.from.x);
  712. var dy = (this.to.y - this.from.y);
  713. var edgeSegmentLength = Math.sqrt(dx * dx + dy * dy);
  714. var fromBorderDist = this.from.distanceToBorder(ctx, angle + Math.PI);
  715. var fromBorderPoint = (edgeSegmentLength - fromBorderDist) / edgeSegmentLength;
  716. var xFrom = (fromBorderPoint) * this.from.x + (1 - fromBorderPoint) * this.to.x;
  717. var yFrom = (fromBorderPoint) * this.from.y + (1 - fromBorderPoint) * this.to.y;
  718. var via;
  719. if (this.options.smoothCurves.dynamic == true && this.options.smoothCurves.enabled == true ) {
  720. via = this.via;
  721. }
  722. else if (this.options.smoothCurves.enabled == true) {
  723. via = this._getViaCoordinates();
  724. }
  725. if (this.options.smoothCurves.enabled == true && via.x != null) {
  726. angle = Math.atan2((this.to.y - via.y), (this.to.x - via.x));
  727. dx = (this.to.x - via.x);
  728. dy = (this.to.y - via.y);
  729. edgeSegmentLength = Math.sqrt(dx * dx + dy * dy);
  730. }
  731. var toBorderDist = this.to.distanceToBorder(ctx, angle);
  732. var toBorderPoint = (edgeSegmentLength - toBorderDist) / edgeSegmentLength;
  733. var xTo,yTo;
  734. if (this.options.smoothCurves.enabled == true && via.x != null) {
  735. xTo = (1 - toBorderPoint) * via.x + toBorderPoint * this.to.x;
  736. yTo = (1 - toBorderPoint) * via.y + toBorderPoint * this.to.y;
  737. }
  738. else {
  739. xTo = (1 - toBorderPoint) * this.from.x + toBorderPoint * this.to.x;
  740. yTo = (1 - toBorderPoint) * this.from.y + toBorderPoint * this.to.y;
  741. }
  742. ctx.beginPath();
  743. ctx.moveTo(xFrom,yFrom);
  744. if (this.options.smoothCurves.enabled == true && via.x != null) {
  745. ctx.quadraticCurveTo(via.x,via.y,xTo, yTo);
  746. }
  747. else {
  748. ctx.lineTo(xTo, yTo);
  749. }
  750. ctx.stroke();
  751. // draw arrow at the end of the line
  752. length = (10 + 5 * this.options.width) * this.options.arrowScaleFactor;
  753. ctx.arrow(xTo, yTo, angle, length);
  754. ctx.fill();
  755. ctx.stroke();
  756. // draw label
  757. if (this.label) {
  758. var point;
  759. if (this.options.smoothCurves.enabled == true && via != null) {
  760. var midpointX = 0.5*(0.5*(this.from.x + via.x) + 0.5*(this.to.x + via.x));
  761. var midpointY = 0.5*(0.5*(this.from.y + via.y) + 0.5*(this.to.y + via.y));
  762. point = {x:midpointX, y:midpointY};
  763. }
  764. else {
  765. point = this._pointOnLine(0.5);
  766. }
  767. this._label(ctx, this.label, point.x, point.y);
  768. }
  769. }
  770. else {
  771. // draw circle
  772. var node = this.from;
  773. var x, y, arrow;
  774. var radius = 0.25 * Math.max(100,this.physics.springLength);
  775. if (!node.width) {
  776. node.resize(ctx);
  777. }
  778. if (node.width > node.height) {
  779. x = node.x + node.width * 0.5;
  780. y = node.y - radius;
  781. arrow = {
  782. x: x,
  783. y: node.y,
  784. angle: 0.9 * Math.PI
  785. };
  786. }
  787. else {
  788. x = node.x + radius;
  789. y = node.y - node.height * 0.5;
  790. arrow = {
  791. x: node.x,
  792. y: y,
  793. angle: 0.6 * Math.PI
  794. };
  795. }
  796. ctx.beginPath();
  797. // TODO: similarly, for a line without arrows, draw to the border of the nodes instead of the center
  798. ctx.arc(x, y, radius, 0, 2 * Math.PI, false);
  799. ctx.stroke();
  800. // draw all arrows
  801. var length = (10 + 5 * this.options.width) * this.options.arrowScaleFactor;
  802. ctx.arrow(arrow.x, arrow.y, arrow.angle, length);
  803. ctx.fill();
  804. ctx.stroke();
  805. // draw label
  806. if (this.label) {
  807. point = this._pointOnCircle(x, y, radius, 0.5);
  808. this._label(ctx, this.label, point.x, point.y);
  809. }
  810. }
  811. };
  812. /**
  813. * Calculate the distance between a point (x3,y3) and a line segment from
  814. * (x1,y1) to (x2,y2).
  815. * http://stackoverflow.com/questions/849211/shortest-distancae-between-a-point-and-a-line-segment
  816. * @param {number} x1
  817. * @param {number} y1
  818. * @param {number} x2
  819. * @param {number} y2
  820. * @param {number} x3
  821. * @param {number} y3
  822. * @private
  823. */
  824. Edge.prototype._getDistanceToEdge = function (x1,y1, x2,y2, x3,y3) { // x3,y3 is the point
  825. if (this.from != this.to) {
  826. if (this.options.smoothCurves.enabled == true) {
  827. var xVia, yVia;
  828. if (this.options.smoothCurves.enabled == true && this.options.smoothCurves.dynamic == true) {
  829. xVia = this.via.x;
  830. yVia = this.via.y;
  831. }
  832. else {
  833. var via = this._getViaCoordinates();
  834. xVia = via.x;
  835. yVia = via.y;
  836. }
  837. var minDistance = 1e9;
  838. var distance;
  839. var i,t,x,y, lastX, lastY;
  840. for (i = 0; i < 10; i++) {
  841. t = 0.1*i;
  842. x = Math.pow(1-t,2)*x1 + (2*t*(1 - t))*xVia + Math.pow(t,2)*x2;
  843. y = Math.pow(1-t,2)*y1 + (2*t*(1 - t))*yVia + Math.pow(t,2)*y2;
  844. if (i > 0) {
  845. distance = this._getDistanceToLine(lastX,lastY,x,y, x3,y3);
  846. minDistance = distance < minDistance ? distance : minDistance;
  847. }
  848. lastX = x; lastY = y;
  849. }
  850. return minDistance
  851. }
  852. else {
  853. return this._getDistanceToLine(x1,y1,x2,y2,x3,y3);
  854. }
  855. }
  856. else {
  857. var x, y, dx, dy;
  858. var radius = this.physics.springLength / 4;
  859. var node = this.from;
  860. if (!node.width) {
  861. node.resize(ctx);
  862. }
  863. if (node.width > node.height) {
  864. x = node.x + node.width / 2;
  865. y = node.y - radius;
  866. }
  867. else {
  868. x = node.x + radius;
  869. y = node.y - node.height / 2;
  870. }
  871. dx = x - x3;
  872. dy = y - y3;
  873. return Math.abs(Math.sqrt(dx*dx + dy*dy) - radius);
  874. }
  875. };
  876. Edge.prototype._getDistanceToLine = function(x1,y1,x2,y2,x3,y3) {
  877. var px = x2-x1,
  878. py = y2-y1,
  879. something = px*px + py*py,
  880. u = ((x3 - x1) * px + (y3 - y1) * py) / something;
  881. if (u > 1) {
  882. u = 1;
  883. }
  884. else if (u < 0) {
  885. u = 0;
  886. }
  887. var x = x1 + u * px,
  888. y = y1 + u * py,
  889. dx = x - x3,
  890. dy = y - y3;
  891. //# Note: If the actual distance does not matter,
  892. //# if you only want to compare what this function
  893. //# returns to other results of this function, you
  894. //# can just return the squared distance instead
  895. //# (i.e. remove the sqrt) to gain a little performance
  896. return Math.sqrt(dx*dx + dy*dy);
  897. }
  898. /**
  899. * This allows the zoom level of the network to influence the rendering
  900. *
  901. * @param scale
  902. */
  903. Edge.prototype.setScale = function(scale) {
  904. this.networkScaleInv = 1.0/scale;
  905. };
  906. Edge.prototype.select = function() {
  907. this.selected = true;
  908. };
  909. Edge.prototype.unselect = function() {
  910. this.selected = false;
  911. };
  912. Edge.prototype.positionBezierNode = function() {
  913. if (this.via !== null && this.from !== null && this.to !== null) {
  914. this.via.x = 0.5 * (this.from.x + this.to.x);
  915. this.via.y = 0.5 * (this.from.y + this.to.y);
  916. }
  917. };
  918. /**
  919. * This function draws the control nodes for the manipulator. In order to enable this, only set the this.controlNodesEnabled to true.
  920. * @param ctx
  921. */
  922. Edge.prototype._drawControlNodes = function(ctx) {
  923. if (this.controlNodesEnabled == true) {
  924. if (this.controlNodes.from === null && this.controlNodes.to === null) {
  925. var nodeIdFrom = "edgeIdFrom:".concat(this.id);
  926. var nodeIdTo = "edgeIdTo:".concat(this.id);
  927. var constants = {
  928. nodes:{group:'', radius:8},
  929. physics:{damping:0},
  930. clustering: {maxNodeSizeIncrements: 0 ,nodeScaling: {width:0, height: 0, radius:0}}
  931. };
  932. this.controlNodes.from = new Node(
  933. {id:nodeIdFrom,
  934. shape:'dot',
  935. color:{background:'#ff4e00', border:'#3c3c3c', highlight: {background:'#07f968'}}
  936. },{},{},constants);
  937. this.controlNodes.to = new Node(
  938. {id:nodeIdTo,
  939. shape:'dot',
  940. color:{background:'#ff4e00', border:'#3c3c3c', highlight: {background:'#07f968'}}
  941. },{},{},constants);
  942. }
  943. if (this.controlNodes.from.selected == false && this.controlNodes.to.selected == false) {
  944. this.controlNodes.positions = this.getControlNodePositions(ctx);
  945. this.controlNodes.from.x = this.controlNodes.positions.from.x;
  946. this.controlNodes.from.y = this.controlNodes.positions.from.y;
  947. this.controlNodes.to.x = this.controlNodes.positions.to.x;
  948. this.controlNodes.to.y = this.controlNodes.positions.to.y;
  949. }
  950. this.controlNodes.from.draw(ctx);
  951. this.controlNodes.to.draw(ctx);
  952. }
  953. else {
  954. this.controlNodes = {from:null, to:null, positions:{}};
  955. }
  956. };
  957. /**
  958. * Enable control nodes.
  959. * @private
  960. */
  961. Edge.prototype._enableControlNodes = function() {
  962. this.controlNodesEnabled = true;
  963. };
  964. /**
  965. * disable control nodes
  966. * @private
  967. */
  968. Edge.prototype._disableControlNodes = function() {
  969. this.controlNodesEnabled = false;
  970. };
  971. /**
  972. * This checks if one of the control nodes is selected and if so, returns the control node object. Else it returns null.
  973. * @param x
  974. * @param y
  975. * @returns {null}
  976. * @private
  977. */
  978. Edge.prototype._getSelectedControlNode = function(x,y) {
  979. var positions = this.controlNodes.positions;
  980. var fromDistance = Math.sqrt(Math.pow(x - positions.from.x,2) + Math.pow(y - positions.from.y,2));
  981. var toDistance = Math.sqrt(Math.pow(x - positions.to.x ,2) + Math.pow(y - positions.to.y ,2));
  982. if (fromDistance < 15) {
  983. this.connectedNode = this.from;
  984. this.from = this.controlNodes.from;
  985. return this.controlNodes.from;
  986. }
  987. else if (toDistance < 15) {
  988. this.connectedNode = this.to;
  989. this.to = this.controlNodes.to;
  990. return this.controlNodes.to;
  991. }
  992. else {
  993. return null;
  994. }
  995. };
  996. /**
  997. * this resets the control nodes to their original position.
  998. * @private
  999. */
  1000. Edge.prototype._restoreControlNodes = function() {
  1001. if (this.controlNodes.from.selected == true) {
  1002. this.from = this.connectedNode;
  1003. this.connectedNode = null;
  1004. this.controlNodes.from.unselect();
  1005. }
  1006. if (this.controlNodes.to.selected == true) {
  1007. this.to = this.connectedNode;
  1008. this.connectedNode = null;
  1009. this.controlNodes.to.unselect();
  1010. }
  1011. };
  1012. /**
  1013. * this calculates the position of the control nodes on the edges of the parent nodes.
  1014. *
  1015. * @param ctx
  1016. * @returns {{from: {x: number, y: number}, to: {x: *, y: *}}}
  1017. */
  1018. Edge.prototype.getControlNodePositions = function(ctx) {
  1019. var angle = Math.atan2((this.to.y - this.from.y), (this.to.x - this.from.x));
  1020. var dx = (this.to.x - this.from.x);
  1021. var dy = (this.to.y - this.from.y);
  1022. var edgeSegmentLength = Math.sqrt(dx * dx + dy * dy);
  1023. var fromBorderDist = this.from.distanceToBorder(ctx, angle + Math.PI);
  1024. var fromBorderPoint = (edgeSegmentLength - fromBorderDist) / edgeSegmentLength;
  1025. var xFrom = (fromBorderPoint) * this.from.x + (1 - fromBorderPoint) * this.to.x;
  1026. var yFrom = (fromBorderPoint) * this.from.y + (1 - fromBorderPoint) * this.to.y;
  1027. var via;
  1028. if (this.options.smoothCurves.dynamic == true && this.options.smoothCurves.enabled == true) {
  1029. via = this.via;
  1030. }
  1031. else if (this.options.smoothCurves.enabled == true) {
  1032. via = this._getViaCoordinates();
  1033. }
  1034. if (this.options.smoothCurves.enabled == true && via.x != null) {
  1035. angle = Math.atan2((this.to.y - via.y), (this.to.x - via.x));
  1036. dx = (this.to.x - via.x);
  1037. dy = (this.to.y - via.y);
  1038. edgeSegmentLength = Math.sqrt(dx * dx + dy * dy);
  1039. }
  1040. var toBorderDist = this.to.distanceToBorder(ctx, angle);
  1041. var toBorderPoint = (edgeSegmentLength - toBorderDist) / edgeSegmentLength;
  1042. var xTo,yTo;
  1043. if (this.options.smoothCurves.enabled == true && via.x != null) {
  1044. xTo = (1 - toBorderPoint) * via.x + toBorderPoint * this.to.x;
  1045. yTo = (1 - toBorderPoint) * via.y + toBorderPoint * this.to.y;
  1046. }
  1047. else {
  1048. xTo = (1 - toBorderPoint) * this.from.x + toBorderPoint * this.to.x;
  1049. yTo = (1 - toBorderPoint) * this.from.y + toBorderPoint * this.to.y;
  1050. }
  1051. return {from:{x:xFrom,y:yFrom},to:{x:xTo,y:yTo}};
  1052. };
  1053. module.exports = Edge;