/**
* Contains a collection of mathematical functions with some additional data
* used for WebVOWL.
*/
module.exports = (function (){
var math = {},
loopFunction = d3.svg.line()
.x(function ( d ){
return d.x;
})
.y(function ( d ){
return d.y;
})
.interpolate("cardinal")
.tension(-1);
/**
* Calculates the normal vector of the path between the two nodes.
* @param source the first node
* @param target the second node
* @param length the length of the calculated normal vector
* @returns {{x: number, y: number}}
*/
math.calculateNormalVector = function ( source, target, length ){
var dx = target.x - source.x,
dy = target.y - source.y;
var nx = -dy,
ny = dx;
var vlength = Math.sqrt(nx * nx + ny * ny);
var ratio = vlength !== 0 ? length / vlength : 0;
return { "x": nx * ratio, "y": ny * ratio };
};
/**
* Calculates the path for a link, if it is a loop. Currently only working for circlular nodes.
* @param link the link
* @returns {*}
*/
math.getLoopPoints = function ( link ){
var node = link.domain(),
label = link.label();
var fairShareLoopAngle = 360 / link.loops().length,
fairShareLoopAngleWithMargin = fairShareLoopAngle * 0.8,
loopAngle = Math.min(60, fairShareLoopAngleWithMargin);
if ( label.increasedLoopAngle === true )
loopAngle = 120;
var dx = label.x - node.x,
dy = label.y - node.y,
labelRadian = Math.atan2(dy, dx),
labelAngle = calculateAngle(labelRadian);
var startAngle = labelAngle - loopAngle / 2,
endAngle = labelAngle + loopAngle / 2;
var arcFrom = calculateRadian(startAngle),
arcTo = calculateRadian(endAngle),
x1 = Math.cos(arcFrom) * node.actualRadius(),
y1 = Math.sin(arcFrom) * node.actualRadius(),
x2 = Math.cos(arcTo) * node.actualRadius(),
y2 = Math.sin(arcTo) * node.actualRadius(),
fixPoint1 = { "x": node.x + x1, "y": node.y + y1 },
fixPoint2 = { "x": node.x + x2, "y": node.y + y2 };
return [fixPoint1, fixPoint2];
};
math.calculateLoopPath = function ( link ){
var node = link.domain(),
label = link.label();
var fairShareLoopAngle = 360 / link.loops().length,
fairShareLoopAngleWithMargin = fairShareLoopAngle * 0.8,
loopAngle = Math.min(60, fairShareLoopAngleWithMargin);
if ( label.increasedLoopAngle === true )
loopAngle = 120;
var dx = label.x - node.x,
dy = label.y - node.y,
labelRadian = Math.atan2(dy, dx),
labelAngle = calculateAngle(labelRadian);
var startAngle = labelAngle - loopAngle / 2,
endAngle = labelAngle + loopAngle / 2;
var arcFrom = calculateRadian(startAngle),
arcTo = calculateRadian(endAngle),
x1 = Math.cos(arcFrom) * node.actualRadius(),
y1 = Math.sin(arcFrom) * node.actualRadius(),
x2 = Math.cos(arcTo) * node.actualRadius(),
y2 = Math.sin(arcTo) * node.actualRadius(),
fixPoint1 = { "x": node.x + x1, "y": node.y + y1 },
fixPoint2 = { "x": node.x + x2, "y": node.y + y2 };
return loopFunction([fixPoint1, link.label(), fixPoint2]);
};
math.calculateLoopPoints = function ( link ){
var node = link.domain(),
label = link.label();
var fairShareLoopAngle = 360 / link.loops().length,
fairShareLoopAngleWithMargin = fairShareLoopAngle * 0.8,
loopAngle = Math.min(60, fairShareLoopAngleWithMargin);
var dx = label.x - node.x,
dy = label.y - node.y,
labelRadian = Math.atan2(dy, dx),
labelAngle = calculateAngle(labelRadian);
var startAngle = labelAngle - loopAngle / 2,
endAngle = labelAngle + loopAngle / 2;
var arcFrom = calculateRadian(startAngle),
arcTo = calculateRadian(endAngle),
x1 = Math.cos(arcFrom) * node.actualRadius(),
y1 = Math.sin(arcFrom) * node.actualRadius(),
x2 = Math.cos(arcTo) * (node.actualRadius()),
y2 = Math.sin(arcTo) * (node.actualRadius()),
fixPoint1 = { "x": node.x + x1, "y": node.y + y1 },
fixPoint2 = { "x": node.x + x2, "y": node.y + y2 };
return [fixPoint1, link.label(), fixPoint2];
};
/**
* @param angle
* @returns {number} the radian of the angle
*/
function calculateRadian( angle ){
angle = angle % 360;
if ( angle < 0 ) {
angle = angle + 360;
}
return (Math.PI * angle) / 180;
}
/**
* @param radian
* @returns {number} the angle of the radian
*/
function calculateAngle( radian ){
return radian * (180 / Math.PI);
}
/**
* Calculates the point where the link between the source and target node
* intersects the border of the target node.
* @param source the source node
* @param target the target node
* @param additionalDistance additional distance the
* @returns {{x: number, y: number}}
*/
math.calculateIntersection = function ( source, target, additionalDistance ){
var dx = target.x - source.x,
dy = target.y - source.y,
length = Math.sqrt(dx * dx + dy * dy);
if ( length === 0 ) {
return { x: source.x, y: source.y };
}
var innerDistance = target.distanceToBorder(dx, dy);
var ratio = (length - (innerDistance + additionalDistance)) / length,
x = dx * ratio + source.x,
y = dy * ratio + source.y;
return { x: x, y: y };
};
/**
* Calculates the position between the two points.
* @param firstPoint
* @param secondPoint
* @returns {{x: number, y: number}}
*/
math.calculateCenter = function ( firstPoint, secondPoint ){
return {
x: (firstPoint.x + secondPoint.x) / 2,
y: (firstPoint.y + secondPoint.y) / 2
};
};
return function (){
/* Use a function here to keep a consistent style like webvowl.path.to.module()
* despite having just a single math object. */
return math;
};
})();