Source: js/link.js

/**
 * Graph link constructor.
 * @class
 * @classdesc Graph link class.
 * @param {ge.GraphEditor}  graph  Link container.
 * @param {ImportLinkData}  data   Link data.
 */
ge.Link = function Link(graph, data) {
	/**
	 * Source node.
	 * @member {ge.Node}
	 */
	this.source = graph.getData(data.source);
	if(!this.source) {
		throw new Error('invalid link source');
	}
	/**
	 * Target node.
	 * @member {ge.Node}
	 */
	this.target = graph.getData(data.target);
	if(!this.target) {
		throw new Error('invalid link target');
	}
	/**
	 * ID.
	 * @readonly
	 * @member {ID}
	 */
	this.id = /*data.id || */graph.linkId(this.source, this.target);
	/**
	 * Path generator.
	 * @member {ge.path.Path}
	 */
	this.shape = data.shape
		? ge.path.fromJson(data.shape)
		: graph.options.link.shape;
	/**
	 * Title.
	 * @member {string}
	 */
	this.title = data.title === undefined ? this.id : data.title;
	/**
	 * User data.
	 * @member {*}
	 */
	this.data = data.data;
	/**
	 * SVG path.
	 * @readonly
	 * @member {string}
	 */
	this.path = '';
	/**
	 * True if text path is reversed.
	 * @readonly
	 * @member {boolean}
	 */
	this.reversed = false;
	/**
	 * SVG element ID.
	 * @readonly
	 * @member {string}
	 */
	this.elementId = graph.options.id.concat('l', this.id);
	/**
	 * SVG element selector.
	 * @readonly
	 * @member {string}
	 */
	this.selector = '#' + this.elementId;
	/**
	 * SVG <path> element ID.
	 * @readonly
	 * @member {string}
	 */
	this.pathId = graph.options.id.concat('p', this.id);
	/**
	 * SVG <path> element selector.
	 * @readonly
	 * @member {Selector}
	 */
	this.pathSelector = '#' + this.pathId;
	/**
	 * SVG text path element ID.
	 * @readonly
	 * @member {string}
	 */
	this.defId = graph.options.id.concat('d', this.id);
	/**
	 * SVG text path element selector.
	 * @readonly
	 * @member {Selector}
	 */
	this.defSelector = '#' + this.defId;

	return this;
};

/**
 * Initialize SVG elements.
 * @static
 * @param {D3Selection} links          SVG element enter selection.
 * @param {D3Selection} defs           SVG element enter selection.
 * @param {object}      opts           Link options.
 * @param {number}      opts.text.dx   Link title X offset.
 * @param {number}      opts.text.dy   Link title Y offset.
 * @param {string}      cls            CSS class.
 */
ge.Link.initSelection = function initSelection(links, defs, opts) {
	defs.attr('id', function(d) { return d.defId; });

	links.attr('id', function(d) { return d.elementId; });
	links.append('path')
		.attr('id', function(d) { return d.pathId; });
	links.append('text')
		.attr('dx', opts.text.dx)
		.attr('dy', opts.text.dy)
		.append('textPath')
		.attr('xlink:href', function(d) { return d.defSelector; });
};

/**
 * Update SVG elements.
 * @static
 * @param {D3Selection} links       SVG element selection.
 * @param {D3Selection} defs        SVG element selection.
 * @param {object}      opts        Link options.
 * @param {function}    transition  Transition generator.
 * @see ge.GraphEditor.updateLink
 */
ge.Link.updateSelection = function updateSelection(
	links, defs,
	opts, transition
) {
	transition(defs)
		.attr('d', function(d) { return d.shape.path(d); });

	transition(links.select('path'))
		.style('stroke-width', function(d) { return d.size + 'px'; })
		.attr('d', function(d) { return d.path; });

	var offset = opts.text.offset;
	var anchor = opts.text.anchor;

	transition(links.select('textPath'))
		.text(function(d) { return d.title; })
		.attr('startOffset', function(d) { return offset[+d.reversed]; })
		.attr('text-anchor', function(d) { return anchor[+d.reversed]; });
};

/**
 * Initialize SVG elements.
 * @param {D3Selection} links  SVG element enter selection.
 * @see ge.GraphEditor.update
 * @see ge.GraphEditor.linkEvents
 */
ge.Link.prototype.initSelection = ge.Link.initSelection;

/**
 * Update SVG elements.
 * @param {D3Selection} links  SVG element selection.
 * @see ge.GraphEditor.updateLink
 */
ge.Link.prototype.updateSelection = ge.Link.updateSelection;

/**
 * Update link data.
 */
ge.Link.prototype.update = function update() {
	this.shape(this);
};

/**
 * Convert to JSON.
 * @param   {ge.GraphEditor} graph      Graph.
 * @returns {ExportLinkData}            JSON data.
 */
ge.Link.prototype.toJson = function toJson(graph) {
	return {
		id: this.id,
		source: this.source.id,
		target: this.target.id,
		size: this.size,
		title: this.title,
		data: this.data,
		shape: this.shape === graph.options.link.shape
			? null
			: ge.path.toJson(this.shape)
	};
};