rbbt-rest 1.6.28 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/lib/rbbt/rest/entity.rb +71 -1
  3. data/lib/rbbt/rest/entity/render.rb +3 -2
  4. data/lib/rbbt/rest/knowledge_base.rb +81 -28
  5. data/lib/rbbt/rest/main.rb +3 -0
  6. data/lib/rbbt/rest/workflow.rb +1 -1
  7. data/lib/rbbt/rest/workflow/jobs.rb +5 -4
  8. data/share/views/compass/app.sass +21 -11
  9. data/share/views/layout/footer.haml +0 -1
  10. data/share/views/layout/header.haml +9 -6
  11. data/share/views/layout/top_menu.haml +1 -0
  12. data/share/views/partials/table.haml +7 -1
  13. data/share/views/public/js/_ajax_replace.js +2 -0
  14. data/share/views/public/js/defer.js +1 -1
  15. data/share/views/public/js/helpers.js +89 -18
  16. data/share/views/public/js/rbbt.aesthetics.js +145 -34
  17. data/share/views/public/js/rbbt.basic.js +20 -0
  18. data/share/views/public/js/rbbt.entity.js +12 -2
  19. data/share/views/public/js/rbbt.entity_list.js +51 -5
  20. data/share/views/public/js/rbbt.exception.js +9 -1
  21. data/share/views/public/js/rbbt.job.js +2 -2
  22. data/share/views/public/js/rbbt.js +5 -1
  23. data/share/views/public/js/rbbt.knowledge_base.js +40 -0
  24. data/share/views/public/js/rbbt.modal.js +82 -75
  25. data/share/views/public/js/rbbt.plots.js +276 -0
  26. data/share/views/public/js/rbbt/list.js +2 -2
  27. data/share/views/public/plugins/cola/js/cola.v3.min.js +3 -0
  28. data/share/views/public/plugins/d3js/js/d3.js +9503 -0
  29. data/share/views/public/plugins/d3js/js/d3js.min.js +5 -0
  30. data/share/views/public/plugins/mithril-node-render/js/index.js +115 -0
  31. data/share/views/public/plugins/mithril/js/mithril.js +212 -77
  32. data/share/views/public/plugins/mithril/js/mithril.min.js +2 -2
  33. data/share/views/public/plugins/mithril/js/mithril.min.js.map +1 -1
  34. data/share/views/public/plugins/nvd3/css/nv.d3.css +641 -0
  35. data/share/views/public/plugins/nvd3/js/nv.d3.js +13298 -0
  36. data/share/views/public/plugins/svg-pan-zoom/js/svg-pan-zoom.js +1861 -0
  37. data/share/views/tools/nvd3.haml +16 -0
  38. data/share/views/tools/nvd3/chart.haml +55 -0
  39. data/share/views/tools/nvd3/histogram.haml +19 -0
  40. data/share/views/tools/nvd3/pie.haml +24 -0
  41. data/share/views/tools/nvd3/scatter.haml +33 -0
  42. data/share/views/wait.haml +2 -2
  43. metadata +15 -3
  44. data/share/views/public/plugins/d3js/d3js.min.js +0 -5
@@ -0,0 +1,1861 @@
1
+ // svg-pan-zoom v3.2.3
2
+ // https://github.com/ariutta/svg-pan-zoom
3
+ (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
4
+ var svgPanZoom = require('./svg-pan-zoom.js');
5
+
6
+ // UMD module definition
7
+ (function(window, document){
8
+ // AMD
9
+ if (typeof define === 'function' && define.amd) {
10
+ define('svg-pan-zoom', function () {
11
+ return svgPanZoom;
12
+ });
13
+ // CMD
14
+ } else if (typeof module !== 'undefined' && module.exports) {
15
+ module.exports = svgPanZoom;
16
+
17
+ // Browser
18
+ // Keep exporting globally as module.exports is available because of browserify
19
+ window.svgPanZoom = svgPanZoom;
20
+ }
21
+ })(window, document)
22
+
23
+ },{"./svg-pan-zoom.js":4}],2:[function(require,module,exports){
24
+ var SvgUtils = require('./svg-utilities');
25
+
26
+ module.exports = {
27
+ enable: function(instance) {
28
+ // Select (and create if necessary) defs
29
+ var defs = instance.svg.querySelector('defs')
30
+ if (!defs) {
31
+ defs = document.createElementNS(SvgUtils.svgNS, 'defs')
32
+ instance.svg.appendChild(defs)
33
+ }
34
+
35
+ // Create style element
36
+ var style = document.createElementNS(SvgUtils.svgNS, 'style')
37
+ style.setAttribute('type', 'text/css')
38
+ style.textContent = '.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; }'
39
+ defs.appendChild(style)
40
+
41
+
42
+ // Zoom Group
43
+ var zoomGroup = document.createElementNS(SvgUtils.svgNS, 'g');
44
+ zoomGroup.setAttribute('id', 'svg-pan-zoom-controls');
45
+ zoomGroup.setAttribute('transform', 'translate(' + ( instance.width - 70 ) + ' ' + ( instance.height - 76 ) + ') scale(0.75)');
46
+ zoomGroup.setAttribute('class', 'svg-pan-zoom-control');
47
+
48
+ // Control elements
49
+ zoomGroup.appendChild(this._createZoomIn(instance))
50
+ zoomGroup.appendChild(this._createZoomReset(instance))
51
+ zoomGroup.appendChild(this._createZoomOut(instance))
52
+
53
+ // Finally append created element
54
+ instance.svg.appendChild(zoomGroup)
55
+
56
+ // Cache control instance
57
+ instance.controlIcons = zoomGroup
58
+ }
59
+
60
+ , _createZoomIn: function(instance) {
61
+ var zoomIn = document.createElementNS(SvgUtils.svgNS, 'g');
62
+ zoomIn.setAttribute('id', 'svg-pan-zoom-zoom-in');
63
+ zoomIn.setAttribute('transform', 'translate(30.5 5) scale(0.015)');
64
+ zoomIn.setAttribute('class', 'svg-pan-zoom-control');
65
+ zoomIn.addEventListener('click', function() {instance.getPublicInstance().zoomIn()}, false)
66
+ zoomIn.addEventListener('touchstart', function() {instance.getPublicInstance().zoomIn()}, false)
67
+
68
+ var zoomInBackground = document.createElementNS(SvgUtils.svgNS, 'rect'); // TODO change these background space fillers to rounded rectangles so they look prettier
69
+ zoomInBackground.setAttribute('x', '0');
70
+ zoomInBackground.setAttribute('y', '0');
71
+ zoomInBackground.setAttribute('width', '1500'); // larger than expected because the whole group is transformed to scale down
72
+ zoomInBackground.setAttribute('height', '1400');
73
+ zoomInBackground.setAttribute('class', 'svg-pan-zoom-control-background');
74
+ zoomIn.appendChild(zoomInBackground);
75
+
76
+ var zoomInShape = document.createElementNS(SvgUtils.svgNS, 'path');
77
+ zoomInShape.setAttribute('d', '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');
78
+ zoomInShape.setAttribute('class', 'svg-pan-zoom-control-element');
79
+ zoomIn.appendChild(zoomInShape);
80
+
81
+ return zoomIn
82
+ }
83
+
84
+ , _createZoomReset: function(instance){
85
+ // reset
86
+ var resetPanZoomControl = document.createElementNS(SvgUtils.svgNS, 'g');
87
+ resetPanZoomControl.setAttribute('id', 'svg-pan-zoom-reset-pan-zoom');
88
+ resetPanZoomControl.setAttribute('transform', 'translate(5 35) scale(0.4)');
89
+ resetPanZoomControl.setAttribute('class', 'svg-pan-zoom-control');
90
+ resetPanZoomControl.addEventListener('click', function() {instance.getPublicInstance().reset()}, false);
91
+ resetPanZoomControl.addEventListener('touchstart', function() {instance.getPublicInstance().reset()}, false);
92
+
93
+ var resetPanZoomControlBackground = document.createElementNS(SvgUtils.svgNS, 'rect'); // TODO change these background space fillers to rounded rectangles so they look prettier
94
+ resetPanZoomControlBackground.setAttribute('x', '2');
95
+ resetPanZoomControlBackground.setAttribute('y', '2');
96
+ resetPanZoomControlBackground.setAttribute('width', '182'); // larger than expected because the whole group is transformed to scale down
97
+ resetPanZoomControlBackground.setAttribute('height', '58');
98
+ resetPanZoomControlBackground.setAttribute('class', 'svg-pan-zoom-control-background');
99
+ resetPanZoomControl.appendChild(resetPanZoomControlBackground);
100
+
101
+ var resetPanZoomControlShape1 = document.createElementNS(SvgUtils.svgNS, 'path');
102
+ resetPanZoomControlShape1.setAttribute('d', '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');
103
+ resetPanZoomControlShape1.setAttribute('class', 'svg-pan-zoom-control-element');
104
+ resetPanZoomControl.appendChild(resetPanZoomControlShape1);
105
+
106
+ var resetPanZoomControlShape2 = document.createElementNS(SvgUtils.svgNS, 'path');
107
+ resetPanZoomControlShape2.setAttribute('d', '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');
108
+ resetPanZoomControlShape2.setAttribute('class', 'svg-pan-zoom-control-element');
109
+ resetPanZoomControl.appendChild(resetPanZoomControlShape2);
110
+
111
+ return resetPanZoomControl
112
+ }
113
+
114
+ , _createZoomOut: function(instance){
115
+ // zoom out
116
+ var zoomOut = document.createElementNS(SvgUtils.svgNS, 'g');
117
+ zoomOut.setAttribute('id', 'svg-pan-zoom-zoom-out');
118
+ zoomOut.setAttribute('transform', 'translate(30.5 70) scale(0.015)');
119
+ zoomOut.setAttribute('class', 'svg-pan-zoom-control');
120
+ zoomOut.addEventListener('click', function() {instance.getPublicInstance().zoomOut()}, false);
121
+ zoomOut.addEventListener('touchstart', function() {instance.getPublicInstance().zoomOut()}, false);
122
+
123
+ var zoomOutBackground = document.createElementNS(SvgUtils.svgNS, 'rect'); // TODO change these background space fillers to rounded rectangles so they look prettier
124
+ zoomOutBackground.setAttribute('x', '0');
125
+ zoomOutBackground.setAttribute('y', '0');
126
+ zoomOutBackground.setAttribute('width', '1500'); // larger than expected because the whole group is transformed to scale down
127
+ zoomOutBackground.setAttribute('height', '1400');
128
+ zoomOutBackground.setAttribute('class', 'svg-pan-zoom-control-background');
129
+ zoomOut.appendChild(zoomOutBackground);
130
+
131
+ var zoomOutShape = document.createElementNS(SvgUtils.svgNS, 'path');
132
+ zoomOutShape.setAttribute('d', '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');
133
+ zoomOutShape.setAttribute('class', 'svg-pan-zoom-control-element');
134
+ zoomOut.appendChild(zoomOutShape);
135
+
136
+ return zoomOut
137
+ }
138
+
139
+ , disable: function(instance) {
140
+ if (instance.controlIcons) {
141
+ instance.controlIcons.parentNode.removeChild(instance.controlIcons)
142
+ instance.controlIcons = null
143
+ }
144
+ }
145
+ }
146
+
147
+ },{"./svg-utilities":5}],3:[function(require,module,exports){
148
+ var SvgUtils = require('./svg-utilities')
149
+ , Utils = require('./utilities')
150
+ ;
151
+
152
+ var ShadowViewport = function(viewport, options){
153
+ this.init(viewport, options)
154
+ }
155
+
156
+ /**
157
+ * Initialization
158
+ *
159
+ * @param {SVGElement} viewport
160
+ * @param {Object} options
161
+ */
162
+ ShadowViewport.prototype.init = function(viewport, options) {
163
+ // DOM Elements
164
+ this.viewport = viewport
165
+ this.options = options
166
+
167
+ // State cache
168
+ this.originalState = {zoom: 1, x: 0, y: 0}
169
+ this.activeState = {zoom: 1, x: 0, y: 0}
170
+
171
+ this.updateCTMCached = Utils.proxy(this.updateCTM, this)
172
+
173
+ // Create a custom requestAnimationFrame taking in account refreshRate
174
+ this.requestAnimationFrame = Utils.createRequestAnimationFrame(this.options.refreshRate)
175
+
176
+ // ViewBox
177
+ this.viewBox = {x: 0, y: 0, width: 0, height: 0}
178
+ this.cacheViewBox()
179
+
180
+ // Process CTM
181
+ this.processCTM()
182
+
183
+ // Update CTM in this frame
184
+ this.updateCTM()
185
+ }
186
+
187
+ /**
188
+ * Cache initial viewBox value
189
+ * If no viewBox is defined, then use viewport size/position instead for viewBox values
190
+ */
191
+ ShadowViewport.prototype.cacheViewBox = function() {
192
+ var svgViewBox = this.options.svg.getAttribute('viewBox')
193
+
194
+ if (svgViewBox) {
195
+ var viewBoxValues = svgViewBox.split(/[\s\,]/).filter(function(v){return v}).map(parseFloat)
196
+
197
+ // Cache viewbox x and y offset
198
+ this.viewBox.x = viewBoxValues[0]
199
+ this.viewBox.y = viewBoxValues[1]
200
+ this.viewBox.width = viewBoxValues[2]
201
+ this.viewBox.height = viewBoxValues[3]
202
+
203
+ var zoom = Math.min(this.options.width / this.viewBox.width, this.options.height / this.viewBox.height)
204
+
205
+ // Update active state
206
+ this.activeState.zoom = zoom
207
+ this.activeState.x = (this.options.width - this.viewBox.width * zoom) / 2
208
+ this.activeState.y = (this.options.height - this.viewBox.height * zoom) / 2
209
+
210
+ // Force updating CTM
211
+ this.updateCTMOnNextFrame()
212
+
213
+ this.options.svg.removeAttribute('viewBox')
214
+ } else {
215
+ var bBox = this.viewport.getBBox();
216
+
217
+ // Cache viewbox sizes
218
+ this.viewBox.x = bBox.x;
219
+ this.viewBox.y = bBox.y;
220
+ this.viewBox.width = bBox.width
221
+ this.viewBox.height = bBox.height
222
+ }
223
+ }
224
+
225
+ /**
226
+ * Recalculate viewport sizes and update viewBox cache
227
+ */
228
+ ShadowViewport.prototype.recacheViewBox = function() {
229
+ var boundingClientRect = this.viewport.getBoundingClientRect()
230
+ , viewBoxWidth = boundingClientRect.width / this.getZoom()
231
+ , viewBoxHeight = boundingClientRect.height / this.getZoom()
232
+
233
+ // Cache viewbox
234
+ this.viewBox.x = 0
235
+ this.viewBox.y = 0
236
+ this.viewBox.width = viewBoxWidth
237
+ this.viewBox.height = viewBoxHeight
238
+ }
239
+
240
+ /**
241
+ * Returns a viewbox object. Safe to alter
242
+ *
243
+ * @return {Object} viewbox object
244
+ */
245
+ ShadowViewport.prototype.getViewBox = function() {
246
+ return Utils.extend({}, this.viewBox)
247
+ }
248
+
249
+ /**
250
+ * Get initial zoom and pan values. Save them into originalState
251
+ * Parses viewBox attribute to alter initial sizes
252
+ */
253
+ ShadowViewport.prototype.processCTM = function() {
254
+ var newCTM = this.getCTM()
255
+
256
+ if (this.options.fit || this.options.contain) {
257
+ var newScale;
258
+ if (this.options.fit) {
259
+ newScale = Math.min(this.options.width/this.viewBox.width, this.options.height/this.viewBox.height);
260
+ } else {
261
+ newScale = Math.max(this.options.width/this.viewBox.width, this.options.height/this.viewBox.height);
262
+ }
263
+
264
+ newCTM.a = newScale; //x-scale
265
+ newCTM.d = newScale; //y-scale
266
+ newCTM.e = -this.viewBox.x * newScale; //x-transform
267
+ newCTM.f = -this.viewBox.y * newScale; //y-transform
268
+ }
269
+
270
+ if (this.options.center) {
271
+ var offsetX = (this.options.width - (this.viewBox.width + this.viewBox.x * 2) * newCTM.a) * 0.5
272
+ , offsetY = (this.options.height - (this.viewBox.height + this.viewBox.y * 2) * newCTM.a) * 0.5
273
+
274
+ newCTM.e = offsetX
275
+ newCTM.f = offsetY
276
+ }
277
+
278
+ // Cache initial values. Based on activeState and fix+center opitons
279
+ this.originalState.zoom = newCTM.a
280
+ this.originalState.x = newCTM.e
281
+ this.originalState.y = newCTM.f
282
+
283
+ // Update viewport CTM and cache zoom and pan
284
+ this.setCTM(newCTM);
285
+ }
286
+
287
+ /**
288
+ * Return originalState object. Safe to alter
289
+ *
290
+ * @return {Object}
291
+ */
292
+ ShadowViewport.prototype.getOriginalState = function() {
293
+ return Utils.extend({}, this.originalState)
294
+ }
295
+
296
+ /**
297
+ * Return actualState object. Safe to alter
298
+ *
299
+ * @return {Object}
300
+ */
301
+ ShadowViewport.prototype.getState = function() {
302
+ return Utils.extend({}, this.activeState)
303
+ }
304
+
305
+ /**
306
+ * Get zoom scale
307
+ *
308
+ * @return {Float} zoom scale
309
+ */
310
+ ShadowViewport.prototype.getZoom = function() {
311
+ return this.activeState.zoom
312
+ }
313
+
314
+ /**
315
+ * Get zoom scale for pubilc usage
316
+ *
317
+ * @return {Float} zoom scale
318
+ */
319
+ ShadowViewport.prototype.getRelativeZoom = function() {
320
+ return this.activeState.zoom / this.originalState.zoom
321
+ }
322
+
323
+ /**
324
+ * Compute zoom scale for pubilc usage
325
+ *
326
+ * @return {Float} zoom scale
327
+ */
328
+ ShadowViewport.prototype.computeRelativeZoom = function(scale) {
329
+ return scale / this.originalState.zoom
330
+ }
331
+
332
+ /**
333
+ * Get pan
334
+ *
335
+ * @return {Object}
336
+ */
337
+ ShadowViewport.prototype.getPan = function() {
338
+ return {x: this.activeState.x, y: this.activeState.y}
339
+ }
340
+
341
+ /**
342
+ * Return cached viewport CTM value that can be safely modified
343
+ *
344
+ * @return {SVGMatrix}
345
+ */
346
+ ShadowViewport.prototype.getCTM = function() {
347
+ var safeCTM = this.options.svg.createSVGMatrix()
348
+
349
+ // Copy values manually as in FF they are not itterable
350
+ safeCTM.a = this.activeState.zoom
351
+ safeCTM.b = 0
352
+ safeCTM.c = 0
353
+ safeCTM.d = this.activeState.zoom
354
+ safeCTM.e = this.activeState.x
355
+ safeCTM.f = this.activeState.y
356
+
357
+ return safeCTM
358
+ }
359
+
360
+ /**
361
+ * Set a new CTM
362
+ *
363
+ * @param {SVGMatrix} newCTM
364
+ */
365
+ ShadowViewport.prototype.setCTM = function(newCTM) {
366
+ var willZoom = this.isZoomDifferent(newCTM)
367
+ , willPan = this.isPanDifferent(newCTM)
368
+
369
+ if (willZoom || willPan) {
370
+ // Before zoom
371
+ if (willZoom) {
372
+ // If returns false then cancel zooming
373
+ if (this.options.beforeZoom(this.getRelativeZoom(), this.computeRelativeZoom(newCTM.a)) === false) {
374
+ newCTM.a = newCTM.d = this.activeState.zoom
375
+ willZoom = false
376
+ }
377
+ }
378
+
379
+ // Before pan
380
+ if (willPan) {
381
+ var preventPan = this.options.beforePan(this.getPan(), {x: newCTM.e, y: newCTM.f})
382
+ // If prevent pan is an object
383
+ , preventPanX = false
384
+ , preventPanY = false
385
+
386
+ // If prevent pan is Boolean false
387
+ if (preventPan === false) {
388
+ // Set x and y same as before
389
+ newCTM.e = this.getPan().x
390
+ newCTM.f = this.getPan().y
391
+
392
+ preventPanX = preventPanY = true
393
+ } else if (Utils.isObject(preventPan)) {
394
+ // Check for X axes attribute
395
+ if (preventPan.x === false) {
396
+ // Prevent panning on x axes
397
+ newCTM.e = this.getPan().x
398
+ preventPanX = true
399
+ } else if (Utils.isNumber(preventPan.x)) {
400
+ // Set a custom pan value
401
+ newCTM.e = preventPan.x
402
+ }
403
+
404
+ // Check for Y axes attribute
405
+ if (preventPan.y === false) {
406
+ // Prevent panning on x axes
407
+ newCTM.f = this.getPan().y
408
+ preventPanY = true
409
+ } else if (Utils.isNumber(preventPan.y)) {
410
+ // Set a custom pan value
411
+ newCTM.f = preventPan.y
412
+ }
413
+ }
414
+
415
+ // Update willPan flag
416
+ if (preventPanX && preventPanY) {
417
+ willPan = false
418
+ }
419
+ }
420
+
421
+ // Check again if should zoom or pan
422
+ if (willZoom || willPan) {
423
+ this.updateCache(newCTM)
424
+
425
+ this.updateCTMOnNextFrame()
426
+
427
+ // After callbacks
428
+ if (willZoom) {this.options.onZoom(this.getRelativeZoom())}
429
+ if (willPan) {this.options.onPan(this.getPan())}
430
+ }
431
+ }
432
+ }
433
+
434
+ ShadowViewport.prototype.isZoomDifferent = function(newCTM) {
435
+ return this.activeState.zoom !== newCTM.a
436
+ }
437
+
438
+ ShadowViewport.prototype.isPanDifferent = function(newCTM) {
439
+ return this.activeState.x !== newCTM.e || this.activeState.y !== newCTM.f
440
+ }
441
+
442
+
443
+ /**
444
+ * Update cached CTM and active state
445
+ *
446
+ * @param {SVGMatrix} newCTM
447
+ */
448
+ ShadowViewport.prototype.updateCache = function(newCTM) {
449
+ this.activeState.zoom = newCTM.a
450
+ this.activeState.x = newCTM.e
451
+ this.activeState.y = newCTM.f
452
+ }
453
+
454
+ ShadowViewport.prototype.pendingUpdate = false
455
+
456
+ /**
457
+ * Place a request to update CTM on next Frame
458
+ */
459
+ ShadowViewport.prototype.updateCTMOnNextFrame = function() {
460
+ if (!this.pendingUpdate) {
461
+ // Lock
462
+ this.pendingUpdate = true
463
+
464
+ // Throttle next update
465
+ this.requestAnimationFrame.call(window, this.updateCTMCached)
466
+ }
467
+ }
468
+
469
+ /**
470
+ * Update viewport CTM with cached CTM
471
+ */
472
+ ShadowViewport.prototype.updateCTM = function() {
473
+ // Updates SVG element
474
+ SvgUtils.setCTM(this.viewport, this.getCTM(), this.defs)
475
+
476
+ // Free the lock
477
+ this.pendingUpdate = false
478
+ }
479
+
480
+ module.exports = function(viewport, options){
481
+ return new ShadowViewport(viewport, options)
482
+ }
483
+
484
+ },{"./svg-utilities":5,"./utilities":7}],4:[function(require,module,exports){
485
+ var Wheel = require('./uniwheel')
486
+ , ControlIcons = require('./control-icons')
487
+ , Utils = require('./utilities')
488
+ , SvgUtils = require('./svg-utilities')
489
+ , ShadowViewport = require('./shadow-viewport')
490
+
491
+ var SvgPanZoom = function(svg, options) {
492
+ this.init(svg, options)
493
+ }
494
+
495
+ var optionsDefaults = {
496
+ viewportSelector: '.svg-pan-zoom_viewport' // Viewport selector. Can be querySelector string or SVGElement
497
+ , panEnabled: true // enable or disable panning (default enabled)
498
+ , controlIconsEnabled: false // insert icons to give user an option in addition to mouse events to control pan/zoom (default disabled)
499
+ , zoomEnabled: true // enable or disable zooming (default enabled)
500
+ , dblClickZoomEnabled: true // enable or disable zooming by double clicking (default enabled)
501
+ , mouseWheelZoomEnabled: true // enable or disable zooming by mouse wheel (default enabled)
502
+ , preventMouseEventsDefault: true // enable or disable preventDefault for mouse events
503
+ , zoomScaleSensitivity: 0.1 // Zoom sensitivity
504
+ , minZoom: 0.5 // Minimum Zoom level
505
+ , maxZoom: 10 // Maximum Zoom level
506
+ , fit: true // enable or disable viewport fit in SVG (default true)
507
+ , contain: false // enable or disable viewport contain the svg (default false)
508
+ , center: true // enable or disable viewport centering in SVG (default true)
509
+ , refreshRate: 'auto' // Maximum number of frames per second (altering SVG's viewport)
510
+ , beforeZoom: null
511
+ , onZoom: null
512
+ , beforePan: null
513
+ , onPan: null
514
+ , customEventsHandler: null
515
+ }
516
+
517
+ SvgPanZoom.prototype.init = function(svg, options) {
518
+ var that = this
519
+
520
+ this.svg = svg
521
+ this.defs = svg.querySelector('defs')
522
+
523
+ // Add default attributes to SVG
524
+ SvgUtils.setupSvgAttributes(this.svg)
525
+
526
+ // Set options
527
+ this.options = Utils.extend(Utils.extend({}, optionsDefaults), options)
528
+
529
+ // Set default state
530
+ this.state = 'none'
531
+
532
+ // Get dimensions
533
+ var boundingClientRectNormalized = SvgUtils.getBoundingClientRectNormalized(svg)
534
+ this.width = boundingClientRectNormalized.width
535
+ this.height = boundingClientRectNormalized.height
536
+
537
+ // Init shadow viewport
538
+ this.viewport = ShadowViewport(SvgUtils.getOrCreateViewport(this.svg, this.options.viewportSelector), {
539
+ svg: this.svg
540
+ , width: this.width
541
+ , height: this.height
542
+ , fit: this.options.fit
543
+ , contain: this.options.contain
544
+ , center: this.options.center
545
+ , refreshRate: this.options.refreshRate
546
+ // Put callbacks into functions as they can change through time
547
+ , beforeZoom: function(oldScale, newScale) {
548
+ if (that.viewport && that.options.beforeZoom) {return that.options.beforeZoom(oldScale, newScale)}
549
+ }
550
+ , onZoom: function(scale) {
551
+ if (that.viewport && that.options.onZoom) {return that.options.onZoom(scale)}
552
+ }
553
+ , beforePan: function(oldPoint, newPoint) {
554
+ if (that.viewport && that.options.beforePan) {return that.options.beforePan(oldPoint, newPoint)}
555
+ }
556
+ , onPan: function(point) {
557
+ if (that.viewport && that.options.onPan) {return that.options.onPan(point)}
558
+ }
559
+ })
560
+
561
+ // Wrap callbacks into public API context
562
+ var publicInstance = this.getPublicInstance()
563
+ publicInstance.setBeforeZoom(this.options.beforeZoom)
564
+ publicInstance.setOnZoom(this.options.onZoom)
565
+ publicInstance.setBeforePan(this.options.beforePan)
566
+ publicInstance.setOnPan(this.options.onPan)
567
+
568
+ if (this.options.controlIconsEnabled) {
569
+ ControlIcons.enable(this)
570
+ }
571
+
572
+ // Init events handlers
573
+ this.lastMouseWheelEventTime = Date.now()
574
+ this.setupHandlers()
575
+ }
576
+
577
+ /**
578
+ * Register event handlers
579
+ */
580
+ SvgPanZoom.prototype.setupHandlers = function() {
581
+ var that = this
582
+ , prevEvt = null // use for touchstart event to detect double tap
583
+ ;
584
+
585
+ this.eventListeners = {
586
+ // Mouse down group
587
+ mousedown: function(evt) {
588
+ return that.handleMouseDown(evt, null);
589
+ }
590
+ , touchstart: function(evt) {
591
+ var result = that.handleMouseDown(evt, prevEvt);
592
+ prevEvt = evt
593
+ return result;
594
+ }
595
+
596
+ // Mouse up group
597
+ , mouseup: function(evt) {
598
+ return that.handleMouseUp(evt);
599
+ }
600
+ , touchend: function(evt) {
601
+ return that.handleMouseUp(evt);
602
+ }
603
+
604
+ // Mouse move group
605
+ , mousemove: function(evt) {
606
+ return that.handleMouseMove(evt);
607
+ }
608
+ , touchmove: function(evt) {
609
+ return that.handleMouseMove(evt);
610
+ }
611
+
612
+ // Mouse leave group
613
+ , mouseleave: function(evt) {
614
+ return that.handleMouseUp(evt);
615
+ }
616
+ , touchleave: function(evt) {
617
+ return that.handleMouseUp(evt);
618
+ }
619
+ , touchcancel: function(evt) {
620
+ return that.handleMouseUp(evt);
621
+ }
622
+ }
623
+
624
+ // Init custom events handler if available
625
+ if (this.options.customEventsHandler != null) { // jshint ignore:line
626
+ this.options.customEventsHandler.init({
627
+ svgElement: this.svg
628
+ , instance: this.getPublicInstance()
629
+ })
630
+
631
+ // Custom event handler may halt builtin listeners
632
+ var haltEventListeners = this.options.customEventsHandler.haltEventListeners
633
+ if (haltEventListeners && haltEventListeners.length) {
634
+ for (var i = haltEventListeners.length - 1; i >= 0; i--) {
635
+ if (this.eventListeners.hasOwnProperty(haltEventListeners[i])) {
636
+ delete this.eventListeners[haltEventListeners[i]]
637
+ }
638
+ }
639
+ }
640
+ }
641
+
642
+ // Bind eventListeners
643
+ for (var event in this.eventListeners) {
644
+ this.svg.addEventListener(event, this.eventListeners[event], false)
645
+ }
646
+
647
+ // Zoom using mouse wheel
648
+ if (this.options.mouseWheelZoomEnabled) {
649
+ this.options.mouseWheelZoomEnabled = false // set to false as enable will set it back to true
650
+ this.enableMouseWheelZoom()
651
+ }
652
+ }
653
+
654
+ /**
655
+ * Enable ability to zoom using mouse wheel
656
+ */
657
+ SvgPanZoom.prototype.enableMouseWheelZoom = function() {
658
+ if (!this.options.mouseWheelZoomEnabled) {
659
+ var that = this
660
+
661
+ // Mouse wheel listener
662
+ this.wheelListener = function(evt) {
663
+ return that.handleMouseWheel(evt);
664
+ }
665
+
666
+ // Bind wheelListener
667
+ Wheel.on(this.svg, this.wheelListener, false)
668
+
669
+ this.options.mouseWheelZoomEnabled = true
670
+ }
671
+ }
672
+
673
+ /**
674
+ * Disable ability to zoom using mouse wheel
675
+ */
676
+ SvgPanZoom.prototype.disableMouseWheelZoom = function() {
677
+ if (this.options.mouseWheelZoomEnabled) {
678
+ Wheel.off(this.svg, this.wheelListener, false)
679
+ this.options.mouseWheelZoomEnabled = false
680
+ }
681
+ }
682
+
683
+ /**
684
+ * Handle mouse wheel event
685
+ *
686
+ * @param {Event} evt
687
+ */
688
+ SvgPanZoom.prototype.handleMouseWheel = function(evt) {
689
+ if (!this.options.zoomEnabled || this.state !== 'none') {
690
+ return;
691
+ }
692
+
693
+ if (this.options.preventMouseEventsDefault){
694
+ if (evt.preventDefault) {
695
+ evt.preventDefault();
696
+ } else {
697
+ evt.returnValue = false;
698
+ }
699
+ }
700
+
701
+ // Default delta in case that deltaY is not available
702
+ var delta = evt.deltaY || 1
703
+ , timeDelta = Date.now() - this.lastMouseWheelEventTime
704
+ , divider = 3 + Math.max(0, 30 - timeDelta)
705
+
706
+ // Update cache
707
+ this.lastMouseWheelEventTime = Date.now()
708
+
709
+ // Make empirical adjustments for browsers that give deltaY in pixels (deltaMode=0)
710
+ if ('deltaMode' in evt && evt.deltaMode === 0 && evt.wheelDelta) {
711
+ delta = evt.deltaY === 0 ? 0 : Math.abs(evt.wheelDelta) / evt.deltaY
712
+ }
713
+
714
+ delta = -0.3 < delta && delta < 0.3 ? delta : (delta > 0 ? 1 : -1) * Math.log(Math.abs(delta) + 10) / divider
715
+
716
+ var inversedScreenCTM = this.svg.getScreenCTM().inverse()
717
+ , relativeMousePoint = SvgUtils.getEventPoint(evt, this.svg).matrixTransform(inversedScreenCTM)
718
+ , 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
719
+
720
+ this.zoomAtPoint(zoom, relativeMousePoint)
721
+ }
722
+
723
+ /**
724
+ * Zoom in at a SVG point
725
+ *
726
+ * @param {SVGPoint} point
727
+ * @param {Float} zoomScale Number representing how much to zoom
728
+ * @param {Boolean} zoomAbsolute Default false. If true, zoomScale is treated as an absolute value.
729
+ * Otherwise, zoomScale is treated as a multiplied (e.g. 1.10 would zoom in 10%)
730
+ */
731
+ SvgPanZoom.prototype.zoomAtPoint = function(zoomScale, point, zoomAbsolute) {
732
+ var originalState = this.viewport.getOriginalState()
733
+
734
+ if (!zoomAbsolute) {
735
+ // Fit zoomScale in set bounds
736
+ if (this.getZoom() * zoomScale < this.options.minZoom * originalState.zoom) {
737
+ zoomScale = (this.options.minZoom * originalState.zoom) / this.getZoom()
738
+ } else if (this.getZoom() * zoomScale > this.options.maxZoom * originalState.zoom) {
739
+ zoomScale = (this.options.maxZoom * originalState.zoom) / this.getZoom()
740
+ }
741
+ } else {
742
+ // Fit zoomScale in set bounds
743
+ zoomScale = Math.max(this.options.minZoom * originalState.zoom, Math.min(this.options.maxZoom * originalState.zoom, zoomScale))
744
+ // Find relative scale to achieve desired scale
745
+ zoomScale = zoomScale/this.getZoom()
746
+ }
747
+
748
+ var oldCTM = this.viewport.getCTM()
749
+ , relativePoint = point.matrixTransform(oldCTM.inverse())
750
+ , modifier = this.svg.createSVGMatrix().translate(relativePoint.x, relativePoint.y).scale(zoomScale).translate(-relativePoint.x, -relativePoint.y)
751
+ , newCTM = oldCTM.multiply(modifier)
752
+
753
+ if (newCTM.a !== oldCTM.a) {
754
+ this.viewport.setCTM(newCTM)
755
+ }
756
+ }
757
+
758
+ /**
759
+ * Zoom at center point
760
+ *
761
+ * @param {Float} scale
762
+ * @param {Boolean} absolute Marks zoom scale as relative or absolute
763
+ */
764
+ SvgPanZoom.prototype.zoom = function(scale, absolute) {
765
+ this.zoomAtPoint(scale, SvgUtils.getSvgCenterPoint(this.svg, this.width, this.height), absolute)
766
+ }
767
+
768
+ /**
769
+ * Zoom used by public instance
770
+ *
771
+ * @param {Float} scale
772
+ * @param {Boolean} absolute Marks zoom scale as relative or absolute
773
+ */
774
+ SvgPanZoom.prototype.publicZoom = function(scale, absolute) {
775
+ if (absolute) {
776
+ scale = this.computeFromRelativeZoom(scale)
777
+ }
778
+
779
+ this.zoom(scale, absolute)
780
+ }
781
+
782
+ /**
783
+ * Zoom at point used by public instance
784
+ *
785
+ * @param {Float} scale
786
+ * @param {SVGPoint|Object} point An object that has x and y attributes
787
+ * @param {Boolean} absolute Marks zoom scale as relative or absolute
788
+ */
789
+ SvgPanZoom.prototype.publicZoomAtPoint = function(scale, point, absolute) {
790
+ if (absolute) {
791
+ // Transform zoom into a relative value
792
+ scale = this.computeFromRelativeZoom(scale)
793
+ }
794
+
795
+ // If not a SVGPoint but has x and y than create a SVGPoint
796
+ if (Utils.getType(point) !== 'SVGPoint' && 'x' in point && 'y' in point) {
797
+ point = SvgUtils.createSVGPoint(this.svg, point.x, point.y)
798
+ } else {
799
+ throw new Error('Given point is invalid')
800
+ return
801
+ }
802
+
803
+ this.zoomAtPoint(scale, point, absolute)
804
+ }
805
+
806
+ /**
807
+ * Get zoom scale
808
+ *
809
+ * @return {Float} zoom scale
810
+ */
811
+ SvgPanZoom.prototype.getZoom = function() {
812
+ return this.viewport.getZoom()
813
+ }
814
+
815
+ /**
816
+ * Get zoom scale for public usage
817
+ *
818
+ * @return {Float} zoom scale
819
+ */
820
+ SvgPanZoom.prototype.getRelativeZoom = function() {
821
+ return this.viewport.getRelativeZoom()
822
+ }
823
+
824
+ /**
825
+ * Compute actual zoom from public zoom
826
+ *
827
+ * @param {Float} zoom
828
+ * @return {Float} zoom scale
829
+ */
830
+ SvgPanZoom.prototype.computeFromRelativeZoom = function(zoom) {
831
+ return zoom * this.viewport.getOriginalState().zoom
832
+ }
833
+
834
+ /**
835
+ * Set zoom to initial state
836
+ */
837
+ SvgPanZoom.prototype.resetZoom = function() {
838
+ var originalState = this.viewport.getOriginalState()
839
+
840
+ this.zoom(originalState.zoom, true);
841
+ }
842
+
843
+ /**
844
+ * Set pan to initial state
845
+ */
846
+ SvgPanZoom.prototype.resetPan = function() {
847
+ this.pan(this.viewport.getOriginalState());
848
+ }
849
+
850
+ /**
851
+ * Set pan and zoom to initial state
852
+ */
853
+ SvgPanZoom.prototype.reset = function() {
854
+ this.resetZoom()
855
+ this.resetPan()
856
+ }
857
+
858
+ /**
859
+ * Handle double click event
860
+ * See handleMouseDown() for alternate detection method
861
+ *
862
+ * @param {Event} evt
863
+ */
864
+ SvgPanZoom.prototype.handleDblClick = function(evt) {
865
+ if (this.options.preventMouseEventsDefault) {
866
+ if (evt.preventDefault) {
867
+ evt.preventDefault()
868
+ } else {
869
+ evt.returnValue = false
870
+ }
871
+ }
872
+
873
+ // Check if target was a control button
874
+ if (this.options.controlIconsEnabled) {
875
+ var targetClass = evt.target.getAttribute('class') || ''
876
+ if (targetClass.indexOf('svg-pan-zoom-control') > -1) {
877
+ return false
878
+ }
879
+ }
880
+
881
+ var zoomFactor
882
+
883
+ if (evt.shiftKey) {
884
+ zoomFactor = 1/((1 + this.options.zoomScaleSensitivity) * 2) // zoom out when shift key pressed
885
+ } else {
886
+ zoomFactor = (1 + this.options.zoomScaleSensitivity) * 2
887
+ }
888
+
889
+ var point = SvgUtils.getEventPoint(evt, this.svg).matrixTransform(this.svg.getScreenCTM().inverse())
890
+ this.zoomAtPoint(zoomFactor, point)
891
+ }
892
+
893
+ /**
894
+ * Handle click event
895
+ *
896
+ * @param {Event} evt
897
+ */
898
+ SvgPanZoom.prototype.handleMouseDown = function(evt, prevEvt) {
899
+ if (this.options.preventMouseEventsDefault) {
900
+ if (evt.preventDefault) {
901
+ evt.preventDefault()
902
+ } else {
903
+ evt.returnValue = false
904
+ }
905
+ }
906
+
907
+ Utils.mouseAndTouchNormalize(evt, this.svg)
908
+
909
+ // Double click detection; more consistent than ondblclick
910
+ if (this.options.dblClickZoomEnabled && Utils.isDblClick(evt, prevEvt)){
911
+ this.handleDblClick(evt)
912
+ } else {
913
+ // Pan mode
914
+ this.state = 'pan'
915
+ this.firstEventCTM = this.viewport.getCTM()
916
+ this.stateOrigin = SvgUtils.getEventPoint(evt, this.svg).matrixTransform(this.firstEventCTM.inverse())
917
+ }
918
+ }
919
+
920
+ /**
921
+ * Handle mouse move event
922
+ *
923
+ * @param {Event} evt
924
+ */
925
+ SvgPanZoom.prototype.handleMouseMove = function(evt) {
926
+ if (this.options.preventMouseEventsDefault) {
927
+ if (evt.preventDefault) {
928
+ evt.preventDefault()
929
+ } else {
930
+ evt.returnValue = false
931
+ }
932
+ }
933
+
934
+ if (this.state === 'pan' && this.options.panEnabled) {
935
+ // Pan mode
936
+ var point = SvgUtils.getEventPoint(evt, this.svg).matrixTransform(this.firstEventCTM.inverse())
937
+ , viewportCTM = this.firstEventCTM.translate(point.x - this.stateOrigin.x, point.y - this.stateOrigin.y)
938
+
939
+ this.viewport.setCTM(viewportCTM)
940
+ }
941
+ }
942
+
943
+ /**
944
+ * Handle mouse button release event
945
+ *
946
+ * @param {Event} evt
947
+ */
948
+ SvgPanZoom.prototype.handleMouseUp = function(evt) {
949
+ if (this.options.preventMouseEventsDefault) {
950
+ if (evt.preventDefault) {
951
+ evt.preventDefault()
952
+ } else {
953
+ evt.returnValue = false
954
+ }
955
+ }
956
+
957
+ if (this.state === 'pan') {
958
+ // Quit pan mode
959
+ this.state = 'none'
960
+ }
961
+ }
962
+
963
+ /**
964
+ * Adjust viewport size (only) so it will fit in SVG
965
+ * Does not center image
966
+ */
967
+ SvgPanZoom.prototype.fit = function() {
968
+ var viewBox = this.viewport.getViewBox()
969
+ , newScale = Math.min(this.width/viewBox.width, this.height/viewBox.height)
970
+
971
+ this.zoom(newScale, true)
972
+ }
973
+
974
+ /**
975
+ * Adjust viewport size (only) so it will contain the SVG
976
+ * Does not center image
977
+ */
978
+ SvgPanZoom.prototype.contain = function() {
979
+ var viewBox = this.viewport.getViewBox()
980
+ , newScale = Math.max(this.width/viewBox.width, this.height/viewBox.height)
981
+
982
+ this.zoom(newScale, true)
983
+ }
984
+
985
+ /**
986
+ * Adjust viewport pan (only) so it will be centered in SVG
987
+ * Does not zoom/fit/contain image
988
+ */
989
+ SvgPanZoom.prototype.center = function() {
990
+ var viewBox = this.viewport.getViewBox()
991
+ , offsetX = (this.width - (viewBox.width + viewBox.x * 2) * this.getZoom()) * 0.5
992
+ , offsetY = (this.height - (viewBox.height + viewBox.y * 2) * this.getZoom()) * 0.5
993
+
994
+ this.getPublicInstance().pan({x: offsetX, y: offsetY})
995
+ }
996
+
997
+ /**
998
+ * Update content cached BorderBox
999
+ * Use when viewport contents change
1000
+ */
1001
+ SvgPanZoom.prototype.updateBBox = function() {
1002
+ this.viewport.recacheViewBox()
1003
+ }
1004
+
1005
+ /**
1006
+ * Pan to a rendered position
1007
+ *
1008
+ * @param {Object} point {x: 0, y: 0}
1009
+ */
1010
+ SvgPanZoom.prototype.pan = function(point) {
1011
+ var viewportCTM = this.viewport.getCTM()
1012
+ viewportCTM.e = point.x
1013
+ viewportCTM.f = point.y
1014
+ this.viewport.setCTM(viewportCTM)
1015
+ }
1016
+
1017
+ /**
1018
+ * Relatively pan the graph by a specified rendered position vector
1019
+ *
1020
+ * @param {Object} point {x: 0, y: 0}
1021
+ */
1022
+ SvgPanZoom.prototype.panBy = function(point) {
1023
+ var viewportCTM = this.viewport.getCTM()
1024
+ viewportCTM.e += point.x
1025
+ viewportCTM.f += point.y
1026
+ this.viewport.setCTM(viewportCTM)
1027
+ }
1028
+
1029
+ /**
1030
+ * Get pan vector
1031
+ *
1032
+ * @return {Object} {x: 0, y: 0}
1033
+ */
1034
+ SvgPanZoom.prototype.getPan = function() {
1035
+ var state = this.viewport.getState()
1036
+
1037
+ return {x: state.x, y: state.y}
1038
+ }
1039
+
1040
+ /**
1041
+ * Recalculates cached svg dimensions and controls position
1042
+ */
1043
+ SvgPanZoom.prototype.resize = function() {
1044
+ // Get dimensions
1045
+ var boundingClientRectNormalized = SvgUtils.getBoundingClientRectNormalized(this.svg)
1046
+ this.width = boundingClientRectNormalized.width
1047
+ this.height = boundingClientRectNormalized.height
1048
+
1049
+ // Reposition control icons by re-enabling them
1050
+ if (this.options.controlIconsEnabled) {
1051
+ this.getPublicInstance().disableControlIcons()
1052
+ this.getPublicInstance().enableControlIcons()
1053
+ }
1054
+ }
1055
+
1056
+ /**
1057
+ * Unbind mouse events, free callbacks and destroy public instance
1058
+ */
1059
+ SvgPanZoom.prototype.destroy = function() {
1060
+ var that = this
1061
+
1062
+ // Free callbacks
1063
+ this.beforeZoom = null
1064
+ this.onZoom = null
1065
+ this.beforePan = null
1066
+ this.onPan = null
1067
+
1068
+ // Destroy custom event handlers
1069
+ if (this.options.customEventsHandler != null) { // jshint ignore:line
1070
+ this.options.customEventsHandler.destroy({
1071
+ svgElement: this.svg
1072
+ , instance: this.getPublicInstance()
1073
+ })
1074
+ }
1075
+
1076
+ // Unbind eventListeners
1077
+ for (var event in this.eventListeners) {
1078
+ this.svg.removeEventListener(event, this.eventListeners[event], false)
1079
+ }
1080
+
1081
+ // Unbind wheelListener
1082
+ this.disableMouseWheelZoom()
1083
+
1084
+ // Remove control icons
1085
+ this.getPublicInstance().disableControlIcons()
1086
+
1087
+ // Reset zoom and pan
1088
+ this.reset()
1089
+
1090
+ // Remove instance from instancesStore
1091
+ instancesStore = instancesStore.filter(function(instance){
1092
+ return instance.svg !== that.svg
1093
+ })
1094
+
1095
+ // Delete options and its contents
1096
+ delete this.options
1097
+
1098
+ // Destroy public instance and rewrite getPublicInstance
1099
+ delete this.publicInstance
1100
+ delete this.pi
1101
+ this.getPublicInstance = function(){
1102
+ return null
1103
+ }
1104
+ }
1105
+
1106
+ /**
1107
+ * Returns a public instance object
1108
+ *
1109
+ * @return {Object} Public instance object
1110
+ */
1111
+ SvgPanZoom.prototype.getPublicInstance = function() {
1112
+ var that = this
1113
+
1114
+ // Create cache
1115
+ if (!this.publicInstance) {
1116
+ this.publicInstance = this.pi = {
1117
+ // Pan
1118
+ enablePan: function() {that.options.panEnabled = true; return that.pi}
1119
+ , disablePan: function() {that.options.panEnabled = false; return that.pi}
1120
+ , isPanEnabled: function() {return !!that.options.panEnabled}
1121
+ , pan: function(point) {that.pan(point); return that.pi}
1122
+ , panBy: function(point) {that.panBy(point); return that.pi}
1123
+ , getPan: function() {return that.getPan()}
1124
+ // Pan event
1125
+ , setBeforePan: function(fn) {that.options.beforePan = fn === null ? null : Utils.proxy(fn, that.publicInstance); return that.pi}
1126
+ , setOnPan: function(fn) {that.options.onPan = fn === null ? null : Utils.proxy(fn, that.publicInstance); return that.pi}
1127
+ // Zoom and Control Icons
1128
+ , enableZoom: function() {that.options.zoomEnabled = true; return that.pi}
1129
+ , disableZoom: function() {that.options.zoomEnabled = false; return that.pi}
1130
+ , isZoomEnabled: function() {return !!that.options.zoomEnabled}
1131
+ , enableControlIcons: function() {
1132
+ if (!that.options.controlIconsEnabled) {
1133
+ that.options.controlIconsEnabled = true
1134
+ ControlIcons.enable(that)
1135
+ }
1136
+ return that.pi
1137
+ }
1138
+ , disableControlIcons: function() {
1139
+ if (that.options.controlIconsEnabled) {
1140
+ that.options.controlIconsEnabled = false;
1141
+ ControlIcons.disable(that)
1142
+ }
1143
+ return that.pi
1144
+ }
1145
+ , isControlIconsEnabled: function() {return !!that.options.controlIconsEnabled}
1146
+ // Double click zoom
1147
+ , enableDblClickZoom: function() {that.options.dblClickZoomEnabled = true; return that.pi}
1148
+ , disableDblClickZoom: function() {that.options.dblClickZoomEnabled = false; return that.pi}
1149
+ , isDblClickZoomEnabled: function() {return !!that.options.dblClickZoomEnabled}
1150
+ // Mouse wheel zoom
1151
+ , enableMouseWheelZoom: function() {that.enableMouseWheelZoom(); return that.pi}
1152
+ , disableMouseWheelZoom: function() {that.disableMouseWheelZoom(); return that.pi}
1153
+ , isMouseWheelZoomEnabled: function() {return !!that.options.mouseWheelZoomEnabled}
1154
+ // Zoom scale and bounds
1155
+ , setZoomScaleSensitivity: function(scale) {that.options.zoomScaleSensitivity = scale; return that.pi}
1156
+ , setMinZoom: function(zoom) {that.options.minZoom = zoom; return that.pi}
1157
+ , setMaxZoom: function(zoom) {that.options.maxZoom = zoom; return that.pi}
1158
+ // Zoom event
1159
+ , setBeforeZoom: function(fn) {that.options.beforeZoom = fn === null ? null : Utils.proxy(fn, that.publicInstance); return that.pi}
1160
+ , setOnZoom: function(fn) {that.options.onZoom = fn === null ? null : Utils.proxy(fn, that.publicInstance); return that.pi}
1161
+ // Zooming
1162
+ , zoom: function(scale) {that.publicZoom(scale, true); return that.pi}
1163
+ , zoomBy: function(scale) {that.publicZoom(scale, false); return that.pi}
1164
+ , zoomAtPoint: function(scale, point) {that.publicZoomAtPoint(scale, point, true); return that.pi}
1165
+ , zoomAtPointBy: function(scale, point) {that.publicZoomAtPoint(scale, point, false); return that.pi}
1166
+ , zoomIn: function() {this.zoomBy(1 + that.options.zoomScaleSensitivity); return that.pi}
1167
+ , zoomOut: function() {this.zoomBy(1 / (1 + that.options.zoomScaleSensitivity)); return that.pi}
1168
+ , getZoom: function() {return that.getRelativeZoom()}
1169
+ // Reset
1170
+ , resetZoom: function() {that.resetZoom(); return that.pi}
1171
+ , resetPan: function() {that.resetPan(); return that.pi}
1172
+ , reset: function() {that.reset(); return that.pi}
1173
+ // Fit, Contain and Center
1174
+ , fit: function() {that.fit(); return that.pi}
1175
+ , contain: function() {that.contain(); return that.pi}
1176
+ , center: function() {that.center(); return that.pi}
1177
+ // Size and Resize
1178
+ , updateBBox: function() {that.updateBBox(); return that.pi}
1179
+ , resize: function() {that.resize(); return that.pi}
1180
+ , getSizes: function() {
1181
+ return {
1182
+ width: that.width
1183
+ , height: that.height
1184
+ , realZoom: that.getZoom()
1185
+ , viewBox: that.viewport.getViewBox()
1186
+ }
1187
+ }
1188
+ // Destroy
1189
+ , destroy: function() {that.destroy(); return that.pi}
1190
+ }
1191
+ }
1192
+
1193
+ return this.publicInstance
1194
+ }
1195
+
1196
+ /**
1197
+ * Stores pairs of instances of SvgPanZoom and SVG
1198
+ * Each pair is represented by an object {svg: SVGSVGElement, instance: SvgPanZoom}
1199
+ *
1200
+ * @type {Array}
1201
+ */
1202
+ var instancesStore = []
1203
+
1204
+ var svgPanZoom = function(elementOrSelector, options){
1205
+ var svg = Utils.getSvg(elementOrSelector)
1206
+
1207
+ if (svg === null) {
1208
+ return null
1209
+ } else {
1210
+ // Look for existent instance
1211
+ for(var i = instancesStore.length - 1; i >= 0; i--) {
1212
+ if (instancesStore[i].svg === svg) {
1213
+ return instancesStore[i].instance.getPublicInstance()
1214
+ }
1215
+ }
1216
+
1217
+ // If instance not found - create one
1218
+ instancesStore.push({
1219
+ svg: svg
1220
+ , instance: new SvgPanZoom(svg, options)
1221
+ })
1222
+
1223
+ // Return just pushed instance
1224
+ return instancesStore[instancesStore.length - 1].instance.getPublicInstance()
1225
+ }
1226
+ }
1227
+
1228
+ module.exports = svgPanZoom;
1229
+
1230
+ },{"./control-icons":2,"./shadow-viewport":3,"./svg-utilities":5,"./uniwheel":6,"./utilities":7}],5:[function(require,module,exports){
1231
+ var Utils = require('./utilities')
1232
+ , _browser = 'unknown'
1233
+ ;
1234
+
1235
+ // http://stackoverflow.com/questions/9847580/how-to-detect-safari-chrome-ie-firefox-and-opera-browser
1236
+ if (/*@cc_on!@*/false || !!document.documentMode) { // internet explorer
1237
+ _browser = 'ie';
1238
+ }
1239
+
1240
+ module.exports = {
1241
+ svgNS: 'http://www.w3.org/2000/svg'
1242
+ , xmlNS: 'http://www.w3.org/XML/1998/namespace'
1243
+ , xmlnsNS: 'http://www.w3.org/2000/xmlns/'
1244
+ , xlinkNS: 'http://www.w3.org/1999/xlink'
1245
+ , evNS: 'http://www.w3.org/2001/xml-events'
1246
+
1247
+ /**
1248
+ * Get svg dimensions: width and height
1249
+ *
1250
+ * @param {SVGSVGElement} svg
1251
+ * @return {Object} {width: 0, height: 0}
1252
+ */
1253
+ , getBoundingClientRectNormalized: function(svg) {
1254
+ if (svg.clientWidth && svg.clientHeight) {
1255
+ return {width: svg.clientWidth, height: svg.clientHeight}
1256
+ } else if (!!svg.getBoundingClientRect()) {
1257
+ return svg.getBoundingClientRect();
1258
+ } else {
1259
+ throw new Error('Cannot get BoundingClientRect for SVG.');
1260
+ }
1261
+ }
1262
+
1263
+ /**
1264
+ * Gets g element with class of "viewport" or creates it if it doesn't exist
1265
+ *
1266
+ * @param {SVGSVGElement} svg
1267
+ * @return {SVGElement} g (group) element
1268
+ */
1269
+ , getOrCreateViewport: function(svg, selector) {
1270
+ var viewport = null
1271
+
1272
+ if (Utils.isElement(selector)) {
1273
+ viewport = selector
1274
+ } else {
1275
+ viewport = svg.querySelector(selector)
1276
+ }
1277
+
1278
+ // Check if there is just one main group in SVG
1279
+ if (!viewport) {
1280
+ var childNodes = Array.prototype.slice.call(svg.childNodes || svg.children).filter(function(el){
1281
+ return el.nodeName !== 'defs' && el.nodeName !== '#text'
1282
+ })
1283
+
1284
+ // Node name should be SVGGElement and should have no transform attribute
1285
+ // Groups with transform are not used as viewport because it involves parsing of all transform possibilities
1286
+ if (childNodes.length === 1 && childNodes[0].nodeName === 'g' && childNodes[0].getAttribute('transform') === null) {
1287
+ viewport = childNodes[0]
1288
+ }
1289
+ }
1290
+
1291
+ // If no favorable group element exists then create one
1292
+ if (!viewport) {
1293
+ var viewportId = 'viewport-' + new Date().toISOString().replace(/\D/g, '');
1294
+ viewport = document.createElementNS(this.svgNS, 'g');
1295
+ viewport.setAttribute('id', viewportId);
1296
+
1297
+ // Internet Explorer (all versions?) can't use childNodes, but other browsers prefer (require?) using childNodes
1298
+ var svgChildren = svg.childNodes || svg.children;
1299
+ if (!!svgChildren && svgChildren.length > 0) {
1300
+ for (var i = svgChildren.length; i > 0; i--) {
1301
+ // Move everything into viewport except defs
1302
+ if (svgChildren[svgChildren.length - i].nodeName !== 'defs') {
1303
+ viewport.appendChild(svgChildren[svgChildren.length - i]);
1304
+ }
1305
+ }
1306
+ }
1307
+ svg.appendChild(viewport);
1308
+ }
1309
+
1310
+ // Parse class names
1311
+ var classNames = [];
1312
+ if (viewport.getAttribute('class')) {
1313
+ classNames = viewport.getAttribute('class').split(' ')
1314
+ }
1315
+
1316
+ // Set class (if not set already)
1317
+ if (!~classNames.indexOf('svg-pan-zoom_viewport')) {
1318
+ classNames.push('svg-pan-zoom_viewport')
1319
+ viewport.setAttribute('class', classNames.join(' '))
1320
+ }
1321
+
1322
+ return viewport
1323
+ }
1324
+
1325
+ /**
1326
+ * Set SVG attributes
1327
+ *
1328
+ * @param {SVGSVGElement} svg
1329
+ */
1330
+ , setupSvgAttributes: function(svg) {
1331
+ // Setting default attributes
1332
+ svg.setAttribute('xmlns', this.svgNS);
1333
+ svg.setAttributeNS(this.xmlnsNS, 'xmlns:xlink', this.xlinkNS);
1334
+ svg.setAttributeNS(this.xmlnsNS, 'xmlns:ev', this.evNS);
1335
+
1336
+ // Needed for Internet Explorer, otherwise the viewport overflows
1337
+ if (svg.parentNode !== null) {
1338
+ var style = svg.getAttribute('style') || '';
1339
+ if (style.toLowerCase().indexOf('overflow') === -1) {
1340
+ svg.setAttribute('style', 'overflow: hidden; ' + style);
1341
+ }
1342
+ }
1343
+ }
1344
+
1345
+ /**
1346
+ * How long Internet Explorer takes to finish updating its display (ms).
1347
+ */
1348
+ , internetExplorerRedisplayInterval: 300
1349
+
1350
+ /**
1351
+ * Forces the browser to redisplay all SVG elements that rely on an
1352
+ * element defined in a 'defs' section. It works globally, for every
1353
+ * available defs element on the page.
1354
+ * The throttling is intentionally global.
1355
+ *
1356
+ * This is only needed for IE. It is as a hack to make markers (and 'use' elements?)
1357
+ * visible after pan/zoom when there are multiple SVGs on the page.
1358
+ * See bug report: https://connect.microsoft.com/IE/feedback/details/781964/
1359
+ * also see svg-pan-zoom issue: https://github.com/ariutta/svg-pan-zoom/issues/62
1360
+ */
1361
+ , refreshDefsGlobal: Utils.throttle(function() {
1362
+ var allDefs = document.querySelectorAll('defs');
1363
+ var allDefsCount = allDefs.length;
1364
+ for (var i = 0; i < allDefsCount; i++) {
1365
+ var thisDefs = allDefs[i];
1366
+ thisDefs.parentNode.insertBefore(thisDefs, thisDefs);
1367
+ }
1368
+ }, this.internetExplorerRedisplayInterval)
1369
+
1370
+ /**
1371
+ * Sets the current transform matrix of an element
1372
+ *
1373
+ * @param {SVGElement} element
1374
+ * @param {SVGMatrix} matrix CTM
1375
+ * @param {SVGElement} defs
1376
+ */
1377
+ , setCTM: function(element, matrix, defs) {
1378
+ var that = this
1379
+ , s = 'matrix(' + matrix.a + ',' + matrix.b + ',' + matrix.c + ',' + matrix.d + ',' + matrix.e + ',' + matrix.f + ')';
1380
+
1381
+ element.setAttributeNS(null, 'transform', s);
1382
+
1383
+ // IE has a bug that makes markers disappear on zoom (when the matrix "a" and/or "d" elements change)
1384
+ // see http://stackoverflow.com/questions/17654578/svg-marker-does-not-work-in-ie9-10
1385
+ // and http://srndolha.wordpress.com/2013/11/25/svg-line-markers-may-disappear-in-internet-explorer-11/
1386
+ if (_browser === 'ie' && !!defs) {
1387
+ // this refresh is intended for redisplaying the SVG during zooming
1388
+ defs.parentNode.insertBefore(defs, defs);
1389
+ // this refresh is intended for redisplaying the other SVGs on a page when panning a given SVG
1390
+ // it is also needed for the given SVG itself, on zoomEnd, if the SVG contains any markers that
1391
+ // are located under any other element(s).
1392
+ window.setTimeout(function() {
1393
+ that.refreshDefsGlobal();
1394
+ }, that.internetExplorerRedisplayInterval);
1395
+ }
1396
+ }
1397
+
1398
+ /**
1399
+ * Instantiate an SVGPoint object with given event coordinates
1400
+ *
1401
+ * @param {Event} evt
1402
+ * @param {SVGSVGElement} svg
1403
+ * @return {SVGPoint} point
1404
+ */
1405
+ , getEventPoint: function(evt, svg) {
1406
+ var point = svg.createSVGPoint()
1407
+
1408
+ Utils.mouseAndTouchNormalize(evt, svg)
1409
+
1410
+ point.x = evt.clientX
1411
+ point.y = evt.clientY
1412
+
1413
+ return point
1414
+ }
1415
+
1416
+ /**
1417
+ * Get SVG center point
1418
+ *
1419
+ * @param {SVGSVGElement} svg
1420
+ * @return {SVGPoint}
1421
+ */
1422
+ , getSvgCenterPoint: function(svg, width, height) {
1423
+ return this.createSVGPoint(svg, width / 2, height / 2)
1424
+ }
1425
+
1426
+ /**
1427
+ * Create a SVGPoint with given x and y
1428
+ *
1429
+ * @param {SVGSVGElement} svg
1430
+ * @param {Number} x
1431
+ * @param {Number} y
1432
+ * @return {SVGPoint}
1433
+ */
1434
+ , createSVGPoint: function(svg, x, y) {
1435
+ var point = svg.createSVGPoint()
1436
+ point.x = x
1437
+ point.y = y
1438
+
1439
+ return point
1440
+ }
1441
+ }
1442
+
1443
+ },{"./utilities":7}],6:[function(require,module,exports){
1444
+ // uniwheel 0.1.2 (customized)
1445
+ // A unified cross browser mouse wheel event handler
1446
+ // https://github.com/teemualap/uniwheel
1447
+
1448
+ module.exports = (function(){
1449
+
1450
+ //Full details: https://developer.mozilla.org/en-US/docs/Web/Reference/Events/wheel
1451
+
1452
+ var prefix = "", _addEventListener, _removeEventListener, onwheel, support, fns = [];
1453
+
1454
+ // detect event model
1455
+ if ( window.addEventListener ) {
1456
+ _addEventListener = "addEventListener";
1457
+ _removeEventListener = "removeEventListener";
1458
+ } else {
1459
+ _addEventListener = "attachEvent";
1460
+ _removeEventListener = "detachEvent";
1461
+ prefix = "on";
1462
+ }
1463
+
1464
+ // detect available wheel event
1465
+ support = "onwheel" in document.createElement("div") ? "wheel" : // Modern browsers support "wheel"
1466
+ document.onmousewheel !== undefined ? "mousewheel" : // Webkit and IE support at least "mousewheel"
1467
+ "DOMMouseScroll"; // let's assume that remaining browsers are older Firefox
1468
+
1469
+
1470
+ function createCallback(element,callback,capture) {
1471
+
1472
+ var fn = function(originalEvent) {
1473
+
1474
+ !originalEvent && ( originalEvent = window.event );
1475
+
1476
+ // create a normalized event object
1477
+ var event = {
1478
+ // keep a ref to the original event object
1479
+ originalEvent: originalEvent,
1480
+ target: originalEvent.target || originalEvent.srcElement,
1481
+ type: "wheel",
1482
+ deltaMode: originalEvent.type == "MozMousePixelScroll" ? 0 : 1,
1483
+ deltaX: 0,
1484
+ delatZ: 0,
1485
+ preventDefault: function() {
1486
+ originalEvent.preventDefault ?
1487
+ originalEvent.preventDefault() :
1488
+ originalEvent.returnValue = false;
1489
+ }
1490
+ };
1491
+
1492
+ // calculate deltaY (and deltaX) according to the event
1493
+ if ( support == "mousewheel" ) {
1494
+ event.deltaY = - 1/40 * originalEvent.wheelDelta;
1495
+ // Webkit also support wheelDeltaX
1496
+ originalEvent.wheelDeltaX && ( event.deltaX = - 1/40 * originalEvent.wheelDeltaX );
1497
+ } else {
1498
+ event.deltaY = originalEvent.detail;
1499
+ }
1500
+
1501
+ // it's time to fire the callback
1502
+ return callback( event );
1503
+
1504
+ };
1505
+
1506
+ fns.push({
1507
+ element: element,
1508
+ fn: fn,
1509
+ capture: capture
1510
+ });
1511
+
1512
+ return fn;
1513
+ }
1514
+
1515
+ function getCallback(element,capture) {
1516
+ for (var i = 0; i < fns.length; i++) {
1517
+ if (fns[i].element === element && fns[i].capture === capture) {
1518
+ return fns[i].fn;
1519
+ }
1520
+ }
1521
+ return function(){};
1522
+ }
1523
+
1524
+ function removeCallback(element,capture) {
1525
+ for (var i = 0; i < fns.length; i++) {
1526
+ if (fns[i].element === element && fns[i].capture === capture) {
1527
+ return fns.splice(i,1);
1528
+ }
1529
+ }
1530
+ }
1531
+
1532
+ function _addWheelListener( elem, eventName, callback, useCapture ) {
1533
+
1534
+ var cb;
1535
+
1536
+ if (support === "wheel") {
1537
+ cb = callback;
1538
+ } else {
1539
+ cb = createCallback(elem,callback,useCapture);
1540
+ }
1541
+
1542
+ elem[ _addEventListener ]( prefix + eventName, cb, useCapture || false );
1543
+
1544
+ }
1545
+
1546
+ function _removeWheelListener( elem, eventName, callback, useCapture ) {
1547
+
1548
+ if (support === "wheel") {
1549
+ cb = callback;
1550
+ } else {
1551
+ cb = getCallback(elem,useCapture);
1552
+ }
1553
+
1554
+ elem[ _removeEventListener ]( prefix + eventName, cb, useCapture || false );
1555
+
1556
+ removeCallback(elem,useCapture);
1557
+
1558
+ }
1559
+
1560
+ function addWheelListener( elem, callback, useCapture ) {
1561
+ _addWheelListener( elem, support, callback, useCapture );
1562
+
1563
+ // handle MozMousePixelScroll in older Firefox
1564
+ if( support == "DOMMouseScroll" ) {
1565
+ _addWheelListener( elem, "MozMousePixelScroll", callback, useCapture);
1566
+ }
1567
+ }
1568
+
1569
+ function removeWheelListener(elem,callback,useCapture){
1570
+ _removeWheelListener(elem,support,callback,useCapture);
1571
+
1572
+ // handle MozMousePixelScroll in older Firefox
1573
+ if( support == "DOMMouseScroll" ) {
1574
+ _removeWheelListener(elem, "MozMousePixelScroll", callback, useCapture);
1575
+ }
1576
+ }
1577
+
1578
+ return {
1579
+ on: addWheelListener,
1580
+ off: removeWheelListener
1581
+ };
1582
+
1583
+ })();
1584
+
1585
+ },{}],7:[function(require,module,exports){
1586
+ module.exports = {
1587
+ /**
1588
+ * Extends an object
1589
+ *
1590
+ * @param {Object} target object to extend
1591
+ * @param {Object} source object to take properties from
1592
+ * @return {Object} extended object
1593
+ */
1594
+ extend: function(target, source) {
1595
+ target = target || {};
1596
+ for (var prop in source) {
1597
+ // Go recursively
1598
+ if (this.isObject(source[prop])) {
1599
+ target[prop] = this.extend(target[prop], source[prop])
1600
+ } else {
1601
+ target[prop] = source[prop]
1602
+ }
1603
+ }
1604
+ return target;
1605
+ }
1606
+
1607
+ /**
1608
+ * Checks if an object is a DOM element
1609
+ *
1610
+ * @param {Object} o HTML element or String
1611
+ * @return {Boolean} returns true if object is a DOM element
1612
+ */
1613
+ , isElement: function(o){
1614
+ return (
1615
+ o instanceof HTMLElement || o instanceof SVGElement || o instanceof SVGSVGElement || //DOM2
1616
+ (o && typeof o === 'object' && o !== null && o.nodeType === 1 && typeof o.nodeName === 'string')
1617
+ );
1618
+ }
1619
+
1620
+ /**
1621
+ * Checks if an object is an Object
1622
+ *
1623
+ * @param {Object} o Object
1624
+ * @return {Boolean} returns true if object is an Object
1625
+ */
1626
+ , isObject: function(o){
1627
+ return Object.prototype.toString.call(o) === '[object Object]';
1628
+ }
1629
+
1630
+ /**
1631
+ * Checks if variable is Number
1632
+ *
1633
+ * @param {Integer|Float} n
1634
+ * @return {Boolean} returns true if variable is Number
1635
+ */
1636
+ , isNumber: function(n) {
1637
+ return !isNaN(parseFloat(n)) && isFinite(n);
1638
+ }
1639
+
1640
+ /**
1641
+ * Search for an SVG element
1642
+ *
1643
+ * @param {Object|String} elementOrSelector DOM Element or selector String
1644
+ * @return {Object|Null} SVG or null
1645
+ */
1646
+ , getSvg: function(elementOrSelector) {
1647
+ var element
1648
+ , svg;
1649
+
1650
+ if (!this.isElement(elementOrSelector)) {
1651
+ // If selector provided
1652
+ if (typeof elementOrSelector === 'string' || elementOrSelector instanceof String) {
1653
+ // Try to find the element
1654
+ element = document.querySelector(elementOrSelector)
1655
+
1656
+ if (!element) {
1657
+ throw new Error('Provided selector did not find any elements. Selector: ' + elementOrSelector)
1658
+ return null
1659
+ }
1660
+ } else {
1661
+ throw new Error('Provided selector is not an HTML object nor String')
1662
+ return null
1663
+ }
1664
+ } else {
1665
+ element = elementOrSelector
1666
+ }
1667
+
1668
+ if (element.tagName.toLowerCase() === 'svg') {
1669
+ svg = element;
1670
+ } else {
1671
+ if (element.tagName.toLowerCase() === 'object') {
1672
+ svg = element.contentDocument.documentElement;
1673
+ } else {
1674
+ if (element.tagName.toLowerCase() === 'embed') {
1675
+ svg = element.getSVGDocument().documentElement;
1676
+ } else {
1677
+ if (element.tagName.toLowerCase() === 'img') {
1678
+ throw new Error('Cannot script an SVG in an "img" element. Please use an "object" element or an in-line SVG.');
1679
+ } else {
1680
+ throw new Error('Cannot get SVG.');
1681
+ }
1682
+ return null
1683
+ }
1684
+ }
1685
+ }
1686
+
1687
+ return svg
1688
+ }
1689
+
1690
+ /**
1691
+ * Attach a given context to a function
1692
+ * @param {Function} fn Function
1693
+ * @param {Object} context Context
1694
+ * @return {Function} Function with certain context
1695
+ */
1696
+ , proxy: function(fn, context) {
1697
+ return function() {
1698
+ return fn.apply(context, arguments)
1699
+ }
1700
+ }
1701
+
1702
+ /**
1703
+ * Returns object type
1704
+ * Uses toString that returns [object SVGPoint]
1705
+ * And than parses object type from string
1706
+ *
1707
+ * @param {Object} o Any object
1708
+ * @return {String} Object type
1709
+ */
1710
+ , getType: function(o) {
1711
+ return Object.prototype.toString.apply(o).replace(/^\[object\s/, '').replace(/\]$/, '')
1712
+ }
1713
+
1714
+ /**
1715
+ * If it is a touch event than add clientX and clientY to event object
1716
+ *
1717
+ * @param {Event} evt
1718
+ * @param {SVGSVGElement} svg
1719
+ */
1720
+ , mouseAndTouchNormalize: function(evt, svg) {
1721
+ // If no cilentX and but touch objects are available
1722
+ if (evt.clientX === void 0 || evt.clientX === null) {
1723
+ // Fallback
1724
+ evt.clientX = 0
1725
+ evt.clientY = 0
1726
+
1727
+ // If it is a touch event
1728
+ if (evt.changedTouches !== void 0 && evt.changedTouches.length) {
1729
+ // If touch event has changedTouches
1730
+ if (evt.changedTouches[0].clientX !== void 0) {
1731
+ evt.clientX = evt.changedTouches[0].clientX
1732
+ evt.clientY = evt.changedTouches[0].clientY
1733
+ }
1734
+ // If changedTouches has pageX attribute
1735
+ else if (evt.changedTouches[0].pageX !== void 0) {
1736
+ var rect = svg.getBoundingClientRect();
1737
+
1738
+ evt.clientX = evt.changedTouches[0].pageX - rect.left
1739
+ evt.clientY = evt.changedTouches[0].pageY - rect.top
1740
+ }
1741
+ // If it is a custom event
1742
+ } else if (evt.originalEvent !== void 0) {
1743
+ if (evt.originalEvent.clientX !== void 0) {
1744
+ evt.clientX = evt.originalEvent.clientX
1745
+ evt.clientY = evt.originalEvent.clientY
1746
+ }
1747
+ }
1748
+ }
1749
+ }
1750
+
1751
+ /**
1752
+ * Check if an event is a double click/tap
1753
+ * TODO: For touch gestures use a library (hammer.js) that takes in account other events
1754
+ * (touchmove and touchend). It should take in account tap duration and traveled distance
1755
+ *
1756
+ * @param {Event} evt
1757
+ * @param {Event} prevEvt Previous Event
1758
+ * @return {Boolean}
1759
+ */
1760
+ , isDblClick: function(evt, prevEvt) {
1761
+ // Double click detected by browser
1762
+ if (evt.detail === 2) {
1763
+ return true;
1764
+ }
1765
+ // Try to compare events
1766
+ else if (prevEvt !== void 0 && prevEvt !== null) {
1767
+ var timeStampDiff = evt.timeStamp - prevEvt.timeStamp // should be lower than 250 ms
1768
+ , touchesDistance = Math.sqrt(Math.pow(evt.clientX - prevEvt.clientX, 2) + Math.pow(evt.clientY - prevEvt.clientY, 2))
1769
+
1770
+ return timeStampDiff < 250 && touchesDistance < 10
1771
+ }
1772
+
1773
+ // Nothing found
1774
+ return false;
1775
+ }
1776
+
1777
+ /**
1778
+ * Returns current timestamp as an integer
1779
+ *
1780
+ * @return {Number}
1781
+ */
1782
+ , now: Date.now || function() {
1783
+ return new Date().getTime();
1784
+ }
1785
+
1786
+ // From underscore.
1787
+ // Returns a function, that, when invoked, will only be triggered at most once
1788
+ // during a given window of time. Normally, the throttled function will run
1789
+ // as much as it can, without ever going more than once per `wait` duration;
1790
+ // but if you'd like to disable the execution on the leading edge, pass
1791
+ // `{leading: false}`. To disable execution on the trailing edge, ditto.
1792
+ // jscs:disable
1793
+ // jshint ignore:start
1794
+ , throttle: function(func, wait, options) {
1795
+ var that = this;
1796
+ var context, args, result;
1797
+ var timeout = null;
1798
+ var previous = 0;
1799
+ if (!options) options = {};
1800
+ var later = function() {
1801
+ previous = options.leading === false ? 0 : that.now();
1802
+ timeout = null;
1803
+ result = func.apply(context, args);
1804
+ if (!timeout) context = args = null;
1805
+ };
1806
+ return function() {
1807
+ var now = that.now();
1808
+ if (!previous && options.leading === false) previous = now;
1809
+ var remaining = wait - (now - previous);
1810
+ context = this;
1811
+ args = arguments;
1812
+ if (remaining <= 0 || remaining > wait) {
1813
+ clearTimeout(timeout);
1814
+ timeout = null;
1815
+ previous = now;
1816
+ result = func.apply(context, args);
1817
+ if (!timeout) context = args = null;
1818
+ } else if (!timeout && options.trailing !== false) {
1819
+ timeout = setTimeout(later, remaining);
1820
+ }
1821
+ return result;
1822
+ };
1823
+ }
1824
+ // jshint ignore:end
1825
+ // jscs:enable
1826
+
1827
+ /**
1828
+ * Create a requestAnimationFrame simulation
1829
+ *
1830
+ * @param {Number|String} refreshRate
1831
+ * @return {Function}
1832
+ */
1833
+ , createRequestAnimationFrame: function(refreshRate) {
1834
+ var timeout = null
1835
+
1836
+ // Convert refreshRate to timeout
1837
+ if (refreshRate !== 'auto' && refreshRate < 60 && refreshRate > 1) {
1838
+ timeout = Math.floor(1000 / refreshRate)
1839
+ }
1840
+
1841
+ if (timeout === null) {
1842
+ return window.requestAnimationFrame || requestTimeout(33)
1843
+ } else {
1844
+ return requestTimeout(timeout)
1845
+ }
1846
+ }
1847
+ }
1848
+
1849
+ /**
1850
+ * Create a callback that will execute after a given timeout
1851
+ *
1852
+ * @param {Function} timeout
1853
+ * @return {Function}
1854
+ */
1855
+ function requestTimeout(timeout) {
1856
+ return function(callback) {
1857
+ window.setTimeout(callback, timeout)
1858
+ }
1859
+ }
1860
+
1861
+ },{}]},{},[1]);