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