Source: js/path/line.js

/**
 * Line.
 * @class
 * @param {?object}  [data]                JSON data.
 * @param {?number}  [data.loopStart=180]  Loop start angle in degrees.
 * @param {?number}  [data.loopEnd=270]    Loop end angle in degrees.
 * @param {?boolean} [data.arrow=false]
 * @param {?number}  [data.arrowLength=10]
 * @param {?number}  [data.arrowAngle=15]
 */
ge.path.Line = function Line(data) {
	ge.path.Path.call(this, data);
	this.arrow = !!data.arrow;
	this.arrowLength = +data.arrowLength || 10;
	this.arrowAngle = new ge.Angle(+data.arrowAngle || 15);
	this.arrowAngle2 = new ge.Angle(-data.arrowAngle || -15);
};

ge.path.Line.prototype = Object.create(ge.path.Line);
Object.defineProperty(
	ge.path.Line.prototype,
	'constructor',
	{ 
		value: ge.path.Line,
		enumerable: false,
		writable: true
	}
);
ge.path.addClass(ge.path.Line);

/**
 * Return arrow path.
 * @param   {ge.Point}   src   Link source.
 * @param   {ge.Point}   dst   Link target.
 * @returns {string}           SVG path.
 */
ge.path.Line.prototype.arrowPath = function arrow(src, dst) {
	var b = src.clone().sub(dst).normalize();
	var c = b.clone().rotate(this.arrowAngle2).mul(this.arrowLength).add(dst);
	b.rotate(this.arrowAngle).mul(this.arrowLength).add(dst);
	return 'M'.concat(
		dst.x, ',', dst.y,
		'L', b.x, ',', b.y,
		'L', c.x, ',', c.y,
		'Z'
	);
};

/**
 * Return text path and set link path.
 * @param   {ge.Link}   link   Link data.
 * @returns {string}           SVG path.
 */
ge.path.Line.prototype.path = function path(link) {
	var src, dst;

	if(link.source === link.target) {
		src = link.source.shape.getPoint(link.source, this.loopStart);
		dst = link.source.shape.getPoint(link.source, this.loopEnd);
		var rx = link.source.width / 2;
		var ry = link.source.height / 2;

		link.reversed = false;
		link.path = 'M'.concat(
			src.x, ',', src.y,
			'A', rx, ',', ry, ',0,1,0,', dst.x, ',', dst.y
		);

		/*if(this.arrow) {
		}*/

		return link.path;
	}

	src = new ge.Point(link.source.x, link.source.y);
	dst = new ge.Point(link.target.x, link.target.y);
	var src2 = link.source.shape.intersect(link.source, link.target);
	var dst2 = link.target.shape.intersect(link.target, link.source);
	src = src2 || src;
	dst = dst2 || dst;

	var dst3 = dst;

	if(this.arrow) {
		dst3 = dst.clone().sub(src);
		var l = dst3.length();
		var l2 = this.arrowLength * this.arrowAngle.cos;
		dst3.mul(1 - l2 / l).add(src);
	}
	
	link.path = 'M'.concat(src.x, ',', src.y, 'L', dst3.x, ',', dst3.y);

	if(this.arrow) {
		link.path += this.arrowPath(src, dst);
	}

	if((link.reversed = src.x > dst.x)) {
		src2 = src;
		src = dst;
		dst = src2;
	}

	return 'M'.concat(src.x, ',', src.y, 'L', dst.x, ',', dst.y);
};