Home | History | Annotate | Download | only in svg
      1 // SVG pan and zoom library.
      2 // See copyright notice in string constant below.
      3 
      4 package svg
      5 
      6 // https://www.cyberz.org/projects/SVGPan/SVGPan.js
      7 
      8 const svgPanJS = `
      9 /** 
     10  *  SVGPan library 1.2.1
     11  * ======================
     12  *
     13  * Given an unique existing element with id "viewport" (or when missing, the first g 
     14  * element), including the the library into any SVG adds the following capabilities:
     15  *
     16  *  - Mouse panning
     17  *  - Mouse zooming (using the wheel)
     18  *  - Object dragging
     19  *
     20  * You can configure the behavior of the pan/zoom/drag with the variables
     21  * listed in the CONFIGURATION section of this file.
     22  *
     23  * Known issues:
     24  *
     25  *  - Zooming (while panning) on Safari has still some issues
     26  *
     27  * Releases:
     28  *
     29  * 1.2.1, Mon Jul  4 00:33:18 CEST 2011, Andrea Leofreddi
     30  *	- Fixed a regression with mouse wheel (now working on Firefox 5)
     31  *	- Working with viewBox attribute (#4)
     32  *	- Added "use strict;" and fixed resulting warnings (#5)
     33  *	- Added configuration variables, dragging is disabled by default (#3)
     34  *
     35  * 1.2, Sat Mar 20 08:42:50 GMT 2010, Zeng Xiaohui
     36  *	Fixed a bug with browser mouse handler interaction
     37  *
     38  * 1.1, Wed Feb  3 17:39:33 GMT 2010, Zeng Xiaohui
     39  *	Updated the zoom code to support the mouse wheel on Safari/Chrome
     40  *
     41  * 1.0, Andrea Leofreddi
     42  *	First release
     43  *
     44  * This code is licensed under the following BSD license:
     45  *
     46  * Copyright 2009-2010 Andrea Leofreddi <a.leofreddi (a] itcharm.com>. All rights reserved.
     47  * 
     48  * Redistribution and use in source and binary forms, with or without modification, are
     49  * permitted provided that the following conditions are met:
     50  * 
     51  *    1. Redistributions of source code must retain the above copyright notice, this list of
     52  *       conditions and the following disclaimer.
     53  * 
     54  *    2. Redistributions in binary form must reproduce the above copyright notice, this list
     55  *       of conditions and the following disclaimer in the documentation and/or other materials
     56  *       provided with the distribution.
     57  * 
     58  * THIS SOFTWARE IS PROVIDED BY Andrea Leofreddi ` + "``AS IS''" + ` AND ANY EXPRESS OR IMPLIED
     59  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
     60  * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Andrea Leofreddi OR
     61  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     62  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
     63  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
     64  * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
     65  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
     66  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     67  * 
     68  * The views and conclusions contained in the software and documentation are those of the
     69  * authors and should not be interpreted as representing official policies, either expressed
     70  * or implied, of Andrea Leofreddi.
     71  */
     72 
     73 "use strict";
     74 
     75 /// CONFIGURATION 
     76 /// ====>
     77 
     78 var enablePan = 1; // 1 or 0: enable or disable panning (default enabled)
     79 var enableZoom = 1; // 1 or 0: enable or disable zooming (default enabled)
     80 var enableDrag = 0; // 1 or 0: enable or disable dragging (default disabled)
     81 
     82 /// <====
     83 /// END OF CONFIGURATION 
     84 
     85 var root = document.documentElement;
     86 
     87 var state = 'none', svgRoot, stateTarget, stateOrigin, stateTf;
     88 
     89 setupHandlers(root);
     90 
     91 /**
     92  * Register handlers
     93  */
     94 function setupHandlers(root){
     95 	setAttributes(root, {
     96 		"onmouseup" : "handleMouseUp(evt)",
     97 		"onmousedown" : "handleMouseDown(evt)",
     98 		"onmousemove" : "handleMouseMove(evt)",
     99 		//"onmouseout" : "handleMouseUp(evt)", // Decomment this to stop the pan functionality when dragging out of the SVG element
    100 	});
    101 
    102 	if(navigator.userAgent.toLowerCase().indexOf('webkit') >= 0)
    103 		window.addEventListener('mousewheel', handleMouseWheel, false); // Chrome/Safari
    104 	else
    105 		window.addEventListener('DOMMouseScroll', handleMouseWheel, false); // Others
    106 }
    107 
    108 /**
    109  * Retrieves the root element for SVG manipulation. The element is then cached into the svgRoot global variable.
    110  */
    111 function getRoot(root) {
    112 	if(typeof(svgRoot) == "undefined") {
    113 		var g = null;
    114 
    115 		g = root.getElementById("viewport");
    116 
    117 		if(g == null)
    118 			g = root.getElementsByTagName('g')[0];
    119 
    120 		if(g == null)
    121 			alert('Unable to obtain SVG root element');
    122 
    123 		setCTM(g, g.getCTM());
    124 
    125 		g.removeAttribute("viewBox");
    126 
    127 		svgRoot = g;
    128 	}
    129 
    130 	return svgRoot;
    131 }
    132 
    133 /**
    134  * Instance an SVGPoint object with given event coordinates.
    135  */
    136 function getEventPoint(evt) {
    137 	var p = root.createSVGPoint();
    138 
    139 	p.x = evt.clientX;
    140 	p.y = evt.clientY;
    141 
    142 	return p;
    143 }
    144 
    145 /**
    146  * Sets the current transform matrix of an element.
    147  */
    148 function setCTM(element, matrix) {
    149 	var s = "matrix(" + matrix.a + "," + matrix.b + "," + matrix.c + "," + matrix.d + "," + matrix.e + "," + matrix.f + ")";
    150 
    151 	element.setAttribute("transform", s);
    152 }
    153 
    154 /**
    155  * Dumps a matrix to a string (useful for debug).
    156  */
    157 function dumpMatrix(matrix) {
    158 	var s = "[ " + matrix.a + ", " + matrix.c + ", " + matrix.e + "\n  " + matrix.b + ", " + matrix.d + ", " + matrix.f + "\n  0, 0, 1 ]";
    159 
    160 	return s;
    161 }
    162 
    163 /**
    164  * Sets attributes of an element.
    165  */
    166 function setAttributes(element, attributes){
    167 	for (var i in attributes)
    168 		element.setAttributeNS(null, i, attributes[i]);
    169 }
    170 
    171 /**
    172  * Handle mouse wheel event.
    173  */
    174 function handleMouseWheel(evt) {
    175 	if(!enableZoom)
    176 		return;
    177 
    178 	if(evt.preventDefault)
    179 		evt.preventDefault();
    180 
    181 	evt.returnValue = false;
    182 
    183 	var svgDoc = evt.target.ownerDocument;
    184 
    185 	var delta;
    186 
    187 	if(evt.wheelDelta)
    188 		delta = evt.wheelDelta / 3600; // Chrome/Safari
    189 	else
    190 		delta = evt.detail / -90; // Mozilla
    191 
    192 	var z = 1 + delta; // Zoom factor: 0.9/1.1
    193 
    194 	var g = getRoot(svgDoc);
    195 	
    196 	var p = getEventPoint(evt);
    197 
    198 	p = p.matrixTransform(g.getCTM().inverse());
    199 
    200 	// Compute new scale matrix in current mouse position
    201 	var k = root.createSVGMatrix().translate(p.x, p.y).scale(z).translate(-p.x, -p.y);
    202 
    203         setCTM(g, g.getCTM().multiply(k));
    204 
    205 	if(typeof(stateTf) == "undefined")
    206 		stateTf = g.getCTM().inverse();
    207 
    208 	stateTf = stateTf.multiply(k.inverse());
    209 }
    210 
    211 /**
    212  * Handle mouse move event.
    213  */
    214 function handleMouseMove(evt) {
    215 	if(evt.preventDefault)
    216 		evt.preventDefault();
    217 
    218 	evt.returnValue = false;
    219 
    220 	var svgDoc = evt.target.ownerDocument;
    221 
    222 	var g = getRoot(svgDoc);
    223 
    224 	if(state == 'pan' && enablePan) {
    225 		// Pan mode
    226 		var p = getEventPoint(evt).matrixTransform(stateTf);
    227 
    228 		setCTM(g, stateTf.inverse().translate(p.x - stateOrigin.x, p.y - stateOrigin.y));
    229 	} else if(state == 'drag' && enableDrag) {
    230 		// Drag mode
    231 		var p = getEventPoint(evt).matrixTransform(g.getCTM().inverse());
    232 
    233 		setCTM(stateTarget, root.createSVGMatrix().translate(p.x - stateOrigin.x, p.y - stateOrigin.y).multiply(g.getCTM().inverse()).multiply(stateTarget.getCTM()));
    234 
    235 		stateOrigin = p;
    236 	}
    237 }
    238 
    239 /**
    240  * Handle click event.
    241  */
    242 function handleMouseDown(evt) {
    243 	if(evt.preventDefault)
    244 		evt.preventDefault();
    245 
    246 	evt.returnValue = false;
    247 
    248 	var svgDoc = evt.target.ownerDocument;
    249 
    250 	var g = getRoot(svgDoc);
    251 
    252 	if(
    253 		evt.target.tagName == "svg" 
    254 		|| !enableDrag // Pan anyway when drag is disabled and the user clicked on an element 
    255 	) {
    256 		// Pan mode
    257 		state = 'pan';
    258 
    259 		stateTf = g.getCTM().inverse();
    260 
    261 		stateOrigin = getEventPoint(evt).matrixTransform(stateTf);
    262 	} else {
    263 		// Drag mode
    264 		state = 'drag';
    265 
    266 		stateTarget = evt.target;
    267 
    268 		stateTf = g.getCTM().inverse();
    269 
    270 		stateOrigin = getEventPoint(evt).matrixTransform(stateTf);
    271 	}
    272 }
    273 
    274 /**
    275  * Handle mouse button release event.
    276  */
    277 function handleMouseUp(evt) {
    278 	if(evt.preventDefault)
    279 		evt.preventDefault();
    280 
    281 	evt.returnValue = false;
    282 
    283 	var svgDoc = evt.target.ownerDocument;
    284 
    285 	if(state == 'pan' || state == 'drag') {
    286 		// Quit pan mode
    287 		state = '';
    288 	}
    289 }
    290 
    291 `
    292