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.

729 lines
20 KiB

  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 {Graph} graph A graph 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, graph, constants) {
  17. if (!graph) {
  18. throw "No graph provided";
  19. }
  20. this.graph = graph;
  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.value = undefined;
  32. this.length = constants.physics.springLength;
  33. this.customLength = false;
  34. this.selected = false;
  35. this.smooth = constants.smoothCurves;
  36. this.from = null; // a node
  37. this.to = null; // a node
  38. this.via = null; // a temp node
  39. // we use this to be able to reconnect the edge to a cluster if its node is put into a cluster
  40. // by storing the original information we can revert to the original connection when the cluser is opened.
  41. this.originalFromId = [];
  42. this.originalToId = [];
  43. this.connected = false;
  44. // Added to support dashed lines
  45. // David Jordan
  46. // 2012-08-08
  47. this.dash = util.extend({}, constants.edges.dash); // contains properties length, gap, altLength
  48. this.color = constants.edges.color;
  49. this.widthFixed = false;
  50. this.lengthFixed = false;
  51. this.setProperties(properties, constants);
  52. }
  53. /**
  54. * Set or overwrite properties for the edge
  55. * @param {Object} properties an object with properties
  56. * @param {Object} constants and object with default, global properties
  57. */
  58. Edge.prototype.setProperties = function(properties, constants) {
  59. if (!properties) {
  60. return;
  61. }
  62. if (properties.from !== undefined) {this.fromId = properties.from;}
  63. if (properties.to !== undefined) {this.toId = properties.to;}
  64. if (properties.id !== undefined) {this.id = properties.id;}
  65. if (properties.style !== undefined) {this.style = properties.style;}
  66. if (properties.label !== undefined) {this.label = properties.label;}
  67. if (this.label) {
  68. this.fontSize = constants.edges.fontSize;
  69. this.fontFace = constants.edges.fontFace;
  70. this.fontColor = constants.edges.fontColor;
  71. if (properties.fontColor !== undefined) {this.fontColor = properties.fontColor;}
  72. if (properties.fontSize !== undefined) {this.fontSize = properties.fontSize;}
  73. if (properties.fontFace !== undefined) {this.fontFace = properties.fontFace;}
  74. }
  75. if (properties.title !== undefined) {this.title = properties.title;}
  76. if (properties.width !== undefined) {this.width = properties.width;}
  77. if (properties.value !== undefined) {this.value = properties.value;}
  78. if (properties.length !== undefined) {this.length = properties.length;
  79. this.customLength = true;}
  80. // Added to support dashed lines
  81. // David Jordan
  82. // 2012-08-08
  83. if (properties.dash) {
  84. if (properties.dash.length !== undefined) {this.dash.length = properties.dash.length;}
  85. if (properties.dash.gap !== undefined) {this.dash.gap = properties.dash.gap;}
  86. if (properties.dash.altLength !== undefined) {this.dash.altLength = properties.dash.altLength;}
  87. }
  88. if (properties.color !== undefined) {this.color = properties.color;}
  89. // A node is connected when it has a from and to node.
  90. this.connect();
  91. this.widthFixed = this.widthFixed || (properties.width !== undefined);
  92. this.lengthFixed = this.lengthFixed || (properties.length !== undefined);
  93. // set draw method based on style
  94. switch (this.style) {
  95. case 'line': this.draw = this._drawLine; break;
  96. case 'arrow': this.draw = this._drawArrow; break;
  97. case 'arrow-center': this.draw = this._drawArrowCenter; break;
  98. case 'dash-line': this.draw = this._drawDashLine; break;
  99. default: this.draw = this._drawLine; break;
  100. }
  101. };
  102. /**
  103. * Connect an edge to its nodes
  104. */
  105. Edge.prototype.connect = function () {
  106. this.disconnect();
  107. this.from = this.graph.nodes[this.fromId] || null;
  108. this.to = this.graph.nodes[this.toId] || null;
  109. this.connected = (this.from && this.to);
  110. if (this.connected) {
  111. this.from.attachEdge(this);
  112. this.to.attachEdge(this);
  113. }
  114. else {
  115. if (this.from) {
  116. this.from.detachEdge(this);
  117. }
  118. if (this.to) {
  119. this.to.detachEdge(this);
  120. }
  121. }
  122. };
  123. /**
  124. * Disconnect an edge from its nodes
  125. */
  126. Edge.prototype.disconnect = function () {
  127. if (this.from) {
  128. this.from.detachEdge(this);
  129. this.from = null;
  130. }
  131. if (this.to) {
  132. this.to.detachEdge(this);
  133. this.to = null;
  134. }
  135. this.connected = false;
  136. };
  137. /**
  138. * get the title of this edge.
  139. * @return {string} title The title of the edge, or undefined when no title
  140. * has been set.
  141. */
  142. Edge.prototype.getTitle = function() {
  143. return this.title;
  144. };
  145. /**
  146. * Retrieve the value of the edge. Can be undefined
  147. * @return {Number} value
  148. */
  149. Edge.prototype.getValue = function() {
  150. return this.value;
  151. };
  152. /**
  153. * Adjust the value range of the edge. The edge will adjust it's width
  154. * based on its value.
  155. * @param {Number} min
  156. * @param {Number} max
  157. */
  158. Edge.prototype.setValueRange = function(min, max) {
  159. if (!this.widthFixed && this.value !== undefined) {
  160. var scale = (this.widthMax - this.widthMin) / (max - min);
  161. this.width = (this.value - min) * scale + this.widthMin;
  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. var distMax = 10;
  180. var xFrom = this.from.x;
  181. var yFrom = this.from.y;
  182. var xTo = this.to.x;
  183. var yTo = this.to.y;
  184. var xObj = obj.left;
  185. var yObj = obj.top;
  186. var dist = this._getDistanceToEdge(xFrom, yFrom, xTo, yTo, xObj, yObj);
  187. return (dist < distMax);
  188. };
  189. /**
  190. * Redraw a edge as a line
  191. * Draw this edge in the given canvas
  192. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  193. * @param {CanvasRenderingContext2D} ctx
  194. * @private
  195. */
  196. Edge.prototype._drawLine = function(ctx) {
  197. // set style
  198. ctx.strokeStyle = this.color;
  199. ctx.lineWidth = this._getLineWidth();
  200. var point;
  201. if (this.from != this.to) {
  202. // draw line
  203. this._line(ctx);
  204. // draw label
  205. if (this.label) {
  206. point = this._pointOnLine(0.5);
  207. this._label(ctx, this.label, point.x, point.y);
  208. }
  209. }
  210. else {
  211. var x, y;
  212. var radius = this.length / 4;
  213. var node = this.from;
  214. if (!node.width) {
  215. node.resize(ctx);
  216. }
  217. if (node.width > node.height) {
  218. x = node.x + node.width / 2;
  219. y = node.y - radius;
  220. }
  221. else {
  222. x = node.x + radius;
  223. y = node.y - node.height / 2;
  224. }
  225. this._circle(ctx, x, y, radius);
  226. point = this._pointOnCircle(x, y, radius, 0.5);
  227. this._label(ctx, this.label, point.x, point.y);
  228. }
  229. };
  230. /**
  231. * Get the line width of the edge. Depends on width and whether one of the
  232. * connected nodes is selected.
  233. * @return {Number} width
  234. * @private
  235. */
  236. Edge.prototype._getLineWidth = function() {
  237. if (this.selected == true) {
  238. return Math.min(this.width * 2, this.widthMax)*this.graphScaleInv;
  239. }
  240. else {
  241. return this.width*this.graphScaleInv;
  242. }
  243. };
  244. /**
  245. * Draw a line between two nodes
  246. * @param {CanvasRenderingContext2D} ctx
  247. * @private
  248. */
  249. Edge.prototype._line = function (ctx) {
  250. // draw a straight line
  251. ctx.beginPath();
  252. ctx.moveTo(this.from.x, this.from.y);
  253. if (this.smooth == true) {
  254. ctx.quadraticCurveTo(this.via.x,this.via.y,this.to.x, this.to.y);
  255. }
  256. else {
  257. ctx.lineTo(this.to.x, this.to.y);
  258. }
  259. ctx.stroke();
  260. };
  261. /**
  262. * Draw a line from a node to itself, a circle
  263. * @param {CanvasRenderingContext2D} ctx
  264. * @param {Number} x
  265. * @param {Number} y
  266. * @param {Number} radius
  267. * @private
  268. */
  269. Edge.prototype._circle = function (ctx, x, y, radius) {
  270. // draw a circle
  271. ctx.beginPath();
  272. ctx.arc(x, y, radius, 0, 2 * Math.PI, false);
  273. ctx.stroke();
  274. };
  275. /**
  276. * Draw label with white background and with the middle at (x, y)
  277. * @param {CanvasRenderingContext2D} ctx
  278. * @param {String} text
  279. * @param {Number} x
  280. * @param {Number} y
  281. * @private
  282. */
  283. Edge.prototype._label = function (ctx, text, x, y) {
  284. if (text) {
  285. // TODO: cache the calculated size
  286. ctx.font = ((this.from.selected || this.to.selected) ? "bold " : "") +
  287. this.fontSize + "px " + this.fontFace;
  288. ctx.fillStyle = 'white';
  289. var width = ctx.measureText(text).width;
  290. var height = this.fontSize;
  291. var left = x - width / 2;
  292. var top = y - height / 2;
  293. ctx.fillRect(left, top, width, height);
  294. // draw text
  295. ctx.fillStyle = this.fontColor || "black";
  296. ctx.textAlign = "left";
  297. ctx.textBaseline = "top";
  298. ctx.fillText(text, left, top);
  299. }
  300. };
  301. /**
  302. * Redraw a edge as a dashed line
  303. * Draw this edge in the given canvas
  304. * @author David Jordan
  305. * @date 2012-08-08
  306. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  307. * @param {CanvasRenderingContext2D} ctx
  308. * @private
  309. */
  310. Edge.prototype._drawDashLine = function(ctx) {
  311. // set style
  312. ctx.strokeStyle = this.color;
  313. ctx.lineWidth = this._getLineWidth();
  314. // only firefox and chrome support this method, else we use the legacy one.
  315. if (ctx.mozDash !== undefined || ctx.setLineDash !== undefined) {
  316. ctx.beginPath();
  317. ctx.moveTo(this.from.x, this.from.y);
  318. // configure the dash pattern
  319. var pattern = [0];
  320. if (this.dash.length !== undefined && this.dash.gap !== undefined) {
  321. pattern = [this.dash.length,this.dash.gap];
  322. }
  323. else {
  324. pattern = [5,5];
  325. }
  326. // set dash settings for chrome or firefox
  327. if (typeof ctx.setLineDash !== 'undefined') { //Chrome
  328. ctx.setLineDash(pattern);
  329. ctx.lineDashOffset = 0;
  330. } else { //Firefox
  331. ctx.mozDash = pattern;
  332. ctx.mozDashOffset = 0;
  333. }
  334. // draw the line
  335. if (this.smooth == true) {
  336. ctx.quadraticCurveTo(this.via.x,this.via.y,this.to.x, this.to.y);
  337. }
  338. else {
  339. ctx.lineTo(this.to.x, this.to.y);
  340. }
  341. ctx.stroke();
  342. // restore the dash settings.
  343. if (typeof ctx.setLineDash !== 'undefined') { //Chrome
  344. ctx.setLineDash([0]);
  345. ctx.lineDashOffset = 0;
  346. } else { //Firefox
  347. ctx.mozDash = [0];
  348. ctx.mozDashOffset = 0;
  349. }
  350. }
  351. else { // unsupporting smooth lines
  352. // draw dashed line
  353. ctx.beginPath();
  354. ctx.lineCap = 'round';
  355. if (this.dash.altLength !== undefined) //If an alt dash value has been set add to the array this value
  356. {
  357. ctx.dashedLine(this.from.x,this.from.y,this.to.x,this.to.y,
  358. [this.dash.length,this.dash.gap,this.dash.altLength,this.dash.gap]);
  359. }
  360. 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
  361. {
  362. ctx.dashedLine(this.from.x,this.from.y,this.to.x,this.to.y,
  363. [this.dash.length,this.dash.gap]);
  364. }
  365. else //If all else fails draw a line
  366. {
  367. ctx.moveTo(this.from.x, this.from.y);
  368. ctx.lineTo(this.to.x, this.to.y);
  369. }
  370. ctx.stroke();
  371. }
  372. // draw label
  373. if (this.label) {
  374. var point = this._pointOnLine(0.5);
  375. this._label(ctx, this.label, point.x, point.y);
  376. }
  377. };
  378. /**
  379. * Get a point on a line
  380. * @param {Number} percentage. Value between 0 (line start) and 1 (line end)
  381. * @return {Object} point
  382. * @private
  383. */
  384. Edge.prototype._pointOnLine = function (percentage) {
  385. return {
  386. x: (1 - percentage) * this.from.x + percentage * this.to.x,
  387. y: (1 - percentage) * this.from.y + percentage * this.to.y
  388. }
  389. };
  390. /**
  391. * Get a point on a circle
  392. * @param {Number} x
  393. * @param {Number} y
  394. * @param {Number} radius
  395. * @param {Number} percentage. Value between 0 (line start) and 1 (line end)
  396. * @return {Object} point
  397. * @private
  398. */
  399. Edge.prototype._pointOnCircle = function (x, y, radius, percentage) {
  400. var angle = (percentage - 3/8) * 2 * Math.PI;
  401. return {
  402. x: x + radius * Math.cos(angle),
  403. y: y - radius * Math.sin(angle)
  404. }
  405. };
  406. /**
  407. * Redraw a edge as a line with an arrow halfway the line
  408. * Draw this edge in the given canvas
  409. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  410. * @param {CanvasRenderingContext2D} ctx
  411. * @private
  412. */
  413. Edge.prototype._drawArrowCenter = function(ctx) {
  414. var point;
  415. // set style
  416. ctx.strokeStyle = this.color;
  417. ctx.fillStyle = this.color;
  418. ctx.lineWidth = this._getLineWidth();
  419. if (this.from != this.to) {
  420. // draw line
  421. this._line(ctx);
  422. var angle = Math.atan2((this.to.y - this.from.y), (this.to.x - this.from.x));
  423. var length = 10 + 5 * this.width; // TODO: make customizable?
  424. // draw an arrow halfway the line
  425. if (this.smooth == true) {
  426. var midpointX = 0.5*(0.5*(this.from.x + this.via.x) + 0.5*(this.to.x + this.via.x));
  427. var midpointY = 0.5*(0.5*(this.from.y + this.via.y) + 0.5*(this.to.y + this.via.y));
  428. point = {x:midpointX, y:midpointY};
  429. }
  430. else {
  431. point = this._pointOnLine(0.5);
  432. }
  433. ctx.arrow(point.x, point.y, angle, length);
  434. ctx.fill();
  435. ctx.stroke();
  436. // draw label
  437. if (this.label) {
  438. point = this._pointOnLine(0.5);
  439. this._label(ctx, this.label, point.x, point.y);
  440. }
  441. }
  442. else {
  443. // draw circle
  444. var x, y;
  445. var radius = 0.25 * Math.max(100,this.length);
  446. var node = this.from;
  447. if (!node.width) {
  448. node.resize(ctx);
  449. }
  450. if (node.width > node.height) {
  451. x = node.x + node.width * 0.5;
  452. y = node.y - radius;
  453. }
  454. else {
  455. x = node.x + radius;
  456. y = node.y - node.height * 0.5;
  457. }
  458. this._circle(ctx, x, y, radius);
  459. // draw all arrows
  460. var angle = 0.2 * Math.PI;
  461. var length = 10 + 5 * this.width; // TODO: make customizable?
  462. point = this._pointOnCircle(x, y, radius, 0.5);
  463. ctx.arrow(point.x, point.y, angle, length);
  464. ctx.fill();
  465. ctx.stroke();
  466. // draw label
  467. if (this.label) {
  468. point = this._pointOnCircle(x, y, radius, 0.5);
  469. this._label(ctx, this.label, point.x, point.y);
  470. }
  471. }
  472. };
  473. /**
  474. * Redraw a edge as a line with an arrow
  475. * Draw this edge in the given canvas
  476. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  477. * @param {CanvasRenderingContext2D} ctx
  478. * @private
  479. */
  480. Edge.prototype._drawArrow = function(ctx) {
  481. // set style
  482. ctx.strokeStyle = this.color;
  483. ctx.fillStyle = this.color;
  484. ctx.lineWidth = this._getLineWidth();
  485. var angle, length;
  486. //draw a line
  487. if (this.from != this.to) {
  488. angle = Math.atan2((this.to.y - this.from.y), (this.to.x - this.from.x));
  489. var dx = (this.to.x - this.from.x);
  490. var dy = (this.to.y - this.from.y);
  491. var edgeSegmentLength = Math.sqrt(dx * dx + dy * dy);
  492. var fromBorderDist = this.from.distanceToBorder(ctx, angle + Math.PI);
  493. var fromBorderPoint = (edgeSegmentLength - fromBorderDist) / edgeSegmentLength;
  494. var xFrom = (fromBorderPoint) * this.from.x + (1 - fromBorderPoint) * this.to.x;
  495. var yFrom = (fromBorderPoint) * this.from.y + (1 - fromBorderPoint) * this.to.y;
  496. if (this.smooth == true) {
  497. angle = Math.atan2((this.to.y - this.via.y), (this.to.x - this.via.x));
  498. dx = (this.to.x - this.via.x);
  499. dy = (this.to.y - this.via.y);
  500. edgeSegmentLength = Math.sqrt(dx * dx + dy * dy);
  501. }
  502. var toBorderDist = this.to.distanceToBorder(ctx, angle);
  503. var toBorderPoint = (edgeSegmentLength - toBorderDist) / edgeSegmentLength;
  504. var xTo,yTo;
  505. if (this.smooth == true) {
  506. xTo = (1 - toBorderPoint) * this.via.x + toBorderPoint * this.to.x;
  507. yTo = (1 - toBorderPoint) * this.via.y + toBorderPoint * this.to.y;
  508. }
  509. else {
  510. xTo = (1 - toBorderPoint) * this.from.x + toBorderPoint * this.to.x;
  511. yTo = (1 - toBorderPoint) * this.from.y + toBorderPoint * this.to.y;
  512. }
  513. ctx.beginPath();
  514. ctx.moveTo(xFrom,yFrom);
  515. if (this.smooth == true) {
  516. ctx.quadraticCurveTo(this.via.x,this.via.y,xTo, yTo);
  517. }
  518. else {
  519. ctx.lineTo(xTo, yTo);
  520. }
  521. ctx.stroke();
  522. // draw arrow at the end of the line
  523. length = 10 + 5 * this.width;
  524. ctx.arrow(xTo, yTo, angle, length);
  525. ctx.fill();
  526. ctx.stroke();
  527. // draw label
  528. if (this.label) {
  529. var point = this._pointOnLine(0.5);
  530. this._label(ctx, this.label, point.x, point.y);
  531. }
  532. }
  533. else {
  534. // draw circle
  535. var node = this.from;
  536. var x, y, arrow;
  537. var radius = 0.25 * Math.max(100,this.length);
  538. if (!node.width) {
  539. node.resize(ctx);
  540. }
  541. if (node.width > node.height) {
  542. x = node.x + node.width * 0.5;
  543. y = node.y - radius;
  544. arrow = {
  545. x: x,
  546. y: node.y,
  547. angle: 0.9 * Math.PI
  548. };
  549. }
  550. else {
  551. x = node.x + radius;
  552. y = node.y - node.height * 0.5;
  553. arrow = {
  554. x: node.x,
  555. y: y,
  556. angle: 0.6 * Math.PI
  557. };
  558. }
  559. ctx.beginPath();
  560. // TODO: similarly, for a line without arrows, draw to the border of the nodes instead of the center
  561. ctx.arc(x, y, radius, 0, 2 * Math.PI, false);
  562. ctx.stroke();
  563. // draw all arrows
  564. length = 10 + 5 * this.width; // TODO: make customizable?
  565. ctx.arrow(arrow.x, arrow.y, arrow.angle, length);
  566. ctx.fill();
  567. ctx.stroke();
  568. // draw label
  569. if (this.label) {
  570. point = this._pointOnCircle(x, y, radius, 0.5);
  571. this._label(ctx, this.label, point.x, point.y);
  572. }
  573. }
  574. };
  575. /**
  576. * Calculate the distance between a point (x3,y3) and a line segment from
  577. * (x1,y1) to (x2,y2).
  578. * http://stackoverflow.com/questions/849211/shortest-distancae-between-a-point-and-a-line-segment
  579. * @param {number} x1
  580. * @param {number} y1
  581. * @param {number} x2
  582. * @param {number} y2
  583. * @param {number} x3
  584. * @param {number} y3
  585. * @private
  586. */
  587. Edge.prototype._getDistanceToEdge = function (x1,y1, x2,y2, x3,y3) { // x3,y3 is the point
  588. if (this.smooth == true) {
  589. var minDistance = 1e9;
  590. var i,t,x,y,dx,dy;
  591. for (i = 0; i < 10; i++) {
  592. t = 0.1*i;
  593. x = Math.pow(1-t,2)*x1 + (2*t*(1 - t))*this.via.x + Math.pow(t,2)*x2;
  594. y = Math.pow(1-t,2)*y1 + (2*t*(1 - t))*this.via.y + Math.pow(t,2)*y2;
  595. dx = Math.abs(x3-x);
  596. dy = Math.abs(y3-y);
  597. minDistance = Math.min(minDistance,Math.sqrt(dx*dx + dy*dy));
  598. }
  599. return minDistance
  600. }
  601. else {
  602. var px = x2-x1,
  603. py = y2-y1,
  604. something = px*px + py*py,
  605. u = ((x3 - x1) * px + (y3 - y1) * py) / something;
  606. if (u > 1) {
  607. u = 1;
  608. }
  609. else if (u < 0) {
  610. u = 0;
  611. }
  612. var x = x1 + u * px,
  613. y = y1 + u * py,
  614. dx = x - x3,
  615. dy = y - y3;
  616. //# Note: If the actual distance does not matter,
  617. //# if you only want to compare what this function
  618. //# returns to other results of this function, you
  619. //# can just return the squared distance instead
  620. //# (i.e. remove the sqrt) to gain a little performance
  621. return Math.sqrt(dx*dx + dy*dy);
  622. }
  623. };
  624. /**
  625. * This allows the zoom level of the graph to influence the rendering
  626. *
  627. * @param scale
  628. */
  629. Edge.prototype.setScale = function(scale) {
  630. this.graphScaleInv = 1.0/scale;
  631. };
  632. Edge.prototype.select = function() {
  633. this.selected = true;
  634. };
  635. Edge.prototype.unselect = function() {
  636. this.selected = false;
  637. };
  638. Edge.prototype.positionBezierNode = function() {
  639. if (this.via !== null) {
  640. this.via.x = 0.5 * (this.from.x + this.to.x);
  641. this.via.y = 0.5 * (this.from.y + this.to.y);
  642. }
  643. };