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.

968 lines
29 KiB

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