vendor: 4,104 bytes, lines 1-74
1// svg-pan-zoom v3.6.1 2// https://github.com/ariutta/svg-pan-zoom 3(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){ 4 var SvgUtils = require("./svg-utilities"); 5 6 module.exports = { 7 enable: function(instance) { 8 // Select (and create if necessary) defs 9 var defs = instance.svg.querySelector("defs"); 10 if (!defs) { 11 defs = document.createElementNS(SvgUtils.svgNS, "defs"); 12 instance.svg.appendChild(defs); 13 } 14 15 // Check for style element, and create it if it doesn't exist 16 var styleEl = defs.querySelector("style#svg-pan-zoom-controls-styles"); 17 if (!styleEl) { 18 var style = document.createElementNS(SvgUtils.svgNS, "style"); 19 style.setAttribute("id", "svg-pan-zoom-controls-styles"); 20 style.setAttribute("type", "text/css"); 21 style.textContent = 22 ".svg-pan-zoom-control { cursor: pointer; fill: black; fill-opacity: 0.333; } .svg-pan-zoom-control:hover { fill-opacity: 0.8; } .svg-pan-zoom-control-background { fill: white; fill-opacity: 0.5; } .svg-pan-zoom-control-background { fill-opacity: 0.8; }"; 23 defs.appendChild(style); 24 } 25 26 // Zoom Group 27 var zoomGroup = document.createElementNS(SvgUtils.svgNS, "g"); 28 zoomGroup.setAttribute("id", "svg-pan-zoom-controls"); 29 zoomGroup.setAttribute( 30 "transform", 31 "translate(" + 32 (instance.width - 70) + 33 " " + 34 (instance.height - 76) + 35 ") scale(0.75)" 36 ); 37 zoomGroup.setAttribute("class", "svg-pan-zoom-control"); 38 39 // Control elements 40 zoomGroup.appendChild(this._createZoomIn(instance)); 41 zoomGroup.appendChild(this._createZoomReset(instance)); 42 zoomGroup.appendChild(this._createZoomOut(instance)); 43 44 // Finally append created element 45 instance.svg.appendChild(zoomGroup); 46 47 // Cache control instance 48 instance.controlIcons = zoomGroup; 49 }, 50 51 _createZoomIn: function(instance) { 52 var zoomIn = document.createElementNS(SvgUtils.svgNS, "g"); 53 zoomIn.setAttribute("id", "svg-pan-zoom-zoom-in"); 54 zoomIn.setAttribute("transform", "translate(30.5 5) scale(0.015)"); 55 zoomIn.setAttribute("class", "svg-pan-zoom-control"); 56 zoomIn.addEventListener( 57 "click", 58 function() { 59 instance.getPublicInstance().zoomIn(); 60 }, 61 false 62 ); 63 zoomIn.addEventListener( 64 "touchstart", 65 function() { 66 instance.getPublicInstance().zoomIn(); 67 }, 68 false 69 ); 70 71 var zoomInBackground = document.createElementNS(SvgUtils.svgNS, "rect"); // TODO change these background space fillers to rounded rectangles so they look prettier 72 zoomInBackground.setAttribute("x", "0"); 73 zoomInBackground.setAttribute("y", "0"); 74 zoomInBackground.setAttribute("width", "1500"); // larger than expected because the whole group is transformed to scale down
75 zoomInBackground.setAttribute("height", "1400"); 76 zoomInBackground.setAttribute("class", "svg-pan-zoom-control-background"); 77 zoomIn.appendChild(zoomInBackground); 78 79 var zoomInShape = document.createElementNS(SvgUtils.svgNS, "path"); 80 zoomInShape.setAttribute( 81 "d", 82 "M1280 576v128q0 26 -19 45t-45 19h-320v320q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-320h-320q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h320v-320q0 -26 19 -45t45 -19h128q26 0 45 19t19 45v320h320q26 0 45 19t19 45zM1536 1120v-960 q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" 83 ); 84 zoomInShape.setAttribute("class", "svg-pan-zoom-control-element"); 85 zoomIn.appendChild(zoomInShape); 86 87 return zoomIn; 88 }, 89 90 _createZoomReset: function(instance) { 91 // reset 92 var resetPanZoomControl = document.createElementNS(SvgUtils.svgNS, "g"); 93 resetPanZoomControl.setAttribute("id", "svg-pan-zoom-reset-pan-zoom"); 94 resetPanZoomControl.setAttribute("transform", "translate(5 35) scale(0.4)"); 95 resetPanZoomControl.setAttribute("class", "svg-pan-zoom-control"); 96 resetPanZoomControl.addEventListener( 97 "click", 98 function() { 99 instance.getPublicInstance().reset(); 100 }, 101 false 102 ); 103 resetPanZoomControl.addEventListener( 104 "touchstart", 105 function() { 106 instance.getPublicInstance().reset(); 107 }, 108 false 109 ); 110 111 var resetPanZoomControlBackground = document.createElementNS( 112 SvgUtils.svgNS, 113 "rect" 114 ); // TODO change these background space fillers to rounded rectangles so they look prettier 115 resetPanZoomControlBackground.setAttribute("x", "2"); 116 resetPanZoomControlBackground.setAttribute("y", "2"); 117 resetPanZoomControlBackground.setAttribute("width", "182"); // larger than expected because the whole group is transformed to scale down 118 resetPanZoomControlBackground.setAttribute("height", "58"); 119 resetPanZoomControlBackground.setAttribute( 120 "class", 121 "svg-pan-zoom-control-background" 122 ); 123 resetPanZoomControl.appendChild(resetPanZoomControlBackground); 124 125 var resetPanZoomControlShape1 = document.createElementNS( 126 SvgUtils.svgNS, 127 "path" 128 ); 129 resetPanZoomControlShape1.setAttribute( 130 "d", 131 "M33.051,20.632c-0.742-0.406-1.854-0.609-3.338-0.609h-7.969v9.281h7.769c1.543,0,2.701-0.188,3.473-0.562c1.365-0.656,2.048-1.953,2.048-3.891C35.032,22.757,34.372,21.351,33.051,20.632z" 132 ); 133 resetPanZoomControlShape1.setAttribute( 134 "class", 135 "svg-pan-zoom-control-element" 136 ); 137 resetPanZoomControl.appendChild(resetPanZoomControlShape1); 138 139 var resetPanZoomControlShape2 = document.createElementNS( 140 SvgUtils.svgNS, 141 "path" 142 ); 143 resetPanZoomControlShape2.setAttribute( 144 "d", 145 "M170.231,0.5H15.847C7.102,0.5,0.5,5.708,0.5,11.84v38.861C0.5,56.833,7.102,61.5,15.847,61.5h154.384c8.745,0,15.269-4.667,15.269-10.798V11.84C185.5,5.708,178.976,0.5,170.231,0.5z M42.837,48.569h-7.969c-0.219-0.766-0.375-1.383-0.469-1.852c-0.188-0.969-0.289-1.961-0.305-2.977l-0.047-3.211c-0.03-2.203-0.41-3.672-1.142-4.406c-0.732-0.734-2.103-1.102-4.113-1.102h-7.05v13.547h-7.055V14.022h16.524c2.361,0.047,4.178,0.344,5.45,0.891c1.272,0.547,2.351,1.352,3.234,2.414c0.731,0.875,1.31,1.844,1.737,2.906s0.64,2.273,0.64,3.633c0,1.641-0.414,3.254-1.242,4.84s-2.195,2.707-4.102,3.363c1.594,0.641,2.723,1.551,3.387,2.73s0.996,2.98,0.996,5.402v2.32c0,1.578,0.063,2.648,0.19,3.211c0.19,0.891,0.635,1.547,1.333,1.969V48.569z M75.579,48.569h-26.18V14.022h25.336v6.117H56.454v7.336h16.781v6H56.454v8.883h19.125V48.569z M104.497,46.331c-2.44,2.086-5.887,3.129-10.34,3.129c-4.548,0-8.125-1.027-10.731-3.082s-3.909-4.879-3.909-8.473h6.891c0.224,1.578,0.662,2.758,1.316,3.539c1.196,1.422,3.246,2.133,6.15,2.133c1.739,0,3.151-0.188,4.236-0.562c2.058-0.719,3.087-2.055,3.087-4.008c0-1.141-0.504-2.023-1.512-2.648c-1.008-0.609-2.607-1.148-4.796-1.617l-3.74-0.82c-3.676-0.812-6.201-1.695-7.576-2.648c-2.328-1.594-3.492-4.086-3.492-7.477c0-3.094,1.139-5.664,3.417-7.711s5.623-3.07,10.036-3.07c3.685,0,6.829,0.965,9.431,2.895c2.602,1.93,3.966,4.73,4.093,8.402h-6.938c-0.128-2.078-1.057-3.555-2.787-4.43c-1.154-0.578-2.587-0.867-4.301-0.867c-1.907,0-3.428,0.375-4.565,1.125c-1.138,0.75-1.706,1.797-1.706,3.141c0,1.234,0.561,2.156,1.682,2.766c0.721,0.406,2.25,0.883,4.589,1.43l6.063,1.43c2.657,0.625,4.648,1.461,5.975,2.508c2.059,1.625,3.089,3.977,3.089,7.055C108.157,41.624,106.937,44.245,104.497,46.331z M139.61,48.569h-26.18V14.022h25.336v6.117h-18.281v7.336h16.781v6h-16.781v8.883h19.125V48.569z M170.337,20.14h-10.336v28.43h-7.266V20.14h-10.383v-6.117h27.984V20.14z" 146 ); 147 resetPanZoomControlShape2.setAttribute( 148 "class", 149 "svg-pan-zoom-control-element" 150 ); 151 resetPanZoomControl.appendChild(resetPanZoomControlShape2); 152 153 return resetPanZoomControl; 154 }, 155 156 _createZoomOut: function(instance) { 157 // zoom out 158 var zoomOut = document.createElementNS(SvgUtils.svgNS, "g"); 159 zoomOut.setAttribute("id", "svg-pan-zoom-zoom-out"); 160 zoomOut.setAttribute("transform", "translate(30.5 70) scale(0.015)"); 161 zoomOut.setAttribute("class", "svg-pan-zoom-control"); 162 zoomOut.addEventListener( 163 "click", 164 function() { 165 instance.getPublicInstance().zoomOut(); 166 }, 167 false 168 ); 169 zoomOut.addEventListener( 170 "touchstart", 171 function() { 172 instance.getPublicInstance().zoomOut(); 173 }, 174 false 175 ); 176
vendor: 80,175 bytes, lines 177-2242
177 var zoomOutBackground = document.createElementNS(SvgUtils.svgNS, "rect"); // TODO change these background space fillers to rounded rectangles so they look prettier 178 zoomOutBackground.setAttribute("x", "0"); 179 zoomOutBackground.setAttribute("y", "0"); 180 zoomOutBackground.setAttribute("width", "1500"); // larger than expected because the whole group is transformed to scale down 181 zoomOutBackground.setAttribute("height", "1400"); 182 zoomOutBackground.setAttribute("class", "svg-pan-zoom-control-background"); 183 zoomOut.appendChild(zoomOutBackground); 184 185 var zoomOutShape = document.createElementNS(SvgUtils.svgNS, "path"); 186 zoomOutShape.setAttribute( 187 "d", 188 "M1280 576v128q0 26 -19 45t-45 19h-896q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h896q26 0 45 19t19 45zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5 t84.5 -203.5z" 189 ); 190 zoomOutShape.setAttribute("class", "svg-pan-zoom-control-element"); 191 zoomOut.appendChild(zoomOutShape); 192 193 return zoomOut; 194 }, 195 196 disable: function(instance) { 197 if (instance.controlIcons) { 198 instance.controlIcons.parentNode.removeChild(instance.controlIcons); 199 instance.controlIcons = null; 200 } 201 } 202 }; 203 204 },{"./svg-utilities":5}],2:[function(require,module,exports){ 205 var SvgUtils = require("./svg-utilities"), 206 Utils = require("./utilities"); 207 208 var ShadowViewport = function(viewport, options) { 209 this.init(viewport, options); 210 }; 211 212 /** 213 * Initialization 214 * 215 * @param {SVGElement} viewport 216 * @param {Object} options 217 */ 218 ShadowViewport.prototype.init = function(viewport, options) { 219 // DOM Elements 220 this.viewport = viewport; 221 this.options = options; 222 223 // State cache 224 this.originalState = { zoom: 1, x: 0, y: 0 }; 225 this.activeState = { zoom: 1, x: 0, y: 0 }; 226 227 this.updateCTMCached = Utils.proxy(this.updateCTM, this); 228 229 // Create a custom requestAnimationFrame taking in account refreshRate 230 this.requestAnimationFrame = Utils.createRequestAnimationFrame( 231 this.options.refreshRate 232 ); 233 234 // ViewBox 235 this.viewBox = { x: 0, y: 0, width: 0, height: 0 }; 236 this.cacheViewBox(); 237 238 // Process CTM 239 var newCTM = this.processCTM(); 240 241 // Update viewport CTM and cache zoom and pan 242 this.setCTM(newCTM); 243 244 // Update CTM in this frame 245 this.updateCTM(); 246 }; 247 248 /** 249 * Cache initial viewBox value 250 * If no viewBox is defined, then use viewport size/position instead for viewBox values 251 */ 252 ShadowViewport.prototype.cacheViewBox = function() { 253 var svgViewBox = this.options.svg.getAttribute("viewBox"); 254 255 if (svgViewBox) { 256 var viewBoxValues = svgViewBox 257 .split(/[\s\,]/) 258 .filter(function(v) { 259 return v; 260 }) 261 .map(parseFloat); 262 263 // Cache viewbox x and y offset 264 this.viewBox.x = viewBoxValues[0]; 265 this.viewBox.y = viewBoxValues[1]; 266 this.viewBox.width = viewBoxValues[2]; 267 this.viewBox.height = viewBoxValues[3]; 268 269 var zoom = Math.min( 270 this.options.width / this.viewBox.width, 271 this.options.height / this.viewBox.height 272 ); 273 274 // Update active state 275 this.activeState.zoom = zoom; 276 this.activeState.x = (this.options.width - this.viewBox.width * zoom) / 2; 277 this.activeState.y = (this.options.height - this.viewBox.height * zoom) / 2; 278 279 // Force updating CTM 280 this.updateCTMOnNextFrame(); 281 282 this.options.svg.removeAttribute("viewBox"); 283 } else { 284 this.simpleViewBoxCache(); 285 } 286 }; 287 288 /** 289 * Recalculate viewport sizes and update viewBox cache 290 */ 291 ShadowViewport.prototype.simpleViewBoxCache = function() { 292 var bBox = this.viewport.getBBox(); 293 294 this.viewBox.x = bBox.x; 295 this.viewBox.y = bBox.y; 296 this.viewBox.width = bBox.width; 297 this.viewBox.height = bBox.height; 298 }; 299 300 /** 301 * Returns a viewbox object. Safe to alter 302 * 303 * @return {Object} viewbox object 304 */ 305 ShadowViewport.prototype.getViewBox = function() { 306 return Utils.extend({}, this.viewBox); 307 }; 308 309 /** 310 * Get initial zoom and pan values. Save them into originalState 311 * Parses viewBox attribute to alter initial sizes 312 * 313 * @return {CTM} CTM object based on options 314 */ 315 ShadowViewport.prototype.processCTM = function() { 316 var newCTM = this.getCTM(); 317 318 if (this.options.fit || this.options.contain) { 319 var newScale; 320 if (this.options.fit) { 321 newScale = Math.min( 322 this.options.width / this.viewBox.width, 323 this.options.height / this.viewBox.height 324 ); 325 } else { 326 newScale = Math.max( 327 this.options.width / this.viewBox.width, 328 this.options.height / this.viewBox.height 329 ); 330 } 331 332 newCTM.a = newScale; //x-scale 333 newCTM.d = newScale; //y-scale 334 newCTM.e = -this.viewBox.x * newScale; //x-transform 335 newCTM.f = -this.viewBox.y * newScale; //y-transform 336 } 337 338 if (this.options.center) { 339 var offsetX = 340 (this.options.width - 341 (this.viewBox.width + this.viewBox.x * 2) * newCTM.a) * 342 0.5, 343 offsetY = 344 (this.options.height - 345 (this.viewBox.height + this.viewBox.y * 2) * newCTM.a) * 346 0.5; 347 348 newCTM.e = offsetX; 349 newCTM.f = offsetY; 350 } 351 352 // Cache initial values. Based on activeState and fix+center opitons 353 this.originalState.zoom = newCTM.a; 354 this.originalState.x = newCTM.e; 355 this.originalState.y = newCTM.f; 356 357 return newCTM; 358 }; 359 360 /** 361 * Return originalState object. Safe to alter 362 * 363 * @return {Object} 364 */ 365 ShadowViewport.prototype.getOriginalState = function() { 366 return Utils.extend({}, this.originalState); 367 }; 368 369 /** 370 * Return actualState object. Safe to alter 371 * 372 * @return {Object} 373 */ 374 ShadowViewport.prototype.getState = function() { 375 return Utils.extend({}, this.activeState); 376 }; 377 378 /** 379 * Get zoom scale 380 * 381 * @return {Float} zoom scale 382 */ 383 ShadowViewport.prototype.getZoom = function() { 384 return this.activeState.zoom; 385 }; 386 387 /** 388 * Get zoom scale for pubilc usage 389 * 390 * @return {Float} zoom scale 391 */ 392 ShadowViewport.prototype.getRelativeZoom = function() { 393 return this.activeState.zoom / this.originalState.zoom; 394 }; 395 396 /** 397 * Compute zoom scale for pubilc usage 398 * 399 * @return {Float} zoom scale 400 */ 401 ShadowViewport.prototype.computeRelativeZoom = function(scale) { 402 return scale / this.originalState.zoom; 403 }; 404 405 /** 406 * Get pan 407 * 408 * @return {Object} 409 */ 410 ShadowViewport.prototype.getPan = function() { 411 return { x: this.activeState.x, y: this.activeState.y }; 412 }; 413 414 /** 415 * Return cached viewport CTM value that can be safely modified 416 * 417 * @return {SVGMatrix} 418 */ 419 ShadowViewport.prototype.getCTM = function() { 420 var safeCTM = this.options.svg.createSVGMatrix(); 421 422 // Copy values manually as in FF they are not itterable 423 safeCTM.a = this.activeState.zoom; 424 safeCTM.b = 0; 425 safeCTM.c = 0; 426 safeCTM.d = this.activeState.zoom; 427 safeCTM.e = this.activeState.x; 428 safeCTM.f = this.activeState.y; 429 430 return safeCTM; 431 }; 432 433 /** 434 * Set a new CTM 435 * 436 * @param {SVGMatrix} newCTM 437 */ 438 ShadowViewport.prototype.setCTM = function(newCTM) { 439 var willZoom = this.isZoomDifferent(newCTM), 440 willPan = this.isPanDifferent(newCTM); 441 442 if (willZoom || willPan) { 443 // Before zoom 444 if (willZoom) { 445 // If returns false then cancel zooming 446 if ( 447 this.options.beforeZoom( 448 this.getRelativeZoom(), 449 this.computeRelativeZoom(newCTM.a) 450 ) === false 451 ) { 452 newCTM.a = newCTM.d = this.activeState.zoom; 453 willZoom = false; 454 } else { 455 this.updateCache(newCTM); 456 this.options.onZoom(this.getRelativeZoom()); 457 } 458 } 459 460 // Before pan 461 if (willPan) { 462 var preventPan = this.options.beforePan(this.getPan(), { 463 x: newCTM.e, 464 y: newCTM.f 465 }), 466 // If prevent pan is an object 467 preventPanX = false, 468 preventPanY = false; 469 470 // If prevent pan is Boolean false 471 if (preventPan === false) { 472 // Set x and y same as before 473 newCTM.e = this.getPan().x; 474 newCTM.f = this.getPan().y; 475 476 preventPanX = preventPanY = true; 477 } else if (Utils.isObject(preventPan)) { 478 // Check for X axes attribute 479 if (preventPan.x === false) { 480 // Prevent panning on x axes 481 newCTM.e = this.getPan().x; 482 preventPanX = true; 483 } else if (Utils.isNumber(preventPan.x)) { 484 // Set a custom pan value 485 newCTM.e = preventPan.x; 486 } 487 488 // Check for Y axes attribute 489 if (preventPan.y === false) { 490 // Prevent panning on x axes 491 newCTM.f = this.getPan().y; 492 preventPanY = true; 493 } else if (Utils.isNumber(preventPan.y)) { 494 // Set a custom pan value 495 newCTM.f = preventPan.y; 496 } 497 } 498 499 // Update willPan flag 500 // Check if newCTM is still different 501 if ((preventPanX && preventPanY) || !this.isPanDifferent(newCTM)) { 502 willPan = false; 503 } else { 504 this.updateCache(newCTM); 505 this.options.onPan(this.getPan()); 506 } 507 } 508 509 // Check again if should zoom or pan 510 if (willZoom || willPan) { 511 this.updateCTMOnNextFrame(); 512 } 513 } 514 }; 515 516 ShadowViewport.prototype.isZoomDifferent = function(newCTM) { 517 return this.activeState.zoom !== newCTM.a; 518 }; 519 520 ShadowViewport.prototype.isPanDifferent = function(newCTM) { 521 return this.activeState.x !== newCTM.e || this.activeState.y !== newCTM.f; 522 }; 523 524 /** 525 * Update cached CTM and active state 526 * 527 * @param {SVGMatrix} newCTM 528 */ 529 ShadowViewport.prototype.updateCache = function(newCTM) { 530 this.activeState.zoom = newCTM.a; 531 this.activeState.x = newCTM.e; 532 this.activeState.y = newCTM.f; 533 }; 534 535 ShadowViewport.prototype.pendingUpdate = false; 536 537 /** 538 * Place a request to update CTM on next Frame 539 */ 540 ShadowViewport.prototype.updateCTMOnNextFrame = function() { 541 if (!this.pendingUpdate) { 542 // Lock 543 this.pendingUpdate = true; 544 545 // Throttle next update 546 this.requestAnimationFrame.call(window, this.updateCTMCached); 547 } 548 }; 549 550 /** 551 * Update viewport CTM with cached CTM 552 */ 553 ShadowViewport.prototype.updateCTM = function() { 554 var ctm = this.getCTM(); 555 556 // Updates SVG element 557 SvgUtils.setCTM(this.viewport, ctm, this.defs); 558 559 // Free the lock 560 this.pendingUpdate = false; 561 562 // Notify about the update 563 if (this.options.onUpdatedCTM) { 564 this.options.onUpdatedCTM(ctm); 565 } 566 }; 567 568 module.exports = function(viewport, options) { 569 return new ShadowViewport(viewport, options); 570 }; 571 572 },{"./svg-utilities":5,"./utilities":7}],3:[function(require,module,exports){ 573 var svgPanZoom = require("./svg-pan-zoom.js"); 574 575// UMD module definition 576 (function(window, document) { 577 // AMD 578 if (typeof define === "function" && define.amd) { 579 define("svg-pan-zoom", function() { 580 return svgPanZoom; 581 }); 582 // CMD 583 } else if (typeof module !== "undefined" && module.exports) { 584 module.exports = svgPanZoom; 585 586 // Browser 587 // Keep exporting globally as module.exports is available because of browserify 588 window.svgPanZoom = svgPanZoom; 589 } 590 })(window, document); 591 592 },{"./svg-pan-zoom.js":4}],4:[function(require,module,exports){ 593 var Wheel = require("./uniwheel"), 594 ControlIcons = require("./control-icons"), 595 Utils = require("./utilities"), 596 SvgUtils = require("./svg-utilities"), 597 ShadowViewport = require("./shadow-viewport"); 598 599 var SvgPanZoom = function(svg, options) { 600 this.init(svg, options); 601 }; 602 603 var optionsDefaults = { 604 viewportSelector: ".svg-pan-zoom_viewport", // Viewport selector. Can be querySelector string or SVGElement 605 panEnabled: true, // enable or disable panning (default enabled) 606 controlIconsEnabled: false, // insert icons to give user an option in addition to mouse events to control pan/zoom (default disabled) 607 zoomEnabled: true, // enable or disable zooming (default enabled) 608 dblClickZoomEnabled: true, // enable or disable zooming by double clicking (default enabled) 609 mouseWheelZoomEnabled: true, // enable or disable zooming by mouse wheel (default enabled) 610 preventMouseEventsDefault: true, // enable or disable preventDefault for mouse events 611 zoomScaleSensitivity: 0.1, // Zoom sensitivity 612 minZoom: 0.5, // Minimum Zoom level 613 maxZoom: 10, // Maximum Zoom level 614 fit: true, // enable or disable viewport fit in SVG (default true) 615 contain: false, // enable or disable viewport contain the svg (default false) 616 center: true, // enable or disable viewport centering in SVG (default true) 617 refreshRate: "auto", // Maximum number of frames per second (altering SVG's viewport) 618 beforeZoom: null, 619 onZoom: null, 620 beforePan: null, 621 onPan: null, 622 customEventsHandler: null, 623 eventsListenerElement: null, 624 onUpdatedCTM: null 625 }; 626 627 var passiveListenerOption = { passive: true }; 628 629 SvgPanZoom.prototype.init = function(svg, options) { 630 var that = this; 631 632 this.svg = svg; 633 this.defs = svg.querySelector("defs"); 634 635 // Add default attributes to SVG 636 SvgUtils.setupSvgAttributes(this.svg); 637 638 // Set options 639 this.options = Utils.extend(Utils.extend({}, optionsDefaults), options); 640 641 // Set default state 642 this.state = "none"; 643 644 // Get dimensions 645 var boundingClientRectNormalized = SvgUtils.getBoundingClientRectNormalized( 646 svg 647 ); 648 this.width = boundingClientRectNormalized.width; 649 this.height = boundingClientRectNormalized.height; 650 651 // Init shadow viewport 652 this.viewport = ShadowViewport( 653 SvgUtils.getOrCreateViewport(this.svg, this.options.viewportSelector), 654 { 655 svg: this.svg, 656 width: this.width, 657 height: this.height, 658 fit: this.options.fit, 659 contain: this.options.contain, 660 center: this.options.center, 661 refreshRate: this.options.refreshRate, 662 // Put callbacks into functions as they can change through time 663 beforeZoom: function(oldScale, newScale) { 664 if (that.viewport && that.options.beforeZoom) { 665 return that.options.beforeZoom(oldScale, newScale); 666 } 667 }, 668 onZoom: function(scale) { 669 if (that.viewport && that.options.onZoom) { 670 return that.options.onZoom(scale); 671 } 672 }, 673 beforePan: function(oldPoint, newPoint) { 674 if (that.viewport && that.options.beforePan) { 675 return that.options.beforePan(oldPoint, newPoint); 676 } 677 }, 678 onPan: function(point) { 679 if (that.viewport && that.options.onPan) { 680 return that.options.onPan(point); 681 } 682 }, 683 onUpdatedCTM: function(ctm) { 684 if (that.viewport && that.options.onUpdatedCTM) { 685 return that.options.onUpdatedCTM(ctm); 686 } 687 } 688 } 689 ); 690 691 // Wrap callbacks into public API context 692 var publicInstance = this.getPublicInstance(); 693 publicInstance.setBeforeZoom(this.options.beforeZoom); 694 publicInstance.setOnZoom(this.options.onZoom); 695 publicInstance.setBeforePan(this.options.beforePan); 696 publicInstance.setOnPan(this.options.onPan); 697 publicInstance.setOnUpdatedCTM(this.options.onUpdatedCTM); 698 699 if (this.options.controlIconsEnabled) { 700 ControlIcons.enable(this); 701 } 702 703 // Init events handlers 704 this.lastMouseWheelEventTime = Date.now(); 705 this.setupHandlers(); 706 }; 707 708 /** 709 * Register event handlers 710 */ 711 SvgPanZoom.prototype.setupHandlers = function() { 712 var that = this, 713 prevEvt = null; // use for touchstart event to detect double tap 714 715 this.eventListeners = { 716 // Mouse down group 717 mousedown: function(evt) { 718 var result = that.handleMouseDown(evt, prevEvt); 719 prevEvt = evt; 720 return result; 721 }, 722 touchstart: function(evt) { 723 var result = that.handleMouseDown(evt, prevEvt); 724 prevEvt = evt; 725 return result; 726 }, 727 728 // Mouse up group 729 mouseup: function(evt) { 730 return that.handleMouseUp(evt); 731 }, 732 touchend: function(evt) { 733 return that.handleMouseUp(evt); 734 }, 735 736 // Mouse move group 737 mousemove: function(evt) { 738 return that.handleMouseMove(evt); 739 }, 740 touchmove: function(evt) { 741 return that.handleMouseMove(evt); 742 }, 743 744 // Mouse leave group 745 mouseleave: function(evt) { 746 return that.handleMouseUp(evt); 747 }, 748 touchleave: function(evt) { 749 return that.handleMouseUp(evt); 750 }, 751 touchcancel: function(evt) { 752 return that.handleMouseUp(evt); 753 } 754 }; 755 756 // Init custom events handler if available 757 // eslint-disable-next-line eqeqeq 758 if (this.options.customEventsHandler != null) { 759 this.options.customEventsHandler.init({ 760 svgElement: this.svg, 761 eventsListenerElement: this.options.eventsListenerElement, 762 instance: this.getPublicInstance() 763 }); 764 765 // Custom event handler may halt builtin listeners 766 var haltEventListeners = this.options.customEventsHandler 767 .haltEventListeners; 768 if (haltEventListeners && haltEventListeners.length) { 769 for (var i = haltEventListeners.length - 1; i >= 0; i--) { 770 if (this.eventListeners.hasOwnProperty(haltEventListeners[i])) { 771 delete this.eventListeners[haltEventListeners[i]]; 772 } 773 } 774 } 775 } 776 777 // Bind eventListeners 778 for (var event in this.eventListeners) { 779 // Attach event to eventsListenerElement or SVG if not available 780 (this.options.eventsListenerElement || this.svg).addEventListener( 781 event, 782 this.eventListeners[event], 783 !this.options.preventMouseEventsDefault ? passiveListenerOption : false 784 ); 785 } 786 787 // Zoom using mouse wheel 788 if (this.options.mouseWheelZoomEnabled) { 789 this.options.mouseWheelZoomEnabled = false; // set to false as enable will set it back to true 790 this.enableMouseWheelZoom(); 791 } 792 }; 793 794 /** 795 * Enable ability to zoom using mouse wheel 796 */ 797 SvgPanZoom.prototype.enableMouseWheelZoom = function() { 798 if (!this.options.mouseWheelZoomEnabled) { 799 var that = this; 800 801 // Mouse wheel listener 802 this.wheelListener = function(evt) { 803 return that.handleMouseWheel(evt); 804 }; 805 806 // Bind wheelListener 807 var isPassiveListener = !this.options.preventMouseEventsDefault; 808 Wheel.on( 809 this.options.eventsListenerElement || this.svg, 810 this.wheelListener, 811 isPassiveListener 812 ); 813 814 this.options.mouseWheelZoomEnabled = true; 815 } 816 }; 817 818 /** 819 * Disable ability to zoom using mouse wheel 820 */ 821 SvgPanZoom.prototype.disableMouseWheelZoom = function() { 822 if (this.options.mouseWheelZoomEnabled) { 823 var isPassiveListener = !this.options.preventMouseEventsDefault; 824 Wheel.off( 825 this.options.eventsListenerElement || this.svg, 826 this.wheelListener, 827 isPassiveListener 828 ); 829 this.options.mouseWheelZoomEnabled = false; 830 } 831 }; 832 833 /** 834 * Handle mouse wheel event 835 * 836 * @param {Event} evt 837 */ 838 SvgPanZoom.prototype.handleMouseWheel = function(evt) { 839 if (!this.options.zoomEnabled || this.state !== "none") { 840 return; 841 } 842 843 if (this.options.preventMouseEventsDefault) { 844 if (evt.preventDefault) { 845 evt.preventDefault(); 846 } else { 847 evt.returnValue = false; 848 } 849 } 850 851 // Default delta in case that deltaY is not available 852 var delta = evt.deltaY || 1, 853 timeDelta = Date.now() - this.lastMouseWheelEventTime, 854 divider = 3 + Math.max(0, 30 - timeDelta); 855 856 // Update cache 857 this.lastMouseWheelEventTime = Date.now(); 858 859 // Make empirical adjustments for browsers that give deltaY in pixels (deltaMode=0) 860 if ("deltaMode" in evt && evt.deltaMode === 0 && evt.wheelDelta) { 861 delta = evt.deltaY === 0 ? 0 : Math.abs(evt.wheelDelta) / evt.deltaY; 862 } 863 864 delta = 865 -0.3 < delta && delta < 0.3 866 ? delta 867 : ((delta > 0 ? 1 : -1) * Math.log(Math.abs(delta) + 10)) / divider; 868 869 var inversedScreenCTM = this.svg.getScreenCTM().inverse(), 870 relativeMousePoint = SvgUtils.getEventPoint(evt, this.svg).matrixTransform( 871 inversedScreenCTM 872 ), 873 zoom = Math.pow(1 + this.options.zoomScaleSensitivity, -1 * delta); // multiplying by neg. 1 so as to make zoom in/out behavior match Google maps behavior 874 875 this.zoomAtPoint(zoom, relativeMousePoint); 876 }; 877 878 /** 879 * Zoom in at a SVG point 880 * 881 * @param {SVGPoint} point 882 * @param {Float} zoomScale Number representing how much to zoom 883 * @param {Boolean} zoomAbsolute Default false. If true, zoomScale is treated as an absolute value. 884 * Otherwise, zoomScale is treated as a multiplied (e.g. 1.10 would zoom in 10%) 885 */ 886 SvgPanZoom.prototype.zoomAtPoint = function(zoomScale, point, zoomAbsolute) { 887 var originalState = this.viewport.getOriginalState(); 888 889 if (!zoomAbsolute) { 890 // Fit zoomScale in set bounds 891 if ( 892 this.getZoom() * zoomScale < 893 this.options.minZoom * originalState.zoom 894 ) { 895 zoomScale = (this.options.minZoom * originalState.zoom) / this.getZoom(); 896 } else if ( 897 this.getZoom() * zoomScale > 898 this.options.maxZoom * originalState.zoom 899 ) { 900 zoomScale = (this.options.maxZoom * originalState.zoom) / this.getZoom(); 901 } 902 } else { 903 // Fit zoomScale in set bounds 904 zoomScale = Math.max( 905 this.options.minZoom * originalState.zoom, 906 Math.min(this.options.maxZoom * originalState.zoom, zoomScale) 907 ); 908 // Find relative scale to achieve desired scale 909 zoomScale = zoomScale / this.getZoom(); 910 } 911 912 var oldCTM = this.viewport.getCTM(), 913 relativePoint = point.matrixTransform(oldCTM.inverse()), 914 modifier = this.svg 915 .createSVGMatrix() 916 .translate(relativePoint.x, relativePoint.y) 917 .scale(zoomScale) 918 .translate(-relativePoint.x, -relativePoint.y), 919 newCTM = oldCTM.multiply(modifier); 920 921 if (newCTM.a !== oldCTM.a) { 922 this.viewport.setCTM(newCTM); 923 } 924 }; 925 926 /** 927 * Zoom at center point 928 * 929 * @param {Float} scale 930 * @param {Boolean} absolute Marks zoom scale as relative or absolute 931 */ 932 SvgPanZoom.prototype.zoom = function(scale, absolute) { 933 this.zoomAtPoint( 934 scale, 935 SvgUtils.getSvgCenterPoint(this.svg, this.width, this.height), 936 absolute 937 ); 938 }; 939 940 /** 941 * Zoom used by public instance 942 * 943 * @param {Float} scale 944 * @param {Boolean} absolute Marks zoom scale as relative or absolute 945 */ 946 SvgPanZoom.prototype.publicZoom = function(scale, absolute) { 947 if (absolute) { 948 scale = this.computeFromRelativeZoom(scale); 949 } 950 951 this.zoom(scale, absolute); 952 }; 953 954 /** 955 * Zoom at point used by public instance 956 * 957 * @param {Float} scale 958 * @param {SVGPoint|Object} point An object that has x and y attributes 959 * @param {Boolean} absolute Marks zoom scale as relative or absolute 960 */ 961 SvgPanZoom.prototype.publicZoomAtPoint = function(scale, point, absolute) { 962 if (absolute) { 963 // Transform zoom into a relative value 964 scale = this.computeFromRelativeZoom(scale); 965 } 966 967 // If not a SVGPoint but has x and y then create a SVGPoint 968 if (Utils.getType(point) !== "SVGPoint") { 969 if ("x" in point && "y" in point) { 970 point = SvgUtils.createSVGPoint(this.svg, point.x, point.y); 971 } else { 972 throw new Error("Given point is invalid"); 973 } 974 } 975 976 this.zoomAtPoint(scale, point, absolute); 977 }; 978 979 /** 980 * Get zoom scale 981 * 982 * @return {Float} zoom scale 983 */ 984 SvgPanZoom.prototype.getZoom = function() { 985 return this.viewport.getZoom(); 986 }; 987 988 /** 989 * Get zoom scale for public usage 990 * 991 * @return {Float} zoom scale 992 */ 993 SvgPanZoom.prototype.getRelativeZoom = function() { 994 return this.viewport.getRelativeZoom(); 995 }; 996 997 /** 998 * Compute actual zoom from public zoom 999 * 1000 * @param {Float} zoom 1001 * @return {Float} zoom scale 1002 */ 1003 SvgPanZoom.prototype.computeFromRelativeZoom = function(zoom) { 1004 return zoom * this.viewport.getOriginalState().zoom; 1005 }; 1006 1007 /** 1008 * Set zoom to initial state 1009 */ 1010 SvgPanZoom.prototype.resetZoom = function() { 1011 var originalState = this.viewport.getOriginalState(); 1012 1013 this.zoom(originalState.zoom, true); 1014 }; 1015 1016 /** 1017 * Set pan to initial state 1018 */ 1019 SvgPanZoom.prototype.resetPan = function() { 1020 this.pan(this.viewport.getOriginalState()); 1021 }; 1022 1023 /** 1024 * Set pan and zoom to initial state 1025 */ 1026 SvgPanZoom.prototype.reset = function() { 1027 this.resetZoom(); 1028 this.resetPan(); 1029 }; 1030 1031 /** 1032 * Handle double click event 1033 * See handleMouseDown() for alternate detection method 1034 * 1035 * @param {Event} evt 1036 */ 1037 SvgPanZoom.prototype.handleDblClick = function(evt) { 1038 if (this.options.preventMouseEventsDefault) { 1039 if (evt.preventDefault) { 1040 evt.preventDefault(); 1041 } else { 1042 evt.returnValue = false; 1043 } 1044 } 1045 1046 // Check if target was a control button 1047 if (this.options.controlIconsEnabled) { 1048 var targetClass = evt.target.getAttribute("class") || ""; 1049 if (targetClass.indexOf("svg-pan-zoom-control") > -1) { 1050 return false; 1051 } 1052 } 1053 1054 var zoomFactor; 1055 1056 if (evt.shiftKey) { 1057 zoomFactor = 1 / ((1 + this.options.zoomScaleSensitivity) * 2); // zoom out when shift key pressed 1058 } else { 1059 zoomFactor = (1 + this.options.zoomScaleSensitivity) * 2; 1060 } 1061 1062 var point = SvgUtils.getEventPoint(evt, this.svg).matrixTransform( 1063 this.svg.getScreenCTM().inverse() 1064 ); 1065 this.zoomAtPoint(zoomFactor, point); 1066 }; 1067 1068 /** 1069 * Handle click event 1070 * 1071 * @param {Event} evt 1072 */ 1073 SvgPanZoom.prototype.handleMouseDown = function(evt, prevEvt) { 1074 if (this.options.preventMouseEventsDefault) { 1075 if (evt.preventDefault) { 1076 evt.preventDefault(); 1077 } else { 1078 evt.returnValue = false; 1079 } 1080 } 1081 1082 Utils.mouseAndTouchNormalize(evt, this.svg); 1083 1084 // Double click detection; more consistent than ondblclick 1085 if (this.options.dblClickZoomEnabled && Utils.isDblClick(evt, prevEvt)) { 1086 this.handleDblClick(evt); 1087 } else { 1088 // Pan mode 1089 this.state = "pan"; 1090 this.firstEventCTM = this.viewport.getCTM(); 1091 this.stateOrigin = SvgUtils.getEventPoint(evt, this.svg).matrixTransform( 1092 this.firstEventCTM.inverse() 1093 ); 1094 } 1095 }; 1096 1097 /** 1098 * Handle mouse move event 1099 * 1100 * @param {Event} evt 1101 */ 1102 SvgPanZoom.prototype.handleMouseMove = function(evt) { 1103 if (this.options.preventMouseEventsDefault) { 1104 if (evt.preventDefault) { 1105 evt.preventDefault(); 1106 } else { 1107 evt.returnValue = false; 1108 } 1109 } 1110 1111 if (this.state === "pan" && this.options.panEnabled) { 1112 // Pan mode 1113 var point = SvgUtils.getEventPoint(evt, this.svg).matrixTransform( 1114 this.firstEventCTM.inverse() 1115 ), 1116 viewportCTM = this.firstEventCTM.translate( 1117 point.x - this.stateOrigin.x, 1118 point.y - this.stateOrigin.y 1119 ); 1120 1121 this.viewport.setCTM(viewportCTM); 1122 } 1123 }; 1124 1125 /** 1126 * Handle mouse button release event 1127 * 1128 * @param {Event} evt 1129 */ 1130 SvgPanZoom.prototype.handleMouseUp = function(evt) { 1131 if (this.options.preventMouseEventsDefault) { 1132 if (evt.preventDefault) { 1133 evt.preventDefault(); 1134 } else { 1135 evt.returnValue = false; 1136 } 1137 } 1138 1139 if (this.state === "pan") { 1140 // Quit pan mode 1141 this.state = "none"; 1142 } 1143 }; 1144 1145 /** 1146 * Adjust viewport size (only) so it will fit in SVG 1147 * Does not center image 1148 */ 1149 SvgPanZoom.prototype.fit = function() { 1150 var viewBox = this.viewport.getViewBox(), 1151 newScale = Math.min( 1152 this.width / viewBox.width, 1153 this.height / viewBox.height 1154 ); 1155 1156 this.zoom(newScale, true); 1157 }; 1158 1159 /** 1160 * Adjust viewport size (only) so it will contain the SVG 1161 * Does not center image 1162 */ 1163 SvgPanZoom.prototype.contain = function() { 1164 var viewBox = this.viewport.getViewBox(), 1165 newScale = Math.max( 1166 this.width / viewBox.width, 1167 this.height / viewBox.height 1168 ); 1169 1170 this.zoom(newScale, true); 1171 }; 1172 1173 /** 1174 * Adjust viewport pan (only) so it will be centered in SVG 1175 * Does not zoom/fit/contain image 1176 */ 1177 SvgPanZoom.prototype.center = function() { 1178 var viewBox = this.viewport.getViewBox(), 1179 offsetX = 1180 (this.width - (viewBox.width + viewBox.x * 2) * this.getZoom()) * 0.5, 1181 offsetY = 1182 (this.height - (viewBox.height + viewBox.y * 2) * this.getZoom()) * 0.5; 1183 1184 this.getPublicInstance().pan({ x: offsetX, y: offsetY }); 1185 }; 1186 1187 /** 1188 * Update content cached BorderBox 1189 * Use when viewport contents change 1190 */ 1191 SvgPanZoom.prototype.updateBBox = function() { 1192 this.viewport.simpleViewBoxCache(); 1193 }; 1194 1195 /** 1196 * Pan to a rendered position 1197 * 1198 * @param {Object} point {x: 0, y: 0} 1199 */ 1200 SvgPanZoom.prototype.pan = function(point) { 1201 var viewportCTM = this.viewport.getCTM(); 1202 viewportCTM.e = point.x; 1203 viewportCTM.f = point.y; 1204 this.viewport.setCTM(viewportCTM); 1205 }; 1206 1207 /** 1208 * Relatively pan the graph by a specified rendered position vector 1209 * 1210 * @param {Object} point {x: 0, y: 0} 1211 */ 1212 SvgPanZoom.prototype.panBy = function(point) { 1213 var viewportCTM = this.viewport.getCTM(); 1214 viewportCTM.e += point.x; 1215 viewportCTM.f += point.y; 1216 this.viewport.setCTM(viewportCTM); 1217 }; 1218 1219 /** 1220 * Get pan vector 1221 * 1222 * @return {Object} {x: 0, y: 0} 1223 */ 1224 SvgPanZoom.prototype.getPan = function() { 1225 var state = this.viewport.getState(); 1226 1227 return { x: state.x, y: state.y }; 1228 }; 1229 1230 /** 1231 * Recalculates cached svg dimensions and controls position 1232 */ 1233 SvgPanZoom.prototype.resize = function() { 1234 // Get dimensions 1235 var boundingClientRectNormalized = SvgUtils.getBoundingClientRectNormalized( 1236 this.svg 1237 ); 1238 this.width = boundingClientRectNormalized.width; 1239 this.height = boundingClientRectNormalized.height; 1240 1241 // Recalculate original state 1242 var viewport = this.viewport; 1243 viewport.options.width = this.width; 1244 viewport.options.height = this.height; 1245 viewport.processCTM(); 1246 1247 // Reposition control icons by re-enabling them 1248 if (this.options.controlIconsEnabled) { 1249 this.getPublicInstance().disableControlIcons(); 1250 this.getPublicInstance().enableControlIcons(); 1251 } 1252 }; 1253 1254 /** 1255 * Unbind mouse events, free callbacks and destroy public instance 1256 */ 1257 SvgPanZoom.prototype.destroy = function() { 1258 var that = this; 1259 1260 // Free callbacks 1261 this.beforeZoom = null; 1262 this.onZoom = null; 1263 this.beforePan = null; 1264 this.onPan = null; 1265 this.onUpdatedCTM = null; 1266 1267 // Destroy custom event handlers 1268 // eslint-disable-next-line eqeqeq 1269 if (this.options.customEventsHandler != null) { 1270 this.options.customEventsHandler.destroy({ 1271 svgElement: this.svg, 1272 eventsListenerElement: this.options.eventsListenerElement, 1273 instance: this.getPublicInstance() 1274 }); 1275 } 1276 1277 // Unbind eventListeners 1278 for (var event in this.eventListeners) { 1279 (this.options.eventsListenerElement || this.svg).removeEventListener( 1280 event, 1281 this.eventListeners[event], 1282 !this.options.preventMouseEventsDefault ? passiveListenerOption : false 1283 ); 1284 } 1285 1286 // Unbind wheelListener 1287 this.disableMouseWheelZoom(); 1288 1289 // Remove control icons 1290 this.getPublicInstance().disableControlIcons(); 1291 1292 // Reset zoom and pan 1293 this.reset(); 1294 1295 // Remove instance from instancesStore 1296 instancesStore = instancesStore.filter(function(instance) { 1297 return instance.svg !== that.svg; 1298 }); 1299 1300 // Delete options and its contents 1301 delete this.options; 1302 1303 // Delete viewport to make public shadow viewport functions uncallable 1304 delete this.viewport; 1305 1306 // Destroy public instance and rewrite getPublicInstance 1307 delete this.publicInstance; 1308 delete this.pi; 1309 this.getPublicInstance = function() { 1310 return null; 1311 }; 1312 }; 1313 1314 /** 1315 * Returns a public instance object 1316 * 1317 * @return {Object} Public instance object 1318 */ 1319 SvgPanZoom.prototype.getPublicInstance = function() { 1320 var that = this; 1321 1322 // Create cache 1323 if (!this.publicInstance) { 1324 this.publicInstance = this.pi = { 1325 // Pan 1326 enablePan: function() { 1327 that.options.panEnabled = true; 1328 return that.pi; 1329 }, 1330 disablePan: function() { 1331 that.options.panEnabled = false; 1332 return that.pi; 1333 }, 1334 isPanEnabled: function() { 1335 return !!that.options.panEnabled; 1336 }, 1337 pan: function(point) { 1338 that.pan(point); 1339 return that.pi; 1340 }, 1341 panBy: function(point) { 1342 that.panBy(point); 1343 return that.pi; 1344 }, 1345 getPan: function() { 1346 return that.getPan(); 1347 }, 1348 // Pan event 1349 setBeforePan: function(fn) { 1350 that.options.beforePan = 1351 fn === null ? null : Utils.proxy(fn, that.publicInstance); 1352 return that.pi; 1353 }, 1354 setOnPan: function(fn) { 1355 that.options.onPan = 1356 fn === null ? null : Utils.proxy(fn, that.publicInstance); 1357 return that.pi; 1358 }, 1359 // Zoom and Control Icons 1360 enableZoom: function() { 1361 that.options.zoomEnabled = true; 1362 return that.pi; 1363 }, 1364 disableZoom: function() { 1365 that.options.zoomEnabled = false; 1366 return that.pi; 1367 }, 1368 isZoomEnabled: function() { 1369 return !!that.options.zoomEnabled; 1370 }, 1371 enableControlIcons: function() { 1372 if (!that.options.controlIconsEnabled) { 1373 that.options.controlIconsEnabled = true; 1374 ControlIcons.enable(that); 1375 } 1376 return that.pi; 1377 }, 1378 disableControlIcons: function() { 1379 if (that.options.controlIconsEnabled) { 1380 that.options.controlIconsEnabled = false; 1381 ControlIcons.disable(that); 1382 } 1383 return that.pi; 1384 }, 1385 isControlIconsEnabled: function() { 1386 return !!that.options.controlIconsEnabled; 1387 }, 1388 // Double click zoom 1389 enableDblClickZoom: function() { 1390 that.options.dblClickZoomEnabled = true; 1391 return that.pi; 1392 }, 1393 disableDblClickZoom: function() { 1394 that.options.dblClickZoomEnabled = false; 1395 return that.pi; 1396 }, 1397 isDblClickZoomEnabled: function() { 1398 return !!that.options.dblClickZoomEnabled; 1399 }, 1400 // Mouse wheel zoom 1401 enableMouseWheelZoom: function() { 1402 that.enableMouseWheelZoom(); 1403 return that.pi; 1404 }, 1405 disableMouseWheelZoom: function() { 1406 that.disableMouseWheelZoom(); 1407 return that.pi; 1408 }, 1409 isMouseWheelZoomEnabled: function() { 1410 return !!that.options.mouseWheelZoomEnabled; 1411 }, 1412 // Zoom scale and bounds 1413 setZoomScaleSensitivity: function(scale) { 1414 that.options.zoomScaleSensitivity = scale; 1415 return that.pi; 1416 }, 1417 setMinZoom: function(zoom) { 1418 that.options.minZoom = zoom; 1419 return that.pi; 1420 }, 1421 setMaxZoom: function(zoom) { 1422 that.options.maxZoom = zoom; 1423 return that.pi; 1424 }, 1425 // Zoom event 1426 setBeforeZoom: function(fn) { 1427 that.options.beforeZoom = 1428 fn === null ? null : Utils.proxy(fn, that.publicInstance); 1429 return that.pi; 1430 }, 1431 setOnZoom: function(fn) { 1432 that.options.onZoom = 1433 fn === null ? null : Utils.proxy(fn, that.publicInstance); 1434 return that.pi; 1435 }, 1436 // Zooming 1437 zoom: function(scale) { 1438 that.publicZoom(scale, true); 1439 return that.pi; 1440 }, 1441 zoomBy: function(scale) { 1442 that.publicZoom(scale, false); 1443 return that.pi; 1444 }, 1445 zoomAtPoint: function(scale, point) { 1446 that.publicZoomAtPoint(scale, point, true); 1447 return that.pi; 1448 }, 1449 zoomAtPointBy: function(scale, point) { 1450 that.publicZoomAtPoint(scale, point, false); 1451 return that.pi; 1452 }, 1453 zoomIn: function() { 1454 this.zoomBy(1 + that.options.zoomScaleSensitivity); 1455 return that.pi; 1456 }, 1457 zoomOut: function() { 1458 this.zoomBy(1 / (1 + that.options.zoomScaleSensitivity)); 1459 return that.pi; 1460 }, 1461 getZoom: function() { 1462 return that.getRelativeZoom(); 1463 }, 1464 // CTM update 1465 setOnUpdatedCTM: function(fn) { 1466 that.options.onUpdatedCTM = 1467 fn === null ? null : Utils.proxy(fn, that.publicInstance); 1468 return that.pi; 1469 }, 1470 // Reset 1471 resetZoom: function() { 1472 that.resetZoom(); 1473 return that.pi; 1474 }, 1475 resetPan: function() { 1476 that.resetPan(); 1477 return that.pi; 1478 }, 1479 reset: function() { 1480 that.reset(); 1481 return that.pi; 1482 }, 1483 // Fit, Contain and Center 1484 fit: function() { 1485 that.fit(); 1486 return that.pi; 1487 }, 1488 contain: function() { 1489 that.contain(); 1490 return that.pi; 1491 }, 1492 center: function() { 1493 that.center(); 1494 return that.pi; 1495 }, 1496 // Size and Resize 1497 updateBBox: function() { 1498 that.updateBBox(); 1499 return that.pi; 1500 }, 1501 resize: function() { 1502 that.resize(); 1503 return that.pi; 1504 }, 1505 getSizes: function() { 1506 return { 1507 width: that.width, 1508 height: that.height, 1509 realZoom: that.getZoom(), 1510 viewBox: that.viewport.getViewBox() 1511 }; 1512 }, 1513 // Destroy 1514 destroy: function() { 1515 that.destroy(); 1516 return that.pi; 1517 } 1518 }; 1519 } 1520 1521 return this.publicInstance; 1522 }; 1523 1524 /** 1525 * Stores pairs of instances of SvgPanZoom and SVG 1526 * Each pair is represented by an object {svg: SVGSVGElement, instance: SvgPanZoom} 1527 * 1528 * @type {Array} 1529 */ 1530 var instancesStore = []; 1531 1532 var svgPanZoom = function(elementOrSelector, options) { 1533 var svg = Utils.getSvg(elementOrSelector); 1534 1535 if (svg === null) { 1536 return null; 1537 } else { 1538 // Look for existent instance 1539 for (var i = instancesStore.length - 1; i >= 0; i--) { 1540 if (instancesStore[i].svg === svg) { 1541 return instancesStore[i].instance.getPublicInstance(); 1542 } 1543 } 1544 1545 // If instance not found - create one 1546 instancesStore.push({ 1547 svg: svg, 1548 instance: new SvgPanZoom(svg, options) 1549 }); 1550 1551 // Return just pushed instance 1552 return instancesStore[ 1553 instancesStore.length - 1 1554 ].instance.getPublicInstance(); 1555 } 1556 }; 1557 1558 module.exports = svgPanZoom; 1559 1560 },{"./control-icons":1,"./shadow-viewport":2,"./svg-utilities":5,"./uniwheel":6,"./utilities":7}],5:[function(require,module,exports){ 1561 var Utils = require("./utilities"), 1562 _browser = "unknown"; 1563 1564// http://stackoverflow.com/questions/9847580/how-to-detect-safari-chrome-ie-firefox-and-opera-browser 1565 if (/*@cc_on!@*/ false || !!document.documentMode) { 1566 // internet explorer 1567 _browser = "ie"; 1568 } 1569 1570 module.exports = { 1571 svgNS: "http://www.w3.org/2000/svg", 1572 xmlNS: "http://www.w3.org/XML/1998/namespace", 1573 xmlnsNS: "http://www.w3.org/2000/xmlns/", 1574 xlinkNS: "http://www.w3.org/1999/xlink", 1575 evNS: "http://www.w3.org/2001/xml-events", 1576 1577 /** 1578 * Get svg dimensions: width and height 1579 * 1580 * @param {SVGSVGElement} svg 1581 * @return {Object} {width: 0, height: 0} 1582 */ 1583 getBoundingClientRectNormalized: function(svg) { 1584 if (svg.clientWidth && svg.clientHeight) { 1585 return { width: svg.clientWidth, height: svg.clientHeight }; 1586 } else if (!!svg.getBoundingClientRect()) { 1587 return svg.getBoundingClientRect(); 1588 } else { 1589 throw new Error("Cannot get BoundingClientRect for SVG."); 1590 } 1591 }, 1592 1593 /** 1594 * Gets g element with class of "viewport" or creates it if it doesn't exist 1595 * 1596 * @param {SVGSVGElement} svg 1597 * @return {SVGElement} g (group) element 1598 */ 1599 getOrCreateViewport: function(svg, selector) { 1600 var viewport = null; 1601 1602 if (Utils.isElement(selector)) { 1603 viewport = selector; 1604 } else { 1605 viewport = svg.querySelector(selector); 1606 } 1607 1608 // Check if there is just one main group in SVG 1609 if (!viewport) { 1610 var childNodes = Array.prototype.slice 1611 .call(svg.childNodes || svg.children) 1612 .filter(function(el) { 1613 return el.nodeName !== "defs" && el.nodeName !== "#text"; 1614 }); 1615 1616 // Node name should be SVGGElement and should have no transform attribute 1617 // Groups with transform are not used as viewport because it involves parsing of all transform possibilities 1618 if ( 1619 childNodes.length === 1 && 1620 childNodes[0].nodeName === "g" && 1621 childNodes[0].getAttribute("transform") === null 1622 ) { 1623 viewport = childNodes[0]; 1624 } 1625 } 1626 1627 // If no favorable group element exists then create one 1628 if (!viewport) { 1629 var viewportId = 1630 "viewport-" + new Date().toISOString().replace(/\D/g, ""); 1631 viewport = document.createElementNS(this.svgNS, "g"); 1632 viewport.setAttribute("id", viewportId); 1633 1634 // Internet Explorer (all versions?) can't use childNodes, but other browsers prefer (require?) using childNodes 1635 var svgChildren = svg.childNodes || svg.children; 1636 if (!!svgChildren && svgChildren.length > 0) { 1637 for (var i = svgChildren.length; i > 0; i--) { 1638 // Move everything into viewport except defs 1639 if (svgChildren[svgChildren.length - i].nodeName !== "defs") { 1640 viewport.appendChild(svgChildren[svgChildren.length - i]); 1641 } 1642 } 1643 } 1644 svg.appendChild(viewport); 1645 } 1646 1647 // Parse class names 1648 var classNames = []; 1649 if (viewport.getAttribute("class")) { 1650 classNames = viewport.getAttribute("class").split(" "); 1651 } 1652 1653 // Set class (if not set already) 1654 if (!~classNames.indexOf("svg-pan-zoom_viewport")) { 1655 classNames.push("svg-pan-zoom_viewport"); 1656 viewport.setAttribute("class", classNames.join(" ")); 1657 } 1658 1659 return viewport; 1660 }, 1661 1662 /** 1663 * Set SVG attributes 1664 * 1665 * @param {SVGSVGElement} svg 1666 */ 1667 setupSvgAttributes: function(svg) { 1668 // Setting default attributes 1669 svg.setAttribute("xmlns", this.svgNS); 1670 svg.setAttributeNS(this.xmlnsNS, "xmlns:xlink", this.xlinkNS); 1671 svg.setAttributeNS(this.xmlnsNS, "xmlns:ev", this.evNS); 1672 1673 // Needed for Internet Explorer, otherwise the viewport overflows 1674 if (svg.parentNode !== null) { 1675 var style = svg.getAttribute("style") || ""; 1676 if (style.toLowerCase().indexOf("overflow") === -1) { 1677 svg.setAttribute("style", "overflow: hidden; " + style); 1678 } 1679 } 1680 }, 1681 1682 /** 1683 * How long Internet Explorer takes to finish updating its display (ms). 1684 */ 1685 internetExplorerRedisplayInterval: 300, 1686 1687 /** 1688 * Forces the browser to redisplay all SVG elements that rely on an 1689 * element defined in a 'defs' section. It works globally, for every 1690 * available defs element on the page. 1691 * The throttling is intentionally global. 1692 * 1693 * This is only needed for IE. It is as a hack to make markers (and 'use' elements?) 1694 * visible after pan/zoom when there are multiple SVGs on the page. 1695 * See bug report: https://connect.microsoft.com/IE/feedback/details/781964/ 1696 * also see svg-pan-zoom issue: https://github.com/ariutta/svg-pan-zoom/issues/62 1697 */ 1698 refreshDefsGlobal: Utils.throttle( 1699 function() { 1700 var allDefs = document.querySelectorAll("defs"); 1701 var allDefsCount = allDefs.length; 1702 for (var i = 0; i < allDefsCount; i++) { 1703 var thisDefs = allDefs[i]; 1704 thisDefs.parentNode.insertBefore(thisDefs, thisDefs); 1705 } 1706 }, 1707 this ? this.internetExplorerRedisplayInterval : null 1708 ), 1709 1710 /** 1711 * Sets the current transform matrix of an element 1712 * 1713 * @param {SVGElement} element 1714 * @param {SVGMatrix} matrix CTM 1715 * @param {SVGElement} defs 1716 */ 1717 setCTM: function(element, matrix, defs) { 1718 var that = this, 1719 s = 1720 "matrix(" + 1721 matrix.a + 1722 "," + 1723 matrix.b + 1724 "," + 1725 matrix.c + 1726 "," + 1727 matrix.d + 1728 "," + 1729 matrix.e + 1730 "," + 1731 matrix.f + 1732 ")"; 1733 1734 element.setAttributeNS(null, "transform", s); 1735 if ("transform" in element.style) { 1736 element.style.transform = s; 1737 } else if ("-ms-transform" in element.style) { 1738 element.style["-ms-transform"] = s; 1739 } else if ("-webkit-transform" in element.style) { 1740 element.style["-webkit-transform"] = s; 1741 } 1742 1743 // IE has a bug that makes markers disappear on zoom (when the matrix "a" and/or "d" elements change) 1744 // see http://stackoverflow.com/questions/17654578/svg-marker-does-not-work-in-ie9-10 1745 // and http://srndolha.wordpress.com/2013/11/25/svg-line-markers-may-disappear-in-internet-explorer-11/ 1746 if (_browser === "ie" && !!defs) { 1747 // this refresh is intended for redisplaying the SVG during zooming 1748 defs.parentNode.insertBefore(defs, defs); 1749 // this refresh is intended for redisplaying the other SVGs on a page when panning a given SVG 1750 // it is also needed for the given SVG itself, on zoomEnd, if the SVG contains any markers that 1751 // are located under any other element(s). 1752 window.setTimeout(function() { 1753 that.refreshDefsGlobal(); 1754 }, that.internetExplorerRedisplayInterval); 1755 } 1756 }, 1757 1758 /** 1759 * Instantiate an SVGPoint object with given event coordinates 1760 * 1761 * @param {Event} evt 1762 * @param {SVGSVGElement} svg 1763 * @return {SVGPoint} point 1764 */ 1765 getEventPoint: function(evt, svg) { 1766 var point = svg.createSVGPoint(); 1767 1768 Utils.mouseAndTouchNormalize(evt, svg); 1769 1770 point.x = evt.clientX; 1771 point.y = evt.clientY; 1772 1773 return point; 1774 }, 1775 1776 /** 1777 * Get SVG center point 1778 * 1779 * @param {SVGSVGElement} svg 1780 * @return {SVGPoint} 1781 */ 1782 getSvgCenterPoint: function(svg, width, height) { 1783 return this.createSVGPoint(svg, width / 2, height / 2); 1784 }, 1785 1786 /** 1787 * Create a SVGPoint with given x and y 1788 * 1789 * @param {SVGSVGElement} svg 1790 * @param {Number} x 1791 * @param {Number} y 1792 * @return {SVGPoint} 1793 */ 1794 createSVGPoint: function(svg, x, y) { 1795 var point = svg.createSVGPoint(); 1796 point.x = x; 1797 point.y = y; 1798 1799 return point; 1800 } 1801 }; 1802 1803 },{"./utilities":7}],6:[function(require,module,exports){ 1804// uniwheel 0.1.2 (customized) 1805// A unified cross browser mouse wheel event handler 1806// https://github.com/teemualap/uniwheel 1807 1808 module.exports = (function(){ 1809 1810 //Full details: https://developer.mozilla.org/en-US/docs/Web/Reference/Events/wheel 1811 1812 var prefix = "", _addEventListener, _removeEventListener, support, fns = []; 1813 var passiveOption = {passive: true}; 1814 1815 // detect event model 1816 if ( window.addEventListener ) { 1817 _addEventListener = "addEventListener"; 1818 _removeEventListener = "removeEventListener"; 1819 } else { 1820 _addEventListener = "attachEvent"; 1821 _removeEventListener = "detachEvent"; 1822 prefix = "on"; 1823 } 1824 1825 // detect available wheel event 1826 support = "onwheel" in document.createElement("div") ? "wheel" : // Modern browsers support "wheel" 1827 document.onmousewheel !== undefined ? "mousewheel" : // Webkit and IE support at least "mousewheel" 1828 "DOMMouseScroll"; // let's assume that remaining browsers are older Firefox 1829 1830 1831 function createCallback(element,callback) { 1832 1833 var fn = function(originalEvent) { 1834 1835 !originalEvent && ( originalEvent = window.event ); 1836 1837 // create a normalized event object 1838 var event = { 1839 // keep a ref to the original event object 1840 originalEvent: originalEvent, 1841 target: originalEvent.target || originalEvent.srcElement, 1842 type: "wheel", 1843 deltaMode: originalEvent.type == "MozMousePixelScroll" ? 0 : 1, 1844 deltaX: 0, 1845 delatZ: 0, 1846 preventDefault: function() { 1847 originalEvent.preventDefault ? 1848 originalEvent.preventDefault() : 1849 originalEvent.returnValue = false; 1850 } 1851 }; 1852 1853 // calculate deltaY (and deltaX) according to the event 1854 if ( support == "mousewheel" ) { 1855 event.deltaY = - 1/40 * originalEvent.wheelDelta; 1856 // Webkit also support wheelDeltaX 1857 originalEvent.wheelDeltaX && ( event.deltaX = - 1/40 * originalEvent.wheelDeltaX ); 1858 } else { 1859 event.deltaY = originalEvent.detail; 1860 } 1861 1862 // it's time to fire the callback 1863 return callback( event ); 1864 1865 }; 1866 1867 fns.push({ 1868 element: element, 1869 fn: fn, 1870 }); 1871 1872 return fn; 1873 } 1874 1875 function getCallback(element) { 1876 for (var i = 0; i < fns.length; i++) { 1877 if (fns[i].element === element) { 1878 return fns[i].fn; 1879 } 1880 } 1881 return function(){}; 1882 } 1883 1884 function removeCallback(element) { 1885 for (var i = 0; i < fns.length; i++) { 1886 if (fns[i].element === element) { 1887 return fns.splice(i,1); 1888 } 1889 } 1890 } 1891 1892 function _addWheelListener(elem, eventName, callback, isPassiveListener ) { 1893 var cb; 1894 1895 if (support === "wheel") { 1896 cb = callback; 1897 } else { 1898 cb = createCallback(elem, callback); 1899 } 1900 1901 elem[_addEventListener](prefix + eventName, cb, isPassiveListener ? passiveOption : false); 1902 } 1903 1904 function _removeWheelListener(elem, eventName, callback, isPassiveListener ) { 1905 1906 var cb; 1907 1908 if (support === "wheel") { 1909 cb = callback; 1910 } else { 1911 cb = getCallback(elem); 1912 } 1913 1914 elem[_removeEventListener](prefix + eventName, cb, isPassiveListener ? passiveOption : false); 1915 1916 removeCallback(elem); 1917 } 1918 1919 function addWheelListener( elem, callback, isPassiveListener ) { 1920 _addWheelListener(elem, support, callback, isPassiveListener ); 1921 1922 // handle MozMousePixelScroll in older Firefox 1923 if( support == "DOMMouseScroll" ) { 1924 _addWheelListener(elem, "MozMousePixelScroll", callback, isPassiveListener ); 1925 } 1926 } 1927 1928 function removeWheelListener(elem, callback, isPassiveListener){ 1929 _removeWheelListener(elem, support, callback, isPassiveListener); 1930 1931 // handle MozMousePixelScroll in older Firefox 1932 if( support == "DOMMouseScroll" ) { 1933 _removeWheelListener(elem, "MozMousePixelScroll", callback, isPassiveListener); 1934 } 1935 } 1936 1937 return { 1938 on: addWheelListener, 1939 off: removeWheelListener 1940 }; 1941 1942 })(); 1943 1944 },{}],7:[function(require,module,exports){ 1945 module.exports = { 1946 /** 1947 * Extends an object 1948 * 1949 * @param {Object} target object to extend 1950 * @param {Object} source object to take properties from 1951 * @return {Object} extended object 1952 */ 1953 extend: function(target, source) { 1954 target = target || {}; 1955 for (var prop in source) { 1956 // Go recursively 1957 if (this.isObject(source[prop])) { 1958 target[prop] = this.extend(target[prop], source[prop]); 1959 } else { 1960 target[prop] = source[prop]; 1961 } 1962 } 1963 return target; 1964 }, 1965 1966 /** 1967 * Checks if an object is a DOM element 1968 * 1969 * @param {Object} o HTML element or String 1970 * @return {Boolean} returns true if object is a DOM element 1971 */ 1972 isElement: function(o) { 1973 return ( 1974 o instanceof HTMLElement || 1975 o instanceof SVGElement || 1976 o instanceof SVGSVGElement || //DOM2 1977 (o && 1978 typeof o === "object" && 1979 o !== null && 1980 o.nodeType === 1 && 1981 typeof o.nodeName === "string") 1982 ); 1983 }, 1984 1985 /** 1986 * Checks if an object is an Object 1987 * 1988 * @param {Object} o Object 1989 * @return {Boolean} returns true if object is an Object 1990 */ 1991 isObject: function(o) { 1992 return Object.prototype.toString.call(o) === "[object Object]"; 1993 }, 1994 1995 /** 1996 * Checks if variable is Number 1997 * 1998 * @param {Integer|Float} n 1999 * @return {Boolean} returns true if variable is Number 2000 */ 2001 isNumber: function(n) { 2002 return !isNaN(parseFloat(n)) && isFinite(n); 2003 }, 2004 2005 /** 2006 * Search for an SVG element 2007 * 2008 * @param {Object|String} elementOrSelector DOM Element or selector String 2009 * @return {Object|Null} SVG or null 2010 */ 2011 getSvg: function(elementOrSelector) { 2012 var element, svg; 2013 2014 if (!this.isElement(elementOrSelector)) { 2015 // If selector provided 2016 if ( 2017 typeof elementOrSelector === "string" || 2018 elementOrSelector instanceof String 2019 ) { 2020 // Try to find the element 2021 element = document.querySelector(elementOrSelector); 2022 2023 if (!element) { 2024 throw new Error( 2025 "Provided selector did not find any elements. Selector: " + 2026 elementOrSelector 2027 ); 2028 return null; 2029 } 2030 } else { 2031 throw new Error("Provided selector is not an HTML object nor String"); 2032 return null; 2033 } 2034 } else { 2035 element = elementOrSelector; 2036 } 2037 2038 if (element.tagName.toLowerCase() === "svg") { 2039 svg = element; 2040 } else { 2041 if (element.tagName.toLowerCase() === "object") { 2042 svg = element.contentDocument.documentElement; 2043 } else { 2044 if (element.tagName.toLowerCase() === "embed") { 2045 svg = element.getSVGDocument().documentElement; 2046 } else { 2047 if (element.tagName.toLowerCase() === "img") { 2048 throw new Error( 2049 'Cannot script an SVG in an "img" element. Please use an "object" element or an in-line SVG.' 2050 ); 2051 } else { 2052 throw new Error("Cannot get SVG."); 2053 } 2054 return null; 2055 } 2056 } 2057 } 2058 2059 return svg; 2060 }, 2061 2062 /** 2063 * Attach a given context to a function 2064 * @param {Function} fn Function 2065 * @param {Object} context Context 2066 * @return {Function} Function with certain context 2067 */ 2068 proxy: function(fn, context) { 2069 return function() { 2070 return fn.apply(context, arguments); 2071 }; 2072 }, 2073 2074 /** 2075 * Returns object type 2076 * Uses toString that returns [object SVGPoint] 2077 * And than parses object type from string 2078 * 2079 * @param {Object} o Any object 2080 * @return {String} Object type 2081 */ 2082 getType: function(o) { 2083 return Object.prototype.toString 2084 .apply(o) 2085 .replace(/^\[object\s/, "") 2086 .replace(/\]$/, ""); 2087 }, 2088 2089 /** 2090 * If it is a touch event than add clientX and clientY to event object 2091 * 2092 * @param {Event} evt 2093 * @param {SVGSVGElement} svg 2094 */ 2095 mouseAndTouchNormalize: function(evt, svg) { 2096 // If no clientX then fallback 2097 if (evt.clientX === void 0 || evt.clientX === null) { 2098 // Fallback 2099 evt.clientX = 0; 2100 evt.clientY = 0; 2101 2102 // If it is a touch event 2103 if (evt.touches !== void 0 && evt.touches.length) { 2104 if (evt.touches[0].clientX !== void 0) { 2105 evt.clientX = evt.touches[0].clientX; 2106 evt.clientY = evt.touches[0].clientY; 2107 } else if (evt.touches[0].pageX !== void 0) { 2108 var rect = svg.getBoundingClientRect(); 2109 2110 evt.clientX = evt.touches[0].pageX - rect.left; 2111 evt.clientY = evt.touches[0].pageY - rect.top; 2112 } 2113 // If it is a custom event 2114 } else if (evt.originalEvent !== void 0) { 2115 if (evt.originalEvent.clientX !== void 0) { 2116 evt.clientX = evt.originalEvent.clientX; 2117 evt.clientY = evt.originalEvent.clientY; 2118 } 2119 } 2120 } 2121 }, 2122 2123 /** 2124 * Check if an event is a double click/tap 2125 * TODO: For touch gestures use a library (hammer.js) that takes in account other events 2126 * (touchmove and touchend). It should take in account tap duration and traveled distance 2127 * 2128 * @param {Event} evt 2129 * @param {Event} prevEvt Previous Event 2130 * @return {Boolean} 2131 */ 2132 isDblClick: function(evt, prevEvt) { 2133 // Double click detected by browser 2134 if (evt.detail === 2) { 2135 return true; 2136 } 2137 // Try to compare events 2138 else if (prevEvt !== void 0 && prevEvt !== null) { 2139 var timeStampDiff = evt.timeStamp - prevEvt.timeStamp, // should be lower than 250 ms 2140 touchesDistance = Math.sqrt( 2141 Math.pow(evt.clientX - prevEvt.clientX, 2) + 2142 Math.pow(evt.clientY - prevEvt.clientY, 2) 2143 ); 2144 2145 return timeStampDiff < 250 && touchesDistance < 10; 2146 } 2147 2148 // Nothing found 2149 return false; 2150 }, 2151 2152 /** 2153 * Returns current timestamp as an integer 2154 * 2155 * @return {Number} 2156 */ 2157 now: 2158 Date.now || 2159 function() { 2160 return new Date().getTime(); 2161 }, 2162 2163 // From underscore. 2164 // Returns a function, that, when invoked, will only be triggered at most once 2165 // during a given window of time. Normally, the throttled function will run 2166 // as much as it can, without ever going more than once per `wait` duration; 2167 // but if you'd like to disable the execution on the leading edge, pass 2168 // `{leading: false}`. To disable execution on the trailing edge, ditto. 2169 throttle: function(func, wait, options) { 2170 var that = this; 2171 var context, args, result; 2172 var timeout = null; 2173 var previous = 0; 2174 if (!options) { 2175 options = {}; 2176 } 2177 var later = function() { 2178 previous = options.leading === false ? 0 : that.now(); 2179 timeout = null; 2180 result = func.apply(context, args); 2181 if (!timeout) { 2182 context = args = null; 2183 } 2184 }; 2185 return function() { 2186 var now = that.now(); 2187 if (!previous && options.leading === false) { 2188 previous = now; 2189 } 2190 var remaining = wait - (now - previous); 2191 context = this; // eslint-disable-line consistent-this 2192 args = arguments; 2193 if (remaining <= 0 || remaining > wait) { 2194 clearTimeout(timeout); 2195 timeout = null; 2196 previous = now; 2197 result = func.apply(context, args); 2198 if (!timeout) { 2199 context = args = null; 2200 } 2201 } else if (!timeout && options.trailing !== false) { 2202 timeout = setTimeout(later, remaining); 2203 } 2204 return result; 2205 }; 2206 }, 2207 2208 /** 2209 * Create a requestAnimationFrame simulation 2210 * 2211 * @param {Number|String} refreshRate 2212 * @return {Function} 2213 */ 2214 createRequestAnimationFrame: function(refreshRate) { 2215 var timeout = null; 2216 2217 // Convert refreshRate to timeout 2218 if (refreshRate !== "auto" && refreshRate < 60 && refreshRate > 1) { 2219 timeout = Math.floor(1000 / refreshRate); 2220 } 2221 2222 if (timeout === null) { 2223 return window.requestAnimationFrame || requestTimeout(33); 2224 } else { 2225 return requestTimeout(timeout); 2226 } 2227 } 2228 }; 2229 2230 /** 2231 * Create a callback that will execute after a given timeout 2232 * 2233 * @param {Function} timeout 2234 * @return {Function} 2235 */ 2236 function requestTimeout(timeout) { 2237 return function(callback) { 2238 window.setTimeout(callback, timeout); 2239 }; 2240 } 2241 2242 },{}]},{},[3]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.