Browse Source

- Improved drawing of arrowheads on smooth curves. #349

v3_develop
Alex de Mulder 10 years ago
parent
commit
072d1a5d1f
3 changed files with 2280 additions and 2189 deletions
  1. +1
    -0
      HISTORY.md
  2. +2062
    -2017
      dist/vis.js
  3. +217
    -172
      lib/network/Edge.js

+ 1
- 0
HISTORY.md View File

@ -10,6 +10,7 @@ http://visjs.org
- improved (not neccesarily fixed) the fontFill offset between different browsers. #365 - improved (not neccesarily fixed) the fontFill offset between different browsers. #365
- Fixed dashed lines on firefox on Unix systems - Fixed dashed lines on firefox on Unix systems
- Altered the Manipulation Mixin to be succesfully destroyed from memory when calling destroy(); - Altered the Manipulation Mixin to be succesfully destroyed from memory when calling destroy();
- Improved drawing of arrowheads on smooth curves. #349
## 20145-01-09, version 3.8.0 ## 20145-01-09, version 3.8.0

+ 2062
- 2017
dist/vis.js
File diff suppressed because it is too large
View File


+ 217
- 172
lib/network/Edge.js View File

@ -322,168 +322,176 @@ Edge.prototype._getLineWidth = function() {
}; };
Edge.prototype._getViaCoordinates = function () { Edge.prototype._getViaCoordinates = function () {
var xVia = null;
var yVia = null;
var factor = this.options.smoothCurves.roundness;
var type = this.options.smoothCurves.type;
var dx = Math.abs(this.from.x - this.to.x);
var dy = Math.abs(this.from.y - this.to.y);
if (type == 'discrete' || type == 'diagonalCross') {
if (Math.abs(this.from.x - this.to.x) < Math.abs(this.from.y - this.to.y)) {
if (this.from.y > this.to.y) {
if (this.from.x < this.to.x) {
xVia = this.from.x + factor * dy;
yVia = this.from.y - factor * dy;
if (this.options.smoothCurves.dynamic == true && this.options.smoothCurves.enabled == true ) {
return this.via;
}
else if (this.options.smoothCurves.enabled == false) {
return {x:0,y:0};
}
else {
var xVia = null;
var yVia = null;
var factor = this.options.smoothCurves.roundness;
var type = this.options.smoothCurves.type;
var dx = Math.abs(this.from.x - this.to.x);
var dy = Math.abs(this.from.y - this.to.y);
if (type == 'discrete' || type == 'diagonalCross') {
if (Math.abs(this.from.x - this.to.x) < Math.abs(this.from.y - this.to.y)) {
if (this.from.y > this.to.y) {
if (this.from.x < this.to.x) {
xVia = this.from.x + factor * dy;
yVia = this.from.y - factor * dy;
}
else if (this.from.x > this.to.x) {
xVia = this.from.x - factor * dy;
yVia = this.from.y - factor * dy;
}
}
else if (this.from.y < this.to.y) {
if (this.from.x < this.to.x) {
xVia = this.from.x + factor * dy;
yVia = this.from.y + factor * dy;
}
else if (this.from.x > this.to.x) {
xVia = this.from.x - factor * dy;
yVia = this.from.y + factor * dy;
}
} }
else if (this.from.x > this.to.x) {
xVia = this.from.x - factor * dy;
yVia = this.from.y - factor * dy;
if (type == "discrete") {
xVia = dx < factor * dy ? this.from.x : xVia;
} }
} }
else if (this.from.y < this.to.y) {
if (this.from.x < this.to.x) {
xVia = this.from.x + factor * dy;
yVia = this.from.y + factor * dy;
else if (Math.abs(this.from.x - this.to.x) > Math.abs(this.from.y - this.to.y)) {
if (this.from.y > this.to.y) {
if (this.from.x < this.to.x) {
xVia = this.from.x + factor * dx;
yVia = this.from.y - factor * dx;
}
else if (this.from.x > this.to.x) {
xVia = this.from.x - factor * dx;
yVia = this.from.y - factor * dx;
}
} }
else if (this.from.x > this.to.x) {
xVia = this.from.x - factor * dy;
yVia = this.from.y + factor * dy;
else if (this.from.y < this.to.y) {
if (this.from.x < this.to.x) {
xVia = this.from.x + factor * dx;
yVia = this.from.y + factor * dx;
}
else if (this.from.x > this.to.x) {
xVia = this.from.x - factor * dx;
yVia = this.from.y + factor * dx;
}
}
if (type == "discrete") {
yVia = dy < factor * dx ? this.from.y : yVia;
} }
}
if (type == "discrete") {
xVia = dx < factor * dy ? this.from.x : xVia;
} }
} }
else if (Math.abs(this.from.x - this.to.x) > Math.abs(this.from.y - this.to.y)) {
if (this.from.y > this.to.y) {
if (this.from.x < this.to.x) {
xVia = this.from.x + factor * dx;
yVia = this.from.y - factor * dx;
else if (type == "straightCross") {
if (Math.abs(this.from.x - this.to.x) < Math.abs(this.from.y - this.to.y)) { // up - down
xVia = this.from.x;
if (this.from.y < this.to.y) {
yVia = this.to.y - (1 - factor) * dy;
} }
else if (this.from.x > this.to.x) {
xVia = this.from.x - factor * dx;
yVia = this.from.y - factor * dx;
else {
yVia = this.to.y + (1 - factor) * dy;
} }
} }
else if (this.from.y < this.to.y) {
else if (Math.abs(this.from.x - this.to.x) > Math.abs(this.from.y - this.to.y)) { // left - right
if (this.from.x < this.to.x) { if (this.from.x < this.to.x) {
xVia = this.from.x + factor * dx;
yVia = this.from.y + factor * dx;
xVia = this.to.x - (1 - factor) * dx;
} }
else if (this.from.x > this.to.x) {
xVia = this.from.x - factor * dx;
yVia = this.from.y + factor * dx;
else {
xVia = this.to.x + (1 - factor) * dx;
} }
}
if (type == "discrete") {
yVia = dy < factor * dx ? this.from.y : yVia;
yVia = this.from.y;
} }
} }
}
else if (type == "straightCross") {
if (Math.abs(this.from.x - this.to.x) < Math.abs(this.from.y - this.to.y)) { // up - down
xVia = this.from.x;
if (this.from.y < this.to.y) {
yVia = this.to.y - (1-factor) * dy;
else if (type == 'horizontal') {
if (this.from.x < this.to.x) {
xVia = this.to.x - (1 - factor) * dx;
} }
else { else {
yVia = this.to.y + (1-factor) * dy;
xVia = this.to.x + (1 - factor) * dx;
} }
yVia = this.from.y;
} }
else if (Math.abs(this.from.x - this.to.x) > Math.abs(this.from.y - this.to.y)) { // left - right
if (this.from.x < this.to.x) {
xVia = this.to.x - (1-factor) * dx;
else if (type == 'vertical') {
xVia = this.from.x;
if (this.from.y < this.to.y) {
yVia = this.to.y - (1 - factor) * dy;
} }
else { else {
xVia = this.to.x + (1-factor) * dx;
yVia = this.to.y + (1 - factor) * dy;
} }
yVia = this.from.y;
} }
}
else if (type == 'horizontal') {
if (this.from.x < this.to.x) {
xVia = this.to.x - (1-factor) * dx;
}
else {
xVia = this.to.x + (1-factor) * dx;
}
yVia = this.from.y;
}
else if (type == 'vertical') {
xVia = this.from.x;
if (this.from.y < this.to.y) {
yVia = this.to.y - (1-factor) * dy;
}
else {
yVia = this.to.y + (1-factor) * dy;
}
}
else { // continuous
if (Math.abs(this.from.x - this.to.x) < Math.abs(this.from.y - this.to.y)) {
if (this.from.y > this.to.y) {
if (this.from.x < this.to.x) {
else { // continuous
if (Math.abs(this.from.x - this.to.x) < Math.abs(this.from.y - this.to.y)) {
if (this.from.y > this.to.y) {
if (this.from.x < this.to.x) {
// console.log(1) // console.log(1)
xVia = this.from.x + factor * dy;
yVia = this.from.y - factor * dy;
xVia = this.to.x < xVia ? this.to.x : xVia;
}
else if (this.from.x > this.to.x) {
xVia = this.from.x + factor * dy;
yVia = this.from.y - factor * dy;
xVia = this.to.x < xVia ? this.to.x : xVia;
}
else if (this.from.x > this.to.x) {
// console.log(2) // console.log(2)
xVia = this.from.x - factor * dy;
yVia = this.from.y - factor * dy;
xVia = this.to.x > xVia ? this.to.x :xVia;
xVia = this.from.x - factor * dy;
yVia = this.from.y - factor * dy;
xVia = this.to.x > xVia ? this.to.x : xVia;
}
} }
}
else if (this.from.y < this.to.y) {
if (this.from.x < this.to.x) {
else if (this.from.y < this.to.y) {
if (this.from.x < this.to.x) {
// console.log(3) // console.log(3)
xVia = this.from.x + factor * dy;
yVia = this.from.y + factor * dy;
xVia = this.to.x < xVia ? this.to.x : xVia;
}
else if (this.from.x > this.to.x) {
xVia = this.from.x + factor * dy;
yVia = this.from.y + factor * dy;
xVia = this.to.x < xVia ? this.to.x : xVia;
}
else if (this.from.x > this.to.x) {
// console.log(4, this.from.x, this.to.x) // console.log(4, this.from.x, this.to.x)
xVia = this.from.x - factor * dy;
yVia = this.from.y + factor * dy;
xVia = this.to.x > xVia ? this.to.x : xVia;
xVia = this.from.x - factor * dy;
yVia = this.from.y + factor * dy;
xVia = this.to.x > xVia ? this.to.x : xVia;
}
} }
} }
}
else if (Math.abs(this.from.x - this.to.x) > Math.abs(this.from.y - this.to.y)) {
if (this.from.y > this.to.y) {
if (this.from.x < this.to.x) {
else if (Math.abs(this.from.x - this.to.x) > Math.abs(this.from.y - this.to.y)) {
if (this.from.y > this.to.y) {
if (this.from.x < this.to.x) {
// console.log(5) // console.log(5)
xVia = this.from.x + factor * dx;
yVia = this.from.y - factor * dx;
yVia = this.to.y > yVia ? this.to.y : yVia;
}
else if (this.from.x > this.to.x) {
xVia = this.from.x + factor * dx;
yVia = this.from.y - factor * dx;
yVia = this.to.y > yVia ? this.to.y : yVia;
}
else if (this.from.x > this.to.x) {
// console.log(6) // console.log(6)
xVia = this.from.x - factor * dx;
yVia = this.from.y - factor * dx;
yVia = this.to.y > yVia ? this.to.y : yVia;
xVia = this.from.x - factor * dx;
yVia = this.from.y - factor * dx;
yVia = this.to.y > yVia ? this.to.y : yVia;
}
} }
}
else if (this.from.y < this.to.y) {
if (this.from.x < this.to.x) {
else if (this.from.y < this.to.y) {
if (this.from.x < this.to.x) {
// console.log(7) // console.log(7)
xVia = this.from.x + factor * dx;
yVia = this.from.y + factor * dx;
yVia = this.to.y < yVia ? this.to.y : yVia;
}
else if (this.from.x > this.to.x) {
xVia = this.from.x + factor * dx;
yVia = this.from.y + factor * dx;
yVia = this.to.y < yVia ? this.to.y : yVia;
}
else if (this.from.x > this.to.x) {
// console.log(8) // console.log(8)
xVia = this.from.x - factor * dx;
yVia = this.from.y + factor * dx;
yVia = this.to.y < yVia ? this.to.y : yVia;
xVia = this.from.x - factor * dx;
yVia = this.from.y + factor * dx;
yVia = this.to.y < yVia ? this.to.y : yVia;
}
} }
} }
} }
}
return {x:xVia, y:yVia};
return {x: xVia, y: yVia};
}
}; };
/** /**
@ -772,7 +780,70 @@ Edge.prototype._drawArrowCenter = function(ctx) {
} }
}; };
Edge.prototype._pointOnBezier = function(t) {
var via = this._getViaCoordinates();
var x = Math.pow(1-t,2)*this.from.x + (2*t*(1 - t))*via.x + Math.pow(t,2)*this.to.x;
var y = Math.pow(1-t,2)*this.from.y + (2*t*(1 - t))*via.y + Math.pow(t,2)*this.to.y;
return {x:x,y:y};
}
/**
* This function uses binary search to look for the point where the bezier curve crosses the border of the node.
*
* @param from
* @param ctx
* @returns {*}
* @private
*/
Edge.prototype._findBorderPosition = function(from,ctx) {
var maxIterations = 10;
var iteration = 0;
var low = 0;
var high = 1;
var pos,angle,distanceToBorder, distanceToNodes, difference;
var threshold = 0.2;
var node = this.to;
if (from == true) {
node = this.from;
}
while (low <= high && iteration < maxIterations) {
var middle = (low + high) * 0.5;
pos = this._pointOnBezier(middle);
angle = Math.atan2((node.y - pos.y), (node.x - pos.x));
distanceToBorder = node.distanceToBorder(ctx,angle);
distanceToNodes = Math.sqrt(Math.pow(pos.x-node.x,2) + Math.pow(pos.y-node.y,2));
difference = distanceToBorder - distanceToNodes;
if (Math.abs(difference) < threshold) {
break; // found
}
else if (difference < 0) { // distance to nodes is larger than distance to border --> t needs to be bigger if we're looking at the to node.
if (from == false) {
low = middle;
}
else {
high = middle;
}
}
else {
if (from == false) {
high = middle;
}
else {
low = middle;
}
}
iteration++;
}
pos.t = middle;
return pos;
};
/** /**
* Redraw a edge as a line with an arrow * Redraw a edge as a line with an arrow
@ -787,59 +858,37 @@ Edge.prototype._drawArrow = function(ctx) {
ctx.fillStyle = ctx.strokeStyle; ctx.fillStyle = ctx.strokeStyle;
ctx.lineWidth = this._getLineWidth(); ctx.lineWidth = this._getLineWidth();
var angle, length;
//draw a line
if (this.from != this.to) {
angle = Math.atan2((this.to.y - this.from.y), (this.to.x - this.from.x));
var dx = (this.to.x - this.from.x);
var dy = (this.to.y - this.from.y);
var edgeSegmentLength = Math.sqrt(dx * dx + dy * dy);
var fromBorderDist = this.from.distanceToBorder(ctx, angle + Math.PI);
var fromBorderPoint = (edgeSegmentLength - fromBorderDist) / edgeSegmentLength;
var xFrom = (fromBorderPoint) * this.from.x + (1 - fromBorderPoint) * this.to.x;
var yFrom = (fromBorderPoint) * this.from.y + (1 - fromBorderPoint) * this.to.y;
var via;
if (this.options.smoothCurves.dynamic == true && this.options.smoothCurves.enabled == true ) {
via = this.via;
}
else if (this.options.smoothCurves.enabled == true) {
via = this._getViaCoordinates();
}
// set vars
var angle, length, arrowPos;
if (this.options.smoothCurves.enabled == true && via.x != null) {
angle = Math.atan2((this.to.y - via.y), (this.to.x - via.x));
dx = (this.to.x - via.x);
dy = (this.to.y - via.y);
edgeSegmentLength = Math.sqrt(dx * dx + dy * dy);
}
var toBorderDist = this.to.distanceToBorder(ctx, angle);
var toBorderPoint = (edgeSegmentLength - toBorderDist) / edgeSegmentLength;
var xTo,yTo;
if (this.options.smoothCurves.enabled == true && via.x != null) {
xTo = (1 - toBorderPoint) * via.x + toBorderPoint * this.to.x;
yTo = (1 - toBorderPoint) * via.y + toBorderPoint * this.to.y;
}
else {
xTo = (1 - toBorderPoint) * this.from.x + toBorderPoint * this.to.x;
yTo = (1 - toBorderPoint) * this.from.y + toBorderPoint * this.to.y;
}
// if not connected to itself
if (this.from != this.to) {
// draw line
this._line(ctx);
ctx.beginPath();
ctx.moveTo(xFrom,yFrom);
if (this.options.smoothCurves.enabled == true && via.x != null) {
ctx.quadraticCurveTo(via.x,via.y,xTo, yTo);
// draw arrow head
if (this.options.smoothCurves.enabled == true) {
var via = this._getViaCoordinates();
arrowPos = this._findBorderPosition(false, ctx);
var guidePos = this._pointOnBezier(Math.max(0.0, arrowPos.t - 0.1))
angle = Math.atan2((arrowPos.y - guidePos.y), (arrowPos.x - guidePos.x));
} }
else { else {
ctx.lineTo(xTo, yTo);
angle = Math.atan2((this.to.y - this.from.y), (this.to.x - this.from.x));
var dx = (this.to.x - this.from.x);
var dy = (this.to.y - this.from.y);
var edgeSegmentLength = Math.sqrt(dx * dx + dy * dy);
var toBorderDist = this.to.distanceToBorder(ctx, angle);
var toBorderPoint = (edgeSegmentLength - toBorderDist) / edgeSegmentLength;
arrowPos = {};
arrowPos.x = (1 - toBorderPoint) * this.from.x + toBorderPoint * this.to.x;
arrowPos.y = (1 - toBorderPoint) * this.from.y + toBorderPoint * this.to.y;
} }
ctx.stroke();
// draw arrow at the end of the line // draw arrow at the end of the line
length = (10 + 5 * this.options.width) * this.options.arrowScaleFactor; length = (10 + 5 * this.options.width) * this.options.arrowScaleFactor;
ctx.arrow(xTo, yTo, angle, length);
ctx.arrow(arrowPos.x,arrowPos.y, angle, length);
ctx.fill(); ctx.fill();
ctx.stroke(); ctx.stroke();
@ -847,9 +896,7 @@ Edge.prototype._drawArrow = function(ctx) {
if (this.label) { if (this.label) {
var point; var point;
if (this.options.smoothCurves.enabled == true && via != null) { if (this.options.smoothCurves.enabled == true && via != null) {
var midpointX = 0.5*(0.5*(this.from.x + via.x) + 0.5*(this.to.x + via.x));
var midpointY = 0.5*(0.5*(this.from.y + via.y) + 0.5*(this.to.y + via.y));
point = {x:midpointX, y:midpointY};
point = this._pointOnBezier(0.5);
} }
else { else {
point = this._pointOnLine(0.5); point = this._pointOnLine(0.5);
@ -902,8 +949,6 @@ Edge.prototype._drawArrow = function(ctx) {
} }
}; };
/** /**
* Calculate the distance between a point (x3,y3) and a line segment from * Calculate the distance between a point (x3,y3) and a line segment from
* (x1,y1) to (x2,y2). * (x1,y1) to (x2,y2).

Loading…
Cancel
Save