1/** 2 * SVGPan library 1.2.2 3 * ====================== 4 * 5 * Given an unique existing element with id "viewport" (or when missing, the 6 * first g-element), including the the library into any SVG adds the following 7 * capabilities: 8 * 9 * - Mouse panning 10 * - Mouse zooming (using the wheel) 11 * - Object dragging 12 * 13 * You can configure the behaviour of the pan/zoom/drag with the variables 14 * listed in the CONFIGURATION section of this file. 15 * 16 * This code is licensed under the following BSD license: 17 * 18 * Copyright 2009-2019 Andrea Leofreddi <[email protected]>. All rights reserved. 19 * 20 * Redistribution and use in source and binary forms, with or without modification, are 21 * permitted provided that the following conditions are met: 22 * 23 * 1. Redistributions of source code must retain the above copyright 24 * notice, this list of conditions and the following disclaimer. 25 * 2. Redistributions in binary form must reproduce the above copyright 26 * notice, this list of conditions and the following disclaimer in the 27 * documentation and/or other materials provided with the distribution. 28 * 3. Neither the name of the copyright holder nor the names of its 29 * contributors may be used to endorse or promote products derived from 30 * this software without specific prior written permission. 31 * 32 * THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS 33 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 34 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDERS OR 35 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 36 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 37 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 38 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 39 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 40 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 41 * 42 * The views and conclusions contained in the software and documentation are those of the 43 * authors and should not be interpreted as representing official policies, either expressed 44 * or implied, of Andrea Leofreddi. 45 */ 46 47"use strict"; 48 49/// CONFIGURATION 50/// ====> 51 52var enablePan = 1; // 1 or 0: enable or disable panning (default enabled) 53var enableZoom = 1; // 1 or 0: enable or disable zooming (default enabled) 54var enableDrag = 0; // 1 or 0: enable or disable dragging (default disabled) 55var zoomScale = 0.2; // Zoom sensitivity 56 57/// <==== 58/// END OF CONFIGURATION 59 60var root = document.documentElement; 61var state = 'none', svgRoot = null, stateTarget, stateOrigin, stateTf; 62 63setupHandlers(root); 64 65/** 66 * Register handlers 67 */ 68function setupHandlers(root){ 69 setAttributes(root, { 70 "onmouseup" : "handleMouseUp(evt)", 71 "onmousedown" : "handleMouseDown(evt)", 72 "onmousemove" : "handleMouseMove(evt)", 73 //"onmouseout" : "handleMouseUp(evt)", // Decomment this to stop the pan functionality when dragging out of the SVG element 74 }); 75 76 if(navigator.userAgent.toLowerCase().indexOf('webkit') >= 0) 77 window.addEventListener('mousewheel', handleMouseWheel, false); // Chrome/Safari 78 else 79 window.addEventListener('DOMMouseScroll', handleMouseWheel, false); // Others 80} 81 82/** 83 * Retrieves the root element for SVG manipulation. The element is then cached into the svgRoot global variable. 84 */ 85function getRoot(root) { 86 if(svgRoot == null) { 87 var r = root.getElementById("viewport") ? root.getElementById("viewport") : root.documentElement, t = r; 88 89 while(t != root) { 90 if(t.getAttribute("viewBox")) { 91 setCTM(r, t.getCTM()); 92 93 t.removeAttribute("viewBox"); 94 } 95 96 t = t.parentNode; 97 } 98 99 svgRoot = r; 100 } 101 102 return svgRoot; 103} 104 105/** 106 * Instance an SVGPoint object with given event coordinates. 107 */ 108function getEventPoint(evt) { 109 var p = root.createSVGPoint(); 110 111 p.x = evt.clientX; 112 p.y = evt.clientY; 113 114 return p; 115} 116 117/** 118 * Sets the current transform matrix of an element. 119 */ 120function setCTM(element, matrix) { 121 var s = "matrix(" + matrix.a + "," + matrix.b + "," + matrix.c + "," + matrix.d + "," + matrix.e + "," + matrix.f + ")"; 122 123 element.setAttribute("transform", s); 124} 125 126/** 127 * Dumps a matrix to a string (useful for debug). 128 */ 129function dumpMatrix(matrix) { 130 var s = "[ " + matrix.a + ", " + matrix.c + ", " + matrix.e + "\n " + matrix.b + ", " + matrix.d + ", " + matrix.f + "\n 0, 0, 1 ]"; 131 132 return s; 133} 134 135/** 136 * Sets attributes of an element. 137 */ 138function setAttributes(element, attributes){ 139 for (var i in attributes) 140 element.setAttributeNS(null, i, attributes[i]); 141} 142 143/** 144 * Handle mouse wheel event. 145 */ 146function handleMouseWheel(evt) { 147 if(!enableZoom) 148 return; 149 150 if(evt.preventDefault) 151 evt.preventDefault(); 152 153 evt.returnValue = false; 154 155 var svgDoc = evt.target.ownerDocument; 156 157 var delta; 158 159 if(evt.wheelDelta) 160 delta = evt.wheelDelta / 360; // Chrome/Safari 161 else 162 delta = evt.detail / -9; // Mozilla 163 164 var z = Math.pow(1 + zoomScale, delta); 165 166 var g = getRoot(svgDoc); 167 168 var p = getEventPoint(evt); 169 170 p = p.matrixTransform(g.getCTM().inverse()); 171 172 // Compute new scale matrix in current mouse position 173 var k = root.createSVGMatrix().translate(p.x, p.y).scale(z).translate(-p.x, -p.y); 174 175 setCTM(g, g.getCTM().multiply(k)); 176 177 if(typeof(stateTf) == "undefined") 178 stateTf = g.getCTM().inverse(); 179 180 stateTf = stateTf.multiply(k.inverse()); 181} 182 183/** 184 * Handle mouse move event. 185 */ 186function handleMouseMove(evt) { 187 if(evt.preventDefault) 188 evt.preventDefault(); 189 190 evt.returnValue = false; 191 192 var svgDoc = evt.target.ownerDocument; 193 194 var g = getRoot(svgDoc); 195 196 if(state == 'pan' && enablePan) { 197 // Pan mode 198 var p = getEventPoint(evt).matrixTransform(stateTf); 199 200 setCTM(g, stateTf.inverse().translate(p.x - stateOrigin.x, p.y - stateOrigin.y)); 201 } else if(state == 'drag' && enableDrag) { 202 // Drag mode 203 var p = getEventPoint(evt).matrixTransform(g.getCTM().inverse()); 204 205 setCTM(stateTarget, root.createSVGMatrix().translate(p.x - stateOrigin.x, p.y - stateOrigin.y).multiply(g.getCTM().inverse()).multiply(stateTarget.getCTM())); 206 207 stateOrigin = p; 208 } 209} 210 211/** 212 * Handle click event. 213 */ 214function handleMouseDown(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( 225 evt.target.tagName == "svg" 226 || !enableDrag // Pan anyway when drag is disabled and the user clicked on an element 227 ) { 228 // Pan mode 229 state = 'pan'; 230 231 stateTf = g.getCTM().inverse(); 232 233 stateOrigin = getEventPoint(evt).matrixTransform(stateTf); 234 } else { 235 // Drag mode 236 state = 'drag'; 237 238 stateTarget = evt.target; 239 240 stateTf = g.getCTM().inverse(); 241 242 stateOrigin = getEventPoint(evt).matrixTransform(stateTf); 243 } 244} 245 246/** 247 * Handle mouse button release event. 248 */ 249function handleMouseUp(evt) { 250 if(evt.preventDefault) 251 evt.preventDefault(); 252 253 evt.returnValue = false; 254 255 var svgDoc = evt.target.ownerDocument; 256 257 if(state == 'pan' || state == 'drag') { 258 // Quit pan mode 259 state = ''; 260 } 261} 262