snap.svg.zpd
Version:
A zoom/pan/drag plugin for Snap.svg
937 lines (728 loc) • 36.8 kB
JavaScript
/* globals Snap, document, navigator */
/**
* snapsvg-zpd.js: A zoom/pan/drag plugin for Snap.svg
* ==================================================
*
* Usage
* =======
* var paper = Snap();
* var bigCircle = paper.circle(150, 150, 100);
* paper.zpd();
*
* // or settings and callback
* paper.zpd({ zoom: false }), function (err, paper) { });
*
* // or callback
* paper.zpd(function (err, paper) { });
*
* // destroy
* paper.zpd('destroy');
*
* // save
* paper.zpd('save');
*
* // load
* // paper.zpd({ load: SVGMatrix {} });
*
* // origin
* paper.zpd('origin');
*
* // zoomTo
* paper.zoomTo(1);
*
* // panTo
* paper.panTo(0, 0); // original location
* paper.panTo('+10', 0); // move right
*
* // rotate
* paper.rotate(15); // rotate 15 deg
*
* // change pan directions
* paper.zpd({'panDirections':'horizontal'});
*
* Notice
* ========
* This usually use on present view only. Not for Storing, modifying the paper.
*
* Reason:
* Usually <pan> <zoom> => <svg transform="matrix(a,b,c,d,e,f)"></svg>
*
* But if you need to store the <drag> location, (for storing)
* we have to use <circle cx="x" cy="y"></circle> not <circle tranform="matrix(a,b,c,d,e,f)"></circle>
*
* License
* =========
* This code is licensed under the following BSD license:
*
* Copyright 2014-2020 Huei Tan (Snap.svg integration). All rights reserved.
* Copyright 2009-2010 Andrea Leofreddi <a.leofreddi@itcharm.com> (original author). All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY Andrea Leofreddi ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Andrea Leofreddi OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those of the
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of Andrea Leofreddi.
*/
SVGElement.prototype.getTransformToElement = SVGElement.prototype.getTransformToElement || function(elem) {
return elem.getScreenCTM().inverse().multiply(this.getScreenCTM());
};
(function (Snap) {
Snap.plugin(function (Snap, Element, Paper, glob, Fragment) {
/**
* Global variable for snap.svg.zpd plugin
*/
var snapsvgzpd = {
uniqueIdPrefix: 'snapsvg-zpd-', // prefix for the unique ids created for zpd
dataStore: {}, // "global" storage for all our zpd elements
enable: true // By default, snapsvgzpd should enable, zpd('toggle') to toggle enable or disable
};
/**
* Global variable to store root of the svg element
*/
var rootSvgObject;
/**
* remove node parent but keep children
*/
var _removeNodeKeepChildren = function removeNodeKeepChildren(node) {
if (!node.parentNode) {
return;
}
while (node.firstChild) {
node.parentNode.insertBefore(node.firstChild, node);
}
node.parentNode.removeChild(node);
};
/**
* Detect is +1 -1 or 1
* increase decrease or just number
*/
var _increaseDecreaseOrNumber = function increaseDecreaseOrNumber(defaultValue, input) {
if (input === undefined) {
return parseInt(defaultValue);
} else if (input[0] == '+') {
return defaultValue + parseInt(input.split('+')[1]);
} else if (input[0] == '-') {
return defaultValue - parseInt(input.split('-')[1]);
} else {
return parseInt(input);
}
};
/**
* Sets the current transform matrix of an element.
*/
var _setCTM = function setCTM(element, matrix) {
var s = "matrix(" + matrix.a + "," + matrix.b + "," + matrix.c + "," + matrix.d + "," + matrix.e + "," + matrix.f + ")";
element.setAttribute("transform", s);
};
/**
* Dumps a matrix to a string (useful for debug).
*/
var _dumpMatrix = function dumpMatrix(matrix) {
var s = "[ " + matrix.a + ", " + matrix.c + ", " + matrix.e + "\n " + matrix.b + ", " + matrix.d + ", " + matrix.f + "\n 0, 0, 1 ]";
return s;
};
/**
* Instance an SVGPoint object with given event coordinates.
*/
var _findPos = function findPos(obj) {
var curleft = curtop = 0;
var boundingClientRect = obj.getBoundingClientRect();
if (boundingClientRect) {
curleft = boundingClientRect.left;
curtop = boundingClientRect.top;
}
return [curleft, curtop];
};
var _getEventPoint = function getEventPoint(event, svgNode) {
var p = svgNode.node.createSVGPoint(),
svgPos = _findPos(svgNode.node);
if (typeof event.touches != 'undefined') {
p.x = event.touches[0].clientX - svgPos[0];
p.y = event.touches[0].clientY - svgPos[1];
} else {
p.x = event.clientX - svgPos[0];
p.y = event.clientY - svgPos[1];
}
return p;
};
/**
* Detect multi-touch (i.e. pinch)
*/
var _isTwoTouch = function isTwoTouch(event) {
var b = false;
if (typeof event.touches != 'undefined' && event.touches.length == 2) {
b = true;
}
return b;
};
/**
* Calculate the distance between the 1st and 2nd touches
*/
var _getMultiTouchDistance = function getMultiTouchDistance(event) {
return Math.sqrt(
(event.touches[0].clientX - event.touches[1].clientX) * (event.touches[0].clientX - event.touches[1].clientX) +
(event.touches[0].clientY - event.touches[1].clientY) * (event.touches[0].clientY - event.touches[1].clientY));
};
/**
* Common function to handle the zooming for both touch and mouse wheel.
*/
var _handleZoomingEvent = function handleZoomingEvent(event, zpdElement, delta) {
var z = Math.pow(1 + zpdElement.options.zoomScale, delta);
var g = zpdElement.element.node;
var p = _getEventPoint(event, zpdElement.data.svg);
p = p.matrixTransform(g.getCTM().inverse());
// Compute new scale matrix in current mouse position
var k = zpdElement.data.root.createSVGMatrix().translate(p.x, p.y).scale(z).translate(-p.x, -p.y);
var matrix = g.getCTM().multiply(k);
// detecting if zoom threshold was exceeded
var recalculateMatrix = function recalculateMatrix(scale) {
z = scale / g.getCTM().a;
k = zpdElement.data.root.createSVGMatrix().translate(p.x, p.y).scale(z).translate(-p.x, -p.y);
matrix = g.getCTM().multiply(k);
matrix.a = matrix.a.toFixed(4);
matrix.d = matrix.d.toFixed(4);
}
var threshold = zpdElement.options.zoomThreshold;
if (threshold && typeof threshold === 'object') { // array [0.5,2]
var oldMatrix = Snap(g).transform().globalMatrix;
if ( (matrix.a < oldMatrix.a && matrix.a < threshold[0])
|| (matrix.d < oldMatrix.d && matrix.d < threshold[0])) {
recalculateMatrix(threshold[0]);
} else if ( (matrix.a > oldMatrix.a && matrix.a > threshold[1])
|| (matrix.d > oldMatrix.d && matrix.d > threshold[1])) {
recalculateMatrix(threshold[1]);
}
}
_setCTM(g, matrix);
if (typeof(stateTf) == 'undefined') {
zpdElement.data.stateTf = g.getCTM().inverse();
}
zpdElement.data.stateTf = zpdElement.data.stateTf.multiply(k.inverse());
};
/**
* Get an svg transformation matrix as string representation
*/
var _getSvgMatrixAsString = function _getMatrixAsString (matrix) {
return 'matrix(' + matrix.a + ',' + matrix.b + ',' + matrix.c + ',' + matrix.d + ',' + matrix.e + ',' + matrix.f + ')';
};
/**
* add a new <g> element to the paper
* add paper nodes into <g> element (Snapsvg Element)
* and give the nodes an unique id like 'snapsvg-zpd-12345'
* and let this <g> Element to global snapsvgzpd.dataStore['snapsvg-zpd-12345']
* and
* <svg>
* <def>something</def>
* <circle cx="10" cy="10" r="100"></circle>
* </svg>
*
* transform to =>
*
* <svg>
* <g id="snapsvg-zpd-12345">
* <def>something</def>
* <circle cx="10" cy="10" r="100"></circle>
* </g>
* </svg>
*/
var _initZpdElement = function initAndGetZpdElement (svgObject, options) {
// get root of svg object
rootSvgObject = svgObject.node;
// get all child nodes in our svg element
var rootChildNodes = svgObject.node.childNodes;
// create a new graphics element in our svg element
var gElement = svgObject.g();
var gNode = gElement.node;
// add our unique id to the element
gNode.id = snapsvgzpd.uniqueIdPrefix + svgObject.id;
// check if a matrix has been supplied to initialize the drawing
if (options.load && typeof options.load === 'object') {
var matrix = options.load;
// create a matrix string from our supplied matrix
var matrixString = "matrix(" + matrix.a + "," + matrix.b + "," + matrix.c + "," + matrix.d + "," + matrix.e + "," + matrix.f + ")";
// load <g> transform matrix
gElement.transform(matrixString);
} else {
// initial set <g transform="matrix(1,0,0,1,0,0)">
gElement.transform('matrix');
}
// initialize our index counter for child nodes
var index = 0;
// get the number of child nodes in our root node
// substract -1 to exclude our <g> element
var noOfChildNodes = rootChildNodes.length - 1;
// go through all child elements
// (except the last one, which is our <g> element)
while (index < noOfChildNodes) {
gNode.appendChild(rootChildNodes[0]);
index += 1;
}
// define some data to be used in the function internally
var data = {
svg: svgObject,
root: svgObject.node, // get paper svg
state: 'none',
stateTarget: null,
stateOrigin: null,
stateTf: null,
touchZoom: false,
prevZoomDistance: null,
};
// create an element with all required properties
var item = {
"element": gElement,
"data": data,
"options": options,
};
// create some mouse event handlers for our item
// store them globally for optional removal later on
item.handlerFunctions = _getHandlerFunctions(item);
// return our element
return item;
};
/**
* create some handler functions for our mouse actions
* we will take advantace of closures to preserve some data
*/
var _getHandlerFunctions = function getHandlerFunctions(zpdElement) {
var handleMouseOrTouchUp = function handleMouseOrTouchUp (event) {
if (event.preventDefault && zpdElement.options.preventDefaultEvent.handleMouseOrTouchUp) {
event.preventDefault();
}
if (!snapsvgzpd.enable) return;
event.returnValue = false;
// On touchend reset the touchZoom variable to false
zpdElement.data.touchZoom = false;
if (zpdElement.data.state == 'pan' || zpdElement.data.state == 'drag') {
// quit pan mode
zpdElement.data.state = '';
}
};
var handleMouseOrTouchDown = function handleMouseOrTouchDown (event) {
if (event.preventDefault && zpdElement.options.preventDefaultEvent.handleMouseOrTouchDown) {
event.preventDefault();
}
if (!snapsvgzpd.enable) return;
event.returnValue = false;
// Detect if multi-touch and set touchZoom variable, this will be used in determining when to pan or zoom
if (zpdElement.options.touch) zpdElement.data.touchZoom = _isTwoTouch(event);
var g = zpdElement.element.node;
if (
event.target.tagName == "svg" || !zpdElement.options.drag // Pan anyway when drag is disabled and the user clicked on an element
) {
// Pan mode
zpdElement.data.state = 'pan';
zpdElement.data.stateTf = g.getCTM().inverse();
zpdElement.data.stateOrigin = _getEventPoint(event, zpdElement.data.svg).matrixTransform(zpdElement.data.stateTf);
} else {
// Drag mode
zpdElement.data.state = 'drag';
zpdElement.data.stateTarget = event.target;
zpdElement.data.stateTf = g.getCTM().inverse();
zpdElement.data.stateOrigin = _getEventPoint(event, zpdElement.data.svg).matrixTransform(zpdElement.data.stateTf);
}
};
var handleMouseMove = function handleMouseMove (event) {
if (event.preventDefault && zpdElement.options.preventDefaultEvent.handleMouseMove) {
event.preventDefault();
}
if (!snapsvgzpd.enable) return;
event.returnValue = false;
var g = zpdElement.element.node;
if (zpdElement.data.state == 'pan' && zpdElement.options.pan) {
// Pan mode
var p = _getEventPoint(event, zpdElement.data.svg).matrixTransform(zpdElement.data.stateTf);
var trans_x=0;
var trans_y=0;
if ((zpdElement.options.panDirections == 'both') || (zpdElement.options.panDirections == 'horizontal')) {
var trans_x=p.x - zpdElement.data.stateOrigin.x;
}
if ((zpdElement.options.panDirections == 'both') || (zpdElement.options.panDirections == 'vertical')) {
var trans_y=p.y - zpdElement.data.stateOrigin.y;
}
_setCTM(g, zpdElement.data.stateTf.inverse().translate(trans_x,trans_y));
} else if (zpdElement.data.state == 'drag' && zpdElement.options.drag) {
// Drag mode
var dragPoint = _getEventPoint(event, zpdElement.data.svg).matrixTransform(g.getCTM().inverse());
var trans_x=0;
var trans_y=0;
if ((zpdElement.options.panDirections == 'both') || (zpdElement.options.panDirections == 'horizontal')) {
var trans_x=dragPoint.x - zpdElement.data.stateOrigin.x;
}
if ((zpdElement.options.panDirections == 'both') || (zpdElement.options.panDirections == 'vertical')) {
var trans_y=dragPoint.y - zpdElement.data.stateOrigin.y;
}
_setCTM(zpdElement.data.stateTarget,
zpdElement.data.root.createSVGMatrix()
.translate(trans_x, trans_y)
.multiply(g.getCTM().inverse())
.multiply(zpdElement.data.stateTarget.getCTM()));
zpdElement.data.stateOrigin = dragPoint;
}
};
var handleMouseWheel = function handleMouseWheel (event) {
if (!zpdElement.options.zoom) {
return;
}
if (event.preventDefault && zpdElement.options.preventDefaultEvent.handleMouseWheel) {
event.preventDefault();
}
if (!snapsvgzpd.enable) return;
event.returnValue = false;
var delta = 0;
if (event.wheelDelta) {
delta = event.wheelDelta / 360; // Chrome/Safari
}
else {
delta = event.detail / -9; // Mozilla
}
_handleZoomingEvent(event, zpdElement, delta);
};
var handleTouchMove = function handleTouchMove (event) {
if (!zpdElement.options.zoom || !zpdElement.options.touch) {
return;
}
if (event.preventDefault && zpdElement.options.preventDefaultEvent.handleTouchMove) {
event.preventDefault();
}
if (!snapsvgzpd.enable) return;
event.returnValue = false;
// If multi-touch is true, then we are zooming instead of panning or dragging.
if (zpdElement.data.touchZoom) {
var distance = _getMultiTouchDistance(event);
if (zpdElement.data.prevZoomDistance != null) {
// The delta value is set to 0.15 as it best matches the zoom sensitivity in browsers. Use zoomScale option to change the zoom sensitivity.
var delta = 0.15; // Case for the pinch being opened.
// Case for pinch being closed, make the delta negative
if (zpdElement.data.prevZoomDistance > distance) delta = delta * -1;
_handleZoomingEvent(event, zpdElement, delta);
}
// Store the distance between touch positions so we can compare the changes to see if it's getting larger or smaller.
zpdElement.data.prevZoomDistance = distance;
} else {
handleMouseMove (event);
}
};
return {
"mouseOrTouchUp": handleMouseOrTouchUp,
"mouseOrTouchDown": handleMouseOrTouchDown,
"mouseMove": handleMouseMove,
"mouseWheel": handleMouseWheel,
"touchMove": handleTouchMove
};
};
/**
* Register handlers
* desktop and mobile
*/
var _setupHandlers = function setupHandlers(svgElement, handlerFunctions) {
// mobile
if ('ontouchend' in document.documentElement) {
svgElement.addEventListener('touchend', handlerFunctions.mouseOrTouchUp, false);
svgElement.addEventListener('touchcancel', handlerFunctions.mouseOrTouchUp, false);
svgElement.addEventListener('touchstart', handlerFunctions.mouseOrTouchDown, false);
// This event handles both panning and zooming
svgElement.addEventListener('touchmove', handlerFunctions.touchMove, false);
// desktop
} else if ('onmouseup' in document.documentElement) {
// IE < 9 would need to use the event onmouseup, but they do not support svg anyway..
svgElement.addEventListener('mouseup', handlerFunctions.mouseOrTouchUp, false);
svgElement.addEventListener('mousedown', handlerFunctions.mouseOrTouchDown, false);
svgElement.addEventListener('mousemove', handlerFunctions.mouseMove, false);
if (navigator.userAgent.toLowerCase().indexOf('webkit') >= 0 ||
navigator.userAgent.toLowerCase().indexOf('trident') >= 0) {
svgElement.addEventListener('mousewheel', handlerFunctions.mouseWheel, false); // Chrome/Safari
}
else {
svgElement.addEventListener('DOMMouseScroll', handlerFunctions.mouseWheel, false); // Others
}
}
};
/**
* remove event handlers
*/
var _tearDownHandlers = function tearDownHandlers(svgElement, handlerFunctions) {
// mobile
if ('ontouchend' in document.documentElement) {
svgElement.removeEventListener('touchend', handlerFunctions.mouseOrTouchUp, false);
svgElement.removeEventListener('touchcancel', handlerFunctions.mouseOrTouchUp, false);
svgElement.removeEventListener('touchstart', handlerFunctions.mouseOrTouchDown, false);
// This event handles both panning and zooming
svgElement.removeEventListener('touchmove', handlerFunctions.touchMove, false);
// desktop
} else if ('onmouseup' in document.documentElement) {
svgElement.removeEventListener('mouseup', handlerFunctions.mouseOrTouchUp, false);
svgElement.removeEventListener('mousedown', handlerFunctions.mouseOrTouchDown, false);
svgElement.removeEventListener('mousemove', handlerFunctions.mouseMove, false);
if (navigator.userAgent.toLowerCase().indexOf('webkit') >= 0 ||
navigator.userAgent.toLowerCase().indexOf('trident') >= 0) {
svgElement.removeEventListener('mousewheel', handlerFunctions.mouseWheel, false);
}
else {
svgElement.removeEventListener('DOMMouseScroll', handlerFunctions.mouseWheel, false);
}
}
};
/* our global zpd function */
var zpd = function (options, callbackFunc) {
// get a reference to the current element
var self = this;
// define some custom options
var zpdOptions = {
pan: true, // enable or disable panning (default enabled)
panDirections: 'both', // "both" | "horizontal" | "vertical"
zoom: true, // enable or disable zooming (default enabled)
drag: false, // enable or disable dragging (default disabled)
zoomScale: 0.2, // define zoom sensitivity
zoomThreshold: null, // define zoom threshold
touch: true, // enable or disable touch (default enabled)
preventDefaultEvent: { // enable or disable preventDefault call in events (default enabled) WARNING may have unwanted effect
handleMouseOrTouchUp: true,
handleMouseOrTouchDown: true,
handleMouseMove: true,
handleMouseWheel: true,
handleTouchMove: true
}
};
// the situation event of zpd, may be init, reinit, destroy, save, origin, toggle
var situation,
situationState = {
init: 'init',
reinit: 'reinit',
destroy: 'destroy',
save: 'save',
origin: 'origin',
callback: 'callback',
toggle: 'toggle'
};
var zpdElement = null;
// it is also possible to only specify a callback function without any options
if (typeof options === 'function') {
callbackFunc = options;
situation = situationState.callback;
}
// check if element was already initialized
if (snapsvgzpd.dataStore.hasOwnProperty(self.id)) {
// return existing element
zpdElement = snapsvgzpd.dataStore[self.id];
// adapt the stored options, with the options passed in
if (typeof options === 'object') {
if (options.preventDefaultEvent !== undefined && typeof options.preventDefaultEvent !== 'object') {
options.preventDefaultEvent = {
handleMouseOrTouchUp: !!options.preventDefaultEvent,
handleMouseOrTouchDown: !!options.preventDefaultEvent,
handleMouseMove: !!options.preventDefaultEvent,
handleMouseWheel: !!options.preventDefaultEvent,
handleTouchMove: !!options.preventDefaultEvent
}
}
for (var prop in options) {
if (typeof options[prop] === 'object') {
if (typeof zpdElement.options[prop] !== 'object') {
zpdElement.options[prop] = options[prop];
}
for (var subprop in options[prop]) {
zpdElement.options[prop][subprop] = options[prop][subprop];
}
} else {
zpdElement.options[prop] = options[prop];
}
}
situation = situationState.reinit;
} else if (typeof options === 'string') {
situation = options;
}
}
else {
// adapt the default options
if (typeof options === 'object') {
if (options.preventDefaultEvent !== undefined && typeof options.preventDefaultEvent !== 'object') {
options.preventDefaultEvent = {
handleMouseOrTouchUp: !!options.preventDefaultEvent,
handleMouseOrTouchDown: !!options.preventDefaultEvent,
handleMouseMove: !!options.preventDefaultEvent,
handleMouseWheel: !!options.preventDefaultEvent,
handleTouchMove: !!options.preventDefaultEvent
}
}
for (var prop2 in options) {
if (typeof options[prop2] === 'object') {
if (typeof zpdOptions[prop2] !== 'object') {
zpdOptions[prop2] = options[prop2];
}
for (var subprop2 in options[prop2]) {
zpdOptions[prop2][subprop2] = options[prop2][subprop2];
}
} else {
zpdOptions[prop2] = options[prop2];
}
}
situation = situationState.init;
} else if (typeof options === 'string') {
situation = options;
}
// initialize a new element and save it to our global storage
zpdElement = _initZpdElement(self, zpdOptions);
// setup the handlers for our svg-canvas
_setupHandlers(self.node, zpdElement.handlerFunctions);
snapsvgzpd.dataStore[self.id] = zpdElement;
}
switch (situation) {
case situationState.init:
case situationState.reinit:
case situationState.callback:
// callback
if (callbackFunc) {
callbackFunc(null, zpdElement);
}
return;
case situationState.destroy:
// remove event handlers
_tearDownHandlers(self.node, zpdElement.handlerFunctions);
// remove our custom <g> element
_removeNodeKeepChildren(self.node.firstChild);
// remove the object from our internal storage
delete snapsvgzpd.dataStore[self.id];
// callback
if (callbackFunc) {
callbackFunc(null, zpdElement);
}
return; // exit all
case situationState.save:
var g = document.getElementById(snapsvgzpd.uniqueIdPrefix + self.id);
var returnValue = g.getCTM();
// callback
if (callbackFunc) {
callbackFunc(null, returnValue);
}
return returnValue;
case situationState.origin:
// back to origin location
self.zoomTo(1, 1000);
// callback
if (callbackFunc) {
callbackFunc(null, zpdElement);
}
return;
case situationState.toggle:
// toggle enabled
snapsvgzpd.enable = !snapsvgzpd.enable;
// callback
if (callbackFunc) {
callbackFunc(null, snapsvgzpd.enable);
}
return;
}
};
/**
* zoom element to a certain zoom factor
*/
var zoomTo = function (zoom, interval, ease, callbackFunction) {
if (zoom < 0 || typeof zoom !== 'number') {
console.error('zoomTo(arg) should be a number and greater than 0');
return;
}
if (typeof interval !== 'number') {
interval = 3000;
}
var self = this;
// check if we have this element in our zpd data storage
if (snapsvgzpd.dataStore.hasOwnProperty(self.id)) {
// get a reference to the element
var zpdElement = snapsvgzpd.dataStore[self.id].element;
var currentTransformMatrix = zpdElement.node.getTransformToElement(rootSvgObject);
var currentZoom = currentTransformMatrix.a;
var originX = currentTransformMatrix.e;
var originY = currentTransformMatrix.f;
var boundingBox = zpdElement.getBBox();
var deltaX = parseFloat(boundingBox.width) / 2.0;
var deltaY = parseFloat(boundingBox.height) / 2.0;
Snap.animate(currentZoom, zoom, function (value) {
// calculate difference of zooming value to initial zoom
var deltaZoom = value / currentZoom;
if (value !== currentZoom) {
// calculate new translation
currentTransformMatrix.e = originX - ((deltaX * deltaZoom - deltaX));
currentTransformMatrix.f = originY - ((deltaY * deltaZoom - deltaY));
// add new scaling
currentTransformMatrix.a = value;
currentTransformMatrix.d = value;
// apply transformation to our element
zpdElement.node.setAttribute('transform', _getSvgMatrixAsString(currentTransformMatrix));
}
}, interval, ease, callbackFunction);
}
};
/**
* move the element to a certain position
*/
var panTo = function (x, y, interval, ease, cb) {
// get a reference to the current element
var self = this;
// check if we have this element in our zpd data storage
if (snapsvgzpd.dataStore.hasOwnProperty(self.id)) {
var zpdElement = snapsvgzpd.dataStore[self.id].element;
var gMatrix = zpdElement.node.getCTM(),
matrixX = _increaseDecreaseOrNumber(gMatrix.e, x),
matrixY = _increaseDecreaseOrNumber(gMatrix.f, y),
matrixString = "matrix(" + gMatrix.a + "," + gMatrix.b + "," + gMatrix.c + "," + gMatrix.d + "," + matrixX + "," + matrixY + ")";
// dataStore[me.id].transform(matrixString); // load <g> transform matrix
zpdElement.animate({ transform: matrixString }, interval || 10, ease || null, function () {
if (cb) {
cb(null, zpdElement);
}
});
}
};
/**
* rotate the element to a certain rotation
*/
var rotate = function (a, x, y, interval, ease, cb) {
// get a reference to the current element
var self = this;
// check if we have this element in our zpd data storage
if (snapsvgzpd.dataStore.hasOwnProperty(self.id)) {
var zpdElement = snapsvgzpd.dataStore[self.id].element;
var gMatrix = zpdElement.node.getCTM(),
matrixString = "matrix(" + gMatrix.a + "," + gMatrix.b + "," + gMatrix.c + "," + gMatrix.d + "," + gMatrix.e + "," + gMatrix.f + ")";
if (!x || typeof x !== 'number') {
x = self.node.offsetWidth / 2;
}
if (!y || typeof y !== 'number') {
y = self.node.offsetHeight / 2;
}
// dataStore[me.id].transform(matrixString); // load <g> transform matrix
zpdElement.animate({ transform: new Snap.Matrix(gMatrix).rotate(a, x, y) }, interval || 10, ease || null, function () {
if (cb) {
cb(null, zpdElement);
}
});
}
};
Paper.prototype.zpd = zpd;
Paper.prototype.zoomTo = zoomTo;
Paper.prototype.panTo = panTo;
Paper.prototype.rotate = rotate;
/** More Features to add (click event) help me if you can **/
// Element.prototype.panToCenter = panToCenter; // arg (ease, interval, cb)
/** UI for zpdr **/
});
})(Snap);