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.

964 lines
29 KiB

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