src_js/iconolab-bundle/src/components/cutout/svgboard.js
author Harris Baptiste <harris.baptiste@iri.centrepompidou.fr>
Wed, 31 Aug 2016 17:20:07 +0200
changeset 158 f9d0d6a840cf
parent 156 e1e14766f608
child 164 44ced96727f7
permissions -rw-r--r--
heatmap and folder design



import Snap from 'snapsvg'
import ShapeResizer from './shape-resizer'
import { eventEmitter } from '../utils'
import SnapsvgZoom from './snapsvg-zoom'

/* custom plugins */
Snap.plugin(function (Snap, Element, Paper, glob) {
	var elproto = Element.prototype;

	elproto.toBack = function () {
		this.prependTo(this.paper);
	};

	elproto.toFront = function () {
		this.appendTo(this.paper);
	};
});

Element.prototype.getTransformedXY = function( x, y ) {
            var m = this.transform().globalMatrix;
            return { x: m.x(x,y), y: m.y(x,y) };             
    };

var paper = null;
var mainImage = null;
var pointData = [];
var viewBoxBounds = {X: 100, Y:100};
var zoomFactor = {x:1, y:1};
var viewPort= {width:850, height:850};
var currentViewBox = []; 
var config = null;
var readOnly = false;
var startPoint = null;
var drawing_path = null;
var drawing_paths = [];
var canDraw = false;
var rectZone = null;
var PATH_COLOR = "#ff00ff";
var STROKE_COLOR = "red";
var FILL_COLOR = "orange";

var SELECTED_COLOR = "#ffff00";
var FIRST_NODE_COLOR = "#FF0000";
var HANDLE_SIZE = 8;
var isDragged = false;
var enablePoint = true;
var pathIsClosed = false;
var ENABLE_NEW_NODE = true;
var RECT_MODE ='RECT';
var drawingMode = RECT_MODE; //free
var FREE_MODE = 'FREE';
var availableModes = [RECT_MODE, FREE_MODE];
var onChangeCallback = null;

var getId = (function () {
		var cpt = 0;
		var defautPrefix = "item_"; 
		return function (prefix) {
			prefix = (typeof prefix === 'string') ? prefix : defautPrefix;
			cpt = cpt + 1;
			return prefix + cpt; 
		}
	}());

var handleRectPath = function (path) {
	if (readOnly) {
		paper.path(path).attr({ stroke:'red', opacity: 0.6});
		return;
	}

	var bbBox = Snap.path.getBBox(path);
	rectZone = paper.rect(bbBox.x, bbBox.y, bbBox.width, bbBox.height);
	rectZone.attr({fill: FILL_COLOR, stroke: STROKE_COLOR, opacity: 0.6});
	drawing_path = rectZone;
	canDraw = false;
	pathIsClosed = true;
	ShapeResizer.enable_resizer(paper, drawing_path, viewPort, currentViewBox);
};

var handleFreePath = function (path) {

	if (readOnly) {
		
		paper.path(path).attr({
			stroke: 'orange',
			fill: 'orange',
			opacity: 0.5,
		});

		return; 
	}

	var pathInfos = Snap.parsePathString(path);
	pathInfos.map(function (pathData) {
		if(pathData[0] !== 'Z') {
			createPoint(paper, pathData[1], pathData[2], pointData);
		} else {
			pathIsClosed = true;
			updatePath(paper, onClosePath);
		}
	});

	/* replay the path here */

}; 
//transform point to path
var updatePath = function (paper, updateCallback) {
	var path = "M";

	if (pointData.length <= 1) {
		return;
	}

	path += pointData[0].x + ',' + pointData[0].y;

	for (var i=0; i < pointData.length; i++) {
		if (i == 0) continue;

		var pointInfos = pointData[i];
		var lPath = "L" + pointInfos.x + "," + pointInfos.y;
		path += " " + lPath;
	}
	
	path += (pathIsClosed) ? " Z": "";

	/* remove prev path */
	if (drawing_path) {
		drawing_path.remove();
	}	

	drawing_path = paper.path(path);

	drawing_path.attr({
		stroke: STROKE_COLOR,
		"vector-effect": "non-scaling-stroke",//prevent line to be zoom in
		"stroke-width": 3,
		fill: "white",
		opacity: 0.1
	});
	
	/* bring all handler to front */
	pointData.map(function (point) {
		
		/*deal with handler size */
		var handleSize = computeHandleSize();
		if (point.handler) {
			point.handler.toFront();
		}
	});

	if (typeof updateCallback === 'function' && pathIsClosed) {
		updateCallback();
	}

	if (!updateCallback && pathIsClosed) {
		applyClosedStyle();	
	}
};

var applyClosedStyle = function () {
	drawing_path.attr({ fill: FILL_COLOR, strokeWidth: 1, opacity:.6 });
} 

var onClosePath = function () {
	ENABLE_NEW_NODE = false;
	applyClosedStyle();
};


var onClickOnHandler = function (point, p, e) {
	//close path
	if (point.isFirst && pointData.length > 2) {
		pathIsClosed = true;
	}
};

var updatePointPosition = function (newPoint, x, y) {
	var index = pointData.indexOf(newPoint);
	if (index !== -1) {
		pointData[index].x = x;
		pointData[index].y = y; 
		return true;
	} else {
		return false;
	}
};

var clearPreviousPath = function () {
	drawing_path.remove();
};

var computeHandleSize = function () {

	if(!currentViewBox.length) {
		currentViewBox = [0, 0, parseInt(mainImage.width()), parseInt(mainImage.height())]
	}
	var currentHandleSize = HANDLE_SIZE * Math.min(currentViewBox[2], currentViewBox[3]) / 850; 
	return currentHandleSize;
}
 
var onMoveHandler = function (dx, dy, posX, posY, e) {
	isDragged = true;
	var tdx, tdy;
    var snapInvMatrix = this.transform().diffMatrix.invert();
    snapInvMatrix.e = snapInvMatrix.f = 0;
    tdx = snapInvMatrix.x( dx,dy ); 
    tdy = snapInvMatrix.y( dx,dy );
	var transformValue = this.data('origTransform') + (this.data('origTransform') ? "T" : "t") + [tdx, tdy];
	this.attr({ transform: transformValue});
	var boxSize = this.getBBox();

	var wasUpdated = updatePointPosition(this.data('point'), boxSize.x + (computeHandleSize() / 2) , boxSize.y + (computeHandleSize() / 2));
	
	if (wasUpdated) {
		updatePath(this.paper);
	}
}

var bindHandlerEvent = function (point, p) {
	point.handler.click(onClickOnHandler.bind(this, point, p));
	/* -- handler -- */
	point.handler.hover(function () {
		point.handler.attr({fill: 'yellow'});
	}, function () {
		var fillColor = point.isFirst ? FIRST_NODE_COLOR : "";
		point.handler.attr({fill: fillColor});
	});
	
	point.handler.drag(onMoveHandler, function () {
        this.data('origTransform', this.transform().local );
	}, function () {
		if (!isDragged) { return true; }
		isDragged = false;
		enablePoint = false;
	});
}

var createPointHandler = function (p, point) {
	var handler;
	var handleSize = computeHandleSize();
	var handleX = point.x - handleSize / 2;
	var handleY = point.y - handleSize / 2;

	/* preserve initial size of 5px a quoi correspond 5 deal with current vp */
	handler = p.rect(handleX, handleY, handleSize, handleSize);
	
	handler.addClass("drawingHandler");
	point.handler = handler;
	point.handler.data('point', point);
	if (pointData.length === 0) {
		point.isFirst = true;
	}
	
	bindHandlerEvent(point, p);
	point.handler.attr({
		fill: (pointData.length === 0) ? FIRST_NODE_COLOR : "",
		opacity: 0.9,
		stroke: PATH_COLOR
	});

	return point;
}

//create paper
var createPoint = function (paper, x, y, pointData) {

	var point = {x:x, y:y, id: getId()};

	if (pathIsClosed) {
		updatePath(paper, onClosePath);
		return;
	}

	if (!enablePoint) {
		enablePoint = true;
		return false;
	}

	point = createPointHandler(paper, point);
	pointData.push(point);
	updatePath(paper);
};

var attachRectEvents = function (paper) {
	if (readOnly) { return false; }

	var startPosition = {};
	var currentPosition = {};
	/* add resizer */

	paper.mousedown(function (e) {

		if (drawingMode === FREE_MODE || pathIsClosed) { return; }
		startPosition.x = e.offsetX;
		startPosition.y = e.offsetY;
		canDraw = true;
	});

	paper.mousemove(function (e) {
		if (drawingMode === FREE_MODE) { return; }
		if (!canDraw) { return; }
		var x, y;
		currentPosition.x = e.offsetX;
		currentPosition.y = e.offsetY;
		
		if (rectZone) {
			rectZone.remove();
		}

		/* bas -> droite */
		var width = Math.abs(currentPosition.x - startPosition.x);
		var height = Math.abs(startPosition.y - currentPosition.y);

		if (currentPosition.y > startPosition.y && currentPosition.x > startPosition.x) {
			x = startPosition.x;
			y = startPosition.y;	
		}
		
		/* haut -> droite */
		if (currentPosition.y < startPosition.y && currentPosition.x > startPosition.x) {
			x = currentPosition.x - width;
			y = currentPosition.y; 
		}
		
		/* haut -> gauche */
		if (currentPosition.y < startPosition.y && currentPosition.x < startPosition.x) {
			x = currentPosition.x;
			y = currentPosition.y;
		}

		/* bas -> gauche */
		if (currentPosition.y > startPosition.y && currentPosition.x < startPosition.x) {
			x = currentPosition.x
			y = currentPosition.y - height;
		}
		if(!x || !y) { return; }	

		rectZone = paper.rect(x, y, width, height);
		rectZone.attr({fill: FILL_COLOR, stroke: STROKE_COLOR, opacity: 0.6});
	});


	paper.mouseup(function () {
		if ((drawingMode === FREE_MODE) || pathIsClosed || !rectZone) { return false; }
		drawing_path = rectZone;
		ShapeResizer.enable_resizer(paper, rectZone, viewPort, currentViewBox);
		canDraw = false;
		pathIsClosed = true;
	});
};

var attachPointEvents = function (paper) {
	if (readOnly) { return; }

	var clickTimeout = null;
	var preventClick = false;
	paper.dblclick( function (e) {
		if (drawingMode === RECT_MODE) {
			return true;
		}
		preventClick = true;
		if (clickTimeout) {
			clickTimeout = clearTimeout(clickTimeout);
			preventClick = false;
			pathIsClosed = true;
			if (pointData.length > 2) {
				updatePath(paper, onClosePath);
			}
		}

		return false;
	});

	var clickHandler = function (e) {
		if (preventClick) { return; }
		
		if (drawingMode === RECT_MODE) {
			return true;
		}
		if (!ENABLE_NEW_NODE) { return true; }
		createPoint(paper, e.offsetX, e.offsetY, pointData);
		clickTimeout = null;
		preventClick = false;
	}

	paper.click(function (e) {
		if (clickTimeout) { return; }
		clickTimeout = setTimeout(clickHandler.bind(this, e), 190);	
	});

};


var attachZoomEvents = function () {

	eventEmitter.on("zoomChanged", function (zoomInfos) {
		zoomFactor = zoomInfos.zoomFactor;
		currentViewBox = zoomInfos.currentViewBox;
		var previousPath = API.getPath();
		API.clear();
		API.setPath(previousPath);
	});

}


var API = {

	getPaper: function () {
		return paper;
	},

	setPath: function (pathString) {
		/* redraw the path */
		var pathInfos = pathString.split(';');
		if ( availableModes.indexOf(pathInfos[1]) === -1) {
			/* We assume then it is a free path */
			pathInfos[1] = "FREE"; 
		}

		this.setDrawingMode(pathInfos[1]);
		var pathData = pathInfos[0];
		
		if (!pathData.length) {
			return;
		}
		/* deal with path nomalization x = ImageWith/MaxXBound */
		var xRatio = mainImage.attr("width") / viewBoxBounds.X;
		var yRatio = mainImage.attr("height") / viewBoxBounds.Y;
		
		if (isNaN(xRatio) || isNaN(yRatio)) {
			new Error('Ratio should be a number.');
		}

		var transformMatrix = Snap.matrix(xRatio, 0, 0, yRatio, 0, 0);
		var path = Snap.path.map(pathData, transformMatrix).toString();
		
		/* always close path */
		if (path.search(/[z|Z]/gi) === -1 ) {
			path += "Z";
		}
		if (pathInfos.length >= 2) {
			if (pathInfos[1] === RECT_MODE) {
				handleRectPath(path);
			}

			if (pathInfos[1] === FREE_MODE) {
				handleFreePath(path);
			}
		}
	},

	setDrawingMode: function (mode) {
		
		if (availableModes.indexOf(mode) !== -1) {
			drawingMode = mode;
		}
		if (typeof onChangeCallback === "function") {
			onChangeCallback(drawingMode);
		}

		this.clear();
	},
	
	clear: function () {
		/* clear previous path, point, handler */
		pointData.map(function (point) {
			if (point.handler) {
				point.handler.remove();
			}
		});

		/*clear path is exists*/
		 if (drawing_path) {
		 	drawing_path.remove();
		 }		
		eventEmitter.emit("cutout:clear");
		pointData = [];
		startPoint = null;
		drawing_path = null;
		isDragged = false;
		enablePoint = true;
		pathIsClosed = false;
		ENABLE_NEW_NODE = true;
	},

	getShapeBBox: function () {
		var currentPath = this.getPath();
		return Snap.path.getBBox(currentPath);
	},
	
	getShape: function () {
		return this.getPath();
	},

	getPath: function () {
		/* retourne le chemin */
		/* send path and BBox | implement edit and load path */
		var path = "";
		if (drawing_path) {
			if (drawingMode === RECT_MODE) {
				var bBox = drawing_path.getBBox();
				var transform = drawing_path.transform();

				if (!transform.global.length) {
					var shapePath = drawing_path.getBBox().path;
				}

				else {
					
					var shapeX = drawing_path.node.getAttribute('x');
					var shapeY = drawing_path.node.getAttribute('y');
					var transformMatrix = transform.totalMatrix;
					var fakeShape = paper.rect(transformMatrix.x(shapeX, shapeY),transformMatrix.y(shapeX, shapeY), bBox.width, bBox.height);
					shapePath = fakeShape.getBBox().path;
					fakeShape.remove();
				}

				path = Snap.path.toAbsolute(shapePath).toString();

			}
			else {
				path = drawing_path.attr('d');
			}
		}

		var xRatio = viewBoxBounds.X / mainImage.attr("width");
		var yRatio = viewBoxBounds.Y / mainImage.attr("height");

		if(isNaN(xRatio) || isNaN(yRatio)) {
			new Error('ratio should be a number.');
		}

		if (!path.length) {
			path = (drawingMode === RECT_MODE) ? ";RECT" : ";FREE";
			return path;
		}
		var normalizeMatrix = Snap.matrix(xRatio, 0, 0, yRatio, 0, 0);

		path = Snap.path.map(path, normalizeMatrix).toString();

				/* save the type */
		var type = (drawingMode === RECT_MODE) ? ";RECT" : ";FREE";
		if (path.search(/[z|Z]/gi) === -1) {
			path += " Z";	
		}
		
		path += type;


		return path;
	}
};

/* change to a component */
export default {

	init: function(config) {
		mainImage = jQuery(config.wrapperId).find('.main-image').eq(0);
		var cutCanvas = jQuery(config.wrapperId).find('.cut-canvas').eq(0);
		var path = jQuery(config.wrapperId).find('.image-path').eq(0);

		if (typeof config.onDrawingModeChange === 'function') {
			onChangeCallback = config.onDrawingModeChange;
		}
		
		if (!mainImage.length) {
			throw new Error("The main image Can't be found ...");
		}

		if (!cutCanvas.length) {
			var cutCanvas = jQuery('<svg version="1.1"></svg>').addClass('cut-canvas');
			jQuery(config.wrapperId).append(cutCanvas);
			cutCanvas.append(mainImage);
		}

		

		cutCanvas.css({
			marginLeft: 'auto',
			marginRight: 'auto',
			width: viewPort.width,
			height: viewPort.height,
		});
		if (typeof config.readOnly === 'boolean' && config.readOnly === true) {
			readOnly = true;
		}

		paper = new Snap(cutCanvas.get(0));

		if (path.length) {
			jQuery(cutCanvas).append(path);
			var pathData = path.attr("d");
			API.setPath(pathData);
			path.remove();
		}
		/* enable zoom */
		attachZoomEvents();
		attachPointEvents(paper);
		attachRectEvents(paper);

		return API;
	}
};