Source: js/events.js

'use strict';

/**
 * Add an event handler.
 * @param   {string}         event
 * @param   {?function}      handler
 * @returns {ge.GraphEditor}
 * @see [d3.dispatch.on]{@link https://github.com/d3/d3-dispatch#dispatch_on}
 */
ge.GraphEditor.prototype.on = function on(event, handler) {
	this.dispatch.on(event, handler);
	return this;
};

/**
 * Get mouse or touch coordinates relative to container.
 * @private
 * @param {(HTMLElement|SVGElement)} [container=this.container.node()]
 * @returns {Point}
 * @see [d3.mouse]{@link https://github.com/d3/d3-selection/blob/master/README.md#mouse}
 * @see [d3.touch]{@link https://github.com/d3/d3-selection/blob/master/README.md#touch}
 */
ge.GraphEditor.prototype.clickPosition = function clickPosition(container) {
	container = container || this.container.node();
	return d3.touch(container) || d3.mouse(container);
};

/**
 * Get touched node.
 * @private
 * @returns {?Node}
 * @see [d3.event]{@link https://github.com/d3/d3-selection/blob/master/README.md#event}
 * @see [document.elementFromPoint]{@link https://developer.mozilla.org/en-US/docs/Web/API/Document/elementFromPoint}
 */
ge.GraphEditor.prototype.touchedNode = function touchedNode() {
	var x = d3.event.touches[0].pageX;
	var y = d3.event.touches[0].pageY;
	var el = document.elementFromPoint(x, y);
	var nodeClass = this.options.css.node;
	while(true) {
		if(!el) {
			return null;
		}
		switch(el.tagName.toLowerCase()) {
			case 'svg':
			case 'body':
				return null;
			case 'g':
				if(el.classList.contains(nodeClass)) {
					return d3.select(el).datum();
				}
		}
		el = el.parentNode;
	}
};

/**
 * Set node drag event handlers.
 * @private
 * @param {D3Drag} drag  Node drag behavior.
 * @fires node-click
 * @fires new-link-start
 * @fires new-link-end
 * @fires new-link-cancel
 * @returns {D3Drag}
 */
ge.GraphEditor.prototype.dragEvents = function dragEvents(drag) {
	var self = this;

	return drag
		.on('start', function dragStart(d) {
			self.state.dragged = false;
			self.state.dragPos = null;
			d3.select(this).raise();

			if(self.state.simulationStarted && !self.state.dragToLink) {
				d.fx = d.x;
				d.fy = d.y;
				self.simulation('restart');
			}
		})
		.on('drag', function drag(d) {
			if(self.state.dragToLink) {
				var mouse = self.clickPosition();
				var path = ''.concat(
					'M', d.x, ',', d.y,
					'L', mouse[0], ',', mouse[1]
				);

				if(!self.state.dragged) {
					self.state.dragged = true;
					self.dragLine
						.classed(self.options.css.hide, false)
						.attr('d', path);
					self.dispatch.call('new-link-start', self, d);
				}
				else {
					self.transition(self.dragLine, 'drag')
						.attr('d', path);
				}
			}
			else {
				self.state.dragged = true;
				d.x += d3.event.dx;
				d.y += d3.event.dy;
				if(self.state.simulationStarted) {
					d.fx = d.x;
					d.fy = d.y;
					self.simulation('restart');
				}
				self.updateNode(this);
			}
		})
		.on('end', function dragEnd(d) {
			d.fx = null;
			d.fy = null;
			
			self.state.dragPos = self.clickPosition();

			if(self.state.dragged) {
				self.state.dragged = false;
				if(self.state.dragToLink) {
					self.dragLine.classed(self.options.css.hide, true);
					if(self.state.newLinkTarget) {
						var target = self.state.newLinkTarget;
						self.state.newLinkTarget = null;
						self.dispatch.call(
							'new-link-end',
							self, d, target
						);
					}
					else {
						self.dispatch.call('new-link-cancel', self, d);
					}
				}
				else if(self.state.simulationStarted) {
					self.simulation('restart');
				}
			}
			else {
				self.dispatch.call('node-click', self, d);
			}
		});
};

/**
 * Set zoom event handlers.
 * @private
 * @param {D3Zoom} zoom  Graph zoom behavior.
 * @fires click
 * @returns {D3Zoom}
 */
ge.GraphEditor.prototype.zoomEvents = function zoomEvents(zoom) {
	var self = this;
	//var prevScale = 1;

	return zoom
		.duration(self.options.transition.zoom)
		.on('end', function zoomEnd() {
			if(!self.state.zoomed) {
				var pos = self.clickPosition();
				if(!ge.equal(self.state.dragPos, pos)) {
					self.dispatch.call('click', self, pos);
				}
				else {
					self.state.dragPos = null;
				}
			}
			self.state.zoomed = false;
		})
		.on('zoom', function zoom() {
			self.state.zoomed = true;

			/*var scale = d3.event.transform.k;

			if(Math.abs(scale - prevScale) > 0.001)
			{
				prevScale = scale;

				if(scale > graph.options.sizeScaleMax)
				{
					self.state.sizeScale = graph.options.sizeScaleMax / scale;
				}
				else if(scale < graph.options.sizeScaleMin)
				{
					self.state.sizeScale = graph.options.sizeScaleMin / scale;
				}
				else
				{
					self.state.sizeScale = 1;
				}

				self.updateSize();
			}*/

			var type;

			if(ge.equal(d3.event.transform.k, self.state.zoom)) {
				type = 'drag';
			}
			else {
				type = 'zoom';
				self.state.zoom = d3.event.transform.k;
			}

			self.transition(self.container, type, 'zoom')
				.attr(
					'transform',
					d3.event.transform.toString()
				);
		});
};

/**
 * Set link event handlers.
 * @private
 * @param {D3Selection} links
 * @fires link-click
 * @returns {D3Selection}
 */
ge.GraphEditor.prototype.linkEvents = function linkEvents(links) {
	var self = this;

	return links
		.on('mousedown', function(/*d*/) {
			d3.event.stopPropagation();
			d3.event.preventDefault();
		})
		.on('touchstart', function(/*d*/) {
			self.state.dragPos = self.clickPosition();
		})
		.on('mouseup', function(d) {
			d3.event.stopPropagation();
			d3.event.preventDefault();
			self.dispatch.call('link-click', self, d);
		});
};


/**
 * Set node event handlers.
 * @private
 * @param {D3Selection} nodes
 * @returns {D3Selection}
 */
ge.GraphEditor.prototype.nodeEvents = function nodeEvents(nodes) {
	var self = this;

	return nodes
		/*.attr(
			'transform',
			function(d) {
				return 'translate('.concat(d.x, ',', d.y, ')');
			}
		)*/
		.on('mouseover', function mouseOver(d) {
			if(self.state.dragToLink) {
				self.state.newLinkTarget = d;
				/*d3.select(this).classed(
					self.options.css.connect,
					true
				);*/
			}
		})
		.on('mouseout', function mouseOut(/*d*/) {
			if(self.state.dragToLink) {
				self.state.newLinkTarget = null;
			}
			/*d3.select(this).classed(
				self.options.css.connect,
				false
			);*/
		})
		.on('touchmove', function touchMove() {
			if(self.state.dragToLink) {
				self.state.newLinkTarget = self.touchedNode();
			}
		})
		.call(self.drag);
};